C++編程模板匹配超詳細的識別手寫數字實現示例
首先,本篇文章用到的方法是模板匹配,而不是基於神經網絡的,還請各位註意瞭!(模板匹配還請自行瞭解,站上有很多介紹)我剛開始做實驗的時候隻有一點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); //遍歷函數 string Image_Compression(string imgpath); //壓縮圖片並返回字符串 int distance(string str1, string str2); //對比函數不一樣的位數 void compare(); int turn(char a); 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) //遍歷並把路徑存到files { string firstfilename = filename + "\\*.bmp"; struct _finddata_t fileinfo; 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) { 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轉化為字符串類型 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; 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的圖片轉化為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; } 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中取數據存在tmp1 string tmp11 = ""; string tmp2 = ""; //從test中取 string tmp22 = ""; 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); tmp22 = tmp2; //在切割字符串之前保留,以便後面知曉該字符串是哪個數字的 if(tmp2.length()==57) tmp2.erase(0,8); //erase函數是用來切割字符串的,這裡是切割第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++) //一個測試樣本和所有訓練樣本對比 { 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; //拒絕識別被設置為否,即識別沒有被拒絕 total = distance(tmp1, tmp2); tr_num = turn(tmp11[0]); //記錄數字 } else if(distance(tmp1, tmp2) == total && tr_num!= turn(tmp11[0])) //發現相同相似度,且兩者歸屬的數字不同 { refused = true; //拒絕識別 continue; //循環繼續 } } train_data.close(); if (!refused) { if (tr_num == turn(tmp22[0])) { //cout << tmp2[0] << endl; num_true++; cout << "識別為:" << tr_num << endl; } else { num_false++; cout << "識別錯誤!" << endl; } } else { num_refused++; cout << "拒絕識別!" << 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; }
代碼中有很多//註釋,都是我調試代碼用的,不用管。
我把遍歷文件夾的參考鏈接放在這裡:點這個
另外,如有錯誤歡迎大傢指正!
以上就是C++編程模板匹配超詳細的識別手寫數字實現示例的詳細內容,更多關於C++編程模板匹配識別手寫數字的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- C++學習貝葉斯分類器實現手寫數字識別示例解析
- VisualStudio2019配置OpenCV的詳細過程
- C++中map 字典的基本使用教程
- C++ STL中五個常用算法使用教程及實例講解
- C++中最常用的容器用法與排序實例