opencv简易数字识别

  • Post author:
  • Post category:其他


前言

使用opencv,进行简单的数字识别

注意:此案例中的数字识别仅当做学习参考,想要真正实现数字识别,实际情况复杂很多

思路

①图片预处理,将图片转灰度后再二值化,使其变成白字黑底

②查找外接矩形,找到原图中数字的最外层轮廓,根据最外层轮廓找到外接矩形

③模板匹配,以外接矩形为ROI区域,截取数字区域,与模板进行匹配

④输出结果

具体步骤

首先读入一张原图,备份(后面需要用),将其转灰度,然后二值化,再备份(后面需要用),因为查找轮廓会破坏原图,所以需要备份,然后我这素材原图就是很简单的黑底写了白字,没有其他噪声之类的,所以只需转灰度然后二值化,如果是其他图片,可能还需要滤波,腐蚀膨胀去除噪声,如果去除不不干净,还需要过滤一下,因为我们只需要得到数字的轮廓,而且最后要变成白字黑底,如下图所示

Mat img = imread("kk.png");  
Mat clone_img = img.clone(); //原图备份
cvtColor(img, img, COLOR_BGR2GRAY); //图片转灰度
threshold(img, img, 100, 255, THRESH_BINARY); //二值化
Mat clone_img1 = img.clone(); //备份二值化后的原图

然后就要开始查找轮廓了,找到轮廓后(可以不绘制轮廓和外接矩形),根据轮廓找到外接矩形,这时我们再定义一个存储原图中有效数字区域的容器,因为你找到外接矩形后,直接ROI就可以截取到该数字,注意,我们要在之前备份的二值化原图中来进行ROI截取,并且重设大小为我们模板图的大小,然后我们把该数字存起来,用于后面的识别

vector<vector<Point>> contours; //存储原图轮廓
vector<Vec4i> hierarcy;
findContours(img, contours, hierarcy, RETR_EXTERNAL, CHAIN_APPROX_NONE);//查找轮廓
drawContours(clone_img, contours, -1, Scalar(0, 255, 0), 1, 8);//绘制轮廓
vector<Rect> sort_rect(contours.size()); //外接矩形
vector<Mat> img_mat;//存储原图中的有效数字区域
for (int i = 0; i < contours.size(); i++)
{
	sort_rect[i] = boundingRect(contours[i]); //外接矩形
	Mat roi = clone_img1(sort_rect[i]);
	Mat dstroi;
	resize(roi, dstroi, Size(40, 50), 0, 0); //重设大小
	img_mat.push_back(dstroi);
    //绘制外接矩形
	rectangle(clone_img, Point(sort_rect[i].x, sort_rect[i].y),
	Point(sort_rect[i].x + sort_rect[i].width, sort_rect[i].y + sort_rect[i].height), 
     Scalar(0, 0, 255), 1, 8);
}

然后我们要对轮廓进行排序,因为opencv查找的轮廓并不是按顺序的,而是随机的,但是我们识别数字肯定是要按顺序,不然识别结果肯定是错误的,我们可以根据轮廓的外接矩形的x坐标来排序,从左往右x坐标依次增大,这里使用冒泡排序,排序过后的顺序就是原图中数字的正确顺序

//对矩形进行排序,因为轮廓的顺序不一定是数字真正的顺序
for (int i = 0; i < sort_rect.size(); i++)
{
	for (int j = i + 1; j < sort_rect.size(); j++)
	{
		int j_x = sort_rect[j].x;
		int i_x = sort_rect[i].x;
		if (j_x < i_x)
		{
			Mat temps = img_mat[i];
			img_mat[i] = img_mat[j];
			img_mat[j] = temps;
		}
	}
}

加载模板,模板是自己准备的,可以自己画一张0123456789,然后根据查找轮廓和外接矩形截取roi存起来,存起来后,我们再建立一个容器存储模板图片,注意这个模板图片也是需要转灰度二值化处理,方便匹配识别

vector<Mat> myTemplate; //模板容器
for (int i = 0; i < 10; i++)
{
	string name = format("%d.png", i);
	Mat temp = imread(name);
	cvtColor(temp, temp, COLOR_BGR2GRAY);
	threshold(temp, temp, 100, 255, THRESH_BINARY);
	myTemplate.push_back(temp);
}

最后就是模板匹配了,匹配的方式有很多种,这里采用的是对数字和模板进行像素点的匹配,根据相同像素点的百分占比来得出结论,相同像素点越多,说明匹配相似度越高,最后以匹配程度最高的作为最终识别结果

double compare(Mat &img, Mat &temp)               
{
	Mat my_temp;
	resize(temp, my_temp, img.size());
	int rows, cols;
	uchar *img_point, *temp_point; //像素类型uchar
	rows = my_temp.rows;
	cols = my_temp.cols*img.channels();
	double result, same = 0.0, different = 0.0;
	for (int i = 0; i < rows; i++)       //遍历图像像素
	{
        //获取像素值
		img_point = img.ptr<uchar>(i); 
		temp_point = my_temp.ptr<uchar>(i);
		for (int j = 0; j < cols; j++)
		{
			if (img_point[j] == temp_point[j])
				same++;         //记录像素相同的个数
			else
				different++;    //记录像素不同的个数
		}
	}
	result = same / (same + different);
	return result;                     //返回匹配结果
}
vector<int> seq;//顺序存放识别结果
for (int i = 0; i < img_mat.size(); i++)
{
	double com = 0;
	double min = 0;
	int min_seq = 0;//记录识别结果
	for (int j = 0; j < myTemplate.size(); j++)
	{
		com = compare(img_mat[i], myTemplate[j]);
		if (com > min)
		{
			min = com;
			min_seq = j;
		}
		com = 0;
	}
	seq.push_back(min_seq);
}
//输出结果
cout << "识别结果为:";
for (int i = 0; i < seq.size(); i++)
cout << seq[i];
	

识别结果,正确识别,即使缩放大小也一样能识别成功

以上就是opencv简易的数字识别

喜欢的话就点个赞吧~

点赞收藏关注就是对我最大的支持~



版权声明:本文为m0_71741835原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。