46次浏览

opencv使用SVM分类器实现简单数字识别

HI, 在开始之前你必须了解一些关于机器学习的知识, 例如决策树等等, 那么我下面给出机器学习的一些教程, 相信你看完之后再来看本文章中的代码会有更深的理解。

谷歌机器学习第一季视频教程

https://youtu.be/cKxRvEZd3

上海交通大学博士卢宪凯出的机器学习视频教程

https://www.bilibili.com/video/av45782812/?p=1

opencv svm手写数字识别教程

https://blog.csdn.net/weixin_41721222/article/details/84953788

以上教程都讲的非常棒!

本人也是小小菜鸟一枚, 刚刚入门而已, 涉及的数学公式以及推导过程本文就不作赘述了, 暂时没那个能力, 好好学习, 天天向上!  ^ – ^

那么我们直入主题, 首先下面给出一张记事本中写的一串0-9的数字图片

test number
test number

把数字进行分割,这里我们采用手动截出尺寸一样且单个的形式。(这里其实可以采用轮廓检测, 把图片截取出来)

single
single

然后再保存到相应文件夹, 方便训练, 我这边创建10个文件夹名称以0-9命名,  把图片按文件夹名称丢进去即可。

file
file

现在我们需要的样本OK了, 接下来就是提取特征, 丢入svm, 对应上相应标签进行训练, 训练好再进行预测即可, 那么我们开始编写代码.  我们只是简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程。当然了, 更好的特征是CNN。

编写代码读取0-9文件夹中的图片, 对图片进行二值化, 简单特征提取, 插入容器, 标上label

ostringstream oss;
int num = -1;
Mat dealimage;
Mat yangben_gray;
Mat yangben_thresh;

////===============================读取训练数据===============================////
const int classsum = 10;//图片共有10类,可修改
						//训了样本图片与测试图片的尺寸应该一样
const int imageRows = 9;//图片尺寸
const int imageCols = 11;
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
for (int p = 0; p < classsum; p++)//依次提取0到9文件夹中的图片
{
	oss << "number\\";
	num += 1;//num从0到9
	int label = num;
	oss << num << "\\*.png";//图片名字后缀,oss可以结合数字与字符串
	string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern



	oss.str("");//每次循环后把oss字符串清空
	vector<Mat> input_images;
	vector<String> input_images_name;
	glob(pattern, input_images_name, false);
	//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
	//此时input_images_name存放符合条件的图片地址
	int all_num = input_images_name.size();
	//文件下总共有几个图片
	cout << num << ":总共有" << all_num << "个图片待测试" << endl;

	for (int i = 0; i < all_num; i++)//依次循环遍历每个文件夹中的图片
	{
		cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换

																			 //设置图片大小
		resize(yangben_gray, yangben_gray, Size(imageRows, imageCols));
		threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化

																	 //循环读取每张图片并且依次放在vector<Mat> input_images内
		input_images.push_back(yangben_thresh);
		dealimage = input_images[i];


		//我们利用reshape()函数完成特征提取,
		//eshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。 
		dealimage = dealimage.reshape(1, 1);//图片序列化
		trainingData.push_back(dealimage);//序列化后的图片依次存入
		labels.push_back(label);//把每个图片对应的标签依次存入
	}
}

//图片数据和标签转变下
Mat(trainingData).copyTo(traindata);//复制
traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
Mat(labels).copyTo(clas);//复制

现在有了标签以及特征, 开始创建SVM模型, 并且设置参数

////===============================创建SVM模型===============================////
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR);  //LINEAR线性核函数。SIGMOID为高斯核函数

SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数; 
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数; 
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值. 
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));

//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, clas);

训练分类器, 这里的保存模型是为了方便以后, 不用每次训练, 来进行预测, 下次直接调用保存好的模型svm.xml预测即可。

// 训练分类器
SVM_params->train(tData);//训练

//保存模型
SVM_params->save("svm.xml");
cout << "训练完毕..." << endl;

输入一张图片, 进行二值化处理, 序列化图片, 转类型, 然后预测, 这里输入的图片, 是刚才用来训练的图片, 免得我们再去截图, 直接用它来测试即可。(这里注意图片尺寸和训练输入图片尺寸一样)

Mat src = imread("number\\5\\5.png");

cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, THRESH_OTSU);

Mat img;

resize(src, img, Size(src.cols * 10, src.rows * 10), 0, 0, INTER_LINEAR);
imshow("img", img);

Mat input;
src = src.reshape(1, 1);//输入图片序列化
src.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错


float r = SVM_params->predict(input);//预测结果
cout << "预测结果: " << r << endl;

我们这里输入的是数字5的截图 , 我们运行程序, 预测结果是: 5  非常ok的结果,它成功判断出这张图片的数字。

test
test

由于这种记事本中的数字非常的规则不像我们手写, 所以我这边采用的数据集只有10张图片, 如果是识别手写的话, 那就得提供很多不同手写风格的样本来提高泛化能力, 这样下来的话每个文件下就会有不同风格的手写图片,使其精确率提高。

前面我们完成了SVM的训练, SVM模型的保存, 以及预测, 每次都要训练一次未免也太麻烦了, 这还是少量数据, 多的话非哭不可, 那么现在来介绍一下如何使用保存好的SVM模型xml文件, 几行代码搞定。

//调用训练好的模型
Ptr<cv::ml::SVM> SVM_params = cv::ml::SVM::StatModel::load<cv::ml::SVM>("svm.xml");

//进行预测
cvtColor(Roi, Roi, COLOR_BGR2GRAY);
threshold(Roi, Roi, 0, 255, CV_THRESH_OTSU);

Mat input;
Roi = Roi.reshape(1, 1);//输入图片序列化
Roi.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错

float r = SVM_params->predict(input);//预测结果

发表评论

电子邮件地址不会被公开。 必填项已用*标注