写在前面
之前写过一给手写数字识别系统,那时候是大一时候,刚刚对数字图像处理有了一点点接触。实际上真正开始学这门课实在上学期,也就是大三上学期,这学期才开始正式做这个实验。上学期用opencv自己实现了均值滤波,直方图均衡化,腐蚀膨胀等相关的图像处理操作,学习了相关理论知识,再回看之前写的代码感觉真的有很多当初根本不理解的地方,现在一看就忽然理解了。而且对于之前的实现还有改进的地方。这次这个实验也是使用mnist的数据集,但是这次的要求和上次不一样了,上次是依葫芦画瓢。感觉懂了,实际上不懂,但这次是真正要实现一个模板匹配的算法。下面的代码注释把所有涉及到的点都说明白了,对着代码看应该会比较好。
在这之前先给出算法流程
下面给出算法流程:
下面给出代码。
我用的环境是vs2019+opencv3.4.7
系统一共是两个程序,一个是训练程序,一个测试程序。训练程序用于把数据集的图片01序列存在txt文档。测试程序通过读取文档对待识别数字进行识别。测试集采用训练集每个数字前20张图片。训练集每个数字有100张图。
下面是训练代码:
/* 因为测试集是训练集的子集,所以本程序在cvtest2(这个程序把训练集1000张图片全部训练)的基础上,把训练集的范围降到100的数据,即每个数字10个样本,用来测试测试集的识别率。 */ #include "cv.h" #include "highgui.h" #include<opencv2/opencv.hpp> #include <stdio.h> #include <stdlib.h> #include <opencv\cv.hpp> #include<sstream> #include <iostream> #include <fstream> #include <Windows.h> using namespace std; using namespace cv; ofstream codeo("E:\\code\\vs\\txtfile\\2lb.txt", ios::binary);//不追加,每次训练都重新覆盖原文件; //计算欧氏距离 int getDist(int test[100], int sample[100]) { int sum = 0; for (int i = 0; i < 100; i++) { sum += pow(test[i] - sample[i], 2);//这里省略开根号 } return sum; } //对图像进行区域分割并字符串化图片区域,得到test数组,里面存放着一张图片的01序列 void getPXSum(Mat& src, int(&test)[100]) { int mark = 0, count = 0;//mark是划分的格子的序号,count是格子内计数像素个数 //threshold(src, src, 100, 1, CV_THRESH_BINARY); while (mark < 100)//100.个格子10*10 { for (int i = 0; i < 30; i++) { for (int j = 0; j < 30; j++)//一个格子里面有30*30=90个像素 { if (src.at <uchar>(i + 30 * (mark / 10), j + 30 * (mark % 10)) > 200) /*Mat类中的at方法作用:用于获取图像矩阵某点的值或改变某点的值。对于单通道图像的使用方法:image.at<uchar>(i,j) = 255;对于RGB三通道图像的使用方法:image.at<Vec3b>(i,j)[0] = 255;image.at<Vec3b>(i,j)[1] = 255;image.at<Vec3b>(i,j)[2] = 255;*/ count++;//mark/7是得出当前是第几行的格子 //printf_s("%d",src.at <uchar>(i + 3 * (mark % 7), j + 3 * (mark / 7))); } //printf_s("\n"); } if (count > 50) test[mark] = 1;//若格子里超过半数像素是亮的,则记这个格子为1 else { test[mark] = 0; } //printf_s("黑色像素占该3*3区域的%d/9\n", count); count = 0; mark++; } } //输出一张图片的字符串序列 void readNum(int(&test)[100]) { //ofstream codeo("E:\\code\\vs\\txtfile\\1.txt", ios::binary | ios::app); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { //printf_s("%d", test[i * 10 + j]);//输出一张图片的字符串序列 int buffer = test[i * 10 + j]; //while (!codei.eof()) { codeo << buffer; } //printf_s("\n"); } codeo << endl; printf_s("\n"); } //统计所有列像素的总和 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);//统计所有列像素的总和 //cout << i << " th " << colValue << endl; 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();//实际上这句话进行了一个裁剪的功能 //clone的作用是对src进行了深拷贝,创建了一个完整的副本 //利用Mat方法直接赋值获取的区域图像仍然会改变原图. //若想直接复制出ROI区域, 需要把原始图像进行clone即可 /*1. 使用拷贝构造函数Mat(constMat& m, const Rect& roi ),矩形roi指定了兴趣区 Mat src = imread(“xx.jpg”); Mat srcROI( src, Rect(0,0,src.cols/2,src.rows/2)); 2. 使用操作符”()”,即Mat operator () ( const Rect&roi ) const,矩形roi指定了兴趣区 Mat src = imread(“xx.jpg”); Mat srcROI = src(Rect(0,0,src.cols/2,src.rows/2)); 注意:以上两种操作,srcROI的数据与源图像src共享存储区,所以此后在srcROI上的操作也会作用在源图像src上。 所以,当需要提取出独立的ROI区域时,应该使用 Mat srcROI = src(Rect(0,0,src.cols/2,src.rows/2)).clone(); */ Rect rectRight(right, 0, src.cols - right, src.rows);//分割后剩下的原图 rightImg = src(rectRight).clone(); cutTop(leftImg, leftImg);//上下切割 return 0; } //test形参传引用,会保存数据,处理图片,并获得字符串 void readImg(Mat& src, int(&test)[100], int i) { imshow("origin0", src);//显示原图片 medianBlur(src, src, 3);//中值滤波去椒盐噪声 // imshow("origin2", src);//显示去噪后图片 Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核 //dilate(src, src, element);//膨胀 //imshow("origin3", src);//显示膨胀后图片 threshold(src, src, 100, 255, THRESH_BINARY);//二值化,100是阈值 Mat rightImg, leftImg; cutLeft(src, leftImg, rightImg); imshow("origin4", leftImg);//显示切割后图片 resize(leftImg, leftImg, Size(300, 300), 0, 0, CV_INTER_LINEAR); imshow("origin5", leftImg);//显示调整尺寸后图片 stringstream ss; ss << i;//此举将int类型转换为字符串类型 /* stringstream还是相当强大的。简单易懂,虽然写的行数比较多! 基本数据类型转换例子 int和string, 也支持string和char* ,int和char* 之间的转换。 注意同一个stringstream对象,再进行多次转换的时候,必须调用stringstream的成员函数clear(). 头文件<sstream> 如int转string int n = 0; std::stringstream ss; std::string str; ss << n; ss >> str; */ imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\" + ss.str() + ".bmp", leftImg);//streamstring在调用str()时,会返回临时的string对象。 getPXSum(leftImg, test);//计算切割好的图片的序列,存到test里 /**/ } int main() { int sample[10][100][100];//保存样本图片字符串,每个数字抽取10副图,每幅图拥有100个字符的序列 //double num[10][3] = { 0 };//统计结果,即10个数字的三率 int test[100];//保存测试图片字符串 int test_num = 7;//测试的数字 int true_num = 0;//数字的识别结果 printf_s("测试样本\n"); char name[100];//测试图片路径 //sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\train-images\\%d_%d.bmp", test_num, test_num); //Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取的测试图片图片 sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\moban\\shou.bmp"); Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取图片 // imshow("origin0", src);//显示原图片 readImg(src, test, test_num);//获得了src的序列 //codeo << "test"; //readNum(test); int minidist = 100, dist; int chou = 20; for (int i = 0; i < 10; i++) { for (int j = 0; j < 80; j++)//每个数字取后80个样本作为训练样本 { //chou++; char name[100];//模板图片的存储位置 sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\train-images\\%d_%d.bmp", i, j + 20);//取每个数字第20个样本以后的图像 Mat Template = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取模板 readImg(Template, sample[i][j], i);//模板第i个数字的第j副图 codeo << i; readNum(sample[i][j]); printf_s("与数字%d第%d个模板", i, j + 1); printf_s("欧式距离:"); dist = getDist(test, sample[i][j]); if (minidist >= dist) { minidist = dist; true_num = i; } printf_s("%d\n\n\n 当前minidist:%d", dist, minidist); } } printf_s("识别结果为:%d", true_num); codeo.close(); waitKey(0); //getchar(); return 0; }
下面是测试程序:
/* 本程序用于测试测试集test的数字识别率。用的数据集是从1000个全部训练集中抽取的100个数据, */ #include "cv.h" #include "highgui.h" #include<opencv2/opencv.hpp> #include <stdio.h> #include <stdlib.h> #include <opencv\cv.hpp> #include<sstream> #include <iostream> #include <fstream> #include <Windows.h> using namespace std; using namespace cv; //计算欧氏距离 int getDist(int test[100], int sample[100]) { int sum = 0; for (int i = 0; i < 100; i++) { sum += pow(test[i] - sample[i], 2);//这里省略开根号 } return sum; } //对图像进行区域分割并字符串化图片区域,得到test数组,里面存放着一张图片的01序列 void getPXSum(Mat& src, int(&test)[100]) { int mark = 0, count = 0;//mark是划分的格子的序号,count是格子内计数像素个数 //threshold(src, src, 100, 1, CV_THRESH_BINARY); while (mark < 100)//49.个格子7*7 { for (int i = 0; i < 30; i++) { for (int j = 0; j < 30; j++)//一个格子里面有3*3九个像素 { if (src.at <uchar>(i + 30 * (mark / 10), j + 30 * (mark % 10)) > 200) /*Mat类中的at方法作用:用于获取图像矩阵某点的值或改变某点的值。对于单通道图像的使用方法:image.at<uchar>(i,j) = 255;对于RGB三通道图像的使用方法:image.at<Vec3b>(i,j)[0] = 255;image.at<Vec3b>(i,j)[1] = 255;image.at<Vec3b>(i,j)[2] = 255;*/ count++;//mark/7是得出当前是第几行的格子 //printf_s("%d",src.at <uchar>(i + 3 * (mark % 7), j + 3 * (mark / 7))); } //printf_s("\n"); } if (count > 50) test[mark] = 1;//若格子里超过半数像素是亮的,则记这个格子为1 else { test[mark] = 0; } // printf_s("黑色像素占该3*3区域的%d/9\n", count); count = 0; mark++; } } //输出一张图片的字符串序列 void readNum(int(&test)[100]) { //ofstream codeo("E:\\code\\vs\\txtfile\\1.txt", ios::binary | ios::app); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { //printf_s("%d", test[i * 7 + j]);//输出一张图片的字符串序列 int buffer = test[i * 10 + j]; //while (!codei.eof()) { } //printf_s("\n"); } //printf_s("\n"); } //统计所有列像素的总和 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);//统计所有列像素的总和 //cout << i << " th " << colValue << endl; 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; } //test形参传引用,会保存数据,处理图片,并获得字符串 void readImg(Mat& src, int(&test)[100]) { imshow("原图", src);//显示原图片 medianBlur(src, src, 3);//中值滤波去椒盐噪声 //imshow("origin2", src);//显示去噪后图片 Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核 //dilate(src, src, element);//膨胀 //imshow("origin3", src);//显示膨胀后图片 threshold(src, src, 100, 255, THRESH_BINARY);//二值化,100是阈值 Mat rightImg, leftImg; cutLeft(src, leftImg, rightImg); imshow("切割后的图片", leftImg);//显示切割后图片 resize(leftImg, leftImg, Size(300, 300), 0, 0, CV_INTER_LINEAR); imshow("显示调整尺寸后图片", leftImg);//显示调整尺寸后图片 stringstream ss; //ss << i;//此举将int类型转换为字符串类型 //imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\" + ss.str() + ".bmp", leftImg);//streamstring在调用str()时,会返回临时的string对象。 getPXSum(leftImg, test);//计算切割好的图片的序列,存到test里 } int main() { //ofstream code("E:\\code\\vs\\txtfile\\1.txt", ios::binary|ios::app); //code << "123122222222222"<<endl; //code.close(); ifstream codei("E:\\code\\vs\\txtfile\\2lb.txt", ios::binary); int sample1[10][80][101];//保存样本图片字符串,每个数字100副图,每幅图拥有49个字符的序列; int sample[10][80][100]; char ch; for (int i = 0; i < 10; i++) { for (int j = 0; j < 80; j++) { for (int k = 0; k < 101; k++) { codei.get(ch); if ((int)ch == 10) { k--; continue; } sample1[i][j][k] = (int)ch - 48; //cout << sample1[i][j][k]; } //cout << endl; } } for (int i = 0; i < 10; i++) { for (int j = 0; j < 80; j++) { for (int k = 0; k + 1 < 101; k++) { sample[i][j][k] = sample1[i][j][k + 1]; cout << sample[i][j][k]; } cout << endl; } } double num[10][3] = { 0 };//统计结果,即10个数字的三率 int test[100];//保存测试图片字符串 int true_num = 0;//数字的识别结果 char name[100];//测试图片路径 double trues = 0;//数字i的正确识别数量 double faluses = 0;//数字i错误识别的数量 double jv = 0;//拒绝识别的数量 for (int t1 = 0; t1 < 10; t1++) { for (int t2 = 0; t2 < 20; t2++) { sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\test-images\\%d_%d.bmp", t1, t2); Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取的测试图片图片 imshow("origin0", src);//显示原图片 readImg(src, test);//获得了src的序列 //codeo << "test"; readNum(test); /*-------------------以上处理测试识别的图像,以下是匹配模板-----------------------------*/ int minidist = 100, dist; for (int i = 0; i < 10; i++) { for (int j = 0; j < 80; j++) { // printf("当前测试数字:%d_%d\n", t1,t2); //printf("当前图片序列:"); readNum(sample[i][j]); // printf_s("与数字%d第%d个模板", i, j + 1); //printf_s("欧式距离:"); dist = getDist(test, sample[i][j]); if (minidist > dist) { minidist = dist; true_num = i; } // printf_s("%d \n", dist); //printf_s("%d \n当前minidist:%d\n\n\n", dist, minidist); } } if (minidist > 26) { jv++; cout <<"mimidist:"<<minidist<< "拒绝识别\n"; continue; } if (true_num == t1) { trues++; } else { faluses++; } printf_s("识别结果为:%d,minidist:%d\n", true_num, minidist); } printf("数字%d成功率为%.2f\n", t1, trues / 20.0); printf("数字%d错误率为%.2f\n", t1, faluses / 20.0); printf("数字%d拒绝识别率为%.2f\n", t1, jv / 20.0); cout << endl; trues = 0; faluses = 0; jv = 0; } waitKey(0); //getchar(); return 0; }
当然,为了填补上次手写数字识别系统的缺陷,我重新改了一下之前的过的东西,在这篇文章一并分享
我在这所用的东西,其实也是算欧氏距离。仔细想一想,上面的方法是要求我们把一张大的图片分成小块,每一块包含了很多像素,然后再对这些块里超过半数为1的块记为1,实际上就是把像素规模进行变小了嘛。我一个300*300的像素要分成10*10的格子,不就相当于我直接把它变成10*10像素的图片吗。而且再opencv里面有专门计算两个Mat之间的对应元素绝对值差绝对值相加的函数,这不就是是欧氏距离吗。我甚至可以不同把它二值化(相当于半数像素为白色,格子置为1,反之置零),而是灰度化,这样计算欧氏距离是不是更准呢?当然这里计算绝对值之和应该是很多个255相加,但是也可以改成只加一。但是这样的效果是不一样的,大家可以去试一下,改一句代码即可,我再代码中有写出注释。这里用的还是1000张图片的数据集。和之前不一样的是,这里是直接读取数据集图片,而没有训练和读取训练数据。
#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++) { //if (src.at <uchar>(i, j) > 200) a++; 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[80]; sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\train-images\\%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);//裁剪; //Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核 //dilate(leftim, leftim, element);//膨胀 resize(leftim, leftim, Size(8, 8), (0, 0), (0, 0), 3);//调整尺寸 //imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\mo.bmp", leftim);//streamstring在调用str()时,会返回临时的string对象。 imshow("模板", leftim); //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); //waitKey(0); return serieNum; } int main() { Mat src = imread("E:\\code\\vs\\cvtest1\\minist\\moban\\2.jpg", CV_LOAD_IMAGE_GRAYSCALE);//读取图片1.jpg,2.jpg Mat leftim, rightim, std, t; //std=imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE); //t = imread("0_0.bmp",1); imshow("原图", src); threshold(src, src, 100, 255, 1);//二值化 //threshold(src, src, 100, 255, 3);//二值化 medianBlur(src, src, 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[40];//存放可变窗口名字 cutLeft(src, leftim, rightim);//裁剪; sprintf_s(nameLeft, "%dth number", i);//可变窗口名字,赋值给nemeleft Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核 dilate(leftim, leftim, element);//膨胀 //dilate(leftim, leftim, element);//膨胀 resize(leftim, leftim, Size(8, 8), (0, 0), (0, 0), 3); imshow(nameLeft, leftim); getSubtract(leftim, 10);//识别函数; // imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\shou.bmp", leftim);//streamstring在调用str()时,会返回临时的string对象。 src = rightim; i++; }//printf("%d ; %d\n", std.cols * 2, std.rows * 2); waitKey(0); return 0; }
可以看到准确率比上一篇要高
2 条评论
Melanie · 2023年5月8日 下午10:20
Thank you for information https://www.calameo.com/accounts/7363149
Florence · 2023年6月2日 下午9:18
Thank you for the information, it will give me a new outlook on my life. bet241