NMS和Soft NMS

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NMS和Soft NMS相关的知识,希望对你有一定的参考价值。

参考技术A

NMS 是 one-stage 和 two-stage 目标检测任务中常用的一种后处理方法,用来过滤无效重叠的检测框。

NMS 全称非极大值抑制,出自 ICPR2006 的论文 《Efficient Non-Maximum Suppression》 。其基本思想很简单,就是保留局部最大值而去除局部非最大值。

NMS 对所有的类别的检测框进行循环过滤。对于某个类别 C ,首先对这些矩形框按照概率降序排列,选中概率最大的框作为候选框,对于剩下的框,依次与候选框求 IOU ,如果 IOU 大于某个阈值(超参),则将这些框丢弃(置0),并标记保留最大概率框。

以此类推,最终所有的框相互之间的 IOU 都是小于超参阈值的,或者概率被置为 0 了。剩下的所有概率非0的框就是最终的检测框。

基于这种计算逻辑的 NMS 有两个缺点。首先, NMS 算法需要一个超参即 IOU Threshold ,这个阈值在不同任务中很难平衡。其次, NMS 会将相邻或者重叠的两个物体对应的两个大概率目标框去掉一个,造成漏检。

实现:

Soft-NMS 出自 CVPR2017 的论文 《Improving Object Detection With One Line of Code》 ,对 NMS 做了一些改进。

Soft-NMS 总体算法流程同 NMS 相同,主要差别循环过程中对阈值的判断部分。 NMS 是简单的对 IOU 大于阈值的检测框进行删除出来,而 Soft-NMS 则是通过权重来降低检测框原有的置信度。对于有重叠的框,重叠区域越大,置信度衰减越严重。

Soft-NMS 计算降低置信度的权重常用两种方法:线性法和高斯法。

线性法:

高斯法:

实现如下,方法 1 为线性法,方法 2 为高斯法,其他参数的话 Soft-NMS 退化为 NMS :

模拟的 5 个候选框:

实验可以看出,在相同 IOU 阈值的情况下, Soft-NMS 相比 NMS 保留了一个检测结果,这在同类别物体重叠的情况下能够提升其召回率。但是 Soft-NMS 又多引入了一个超参,这个参数的设置也会显著影响后处理的结果;而且,由于 Soft-NMS 在每次迭代都会修改 score 值,其最大值是在动态变化的需要在每次迭代都寻找一次,因此 Soft-NMS 相比 NMS 计算效率有所降低。

一行代码改进NMS

一篇讲通过改进NMS来提高检测效果的论文。

文章链接: 《Improving Object Detection With One Line of Code》

Github链接: https://github.com/bharatsingh430/soft-nms

Motivation

绝大部分目标检测方法,最后都要用到 NMS-非极大值抑制进行后处理。 通常的做法是将检测框按得分排序,然后保留得分最高的框,同时删除与该框重叠面积大于一定比例的其它框。

这种贪心式方法存在如下图所示的问题: 红色框和绿色框是当前的检测结果,二者的得分分别是0.95和0.80。如果按照传统的NMS进行处理,首先选中得分最高的红色框,然后绿色框就会因为与之重叠面积过大而被删掉。

另一方面,NMS的阈值也不太容易确定,设小了会出现下图的情况(绿色框因为和红色框重叠面积较大而被删掉),设置过高又容易增大误检。

float

思路:不要粗鲁地删除所有IOU大于阈值的框,而是降低其置信度。

Method

先直接上伪代码,如下图:如文章题目而言,就是用一行代码来替换掉原来的NMS。按照下图整个处理一遍之后,指定一个置信度阈值,然后最后得分大于该阈值的检测框得以保留

float

原来的NMS可以描述如下:将IOU大于阈值的窗口的得分全部置为0。

float

文章的改进有两种形式,一种是线性加权的:

float

一种是高斯加权的:

float

分析上面的两种改进形式,思想都是:M为当前得分最高框,\\(b_i\\) 为待处理框,\\(b_i\\) 和M的IOU越大,\\(b_i\\) 的得分\\(s_i\\) 就下降的越厉害。

具体地,下面是作者给出的代码:(当然不止一行T_T)

def cpu_soft_nms(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0):
    cdef unsigned int N = boxes.shape[0]
    cdef float iw, ih, box_area
    cdef float ua
    cdef int pos = 0
    cdef float maxscore = 0
    cdef int maxpos = 0
    cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov

    for i in range(N):
        maxscore = boxes[i, 4]
        maxpos = i

        tx1 = boxes[i,0]
        ty1 = boxes[i,1]
        tx2 = boxes[i,2]
        ty2 = boxes[i,3]
        ts = boxes[i,4]

        pos = i + 1
    # get max box
        while pos < N:
            if maxscore < boxes[pos, 4]:
                maxscore = boxes[pos, 4]
                maxpos = pos
            pos = pos + 1

    # add max box as a detection 
        boxes[i,0] = boxes[maxpos,0]
        boxes[i,1] = boxes[maxpos,1]
        boxes[i,2] = boxes[maxpos,2]
        boxes[i,3] = boxes[maxpos,3]
        boxes[i,4] = boxes[maxpos,4]

    # swap ith box with position of max box
        boxes[maxpos,0] = tx1
        boxes[maxpos,1] = ty1
        boxes[maxpos,2] = tx2
        boxes[maxpos,3] = ty2
        boxes[maxpos,4] = ts

        tx1 = boxes[i,0]
        ty1 = boxes[i,1]
        tx2 = boxes[i,2]
        ty2 = boxes[i,3]
        ts = boxes[i,4]

        pos = i + 1
    # NMS iterations, note that N changes if detection boxes fall below threshold
        while pos < N:
            x1 = boxes[pos, 0]
            y1 = boxes[pos, 1]
            x2 = boxes[pos, 2]
            y2 = boxes[pos, 3]
            s = boxes[pos, 4]

            area = (x2 - x1 + 1) * (y2 - y1 + 1)
            iw = (min(tx2, x2) - max(tx1, x1) + 1)
            if iw > 0:
                ih = (min(ty2, y2) - max(ty1, y1) + 1)
                if ih > 0:
                    ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
                    ov = iw * ih / ua #iou between max box and detection box

                    if method == 1: # linear
                        if ov > Nt: 
                            weight = 1 - ov
                        else:
                            weight = 1
                    elif method == 2: # gaussian
                        weight = np.exp(-(ov * ov)/sigma)
                    else: # original NMS
                        if ov > Nt: 
                            weight = 0
                        else:
                            weight = 1

                    boxes[pos, 4] = weight*boxes[pos, 4]

            # if box score falls below threshold, discard the box by swapping with last box
            # update N
                    if boxes[pos, 4] < threshold:
                        boxes[pos,0] = boxes[N-1, 0]
                        boxes[pos,1] = boxes[N-1, 1]
                        boxes[pos,2] = boxes[N-1, 2]
                        boxes[pos,3] = boxes[N-1, 3]
                        boxes[pos,4] = boxes[N-1, 4]
                        N = N - 1
                        pos = pos - 1

            pos = pos + 1

    keep = [i for i in range(N)]
    return keep

Experiments

下图可以看出,基本可以获得平均1%的提升,且不增加额外的训练和计算负担。

float

高斯方差以及NMS的IOU阈值的敏感性测试:

float

以上是关于NMS和Soft NMS的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Soft-NMS实现目标检测并提升准确率

一行代码改进NMS

Pytorch机器学习—— YOLOV5中NMS非极大值抑制与DIOU-NMS等改进

从0开始的CNN学习之路

从0开始的CNN学习之路

NMS——非极大值抑制