第一次接触YOLO时,我也被Anchors这个概念困扰了很久。明明网络可以直接预测边界框,为什么还要多此一举引入Anchors?后来在真实项目中踩过几次坑才明白,这就像教小朋友画画——如果直接让他在空白纸上画一只猫,结果可能惨不忍睹;但如果先给他几个猫的简笔画轮廓作为参考,他就能画出更准确的形状。
Anchors本质上就是这样的参考模板。在COCO数据集上,典型的9个Anchors可能是这样的宽高组合:
python复制anchors = [
[27, 183], # 瘦高型(如站立的人)
[87, 31], # 扁平型(如汽车)
[51, 62], # 接近正方形(如动物)
... # 其他6种比例
]
这些数值不是随机设定的,而是通过k-means聚类算法分析训练集中所有标注框的宽高分布得到的。比如在行人检测场景中,由于人体通常呈垂直长方形,对应的Anchors就会有一个明显瘦高的比例。我在处理工业零件检测时,就发现需要根据螺母、垫片等特定形状调整Anchors比例。
原始图片经过YOLO主干网络后,会生成三个不同尺度的特征图(如20x20、40x40、80x80)。这里有个关键但容易被忽视的细节:Anchors的坐标单位需要从"像素空间"转换到"特征图空间"。
假设原图尺寸是640x640,在80x80的特征图上:
用代码表示这个转换过程:
python复制def anchor_to_feat(anchors, stride):
return anchors / stride # stride=原图尺寸/特征图尺寸
我曾遇到一个典型错误:直接将原图Anchors与特征图预测结果做比较,导致IOU计算完全错误。正确的做法是:
不是所有Anchors都参与每个物体的检测,YOLO采用"最佳匹配"原则:
这里有个实用技巧:通过可视化匹配结果可以快速发现Anchors设计是否合理。我在某次实验中发现,90%的汽车检测都集中在两个特定比例的Anchors上,说明其他Anchors基本是冗余的。
匹配过程的代码实现:
python复制def match_anchors(gt_boxes, anchors):
ious = compute_iou(gt_boxes, anchors)
matched = []
for i in range(len(gt_boxes)):
top3_idx = np.argsort(ious[i])[-3:]
matched.append(top3_idx)
return matched
Anchors的最终使命是通过4个关键偏移量变身预测框:
具体公式为:
code复制pred_x = (sigmoid(tx) + grid_x) * stride
pred_y = (sigmoid(ty) + grid_y) * stride
pred_w = anchor_w * exp(tw)
pred_h = anchor_h * exp(th)
这个设计有几个精妙之处:
在调试模型时,我习惯用这个可视化代码检查回归效果:
python复制def visualize_regression(anchor, pred):
plt.figure(figsize=(10,5))
plt.subplot(121)
draw_box(anchor, color='r')
plt.title("Original Anchor")
plt.subplot(122)
draw_box(pred, color='g')
plt.title("After Regression")
YOLO的三个特征层分工明确:
实际操作中需要特别注意:
一个常见的错误是混淆Anchors分配,比如把本应分配给20x20特征层的大Anchors错误用于80x80层。这会导致模型对小物体的检测性能急剧下降。
经过多个项目的实践,我总结出Anchors调优的步骤:
Python实现k-means聚类的代码片段:
python复制from sklearn.cluster import KMeans
def generate_anchors(boxes, k=9):
wh = np.vstack([boxes[:,2:4]-boxes[:,:2] for boxes in all_boxes])
kmeans = KMeans(n_clusters=k)
kmeans.fit(wh)
return kmeans.cluster_centers_
在工业缺陷检测项目中,通过自定义Anchors使mAP提升了7%,关键是把主要缺陷的典型尺寸(如裂纹的长宽比)单独设为一类Anchors。
问题1:模型预测框全部偏向某种固定比例
问题2:小物体检测效果差
问题3:预测框中心点总是偏离物体
记得有次调试时发现所有预测框都集中在网格中心,最后发现是忘记对tx/ty应用sigmoid激活,导致偏移量失去约束。这种细节问题往往需要逐行检查计算流程。