ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

物体检测后处理全解析:NMS、softNMS、softerNMS与IoU-Net

2026/10/5 1:32:59 拓冰建站 浏览量
物体检测后处理全解析:NMS、softNMS、softerNMS与IoU-Net NMS这个缩写在物体检测领域几乎每天都要见到全称是Non-Maximum Suppression中文一般叫非极大值抑制。它不在backbone里也不算loss的一部分但只要是做检测从经典的两阶段Faster R-CNN、单阶段YOLO到最近很火的多模态大模型微调物体检测输出端永远绕不开它。这篇文章不打算只讲表面流程而是把NMS、softNMS、softerNMS、IoU-Net这五类后处理方案放在一起从nms原理讲到每个改进方法的动机、公式、实现细节和工程调参经验。对于正在跑检测模型、想提升mAP但又不知道从哪里下手的人这篇文章应该能帮你把后处理这笔账算清楚。1. 从物体检测的输出结构讲起NMS为什么必须存在1.1 检测框的来源与天然冗余一个检测模型在推理时输出并不是“这张图里有哪些目标”这么干净而是一大堆候选框。每个候选框包含四维坐标和若干个类别置信度或者再加一个目标性得分。这些框的来源并不统一两阶段模型里RPN网络会在特征图上铺大量anchor每个位置有多个尺度、多个长宽比一个目标往往同时命中几十个anchor单阶段模型直接在特征图每个点预测框天然会产生密集覆盖哪怕是DETR这类基于query的端到端模型虽然去掉了anchor但在困难样本上同一个目标仍然会对应多个query。所以候选框冗余是检测任务的结构性问题不是某一种模型的bug。既然一个目标对应了多个框每个框又有不同的分数和位置精度就需要一个后处理步骤来“合并同类项”保留最合适的那个框把多余的重叠框去掉。这个步骤不做的话模型一次输出几百个框评估指标mAP会很难看因为同一个GT被预测了多次precision会被压得很低。后处理看起来是小事处理不好对最终AP的影响往往比换一个更强的backbone还要大尤其是密集场景和边界模糊的目标。1.2 NMS原理一句话版本与完整流程nms原理用一句话说就是在所有候选框里先挑分数最高的框保留下来然后把所有和它重叠度过高的框都干掉再在剩下的框里重复同样的操作直到没有框可以选。这里的“重叠度”用IoU衡量IoU是两个框交集面积除以并集面积范围0到1值越大说明两个框越像在描述同一个目标。IoU等于1代表完全重合等于0代表完全没有重叠这个指标本身也是后面所有NMS变体共同依赖的基础。标准NMS的流程拆开看是这样按分数降序排列所有候选框取当前分数最高的框加入保留列表计算它和其余所有框的IoU把IoU大于阈值的框从候选集合里删除回到第二步直到候选集合为空。Python实现如下这段代码是纯numpy的适合理解逻辑import numpy as np def nms(dets, thresh): 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 iou inter / (areas[i] areas[order[1:]] - inter) inds np.where(iou thresh)[0] order order[inds 1] return keep这段代码里iou thresh的框会留下等于说和当前最高分框重叠度超过阈值的都被抑制。实际使用时通常会按类别分别做NMS因为不同类别的目标可以合法地重叠在一起比如旁边的人抱着一只猫人和猫的框重叠很正常。实现上可以用torchvision.ops.batched_nms一次做完按类NMS省掉手工循环后面第6章会专门说加速和算子选择。2. 传统NMS的三大硬伤阈值、密集场景与分数误导2.1 IoU阈值是一把双刃剑NMS最关键的参数就是IoU阈值它直接决定“两个框有多像才应该被合并”。这个参数非常敏感阈值调大比如0.7意味着只有高度重叠的框才会被删除结果是框保留得多误检增多输出里会出现大量重复检测阈值调小比如0.3又会让稍微重叠一点的不同目标被误删漏检飙升。我见过不少同学拿到模型第一件事就是改NMS阈值改成0.4、0.45、0.5结果AP忽上忽下最后依然不知道哪个更好。正确做法应该是把阈值当成超参来做网格搜索。在验证集上从0.3到0.7每隔0.05跑一遍NMS观察AP曲线通常会出现一个比较平缓的平台选平台中间的值最稳。还要注意不同数据集、不同模型的“最佳阈值”差异很大。比如anchor密集的模型和DETR类模型候选框分布完全不同后处理和分数分布也不一样不能照搬别人论文里的参数。2.2 密集遮挡场景NMS最容易误伤目标的地方如果画面里目标彼此挨得很近比如超市货架上并排的罐头、人群中的行人、显微镜下的细胞传统NMS就很容易翻车。原因很简单两个不同目标的框IoU可能超过阈值其中一个分数略低就会被直接删掉。典型的情况是一个目标被另一个目标遮住一大半分数低一点的检测框本来定位是对的但因为和分数更高的相邻框重叠过多被抑制最终形成漏检。这种情况在行人检测里尤其常见。行人经常成群出现互相遮挡严重早期行人检测论文里专门研究怎么改进NMSsoftNMS就是在这样的背景下火起来的。密集场景下把IoU阈值调低只会更糟因为不同目标的框之间哪怕只重叠了三分之一也会被当成同一个目标处理调高又会让一个目标多个框被同时保留输出混乱。所以传统NMS在密集场景下几乎是无解的必须改后处理逻辑本身。2.3 分类分数不等于定位质量还有一个更隐蔽的问题NMS本质上是拿分类置信度来代表框的质量但分类分数高并不代表这个框定位准。模型可能对某个目标非常确信但框偏了半个身位另一个框IoU更高、定位更准分类分数反而低一些。用分类分数做排序很容易留下一个“高置信度但框偏了”的结果把更准的框排挤掉。这暴露了传统检测框架里分类和定位被耦合在一起的问题。mAP的计算要求框和GT的IoU达到一定阈值才算检测成功如果留下的框定位不准哪怕分类分数再高AP依然上不去。后面要讲的softerNMS和IoU-Net本质上都在处理这件事要么把定位不确定性显式建模出来要么直接预测IoU来指导后处理排序。3. softNMS把“删除”改成“衰减”3.1 从二值惩罚到连续衰减softNMS是2017年提出的经典改进思路非常直接传统NMS遇到和最高分框重叠度超过阈值的框直接删掉这是一个二值决策softNMS则把“删除”变成“分数衰减”重叠度越高的框分数降得越狠但不会直接消失。这样一来如果一个框只是因为和最高分框重叠而被削弱但它本身确实对应着另一个真实目标只要分数没有降到最底部后面仍有机会被选中。这个改动的意义在于NMS从“非黑即白”变成了“软惩罚”减少了密集场景下的误删风险。原论文实验显示在PASCAL VOC和MS COCO上faster R-CNN等经典模型换上softNMS后mAP通常能提升1到2个点密集场景下收益更明显。最关键的是softNMS不需要重新训练模型任何已经训好的检测器都可以直接换后处理这是它最大的工程价值。3.2 两种衰减方式与实现代码softNMS里有两种常用的衰减方式线性衰减当IoU超过阈值Nt时score score * (1 - IoU)IoU等于1时分数直接变成0。高斯衰减score score * exp(-IoU^2 / sigma)对所有与最高分框有重叠的框都会降分IoU越大降幅越大sigma控制衰减速度。高斯版本对重叠度不那么高的框更宽容实际效果普遍比线性版本更稳。原论文实验里线性版本阈值Nt取0.3、高斯版本sigma取0.5可以作为一个起点但具体数值还是要结合自己的验证集调整。核心更新逻辑可以写成这样import numpy as np def soft_nms_update(scores, iou, gamma, methodgaussian): if method linear: new_scores np.where(iou gamma, scores * (1 - iou), scores) elif method gaussian: new_scores scores * np.exp(-iou * iou / gamma) return new_scores实际完整流程和标准NMS类似每次从当前集合里找最高分框把它保留下来计算它和其余框的IoU再用上面的函数更新所有剩余框的score而不是直接删除。重复这个过程直到所有框都被选完或者剩余最高分低于设定的score阈值。因为每一轮都在动态更新分数排序结果会随着衰减过程变化所以实现时不能用一次性排序替代。3.3 softNMS的收益边界和适用场景softNMS不是万能的。它缓解了“误删”问题但并没有解决“分类分数不等于定位质量”的问题因为排序依据仍然是分类score。如果某个框分类分数虚高但定位很差softNMS依然会优先保留它。另外由于低分框不会被硬删除输出候选框数量会比传统NMS多score阈值需要适当提高否则假阳性框会变多。在实际项目里softNMS最适合的场景是检测模型已经训练好不想改训练代码且验证集上明显存在“目标挨得近导致漏检”的问题。先做一次NMS阈值网格搜索如果发现最优阈值偏低比如0.3左右说明误删问题比较严重这时候换softNMS通常能再涨一个台阶。注意如果目标本身非常稀疏每张图就一两个目标softNMS的收益会很有限没必要增加那点计算量。4. softerNMS让定位质量参与后处理4.1 核心动机网络不确定自己的框准不准softerNMS和softNMS名字像解决的问题却完全不同。softNMS处理的是“重叠目标被误删”softerNMS处理的是“保留的框定位不够准”。如果把分类分数比喻成“这个框是不是一个目标”那定位质量就是“这个框有多贴合目标”。这两件事是可以分开建模的而传统检测框架往往只用分类分数来代表框的质量这会带来一个明显问题模型对目标很确信但对边界框的位置可能并没有那么确信尤其是遮挡边缘、运动模糊、小目标这些场景。softerNMS的核心思想是让网络显式输出“我对这个框每个坐标的置信度”。具体做法是在回归头里额外预测每个坐标的方差这个方差表示网络对这个坐标的不确定程度。推理时方差小的框参与定位的权重更大方差大的框权重更小从而得到一个更稳、更准的最终框。4.2 训练阶段KL Loss与方差输出要让网络学会输出方差需要把原本的回归损失换成概率形式的损失。对每个坐标假设网络输出的预测值x_e是高斯分布的均值方差sigma^2由网络同时输出回归目标x_gt是这个分布的一个采样点那么负对数似然损失可以写成L_reg log(sigma) (x_gt - x_e)^2 / (2 * sigma^2)这个式子很直观当预测坐标偏离GT很远时让方差变大可以避免loss爆炸当预测坐标很准模型会倾向于把方差压小拿这个坐标当作高置信度输出。实际训练时网络通常直接输出log(sigma^2)再用exp映射回正数避免方差出现负值。训练初期要让方差头保持较大的值否则loss会不稳定常见的做法是给方差分支一个较小的学习率或者对GT坐标也引入一个很小的方差做平滑。训练改动其实不大在原有bbox回归头旁边并联一个方差输出头loss加一项KL回归即可。但要注意这个分支需要和检测head一起训练才能学到有意义的方差已训练好的模型没法直接补一个方差头也就是说softerNMS的落地成本比softNMS高。4.3 推理阶段方差加权投票推理时softerNMS并不改变候选框的筛选流程一般还是先用softNMS或者传统NMS选出一组属于同一目标的候选框然后对它们的坐标做加权平均替代“直接保留最高分框”的硬选择。加权公式大致是x_final sum(score_i / sigma_i^2 * x_i) / sum(score_i / sigma_i^2)权重由分类分数和方差的倒数共同决定。分类分数高说明“这个框大概率对应一个真实目标”方差小说明“这个框的边界位置可信”两者同时高这个框才能主导最终的定位结果。反之一个分数还行但方差很大的框对最终坐标的贡献会被压得很低。我自己的使用体验是softerNMS在标注噪声比较大的数据集上收益最明显。比如外包标注的框质量参差不齐或者小目标边缘本身模糊网络回归方差会比较大投票机制能把几个互相偏移的框平均出一个更稳的结果。但如果标注质量本来就很高、模型定位已经足够准投票带来的提升会比较有限反而增加了训练和推理的复杂度。5. IoU-Net用预测IoU替代分类分数做排序5.1 解耦分类与定位为什么需要IoU预测IoU-Net的思路比softerNMS更直接与其用分类分数去猜框好不好不如直接训练一个分支预测“当前这个框和真实目标之间的IoU”。这个预测值就是定位质量本身排序和抑制都不再用分类score而是用预测IoU。整个逻辑在两阶段检测器里很顺RPN出来的proposal经过RoI池化后喂给分类和回归头旁边再接一个IoU预测头输入还是同一份特征输出一个0到1的标量。这个设计解决了一个根本问题分类分数和定位质量的相关性并没有我们想象的那么强。我见过不少失败案例模型对某个目标非常确定分类分数高达0.95但它在目标边缘上多框了一个背景区域IoU只有0.5另一个框分类分数0.6但边界贴合得非常好IoU有0.85。传统NMS会把前一个框当成“更高质量”的检测结果保留后续的评估自然吃亏。IoU-Net直接绕开了这个误导。5.2 IoU预测分支的训练与实现IoU预测头的训练标签很好构造当前候选框和它匹配到的GT框之间算一个真实IoU这个值就是回归目标。损失函数可以用MSE或者BCE输入特征是来自backbone的RoI特征经过几层全连接之后输出一个标量。这个头可以和分类、回归头并行训练也可以放到多任务学习的框架里一起优化。论文里还配套提出了Precise RoI Pooling用来消除RoI Align的离散量化误差。因为RoI Align在做双线性采样时会对坐标取整或做有限点采样这个误差在普通检测任务里可能无所谓但对IoU预测这种精度敏感的任务会有影响。所以IoU-Net在实现细节上对池化层的要求更高。如果只在标准检测模型上简单加一个IoU头而不换池化层效果会打一些折扣。5.3 IoU引导的NMS与定位精修推理阶段的改动主要有两点。第一点NMS排序依据从分类分数改成预测IoU先处理“定位质量高”的框而不是“分类置信度高”的框这样可以避免高分类分但框偏了的候选抢跑。第二点预测IoU可以作为最终检测分数的组成部分比如和分类分数相乘或者做一个带权融合让最终排序更接近真实质量。IoU-Net还有一个衍生技巧用预测IoU对保留框做定位精修。因为在两阶段框架里回归头已经预测了一个框偏移IoU头可以判断这个偏移之后的结果到底好不好如果预测IoU很低说明回归头对这个proposal的输出并不可靠可以选择继续迭代回归或者直接降低这个框的排名。这种“用预测质量反过来指导位置”的思路和单纯做回归或者单纯做NMS相比又往前走了一步。5.4 NMS家族横向对比表为了把几种方案放在同一张图里看清楚我整理了一个对照表方法需要重新训练推理额外成本主要解决适用场景标准NMS否极小重复框去重通用基线softNMS否低密集重叠导致的漏检已训练模型快速提升softerNMS是中保留框定位不准定位精度要求高IoU-Net是中分类分数误导排序新模型训练、追求整体AP从这张表能看出来softNMS是“零成本换算法”softerNMS和IoU-Net都是“训练阶段就要开始改造”。如果只是想在已有模型上快速涨点softNMS几乎是唯一选择如果要从零训一个新模型并且希望后处理上限更高IoU-Net或者softerNMS更值得投入。6. 工程落地选型、调参与多模态微调pipeline的NMS集成6.1 后处理调参的正确顺序很多人在训练检测模型时把精力全放在backbone、数据增强和loss上后处理只用一个默认的NMS阈值这是非常可惜的。后处理调参几乎是最便宜的涨点手段成本几乎为零。我建议的顺序是先固定一个用起来最顺手的NMS实现比如torchvision.ops.nms然后对分数阈值、IoU阈值、最大输出框数三个参数做网格搜索最后再考虑是否需要换算法。具体来说分数阈值可以从0.05开始试太低会留下大量低质量框太高会漏检NMS阈值在0.3到0.7之间按0.05步长搜索最大输出框数需要结合任务看小目标多的图可以放到300甚至500。还有个容易被忽略的点如果对多个类别同时做NMS别忘了一定要按类别操作否则不同类别目标重叠时会被错误抑制。6.2 加速与算子选择NMS本身是一个非常小的算子但在视频流和大batch推理时累计耗时也不容忽视。PyTorch环境下最推荐直接用torchvision.ops.nms它支持CUDA单卡推理时基本没有瓶颈。如果输出检测框数量特别大比如每张图几千个候选框可以在NMS之前先按score做一次topk截断比如保留前2000个框这样可以显著减少后续排序和IoU计算的耗时代价只是可能丢掉极少数低分真框实际操作中我几乎不会因为topk截断掉点。如果做TensorRT部署建议直接用EfficientNMS插件它把score阈值、IoU阈值、最大输出框数都集成在一个层里还能自动完成按batch处理。EfficientNMS支持的排序方式通常只有按score硬排序如果需要softNMS或者自定义IoU排序就得在模型外部用host端逻辑实现部署代价会变大。所以工程选型时还要考虑部署平台的算子支持不能只看论文里的mAP。6.3 qwen3-vl微调物体检测VLM输出同样需要NMS最近多模态大模型做物体检测越来越常见比如用qwen3-vl微调物体检测本质是把检测任务转成对话式生成模型输出一段文本里面包含框坐标和类别。但就算模型是生成式输出同一个目标依然可能被多个query或多次采样生成相似的框而且VLM的坐标通常是归一化数值解码回原图分辨率后这些重复框之间会因为文本量化和采样随机性产生小偏移。如果不去重mAP会被同一目标的重复框拉低。实际接入时我的处理管线是模型解码出结构化结果类别、置信度、归一化坐标→ 坐标反算到原图 → 按类别收集候选框 → 做NMS后处理。这里有个细节很容易踩坑多模态模型输出的“置信度”往往不是校准良好的概率直接拿它做NMS排序容易出问题。我的经验是先做一次score校准比如在验证集上统计置信度分布后做一个单调映射或者干脆用softNMS它对置信度噪声的容忍度更高。在qwen3-vl微调物体检测的评测里我把后处理从普通NMS换成softNMS在目标较密集的自定义数据集上mAP提升了1个多点而且不需要重新训练模型。还要注意一个VLM独有的问题模型可能会在说“图片里没有目标”的句子里也生成坐标token解析时如果只按格式抽取不做非空判断和类别过滤后处理阶段会多出一堆假框。所以建议在进入NMS之前先做两层过滤第一层过滤掉不在指定类别列表中的结果第二层过滤掉置信度过低的输出。这样后续NMS的输入才比较干净。6.4 方法选型速查最后放一个选型速查表可以根据自己项目的处境直接找对应方案你的处境推荐后处理训练好的常规模型想快速提升mAP先调NMS阈值再试softNMS密集遮挡场景softNMS重新训练时可考虑IoU-Net定位精度/框质量要求高softerNMS或IoU-Net完全不想动训练代码NMS加调参或softNMS多模态大模型微调物体检测softNMS加score校准我在实际项目里被NMS坑过不止一次。早前做一个货架商品检测项目模型在训练集上mAP涨得很好一到现场就漏检并排的同类商品。当时第一反应是加数据、调backbone折腾了好几版都没太大变化。后来查了一圈发现是默认的NMS阈值0.5把所有重叠度稍高的不同商品当成一个目标处理了。换成softNMS之后AP直接涨了2个点。这次之后我做任何检测项目都会先把后处理三件套——score阈值、IoU阈值、最大输出框数——在验证集上扫一遍再考虑要不要动网络。另一个小技巧是如果同时在很多类别上做NMS别写循环直接batched_nms一次搞定速度快不少。后处理看起来是“脏活”但恰恰是性价比最高、最容易拿到免费涨点的地方。希望这篇文章能帮你把NMS这笔账算清楚下次调模型少走点弯路。