Opencv之HOG特征与SVM相结合的人体检测(增加自举法)

Hello~洛基在上一篇关于人体检测的文章末尾提到了自举法,这里科普一下,所谓自举法,即在一个容量为n的原始样本中重复抽取一系列容量也是n的随机样本,并保证每次抽样中每一样本观察值被抽取的概率都为1/n。好像不是很通俗易懂,说人话就是——应用于行人检测中的自举方法是,对于训练好的HOG检测器,将原来进行训练的负样本当做检测样本,利用检测器进行检测,检测出来的图像必定是不包含人体的样本,这些不包含人体的样本集合便成为了自举训练样本,将自举训练样本处理之后重新作为负样本,输入到SVM中进行再一次训练,如此一来得到的检测器会“进化”为错检率更低的进化版检测器。

Of course,自举训练可以进行多次,这样分类的效果可能会更好,不过也不排除,在进行了多次自举训练之后,检测器出现大量漏检情况哦- -所以自举训练的次数,可能并不是越多越好,这是我在进行试验的小总结(认真脸)。

Okay进入正题,也就是代码部分。

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

using namespace std;
using namespace cv;

#define str "E:\\INRIAPerson\\HardExample\\"
int hardExampleCount = 0;

//因为在生成setSVMDetector中用到的检测子参数时要用到训练好的SVM的decision_func的protected类型参数alpha和rho,故只能通过继承之后利用函数访问了
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()
{
	Mat src;
	char saveName[256];//剪裁出来的hard example图片的文件名
	string ImgName;
	//ifstream fin_detector("HOGDetectorParagram.txt");
	ifstream fin_imgList("neg.txt");//打开原始负样本图片文件列表
        //每次在载入计算好的HOG描述子,然后设置SVM检测器时,都会报错——Assertion failed checkDetectorSize。令人抓狂的一个错误
        //于是只能每次都重新根据训练好的SVM.xml,计算HOG描述子,然后再设置SVM检测器了。这个问题留到后面再解决!

	cout<<"载入训练好的SVM分类器"<<endl;
	int DescriptorDim;//特征向量维数,即HOG描述子的维数
	MySVM svm;
	svm.load("SVM_HOG.xml");
	DescriptorDim = svm.get_var_count();
	cout<<"描述子维数:"<<DescriptorDim<<endl;
	int supportVectorNum = svm.get_support_vector_count();//支持向量的个数
	cout<<"支持向量个数:"<<supportVectorNum<<endl;

	//Mat alphaMat = Mat::zeros(1, supportVectorNum, CV_32FC1);//alpha向量,长度等于支持向量个数
	Mat alphaMat = Mat::zeros(1, supportVectorNum, CV_32FC1);
	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向量,长度等于支持向量个数
	}

	//计算-(alphaMat * supportVectorMat),结果放到resultMat中
	resultMat = -1 * alphaMat * supportVectorMat;//上一篇博文中有解释为什么是负号,这里不赘述

	//得到最终的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 hog(Size(64,128),Size(16,16),Size(8,8),Size(8,8),9);
	hog.setSVMDetector(myDetector);



	//从文件中读入自己训练的SVM参数
	//float temp;
	//vector<float> myDetector;//自己的检测器数组
	//while(!fin_detector.eof())
	//{
		//fin_detector >> temp;
		//myDetector.push_back(temp);
	//}
	//cout<<"检测子维数:"<<myDetector.size()<<endl;
	//设置检测器参数为自己训练的SVM参数
	//HOGDescriptor hog;
	//hog.setSVMDetector(myDetector);//上面说过了,如果直接读入之前保存好的HOG描述子txt文件,会一直报错= =no idea to solve it



	while(getline(fin_imgList,ImgName))
	{
		cout<<"处理:"<<ImgName<<endl;
		string fullName = "E:\\INRIAPerson\\Negjpg_undesign\\" + ImgName;
		src = imread(fullName);
		Mat img = src.clone();

		vector<Rect> found;
		hog.detectMultiScale(src, found, 0, Size(8,8),Size(32,32), 1.05, 2);
		//处理得到的矩形框
		for(int i=0; i < found.size(); i++)
		{
			//将矩形框轮廓限定在图像内部,r的x、y坐标是相对于源图像src来定义的
			Rect r = found[i];
			if(r.x < 0)
				r.x = 0;
			if(r.y < 0)
				r.y = 0;
			if(r.x + r.width > src.cols)
				r.width = src.cols - r.x;
			if(r.y + r.height > src.rows)
				r.height = src.rows - r.y;

			//将矩形框框出的图片保存为难例
			Mat hardExampleImg = src(r);//从原图上截取矩形框大小的图片
			resize(hardExampleImg,hardExampleImg,Size(64,128));//将剪裁出来的图片缩放为64*128大小
			sprintf(saveName,"hardexample%09d.jpg",hardExampleCount++);//生成hard example图片的文件名
			imwrite(saveName, hardExampleImg);//保存框出的图片部分,这里没办法添加一个路径名称,所以全部保存在VS项目里了,好乱= =

			//画矩形框,因为hog检测出的矩形框比实际人体框要稍微大些,所以这里需要做一些调整
			r.x += cvRound(r.width*0.1);
			r.width = cvRound(r.width*0.8);
			rectangle(img, r.tl(), r.br(), Scalar(0,255,0), 2);
		}
		imwrite(str+ImgName,img);//将处理过的图像保存下来,这里保存的就是原始负样本加上各种框框之后的结果图片
		imshow("src",src);
		waitKey(20);
	}
	system("pause");
}

这个是经过自举法之后得到的新检测器的成果,是不是很Duang Duang Duang?来对比一下没有通过自举法得到的检测结果:

其实我只利用了一次自举训练,便得到了以上的良好检测效果,因为检测的正负样本基数不够大,所以不敢进行多次自举训练,担心会造成大量漏检。最后值得一提的是,利用原始1000多张负样本进行自举检测,竟然得到了7000+张新的负样本,这说明原来的检测器检测效果堪忧。。。不过好在拥有了自举训练这个关键的idea,使得我们的检测器功能优化了很多~

Last but not least,洛基祝大家新年快乐!大家在刻苦敲代码的同时也别忘了去锻炼身体哦!现在是成都时间2017年1月3日凌晨12点38分

Nighty night~ :-D

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值