浅易理解:非极大抑制NMS

什么是非极大抑制NMS

非极大值抑制(Non-Maximum Suppression,简称NMS)是一种在计算机视觉和图像处理领域中广泛使用的后处理技术,特别是在目标检测任务中。它的主要目的是解决目标检测过程中出现的重复检测问题,即对于同一个物体,算法可能会预测出多个重叠或相似的边界框(bounding boxes)。

在目标检测算法得出一系列候选边界框及其对应的类别得分(confidence score)之后,NMS过程如下:

  1. 排序:首先根据每个边界框的得分进行降序排序,选取得分最高的边界框作为保留的对象。

  2. 抑制:对于排序后的边界框列表,对每一个框i,检查其与得分低于它的所有其他框j之间的重叠程度。通常使用交并比(Intersection over Union,IoU)来量化两个框的重叠面积占它们并集面积的比例。

  3. 剔除:如果框i与某个框j的IoU超过预设的阈值(比如0.5),则认为框j是冗余的,将其剔除(抑制)。

  4. 迭代:重复上述步骤,直到处理完所有候选边界框,最终剩下的边界框集合即是经过非极大值抑制后的结果,这些框代表了各自区域内最有可能对应真实物体的检测结果。

总之,非极大值抑制确保了对同一物体只有一个最精确的边界框被保留下来,从而减少误报和重复检测,提高了目标检测的精度。

在目标检测中,NMS的目的就是要去除冗余的检测框,保留最好的一个

非极大抑制的概念只需要看这两幅图就知道了:

下图是经过非极大抑制的。

下图是未经过非极大抑制的。

NMS的原理是对于预测框的集合S及其对应的置信度score(这里的置信度就是softmax得出的概率值,它的含义是多大的把握预测正确,也就是有多大的把握确定检测框中存在真正的目标),选择具有最大score的检测框,记为M,将其从集合S中移除并加入到最终的检测结果集合中.并且将集合S中剩余检测框中与检测框M的IoU大于阈值的框从集合S中移除.重复这个过程,直到集合S为空。

使用流程如下图所示:

首先是检测出一系列的检测框

将检测框按照类别进行分类

对同一类别的检测框应用NMS获取最终的检测结果

代码:

NMS 算法一般是为了去掉模型预测后的多余框,其一般设有一个nms_threshold=0.5,具体的实现思路如下:

  • 选取这类box中scores最大的哪一个,它的index记为 i ,并保留它;
  • 计算 boxes[i] 与其余的 boxes 的 IOU 值;
  • 如果其 IOU>0.5 了,那么就舍弃这个box(由于可能这两个box表示同一目标,所以保留分数高的哪一个);
  • 从最后剩余的boxes中,再找出最大scores的哪一个,如此循环往复。
def nms(boxes, scores, threshold=0.5, top_k=200):'''Args:boxes: 预测出的box, shape[M,4]scores: 预测出的置信度,shape[M]threshold: 阈值top_k: 要考虑的box的最大个数Return:keep: nms筛选后的box的新的index数组count: 保留下来box的个数'''keep = scores.new(scores.size(0)).zero_().long()x1 = boxes[:, 0]y1 = boxes[:, 1]x2 = boxes[:, 2]y2 = boxes[:, 3]area = (x2-x1)*(y2-y1)  # 面积,shape[M]_, idx = scores.sort(0, descending=True) # 降序排列scores的值大小# 取前top_k个进行nmsidx = idx[:top_k]count = 0while idx.numel():# 记录最大score值的indexi = idx[0]# 保存到keep中keep[count] = i# keep 的序号count += 1if idx.size(0) == 1: # 保留框只剩一个breakidx = idx[1:] # 移除已经保存的index# 计算boxes[i]和其他boxes之间的iouxx1 = x1[idx].clamp(min=x1[i])yy1 = y1[idx].clamp(min=y1[i])xx2 = x2[idx].clamp(max=x2[i])yy2 = y2[idx].clamp(max=y2[i])w = (xx2 - xx1).clamp(min=0)h = (yy2 - yy1).clamp(min=0)# 交集的面积inter = w * h  # shape[M-1]iou = inter / (area[i] + area[idx] - inter)# iou满足条件的idxidx = idx[iou.le(threshold)] # Shape[M-1]return keep, count

