时隔很多天了,今天把opencv做的一个手写数字识别的教程写一下吧。这个实验原本是大三下学期的实验,导师在大二上就把这个任务丢给我们了。其实这个任务很简单,主要是为了熟悉一下图像处理的基本流程以及opencv的基本操作。
首先要下载以及安装我们的opencv。这里呢我是安装的opencv3,2和3大部分是一样的,有小部分opencv3进行了删减,比如SURF特征点检测,ORB算法描述与匹配等经典算法被移除。
安装的过程中会遇到种种困难。由于我是19年10月份做的,距今已经3个多月了,我清晰的记得我安装时遇到了很多困难,但是我清楚的知道我记不得了。我就记得多少写多少吧。在安装的过程中,我已经选择了最高版本的,但是按照书上的说法,它最高支持到vs2015版,而我的是2017版的vs,我抱着试试看得心态完成了安装,发现是可以用的。另外还有一些地方,就是我们得调试,采用得是debug啊还是release啊一定要看好,是32位得还是64位得也要看好。不过就目前来看,32位还是比较稳定一些,没有书可以看一下这篇帖子https://blog.csdn.net/qq_41175905/article/details/80560429
英文的:https://www.cnblogs.com/wwf828/p/8196165.html
这是我看的那本书的作者的博客:https://blog.csdn.net/poem_qianmo/article/details/19809337
这个博主有一些实用的解决办法:https://www.dididongdong.com/archives/322 在这个属性管理器里面一定要按照书上或者教程上该改得都改了,该添加得添加了。
完成了这个配置,我们就已经成功了一半啦,下面我们来谈谈怎么实现手写数字识别。目前数字图像处理识别这一块有很多方法,比如模板匹配,特征识别,贝叶斯等,我们这里使用的是比较简单易懂的模板匹配法,即用足够多的模板样本和要识别的数字进行一一比对,像素最相似的即我们要匹配的那个数字,故我们从0到9,每个数字分别有100个模板模板样本,一共是1000个样本,用x-y进行命名,这样方便进行循环比对,也能够对匹配出的数字进行输出。
那么常规操作就是先灰度化,然后二值化,或者直接二值化,这些操作在opencv中是有现成的算法和函数的,你只需要调用它就行了,然后再进行裁剪,把多余的地方裁掉,把每个数字单独裁开,然后再调整成统一的大小。由于我们的模板库的图像都是调整好的,我们就不需要再做调整了。但是实际上我当时做的时候没有意识到,模板的数字实际上是有留边的,但是我裁剪的时候是贴着边剪得,所以误差可能会比较大。 这是我自己手写的数字,作为识别对象:  

#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++)
		{
			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[20];
				sprintf_s(name, "%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);//裁剪;

				resize(leftim,leftim, Size(56, 56), (0, 0), (0, 0), 3);//调整尺寸	
																				//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);
		return serieNum;
	
}


int main()
{
	Mat src = imread("0.jpg", CV_LOAD_IMAGE_GRAYSCALE), leftim, rightim, std,t;//读取图片	
	//std=imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
	//t = imread("0_0.bmp",1);
	//imshow("trin", t);

	threshold(src, src, 100, 255, 1);//二值化
	//threshold(src, src, 100, 255, 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[20];//存放可变窗口名字

		cutLeft(src, leftim, rightim);//裁剪;
		sprintf(nameLeft, "%dth number", i);//可变窗口名字,赋值给nemeleft
		resize(leftim, leftim, Size(56 , 56), (0, 0), (0, 0), 3);

	imshow(nameLeft, leftim);
	getSubtract(leftim, 10);//识别函数;
	src = rightim;
	i++;
	
	
}//printf("%d  ;  %d\n", std.cols * 2, std.rows * 2);

	waitKey(0);
	return 0;
}

运行结果是这个样子

 


0 条评论

发表回复

Avatar placeholder

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