基于C++,OpenCV3以及SVM的MNIST手写数字识别系统的设计

一、OpenCV3对MNIST数据集的解析

1.1 MNIST数据集介绍

关于MNIST数据集,我曾写过一篇文章进行了详细描述:https://blog.csdn.net/didi_ya/article/details/105075859
这里就不过多介绍了。
这里重点介绍一下小端格式和大端格式。

1.2 小端格式和大端格式

大端模式:高位字节放在内存低地址处,低位字节放在内存高地址处;最直观,因为存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出,两者对照,一个字节一个字节的填充进去 ;
小端模式:低位字节放在内存低地址处,高位字节放在内存高地址处;Intel处理器一般为小端模式,比较符合人的思维;

看个例子:
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为

大端模式小端模式
0x00000x120xcd
0x00010x230xab
0x00020xab0x34
0x00030xcd0x12

参考链接:https://blog.csdn.net/liuweiyuxiang/article/details/78045344

需要注意的是,MNIST数据集采用小端存储的方式,因此提取需要进行一定操作。

1.3 小端存储转换代码示例

由于大端模式是把高位字节放在内存的低位处,所以,c1,c2,c3,c4依次表示的就是原来的数的高位,所以,在将c1,c2,c3,c4转化为整数后,只要依次取出整数的后8位,左移24位;次后八位,左移16位;次次后8位,左移8位;次次后8位,然后将这些左移后或者没有左移的元素相加起来就可以了。

在C++标准中,char类型的长度被定义为一个字节,这个在不同的处理器上面是不变的,因此,可以采用char类型的数组来存储文件头的部分,同时,使用char类型的数组来进行大端模式到小端模式的转换也是很容易的。

代码示例:

int reverseInt(int i) {
	unsigned char c1, c2, c3, c4;

	c1 = i & 255;//&——与运算;255——11111111;该语句的意思是取出i的后八位
	c2 = (i >> 8) & 255;//右移8位
	c3 = (i >> 16) & 255;//右移16位
	c4 = (i >> 24) & 255;//右移24位

	return ((int)c1 << 24) + ((int)c2 << 16) + ((int)c3 << 8) + c4;
}

如果还是不懂,我们直接来看一个例子:
i读取的第一个十进制数字是:17301504;转化为二进制是:1000010000000000000000000;c1表示该二进制与11111111相与,其结果为0;c2表示该二进制再次右移8位与11111111相与,其结果为0;c3表示该二进制再次右移8位与11111111相与,其结果为8;c4表示该二进制再次右移8位与11111111相与,其结果为1;返回值为c1左移24位+c2左移16位+c3左移8位+c4,其结果为2049。

1.4 OpenCV3对图像数据的解析代码示例

代码如下:

Mat read_mnist_image(const string fileName) {
	int magic_number = 0;
	int number_of_images = 0;
	int n_rows = 0;
	int n_cols = 0;

	Mat DataMat;

	ifstream file(fileName, ios::binary);
	if (file.is_open())
	{
		cout << "成功打开图像集 ..." << endl;

		file.read((char*)&magic_number, sizeof(magic_number));//幻数(文件格式)
		file.read((char*)&number_of_images, sizeof(number_of_images));//图像总数
		file.read((char*)&n_rows, sizeof(n_rows));//每个图像的行数
		file.read((char*)&n_cols, sizeof(n_cols));//每个图像的列数

		magic_number = reverseInt(magic_number);
		number_of_images = reverseInt(number_of_images);
		n_rows = reverseInt(n_rows);
		n_cols = reverseInt(n_cols);
		cout << "magic number(文件格式):" << magic_number
			<< " 图像总数:" << number_of_images
			<< " 每个图像的行数:" << n_rows
			<< " 每个图像的列数:" << n_cols << endl;

		cout << "开始读取Image数据......" << endl;

		DataMat = Mat::zeros(number_of_images, n_rows * n_cols, CV_32FC1);
		for (int i = 0; i < number_of_images; i++) {
			for (int j = 0; j < n_rows * n_cols; j++) {
				unsigned char temp = 0;
				file.read((char*)&temp, sizeof(temp));
				//可以在下面这一步将每个像素值归一化
				float pixel_value = float(temp);
				//按照行将像素值一个个写入Mat中
				DataMat.at<float>(i, j) = pixel_value;
			}
		}
		cout << "读取Image数据完毕......" << endl;

	}
	file.close();
	return DataMat;
}

