前言
使用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简易的数字识别
喜欢的话就点个赞吧~
点赞收藏关注就是对我最大的支持~