其中:

  • torch.numel(): 表示一个张量总元素的个数
  • torch.clamp(min, max): 设置上下限
  • tensor.le(x): 返回tensor<=x的判断 

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

通过一个例子看些NMS的使用方法,假设定位车辆,算法就找出了一系列的矩形框,我们需要判别哪些矩形框是没用的,需要使用NMS的方法来实现。

假设集合S中有A、B、C、D、E 5个候选框,每个框旁边的数字是它的置信度,我们设定NMS的iou阈值是0.5,接下来进行迭代计算:

第一轮:因为B是得分最高的(即B的置信度最高),在集合S的其余候选框中,如果与B的IoU>0.5会被删除。A,C,D,E中现在分别与B计算IoU,DE结果>0.5,剔除DE(说明BDE检测的是同一个目标,保留置信度最大的候选框;而AC可能检测的是另一个目标),B作为一个预测结果,从集合S中移除,并放入最终的检测结果集合中。此时新的集合S中只剩下候选框A,C

第二轮:在新的集合S中,A的置信度得分最高,将集合S中剩下的候选框分别与A计算IoU,因为A与C的iou>0.5,所以剔除C,A作为另外一个预测结果从集合S中移除,并放入最终的检测结果集合中,此时集合S为空,所以循环结束。

最终结果为在这个5个中检测出了两个目标为A和B。

单类别的NMS的实现方法如下所示:

import numpy as np
def nms(bboxes, confidence_score, threshold):"""非极大抑制过程:param bboxes: 同类别候选框坐标:param confidence: 同类别候选框分数(即置信度):param threshold: iou阈值:return:"""# 1、没有传入候选框则返回空列表if len(bboxes) == 0:return [], []#强制转换为numpy类型的数组,这样才可以进行切片等numpy所支持的操作bboxes = np.array(bboxes)score = np.array(confidence_score)# 取出所有候选框的左上角坐标和右下角坐标x1 = bboxes[:, 0]y1 = bboxes[:, 1]x2 = bboxes[:, 2]y2 = bboxes[:, 3]# 2、对候选框进行NMS筛选# 返回的框坐标和分数picked_boxes = []picked_score = []# 对置信度进行排序, 获取排序后的下标序号, argsort默认从小到大排序order = np.argsort(score)#计算所有候选框的面积 areas = (x2 - x1) * (y2 - y1)while order.size > 0:# 将当前置信度最大的候选框的索引,加入返回值列表中,因为是从小到大排序,所有最后一个值最大,即 order[-1]表示最后一个元素index = order[-1]#将置信度最大的候选框及其置信度值加入返回值列表中picked_boxes.append(bboxes[index])picked_score.append(score[index])# 获取当前置信度最大的候选框与其他任意候选框的相交面积,这里的order[:-1]表示除了最后一个元素之外的所有元素,np.max和np.maximum的实现功能是不同的#np.maximum的用法:np.maximum([2,4,7],[3,1,5])输出的结果是array([3, 4, 7]);np.maximum([2],[3,1,5])的输出结果是array([3, 2, 5])#np.max的用法:np.max([2,4,7])输出结果是7x11 = np.maximum(x1[index], x1[order[:-1]])y11 = np.maximum(y1[index], y1[order[:-1]])x22 = np.minimum(x2[index], x2[order[:-1]])y22 = np.minimum(y2[index], y2[order[:-1]])# 计算相交的面积,不重叠时面积设为0w = np.maximum(0.0, x22 - x11)h = np.maximum(0.0, y22 - y11)inter_area = w * h# 计算交并比iou = inter_area / (areas[index] + areas[order[:-1]] - inter_area)# 获取IoU小于阈值的候选框的索引keep_boxes = np.where(iou < threshold)#更新order,以便保留IoU小于阈值的框,order = order[keep_boxes]# 返回NMS后的框及分类结果   return picked_boxes, picked_score

