目标检测中的非极大值抑制(NMS)原理与实现解析

一、技术背景

在目标检测任务中,模型通常会对同一目标生成多个重叠的候选框(如锚框或预测框)。非极大值抑制(Non-Maximum Suppression, NMS) 是一种关键的后处理技术,用于去除冗余的检测结果,保留置信度最高且位置最优的边界框。本文将通过一段Python代码解析NMS的核心实现逻辑,并演示其在OpenCV环境中的实际效果。


二、算法核心思想

NMS的核心是通过以下步骤筛选边界框:

  1. 按置信度排序:优先处理置信度最高的预测框。
  2. 计算交并比(IoU):与当前框重叠度高的候选框将被抑制。
  3. 迭代筛选:重复上述过程直至处理完所有候选框。
    在这里插入图片描述

三、代码实现解析

1. 输入数据结构

输入为字典类型 predicts_dict,键为类别名称,值为该类别对应的边界框列表。每个边界框格式为 [x1, y1, x2, y2, score],表示左上角和右下角坐标及置信度。

predicts_dict = {'black1': [[83,54,165,163,0.8], [67,48,118,132,0.5], ...]}

2. 核心函数 non_max_suppress

def non_max_suppress(predicts_dict, threshold):for object_name, bbox in predicts_dict.items():bbox_array = np.array(bbox, dtype=float)# 提取坐标和置信度x1, y1, x2, y2, score = bbox_array[:,0], bbox_array[:,1], bbox_array[:,2], bbox_array[:,3], bbox_array[:,4]# 按置信度降序排序order = score.argsort()[::-1]area = (x2 - x1 + 1) * (y2 - y1 + 1)keep = []  # 保留的索引列表while order.size > 0:i = order[0]  # 当前最高分框keep.append(i)# 计算IoUxx1 = 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:]])inter = np.maximum(0.0, xx2 - xx1 + 1) * np.maximum(0.0, yy2 - yy1 + 1)iou = inter / (area[i] + area[order[1:]] - inter)# 保留IoU低于阈值的框inds = np.where(iou <= threshold)[0]order = order[inds + 1]# 更新筛选后的结果predicts_dict[object_name] = bbox_array[keep].tolist()return predicts_dict

关键步骤说明:

  • 坐标提取与排序:将边界框转换为NumPy数组后,按置信度降序排列。
  • IoU计算:通过最大-最小值法计算交集区域,公式为:
    IoU = Intersection Union − Intersection \text{IoU} = \frac{\text{Intersection}}{\text{Union} - \text{Intersection}} IoU=UnionIntersectionIntersection
  • 动态索引更新:通过 order = order[inds + 1] 跳过被抑制的框,逐步缩小处理范围。
3. 可视化测试代码
  • 绘制原始预测框:在全黑图像上绘制未经过NMS处理的边界框及置信度。
  • NMS处理与对比:调用 non_max_suppress 后,在另一窗口展示抑制后的结果。
# 绘制原始框
for box in bbox:cv2.rectangle(img, (x1, y1), (x2, y2), (255,255,255), 2)
# 处理并绘制NMS后的框
predicts_dict_nms = non_max_suppress(predicts_dict, 0.1)
for box in bbox_nms:cv2.rectangle(img_cp, (x1, y1), (x2, y2), (255,255,255), 2)

四、优化与注意事项

  1. 阈值选择:阈值过小可能导致漏检,过大则冗余框增多(通常目标检测任务中阈值设为0.5)。
  2. 多类别处理:代码支持同时对多个类别独立进行NMS,如输入 black1black2 两个类别的预测结果。
  3. 坐标修正:代码中 +1 的操作是为了避免零宽度/高度,确保面积计算正确。
