HOG, Histogram of Oriented Gradients / 方向梯度直方图 介绍

本文翻译自 Histogram of Oriented Gradients”

 
在这篇文章中,我们将会学习 HOG (Histogram of Oriented Gradients,方向梯度直方图)特征描述子 的详细内容。
我们将学习 HOG 算法是如何实现的,以及在 OpenCv / MATLAB 或者其他工具里面如何计算特征子。
这篇文章是我正在写的,关于 Image Recognition / 图像识别 和 Object Detection / 目标检测 系列文章中的一部分。
 
完整的系列文章如下所示:
1. 使用传统计算机视觉技术来进行 Image recognition / 图像识别 :Part 1 ( https://www.learnopencv.com/image-recognition-and-object-detection-part1/ )
2. HOG 方向梯度直方图:Part 2
3. 图像识别的例子:Part 3 ( https://www.learnopencv.com/handwritten-digits-classification-an-opencv-c-python-tutorial )
4. 训练一个更好的眼睛检测器:Part 4a ( https://www.learnopencv.com/training-better-haar-lbp-cascade-eye-detector-opencv )
5. 使用传统计算机视觉技术来进行 Object detection / 目标检测:Part 4b
6. 如何训练和测试你自己的 OpenCv 目标检测器:Part 5
7. 使用 Deep learning / 深度学习来进行图像识别:Part 6
介绍 Neural Networks / 神经网络 ( https://www.learnopencv.com/neural-networks-a-30000-feet-view-for-beginners/ )
理解 Feedforward Neural Networks / 负反馈神经网络 ( https://www.learnopencv.com/understanding-feedforward-neural-networks/ )
使用 Convolutional Neural Networks / 卷积神经网络 来进行图像识别 ( https://www.learnopencv.com/image-classification-using-convolutional-neural-networks-in-keras/ )
8. 使用 Deep learning / 深度学习来进行目标检测:Part 7
 
很多事情看起来困难又神秘,但是你一旦花时间去了解,揭开神秘面纱,你就会发现神奇之处。
如果你是一个初学者,觉得计算机视觉又难又神秘,请记住一句话:
问:如何吃掉一个大象?
答:一口一口吃
 

什么是 Feature Desciptor / 特征描述子

Feature Desciptor / 特征描述子 从图像中提取有用信息,剔除无关信息;
典型的,特征描述子从将一张 宽度 * 高度 * 3 ( 通道数 ) 大小的图像,提取出长度为 n 的 Feature Vector / 特征向量 或者 Feature Array / 特征矩阵
比如 HOG 特征描述子会从一张 64 * 128 * 3 的图像中提取出长度为 3780 的特征向量;
请记住, HOG 的特征描述子也可以计算其他尺寸,但是这篇文章中,我使用上述尺寸,以便你能够轻松的理解概念。
 
这些概念听起来都挺不错,但是哪些是“有用的信息”,有些又是“无用的信息”
定义“有用的信息”,我们需要知道有用的信息用来干什么的;
很明显,通过特征向量用来浏览图像是没用的,但是在图像识别或者目标检测中,特征向量会变得很有用;
在一些图像分类算法中比如 SVM,Support Vector Machine,支持向量机 中,用特征向量进行分类会达到很好的结果。
 
但是在分类任务中,哪些特征是有用的呢?
我们借助下面的例子来讨论,比如现在我们想通过一个目标检测器,可以检测衬衫和大衣的纽扣;
一个纽扣是一个圆形(图片中也有可能看起来像是椭圆),一般来说有几个孔,用于缝到衣服上面;
你可以在纽扣的图像上使用一个 Edge detector / 边缘检测器,可以轻松通过检测边缘来辨别它是不是一个纽扣;
这个例子中,边缘信息是“有用的”而颜色信息是 ”无用的“;
除此之外,特征也需要有足够特殊的地方。比如一个好的特征,应该能够让你辨别出纽扣和其他圆形的物体,比如硬币和汽车轮胎。
 

如何计算 Histogram of Oriented Gradients / 方向梯度直方图?

在这一节,我们会继续深入学习如何计算 HOG 特征描述子。

步骤1:预加工

之前提到用于行人检测的 HOG 特征描述子,是基于 64×128 大小的图像。当然,图像可能是任何尺寸的;
对于这些之后用于分析的图像,唯一需要进行的处理是调整纵横比图像大小;
在我们的例子中,需要调整纵横比为1:2,比如图像可以被调整为 100×200, 128×256, 或者 1000×2000,但是不能是 101×205;
原始图像大小是 720×475,我们截切出来 100×200 大小图像用来计算 HOG 特征描述子,然后重新调整大小到 64×128;
现在我们就做完了计算 HOG 特征描述子准备工作。

Dalal 和 Triggs 的论文也提到了 Gamma Correction / 伽马校正 作为预处理步骤,但性能提升很小,因此我们选择预处理中跳过这一步。
 

步骤 2 :计算梯度图像

为了计算 HOG 特征描述子,我们第一步需要计算水平和垂直方向的梯度。我们通过下面的 Kernel / 核 来处理图像,很容易计算出梯度的直方图。

我们可以使用核大小为 1 的 OpenCv 的 Sobel 算子:

1    // C++ gradient calculation.
2    // Read image
3    Mat img = imread("bolt.png");
4    img.convertTo(img, CV_32F, 1/255.0);
5     
6    // Calculate gradients gx, gy
7    Mat gx, gy;
8    Sobel(img, gx, CV_32F, 1, 0, 1);
9    Sobel(img, gy, CV_32F, 0, 1, 1);

 

复制代码
1    # Python gradient calculation
2     
3    # Read image
4    im = cv2.imread('bolt.png')
5    im = np.float32(im) / 255.0
6     
7    # Calculate gradient
8    gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
9    gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)
复制代码

 
接下来,我们通过下面的公式来计算梯度的幅值和方向:

在 OpenCv 中,我们可以使用 cartToPolar 函数来计算上述数值:

// C++ Calculate gradient magnitude and direction (in degrees)
Mat mag, angle;
cartToPolar(gx, gy, mag, angle, 1);
The same code in python looks like this.

 

# Python Calculate gradient magnitude and direction ( in degrees )
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

 
下图展示了梯度计算结果:


左边:x 方向梯度的绝对值
中间:y 方向梯度的绝对值
右边:梯度的幅值
 
注意 x 方向的梯度代表垂直方向的变化趋势,而 y 方向代表的是水平方向的变化;
如果图像像素变换迅速的话,可以在梯度图中明显看出,而当区域内变化缓慢时,不会出现梯度幅值;
梯度图像去除了很多不必要的信息,保留了关键信息。换句话说,你可以看着梯度图,然后轻松的辨别出来照片里的人;
每一个像素点,都有一个 Magnitude / 幅值 和 Direction / 方向
对于彩色的图像,三种通道的梯度都会被评估计算,取的是最大的梯度。
 

步骤 3:在 8*8 cells / 网格 中计算梯度直方图

这一步,图像会被分割成 8*8 大小的单独 cells / 小格子,然后对于每个 8*8 的小格子,分别计算梯度直方图;
 
我们先来了解下为什么要把图像分割为 8*8 的小格子;
有一个重要的原因是使用特征描述子来描述一幅 image / 图像 的一个 patch / 子图像 的话,网格分割会提供了一个 compact / 紧凑 的表示方式;
一个 8*8 的子图像包含 8*8*3 = 192 个像素值。每个像素梯度有两个值( Magnitude / 幅值 和 Direction / 方向 ),所以每个子图像会有 8*8*2=128 个数值;
在这节结束之前,我们会看到这 128 个数值如何使用 9 位的数组来 存储在 9 位的直方图中。经过压缩处理之后的数据具有更好的 抗噪性
 
但是为什么取得是 8*8 的子图像而不是 32*32? 这是根据我们所要检测的目标来决定的;
对于 HOG 行人检测, 从 64*128 的行人图像中提取出的 8*8 子图像,已经足够用来提取出有用的信息(比如脸部,头的顶部等等)
 
直方图有必要是 9 位的向量,与 0,20,40,60… 160 度对应;
让我们来看看在一个 8*8 的子图像中,梯度是什么样的:

 
中间:用箭头来代表颜色和梯度的变化;
右边:用数字来代表子图像中的梯度;
 
如果你是一个计算机视觉的初学者,中间的图像会很有帮助很形象;
通过箭头来表示图像中梯度的变化,箭头的方向表示着像素强度变化的方向,幅值表示变化的缓慢;
通过右边的图,我们可以看到 8*8 子图像中提取出来的代表梯度的数值,这些角度从 0~180 度而不是 0~360 度,这些被称之为 unsigned gradients / 无符号梯度,因为一个梯度和它取负之后得到的是同样的数值;
换句话说,一个梯度箭头旋转180度之后被认为是一样的;
但是为什么我们不使用 0-360 度呢?经验告诉我们使用无符号的梯度,比使用有符号的梯度在行人检测中性能更好。不过一些 HOG 的实现中也可以允许你使用有符号的梯度。
 
接下来就是为这些 8*8 的子图像,建立一个梯度直方图。直方图有 9 位,来与 0, 20, 40…160 度相对应;
下面的图像向我们展示了操作过程,我们关注从 8*8 子图像中提取出来的幅值和方向;
 
* 根据梯度的方向来选择使用填充到哪一位,然后根据梯度的幅值来填充数值
 
我们先来看看 蓝圈的数值,角度为 80,幅值为2,所以在直方图第五位加 2;
再来看看 红圈的数值,角度为 10,幅值为 4,角度 10 的话在 0 和 20 之间,所以将它的幅值 4 被一分为 2 ,分别在直方图的 “0 位” 和 “20 位” 里面放 2 。
还需要注意的一点是,如果 角度比 160 大,在 160 和 180 之间。我们知道在这里 0 度和 180 度一样,所以下面这个例子,角度 165 度被分到了 0 度和 160 度 两个位里面。
8*8 子图像提取出来的数值,经过处理,可以得到一个 9 位的直方图,对于上面的子图像,我们可以得到如下的直方图:
 
在我们的表示中,y 轴默认为 0 度。你可以从直方图中看到,在 0~180 度之间有很多分布,这也表明子图像中的梯度方向要么朝上要么朝下。
 

步骤 4:16*16 块归一化

在之前的步骤中,我们根据图像的梯度制作了直方图。但是对于亮度不同的图像,梯度很敏感。
如果你让所有像素点的数值除 2 来让图像变暗,梯度幅值也会相应的减半,因此直方图也会对应着减半。
理想情况下,我们希望我们的描述器是不随着亮度变化而变化的,换句话说,我们想要归一化直方图,所以让它不受亮度影响;
 

在我说明直方图如何被归一化之前,让我们来看看,一个长度为 3 的向量是如何被归一化的;
比如我们有个 RGB 颜色向量为 [ 128, 64, 32 ],计算出长度为:
这也被称为这个向量的 L2 范数;
对向量的每个元素除以 146.64,得到归一化之后的向量 [ 0.87, 0.43, 0.22 ]。
 
现在考虑另一个向量,它的数值是之前向量的两倍,2 x [ 128, 64, 32 ] = [ 256, 128, 64 ];
通过同样的计算方式,你可以得到同样的归一化向量 [ 0.87, 0.43, 0.22 ],这就可以解决之前提到的亮度的影响问题。
现在我们知道了如何去归一化向量,也许你会认为,归一化 9*1 的直方图和上面介绍的 3*1 的向量归一化一样。这想法并没有错,但是更好的方式是用一个更大尺寸 16*16 的块去归一化;
也就是 36*1 的直方图可以看成 4 个 9*1 的直方图构成,然后窗口以 8 像素移动(见上图),计算出归一化的 36*1 大小的向量然后重复这个过程遍历图像。
 

可视化 HOG

通过在 8*8 子图像里面进行 9*1 归一化的直方图,我们可以可视化子图像的 HOG 的描述子。
在下图中你会发现,直方图的 Dominant direction / 主要方向 捕获了这个人的外形,尤其在躯干和腿。
不幸的是,在 OpenCv 中进行 HOG 的特征描述子的可视化比较困难。

发表评论

电子邮件地址不会被公开。 必填项已用*标注