IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> c++基于模板匹配的手写数字识别(超详细) -> 正文阅读

[人工智能]c++基于模板匹配的手写数字识别(超详细)

大家好!本篇文章是关于手写数字识别的,接下来我将在这里记录我的手写数字识别的从零到有。当初我刚刚做这个实验的时候在csdn上找不到相应的例子,我在这里把我自己的写代码过程发出来,希望能帮到和我一样努力求知的人!

首先,本篇文章用到的方法是模板匹配,而不是基于神经网络的,还请各位注意了!(模板匹配还请自行了解,站上有很多介绍)我刚开始做实验的时候只有一点c++基础,对于文件和opencv我一点都不了解,所以导致了我刚开始迷茫了很久,直到后来才渐渐做起来。废话不多说,让我们开始吧!

过程很简单,如下:

?匹配成功:存在一个最小距离(这些距离相等),且为一个数字;存在多个最小距离,且为同一个数字。

拒绝识别:存在多个最小距离,且为不同数字。

识别错误:存在一个最小距离,但与被测数字不是相同的数字。

也许乍一看看不明白,我在这里解释一下,明白的可以绕过。我们这里假设1,2,3(注意,他们的样本都有多个)为训练集,d为测试样本。匹配时匹配到d与1距离最小且只与1距离最小,(可能与多个1的样本距离最小或者只有一个)那么匹配成功;匹配时匹配到d与1和2的某个样本都有最小距离,那么拒绝匹配;匹配时匹配到d(假如d是1的样本)与2有最小距离,那么识别错误。

因为图片处理不是本文章的主要内容,我们跳过图像处理步骤(有兴趣的可以去看图像处理这门课),直接给处理好的图片。那么我们该如何构建训练库,又该如何让计算机能够识别我们的图片呢?接下来我们来看看如何实现构建训练库。

我的实验中有1000张训练样本(200张测试样本),既然要让计算机能够识别,那当然是把图片数字化。在图像处理的步骤里,我们得到的训练样本都是28*28像素点的图片,可以想到28*28是一个不小的数量,为了提高处理速度,我们把图片压缩成7*7大小的,这样即提高了处理速度,也方便我们写代码,因为7*7和4*4都是正方形。如下图:

?压缩图片:我们纵向遍历7*7的方格,将里面像素大于127的小格子进行计数,当其数量超过6(有的同学会觉得应该是8,因为8是一半,但是8最终得到的正确率太低了,所以我找了一个合适的参数)我们就把大格子对应的7*7的二维数组的相应位置设置为1,反之为0;然后再将数组转换成字符串,这样下来我们就会得到一个长度为49的字符串,这个字符串就是我们计算机匹配的核心。

另外,我是先把训练集和测试集分别数据化,再依次取出来作比较。也可以采用一边遍历测试集和处理,一遍作比较,我没有输出文件名,因为我采用的方式比较笨,代码量也很多,主要是因为我之前写完之后有很多bug,导致我不能成功运行,所以我采用这种简单代码来避免错误,小伙伴们大可不必用这种方式!

值得注意的是,文件流的打开和关闭的时机也会很大程度上影响代码运行,这个问题困扰了我很久,希望大家引以为戒,代码中具体位置我已经标出来了。(标***的位置)另外,大家对于读文件写文件的文件流自己去了解,读文件是ofstream,写文件是ifstream,每次访问文件都要打开文件和关闭文件。getline函数每次依次取一行数据,所以我们在遍历完一个文档之前不会关闭文档,也就不会再打开文档。

最后我对我字符串比较做一个解释,我是采用了一个标志refused来标志当前字符串有没有被拒绝识别,当发现相似度(代码中用total表示的)小于49的就把它赋值给相似度,并且把拒绝识别设置为假,直到找到最小的,当找到最小的之后又找到了另一个相同相似度的,则判断两个样本数字是不是相同的,不是的话就把refused设置为真,即在后面直接输出拒绝识别。

