写在前面

之前写过一给手写数字识别系统,那时候是大一时候,刚刚对数字图像处理有了一点点接触。实际上真正开始学这门课实在上学期,也就是大三上学期,这学期才开始正式做这个实验。上学期用opencv自己实现了均值滤波,直方图均衡化,腐蚀膨胀等相关的图像处理操作,学习了相关理论知识,再回看之前写的代码感觉真的有很多当初根本不理解的地方,现在一看就忽然理解了。而且对于之前的实现还有改进的地方。这次这个实验也是使用mnist的数据集,但是这次的要求和上次不一样了,上次是依葫芦画瓢。感觉懂了,实际上不懂,但这次是真正要实现一个模板匹配的算法。下面的代码注释把所有涉及到的点都说明白了,对着代码看应该会比较好。

在这之前先给出算法流程

下面给出算法流程:

下面给出代码。

我用的环境是vs2019+opencv3.4.7

系统一共是两个程序,一个是训练程序,一个测试程序。训练程序用于把数据集的图片01序列存在txt文档。测试程序通过读取文档对待识别数字进行识别。测试集采用训练集每个数字前20张图片。训练集每个数字有100张图。

下面是训练代码:

/*
因为测试集是训练集的子集,所以本程序在cvtest2(这个程序把训练集1000张图片全部训练)的基础上,把训练集的范围降到100的数据,即每个数字10个样本,用来测试测试集的识别率。
*/
#include "cv.h"
#include "highgui.h"
#include<opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <opencv\cv.hpp>
#include<sstream>
#include <iostream>
#include <fstream>
#include <Windows.h>
using namespace std;
using namespace cv;
ofstream codeo("E:\\code\\vs\\txtfile\\2lb.txt", ios::binary);//不追加,每次训练都重新覆盖原文件;
//计算欧氏距离
int getDist(int test[100], int sample[100])
{
	int sum = 0;
	for (int i = 0; i < 100; i++)
	{
		sum += pow(test[i] - sample[i], 2);//这里省略开根号
	}
	return sum;
}
//对图像进行区域分割并字符串化图片区域,得到test数组,里面存放着一张图片的01序列
void getPXSum(Mat& src, int(&test)[100])
{
	int mark = 0, count = 0;//mark是划分的格子的序号,count是格子内计数像素个数
	//threshold(src, src, 100, 1, CV_THRESH_BINARY);
	while (mark < 100)//100.个格子10*10
	{
		for (int i = 0; i < 30; i++)
		{
			for (int j = 0; j < 30; j++)//一个格子里面有30*30=90个像素
			{
				if (src.at <uchar>(i + 30 * (mark / 10), j + 30 * (mark % 10)) > 200) /*Mat类中的at方法作用:用于获取图像矩阵某点的值或改变某点的值。对于单通道图像的使用方法:image.at<uchar>(i,j) = 255;对于RGB三通道图像的使用方法:image.at<Vec3b>(i,j)[0] =  255;image.at<Vec3b>(i,j)[1] =  255;image.at<Vec3b>(i,j)[2] = 255;*/
					count++;//mark/7是得出当前是第几行的格子
				//printf_s("%d",src.at <uchar>(i + 3 * (mark % 7), j + 3 * (mark / 7)));
			}
			//printf_s("\n");
		}
		if (count > 50) test[mark] = 1;//若格子里超过半数像素是亮的,则记这个格子为1
		else
		{
			test[mark] = 0;
		}
		//printf_s("黑色像素占该3*3区域的%d/9\n", count);
		count = 0;
		mark++;
	}
}

//输出一张图片的字符串序列
void readNum(int(&test)[100])
{
	//ofstream codeo("E:\\code\\vs\\txtfile\\1.txt", ios::binary | ios::app);
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			//printf_s("%d", test[i * 10 + j]);//输出一张图片的字符串序列

			int buffer = test[i * 10 + j];

			//while (!codei.eof()) {
			codeo << buffer;
		}

		//printf_s("\n");
	}
	codeo << endl;
	printf_s("\n");

}

