基于VS的印刷数字识别系统

工程文件详见 :z199912230/-VS-: 数字图像处理;图像识别 (github.com)

要求 

  1. 根据题目要求,利用图像处理相关知识,完成印刷数字图像的采集及识别,确定具体数字。
  2. 采集印刷体数字图像。识别0-9两种以上字体的数字图像识别。
  3. 熟悉Visaul Studio2019软件开发平台,Opencv及相关功能的配置。
  4. 利用Opencv库函数,驱动笔记本电脑自带的摄像头进行图像采集及显示。
  5. 整个系统的设计过程包括:模板制作,图像采集及预处理,图像识别。
  6. 通过调试、运行,查看数字识别的结果,进行识别率的分析。

系统总体设计 

基于Visaul Studio2019软件开发平台,搭载OpenCv库的印刷数字识别系统的设计,需要识别不同字体的印刷数字,其中涉及到了Opencv库以及相关的操作。

OpenCV图像处理库,它轻量级而且高效,由一系列C函数和少量C++类构成,实现了图像处理和计算机视觉方面的很多通用算法。因此本次课题将使用OpenCv库函数实现以下功能:模板制作、灰度转换、二值化、图像采集、鼠标回调函数操作、图像分割、模板匹配等。最终实现不同印刷数字的识别。系统总体框图如图1所示。

配置环境

  1. 添加环境变量,将OpenCv动态库的路径添加到Path环境变量中。
  2. 在VS2015中新建空项目,配置包含目录:添加OpenCv头文件的路径。
  3. 配置库目录:添加OpenCv动态库的路径。
  4. 配置链接器的输入:添加OpenCv的动态库。

模板制作模块程序设计

      首先准备一张有0~9数字的图片,然后将图片进行灰度处理。

之后将灰度图进行二值化,阈值设为100,这样可以将图片变成黑纸白字。利用边缘检测的原理将每个数字分割开制成模板。

void makeModel()
{
	//Mat src = imread("model.png", CV_LOAD_IMAGE_GRAYSCALE);
	//threshold(src, src, 100, 255, CV_THRESH_BINARY_INV);	// 二值化
	Mat src = imread("model.png");//读取模板
	paintGrey(src, src);//对模板进行灰度化处理
	//imshow("grey", src);
	towNum(src, src, 150, 255, 0);//二值化
	//imshow("tow", src);//显示图像
	imwrite("tow.jpg", src);
	for (int i = 0; i <20; i++)
	{
		char fileName[21];
		Mat rImg, dst;
		cutLeft(src, dst, rImg);
		sprintf_s(fileName, "%d.png", i);//生成0~19.png
		imwrite(fileName, dst);//
		src = rImg;
	}
}

// 二值化
void towNum(Mat &src, Mat &dst, int thresh, int maxVul, bool flag)
{
	Mat tmp = Mat(src.size(), CV_8U);//获取大小
	for (int i = 0; i < src.rows; i++)//行
	{
		for (int j = 0; j < src.cols; j++)//列
		{
			uchar num = src.at<uchar>(i, j);
			if (flag == 0)
			{
				if (num <= thresh)
					tmp.at<uchar>(i, j) = 255;
				else
					tmp.at<uchar>(i, j) = 0;
			}
			else
			{
				if (num <= thresh)
					tmp.at<uchar>(i, j) = 0;
				else
					tmp.at<uchar>(i, j) = 255;
			}
		}
	}
	dst = tmp;
}

// 灰度转换
void paintGrey(Mat &src, Mat &dst)
{
	Mat temp = Mat(src.size(), CV_8U);
	int row = src.rows;
	int col = src.cols;
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			uchar b = src.at<Vec3b>(i, j)[0] * 0.144;
			uchar g = src.at<Vec3b>(i, j)[1] * 0.587;
			uchar r = src.at<Vec3b>(i, j)[2] * 0.299;
			temp.at<uchar>(i, j) = saturate_cast<uchar>(b + g + r);
			//防止溢出
			if ((b + g + r) > 255)
				temp.at<uchar>(i, j) = 255;
			else
				temp.at<uchar>(i, j) = b + g + r;
		}
	}
	dst = temp;
}