我判断两个样本是否为同一个数字是通过范围比对,简单地来说就是训练样本的第0——99个对应测试样本的第0——19个,这是一个偷懒的办法,我没时间改代码了所以就这样代替了别人那种文本带文件名的。(带文件名比对时还需要去文件名)

其他的解释我放在代码里,有助于大家更直接的理解!

#include<iostream>
#include<fstream>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/core.hpp>
#include<io.h>                          //api和结构体
#include<string.h>
#include<string>


using namespace std;
using namespace cv;

void ergodicTest(string filename, string name);    //遍历函数,name是49为数据写入的文本文档名
string Image_Compression(string imgpath);          //压缩图片并返回字符串
int distance(string str1, string str2);            //对比函数不一样的位数
void compare();

void main()
{
	const char* filepath = "E:\\learn\\vsfile\\c++project\\pictureData\\train-images";//训练集    
	ergodicTest(filepath,"train_num.txt");         //处理训练集
	const char* test_path= "E:\\learn\\vsfile\\c++project\\pictureData\\test-images";//测试机
	ergodicTest(test_path, "test_num.txt");
	compare();   
}

void ergodicTest(string filename,string name)       //遍历并把得到的49位数据存入文档
{
	string firstfilename = filename + "\\*.bmp";  //_findfirst需要在路径的基础上加\\*.bmp  bmp是文件格式
	struct _finddata_t fileinfo;  //这是文件的一个结构体,由头文件io.h包含
	intptr_t handle;            //遍历文件的关键:句柄;不能用long类型,因为精度问题会导致访问冲突,longlong也可以
	string rout = "E:\\learn\\vsfile\\c++project\\pictureData\\" + name;//数据写入路径
	ofstream file;//创建写文件流
	file.open(rout, ios::out);//打开文件
	handle = _findfirst(firstfilename.c_str(), &fileinfo);//句柄
	if ( _findfirst(firstfilename.c_str(), &fileinfo) != -1)//若返回-1则说明访问失败
	{
		do
		{
			file << Image_Compression(filename + "\\" + fileinfo.name) << endl;//将数据存到文本中
		} while (!_findnext(handle, &fileinfo));//能找到下一个文件
		file.close();//关闭文本文档
		_findclose(handle);//关闭句柄
	}
}//该函数有一个文档和一个文件,注意区分。我们做的就是把文件里的图片数据化存到文档里

string Image_Compression(string imgpath)   //输入图片地址返回图片二值像素字符
{
	Mat Image = imread(imgpath);               //输入的图片
	cvtColor(Image, Image, COLOR_BGR2GRAY);   //大家可以去搜一下这个函数
	int Matrix[28][28];                        //用来存01的数组,图片转化成数组
	for (int row = 0; row < Image.rows; row++)  //把图片的像素点传给数组
		for (int col = 0; col < Image.cols; col++)
		{
			Matrix[row][col] = Image.at<uchar>(row, col); //注意这里的类型转化
		}
	string img_str = "";                   //用来存储结果字符串
	int x = 0, y = 0;        //这里的x,y是用来表示矩阵中真是的位置的
	for (int k = 1; k < 50; k++)//遍历49个格子,实际上这里操作的是二维数组
	{
		int total = 0;
		for (int q = 0; q < 4; q++)
			for (int p = 0; p < 4; p++)
				if (Matrix[x + q][y + p] > 127) total += 1;//计数像素超过127的小格子数
		y = (y + 4) % 28; //这里体现了纵向遍历
		if (total >= 6) img_str += '1';    //设置大格子,将28*28的图片转化为7*7即压缩
		else img_str += '0';
		if (k % 7 == 0)//这里就是到了纵行的末尾了
		{
			x += 4;   //遍历下一纵行
			y = 0;    //回到下一纵行的头部
		}
	}
	return img_str;
}

