《Mastering Opencv ...讀書筆記系列》車牌識別(II)

繼上一篇文章後,現在要做的就是從車牌圖像上使用optical character recognition算法將字符提取出來。對於每一塊被檢測的車牌,使用帶監督的神經網絡機器學習算法來識別字符。

本文內容:

1.字符分割 

2.神經網絡訓練方法

3.使用神經網絡預測字符


一、字符分割【OCR Segment】

在使用神經網絡對每個字符進行預測之前,我們必須從車牌圖像中扣取改字符圖片,因此有如下步驟:

本文的輸入圖像爲上一篇文章的車牌:


a.二值化車牌


b.求輪廓


c.求最小外接矩形


d.用縱橫比及面積,篩選外接矩形


e.調整統一矩形大小並保存每個字符的圖片【注意:分割得到順序和車牌字符順序無關,可能不同】



代碼:

  1. // car_plate_ann.cpp : 定義控制檯應用程序的入口點。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <cv.h>  
  6. #include <highgui.h>  
  7. #include <cvaux.h>  
  8. #include <ml.h>  
  9. #define HORIZONTAL    1  
  10. #define VERTICAL    0  
  11. using namespace std;  
  12. using namespace cv;  
  13.   
  14. //typedef struct CharSegment{  
  15. //  Mat img;  
  16. //  Rect mr;  
  17. //  CharSegment(Mat a,Rect b){  
  18. //      img=a;  
  19. //      mr=b;  
  20. //  }  
  21. //};  
  22.   
  23. bool verifySizes(Mat r){  
  24.     //Char sizes 45x77  
  25.     float aspect=45.0f/77.0f;  
  26.     float charAspect= (float)r.cols/(float)r.rows;  
  27.     float error=0.35;  
  28.     float minHeight=15;  
  29.     float maxHeight=28;  
  30.     //We have a different aspect ratio for number 1, and it can be ~0.2  
  31.     float minAspect=0.2;  
  32.     float maxAspect=aspect+aspect*error;  
  33.     //area of pixels  
  34.     float area=countNonZero(r);  
  35.     //bb area  
  36.     float bbArea=r.cols*r.rows;  
  37.     //% of pixel in area  
  38.     float percPixels=area/bbArea;  
  39.   
  40.     /*if(DEBUG) 
  41.         cout << "Aspect: "<< aspect << " ["<< minAspect << "," << maxAspect << "] "  << "Area "<< percPixels <<" Char aspect " << charAspect  << " Height char "<< r.rows << "\n";*/  
  42.     if(percPixels < 0.8 && charAspect > minAspect && charAspect < maxAspect && r.rows >= minHeight && r.rows < maxHeight)  
  43.         return true;  
  44.     else  
  45.         return false;  
  46.   
  47. }  
  48.   
  49. Mat preprocessChar(Mat in){  
  50.     //Remap image  
  51.     int h=in.rows;  
  52.     int w=in.cols;  
  53.     int charSize=20;    //統一每個字符的大小  
  54.     Mat transformMat=Mat::eye(2,3,CV_32F);  
  55.     int m=max(w,h);  
  56.     transformMat.at<float>(0,2)=m/2 - w/2;  
  57.     transformMat.at<float>(1,2)=m/2 - h/2;  
  58.   
  59.     Mat warpImage(m,m, in.type());  
  60.     warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0) );  
  61.   
  62.     Mat out;  
  63.     resize(warpImage, out, Size(charSize, charSize) );   
  64.   
  65.     return out;  
  66. }  
  67.   
  68. //create the accumulation histograms,img is a binary image, t is 水平或垂直  
  69. Mat ProjectedHistogram(Mat img, int t)  
  70. {  
  71.     int sz=(t)?img.rows:img.cols;  
  72.     Mat mhist=Mat::zeros(1,sz,CV_32F);  
  73.   
  74.     for(int j=0; j<sz; j++){  
  75.         Mat data=(t)?img.row(j):img.col(j);  
  76.         mhist.at<float>(j)=countNonZero(data);    //統計這一行或一列中,非零元素的個數,並保存到mhist中  
  77.     }  
  78.   
  79.     //Normalize histogram  
  80.     double min, max;  
  81.     minMaxLoc(mhist, &min, &max);  
  82.   
  83.     if(max>0)  
  84.         mhist.convertTo(mhist,-1 , 1.0f/max, 0);//用mhist直方圖中的最大值,歸一化直方圖  
  85.   
  86.     return mhist;  
  87. }  
  88.   
  89. Mat getVisualHistogram(Mat *hist, int type)  
  90. {  
  91.   
  92.     int size=100;  
  93.     Mat imHist;  
  94.   
  95.   
  96.     if(type==HORIZONTAL){  
  97.         imHist.create(Size(size,hist->cols), CV_8UC3);  
  98.     }else{  
  99.         imHist.create(Size(hist->cols, size), CV_8UC3);  
  100.     }  
  101.   
  102.     imHist=Scalar(55,55,55);  
  103.   
  104.     for(int i=0;i<hist->cols;i++){  
  105.         float value=hist->at<float>(i);  
  106.         int maxval=(int)(value*size);  
  107.   
  108.         Point pt1;  
  109.         Point pt2, pt3, pt4;  
  110.   
  111.         if(type==HORIZONTAL){  
  112.             pt1.x=pt3.x=0;  
  113.             pt2.x=pt4.x=maxval;  
  114.             pt1.y=pt2.y=i;  
  115.             pt3.y=pt4.y=i+1;  
  116.   
  117.             line(imHist, pt1, pt2, CV_RGB(220,220,220),1,8,0);  
  118.             line(imHist, pt3, pt4, CV_RGB(34,34,34),1,8,0);  
  119.   
  120.             pt3.y=pt4.y=i+2;  
  121.             line(imHist, pt3, pt4, CV_RGB(44,44,44),1,8,0);  
  122.             pt3.y=pt4.y=i+3;  
  123.             line(imHist, pt3, pt4, CV_RGB(50,50,50),1,8,0);  
  124.         }else{  
  125.   
  126.             pt1.x=pt2.x=i;  
  127.             pt3.x=pt4.x=i+1;  
  128.             pt1.y=pt3.y=100;  
  129.             pt2.y=pt4.y=100-maxval;  
  130.   
  131.   
  132.             line(imHist, pt1, pt2, CV_RGB(220,220,220),1,8,0);  
  133.             line(imHist, pt3, pt4, CV_RGB(34,34,34),1,8,0);  
  134.   
  135.             pt3.x=pt4.x=i+2;  
  136.             line(imHist, pt3, pt4, CV_RGB(44,44,44),1,8,0);  
  137.             pt3.x=pt4.x=i+3;  
  138.             line(imHist, pt3, pt4, CV_RGB(50,50,50),1,8,0);  
  139.   
  140.         }  
  141.   
  142.     }  
  143.   
  144.     return imHist ;  
  145. }  
  146.   
  147. void drawVisualFeatures(Mat character, Mat hhist, Mat vhist, Mat lowData,int count){  
  148.     Mat img(121, 121, CV_8UC3, Scalar(0,0,0));  
  149.     Mat ch;  
  150.     Mat ld;  
  151.     char res[20];  
  152.   
  153.     cvtColor(character, ch, CV_GRAY2RGB);  
  154.   
  155.     resize(lowData, ld, Size(100, 100), 0, 0, INTER_NEAREST );//將ld從15*15擴大到100*100  
  156.     cvtColor(ld,ld,CV_GRAY2RGB);  
  157.   
  158.     Mat hh=getVisualHistogram(&hhist, HORIZONTAL);  
  159.     Mat hv=getVisualHistogram(&vhist, VERTICAL);  
  160.   
  161.     //Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height)  
  162.     Mat subImg=img(Rect(0,101,20,20));//ch:20*20  
  163.     ch.copyTo(subImg);  
  164.   
  165.     subImg=img(Rect(21,101,100,20));//hh:100*hist.cols  
  166.     hh.copyTo(subImg);  
  167.   
  168.     subImg=img(Rect(0,0,20,100));//hv:hist.cols*100  
  169.     hv.copyTo(subImg);  
  170.   
  171.     subImg=img(Rect(21,0,100,100));//ld:100*100  
  172.     ld.copyTo(subImg);  
  173.   
  174.     line(img, Point(0,100), Point(121,100), Scalar(0,0,255));  
  175.     line(img, Point(20,0), Point(20,121), Scalar(0,0,255));  
  176.   
  177.     sprintf(res,"hist%d.jpg",count);  
  178.     imwrite(res,img);  
  179.     //imshow("Visual Features", img);  
  180.   
  181.     cvWaitKey(0);  
  182. }  
  183.   
  184.   
  185. Mat features(Mat in, int sizeData,int count){  
  186.     //Histogram features  
  187.     Mat vhist=ProjectedHistogram(in,VERTICAL);  
  188.     Mat hhist=ProjectedHistogram(in,HORIZONTAL);  
  189.   
  190.     //Low data feature  
  191.     Mat lowData;  
  192.     resize(in, lowData, Size(sizeData, sizeData) );  
  193.   
  194.     //畫出直方圖  
  195.     drawVisualFeatures(in, hhist, vhist, lowData,count);  
  196.   
  197.   
  198.   
  199.     //Last 10 is the number of moments components  
  200.     int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;  
  201.   
  202.     Mat out=Mat::zeros(1,numCols,CV_32F);  
  203.     //Asign values to feature,ANN的樣本特徵爲水平、垂直直方圖和低分辨率圖像所組成的矢量  
  204.     int j=0;  
  205.     for(int i=0; i<vhist.cols; i++)  
  206.     {  
  207.         out.at<float>(j)=vhist.at<float>(i);  
  208.         j++;  
  209.     }  
  210.     for(int i=0; i<hhist.cols; i++)  
  211.     {  
  212.         out.at<float>(j)=hhist.at<float>(i);  
  213.         j++;  
  214.     }  
  215.     for(int x=0; x<lowData.cols; x++)  
  216.     {  
  217.         for(int y=0; y<lowData.rows; y++){  
  218.             out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);  
  219.             j++;  
  220.         }  
  221.     }  
  222.     //if(DEBUG)  
  223.     //  cout << out << "\n===========================================\n";  
  224.     return out;  
  225. }  
  226.   
  227. int _tmain(int argc, _TCHAR* argv[])  
  228. {  
  229.     Mat input = imread("haha_1.jpg",CV_LOAD_IMAGE_GRAYSCALE);  
  230.     char res[20];  
  231.     int i = 0;  
  232.     //vector<CharSegment> output;  
  233.   
  234.     //Threshold input image  
  235.     Mat img_threshold;  
  236.     threshold(input, img_threshold, 60, 255, CV_THRESH_BINARY_INV);  
  237.   
  238.     Mat img_contours;  
  239.     img_threshold.copyTo(img_contours);  
  240.     //Find contours of possibles characters  
  241.     vector< vector< Point> > contours;  
  242.     findContours(img_contours,  
  243.         contours, // a vector of contours  
  244.         CV_RETR_EXTERNAL, // retrieve the external contours  
  245.         CV_CHAIN_APPROX_NONE); // all pixels of each contours  
  246.   
  247.     // Draw blue contours on a white image  
  248.     cv::Mat result;  
  249.     input.copyTo(result);  
  250.     cvtColor(result, result, CV_GRAY2RGB);  
  251.     //cv::drawContours(result,contours,  
  252.     //  -1, // draw all contours  
  253.     //  cv::Scalar(0,0,255), // in blue  
  254.     //  1); // with a thickness of 1  
  255.       
  256.     //Start to iterate to each contour founded  
  257.     vector<vector<Point> >::iterator itc= contours.begin();  
  258.   
  259.     //Remove patch that are no inside limits of aspect ratio and area.      
  260.     while (itc!=contours.end()) {  
  261.   
  262.         //Create bounding rect of object  
  263.         Rect mr= boundingRect(Mat(*itc));  
  264.         //rectangle(result, mr, Scalar(255,0,0),2);  
  265.         //Crop image  
  266.         Mat auxRoi(img_threshold, mr);  
  267.         if(verifySizes(auxRoi)){  
  268.             auxRoi=preprocessChar(auxRoi);  
  269.             //output.push_back(CharSegment(auxRoi, mr));  
  270.               
  271.             //保存每個字符圖片  
  272.             sprintf(res,"train_data_%d.jpg",i);  
  273.             i++;  
  274.             imwrite(res,auxRoi);  
  275.             rectangle(result, mr, Scalar(0,0,255),2);  
  276.   
  277.   
  278.             //對每一個小方塊,提取直方圖特徵  
  279.             Mat f=features(auxRoi,15,i);  
  280.         }  
  281.         ++itc;  
  282.     }  
  283.   
  284.     imwrite("result1.jpg",result);  
  285.     imshow("car_plate",result);  
  286.     waitKey(0);  
  287.     return 0;  
  288. }  

