Selective Search
在目标检测学习系列的文章中,很多检测算法都会涉及到Selective Search的使用,比如R-CNN。本文介绍Selective Search
参考:
- https://zhuanlan.zhihu.com/p/39927488
- https://learnopencv.com/selective-search-for-object-detection-cpp-python/
简介
Selective Search是一种用于目标检测的区域建议算法。它的设计速度快,召回率高。它基于颜色、纹理、大小和形状相容性计算相似区域的层次分组。
Selective Search首先使用Felzenszwalb和Huttenlocher的基于图的分割方法,根据像素的强度对图像进行过分割。算法输出如下图所示。右边的图像包含用纯色表示的分割区域。
Selective Search方法主要有三个优势: 捕捉不同尺度(Capture All Scales)、多样化(Diversification)、快速计算(Fast to Compute)总结为:选择性搜索是用于目标检测的区域提议算法,它计算速度快,具有很高的召回率,基于颜色,纹理,大小和形状兼容计算相似区域的分层分组。Selective Search算法主要包含两个内容:Hierarchical Grouping Algorithm、Diversification Strategies。
Hierarchical Grouping Algorithm
图像中区域特征比像素更具代表性,HGA产生图像初始区域,使用贪心算法对区域进行迭代分组:
- 计算所有邻近区域之间的相似性;
- 两个最相似的区域被组合在一起;
- 计算合并区域和相邻区域的相似度;
- 重复2、3过程,直到整个图像变为一个地区。
在每次迭代中,形成更大的区域并将其添加到区域提议列表中。以自下而上的方式创建从较小的细分segments到较大细分segments的区域提案,如下图。
Diversification Strategies
这个部分讲述作者提到的多样性的一些策略,使得抽样多样化,主要有下面三个不同方面:
- 利用各种不同不变性的色彩空间;
- 采用不同的相似性度量;
- 改变起始区域
相似性的度量指标:
1. 颜色相似度
这里用25个bin的颜色直方图,对于3通道的图片,就是\(25*3=75\)个bin
\(s_{color}(r_i, r_j) = \sum_{k=1}^n min(c^k_i, c^k_j)\)
这里\(c_i^k\)是第k个bin的直方图描述子
2. 纹理相似度
纹理特征通过提取每个通道在8个方向上的高斯导数来计算。对于每个方向和每个颜色通道,一个10-bin直方图被计算成一个10x8x3 = 240维特征描述符。
利用直方图相交计算两个区域的纹理相似度。
\(s_{texture}(r_i, r_j) = \sum_{k=1}^n min(t^k_i, t^k_j)\)
\(t^k_i\)是纹理描述符中\(k^{th}\) bin的直方图值
3. 尺寸相似度
大小相似性鼓励较小的区域尽早合并。它确保在图像的所有部分形成所有尺度的区域建议。如果不考虑这种相似性度量,单个区域将会一个接一个地吞噬所有较小的相邻区域,因此只会在这个位置产生多个尺度的区域建议。大小相似度定义为:
\(s_{size}(r_i, r_j) = 1 - \frac{size(r_i) + size(r_j)}{size(im)}\)
其中size(im)为图像像素大小。
4. 形状重合度
形状相容性衡量两个区域(\(r_i\)和\(r_j\))相互适应的程度。如果\(r_i\)适合\(r_j\),我们想要合并它们以填补空白,如果它们甚至没有相互接触,它们就不应该被合并。
形状相容性定义为:
\(s_{fill}(r_i, r_j) = 1 - \frac{size(BB_{ij}) - size(r_i) - size(r_j)}{size(im)}\)
其中\(size(BB{ij})\)是一个围绕\(r_i\)和\(r_j\)的边界框。
5. 最终重合度
最终重合度是上述4个重合度加权的结果:
\((r_i, r_j) = a_1s_{color}(r_i, r_j) + a_2s_{texture}(r_i, r_j) + a_3s_{size}(r_i, r_j)+ a_4s_{fill}(r_i, r_j)\)
python-opencv中的应用
opencv自从3.3.0集成了Selective Search,代码:
#!/usr/bin/env python
'''
Usage:
./ssearch.py input_image (f|q)
f=fast, q=quality
Use "l" to display less rects, 'm' to display more rects, "q" to quit.
'''
import sys
import cv2
if __name__ == '__main__':
# If image path and f/q is not passed as command
# line arguments, quit and display help message
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
# speed-up using multithreads
cv2.setUseOptimized(True);
cv2.setNumThreads(4);
# read image
im = cv2.imread(sys.argv[1])
# resize image
newHeight = 200
newWidth = int(im.shape[1]*200/im.shape[0])
im = cv2.resize(im, (newWidth, newHeight))
# create Selective Search Segmentation Object using default parameters
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
# set input image on which we will run segmentation
ss.setBaseImage(im)
# Switch to fast but low recall Selective Search method
if (sys.argv[2] == 'f'):
ss.switchToSelectiveSearchFast()
# Switch to high recall but slow Selective Search method
elif (sys.argv[2] == 'q'):
ss.switchToSelectiveSearchQuality()
# if argument is neither f nor q print help message
else:
print(__doc__)
sys.exit(1)
# run selective search segmentation on input image
rects = ss.process()
print('Total Number of Region Proposals: {}'.format(len(rects)))
# number of region proposals to show
numShowRects = 100
# increment to increase/decrease total number
# of reason proposals to be shown
increment = 50
while True:
# create a copy of original image
imOut = im.copy()
# itereate over all the region proposals
for i, rect in enumerate(rects):
# draw rectangle for region proposal till numShowRects
if (i < numShowRects):
x, y, w, h = rect
cv2.rectangle(imOut, (x, y), (x+w, y+h), (0, 255, 0), 1, cv2.LINE_AA)
else:
break
# show output
cv2.imshow("Output", imOut)
# record key press
k = cv2.waitKey(0) & 0xFF
# m is pressed
if k == 109:
# increase total number of rectangles to show by increment
numShowRects += increment
# l is pressed
elif k == 108 and numShowRects > increment:
# decrease total number of rectangles to show by increment
numShowRects -= increment
# q is pressed
elif k == 113:
break
# close image show window
cv2.destroyAllWindows()
效果: