HOG(Histogram of Oriented Gradients)是一种用于图像处理和计算机视觉任务的特征描述方法,它通常用于目标检测和物体识别。HOG 特征是一种用于描述图像中局部纹理和形状的特征向量,其主要思想是利用图像中局部区域的梯度信息来表示图像的特征。
Paper:http://vision.stanford.edu/teaching/cs231b_spring1213/papers/CVPR05_DalalTriggs.pdf
- HOG 图像特征示例 {#title-0} ========================
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()
- HOG 特征计算过程 {#title-1} ========================
2.1 图像预处理 {#title-2}
在进行后续特征提取工作之前,一般会先进行图像的预处理工作。这部分工作包括将图像转换为灰度图、图像的归一化和 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 计算图像梯度 {#title-3}
使用 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 直方图 {#title-4}
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 直方图 {#title-5}
以 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 标准化的作用是可以减少光照等因素的影响,从而更好地提取图像的纹理特征。