文章目录
- 一.选择性搜索的具体算法
- 二.保持多样性的策略
- 三.给区域打分
- 四.选择性搜索性能评估
- 五.代码实现
论文地址: https://www.koen.me/research/selectivesearch/
代码地址: https://github.com/AlpacaDB/selectivesearch
参考: https://www.cnblogs.com/zyly/p/9259392.html
一.选择性搜索的具体算法
输入:(彩色)图片
输出:候选的目标位置集合
算法:
利用切分方法得到候选的区域集合R = {r1,r2,…,rn}
初始化相似集合S = ϕ
foreach 遍历邻居区域对(ri,rj) do
计算相似度s(ri,rj)
S = S ∪ s(ri,rj)
while S not=ϕ do
从S中得到最大的相似度s(ri,rj)=max(S)
合并对应的区域rt = ri ∪ rj
移除ri对应的所有相似度:S = S\s(ri,r*)
移除rj对应的所有相似度:S = S\s(r*,rj)
计算rt对应的相似度集合St
S = S ∪ St
R = R ∪ rt
L = R中所有区域对应的边框
首先通过基于图的图像分割方法初始化原始区域,就是将图像分割成很多很多的小块。然后我们使用贪心策略,计算每两个相邻的区域的相似度,然后每次合并最相似的两块,直到最终只剩下一块完整的图片。然后这其中每次产生的图像块包括合并的图像块我们都保存下来,这样就得到图像的分层表示了呢。那我们如何计算两个图像块的相似度呢?
二.保持多样性的策略
区域合并采用了多样性的策略,如果简单采用一种策略很容易错误合并不相似的区域,比如只考虑纹理时,不同颜色的区域很容易被误合并。选择性搜索采用三种多样性策略来增加候选区域以保证召回:
- 多种颜色空间,考虑RGB、灰度、HSV及其变种等
- 多种相似度度量标准,既考虑颜色相似度,又考虑纹理、大小、重叠情况等。
- 通过改变阈值初始化原始区域,阈值越大,分割的区域越少。
-
颜色空间变换
通过色彩空间变换,将原始色彩空间转换到多达八中的色彩空间。作者采用了8中不同的颜色方式,主要是为了考虑场景以及光照条件等。这个策略主要应用于图像分割算法中原始区域的生成(两个像素点的相似度计算时,计算不同颜色空间下的两点距离)。主要使用的颜色空间有:
(1)RGB
(2)灰度I
(3)Lab
(4)rgI(归一化的rg通道加上灰度)
(5)HSV
(6)rgb(归一化的RGB)
(7)C
(8)H(HSV的H通道)
-
区域相似度计算
我们在计算多种相似度的时候,都是把单一相似度的值归一化到[0,1]之间,1表示两个区域之间相似度最大。
-
颜色相似度
使用L1-norm归一化获取图像每个颜色通道的25 bins的直方图,这样每个区域都可以得到一个75维的向量 { c i 1 , ⋯ , c i n } \left\{c_{i}^{1}, \cdots, c_{i}^{n}\right\} {ci1,⋯,cin},区域之间颜色相似度通过下面的公式计算:
s colour ( r i , r j ) = ∑ k = 1 n min ( c i k , c j k ) s_{\text {colour }}\left(r_{i}, r_{j}\right)=\sum_{k=1}^{n} \min \left(c_{i}^{k}, c_{j}^{k}\right) scolour (ri,rj)=k=1∑nmin(cik,cjk)
上面这个公式可能你第一眼看过去看不懂,那咱们打个比方,由于 { c i 1 , ⋯ , c i n } \left\{c_{i}^{1}, \cdots, c_{i}^{n}\right\} {ci1,⋯,cin}是归一化后值,每一个颜色通道的直方图累加和为1.0,三个通道的累加和就为3.0,如果区域和区域直方图完全一样,则此时颜色相似度最大为3.0,如果不一样,由于累加取两个区域bin的最小值进行累加,当直方图差距越大,累加的和就会越小,即颜色相似度越小。
在区域合并过程中使用需要对新的区域进行计算其直方图,计算方法:
C t = size ( r i ) × C i + size ( r j ) × C j size ( r i ) + size ( r j ) C_{t}=\frac{\operatorname{size}\left(r_{i}\right) \times C_{i}+\operatorname{size}\left(r_{j}\right) \times C_{j}}{\operatorname{size}\left(r_{i}\right)+\operatorname{size}\left(\mathrm{r}_{j}\right)} Ct=size(ri)+size(rj)size(ri)×Ci+size(rj)×Cj
-
纹理相似度
这里的纹理采用SIFT-Like特征。具体做法是对每个颜色通道的8个不同方向计算方差σ=1的高斯微分(Gaussian Derivative),使用L1-norm归一化获取图像每个颜色通道的每个方向的10 bins的直方图,这样就可以获取到一个240(10x8x3)维的向量 T i = { t i 1 , ⋯ , t i n } T_{i}=\left\{t_{i}^{1}, \cdots, t_{i}^{n}\right\} Ti={ti1,⋯,tin},区域之间纹理相似度计算方式和颜色相似度计算方式类似,合并之后新区域的纹理特征计算方式和颜色特征计算相同:
s texture ( r i , r j ) = ∑ k = 1 n min ( t i k , t j k ) s_{\text {texture }}\left(r_{i}, r_{j}\right)=\sum_{k=1}^{n} \min \left(t_{i}^{k}, t_{j}^{k}\right) stexture (ri,rj)=k=1∑nmin(tik,tjk)
-
优先合并小的区域
如果仅仅是通过颜色和纹理特征合并的话,很容易使得合并后的区域不断吞并周围的区域,后果就是多尺度只应用在了那个局部,而不是全局的多尺度。因此我们给小的区域更多的权重,这样保证在图像每个位置都是多尺度的在合并。下面的公式表示,两个区域越小,其相似度越大,越接近1。
s size ( r i , r j ) = 1 − size ( r i ) + size ( r j ) size ( i m ) s_{\text {size }}\left(r_{i}, r_{j}\right)=1-\frac{\operatorname{size}\left(r_{i}\right)+\operatorname{size}\left(\mathrm{r}_{\mathrm{j}}\right)}{\operatorname{size}(\mathrm{im})} ssize (ri,rj)=1−size(im)size(ri)+size(rj)
-
区域的合适度距离
如果区域 r i r_{i} ri包含在 r j r_{j} rj内,我们首先应该合并,另一方面,如果 r i r_{i} ri很难与 r j r_{j} rj相接,他们之间会形成断崖,不应该合并在一块。这里定义区域的合适度距离主要是为了衡量两个区域是否更加“吻合”,其指标是合并后的区域的Bounding Box(能够框住区域的最小矩形 B B i j BB_{ij} BBij)越小,其吻合度越高,即相似度越接近1。其计算方式:
fill ( r i , r j ) = 1 − size ( B B i j ) − size ( r i ) − size ( r i ) size ( i m ) \operatorname{fill}\left(r_{i}, r_{j}\right)=1-\frac{\operatorname{size}\left(B B_{i j}\right)-\operatorname{size}\left(r_{i}\right)-\operatorname{size}\left(r_{i}\right)}{\operatorname{size}(i m)} fill(ri,rj)=1−size(im)size(BBij)−size(ri)−size(ri)
- 合并上面四种相似度,其中$a_{i} \in{0,1}
-
s ( r i , r j ) = a 1 s colour ( r i , r j ) + a 2 s texture ( r i , r j ) + a 3 s size ( r i , r j ) + a 4 s fill ( r i , r j ) \begin{aligned} s\left(r_{i}, r_{j}\right)=& a_{1} s_{\text {colour }}\left(r_{i}, r_{j}\right)+a_{2} s_{\text {texture }}\left(r_{i}, r_{j}\right)+\\ & a_{3} s_{\text {size }}\left(r_{i}, r_{j}\right)+a_{4} s_{\text {fill }}\left(r_{i}, r_{j}\right) \end{aligned} s(ri,rj)=a1scolour (ri,rj)+a2stexture (ri,rj)+a3ssize (ri,rj)+a4sfill (ri,rj)
三.给区域打分
通过上述的步骤我们能够得到很多很多的区域,但是显然不是每个区域作为目标的可能性都是相同的,因此我们需要衡量这个可能性,这样就可以根据我们的需要筛选区域建议个数啦。
这篇文章做法是,给予最先合并的图片块较大的权重,比如最后一块完整图像权重为1,倒数第二次合并的区域权重为2以此类推。但是当我们策略很多,多样性很多的时候呢,这个权重就会有太多的重合了,排序不好搞啊。文章做法是给他们乘以一个随机数,毕竟3分看运气嘛,然后对于相同的区域多次出现的也叠加下权重,毕竟多个方法都说你是目标,也是有理由的嘛。这样我就得到了所有区域的目标分数,也就可以根据自己的需要选择需要多少个区域了。
四.选择性搜索性能评估
自然地,通过算法计算得到的包含物体的Bounding Boxes与真实情况(ground truth)的窗口重叠越多,那么算法性能就越好。这时使用的指标是平均最高重叠率ABO(Average Best Overlap)。对于每个固定的类别 c,每个真实情况(ground truth)表示为 g i c ∈ G c g_{i}^{c} \in G^{c} gic∈Gc ,令计算得到的位置假设 L L L中的每个值 L j L_j Lj,那么 ABO的公式表达为:
A B O = 1 ∣ G c ∣ ∑ g i c ∈ G c max l j ∈ L O v e r l a p ( g i c , l j ) \mathrm{ABO}=\frac{1}{\left|G^{c}\right|} \sum_{g_{i}^{c} \in G^{c}} \max _{l_{j} \in L} \mathrm{Overlap}\left(g_{i}^{c}, l_{j}\right) ABO=∣Gc∣1gic∈Gc∑lj∈LmaxOverlap(gic,lj)
- 重叠率的计算方式:
Overlap ( g i c , l j ) = area ( g i c ) ∩ area ( l j ) area ( g i c ) ∪ area ( l j ) \text { Overlap }\left(g_{i}^{c}, l_{j}\right)=\frac{\operatorname{area}\left(g_{i}^{c}\right) \cap \operatorname{area}\left(\mathrm{l}_{\mathrm{j}}\right)}{\operatorname{area}\left(g_{i}^{c}\right) \cup \operatorname{area}\left(\mathrm{l}_{\mathrm{j}}\right)} Overlap (gic,lj)=area(gic)∪area(lj)area(gic)∩area(lj)
上面结果给出的是一个类别的ABO,对于所有类别下的性能评价,很自然就是使用所有类别的ABO的平均值MABO(Mean Average Best Overlap)来评价。
-
单一策略评估
我们可以通过改变多样性策略中的任何一种,评估选择性搜索的MABO性能指标。论文中采取的策略如下:
- 使用RGB色彩空间(基于图的图像分割会利用不同的色彩进行图像区域分割)
- 采用四种相似度计算的组合方式
- 设置图像分割的阈值k=50
然后通过改变其中一个策略参数,获取MABO性能指标如下表(第一列为改变的参数,第二列为MABO值,第三列为获取的候选区的个数):
表中左侧为不同的相似度组合,单独的,我们可以看到纹理相似度表现最差,MABO为0.581,其他的MABO值介于0.63和0.64之间。当使用多种相似度组合时MABO性能优于单种相似度。表的右上角表名使用HSV颜色空间,有463个候选区域,而且MABO值最大为0.693。表的右下角表名使用较小的阈值,会得到更多的候选区和较高的MABO值。
-
多样性策略组合
我们使用贪婪的搜索算法,把单一策略进行组合,会获得较高的MABO,但是也会造成计算成本的增加。下表给出了三种组合的MABO性能指标:
上图中的绿色边框为对象的标记边框,红色边框为我们使用 ‘Quality’ Selective Search算法获得的Overlap最高的候选框。可以看到我们这个候选框和真实标记非常接近。
下表为和其它算法在VOC 2007测试集上的比较结果:下图为各个算法在选取不同候选区数量,Recall和MABO性能的曲线图,从计算成本、以及性能考虑,Selective Search Fast算法在2000个候选区时,效果较好。
五.代码实现
import skimage.io
import skimage.feature
import skimage.color
import skimage.transform
import skimage.util
import skimage.segmentation
import numpy
def _generate_segments(im_orig, scale, sigma, min_size):
"""
通过(Felzenswalb and Huttenlocher)的算法来分割最小的区域
:param im_orig: ndarray 输入图像:(宽度,高度,3)或(宽度,高度)即灰度图或者rgb三通道彩色图
[[[...] [[...] [[...]
[...] [...] [...]
[...]] [...]] [...]]]
:param scale: float 设置观察级别,规模越大意味着越来越小的部分
:param sigma: float 高斯核的直径,用于在分割之前平滑图像
:param min_size: int 最小组件大小,使用后处理强制执行
:return: im_orig:
"""
# 使用(Felzenswalb and Huttenlocher)的算法来对图像进行初始的图像区域分割
im_mask = skimage.segmentation.felzenszwalb(
skimage.util.img_as_float(im_orig), scale=scale, sigma=sigma,
min_size=min_size)
# 将初始分割好的数据添加到图像数据中,图像数据成为4维数组
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):
"""
计算颜色相似度
:param r1:候选区域r1
:param r2:候选区域r2
:return:[0,3]之间的数值
"""
return sum([min(a, b) for a, b in zip(r1["hist_c"], r2["hist_c"])])
def _sim_texture(r1, r2):
"""
计算文理相似度
:param r1:候选区域r1
:param r2:候选区域r2
:return:[0,3]之间的数值
"""
return sum([min(a, b) for a, b in zip(r1["hist_t"], r2["hist_t"])])
def _sim_size(r1, r2, imsize):
"""
计算候选区域大小相似度
:param r1:候选区域r1
:param r2:候选区域r2
:param imsize:原图像像素数
:return:[0,1]之间的数值
"""
return 1.0 - (r1["size"] + r2["size"]) / imsize
def _sim_fill(r1, r2, imsize):
"""
计算候选区域的距离合适度相似度
:param r1:候选区域r1
:param r2:候选区域r2
:param imsize:原图像像素数
:return:[0,1]之间的数值
"""
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):
"""
计算两个候选区域的相似度,权重系数默认都是1
:param r1:候选区域r1
:param r2:候选区域r2
:param imsize:原图片像素数
:return:[0,1]之间的数值
"""
return (_sim_colour(r1, r2) + _sim_texture(r1, r2)
+ _sim_size(r1, r2, imsize) + _sim_fill(r1, r2, imsize))
def _calc_colour_hist(img):
"""
使用L1-norm归一化获取图像每个颜色通道的25 bins的直方图,这样每个区域都可以得到一个75维的向量
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
:param img:ndarray类型, 形状为候选区域像素数 x 3(h,s,v)
:return:一维的ndarray类型,长度为75
"""
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(*)]
:param img:
:return:
"""
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):
"""
提取每一个候选区域的信息 比如类别(region)为5的区域表示的是一只猫的选区,这里就是提取这只猫的边界框,左上角后右下角坐标
args:
img: ndarray类型,形状为height x width x 4,每一个像素的值为 [r,g,b,(region)]
return :
R:dict 每一个元素对应一个候选区域, 每个元素也是一个dict类型
{min_x:边界框的左上角x坐标,
min_y:边界框的左上角y坐标,
max_x:边界框的右下角x坐标,
max_y:边界框的右下角y坐标,
size:像素个数,
hist_c:颜色的直方图,
hist_t:纹理特征的直方图,}
"""
R = {}
hsv = skimage.color.rgb2hsv(img[:, :, :3]) # 获取hsv图像
# 1:获取每个像素点所在的区域
for y, i in enumerate(img):
for x, (r, g, b, l) in enumerate(i):
# 判断像素点所在区域是否已经添加,还没有添加的话创建一个新的区域
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
tex_grad = _calc_texture_gradient(img) # 2:计算文理梯度
# 3:计算每个区域的颜色直方图
for k, v in R.items():
# colour histogram
masked_pixels = hsv[:, :, :][img[:, :, 3] == k] # (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):
"""
提取 邻居候选区域对(ri,rj)(即两两相交)
args:
regions:dict 每一个元素都对应一个候选区域
return:
返回一个list,每一个元素都对应一个邻居候选区域对
"""
# 判断两个候选区域是否相交
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):
"""
合并两个候选区域
args:
r1:候选区域1
r2:候选区域2
return:
返回合并后的候选区域rt
"""
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):
"""
首先通过基于图的图像分割方法初始化原始区域,就是将图像分割成很多很多的小块
然后我们使用贪心策略,计算每两个相邻的区域的相似度
然后每次合并最相似的两块,直到最终只剩下一块完整的图片
然后这其中每次产生的图像块包括合并的图像块我们都保存下来
:param im_orig: ndarray 输入图像:(宽度,高度,3)或(宽度,高度)即灰度图或者rgb三通道彩色图
:param scale: float 设置观察级别,规模越大意味着越来越小的部分
:param sigma: float 高斯核的直径,用于在分割之前平滑图像
:param min_size: int 最小组件大小,使用后处理强制执行
:return: img : ndarray 包含区域信息的图像数据(宽度,高度,4)[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"
# 载入图像,并获取图像的最小分割区域信息
# region label is stored in the 4th value of each pixel [r,g,b,(region)]
# 图片分割 把候选区域标签合并到最后一个通道上 height x width x 4 每一个像素的值为[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) # dict类型,键值为候选区域的标签 值为候选区域的信息,包括候选区域的边框,以及区域的大小,颜色直方图,纹理特征直方图等信息
# extract neighbouring information
# list类型 每一个元素都是邻居候选区域对(ri,rj) (即两两相交的候选区域)
neighbours = _extract_neighbours(R)
# calculate initial similarities
S = {}
# 计算每一个邻居候选区域对的相似度s(ri,rj)
for (ai, ar), (bi, br) in neighbours:
# S=S∪s(ri,rj) ai表示候选区域ar的标签 比如当ai=1 bi=2 S[(1,2)就表示候选区域1和候选区域2的相似度
S[(ai, bi)] = _calc_sim(ar, br, imsize)
# hierarchal search 层次搜索 直至相似度集合为空
while S != {}:
# get highest similarity 获取相似度最高的两个候选区域 i,j表示候选区域标签
i, j = sorted(S.items(), key=lambda i: i[1])[-1][0] # 按照相似度排序
# merge corresponding regions 合并相似度最高的两个邻居候选区域 rt = ri∪rj ,R = R∪rt
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 S.items(): # k表示邻居候选区域对(i,j) v表示候选区域(i,j)表示相似度
if (i in k) or (j in k):
key_to_delete.append(k)
# remove old similarities of related regions 移除候选区域ri对应的所有相似度:S = S\s(ri,r*) 移除候选区域rj对应的所有相似度:S = S\s(r*,rj)
for k in key_to_delete:
del S[k]
# calculate similarity set with the new region 计算新的候选区域rt对应的相似度集合St,S = S∪St
for k in filter(lambda a: a != (i, j), key_to_delete): # 过滤除了(i,j)之外的候选区域
n = k[1] if k[0] in (i, j) else k[0]
# 计算新的候选区域t与候选区域n之间的相似度
S[(t, n)] = _calc_sim(R[t], R[n], imsize)
# 获取每一个候选区域的的信息 边框、以及候选区域size,标签
regions = []
for k, r in 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']
})
# img:ndarray 基于图的图像分割得到的候选区域 regions:list Selective Search算法得到的候选区域
return img, regions