數字識別和其餘的全部計算機視覺相關的應用都會分爲兩個步驟:ROI抽取和識別。html
1. ROI抽取即將感興趣的區域從原始圖像中分離初來,這個步驟包括二值化,噪點的消除等
2. 識別即經過一些分類器將第一步中的結果進行分類,事實上屬於機器學習的一個典型應用ios
數字識別步驟:數組
1.先處理圖像:機器學習
轉換爲灰度值(灰度圖較之原始圖片,將三個維度的矩陣變成了一個維度)函數
轉換爲二值圖(二值圖即將灰度圖轉換成黑白圖,每一個點只有兩種可能:非黑即白)學習
Mat srcImage = imread("number.png"); Mat dstImage, grayImage, Image; cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); threshold(grayImage, Image, 48, 255, CV_THRESH_BINARY_INV);
PS:48即爲閾值,若是灰度高於48,那麼該點會被認爲是255,不然爲0。spa
2.檢測並勾勒輪廓:
輪廓檢測將二值圖中的可連通的區域用一坨點表示,默認的輪廓檢查會返回一個點的序列,使這個序列構成一個圖形將該連通區域的全部點包圍起來,好比四個點構成一個矩形。code
特例:因爲8這個數字中有兩個圓圈,默認的輪廓檢查會將這兩個圓圈都檢測到,8就會有三個輪廓,一樣還可能出現這種狀況的還有數字4,6,9。htm
所以須要指定findContours
()函數僅搜索最外層的輪廓,而不關注內部可能出現的任何輪廓。blog
vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(Image,contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); drawContours(dstImage, contours, -1, (255,255,255) );
檢測完輪廓後,使用contours迭代器遍歷每個輪廓,找到並畫出包圍這個輪廓的最小矩陣。
vector<vector<Point>>::iterator It; for(It = contours.begin();It < contours.end();It++){ //畫出可包圍數字的最小矩形 Point2f vertex[4]; RotatedRect rect = minAreaRect(*It); rect.points(vertex); for( int j = 0; j < 4; j++) line(dstImage,vertex[j], vertex[ (j+1)%4 ],Scalar(0,0,255),1); }
可是,上述方法畫出的矩形爲旋轉矩形(不必定水平) ,因此不採用這種方法。應使用boundingRect()畫出矩形。
vector<vector<Point>>::iterator It; for(It = contours.begin();It < contours.end();It++){ //畫出可包圍數字的最小矩形 Point2f vertex[4]; Rect rect = boundingRect(*It); vertex[0] = rect.tl(); //矩陣左上角的點 vertex[1].x = (float)rect.tl().x, vertex[1].y = (float)rect.br().y; //矩陣左下方的點 vertex[2] = rect.br(); //矩陣右下角的點 vertex[3].x = (float)rect.br().x, vertex[3].y = (float)rect.tl().y; //矩陣右上方的點 for( int j = 0; j < 4; j++) line(dstImage,vertex[j], vertex[ (j+1)%4 ],Scalar(0,0,255),1);
畫出圖像以下圖
3.數字順序整理:
因爲輪廓檢測時,不必定按照圖中所給順序進行檢測,因此在檢測輪廓時,要記錄所給數字的座標,根據x,y座標進行排序。
因爲用上述方法在同一行畫出的矩形位於同一水平面,所以直接比較其某一點座標便可。對此,我寫出以下結構體:
struct con{ double x,y; //輪廓位置 int order; //輪廓向量contours中的第幾個 bool operator<(con &m){ if(y > m.y) return false; else if( y == m.y){ if(x < m.x) return true; else return false; } else return true; } }con[100];
我按輪廓檢測順序的將矩陣的中心點存入結構體中,而後調用sort()函數。
con[i].x = (vertex[0].x+vertex[1].x+vertex[2].x+vertex[3].x) / 4.0; //根據中心點判斷圖像的位置 con[i].y = (vertex[0].y+vertex[1].y+vertex[2].y+vertex[3].y) / 4.0; //cout << i <<":"<< endl; //cout << vertex[3].x<<" "<< vertex[3].y<<endl; con[i].order = i;
可是用這種方法上圖中的數字」4「一直在最前面,改了很久也沒有結果,就先着手下一步。
PS: 最後發現了問題,以下:
sort(con,con+i); //正確 sort(con,con+i+1); //錯誤
4.切割各個數字:
使用ROI進行切割,關於ROI詳見 http://www.cnblogs.com/farewell-farewell/p/5905107.html
我在此處寫的ROI法分隔圖片的方法以下,可是存在內存訪問上的問題。
IplImage* num[10]; for(int j = 0; j < i; j++){ int k = con[i].order; IplImage* src = cvLoadImage("number.jpg"); cvSetImageROI(src,rect[k]); num[j] = cvCreateImage(cvSize(rect[k].width,rect[k].height),IPL_DEPTH_8U,2); cvCopy(src,num[j]); cvResetImageROI(src); }
最後換另外一種方法,更簡單,將其分割
Mat num[10]; for(int j = 0; j < i; j++){ cout << "s "<<j<<endl; int k = con[j].order; cout << "k "<<k<<endl; srcImage(rect[k]).copyTo(num[j]); }
分割後的數字按順序存放在num[10]圖像數組中。
5.最後的識別
將按輪廓線切割好的數字放於程序文件中,而後採用逐點像素遍歷的方法來進行對比
//兩圖象逐像素對比的函數 double compare(Mat &src, Mat &sample) { double same = 0.0, difPoint = 0.0; Mat now; resize(sample,now,src.size()); int row = now.rows; int col = now.cols * now.channels(); for(int i = 0; i < 1; i++){ uchar * data1 = src.ptr<uchar>(i); uchar * data2 = now.ptr<uchar>(i); for(int j = 0; j < row * col; j++){ int a = data1[j]; int b = data2[j]; if( a == b)same++; else difPoint++; } } return same/(same+difPoint) ; }
//選取符合程度最高的數字 void deal(Mat &src,int order) { sample = imread("0.jpg"); Threshold(src,sample,0); sample = imread("1.jpg"); Threshold(src,sample,1); sample = imread("2.jpg"); Threshold(src,sample,2); sample = imread("3.jpg"); Threshold(src,sample,3); sample = imread("4.jpg"); Threshold(src,sample,4); sample = imread("5.jpg"); Threshold(src,sample,5); sample = imread("6.jpg"); Threshold(src,sample,6); sample = imread("7.jpg"); Threshold(src,sample,7); sample = imread("8.jpg"); Threshold(src,sample,8); sample = imread("9.jpg"); Threshold(src,sample,9); sort(result,result+10); if(result[9].bi > 0.6) { cout << "第" << order << "個數字爲 "<< result[9]. num<<endl; cout << "識別精度爲 " << result[9].bi <<endl; } else cout << "第" << order << "個數字沒法識別"<<endl; }
void Threshold(Mat &src,Mat &sample ,int m) { cvtColor(sample, sample, COLOR_BGR2GRAY); threshold(sample, sample, 48, 255, CV_THRESH_BINARY_INV); result[m].bi = compare(src,sample); result[m].num = m; } }con[15]; struct result{ double bi; int num; bool operator<(result &m){ if(bi < m.bi)return true; else return false; } }result[15];
大功告成~
完整的代碼:
#include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace cv; using namespace std; struct con{ double x,y; //輪廓位置 int order; //輪廓向量contours中的第幾個 bool operator<(con &m){ if(y > m.y) return false; else if( y == m.y){ if(x < m.x) return true; else return false; } else return true; } }con[15]; struct result{ double bi; int num; bool operator<(result &m){ if(bi < m.bi)return true; else return false; } }result[15]; Mat num[15]; Mat sample; void deal(Mat &src,int order); double compare(Mat &src, Mat &sample); void Threshold(Mat &src,Mat &sample,int m); int main( ) { Mat srcImage = imread("number.png"); Mat dstImage, grayImage, Image; srcImage.copyTo(dstImage); cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); threshold(grayImage, Image, 48, 255, CV_THRESH_BINARY_INV); //定義輪廓和層次結構 vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(Image,contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); int i = 0; Point2f pp[5][4]; vector<vector<Point>>::iterator It; Rect rect[10]; for(It = contours.begin();It < contours.end();It++){ //畫出可包圍數字的最小矩形 Point2f vertex[4]; rect[i] = boundingRect(*It); vertex[0] = rect[i].tl(); //矩陣左上角的點 vertex[1].x = (float)rect[i].tl().x, vertex[1].y = (float)rect[i].br().y; //矩陣左下方的點 vertex[2] = rect[i].br(); //矩陣右下角的點 vertex[3].x = (float)rect[i].br().x, vertex[3].y = (float)rect[i].tl().y; //矩陣右上方的點 for( int j = 0; j < 4; j++) line(dstImage,vertex[j], vertex[ (j+1)%4 ],Scalar(0,0,255),1); con[i].x = (vertex[0].x+vertex[1].x+vertex[2].x+vertex[3].x) / 4.0; //根據中心點判斷圖像的位置 con[i].y = (vertex[0].y+vertex[1].y+vertex[2].y+vertex[3].y) / 4.0; con[i].order = i; i++; } sort(con,con+i); for(int j = 0; j < i; j++){ int k = con[j].order; srcImage(rect[k]).copyTo(num[j]); cvtColor(num[j], num[j], COLOR_BGR2GRAY); threshold(num[j], num[j], 48, 255, CV_THRESH_BINARY_INV); deal(num[j],j+1); } system("pause"); return 0; } void Threshold(Mat &src,Mat &sample ,int m) { cvtColor(sample, sample, COLOR_BGR2GRAY); threshold(sample, sample, 48, 255, CV_THRESH_BINARY_INV); result[m].bi = compare(src,sample); result[m].num = m; } void deal(Mat &src,int order) { sample = imread("0.jpg"); Threshold(src,sample,0); sample = imread("1.jpg"); Threshold(src,sample,1); sample = imread("2.jpg"); Threshold(src,sample,2); sample = imread("3.jpg"); Threshold(src,sample,3); sample = imread("4.jpg"); Threshold(src,sample,4); sample = imread("5.jpg"); Threshold(src,sample,5); sample = imread("6.jpg"); Threshold(src,sample,6); sample = imread("7.jpg"); Threshold(src,sample,7); sample = imread("8.jpg"); Threshold(src,sample,8); sample = imread("9.jpg"); Threshold(src,sample,9); sort(result,result+10); if(result[9].bi > 0.6) { cout << "第" << order << "個數字爲 "<< result[9]. num<<endl; cout << "識別精度爲 " << result[9].bi <<endl; } else cout << "第" << order << "個數字沒法識別"<<endl; } double compare(Mat &src, Mat &sample) { double same = 0.0, difPoint = 0.0; Mat now; resize(sample,now,src.size()); int row = now.rows; int col = now.cols * now.channels(); for(int i = 0; i < 1; i++){ uchar * data1 = src.ptr<uchar>(i); uchar * data2 = now.ptr<uchar>(i); for(int j = 0; j < row * col; j++){ int a = data1[j]; int b = data2[j]; if( a == b)same++; else difPoint++; } } return same/(same+difPoint) ; }