ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

NMS非极大值抑制完全指南:原理、缺陷与Soft-NMS等改进方法

NMS非极大值抑制完全指南:原理、缺陷与Soft-NMS等改进方法 做过几年目标检测相关项目几乎每个跟检测打交道的同学都会遇到同一件事模型跑通了mAP也算得出来可一到自己部署或者看可视化结果总发现同一个物体上叠了七八个框又或者两个挨得很近的目标被莫名其妙砍掉一个。这时候大家都在找同一个东西——NMS非极大值抑制Non-Maximum Suppression。我最早接触NMS是在看Faster R-CNN源码的时候当时只觉得这是个“去重”的小步骤后来越调模型越发现NMS选得好不好直接影响最终检测效果的上限。这篇技术总结我打算把NMS的完整脉络梳理一遍从原理开始到多类别NMS的写法再到它本身的缺陷以及如今各种NMS改进方法的思路。内容比较硬核但我会尽量把每个细节都讲透适合正在做目标检测算法研究、或者准备优化检测后处理的工程师参考。1. 别再只把NMS当成“去重工具”——先看它站在检测流程的哪个位置1.1 从检测头的原始输出说起要理解NMS得先搞清楚它前面那一堆数据是从哪来的。以两阶段检测器为例RPN或者区域建议网络会先吐出大量候选框一阶段检测器如YOLO、SSD则直接在特征图的每个位置上生成预设的anchor。网络最后回归的是每个候选框相对anchor的偏移量同时输出一个“这个框里有目标”的置信度分数。这个阶段的数据是极其冗余的一个真正的前景目标周围经常有几十甚至上百个框都自认为“我是最准的那个”。举个例子一张图里有一只猫检测头可能输出20000个候选框其中大约几百个都和这只猫的真实位置有重叠。它们的置信度从0.99到0.3不等坐标也各不相同——有的把猫头包住了有的把整个猫身子包住了有的只框住了猫尾巴。这时候如果没有后续处理最终呈现给用户的就是一坨杂乱无章的框。1.2 NMS解决的核心矛盾是“重复”而不是“误检”很多人把NMS的功能理解成“把评分低的框去掉”这其实不够准确。从本质上说目标检测的评估和部署都要求“一个目标只输出一个检测框”而模型本身又倾向于“一个目标输出多个重叠候选”。NMS要做的事情就是在大量重叠的候选中保留一个最可信的框抑制掉那些和它高度重叠、置信度又更低的框。注意它是为了解决“重复检测”这个矛盾而生的。拿实际数据说话在COCO这种数据集上一个中等复杂度的场景经过NMS前后检测框的数量通常能从几千个直接压缩到几十个。这个压缩过程看似简单实际上每一步都直接影响最终mAP的高低。很多刚入门的同学会忽略一个事实NMS是整个检测pipeline里最后一个有决定性影响的模块前面的特征提取、候选生成、分类回归做得再好NMS处理不当一样会掉点。1.3 它在训练和推理阶段有完全不同的角色这里必须提醒一下NMS在训练阶段和推理阶段的作用是不同的。训练阶段绝大多数检测器并不直接对最终输出框做NMS而是通过匹配策略比如MaxIoU assigner给每个anchor分配标签让网络学习哪些框该保留、哪些框该抑制。You Only Look Once系列在训练时会计算每个anchor和GT的IoU然后挑正样本这个过程其实隐含了“非极大”的思想但没有真正执行抑制。推理阶段NMS才是真正的主角。模型得到所有预测框之后必须经过NMS才能把冗余框消掉变成最终可交付的结果。在TensorFlow Object Detection API和MMDetection这类框架里NMS通常写在模型的postprocess部分。这也就解释了一个常见现象为什么有些模型在训练时mAP很高导出的模型部署后效果却变差——多半是后处理阶段把NMS的阈值或者实现方式改动了。2. NMS原理拆解与多类别NMS的一次讲透2.1 经典NMS的四个步骤每一步都很关键标准NMS的算法流程本质上是一个贪心式的局部最大值搜索。假设所有预测框组成了一个集合每个框带有一个置信度分数算法按下面的步骤执行第一步按照置信度分数从高到低对所有框排序。这一步决定了“谁先被选中”所以置信度的排序稳定性很重要。如果两个框分数完全相同不同框架的排序稳定性可能导致不同的保留结果但总体影响不大。第二步从排序后的列表中取出分数最高的框把这个框加入最终保留列表同时从候选列表中删除。第三步计算这个最高分框和候选列表中所有剩余框的IoU。IoU大于某个阈值一般取0.5或0.45的框直接删除。这一步的含义是“剩下这些框里凡是和当前最优框重叠度足够高的都视为对同一个目标的重复检测既然你已经有了更好的代表你们就没必要存在了。”第四步回到第二步在剩余候选中再取出最高分框重复直到候选列表为空。这个算法复杂度乍一看是O(N^2)N是候选框数量。但在实际工程里候选框数量通常在几百到几千并且越往后候选越少所以实际耗时可控。真到了模型FP16推理、要跑实时视频流的时候NMS的耗时就不能忽视了这个后面我会单独讲。2.2 IoU计算NMS的唯一度量工具NMS里最核心的计算就是IoUIntersection over Union也被称为杰卡德相似系数。假设两个框分别是A和BIoU (A∩B的面积) / (A∪B的面积)。分母是两张框覆盖的总面积分子是它们重叠的面积。这个数值越接近1说明两个框重叠程度越高越接近0说明它们几乎不相干。具体到代码层面计算两个框的IoU时有一个常见的坑坐标系的定义。大部分目标检测框架里一个框用[x1, y1, x2, y2]表示并且x2和y2通常表示右下角的坐标但这个坐标到底是包含边界还是不包含边界不同库有不同的约定。比如在pyxell和MMDetection里有些实现计算宽度时用x2 - x1有些用x2 - x1 1。单独看一个实现没问题但你在自己写可视化或者评估脚本时如果不注意这个细节两个模块算出来的IoU会有细微偏差然后你会发现NMS结果对上了、评估结果却对不上非常折腾人。另一个常见问题是浮点精度。当两个框完全不重叠时相交面积算出来可能是0但有时候因为浮点取整误差会得到一个很小的负数所以在计算相交区域时通常要加一层np.maximum(0, inter_w)的保护。2.3 多类别NMS直接全局做还是分开做多类别NMS指的是目标检测器要同时检测多个类别比如人、车、猫、狗。NMS在处理这些不同类别的框时有一个很关键的选择按类别分别执行还是所有类别一起执行标准做法是按类别分别执行NMS。也就是说先把所有预测框按类别分组对每一类的框单独做NMS最后再合并结果。这样做的好处是两个不同类别的候选框哪怕IoU高达0.99也不会互相抑制。比如一个“人”框和一个“马”框重叠度很高按类别做NMS的话两者都可以被保留。但在某些特殊场景下这种做法的缺陷也明显如果两个类别的语义本来就容易混淆比如“猫”和“狗”它们之间可能出现一个框同时被两个类别以较高置信度预测的情况这时候分开做NMS会导致一个目标输出两个类别的框。解决思路是要么在NMS之前做类别级别的置信度校准要么在某些特定任务里使用跨类别的NMS也叫多类别联合NMS。实操中绝大多数通用检测模型都按类别分别做NMS尤其是COCO这种类别重叠不相斥的数据集。另外要注意背景类。在检测器里通常会有一个背景类别或者对应低置信度但并非背景的难分样本。做多类别NMS时背景类不应该参与抑制否则很容易把有价值的前景框错杀。这也是我在工程化时踩过的坑有的框架输出的类别编号直接包含背景需要先过滤掉背景类别再做NMS。2.4 NMS的阈值选择0.45、0.5、0.6还是动态的NMS的IoU阈值直接控制着“抑制的严格程度”。阈值越低抑制越狠最终留下的框越少阈值越高抑制越宽松可能会有更多重叠框残留。经典的VOC评估协议用的NMS阈值是0.5COCO评估协议用得较多的是0.5和0.75两个档位。需要特别说明的是mAP0.5里的0.5是评估时判断检测框和GT匹配的IoU阈值跟NMS执行时的抑制阈值不完全是一回事但二者确实会互相影响——NMS把重叠度较高的框抑制掉之后评估时候选框更容易被干净地匹配到GT上。实际项目里NMS阈值到底取多少不能拍脑袋。我的习惯是先设定一个候选范围比如[0.3, 0.7]然后在验证集上做一次网格搜索具体做法是固定所有其他超参数只调整NMS阈值观察mAP曲线的变化。这里有一个经验规律目标越小、分布越密集NMS阈值需要相对调高比如0.6因为小目标之间的自然位置相近阈值太低了容易误伤相邻的独立目标目标大而稀疏时0.4、0.5就够用了。此外如果你用了Soft-NMS这类改进方法原先0.5的阈值可能还要重新调。3. NMS的真实缺陷为什么学者们前赴后继“改良”它3.1 缺陷一密集场景下它会把该留下的框一起干掉经典NMS最被诟病的问题出现在密集人群、密集车流这类目标聚集的场景。假设两个人站得很近两个检测框的重叠度很高但分别对应两个不同的真实目标。经典NMS在排序时如果第一个人的框分数略高一些就会直接抑制掉第二个人的框哪怕第二个框也是个完美的检测结果。这个现象被称为“误抑制”。我做行人检测时经常遇到一个情况一个行人被前面的路人挡住大半检测器给出的置信度本来就偏低如果这部分人群的框重叠度高经典NMS会在极其早期就把低分的行人框删掉。最后可视化结果里原本该有两个人的地方只留下一个框误检倒是没有了漏检却变多了。3.2 缺陷二置信度分数并不等于定位精度经典NMS筛选框时唯一依据就是分类置信度。但置信度高意味着“这框里很可能有目标”并不代表“这个框的位置有多准”。一个中心偏了半个身位的框只要分类分数高就能在NMS里占据优势反过来压制一个定位非常准确但分类分数稍低的框。这就导致一个后果NMS选出来的框往往不一定是IoU和GT最接近的框。用数据说话在很多COCO类模型上分类置信度和定位质量之间的相关系数其实并不高。这个现象也是后来Softer-NMS这类方法出现的直接导火索它提出要利用网络预测的定位方差来重新排序把“置信度”和“定位质量”解耦。对于没接触过这个概念的同学可以这样理解经典NMS是“只看胆子大不大不看得准不准”显然这不合理。3.3 缺陷三单一阈值处理不了所有分布经典NMS用的是固定IoU阈值比如0.5。这里隐含的假设是所有目标类别、所有尺度的物体其“合理的重叠抑制边界”是一致的。但真实场景远远不是这样。行人目标目标小且密集阈值0.5可能太严交通标志目标稀疏且尺寸大阈值0.5可能太松。单阈值还体现在另一层同一个物体在不同尺度下框与框之间的IoU分布完全不同。比如一个很大的公交车和一个很小的行人它们各自生成的一堆候选框重叠情况差异极大。用同一个阈值去切总有一部分场景被过度抑制另一部分场景抑制不足。Adaptive NMS这类方法就是为了解决这个问题通过预测目标密度来自适应调整抑制阈值。3.4 缺陷四它把“抑制”做成了一刀切的二值操作经典NMS对IoU超过阈值的框直接“死刑”分数归零对IoU低于阈值的框完全保留分数不动。这种二值化操作没有过渡区导致抑制结果对阈值非常敏感。阈值稍微动一点最终的检测框集合可能就完全不同。这个“脆弱性”在实际调参中很令人头疼也是Soft-NMS改良的核心出发点之一。4. NMS的改进思路和代表方法——从Soft-NMS到各式高阶变体4.1 Soft-NMS把“直接删除”改成“降低分数”Soft-NMS的出发点是IoU超过阈值的框未必是多余的它可能只是和最高分框重叠度太高但本身仍对应一个目标或者更好的定位。与其直接删掉不如把它的置信度分数降低这样在后面继续循环时它还有机会被重新遍历到不至于被彻底消灭。具体做法有线性加权和高斯加权两种方式。线性版本是如果当前框和最高分框的IoU大于阈值则新的分数 原分数 × (1 - IoU)高斯版本是新分数 原分数 × exp(-IoU^2 / σ)σ是超参数。两种方式都比二值删除温和。在实现Soft-NMS时我建议直接看原论文给的高斯形式。高斯加权的好处是它没有硬阈值IoU越大惩罚越强IoU略高时惩罚相对平缓在密集场景下不容易发生过激抑制。Soft-NMS在COCO上能够带来稳定的小幅涨点大概0.4到1.0个mAP几乎不增加推理时间。它的最大价值在于改动极小直接替换NMS函数就能复现收益。不过要注意Soft-NMS并没有改变“按置信度排序”这个底层逻辑它只是把0/1二值抑制改成连续降分。如果候选框本身的置信度和定位质量严重不一致Soft-NMS依然解决不了。另外它比经典NMS的时间复杂度更差一些因为无法一次性完成向量化的抑制在候选框数量很大的情况下可能成为瓶颈。4.2 DIoU-NMS把中心点距离引入抑制条件DIoU是Distance-IoU的缩写最早在DIoU损失函数的论文中被提出同时也顺带给出了一个NMS的改进版本。经典NMS只考虑框与框之间的IoUDIoU-NMS在IoU的基础上加了一个惩罚项两个框中心点的归一化距离。这么做的直接原因是两个框的中心点距离越远它们越有可能是两个独立目标不能仅仅因为IoU高就互相抑制。具体计算公式可以化简为新的抑制指标 IoU - R_DIoU其中R_DIoU和两框中心点的欧氏距离以及最小外接框对角线长度有关。当两个框中心点完全重合时R_DIoU为0退化成经典IoU中心点距离越大R_DIoU越大抑制指标越小越不容易触发抑制。在密集场景测试中DIoU-NMS比经典NMS更宽容能有效降低漏检率。我个人的实测体会是它在行人、车辆这类长宽比接近的物体上表现不错但在长条形物体比如躺着的动物、长条卡车上需要谨慎调参因为长条形框的中心点距离并不总能反映真实的重叠关系。给一个直觉类比两本书竖着并排放在书架上它们的中心点距离很远即使它们在图像上有部分重叠你也应该同时保留两个框这恰恰是DIoU-NMS思路想解决的问题。4.3 Softer-NMS让网络自己告诉我哪个框更准前面提到经典NMS最大的隐患就是“置信度不等于定位精度”。Softer-NMS从另一个角度切入既然分类头可以输出一个置信度那么回归头也能输出一个位置方差。网络在每个候选框上除了预测坐标外还预测一个方差值方差越小说明该框定位越可靠。进行NMS时不再只拿分类置信度作为排序依据而是把分类置信度和定位方差结合起来方差小的框即使分类分数略低也更有机会被保留。Softer-NMS还引入了一种加权平均策略对于一组高度重叠的候选框不是只留下最高分的那个而是根据每个框的定位方差和置信度对它们的位置做加权平均融合。这个思路和“模型集成”有相似之处多个有噪声的估计互相纠正最终得到一个更稳定的坐标输出。这个方法的代价是需要修改检测头的结构在训练时给回归分支加一个方差预测头同时改写损失函数用高斯分布建模回归误差。相比Soft-NMS那种“即插即用”的替换方式Softer-NMS的迁移成本高不少。但如果你对目标框的定位精度有很高要求比如自动驾驶里的障碍物检测、医学影像里的病灶定位那么Softer-NMS带来的收益是值得付出的。4.4 更高阶的探索Adaptive NMS、Cluster NMS、Matrix NMSAdaptive NMS想解决密度变化的问题。它给网络加一个密度预测分支根据目标密度的大小自适应地调整NMS阈值。目标密集的地方阈值自动升高减少误杀目标稀疏的地方阈值自动降低增强抑制能力。这个思路在人群计数、密集场景检测上有明显效果。Cluster NMS的思路是把NMS看成“聚类”把高度重叠的候选框聚成一个簇然后在簇内部进行抑制或融合。这种方式比起逐个框贪心判断天然更适合并行计算。Matrix NMS则把NMS过程矩阵化一次性计算所有框之间的IoU矩阵然后通过迭代方式更新抑制分数避免了循环依赖在GPU上的加速效果非常明显。如果你在部署时发现NMS成了推理瓶颈可以重点看看Matrix NMS它的实现思路几乎就是为并行加速设计的。4.5 各种NMS方法横向对比与选型参考方法核心改动点适用场景额外成本涨点效果经典NMS无通用、目标稀疏场景无基准Soft-NMS连续降分替代二值删除密集场景、目标互相遮挡几乎无稳定小幅提升DIoU-NMS添加中心点距离惩罚行人、车辆等近似物体计算量略增中幅提升Softer-NMS预测定位方差并加权融合高精度定位需求需改网络结构定位精度提升明显Adaptive NMS密度自适应阈值人群、密集目标需加密度预测分支密集场景提升明显Matrix NMS矩阵化并行计算大规模候选框、实时推理实现复杂度高提速明显选型时我的建议是如果只想快速提点优先试Soft-NMS如果做的是自动驾驶或安防这类对定位要求高的直接上Softer-NMS如果遇到的是密集场景且候选框数量很大可以组合Soft-NMS Matrix NMS的思路来做。5. 核心环节实现——手写一个靠谱的NMS再到工程化部署5.1 一段最简NMS的Python参考实现先给出一段经典NMS的PyTorch实现我建议每个检测方向的工程师都自己手写过一次不依赖框架自带函数才能真正理解它的边界条件。import numpy as np def nms(dets, thresh0.5): dets: N x 5, 每行是 [x1, y1, x2, y2, score] thresh: IoU抑制阈值 返回: 保留的检测框索引列表 if len(dets) 0: return [] x1 dets[:, 0] y1 dets[:, 1] x2 dets[:, 2] y2 dets[:, 3] scores dets[:, 4] areas (x2 - x1) * (y2 - y1) # 按score降序 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) h np.maximum(0.0, yy2 - yy1) inter w * h iou inter / (areas[i] areas[order[1:]] - inter) inds np.where(iou thresh)[0] order order[inds 1] return keep这段代码有几个细节是面试常考、也是工程里容易错的点第一order[inds 1]里的1是因为iou是对应order[1:]计算的我们只想保留那些与当前最高分框IoU不超过阈值的框所以索引要整体偏移一位。第二w np.maximum(0.0, xx2 - xx1)这行是为了防止两个框不相交时出现负面积。第三这版实现没有处理坐标越界和空候选实际使用要加防御性判断。5.2 多类别NMS的正确工程写法多类别NMS的工程实现通常有两种姿态。第一种是循环每个类别分别调用上面的nms函数简单可靠第二种是借助类别的唯一边界或者按类别偏移坐标实现一次调用完成多类别NMS。我个人在初版工程里倾向于循环因为逻辑清晰也好调试。下面给一个循环版示例def multiclass_nms(boxes, scores, score_thr0.05, iou_thr0.5): boxes: N x 4 scores: N x C, C是类别数 n_classes scores.shape[1] final_boxes [] final_scores [] final_labels [] for cls_id in range(n_classes): cls_scores scores[:, cls_id] cls_mask cls_scores score_thr if not np.any(cls_mask): continue cls_boxes boxes[cls_mask] cls_scores cls_scores[cls_mask] dets np.hstack([cls_boxes, cls_scores[:, None]]) keep nms(dets, iou_thr) final_boxes.append(cls_boxes[keep]) final_scores.append(cls_scores[keep]) final_labels.append(np.full(len(keep), cls_id)) if len(final_boxes) 0: return np.empty((0, 4)), np.empty(0), np.empty(0) final_boxes np.vstack(final_boxes) final_scores np.hstack(final_scores) final_labels np.hstack(final_labels) return final_boxes, final_scores, final_labels这里我建议先把低置信度框过滤一遍再做NMS。经典流程是先做一次score_thr过滤把置信度低于0.05的框直接丢弃然后再按类别NMS。这样做的好处是大幅减少NMS的输入规模提高推理速度而且低置信度框本来就不该占太多决策权重。5.3 把NMS放进后处理全流程里看NMS从来不是孤立的一步。在典型的目标检测推理pipeline中完整的后处理顺序是解码decode模型输出得到框坐标和得分 → 阈值过滤score_thr → 类别NMS → 可选的对象类别过滤 → 最终输出。如果模型输出的是特征图上的anchor偏移量解码时需要按照每个特征层的stride把坐标映射回原图如果用FCOS这类anchor-free方法解码方式又有区别。NMS的位置基本固定在解码之后。这里要给一个新手的忠告不要在解码之前做NMS也不要在还没过滤低分框的时候做全局NMS。解码之前的坐标还是相对于特征图的做NMS毫无意义不先过滤低分框直接全局NMS会把大量无意义框带入计算拖慢速度不说还可能造成误抑制。5.4 性能优化从纯Python到GPU加速NMS是后处理里典型的计算热点纯Python版本的NMS在目标数量上千时单帧耗时可能到几十毫秒。在实时系统中这个开销不可接受。方向有几个第一用矢量化和矩阵化计算。像Matrix NMS那样一次性算出所有框两两之间的IoU矩阵然后用迭代更新替代逐个循环。PyTorch里可以用torch.Tensor直接实现这类逻辑利用GPU并行加速。第二减少NMS的输入规模。NMS的复杂度是依赖于候选框数量的所以最直接的优化是用score_thr提前过滤。很多模型在0.05阈值下就能过滤掉80%以上的候选框NMS瞬间就不是瓶颈了。第三用小技巧替代部分循环。比如在经典NMS里最高分框的IoU计算可以只和一个预筛选后的子集做不一定每次都和全部剩余框算一遍。工程上的“fast NMS”就是用这种局部近似换速度。6. 常见问题与排查技巧实录6.1 坐标格式用错NMS结果错得悄无声息这个问题我在自己项目里出现过也在帮别人看代码时遇到过。有些模型输出的坐标是中心点加宽高cx, cy, w, h有些输出的是左上角加宽高x, y, w, h还有些是左上角加右下角x1, y1, x2, y2。如果不统一转成同一种格式就喂给NMS计算出来的IoU会出现系统性偏差。排查方法很简单随机抽几张图打印NMS前后的框数量变化如果发现NMS几乎没删框或者把明显重叠的框全删了先检查坐标格式。6.2 多类别NMS把不同类别的框误杀了如果你发现检测结果里“人”和“马”重叠区域只剩一个框大概率是代码里把所有类别的框放在一起做了全局NMS而不是按类别分开。排查时可以打印每个类别的框数量统计看是否存在类别间互相抑制的迹象。如果确认代码是按类别NMS但个别场景下两个类别的语义确实容易互斥比如“上衣”和“连衣裙”这类那就要考虑在类别之间做合理的规则抑制了。6.3 NMS后的框数量异常少或者异常多框数量异常少通常是NMS阈值设置得太低或者score_thr设置得过高导致大量候选框在进入NMS前就被过滤掉了。反之框数量异常多是阈值设得过高比如0.9几乎所有重叠框都保留了下来。可以参考我前面说的网格搜索方法在验证集上画一条“阈值 vs mAP”曲线找到平台期附近的数值。6.4 Soft-NMS和高性能部署之间的取舍Soft-NMS的时间复杂度比经典NMS高GPU上不好写完全并行的版本。如果部署环境对推理延迟极其敏感又想保留Soft-NMS的涨点效果可以只在离线评测时用Soft-NMS线上用经典NMS或者用蒸馏的方式把Soft-NMS的检测结果当作训练时的伪标记让网络自己学会更“干净”的输出。这个属于工程上的妥协思路我实测下来还算可行。6.5 一个排查NMS问题的标准流程我把自己排查NMS相关问题的思路整理成了一套流程先打印NMS输入输出的框数量变化确认是否执行了抑制然后随机选一张图可视化NMS前和NMS后的框观察是误杀还是漏删再用单元测试固定几组框手算IoU和NMS结果对照代码实现输出最后检查阈值参数和坐标格式。大多数NMS问题走完这套流程都能定位到具体环节。7. 关于NMS边界情况的几个实战提醒NMS虽然流程简单但边界情况处理不好很容易出bug。比如空检测结果模型在图上什么都没检测到时后处理函数必须能返回空数组而不是报索引错误。再比如只有一个候选框时NMS循环只执行一次就结束不能因为有边界条件就写出特殊逻辑增加出错可能。另外当两个框完全重合时IoU等于1这一定是要被抑制的如果算出来是0那你代码里肯定有bug。还有一个不太容易被注意到的点当图像缩放到模型的输入尺寸时坐标是相对于缩放后图像的推理结束后要把坐标映射回原图尺度。很多人只在可视化时做这一步结果NMS用的坐标和评估用的坐标不在一个坐标系里导致精度异常。我在实际项目里还养成了一个习惯把所有后处理相关的参数统一集中在一个配置文件里。score_thr、iou_thr、是否按类别NMS、是否使用Soft-NMS这些参数被零散写在代码各处时排查问题的难度会翻倍。集中的好处是调试时可以快速做参数扫描也方便对比不同版本模型的后处理差异。NMS这个模块单个看起来不起眼但它处在检测算法的最末端是直接影响用户感知的那一步。把它的原理、缺陷和改进思路吃透很多模型调优和部署问题都会迎刃而解。后续有时间我再单独聊聊Soft-NMS和多尺度检测的搭配、以及在Transformer类检测器里NMS的角色变化那些都是另外一批有意思的坑。
返回列表