import cv2
import random
import numpy as npdef non_max_suppress(predicts_dict, threshold):for object_name, bbox in predicts_dict.items():  # 对每一个类别分别进行NMS;一次读取一对键值(即某个类别的所有框)bbox_array = np.array(bbox, dtype=np.float)print(bbox_array)# 下面分别获取框的左上角坐标(x1,y1),右下角坐标(x2,y2)及此框的置信度;这里需要注意的是图像左上角可以看做坐标点(0,0),右下角可以看做坐标点(1,1),也就是说从左往右x值增大,从上往下y值增大x1 = bbox_array[:, 0]y1 = bbox_array[:, 1]x2 = bbox_array[:, 2]y2 = bbox_array[:, 3]scores = bbox_array[:, 4]  # class confidence, ndarrayprint(scores, type(scores))        order = scores.argsort()[::-1]  # argsort函数返回的是数组值从小到大的索引值,[::-1]表示取反。即这里返回的是数组值从大到小的索引值areas = (x2 - x1 + 1) * (y2 - y1 + 1)  # 当前类所有框的面积(python会自动使用广播机制,相当于MATLAB中的.*即两矩阵对应元素相乘);x1=3,x2=5,习惯上计算x方向长度就是x=3、4、5这三个像素,即5-3+1=3,而不是5-3=2,所以需要加1print(areas, type(areas))    keep = []# 按confidence从高到低遍历bbx,移除所有与该矩形框的IoU值大于threshold的矩形框while order.size > 0:i = order[0]keep.append(i)  # 保留当前最大confidence对应的bbx索引# 获取所有与当前bbx的交集对应的左上角和右下角坐标,并计算IoU(注意这里是同时计算一个bbx与其他所有bbx的IoU)xx1 = np.maximum(x1[i], x1[order[1:]])  # 最大置信度的左上角坐标分别与剩余所有的框的左上角坐标进行比较,分别保存较大值;因此这里的xx1的维数应该是当前类的框的个数减1print("xx1:", xx1)yy1 = np.maximum(y1[i], y1[order[1:]])xx2 = np.minimum(x2[i], x2[order[1:]])yy2 = np.minimum(y2[i], y2[order[1:]])inter = np.maximum(0.0, xx2-xx1+1) * np.maximum(0.0, yy2-yy1+1)iou = inter / (areas[i] + areas[order[1:]] - inter)  # 注意这里都是采用广播机制,同时计算了置信度最高的框与其余框的IoUprint(iou, type(iou))print(np.where(iou <= threshold))inds = np.where(iou <= threshold)[0]  # 保留iou小于等于阙值的框的索引值print('inds:', inds)order = order[inds + 1]  # 将order中的第inds+1处的值重新赋值给order;即更新保留下来的索引,加1是因为因为没有计算与自身的IOU,所以索引相差1,需要加上bbox = bbox_array[keep]predicts_dict[object_name] = bbox.tolist()return predicts_dict# 下面在一张全黑图片上测试非极大值抑制的效果
img = np.zeros((600,600), np.uint8)
predicts_dict = {'black1': [[83, 54, 165, 163, 0.8], [67, 48, 118, 132, 0.5], [91, 38, 192, 171, 0.6]]}
# predicts_dict = {'black1': [[83, 54, 165, 163, 0.8], [67, 48, 118, 132, 0.5], [91, 38, 192, 171, 0.6]], 'black2': [[59, 120, 137, 368, 0.12], [54, 154, 148, 382, 0.13]] }
"""
# 在全黑的图像上画出设定的几个框
for object_name, bbox in predicts_dict.items():for box in bbox:x1, y1, x2, y2, score = box[0], box[1], box[2], box[3], box[-1]y_text = int(random.uniform(y1, y2))  # uniform()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。uniform() 方法将随机生成下一个实数,它在 [x, y) 范围内cv2.rectangle(img, (x1, y1), (x2, y2), (255, 255, 255), 2)cv2.putText(img, str(score), (x2 - 30, y_text), 2, 1, (255, 255, 0))cv2.namedWindow("black1_roi")  # 创建一个显示图像的窗口cv2.imshow("black1_roi", img)  # 在窗口中显示图像;注意这里的窗口名字如果不是刚刚创建的窗口的名字则会自动创建一个新的窗口并将图像显示在这个窗口cv2.waitKey(0)  # 如果不添这一句,在IDLE中执行窗口直接无响应。在命令行中执行的话,则是一闪而过。
cv2.destroyAllWindows()  # 最后释放窗口是个好习惯!
"""
# 在全黑图片上画出经过非极大值抑制后的框
img_cp = np.zeros((600,600), np.uint8)
predicts_dict_nms = non_max_suppress(predicts_dict, 0.1)
for object_name, bbox in predicts_dict_nms.items():for box in bbox:x1, y1, x2, y2, score = int(box[0]), int(box[1]), int(box[2]), int(box[3]), box[-1]y_text = int(random.uniform(y1, y2))  # uniform()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。uniform() 方法将随机生成下一个实数,它在 [x, y) 范围内cv2.rectangle(img_cp, (x1, y1), (x2, y2), (255, 255, 255), 2)cv2.putText(img_cp, str(score), (x2 - 30, y_text), 2, 1, (255, 255, 0))cv2.namedWindow("black1_nms")  # 创建一个显示图像的窗口cv2.imshow("black1_nms", img_cp)  # 在窗口中显示图像;注意这里的窗口名字如果不是刚刚创建的窗口的名字则会自动创建一个新的窗口并将图像显示在这个窗口cv2.waitKey(0)  # 如果不添这一句,在IDLE中执行窗口直接无响应。在命令行中执行的话,则是一闪而过。
cv2.destroyAllWindows()  # 最后释放窗口是个好习惯!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/898852.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探秘鸿蒙 HarmonyOS NEXT:鸿蒙存储核心技术全解析