1.5 OpenCV3对标签数据的解析代码示例

代码如下:

Mat read_mnist_label(const string fileName) {
	int magic_number;
	int number_of_items;

	Mat LabelMat;

	ifstream file(fileName, ios::binary);
	if (file.is_open())
	{
		cout << "成功打开标签集 ... " << endl;

		file.read((char*)&magic_number, sizeof(magic_number));
		file.read((char*)&number_of_items, sizeof(number_of_items));
		magic_number = reverseInt(magic_number);
		number_of_items = reverseInt(number_of_items);

		cout << "magic number(文件格式):" << magic_number << "  ;标签总数:" << number_of_items << endl;

		cout << "开始读取Label数据......" << endl;
		//CV_32SC1代表32位有符号整型 通道数为1
		LabelMat = Mat::zeros(number_of_items, 1, CV_32SC1);
		//error
		for (int i = 0; i < number_of_items; i++) {
			char temp = 0;
			file.read((char*)&temp, sizeof(temp));
			LabelMat.at<int>(i, 0) = (int)temp;
		}
		//error finish
		cout << "读取Label数据完毕......" << endl;

	}
	file.close();
	return LabelMat;
}

二、模型训练

2.1 训练数据准备

代码如下:

string train_images_path = "G:/mnist/train-images.idx3-ubyte";
string train_labels_path = "G:/mnist/train-labels.idx1-ubyte";
string test_images_path = "G:/mnist/t10k-images.idx3-ubyte";
string test_labels_path = "G:/mnist/t10k-labels.idx1-ubyte";

//读取训练标签数据 (60000,1) 类型为int32
Mat train_labels = read_mnist_label(train_labels_path);

//读取训练图像数据 (60000,784) 类型为float32 数据未归一化
Mat train_images = read_mnist_image(train_images_path);
//将图像数据归一化为[0,1]
train_images = train_images / 255.0;

//读取测试数据标签(10000,1) 类型为int32
Mat test_labels = read_mnist_label(test_labels_path);

//读取测试数据图像 (10000,784) 类型为float32 数据未归一化
Mat test_images = read_mnist_image(test_images_path);
//归一化为[0,1]
test_images = test_images / 255.0;

2.2 构建支持向量机SVM并进行训练

代码如下:

	Ptr<SVM> svm = SVM::create();
	//设置类型为C_SVC代表分类
	svm->setType(SVM::C_SVC);
	//设置核函数
	svm->setKernel(SVM::POLY);
	//设置其它属性
	svm->setGamma(3.0);
	svm->setDegree(3.0);
	//设置迭代终止条件 
	svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 300, 0.0001));

	//开始训练
	Ptr<TrainData> train_data = TrainData::create(train_images, ROW_SAMPLE, train_labels);
	cout << "开始进行训练..." << endl;
	svm->train(train_data);
	cout << "训练完成" << endl;

2.3 在测试数据集上预测训练准确率

代码如下:

Mat pre_out;
	//返回值为第一个图像的预测值 pre_out为整个batch的预测值集合
	cout << "开始进行预测..." << endl;
	float ret = svm->predict(test_images, pre_out);
	cout << "预测完成" << endl;

	//计算准确率必须将两种标签化为同一数据类型
	pre_out.convertTo(pre_out, CV_8UC1);
	test_labels.convertTo(test_labels, CV_8UC1);

	int equal_nums = 0;
	for (int i = 0; i < pre_out.rows; i++)
	{
		if (pre_out.at<uchar>(i, 0) == test_labels.at<uchar>(i, 0))
		{
			equal_nums++;
		}
	}
	float acc = float(equal_nums) / float(pre_out.rows);
	cout << "测试数据集上的准确率为:" << acc * 100 << "%" << endl;

	//保存模型
	svm->save("mnist_svm.xml");