假设有检测结果如下:当阈值threshold设置的越大,则保留越多的候选框

  • 当threshold取0.3时:

bounding = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.65, 0.8]
threshold = 0.3
picked_boxes, picked_score = nms(bounding, confidence_score, threshold)
print('阈值threshold为:', threshold)
print('NMS后得到的bbox是:', picked_boxes)
print('NMS后得到的bbox的confidences是:', picked_score)

返回结果:

阈值threshold为: 0.3
NMS后得到的bbox是: [array([187,  82, 337, 317])]
NMS后得到的bbox的confidences是: [0.9]

当threshold取0.5时:

bounding = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.65, 0.8]
threshold = 0.5
picked_boxes, picked_score = nms(bounding, confidence_score, threshold)
print('阈值threshold为:', threshold)
print('NMS后得到的bbox是:', picked_boxes)
print('NMS后得到的bbox的confidences是:', picked_score)

返回结果:

阈值threshold为: 0.5
NMS后得到的bbox是: [array([187,  82, 337, 317]), array([246, 121, 368, 304])]
NMS后得到的bbox的confidences是: [0.9, 0.8]

上述所讲的NMS方法都是先将检测框按照类别进行分类,然后对对同一类别的检测框应用NMS。但是在实际的任务中,如果所预测的类别很多时,那么这种效率非常低。所以有些时候我们会使用新的方法进行NMS:它的大致思想是先将不同类别的预测框在坐标位置上尽可能的区分开,然后就可以一次性对所有预测框进行NMS(此时不用先进行分类,然后分别对每一个类别依次做NMS)

,比如下图所示,蓝色方框的类别索引是1,黄色方框的类别索引是2,这些不同类别的预测框在位置上靠的很近,此时如果直接对所有类别同时做NMS,效果就很差。所以我们会设法将蓝色方框和黄色方框分离开,本例的方法是首先找到所有方框中坐标值最大的数值max_value,比如这里是81,

然后使用类别索引 indxs与val_value相乘,得到不同类别框的偏移量offsets,它的公式是:offsets=indxs*max_value

比如对于类别索引为1的方框,它的偏移量是offsets=indxs*max_value=1*81=81,对于类别索引为1的方框,它的偏移量是offsets=indxs*max_value=2*81=162

计算完每个类别的偏移量后,我们就得到新的预测框的坐标以及其对于的新位置,如下所示。然后就可以一次性对所有预测框进行NMS(此时不用先进行分类,然后分别对每一个类别依次做NMS)

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

概括非极大抑制的功能就是:

筛选出一定区域内属于同一种类得分最大的框。

1、非极大抑制NMS的实现过程
本博文实现的是多分类的非极大抑制:
输入shape为[ batch_size, all_anchors, 5+num_classes ]

第一个维度是图片的数量。
第二个维度是所有的预测框。
第三个维度是所有的预测框的预测结果。

非极大抑制的执行过程如下所示:
1、对所有图片进行循环。
2、找出该图片中得分大于门限函数的框。在进行重合框筛选前就进行得分的筛选可以大幅度减少框的数量。
3、判断第2步中获得的框的种类与得分。取出预测结果中框的位置与之进行堆叠。此时最后一维度里面的内容由5+num_classes变成了4+1+2,四个参数代表框的位置,一个参数代表预测框是否包含物体,两个参数分别代表种类的置信度与种类。
4、对种类进行循环,非极大抑制的作用是筛选出一定区域内属于同一种类得分最大的框,对种类进行循环可以帮助我们对每一个类分别进行非极大抑制。
5、根据得分对该种类进行从大到小排序。
6、每次取出得分最大的框,计算其与其它所有预测框的重合程度,重合程度过大的则剔除。

视频中实现的代码是numpy形式,而且库比较久远。这里改成pytorch的形式,且适应当前的库。

实现代码如下:

