时隔很多天了,今天把opencv做的一个手写数字识别的教程写一下吧。这个实验原本是大三下学期的实验,导师在大二上就把这个任务丢给我们了。其实这个任务很简单,主要是为了熟悉一下图像处理的基本流程以及opencv的基本操作。
首先要下载以及安装我们的opencv。这里呢我是安装的opencv3,2和3大部分是一样的,有小部分opencv3进行了删减,比如SURF特征点检测,ORB算法描述与匹配等经典算法被移除。
安装的过程中会遇到种种困难。由于我是19年10月份做的,距今已经3个多月了,我清晰的记得我安装时遇到了很多困难,但是我清楚的知道我记不得了。我就记得多少写多少吧。在安装的过程中,我已经选择了最高版本的,但是按照书上的说法,它最高支持到vs2015版,而我的是2017版的vs,我抱着试试看得心态完成了安装,发现是可以用的。另外还有一些地方,就是我们得调试,采用得是debug啊还是release啊一定要看好,是32位得还是64位得也要看好。不过就目前来看,32位还是比较稳定一些,没有书可以看一下这篇帖子https://blog.csdn.net/qq_41175905/article/details/80560429
英文的:https://www.cnblogs.com/wwf828/p/8196165.html
这是我看的那本书的作者的博客:https://blog.csdn.net/poem_qianmo/article/details/19809337
这个博主有一些实用的解决办法:https://www.dididongdong.com/archives/322
在这个属性管理器里面一定要按照书上或者教程上该改得都改了,该添加得添加了。
完成了这个配置,我们就已经成功了一半啦,下面我们来谈谈怎么实现手写数字识别。目前数字图像处理识别这一块有很多方法,比如模板匹配,特征识别,贝叶斯等,我们这里使用的是比较简单易懂的模板匹配法,即用足够多的模板样本和要识别的数字进行一一比对,像素最相似的即我们要匹配的那个数字,故我们从0到9,每个数字分别有100个模板模板样本,一共是1000个样本,用x-y进行命名,这样方便进行循环比对,也能够对匹配出的数字进行输出。
那么常规操作就是先灰度化,然后二值化,或者直接二值化,这些操作在opencv中是有现成的算法和函数的,你只需要调用它就行了,然后再进行裁剪,把多余的地方裁掉,把每个数字单独裁开,然后再调整成统一的大小。由于我们的模板库的图像都是调整好的,我们就不需要再做调整了。但是实际上我当时做的时候没有意识到,模板的数字实际上是有留边的,但是我裁剪的时候是贴着边剪得,所以误差可能会比较大。
这是我自己手写的数字,作为识别对象:
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
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;
}
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 += src.at <uchar>(row, i);
}
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);//统计所有行像素的总和
//cout <<i<<" th "<< colValue << endl;
if (colValue > 0)//扫描直到行像素的总和大于0时,记下当前位置top
{
top = i;
break;
}
}
for (; i < src.rows; i++)
{
int colValue = getRowSum(src, i);//统计所有行像素的总和
//cout << i << " th " << colValue << endl;
if (colValue == 0)//继续扫描直到行像素的总和等于0时,记下当前位置bottom
{
bottom = i;
break;
}
}
int height = bottom - top;
Rect rect(0, top, src.cols, height);
dstImg = src(rect).clone();
}
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);//统计所有列像素的总和
//cout <<i<<" th "<< colValue << endl;
if (colValue > 0)//扫描直到列像素的总和大于0时,记下当前位置left
{
left = i;
break;
}
}
if (left == 0)
{
return 1;
}//扫描完成
for (; i < src.cols; i++)
{
int colValue = getColSum(src, i);//统计所有列像素的总和
if (colValue == 0)//继续扫描直到列像素的总和等于0时,记下当前位置right
{
right = i;
break;
}
}
int width = right - left;//分割图片的宽度则为right - left
Rect rect(left, 0, width, src.rows);//构造一个矩形,参数分别为矩形左边顶部的X坐标、Y坐标,右边底部的X坐标、Y坐标(左上角坐标为0,0)
leftImg = src(rect).clone();
Rect rectRight(right, 0, src.cols - right, src.rows);//分割后剩下的原图
rightImg = src(rectRight).clone();
cutTop(leftImg, leftImg);//上下切割
return 0;
}
void getPXSum(Mat &src, int &a)//获取所有像素点和(白色)
{
threshold(src, src, 100, 255, 0);
a = 0; for (int i = 0; i < src.rows;i++)
{
for (int j = 0; j < src.cols; j++)
{
a += src.at <uchar>(i, j);//src.at <uchar>(i, j)表示(i,j)点的像素值;
}
}
}
int getSubtract(Mat &src, int TemplateNum) //数字识别
{
Mat leftim, rightim;
Mat img_result;
int min = 10000000;
int serieNum = 0;
for (int i = 0; i < TemplateNum; i++)
{
for (int j = 0;j <= 99;j++)
{
char name[20];
sprintf_s(name, "%d_%d.bmp", i, j);
Mat Template = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取模板
threshold(Template, Template, 100, 255,3);
//threshold(src, src, 100, 255, CV_THRESH_BINARY);
//resize(src, src, Size(32, 48), 0, 0, CV_INTER_LINEAR);
cutLeft(Template, leftim, rightim);//裁剪;
resize(leftim,leftim, Size(56, 56), (0, 0), (0, 0), 3);//调整尺寸
//imshow(name, Template);
/*让需要匹配的图分别和10个模板对应像素点值相减,
然后求返回图片的整个图片的像素点值得平方和,
和哪个模板匹配时候返回图片的平方和最小则就可以得到结果*/
absdiff(leftim, src, img_result);//AbsDiff,OpenCV中计算两个数组差的绝对值的函数。
int diff = 0;
getPXSum(img_result, diff);//获取所有像素点和
//printf("%d,%d\n", i, diff);
if (diff < min)//像素点对比
{
min = diff;
serieNum = i;
//printf("%d,%d\n", i, min);
}
}
}
printf("匹配的数字是%d\n", serieNum);
return serieNum;
}
int main()
{
Mat src = imread("0.jpg", CV_LOAD_IMAGE_GRAYSCALE), leftim, rightim, std,t;//读取图片
//std=imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
//t = imread("0_0.bmp",1);
//imshow("trin", t);
threshold(src, src, 100, 255, 1);//二值化
//threshold(src, src, 100, 255, 3);//二值化
resize(src, src, Size(src.cols / 4, src.rows / 4), (0, 0), (0, 0), 3);
//resize(src, src, Size(32, 48), 0, 0, CV_INTER_LINEAR);//调整尺寸
imshow("二值化图片", src);//显示二值化后图片
int i = 1;
while ( cutLeft(src, leftim, rightim)!=1)
{
char nameLeft[20];//存放可变窗口名字
cutLeft(src, leftim, rightim);//裁剪;
sprintf(nameLeft, "%dth number", i);//可变窗口名字,赋值给nemeleft
resize(leftim, leftim, Size(56 , 56), (0, 0), (0, 0), 3);
imshow(nameLeft, leftim);
getSubtract(leftim, 10);//识别函数;
src = rightim;
i++;
}//printf("%d ; %d\n", std.cols * 2, std.rows * 2);
waitKey(0);
return 0;
}
运行结果是这个样子

0 条评论