引言 本文章基于HarmonyOS NEXT操作系统&#xff0c;API12以上的版本。 在 ArkTS (ArkUI 框架) 中&#xff0c;用户首选项 (Preferences) 和 持久化存储 (PersistentStorage) 都用于数据存储&#xff0c;但它们有不同的应用场景和特点。 1. 用户首选项 (Preferences) 概念&a…

Leetcode—15. 三数之和(哈希表—基础算法)

题目&#xff1a; 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的…

Linux 启动Jar脚本设置开机自启【超级详细】

Linux 启动Jar脚本&&设置开机自启【超级详细】 概要服务器开机自启服务重启脚本 概要 最近在Linux服务器中部署了一个项目&#xff08;单机版&#xff09;&#xff0c;每次更新服务的时候需要用到好几个命令&#xff0c;停止服务&#xff0c;再重启&#xff0c;并且服…

【第21节】windows sdk编程:网络编程基础

目录 引言&#xff1a;网络编程基础 一、socket介绍(套接字) 1.1 Berkeley Socket套接字 1.2 WinSocket套接字 1.3 WSAtartup函数 1.4 socket函数 1.5 字节序转换 1.6 绑定套接字 1.7 监听 1.8 连接 1.9 接收数据 1.10 发送数据 1.11 关闭套接字 二、UDP连接流程…

QT 图表(拆线图,栏状图,饼状图 ,动态图表)

效果 折线图 // 创建折线数据系列// 创建折线系列QLineSeries *series new QLineSeries;// series->append(0, 6);// series->append(2, 4);// series->append(3, 8);// 创建图表并添加系列QChart *chart new QChart;chart->addSeries(series);chart->setTit…

vector和list的区别是什么

vector 和 list 都是C 标准模板库&#xff08;STL&#xff09;中的容器&#xff0c;它们的区别如下&#xff1a; 内存结构 - vector &#xff1a;是连续的内存空间&#xff0c;就像数组一样&#xff0c;元素在内存中依次排列。 - list &#xff1a;是由节点组成的双向链表…

【AI】【AIGC】降低AIGC检测率:技术、挑战与应对策略

引言 随着生成式人工智能&#xff08;AIGC&#xff09;技术的迅速发展&#xff0c;越来越多的内容开始由人工智能生成。AIGC技术的应用非常广泛&#xff0c;包括文本生成、图像生成、音频生成等。然而&#xff0c;随着这些技术的普及&#xff0c;如何有效识别并检测AIGC生成的…

vue3 ts 请求封装后端接口

一 首页-广告区域-小程序 首页-广告区域-小程序 GET/home/banner1.1 请求封装 首页-广告区域 home.ts export const getHomeBannerApi (distributionSite 1) > {return http<BannerItem[]>({method: GET,url: /home/banner,data: {distributionSite,},}) }函数定…

响应式CMS架构优化SEO与用户体验

内容概要 在数字化内容生态中&#xff0c;响应式CMS架构已成为平衡搜索引擎可见性与终端用户体验的核心载体。该系统通过多终端适配技术&#xff0c;确保PC、移动端及平板等设备的内容渲染一致性&#xff0c;直接降低页面跳出率并延长用户停留时长。与此同时&#xff0c;智能S…