def bbox_iou(self, box1, box2, x1y1x2y2=True):"""计算IOU"""if not x1y1x2y2:b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2else:b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3]b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3]inter_rect_x1 = torch.max(b1_x1, b2_x1)inter_rect_y1 = torch.max(b1_y1, b2_y1)inter_rect_x2 = torch.min(b1_x2, b2_x2)inter_rect_y2 = torch.min(b1_y2, b2_y2)inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1, min=0) * \torch.clamp(inter_rect_y2 - inter_rect_y1, min=0)b1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)b2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)iou = inter_area / torch.clamp(b1_area + b2_area - inter_area, min = 1e-6)return ioudef non_max_suppression(self, prediction, num_classes, input_shape, image_shape, letterbox_image, conf_thres=0.5, nms_thres=0.4):#----------------------------------------------------------##   将预测结果的格式转换成左上角右下角的格式。#   prediction  [batch_size, num_anchors, 85]#----------------------------------------------------------#box_corner          = prediction.new(prediction.shape)box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2prediction[:, :, :4] = box_corner[:, :, :4]output = [None for _ in range(len(prediction))]for i, image_pred in enumerate(prediction):#----------------------------------------------------------##   对种类预测部分取max。#   class_conf  [num_anchors, 1]    种类置信度#   class_pred  [num_anchors, 1]    种类#----------------------------------------------------------#class_conf, class_pred = torch.max(image_pred[:, 5:5 + num_classes], 1, keepdim=True)#----------------------------------------------------------##   利用置信度进行第一轮筛选#----------------------------------------------------------#conf_mask = (image_pred[:, 4] * class_conf[:, 0] >= conf_thres).squeeze()#----------------------------------------------------------##   根据置信度进行预测结果的筛选#----------------------------------------------------------#image_pred = image_pred[conf_mask]class_conf = class_conf[conf_mask]class_pred = class_pred[conf_mask]if not image_pred.size(0):continue#-------------------------------------------------------------------------##   detections  [num_anchors, 7]#   7的内容为:x1, y1, x2, y2, obj_conf, class_conf, class_pred#-------------------------------------------------------------------------#detections = torch.cat((image_pred[:, :5], class_conf.float(), class_pred.float()), 1)#------------------------------------------##   获得预测结果中包含的所有种类#------------------------------------------#unique_labels = detections[:, -1].cpu().unique()if prediction.is_cuda:unique_labels = unique_labels.cuda()detections = detections.cuda()for c in unique_labels:#------------------------------------------##   获得某一类得分筛选后全部的预测结果#------------------------------------------#detections_class = detections[detections[:, -1] == c]# #------------------------------------------## #   使用官方自带的非极大抑制会速度更快一些!# #------------------------------------------## keep = nms(#     detections_class[:, :4],#     detections_class[:, 4] * detections_class[:, 5],#     nms_thres# )# max_detections = detections_class[keep]# 按照存在物体的置信度排序_, conf_sort_index = torch.sort(detections_class[:, 4]*detections_class[:, 5], descending=True)detections_class = detections_class[conf_sort_index]# 进行非极大抑制max_detections = []while detections_class.size(0):# 取出这一类置信度最高的,一步一步往下判断,判断重合程度是否大于nms_thres,如果是则去除掉max_detections.append(detections_class[0].unsqueeze(0))if len(detections_class) == 1:breakious = self.bbox_iou(max_detections[-1], detections_class[1:])detections_class = detections_class[1:][ious < nms_thres]# 堆叠max_detections = torch.cat(max_detections).data# Add max detections to outputsoutput[i] = max_detections if output[i] is None else torch.cat((output[i], max_detections))if output[i] is not None:output[i]           = output[i].cpu().numpy()box_xy, box_wh      = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]output[i][:, :4]    = self.yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)return output

 参考文章:【SSD算法】史上最全代码解析-核心篇 - 知乎

参考文章:睿智的目标检测31——非极大抑制NMS与Soft-NMS-CSDN博客

参考文章:NMS(非极大值抑制)_nms非极大值抑制-CSDN博客

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

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

相关文章