//切掉左右边空白和数字切割//行
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);
		if (colValue > 0)
		{
			left = i;
			break;
		}
	}
	if (left == 0)
		return 1;
	for (; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);
		if (colValue == 0)
		{
			right = i;
			break;
		}
	}
	int width = right - left;
	Rect rect(left, 0, width, src.rows);
	leftImg = src(rect).clone();//

	Rect rectRight(right, 0, src.cols - right, src.rows);//下x,y坐标长宽
	rightImg = src(rectRight).clone();

	cutTop(leftImg, leftImg);
	return 0;
}

//统计所有列的二值总和
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;
}

//切掉图片的上下空白
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);
		if (colValue > 0)
		{
			top = i;//统计高度
			break;
		}
	}
	for (; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);
		if (colValue == 0)
		{
			bottom = i;
			break;
		}
	}
	int height = bottom - top;
	Rect rect(0, top, src.cols, height);
	dstImg = src(rect).clone();
}

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 = sum + src.at<uchar>(row, i);
	}
	return sum;
}

图像采集模块程序设计

首先使用OpenCv库函数启动IP摄像头,采集图像,然后用鼠标选择要识别的具体区域,此处使用到了OpenCv库的鼠标操作回调函数,之后将识别区域的图片进行灰度转换与二值化处理。

Mat getPicture()
{
	
	int c;
	
	VideoCapture capture("“http://账号:密码@广域网IP:端口号”");//调用摄像头
	while (true)
	{
		capture.read(src);
		imshow("origion", src);
		c = waitKey(30);//延时
		if (c == 13)
		{
			capture.release();
			break;
		}
	}
	/*VideoCapture v(0);//调用电脑摄像头
	int c;
	while (v.read(src))
	{
		imshow("origin", src);
		c = waitKey(30);//延时
		if (c == 13)
		{
			v.release();
			break;
		}
	}*/
	//imshow("test", src);
	//src = imread("test.png");
	imshow("t", src);
	// 设置鼠标事件回调函数
	setMouseCallback("t", mouse_callback);	
	while (char(waitKey(1)) != 13)
	{
	}
	paintGrey(crop, crop);
	towNum(crop, crop, 150, 255, 0);
	return crop;
}

// 鼠标事件回调函数
void mouse_callback(int event, int x, int y, int, void*)
{
	// 当鼠标左键按下时,记录其状态和坐标
	if (event == EVENT_LBUTTONDOWN)
	{
		ldown = true;
		corner1.x = x;
		corner1.y = y;
		cout << "Corner 1 recorded at" << corner1 << endl;
	}

	// 当鼠标左键放开时,记录其状态和坐标
	if (event == EVENT_LBUTTONUP)
	{
		// 判断选取的区域是否大于20个像素
		if (abs(x - corner1.x) > 20 && abs(y - corner1.y) >20)
		{
			lup = true;
			corner2.x = x;
			corner2.y = y;
			cout << "Corner 2 recorded at" << corner2 << endl << endl;
		}
		else
		{
			cout << "Please select a bigger region" << endl;
			ldown = false;
		}
	}

	//当移动鼠标时, 更新选择区域, 并绘制矩形选择区域图形
	if (ldown == true && lup == false)
	{
		Point pt;
		pt.x = x;
		pt.y = y;
		Mat local_img = src.clone();
		rectangle(local_img, corner1, pt, Scalar(0, 0, 255));
		imshow("t", local_img);
		

	}

	// 定义感兴趣区域,并对原图进行剪裁
	if (ldown == true && lup == true)
	{
		box.width = abs(corner1.x - corner2.x);
		box.height = abs(corner1.y - corner2.y);
		box.x = min(corner1.x, corner2.x);
		box.y = min(corner1.y, corner2.y);

		// 对原图进行剪裁, 生成新图
		crop = 0;
		crop = src(box);
		namedWindow("Crop", CV_WINDOW_AUTOSIZE);
		imshow("Crop", crop);
		ldown = false;
		lup = false;
		imshow("t", src);
	}
}

具体启动IP摄像头的教程参考

 通过局域网访问手机摄像头数据流 (baidu.com)

利用OpenCV4调用安卓手机摄像头、电脑摄像头的实现 (baidu.com)

