一种非极大值抑制(non_max_suppression, nms)的代码实现方式

该博客介绍了非极大值抑制(NMS)在目标检测中的应用,特别是在YOLOv5模型的后处理阶段。首先,详细解释了NMS的工作原理,接着提供了从坐标形式转换、IOU计算到NMS实施的Python代码。代码段展示了如何将YOLOv5模型的输出转换为合适的坐标格式,并进行IOU计算以执行NMS,从而消除重叠的检测框。最后,讨论了如何将检测结果映射回原始图像尺寸,考虑了resize和padding的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 简介

  • 非极大值抑制,non_max_suppression,简称nms,常用于目标检测的后处理,去除多余的检测框。
  • 流程大致是:根据某个类别,按照检测框的置信度从大到小排序,选择置信度最高的检测框记为A,计算A与剩余的检测框(B1、B2、… BN)的iou值,若iou值大于设置的阈值,则将B1、B2、… BN中对应的检测框去掉,如此重复操作。

2. 代码

  • 结合yolov5模型输出,实现nms的代码。
  • 在yolov5中,模型的输出记为pred,(这里pred是模型的直接输出,还没有经过后处理),pred的shape为:(batch_size, num_bbox, 4 + 1 + num_classes),其中batch_size表示输入的图片数量,num_bbox表示检测到的矩形框数量,4表示4个坐标,1表示检测框的置信度,num_classes表示每个类别的分数。
  • pred的检测框坐标格式为:xywh,即检测框的中心坐标以及它的宽高

2.1 坐标形式转换

# coding=utf-8
import numpy as np

def xywh2xyxy(x):
    y = np.copy(x)
    y[..., 0] = x[..., 0] - x[..., 2] / 2  # top left x
    y[..., 1] = x[..., 1] - x[..., 3] / 2  # top left y
    y[..., 2] = x[..., 0] + x[..., 2] / 2  # bottom right x
    y[..., 3] = x[..., 1] + x[..., 3] / 2  # bottom right y
    return y

2.2 iou计算

def cal_iou(det1, det2):
    det1_x1, det1_y1 = det1[..., 0], det1[..., 1]
    det1_x2, det1_y2 = det1[..., 2], det1[..., 3]

    det2_x1, det2_y1 = det2[..., 0], det2[..., 1]
    det2_x2, det2_y2 = det2[..., 2], det2[..., 3]

    x1 = np.maximum(det1_x1, det2_x1)
    y1 = np.maximum(det1_y1, det2_y1)
    x2 = np.minimum(det1_x2, det2_x2)
    y2 = np.minimum(det1_y2, det2_y2)

    area_det1 = (det1_y2 - det1_y1 + 1) * (det1_x2 - det1_x1 + 1)
    area_det2 = (det2_y2 - det2_y1 + 1) * (det2_x2 - det2_x1 + 1)
    inter = np.maximum(0, (y2 - y1 + 1)) * np.maximum(0, (x2 - x1 + 1))

    ious = inter / (area_det1 + area_det2 - inter)
    return ious

2.3 nms

def nms(detections, conf_thres=0.4, nms_thres=0.5):
    outputs = []
    detections = xywh2xyxy(detections)               # 检测框格式转换 xywh -> xyxy
    detections[..., 5:] *= detections[..., 4:5]      # 类别的分数是用obj_conf * cls_conf
    num_classes = detections.shape[2] - 5            # detections shape: (batch_size, num_bbox, 4 + 1 + num_classes)
    candidates = detections[..., 4] > conf_thres     # 用于后续的过滤筛选
    for img_idx, dets in enumerate(detections):
        output = []
        dets = dets[candidates[img_idx]]             # 根据conf_thres过滤
        indexes = dets[..., 4].argsort()[::-1]       # 根据obj_conf大小排序
        dets = dets[indexes]
        classes_dets = dets[..., 5:].argmax(axis=1)  # 每个检测框的类别
        for cls in range(num_classes):               # 按类别循环遍历
            dets_cls = dets[classes_dets == cls]     # shape: (num_bbox, 4 + 1 + num_classes)
            while len(dets_cls):
                det_select = dets_cls[0]
                # det_select shape: (x1, y1, x2, y2, score, label)
                det_select = np.concatenate((det_select[..., :4], [det_select[..., 5:].max(), cls]))
                output.append(det_select)
                dets_cls = dets_cls[1:]
                if len(dets_cls):
                    ious = cal_iou(det_select, dets_cls)
                    indexes = np.where(ious <= nms_thres)[0]
                    dets_cls = dets_cls[indexes]
        outputs.append(output)
    return np.array(outputs)