租房网站|基于springboot框架+ Mysql+Java+B/S架构的租房网站设计与实现(可运行源码+数据库+设计文档+部署说明)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 用户功能模块 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参…

三种方式使用纯 CSS 实现星级评分

本文介绍三种使用纯 CSS 实现星级评分的方式。每种都值得细品一番~ 五角星取自 Element Plus 的 svg 资源 <svg xmlns"http://www.w3.org/2000/svg" viewBox"0 0 1024 1024" style""><pathfill"currentColor"d"M283.84 …

分析型数据库的主要使用场景有哪些?

如今数据已经成为了企业和组织的核心资产。如何有效地管理和利用这些数据&#xff0c;成为了决定竞争力的关键。分析型数据库作为数据处理领域的重要工具&#xff0c;为各行各业提供了强大的数据分析和洞察能力。基于分析型数据库&#xff08;Apache Doris &#xff09;构建的现…

【机器学习】机器学习创建算法第2篇:K-近邻算法【附代码文档】

机器学习&#xff08;算法篇&#xff09;完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;机器学习算法课程定位、目标&#xff0c;K-近邻算法&#xff0c;1.1 K-近邻算法简介&#xff0c;1.2 k近邻算法api初步使用定位,目标,学习目标,1 什么是K-近邻算法,…

Trie树(详解+例题)

1、介绍Trie树 Trie树&#xff0c;即字典树&#xff0c;又称单词查找树或键树&#xff0c;是一种树形结构&#xff0c;每个节点保存一个字符&#xff0c;一条路径表示一个字符串。它的优点是&#xff1a;利用字符串的公共前缀来减少查询时间&#xff0c;最大限度地减少无谓的字…

Java代码审计安全篇-反序列化漏洞

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全java代码审计和部分师傅审计思路以及webgoat靶场&#xff0c;记录…

ASP.NET Mvc+FFmpeg+Video实现视频转码

目录 首先&#xff0c;做了视频上传的页面&#xff1a; FFmpeg&#xff1a;视频转码 FFmpegHelper工作类&#xff1a; 后台控制器代码&#xff1a; 前端视图代码&#xff1a; 参考文章&#xff1a; 首先&#xff0c;做了视频上传的页面&#xff1a; 借鉴了这篇文章 ASP.…

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境

【Miniconda】基于conda列出当前环境下所有已创建的虚拟环境 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的…

代理IP速度变慢的原因是什么,要如何解决?

许多用户在使用代理IP时都可能会遇到网络速度变慢的问题&#xff0c;这和我们很多人使用代理IP的初衷背道而驰了&#xff0c;所以&#xff0c;代理IP的网络延迟到底是什么原因造成的&#xff0c;我们又要如何解决这个问题呢&#xff1f;今天就和大家一起来探讨探讨。 一、原因 …

C#,图论与图算法,无向图断开点(Articulation Points)的算法与源代码

1 无向图断开点 如果移除无向连通图中的顶点(以及穿过该顶点的边)会断开该图,则该顶点是一个连接点(或切割顶点Cutting Point)。连接点表示连接网络中的漏洞–单点故障会将网络拆分为两个或多个组件。它们对于设计可靠的网络很有用。 对于断开连接的无向图,连接点是顶点…

SSM SpringBoot vue智能手机参数分析平台

SSM SpringBoot vue智能手机参数分析平台 系统功能 首页 图片轮播 新闻资讯 手机信息 手机百科 登录注册 个人中心 后台管理 登录注册 个人中心 手机百科管理 用户管理 手机对比管理 配置管理 新闻资讯管理 手机信息管理 对比信息管理 我的收藏管理 开发环境和技术 开发语言…

【New Release】PostgreSQL小版本(16.2, 15.6, 14.11, 13.14,12.18) 发布了

前言 PostgreSQL遵循小版本的发布规律&#xff0c;这一个季度的小版本又发布了。可以算作是2024年第一个季度的版本发布。如果总结其规律&#xff1a;大概就是2月、5月、8月、11月的样子。通常因为11月配合大版本的发布&#xff0c;它是起点&#xff0c;也有可能就是终点。起点…