//统计所有列像素的总和
int getColSum(Mat src, int col)
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < height; i++)
	{
		sum = sum + src.at <uchar>(i, col);
	}
	return sum;
}
//统计所有行像素的总和
int getRowSum(Mat src, int row)
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < width; i++)
	{
		sum += src.at <uchar>(row, i);
	}
	return sum;
}
//上下切割
void cutTop(Mat& src, Mat& dstImg)
{
	int top, bottom;
	top = 0;
	bottom = src.rows;

	int i;
	for (i = 0; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和
										 //cout <<i<<" th "<< colValue << endl;
		if (colValue > 0)//扫描直到行像素的总和大于0时,记下当前位置top
		{
			top = i;
			break;
		}
	}
	for (; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和
										 //cout << i << " th " << colValue << endl;
		if (colValue == 0)//继续扫描直到行像素的总和等于0时,记下当前位置bottom
		{
			bottom = i;
			break;
		}
	}

	int height = bottom - top;
	Rect rect(0, top, src.cols, height);
	dstImg = src(rect).clone();
}

//左右切割
int cutLeft(Mat& src, Mat& leftImg, Mat& rightImg)
{
	int left, right;
	left = 0;
	right = src.cols;

	int i;
	for (i = 0; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和
										 //cout <<i<<" th "<< colValue << endl;
		if (colValue > 0)//扫描直到列像素的总和大于0时,记下当前位置left
		{
			left = i;
			break;
		}
	}
	if (left == 0)
	{
		return 1;
	}

	//继续扫描
	for (; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和
										 //cout << i << " th " << colValue << endl;
		if (colValue == 0)//继续扫描直到列像素的总和等于0时,记下当前位置right
		{
			right = i;
			break;
		}
	}
	int width = right - left;//分割图片的宽度则为right - left
	Rect rect(left, 0, width, src.rows);//构造一个矩形,参数分别为矩形左边顶部的X坐标、Y坐标,右边底部的X坐标、Y坐标(左上角坐标为0,0)
	leftImg = src(rect).clone();//实际上这句话进行了一个裁剪的功能
//clone的作用是对src进行了深拷贝,创建了一个完整的副本
//利用Mat方法直接赋值获取的区域图像仍然会改变原图.
	//若想直接复制出ROI区域, 需要把原始图像进行clone即可
	/*1. 使用拷贝构造函数Mat(constMat& m, const Rect& roi ),矩形roi指定了兴趣区
Mat src = imread(“xx.jpg”);
Mat srcROI( src, Rect(0,0,src.cols/2,src.rows/2));
2. 使用操作符”()”,即Mat operator () ( const Rect&roi ) const,矩形roi指定了兴趣区
Mat src = imread(“xx.jpg”);
Mat srcROI = src(Rect(0,0,src.cols/2,src.rows/2));
注意:以上两种操作,srcROI的数据与源图像src共享存储区,所以此后在srcROI上的操作也会作用在源图像src上。
所以,当需要提取出独立的ROI区域时,应该使用
Mat srcROI = src(Rect(0,0,src.cols/2,src.rows/2)).clone();
*/
	Rect rectRight(right, 0, src.cols - right, src.rows);//分割后剩下的原图
	rightImg = src(rectRight).clone();
	cutTop(leftImg, leftImg);//上下切割
	return 0;
}

