转自

一文讲解方向梯度直方图(hog)

 

目录:

  1. 特征描述符
  2. 方向梯度直方图(hog)
    1. 图像预处理
    2. 计算梯度图
    3. 计算梯度直方图
    4. Block 归一化
    5. 计算HOG特征向量
    6. 代码实现

一、特征描述符

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

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

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

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

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

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

二、方向梯度直方图(hog)

接下来讲一下计算图像的HOG特征描述符的具体步骤。

我们以下面这张图片为例(宽高为100x200):

1、图像预处理

可以对图像进行裁剪,并缩放到固定尺寸。

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

然后进行伽马矫正,调节图像对比度,减少光照对图像的影响(包括光照不均和局部阴影),使过曝或者欠曝的图像恢复正常,更接近人眼看到的图像。

伽马矫正公式:

即输出图像是输入图像的幂函数,指数为 ​。

代码实现如下:

import cv2
import numpy as np
img = cv2.imread('gamma.jpg', 0)
img2 = np.power(img/float(np.max(img)), 1.5)

​越大,图像越暗;为1时,表示没有变化。

2、计算梯度图

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

我们可以使用内核大小为1的Sobel算子,来获得相同的结果。

关于Sobel计算梯度的详细内容,可以去看 这篇文章,本文不过多介绍了哈。

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

注意:梯度方向会取绝对值,因此得到的角度范围是 [0,180°]。

代码如下:

import cv2
import numpy as np
# Read image
img = cv2.imread('runner.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)

如下分别为x方向梯度图,y方向梯度图,梯度幅值图,梯度方向图:

在每个像素处,梯度有一个大小和一个方向。x方向梯度图会强化垂直边缘特征,y方向梯度图会强化水平边缘特征。这就使得有用的特征(轮廓)得到保留,无关不重要的信息被去除。

3、计算梯度直方图

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

为什么我们要把图像分成若干个8x8的小单元?

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

现在我们要把这个8x8的小单元用长度为9的数组来表示,这个数组就是梯度直方图。这种表示方法不仅使得特征更加紧凑,而且对单个像素值的变化不敏感,也就是能够抗噪声干扰。

我们来看一下图片中的一个cell中的梯度:

中间那张图中的箭头表示梯度,箭头方向表示梯度方向,箭头长度表示梯度大小。

右图是 8×8 的cell中表示梯度的原始数字,注意角度的范围介于0到180度之间,而不是0到360度, 这被称为“无符号”梯度,因为两个完全相反的方向被认为是相同的。

现在我们来计算cell中像素的梯度直方图,先将角度范围分成9份,也就是9 bins,每20°为一个单元,也就是这些像素可以根据角度分为9组。将每一份中所有像素对应的梯度值进行累加,可以得到9个数值。直方图就是由这9个数值组成的数组,对应于角度0、20、40、60... 160。

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

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

将这 8x8 的cell中所有像素的梯度值加到各自角度对应的bin中,就形成了长度为9的直方图:

可以看到直方图中,0度和160附近有很大的权重,说明了大多数像素的梯度向上或者向下,也就是这个cell是个横向边缘。

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

4、Block 归一化

HOG将8×8的一个区域作为一个cell,再以2×2个cell作为一组,称为block。由于每个cell有9个值,2×2个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个像素,所以每滑动一次,就在这个窗口上进行归一化计算得到长度为36的向量,并重复这个过程。

5、计算HOG特征向量

终于可以计算整个图像的特征描述符了,每滑动一次,一个block就得到一个长度为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 特征描述符进行可视化。OpenCV 好像没有提供简单的方法可视化 HOG 描述符,所以这里使用python库skimage

from skimage import feature, exposure
import cv2
image = cv2.imread('/home/zxd/Pictures/Selection_018.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)==ord('q')

注:使用命令pip install scikit-image来安装 skimage

这里最关键的函数就是 feature.hog,它把上面介绍的几个步骤都替我们完成了,其中有几个关键参数:

  • image:可以是灰度图或者彩色图;
  • orientations`:就是把180度分成几份,也就是bin的数量;
  • pixels_per_cell:一个cell里包含的像素个数;
  • cells_per_block:一个block包含的cell个数;
  • visualize:是否返回一个hog图像用于显示,下面会显示这张图;

为了显示效果,我把cell的尺寸改为(16, 16),对于每一个cell,画出它归一化后的梯度直方图。如下图所示,我们可以很明显的看出一个人的轮廓。

参考:

HOG特征详解

Histogram of Oriented Gradients

01-15 22:23