Opencv之HOG特征与SVM相结合的人体检测

Hi洛基又回来了,最近正在学习计算机视觉和机器学习,这两门课程都要求做课程设计,于是我想到了这个一举两得(偷懒- -)的题目。

首先来说说图像处理领域的HOG特征:

HOG(histogram of oriented gradient) 是用于目标检测的特征描述子,由Navneet DalalBill Triggs于05年在CVPR首次提出,是用于静态图像或视频行人检测的实用方法。
HOG的核心思想:是利用物体的像素梯度以及边缘方向分布来描述该物体的appearance 和shape。

HOG算法步骤:

扫描一副图像,

①将图像灰度化

②采用Gamma校正法进行颜色空间的标准化(归一化)——调节图像的对比度,降低图像局部的阴影、光照变化所造成的影响,抑制噪音的干扰

③计算每个像素梯度的大小和方向

④将图像划分成小cells

⑤统计每个cell的梯度直方图

⑥将2x2cell或者3x3cell或者更多...组成一个block,一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。将图像内的所有block的HOG特征descriptor串联起来就可以得到该图像的HOG特征descriptor。

说了这么多,其实我这种一般的玩家并没有懂得HOG算法的深层数学性原理,以后有空要好好研究一下。下面来说说HOG与SVM的结合。

我利用opencv+VS2010 for win7 64bits,很方便地实现了HOG特征的提取和SVM训练。OpenCV中的HOG特征提取功能使用了HOGDescriptor这个类来进行封装,其中也有现成的行人检测的接口。

①HOGDescriptor类,默认构造函数如下(更详细资料请看http://blog.csdn.net/raodotcong/article/details/6239431)

winSize(64,128), blockSize(16,16), blockStride(8,8),  
        cellSize(8,8), nbins(9), derivAperture(1), winSigma(-1),  
        histogramNormType(HOGDescriptor::L2Hys), L2HysThreshold(0.2), gammaCorrection(true),  
        nlevels(HOGDescriptor::DEFAULT_NLEVELS)

winSize : 窗口的大小

blockSize :块的大小

blockStride:块步长

cellSize: 胞元的大小

nbins: 方向bin的个数 nBins表示在一个胞元(cell)中统计梯度的方向数目,例如nBins=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为360/9=40度

②HOGDescriptor.compute(src,descriptors,Size(8,8))方法:计算源图像src的描述子,步长为(8,8)

然后是SVM训练方法。首先需要了解CvSVM类,更详细资料请看http://blog.csdn.net/xidianzhimeng/article/details/41979785。

用opencv训练SVM的一般方法是:

①设置训练样本集

需要两组数据,一组是数据的类别,一组是数据的向量信息。

②设置SVM参数

利用CvSVMParams类实现类内的成员变量svm_type表示SVM类型:

CvSVM::C_SVC C-SVC

CvSVM::NU_SVCv-SVC

CvSVM::ONE_CLASS一类SVM

CvSVM::EPS_SVRe-SVR

CvSVM::NU_SVRv-SVR

成员变量kernel_type表示核函数的类型:

CvSVM::LINEAR线性:u‘v

CvSVM::POLY多项式:(r*u'v + coef0)^degree

CvSVM::RBFRBF函数:exp(-r|u-v|^2)

CvSVM::SIGMOIDsigmoid函数:tanh(r*u'v + coef0)

成员变量degree针对多项式核函数degree的设置,gamma针对多项式/rbf/sigmoid核函数的设置,coef0针对多项式/sigmoid核函数的设置,Cvalue为损失函数,在C-SVC、e-SVR、v-SVR中有效,nu设置v-SVC、一类SVM和v-SVR参数,p为设置e-SVR中损失函数的值,class_weightsC_SVC的权重,term_crit为SVM训练过程的终止条件。其中默认值degree = 0,gamma = 1,coef0 = 0,Cvalue = 1,nu = 0,p = 0,class_weights = 0

③训练SVM

调用CvSVM::train函数建立SVM模型,第一个参数为训练数据,第二个参数为分类结果,最后一个参数即CvSVMParams

用这个SVM进行分类

调用函数CvSVM::predict实现分类

④获得支持向量

除了分类,也可以得到SVM的支持向量,调用函数CvSVM::get_support_vector_count获得支持向量的个数,CvSVM::get_support_vector获得对应的索引编号的支持向量。

// step 1:  
float labels[4] = {1.0, -1.0, -1.0, -1.0};  
Mat labelsMat(3, 1, CV_32FC1, labels);  
  
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };  
Mat trainingDataMat(3, 2, CV_32FC1, trainingData);  
  
// step 2:  
CvSVMParams params;  
params.svm_type = CvSVM::C_SVC;  
params.kernel_type = CvSVM::LINEAR;  
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);  
  
// step 3:  
CvSVM SVM;  
SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);  
  