圖片顯示可以自己邊註釋邊顯示,另外提前給出了第二部分的累計水平垂直直方圖和低分辨率採樣的方法,並把這種特徵的圖像保存下來了。


二、神經網絡訓練

1.多層感知機簡介:

多層感知機結構:【隱層數量爲1層或多層,實際上自從引入了深度學習後,纔有多層】


其中,每個神經元結構如下:


每個神經元都是相似的且每個神經元都有自己的判定邊界,有多個輸入和多個輸出。不同權重的輸入結合激勵函數得到不同的輸出。常見的激勵函數有S型、高斯型、上圖的hadrlim型。單層的單個神經元可以將輸入向量分爲兩類,而一個有S個神經元的感知機,可以將輸入向量分爲2^S類


2.獲取訓練數據

和上一篇訓練SVM所使用的特徵不同,現在使用每個字符的累計直方圖和低分辨率採樣圖像構成的高維向量作爲訓練神經網絡的特徵。訓練的樣本矩陣P爲N*M,其中N(行)代表各個樣本圖片的融合特徵,M(列)爲類別。從書中給的已經訓練好的orc.xml看,N有675行,M有30列,30列代表西班牙車牌有30種字符0-9和20個英文字母組成,675是這麼來的,比如字符0有35張圖片樣本,對應產生35行高維向量,字符1有40張樣本圖片,對應產生40行高維向量,然後按照不同分辨率5*5、10*10、15*15、20*20採樣【書中ocr.xml只有675,只採用5*5分辨率】。矩陣P實際上是對每一種高維向量的類別標註:


  在Opencv中使用多層感知機需要配置training data矩陣、classes矩陣、隱層神經元數量。其中,訓練數據矩陣和列別標識矩陣均從ocr.xml文件獲取【下文會介紹】,這裏只採用單隱層,包含10個神經元,輸入層爲675行,輸出層爲30行。

  計算ocr.xml文件具體步驟:

