[初窥目标检测]——《目标检测学习笔记(3):注解python package: selectivesearch源码》
介绍
前两篇博客初步介绍了selective search算法,这篇我们选取了系列1中selective search包的源码进行简单的分析。源码网址
话不多说,上车!
源码分析
# -*- 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
#需要引入skimage、numpy包
# "Selective Search for Object Recognition" by J.R.R. Uijlings et al.
#
# - Modified version with LBP extractor for texture vectorization
# 感谢J.R.R. Uijlings等人给出的 "Selective Search for Object Recognition" 论文
# 基于LBP提取器的纹理矢量化改进版本(译者注:_calc_texture_gradient函数中未使用论文中提到的高斯微分Gaussian derivative)
def _generate_segments(im_orig, scale, sigma, min_size):
"""
segment smallest regions by the algorithm of Felzenswalb and
Huttenlocher
"""
'''
function:基于Felzenswalb 和 Huttenlocher算法的最小区域分割
param:im_orig:原图像,scale, sigma, min_size为skimage.segmentation.felzenszwalb函数参数
output:合并掩码通道后的图像
'''
# 打开图像,使用Felzenszwalb算法生成图像掩码通道
im_mask = skimage.segmentation.felzenszwalb(
skimage.util.img_as_float(im_orig), scale=scale, sigma=sigma,
min_size=min_size)
# 把掩码通道合并成图像的第四通道
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):
"""
计算颜色直方图相交的和,即为上一篇博客中的公式(1)
"""
return sum([min(a, b) for a, b in zip(r1["hist_c"], r2["hist_c"])])
def _sim_texture(r1, r2):
"""
计算纹理直方图相交的和,即为上一篇博客中的公式(3)
"""
return sum([min(a, b) for a, b in zip(r1["hist_t"], r2["hist_t"])])
def _sim_size(r1, r2, imsize):
"""
计算大小相似度:这里的大小是指区域中包含像素点的个数。使用大小的相似度计算,主要是为了尽量让小的区域先合并。 即为上一篇博客中的公式(4)
"""
return 1.0 - (r1["size"] + r2["size"]) / imsize
def _sim_fill(r1, r2, imsize):
"""
计算吻合相似度。这里主要是为了衡量两个区域是否更加“吻合”。其指标是合并后的区域的Bounding Box(能够框住区域的最小矩形)越小,其吻合度越高。即为上一篇博客中的公式(5)
"""
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):
"""
计算最终相似度,即公式(6)。[?]在此的疑问是为何没加权。
"""
return (_sim_colour(r1, r2) + _sim_texture(r1, r2)
+ _sim_size(r1, r2, imsize) + _sim_fill(r1, r2, imsize))
def _calc_colour_hist(img):
"""
计算每个区域的颜色直方图
输出直方图的大小为BINS*色彩通道(3)
和论文相同,bins数量为25。
使用HSV颜色模式来提取。
"""
BINS = 25
#定义BINS数量为25
hist = numpy.array([])
#初始化hist
for colour_channel in (0, 1, 2):
# 提取一个通道
c = img[:, colour_channel]
#计算每个颜色的直方图并与结果连接
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):
"""
计算整个图像的纹理梯度
原有的论文中提出的是高斯微分提取8个方向,但是这里使用的是LBP(译者注:LBP指局部二值模式,英文全称:Local Binary Pattern,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点。)
"""
ret = numpy.zeros((img.shape[0], img.shape[1], img.shape[2]))
#初始化ret
for colour_channel in (0, 1, 2):
#遍历每一个通道
ret[:, :, colour_channel] = skimage.feature.local_binary_pattern(
img[:, :, colour_channel], 8, 1.0)
使用skimage中提取特征的feature包中LBP函数
return ret
def _calc_texture_hist(img):
"""
计算每个区域直方图的纹理
计算每个颜色的梯度直方图
输出直方图的大小将是BINS*方向*色通道(3)
"""
BINS = 10
#论文中BINS=10
hist = numpy.array([])
#初始化直方图
for colour_channel in (0, 1, 2):
#遍历每个通道
# 颜色通道掩码
fd = img[:, colour_channel]
# 计算每个方向的直方图并将它们连接并与结果连接。
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):
"""
function:提取区域
"""
R = {}
# 将图像从RGB模式转为HSV
hsv = skimage.color.rgb2hsv(img[:, :, :3])
# 步骤一:计数像素位置
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]}
# 圈框
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
# 步骤 2: 计算纹理梯度
tex_grad = _calc_texture_gradient(img)
# 步骤 3: 计算每个区域的颜色直方图
for k, v in list(R.items()):
# 颜色直方图
masked_pixels = hsv[:, :, :][img[:, :, 3] == k]
R[k]["size"] = len(masked_pixels / 4)
R[k]["hist_c"] = _calc_colour_hist(masked_pixels)
# 纹理直方图
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"]
# 上篇博客中的公式(size(t) = size(r1) + size(r2))
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,
#公式(2)合成新的色彩值
"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):
'''选择性搜索
参数
----------
im_orig : ndarray
输入图像
scale : int
可选参数,为Felzenszwalb分割算法中的一个参数,值越高意味着得到更多的候选框群
sigma : float
FelZeZnZWalb分割中的高斯核宽度
min_size : int
Felzenszwalb分割的最小分量大小。
输出
-------
img : ndarray
带有识别区域的图像
区域标记存储在每个像素的第四个值中[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"
# 载入图片并得到最小区域
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)
# 提取相邻信息
neighbours = _extract_neighbours(R)
#计算初始相似性度
S = {}
for (ai, ar), (bi, br) in neighbours:
S[(ai, bi)] = _calc_sim(ar, br, imsize)
# 分层搜索算法
while S != {}:
# 得到最高相似度
i, j = sorted(S.items(), key=lambda i: i[1])[-1][0]
# 合并对应区域
t = max(R.keys()) + 1.0
R[t] = _merge_regions(R[i], R[j])
# 标记待移除区域的相似性
key_to_delete = []
for k, v in list(S.items()):
if (i in k) or (j in k):
key_to_delete.append(k)
# 剔除相关区域的旧相似性
for k in key_to_delete:
del S[k]
# 计算新区域的相似集
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
结语
以上是python中selective search包的简略分析,可以与上一篇博客一起对比看,了解其算法的复现。在此关于selective search算法就到此结束,欢迎各位大佬前来斧正,下篇博客中对RCNN等算法进行简要介绍。
——Nuller
——写于2018.4.23中午