论文地址:https://arxiv.org/pdf/1704.04503.pdf
Github项目:https://github.com/bharatsingh430/soft-nms
论文摘要
非最大抑制(Non-maximum suppression, NMS)是物体检测流程中重要的组成部分。它首先基于物体检测分数产生检测框,分数最高的检测框M被选中,其他与被选中检测框有明显重叠的检测框被抑制。该过程被不断递归的应用于其余检测框。根据算法的设计,如果一个物体处于预设的重叠阈值之内,可能会导致检测不到该待检测物体。因此,我们提出了Soft-NMS算法,该连续函数对非最大检测框的检测分数进行衰减而非彻底移除。它仅需要对传统的NMS算法进行简单的改动且不增额外的参数。该Soft-NMS算法在标准数据集PASCAL VOC2007(较R-FCN和Faster-RCNN提升1.7%)和MS-COCO(较R-FCN提升1.3%,较Faster-RCNN提升1.1%)上均有提升。此外,Soft-NMS具有与传统NMS相同的算法复杂度,使用高效。Soft-NMS也不需要额外的训练,并易于实现,它可以轻松的被集成到任何物体检测流程中。
传统的NMS处理方法可以通过以下的分数重置函数(Rescoring Function)来表达:
在这个公式中, NMS采用了硬阈值来判断相邻检测框是否保留。但是,换一种方法,假设我们对一个与M高度重叠的检测框bi的检测分数进行衰减,而非全部抑制。如果检测框bi中包含不同于M中的物体,那么在检测阈值比较低的情况下,该物体并不会错过检测。但是,如果bi中并不包含任何物体,即使在衰减过后,bi的分数仍然较高,它还是会产生一个假阳性的结果。因此,在使用NMS做物体检测处理的时候,需要注意以下几点:
- 相邻检测框的检测分数应该被降低,从而减少假阳性结果,但是,衰减后的分数仍然应该比明显的假阳性结果要高。
- 通过较低的NMS重叠阈值来移除所有相邻检测框并不是最优解,并且很容易导致错过被检测物体,特别是在物体高度重叠的地方
- 当NMS采用一个较高的重叠阈值时,平均准确率可能会相应降低。
Motivation
绝大部分目标检测方法,最后都要用到 NMS-非极大值抑制进行后处理。 通常的做法是将检测框按得分排序,然后保留得分最高的框,同时删除与该框重叠面积大于一定比例的其它框。
这种贪心式方法存在如下图所示的问题: 红色框和绿色框是当前的检测结果,二者的得分分别是0.95和0.80。如果按照传统的NMS进行处理,首先选中得分最高的红色框,然后绿色框就会因为与之重叠面积过大而被删掉。
另一方面,NMS的阈值也不太容易确定,设小了会出现下图的情况(绿色框因为和红色框重叠面积较大而被删掉),设置过高又容易增大误检。
思路:不要粗鲁地删除所有IOU大于阈值的框,而是降低其置信度。
Method
如下图:如文章题目而言,就是用一行代码来替换掉原来的NMS。按照下图整个处理一遍之后,指定一个置信度阈值,然后最后得分大于该阈值的检测框得以保留
原来的NMS可以描述如下:将IOU大于阈值的窗口的得分全部置为0。
文章的改进有两种形式,一种是线性加权的:
一种是高斯加权的:
分析上面的两种改进形式,思想都是:M为当前得分最高框,bi为待处理框,bi和M的IOU越大,bi的得分si就下降的越厉害。
值得注意的是,soft-NMS也是一种贪心算法,并不能保证找到全局最优的检测框分数重置。但是,soft-NMS算法是一种更加通用的非最大抑制算法,传统的NMS算法可以看做是它的一个采用不连续二值权重函数的特例。除了以上这两种分数重置函数,我们也可以考虑开发其他包含更多参数的分数重置函数,比如Gompertz函数等。但是它们在完成分数重置的过程中增加了额外的参数。
NMS代码:
%% NMS:non maximum suppression function pick = nms(boxes,threshold,type) % boxes: m x 5,表示有m个框,5列分别是[x1 y1 x2 y2 score] % threshold: IOU阈值 % type:IOU阈值的定义类型 % 输入为空,则直接返回 if isempty(boxes) pick = []; return; end % 依次取出左上角和右下角坐标以及分类器得分(置信度) x1 = boxes(:,1); y1 = boxes(:,2); x2 = boxes(:,3); y2 = boxes(:,4); s = boxes(:,5); % 计算每一个框的面积 area = (x2-x1+1) .* (y2-y1+1); %将得分升序排列 [vals, I] = sort(s); %初始化 pick = s*0; counter = 1; % 循环直至所有框处理完成 while ~isempty(I) last = length(I); %当前剩余框的数量 i = I(last);%选中最后一个,即得分最高的框 pick(counter) = i; counter = counter + 1; %计算相交面积 xx1 = max(x1(i), x1(I(1:last-1))); yy1 = max(y1(i), y1(I(1:last-1))); xx2 = min(x2(i), x2(I(1:last-1))); yy2 = min(y2(i), y2(I(1:last-1))); w = max(0.0, xx2-xx1+1); h = max(0.0, yy2-yy1+1); inter = w.*h; %不同定义下的IOU if strcmp(type,'Min') %重叠面积与最小框面积的比值 o = inter ./ min(area(i),area(I(1:last-1))); else %交集/并集 o = inter ./ (area(i) + area(I(1:last-1)) - inter); end %保留所有重叠面积小于阈值的框,留作下次处理 I = I(find(o<=threshold)); end pick = pick(1:(counter-1)); end
Soft-NMS代码:
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%的提升,且不增加额外的训练和计算负担。
高斯方差以及NMS的IOU阈值的敏感性测试: