时隔很多天了,今天把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 条评论