非极大值抑制(Non-Maximum Suppression, NMS)算法概述

非极大值抑制(Non-Maximum Suppression, NMS)的核心目标是: 在目标检测模型生成的大量重叠候选框(Bounding Boxes)中,筛选出最有可能代表真实目标且不重叠(或重叠度很低)的框,去除冗余的、重复的检测结果。

为什么需要 NMS?

1.模型会为同一个目标生成多个检测框: 无论是基于锚框(Anchor-based)的方法(如 Faster R-CNN, SSD, YOLOv2-v4),还是基于关键点或中心点(Anchor-free)的方法(如 CornerNet, CenterNet),模型在预测时,同一个目标通常会被多个邻近的预测框以不同的置信度(Confidence Score)检测到。这些框往往高度重叠(IoU 很高)。

2.冗余输出: 如果不对这些重叠框进行处理,最终结果会包含大量指向同一个物体的框,这不仅不直观,也干扰后续处理(如跟踪、行为识别)。

3.追求简洁精准的输出: 理想情况下,我们希望每个真实目标只保留一个置信度最高且定位最准确的检测框。

NMS 的工作原理

NMS 是一个迭代的、贪心式的算法。其核心思想是:“保留局部最大值”。以下是其标准步骤:

1.输入准备:

  • 模型预测的所有候选框集合 B = [b_1, b_2, \dots, b_n]

  • 每个候选框 bᵢ 对应的置信度分数 S = [s_1, s_2, \dots, s_n]

  • 预先设定的两个关键阈值:

    • 置信度阈值 (Confidence Threshold): 过滤掉置信度过低的框(通常在做 NMS 前或同时进行)。例如,只考虑 sᵢ > 0.25 的框。

    • IoU 阈值 (NMS Threshold): 用于判断两个框是否“重叠过多”,应该抑制其中一个。常用值如 0.50.7

2.按置信度排序:将候选框集合 B 按照它们的置信度分数 S 从高到低进行排序。得到一个有序列表 B_{\text{sorted}} = \left[ b_{(1)}, b_{(2)}, \dots, b_{(n)} \right],其中 s_{(1)} \geq s_{(2)} \geq \cdots \geq s_{(n)}

3.初始化结果集:创建一个空集合 D 用于存放最终保留的检测结果(去冗余后的框)。

4.迭代抑制:

  • While B_sorted 不为空:

    1. 选出当前最高分框: 从 B_sorted 中取出置信度最高的框 M = b₍₁₎(即当前列表的第一个框)。

    2. 保留该框: 将 M 移出 B_sorted 并添加到最终结果集 D 中。

    3. 计算重叠度 (IoU): 对于 B_sorted 中剩余的每一个框 bᵢ:计算 M 与 bᵢ 的交并比 IoU(M, bᵢ)

    4. 抑制重叠框: 将 B_sorted 中所有满足 IoU(M, bᵢ) >= NMS Threshold 的框 bᵢ 移除。这些框被认为是与当前最高分框 M 检测到了同一个目标,且置信度比 M 低,因此被抑制(丢弃)。

  • End While

5.输出:

集合 D 中包含的就是经过 NMS 处理后的检测结果,每个目标(在理想情况下)只保留了一个置信度最高的框。

以图来演示(图源见水印):

NMS计算举例

假设有 5 个检测框 (A, B, C, D, E) 及其置信度 (0.9, 0.85, 0.8, 0.7, 0.6)。A 和 B、C、D 的 IoU 都很高(> NMS Thresh),B 和 C 的 IoU 也很高。

  1. 排序: [A(0.9), B(0.85), C(0.8), D(0.7), E(0.6)]

  2. 取最高分 A(0.9) 放入 D。

  3. 计算 A 与剩余框的 IoU: IoU(A, B), IoU(A, C), IoU(A, D) 都 > Thresh。

  4. 移除 B, C, D(因为它们与 A 高度重叠)。

  5. 剩余列表: [E(0.6)]

  6. 取最高分 E(0.6) 放入 D。(假设 E 与 A 的 IoU < Thresh)

  7. 输出 D = [A, E]

代码实现

import numpy as np

def calculate_iou(box1, box2):
    """
    计算两个边界框的交并比(IoU)
    
    参数:
        box1: [x1, y1, x2, y2] 左上角和右下角坐标
        box2: [x1, y1, x2, y2] 左上角和右下角坐标
    
    返回:
        iou: 交并比值 (0.0 ~ 1.0)
    """
    # 计算相交区域的坐标
    x1_inter = max(box1[0], box2[0])
    y1_inter = max(box1[1], box2[1])
    x2_inter = min(box1[2], box2[2])
    y2_inter = min(box1[3], box2[3])
    
    # 计算相交区域面积
    inter_area = max(0, x2_inter - x1_inter) * max(0, y2_inter - y1_inter)
    
    # 计算两个框各自的面积
    box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
    box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
    
    # 计算并集面积
    union_area = box1_area + box2_area - inter_area
    
    # 避免除以零
    iou = inter_area / union_area if union_area > 0 else 0.0
    
    return iou

