C++編程模板匹配超詳細(xì)的識(shí)別手寫數(shù)字實(shí)現(xiàn)示例
首先,本篇文章用到的方法是模板匹配,而不是基于神經(jīng)網(wǎng)絡(luò)的,還請(qǐng)各位注意了!(模板匹配還請(qǐng)自行了解,站上有很多介紹)我剛開始做實(shí)驗(yàn)的時(shí)候只有一點(diǎn)c++基礎(chǔ),對(duì)于文件和opencv我一點(diǎn)都不了解,所以導(dǎo)致了我剛開始迷茫了很久,直到后來(lái)才漸漸做起來(lái)。廢話不多說(shuō),讓我們開始吧!
過程很簡(jiǎn)單,如下:

匹配成功:存在一個(gè)最小距離(這些距離相等),且為一個(gè)數(shù)字;存在多個(gè)最小距離,且為同一個(gè)數(shù)字。
拒絕識(shí)別:存在多個(gè)最小距離,且為不同數(shù)字。
識(shí)別錯(cuò)誤:存在一個(gè)最小距離,但與被測(cè)數(shù)字不是相同的數(shù)字。
也許乍一看看不明白,我在這里解釋一下,明白的可以繞過。我們這里假設(shè)1,2,3(注意,他們的樣本都有多個(gè))為訓(xùn)練集,d為測(cè)試樣本。匹配時(shí)匹配到d與1距離最小且只與1距離最小,(可能與多個(gè)1的樣本距離最小或者只有一個(gè))那么匹配成功;匹配時(shí)匹配到d與1和2的某個(gè)樣本都有最小距離,那么拒絕匹配;匹配時(shí)匹配到d(假如d是1的樣本)與2有最小距離,那么識(shí)別錯(cuò)誤。
因?yàn)閳D片處理不是本文章的主要內(nèi)容,我們跳過圖像處理步驟(有興趣的可以去看圖像處理這門課),直接給處理好的圖片。那么我們?cè)撊绾螛?gòu)建訓(xùn)練庫(kù),又該如何讓計(jì)算機(jī)能夠識(shí)別我們的圖片呢?接下來(lái)我們來(lái)看看如何實(shí)現(xiàn)構(gòu)建訓(xùn)練庫(kù)。
我的實(shí)驗(yàn)中有1000張訓(xùn)練樣本(200張測(cè)試樣本),既然要讓計(jì)算機(jī)能夠識(shí)別,那當(dāng)然是把圖片數(shù)字化。在圖像處理的步驟里,我們得到的訓(xùn)練樣本都是28*28像素點(diǎn)的圖片,可以想到28*28是一個(gè)不小的數(shù)量,為了提高處理速度,我們把圖片壓縮成7*7大小的,這樣即提高了處理速度,也方便我們寫代碼,因?yàn)?*7和4*4都是正方形。如下圖:

