非极大值抑制(nms)算法详解[python]

非极大值抑制(nms)算法详解[python]一 起源目标检测在使用了基于深度学习的端到端模型后效果斐然 目前 常用的目标检测算法 无论是 One stage 的 SSD 系列算法 YOLO 系列算法还是 Two stage 的基于 RCNN 系列的算法 非极大值抑制都是其中必不可少的一个组件 在现有的基于 anchor 的目标检测算法中 都会产生数量巨大的候选矩形框 这些矩形框有很多是指向同一目标 因此就存在大量冗余的候选矩形框 非极大值抑制算法的目的正在于

一、起源

目标检测在使用了基于深度学习的端到端模型后效果斐然。目前,常用的目标检测算法,无论是One-stage的SSD系列算法、YOLO系列算法还是Two-stage的基于RCNN系列的算法,非极大值抑制都是其中必不可少的一个组件。在现有的基于anchor的目标检测算法中,都会产生数量巨大的候选矩形框,这些矩形框有很多是指向同一目标,因此就存在大量冗余的候选矩形框。非极大值抑制算法的目的正在于此,它可以消除多余的框,找到最佳的物体检测位置。

非极大值抑制(Non-Maximum Suppression,以下简称NMS算法)的思想搜索局部极大值,抑制非极大值元素。针对不同的应用场景和检测算法,由于矩形框的表征方式不同,NMS算法具有各种变体,本文针对NMS及其各种变体(主要是本文检测相关)进行介绍。

二、经典NMS

经典NMS最初第一次应用到目标检测中是在RCNN算法中,其实现严格按照搜索局部极大值,抑制非极大值元素的思想来实现的,具体的实现步骤如下:

  1. 设定目标框的置信度阈值,常用的阈值是0.5左右
  2. 根据置信度降序排列候选框列表
  3. 选取置信度最高的框A添加到输出列表,并将其从候选框列表中删除
  4. 计算A与候选框列表中的所有框的IoU值,删除大于阈值的候选框
  5. 重复上述过程,直到候选框列表为空,返回输出列表

其中IoU(Intersection over Union)为交并比,如图1所示,IoU相当于两个区域交叉的部分除以两个区域的并集部分得出的结果。图2是IoU为各个取值时的情况展示,一般来说,这个score > 0.5 就可以被认为一个不错的结果了。

非极大值抑制(nms)算法详解[python]

                                                                                              图1

非极大值抑制(nms)算法详解[python]

                                                                                              图2

下面,通过一个具体例子来说明经典NMS究竟做了什么。图3的左图是包含一个检测目标(王闹海)的实例图片。其中的绿色矩形框代表了经过目标检测算法后,生成的大量的带置信度的Bounding box,矩形框左下角的浮点数即代表该Bounding box的置信度。在这里,使用Python对经典NMS算法实现,并应用到该实例中去。当NMS的阈值设为0.2时,最后的效果如图3中右图所示。

非极大值抑制(nms)算法详解[python]

​                                                                                              图3

Python代码如下:

本文所有代码和数据的github地址:https://github.com/lzneu/letGo/tree/master/nms

def nms(bounding_boxes, Nt):    if len(bounding_boxes) == 0:        return [], []    bboxes = np.array(bounding_boxes) ​    # 计算 n 个候选框的面积大小    x1 = bboxes[:, 0]    y1 = bboxes[:, 1]    x2 = bboxes[:, 2]    y2 = bboxes[:, 3]    scores = bboxes[:, 4]    areas = (x2 - x1 + 1) * (y2 - y1 + 1) ​    # 对置信度进行排序, 获取排序后的下标序号, argsort 默认从小到大排序    order = np.argsort(scores) ​    picked_boxes = []  # 返回值    while order.size > 0:        # 将当前置信度最大的框加入返回值列表中        index = order[-1]        picked_boxes.append(bounding_boxes[index]) ​        # 获取当前置信度最大的候选框与其他任意候选框的相交面积        x11 = 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]])        w = np.maximum(0.0, x22 - x11 + 1)        h = np.maximum(0.0, y22 - y11 + 1)        intersection = w * h ​        # 利用相交的面积和两个框自身的面积计算框的交并比, 将交并比大于阈值的框删除        ious = intersection / (areas[index] + areas[order[:-1]] - intersection)        left = np.where(ious < Nt)        order = order[left]    return picked_boxes

