之前读了一堆关于SVM的论文,最近终于开始用opencv的SVM来实战了,事实证明果然不能停留于理论,真正实践又花了一定时间,把自己的历程记录如下:
首先来看一下我的main函数:
int main() {
Mat data_mat, res_mat;
Mat res;
vector<string> img_path;
vector<int> img_catg;
int nLine = 0;
//loadCatePath(img_path, img_catg, nLine);
//生成测试数据
//createData(img_path, data_mat, res_mat, res, nLine);
//生成xml模型
//createXML(img_path, img_catg, data_mat, res_mat, res, nLine);
//测试
testSVM();
//system("pause");
return 0;
}
由于是第一次写SVM的代码,刚开始写的很乱,后来自己又整理了一下,大致就分为如上流程.
img_path存的是图片的路径,img_catg存的是图片对应的label,然后就先回到loadCatePath函数:
void loadCatePath(vector<string> &img_path, vector<int> &img_catg, int &nLine) {
string buf;
char c[10];
for (int i = 2; i < 10; ++i) {
_itoa(i, c, 10);
string tmp(c);
string addre = filename + tmp + "/trainpath.txt";
ifstream svm_data(addre);
while (svm_data) {
if (getline(svm_data, buf)) {
nLine++;
img_catg.emplace_back(i);
img_path.emplace_back(buf);
}
}
svm_data.close();
}
}
我的图片存放在多个文件夹下,文件夹名称从1、2、3一直到9,filename是公共目录,因此我们就可以用如上的方法实现依次读取文件夹,并用对应的label给图片“赋值”。
生成测试数据部分就因人而异了,针对各项目预处理方法都不同,就不多说,然后是生成XML模型部分:
void createXML(vector<string> &img_path, vector<int> &img_catg, Mat &data_mat, Mat &res_mat, Mat &res, int nImgNum) {
Mat trainImg = Mat::zeros(IMGHEIGHT, IMGWIDTH, CV_8UC3);
int hogImgWidht = 64, hogImgHeight = 64, n;
for (string::size_type i = 0; i < img_path.size(); ++i) {
res = imread(img_path[i].c_str(), 1);
cout << " processing " << img_path[i].c_str() << endl;
resize(res, trainImg, cv::Size(hogImgWidht, hogImgHeight), 0, 0, INTER_CUBIC);
HOGDescriptor *hog = new HOGDescriptor(cvSize(hogImgWidht, hogImgHeight), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);
vector<float> descriptors;
hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0));
if (i == 0) {
data_mat = Mat::zeros(nImgNum, descriptors.size(), CV_32FC1);
}
cout << "HOG dims: " << descriptors.size() << endl;
n = 0;
for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); ++iter) {
data_mat.at<float>(i, n) = *iter;
n++;
}
res_mat.at<float>(i, 0) = img_catg[i];
cout << " end processing " << img_path[i].c_str() << " " << img_catg[i] << endl;
}
CvSVM svm;
CvSVMParams param;
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, Mat(), Mat(), param);
svm.save("F:/compiler/opencv/forfun/SVM_DATA.xml");
}
都是调用了ML.h中的方法,那几个参数具体的功能还没测试过,之后需要多看一下,最后通过svm.train和svm.svm就在对应路径下生成了xml文件。
既然模型有了,接下来就可以针对我们自己的图片进行测试了:
void testSVM() {
IplImage *srcImg = cvLoadImage("D:\\other\\verifycode2\\2835.jpg");
showImage("2835", srcImg);
IplImage *grayImg = cvCreateImage(cvGetSize(srcImg), IPL_DEPTH_8U, 1);
//灰度
grayImage(srcImg, grayImg);
binary(grayImg);
cvSmooth(grayImg, grayImg, CV_MEDIAN);
drawContours(srcImg, grayImg);
cvReleaseImage(&srcImg);
cvReleaseImage(&grayImg);
}
这里有几个showImage、grayImage等函数,看名字就知道意思了,重点是在drawContours这里,将原图和处理后的二值图传递过去,因为我这里做的是验证码识别,用到了分割,这一部分的实现过程如下:
CvSeq* contours = NULL;
CvMemStorage* storage = cvCreateMemStorage(0);
cvFindContours(grayImg, storage, &contours, sizeof(CvContour), CV_RETR_EXTERNAL);
int count = 0;
int idx = 0;
int tempCount = 0;
cvSeqSort(contours, cmp_func);
int z = 0;
vector<float> descriptors;
vector<CvRect> v;
for (CvSeq* c = contours; c != NULL; c = c->h_next) {
v.emplace_back(cvBoundingRect(c, 0));
}
sort(v.begin(), v.end(), mycmp);
for (int i = 0; i < v.size(); ++i) {
CvRect rc = v[i];
if (rc.height > srcImg->width / 10) {
cvDrawRect(srcImg, cvPoint(rc.x, rc.y), cvPoint(rc.x + rc.width, rc.y + rc.height), CV_RGB(100, 100, 100));
count++;
IplImage* resImg = cvCreateImage(cvSize(rc.width, rc.height), srcImg->depth, srcImg->nChannels);
cvSetImageROI(srcImg, rc);
cvCopyImage(srcImg, resImg);
cvResetImageROI(srcImg);
Mat dstImg = (Mat)resImg;
resize(dstImg, dstImg, Size(64, 64));
//测试部分
CvSVM svm;
svm.load("SVM_DATA.xml");
HOGDescriptor *hog = new HOGDescriptor(cvSize(64, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);
vector<float>descriptors;//结果数组
Mat trainImg = Mat::zeros(64, 64, CV_8UC3);
resize(dstImg, trainImg, cv::Size(64, 64), 0, 0, INTER_CUBIC);
hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0)); //调用计算函数开始计算
Mat SVMtrainMat = Mat::zeros(1, descriptors.size(), CV_32FC1);
int n = 0;
for (vector<float>::iterator iter = descriptors.begin(); iter != descriptors.end(); iter++)
{
SVMtrainMat.at<float>(0, n) = *iter;
n++;
}
int ret = svm.predict(SVMtrainMat);
cout << ret;
}
cvReleaseMemStorage(&storage);
}
以上即为我与SVM的第一次亲密接触,接下来还有许多研究的机会,希望能有更多进展,最后附上我的结果图: