HOG特征提取

原文链接:戳这里!

HOG

前言:特征描述符

特征描述符就是通过提取图像的有用信息,并且丢弃无关信息来简化图像的表示。

HOG特征描述符可以将3通道的彩色图像转换成一定长度的特征向量。

Info

什么是有用信息,什么是无用信息?

这里的“有用”,是指对于什么目的有用,显然特征向量对于观察图像是没有用的,但是它对于像图像识别和目标检测这样的任务非常有用。当将这些特征向量输入到类似支持向量机($svm$)这样的图像分类算法中时,会得到较好的结果。

那什么样的“特征”对分类任务是有用,比如我们想检测出马路上的车道线,那么我们可以通过边缘检测来找到这些车道线,在这种情况下,边缘信息就是“有用的”,而颜色信息是无关的。

在HOG特征描述符中,梯度方向的分布,也就是梯度方向的直方图被视作特征。图像的梯度(x和y的导数)非常有用,因为边缘和拐角周围幅度梯度很大,并且边缘和拐角比平坦区域包含更多关于物体形状的信息。

方向梯度直方图(HOG)特征描述符常和线性支持向量机(SVM)配合使用,用于训练高精度的目标分类器。

一、计算图像的方向梯度直方图(HOG)步骤

以下图为例子:

image-20210421193206285

1. 图像预处理

  • 对图像进行裁剪,缩放到固定的尺寸。

  • 灰度处理是可选操作,灰度图像和彩色图像都可以用于计算梯度图。对于彩色图像,先对三通道颜色值分别计算梯度,然后取梯度值最大的作为该像素的梯度。

  • 进行伽马矫正,调节图像对比度来减少光照对图像的影响(包括光照不均和局部阴影),使过曝或者欠曝的图像恢复正常。

  • 附:伽马矫正公式

    即输出图像是输入图像的幂函数,指数为$\gamma$ 。

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import cv2
    import numpy as np
    from matplotlib import pyplot as plt

    img = cv2.imread('a.jpg')
    gamma = [0.1,0.5,5.0,10.0]
    plt.figure(figsize = (15,15))
    for idx,i in enumerate(gamma):
    plt.subplot(221+idx)
    plt.title("gamma:%s"%str(i))
    img2 = np.power(img/float(np.max(img)),i)
    plt.imshow(img2)

    plt.savefig('b.jpg')
    plt.show()

    $\gamma$越大,图像越暗,为1不变。

    b

2. 计算梯度图

为了得到梯度直方图,首先需要计算水平和垂直梯度,这可以通过使用一下内核过滤图像来实现,分别用于计算水平梯度和垂直梯度。

image-20210421200805443

之后再计算$x$和$y$方向的梯度,包括幅值和方向:

Warning

注意梯度方向会取绝对值,因此得到的角度范围为0-180。

代码如下:

1
2
3
4
5
6
7
8
9
10
img = cv2.imread('a.jpg')
#归一化
img = np.float32(img) / 255.0

#计算x和y方向上的梯度
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize = 1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize = 1)

# 计算合梯度的方向和幅值
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

c

3. 计算梯度直方图

在这一步,我们先将整个图像划分为若干个8X8的小单元,称作cell,计算每个cell的梯度直方图。这个cell的尺寸也可以是其他值,根据具体特征而定。

Attention

为什么我们要将图像分为若干个8X8的小单元?

因为一整张梯度图,其中的有效特征是非常稀疏的,不但运算量大,而且效果可能不好。于是我们用特征描述符来表示更紧凑的特征。一个8X8的小单元就包含了8X8X2=128个值(每个像素点包括梯度大小和梯度方向)。

现在将这8X8的小单元用长度为9的数组来表示,这个数组就是梯度直方图。

为什么是长度为9的数组呢?

因为梯度方向范围是0-180,将角度范围分为9份,每20°为一bins,将每一份中所有像素的梯度值进行累加(注意不是角度范围内像素的个数),得到9个数值。直方图就是由这9个数值所组成的数组,对应角度0°、20°、40°…160°

image-20210421205314930

比如上面方向图中蓝圈包围的像素,角度为80度,这个像素对应的幅值为2,所以在直方图80度对应的bin加上2。红圈包围的像素,角度为10度,介于0度和20度之间,其幅值为4,那么这个梯度值就被按比例分给0度和20度对应的bin,也就是各加上2。

Warning

还有一个细节需要注意,如果某个像素的梯度角度大于160度,也就是在160度到180度之间,那么把这个像素对应的梯度值按比例分给0度和160度对应的bin。

现在我们就可以用这9个数的梯度直方图来代替原来很大的三维矩阵,即代替了8x8x2个值。

这种表示法不仅使特征更紧凑,而且对单个像素值的变化不敏感,也就是能够抗噪声干扰。

4. Block归一化

上一步中将8X8的区域做为一个cell,接着再以2X2个cell做为一组,称为block。由于每个cell有9个值,所以2X2个cell有36个值。HOG通过滑动窗口的方式来获得Block。

在第三步中,我们对每个cell创建了一个直方图,但是图像的梯度直方图对整体光照非常敏感,比如通过将所有像素值除以2来使图像变暗,那么梯度幅值将减小一半,因此直方图中的值也将减少一半。如果希望我们的特征描述符不会受到光照变化的影响,那么我们就需要将直方图归一化。

首先,以长度为3的向量为例:假设我们有一个向量 [128,64,32],向量的长度为

这叫做向量的L2范数。将这个向量的每个元素除以146.64就得到了归一化向量 [0.87, 0.43, 0.22]

现在有一个新向量,是第一个向量的2倍[128x2, 64x2, 32x2],也就是 [256, 128, 64],我们将这个向量进行归一化,你可以看到归一化后的结果与第一个向量归一化后的结果相同。所以,对向量进行归一化可以消除整体光照的影响。

知道了如何归一化,现在来对block的梯度直方图进行归一化(注意不是cell),一个block有4个直方图,将这4个直方图拼接成长度为36的向量,然后对这个向量进行归一化。

因为使用的是滑动窗口,滑动步长为8个像素(即一个cell),所以每滑动一次,就在这个窗口上进行归一化计算得到长度为36的向量,并重复这个过程。

5. 计算HOG特征向量

最后计算真个图像的特征描述符,每滑动一次就得到一个长度为36的特征向量。那么总共有多少特征向量呢?

比如上面这个图,将整幅图像划分成cell的个数为8x16,就是横向有8个cell,纵向有16个cell。每个block有2x2个cell的话,那么cell的个数为:(16-1)x(8-1)=105。即有7个水平block和15个竖直block。

再将这105个block合并,就得到了整个图像的特征描述符,长度为 105×36=3780。

6. HOG可视化

使用Python库skimage

代码如下:

1
2
3
4
5
6
7
8
9
10
11
from skimage import feature, exposure
import cv2

image = cv2.imread('a.jpg')
fd, hog_image = feature.hog(image, orientations=9, pixels_per_cell=(16, 16),cells_per_block=(2, 2), visualize=True)
# Rescale histogram for better display
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))

cv2.imshow('img', image)
cv2.imshow('hog', hog_image_rescaled)
cv2.waitKey(0)

image-20210421213911095

这里最关键的函数就是 feature.hog,关键参数如下:

  • image : 可以是灰度图或者彩色图
  • orientations : 把180度分成几份,也就是bin的数量
  • pixels_per_cell : 一个cell中的像素数目
  • cells_per_block : 一个block中包含的cell个数
  • visualize : 是否返回一个hog图像用于显示。
0%