1、数据准备

在OpenCV的安装路径下,搜索digits.png,可以得到一张图片,图片大小为1000* 2000,有0-9的10个数字,每5行为一个数字,总共50行,共有5000个手写数字,每个数字块大小为20* 20。 如下图所示:
在这里插入图片描述
下面将把这些数字中的0和1作为二分类的准备数据。其中0有500张,1有500张。

代码如下:

#include <opencv2/opencv.hpp>
#include <iostream>
#include<sstream>
#include<string>
using namespace std;
using namespace cv;

int main()
{
	//char ad[128] = { 0 };
	int  filename = 0, filenum = 0;
	Mat img = imread("digits.png");
	if (!img.data)
	{
		cout << "src image load failed!" << endl;
		system("pause");
		return -1;
	}
	/*imshow("原始图像", img);*/
	Mat gray;
	cvtColor(img, gray, CV_BGR2GRAY);
	int b = 20;
	int m = gray.rows / b;   //原图为1000*2000,裁剪为50行100列共计5000个20*20的小图
	int n = gray.cols / b; 

	for (int i = 0; i < m; i++)
	{
		int offsetRow = i * b;  //行上的偏移量
		if (i % 5 == 0 && i != 0)
		{
			filename++;
			filenum = 0;
		}
		for (int j = 0; j < n; j++)
		{
			int offsetCol = j * b; //列上的偏移量
			stringstream ss;
			// 将多个字符串放入 ss 中
			ss << "E:\\data\\" << filename<<"\\"<<filenum++<<".png" ;
			string file = ss.str();
			ss.str("");//清空ss
			//截取20*20的小块
			Mat tmp;
			gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
			imshow("tuxiang",tmp);
			/*waitKey(100);*/
			imwrite(file, tmp);

		}
	}
	waitKey();
	return 0;
}

【注意】:在运行命令前,需要在E盘data文件夹下新建两个子文件夹,命名为0和1。这样才能把图片存入。

部分处理结果如下:
在这里插入图片描述

2、训练集和测试集划分

我们可以手动将1000张图片划分为训练集和测试集,其中训练数据800张,0,1各400张;测试数据200张,0,1各100张。
(至于更简单的方法,暂时还没想到,读者可以自行探索)

3、SVM模型训练

(关于这一部分,可参考博客:利用C++和OpenCV3设计支持向量机SVM分类器

3.1 数据准备

由于只识别数字0和数字1,因此定义了两个子函数:

get_1(trainingImages, trainingLabels);
get_0(trainingImages, trainingLabels);

主要功能为:
get_1()用来将1文件夹中的图像Mat类型和标签写入训练集。
get_0()用来将0文件夹中的图像Mat类型和标签写入训练集。
这样就保证了特征和标签是一一对应的关系push_back(0)或者push_back(1)其实就是我们贴标签的过程。

在主函数中,将get_1()与get_0()写好的包含特征的矩阵拷贝给trainingImages,将包含标签的vector容器进行类型转换后拷贝到trainingLabels里,至此,数据准备工作完成,trainingImages与trainingLabels就是我们要训练的数据。

子函数代码如下:

void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
	string filePath = "E:\\data\\train_image\\1";
	int number =400;
	for (int i = 0; i < number; i++)
	{
		stringstream ss;
		// 将多个字符串放入 ss 中
		ss << filePath <<"\\"<< i << ".png";
		string files = ss.str();
		ss.str("");//清空ss
		Mat  SrcImage = imread(files);
		SrcImage = SrcImage.reshape(1, 1);//转换为单通道行向量
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(1);
	}
}
void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
	string filePath = "E:\\data\\train_image\\0";
	vector<string> files;
	int number = 400;
	for (int i = 0; i < number; i++)
	{
		stringstream ss;
		// 将多个字符串放入 ss 中
		ss << filePath << "\\" << i << ".png";
		string files = ss.str();
		ss.str("");//清空ss
		Mat  SrcImage = imread(files);

		SrcImage = SrcImage.reshape(1, 1);//转换为单通道列向量
		trainingImages.push_back(SrcImage);
		trainingLabels.push_back(0);
	}

除了这两个子函数,需要注意的是,
①样本数据必须是CV_32FC1类型。这是由opencv3版本决定的;
②样本标签必须是CV_32SC1,opencv3后从int数组转换为CV_32SC1类型,而opencv2是从float数据转换。

3.2 特征选取

特征提取和数据的准备是同步完成的。这里我们简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程,所以选择了最简单的方式完成特征提取工作,除此中外,特征提取的方式有很多,比如LBP,HOG等等。
在这里插入图片描述
上面是行向量,这里注释写错了

3.3 配置SVM训练器参数

程序如下:

//配置SVM训练器参数
	Ptr<SVM> svm = SVM::create();//创建一个svm对象
	svm->setType(SVM::C_SVC);//设置SVM公式类型
	svm->setKernel(SVM::RBF);//设置SVM核函数类型
	svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 10000, 1e-6));//设置SVM训练时迭代终止条件
	svm->train(trainingData, ROW_SAMPLE, classes);//训练数据

3.4 保存模型

程序如下:

//保存模型
	svm->save("svm.xml");
	cout << "训练好了!!!" << endl;

ok,这样,SVM就训练完成了。
下面开始测试。

4、加载模型实现分类

核心代码如下:

#include <opencv2/opencv.hpp>
#include <iostream>
#include<string>
#include <opencv2/ml.hpp>
using namespace std;
using namespace cv;
using namespace cv::ml;

int main()
{
	int result = 0;
	int number = 100;
	Ptr<SVM> svm = SVM::load("svm.xml");//加载svm对象

	string filePath = "E:\\data\\train_image\\";
	for (int i = 0; i < number; i++)
	{
		stringstream ss;
		// 将多个字符串放入 ss 中
		ss << filePath << "0\\" << i << ".png";
		string files = ss.str();
		ss.str("");//清空ss
		Mat inMat = imread(files);
		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;
}

可以发现,识别正确率还是很高的;当然,也可以加入下列代码进行测试:

	/test
	stringstream sss;
	// 将多个字符串放入 ss 中
	sss << filePath << "1\\" << 56 << ".png";
	string filess = sss.str();
	sss.str("");//清空ss
	Mat src = imread(filess);
	Mat p = src.reshape(1, 1);
	p.convertTo(p, CV_32FC1);
	int test = (int)svm->predict(p);
	cout << "测试一下:" << test << endl;
	//

输出结果:
在这里插入图片描述

ok,大功告成!
如果想要全部的三个文件的代码,可以从下列链接中下载:
https://download.csdn.net/download/didi_ya/15561230

如果对你有所帮助,记得点个赞哟~

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