快速非最大抑制算法Python版

本文译自:https://www.pyimagesearch.com/2015/02/16/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

# Malisiewicz et al.
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]

	# compute the area of the bounding boxes and sort the bounding
	# boxes by the bottom-right y-coordinate of the bounding box
	area = (x2 - x1 + 1) * (y2 - y1 + 1)
	idxs = np.argsort(y2)

	# 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]]

		# delete all indexes from the index list that have
		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].astype("int")

代码看起来似乎相同对吧?所以你肯定会问,“这个100倍速度从何而来?”

答案是删除了一个内部for循环。

上周的算法实现需要一个额外的内循环来计算边界区域的大小并计算重叠区域的比例,而Malisiewicz博士则使用了矢量化代码取而代之。不像上周的代码那样逐行重复检验,这次我们只考察有用的部分。

6-22行与上周代码基本相同,首先获得边界框的(x,y)坐标,计算其面积,并根据每个框的右下角y坐标在boxes列表中建立索引。

31-55行是算法提速的关键,41-55行尤为重要。

我们使用np.maximumnp.minimum函数向量化代码以替代使用for循环遍历每个单独的边界框——这使我们能够在轴上找到最大值和最小值,而不仅仅是单个标量。

注意:np.maximumnp.minimum函数允许你混合标量和矢量,而np.maxnp.min函数不可以,使用这俩函数可能产生许多bug,为了将算法从MATLAB移植到Python,我花了我很长时间来调试这个问题。

第47和48行也是矢量化的,这里我们计算每个要检查的矩形的宽度和高度。类似地,第51行计算重叠率也是矢量化的。然后,我们从idx列表中删除了大于重叠阈值的所有条目,重叠阈值的典型值通常落在0.3-0.5内。

Malisiewicz等人的方法基本上与Felzenszwalb等人提出的相同,但通过矢量化代码,可以将该算法的速度提升100倍。

快速非最大抑制算法的应用

下面我们来看几个例子,先从这个让人毛骨悚然的僵尸女孩开始:

快速非最大抑制算法举例
图一、图像中检测出三个边界框,非最大抑制算法去除了其中的两个

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

非最大抑制算法举例
图二、看起来我们的人脸检测误差还是很大的,它将僵尸的牙齿当成了人脸,如果我们用僵尸图片来训练HOG+线性SVM检测器,效果可能会好一些
非最大抑制算法举例
图三、应用非最大抑制6个边界框缩减为1个

最后一个例子中,我们可以再次看到,我们的非最大抑制算法工作正常——HOG +线性SVM检测器检测到六个原始边界框,非最大抑制算法正确抑制了其中五个边界框,得到了最终检测结果。

总结

在这篇博文中,我们回顾了Malisiewicz等人提出的非最大抑制算法,这种算法几乎与Felzenszwalb等人提出的相同,但通过去除内循环并使用向量化代码,使运算速度得到了显著的提升。

如果你使用了该算法,一定要记得感谢Tomasz Malisiewicz博士!