这里得到的outputs还需要映射回原图像,以下图为例:
在这里插入图片描述

  • 如图所示,原始图像经过resize之后得到红色框,红色框经过padding得到黑色框,绿色框表示检测框在黑色框中的位置(即以黑色框左上角为原点的位置)。
  • 将检测结果映射回原始图像时,要先去除padding的影响,再去除resize的影响,这样就得到检测框在原图中的位置。具体步骤如下:
  1. 检测框先向上移动t个像素,再向左移动p个像素,其中t、p表示在上方向、左方向padding的像素个数,具体操作将x1 = x1 - p, x2 = x2 - p, y1 = y1 - t, y2 = y2 - t; 这样就得到检测框在没有padding操作下的位置(即以红色框左上角为原点的位置);
  2. 去除resize的影响。只要将坐标除以图像resize后与原图像的的比例gain即可,假设原始图像高、宽为ori_h, ori_w,resize之后的宽高为new_h, new_w,则ratio = min(new_h/ori_h, new_w/ori_w), 然后让x1 = x1/ratio, y1 = y1/ratio, x2 = x2/ratio, y2 = y2/ratio,即得到原始图像的位置。
  3. 代码如下:
def scale_coords(output, ori_shape, ratio=1.0, top_pad=0, left_pad=0):
    ori_h, ori_w = ori_shape
    output[:, [0, 2]] -= left_pad
    output[:, [1, 3]] -= top_pad
    output[:, :4] /= ratio
    output[:, [0, 2]] = output[:, [0, 2]].clip(0, ori_w)  # x1, x2
    output[:, [1, 3]] = output[:, [1, 3]].clip(0, ori_h)  # y1, y2

完成。

非极大值抑制(Non-Maximum Suppression,简称 NMS)是一种广泛应用于目标检测领域的关键技术,用于消除冗余的边界框,只保留最可能表示真实物体的那个框。它的核心思想是比较多个重叠的候选框,并通过一定的规则选择其中最有代表性的那个,其余则被抑制。 --- ### 一、非极大值抑制的基本原理 假设我们有一个目标检测算法生成了一组候选框 \( \text{boxes} = [b_1, b_2, ..., b_n] \),以及对应每个框的置信度得分 \( \text{scores} = [s_1, s_2, ..., s_n] \)NMS 的过程通常分为以下几个步骤: 1. **按得分排序** 将所有候选框按照它们的置信度从高到低进行排序。 2. **初始化保留列表** 创建一个空列表 `keep` 来存储最终保留下来的框索引。 3. **逐个检查并过滤** 对于当前最高得分的框 \( b_{max} \),将其加入 `keep` 列表;然后与其他剩下的框逐一计算交并比(IoU),若某个框与 \( b_{max} \) 的 IoU 超过设定阈值,则认为它是冗余的,直接删除。 4. **迭代直到结束** 持续上述操作,直至所有的框都被处理完毕。 通过以上流程,我们可以得到一组相互之间不高度重叠的最佳候选框。 --- ### 二、Matlab 实现代码 下面是一个简单的 Matlab 示例代码来演示如何实现非极大值抑制: ```matlab function keep_indices = non_max_suppression(boxes, scores, iou_threshold) % 输入参数说明: % boxes: 候选框坐标数组,大小为[N,4],每行格式为[x_min,y_min,x_max,y_max] % scores: 每个框对应的置信度分数,大小为[N,1] % iou_threshold: 设定的交并比阈值 % 初始化变量 n_boxes = size(boxes, 1); if n_boxes == 0 keep_indices = []; return; end % 排序:将 candidate box 按照 score 分数降序排列 [~, sorted_indices] = sort(scores, 'descend'); keep_indices = []; % 存储最终保留的box索引 while length(sorted_indices) > 0 max_index = sorted_indices(1); % 当前最高的score对应的box index keep_indices = [keep_indices, max_index]; % 加入保留list rest_indices = sorted_indices(2:end); % 获取剩余未处理的box indices if isempty(rest_indices) break; % 如果已经没有剩下box就退出循环 end % 计算当前最高score box与剩余boxes之间的iou overlaps = compute_iou(boxes(max_index,:), boxes(rest_indices,:)); % 找到所有iou超过threshold的indices suppressed = find(overlaps >= iou_threshold); sorted_indices = setdiff(sorted_indices, rest_indices(suppressed)); % 删除这些冗余box end end % 辅助函数:计算两个bounding-boxes间的IOU function ious = compute_iou(boxA, boxesB) xA = max(boxA(1), boxesB(:,1)); % 左上角x取较大值 yA = max(boxA(2), boxesB(:,2)); % 左上角y取较大值 xB = min(boxA(3), boxesB(:,3)); % 右下角x取较小值 yB = min(boxA(4), boxesB(:,4)); % 右下角y取较小值 interArea = max(0, xB-xA+1).*max(0, yB-yA+1); % intersection面积 area_boxA = (boxA(3)-boxA(1)+1)*(boxA(4)-boxA(2)+1); % A的面积 area_boxesB = (boxesB(:,3)-boxesB(:,1)+1).*(boxesB(:,4)-boxesB(:,2)+1); % B们的面积 unionAreas = area_boxA + area_boxesB - interArea; % 总union面积 ious = interArea ./ unionAreas; % IOUs结果 end ``` 你可以测试这个函数是否能够正常运作。例如构造一些人工数据传进去查看返回的答案是不是合理。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值