目标检测中的非最大值抑制 NMS

综述

NMS 主要就是通过迭代的形式, 不断地以最大得分的框去与其他框做 IoU 操作, 并过滤那些 IoU 较大的框。 其实现的思想主要是将各个框的置信度进行排序, 然后选择其中置信度最高的框 F, 将其作为标准选择其他框, 同时设置一个阈值, 当其他框 BF 的重合程度超过阈值就将 B 舍弃掉, 然后在剩余的框中选择置信度最大的框, 重复上述操作.

算法过程如下

  1. 根据候选框类别分类概率排序: F>E>D>C>B>A, 并标记最大概率的矩形框 F 作为标准框;
  2. 分别判断 A~EF 的重叠度 IOU (两框的交并比) 是否大于某个设定的阈值, 假设 BDF 的重叠度超过阈值, 那么就扔掉 BD;
  3. 从剩下的矩形框 ACE 中, 选择概率最大的 E, 标记为要保留下来的, 然后判读 EAC 的重叠度, 扔掉重叠度超过设定阈值的矩形框;
  4. 对剩下的 bbx, 循环执行 (2)(3) 直到所有的 bbx 均满足要求(即不能再移除 bbx).

NMS 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import numpy as np

def py_nms(dets, thresh):
"""Pure Python NMS baseline.注意,这里的计算都是在矩阵层面上计算的
greedily select boxes with high confidence and overlap with current maximum <= thresh
rule out overlap >= thresh
:param dets: [[x1, y1, x2, y2 score],] # ndarray, shape(-1,5)
:param thresh: retain overlap < thresh
:return: indexes to keep
"""
# x1、y1、x2、y2、以及score赋值
x1 = dets[:, 0]
y1 = dets[:, 1]
x2 = dets[:, 2]
y2 = dets[:, 3]

# 计算每一个候选框的面积, 纯矩阵加和乘法运算,为何加1?
# 大概是因为矩阵index从0开始
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
# order是将confidence降序排序后得到的矩阵索引
order = np.argsort(dets[:, 4])[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
# 计算当前概率最大矩形框与其他矩形框的相交框的坐标,会用到numpy的broadcast机制,得到的是向量
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或h算出来会是负数,用0代替
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h

# 计算重叠度IOU:重叠面积/(面积1+面积2-重叠面积)
iou = inter / (areas[i] + areas[order[1:]] - inter)

# 找到重叠度不高于阈值的矩形框索引
inds = np.where(iou < thresh)[0]

# 将order序列更新,由于前面得到的矩形框索引要比矩形框在原order序列中的索引小1,所以要把这个1加回来
order = order[inds + 1]
return keep

# test
if __name__ == "__main__":
dets = np.array([[30, 20, 230, 200, 1],
[50, 50, 260, 220, 0.9],
[210, 30, 420, 5, 0.8],
[430, 280, 460, 360, 0.7]])
thresh = 0.35
keep_dets = py_nms(dets, thresh)
print(keep_dets)
print(dets[keep_dets])

程序输出如下:

1
[0, 2, 3] [[ 30. 20. 230. 200. 1. ] [210. 30. 420. 5. 0.8] [430. 280. 460. 360. 0.7]]

Q&A

Q: 直接使用置信度最高的窗口不就可以了吗,为什么还需要逐步过滤掉其他窗口?
A: 一张图只有一个全局的框,这里这个目标只挑最大的把剩下的都删除以后,那么下个目标的时候会没有检测框。

0%