一.概述
在目标检测中,常会利用非极大值抑制算法(NMS)对生成的大量候选框进行后处理,去除冗余的候选框,得到最具代表性的结果,以提高目标检测的效率。即如下图所示,消除多余的候选框,找到最佳的bbox。
二.NMS算法详解
NMS算法的核心思想如下:
根据候选框的类别分类概率做排序:A<B<C<D<E<F
- 先标记最大概率矩形框F是我们要保留下来的;
- 从最大概率矩形框F开始,分别判断A~E与F的重叠度IOU(两框的交并比)是否大于某个设定的阈值,假设B、D与F的重叠度超过阈值,那么就扔掉B、D;
- 从剩下的矩形框A、C、E中,选择概率最大的E,标记为要保留下来的,然后判读E与A、C的重叠度,扔掉重叠度超过设定阈值的矩形框
就这样一直重复下去,直到剩下的矩形框没有了,标记完所有要保留下来的矩形框。
注:nms是针对每一个类别分别进行的
三.python代码实现
# coding=utf-8
import numpy as np
def nms(dets, thresh):
# dets.shape = (n, 5)
# n代表有n个候选框,5代表每个候选框用5个值表示,即[x1,y1,x2,y2,score]
# x1.shape = y1.shape = x2.shape = y2.shape = scores.shape = (n, 1)
x1 = dets[:, 0] # 第一列,即所有候选框左上角点的x坐标值
y1 = dets[:, 1] # 第二列,即所有候选框左上角点的y坐标值
x2 = dets[:, 2] # 第三列,即所有候选框右下角点的x坐标值
y2 = dets[:, 3] # 第四列,即所有候选框右下角点的y坐标值
scores = dets[:, 4] # 第五列,即所有候选框的bbox评分
# 计算每一个候选框的面积
# areas.shape = (n, 1)
areas = (x2 - x1 + 1)*(y2 - y1 + 1)
# order是按照scores的大小来对整个dets降序排列
# order中存放的是dets的各行按scores的大小降序排列的索引值
# order.shape = (n, 1)
order = scores.argsort()[::-1]
# 初始化一个空的列表keep,keep中将会存放经过非极大值抑制后留下的候选框在dets中的索引值
keep = []
while order.size > 0: # 循环条件:只要order中还有元素
# 将i初始化为dets中scores最大值的那一行的索引
i = order[0]
# 将i的值存放到keep中
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-重叠面积)
ovr = inter / (areas[i] + areas[order[1:]] - inter)
# 找到重叠度不高于阈值的矩形框索引
inds = np.where(ovr <= 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 = nms(dets, thresh)
print(keep_dets)
print(dets[keep_dets])