OpenCV C++案例实战三十二《字符识别》

OpenCV C++案例实战三十二《字符识别》

  • 前言
  • 一、结果演示
  • 二、制作数据集
  • 三、字符识别
  • 四、源码
  • 总结


前言

本案例将使用OpenCV C++ 进行字符识别。主要包括制作数据集、以及模型预测两部分。先看看效果如何吧。

一、结果演示

OpenCV C++案例实战三十二《字符识别》_第1张图片

二、制作数据集

首先第一步,我们需要制作数据集。这里我的方法是,读取一张字符图像,然后通过提取字符轮廓找到字符ROI图像,利用键盘输入给字符打上相应的标签,即完成数据集制作。由于我这里的数据字符图像只包含数字以及大写英文字符,故只识别数字字符以及大写英文字符。如图所示,这是我使用的字符图像,下面需要进行图像预处理提取到字符轮廓。
OpenCV C++案例实战三十二《字符识别》_第2张图片

//进行图像预处理,提取字符轮廓
Mat grayImg;
cvtColor(Train_Chars, grayImg, COLOR_BGR2GRAY);

Mat blurImg;
GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

Mat binImg;
threshold(blurImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

OpenCV C++案例实战三十二《字符识别》_第3张图片
接下来需要提取字符轮廓,使用findContours提取最外轮廓。然后提取字符ROI图像,并通过键盘输入对应的ASCII码给其打上标签(注:区分大小写)。
OpenCV C++案例实战三十二《字符识别》_第4张图片

Rect rect = boundingRect(contours[cnt]);
rectangle(Train_Chars, rect, Scalar(0, 255, 0), 2);

Mat ROI = binImg(rect);
imshow("ROI", ROI);
imshow("Training_Chars", Train_Chars);
int charVal = waitKey(0); //将字符通过键盘输入给予标签

进行KNN训练,具体看源码相应注释。

	//由于我这里的数据字符图像只包含数字以及大写英文字符,只识别数字字符以及大写英文字符
	vector<int>ValidChars =
	{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
	'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
	'U', 'V', 'W', 'X', 'Y', 'Z' };

	//进行图像预处理,提取字符轮廓
	Mat grayImg;
	cvtColor(Train_Chars, grayImg, COLOR_BGR2GRAY);

	Mat blurImg;
	GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

	Mat binImg;
	threshold(blurImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	imwrite("result.jpg", binImg);

	vector<vector<Point>>contours;
	findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	//准备训练所用数据集,将字符通过键盘输入给予标签
	Mat Train_Data, Train_Label;
	for (int cnt = 0; cnt < contours.size(); cnt++)
	{
		if (contourArea(contours[cnt]) > 10)
		{
			Rect rect = boundingRect(contours[cnt]);
			rectangle(Train_Chars, rect, Scalar(0, 255, 0), 2);

			Mat ROI = binImg(rect);
			imshow("ROI", ROI);
			imshow("Training_Chars", Train_Chars);
			int charVal = waitKey(0); //将字符通过键盘输入给予标签

			if (find(ValidChars.begin(), ValidChars.end(), charVal) != ValidChars.end())
			{
				//如果输入的字符在字符匹配表中,则进行存储
				
				//由于我们在识别字符时,会遇到各种尺寸的字符,故将所有的字符固定同一尺寸
				Mat resizeRoi;
				resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

				//将图像转成浮点型,因为KNN训练数据集读取的是浮点型数据
				Mat RoiFloat;
				resizeRoi.convertTo(RoiFloat, CV_32FC1);

				Train_Data.push_back(RoiFloat.reshape(0,1));
				Train_Label.push_back(charVal);
				cout << charVal << endl;
			}
		}
	}

	//进行KNN训练
	Train_Data.convertTo(Train_Data, CV_32FC1);
	Train_Label.convertTo(Train_Label, CV_32FC1);
	const int k = 3;//k取值,基数
	Ptr<ml::KNearest>knn = ml::KNearest::create();//构造KNN模型
	knn->setDefaultK(k);//设定k值
	knn->setIsClassifier(true);//KNN算法可用于分类,回归
	knn->setAlgorithmType(ml::KNearest::BRUTE_FORCE);//字符匹配算法
	knn->train(Train_Data, ml::ROW_SAMPLE, Train_Label);//模型训练
	knn->save(filename);//模型保存

	cout << "Model training is complete!" << endl;

三、字符识别

当我们完成训练后,会保存相应的模型。这时我们仅需直接读取模型数据就可以了。

	Ptr<ml::KNearest>knn = cv::Algorithm::load<cv::ml::KNearest>(filename);//加载KNN模型

	//图像预处理
	Mat grayImg;
	cvtColor(Test_Chars, grayImg, COLOR_BGR2GRAY);

	Mat blurImg;
	GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

	Mat binImg;
	threshold(blurImg, binImg, 200, 255, THRESH_BINARY_INV);

	vector<vector<Point>>contours;
	findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//查找最外轮廓
	for (int cnt = 0; cnt < contours.size(); cnt++)
	{
		if (contourArea(contours[cnt]) > 10)
		{
			Rect rect = boundingRect(contours[cnt]);//轮廓外接矩形
			rectangle(Test_Chars, rect, Scalar(0, 0, 255), 2);

			Mat ROI = binImg(rect);//字符ROI图像

			Mat resizeRoi;//将字符resize成固定大小
			resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

			Mat RoiFloat;//将图像转化成CV_32FC1
			resizeRoi.convertTo(RoiFloat, CV_32FC1);
			RoiFloat = RoiFloat.reshape(0, 1);

			float f = knn->predict(RoiFloat);//进行字符识别预测

			//结果显示
			char text[50];
			sprintf_s(text, "%c",char(int(f)));
			cout << char(int(f)) << endl;//将字符结果float转成char
			double scale = rect.width * 0.02;
			putText(Test_Chars, text, rect.br(), FONT_HERSHEY_SIMPLEX, scale, Scalar(0, 255, 0), 2);
			imshow("Test_Chars", Test_Chars);
			waitKey(0); 

		}
	}

四、源码

#include
#include
#include
#include
using namespace std;
using namespace cv;

const int ImgWidth = 20;//图片宽
const int ImgHeight = 30;//图片高

int main()
{
	//准备数据集
	Mat Train_Chars = imread("training_chars.png");//数据集图像
	Mat Test_Chars = imread("test.jpg");//测试图像
	if (Train_Chars.empty()|| Test_Chars.empty())
	{
		cout << "can not read the image..." << endl;
		system("pause");
		return -1;
	}

	string filename = "model.xml";//模型文件
	fstream fin;
	fin.open(filename, ios::in);
	if (fin.is_open())
	{
		Ptr<ml::KNearest>knn = cv::Algorithm::load<cv::ml::KNearest>(filename);//加载KNN模型

		//图像预处理
		Mat grayImg;
		cvtColor(Test_Chars, grayImg, COLOR_BGR2GRAY);

		Mat blurImg;
		GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

		Mat binImg;
		threshold(blurImg, binImg, 200, 255, THRESH_BINARY_INV);

		vector<vector<Point>>contours;
		findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//查找最外轮廓
		for (int cnt = 0; cnt < contours.size(); cnt++)
		{
			if (contourArea(contours[cnt]) > 10)
			{
				Rect rect = boundingRect(contours[cnt]);//轮廓外接矩形
				rectangle(Test_Chars, rect, Scalar(0, 0, 255), 2);

				Mat ROI = binImg(rect);//字符ROI图像

				Mat resizeRoi;//将字符resize成固定大小
				resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

				Mat RoiFloat;//将图像转化成CV_32FC1
				resizeRoi.convertTo(RoiFloat, CV_32FC1);
				RoiFloat = RoiFloat.reshape(0, 1);

				float f = knn->predict(RoiFloat);//进行字符识别预测

				//结果显示
				char text[50];
				sprintf_s(text, "%c",char(int(f)));
				cout << char(int(f)) << " ";//将字符结果float转成char
				double scale = rect.width * 0.02;
				putText(Test_Chars, text, rect.br(), FONT_HERSHEY_SIMPLEX, scale, Scalar(0, 255, 0), 2);
				imshow("Test_Chars", Test_Chars);
				waitKey(0); 

			}
		}
		cout << endl;
	}
	else
	{
		//由于我这里的数据字符图像只包含数字以及大写英文字符,只识别数字字符以及大写英文字符
		vector<int>ValidChars =
		{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
		'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
		'U', 'V', 'W', 'X', 'Y', 'Z' };

		//进行图像预处理,提取字符轮廓
		Mat grayImg;
		cvtColor(Train_Chars, grayImg, COLOR_BGR2GRAY);

		Mat blurImg;
		GaussianBlur(grayImg, blurImg, Size(3, 3), 0);

		Mat binImg;
		threshold(blurImg, binImg, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

		vector<vector<Point>>contours;
		findContours(binImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

		//准备训练所用数据集,将字符通过键盘输入给予标签
		Mat Train_Data, Train_Label;
		for (int cnt = 0; cnt < contours.size(); cnt++)
		{
			if (contourArea(contours[cnt]) > 10)
			{
				Rect rect = boundingRect(contours[cnt]);
				rectangle(Train_Chars, rect, Scalar(0, 255, 0), 2);

				Mat ROI = binImg(rect);
				imshow("ROI", ROI);
				imshow("Training_Chars", Train_Chars);
				int charVal = waitKey(0); //将字符通过键盘输入给予标签

				if (find(ValidChars.begin(), ValidChars.end(), charVal) != ValidChars.end())
				{
					//如果输入的字符在字符匹配表中,则进行存储
					
					//由于我们在识别字符时,会遇到各种尺寸的字符,故将所有的字符固定同一尺寸
					Mat resizeRoi;
					resize(ROI, resizeRoi, Size(ImgWidth, ImgHeight));

					//将图像转成浮点型,因为KNN训练数据集读取的是浮点型数据
					Mat RoiFloat;
					resizeRoi.convertTo(RoiFloat, CV_32FC1);

					Train_Data.push_back(RoiFloat.reshape(0,1));
					Train_Label.push_back(charVal);
					cout << charVal << endl;
				}
			}
		}

		//进行KNN训练
		Train_Data.convertTo(Train_Data, CV_32FC1);
		Train_Label.convertTo(Train_Label, CV_32FC1);
		const int k = 3;//k取值,基数
		Ptr<ml::KNearest>knn = ml::KNearest::create();//构造KNN模型
		knn->setDefaultK(k);//设定k值
		knn->setIsClassifier(true);//KNN算法可用于分类,回归
		knn->setAlgorithmType(ml::KNearest::BRUTE_FORCE);//字符匹配算法
		knn->train(Train_Data, ml::ROW_SAMPLE, Train_Label);//模型训练
		knn->save(filename);//模型保存

		cout << "Model training is complete!" << endl;
	}

	destroyAllWindows();
	system("pause");
	return 0;
}

总结

本文使用OpenCV C++ 进行字符识别,主要操作有以下几点。
1、制作数据集,利用键盘输入字符相应的ASCII码
2、进行KNN训练,得到训练模型
3、将预测结果转回相应的字符char类型

你可能感兴趣的:(OpenCV,C++项目实战,opencv,c++,计算机视觉)