opencv学习笔记(七)SVM+HOG
一、简介
方向梯度直方图(Histogram of Oriented Gradient,HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度直方图来构成特征。Hog特征结合SVM分类器已经被广泛用于图像识别中,尤其在行人检测中获得了极大的成功。需要提醒的是,HOG+SVM进行行人检测的方法是法国研究院Dalal在2005的CVPR上提出的。
最近在做车标识别相关的研究,用到了SVM+HOG的方法进行识别,下面的例子,使用的数据样本是6类车标:本田、大众、丰田、现代、马自达和雪铁龙。
二、SVM+HOG进行车标识别
批处理:
首先在训练样本和测试样本的文件夹下,使用dos批处理命令:
dir /b > trainsamsFilenameDecribeTxt.txt dir /b > testsamsFilenameDecribeTxt.txt
得到训练样本和测试样本的文件名列表,如下所示:
注意将最后一行的“trainsamsFilenameDecribeTxt.txt”删掉。
然后需要将训练样本和测试样本文件夹所在的路径加到上述文件名列表的前面,形成样本的完整路径。我使用如下代码帮助我完成,
首先是训练样本:
bool ClogoRecognition::createTrainSamDescribeTxt()
{
string s;
ifstream in(DEFAULT_TRAINSAMPLES_FILESNAME_TXT_DECRIBE_PATH);
if (!in)
return FALSE;
ofstream out;
out.open(DEFAULT_TRAINSAMPLES_TXT_DECRIBE_PATH, ios::trunc); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
while (getline(in, s))//逐行读取数据并存于s中,直至数据全部读取
{
out <<DEFAULT_TRAINSAMPLES_PATH<< s.c_str() << '\n';//路径后面加上训练样本的filename
int n = s.c_str()[] - '';//每个训练样本文件都以数字开头命令,数字即代表该文件的类别
out << n << '\n';//每个样本的后面写入其类别,用于SVM训练时指定type
}
in.close();
out.close();
return TRUE;
}
运行结果:
对于训练样本,路径之后紧接着该文件对应的标签类别。便于之后的训练步骤。
其次是测试样本:
/*创建测试样本描述文件*/
bool ClogoRecognition::createTestSamDescribeTxt()
{
string s;
ifstream in(DEFAULT_TESTSAMPLES_FILESNAME_TXT_DESCRIBE_PATH);
if (!in)
return FALSE;
ofstream out;
out.open(DEFAULT_TESTSAMPLES_TXT_DECRIBE_PATH, ios::trunc); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
while (getline(in, s))//逐行读取数据并存于s中,直至数据全部读取
{
out << DEFAULT_TESTSAMPLES_PATH << s.c_str() << '\n';//路径后面加上测试样本的filename
}
in.close();
out.close();
return TRUE;
}
运行结果:
样本图片和数量,第一行从左往右依次为:本田、大众、丰田;第二行从左往右一次为:现代、马自达、雪铁龙;
SVM训练:
bool ClogoRecognition::svmTrain()
{
vector<string> img_path;//图像路径容器
vector<int> img_catg;//图像类别容器
int nLine = ;
string buf;
ifstream svm_data(DEFAULT_TRAINSAMPLES_TXT_DECRIBE_PATH);//训练样本图片的路径都写在这个txt文件中,使用bat批处理文件可以得到这个txt文件
if (!svm_data)
return FALSE;
unsigned long n;
while (svm_data)//将训练样本文件依次读取进来
{
if (getline(svm_data, buf))
{
nLine++;
if (nLine % == )//注:奇数行是图片全路径,偶数行是标签
{
img_catg.push_back(atoi(buf.c_str()));//atoi将字符串转换成整型,标志(0,1,2,...,9),注意这里至少要有两个类别,否则会出错
}
else
{
img_path.push_back(buf);//图像路径
}
}
}
svm_data.close();//关闭文件
CvMat *data_mat, *res_mat;
int nImgNum = nLine / ; //nImgNum是样本数量,只有文本行数的一半,另一半是标签
data_mat = cvCreateMat(nImgNum, , CV_32FC1); //第二个参数,即矩阵的列是由下面的descriptors的大小决定的,可以由descriptors.size()得到,且对于不同大小的输入训练图片,这个值是不同的
cvSetZero(data_mat);
//类型矩阵,存储每个样本的类型标志
res_mat = cvCreateMat(nImgNum, , CV_32FC1);
cvSetZero(res_mat);
IplImage* src;
IplImage* trainImg = cvCreateImage(cvSize(, ), , );//需要分析的图片,这里车标的尺寸归一化至40*32,所以上面定义了432,如果要更改图片大小,可以先用debug查看一下descriptors是多少,然后设定好再运行 //处理HOG特征
for (string::size_type i = ; i != img_path.size(); i++)
{
src = cvLoadImage(img_path[i].c_str(), );
if (src == NULL)
{
cout << " can not load the image: " << img_path[i].c_str() << endl;
continue;
} cout << " 处理: " << img_path[i].c_str() << endl; cvResize(src, trainImg);
HOGDescriptor *hog = new HOGDescriptor(cvSize(, ), cvSize(, ), cvSize(, ), cvSize(, ), );//图片尺寸:40*32;block尺寸:16*16;cell尺寸:8*8;检测窗口的滑动步长:8*8;一个单元格内统计9个方向的梯度直方图
vector<float>descriptors;//存放结果
hog->compute(trainImg, descriptors, Size(, ), Size(, )); //Hog特征计算
cout << "HOG dims: " << descriptors.size() << endl;
n = ;
for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++)
{
cvmSet(data_mat, i, n, *iter);//存储HOG特征
n++;
}
cvmSet(res_mat, i, , img_catg[i]);
cout << " 处理完毕: " << img_path[i].c_str() << " " << img_catg[i] << endl;
} // CvSVM svm = CvSVM();//新建一个SVM
CvSVM svm;
CvSVMParams param;//这里是SVM训练相关参数
CvTermCriteria criteria;
criteria = cvTermCriteria(CV_TERMCRIT_EPS, , FLT_EPSILON);
param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria);
// param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.3, 1.0, 5, 0.5, 1.0, NULL, criteria);
svm.train(data_mat, res_mat, NULL, NULL, param);//训练数据
//保存训练好的分类器
svm.save(DEFAULT_SVMMODEL_PATH);
cvReleaseMat(&data_mat);
cvReleaseMat(&res_mat);
cvReleaseImage(&trainImg);
return TRUE;
}
等待几分钟即可得到训练好的xml模型;
SVM测试:
bool ClogoRecognition::svmTest()
{
string buf;
CvSVM svm;
svm.load(DEFAULT_SVMMODEL_PATH);//加载训练好的xml文件
//检测样本
IplImage *test;
char result[];
vector<string> img_tst_path;
ifstream img_tst(DEFAULT_TESTSAMPLES_TXT_DECRIBE_PATH); //加载需要预测的图片集合,这个文本里存放的是图片全路径,不要标签
if (!img_tst)
return FALSE;
while (img_tst)
{
if (getline(img_tst, buf))
{
img_tst_path.push_back(buf);
}
}
img_tst.close(); ofstream predict_txt(DEFAULT_TESTSAMPLES_RECOGNITION_RESULT_TXT_DECRIBE_PATH);//把预测结果存储在这个文本中
for (string::size_type j = ; j != img_tst_path.size(); j++)//依次遍历所有的待检测图片
{
test = cvLoadImage(img_tst_path[j].c_str(), );
if (test == NULL)
{
cout << " can not load the image: " << img_tst_path[j].c_str() << endl;
continue;//结束本次循环
}
IplImage* trainTempImg = cvCreateImage(cvSize(, ), , );
cvZero(trainTempImg);
cvResize(test, trainTempImg);
HOGDescriptor *hog = new HOGDescriptor(cvSize(, ), cvSize(, ), cvSize(, ), cvSize(, ), );
vector<float>descriptors;//结果数组
hog->compute(trainTempImg, descriptors, Size(, ), Size(, ));
cout << "HOG dims: " << descriptors.size() << endl;
CvMat* SVMtrainMat = cvCreateMat(, descriptors.size(), CV_32FC1);
int n = ;
for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++)
{
cvmSet(SVMtrainMat, , n, *iter);
n++;
} int ret = svm.predict(SVMtrainMat);//检测结果
sprintf(result, "%s %d\r\n", img_tst_path[j].c_str(), ret);
predict_txt << result; //输出检测结果到文本
}
predict_txt.close();
cvReleaseImage(&test);
return TRUE;
}
运行SVM测试代码后,运行结果写入指定的txt中,每一行的最后一个数字代表该行路径下的图片的识别结果。如下图所示:
图片文件名中第一个数字代表其类别,当和该行中最后一个数字一致时,说明识别正确,否则识别错误。由图中可以看到,识别结果还是挺不错的。