壓縮圖片:我們縱向遍歷7*7的方格,將里面像素大于127的小格子進(jìn)行計(jì)數(shù),當(dāng)其數(shù)量超過6(有的同學(xué)會(huì)覺得應(yīng)該是8,因?yàn)?是一半,但是8最終得到的正確率太低了,所以我找了一個(gè)合適的參數(shù))我們就把大格子對(duì)應(yīng)的7*7的二維數(shù)組的相應(yīng)位置設(shè)置為1,反之為0;然后再將數(shù)組轉(zhuǎn)換成字符串,這樣下來(lái)我們就會(huì)得到一個(gè)長(zhǎng)度為49的字符串,這個(gè)字符串就是我們計(jì)算機(jī)匹配的核心。
另外,我是先把訓(xùn)練集和測(cè)試集分別數(shù)據(jù)化,再依次取出來(lái)作比較。也可以采用一邊遍歷測(cè)試集和處理,一遍作比較,我沒有輸出文件名,因?yàn)槲也捎玫姆绞奖容^笨,代碼量也很多,主要是因?yàn)槲抑皩懲曛笥泻芏郻ug,導(dǎo)致我不能成功運(yùn)行,所以我采用這種簡(jiǎn)單代碼來(lái)避免錯(cuò)誤,小伙伴們大可不必用這種方式!
值得注意的是,文件流的打開和關(guān)閉的時(shí)機(jī)也會(huì)很大程度上影響代碼運(yùn)行,這個(gè)問題困擾了我很久,希望大家引以為戒,代碼中具體位置我已經(jīng)標(biāo)出來(lái)了。(標(biāo)***的位置)另外,大家對(duì)于讀文件寫文件的文件流自己去了解,讀文件是ofstream,寫文件是ifstream,每次訪問文件都要打開文件和關(guān)閉文件。getline函數(shù)每次依次取一行數(shù)據(jù),所以我們?cè)诒闅v完一個(gè)文檔之前不會(huì)關(guān)閉文檔,也就不會(huì)再打開文檔。
最后我對(duì)我字符串比較做一個(gè)解釋,我是采用了一個(gè)標(biāo)志refused來(lái)標(biāo)志當(dāng)前字符串有沒有被拒絕識(shí)別,當(dāng)發(fā)現(xiàn)相似度(代碼中用total表示的)小于49的就把它賦值給相似度,并且把拒絕識(shí)別設(shè)置為假,直到找到最小的,當(dāng)找到最小的之后又找到了另一個(gè)相同相似度的,則判斷兩個(gè)樣本數(shù)字是不是相同的,不是的話就把refused設(shè)置為真,即在后面直接輸出拒絕識(shí)別。
我判斷兩個(gè)樣本是否為同一個(gè)數(shù)字是通過范圍比對(duì),簡(jiǎn)單地來(lái)說(shuō)就是訓(xùn)練樣本的第0——99個(gè)對(duì)應(yīng)測(cè)試樣本的第0——19個(gè),這是一個(gè)偷懶的辦法,我沒時(shí)間改代碼了所以就這樣代替了別人那種文本帶文件名的。(帶文件名比對(duì)時(shí)還需要去文件名)
其他的解釋我放在代碼里,有助于大家更直接的理解!
#include<iostream>
#include<fstream>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/core.hpp>
#include<io.h> //api和結(jié)構(gòu)體
#include<string.h>
#include<string>
using namespace std;
using namespace cv;
void ergodicTest(string filename, string name); //遍歷函數(shù)
string Image_Compression(string imgpath); //壓縮圖片并返回字符串
int distance(string str1, string str2); //對(duì)比函數(shù)不一樣的位數(shù)
void compare();
int turn(char a);
void main()
{
const char* filepath = "E:\\learn\\vsfile\\c++project\\pictureData\\train-images";
ergodicTest(filepath,"train_num.txt"); //處理訓(xùn)練集
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) //遍歷并把路徑存到files
{
string firstfilename = filename + "\\*.bmp";
struct _finddata_t fileinfo;
intptr_t handle; //不能用long,因?yàn)榫葐栴}會(huì)導(dǎo)致訪問沖突,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)
{
do
{
file << fileinfo.name << ":" << 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]; //將digitization轉(zhuǎn)化為字符串類型
for (int row = 0; row < Image.rows; row++) //把圖片的像素點(diǎn)傳給數(shù)組
for (int col = 0; col < Image.cols; col++)
{
Matrix[row][col] = Image.at<uchar>(row, col);
}
string img_str = ""; //用來(lái)存儲(chǔ)結(jié)果字符串
int x = 0, y = 0;
for (int k = 1; k < 50; k++)
{
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;
y = (y + 4) % 28;
if (total >= 6) img_str += '1'; //將28*28的圖片轉(zhuǎn)化為7*7即壓縮
else img_str += '0';
if (k % 7 == 0)
{
x += 4;
y = 0;
}
}
return img_str;
}
int distance(string str1, string str2) //比對(duì)兩個(gè)字符串有多少個(gè)不一樣
{
int counts=0;
for (int i = 0; i < str1.length(); i++)
{
if (str1[i] == str2[i]) continue;
else counts++;
}
return counts;
}
int turn(char a)
{
stringstream str;
int f = 1;
str << a;
str >> f;
str.clear();
return f;
}
void compare()
{
ifstream train_data;//建立讀文件流
ifstream test_data;
string tmp1 = ""; //從train中取數(shù)據(jù)存在tmp1
string tmp11 = "";
string tmp2 = ""; //從test中取
string tmp22 = "";
bool refused = false; //拒絕識(shí)別標(biāo)志
int tr_num = 0; //用來(lái)存儲(chǔ)最小值的文件名(訓(xùn)練集)
int num_refused = 0; //拒絕識(shí)別個(gè)數(shù)
int num_false = 0; //識(shí)別錯(cuò)誤個(gè)數(shù)
int num_true = 0; //正確識(shí)別個(gè)數(shù)
test_data.open("E:\\learn\\vsfile\\c++project\\pictureData\\test_num.txt");
for (int p = 0; p < 200; p++)
{
int total = 49; //方便比大小,設(shè)置初值為49
getline(test_data, tmp2);
tmp22 = tmp2; //在切割字符串之前保留,以便后面知曉該字符串是哪個(gè)數(shù)字的
if(tmp2.length()==57) tmp2.erase(0,8); //erase函數(shù)是用來(lái)切割字符串的,這里是切割第0位的后面8位,存剩余的其他位
else tmp2.erase(0,9);
train_data.open("E:\\learn\\vsfile\\c++project\\pictureData\\train_num.txt");
for (int j = 0; j < 1000; j++) //一個(gè)測(cè)試樣本和所有訓(xùn)練樣本對(duì)比
{
getline(train_data, tmp1);
tmp11 = tmp1;
if (tmp1.length() == 57) tmp1.erase(0, 8);
else tmp1.erase(0, 9);
if (distance(tmp1, tmp2) < total) //取最相近的
{
refused = false; //拒絕識(shí)別被設(shè)置為否,即識(shí)別沒有被拒絕
total = distance(tmp1, tmp2);
tr_num = turn(tmp11[0]); //記錄數(shù)字
}
else if(distance(tmp1, tmp2) == total && tr_num!= turn(tmp11[0])) //發(fā)現(xiàn)相同相似度,且兩者歸屬的數(shù)字不同
{
refused = true; //拒絕識(shí)別
continue; //循環(huán)繼續(xù)
}
}
train_data.close();
if (!refused)
{
if (tr_num == turn(tmp22[0]))
{
//cout << tmp2[0] << endl;
num_true++;
cout << "識(shí)別為:" << tr_num << endl;
}
else
{
num_false++;
cout << "識(shí)別錯(cuò)誤!" << endl;
}
}
else
{
num_refused++;
cout << "拒絕識(shí)別!" << endl;
}
}
test_data.close();
double t = num_true / 200.0, f = num_false / 200.0, r = num_refused / 200.0;
cout << "正確率為:" << t << endl;
cout << "錯(cuò)誤率為:" << f << endl;
cout << "拒絕識(shí)別率為:" << r << endl;
}
代碼中有很多//注釋,都是我調(diào)試代碼用的,不用管。
我把遍歷文件夾的參考鏈接放在這里://www.fzitv.net/article/225454.htm
另外,如有錯(cuò)誤歡迎大家指正!
以上就是C++編程模板匹配超詳細(xì)的識(shí)別手寫數(shù)字實(shí)現(xiàn)示例的詳細(xì)內(nèi)容,更多關(guān)于C++編程模板匹配識(shí)別手寫數(shù)字的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c語(yǔ)言動(dòng)態(tài)內(nèi)存分配知識(shí)點(diǎn)及實(shí)例
在本篇文章里小編給大家整理的是關(guān)于c語(yǔ)言動(dòng)態(tài)內(nèi)存分配知識(shí)點(diǎn)及實(shí)例,需要的朋友們可以學(xué)習(xí)下。2020-03-03
udp socket客戶端和udp服務(wù)端程序示例分享
這篇文章主要介紹了udp socket客戶端和udp服務(wù)端程序示例,需要的朋友可以參考下2014-03-03
C++驅(qū)動(dòng)bash的實(shí)現(xiàn)代碼
這篇文章主要介紹了C++驅(qū)動(dòng)bash的實(shí)現(xiàn)代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11
C++可變參數(shù)的函數(shù)與模板實(shí)例分析
這篇文章主要介紹了C++可變參數(shù)的函數(shù)與模板,非常重要的概念,需要的朋友可以參考下2014-08-08
老生常談C語(yǔ)言靜態(tài)函數(shù)庫(kù)的制作和使用
下面小編就為大家?guī)?lái)一篇老生常談C語(yǔ)言靜態(tài)函數(shù)庫(kù)的制作和使用。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2016-08-08
C++ 實(shí)現(xiàn)求小于n的最大素?cái)?shù)的實(shí)例
這篇文章主要介紹了C++ 實(shí)現(xiàn)求小于n的最大素?cái)?shù)的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05
使用C語(yǔ)言編寫一個(gè)關(guān)機(jī)惡搞小程序
system函數(shù)的參數(shù)是"shutdown"時(shí),它將會(huì)執(zhí)行系統(tǒng)的關(guān)機(jī)命令,所以本文將利用這一特點(diǎn)制作一個(gè)關(guān)機(jī)惡搞小程序,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02
Clion(CMake工具)中引入第三方庫(kù)的詳細(xì)方法
這篇文章主要介紹了Clion(CMake工具)中引入第三方庫(kù)的詳細(xì)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
c++如何將一個(gè)char轉(zhuǎn)化為string
這篇文章主要介紹了c++如何將一個(gè)char轉(zhuǎn)化為string問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08

