news 2026/10/5 4:30:53

NMS非极大值抑制:从标准算法到softNMS/IoU-Net的演进解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NMS非极大值抑制:从标准算法到softNMS/IoU-Net的演进解析

做物体检测的人,大概率都有过这种经历:模型结构改了一版又一版,训练技巧拉到满,可mAP就是差那么零点几个点,最后排查来排查去,问题居然出在后处理——NMS阈值调得太狠,把本该留下的框连坐删掉了;或者阈值太松,重复框比检测结果还密。说实话,NMS这玩意儿表面上就是几十行代码的“暴力去重”,但它确实是检测器性能的一块隐形天花板。同样一套检测模型,后处理写得糙和写得精细,最终指标能差出1到2个点,这在榜单上往往就是两三个名次的差距。

这篇文章我就把NMS这条线完整捋一遍:标准NMS、softNMS、softerNMS、IoU-Net,这四代方案分别解决了什么问题、原理是什么、代码怎么改、实际落地效果如何。无论你是刚入坑的初学者,还是已经在调检测模型的老手,都应该能从里面找到点有用的东西。

1. 先搞清楚一件事:NMS到底在解决什么问题

1.1 一个典型检测头的输出到底长什么样

在讨论NMS之前,得先回到检测器的输出端。无论是两阶段检测器(Faster R-CNN、Cascade R-CNN)还是单阶段检测器(YOLO、SSD、FCOS),推理时的原始输出绝对不是一张干净的“框好类别的图”,而是一大堆候选框的集合。

抛开不同检测器的细节差异,最终输出通常包含四类信息:

  • 目标的类别概率(或者分类 logits,用于判断这个框属于哪个类)
  • 目标的边界框坐标(通常表示为 x1, y1, x2, y2,或者中心点加宽高)
  • 目标置信度分数(分类分数或者 objectness 分数,用于衡量这个框有多可信)
  • 一些辅助信息(比如 Mask 分支的 mask,或者姿态估计里的 keypoints)

关键在于,一个目标物体在图像上会被多个锚点或多个滑动位置同时“看见”,于是检测头会输出大量重叠的候选框。以 Faster R-CNN 为例,RPN 在一张图上可能生成几千个 proposal,经过分类和回归后仍然会存在大量针对同一目标的“重复框”。这些框彼此之间的 IoU 可能从 0.3 到 0.9 不等,如果不做任何后处理,画出来的图就是一团糊,同一个行人被十几二十个框套着。

1.2 没有NMS会怎样:重复框的灾难现场

可以做一个简单的思想实验:假设一张图片里有一个行人,检测器输出了 10 个框,这 10 个框都大致落在行人身上,IoU 两两之间都超过 0.5,每个框的分类分数分别是 0.95, 0.93, 0.90, 0.88……一直到 0.76。

如果没有 NMS,这张图的最终输出就是这 10 个框全部报出来。评估时,检测器会说“这里有 10 个目标”,但实际上只有一个行人。mAP 的召回率没有提升,但精确率被严重拉低。在真实的业务场景里更灾难,比如工业质检,同一个缺陷零件被重复框选,后台统计就会误判成多倍数量的缺陷。

NMS 的核心目标非常朴素:在一堆重叠的候选框中,只保留最有代表性的那一个,把其他“重复表达同一个目标”的框全部干掉。这里的“最有代表性”,最直观的定义就是分类分数最高的那个。

所以,NMS 的本质是一个去重算法,它的输入是检测器的原始输出,输出是一个精简后的检测结果集合。正是因为它处在整个检测流程的最后一环,所以它对最终效果的影响极其直接粗暴——前面的卷积、特征金字塔、注意力机制再怎么卷,最后这一刀要是切错了,前面的努力全部白费。

2. 标准NMS:每一代检测器都绕不开的“暴力去重”

2.1 NMS的核心流程与理解方式

标准NMS算法其实几句话就能说清楚,用“锦标赛淘汰制”来理解最直观:

第一步:所有候选框按照分数从高到低排序,最高分的框直接晋级,因为它最可能是一个真实目标。

