使用机器学习的方法识别手写数字0和1

最近在学习OpenCV中机器学习的相关部分,想了解机器学习中从模型的训练到用模型去预测的具体实现过程,所以做了一个识别别手写数字0和1的简单项目(此文只识别手写数字0和1,如果想识别数字0到9,可以根据示例自己扩展),下面进行详细讲解。

这里用两种方式实现,一种是将模型训练和数字识别分别写在两个项目里,逻辑清晰,便于理解训练模型的过程和查看训练的结果;一种是将所有的功能写在一个项目里,可以直接查看最后的识别结果。

用于实验的数据:https://pan.baidu.com/s/1g212b14CBubZkASsxsEInw

我是将data文件夹存放在D盘,所以后面使用数据的时候,用到的绝对的路径为"D:\\data\\train_image\\1"。可以根据自己下载后存放的路径做相应的修改。用于训练的数字0和1各有400张图片,用于测试的数字0和1各有100张图片。

分两个项目实现

1.模型训练

#include <stdio.h>  
#include <time.h>  
#include <opencv2/opencv.hpp> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <opencv/cv.h>  
#include <iostream> 
#include <io.h>

using namespace std;
using namespace cv;

void getFiles(string path, vector<string>& files);
void get_1(Mat& trainingImages, vector<int>& trainingLabels);
void get_0(Mat& trainingImages, vector<int>& trainingLabels);

int main()
{
	//获取训练数据
	Mat classes;
	Mat trainingData;
	Mat trainingImages;
	vector<int> trainingLabels;
	//数字1先存入trainingImages,接着数字0存入trainingImages
	get_1(trainingImages, trainingLabels);//数字1贴标签
	get_0(trainingImages, trainingLabels);//数字0贴标签
	Mat(trainingImages).copyTo(trainingData);//将写好的包含特征的矩阵拷贝给trainingData
	trainingData.convertTo(trainingData, CV_32FC1);
	Mat(trainingLabels).copyTo(classes);//将包含标签的vector容器进行类型转换后拷贝到classes里
	//配置SVM训练器参数
	CvSVMParams SVM_params;
	SVM_params.svm_type = CvSVM::C_SVC;
	SVM_params.kernel_type = CvSVM::LINEAR;
	SVM_params.degree = 0;
	SVM_params.gamma = 1;
	SVM_params.coef0 = 0;
	SVM_params.C = 1;
	SVM_params.nu = 0;
	SVM_params.p = 0;
	SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);
	//训练
	CvSVM svm;
	svm.train(trainingData, classes, Mat(), Mat(), SVM_params);
	//保存模型
	svm.save("svm.xml");
	cout << "训练好了!!!" << endl;
	getchar();
	return 0;
}

//通过文件路径遍历文件夹,实现图像批处理
void getFiles(string path, vector<string>& files)
{
	long   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);

		_findclose(hFile);
	}
}

//将数字1的图像贴标签
void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
	/*这里使用绝对路径,也可以使用相对路径,先得将data文件夹放到.cpp所在的文件夹
	  char * filePath = "data\\train_image\\1";
	*/
	char * filePath = "D:\\data\\train_image\\1";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		//利用循环遍历文件夹里的每一张图像
		Mat  SrcImage = imread(files[i].c_str());
		/*特征提取
		  函数原型Mat reshape(int cn, int rows=0) const;
		  cn为新的通道数,如果cn = 0,表示通道数不会改变
		  rows为新的行数,如果rows = 0,表示行数不会改变
		  reshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量
		*/
		SrcImage = SrcImage.reshape(1, 1);
		//获取一张图片后会将图片(特征)写入到容器中,紧接着会将标签写入另一个容器中,这样就保证了特征和标签是一一对应的关系
		trainingImages.push_back(SrcImage);//将图片特征写入容器
		trainingLabels.push_back(1);//将标签写入容器
	}
}

//将数字0的图像贴标签
void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
	char * filePath = "D:\\data\\train_image\\0";
	vector<string> files;
	getFiles(filePath, files);//将路径对应的文件夹下的图片都存放在vector容器中
	int number = files.size();
	for (int i = 0; i < number; i++)//遍历文件夹
	{
		Mat  SrcImage = imread(files[i].c_str());
		SrcImage = SrcImage.reshape(1, 1);
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(0);
	}
}

训练好的模型保存在“svm.xml”文件中,使用的时候加载就好。运行结果和生成的xml如下:

运行过程中如果遇到下图所示错误,可以参考博客:https://blog.csdn.net/skye_95/article/details/81083145

2.数字识别

#include <stdio.h>  
#include <time.h>  
#include <opencv2/opencv.hpp>  
#include <opencv/cv.h>  
#include <iostream> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <io.h>

using namespace std;
using namespace cv;

void getFiles(string path, vector<string>& files);

int main()
{
	int result = 0;
	//也可以使用相对路径char * filePath = "data\\test_image\\0",data文件夹需先与.cpp放在同一个文件夹下
	char * filePath = "D:\\Projects\\visual studio 2013\\SVMTest1\\data\\test_image\\0";//识别数字0
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	cout << number << endl;
	CvSVM svm;
	svm.clear();
	/*当.xml文件与.cpp文件在同一个文件夹下时可以使用相对路径string modelpath ="svm.xml"
	  在不同文件夹下时需使用绝对路径
	*/
	string modelpath = "D:\\Projects\\visual studio 2013\\SVMTest2\\SVMTest2\\svm.xml";
	FileStorage svm_fs(modelpath, FileStorage::READ);
	if (svm_fs.isOpened())
	{
		svm.load(modelpath.c_str());//加载模型
	}
	for (int i = 0; i < number; i++)
	{
		Mat inMat = imread(files[i].c_str());
		Mat p = inMat.reshape(1, 1);
		p.convertTo(p, CV_32FC1);
		int response = (int)svm.predict(p);
		if (response == 0)
		{
			result++;//用result记录识别正确的个数
		}
	}
	cout << result << endl;
	getchar();
	return  0;
}

