目标检测算法之NMS后处理相关

  • 2019 年 12 月 9 日
  • 筆記

前言

昨天盘点了一下目标检测算法的常见数据集还有评判标准,但目标检测过程还有一个后处理算法的重要性确常被忽略,今天我们就来盘点一下目标检测算法中的NMS相关知识吧。

NMS

介绍

非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素。在目标检测任务,例如行人检测中,滑动窗口经过特征提取和分类器识别后,每个窗口都会得到一个分数。但滑动窗口会导致很多窗口和其它窗口存在包含大部分交叉的情况。这个时候就需要用到NMS来选取那些邻域里分数最高,同时抑制那些分数低的窗口。

原理

在目标检测任务中,定义最后的候选框集合为,每个候选框对应的置信度是,IOU阈值设为,然后NMS的算法过程可以表示如下:

  • 选择具有最大score的候选框
  • 将从集合中移除并加入到最终的检测结果中
  • 将中剩余检测框中和的交并比(IOU,昨天的推文有介绍)大于阈值的框从B中移除
  • 重复上面的步骤,直到为空

代码实现

rgb大神实现Faster-RCNN中的单类别物体nms代码解释如下:

# --------------------------------------------------------  # Fast R-CNN  # Copyright (c) 2015 Microsoft  # Licensed under The MIT License [see LICENSE for details]  # Written by Ross Girshick  # --------------------------------------------------------    import numpy as np    def py_cpu_nms(dets, thresh):      """Pure Python NMS baseline."""      #x1、y1、x2、y2、以及score赋值      x1 = dets[:, 0]      y1 = dets[:, 1]      x2 = dets[:, 2]      y2 = dets[:, 3]      scores = dets[:, 4]  	#每一个检测框的面积      areas = (x2 - x1 + 1) * (y2 - y1 + 1)      #按照score置信度降序排序      order = scores.argsort()[::-1]      #保留的结果框集合      keep = []      while order.size > 0:          i = order[0]          #保留该类剩余box中得分最高的一个          keep.append(i)          # 得到相交区域,左上及右下          xx1 = 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:]])  	    #计算相交的面积,不重叠时面积为0          w = np.maximum(0.0, xx2 - xx1 + 1)          h = np.maximum(0.0, yy2 - yy1 + 1)          inter = w * h          #计算IoU:重叠面积 /(面积1+面积2-重叠面积)          ovr = inter / (areas[i] + areas[order[1:]] - inter)  		# 保留IoU小于阈值的box          inds = np.where(ovr <= thresh)[0]          # 因为ovr数组的长度比order数组少一个,所以这里要将所有下标后移一位          order = order[inds + 1]    	return keep  

效果

在这里插入图片描述

Soft-NMS

上面说的NMS算法有一个缺点就是当两个候选框的重叠度很高时,NMS会将具有较低置信度的框去掉,也就是将其置信度变成0,如下图所示,红色框和绿色框是当前的检测结果,二者的得分分别是0.95和0.80。如果按照传统的NMS进行处理,首先选中得分最高的红色框,然后绿色框就会因为与之重叠面积过大而被删掉。

在这里插入图片描述

因此为了改善这个缺点,Soft-NMS被提出,核心思路就是不要粗鲁地删除所有IOU大于阈值的框,而是降低其置信度。这个方法的论文地址为:https://arxiv.org/pdf/1704.04503.pdf 。算法伪代码如下:

在这里插入图片描述

正如作者所说,改一行代码就OK了。这里的函数可以是线性函数,也可以是高斯函数。我们来对比一下:

  • 线性函数:
  • 高斯函数:

代码实现

作者的代码如下:

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  

效果

左边是使用了NMS的效果,右边是使用了Soft-NMS的效果。

论文的实验结果

可以看到在MS-COCO数据集上map$[0.5:0.95]可以获得大约1%的提升,如果应用到训练阶段的proposal选取过程理论上也能获得提升。顺便说一句,soft-NMS在不是基于Proposal的方法如SSD,YOLO中没什么提升。这里猜测原因可能是因为YOLO和SSD产生的框重叠率较低引起的。

后记

今天介绍了目标检测任务中的后处理过程最重要的NMS算法以及它的改进方案Soft-NMS算法,并提供了实现源码,希望大家能彻底理解这两个算法。

思考

NMS的阈值是否可以自适应?