HOG(Histogram of Oriented Gradients)是一种用于图像处理和计算机视觉任务的特征描述方法,它通常用于目标检测和物体识别。HOG 特征是一种用于描述图像中局部纹理和形状的特征向量,其主要思想是利用图像中局部区域的梯度信息来表示图像的特征。
Paper:http://vision.stanford.edu/teaching/cs231b_spring1213/papers/CVPR05_DalalTriggs.pdf
1. HOG 图像特征示例
import cv2 from skimage.feature import hog import matplotlib.pyplot as plt import warnings warnings.filterwarnings('ignore') def test(): # (500, 500) image = cv2.imread('img.png') image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # hog_vector 特征向量用于算法训练 # hog_image 用于可视化 HOG 特征 hog_vector, hog_image = hog(image, orientations=9, pixels_per_cell=(50, 50), cells_per_block=(3, 3), block_norm='L2-Hys', visualize=True) # (5184,) (5184,) print(hog_vector.shape, hog_vector.shape) plt.subplot(1, 2, 1) plt.axis('off') plt.title('Origin') plt.imshow(image, cmap='gray') plt.subplot(1, 2, 2) plt.axis('off') plt.title('HOG') plt.imshow(hog_image, cmap='gray') plt.show() if __name__ == '__main__': test()
2. HOG 特征计算过程
2.1 图像预处理
在进行后续特征提取工作之前,一般会先进行图像的预处理工作。这部分工作包括将图像转换为灰度图、图像的归一化和 gamma 校正。
为什么需要将图像转换为灰度图?
- 可以消除物体颜色带来的影响,使得特征提取更加关注于形状和结构信息,有助于区分不同的物体。
- 图像的像素较少,可以降低图像的运算难度和复杂度,有利于更快地训练模型。
图像归一化的作用是什么?
- 将图像像素值除以 255 进行归一化的将像素值映射到 0 到 1 之间,使得像素值具有相同的比例,避免某些值过大或过小,使图像梯度值在一定的范围内保持稳定
- 归一化可以加速模型的收敛速度,并提高模型的泛化能力
- 需要注意的是,尽管像素值被归一化,但是归一化后的图像与原图像在视觉上并无差别,这是因为归一化过程并没有改变像素值的本质信息,只是改变了它们的表示范围和分布
gamma 校正的作用是什么?
- 校正亮度偏差:通过指数变换,校正图像的亮度,使得图像的亮度符合人眼的特性
- 提高对比度:通过改变图像的亮度分布,提高图像的对比度,使得图像的细节更加清晰
- gamma值越大,图像的对比度越低,图像整体显得较暗;gamma值越小,对比度越高,图像整体显得较亮
gamma 校正的计算公式如下:
- \(l_{out}\) 表示输出像素值
- \(l_{in}\) 表示输入像素值
示例代码如下:
import numpy as np from skimage import io import cv2 import matplotlib.pyplot as plt import warnings warnings.filterwarnings('ignore') def show_image(image, index, title): plt.subplot(3, 2, index) plt.title(title) plt.axis('off') plt.imshow(image) def test(): image = io.imread('demo.jpg') show_image(image, 1, 'origin') # 转换为灰度图 image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) show_image(image, 2, 'gray') # 图像归一化 image = image / 255 show_image(image, 3, 'norm') # 伽马校正 image1 = image ** 0.2 show_image(image1, 4, 'gamma=0.2') image2 = image ** 0.8 show_image(image2, 5, 'gamma=0.8') image3 = image ** 1.5 show_image(image3, 6, 'gamma=1.5') plt.show() if __name__ == '__main__': test()
2.2 计算图像梯度
使用 kernel size 为 1 的 sobel 算子计算图像两个方向上的梯度,并计算幅度值和方向。
- θ 表示梯度方向,请注意,这里的角度θ是以弧度为单位的
- G 表示梯度幅值
import cv2 import numpy as np def test(): # np.random.seed(0) # 灰度图 image = np.random.randint(0, 255, size=(4, 4), dtype=np.uint8) # 归一化 image = image / 255.0 # x 方向梯度 gx = cv2.Sobel(image, -1, dx=1, dy=0, ksize=1) # y 方向梯度 gy = cv2.Sobel(image, -1, dx=0, dy=1, ksize=1) # 幅度 magnitude = np.sqrt(gx ** 2 + gy ** 2) # 方向 direction = np.arctan2(gy, gx) print(np.degrees(direction)) print(magnitude) if __name__ == '__main__': test()
2.3 计算 Cell 直方图
Cell 指的是我们将图像划分成一个又一个的子区域,如下图:560×400 的图像,我们以 80×80 为一个 Cell 划分成了 35 个 Cell。
计算每一个 Cell 的 x、y 方向的图像梯度,并根据梯度值计算梯度幅值、梯度方向。有了梯度幅值和方向就可以构建每一个 Cell 的方向梯度直方图了。
每个像素的方向都在 0-180 度之间,我们将这个范围划分出 9 个 bin,分别是:
0 20 40 60 80 100 120 140 160
然后,将每个像素的梯度幅值加权累计到每个 bin 中,得到直方图:
此时,原来每个 Cell 是 80×80=6400 个像素值表示,现在变成 9 个bin 对应的值的表示。
每个像素的幅度值是如何加权到不同的 bin 中的?
上图中,蓝色标注的像素的方向为 80,对应 80 的bin, 直接将其对应的幅度值添加到该 bin 中。对于红色标注像素的方向为 10,其介于 bin 0 ~ 20 之间,正好在中间位置,我们将其对应的幅度值 4 平分到 bin 0 和 bin 20 内。
2.4 标准化 Block 直方图
以 Cell 为基本单位,组成一个 Block,例如:block=(2, 2) 则表示 4 个 Cell 为一个 block,如下图所示。然后通过滑动窗口的形式来标准化每一个 Block,每次滑动窗口进行标准化时,都会产生的一个 36 维度的数据,最终将所有的 36 维拼接起来得到最终的图像的 HOG 特征表示。
例如:上图中,将所有图像以滑动窗口形式标准化共需要滑动 24 次,每次产生 36 维度特征,将其展开拼接到一起就得到 24 * 36 = 864 维度的图像 HOG 特征。
用于标准化 Block 数据的方法主要有:L1-norm、L1-sqrt、L2-norm、L2-Hys,在 skimage 库中默认使用 L2-Hys 标准化方法。
这一步进行 Block 标准化的作用是可以减少光照等因素的影响,从而更好地提取图像的纹理特征。