void getFiles(string path, vector<string>& files)
{
	long   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

使用相对路径加载模型时,需要将生成好的.xml模型复制到.cpp文件所在的文件夹。

最终的 运行结果为:

主函数中测试的是数字0的100张测试图片,最后识别正确的图片也是100张。

一个项目实现

准备工作:将data文件夹复制到main.cpp文件所在的文件夹

#include <stdio.h>  
#include <time.h>  
#include <opencv2/opencv.hpp> 
#include <opencv2/core/core.hpp>  
#include <opencv2/highgui/highgui.hpp>  
#include <opencv2/ml/ml.hpp>  
#include <opencv/cv.h>  
#include <iostream> 
#include <io.h>

using namespace std;
using namespace cv;

void getFiles(string path, vector<string>& files);
void get_1(Mat& trainingImages, vector<int>& trainingLabels);
void get_0(Mat& trainingImages, vector<int>& trainingLabels);
void train();

int main()
{
	int result = 0;
	//char * filePath = "D:\\data\\test_image\\0";
	char * filePath = "data\\test_image\\0";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	cout << number << endl;
	train();
	CvSVM svm;
	svm.clear();
	//string modelpath = "D:\\svm.xml";
	string modelpath = "svm.xml";
	FileStorage svm_fs(modelpath, FileStorage::READ);
	if (svm_fs.isOpened())
	{
		svm.load(modelpath.c_str());
	}
	for (int i = 0; i < number; i++)
	{
		Mat inMat = imread(files[i].c_str());
		Mat p = inMat.reshape(1, 1);
		p.convertTo(p, CV_32FC1);
		int response = (int)svm.predict(p);
		if (response == 0)
		{
			result++;
		}
	}
	cout << result << endl;
	getchar();
	return  0;
}

//通过文件路径遍历文件夹,实现图像批处理
void getFiles(string path, vector<string>& files)
{
	long   hFile = 0;
	struct _finddata_t fileinfo;
	string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			if ((fileinfo.attrib &  _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);

		_findclose(hFile);
	}
}

//将数字1的图像贴标签
void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
	//char * filePath = "D:\\data\\train_image\\1";
	char * filePath = "data\\train_image\\1";
	vector<string> files;
	getFiles(filePath, files);
	int number = files.size();
	for (int i = 0; i < number; i++)
	{
		//利用循环遍历文件夹里的每一张图像
		Mat  SrcImage = imread(files[i].c_str());
		/*特征提取
		函数原型Mat reshape(int cn, int rows=0) const;
		cn为新的通道数,如果cn = 0,表示通道数不会改变
		rows为新的行数,如果rows = 0,表示行数不会改变
		reshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量
		*/
		SrcImage = SrcImage.reshape(1, 1);
		//获取一张图片后会将图片(特征)写入到容器中,紧接着会将标签写入另一个容器中,这样就保证了特征和标签是一一对应的关系
		trainingImages.push_back(SrcImage);//将图片特征写入容器
		trainingLabels.push_back(1);//将标签写入容器
	}
}

//将数字0的图像贴标签
void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
	//char * filePath = "D:\\Projects\\visual studio 2013\\SVMTest1\\data\\train_image\\0";
	char * filePath = "data\\train_image\\0";
	vector<string> files;
	getFiles(filePath, files);//将路径对应的文件夹下的图片都存放在vector容器中
	int number = files.size();
	for (int i = 0; i < number; i++)//遍历文件夹
	{
		Mat  SrcImage = imread(files[i].c_str());
		SrcImage = SrcImage.reshape(1, 1);
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(0);
	}
}

//训练模型
void train()
{
	Mat classes;
	Mat trainingData;
	Mat trainingImages;
	vector<int> trainingLabels;
	//数字1先存入trainingImages,接着数字0存入trainingImages
	get_1(trainingImages, trainingLabels);//数字1贴标签
	get_0(trainingImages, trainingLabels);//数字0贴标签
	Mat(trainingImages).copyTo(trainingData);//将写好的包含特征的矩阵拷贝给trainingData
	trainingData.convertTo(trainingData, CV_32FC1);
	Mat(trainingLabels).copyTo(classes);//将包含标签的vector容器进行类型转换后拷贝到classes里
	//配置SVM训练器参数
	CvSVMParams SVM_params;
	SVM_params.svm_type = CvSVM::C_SVC;
	SVM_params.kernel_type = CvSVM::LINEAR;
	SVM_params.degree = 0;
	SVM_params.gamma = 1;
	SVM_params.coef0 = 0;
	SVM_params.C = 1;
	SVM_params.nu = 0;
	SVM_params.p = 0;
	SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);
	//训练
	CvSVM svm;
	svm.train(trainingData, classes, Mat(), Mat(), SVM_params);
	//保存模型
	svm.save("svm.xml");
	cout << "训练好了!!!" << endl;
}

运行结果如下:

参考博客:https://blog.csdn.net/chaipp0607/article/details/68067098

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值