a.將上一步分割得到的每個字符進行人工分類【可放在不同目錄下】,比如最終字符0有35張圖片,字符a有30張圖片並定義數組【這些數字之和爲675】:

  1. const int numFilesChars[]={35, 40, 42, 41, 42, 33, 30, 31, 49, 44, 30, 24, 21, 20, 34, 9, 10, 3, 11, 3, 15, 4, 9, 12, 10, 21, 18, 8, 15, 7};  
b.讀取某個字符目錄下的一張圖片,提取累計直方圖特徵和不同低分辨率圖像,具體如下:
(1)統計水平、垂直方向直方圖,比如水平直方圖,掃描圖像每一行,統計每行非零元素的個數,這樣就構成1*row矩陣,然後用該矩陣的最大值,規範化該矩陣。
(2)使用resize函數,按照不同分辨率得到圖像矩陣
(3)將垂直直方圖,水平直方圖,低分辨率圖像【按行讀取】,都存進一個1*(vcol+hcol+h*w)的矩陣
(4)將上述矩陣連同標記一起寫入xml文件中
累計直方圖及低分辨率圖像效果如下:【左下角爲字符圖像原始大小20*20,由上角爲低分辨率採樣後放大的圖像100*100,右下角爲水平直方圖,左上角爲垂直直方圖】