算法基础篇(1)(蓝桥杯常考点)

算法基础篇 前言 算法内容还有搜索&#xff0c;数据结构&#xff08;进阶&#xff09;&#xff0c;动态规划和图论 数学那个的话大家也知道比较难&#xff0c;放在最后讲 这期包含的内容可以看目录 模拟那个算法的话就是题说什么写什么&#xff0c;就不再分入目录中了 注意事…

MyBatis一级缓存和二级缓存

介绍 在开发基于 MyBatis 的应用时&#xff0c;缓存是提升性能的关键因素之一。MyBatis 提供了一级缓存和二级缓存&#xff0c;合理使用它们可以显著减少数据库的访问次数&#xff0c;提高系统的响应速度和吞吐量。本文将深入探讨 MyBatis 一级缓存和二级缓存的工作原理、使用…

C++核心语法快速整理

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文主要为学过多门语言玩家快速入门C 没有基础的就放弃吧。 全部都是精华&#xff0c;看完能直接上手改别人的项目。 输出内容 std::代表了这里的cout使用的标准库&#xff0c;避免不同库中的相同命名导致混乱 …

如何让自动驾驶汽车“看清”世界?坐标映射与数据融合概述

在自动驾驶领域,多传感器融合技术是实现车辆环境感知和决策控制的关键。其中,坐标系映射和对应是多传感器融合的重要环节,它涉及到不同传感器数据在统一坐标系下的转换和匹配,以实现对车辆周围环境的准确感知。本文将介绍多传感器融合中坐标系映射和对应的数学基础和实际应…

Unity Shader 的编程流程和结构

Unity Shader 的编程流程和结构 Unity Shader 的编程主要由以下三个核心部分组成&#xff1a;Properties&#xff08;属性&#xff09;、SubShader&#xff08;子着色器&#xff09; 和 Fallback&#xff08;回退&#xff09;。下面是它们的具体作用和结构&#xff1a; 1. Pr…

第十四天- 排序

一、排序的基本概念 排序是计算机科学中一项重要的操作&#xff0c;它将一组数据元素按照特定的顺序&#xff08;如升序或降序&#xff09;重新排列。排序算法的性能通常通过时间复杂度和空间复杂度来衡量。在 Python 中&#xff0c;有内置的排序函数&#xff0c;同时也可以手…

移除idea External Liraries 中maven依赖包

问题背景 扩展包里面不停的出现已经在POM文件注释的包&#xff0c;其实是没有查询到根源位置。 在IDEA插件中搜索Maven Helper 点击pom.xml文件 会出现扩展插件 定位之后在pom中添加exclusions&#xff0c;如下代码 <dependency><groupId>com.disney.eva.framewo…

AI革命!蓝耘携手海螺AI视频,打造智能化视频新纪元

AI革命&#xff01;蓝耘携手海螺AI视频&#xff0c;打造智能化视频新纪元 前言 在这个信息爆炸的时代&#xff0c;视频已经成为我们获取信息、学习新知识的重要方式。而随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;AI与视频内容的结合为我们带来了全新的…

dify1.1.1安装

1、 按照GitHub上操作 下载源码&#xff0c;没有安装git的&#xff0c;可以下载成zip包&#xff0c; unzip 解压 git clone https://github.com/langgenius/dify.git cd dify cd docker cp .env.example .env2、启动前 &#xff0c;先改下 docker-compose.yaml&#xff0c;…

ElasticSearch 可观测性最佳实践

ElasticSearch 概述 ElasticSearch 是一个开源的高扩展的分布式全文检索引擎&#xff0c;它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理 PB 级别&#xff08;大数据时代&#xff09;的数据。ES 也使用 Java 开…

Excel处理控件Spire.XLS系列教程:C# 在 Excel 中添加或删除单元格边框

单元格边框是指在单元格或单元格区域周围添加的线条。它们可用于不同的目的&#xff0c;如分隔工作表中的部分、吸引读者注意重要的单元格或使工作表看起来更美观。本文将介绍如何使用 Spire.XLS for .NET 在 C# 中添加或删除 Excel 单元格边框。 安装 Spire.XLS for .NET E-…