def non_max_suppression(boxes, scores, iou_threshold=0.5, score_threshold=0.25):
    """
    非极大值抑制(NMS)实现
    
    参数:
        boxes: 边界框列表, 格式为 [[x1, y1, x2, y2], ...]
        scores: 对应的置信度分数列表
        iou_threshold: IoU阈值, 高于此值的框将被抑制
        score_threshold: 置信度阈值, 低于此值的框将被过滤
    
    返回:
        keep_indices: 保留的边界框索引列表
    """
    # 1. 按置信度分数降序排序
    sorted_indices = np.argsort(scores)[::-1]
    
    # 2. 初始化保留列表
    keep_indices = []
    
    while len(sorted_indices) > 0:
        # 3. 选取当前最高分的框
        current_index = sorted_indices[0]
        current_box = boxes[current_index]
        
        # 4. 如果当前框分数低于阈值,直接跳过
        if scores[current_index] < score_threshold:
            sorted_indices = sorted_indices[1:]
            continue
        
        # 5. 保留当前框
        keep_indices.append(current_index)
        
        # 6. 计算当前框与剩余框的IoU
        remaining_indices = sorted_indices[1:]
        suppress_indices = []
        
        for idx in remaining_indices:
            iou = calculate_iou(current_box, boxes[idx])
            
            # 7. 如果IoU高于阈值,则抑制该框
            if iou > iou_threshold:
                suppress_indices.append(idx)
        
        # 8. 从排序列表中移除已被抑制的框
        suppress_mask = np.isin(sorted_indices, suppress_indices, invert=True)
        sorted_indices = sorted_indices[suppress_mask][1:]
    
    return keep_indices

# 测试示例
if __name__ == "__main__":
    # 模拟YOLOv8的输出结果 (格式: [x1, y1, x2, y2, score, class_id])
    # 实际应用中,这些数据来自检测头的输出
    boxes = np.array([
        [100, 100, 220, 220],   # 框1
        [120, 120, 240, 240],   # 框2 (与框1高度重叠)
        [300, 300, 400, 400],   # 框3 (独立框)
        [110, 110, 230, 230],   # 框4 (与框1重叠)
        [305, 305, 405, 405]    # 框5 (与框3重叠)
    ])
    
    scores = np.array([0.95, 0.90, 0.85, 0.80, 0.75])
    class_ids = np.array([0, 0, 1, 0, 1])
    
    # 应用NMS
    keep_indices = non_max_suppression(
        boxes, 
        scores, 
        iou_threshold=0.5,
        score_threshold=0.25
    )
    
    # 打印结果
    print("保留的边界框索引:", keep_indices)
    print("保留的边界框:")
    for idx in keep_indices:
        print(f"  框 {idx}: {boxes[idx]}, 分数: {scores[idx]:.2f}, 类别: {class_ids[idx]}")import numpy as np

IoU (Intersection over Union) 计算

IoU 是衡量两个边界框重叠程度的指标,定义为两个框交集面积与并集面积的比值。

\text{IoU} = \frac{\text{Area of Intersection}}{\text{Area of Union}} = \frac{\text{Area of Intersection}}{\text{Area}(\text{Box}_1) + \text{Area}(\text{Box}_2) - \text{Area of Intersection}}

  • IoU = 1: 两个框完全重合。

  • IoU = 0: 两个框没有重叠。

  • NMS Threshold 决定了多大程度的重叠被认为是“检测同一个目标”。阈值越高,越不容易抑制(保留的框可能越多,冗余度可能越高);阈值越低,抑制越严格(保留的框更少,但可能抑制掉靠得很近的真实不同目标)。

NMS 的局限性

1.密集/小目标漏检: 当多个目标靠得非常近(如人群、鸟群、叶片上的密集缺陷)时,它们的预测框 IoU 很容易超过阈值。NMS 会错误地只保留其中置信度最高的一个目标框,抑制掉其他真实目标(即使它们的置信度也较高),导致漏检。这是 NMS 在工业缺陷检测(如叶片密集小缺陷)中的主要痛点

2.阈值依赖: NMS 的效果高度依赖于预设的 IoU 阈值。选择不当会导致过抑制(漏检)或欠抑制(冗余)。

3.置信度不一定代表定位精度: 置信度最高的框不一定定位最准。NMS 只依赖置信度排序,可能保留了一个定位不准但置信度高的框,而抑制了定位更准但置信度稍低的框。

4.计算效率: 标准 NMS 是 O(n²) 的复杂度(需要计算每对框的 IoU)。当候选框数量 n 很大时(如密集场景或 Anchor-based 方法),计算开销显著。

NMS 的改进算法