具體訓練代碼爲:

  1. // Main entry code OpenCV  
  2.   
  3. #include <cv.h>  
  4. #include <highgui.h>  
  5. #include <cvaux.h>  
  6.   
  7. #include <iostream>  
  8. #include <vector>  
  9. #define HORIZONTAL    1  
  10. #define VERTICAL    0  
  11. using namespace std;  
  12. using namespace cv;  
  13.   
  14. //西班牙車牌共30種字符,下面爲每個字符的圖片個數【沒給,需人工挑選】  
  15. const int numFilesChars[]={35, 40, 42, 41, 42, 33, 30, 31, 49, 44, 30, 24, 21, 20, 34, 9, 10, 3, 11, 3, 15, 4, 9, 12, 10, 21, 18, 8, 15, 7};  
  16. const char strCharacters[] = {'0','1','2','3','4','5','6','7','8','9','B''C''D''F''G''H''J''K''L''M''N''P''R''S''T''V''W''X''Y''Z'};  
  17. const int numCharacters=30;  
  18.   
  19.   
  20. Mat features(Mat in, int sizeData,int count){  
  21.     //Histogram features  
  22.     Mat vhist=ProjectedHistogram(in,VERTICAL);  
  23.     Mat hhist=ProjectedHistogram(in,HORIZONTAL);  
  24.   
  25.     //Low data feature  
  26.     Mat lowData;  
  27.     resize(in, lowData, Size(sizeData, sizeData) );  
  28.   
  29.     //Last 10 is the number of moments components  
  30.     int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;  
  31.   
  32.     Mat out=Mat::zeros(1,numCols,CV_32F);  
  33.     //Asign values to feature,ANN的樣本特徵爲水平、垂直直方圖和低分辨率圖像所組成的矢量  
  34.     int j=0;  
  35.     for(int i=0; i<vhist.cols; i++)  
  36.     {  
  37.         out.at<float>(j)=vhist.at<float>(i);  
  38.         j++;  
  39.     }  
  40.     for(int i=0; i<hhist.cols; i++)  
  41.     {  
  42.         out.at<float>(j)=hhist.at<float>(i);  
  43.         j++;  
  44.     }  
  45.     for(int x=0; x<lowData.cols; x++)  
  46.     {  
  47.         for(int y=0; y<lowData.rows; y++){  
  48.             out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);  
  49.             j++;  
  50.         }  
  51.     }  
  52.     //if(DEBUG)  
  53.     //  cout << out << "\n===========================================\n";  
  54.     return out;  
  55. }  
  56.   
  57. int main ( int argc, char** argv )  
  58. {  
  59.     cout << "OpenCV Training OCR Automatic Number Plate Recognition\n";  
  60.     cout << "\n";  
  61.   
  62.     char* path;  
  63.       
  64.     //Check if user specify image to process  
  65.     if(argc >= 1 )  
  66.     {  
  67.         path= argv[1];  
  68.       
  69.     }else{  
  70.         cout << "Usage:\n" << argv[0] << " <path to chars folders files> \n";  
  71.         return 0;  
  72.     }          
  73.   
  74.   
  75.   
  76.   
  77.   
  78.   
  79.     Mat classes;  
  80.     Mat trainingDataf5;  
  81.     Mat trainingDataf10;  
  82.     Mat trainingDataf15;  
  83.     Mat trainingDataf20;  
  84.   
  85.     vector<int> trainingLabels;  
  86.     OCR ocr;  
  87.   
  88.     for(int i=0; i< numCharacters; i++)  
  89.     {  
  90.         int numFiles=numFilesChars[i];  
  91.         for(int j=0; j< numFiles; j++){  
  92.             cout << "Character "<< strCharacters[i] << " file: " << j << "\n";  
  93.             stringstream ss(stringstream::in | stringstream::out);  
  94.             ss << path << strCharacters[i] << "/" << j << ".jpg";  
  95.             Mat img=imread(ss.str(), 0);  
  96.             Mat f5=features(img, 5);  
  97.             Mat f10=features(img, 10);  
  98.             Mat f15=features(img, 15);  
  99.             Mat f20=features(img, 20);  
  100.   
  101.             trainingDataf5.push_back(f5);  
  102.             trainingDataf10.push_back(f10);  
  103.             trainingDataf15.push_back(f15);  
  104.             trainingDataf20.push_back(f20);  
  105.             trainingLabels.push_back(i);            //每一幅字符圖片所對應的字符類別索引下標  
  106.         }  
  107.     }  
  108.   
  109.       
  110.     trainingDataf5.convertTo(trainingDataf5, CV_32FC1);  
  111.     trainingDataf10.convertTo(trainingDataf10, CV_32FC1);  
  112.     trainingDataf15.convertTo(trainingDataf15, CV_32FC1);  
  113.     trainingDataf20.convertTo(trainingDataf20, CV_32FC1);  
  114.     Mat(trainingLabels).copyTo(classes);  
  115.   
  116.     FileStorage fs("OCR.xml", FileStorage::WRITE);  
  117.     fs << "TrainingDataF5" << trainingDataf5;  
  118.     fs << "TrainingDataF10" << trainingDataf10;  
  119.     fs << "TrainingDataF15" << trainingDataf15;  
  120.     fs << "TrainingDataF20" << trainingDataf20;  
  121.     fs << "classes" << classes;  
  122.     fs.release();  
  123.   
  124.     return 0;  
  125. }  

