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

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

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

HOG(histogram of oriented gradient) 是用于目标检测的特征描述子,由 Navneet Dalal Bill 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(1,2) << i,j);  
        float response = SVM.predict(sampleMat);  
  
        if (fabs(response-1.0) < 0.0001)  
        {  
            image.at(j, i) = green;  
        }  
        else if (fabs(response+1.0) < 0.001)  
        {  
            image.at(j, i) = blue;  
        }  
    }  
}  
  
// step 5:  
int c = SVM.get_support_vector_count();  
  
for (int i=0; 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
#include 
#include 
#include 
#include 
#include 
#include 

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 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(num,i) = descriptors[i];//第num个样本的特征向量中的第i个元素

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

	//处理负样本的流程和正样本大同小异
	for(int num=0; num descriptors;//HOG描述子向量
		hog.compute(src,descriptors,Size(8,8));//计算HOG描述子,检测窗口移动步长(8,8)

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

	//输出样本的HOG特征向量矩阵到文件
	ofstream fout("SampleFeatureMat.txt");
	for(int i=0; i(i,j)<<"  ";
	  fout<(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(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& detector)参数中可用的检测子
	vector myDetector;
	//将resultMat中的数据复制到数组myDetector中
	for(int i=0; i(0,i));
	}
	myDetector.push_back(svm.get_rho());//最后添加偏移量rho,得到检测子
	cout<<"检测子维数:"< found, found_filtered;//矩形框数组
	cout<<"进行多尺度HOG人体检测"<

Opencv之HOG特征与SVM相结合的人体检测_第1张图片

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








你可能感兴趣的:(opencv)