第二步:把晋级框和剩下的框逐一计算 IoU。如果一个剩余框和晋级框的 IoU 超过设定的阈值,说明它大概率是在同一个目标上产生的冗余框,直接淘汰。

第三步:重复这个过程,每次都从剩余框里选分数最高的框晋级,直到所有框都被处理完。

用伪码表示就是这样:

输入:候选框集合 B,对应分数集合 S,阈值 Nt 输出:保留的框集合 D 初始化 D 为空 循环直到 B 为空: 从 S 中取出最高分对应的框 M 从 B 中移除 M,加入 D 对 B 中每一个框 b: 计算 b 与 M 的 IoU 如果 IoU > Nt: 从 B 中移除 b,从 S 中移除对应分数 返回 D

这个算法的核心参数只有一个:IoU 阈值 Nt。它表达了一个假设:两个框的 IoU 超过阈值,就认为它们“指代的是同一个目标”。这个假设看起来没问题,但实际应用中坑非常多,这部分到第三章详细说。

2.2 一段可以直接抄的标准NMS实现

用 Python 实现一个标准 NMS 非常直白。下面这段是经典写法,网上流传的大部分版本都是它的变体:

import numpy as np def nms(dets, thresh): """Pure Python NMS baseline.""" x1 = dets[:, 0] y1 = dets[:, 1] x2 = dets[:, 2] y2 = dets[:, 3] scores = dets[:, 4] areas = (x2 - x1 + 1) * (y2 - y1 + 1) order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) # 计算当前最高分框与其余所有框的IoU xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h ovr = inter / (areas[i] + areas[order[1:]] - inter) # 保留IoU小于阈值的框 inds = np.where(ovr <= thresh)[0] order = order[inds + 1] return keep

有两点需要注意:

第一,计算面积时为什么要加1?如果是像素坐标,一个框从坐标 (x1, y1) 到 (x2, y2),它实际占据的像素宽度是 x2 - x1 + 1,而不是 x2 - x1。加 1 是为了把离散坐标映射回连续的面积。虽然对于最终排序来说,少加一个 1 影响不大,但如果做严格的指标复现,这个细节可能造成千分之几的差异。

第二,order[inds + 1]这个下标的偏移。因为ovr是当前框和order[1:]之间的 IoU,所以inds对应的索引序号是基于order[1:]的。需要加 1 才能映射回order的原始位置。这是新手最容易抄错的地方,一旦写错,结果会完全乱掉。

2.3 阈值到底怎么调:从0.3到0.7之间的权衡

NMS 的 IoU 阈值是一个典型的天平参数。阈值设得低(比如 0.3),条件更严格,两个框只要交叠稍微多一点就被干掉一个。这样输出的框数量少、重叠少,但如果两个真实目标恰好挨得很近,一个框就会被误删,导致召回率下降。

阈值设得高(比如 0.7),条件宽松,只有高度重叠的框才会被抑制,召回率有保障,但输出框的重叠度也高,可能会把同一个目标的多个框都报出来,拉低精确率。

在 COCO 数据集上,标准的评价协议默认使用 0.5 到 0.95 的多个 IoU 阈值来算 mAP,但后处理阶段的 NMS 阈值一般取 0.5 左右。实际业务里怎么调,一定要结合场景:

  • 行人检测、密集人群:建议 0.4 到 0.5,因为行人间相互遮挡严重,阈值太高会导致大量重复框
  • 遥感图像中的密集小目标:建议 0.3 到 0.5,小目标之间的相对距离更近,IoU 很容易超阈值
  • 通用目标检测:0.5 是一个比较稳妥的起点,COCO 上大多数检测器也是用 0.5

需要特别注意的是,NMS 阈值和数据集评估时的 IoU 阈值完全是两个概念。NMS 阈值是后处理去重时的判定标准,而评估时的 IoU 阈值是判断“预测框是否和 GT 匹配”的标准。很多人一开始会把这两个搞混,调试时一头雾水。

3. 标准NMS的三个致命痛点:阈值焦虑、遮挡失效、分数错位

