副标题 / 摘要
NMS(Non-Maximum Suppression)是目标检测后处理的核心步骤。本文用 ACERS 框架拆解 NMS 的原理、流程与工程实践,并提供可运行的 PyTorch 示例。
- 预计阅读时长:14~18 分钟
- 标签:
nms、object-detection、iou - SEO 关键词:NMS, 非极大值抑制, IoU, 目标检测
- 元描述:讲清 NMS 的核心算法、复杂度与工程取舍。
目标读者
- 想理解目标检测后处理的初学者
- 需要调参 IoU 阈值的工程实践者
- 关注推理速度与精度平衡的开发者
背景 / 动机
检测模型通常会输出多个重叠框。
如果不做抑制,会出现“同一目标被重复检测”。
NMS 用最简单的规则实现去重,是工业界的标准方案。
核心概念
- IoU(Intersection over Union):衡量两个框重叠程度。
- score:置信度分数,决定优先保留的框。
- 阈值:IoU 超过阈值则抑制。
A — Algorithm(题目与算法)
用通俗语言说明主题内容
NMS 的逻辑很直观:
- 选出最高分的框。
- 删除与它重叠度过高的框。
- 重复直到没有框。
基础示例(1)
- 两个高度重叠的人脸框,只保留分数更高的一个。
基础示例(2)
- 多个类别的检测结果,先按类别分开再做 NMS(class-wise)。
实践指南 / 步骤
- 对检测框按 score 排序。
- 取最高分框作为保留结果。
- 计算 IoU,过滤高重叠框。
- 重复直到框集合为空。
可运行示例(最小 PyTorch NMS)
import torch
def iou(box, boxes):
x1 = torch.maximum(box[0], boxes[:, 0])
y1 = torch.maximum(box[1], boxes[:, 1])
x2 = torch.minimum(box[2], boxes[:, 2])
y2 = torch.minimum(box[3], boxes[:, 3])
inter = torch.clamp(x2 - x1, min=0) * torch.clamp(y2 - y1, min=0)
area1 = (box[2] - box[0]) * (box[3] - box[1])
area2 = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
union = area1 + area2 - inter
return inter / (union + 1e-6)
def nms(boxes, scores, thresh=0.5):
idx = scores.argsort(descending=True)
keep = []
while idx.numel() > 0:
i = idx[0]
keep.append(i.item())
if idx.numel() == 1:
break
rest = idx[1:]
ious = iou(boxes[i], boxes[rest])
idx = rest[ious <= thresh]
return keep
boxes = torch.tensor([
[0.0, 0.0, 1.0, 1.0],
[0.1, 0.1, 1.1, 1.1],
[2.0, 2.0, 3.0, 3.0],
])
scores = torch.tensor([0.9, 0.8, 0.7])
print(nms(boxes, scores, thresh=0.5))
解释与原理
- NMS 的核心是“先保留最可信框”。
- IoU 阈值越大,保留框越多;越小,抑制越强。
C — Concepts(核心思想)
方法类型
NMS 属于后处理过滤算法,用局部贪心策略去重。
关键公式
IoU:
$ \text{IoU}(A, B) = \frac{\text{area}(A \cap B)}{\text{area}(A \cup B)} $
解释与原理
- IoU 衡量重叠程度。
- 通过阈值控制抑制强度。
E — Engineering(工程应用)
场景 1:目标检测去重
- 背景:同一目标被多个框预测。
- 为什么适用:NMS 快速去重。
- 代码示例(Python):
import torch
boxes = torch.randn(5, 4).abs()
scores = torch.rand(5)
print(scores.argsort(descending=True))
场景 2:多类别检测(class-wise NMS)
- 背景:不同类别框不应互相抑制。
- 为什么适用:按类别分组 NMS。
- 代码示例(Python):
import torch
labels = torch.tensor([0, 0, 1, 1])
for c in labels.unique():
idx = (labels == c).nonzero().flatten()
print(c.item(), idx.tolist())
场景 3:实时检测加速
- 背景:NMS 成为推理瓶颈。
- 为什么适用:减少候选框数量。
- 代码示例(Python):
import torch
scores = torch.rand(1000)
keep = scores > 0.3
print(keep.sum().item())
R — Reflection(反思与深入)
- 时间复杂度:经典 NMS 为
O(N^2)。 - 空间复杂度:主要存储候选框,
O(N)。 - 替代方案:
- Soft-NMS:降低分数而非直接删除。
- DIoU/CIoU-NMS:更精细的抑制策略。
- NMS-Free:直接在模型中建模去重。
- 工程可行性:NMS 简单稳定,是默认首选。
常见问题与注意事项
- 阈值过大 → 重复检测。
- 阈值过小 → 漏检。
- 跨类别是否抑制需明确策略。
最佳实践与建议
- 先用 class-wise NMS 再做全局筛选。
- 在高密度场景考虑 Soft-NMS。
- 用验证集调 IoU 阈值。
S — Summary(总结)
核心收获
- NMS 是目标检测后处理的核心算法。
- IoU 阈值决定去重强度。
- 复杂度高时需要候选筛选或改进算法。
- Soft-NMS 与 NMS-Free 是重要替代方向。
推荐延伸阅读
- Faster R-CNN
- Soft-NMS 论文
- YOLO 系列检测模型
参考与延伸阅读
小结 / 结论
NMS 是目标检测工程化的“最后一公里”。
理解它的取舍,能让检测系统更稳、更快。
行动号召(CTA)
用本文的 NMS 实现替换你的后处理代码,观察精度与速度变化。