本案例将使用OpenCV C++ 进行字符识别。主要包括制作数据集、以及模型预测两部分。先看看效果如何吧。
首先第一步,我们需要制作数据集。这里我的方法是,读取一张字符图像,然后通过提取字符轮廓找到字符ROI图像,利用键盘输入给字符打上相应的标签,即完成数据集制作。由于我这里的数据字符图像只包含数字以及大写英文字符,故只识别数字字符以及大写英文字符。如图所示,这是我使用的字符图像,下面需要进行图像预处理提取到字符轮廓。
//进行图像预处理,提取字符轮廓
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);
接下来需要提取字符轮廓,使用findContours提取最外轮廓。然后提取字符ROI图像,并通过键盘输入对应的ASCII码给其打上标签(注:区分大小写)。
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类型