3.1 IoU阈值是“一锤子买卖”,不同场景最优值完全不同

标准 NMS 最大的问题,在于那个阈值Nt是一个全局固定值。不管图像里是大目标还是小目标、密集场景还是稀疏场景、目标之间是相互独立还是高度重叠,用的都是同一个阈值。

这就导致了一个很尴尬的局面:阈值调优变成了一场“拆东墙补西墙”的游戏。把阈值调低一点,稀疏场景效果变好了,但密集场景的召回率立刻崩;把阈值调高一点,密集场景保住了,但输出里到处都是重复框,精确率往下掉。

我在实际项目里曾经调过一批安全帽检测的数据,场景非常单一:工地上的人基本都在画面里,距离摄像头远近不同,密集程度差异很大。远距离时目标小,彼此不重叠,NMS 阈值调到 0.3 就够用;但到了进出闸机这种场景,好几个工人挤在一起,框与框的 IoU 经常到 0.6 以上,阈值 0.3 会直接把旁边工人的框删掉。最后我只能折中取 0.45,两边都牺牲一点,但这个问题始终没有根治。

3.2 密集遮挡场景:抑制过头把该留的框也删了

标准 NMS 的抑制逻辑是硬抑制:一旦评判两个框是“同一个目标”,另一个框的分数直接置 0,完全从候选集合里消失。

这种“零容忍”策略在密集遮挡场景下非常致命。举一个非常典型的例子:三个人并排站着,检测器在每个人身上都产生了多个候选框,其中一个人身上的次级候选框(分数 0.8)和旁边一个人的主要候选框(分数 0.85)之间有 0.55 的 IoU。如果 NMS 阈值是 0.5,那么旁边那个人的主要候选框就会被“误杀”,因为它的分数比前一个人身上的次级框低一点。

这种错误在标准 NMS 里是不可逆的——被置 0 的框直接出局,没有任何“申诉”机制。

在 COCO 的 mAP 评估中,这种误杀会导致两个后果同时出现:一个是本来正确的检测结果不见了(召回率下降),另一个是这个误删的框不可能再被匹配到 GT 上(精确率也受影响)。密集场景下,NMS 引入的损失甚至可能超过网络本身引入的损失。

3.3 分类分数≠定位质量,NMS排序依据本身就是错的

标准 NMS 用分类分数作为“框好坏”的衡量标准,但这隐含了一个非常强的假设:分数越高的框,定位越准。

但在实际训练中,分类分支和回归分支是相对独立的,它们的输出没有天然的强相关性。一个分类分数 0.95 的框,其定位可能与 GT 差了一大截;而旁边一个分类分数 0.88 的框,却可能正好精准框住了目标。

这就是所谓的 classification score 和 localization quality 之间的**错位(misalignment)**问题。这个错位对 NMS 的影响是致命的,因为 NMS 每一步都从当前候选集合里挑“最高分”的框作为基准,去抑制其他框。如果这个“最高分”框本身定位就很差,那么它非但不能代表真实目标,反而会把真正定位精准的框全部抑制掉。

打个比方:一个队长自己站错了位置,还要求所有跟着他的人也站到错误的位置上,队友但凡站得稍微准一点就被他“淘汰”了。最后整个队伍覆灭,而真正站在正确位置上的队员一个都没留下来。

这个痛点,正是后面 softerNMS 和 IoU-Net 想要解决的核心问题。

4. softNMS:与其物理删除,不如让分数“软着陆”

4.1 核心思想:IoU越大,分数衰减越狠

softNMS 发表于 ICCV 2017,作者是 Navaneeth Bodla 等人。它的核心改进思路非常简洁:标准 NMS 发现某个框 A 和当前最高分框 M 的 IoU 超过阈值时,直接删掉 A;softNMS 则是不删框,只降分。

根据 IoU 的大小,衰减的程度不同。两个框重叠得越厉害,说明它们是同一个目标的可能性越大,分数就衰减得越狠;重叠得没那么厉害,分数衰减得就少一些。这样,在密集场景中,那些和最高分框有一定重叠、但可能属于另一个目标的框,虽然分数下降了,但没有被直接“杀死”,只要最后分数仍高于输出阈值,它就还有机会被保留下来。

