1. NMS(non maximum suppression)的定义和算法步骤
NMS(non maximum suppression),中文名非极大值抑制,在很多计算机视觉任务中都有广泛应用,如:边缘检测、目标检测等。
非极大值抑制(NMS)顾名思义就是抑制不是极大值的元素,搜索局部的极大值。这个局部代表的是一个邻域,邻域有两个参数可变,一是邻域的维数,二是邻域的大小。这里不讨论通用的NMS算法,而是用于在目标检测中用于提取分数最高的窗口的。
假设有N个框,每个框被分类器计算得到的分数为Si, 1<=i<=N。
步骤1,建造一个存放待处理候选框的集合H,初始化为包含全部N个框;建造一个存放最优框的集合M,初始化为空集。
步骤2,将所有集合 H 中的框进行排序,选出分数最高的框 m,从集合 H 移到集合 M;
步骤3,遍历集合 H 中的框,分别与框m计算交并比(Interection-over-union,IoU),如果高于某个阈值(一般为0~0.7),则认为此框与 m重叠,将此框从集合 H 中去除。
步骤4,回到第2步进行迭代,直到集合 H 为空。集合 M 中的框为我们所需。
参考:
NMS算法(NonMaximumSuppression):https://blog.csdn.net/weixin_40123108/article/details/86300489
给除了NMS的定义和算法步骤
NMS——非极大值抑制:https://blog.csdn.net/shuzfan/article/details/52711706#commentsedit
用一个实例详细讲解了重叠框消除的过程。
2. faster rcnn 中NMS代码详解
参考:
目标窗口检测算法-NMS非极大值抑制:https://chenzomi12.github.io/2016/12/14/YOLO-nms/
对python程序进行了详细注释
2.1. faster rcnn中的NMS函数
def nms(dets, thresh):
x1 = dets[:, 0] # 每个boundingbox的左上角x坐标
y1 = dets[:, 1] # 每个boundingbox的左上角y坐标
x2 = dets[:, 2] # 每个boundingbox的右下角x坐标
y2 = dets[:, 3] # 每个boundingbox的右下角y坐标
scores = dets[:, 4] # 每个boundingbox的置信度,置信度越高越有可能是目标
print('scores')
print(scores)
print('')
areas = (x2 - x1 + 1) * (y2 - y1 + 1) # 计算每个boundingbox的面积
# argsort 得到的是从小到大排序的index
# 为了变成从大到小排列,使用了[::-1]切片操作
# 也可以使用如下方法按降序排列: np.argsort(-scores)
order = scores.argsort()[::-1] # boundingbox的置信度排序,order中是各个boundingbox的index
keep = [] # 用来保存最后留下来的boundingbox的index
while order.size > 0: # 待处理boundingbox的个数为0时,结束循环
print('---------start a new iter----------')
print('order')
print(order)
print('')
i = order[0] # 置信度最高的boundingbox的index
keep.append(i) # 添加本次置信度最高的boundingbox的index
print('keep')
print(keep)
print('')
# 当前bbox和剩下bbox之间的交叉区域
# 选择大于x1,y1和小于x2,y2的区域
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:]])
# 当前bbox和其他剩下bbox之间交叉区域的面积
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h
# IOU=交叉区域面积 / (bbox + 某区域面积 - 交叉区域面积)
# inter 和 areas都是一维向量,areas是标量。
ovr = inter / (areas[i] + areas[order[1:]] - inter)
print('ovr')
print(ovr)
print('')
#保留IOU小于一定阈值的boundingbox
# np.where的用法
# (用法1) np.where(condition, x, y)
# 满足条件(condition),输出x,不满足输出y。
# (用法2) np.where(condition)
# 只有条件 (condition),没有x和y,则输出满足条件 (即非0) 元素的坐标 (等价于numpy.nonzero)。
# 这里的坐标以tuple的形式给出,通常原数组有多少维,输出的tuple中就包含几个数组,
# 分别对应符合条件元素的各维坐标。
inds = np.where(ovr <= thresh)[0]
# ovr是从 order[1:]计算得到,而不是order[0:]
# ovr中的index再加上1,对应order中的index
order = order[inds + 1]
print('Reserved detections id')
print(order)
print('')
return keep
参考博客:
[python]numpy.where功能详解-位置函数
https://blog.csdn.net/brucewong0516/article/details/80098524
python中np.where的使用方法
https://jingyan.baidu.com/article/f96699bb0d626a894e3c1b36.html
全面深入彻底理解Python切片操作
https://blog.csdn.net/xpresslink/article/details/77727507
2.2. 测试代码
import numpy as np
# 假设已经得到了4个boundingbox,
# dets中每一行表示一个boundingbox的坐标和置信度。
dets = np.array([
[204, 102, 358, 250, 0.5],
[257, 118, 380, 250, 0.7],
[280, 135, 400, 250, 0.6],
[255, 118, 360, 235, 0.8]])
thresh = 0.7 #设置重叠率阈值
# 调用faster rcnn中的NMS函数
keep = nms(dets, thresh)
# 打印NMS结果
print('nms result:')
print('keep')
print(keep)
print('dets[keep,:]')
print(dets[keep,:])
2.3.打印中间结果
scores
[0.5 0.7 0.6 0.8]
---------start a new iter----------
order
[3 1 2 0]
keep
[3]
ovr
[0.73362028 0.44551544 0.52599546]
Reserved detections id
[2 0]
---------start a new iter----------
order
[2 0]
keep
[3, 2]
ovr
[0.3276719]
Reserved detections id
[0]
---------start a new iter----------
order
[0]
keep
[3, 2, 0]
ovr
[]
Reserved detections id
[]
nms result:
keep
[3, 2, 0]
dets[keep,:]
[[255. 118. 360. 235. 0.8]
[280. 135. 400. 250. 0.6]
[204. 102. 358. 250. 0.5]]
3.其他参考资料
3.1. 非极大值抑制(NMS)的几种实现(提高运行速度)
https://www.cnblogs.com/king-lps/p/9031568.html
非极大值抑制(NMS)的几种实现,和原始python代码相比,上述博客中的运行速度更快。
因为之前对比了RoI pooling的几种实现,发现python、pytorch的自带工具函数速度确实很慢,所以这里再对Faster-RCNN中另一个速度瓶颈NMS做一个简单对比试验。
这里做了四组对比试验,来简单验证不同方法对NMS速度的影响。
方法1:纯python语言实现:简介方便、速度慢
测试数据为6000个初始的rois,并设置nms阈值为0.7~0.9。阈值越大越慢,因为满足小于阈值的roi越多,需要循环的次数也越多。对每个阈值循环执行NMS 50次求平均,直接运行得到运行时间:
thresh=0.7, time wastes:0.0287
thresh=0.8, time wastes:0.1057
thresh=0.9, time wastes:0.4204
方法2:直接利用Cython模块编译
发现与纯python速度相比仅有微小提升,
方法3:先将全部变量定义为静态类型,再利用Cython模块编译
发现速度相较于纯python分别提升了15倍、38倍、118倍!
方法4:在方法3的基础上再加入cuda加速模块, 再利用Cython模块编译,即利用gpu加速
发现比方法3还要慢一点,应该是计算量较小,而且时间损耗在调用GPU上吧。
3.2. NMS与Soft-NMS
参考博客:NMS与Soft-NMS: https://www.jianshu.com/p/8da5b4593a16
非最大抑制算法中的最大问题就是它将相邻检测框的分数均强制归零。在这种情况下,如果一个真实物体在重叠区域出现,则将导致对该物体的检测失败并降低了算法的平均检测率(average precision, AP)。
针对NMS存在的这个问题,提出了一种新的Soft-NMS算法,它只需改动一行代码即可有效改进传统贪心NMS算法。在该算法中,基于重叠部分的大小为相邻检测框设置一个衰减函数而非彻底将其分数置为零。简单来讲,如果一个检测框与M有大部分重叠,它会有很低的分数;而如果检测框与M只有小部分重叠,那么它的原有检测分数不会受太大影响。在标准数据集PASCAL VOC 和 MS-COCO等标准数据集上,Soft-NMS对现有物体检测算法在多个重叠物体检测的平均准确率有显著的提升。同时,Soft-NMS不需要额外的训练且易于实现,因此,它很容易被集成到当前的物体检测流程中。
NMS和Soft-NMS的算法流程对比如下图所示: