方向梯度直方图特征描述子(Histogram of Oriented Gradient, HOG)的主要思想是,图像中局部目标的外观和形状(Appearance and Shape)可以使用梯度以及边缘方向密度的分布进行表现。HOG的本质是统计图形边缘位置的梯度的分布信息。
读取样本图像;
对样本图像进行灰度变换;
使用Gamma校正算法对样本图像的颜色空间进行标准化(归一化)矫正,调节图像的对比度,抑制噪音干扰,降低图像局部阴影以及光照变化对图像特征值的影响;
计算图像每个像素的梯度;
将图像划分成小cells(如,6*6像素/cell);
统计所有细胞单元(cell)不同梯度的个数,生成每个cell的梯度直方图形成cell特征描述子;
将每几个cell组成一个区间(block)(如,3*3个cell/block),将每个block内部的所有cell特征描述子串联起来,组合得到该block的HOG特征描述子。
将样本图像内的所有block的HOG特征描述子串联起来,得到该样本图像的HOG特征向量。
图像中像素点的梯度为:
G x ( x , y ) = H ( x + 1 , y ) − H ( x − 1 , y ) G_x(x,y) = H(x+1,y)-H(x-1,y) Gx(x,y)=H(x+1,y)−H(x−1,y)
G y ( x , y ) = H ( x , y + 1 ) − H ( x , y − 1 ) G_y(x,y) = H(x,y+1)-H(x,y-1) Gy(x,y)=H(x,y+1)−H(x,y−1)
像素点 ( x , y ) (x,y) (x,y)处的梯度幅值以及梯度方向的计算公式如下:
G ( x , y ) = G x ( x , y ) 2 + G y ( x , y ) 2 G(x,y)=\sqrt{G_x(x,y) ^2+G_y(x,y)^2} G(x,y)=Gx(x,y)2+Gy(x,y)2
α ( x , y ) = tan − 1 G y ( x , y ) G x ( x , y ) \alpha(x,y) =\tan^{-1}\frac{G_y(x,y)}{G_x(x,y) } α(x,y)=tan−1Gx(x,y)Gy(x,y)
在实现中,可以使用 [ − 1 , 0 , 1 ] [-1,0,1] [−1,0,1]梯度算子对原始图像进行卷积,得到图像的水平方向梯度分量,用 [ 1 , 0 , − 1 ] T [1, 0, -1]^T [1,0,−1]T梯度算子对原始图像进行卷积,得到图像的垂直方向梯度分量。
优点:
核心思想是所检测的局部物体外形能够被梯度或边缘方向的分布所描述,HOG能较好地捕捉局部形状信息,对几何和光学变化都有很好的不变性;
HOG是在密集采样的图像块中求取的,在计算得到的HOG特征向量中隐含了该块与检测窗口之间的空间位置关系。
缺点:
很难处理遮挡问题,人体姿势动作幅度过大或物体方向改变也不易检测(这个问题后来在DPM中采用可变形部件模型的方法得到了改善);
跟SIFT相比,HOG没有选取主方向,也没有旋转梯度方向直方图,因而本身不具有旋转不变性(较大的方向变化),其旋转不变性是通过采用不同旋转方向的训练样本来实现的;
跟SIFT相比,HOG本身不具有尺度不变性,其尺度不变性是通过缩放检测窗口图像的大小来实现的;
此外,由于梯度的性质,HOG对噪点相当敏感,在实际应用中,在Block和Cell划分之后,对于得到各个像区域中,有时候还会做一次高斯平滑去除噪点。
import cv2
import numpy as np
import math
import matplotlib.pyplot as plt
class Hog_descriptor():
def __init__(self, img, cell_size=16, bin_size=8):
self.img = img
self.img = (np.sqrt(img / np.max(img)))* 255 # 对输入图像进行颜色空间的标准化(归一化)
self.cell_size = cell_size
self.bin_size = bin_size
self.angle_unit = int(360 / self.bin_size)
assert type(self.bin_size) == int, "bin_size should be integer,"
assert type(self.cell_size) == int, "cell_size should be integer,"
def extract(self):
height, width = self.img.shape
gradient_magnitude, gradient_angle = self.global_gradient()
gradient_magnitude = np.abs(gradient_magnitude)
cell_gradient_vector = np.zeros((int(height / self.cell_size), int(width / self.cell_size), self.bin_size))
for i in range(cell_gradient_vector.shape[0]):
for j in range(cell_gradient_vector.shape[1]):
cell_magnitude = gradient_magnitude[i * self.cell_size:(i + 1) * self.cell_size,
j * self.cell_size:(j + 1) * self.cell_size]
cell_angle = gradient_angle[i * self.cell_size:(i + 1) * self.cell_size,
j * self.cell_size:(j + 1) * self.cell_size]
cell_gradient_vector[i][j] = self.cell_gradient(cell_magnitude, cell_angle)
hog_image = self.render_gradient(np.zeros([height, width]), cell_gradient_vector)
hog_vector = []
for i in range(cell_gradient_vector.shape[0] - 1):
for j in range(cell_gradient_vector.shape[1] - 1):
block_vector = []
block_vector.extend(cell_gradient_vector[i][j])
block_vector.extend(cell_gradient_vector[i][j + 1])
block_vector.extend(cell_gradient_vector[i + 1][j])
block_vector.extend(cell_gradient_vector[i + 1][j + 1])
mag = lambda vector: math.sqrt(sum(i ** 2 for i in vector))
magnitude = mag(block_vector)
if magnitude != 0:
normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
block_vector = normalize(block_vector, magnitude)
hog_vector.append(block_vector)
return hog_vector, hog_image
def global_gradient(self):
gradient_values_x = cv2.Sobel(self.img, cv2.CV_64F, 1, 0, ksize=5)
gradient_values_y = cv2.Sobel(self.img, cv2.CV_64F, 0, 1, ksize=5)
gradient_magnitude = cv2.addWeighted(gradient_values_x, 0.5, gradient_values_y, 0.5, 0)
gradient_angle = cv2.phase(gradient_values_x, gradient_values_y, angleInDegrees=True)
return gradient_magnitude, gradient_angle
def cell_gradient(self, cell_magnitude, cell_angle):
orientation_centers = [0] * self.bin_size
for i in range(cell_magnitude.shape[0]):
for j in range(cell_magnitude.shape[1]):
gradient_strength = cell_magnitude[i][j]
gradient_angle = cell_angle[i][j]
min_angle, max_angle, mod = self.get_closest_bins(gradient_angle)
orientation_centers[min_angle] += (gradient_strength * (1 - (mod / self.angle_unit)))
orientation_centers[max_angle] += (gradient_strength * (mod / self.angle_unit))
return orientation_centers
def get_closest_bins(self, gradient_angle):
idx = int(gradient_angle / self.angle_unit)
mod = gradient_angle % self.angle_unit
return idx, (idx + 1) % self.bin_size, mod
def render_gradient(self, image, cell_gradient):
cell_width = self.cell_size / 2
max_mag = np.array(cell_gradient).max()
for x in range(cell_gradient.shape[0]):
for y in range(cell_gradient.shape[1]):
cell_grad = cell_gradient[x][y]
cell_grad /= max_mag
angle = 0
angle_gap = self.angle_unit
for magnitude in cell_grad:
angle_radian = math.radians(angle)
x1 = int(x * self.cell_size + magnitude * cell_width * math.cos(angle_radian))
y1 = int(y * self.cell_size + magnitude * cell_width * math.sin(angle_radian))
x2 = int(x * self.cell_size - magnitude * cell_width * math.cos(angle_radian))
y2 = int(y * self.cell_size - magnitude * cell_width * math.sin(angle_radian))
cv2.line(image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
angle += angle_gap
return image
img = cv2.imread('C:/Users/Ivy/Desktop/a.jpg', cv2.IMREAD_GRAYSCALE)
hog = Hog_descriptor(img, cell_size=8, bin_size=8)
vector, image = hog.extract()
print(np.array(vector).shape)
plt.imshow(image, cmap=plt.cm.gray)
plt.show()
Sobel算子是一种过滤器,只是其是带有方向的。在OpenCV中,使用Sobel的算子的函数原型如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
必须的参数:
src:需要处理的图像;
ddepth:图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
dx和dy: 表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
可选的参数:
ksize:Sobel算子的大小,必须为1、3、5、7。
scale:缩放导数的比例常数,默认情况下没有伸缩系数;
delta:可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
borderType:判断图像边界的模式,默认值为cv2.BORDER_DEFAULT。