4.2 线性加权与高斯加权的实现细节

softNMS 论文里给出了两种衰减函数:

线性加权:

score = score * (1 - iou) 当 iou >= Nt 时 score = score 当 iou < Nt 时

高斯加权:

score = score * exp(-iou^2 / sigma)

高斯版本对所有的重叠框都做衰减,不需要设置阈值Nt,只需要调一个超参数sigma。论文里的实验建议sigma取 0.5,实测下来高斯版本通常比线性版本稳定一点,但注意,线性版本在密集场景中的可解释性更好,因为它的衰减范围和标准 NMS 直接对应。

代码上,softNMS 对比标准 NMS 的改动非常小:

def soft_nms(dets, sigma=0.5, Nt=0.3, threshold=0.001): x1 = dets[:, 0]; y1 = dets[:, 1] x2 = dets[:, 2]; y2 = dets[:, 3] scores = dets[:, 4] areas = (x2 - x1 + 1) * (y2 - y1 + 1) order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h ovr = inter / (areas[i] + areas[order[1:]] - inter) # 高斯衰减 scores[order[1:]] *= np.exp(-ovr * ovr / sigma) # 重新排序 idx = np.where(scores[order[1:]] > threshold)[0] order = order[1:][idx] order = order[np.argsort(scores[order])[::-1]] return keep, scores

注意到这里有一个操作变化:在 softNMS 里,每处理完当前最高分框后,剩余框的分数会被更新,所以需要重新按更新后的分数排序,这比标准 NMS 多了不少排序开销。论文里提到,如果想提速,可以在原始分数排序的基础上做近似实现,实际效果几乎不损失。

4.3 实验效果与适用场景:密集行人检测的救星

softNMS 最典型的应用价值体现在密集行人检测这类场景。常规的行人检测数据集中,一个人被另一个人大面积遮挡的情况非常常见。标准 NMS 在这种情况下很容易把被遮挡者的候选框直接删掉,softNMS 通过降分而不是删框,给了被遮挡目标一个“复活”的机会。

在 COCO 数据集上做基准测试,softNMS 在不改变任何网络结构的情况下,单模型大概能带来 0.5 到 1.1 个点的 mAP 提升,而且这个提升在“遮挡程度高”的子集上更明显。关键是它的代价几乎为零——不需要额外训练,不需要修改网络结构,推理多花的只是排序时间。

我在实际项目里观察到一个现象:很多模型在训练时已经做了数据增强(比如随机裁剪、马赛克),对遮挡有一定鲁棒性,但是推理时的 NMS 仍然是标准版。这种情况下换上 softNMS,提升可能不如论文里那么大,但基本是正向的。不过要留意,softNMS 会让输出框的置信度和标准 NMS 分布不一样,如果你后续有基于置信度阈值的过滤逻辑,可能要从头重新标定一遍阈值。

5. softerNMS:定位质量感知,让“方差越小越可信”

5.1 出发点:定位精度高的框在NMS中更容易被保留

softNMS 解决了“框被误删”的问题,但没有解决 NMS 排序依据的问题——它仍然用分类分数来排序,仍然会让那些“分数高但定位差”的框当老大,去压制“分数低但定位准”的框。

softerNMS 这篇工作(CVPR 2019,论文全称《Bounding Box Regression with Uncertainty for Accurate Object Detection》)打的就是这个靶子。它的核心观点是:我们应该在 NMS 流程中引入定位质量的信息,让那些定位更准确的框在竞争中占优势。

但这里有一个先有鸡还是先有蛋的问题:在后处理阶段,我们怎么知道哪个框定位更准?常规的检测器只会输出四边形的坐标,不会输出定位置信度。softerNMS 的做法是,从检测器训练阶段就开始下手,让检测头额外预测一个定位不确定性(方差)。

5.2 如何让检测头输出定位不确定性(方差)

