方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述算法,通过计算和统计图像局部区域的梯度方向直方图来构成特征。在一幅图像中,局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。其本质为:梯度的统计信息,而梯度主要存在于边缘的地方。Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功。
首先将图像分成小的连通区域,这些连通区域被叫做细胞单元。然后采集细胞单元中各像素点的梯度的或边缘的方向直方图。最后把这些直方图组合起来,就可以构成特征描述符。
将这些局部直方图在图像的更大的范围内(叫做区间)进行对比度归一化,可以提高该算法的性能,所采用的方法是:先计算各直方图在这个区间中的密度,然后根据这个密度对区间中的各个细胞单元做归一化。通过这个归一化后,能对光照变化和阴影获得更好的效果。
1)由于HOG是 在图像的局部方格单元上操作,所以它对图像几何的和光学的形变都能保持很好的不变性,这两种形变只会出现在更大的空间领域上。
2)在粗的空域抽样、精细 的方向抽样以及较强的局部光学归一化等条件下,只要行人大体上能够保持直立的姿势,可以容许行人有一些细微的肢体动作,这些细微的动作可以被忽略而不影响 检测效果。因此HOG特征是特别适合做图像中的人体检测的 。
HOG特征检测算法的几个步骤:颜色空间归一化—>梯度计算—>梯度方向直方图—>重叠块直方图归一化—>HOG特征。下面分别对其进行介绍。
由于图像的采集环境、装置等因素,采集到的人脸图像效果可能不是很好,容易出现误检或漏检的情况,所以需要对采集到的人脸进行图像预处理,主要是处理光线太暗或太强的情况,这里有两次处理:图像灰度化、Gamma校正。
①图像灰度化
对于彩色图像,将RGB分量转化成灰度图像,其转化公式为:
②Gamma校正
在图像照度不均匀的情况下,可以通过Gamma校正,将图像整体亮度提高或降低。在实际中可以采用两种不同的方式进行Gamma标准化,平方根、对数法。这里我们采用平方根的办法,公式如下(其中γ=0.5):
实现代码:
//测试
void CCutImageVS2013Dlg::OnBnClickedTestButton1()
{
Mat picture = imread("D:\\test\\hog2.png", 0);//灰度
Mat img;
picture.convertTo(img, CV_32F); //转换成浮点
sqrt(img, img); //gamma校正
normalize(img, img, 0, 255, NORM_MINMAX, CV_8UC1);//归一化像素值[0,255]
imshow("原图", picture);
imshow("Gamma校正", img);
waitKey();
return;
}
原图及处理结果:
图1
(明人不说暗话,放这张图就是觉得用Gamma校正处理完太美了,O(∩_∩)O哈哈~,真正效果图看图2)
图2
对经过颜色空间归一化后的图像,求取其梯度及梯度方向。分别在水平和垂直方向进行计算,梯度算子为:
实现代码:
//梯度计算
void Gradient()
{
Mat picture = imread("D:\\test\\hog2.png", 0);//灰度
Mat img;
picture.convertTo(img, CV_32F); //转换成浮点
sqrt(img, img); //gamma校正
normalize(img, img, 0, 255, NORM_MINMAX, CV_32F);//归一化[0,255]浮点数
Mat gradient = Mat::zeros(img.rows, img.cols, CV_32F);//梯度
Mat theta = Mat::zeros(img.rows, img.cols, CV_32F);//角度
for (int i = 1; i < img.rows - 1; i++)
{
for (int j = 1; j < img.cols - 1; j++)
{
float Gx, Gy;
Gx = img.at(i, j + 1) - img.at(i, j - 1);
Gy = img.at(i + 1, j) - img.at(i - 1, j);
gradient.at(i, j) = sqrt(Gx * Gx + Gy * Gy);//梯度模值
theta.at(i, j) = float(atan2(Gy, Gx) * 180 / CV_PI);//梯度方向[-180°,180°]
}
}
normalize(gradient, gradient, 0, 255, NORM_MINMAX, CV_8UC1);//归一化[0,255] 无符号整型
normalize(img, img, 0, 255, NORM_MINMAX, CV_8UC1);
imshow("原图", picture);
imshow("Gamma校正", img);
imshow("梯度图", gradient);
waitKey();
return;
}
代码结果:
3、梯度方向直方图
将图像划分成若干个cells(单元),8x8=64个像素为一个cell,相邻的cell之间不重叠。在每个cell内统计梯度方向直方图,将所有梯度方向划分为9个bin(即9维特征向量),作为直方图的横轴,角度范围所对应的梯度值累加值作为直方图纵轴,每个bin的角度范围如下。
4、重叠块直方图归一化
【以下有关计算,请认真分析】假设有一幅图像大小为220x310,将其划分成若干个8x8的cells,显然220÷8=27.5、310÷8=38.75不是整数,也就是说划分之后依然还有多余像素不能构成cell。处理办法是将图像缩放成能被8整除的长宽(如216x304),再划分。216÷8=27,304÷8=38,因此,216x304的图像可以得到27x38个cells,没有重叠。
Mat picture = imread("test.jpg", 0);//灰度
resize(picture, picture, cvSize(int(picture.cols / 8) * 8, int(picture.rows / 8) * 8));//转化成能被8除尽的行、列
由于图像中光照情况和背景的变化多样,梯度值的变化范围会比较大,因而良好的特征标准化对于检测率的提高相当重要。标准化的方法多种多样,大多数的都是将cell放在block中,然后标准化每个block。
以上述缩放后的图像为例,共得到27x38个cell,也就是将图像划分成了27x38个单元;将上下左右相邻的2x2个cells当做一个block整体,如下所示(为方便观察,每个颜色框故意错开了一点),黑色的8x8像素为一个cell,红、蓝、黄、粉红、绿框都是一个block,即每个框内2x2的cell组成一个block。故27x38个cell可划分成26x37个block,每个block为16x16像素。相邻block之间是有重叠的,这样有效的利用了相邻像素信息,对检测结果有很大的帮助。
接下分别对每个block进行标准化,一个block内有4个cell,每个cell含9维特征向量,故每个block就由4x9=36维特征向量来表征。
由于L2-norm简单且在检测中效果相对较好,故一般采用它。
经过上述对有重叠部分block的直方图归一化之后,将所有block的特征向量都组合起来,则形成26x37x36=34632维特征向量,这就是HOG特征,这个特征向量就可以用来表征整个图像了。
实际上,在运用的时候,我们通常是选取一幅图像中的一个窗口来进行特征提取,依然以上述220X310大小图像为例,经过缩放处理后为216x304,但并不直接提取整个图像的HOG特征,而是用一个固定大小的窗口在图像上滑动,滑动的间隔为8个像素,opencv中默认的窗口大小为128x64(高128,宽64),即有(128÷8)x(64÷8)=16x8个cell,也即有15x7个block,这样一来一幅图像就可以取到27-15)x(38-7)=12x31=372个窗口。现在提取每个窗口的HOG特征,则可得到105x36=3780维HOG特征向量。
将这330个3780维的HOG特征当做测试样本,用支持向量机(SVM)分类器来判别出,这些窗口的HOG特征是否有行人,有行人的用矩形框标记起来。HOG行人特征及所对应的SVM分类器的参数,在opencv中已经训练好了,我们只需要得到HOG特征,然后调用SVM即可得到判别结果。
代码:
//测试
void CCutImageVS2013Dlg::OnBnClickedTestButton1()
{
Mat image = imread("D:\\test\\xingren.jpg");
// 1. 定义HOG对象
HOGDescriptor hog(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);//HOG检测器,用来计算HOG描述子的
// 2. 设置SVM分类器
hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); // 采用已经训练好的行人检测分类器
// 3. 在测试图像上检测行人区域
vector regions;
hog.detectMultiScale(image, regions, 0, Size(8, 8), Size(32, 32), 1.05, 1);
// 显示
for (size_t i = 0; i < regions.size(); i++)
{
rectangle(image, regions[i], Scalar(0, 0, 255), 2);
}
imshow("HOG行人检测", image);
waitKey();
return;
}
代码结果:
个人认为前面这个人由于镜头没有聚焦,导致边缘模糊了,所以损失了梯度信息,因此没检测出来。
这篇是用Python实现的:80行Python实现-HOG梯度特征提取