为了克服标准 NMS 的缺点,研究者提出了多种改进方案:

  1. Soft-NMS:

    • 核心思想: 不直接删除与高分框 IoU 高的低分框,而是根据 IoU 值连续地降低这些低分框的置信度。

    • 实现方式: 有两种主要方法:

      • 线性加权: sᵢ = sᵢ * (1 - IoU(M, bᵢ)),如果 IoU(M, bᵢ) > thresh

      • 高斯加权: sᵢ = sᵢ * exp(-(IoU(M, bᵢ)² / σ)σ 是控制衰减速度的参数。

    • 优点: 缓解了密集目标漏检问题。被高分框部分重叠的低分框不会被立即丢弃,如果它与其他高分框重叠度不高,降低后的置信度仍可能高于阈值而被保留。

    • 缺点: 引入了额外的超参数(σ),计算量略有增加。

  2. IoU-Guided NMS:

    • 核心思想: 认识到置信度 (s) 主要反映分类置信度,不一定反映定位精度(预测框与真实框的贴合度)。引入一个额外的 预测 IoU 值 (pIoU) 作为框定位质量的估计。

    • 排序依据: 不再仅根据分类置信度 s 排序,而是根据 s * pIoU 或者直接根据 pIoU 排序。

    • 优点: 优先保留定位更准的框,抑制定位不准但分类置信度可能较高的框,提高最终框的定位精度。在 NMS 步骤中使用 pIoU 计算抑制权重(如 Soft-NMS 中用 pIoU 代替 IoU)。

    • 缺点: 需要模型额外预测 pIoU

  3. Weighted NMS:

    • 核心思想: 不直接删除被抑制的框,而是将与高分框 M 高度重叠 (IoU > thresh) 的框 bᵢ 的坐标和置信度,以 IoU 或 sᵢ 为权重,加权融合到框 M 上。

    • 优点: 最终保留的框 M 的坐标和置信度融合了周围重叠框的信息,可能更稳定、更准确。

    • 缺点: 计算更复杂,融合后的框不一定更准,尤其在被融合框定位都很差时。

  4. Adaptive NMS:

    • 核心思想: 认识到在密集区域需要更宽松的抑制(更高的 IoU 阈值)以避免漏检,在稀疏区域需要更严格的抑制(更低的 IoU 阈值)以去除冗余。为每个目标预测一个自适应的 NMS 阈值

    • 实现: 模型额外预测一个密度值或自适应阈值 tᵢ。NMS 时,对框 bᵢ 使用其对应的 tᵢ 作为抑制阈值(或基于 tᵢ 动态调整标准阈值)。

    • 优点: 能更好地处理密度变化大的场景。

    • 缺点: 需要模型额外预测密度/阈值。

  5. DIoU-NMS / CIoU-NMS:

    • 核心思想: 标准 NMS 只用 IoU,忽略了框中心点距离和长宽比。使用 DIoU (Distance-IoU) 或 CIoU (Complete-IoU) 代替标准 IoU 作为抑制的判断依据。

    • DIoU: DIoU = IoU - R_diou,其中 R_diou 是归一化的中心点距离惩罚项。DIoU 越小表示框中心点越远。

    • CIoU: CIoU = IoU - R_diou - R_ciou,在 DIoU 基础上增加了长宽比一致性惩罚项 R_ciou

    • 优点: 当两个框中心点距离远时,即使 IoU 较高(如两个框是包含关系),DIoU/CIoU 也会较低,不容易被错误抑制。能更准确地反映两个框的差异。

    • 缺点: 计算比标准 IoU 稍复杂。

NMS 在端到端检测器 (如 DETR) 中的角色

  • DETR 的核心创新之一就是摒弃了 NMS。 它通过 Transformer 的全局推理能力 和 二分图匹配损失 (Bipartite Matching Loss),在训练时强制模型学习为每个目标生成唯一的预测(通过对象查询),并在推理时直接输出固定数量的、理论上不重叠的预测结果。理论上不再需要 NMS 后处理。

  • 优势: 避免了 NMS 固有的超参数敏感性和密集目标漏检问题。

  • 现状: 虽然 DETR 宣称无需 NMS,但一些后续的改进工作(如 Conditional DETR, DAB-DETR)发现,在推理时对输出应用一个轻量级的 NMS(使用非常宽松的阈值,如 0.8)有时能过滤掉少量极端重叠的冗余预测或低质量预测,带来轻微的性能提升。但这并非其架构必需。

总结

  • NMS 是什么? 一种用于去除目标检测结果中冗余框的后处理算法。

  • 核心原理: 按置信度排序,保留最高分框,抑制与其高度重叠(IoU > Thresh)的低分框。

  • 为什么重要? 模型对单目标会产生多个重叠预测框,NMS 是获得简洁、无冗余结果的关键步骤。

  • 关键参数: IoU 阈值 (NMS Threshold)。

  • 主要缺点: 密集小目标漏检、阈值依赖、抑制定位更准的低分框。

  • 改进方向: Soft-NMS (软化抑制)、IoU-Guided NMS (定位质量感知)、Weighted NMS (融合信息)、Adaptive NMS (动态阈值)、DIoU/CIoU-NMS (更优重叠度量)。

  • 与 DETR 的关系: DETR 通过端到端设计和二分匹配损失,理论上避免了 NMS 的需求,代表了该问题的一种不同解决思路。

理解 NMS 及其变体对于深入掌握目标检测模型的原理、调优模型性能以及处理实际应用中的挑战(如密集缺陷检测)至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北京地铁1号线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值