Darknetフレームワークは、YOLOv4やScaled-YOLOv4などの高性能検出アーキテクチャを実装するための軽量かつ柔軟な基盤です。しかし、その検出性能は、単にネットワーク構造や学習率に依存するものではなく、Anchor Boxの設計品質に強く影響されます。本稿では、データセット固有のAnchorを科学的に導出するためのK-meansベースのクラスタリング手法を、実装中心で解説します。
Anchor Boxが検出精度に与える影響
Anchor Boxは、事前に定義された高さ・幅を持つ矩形テンプレートであり、モデルが各特徴マップのグリッドセルに対して予測する「候補ボックス」の基準となります。デフォルトのAnchor(例:YOLOv4公式設定)はCOCOデータセット向けに最適化されており、医療画像やドローン映像など、スケールやアスペクト比が異なる独自データセットでは、適合性が著しく低下します。このミスマッチは、IOUの低下、誤検出の増加、特に小物体検出の失敗を招きます。
クラスタリングツールの選択肢
Darknetは、Anchor生成を支援する2つの標準手段を提供します:
scripts/kmeans_anchors.py— Python実装(scikit-learn依存)darknet detector calc_anchors— C言語で記述されたネイティブコマンド
両者ともユークリッド距離ではなく、IoU距離(1 − IoU)をクラスタリングの類似度指標として採用しており、検出タスクに特化した最適化が可能です。
PythonスクリプトによるカスタムAnchor生成手順
以下は、kmeans_anchors.pyを用いた実践的ワークフローです(ファイル名・パスは環境に応じて調整):
import numpy as np
from sklearn.cluster import KMeans
from pathlib import Path
def load_bboxes_from_txt(txt_path: str) -> np.ndarray:
"""訓練リストから各アノテーションの正規化済みbbox寸法を抽出"""
bboxes = []
with open(txt_path, 'r') as f:
for line in f:
img_path = line.strip()
label_path = Path(img_path).with_suffix('.txt')
if not label_path.exists():
continue
with open(label_path, 'r') as lf:
for ann_line in lf:
parts = ann_line.strip().split()
if len(parts) < 5:
continue
_, cx_norm, cy_norm, w_norm, h_norm = map(float, parts)
# 入力解像度416×416を仮定 → 絶対ピクセル値へ変換
w_px, h_px = w_norm * 416, h_norm * 416
if w_px > 0 and h_px > 0:
bboxes.append([w_px, h_px])
return np.array(bboxes)
def iou_distance(box_a, box_b):
"""IoU距離:1 - IoU(box_a, box_b)"""
w1, h1 = box_a
w2, h2 = box_b
intersection = min(w1, w2) * min(h1, h2)
union = w1 * h1 + w2 * h2 - intersection
return 1.0 - (intersection / union) if union > 0 else 1.0
# クラスタリング実行
dataset_list = "data/train_list.txt"
raw_boxes = load_bboxes_from_txt(dataset_list)
if len(raw_boxes) == 0:
raise ValueError("アノテーションが見つかりません")
# K-means初期化:k-means++ + IoU距離に基づくカスタムメトリクス(簡易近似)
kmeans = KMeans(n_clusters=9, init='k-means++', n_init=20, random_state=42)
labels = kmeans.fit_predict(raw_boxes)
# 各クラスタの代表Anchor(クラスタ重心)を算出し、整数化
centroids = kmeans.cluster_centers_
anchors = np.round(centroids).astype(int)
anchors = anchors[anchors[:, 0].argsort()] # 幅でソート
print("生成されたAnchor(width, height):")
for i, (w, h) in enumerate(anchors):
print(f"{i+1}. {w}×{h}")
# 平均IoU推定(簡易評価)
avg_iou = np.mean([
max([1 - iou_distance(box, anchor) for anchor in anchors])
for box in raw_boxes
])
print(f"\n推定平均IoU: {avg_iou:.4f}")
ネイティブコマンドによる高速生成
コンパイル済みDarknetバイナリがある場合、以下の1行コマンドで即時生成可能です:
./darknet detector calc_anchors data/custom.data -num_of_clusters 9 -width 608 -height 608 -show
オプション解説:
-num_of_clusters: Anchor数(YOLOv4.cfgでは通常9)-width/-height: 推論時の入力サイズと一致させる-show: 各クラスタの中心座標を可視化(オプション)
Anchor適用時の重要な注意点
- 入力サイズとの整合性:Anchor値は絶対ピクセル単位であるため、
yolov4-custom.cfg内のanchors =行には、608×608で生成したAnchorをそのまま記述。リサイズ不要。 - クラスタ数の実験:5〜12の範囲で複数回実行し、
avg_iouが最大となるnを選択。過剰なクラスタ数はmAP向上を妨げ、推論遅延を引き起こす。 - アノテーション品質の確認:不正確なbbox(例:ゼロ面積、逆方向、外れ値)はクラスタリングを歪める。前処理段階で
labelImgやCVATで検証することを推奨。
効果検証のための再学習
生成したAnchorを.cfgに反映後、必ず以下を実行してください:
- 学習済み重みの読み込み(例:
yolov4.conv.137) - 最低でも30エポックのファインチューニング
- 検証セットでのmAP@0.5およびmAP@0.5:0.95の計測
実際の改善幅はデータセットにより異なりますが、小物体が多いシーンではmAP向上が5〜12%観測されるケースが多く、特にRecallの向上が顕著です。