三、soft-NMS

经典NMS是为了去除重复的预测框,这种算法在图片中只有单个物体被检测的情况下具有很好的效果,如上图所示,“腰子”王闹海被成功的检测出来。然而,经典NMS算法存在着一些问题:对于重叠物体无法很好的检测。当图像中存在两个重叠度很高的物体时,经典NMS会过滤掉其中置信度较低的一个。如下图所示,图中存在两个王闹海,经典NMS过滤后的结果如下图4所示:

非极大值抑制(nms)算法详解[python]

                                                                                              图4

而我们期望的结果是两个腰子都被算法成功检测出来。

为了解决这类问题,Jan Hosang,等人提出了Soft-NMS算法。Soft-NMS的算法伪代码如图5所示。其中红色框为经典NMS的步骤,而绿色框中的内容为Soft-NMS改进的步骤。可以看出,相对于经典NMS算法,Soft-NMS仅仅修改了一行代码。当选取了最大置信度的Bounding box之后,计算其余每个Bounding box与Bounding box的I ou值,经典NMS算法的做法是直接删除Iou大于阈值的Bounding box;而Soft-NMS则是使用一个基于Iou的衰减函数,降低Iou大于阈值Nt的Bounding box的置信度,IoU越大,衰减程度越大。

换一个角度来理解,经典NMS同样可以用一个基于Iou的衰减函数来表示,只是这个衰减函数是个0-1函数,当Iou大于阈值Nt,直接降低该Bounding box的置信度到0。关于经典NMS和Soft-NMS的衰减函数区别,可以通过如下的公式来表示[1]:

  • 经典NMS:                          非极大值抑制(nms)算法详解[python]
  • Soft-NMS-线性:               非极大值抑制(nms)算法详解[python]
  • Soft-NMS-高斯:                非极大值抑制(nms)算法详解[python]

非极大值抑制(nms)算法详解[python]

                                                                                              图5​

接下来, 继续使用图4的实例来验证Soft-NMS的效果,使用Python对Soft-NMS算法的实现如下

Python代码:

def soft_nms(bboxes, Nt=0.3, sigma2=0.5, score_thresh=0.3, method=2):    # 在 bboxes 之后添加对于的下标[0, 1, 2...], 最终 bboxes 的 shape 为 [n, 5], 前四个为坐标, 后一个为下标    res_bboxes = deepcopy(bboxes)    N = bboxes.shape[0]  # 总的 box 的数量    indexes = np.array([np.arange(N)])  # 下标: 0, 1, 2, ..., n-1    bboxes = np.concatenate((bboxes, indexes.T), axis=1)  # concatenate 之后, bboxes 的操作不会对外部变量产生影响    # 计算每个 box 的面积    x1 = bboxes[:, 0]    y1 = bboxes[:, 1]    x2 = bboxes[:, 2]    y2 = bboxes[:, 3]    scores = bboxes[:, 4]    areas = (x2 - x1 + 1) * (y2 - y1 + 1) ​    for i in range(N):        # 找出 i 后面的最大 score 及其下标        pos = i + 1        if i != N - 1:            maxscore = np.max(scores[pos:], axis=0)            maxpos = np.argmax(scores[pos:], axis=0)        else:            maxscore = scores[-1]            maxpos = 0        # 如果当前 i 的得分小于后面的最大 score, 则与之交换, 确保 i 上的 score 最大        if scores[i] < maxscore:            bboxes[[i, maxpos + i + 1]] = bboxes[[maxpos + i + 1, i]]            scores[[i, maxpos + i + 1]] = scores[[maxpos + i + 1, i]]            areas[[i, maxpos + i + 1]] = areas[[maxpos + i + 1, i]]        # IoU calculate        xx1 = np.maximum(bboxes[i, 0], bboxes[pos:, 0])        yy1 = np.maximum(bboxes[i, 1], bboxes[pos:, 1])        xx2 = np.minimum(bboxes[i, 2], bboxes[pos:, 2])        yy2 = np.minimum(bboxes[i, 3], bboxes[pos:, 3])        w = np.maximum(0.0, xx2 - xx1 + 1)        h = np.maximum(0.0, yy2 - yy1 + 1)        intersection = w * h        iou = intersection / (areas[i] + areas[pos:] - intersection)        # Three methods: 1.linear 2.gaussian 3.original NMS        if method == 1:  # linear            weight = np.ones(iou.shape)            weight[iou > Nt] = weight[iou > Nt] - iou[iou > Nt]        elif method == 2:  # gaussian            weight = np.exp(-(iou * iou) / sigma2)        else:  # original NMS            weight = np.ones(iou.shape)            weight[iou > Nt] = 0        scores[pos:] = weight * scores[pos:]    # select the boxes and keep the corresponding indexes    inds = bboxes[:, 5][scores > score_thresh]    keep = inds.astype(int)    return res_bboxes[keep]