三、使用神經網絡檢測字符
a.讀取一張車牌圖像
b.配置神經網絡參數,並使用xml文件訓練神經網絡【參數配置上述已經說過了】
c.提取該車牌圖像的累計直方圖和低分辨率圖像特徵矩陣
d.將該特徵矩陣作爲神經網絡輸入,經網絡計算,得到預測結果【字符索引】
e.按照每個字符圖像的相對位置,進行字符重新排序
f.得到最終字符【和書中不同的是,我的輸入圖像是車牌而不是整幅圖像,因此絕對座標是不同的,但字符間的相對位置還是對的,只是不能在車牌照片上顯示數字而已,我直接答應到控制檯上】


具體代碼:

又用到了車牌類,這裏面有車牌字符相對位置調整的函數,都給出來吧:

Plate.h:

  1. #ifndef Plate_h  
  2. #define Plate_h  
  3.   
  4. #include <string.h>  
  5. #include <vector>  
  6.   
  7. #include <cv.h>  
  8. #include <highgui.h>  
  9. #include <cvaux.h>  
  10.   
  11. using namespace std;  
  12. using namespace cv;  
  13.   
  14. class Plate{  
  15.     public:  
  16.         Plate();  
  17.         Plate(Mat img, Rect pos);  
  18.         string str();  
  19.         Rect position;  
  20.         Mat plateImg;  
  21.         vector<char> chars;  
  22.         vector<Rect> charsPos;          
  23. };  
  24.   
  25. #endif  