// step 4:  
Vec3b green(0, 255, 0), blue(255, 0, 0);  
for (int i=0; i<image.rows; i++)  
{  
    for (int j=0; j<image.cols; j++)  
    {  
        Mat sampleMat = (Mat_<float>(1,2) << i,j);  
        float response = SVM.predict(sampleMat);  
  
        if (fabs(response-1.0) < 0.0001)  
        {  
            image.at<Vec3b>(j, i) = green;  
        }  
        else if (fabs(response+1.0) < 0.001)  
        {  
            image.at<Vec3b>(j, i) = blue;  
        }  
    }  
}  
  
// step 5:  
int c = SVM.get_support_vector_count();  
  
for (int i=0; i<c; i++)  
{  
    const float* v = SVM.get_support_vector(i);  
}

好了接下来进入正题(咳咳,其实前面也是正题,不要在意我的措辞细节):

首先是找样本,正负样本当然是越多越好,我找的样本不多,正负样本各1200张左右,网上能找到很多有关人体检测的样本的,这里贴一下我的样本来源:http://pascal.inrialpes.fr/data/human/(很有名的INRIA行人数据库~)。样本搜集好之后,还要对正负样本的格式进行处理,我的处理步骤比较粗略:

①格式转换为统一的jpg格式(下载好的图片既有jpg也有png,所以为了处理方便,进行了统一转换);②正样本的大小进行归一化处理,我将正样本统一处理成64×128像素尺寸,负样本倒是不需要过多处理;③命名规则化——正负样本放入不同的文件夹,分别命名为1.jpg,2.jpg,3.jpg...这样做能方便图片的输入。

In particular...我刚开始自己编程写了个文件批量改名程序和格式转换程序,花了不少时间- -然后发现可以直接用专业的软件进行快速处理,当时心情简直了- -By the way,在这里洛基推荐一款图像处理方便挺不错的软件——ACDSee 18。除了会一直占用内存外,这款软件还是很好用的。

OK,now let's begin our game.

#include<iostream>
#include <fstream>
#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>

using namespace cv;
using namespace std;

#define PosSamNO 1126    //正样本个数
#define NegSamNO 1210    //负样本个数

//生成setSVMDetector()中用到的检测子参数时要用到的SVM的decision_func参数时protected类型,只能继承之后通过函数访问
class MySVM : public CvSVM
{
	public:
		//获得SVM的决策函数中的alpha数组
		double * get_alpha_vector()
		{
			return this->decision_func->alpha;
		}

		//获得SVM的决策函数中的rho参数,即偏移量
		float get_rho()
		{
			return this->decision_func->rho;
		}
};