//test形参传引用,会保存数据,处理图片,并获得字符串
void readImg(Mat& src, int(&test)[100], int i)
{
	imshow("origin0", src);//显示原图片

	medianBlur(src, src, 3);//中值滤波去椒盐噪声
//	imshow("origin2", src);//显示去噪后图片

	Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核
	//dilate(src, src, element);//膨胀
	//imshow("origin3", src);//显示膨胀后图片

	threshold(src, src, 100, 255, THRESH_BINARY);//二值化,100是阈值

	Mat rightImg, leftImg;
	cutLeft(src, leftImg, rightImg);
	imshow("origin4", leftImg);//显示切割后图片

	resize(leftImg, leftImg, Size(300, 300), 0, 0, CV_INTER_LINEAR);
	imshow("origin5", leftImg);//显示调整尺寸后图片

	stringstream ss;
	ss << i;//此举将int类型转换为字符串类型
	/*
	stringstream还是相当强大的。简单易懂,虽然写的行数比较多!

	基本数据类型转换例子 int和string, 也支持string和char* ,int和char* 之间的转换。

		注意同一个stringstream对象,再进行多次转换的时候,必须调用stringstream的成员函数clear().

		头文件<sstream>

		如int转string
		int n = 0;
	std::stringstream ss;
	std::string str;
	ss << n;
	ss >> str;
	*/
	imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\" + ss.str() + ".bmp", leftImg);//streamstring在调用str()时,会返回临时的string对象。

	getPXSum(leftImg, test);//计算切割好的图片的序列,存到test里

	/**/
}
int main()
{


	int sample[10][100][100];//保存样本图片字符串,每个数字抽取10副图,每幅图拥有100个字符的序列
	//double num[10][3] = { 0 };//统计结果,即10个数字的三率
	int test[100];//保存测试图片字符串
	int test_num = 7;//测试的数字
	int true_num = 0;//数字的识别结果

	printf_s("测试样本\n");
	char name[100];//测试图片路径
	//sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\train-images\\%d_%d.bmp", test_num, test_num);
	//Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取的测试图片图片
	sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\moban\\shou.bmp");
	Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取图片

//	imshow("origin0", src);//显示原图片

	readImg(src, test, test_num);//获得了src的序列
	//codeo << "test";
	//readNum(test);

	int minidist = 100, dist;
	int chou = 20;
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 80; j++)//每个数字取后80个样本作为训练样本
		{
			//chou++;
			char name[100];//模板图片的存储位置
			sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\train-images\\%d_%d.bmp", i, j + 20);//取每个数字第20个样本以后的图像
			Mat Template = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取模板
			readImg(Template, sample[i][j], i);//模板第i个数字的第j副图

			codeo << i;
			readNum(sample[i][j]);
			printf_s("与数字%d第%d个模板", i, j + 1);
			printf_s("欧式距离:");

			dist = getDist(test, sample[i][j]);
			if (minidist >= dist) {
				minidist = dist;
				true_num = i;
			}
			printf_s("%d\n\n\n 当前minidist:%d", dist, minidist);
		}
	}

	printf_s("识别结果为:%d", true_num);
	codeo.close();
	waitKey(0);
	//getchar();
	return 0;

}



下面是测试程序:

/*
本程序用于测试测试集test的数字识别率。用的数据集是从1000个全部训练集中抽取的100个数据,
*/
#include "cv.h"
#include "highgui.h"
#include<opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <opencv\cv.hpp>
#include<sstream>
#include <iostream>
#include <fstream>
#include <Windows.h>
using namespace std;
using namespace cv;
//计算欧氏距离
int getDist(int test[100], int sample[100])
{

	int sum = 0;
	for (int i = 0; i < 100; i++)
	{
		sum += pow(test[i] - sample[i], 2);//这里省略开根号
	}
	return sum;
}
//对图像进行区域分割并字符串化图片区域,得到test数组,里面存放着一张图片的01序列
void getPXSum(Mat& src, int(&test)[100])
{
	int mark = 0, count = 0;//mark是划分的格子的序号,count是格子内计数像素个数
	//threshold(src, src, 100, 1, CV_THRESH_BINARY);
	while (mark < 100)//49.个格子7*7
	{
		for (int i = 0; i < 30; i++)
		{
			for (int j = 0; j < 30; j++)//一个格子里面有3*3九个像素
			{
				if (src.at <uchar>(i + 30 * (mark / 10), j + 30 * (mark % 10)) > 200) /*Mat类中的at方法作用:用于获取图像矩阵某点的值或改变某点的值。对于单通道图像的使用方法:image.at<uchar>(i,j) = 255;对于RGB三通道图像的使用方法:image.at<Vec3b>(i,j)[0] =  255;image.at<Vec3b>(i,j)[1] =  255;image.at<Vec3b>(i,j)[2] = 255;*/
					count++;//mark/7是得出当前是第几行的格子
				//printf_s("%d",src.at <uchar>(i + 3 * (mark % 7), j + 3 * (mark / 7)));
			}
			//printf_s("\n");
		}
		if (count > 50) test[mark] = 1;//若格子里超过半数像素是亮的,则记这个格子为1
		else
		{
			test[mark] = 0;
		}
		//	printf_s("黑色像素占该3*3区域的%d/9\n", count);
		count = 0;
		mark++;
	}
}

