大家好,我是行路南。
本文是CV面试系列第一篇。上段时间,笔者对2020年各大视觉大厂面试题目进行了梳理。结果发现NMS真的是一道基本题和高频题。这里可以列出几家的题目给大家看看:
百度图像算法工程师社招二面:
- 编程实现一下NMS
图森未来校招一面:
- Soft-NMS和NMS的区别,解决了什么问题。Soft-NMS的具体过程
BIGO校招一面:
- python实现NMS
货拉拉一面:
- 编程实现NMS
阿里达摩院二面:
- C++实现非极大值抑制
快手一面:
- 编程实现NMS
地平线一面:
- 编程实现NMS
…
可见,对于NMS,我们不仅需要理解它的概念,还要进一步它的不足和改进的方法,最最最重要的要掌握一门语言能够实现它。这样我们才能在面试中游(bu)刃(bei)有(diao)余(da)…
夺命第一问:NMS是什么?描述一下它的基本过程
NMS又称非极大值抑制,它是目标检测管道中的一个必要组成部分。
初始时,非极大值抑制过程开始于一个检测框列表 B B B、对应的置信度列表 S S S、空的检测框列表 D D D、阈值 N t N_t Nt。
首先,找到最高置信度对应的检测框 M M M, 将其从 B B B中删除,并添加到最终检测框列表 D D D中。然后,计算 M M M与 B B B中其他检测框的重叠程度IOU,将IOU大于阈值 N t N_t Nt的目标检测框从 B B B中删除。
对于 B B B中剩余的检测框,继续重复这个过程,直到 B B B为空时结束。
下图中是Fast R-CNN目标检测中应用NMS的例子。从左上到右上,是生成候选区域(Proposal)的过程;从右上到右下,是使用分类网络得到类别置信度和使用回归网络得到偏移量进而修正位置的过程;从右下到左下,是对每个目标上检测到的框进行筛选的过程,这个过程就是通过NMS来实现。
夺命第二问: Soft-NMS和NMS的区别,解决了NMS什么问题?
NMS 的一个主要问题是,它将与 M M M重叠度大于阈值的其他检测框直接从B中删除了。
这就造成了,如果在 M M M的邻域,实际上真的有一个目标,也会因为该目标的检测框与M重叠度大于阈值而被抑制。这就造成该目标没有检测出来,影响了整体的平均精度(AP)。
这主要发生在密集场景下,如下图所示。
刚才说到了NMS的主要问题,NMS还有一个问题是阈值 N t N_t Nt不容易设置,设置过小则造成错误率;设置过大又会造成误检率。(为难啊…
Soft-NMS是如何解决这个问题的呢?
Soft-NMS的思想是与 M M M重叠度大于阈值的其他检测框不会直接从B中删除了,直接采用一个函数来衰减这些检测框的置信度。
衰减的一个原则是如果一个检测框与 M M M有很高的重叠,那么它应该被衰减得严重一点,即置信度会变为一个很低的分数;相反,如果一个检测框与 M M M有很低的重叠甚至没有重叠,那么它应该保持原来的置信度,不受影响。
在上图检测马的例子中,运用Soft-NMS绿色检测框的置信度被衰减到0.4,因为满足置信度大于conf_thresh(一般设置为0.25),所以相邻的这匹马也会被检测出来了。
下面看一下Soft-NMS的具体流程:
扩展延伸一下,在Soft-NMS中使用的惩罚函数 f ( i o u ( M , b I ) ) f(iou(M,b_I)) f(iou(M,bI))有两种形式可以使用:
第一种是线性的惩罚函数,表达式为:
s i = { s i i o u ( M , b i ) < N t s i ( 1 − i o u ( M , b i ) ) i o u ( M , b i ) > = N t s_i= \begin{cases} s_i& {iou(M,b_i) < N_t}\\ s_i(1-iou(M,b_i))& {iou(M,b_i) >= N_t} \end{cases} si={
sisi(1−iou(M,bi))iou(M,bi)<Ntiou(M,bi)>=Nt
这个惩罚函数满足了距离较远的检测框不受影响,距离较近的检测框会受到很大的惩罚这样的原则。但是,这个函数不是连续的,即当检测框满足重叠阈值 N t N_t Nt时,会突然施加惩罚。
为此Soft-NMS惩罚函数还有第二种形式,即高斯惩罚函数,表达式如下:
s i = s i e − i o u ( M , b i ) 2 σ s_i= s_i e^{-\frac{iou(M,b_i)^2}{\sigma}} si=sie−σiou(M,bi)2
其中高斯惩罚函数中不再有参数 N t N_t Nt,多了一个参数 σ \sigma σ。这个参数通常设置为0.5。
夺命第三问:请使用C++或者Python 实现NMS,注意边界条件
import numpy as np
def nms(dets, thresh):
x1 = dets[:, 0]
y1 = dets[:, 1]
x2 = dets[:, 2]
y2 = dets[:, 3]
scores = dets[:, 4]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
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:]])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= thresh)[0]
order = order[inds + 1]
return keep
上面是Fast RCNN中实现NMS的代码。
其中,dets代表所有的目标检测框,thresh 为NMS阈值。这里使用numpy来对所有检测框进行计算,效率比较高。大家了解numpy 常用操作的话应该都能看懂的这个实现。
nms的代码Github链接: https://github.com/rbgirshick/fast-rcnn/blob/master/lib/utils/nms.py
soft-nms 的代码Github链接:https://github.com/bharatsingh430/soft-nms/blob/master/lib/nms/cpu_nms.pyx
c++ darknet中实现的nms代码Github链接:https://github.com/pjreddie/darknet/blob/master/src/box.c
关注【CV面试宝典】,分享面试高频题目、工业实践项目等技术文章
参考链接:
-
Bounding Box Regression with Uncertainty for Accurate Object Detection
-
https://github.com/rbgirshick/fast-rcnn/blob/master/lib/utils/nms.py
-
https://github.com/bharatsingh430/soft-nms/blob/master/lib/nms/cpu_nms.pyx
-
https://github.com/pjreddie/darknet/blob/master/src/box.c