图像识别模块程序设计

图像匹配有很多方法:比如模板匹配、直方图匹配等,本次课题中采用的方法是模板匹配。

在匹配图像之前需要将模板与待识别图像设定为同样大小得像素值,用到了图像缩放,实习中用的双线性插值法。双线性插值法的原理:当求出的分数地址与像素点不一致时,求出其与周围4个像素点的距离比,根据该比例,由4个邻域的像素灰度值进行双线性插值。

图像匹配的原理:将待识别的二值图像的像数值与20个模板的二值图像的像数值一一对应,求其差的绝对值。

void getPXSum(Mat &src, int &a) //计算图像最小距离
{
	towNum(src, src, 150, 255, 1);
	a = 0;
	for (int i = 0; i < src.rows; i++)//行像素
	{
		for (int j = 0; j < src.cols; j++)//列像素
		{
			a += src.at<uchar>(i, j);//第i行第j列的的值.at()是一个返回值
		}
	}
}

// 求绝对值
void my_abs(Mat &src, Mat &dst, Mat &result)
{
	Mat tmp = Mat(src.size(), CV_8U);//无符号整数
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			uchar a = src.at<uchar>(i, j);
			uchar b = dst.at<uchar>(i, j);
			tmp.at<uchar>(i, j) = a > b ? a - b : b - a;//如果a>b成立则a-b,否则b-a
		}
	}
	result = tmp;
}

int getSubtract(Mat &src, int TemplateNum)	//用于识别数字
{
	Mat img_result = Mat(src.size(), CV_8U);;
	int min = 1000000;
	int serieNum = 0;
	int serienum = 0;
	int num[20];
	for (int p = 0; p < 20; p++)
		num[p] = 0;
	for (int i = 0; i <= TemplateNum; i++)
	{
		char name[20];
		sprintf_s(name, "%d.png", i);
		Mat Template = imread(name);//读取图片文件
		paintGrey(Template, Template);//灰度转换
		//threshold(src, src, 100, 255, CV_THRESH_BINARY);
		towNum(Template, Template, 150, 255, 1);//二值化test模板
		towNum(src, src, 150, 255, 1);
		resize(src, src, Size(50, 50), 0, 0, CV_INTER_LINEAR);//缩放的大小为50*50,0,height/row,0,,双线性插值
		resize(Template, Template, Size(50, 50), 0, 0, CV_INTER_LINEAR);
		my_abs(Template, src, img_result);//结果保存到img_result
		int diff = 0;
		getPXSum(img_result, diff);//距离
		num[i] = diff;
			
		/*if (diff < min)
		{
			min = diff;
			serieNum = i;
			serienum = i;
			if (serienum >= 10)
				serienum =serienum -10;
		}*/
	}
	for (int i = 0; i < 20; i++)
	{
		int cin;
		cin >> num[i];

		if (num[i] < num[serieNum])
		{
			serieNum = i;
			serienum = i;
			if (serienum >= 10)
				serienum = serienum - 10;
		}
	}
	//cout << "最小距离是" << min << ",";
	cout << "最小距离是" << num [serienum] << ",";
	
		cout << "匹配的是第" << serieNum << "个模板,匹配的数字是" << serienum << endl;
	
	return serieNum;
}

int main()
{
	// 建模板
	makeModel();


	//用于识别
	Mat src = imread("test.png", CV_LOAD_IMAGE_GRAYSCALE);
	Mat test = getPicture();
	while (char(waitKey(0))!='q')//无返回值,必须有按键
	{
		imshow("test", test);//
		Mat leftImg, rightImg;
		int res = cutLeft(test, leftImg, rightImg);//切割
		while (res == 0)//切割完成后
		{
			Mat srcTmp = rightImg;
			getSubtract(leftImg, 19 );
			res = cutLeft(srcTmp, leftImg, rightImg);
		}
		
		while (char(waitKey(1)) != 13)
		{
		}
		paintGrey(crop, crop);
		imshow("crop", crop);
		towNum(crop, crop, 150, 255, 0);
		test = crop;
	}
	waitKey(0);
	return 0;
}

结果

采集到的图像 

 

选取测试图片 

 

 识别结果

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值