本文是对下面这篇文章的一些略微详细的解释。。。
OpenCV Hog+SVM 学习
最近在学习数字识别,搜索资料的时候,发现了这篇文章。文章很久了,是2013年发的,那时候我才刚上大学。。。。。用的是HOG+SVM来进行手写体数字识别。现在都是用神经网络来进行手写体的识别。但是老的方法有些时候还是很好用的。
这篇文章写得很详细了,我在参考这篇文章复现这个小工程的时候遇到了一些问题,主要记录一下这些问题。。
训练需要的数据集以及一些需要的标签文本文件都在下面的网盘链接里,可以下载下来直接用
链接:https://pan.baidu.com/s/1aN-LSzL7y2RF2RDkjddcjA 密码:iays
第一个文件夹是训练样本,1.txt是训练样本路径和标签,HOG_SVM_DATA.XML文件是训练生成的模型,SVM_PTEDICT是预测保存的文本,SVM_TEST是测试样本的路径。。。这里面的我的测试样本很少,就三个,但是值预测出来了前两个。。。。
1.txt
奇数行为训练样本路径,偶数行为标签,这里就0-9,做起来挺麻烦的,我用Excel来做的,挺烦的,不想做的可以直接下载的用
SVM_TEST.txt
测试图片,不需要标签,添加路径即可
SVM_PTEDICT.txt
这是测试的结果,我的测试图片有三张,但是,测试结果是有两个。。。。。
下面开始贴代码。。。跟原文差不多。。改动的只有路径和一些小地方,因为我用的是VS2012和opencv2.4.10,有些地方需要改动。。。这里注意一下,这个工程opencv的版本最好用2不要用3,否则代码改动太大,我对编程一窍不通。。。。。会的,可以自己改。。。
#include "opencv2/opencv.hpp"
#include "windows.h"
#include "fstream"
#include <tchar.h>
using namespace std;
using namespace cv;
int _tmain(int argc, _TCHAR* argv[])
{
vector<string> img_path;//输入文件名变量
vector<int> img_catg;
int nLine = 0;
string buf;
ifstream svm_data( "D:/vistual studio2012/ConsoleApplication3/ConsoleApplication3/1.txt" );//训练样本图片的路径都写在这个txt文件中,使用bat批处理文件可以得到这个txt文件
unsigned long n;
while( svm_data )//将训练样本文件依次读取进来
{
if( getline( svm_data, buf ) )
{
nLine ++;
if( nLine % 2 == 0 )//注:奇数行是图片全路径,偶数行是标签
{
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 / 2; //nImgNum是样本数量,只有文本行数的一半,另一半是标签
data_mat = cvCreateMat( nImgNum, 324, CV_32FC1 ); //第二个参数,即矩阵的列是由下面的descriptors的大小决定的,可以由descriptors.size()得到,且对于不同大小的输入训练图片,这个值是不同的
cvSetZero( data_mat );
//类型矩阵,存储每个样本的类型标志
res_mat = cvCreateMat( nImgNum, 1, CV_32FC1 );
cvSetZero( res_mat );
IplImage* src;
IplImage* trainImg=cvCreateImage(cvSize(28,28),8,3);//需要分析的图片,这里默认设定图片是28*28大小,所以上面定义了324,如果要更改图片大小,可以先用debug查看一下descriptors是多少,然后设定好再运行
//处理HOG特征
for( string::size_type i = 0; i != img_path.size(); i++ )
{
src=cvLoadImage(img_path[i].c_str(),1);
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(28,28),cvSize(14,14),cvSize(7,7),cvSize(7,7),9);
vector<float>descriptors;//存放结果
hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); //Hog特征计算
cout<<"HOG dims: "<<descriptors.size()<<endl;
n=0;
for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
{
cvmSet(data_mat,i,n,*iter);//存储HOG特征
n++;
}
cvmSet( res_mat, i, 0, img_catg[i] );
cout<<" 处理完毕: "<<img_path[i].c_str()<<" "<<img_catg[i]<<endl;
}
CvSVM svm;//新建一个SVM
CvSVMParams param;//这里是SVM训练相关参数
CvTermCriteria criteria;
criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON );
param = CvSVMParams( CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria );
svm.train( data_mat, res_mat, NULL, NULL, param );//训练数据
//保存训练好的分类器
svm.save( "HOG_SVM_DATA.xml" );
//检测样本
IplImage *test;
char result[512];
vector<string> img_tst_path;
ifstream img_tst( "D:/vistual studio2012/ConsoleApplication3/ConsoleApplication3/SVM_TEST.txt" ); //加载需要预测的图片集合,这个文本里存放的是图片全路径,不要标签
while( img_tst )
{
if( getline( img_tst, buf ) )
{
img_tst_path.push_back( buf );
}
}
img_tst.close();
ofstream predict_txt( "SVM_PREDICT.txt" );//把预测结果存储在这个文本中
for( string::size_type j = 0; j != img_tst_path.size(); j++ )//依次遍历所有的待检测图片
{
test = cvLoadImage( img_tst_path[j].c_str(), 1);
if( test == NULL )
{
cout<<" can not load the image: "<<img_tst_path[j].c_str()<<endl;
continue;
}
IplImage* trainTempImg=cvCreateImage(cvSize(28,28),8,3);
cvZero(trainTempImg);
cvResize(test,trainTempImg);
HOGDescriptor *hog=new HOGDescriptor(cvSize(28,28),cvSize(14,14),cvSize(7,7),cvSize(7,7),9);
vector<float>descriptors;//结果数组
hog->compute(trainTempImg, descriptors,Size(1,1), Size(0,0));
cout<<"HOG dims: "<<descriptors.size()<<endl;
CvMat* SVMtrainMat=cvCreateMat(1,descriptors.size(),CV_32FC1);
int n=0;
for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
{
cvmSet(SVMtrainMat,0,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();
cvReleaseMat( &data_mat );
cvReleaseMat( &res_mat );
cvReleaseImage(&test);
cvReleaseImage(&trainImg);
return 0;
}
下面的是测试程序。。。用生成的xml文件,进行测试。。。
#include "opencv2/opencv.hpp"
#include "windows.h"
#include "fstream"
#include <tchar.h>
using namespace std;
using namespace cv;
int _tmain(int argc, _TCHAR* argv[])
{
CvSVM svm;
svm.load("HOG_SVM_DATA.xml");//加载训练好的xml文件,这里训练的是10K个手写数字
//检测样本
IplImage *test;
char result[300]; //存放预测结果
test = cvLoadImage("2.bmp", 1); //待预测图片,用系统自带的画图工具随便手写
if (!test)
{
MessageBox(NULL,TEXT("待预测图像不存在!"),TEXT("提示"),MB_ICONWARNING);
return -1;
}
IplImage* trainTempImg=cvCreateImage(cvSize(28,28),8,3);
cvZero(trainTempImg);
cvResize(test,trainTempImg);
HOGDescriptor *hog=new HOGDescriptor(cvSize(28,28),cvSize(14,14),cvSize(7,7),cvSize(7,7),9);
vector<float>descriptors;//存放结果
hog->compute(trainTempImg, descriptors,Size(1,1), Size(0,0)); //Hog特征计算
cout<<"HOG dims: "<<descriptors.size()<<endl; //打印Hog特征维数 ,这里是324
CvMat* SVMtrainMat=cvCreateMat(1,descriptors.size(),CV_32FC1);
int n=0;
for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
{
cvmSet(SVMtrainMat,0,n,*iter);
n++;
}
int ret = svm.predict(SVMtrainMat);//检测结果
sprintf(result, "%d\r\n",ret );
cvNamedWindow("dst",1);
cvShowImage("dst",test);
MessageBox(NULL,result,TEXT("预测结果"),MB_OK);
cvReleaseImage(&test);
cvReleaseImage(&trainTempImg);
return 0;
}
测试结果
亲测,好用。。。。
在复现这个工程的时候,最大的难题就是制作那个标签文本。。。原文说bat生成,但是我下载了一个bat,发现没那么好用,对于会编程的同学,可能编个程序就能实现,但是我不会,我就只能用EXcel。。。。。
程序的几个改动之处。。。这里面的原因我就不解释了,我自己也不太清楚,都是百度的解决方案。。。。
第一处
int _tmain(int argc, _TCHAR* argv[])
这句代码代码报错。。。。
我去掉了
#include "stdafx.h"
因为它本身也报错
添加了
#include <tchar.h>
上面那句代码就不会报错了
第二处
CvSVM svm = CvSVM();
这句代码报错。。。
将
CvSVM svm = CvSVM();
改为
CvSVM svm;
第三处
报错
“error C2664: “MessageBoxW”: 不能将参数 2 从“char [300]”转换为“LPCWSTR” 1> 与指向的类型无关;转换要求 reinterpret_”问题解决
具体的解决方案参考 这篇博文
“error C2664: “MessageBoxW”: 不能将参数 2 从“char [300]”转换为“LPCWSTR” 1> 与指向的类型无关;转换要求 reinterpret_”问题解决