本文译自:https://www.pyimagesearch.com/2015/02/16/faster-non-maximum-suppression-python,原文代码存在一定问题,译文中给出了更改,译文有删改,转载请参看http://www.icoder.top/blog/index.php/2018/05/03/faster-non-maximum-suppression-python/左侧要求。
最近一段时间,我一直沉迷于思考物体检测问题,以至于昨晚我看《行尸走肉》的时候,并没有被其残暴的场面和精彩的剧情所吸引,而是情不自禁地想搭建一个僵尸检测系统。
实际一点看,这系统有用吗?可能半毛用也没有。当僵尸出现在你身后的时候,很明显,你已经面临死亡了,恐怕只有惊叫的份,这种情况下,压根就不需要什么计算机视觉系统。当然了,只是因为我最近一直在思考检测的问题,才会突发奇想。
两周前,我写了篇《利用面向梯度直方图和线性支持向量机来搭建目标检测系统》,抛开Haar级联分类器糟糕的性能不说,训练花费的时间也非常长,所以我决心编写自己的Python目标检测框架。
目前为止,进展非常顺利。
在构建目标检测系统时有一个无法绕开的问题——边界框重叠(实际上这是个好兆头,至少代表你的检测系统运行良好,所以甚至可以不将其称作“问题”)。为了去除重叠的边界框(指向同一物体的),我们可以对Mean-Shift算法应用非最大抑制。虽然Dalal和Triggs都喜欢用Mean-Shift,但不得不说Mean-Shift给出的结果时低于标准的。
在得到我的朋友Tomasz Malisiewicz博士(目标检测方面的专家)的帮助后,我打算将他的非最大抑制算法从MatLab移植到Python。
上周我们探讨了Felzenszwalb等人提供的算法,而这次我们要使用的算法,速度将会是之前的100倍。
所以不禁要问,为什么这个算法速度会有如此大的提升呢?请仔细阅读下文。
快速非最大抑制算法Python版
再次之前,如果你还没有读过《目标检测中的非最大抑制算法》,建议你花点时间读一下,并和这次的代码做些比较。
# import the necessary packages
import numpy as np
# Tomasz Malisiewicz 提出的非最大抑制算法
def non_max_suppression_fast(boxes, overlapThresh):
# if there are no boxes, return an empty list
if len(boxes) == 0:
return []
# if the bounding boxes integers, convert them to floats --
# this is important since we'll be doing a bunch of divisions
if boxes.dtype.kind == "i":
boxes = boxes.astype("float")
# initialize the list of picked indexes
pick = []
# grab the coordinates of the bounding boxes
x1 = boxes[:, 0]
y1 = boxes[:, 1]
x2 = boxes[:, 2]
y2 = boxes[:, 3]
scores = boxes[:, 4]
# compute the area of the bounding boxes and sort the bounding
# boxes by the score of the bounding box
area = (x2 - x1 + 1) * (y2 - y1 + 1)
idxs = np.argsort(scores)
# keep looping while some indexes still remain in the indexes
# list
while len(idxs) > 0:
# grab the last index in the indexes list and add the
# index value to the list of picked indexes
last = len(idxs) - 1
i = idxs[last]
pick.append(i)
# find the largest (x, y) coordinates for the start of
# the bounding box and the smallest (x, y) coordinates
# for the end of the bounding box
# 计算重叠区域的左上与右下坐标
xx1 = np.maximum(x1[i], x1[idxs[:last]])
yy1 = np.maximum(y1[i], y1[idxs[:last]])
xx2 = np.minimum(x2[i], x2[idxs[:last]])
yy2 = np.minimum(y2[i], y2[idxs[:last]])
# compute the width and height of the bounding box
# 计算重叠区域的长宽
w = np.maximum(0, xx2 - xx1 + 1)
h = np.maximum(0, yy2 - yy1 + 1)
# compute the ratio of overlap
# 计算重叠区域占原区域的面积比(重叠率)
overlap = (w * h) / area[idxs[:last]]
# 删除所有重叠率大于阈值的边界框
idxs = np.delete(idxs, np.concatenate(([last],
np.where(overlap > overlapThresh)[0])))
# return only the bounding boxes that were picked using the
# integer data type
return boxes[pick]
代码看起来似乎相同对吧?所以你肯定会问,“这个100倍速度从何而来?”
答案是删除了一个内部for
循环。
上周的算法实现需要一个额外的内循环来计算边界区域的大小并计算重叠区域的比例,而Malisiewicz博士则使用了矢量化代码取而代之。不像上周的代码那样逐行重复检验,这次我们只考察有用的部分。
6-22行与上周代码基本相同,首先获得边界框的(x,y)坐标,计算其面积,并根据每个框权重在boxes
列表中建立索引。
31-55行是算法提速的关键,41-55行尤为重要。
我们使用np.maximum
和np.minimum
函数向量化代码以替代使用for循环遍历每个的边界框——这使我们能够在坐标轴上找到最大值和最小值,而不仅仅是单个标量。
注意:np.maximum
和np.minimum
函数允许你混合标量和矢量,而np.max
和np.min
函数不可以,使用这俩函数可能产生许多bug,为了将算法从MATLAB移植到Python,我花了我很长时间来调试这个问题。
第47和48行也是矢量化的,这里我们计算每个要检查的矩形的宽度和高度。类似地,第51行计算重叠率也是矢量化的。然后,我们从idx列表中删除了大于重叠阈值的所有条目,重叠阈值的典型值通常落在0.3-0.5内。
Malisiewicz等人的方法基本上与Felzenszwalb等人提出的相同,但通过矢量化代码,可以将该算法的速度提升100倍。
快速非最大抑制算法的应用
下面我们来看几个例子,先从这个让人毛骨悚然的僵尸女孩开始:

可以发现有趣的一点,用健康人脸训练出的人脸检测器对僵尸同样适用,因为它们仍然是“人”的面孔,只是充满血污和缺陷,这样的结果并不令人惊讶。


最后一个例子中,我们可以再次看到,我们的非最大抑制算法工作正常——HOG +线性SVM检测器检测到六个原始边界框,非最大抑制算法正确抑制了其中五个边界框,得到了最终检测结果。
总结
在这篇博文中,我们回顾了Malisiewicz等人提出的非最大抑制算法,这种算法几乎与Felzenszwalb等人提出的相同,但通过去除内循环并使用向量化代码,使运算速度得到了显著的提升。
如果你使用了该算法,一定要记得感谢Tomasz Malisiewicz博士!