//输出一张图片的字符串序列
void readNum(int(&test)[100])
{
	//ofstream codeo("E:\\code\\vs\\txtfile\\1.txt", ios::binary | ios::app);
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			//printf_s("%d", test[i * 7 + j]);//输出一张图片的字符串序列

			int buffer = test[i * 10 + j];

			//while (!codei.eof()) {

		}

		//printf_s("\n");
	}

	//printf_s("\n");

}

//统计所有列像素的总和
int getColSum(Mat src, int col)
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < height; i++)
	{
		sum = sum + src.at <uchar>(i, col);
	}
	return sum;
}
//统计所有行像素的总和
int getRowSum(Mat src, int row)
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < width; i++)
	{
		sum += src.at <uchar>(row, i);
	}
	return sum;
}
//上下切割
void cutTop(Mat& src, Mat& dstImg)
{
	int top, bottom;
	top = 0;
	bottom = src.rows;

	int i;
	for (i = 0; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和
										 //cout <<i<<" th "<< colValue << endl;
		if (colValue > 0)//扫描直到行像素的总和大于0时,记下当前位置top
		{
			top = i;
			break;
		}
	}
	for (; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和
										 //cout << i << " th " << colValue << endl;
		if (colValue == 0)//继续扫描直到行像素的总和等于0时,记下当前位置bottom
		{
			bottom = i;
			break;
		}
	}

	int height = bottom - top;
	Rect rect(0, top, src.cols, height);
	dstImg = src(rect).clone();
}

//左右切割
int cutLeft(Mat& src, Mat& leftImg, Mat& rightImg)
{
	int left, right;
	left = 0;
	right = src.cols;

	int i;
	for (i = 0; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和
										 //cout <<i<<" th "<< colValue << endl;
		if (colValue > 0)//扫描直到列像素的总和大于0时,记下当前位置left
		{
			left = i;
			break;
		}
	}
	if (left == 0)
	{
		return 1;
	}

	//继续扫描
	for (; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和
										 //cout << i << " th " << colValue << endl;
		if (colValue == 0)//继续扫描直到列像素的总和等于0时,记下当前位置right
		{
			right = i;
			break;
		}
	}
	int width = right - left;//分割图片的宽度则为right - left
	Rect rect(left, 0, width, src.rows);//构造一个矩形,参数分别为矩形左边顶部的X坐标、Y坐标,右边底部的X坐标、Y坐标(左上角坐标为0,0)
	leftImg = src(rect).clone();//实际上这句话进行了一个裁剪的功能

	Rect rectRight(right, 0, src.cols - right, src.rows);//分割后剩下的原图
	rightImg = src(rectRight).clone();
	cutTop(leftImg, leftImg);//上下切割
	return 0;
}

