非极大值抑制(NMS)顾名思义就是抑制舍弃掉得分不高的元素,搜索局部的极大值。此处不讨论通用的NMS算法,仅关注用于目标检测中用于提取分数最高的窗口的NMS。例如在行人检测中,滑动窗口经提取特征,经分类器分类识别后,每个窗口都会得到一个分数。但是滑动窗口会导致很多窗口与其他窗口存在包含或者大部分交叉的情况。这时就需要用到NMS来选取那些是行人的概率最大,并且抑制舍弃掉那些分数低的窗口。
RCNN或其他的神经网络模型会从一张图片中找出n个可能是物体的矩形框,然后为每个矩形框为做类别分类概率,就像上面的图片一样,为定位一个车辆,最后算法找出了一堆的方框,我们需要判别哪些矩形框是没用的。我们还是从一个例子中来阐述下nms是如何做的.
假设网络预测出了6个矩形框A1~A6,对每个box按照置信度从小到大做排序,排队结果为:A1,A2,A3,A4,A5,A6
(1)从最大置信度矩形框A6开始,分别判断A1,A2,A3,A4,A5与A6的重叠度IOU是否大于某个设定的阈值(假设阈值a=0.7);
(2)假设A2、A3与A6的重叠度超过阈值,那么就扔掉A2、A3;并标记第一个矩形框A6,此时只剩下了矩形框A1,A4,A5,A6;
(3)从剩下的矩形框A1、A4、A5中,选择概率最大的A5,然后判断A1,A4与A5的重叠度,若重叠度大于设定的阈值,那么就扔掉这个box;假设扔掉了A1,则此时只剩下了矩形框A4,A5,A6;
(4)这样就找到所有被保留下来的矩形框A4,A5,A6,而且这些框之间的相互重叠度不高,即iou较小
程序实现:
在tensorflow中,已经有了nms的实现
tf.image.non_max_suppression(boxes,
scores,
max_output_size,
iou_threshold=0.5,
score_threshold=float('-inf'),
name=None):
参数说明:
boxes: 2维张量[num_boxes,4], 形如[y1, x1, y2, x2]
scores: 一维张量[num_boxes), 代表每个boxes的分数
max_output_size,非极大值移植最多输出boxes的数量
iou_threshold, iou阈值, 大于阈值被删除
name:可选项
返回的是selected_indices:表示的是一个1-d的整数张量,大小为m,代表的是选出来的留下来的边框下标,m<=max_output_size
在retinanet中的应用,keras_retinanet/layers/filter_detections.py/filter_detections-->_filter_detecions()-->backend.non_max_supperession(),本质上调用了tensorflow.image.non_max_suppression()
def _filter_detections(scores, labels):
# threshold based on score
indices = backend.where(keras.backend.greater(scores, score_threshold))#tf.greater()功能:比较scores, score_threshold两个值的大小,返回值:一个列表,元素值都是true和false
#在用where返回索引值,总体返回了scores中那些值>score_threshold的元素的位置
if nms:#true
filtered_boxes = backend.gather_nd(boxes, indices)#先取出那些置信度大于阈值的box
filtered_scores = keras.backend.gather(scores, indices)[:, 0]#
# perform NMS 调用了tensorflow.image.non_max_suppression执行最大值抑制,最多输出的300个
nms_indices = backend.non_max_suppression(filtered_boxes, filtered_scores, max_output_size=max_detections, iou_threshold=nms_threshold)
# filter indices based on NMS
indices = keras.backend.gather(indices, nms_indices)#找出经过极大值抑制之后保留的box的索引号
# add indices to list of all indices
labels = backend.gather_nd(labels, indices)#找出经过极大值抑制之后保留的labels的索引号
indices = keras.backend.stack([indices[:, 0], labels], axis=1)#为什么把labels两个混在一起呢?
return indices
一些改进:待补充,可参考文献
https://arxiv.org/pdf/1809.08545.pdf
soft nms
learning nms