svm+hog的手写体识别
svm训练.xml文件
#include "opencv2/opencv.hpp"
#include "iostream"
#include "fstream"
using namespace cv;
using namespace std;
void getdata()
{
vector<string> img_path;//输入文件名变量
vector<int> img_catg; //标签
int nLine = 0;
int i;
string buf;
char c[20];
ifstream svm_data( "bh.txt" );//训练样本图片的路径都写在这个txt文件中,bat批处理, ifstream从已有的文件读
unsigned long n;
while( svm_data )//将训练样本文件依次读取进来
{
if( getline( svm_data, buf )) //将输入流svm_data中读到的字符存入buf中,getline把svm_data中的文件输出到buf字符串
{
nLine ++;
if( nLine % 2 == 0 )//注:奇数行是图片全路径,偶数行是标签 ---一半训练一半测试
{
strcpy(c,buf.c_str());
img_catg.push_back(c[0]-'0');
//sscanf( buf.c_str(), "%d", &i );
// img_catg.push_back( atoi( buf.c_str() ) ); //int atoi(const char *nptr);atoi函数遇到字符开始转换遇到非字符停止转换
//atoi将字符串转换成整型,标志(0,1,2,...,9),注意这里至少要有两个类别,否则会出错
// 函数声明:const char *c_str(); c_str()函数返回一个指向正规字符串的指针, 内容与本字符串相同
}
else
{
img_path.push_back( buf );//图像路径
}
}
}
svm_data.close(); //关闭文件
//CvMat *data_mat, *res_mat; //定义样本矩阵,类型矩阵
int nImgNum = nLine / 2; //nImgNum:横坐标是样本数量,只有文本行数的一半,另一半是标签
Mat data_mat=Mat::zeros(nImgNum, 324, CV_32FC1); //样本矩阵
Mat res_mat=Mat::zeros(nImgNum, 1, CV_32FC1); //类型矩阵,存储每个样本的类型标志
Mat trainImg=cvCreateImage(Size(28,28),8,3);
Mat src;
for(int i = 0; i<img_path.size()-1; i++ )
{
if(img_path[i].c_str()==NULL)
break;
src=imread(img_path[i].c_str(),1);
if( src.empty() == 1 )
{
cout<<" can not load the image: "<<img_path[i].c_str()<<endl;
break;
}
cout<<" 处理: "<<img_path[i].c_str()<<endl;
resize(src,trainImg,Size(28,28)); //读取图片,归一化处理,
HOGDescriptor *hog=new HOGDescriptor(Size(28,28),Size(14,14),Size(7,7),Size(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;
Mat descriptors_mat=Mat(descriptors);
Mat row1=descriptors_mat.reshape(0,1);//矩阵转置成行
for(int j=0;j<row1.cols;j++)//把每一个数字的特征存储为Mat矩阵的一行
{
data_mat.at<float>(i,j)=row1.at<float>(0,j);//存储HOG特征到data_mat矩阵中 at(row,cols)=at(y,x)
}
//res_mat.at<int>(i,0)=row2.at<int>(0,i);
//为每一个数字创建一个标签
/*cout<<"data_mat"<<data_mat.cols<<endl;
cout<<"res_mat"<<res_mat.cols<<endl;*/
//for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++) //迭代器
//{
// cvmSet(data_mat,i,n,*iter);//存储HOG特征到data_mat矩阵中
// n++;
//}
//cvmSet( res_mat, i, 0, img_catg[i] ); //输出矩阵,第i行第j列,输入需转置的vector
//cout<<" 处理完毕: "<<img_path[i].c_str()<<" "<<img_catg[i]<<endl;
}
Mat img_catg_mat=Mat(img_catg);
Mat row2=img_catg_mat.reshape(0,1);//矩阵转置成行
res_mat = row2.t(); //把第i行变成第j列
CvSVM svm;
CvSVMParams params;
params.svm_type = CvSVM::C_SVC;
params.kernel_type = CvSVM::LINEAR;
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
svm.train(data_mat,res_mat,Mat(),Mat(),params);
svm.save( "HOG_SVM_DATA.xml" );
}
void main()
{
getdata();
/*Mat src=imread("\0\1.jpg");
imshow("src",src);*/
waitKey(0);
}
我训练的过程中遇到过几个问题
1.svm.train报错,原因是由于svm必须要有大于两个的标签
2.矩阵转置问题:由于svm训练时监督式学习,需要给计算机标签和训练样本,svm的训练样本是把一副图片通过hog提取特征,然后把一副图片的特征保存为矩阵的一行,那么有多少图片就有多少的列,转置成行的函数是reshape函数,标签是0,1,,2,3,4,5,6,7,8,9对应图片数字,标签矩阵是1列,以图片数量为行,转置成列的方法是先把矩阵转置成行,然后用Mat.t()函数行列互换
利用训练好的.xml文件识别数字
#include "opencv2/opencv.hpp"
#include "iostream"
using namespace cv;
using namespace std;
void main()
{
CvSVM svm;
CvSVMParams params;
params.svm_type = CvSVM::C_SVC;
params.kernel_type = CvSVM::LINEAR;
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
Mat test=imread("2.jpg");
Mat trainTempImg=Mat::zeros(28,28,CV_8UC3);
resize(test,trainTempImg,Size(28,28));
HOGDescriptor *hog=new HOGDescriptor(cvSize(28,28),cvSize(14,14),cvSize(7,7),cvSize(7,7),9);
vector<float>descriptors;//结果数组
//计算hog特征
hog->compute(trainTempImg, descriptors,Size(1,1), Size(0,0));
Mat descriptors_Mat=Mat(descriptors);
Mat data=descriptors_Mat.reshape(0,1);
svm.load("HOG_SVM_DATA.xml");
int ret = svm.predict(data);
cout<<ret<<endl;
imshow("rest",test);
waitKey(0);
}
相比较与我上次写的knn手写体识别svm的离线学习方式有更大的效率