在标准检测器里,边界框回归分支输出的是四个值,一般解释为目标框相对 anchor 或 proposal 的偏移量(dx, dy, dw, dh),训练时用 Smooth L1 损失约束。

softerNMS 的思路是:不直接回归四个偏移量,而是假设每个偏移量服从一个高斯分布,网络回归这个分布的均值和方差。均值就是原来的偏移量预测,方差就是定位不确定性。方差越大,说明网络对这个框的位置越“没底”。

数学上,训练时的损失函数用的是高斯分布的负对数似然(NLL)。对于一个回归目标 x_g(GT偏移量),如果网络预测的分布是 N(x_e, σ²),则损失为:

L = log(σ²) / 2 + (x_g - x_e)² / (2 * σ²)

注意这里有个关键的细节:分母中的 σ² 使得网络在梯度回传时会自动调节方差的权重。当预测的均值偏离 GT 很远时,网络会倾向于增大方差来降低这个样本的损失权重;当预测很准的时候,方差会被压小,这个样本对训练的贡献更大。这种机制天然地在训练中实现了“难例降权,准例升权”。

另外,因为 σ 作为分母有可能导致训练不稳定,论文里实际实现通常让网络预测log(σ²)而不是直接预测σ²,这样能确保方差始终为正,且数值更稳定。pytorch 里加一个回归头非常方便,比如在原有的 bbox 回归分支旁边并一个全连接层,输出 4 维的方差 log 值即可。

5.3 基于方差的框合并策略(vote机制)实现

训练完成后,网络不仅输出每个框的坐标,还输出每个框的定位方差。在 NMS 后处理阶段,softerNMS 使用了一种**方差投票(Variance Voting)**的机制来替代“直接选出最高分框”的策略。

流程如下:

第一步:用标准 NMS(或 softNMS)选出一组候选框。注意,这里的 NMS 选出的“最高分框”只是一个初始基准,不再直接作为最终输出。

第二步:对于每个被保留下来的框 M,寻找所有和它 IoU 大于某个阈值(论文里通常用 0.5)的邻域框。

第三步:对邻域框集合中所有框的坐标做加权平均,权重与方差的倒数成正比:

x_final = Σ(x_i / σ_xi²) / Σ(1 / σ_xi²) y_final = Σ(y_i / σ_yi²) / Σ(1 / σ_yi²) w_final = Σ(w_i / σ_wi²) / Σ(1 / σ_wi²) h_final = Σ(h_i / σ_hi²) / Σ(1 / σ_hi²)

这个公式的意义很直观:方差小(定位可信度高)的框贡献大,方差大(定位不可信)的框贡献小。相当于在多个“专家”的意见中,根据每个专家的可靠程度做加权投票,而不是让分数最高的“专家”一个人说了算。

实现时,上述加权平均是对 x、y、w、h 分别独立计算的,而且注意 w 和 h 的方向。实际代码里,因为网络回归的是偏移量而不是最终坐标,投票时可以用最终坐标,也可以用偏移量再解码,两者在数学上等价,但用最终坐标更方便调试。

我在复现 softerNMS 时踩过一个坑:方差投票只在一个簇内部进行,不要跨簇投票。最开始实现的时候,我图省事,对所有 IoU 大于阈值的框做了全局投票,结果把簇边的框坐标也拉歪了,mAP 反而比标准 NMS 掉了 0.3。后来检查才发现,聚类那一步没做干净。

另外有一点要提醒:softerNMS 论文的作者在实验部分也指出,收益最大的是 KL Loss 训练本身——它让检测头的回归更准了;方差投票对最终结果的提升相对有限。但对我们做应用的来说,两者结合仍然是一个合理的选择。

6. IoU-Net:直接把“框的质量”当成一个回归目标来学

6.1 核心洞察:分类置信度和IoU之间的错位有多大

