特征描述子就是将图像抽取部分有用信息,丢掉不相关的信息。特征描述子可以将一个weight*height*3(宽*高*通道数量)的图像转换成一个长为n的向量/矩阵。eg. 一副64*128*3的图像,经过转换之后可以输出长度为3780图像向量的图像向量,相比64*128*3肯定是少了的。因为,特征描述子仅仅将所需要显示的图像保留下来了。
比如说,我们想知道一个人有没有带眼镜,在这种情况下,我们仅仅需要提取人的面部特征,并且进行边缘检测,这个时候,我们可以忽略掉图片的彩色特征,将照片转换成变成仅仅有边缘轮廓的特征图。这种特征图不仅仅数据量小,而且具有很强的目标指向性。
方向梯度直方图(HOG)中,梯度的方向被用作特征。因为,物体的边缘是如果图像中反映出来,变化是很大的。
什么是梯度?
就像一元函数的导数表示这个函数图形的切线的斜率,如果多元函数在点的梯度不是零向量,则它的方向是这个函数在上最大增长的方向、而梯度的值是在这个方向上的增长率。
第一步,预处理
将图片的patch选取出来(patch可以是任意的储存,但是纵横比要保持不变),也就是从原照片中选取一小部分。
我们以此图为例
图源https://learnopencv.com/histogram-of-oriented-gradients/
计算梯度图像的第一步先要计算图像的水平梯度以及垂直梯度。可以用以下的两个kernel算,也可以使用opencv中的sobel算子。
import cv2
import numpy as np
im = cv2.imread('bolt.jpg')
"""进行归一化"""
img = np.float32(im) / 255.0
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)
以此公式,来算幅值g以及方向角
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)
经过计算得到以下图像
左边是X方向的边缘检测,中间是Y方向上的,右边为梯度幅值
对于以上的照片我们可以看到,照片中加重了轮廓信息。对于单通道图像来讲,每个像素点的梯度就是图像像素点上的梯度。然而,对于多通道图片来讲,像素点上的梯度,是三个通道当中的梯度当中,梯度最大的那个,角度也是最大幅值所对应的角。
第三步:在8*8的网格中计算梯度直方图
在这一步当中,上面的patch图像,被分割成8*8的网格图像,每个网格都会计算得出一个梯度直方图。被分成8*8的原因是它提供了一个紧凑(compact)或者说是压缩的表示。一个8*8的图像有8*8*3=192个像素值,每个像素有两个值(magnitude&direction)。但是,对于三通道图像来讲,3个通道只会将最大的梯度值以及方向角记录下来,所以说实际的梯度直方图应当含有8*8*2=128个值,后面我们也会看到这128个数字如何用一个9个bin的直方图来表示9个数的数组,不仅仅是可以有紧凑的表示,用直方图表示一个patch也可以更加的抗噪,一个gradient可能会有噪音,但是用直方图表示之后可能不会对噪音那么敏感。
如图,这个patch是64*128像素的,被分成了8*8的cell,那么一共有64/8 * 128/8 = 8*16=128个网络
对于64*128的这幅patch来讲,8*8的网格已经足够大来表示有趣的特征比如说脸、头等等
直方图是有9个bin的向量,代表的是角度0,20,40,60.....160
可以看一下8*8的cell的梯度都是什么样子
中间的图像:一个网格用箭头表示梯度的大小 右边的图像:梯度的具体数值
右边的梯度方向矩阵中可以看到角度是0-180度,不是0-360度,这种被称之为"无符号"梯度("unsigned" gradients),因为一个梯度和它的负数是用同一个数字表示的,也就是说一个梯度的箭头以及它旋转180度之后的箭头方向被认为是一样的。那为什么不用0-360度的表示呢?在事件中发现unsigned gradients比signed gradients在行人检测任务中效果更好。一些HOG的实现中可以让你指定signed gradients
以下图像为上边图像的右侧的部分
以上图为例,此过程是根据梯度和角度来确定bin的过程。蓝色和红色分别是两个过程,先看蓝色。我们可以看到蓝色像素梯度值是2,方向为80°角,于是在第5个bin中加2;对于红色,角度为10、梯度值为4,但是bin中没有10,只有0和20,于是将梯度值各分2分到bin里面的1和2当中。
这里有个细节要注意,如果一个角度大于160度,也就是在160-180度之间,我们知道这里角度0,180度是一样的,所以在下面这个例子里,像素的角度为165度的时候,要把幅值按照比例放到0和160的bin里面去。
把8*8的cell里面的像素点全都加到这9个bin里面去,就构建了一个9-bin的直方图,上边的网络对应的直方图如下:
第四步: 16*16块归一化
上面的步骤,我们创建了基于图片的梯度直方图,但是一个图片的梯度对于整张图片的光线会很敏感。如果你能把所有的像素点都除以2,那么梯度的幅值也会减半,那么直方图里面的值也会减半,所以这样并不能消除光线的影响。所以理想情况下,我们希望我们的特征描述子可以和光线变化无关,所以我们就想让我们的直方图归一化而不想受到光线的影响。
先考虑对向量用l2归一化的步骤是:
v=[128,64,32]
[(128^2) + (64^2) + (32^2) ]^0.5=146.64
把v中每一个元素除以146.64得到[0.87,0.43,0.22]
考虑另一个向量2*v,归一化后可以得到向量依旧是[0.87, 0.43, 0.22]。你可以明白归一化是把scale给移除了。
你也许想到直接在我们得到的9*1的直方图上面做归一化,这也可以,但是更好的方法是从一个16*16的块上做归一化,也就是4个9*1的直方图组合成一个36*1的向量,然后做归一化,接着,窗口再朝后面挪8个像素(看动图)。重复这个过程把整张图遍历一遍。
第五步:计算HOG特征向量
为了计算这整个patch的特征向量,需要把36*1的向量全部合并组成一个巨大的向量。向量的大小可以这么计算:
我们有多少个16*16的块?水平7个,垂直15个,总共有7*15=105次移动。
每个16*16的块代表了36*1的向量。所以把他们放在一起也就是36*105=3780维向量。
通常HOG特征描述子是画出8*8网格中9*1归一化的直方图,见下图。你可以发现直方图的主要方向捕捉了这个人的外形。