一、Selective Search的提出背景、时间及作者
提出背景:
在计算机视觉领域,目标检测是一项重要的任务,它要求从图像中识别和定位出感兴趣的目标。然而,传统的滑动窗口方法在处理大规模图像时效率低下,且生成的候选区域数量过多,导致计算复杂度高。因此,需要一种更高效的算法来生成高质量的候选区域,以提高目标检测的效率和准确性。Selective Search算法正是在这样的背景下被提出的。
提出时间:
Selective Search算法由Adobe实验室的Hadi Arbelaez和他的团队在2011年提出,并在2013年的IJCV(International Journal of Computer Vision)上发表了相关论文《Selective Search for Object Recognition》。
作者:
主要作者包括Hadi Arbelaez等人,他们来自Adobe实验室,并在计算机视觉领域有着深入的研究和贡献。
二、算法原理
Selective Search算法的原理是基于图的分割和区域合并技术。它首先使用一种高效的图像分割算法(如Felzenszwalb and Huttenlocher算法)将图像划分为许多初始的小区域。然后,根据区域之间的相似度(包括颜色、纹理、大小和形状等特征)逐步合并这些小区域,形成较大的、具有相似特征的图像区域。在合并过程中,算法会考虑多种策略和规则,以确保生成的候选区域既具有多样性又具有较高的质量。
Selective Search 主要思想:
- 使用一种过分割手段,将图像分割成小区域 (1k~2k 个)
- 查看现有小区域,按照合并规则合并可能性最高的相邻两个区域。重复直到整张图像合并成一个区域位置
- 输出所有曾经存在过的区域,所谓候选区域
其中合并规则如下: 优先合并以下四种区域:
- 颜色(颜色直方图)相近的
- 纹理(梯度直方图)相近的
- 合并后总面积小的: 保证合并操作的尺度较为均匀,避免一个大区域陆续“吃掉”其他小区域 (例:设有区域a-b-c-d-e-f-g-h。较好的合并方式是:ab-cd-ef-gh -> abcd-efgh -> abcdefgh。 不好的合并方法是:ab-c-d-e-f-g-h ->abcd-e-f-g-h ->abcdef-gh -> abcdefgh)
- 合并后,总面积在其BBOX中所占比例大的: 保证合并后形状规则。
上述四条规则只涉及区域的颜色直方图、梯度直方图、面积和位置。合并后的区域特征可以直接由子区域特征计算而来,速度较快。
三、网络结构
需要注意的是,Selective Search算法本身并不涉及深度学习中的“网络结构”概念。它是一个基于传统图像处理技术的算法,主要通过图论和聚类等方法来实现。然而,在深度学习框架中,Selective Search算法可以作为预处理步骤,与卷积神经网络(CNN)等深度学习模型结合使用,以提高目标检测的效率和准确性。
在Fast R-CNN的选择性搜算(Selective Search))算法
SelectiveSearch在Fast R-CNN中的位置
- 一张图像中通过选择性搜算(Selective Search)算法生成1K-2K的候选区域;
-
将图像输入到深度神经网络
VGG16
中,得到相应的特征图,将SS
算法生成的候选框投影到特征图上相应的特征矩阵; -
将每个特征矩阵通过
ROI Pooling
层缩放到7*7
大小特征图,并将特征图展平通过一全连接层得到预测结果;
选择性搜算(Selective Search)算法来源于一片名为《Selective Search for Object Recognition》的论文,selective search 算法首先需要一个基于像素的图像分割。这里用的是 Felzenszwalb and Huttenlocher 算法 (因为是当时速度最快的算法,而且是公开的),得到一个 oversegmented 的图像分割。
四、实现步骤
- 初始区域生成:
- 算法首先使用一种基于图的图像分割方法(如“Felzenswalb and Huttenlocher”算法)将图像划分为许多初始的小区域。
- 这些小区域是后续合并操作的基础。
- 相似度计算:
- 对于每两个相邻的区域,算法计算它们之间的相似度。
- 相似度的计算通常基于多种特征,包括颜色、纹理、大小和形状等。
- 具体的相似度度量方法可能包括颜色直方图的相似度、纹理特征的相似度、区域大小的相似度以及区域之间填充度的相似度等。
纹理相似度(texture similarity)
采用方差为1的高斯分布在8个方向做梯度统计,然后将统计结果(尺寸与区域大小一致)以bins=10计算直方图。直方图区间数为8310=240(使用RGB色彩空间).
颜色相似度(color similarity)
将色彩空间转为HSV,每个通道下以bins=25计算直方图,这样每个区域的颜色直方图有25*3=75个区间。对直方图除以区域尺寸做归一化后使用下式计算相似度:
尺寸相似度(size similarity)
!! 保证合并操作的尺度较为均匀,避免一个大区域陆续“吃掉”其他小区域。例:设有区域a-b-c-d-e-f-g-h。较好的合并方式是:ab-cd-ef-gh -> abcd-efgh -> abcdefgh。不好的合并方法是:ab-c-d-e-f-g-h ->abcd-e-f-g-h ->abcdef-gh -> abcdefgh。
交叠相似度(shape compatibility measure)
最终的相似度
- 区域合并:
- 根据相似度计算结果,算法将相似度最高的两个相邻区域合并成一个新的区域。
- 合并后,重新计算新区域与其相邻区域的相似度,并将新区域加入到待合并的区域列表中。
- 重复上述合并过程,直到达到某个停止条件(如所有区域都被合并成一个区域,或者达到预设的区域数量)。
- 候选区域生成:
- 在合并过程中,算法会记录下每次合并操作后产生的区域边界。
- 这些边界可以作为候选区域,用于后续的目标检测或识别任务。
五、算法特点
- 多尺度适应性:
- Selective Search算法通过不断合并小区域来生成大区域,从而能够适应不同尺度的物体。
- 这种层次性的合并策略有助于捕捉到图像中各种尺度的目标。
- 多样性:
- 算法结合了多种特征(如颜色、纹理、大小等)来计算区域之间的相似度,从而提高了候选区域的多样性。
- 多样性的候选区域有助于提高后续目标检测或识别的准确性和鲁棒性。
- 快速计算:
- 通过在合并过程中重用已计算的特征信息,算法能够快速地生成候选区域。
- 这使得Selective Search算法在实际应用中具有较高的效率。
六、优缺点
优点:
- 生成的候选区域质量高,有利于后续的目标检测任务。
- 相比滑动窗口方法,计算效率更高,能够处理大规模图像。
- 具有较强的可扩展性,可以根据实际需求进行定制和优化。
缺点:
- 算法参数较多,需要仔细调整以获得最佳效果。
- 在某些复杂场景下,可能无法准确分割出目标区域。
七、pytorch实现
selectivesearch.py
# -*- coding: utf-8 -*-
from __future__ import division
import skimage.io
import skimage.feature
import skimage.color
import skimage.transform
import skimage.util
import skimage.segmentation
import numpy
# "Selective Search for Object Recognition" by J.R.R. Uijlings et al.
#
# - Modified version with LBP extractor for texture vectorization
def _generate_segments(im_orig, scale, sigma, min_size):
"""
segment smallest regions by the algorithm of Felzenswalb and
Huttenlocher
"""
# open the Image
im_mask = skimage.segmentation.felzenszwalb(
skimage.util.img_as_float(im_orig), scale=scale, sigma=sigma,
min_size=min_size)
# merge mask channel to the image as a 4th channel
im_orig = numpy.append(
im_orig, numpy.zeros(im_orig.shape[:2])[:, :, numpy.newaxis], axis=2)
im_orig[:, :, 3] = im_mask
return im_orig
def _sim_colour(r1, r2):
"""
calculate the sum of histogram intersection of colour
"""
return sum([min(a, b) for a, b in zip(r1["hist_c"], r2["hist_c"])])
def _sim_texture(r1, r2):
"""
calculate the sum of histogram intersection of texture
"""
return sum([min(a, b) for a, b in zip(r1["hist_t"], r2["hist_t"])])
def _sim_size(r1, r2, imsize):
"""
calculate the size similarity over the image
"""
return 1.0 - (r1["size"] + r2["size"]) / imsize
def _sim_fill(r1, r2, imsize):
"""
calculate the fill similarity over the image
"""
bbsize = (
(max(r1["max_x"], r2["max_x"]) - min(r1["min_x"], r2["min_x"]))
* (max(r1["max_y"], r2["max_y"]) - min(r1["min_y"], r2["min_y"]))
)
return 1.0 - (bbsize - r1["size"] - r2["size"]) / imsize
def _calc_sim(r1, r2, imsize):
return (_sim_colour(r1, r2) + _sim_texture(r1, r2)
+ _sim_size(r1, r2, imsize) + _sim_fill(r1, r2, imsize))
def _calc_colour_hist(img):
"""
calculate colour histogram for each region
the size of output histogram will be BINS * COLOUR_CHANNELS(3)
number of bins is 25 as same as [uijlings_ijcv2013_draft.pdf]
extract HSV
"""
BINS = 25
hist = numpy.array([])
for colour_channel in (0, 1, 2):
# extracting one colour channel
c = img[:, colour_channel]
# calculate histogram for each colour and join to the result
hist = numpy.concatenate(
[hist] + [numpy.histogram(c, BINS, (0.0, 255.0))[0]])
# L1 normalize
hist = hist / len(img)
return hist
def _calc_texture_gradient(img):
"""
calculate texture gradient for entire image
The original SelectiveSearch algorithm proposed Gaussian derivative
for 8 orientations, but we use LBP instead.
output will be [height(*)][width(*)]
"""
ret = numpy.zeros((img.shape[0], img.shape[1], img.shape[2]))
for colour_channel in (0, 1, 2):
ret[:, :, colour_channel] = skimage.feature.local_binary_pattern(
img[:, :, colour_channel], 8, 1.0)
return ret
def _calc_texture_hist(img):
"""
calculate texture histogram for each region
calculate the histogram of gradient for each colours
the size of output histogram will be
BINS * ORIENTATIONS * COLOUR_CHANNELS(3)
"""
BINS = 10
hist = numpy.array([])
for colour_channel in (0, 1, 2):
# mask by the colour channel
fd = img[:, colour_channel]
# calculate histogram for each orientation and concatenate them all
# and join to the result
hist = numpy.concatenate(
[hist] + [numpy.histogram(fd, BINS, (0.0, 1.0))[0]])
# L1 Normalize
hist = hist / len(img)
return hist
def _extract_regions(img):
R = {}
# get hsv image
hsv = skimage.color.rgb2hsv(img[:, :, :3])
# pass 1: count pixel positions
for y, i in enumerate(img):
for x, (r, g, b, l) in enumerate(i):
# initialize a new region
if l not in R:
R[l] = {
"min_x": 0xffff, "min_y": 0xffff,
"max_x": 0, "max_y": 0, "labels": [l]}
# bounding box
if R[l]["min_x"] > x:
R[l]["min_x"] = x
if R[l]["min_y"] > y:
R[l]["min_y"] = y
if R[l]["max_x"] < x:
R[l]["max_x"] = x
if R[l]["max_y"] < y:
R[l]["max_y"] = y
# pass 2: calculate texture gradient
tex_grad = _calc_texture_gradient(img)
# pass 3: calculate colour histogram of each region
for k, v in list(R.items()):
# colour histogram
masked_pixels = hsv[:, :, :][img[:, :, 3] == k]
R[k]["size"] = len(masked_pixels / 4)
R[k]["hist_c"] = _calc_colour_hist(masked_pixels)
# texture histogram
R[k]["hist_t"] = _calc_texture_hist(tex_grad[:, :][img[:, :, 3] == k])
return R
def _extract_neighbours(regions):
def intersect(a, b):
if (a["min_x"] < b["min_x"] < a["max_x"]
and a["min_y"] < b["min_y"] < a["max_y"]) or (
a["min_x"] < b["max_x"] < a["max_x"]
and a["min_y"] < b["max_y"] < a["max_y"]) or (
a["min_x"] < b["min_x"] < a["max_x"]
and a["min_y"] < b["max_y"] < a["max_y"]) or (
a["min_x"] < b["max_x"] < a["max_x"]
and a["min_y"] < b["min_y"] < a["max_y"]):
return True
return False
R = list(regions.items())
neighbours = []
for cur, a in enumerate(R[:-1]):
for b in R[cur + 1:]:
if intersect(a[1], b[1]):
neighbours.append((a, b))
return neighbours
def _merge_regions(r1, r2):
new_size = r1["size"] + r2["size"]
rt = {
"min_x": min(r1["min_x"], r2["min_x"]),
"min_y": min(r1["min_y"], r2["min_y"]),
"max_x": max(r1["max_x"], r2["max_x"]),
"max_y": max(r1["max_y"], r2["max_y"]),
"size": new_size,
"hist_c": (
r1["hist_c"] * r1["size"] + r2["hist_c"] * r2["size"]) / new_size,
"hist_t": (
r1["hist_t"] * r1["size"] + r2["hist_t"] * r2["size"]) / new_size,
"labels": r1["labels"] + r2["labels"]
}
return rt
def selective_search(
im_orig, scale=1.0, sigma=0.8, min_size=50):
'''Selective Search
Parameters
----------
im_orig : ndarray
Input image
scale : int
Free parameter. Higher means larger clusters in felzenszwalb segmentation.
sigma : float
Width of Gaussian kernel for felzenszwalb segmentation.
min_size : int
Minimum component size for felzenszwalb segmentation.
Returns
-------
img : ndarray
image with region label
region label is stored in the 4th value of each pixel [r,g,b,(region)]
regions : array of dict
[
{
'rect': (left, top, width, height),
'labels': [...],
'size': component_size
},
...
]
'''
assert im_orig.shape[2] == 3, "3ch image is expected"
# load image and get smallest regions
# region label is stored in the 4th value of each pixel [r,g,b,(region)]
img = _generate_segments(im_orig, scale, sigma, min_size)
if img is None:
return None, {}
imsize = img.shape[0] * img.shape[1]
R = _extract_regions(img)
# extract neighbouring information
neighbours = _extract_neighbours(R)
# calculate initial similarities
S = {}
for (ai, ar), (bi, br) in neighbours:
S[(ai, bi)] = _calc_sim(ar, br, imsize)
# hierarchal search
while S != {}:
# get highest similarity
i, j = sorted(S.items(), key=lambda i: i[1])[-1][0]
# merge corresponding regions
t = max(R.keys()) + 1.0
R[t] = _merge_regions(R[i], R[j])
# mark similarities for regions to be removed
key_to_delete = []
for k, v in list(S.items()):
if (i in k) or (j in k):
key_to_delete.append(k)
# remove old similarities of related regions
for k in key_to_delete:
del S[k]
# calculate similarity set with the new region
for k in [a for a in key_to_delete if a != (i, j)]:
n = k[1] if k[0] in (i, j) else k[0]
S[(t, n)] = _calc_sim(R[t], R[n], imsize)
regions = []
for k, r in list(R.items()):
regions.append({
'rect': (
r['min_x'], r['min_y'],
r['max_x'] - r['min_x'], r['max_y'] - r['min_y']),
'size': r['size'],
'labels': r['labels']
})
return img, regions
test.py
# -*- coding: utf-8 -*-
from __future__ import (
division,
print_function,
)
import skimage.data
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import selectivesearch
def main():
# loading astronaut image
img = skimage.data.astronaut()
# perform selective search
img_lbl, regions = selectivesearch.selective_search(
img, scale=500, sigma=0.9, min_size=10)
candidates = set()
for r in regions:
# excluding same rectangle (with different segments)
if r['rect'] in candidates:
continue
# excluding regions smaller than 2000 pixels
if r['size'] < 2000:
continue
# distorted rects
x, y, w, h = r['rect']
if w / h > 1.2 or h / w > 1.2:
continue
candidates.add(r['rect'])
# draw rectangles on the original image
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
ax.imshow(img)
for x, y, w, h in candidates:
print(x, y, w, h)
rect = mpatches.Rectangle(
(x, y), w, h, fill=False, edgecolor='red', linewidth=1)
ax.add_patch(rect)
plt.show()
if __name__ == "__main__":
main()
八、应用场景
Selective Search算法在目标检测、图像分割、语义分割等计算机视觉任务中得到了广泛应用。特别是在深度学习模型中,它常作为预处理步骤来生成高质量的候选区域,以提高模型的检测效率和准确性。此外,它还可以用于图像理解、视觉搜索等领域,为图像分析和处理提供有力的支持。
参考: