行人检测之HOG——方向梯度直方图(Histogram of Oriented Gradient)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_14845119/article/details/52187774

写在前面的话

       HOG绝对说的是行人检测领域的一个标志性的里程碑,虽然2008年出现的DPM(DeformablePart Model)方法取得了更好的检测效果,但是实时性实在堪忧,就算用多线程实现也得大约200ms每帧的样子,勉强可以用于实时检测。而就算是DPM在中间过程还是用到了HOG

       HOG的发明者是Navneet Dalal,在2005年其在CVPR上发表了Histograms of Oriented Gradients forHuman Detection这一篇论文,HOG一战成名。当然ND大神也就是我们经常使用的Inria数据集的缔造者。其博士的毕业论文Finding People in Images and Videos更是HOG研究者的一手资料。让我们不经感慨,人家的毕业论文才叫真干货啊。

HOG算法思想:

      在计算机视觉以及数字图像处理中梯度方向直方图(HOG)是一种能对物体进行检测的基于形状边缘特征的描述算子,它的基本思想是利用梯度信息能很好的反映图像目标的边缘信息并通过局部梯度的大小将图像局部的外观和形状特征化。一些研究者利用梯度HOG特征并结合其他特征对人体进行检测得到了较好的结果。

       HOG特征的提取可以用下图所示的过程表示: 颜色空间的归一化是为了减少光照以及背景等因素的影响;划分检测窗口成大小相同的细胞单元(cell),并分别提取相应的梯度信息;组合相邻的细胞单元成大的相互有重叠的块(block),这样能有效的利用重叠的边缘信息,以统计整个块的直方图;并对每个块内的梯度直方图进行归一化,从而进一步减少背景颜色及噪声的影响;最后将整个窗口中所有块的HOG特征收集起来,并使用特征向量来表示其特征。在这一过程中,不同尺度的参数模板、梯度方向的选择、重叠块及单元格的大小还有归一化因子等因素都会影响最终的检测结果。最终通过SVM分类器分离出正确的行人目标。

       HOG的整体流程图如下所示。



颜色空间归一化:

在现实的情况,图像目标会出现在不同的环境中,光照也会有所不一样,颜色空间归一化就是对整幅图像的颜色信息作归一化处理从而减少不同光照及背景的影响,也为了提高检测的鲁棒性,引入图像Gamma和颜色空间归一化来作为特征提取的预处理手段。ND大神等人也对不同的图像像素点的表达方式包括灰度空间等进行了评估,最终验证RGB还有LAB色彩空间能使检测结果大致相同且能起到积极的影响,且另一方面,ND大神等人在研究中分别在每个颜色通道上使用了两种不同的Gamma归一化方式,取平方根或者使用对数法,最终验证这一预处理对检测的结果几乎没有影响,而不能对图像进行高斯平滑处理,因平滑处理会降低图像目标边缘信息的辨识度,影响检测结果。

梯度计算:

边缘是由图像局部特征包括灰度、颜色和纹理的突变导致的。一幅图像中相邻的像素点之间变化比较少,区域变化比较平坦,则梯度幅值就会比较小,反之,则梯度幅值就会比较大。梯度在图像中对应的就是其一阶导数。模拟图像f(x,y)中任一像素点(x,y)的梯度是一个矢量:


其中,Gx是沿x方向上的梯度,Gy是沿y方向上的梯度,梯度的幅值及方向角可表示如下:


数字图像中像素点的梯度是用差分来计算的:

一维离散微分模板在将图像的梯度信息简单、快速且有效地计算出来,其公式如下:


式中,Gx,Gy,H(x,y)分别表示的是像素点(x,y)在水平方向上及垂直方向上的梯度以及像素的灰度值,其梯度的幅值及方向计算公式如下:

ND大神等人也验证,不同的梯度运算模板在其检测效果上也不一样,如下表,可以看出,使用简单的一维离散微分模板[-1,0,1]进行的梯度运算得到的检测效果是最好的,而使用其他形式的梯度运算模板如Prewitt和Sobel等算子,如下图所示,不仅增加运算量而同时也降低了其检测效果。


计算细胞单元的梯度直方图:

对于整个目标窗口,我们需要将其分成互不重叠大小相同的细胞单元(cell),然后分别计算出每个cell的梯度信息,包括梯度大小和梯度方向。ND大神等人实验指出,将像素的梯度方向在0-180°区间内平均划分为9个bins,超过9个时不仅检测性能没有明显的提高反而增加了检测运算量, 每个cell内的像素为其所在的梯度方向直方图进行加权投票,加权的权值可以是像素本身的梯度幅值,也可以是幅值的平方或平方根等,而若使用平方或平方根,实验的检测性能会有所降低,ND大神等人也验证,使用梯度幅值的实验效果更可靠。


梯度直方图的计算

对组合成块的梯度直方图作归一化:

从梯度计算公式中可以看出,梯度幅值绝对值的大小容易受到前景与背景对比度及局部光照的影响,要减少这种影响得到较准确的检测效果就必须对局部细胞单元进行归一化处理。归一化方法多种多样,但整体思想基本上是一致的:将几个细胞单元(cell)组合成更大的块(block),这时整幅图像就可看成是待检测窗口,将更大的块看成是滑动窗口,依次从左到右从上到下进行滑动,得到一些有重复细胞单元的块及一些相同细胞单元(cell)在不同块(block)中的梯度信息,再对这些块(block)信息分别作归一化处理,不同的细胞单元尺寸大小及不同块的尺寸大小会影响最终的检测效果。

假设64×64像素是检测的窗口的尺寸,分成 4×4=16 个细胞单元(cell),如下图中黑色的小框(1-16),16×16 像素是每个细胞单元的大小,块(block)是由相邻的 2×2=4 个细胞单元组成的,如图紫色的小框,滑动窗口的大小为一个块的大小,依次将滑动窗口从左到右从上到下进行滑动来获得整个待测窗口的边缘信息,得到9个块,统计这9个块在9个不同方向上的梯度信息,在整个窗口中得到的梯度特征是9×9=81 维的向量。在实际情况中我们的检测窗口的大小为128×64像素,一个细胞单元的大小为8×8像素,由2×2个细胞单元组成大小为16×16像素的块,一个细胞单元的梯度直方图化成9个bins,块的移动步长是8个像素,则检测窗口在图像中移动的步长也为8个像素,这样检测窗口就有((128-16)/8+1)×((64-16)/8+1)= 105个块,一个块有4个细胞单元,每个细胞单元的HOG特征向量长度是9,则最终的HOG特征描述符大小就是105×4×9 = 3780维。



对于块的梯度直方图向量的归一化,ND大神等人使用了不同方法,并对结果进行了比较,假设V是未归一化的向量,是一个很小的必要的常数,下式是定义的范数的函数表达式:

L1范数:

L2范数:

归一化计算公式为:

L2常规:

L1常规:

L1平方根:

ND大神等人指出,用L1常规方法进行向量归一化比用L2常规和L1平方根方法而得到的检测效果降低5%。


对归一化梯度特征进行SVM分类:

如下图所示,对于滑动窗口提取的2个窗口,分别计算出归一化的梯度特征,然后应用SVM实现是人还是背景的分类判定。

程序实现:

       opencv中包含了2种HOG的实现途径,一种是HOG+SVM的实现方法,另一种是HOG+Cascade的实现方法。两种方法各有千秋,具体实现程序如下所示。

#include <iostream>
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/ml/ml.hpp>
#include<ctime>

using namespace std;
using namespace cv;

int main()
{
	cout<<"Red:Hog+svm------Green:Hog+cascade"<<endl;
	Mat src = imread("1.jpg",1);
	vector<Rect> found1, found_filtered1,found2, found_filtered2;//矩形框数组

	clock_t start1,end1,start2,end2;
	//方法1,Hog+svm
	start1 = clock();
	HOGDescriptor hog;//HOG特征检测器
	hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());//设置SVM分类器为默认参数	
	hog.detectMultiScale(src, found1, 0, Size(2,2), Size(0,0), 1.05, 2);//对图像进行多尺度检测,检测窗口移动步长为(8,8)
	end1=(double)(1000*(clock()-start1)/CLOCKS_PER_SEC);
	//方法2.Hog+cascade
	start2 = clock();
	CascadeClassifier *cascade = new CascadeClassifier;
	cascade->load("hogcascade_pedestrians.xml");
	cascade->detectMultiScale(src, found2);
	end2=(double)(1000*(clock()-start2)/CLOCKS_PER_SEC) ;

	cout<<"Hog+svm:  "<<end1<<"ms"<<"    Hog+cascade:  "<<end2<<"ms"<<endl;
	//找出所有没有嵌套的矩形框r,并放入found_filtered中,如果有嵌套的话,则取外面最大的那个矩形框放入found_filtered中
	for(int i=0; i < found1.size(); i++)
	{
		Rect r = found1[i];
		int j=0;
		for(; j < found1.size(); j++)
			if(j != i && (r & found1[j]) == r)
				break;
		if( j == found1.size())
			found_filtered1.push_back(r);
	}
	for(int i=0; i < found2.size(); i++)
	{
		Rect r = found2[i];
		int j=0;
		for(; j < found2.size(); j++)
			if(j != i && (r & found2[j]) == r)
				break;
		if( j == found2.size())
			found_filtered2.push_back(r);
	}

	//画矩形框,因为hog检测出的矩形框比实际人体框要稍微大些,所以这里需要做一些调整
	for(int i=0; i<found_filtered1.size(); i++)
	{
		Rect r = found_filtered1[i];
		r.x += cvRound(r.width*0.1);
		r.width = cvRound(r.width*0.8);
		r.y += cvRound(r.height*0.07);
		r.height = cvRound(r.height*0.8);
		rectangle(src, r.tl(), r.br(), Scalar(0,0,255), 3);
	}
	for(int i=0; i<found_filtered2.size(); i++)
	{
		Rect r = found_filtered2[i];
		r.x += cvRound(r.width*0.1);
		r.width = cvRound(r.width*0.8);
		r.y += cvRound(r.height*0.07);
		r.height = cvRound(r.height*0.8);
		rectangle(src, r.tl(), r.br(), Scalar(0,255,0), 3);
	}
	imshow("src",src);
	waitKey();

	system("pause");
	return 0;
}

运行当前目录下的Shell脚本,run.sh,就会生成运行结果,程序运行效果如下图所示,其中红色的为HOG+SVM的检测效果,绿色的为HOG+Cascade的检测效果。其中,HOG+SVM的运行时间为739ms, HOG+Cascade的运行时间为30ms.




    经过多张图片的测试可以得出下表的结论



程序下载链接为https://github.com/watersink/hog

        2篇论文下载链接为http://download.csdn.net/detail/qq_14845119/9601306




猜你喜欢

转载自blog.csdn.net/qq_14845119/article/details/52187774