温故而知新,可以为师矣!
一、通俗易懂理解NMS
在YOLO系列目标检测模型中,NMS(Non-Maximum Suppression,非极大值抑制)是后处理阶段最核心的算法。它的任务是对模型预测出的成百上千个候选框中,筛选出最准确、最不重复的检测结果。
1. 置信度阈值 vs. IOU阈值
置信度阈值(Confidence Threshold)和IoU阈值(IoU Threshold),在NMS中扮演着完全不同的角色,一个负责“初筛”,一个负责“精选”。
这两个阈值虽然都在NMS过程中发挥作用,但目的和影响完全不同:
| 对比维度 | 置信度阈值 (Confidence Threshold) | IoU阈值 (IoU Threshold) |
|---|---|---|
| 作用阶段 | 预处理阶段 | 核心抑制阶段 |
| 筛选对象 | 所有预测框 | 与被选中的最高分框进行比较的框 |
| 判断依据 | 框自身的置信度得分 | 框与最高分框的重叠程度(IoU) |
| 核心目的 | 过滤低质量噪声,减少计算量 | 剔除重复检测,保留唯一最优框 |
| 数值影响 | 设得太高:漏检(错过真正的物体) 设得太低:保留大量无效框 | 设得太高(如0.9):易产生重复检测(抑制不够) 设得太低(如0.3):易误删重叠但不同的物体(如两人紧挨) |
一句话总结:置信度阈值是为了去掉“不像是物体”的框,IoU阈值是为了去掉“同一个物体”的冗余框。
2. NMS算法流程
假设模型对一张图片输出了N个预测框,每个框包含:(x, y, w, h, confidence, class_scores)。
流程详解:
- 排序:将所有预测框按照其**置信度(Confidence)**从高到低排序。置信度越高,说明模型对这个框越有信心。
- 置信度阈值初筛(关键点1):剔除掉置信度低于设定值的框。这相当于一个“海选”环节,快速过滤掉大量明显错误的、低质量的预测。
- 挑选最高分框:从剩余的框中,选出置信度最高的那个,将其作为“确定检测结果”,放入最终输出列表。
- IoU阈值去重(关键点2):计算这个最高分框与所有其他剩余框的IoU(交并比)。剔除 IoU > iou_threshold 的框 (即与最高分框重叠严重的框),保留 IoU <= iou_threshold 的框 (即与最高分框重叠不大的框)。
- 循环迭代:重复步骤3和步骤4,直到所有框要么被选入输出列表,要么被剔除。
3. NMS 代码示例
下面是一个简化但完整的NMS实现,清晰展示两个阈值的作用:
importnumpyasnpdefnms(boxes,scores,conf_threshold=0.5,iou_threshold=0.4):""" 非极大值抑制 (NMS) 简化实现 参数: boxes: 预测框列表, 格式为 [x1, y1, x2, y2] scores: 每个框对应的置信度得分 conf_threshold: 置信度阈值, 低于此值的框被过滤 iou_threshold: IoU 阈值, 高于此值的冗余框被抑制 返回: 保留的框索引列表 """# --- 步骤1: 置信度阈值初筛 ---# 只保留置信度 >= conf_threshold 的框valid_indices=np.where(scores>=conf_threshold)[0]boxes=boxes[valid_indices]scores=scores[valid_indices]# 如果所有框都被过滤掉,直接返回空iflen(boxes)==0:return[]# --- 步骤2: 按置信度降序排序 ---order=np.argsort(scores)[::-1]# 从高到低排序keep=[]# 最终保留的框索引# --- 步骤3: 循环抑制 ---whilelen(order)>0:# 取出当前置信度最高的框idx=order[0]keep.append(idx)# 计算最高分框与其他所有框的 IoUx1=np.maximum(boxes[idx,0],boxes[order[1:],0])y1=np.maximum(boxes[idx,1],boxes[order[1:],1])x2=np.minimum(boxes[idx,2],boxes[order[1:],2])y2=np.minimum(boxes[idx,3],boxes[order[1:],3])# 计算交集面积inter_area=np.maximum(0,x2-x1)*np.maximum(0,y2-y1)# 计算并集面积box_area=(boxes[idx,2]-boxes[idx,0])*(boxes[idx,3]-boxes[idx,1])other_area=(boxes[order[1:],2]-boxes[order[1:],0])*\(boxes[order[1:],3]-boxes[order[1:],1])union_area=box_area+other_area-inter_area# 计算 IoUiou=inter_area/union_area# --- 步骤4: 应用 IoU 阈值 ---# 保留 IoU <= iou_threshold 的框 (即与最高分框重叠不大的框)# 剔除 IoU > iou_threshold 的框 (即与最高分框重叠严重的框)order=order[1:][iou<=iou_threshold]returnvalid_indices[keep]# 返回原始数组中的索引# --- 使用示例 ---if__name__=="__main__":# 模拟5个预测框: [x1, y1, x2, y2]boxes=np.array([[10,10,50,50],[12,12,48,48],# 与第一个框高度重叠[30,30,80,80],# 与第一个框有部分重叠,但主体不同[100,100,150,150],[102,102,148,148]# 与第四个框高度重叠])scores=np.array([0.95,0.80,0.70,0.90,0.60])# 设置阈值: 置信度0.5, IoU阈值0.4result_indices=nms(boxes,scores,conf_threshold=0.5,iou_threshold=0.4)print("最终保留的框索引:",result_indices)# 预期输出: 索引0 (0.95), 索引2 (0.70), 索引3 (0.90)# - 索引1 因为与索引0的 IoU 很大 (>0.4) 被抑制# - 索引4 因为与索引3的 IoU 很大 (>0.4) 被抑制4. 调参建议
在不同场景下,对这两个阈值的设定会有不同的侧重:
- 场景1:追求高精度,容忍漏检(如安防监控)
- 置信度阈值设得较高(如0.7~0.9),确保保留下来的框几乎都是正确检测,减少误报。
- IoU阈值设得较高(如0.6~0.7),避免抑制掉真正不同的物体。
- 场景2:追求高召回率,避免漏检(如自动驾驶行人检测)
- 置信度阈值设得较低(如0.3~0.5),宁可多保留一些可疑框,后续再处理。
- IoU阈值设得较低(如0.3~0.5),更激进地抑制重叠框,但要注意不要误删紧紧挨着的不同行人。
- 通用默认值:在YOLOv5/v8等模型中,默认值通常是置信度阈值=0.25(测试时)或0.001(训练时),IoU阈值=0.45。这些默认值在大多数场景下都有不错的表现。