写的很好的HOG
Dalal提出的HOG特征提取的过程:把样本图像分割为若干个像素的单元,把梯度方向平均划分为多个区间,在每个单元里面对所有像素的梯度方向在各个方向区间进行直方图(横轴是角度,纵轴是幅值)统计,得到一个多维的特征向量,每相邻的单元构成一个区间,把一个区间内的特征向量联起来得到多维的特征向量,用区间对样本图像进行扫描,扫描步长为一个单元。最后将所有块的特征串联起来,就得到了人体的特征。至今虽然有很多行人检测算法,但基本都是以HOG+SVM的思路为主。
方向梯度直方图(HOG)中,梯度的方向分布被用作特征。沿着一张图片X和Y轴的方向上的梯度是很有用的,因为在边缘和角点的梯度值是很大的,我们知道边缘和角点包含了很多物体的形状信息。
主要流程:
检测窗口 – 归一化图像 – 计算梯度 – 对于每一个
**比较难的应该是投影到梯度直方图上,怎么做呢? 就是按照角度和像素值,把像素值加入到对应的角度那一列上。**注意是像素值,不是该角度包含的像素数目。 不然后面的归一化就没有意义了。
怎么计算方向梯度直方图呢?
我们会先用图像的一个patch来解释。
Patch可以是任意的尺寸,但是有一个固定的比例,比如当patch长宽比1:2,那patch大小可以是100200, 128256或者10002000但不可以是101205。
这里有张图是720475的,我们选100200大小的patch来计算HOG特征,把这个patch从图片里面抠出来,然后再把大小调整成64*128。
首先我们计算水平和垂直方向的梯度,再来计算梯度的直方图。可以用下面的两个kernel来计算,也可以直接用OpenCV里面的kernel大小为1的Sobel算子来计算。
#Calculate gradient
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)
# Python Calculate gradient magnitude and direction ( in degrees )
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)
从上面的图像中可以看到x轴方向的梯度主要凸显了垂直方向的线条,y轴方向的梯度凸显了水平方向的梯度,梯度幅值凸显了像素值有剧烈变化的地方。(注意:图像的原点是图片的左上角,x轴是水平的,y轴是垂直的)
图像的梯度去掉了很多不必要的信息(比如不变的背景色),加重了轮廓。换句话说,你可以从梯度的图像中轻而易举的发现有个人。
在每个像素点,都有一个幅值(magnitude)和方向,对于有颜色的图片,会在三个channel上都计算梯度。那么相应的幅值就是三个channel上最大的幅值,角度(方向)是最大幅值所对应的角。
上面的patch图像会被分割成8*8大小的网格(如下图),每个网格都会计算一个梯度直方图。那为什么要分成88的呢?用特征描述子的一个主要原因是它提供了一个紧凑(compact)/压缩的表示。**一个88的图像有883=192个像素值,每个像素有两个值(幅值magnitude和方向direction,三个channel取最大magnitude那个),加起来就是882=128**,后面我们会看到这128个数如何用一个9个bin的直方图来表示成9个数的数组。不仅仅是可以有紧凑的表示,用直方图来表示一个patch也可以更加抗噪,一个gradient可能会有噪音,但是用直方图来表示后就不会对噪音那么敏感了。
每个小格子是8*8
这个patch的大小是64128,**分割成88的cell**,那么一共有64/8 * 128/8 = 8*16=128个网格
直方图是有9个bin的向量,代表的是角度0,20,40,60…160。
中间: 一个网格用箭头表示梯度 右边: 这个网格用数字表示的梯度
中间这个图的箭头是梯度的方向,长度是梯度的大小,可以发现箭头的指向方向是像素强度变化方向,幅值是强度变化的大小。
右边的梯度方向矩阵中可以看到角度是0-180度,不是0-360度,这种被称之为"无符号"梯度(“unsigned” gradients),因为一个梯度和它的负数是用同一个数字表示的,也就是说一个梯度的箭头以及它旋转180度之后的箭头方向被认为是一样的。那为什么不用0-360度的表示呢?在事件中发现unsigned gradients比signed gradients在行人检测任务中效果更好。一些HOG的实现中可以让你指定signed gradients。
**原来如此,梯度方向直方图其实就是 根据梯度的角度 作为横坐标,梯度幅值作为纵坐标 来得到直方图的。**左图是梯度的角度,右图是幅值。
这里有个细节要注意,如果一个角度大于160度,也就是在160-180度之间,我们知道这里角度0,180度是一样的,所以在下面这个例子里,像素的角度为165度的时候,要把幅值按照比例放到0和160的bin里面去。
165拆成 15/20 5/20
得到直方图:
可以看到有很多值分布在0,180的bin里面,这其实也就是说明这个网格中的梯度方向很多都是要么朝上,要么朝下。
上面的步骤中,我们创建了基于图片的梯度直方图,但是一个图片的梯度对于整张图片的光线会很敏感。如果你把所有的像素点都除以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给移除了。
更好的方法是从一个1616的块上做归一化,也就是4个91的直方图组合成一个36*1的向量,然后做归一化,接着,窗口再朝后面挪8个像素(看动图)。重复这个过程把整张图遍历一遍。
为了计算这整个patch的特征向量,需要把36*1的向量全部合并组成一个巨大的向量。向量的大小可以这么计算:
我们有多少个1616的块?水平7个,垂直15个,总共有715=105次移动。
每个1616的块代表了361的向量。所以把他们放在一起也就是36*105=3780维向量。
最后 把提取的HOG特征输入到SVM分类器中,寻找一个最优超平面作为决策函数, 就可以实现分类。
Dalal提出的HOG特征提取的过程:把样本图像分割为若干个像素的单元,把梯度方向平均划分为多个区间,在每个单元里面对所有像素的梯度方向在各个方向区间进行直方图统计,得到一个多维的特征向量,每相邻的单元构成一个区间,把一个区间内的特征向量联起来得到多维的特征向量,用区间对样本图像进行扫描,扫描步长为一个单元。最后将所有块的特征串联起来,就得到了人体的特征。至今虽然有很多行人检测算法,但基本都是以HOG+SVM的思路为主。
在HOG特征描述符中,梯度方向的分布,也就是梯度方向的直方图被视作特征。图像的梯度(x和y导数)非常有用,因为边缘和拐角(强度突变的区域)周围的梯度幅度很大,并且边缘和拐角比平坦区域包含更多关于物体形状的信息。
每个cell9个方向,幅值越大,线颜色越深,由此得到下图:把所有的cell组合起来就是整体的特征。
import cv2
import numpy as np
#载入灰度原图,并且归一化
img_original=cv2.imread('E:\ShannonT\\notebook workspace\\images\\4.28.9.jpg',0)/255
#分别求X,Y方向的梯度
grad_X=cv2.Sobel(img_original,-1,1,0)
grad_Y=cv2.Sobel(img_original,-1,0,1)
#求梯度图像
grad=cv2.addWeighted(grad_X,0.5,grad_Y,0.5,0)
cv2.imshow('gradient',grad)
cv2.waitKey()
cv2.destroyAllWindows()
从上面的图像中可以看到x轴方向的梯度主要凸显了垂直方向的线条,y轴方向的梯度凸显了水平方向的梯度,梯度幅值凸显了像素值有剧烈变化的地方。(注意:图像的原点是图片的左上角,x轴是水平的,y轴是垂直的)
图像的梯度去掉了很多不必要的信息(比如不变的背景色),加重了轮廓。换句话说,你可以从梯度的图像中轻而易举的发现有个人。
在每个像素点,都有一个幅值(magnitude)和方向,对于有颜色的图片,会在三个channel上都计算梯度。那么相应的幅值就是三个channel上最大的幅值,角度(方向)是最大幅值所对应的角。