1. NMS基础回顾:什么是NMS,为什么需要NMS,NMS在YOLO中的位置

做目标检测的朋友,对NMS肯定不陌生。但说实话,我见过不少工程师调了几年YOLO,对NMS的理解还停留在「就是去掉重复框」这个层面。

这其实挺危险的。NMS选不好,你的mAP可能直接掉3-5个点。今天咱们就把这个基础打扎实。

1.1 什么是NMS?

NMS,全称Non-Maximum Suppression,非极大值抑制。名字听着绕口,本质很简单:从一堆重叠的检测框中,挑出最好的那个,把其他的干掉。

你想想看,YOLO在推理时,同一个物体上会生成好多预测框。有的框得准,有的偏一点,有的完全不对。但模型不知道哪个最好,它把所有框都输出来了。

NMS就是那个「裁判」——它根据置信度排序,保留最高分的框,然后干掉和它重叠度太高的其他框。

核心思想: 局部最大保留,冗余抑制。

1.2 为什么需要NMS?

这个问题我问过不少新人。有人回答「因为模型会输出很多框」,这只说对了一半。

真正的原因是:目标检测是一个「定位+分类」的联合任务,天然存在多解性。

具体来说,有三大原因:

  1. 滑动窗口的遗产:YOLO虽然不用滑动窗口,但它的网格+Anchor机制,本质上还是在图像上密集采样。一个物体落在哪个网格、哪个Anchor上,模型自己也不确定,所以它会在多个位置都输出框。
  2. 训练时的鼓励:训练时,我们通常会给多个预测框分配正样本(比如YOLOv5的匹配策略)。模型被鼓励去「多猜」,推理时自然就多输出。
  3. 后处理必须做:没有NMS,一张图可能输出几千个框,99%都是冗余的。下游任务根本没法用。

我记得有一次做工业质检项目,没调NMS参数,直接用了默认值。结果一个零件上出了20多个框,PLC那边直接报警了。嗯,从那以后我再也不敢轻视NMS。

1.3 NMS在YOLO中的位置

很多同学以为NMS是模型的一部分,其实不是。它是后处理环节,在模型推理之后、输出最终结果之前。

YOLO的完整推理流程是这样的:

步骤 阶段 说明
1 前向推理 图像经过Backbone+Neck+Head,输出原始预测张量
2 解码 将张量解码为边界框坐标(x,y,w,h)和置信度、类别概率
3 置信度过滤 去掉置信度低于阈值(如0.25)的框,减少NMS输入量
4 NMS 对每个类别分别执行非极大值抑制,去除冗余框
5 输出 得到最终检测结果,每张图通常只剩几十个框

看到没?NMS是第4步,紧跟在置信度过滤之后。它处理的是已经「初筛」过的候选框,而不是原始输出。

我个人习惯把NMS放在GPU上做(比如Torchvision提供的batched_nms),这样能省掉CPU和GPU之间的数据传输。不过这个后面再细聊。

1.4 NMS的核心流程

标准NMS的算法其实很简单,我直接贴伪代码:

输入:所有检测框 B = {b1, b2, ..., bn},每个框有坐标和置信度
输出:保留的检测框集合 D

1. 按置信度从高到低排序 B
2. 初始化 D = []
3. while B 不为空:
   a. 取出最高置信度框 m,加入 D
   b. 从 B 中移除 m
   c. 计算 m 与 B 中每个框的 IoU
   d. 移除 IoU > 阈值(如0.5)的所有框
4. 返回 D

这里有个关键点:NMS是逐类别执行的。也就是说,人、车、狗各自做一遍NMS,互不干扰。为什么?因为不同类别的物体可以重叠(比如人骑自行车),不能互相抑制。

小技巧: 实际工程中,我会把不同类别的框分开,然后并行执行NMS。如果用Python写循环,一张图几百个框可能就要几十毫秒。用向量化操作,可以压到1毫秒以内。

1.5 NMS的数学基础:IoU

NMS的核心判断依据是IoU(Intersection over Union),也就是交并比。

公式很简单:

IoU = (A ∩ B) / (A ∪ B)

两个框的交集面积除以并集面积。值越大,说明重叠越严重。

我见过有人把IoU和GloU、DloU搞混。这里说清楚:NMS用的是最原始的IoU,不是变体。变体用在损失函数里,不用于后处理。

指标 公式 用途
IoU 交集/并集 NMS后处理、mAP评估
GloU IoU - (差集/最小外接矩形) 回归损失函数
DloU IoU - (中心点距离/对角线距离)² 回归损失函数
CloU DloU + 长宽比惩罚 回归损失函数(YOLOv5/v8默认)

你看,NMS只用最朴素的IoU。为什么?因为后处理追求的是速度和稳定性,不需要那么复杂的几何计算。

1.6 NMS的直观理解:一张SVG流程图

下面这张图展示了NMS的完整处理流程,从原始检测框到最终输出:

NMS 处理流程 原始检测框 置信度过滤 按置信度排序 取出最高分框 m 加入保留集合 D 计算 m 与其余框的 IoU 移除 IoU > 阈值的框 循环直到 B 为空 最终检测结果 D 关键参数 • IoU阈值:0.5~0.7 • 置信度阈值:0.25~0.5 • 最大检测数:100~300

这张图把NMS的循环逻辑画清楚了。注意那个回环箭头——NMS本质上是一个迭代过程,每次只保留一个框,然后干掉和它重叠的,直到没有框剩下。

1.7 NMS的局限性

NMS不是完美的。我踩过不少坑,这里列几个常见的:

  • 密集场景漏检:两个物体挨得很近,IoU超过阈值,NMS会干掉其中一个。这在人群、货架场景下特别明显。
  • 阈值敏感:IoU阈值设0.5,漏检多;设0.7,误检多。没有万能值,得根据场景调。
  • 置信度偏差:NMS完全依赖置信度排序。如果模型对某个物体的置信度偏低,即使框得很准,也可能被高置信度的差框抑制掉。

避坑指南: 我曾经在一个交通监控项目里,用默认的0.5 IoU阈值,结果两辆并排的车总是只检测出一辆。后来把阈值降到0.3,问题解决了,但误检又多了。最后用了Soft-NMS才平衡过来。这个后面章节会详细讲。

1.8 本章小结

NMS是YOLO检测流程中不可或缺的一环。它不复杂,但影响巨大。理解它的原理、位置和局限性,是后续调优的基础。

记住三个要点:

  • NMS是后处理,不是模型的一部分
  • 它逐类别执行,核心是IoU判断
  • 阈值选择直接影响检测效果,没有银弹