int main()
{
	HOGDescriptor hog(Size(64,128),Size(16,16),Size(8,8),Size(8,8),9);//窗口大小(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9
	int DescriptorDim;//HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定
	MySVM svm;
	string ImgName;//图片名
	ifstream finPos("pos.txt");//正样本图片的文件名列表
	ifstream finNeg("neg.txt");//负样本图片的文件名列表
	Mat sampleFeatureMat;//所有训练样本的特征向量组成的矩阵,行数等于所有样本的个数,列数等于HOG描述子维数
	Mat sampleLabelMat;//训练样本的类别向量,行数等于所有样本的个数,列数等于1;1表示有人,-1表示无人

	//依次读取正样本图片,生成HOG描述子
	for(int num=0; num<PosSamNO && getline(finPos,ImgName); num++)
	{
		ImgName = "E:\\INRIAPerson\\Posjpg64_128\\" + ImgName;//加上正样本的路径名
		Mat src = imread(ImgName);//读取图片

		vector<float> descriptors;//HOG描述子向量
		hog.compute(src,descriptors,Size(8,8));//计算HOG描述子,检测窗口移动步长(8,8)

		//处理第一个样本时初始化特征向量矩阵和类别矩阵,因为只有知道了特征向量的维数才能初始化特征向量矩阵
		if( 0 == num )
		{
			DescriptorDim = descriptors.size();//HOG描述子的维数
			//初始化所有训练样本的特征向量组成的矩阵sampleFeatureMat,行数等于所有样本的个数,列数等于HOG描述子维数
			sampleFeatureMat = Mat::zeros(PosSamNO+NegSamNO, DescriptorDim, CV_32FC1);
			//初始化训练样本的类别向量,行数等于所有样本的个数,列数等于1;1表示有人,-1表示无人
			sampleLabelMat = Mat::zeros(PosSamNO+NegSamNO+HardExampleNO, 1, CV_32FC1);
		}

		//将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat
		for(int i=0; i<DescriptorDim; i++)
			sampleFeatureMat.at<float>(num,i) = descriptors[i];//第num个样本的特征向量中的第i个元素

		sampleLabelMat.at<float>(num,0) = 1;//正样本类别为1,有人
	}

	//处理负样本的流程和正样本大同小异
	for(int num=0; num<NegSamNO && getline(finNeg,ImgName); num++)
	{
		ImgName = "E:\\INRIAPerson\\Negjpg_undesign\\" + ImgName;//加上负样本的路径名
		Mat src = imread(ImgName);//读取图片

		vector<float> descriptors;//HOG描述子向量
		hog.compute(src,descriptors,Size(8,8));//计算HOG描述子,检测窗口移动步长(8,8)

		//将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat
		for(int i=0; i<DescriptorDim; i++)
			sampleFeatureMat.at<float>(num+PosSamNO,i) = descriptors[i];//第PosSamNO+num个样本的特征向量中的第i个元素
		sampleLabelMat.at<float>(num+PosSamNO,0) = -1;//负样本类别为-1,无人
	}

	//输出样本的HOG特征向量矩阵到文件
	ofstream fout("SampleFeatureMat.txt");
	for(int i=0; i<PosSamNO+NegSamNO; i++)
	{
	  fout<<i<<endl;
	  for(int j=0; j<DescriptorDim; j++)
	      fout<<sampleFeatureMat.at<float>(i,j)<<"  ";
	  fout<<endl;
	}

	//训练SVM分类器,迭代终止条件,当迭代满1000次或误差小于FLT_EPSILON时停止迭代
	CvTermCriteria criteria = cvTermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS, 1000, FLT_EPSILON);
	//SVM参数:SVM类型为C_SVC;线性核函数;松弛因子C=0.01
	CvSVMParams param(CvSVM::C_SVC, CvSVM::LINEAR, 0, 1, 0, 0.01, 0, 0, 0, criteria);
	cout<<"开始训练SVM分类器"<<endl;
	svm.train(sampleFeatureMat, sampleLabelMat, Mat(), Mat(), param);
	cout<<"训练完成"<<endl;
	svm.save("SVM_HOG.xml");//将训练好的SVM模型保存为xml文件

	DescriptorDim = svm.get_var_count();//特征向量的维数,即HOG描述子的维数
	cout<<"描述子维数:"<<DescriptorDim<<endl;
	int supportVectorNum = svm.get_support_vector_count();//支持向量的个数
	cout<<"支持向量个数:"<<supportVectorNum<<endl;

	Mat alphaMat = Mat::zeros(1, supportVectorNum, CV_32FC1);//alpha向量,长度等于支持向量个数
	Mat supportVectorMat = Mat::zeros(supportVectorNum, DescriptorDim, CV_32FC1);//支持向量矩阵
	Mat resultMat = Mat::zeros(1, DescriptorDim, CV_32FC1);//alpha向量乘以支持向量矩阵的结果

	//将支持向量的数据复制到supportVectorMat矩阵中,共有supportVectorNum个支持向量,每个支持向量的数据有DescriptorDim维(种)
	for(int i=0; i<supportVectorNum; i++)
	{
		const float * pSVData = svm.get_support_vector(i);//返回第i个支持向量的数据指针
		for(int j=0; j<DescriptorDim; j++)
			supportVectorMat.at<float>(i,j) = pSVData[j];//第i个向量的第j维数据
	}

	//将alpha向量的数据复制到alphaMat中
	//double * pAlphaData = svm.get_alpha_vector();//返回SVM的决策函数中的alpha向量
	double * pAlphaData = svm.get_alpha_vector();
	for(int i=0; i<supportVectorNum; i++)
	{
		alphaMat.at<float>(0,i) = pAlphaData[i];//alpha向量,长度等于支持向量个数
	}
            
        resultMat = -1 * alphaMat * supportVectorMat;//计算-(alphaMat * supportVectorMat),结果放到resultMat中,
       //注意因为svm.predict使用的是alpha*sv*another-rho,如果为负的话则认为是正样本,在HOG的检测函数中,
       //使用rho-alpha*sv*another如果为正的话是正样本,所以需要将后者变为负数之后保存起来
	//得到最终的setSVMDetector(const vector<float>& detector)参数中可用的检测子
	vector<float> myDetector;
	//将resultMat中的数据复制到数组myDetector中
	for(int i=0; i<DescriptorDim; i++)
	{
		myDetector.push_back(resultMat.at<float>(0,i));
	}
	myDetector.push_back(svm.get_rho());//最后添加偏移量rho,得到检测子
	cout<<"检测子维数:"<<myDetector.size()<<endl;
	//设置HOGDescriptor的检测子,用我们训练的检测器代替默认的检测器
	HOGDescriptor myHOG;
	myHOG.setSVMDetector(myDetector);

	//保存检测子参数到文件
	ofstream fout("HOGDetectorParagram.txt");
	for(int i=0; i<myDetector.size(); i++)
		fout<<myDetector[i]<<endl;

	//读入图片进行人体检测
	Mat src = imread("test1.png");
	vector<Rect> found, found_filtered;//矩形框数组
	cout<<"进行多尺度HOG人体检测"<<endl;
	myHOG.detectMultiScale(src, found, 0, Size(8,8), Size(32,32), 1.05, 2);//对图片进行多尺度行人检测
	cout<<"找到的矩形框个数:"<<found.size()<<endl;

	//找出所有没有嵌套的矩形框r,并放入found_filtered中,如果有嵌套的话,则取外面最大的那个矩形框放入found_filtered中
	for(int i=0; i < found.size(); i++)
	{
		Rect r = found[i];
		int j=0;
		for(; j < found.size(); j++)
		{
			if(j != i && (r & found[j]) == r)//说明r是被嵌套在found[j]里面的,舍弃当前的r
				break;
		}
		if( j == found.size())//r没有被嵌套在第0,1,2...found.size()-1号的矩形框内,则r是符合条件的
			found_filtered.push_back(r);
	}

	//对画出来的矩形框做一些大小调整
	for(int i=0; i<found_filtered.size(); i++)
	{
		Rect r = found_filtered[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(255,0,0), 2);
	}

	imwrite("ImgProcessed.jpg",src);
	namedWindow("src",0);
	imshow("src",src);
	waitKey();
	system("pause");
}



这些是部分检测结果图~其实检测效果一般,很多误判和漏检,所以需要注意的是,正样本一定要把格式处理好,最好是一张图片大部分是包含那个人体,其他物体、风景不要包含太多进去了,同时正负样本的数量要多~~等过两天改进一下,添加一个自举检测的功能试试看,据说效果很好,拭目以待吧~Bye~!

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值