3. 目标检测基础:任务定义、IoU、NMS 与 Anchor 机制
各位同学,欢迎来到第三章。从这一章开始,我们正式进入目标检测的核心地带。很多初学者一上来就调 YOLO 的代码,跑通了就觉得自己会了。但遇到工业场景里的刁钻问题——比如小目标漏检、重复框太多、正负样本不平衡——马上就懵了。
为什么?因为底层的四个概念没吃透:任务定义、IoU、NMS 和 Anchor。今天我就把这四个东西掰开了讲,结合我踩过的坑,帮你把地基打牢。
3.1 目标检测任务定义
先问个问题:图像分类和目标检测,到底差在哪?
分类只回答「这是什么」,检测要回答「这是什么 + 在哪」。所以一个检测模型的输出,本质上是一组 边界框 + 类别标签 + 置信度 的集合。
每张图输出 N 个检测结果,每个结果包含:
[x_center, y_center, width, height, class_id, confidence]
在工业项目里,我习惯把检测任务拆成两个子问题:
- 定位——框得准不准(用 IoU 衡量)
- 分类——认得对不对(用置信度 + 类别概率衡量)
你想想看,如果框偏了但类别对了,产线可能误判;如果框准了但类别错了,那更麻烦。所以两个都得抓。
3.2 IoU(交并比)—— 框的「重合度」尺子
IoU 全称 Intersection over Union,说白了就是两个框的交集面积 ÷ 并集面积。值域 [0, 1],越大说明框得越准。
def compute_iou(box1, box2):
# box: [x1, y1, x2, y2]
x1 = max(box1[0], box2[0])
y1 = max(box1[1], box2[1])
x2 = min(box1[2], box2[2])
y2 = min(box1[3], box2[3])
inter = max(0, x2 - x1) * max(0, y2 - y1)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - inter
return inter / union if union > 0 else 0.0
这段代码我几乎每个项目都会用到。注意那个 max(0, ...),防止两个框不相交时出现负面积。
IoU 还有几个变种,我列个表给你参考:
| 指标 | 公式 | 特点 |
|---|---|---|
| IoU | 交集 / 并集 | 最常用,对尺度不敏感 |
| GIoU | IoU - (外接矩形差 / 外接矩形) | 解决 IoU=0 时梯度消失 |
| DIoU | IoU - (中心点距离² / 外接对角线²) | 加入中心点距离惩罚 |
| CIoU | DIoU + 长宽比惩罚 | YOLOv5/v8 默认损失 |
我曾经在一个 PCB 缺陷检测项目里,用普通 IoU 做 NMS,结果两个挨得很近的焊点总被合并成一个。换成 DIoU 后,召回率直接提了 5 个点。所以别小看这个指标,选对了事半功倍。
3.3 NMS(非极大值抑制)—— 去重利器
模型预测时,同一个目标周围会冒出几十个框。NMS 的作用就是:只留下最好的那个。
算法流程其实很简单:
- 把所有框按置信度从高到低排序
- 选置信度最高的框,保留它
- 跟剩下的框算 IoU,如果 IoU > 阈值(比如 0.5),就干掉
- 重复 2-3,直到没框了
def nms(boxes, scores, iou_threshold=0.5):
indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
keep = []
while indices:
i = indices[0]
keep.append(i)
rest = indices[1:]
# 计算当前框与其余框的 IoU
ious = [compute_iou(boxes[i], boxes[j]) for j in rest]
indices = [rest[j] for j in range(len(rest)) if ious[j] <= iou_threshold]
return keep
NMS 也有改进版:Soft-NMS。它不直接删框,而是按 IoU 大小降低置信度。我试过几次,效果有提升但速度慢了些。工业部署时,如果算力有限,还是用标准 NMS 加调参更实在。
3.4 Anchor 机制 —— 检测的「先验框」
Anchor 是什么?说白了就是预设好的一组框的尺寸和比例。模型不是直接预测框的坐标,而是预测「相对于 Anchor 的偏移量」。
为什么要这么干?因为直接回归坐标太难了。给模型一个先验知识——「目标大概这么宽、这么高」——训练会快很多。
预测值 = Anchor 基准值 + 偏移量
模型只需要学「微调」,不需要从零学「定位」。
YOLOv5 的 Anchor 是怎么来的?用 K-Means 对训练集的标注框做聚类。我一般设 9 个 Anchor,分 3 个尺度:
| 特征图尺度 | 感受野 | Anchor 尺寸(举例) | 适合目标 |
|---|---|---|---|
| 大 (80×80) | 小 | (10,13), (16,30), (33,23) | 小目标 |
| 中 (40×40) | 中 | (30,61), (62,45), (59,119) | 中等目标 |
| 小 (20×20) | 大 | (116,90), (156,198), (373,326) | 大目标 |
注意,这些尺寸是相对于输入图像(比如 640×640)的像素值。你换一个输入尺寸,Anchor 要重新算。
kmeans_anchors.py,看看默认 Anchor 跟实际标注框的匹配度。如果平均 IoU 低于 0.6,说明 Anchor 不合适,必须重新聚类。
Anchor 的匹配策略也很关键。训练时,每个标注框会跟所有 Anchor 算 IoU,选 IoU 最大的那个作为正样本。如果最大 IoU 都小于阈值(比如 0.3),就当成背景。这个阈值设高了,正样本太少;设低了,噪声太多。我一般设在 0.3~0.4 之间。
3.5 知识结构总览
下面这张图把本章四个核心概念串起来了,你可以对照着复习:
这四个概念环环相扣:任务定义告诉你「做什么」,IoU 告诉你「做多好」,NMS 帮你「去冗余」,Anchor 帮你「定起点」。后面讲 YOLO 网络结构时,你会发现每个模块都在跟这四个东西打交道。
· 目标检测 = 定位 + 分类
· IoU 是框重合度的核心度量,有多个改进版本
· NMS 用于去除重复检测框,阈值选择看场景
· Anchor 是预设先验框,需要根据数据集聚类调整