//test形参传引用,会保存数据,处理图片,并获得字符串
void readImg(Mat& src, int(&test)[100])
{
	imshow("原图", src);//显示原图片

	medianBlur(src, src, 3);//中值滤波去椒盐噪声
	//imshow("origin2", src);//显示去噪后图片

	Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核
	//dilate(src, src, element);//膨胀
	//imshow("origin3", src);//显示膨胀后图片

	threshold(src, src, 100, 255, THRESH_BINARY);//二值化,100是阈值

	Mat rightImg, leftImg;
	cutLeft(src, leftImg, rightImg);
	imshow("切割后的图片", leftImg);//显示切割后图片

	resize(leftImg, leftImg, Size(300, 300), 0, 0, CV_INTER_LINEAR);
	imshow("显示调整尺寸后图片", leftImg);//显示调整尺寸后图片

	stringstream ss;
	//ss << i;//此举将int类型转换为字符串类型

	//imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\" + ss.str() + ".bmp", leftImg);//streamstring在调用str()时,会返回临时的string对象。

	getPXSum(leftImg, test);//计算切割好的图片的序列,存到test里


}
int main()
{

	//ofstream code("E:\\code\\vs\\txtfile\\1.txt",  ios::binary|ios::app);
	//code << "123122222222222"<<endl;
	//code.close();

	ifstream codei("E:\\code\\vs\\txtfile\\2lb.txt", ios::binary);
	int sample1[10][80][101];//保存样本图片字符串,每个数字100副图,每幅图拥有49个字符的序列;
	int sample[10][80][100];
	char ch;
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 80; j++)
		{

			for (int k = 0; k < 101; k++)
			{

				codei.get(ch);

				if ((int)ch == 10)

				{
					k--;
					continue;
				}
				sample1[i][j][k] = (int)ch - 48;
				//cout << sample1[i][j][k];

			}
			//cout << endl;
		}

	}

	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 80; j++)
		{
			for (int k = 0; k + 1 < 101; k++)
			{
				sample[i][j][k] = sample1[i][j][k + 1];
				cout << sample[i][j][k];

			}
			cout << endl;

		}

	}

	double num[10][3] = { 0 };//统计结果,即10个数字的三率
	int test[100];//保存测试图片字符串

	int true_num = 0;//数字的识别结果
	char name[100];//测试图片路径
	double trues = 0;//数字i的正确识别数量
	double faluses = 0;//数字i错误识别的数量
	double jv = 0;//拒绝识别的数量
	for (int t1 = 0; t1 < 10; t1++)
	{
		for (int t2 = 0; t2 < 20; t2++)
		{

			sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\test-images\\%d_%d.bmp", t1, t2);
			Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取的测试图片图片


			imshow("origin0", src);//显示原图片

			readImg(src, test);//获得了src的序列
			//codeo << "test";
			readNum(test);
			/*-------------------以上处理测试识别的图像,以下是匹配模板-----------------------------*/
			int minidist = 100, dist;
			for (int i = 0; i < 10; i++)
			{
				for (int j = 0; j < 80; j++)
				{
					//	printf("当前测试数字:%d_%d\n", t1,t2);
						//printf("当前图片序列:");
					readNum(sample[i][j]);
					//	printf_s("与数字%d第%d个模板", i, j + 1);
						//printf_s("欧式距离:");

					dist = getDist(test, sample[i][j]);
					if (minidist > dist) {
						minidist = dist;
						true_num = i;
					}
				//	printf_s("%d \n", dist);
					//printf_s("%d \n当前minidist:%d\n\n\n", dist, minidist);
				}
			}

			if (minidist > 26)
			{
				jv++;
				cout <<"mimidist:"<<minidist<< "拒绝识别\n";
				continue;
			}
			if (true_num == t1)
			{
				trues++;
			}
			else
			{
				faluses++;
			}
			printf_s("识别结果为:%d,minidist:%d\n", true_num, minidist);

		}
		printf("数字%d成功率为%.2f\n", t1, trues / 20.0);
		printf("数字%d错误率为%.2f\n", t1, faluses / 20.0);
		printf("数字%d拒绝识别率为%.2f\n", t1, jv / 20.0);
		cout << endl;
		trues = 0;
		faluses = 0;
		jv = 0;
	}

	waitKey(0);
	//getchar();
	return 0;
}

            当然,为了填补上次手写数字识别系统的缺陷,我重新改了一下之前的过的东西,在这篇文章一并分享