非极大值抑制(nms)算法详解[python]

                                                                                              图6

四、Locality-Aware NMS

以上的NMS或相关算法均为针对目标检测问题而提出的,而文本检测作为目标检测的一个特殊领域,具有其特殊之处,比如文本检测中面临的成千上万的候选几何体,如果使用计算复杂度为 ​ 的经典NMS类算法,由于n的数量过大,这是不可接受的。为此旷世公司于2017在EAST(一种非常好用的One-stage的文本检测算法,敬请关注交易只能团队下期文章!)论文[2]中提出了Locality-Aware NMS算法。

Locality-Aware NMS算法考虑了文本检测任务中临近像素高度相关的特征,提出了一种基于行的几何体合并方法,它的步骤主要是两个:

  1. 合并同一行区域中的几何体积
  2. 对合并后的Bounding box集合进行标准的NMS操作

Locality-Aware NMS提升了算法的时间复杂度,在最优情况下可以提升至 ​ 。即使是最坏情况,也不会比经典NMS的计算量更大。

Python代码如下:

import numpy as np from shapely.geometry import Polygon ​ def intersection(g, p):    # 取g,p中的几何体信息组成多边形    g = Polygon(g[:8].reshape((4, 2)))    p = Polygon(p[:8].reshape((4, 2)))    # 判断g,p是否为有效的多边形几何体    if not g.is_valid or not p.is_valid:        return 0    # 取两个几何体的交集和并集    inter = Polygon(g).intersection(Polygon(p)).area    union = g.area + p.area - inter    if union == 0:        return 0    else:        return inter / union ​ def weighted_merge(g, p):    # 取g,p两个几何体的加权(权重根据对应的检测得分计算得到)    g[:8] = (g[8] * g[:8] + p[8] * p[:8]) / (g[8] + p[8])    # 合并后的几何体的得分为两个几何体得分的总和    g[8] = (g[8] + p[8])    return g ​ def standard_nms(S, thres):    # 标准NMS    order = np.argsort(S[:, 8])[::-1]    keep = []    while order.size > 0:        i = order[0]        keep.append(i)        ovr = np.array([intersection(S[i], S[t]) for t in order[1:]])        inds = np.where(ovr <= thres)[0]        order = order[inds + 1]    return S[keep] ​ ​ def nms_locality(polys, thres=0.3):    '''   locality aware nms of EAST   :param polys: a N*9 numpy array. first 8 coordinates, then prob   :return: boxes after nms   '''    S = []  # 合并后的几何体集合    p = None  # 合并后的几何体    for g in polys:        if p is not None and intersection(g, p) > thres:  # 若两个几何体的相交面积大于指定的阈值,则进行合并            p = weighted_merge(g, p)        else:  # 反之,则保留当前的几何体            if p is not None:                S.append(p)            p = g    if p is not None:        S.append(p)    if len(S) == 0:        return np.array([])    return standard_nms(np.array(S), thres)

五、Softer-NMS