前面提到,标准 NMS 用分类分数排序的最大隐患,是分类分数无法反映定位质量。IoU-Net(ECCV 2018,论文全称《Acquisition of Localization Confidence for Accurate Object Detection》)用一组数据把这个隐患量化了:他们统计了 Faster R-CNN 在 COCO 验证集上的分类分数和最终框的 IoU 之间的关系,发现在分类分数最高的那些检测框里,有相当一部分的 IoU 只有 0.5 甚至更低;而 IoU 高达 0.9 的框,分类分数却可能排在几百名开外。

也就是说,在 NMS 这个“淘汰赛”里,排在最前面的选手并不一定是真正的冠军,而且这个排序错误是系统性的,不是偶然的。

IoU-Net 的解决方案非常直接:你既然担心排序依据不靠谱,那就训练一个新的分支,直接预测每个框和 GT 的 IoU,用这个预测出来的 IoU 替代分类分数作为 NMS 的排序依据。

6.2 IoU预测头的结构与训练方式

IoU-Net 在检测头中增加了一个并行的 IoU 预测分支。这个分支的输入通常是从 RoI 池化后的特征图(或单阶段的特征图位置),经过几层全连接,输出一个标量IoU_pred。

训练时,IoU 预测头的目标函数非常简单:

L = SmoothL1(IoU_pred, IoU_gt)

其中IoU_gt是在训练时实时计算的:对于当前的 proposal 或 anchor 经过回归后的框,和它匹配的 GT 框做一次 IoU 计算,得到 0 到 1 之间的实数,直接作为回归目标。

这里有几个实现细节值得注意:

第一,IoU_gt 的范围是连续的 0 到 1,不是二分类也不是离散分类。很多人在实现时会把 IoU 量化成 0.05 一个桶再分类,但论文和后续实践都证明直接回归连续值的表现更好。

第二,IoU 预测头的训练数据来源一定要注意采样策略。RPN 或单阶段检测器的预选框在训练初期质量很差,很多框的 IoU 是 0,如果全部用这些数据训练 IoU 预测器,预测值会整体偏低。比较稳妥的做法是:在训练的前期阶段,先用 RPN 的输出和 GT 做匹配,采样 IoU 分布在 0.1 到 0.9 之间的框来训练,避免极端不均衡。

第三,IoU 预测分支和回归分支是并行关系,不共享坐标编码。IoU 预测头学习的是“这个框(回归后的结果)距离 GT 有多近”,而不是学习“怎么回归”,所以它的更新不会直接影响坐标回归。

6.3 IoU-guided NMS:用预测IoU排序,用IoU作为定位精化准则

IoU-Net 的推理阶段提出了两种用法。

第一种是 IoU-guided NMS。候选框不再按分类分数排序,而是按预测的 IoU 排序。也就是把标准 NMS 代码里scores = dets[:, 4]这一行,替换成 IoU 预测头的输出。这个改动非常小,但效果立竿见影,因为排序的“裁判”从分类分数换成了定位质量,那些“位置精确但分类分数一般”的框终于能上位了。

第二种是优化式的框精化(Optimization-based refinement)。这个思路更大胆:既然 IoU 预测器可以打分,那我能不能反过来用?——固定 IoU 预测器的网络参数,把框的坐标当成可学习的变量,用梯度上升不断调整坐标,让 IoU 预测值最大化。

这个过程不需要任何标注信息,完全是在推理时的一个自优化过程:

  1. 对 NMS 选出的框,初始化其坐标
  2. 把框坐标转成可微的形式,通过 IoU 预测网络前向传播
  3. 计算损失(负的预测 IoU),对坐标求梯度
  4. 沿梯度方向更新坐标若干步

论文里这个操作能显著提升框的定位精度,在 COCO 上可以比基准提升 1 个多点。但代价是推理时间成倍增加——每个框都要跑多次前向和反向传播,这对实时应用来说基本不可行。我的建议是:离线场景或者对精度要求极致的竞赛中可以用,线上低延迟服务就别考虑了。

另外,IoU-Net 还有一个非常实用的用法是替代 RPN 的 NMS。在小目标检测、实例分割等任务中,先用 IoU 分数对 RPN 的输出做排序筛选,比直接用 objectness 分数能筛出更多高质量 proposal。这个技巧在两阶段检测器里价值很高,很多人不知道。