我在这所用的东西,其实也是算欧氏距离。仔细想一想,上面的方法是要求我们把一张大的图片分成小块,每一块包含了很多像素,然后再对这些块里超过半数为1的块记为1,实际上就是把像素规模进行变小了嘛。我一个300*300的像素要分成10*10的格子,不就相当于我直接把它变成10*10像素的图片吗。而且再opencv里面有专门计算两个Mat之间的对应元素绝对值差绝对值相加的函数,这不就是是欧氏距离吗。我甚至可以不同把它二值化(相当于半数像素为白色,格子置为1,反之置零),而是灰度化,这样计算欧氏距离是不是更准呢?当然这里计算绝对值之和应该是很多个255相加,但是也可以改成只加一。但是这样的效果是不一样的,大家可以去试一下,改一句代码即可,我再代码中有写出注释。这里用的还是1000张图片的数据集。和之前不一样的是,这里是直接读取数据集图片,而没有训练和读取训练数据。

#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int getColSum(Mat src, int col)//统计所有列像素的总和
{
	int sum = 0;
	int height = src.rows;	int width = src.cols;
	for (int i = 0; i < height; i++)
	{
		sum = sum + src.at <uchar>(i, col);
	}
	return sum;
}
int getRowSum(Mat src, int row)//统计所有行像素的总和
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < width; i++)
	{
		sum += src.at <uchar>(row, i);
	}
	return sum;
}

void cutTop(Mat& src, Mat& dstImg)//上下切割
{
	int top, bottom;
	top = 0;
	bottom = src.rows;
	int i;
	for (i = 0; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和	
			//cout <<i<<" th "<< colValue << endl;		
		if (colValue > 0)//扫描直到行像素的总和大于0时,记下当前位置top	
		{
			top = i;
			break;
		}
	}
	for (; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和		
										 //cout << i << " th " << colValue << endl;	
		if (colValue == 0)//继续扫描直到行像素的总和等于0时,记下当前位置bottom		
		{
			bottom = i;
			break;
		}
	}
	int height = bottom - top;
	Rect rect(0, top, src.cols, height);
	dstImg = src(rect).clone();
}
int cutLeft(Mat& src, Mat& leftImg, Mat& rightImg)//左右切割
{
	int left, right;
	left = 0;
	right = src.cols;
	int i;

	for (i = 0; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和		
										 //cout <<i<<" th "<< colValue << endl;	
		if (colValue > 0)//扫描直到列像素的总和大于0时,记下当前位置left		
		{
			left = i;
			break;


		}
	}
	if (left == 0)
	{
		return 1;
	}//扫描完成


	for (; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和		

		if (colValue == 0)//继续扫描直到列像素的总和等于0时,记下当前位置right		
		{
			right = i;
			break;
		}
	}
	int width = right - left;//分割图片的宽度则为right - left
	Rect rect(left, 0, width, src.rows);//构造一个矩形,参数分别为矩形左边顶部的X坐标、Y坐标,右边底部的X坐标、Y坐标(左上角坐标为0,0)
	leftImg = src(rect).clone();
	Rect rectRight(right, 0, src.cols - right, src.rows);//分割后剩下的原图	
	rightImg = src(rectRight).clone();
	cutTop(leftImg, leftImg);//上下切割
	return 0;
}

void getPXSum(Mat& src, int& a)//获取所有像素点和(白色)
{

	threshold(src, src, 100, 255, 0);
	a = 0;	
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			//if (src.at <uchar>(i, j) > 200) a++;
			a += src.at <uchar>(i, j);//src.at <uchar>(i, j)表示(i,j)点的像素值;
		}
	}
}