Plate.cpp:

  1. /***************************************************************************** 
  2. *   Number Plate Recognition using SVM and Neural Networks 
  3. ****************************************************************************** 
  4. *   by David Mill醤 Escriv? 5th Dec 2012 
  5. *   http://blog.damiles.com 
  6. ****************************************************************************** 
  7. *   Ch5 of the book "Mastering OpenCV with Practical Computer Vision Projects" 
  8. *   Copyright Packt Publishing 2012. 
  9. *   http://www.packtpub.com/cool-projects-with-opencv/book 
  10. *****************************************************************************/  
  11.   
  12. #include "Plate.h"  
  13.   
  14. Plate::Plate(){  
  15. }  
  16.   
  17. Plate::Plate(Mat img, Rect pos){  
  18.     plateImg=img;  
  19.     position=pos;  
  20. }  
  21.   
  22. string Plate::str(){  
  23.     string result="";  
  24.     //Order numbers  
  25.     vector<int> orderIndex;  
  26.     vector<int> xpositions;  
  27.     for(int i=0; i< charsPos.size(); i++){  
  28.         orderIndex.push_back(i);  
  29.         xpositions.push_back(charsPos[i].x);  
  30.     }  
  31.     float min=xpositions[0];  
  32.     int minIdx=0;  
  33.     for(int i=0; i< xpositions.size(); i++){  
  34.         min=xpositions[i];  
  35.         minIdx=i;  
  36.         for(int j=i; j<xpositions.size(); j++){  
  37.             if(xpositions[j]<min){  
  38.                 min=xpositions[j];  
  39.                 minIdx=j;  
  40.             }  
  41.         }  
  42.         int aux_i=orderIndex[i];  
  43.         int aux_min=orderIndex[minIdx];  
  44.         orderIndex[i]=aux_min;  
  45.         orderIndex[minIdx]=aux_i;  
  46.           
  47.         float aux_xi=xpositions[i];  
  48.         float aux_xmin=xpositions[minIdx];  
  49.         xpositions[i]=aux_xmin;  
  50.         xpositions[minIdx]=aux_xi;  
  51.     }  
  52.     for(int i=0; i<orderIndex.size(); i++){  
  53.         result=result+chars[orderIndex[i]];  
  54.     }  
  55.     return result;  
  56. }  