2.4 运行结果

在这里插入图片描述

三、调用训练好的模型进行手写数字识别

3.1 代码示例

代码如下:

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

int main()
{
	//读取一张手写数字图片(28,28)
	Mat image = imread("G:\\testIMG\\TestImage_00013.bmp",0);
	Mat img_show = image.clone();
	//更换数据类型有uchar->float32
	image.convertTo(image, CV_32F);
	//归一化
	image = image / 255.0;
	//(1,784)
	image = image.reshape(1, 1);

	//加载svm模型
	Ptr<SVM> svm = StatModel::load<SVM>("mnist_svm.xml");
	//预测图片
	float ret = svm->predict(image);
	cout << ret << endl;

	cv::imshow("img", img_show);
	cv::waitKey(0);
	getchar();
	return 0;
}

3.2 运行结果

在这里插入图片描述

【注意】:当调用训练好的模型进行识别时,需要注意输入图像必须是28*28黑底白字图,如果不是,可以通过图像处理得到。

ok,以上便是全部内容了,如果对你有所帮助,记得点个赞哟,如果想获取完整代码,可以参考资源:https://download.csdn.net/download/didi_ya/15656807

好的,这是一个非常有趣的问题。MNIST是一个非常著名的手写数字数据集,用于测试机器学习算法的性能。在C++中,我们可以使用OpenCV库来读取和处理图像数据,并使用机器学习库(如TensorFlow)来构建数字识别模型。下面是一个基本的MNIST手写数字识别示例: 1. 首先,我们需要下载MNIST数据集,可以从官方网站下载:http://yann.lecun.com/exdb/mnist/ 2. 接下来,我们需要使用OpenCV库来读取图像数据。可以使用以下代码: ``` cv::Mat image = cv::imread("mnist_image.png", CV_LOAD_IMAGE_GRAYSCALE); ``` 这将读取图像数据并将其转换为灰度图像。 3. 然后,我们需要将图像数据转换为适合机器学习算法的格式。可以使用以下代码: ``` cv::Mat_<float> image_data = image.reshape(1, 1); image_data /= 255.0f; ``` 这将将图像数据转换为一个浮点数矩阵,并将其归一化为0到1之间的值。 4. 接下来,我们可以使用机器学习库来构建数字识别模型。这里假设我们使用TensorFlow库。可以使用以下代码: ``` tensorflow::GraphDef graph_def; tensorflow::Session* session; tensorflow::SessionOptions session_options; tensorflow::ReadBinaryProto(tensorflow::Env::Default(), "mnist_model.pb", &graph_def); session_options.config.mutable_gpu_options()->set_allow_growth(true); session = tensorflow::NewSession(session_options); tensorflow::Status status = session->Create(graph_def); ``` 这将读取预训练的数字识别模型,并创建一个TensorFlow会话。 5. 最后,我们可以使用以下代码来预测图像中的数字: ``` tensorflow::Tensor input_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({1, 784})); auto input_tensor_mapped = input_tensor.tensor<float, 2>(); for (int i = 0; i < 784; i++) { input_tensor_mapped(0, i) = image_data(0, i); } std::vector<tensorflow::Tensor> output_tensors; tensorflow::Status status = session->Run({{"input", input_tensor}}, {"output"}, {}, &output_tensors); float* prediction = output_tensors[0].flat<float>().data(); ``` 这将将图像数据输入到数字识别模型中,并返回一个浮点数数组,表示每个数字的概率。我们可以选择具有最高概率的数字作为预测结果。 希望这个示例可以帮助你了解如何在C++中基于OpenCV实现MNIST手写数字识别
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wendy_ya

您的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值