int  getSubtract(Mat& src, int TemplateNum) //数字识别
{
	Mat leftim, rightim;
	Mat img_result;

	int min = 10000000;

	int serieNum = 0;
	for (int i = 0; i < TemplateNum; i++)
	{
		for (int j = 0; j <= 99; j++)
		{
			char name[80];
			sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\train-images\\%d_%d.bmp", i, j);
			Mat Template = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取模板	
			threshold(Template, Template, 100, 255, 3);
			//threshold(src, src, 100, 255, CV_THRESH_BINARY);
			//resize(src, src, Size(32, 48), 0, 0, CV_INTER_LINEAR);
			cutLeft(Template, leftim, rightim);//裁剪;
			//Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核

			//dilate(leftim, leftim, element);//膨胀

			resize(leftim, leftim, Size(8, 8), (0, 0), (0, 0), 3);//调整尺寸	
	//imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\mo.bmp", leftim);//streamstring在调用str()时,会返回临时的string对象。

			imshow("模板", leftim);	
			
													//imshow(name, Template);		
			/*让需要匹配的图分别和10个模板对应像素点值相减,
			然后求返回图片的整个图片的像素点值得平方和,
			和哪个模板匹配时候返回图片的平方和最小则就可以得到结果*/
			absdiff(leftim, src, img_result);//AbsDiff,OpenCV中计算两个数组差的绝对值的函数。	
			int diff = 0;
			getPXSum(img_result, diff);//获取所有像素点和	
			//printf("%d,%d\n", i, diff);

			if (diff < min)//像素点对比	
			{
				min = diff;
				serieNum = i;
				//printf("%d,%d\n", i, min);

			}

		}
	}
	printf("匹配的数字是%d\n", serieNum);
	//waitKey(0);
	return serieNum;

}


int main()
{
	Mat src = imread("E:\\code\\vs\\cvtest1\\minist\\moban\\2.jpg", CV_LOAD_IMAGE_GRAYSCALE);//读取图片1.jpg,2.jpg
	Mat leftim, rightim, std, t;	
	//std=imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
	//t = imread("0_0.bmp",1);
	imshow("原图", src);

	threshold(src, src, 100, 255, 1);//二值化
	//threshold(src, src, 100, 255, 3);//二值化
	medianBlur(src, src, 3);//中值滤波去椒盐噪声

	resize(src, src, Size(src.cols / 4, src.rows / 4), (0, 0), (0, 0), 3);
	//resize(src, src, Size(32, 48), 0, 0, CV_INTER_LINEAR);//调整尺寸	

	imshow("二值化图片", src);//显示二值化后图片 	

	int i = 1;
	while (cutLeft(src, leftim, rightim) != 1)
	{
		char nameLeft[40];//存放可变窗口名字

		cutLeft(src, leftim, rightim);//裁剪;
		sprintf_s(nameLeft, "%dth number", i);//可变窗口名字,赋值给nemeleft
		Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核

		dilate(leftim, leftim, element);//膨胀
		//dilate(leftim, leftim, element);//膨胀

		resize(leftim, leftim, Size(8, 8), (0, 0), (0, 0), 3);

		imshow(nameLeft, leftim);
		getSubtract(leftim, 10);//识别函数;
	//	imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\shou.bmp", leftim);//streamstring在调用str()时,会返回临时的string对象。

		src = rightim;
		i++;


	}//printf("%d  ;  %d\n", std.cols * 2, std.rows * 2);

	waitKey(0);
	return 0;
}

可以看到准确率比上一篇要高


2 条评论

Melanie · 2023年5月8日 下午10:20

Thank you for information https://www.calameo.com/accounts/7363149

Florence · 2023年6月2日 下午9:18

Thank you for the information, it will give me a new outlook on my life. bet241

回复 Florence 取消回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注