int distance(string str1, string str2)  //比对两个字符串有多少个不一样
{
	int counts=0;
	for (int i = 0; i < str1.length(); i++)
	{
		if (str1[i] == str2[i]) continue;
		else counts++; 
	}
	return counts;
}

void compare()//该函数用来对比字符串
{
	ifstream train_data;//建立读文件流
	ifstream test_data;
	string tmp1 = "";         //从train中取数据存在tmp
	string tmp2 = "";         //从test中取
	bool refused = false; //拒绝识别标志
	int tr_num = 0;       //用来存储最小值的位置(训练集)
	int num_refused = 0;   //拒绝识别个数
	int num_false = 0;     //识别错误个数
	int num_true = 0;      //正确识别个数
	test_data.open("E:\\learn\\vsfile\\c++project\\pictureData\\test_num.txt");  //***
	for (int p = 0; p < 200; p++)
	{
		int total = 49;      //方便比大小,设置初值为49
		getline(test_data, tmp2);
		train_data.open("E:\\learn\\vsfile\\c++project\\pictureData\\train_num.txt"); //***
		for (int j = 0; j < 1000; j++)         //一个测试样本和所有训练样本对比
		{
			getline(train_data, tmp1);
			if (distance(tmp1, tmp2) < total)  //取最相近的
			{
				refused = false;   //被拒绝识别被设置为否,即识别没有被拒绝
				total = distance(tmp1, tmp2);
				tr_num = j;          //记录训练集中的位置
			}
			if (distance(tmp1, tmp2) == total)  //发现相同相似度
			{
				int q = p;
				while (q % 20 != 0) q--;
				//cout << q << endl;
				int l = q * 5, h = (q + 20) * 5;
				if (l > j || j >= h)
				{

					//cout << q * 5 << endl;//900
					//cout << j << endl;

					refused = true;   //拒绝识别
					continue;          //循环继续
				}
			}
		}
		train_data.close();//***
		if (!refused)
		{
			cout << p;
			if (tr_num >= 0 && tr_num < 100)
			{
				if (p >= 0 && p < 20)
				{
					num_true++;
					cout << "识别为:0" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 100 && tr_num < 200)
			{
				if (p >= 20 && p < 40)
				{
					num_true++;
					cout << "识别为:1" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 200 && tr_num < 300)
			{
				if (p >= 40 && p < 60)
				{
					num_true++;
					cout << "识别为:2" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 300 && tr_num < 400)
			{
				if (p >= 60 && p < 80)
				{
					num_true++;
					cout << "识别为:3" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 400 && tr_num < 500)
			{
				if (p >= 80 && p < 100)
				{
					num_true++;
					cout << "识别为:4" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 500 && tr_num < 600)
			{
				if (p >= 100 && p < 120)
				{
					num_true++;
					cout << "识别为:5" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 600 && tr_num < 700)
			{
				if (p >= 120 && p < 140)
				{
					num_true++;
					cout << "识别为:6" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 700 && tr_num < 800)
			{
				if (p >= 140 && p < 160)
				{
					num_true++;
					cout << "识别为:7" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 800 && tr_num < 900)
			{
				if (p >= 160 && p < 180)
				{
					num_true++;
					cout << "识别为:8" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
			if (tr_num >= 900 && tr_num < 1000)
			{
				if (p >= 180 && p < 200)
				{
					num_true++;
					cout << "识别为:9" << endl;
				}
				else
				{
					num_false++;
					cout << "识别错误!" << endl;
				}
			}
		}
		else
		{
			num_refused++;
			cout << p << "拒绝识别!" << endl;
		}
	}
	test_data.close();//***
	double t = num_true / 200.0, f = num_false / 200.0, r = num_refused / 200.0;
	cout << "正确率为:" << t << endl;
	cout << "错误率为:" << f << endl;
	cout << "拒绝识别率为:" << r << endl;
}

我把遍历文件夹的参考链接放在这里:点这个

另外,如有错误欢迎大家指正!

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-09-30 11:56:30  更:2021-09-30 11:57:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 12:35:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码