以上的NMS算法,无论是经典NMS还是Soft-NMS算法,都是在一种假设前提下:置信度最高的Bounding box就是目标的候选位置最精确的物体位置。但是事实上,这个假设可能并不成立,或者说并不那么精确。针对这个问题,来自卡内基梅隆大学与旷视科技的研究人员在文中提出了一种新的非极大抑制算法Softer-NMS,显著改进了目标检测的定位精度,代码已经开源,目前Github上的Star已超100,可谓短短两天已经引起了不小的关注。作者关注了两种目前NMS会出现的问题:

  1. 所有的候选Bounding box都够精确,无法确定该选择哪一个,见图7a
  2. 具有高置信度的Bounding box不够精确,见图7b

非极大值抑制(nms)算法详解[python]

​                                                                                              图7

Softer-NMS正是为了这两个问题而设计的。关于Softer-NMS的相关解析,敬请关注交易智能团队后续分享。

参考文献:

[1] Bodla N , Singh B , Chellappa R , et al. Improving Object Detection With One Line of Code[J]. 2017.

[2] Zhou X , Yao C , Wen H , et al. EAST: An Efficient and Accurate Scene Text Detector[J]. 2017.

[3] He Y , Zhu C , Wang J , et al. Bounding Box Regression with Uncertainty for Accurate Object Detection[J]. 2018.

[4] 非极大值抑制(Non-Maximum Suppression,NMS) - 康行天下 - 博客园

[5] 非极大值抑制(Non-Maximum Suppression) - 云+社区 - 腾讯云

[6] Intersection over Union (IoU) for object detection - PyImageSearch

[7] NMS 算法源码实现 | 从零开始的BLOG

[8] https://www.52cv.net/?p=1434

[9] https://www.jishuwen.com/d/24Tx#tuit

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/210366.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午7:45
下一篇 2026年3月19日 上午7:45


相关推荐

  • 数据结构:顺序表的基本操作

    数据结构:顺序表的基本操作线性表的顺序存储顺序表的线性存储示意图 C 语言定义线性表的顺序存储结构线性表的顺序存储 nbsp nbsp nbsp nbsp 线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素 使得线性表在逻辑结构上相邻的元素存储在连续的物理存储单元中 即 通过数据元素物理存储的连续性来反应元素之间逻辑上的相邻关系 采用顺序存储结构存储的线性表通常简称为顺序表 nbsp nbsp nbsp nbsp 顺序存

    2026年3月18日
    1
  • CentOS6 更换yum源的方法

    CentOS6 更换yum源的方法

    2021年6月4日
    149
  • sql日期查询 中文格式转换

    sql日期查询 中文格式转换显示年月日格式 xxxx 年 xx 月 xx 日 SELECTCONVER VARCHAR DATEPART YYYY GETDATE 年 CONVERT VARCHAR DATEPART MM GETDATE 月 CONVERT VARCHAR DATEPART DD GETDATE 日 保留四位小数转换 SELECTCONVER DECIMAL

    2026年3月26日
    1
  • 一文轻松掌握python语言命名规则(规范)

    一文轻松掌握python语言命名规则(规范)和C/C++、Java等语言一样,python在命名上也有一套约定俗成的规则,符合规范的命名可以让程序的可读性大大增加,从而使得代码的逻辑性增强,易于自己和其他协作者在以后的拓展中理解代码的意义,从而提高编写代码的效率。我们在平常编写程序的时候需要注意以下几点:一、python变量名命名的硬性规则1.1.变量名大小写敏感python变量名区分大小写,也就是Student和student在…

    2022年5月24日
    37
  • mmc卡和sd卡区别「建议收藏」

    mmc卡和sd卡区别「建议收藏」转载:https://zhidao.baidu.com/question/296690750.html区别:1、尺寸不同:SD卡的技术是基于MultiMedia卡(MMC)格式上发展而来,大小和MMC卡差不多,尺寸为32mmx24mmx2.1mm。长宽和MMC卡一样,只是比MMC卡厚了0.7mm,以容纳更大容量的存贮单元。2、兼容性不同:SD卡与MMC卡保持着向上兼容,…

    2022年6月11日
    38

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号