7. 从NMS到IoU-Net的完整对比:我该在什么场景用哪个

7.1 四个方案的主干逻辑对比

为了让你能一眼看明白四个方案的区别,我把它们的主干逻辑整理成了表格:

方案核心问题处理方法排序依据抑制方式额外成本
标准NMS重复框冗余直接删除IoU超阈值的框分类分数硬抑制(置0)无
softNMS密集场景误删不删框,只降分分类分数软抑制(衰减)几乎为零
softerNMS排序依据不可靠预测定位方差,投票合并坐标分类分数 + 方差投票软抑制 + 坐标精化额外回归头 + 训练改动
IoU-Net排序依据不可靠直接预测IoU,用IoU排序预测IoU硬抑制 + 可选坐标精化IoU预测头 + 训练改动

从这个表格可以清晰看出,四个方案的演进本质上是沿着两条线展开的:

一条线是抑制策略的软化:标准 NMS 硬删,softNMS 衰减分数而不是删除,softerNMS 更进一步连坐标都做合并。

另一条线是排序依据的升级:标准 NMS 和 softNMS 都用分类分数排序,softerNMS 用方差作为辅助信息,IoU-Net 则直接预测 IoU 作为排序依据。

7.2 实际落地经验:哪些场景用软化策略就够了,哪些需要IoU-Net

结合我的项目经验,可以给出下面几个比较靠谱的选型建议:

1. 项目刚起步,后端时间紧张,模型已经训好,只想快速提升几个点。首选 softNMS,因为它不需要重新训练模型,推理代码改动不超过十行。在密集目标场景(行人、车辆、货架商品)下,这个是性价比最高的方案。

2. 目标尺寸差异大,小目标和大目标混在同一个场景。这种情况建议换 softerNMS。小目标因为像素少,回归天然不稳定,方差预测可以帮助识别那些“虽然分数高但位置其实不可靠”的小目标框,在投票合并时把大目标的稳定性和小目标的可信度做平衡。

3. 对精度要求极高,推理延迟不敏感,比如离线分析、竞赛刷分。IoU-Net 的 IoU-guided NMS + optimization refinement 是最优选择。特别是当你发现模型的分类分数和定位质量明显不对齐时,用 IoU 排序几乎是立竿见影的。

4. 实时视频流场景,边缘设备部署。我的经验是,标准 NMS 的在 CPU/GPU 上的实现经过高度优化(比如 torchvision.ops.nms 的 CUDA 版本),而 softNMS、softerNMS 的纯 Python 实现会有额外的排序开销。这种情况下,可以把排除阈值前的“候选簇排布”分析做一次缓存,或者直接在 C++ 层面实现 softNMS 的衰减逻辑,性能损失会小很多。

7.3 容易被忽视的工程细节:多类别NMS、坐标夹紧、置信度联调

最后聊几个工程上容易踩的坑。

多类别怎么处理?标准做法是逐类别做 NMS。事实上绝大多数检测器(包括 YOLO、Faster R-CNN)在推理时都是对每个类别分别调用 NMS,类别之间互不影响。但这里有个隐患:如果两个不同类别的框高度重叠(比如“人”和“骑的人”这种),逐类别 NMS 不会处理跨类别的重复框。更严格的做法是跨类别 NMS,把所有类别的框合在一起做去重,但这样容易误删“一个人骑车”这种本身就需要两个框同时存在的场景。具体业务里要按语义取舍。

坐标夹紧。NMS 计算 IoU 之前,推荐先把所有框的坐标夹紧到图像边界内(x1 = max(x1, 0) 等),否则框超出图像边界的部分会把面积算得虚高,导致 IoU 偏低。虽然大部分检测头的输出已经做了 clipping,但保不齐某些自定义检测头的输出没做。

