前言:主要包括HOG原理简介、代码实现、opencv相应库函数的调用
参考
文章目录
0. 相关基本概念
图像特征
- 颜色特征:RGB颜色空间,HSV颜色空间。颜色直方图、颜色矩
- 形状特征:包含着轮廓特征,区域特征。SIFT、HOG
- 纹理特征:无法通过纹理特征获得高层次图像内容。
- 边缘特征:形状特征中的轮廓特征。常用算法 canny、sobel
对图像的处理,尤其是区域分割或模式识别,基本上都逃脱不了图像特征提取,提取出感兴趣的部分(ROI),加以分析和后续处理。
HOG特征就是用来描述图片特征用于物体检测的特征描述子。它是通过计算和统计图像的局部区域的梯度方向直方图来构建特征。
Q:为何能够有效地描述某些图像特征?
首先,图像从计算机的角度来看,都是一个个离散的点组成的数据矩阵。数字的大小表征着数字信息。图像处理,就是在这些数字基础上进行一系列的操作。可以用函数 f(x, y)表示图像矩阵x行y列的数值(像素值)。
图像梯度
在微积分中,对于二维函数的偏微分
∂
f
(
x
,
y
)
∂
x
=
lim
δ
→
0
f
(
x
+
δ
,
y
)
−
f
(
x
,
y
)
δ
∂
f
(
x
,
y
)
∂
y
=
lim
δ
→
0
f
(
x
,
y
+
δ
)
−
f
(
x
,
y
)
δ
\frac{\partial f(x,y)}{\partial x}=\lim\limits_{\delta\rightarrow0} \frac{f(x+\delta,y)-f(x,y)}{\delta} \\ \frac{\partial f(x,y)}{\partial y}=\lim\limits_{\delta\rightarrow0} \frac{f(x,y+\delta)-f(x,y)}{\delta} \\
∂x∂f(x,y)=δ→0limδf(x+δ,y)−f(x,y)∂y∂f(x,y)=δ→0limδf(x,y+δ)−f(x,y)
图像是离散的二维函数,
δ
\delta
δ取最小值为1像素。因此有
∂
f
(
x
,
y
)
∂
x
=
f
(
x
+
1
,
y
)
−
f
(
x
,
y
)
=
G
(
x
)
∂
f
(
x
,
y
)
∂
y
=
f
(
x
,
y
+
1
)
−
f
(
x
,
y
)
=
G
(
y
)
\frac{\partial f(x,y)}{\partial x}=f(x+1,y)-f(x,y) =G(x)\\ \frac{\partial f(x,y)}{\partial y}=f(x,y+1)-f(x,y) =G(y)
∂x∂f(x,y)=f(x+1,y)−f(x,y)=G(x)∂y∂f(x,y)=f(x,y+1)−f(x,y)=G(y)
而这两个式子就是图像的梯度了,分别为图像点(x, y)在x方向和y方向的梯度G(x), G(y)。
但对一个点,上述式子是使用后驱点与该点差值,更一般情况是同时考虑该点前后点,取均值,即
G
(
x
)
=
[
f
(
x
+
1
,
y
)
−
f
(
x
,
y
)
]
−
[
f
(
x
,
y
)
−
f
(
x
−
1
,
y
)
]
2
=
f
(
x
+
1
,
y
)
−
f
(
x
−
1
,
y
)
2
G(x)=\frac{[f(x+1,y)-f(x,y)]-[f(x,y)-f(x-1,y)]}{2}=\frac{f(x+1,y)-f(x-1,y)}{2}
G(x)=2[f(x+1,y)−f(x,y)]−[f(x,y)−f(x−1,y)]=2f(x+1,y)−f(x−1,y)
分母为定值,对结果无关紧要,略去,因此
G
(
x
)
=
f
(
x
+
1
,
y
)
−
f
(
x
−
1
,
y
)
G
(
y
)
=
f
(
x
,
y
+
1
)
−
f
(
x
,
y
−
1
)
G(x)=f(x+1,y)-f(x-1,y) \\ G(y)=f(x,y+1)-f(x,y-1)
G(x)=f(x+1,y)−f(x−1,y)G(y)=f(x,y+1)−f(x,y−1)
图像梯度表征了图像像素值的变化快慢,梯度计算对噪声十分敏感。很明显,对于一些图像中轮廓边缘,图像梯度就会很大,对于相似区域,图像梯度很小。因此很多边缘检测算子都是基于对图像梯度的计算来实现对于边缘的检测。
补充知识:
-
梯度增强:是将原始图像每点的值叠加每点的图像梯度,以达到增强目的。
-
sobel算子:就是在上述基础上,附加考虑该点上下两行的六点,同时,赋予中间一行更多的权值。如下
方向梯度
上面是描述了两个方向上图像像素值的变化率。考虑把两个方向梯度结合起来,毕竟描述的图像上同一点。方法类似矢量合并。
幅
值
:
M
(
x
,
y
)
=
G
(
x
)
2
+
G
(
y
)
2
方
向
角
:
θ
=
t
a
n
−
1
G
(
y
)
G
(
x
)
幅值:M(x,y)= \sqrt {G(x)^2 + G(y)^2} \\ 方向角:\theta = tan^{-1} \frac{G(y)}{G(x)}
幅值:M(x,y)=G(x)2+G(y)2方向角:θ=tan−1G(x)G(y)
有时候为方便计算,幅值可以使用
M
(
x
,
y
)
=
∣
G
(
x
)
∣
+
∣
G
(
y
)
∣
M(x,y)=|G(x)|+|G(y)|
M(x,y)=∣G(x)∣+∣G(y)∣
1. HOG的基本原理与特征获取步骤
基本原理
图像局部梯度可以一定程度描述图像局部的外表和形状信息。例如,行人检测,利用对梯度信息的统计分析,提取包含表征人物轮廓信息的内容。
处理步骤
-
写在前面
对于一个图像处理,通常不是直接进行处理,而是采用滑动窗口的思想,从图像提取出部分,进行下面的HOG计算。
-
图像预处理
- 灰度化:颜色信息对任务没有很大意义
- 伽马矫正:提高图像对比度,减少光度对结果的影响
- 图像平滑:去除噪点
-
计算梯度和方向角
-
图像区域划分(cell),每个区域方向角等分,进行直方图统计
将图像化成小的单元(如 8x8),然后统计每个单元的梯度直方图。
统计方法:考虑根据梯度方向角 [ 0 , π ) [0,\pi) [0,π),将其划分若干份(行人检测中分为9就较为理想),然后统计属于某份的量。量的计算不是单纯地加一,而是使用该点的梯度幅值依据方向角加权,这样能取得较好效果。当然,也可以使用别的来表征,例如关于幅值的函数。
-
块划分(block),进行归一化计算,得到HOG特征
块划分是将上述单元组成更大的块(如 2cell x 2cell),然后对每个块数值进行归一化处理。其目的是为了消除局部光照、阴影的影响。
块结构通常有三种:矩形HOG(R-HOG/SIFT)、圆形HOG(C-HOG)和中心环绕HOG(Single centre C-HOG)
归一化方法通常有四种:
- L2-norm
f = v ∣ ∣ v ∣ ∣ 2 + ϵ f=\frac{v}{\sqrt{||v||^2 + \epsilon}} f=∣∣v∣∣2+ϵv
-
L2-Hys
先计算 L2-norm ,然后限制v的最大值为0.2,再重新归一化
-
L1-norm
f = v ∣ ∣ v ∣ ∣ + ϵ f=\frac{v}{||v|| + \epsilon} f=∣∣v∣∣+ϵv
- L1-sqrt
f = v ∣ ∣ v ∣ ∣ + ϵ f=\sqrt{\frac{v}{||v||+\epsilon}} f=∣∣v∣∣+ϵv
通常,L1-norm效果可能会欠佳一点点。
-
写在后面
通常将得到的HOG特征向量导入分类器(如 SVM),实现分类任务。
注意事项
!!!说明:下面细节为后面手写编程跟opencv中比较发现结果不太相同后,发现的细节问题。下叙手写编程仍有这方面细节问题,不再修改过来,为下次浏览时提醒自己。
-
cell的梯度直方图实现细节(映射到单一角度值对应的bin,而不采用范围bin,且是通过二次插值方式)
-
block滑动计算时,通常会使用重复的cell。该cell为前面未经归一化后的。
-
block滑动计算,是将HOG特征向量拼接。有理论上的重复部分。这涉及到最后特征向量大小的计算,重复部分切勿忘记。
2. HOG手写代码实现
使用Python实现
import cv2
import numpy as np
# 预处理
def preprocess(img_path):
img0 = cv2.imread(img_path)
img1 = cv2.cvtColor(img0, cv2.COLOR_BGR2GRAY)
h, w = img1.shape
img2 = cv2.resize(img1, (int(w), int(h)))
img3 = np.power(img2/float(np.max(img2)), 1)
#img = cv2.GaussianBlur(img3, (3,3), 0)
img = img3
return img
# 获取x,y方向梯度
def get_gradXY(img):
h, w = img.shape
img = np.pad(img, (1,1), 'edge')
gx = img[1:h+1, 2:] - img[1:h+1, :w]
gy = img[2:, 1:w+1] - img[:h, 1:w+1]
return gx,gy
# 获取幅值和方向角
def get_M_A(gx, gy):
magnitude = np.sqrt(gx ** 2 + gy ** 2)
gx[gx==0] = 1e-10 # 避免分母为0
angle = np.arctan(gy / gx)
angle[angle<0] = np.pi + angle[angle<0] # 负值转为正
return magnitude,angle
# 获得每个cell的梯度直方图统计
def get_cell_histogram(magnitude, angle, n=8):
h, w = magnitude.shape
cell_h = h // n
cell_w = w // n
cell_histogram = np.zeros((cell_h, cell_w, 9), dtype=np.float32)
for y in range(cell_h):
for x in range(cell_w):
for j in range(n):
for i in range(n):
channel = int(angle[y*n+i, x*n+j]//(np.pi/9))
cell_histogram[y, x, channel] += magnitude[y*n+i, x*n+j]
return cell_histogram
# 块划分,进行归一化计算
def normalization(histogram, c=2, epsilon=1):
h, w , channel= histogram.shape
for y in range(h):
for x in range(w):
histogram[y,x] /= np.sqrt(np.sum(histogram[max(y-1,0):min(y+2,h),max(x-1,0):min(x+2,w)]**2) + epsilon)
return histogram
# 可视化
def draw(img, histogram, N=8):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
g_h, g_w = gray.shape
H, W, C = histogram.shape
out = gray[1 : g_h + 1, 1 : g_w + 1].copy().astype(np.uint8)
for y in range(H):
for x in range(W):
cx = x * N + N // 2
cy = y * N + N // 2
x1 = cx + N // 2 - 1
y1 = cy
x2 = cx - N // 2 + 1
y2 = cy
h = histogram[y, x] / np.sum(histogram[y, x])
h /= h.max()
for c in range(9):
angle = (20 * c + 10) / 180. * np.pi
rx = int(np.sin(angle) * (x1 - cx) + np.cos(angle) * (y1 - cy) + cx)
ry = int(np.cos(angle) * (x1 - cx) - np.cos(angle) * (y1 - cy) + cy)
lx = int(np.sin(angle) * (x2 - cx) + np.cos(angle) * (y2 - cy) + cx)
ly = int(np.cos(angle) * (x2 - cx) - np.cos(angle) * (y2 - cy) + cy)
c = int(255. * h[c])
cv2.line(out, (lx, ly), (rx, ry), (c, c, c), thickness=1)
return out
if __name__ == '__main__':
img_path = '/home/zhangwei/data/image/1988.png'
img = preprocess(img_path)
gx, gy = get_gradXY(img)
m, a = get_M_A(gx, gy)
cell_histogram =get_cell_histogram(m, a)
histogram = normalization(cell_histogram)
img0 = cv2.imread(img_path)
img = draw(img0, histogram)
cv2.imshow('img', img)
cv2.waitKey()
结果展示
wuli珍珠
HOG可视化
3. 对应opencv库函数
说明
# 定义对象
hog = cv2.HOGDescriptor(
winSize, # 尺寸大小
blockSize, # 块大小
blockStride, # 块步长
cellSize, # 单元大小
nbins, # 箱数
derivAperture,
winSigma, # 高斯平滑窗口参数
hsitogramNormType, # 归一化方式
L2HysThreshold, # L2-Hys归一化阈值
gammaCorrecton, # 伽马矫正参数
nlevels # 检测窗口最大数
)
# 开始计算
res = hog.compute(img, winStride, padding)
result = res.reshape((-1,))
使用
import cv2
img_path = '/home/zhangwei/data/image/1988.png'
img = cv2.imread(img_path)[:256, :256, :]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
winSize = (128, 128)
blockSize = (16, 16)
blockStride = (8, 8)
cellSize = (8, 8)
nbins = 9
hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins)
winStride = (8, 8)
padding = (8, 8)
res = hog.compute(gray, padding)
print(res.shape)
# > (2340900,1)
特征数量计算
为了进一步理解HOG,手动计算下最终输出的HOG特征数量。跟上面代码输出的结果相比较。
- window数目: ( 128 8 + 1 ) × ( 128 8 + 1 ) = 289 (\frac{128}{8}+1)\times(\frac{128}{8}+1)=289 (8128+1)×(8128+1)=289
- 每个window中block数目: 128 − 8 8 × 128 − 8 8 = 225 \frac{128-8}{8}\times\frac{128-8}{8}=225 8128−8×8128−8=225
- 每个block中cell数目:4
- 每个cell中特征数量:9
因此最终结果:
r
e
s
.
s
h
a
p
e
=
289
×
225
×
4
×
9
=
2340900
res.shape = 289\times225\times4\times9=2340900
res.shape=289×225×4×9=2340900