主要處理的函數:

  1. // car_plate_classify.cpp : 定義控制檯應用程序的入口點。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <cv.h>  
  6. #include <highgui.h>  
  7. #include <cvaux.h>  
  8. #include <ml.h>  
  9.   
  10. #include <iostream>  
  11. #include <vector>  
  12. #include "Plate.h"  
  13. #define HORIZONTAL    1  
  14. #define VERTICAL    0  
  15. using namespace std;  
  16. using namespace cv;  
  17.   
  18. CvANN_MLP  ann;  
  19. const char strCharacters[] = {'0','1','2','3','4','5','6','7','8','9','B''C''D''F''G''H''J''K''L''M''N''P''R''S''T''V''W''X''Y''Z'};  
  20. const int numCharacters=30;  
  21.   
  22. bool verifySizes(Mat r){  
  23.     //Char sizes 45x77  
  24.     float aspect=45.0f/77.0f;  
  25.     float charAspect= (float)r.cols/(float)r.rows;  
  26.     float error=0.35;  
  27.     float minHeight=15;  
  28.     float maxHeight=28;  
  29.     //We have a different aspect ratio for number 1, and it can be ~0.2  
  30.     float minAspect=0.2;  
  31.     float maxAspect=aspect+aspect*error;  
  32.     //area of pixels  
  33.     float area=countNonZero(r);  
  34.     //bb area  
  35.     float bbArea=r.cols*r.rows;  
  36.     //% of pixel in area  
  37.     float percPixels=area/bbArea;  
  38.   
  39.     /*if(DEBUG) 
  40.     cout << "Aspect: "<< aspect << " ["<< minAspect << "," << maxAspect << "] "  << "Area "<< percPixels <<" Char aspect " << charAspect  << " Height char "<< r.rows << "\n";*/  
  41.     if(percPixels < 0.8 && charAspect > minAspect && charAspect < maxAspect && r.rows >= minHeight && r.rows < maxHeight)  
  42.         return true;  
  43.     else  
  44.         return false;  
  45.   
  46. }  
  47.   
  48. Mat preprocessChar(Mat in){  
  49.     //Remap image  
  50.     int h=in.rows;  
  51.     int w=in.cols;  
  52.     int charSize=20;    //統一每個字符的大小  
  53.     Mat transformMat=Mat::eye(2,3,CV_32F);  
  54.     int m=max(w,h);  
  55.     transformMat.at<float>(0,2)=m/2 - w/2;  
  56.     transformMat.at<float>(1,2)=m/2 - h/2;  
  57.   
  58.     Mat warpImage(m,m, in.type());  
  59.     warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0) );  
  60.   
  61.     Mat out;  
  62.     resize(warpImage, out, Size(charSize, charSize) );   
  63.   
  64.     return out;  
  65. }  
  66.   
  67. //create the accumulation histograms,img is a binary image, t is 水平或垂直  
  68. Mat ProjectedHistogram(Mat img, int t)  
  69. {  
  70.     int sz=(t)?img.rows:img.cols;  
  71.     Mat mhist=Mat::zeros(1,sz,CV_32F);  
  72.   
  73.     for(int j=0; j<sz; j++){  
  74.         Mat data=(t)?img.row(j):img.col(j);  
  75.         mhist.at<float>(j)=countNonZero(data);    //統計這一行或一列中,非零元素的個數,並保存到mhist中  
  76.     }  
  77.   
  78.     //Normalize histogram  
  79.     double min, max;  
  80.     minMaxLoc(mhist, &min, &max);  
  81.   
  82.     if(max>0)  
  83.         mhist.convertTo(mhist,-1 , 1.0f/max, 0);//用mhist直方圖中的最大值,歸一化直方圖  
  84.   
  85.     return mhist;  
  86. }  
  87.   
  88. Mat features(Mat in, int sizeData){  
  89.     //Histogram features  
  90.     Mat vhist=ProjectedHistogram(in,VERTICAL);  
  91.     Mat hhist=ProjectedHistogram(in,HORIZONTAL);  
  92.   
  93.     //Low data feature  
  94.     Mat lowData;  
  95.     resize(in, lowData, Size(sizeData, sizeData) );  
  96.   
  97.     //Last 10 is the number of moments components  
  98.     int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;  
  99.   
  100.     Mat out=Mat::zeros(1,numCols,CV_32F);  
  101.     //Asign values to feature,ANN的樣本特徵爲水平、垂直直方圖和低分辨率圖像所組成的矢量  
  102.     int j=0;  
  103.     for(int i=0; i<vhist.cols; i++)  
  104.     {  
  105.         out.at<float>(j)=vhist.at<float>(i);  
  106.         j++;  
  107.     }  
  108.     for(int i=0; i<hhist.cols; i++)  
  109.     {  
  110.         out.at<float>(j)=hhist.at<float>(i);  
  111.         j++;  
  112.     }  
  113.     for(int x=0; x<lowData.cols; x++)  
  114.     {  
  115.         for(int y=0; y<lowData.rows; y++){  
  116.             out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);  
  117.             j++;  
  118.         }  
  119.     }  
  120.       
  121.     return out;  
  122. }  
  123.   
  124.   
  125. int classify(Mat f){  
  126.     int result=-1;  
  127.     Mat output(1, 30, CV_32FC1); //西班牙車牌只有30種字符  
  128.     ann.predict(f, output);  
  129.     Point maxLoc;  
  130.     double maxVal;  
  131.     minMaxLoc(output, 0, &maxVal, 0, &maxLoc);  
  132.     //We need know where in output is the max val, the x (cols) is the class.  
  133.   
  134.     return maxLoc.x;  
  135. }  
  136.   
  137. void train(Mat TrainData, Mat classes, int nlayers){  
  138.     Mat layers(1,3,CV_32SC1);  
  139.     layers.at<int>(0)= TrainData.cols;  
  140.     layers.at<int>(1)= nlayers;  
  141.     layers.at<int>(2)= 30;  
  142.     ann.create(layers, CvANN_MLP::SIGMOID_SYM, 1, 1);  
  143.   
  144.     //Prepare trainClases  
  145.     //Create a mat with n trained data by m classes  
  146.     Mat trainClasses;  
  147.     trainClasses.create( TrainData.rows, 30, CV_32FC1 );  
  148.     forint i = 0; i <  trainClasses.rows; i++ )  
  149.     {  
  150.         forint k = 0; k < trainClasses.cols; k++ )  
  151.         {  
  152.             //If class of data i is same than a k class  
  153.             if( k == classes.at<int>(i) )  
  154.                 trainClasses.at<float>(i,k) = 1;  
  155.             else  
  156.                 trainClasses.at<float>(i,k) = 0;  
  157.         }  
  158.     }  
  159.     Mat weights( 1, TrainData.rows, CV_32FC1, Scalar::all(1) );  
  160.   
  161.     //Learn classifier  
  162.     ann.train( TrainData, trainClasses, weights );  
  163. }  
  164.   
  165. int _tmain(int argc, _TCHAR* argv[])  
  166. {  
  167.     Mat input = imread("test.jpg",CV_LOAD_IMAGE_GRAYSCALE);  
  168.     Plate mplate;  
  169.     //Read file storage.  
  170.     FileStorage fs;  
  171.     fs.open("OCR.xml", FileStorage::READ);  
  172.     Mat TrainingData;  
  173.     Mat Classes;  
  174.     fs["TrainingDataF15"] >> TrainingData;  
  175.     fs["classes"] >> Classes;  
  176.     //訓練神經網絡  
  177.     train(TrainingData, Classes, 10);  
  178.   
  179. //dealing image and save each character image into vector<CharSegment>  
  180.     //Threshold input image  
  181.     Mat img_threshold;  
  182.     threshold(input, img_threshold, 60, 255, CV_THRESH_BINARY_INV);  
  183.   
  184.     Mat img_contours;  
  185.     img_threshold.copyTo(img_contours);  
  186.     //Find contours of possibles characters  
  187.     vector< vector< Point> > contours;  
  188.     findContours(img_contours,  
  189.         contours, // a vector of contours  
  190.         CV_RETR_EXTERNAL, // retrieve the external contours  
  191.         CV_CHAIN_APPROX_NONE); // all pixels of each contours  
  192.     //Start to iterate to each contour founded  
  193.     vector<vector<Point> >::iterator itc= contours.begin();  
  194.   
  195.     //Remove patch that are no inside limits of aspect ratio and area.      
  196.     while (itc!=contours.end()) {  
  197.   
  198.         //Create bounding rect of object  
  199.         Rect mr= boundingRect(Mat(*itc));  
  200.         //rectangle(result, mr, Scalar(255,0,0),2);  
  201.         //Crop image  
  202.         Mat auxRoi(img_threshold, mr);  
  203.         if(verifySizes(auxRoi)){  
  204.             auxRoi=preprocessChar(auxRoi);  
  205.   
  206.             //對每一個小方塊,提取直方圖特徵  
  207.             Mat f=features(auxRoi,15);  
  208.             //For each segment feature Classify  
  209.             int character=classify(f);  
  210.             mplate.chars.push_back(strCharacters[character]);  
  211.             mplate.charsPos.push_back(mr);  
  212.             //printf("%c ",strCharacters[character]);  
  213.         }  
  214.         ++itc;  
  215.     }  
  216.     string licensePlate=mplate.str();  
  217.     cout<<licensePlate<<endl;  
  218.       
  219.     return 0;  
  220. }  


這邊運行時間略長,大概10s以下吧。這就是android不能做太多圖像處理的原因,運行速度不給力啊。

上上後面做的評估是對隱層神經元數量和不同分辨率的一種統計,沒多大花頭,以後要用再看吧。而且車牌識別已經做爛了,沒什麼動力了~~

好吧,下一篇嘗試將車牌檢測與識別都移植到android上試試。


源自:http://blog.csdn.net/jinshengtao/article/details/17954427

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章