置信度阈值和 NMS 阈值的联调。注意,NMS 的输入一般是“所有分数超过某个低阈值的候选框”,而不是“所有框”。这个低阈值通常取得很宽松(比如 0.05,保证召回),然后 NMS 之后再用一个高置信度阈值(比如 0.5)过滤最终输出。如果你调了 softNMS 或 IoU-Net,分数的分布会变化,你原来定好的高置信度阈值必须重新标定,否则最终输出框的数量和精度都会变。

训练和推理要一致。很多检测框架在训练时也会加一个 NMS 模拟(比如 ATSS 的 label assignment 过程涉及候选框筛选),推理时标准 NMS 和训练时的一致性也很重要。如果模型是在带 softNMS 效果的数据增强下训练的,推理时换回标准 NMS,性能可能不升反降。

前阵子我在试着做 qwen3-vl 微调物体检测相关的工作,发现一个有意思的现象:即便是基于视觉语言大模型的检测输出,最后也常常需要一层类似 NMS 的去重逻辑,因为模型在多个 token 位置可能输出对同一目标的多个框。很多做微调的人只看重模型本身,忽略了后处理设计,结果评测时被重复框坑了还不知道。说明 NMS 这条线无论检测范式怎么变,都不会过时,理解它的原理始终有价值。

最后分享一个个人感受:很多人觉得 NMS 这类“后处理”是雕虫小技,不值得深入研究,但实际上在检测器性能越来越卷的今天,把排序依据从分类分数换成 IoU、把硬抑制换成软衰减,往往比换一个 backbone 带来的收益更直接、更可控。不要小看这最后一步,它可能是你 mAP 突破某个瓶颈的最后一块拼图。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 4:30:34

高德车机版9.1.87美化包实战:从界面替换到共存版与悬浮导航

高德地图车机版9.1.87&#xff0c;是我最近在车机上折腾得最顺手的一个版本。最初只是嫌弃原版界面配色发灰、按钮布局太挤&#xff0c;想着把图标换一换、颜色调一调&#xff0c;结果越折腾越深&#xff0c;从简单的皮肤替换一路玩到了共存版、悬浮导航、巡航倒计时。前前后后…

作者头像 李华
网站建设 2026/10/5 4:30:31

FPGA电梯控制器Verilog实现:两层楼数字系统设计实战

1. 项目概述&#xff1a;为什么一个两层楼电梯控制器值得花两周时间手写Verilog&#xff1f;你可能刚做完数字逻辑实验课的七段数码管显示&#xff0c;或者正对着Quartus II里报错的“17.1 error: failure to obtain a verilog simulation license”发愁——别急&#xff0c;这…

作者头像 李华
网站建设 2026/10/5 4:30:27

Django+LLM大模型智能路线规划与个性化推荐系统设计详解

如果你的毕业设计题目同时出现了Django、LLM、大数据、推荐系统这几个关键词&#xff0c;那咱们可以好好聊一聊。这个组合几乎把计算机专业毕设的加分项叠满了&#xff1a;Django提供完整可靠的后端工程框架&#xff0c;LLM让系统具备真正的智能对话和个性化生成能力&#xff0…

作者头像 李华
网站建设 2026/10/5 4:30:25

抓包+大模型:API自动分析流水线实战

1. 项目概述&#xff1a;当抓包工具遇上大模型&#xff0c;API分析进入“读心”时代小黄鸟&#xff08;Reqable&#xff09;不是新面孔&#xff0c;它在移动App网络调试圈里早就是口碑担当——界面清爽、规则灵活、支持HTTPS解密、能导出Har和Curl&#xff0c;连iOS越狱设备上的…

作者头像 李华
网站建设 2026/10/5 4:30:19

企业级 DeepSeek 落地实战:从本地部署到 API 封装与压测

简介&#xff1a;这份《2025 DeepSeek企业落地应用讲义精华全版》面向企业管理者、数字化转型负责人及AI应用开发者&#xff0c;系统梳理DeepSeek在企业场景中的落地路径与创新实践。内容涵盖特征价值、交互生成、智能增强、部署开发四大篇章&#xff0c;从大任智库的培训方法论…

作者头像 李华
网站建设 2026/10/5 4:29:47

Linux下从源码编译安装muduo网络库全程指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华