OpenCV实现车牌识别,OCR分割,ANN神经网络

来源:互联网 发布:淘宝转化率如何计算 编辑:程序博客网 时间:2024/05/02 06:11

主要步骤:
准备车牌单个字符图像作为神经网络分类器的训练数据,越多越好。当然需要对每幅图像提取特征,这里使用的是水平和垂直累计直方图和缩小后的图像信息。
获取车牌图像,这里的车牌图像已经完成抠图,并且是灰度图像。
将车牌图像中每个字符分割成单一图像(OCR类实现)。
提取分割出的字符图像特征信息,并使用分类识别字符(OCR类实现)。

 程序运行过程:

              

                             原始带有车牌的图片

                         

                           抠图并输入的车牌图片

                             

                      二值化并分割成单个字符图片

       

                                  程序运行结果

代码:

[cpp] view plain copy
  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. //车牌类  
  15. class Plate{  
  16.     public:  
  17.         Plate();  
  18.         Plate(Mat img, Rect pos);  
  19.         string str();  
  20.         Rect position;//当前车牌在大图的位置,为了把识别出的车牌号显示到原图的车牌位置处  
  21.         Mat plateImg;//车牌图像,必须是灰度图像  
  22.         vector<char> chars;  
  23.         vector<Rect> charsPos;          
  24. };  
  25.   
  26. #endif  
[cpp] view plain copy
  1. #include "stdafx.h"  
  2. #include "Plate.h"  
  3.   
  4. Plate::Plate(){  
  5. }  
  6.   
  7. Plate::Plate(Mat img, Rect pos){  
  8.     plateImg=img;  
  9.     position=pos;  
  10. }  
  11. //将车牌号码按照间隔长短拼接成字符串  
  12. string Plate::str(){  
  13.     string result="";  
  14.     //Order numbers  
  15.     vector<int> orderIndex;  
  16.     vector<int> xpositions;  
  17.     for(int i=0; i< charsPos.size(); i++){  
  18.         orderIndex.push_back(i);  
  19.         xpositions.push_back(charsPos[i].x);  
  20.     }  
  21.     float min=xpositions[0];  
  22.     int minIdx=0;  
  23.     for(int i=0; i< xpositions.size(); i++){  
  24.         min=xpositions[i];  
  25.         minIdx=i;  
  26.         for(int j=i; j<xpositions.size(); j++){  
  27.             if(xpositions[j]<min){  
  28.                 min=xpositions[j];  
  29.                 minIdx=j;  
  30.             }  
  31.         }  
  32.         int aux_i=orderIndex[i];  
  33.         int aux_min=orderIndex[minIdx];  
  34.         orderIndex[i]=aux_min;  
  35.         orderIndex[minIdx]=aux_i;  
  36.           
  37.         float aux_xi=xpositions[i];  
  38.         float aux_xmin=xpositions[minIdx];  
  39.         xpositions[i]=aux_xmin;  
  40.         xpositions[minIdx]=aux_xi;  
  41.     }  
  42.     for(int i=0; i<orderIndex.size(); i++){  
  43.         result=result+chars[orderIndex[i]];  
  44.     }  
  45.     return result;  
  46. }  
                                                      车牌类代码
[cpp] view plain copy
  1. #ifndef OCR_h  
  2. #define OCR_h  
  3.   
  4. #include <string.h>  
  5. #include <vector>  
  6.   
  7. #include "Plate.h"  
  8.   
  9. #include <cv.h>  
  10. #include <highgui.h>  
  11. #include <cvaux.h>  
  12. #include <ml.h>  
  13.   
  14. using namespace std;  
  15. using namespace cv;  
  16.   
  17.   
  18. #define HORIZONTAL    1  
  19. #define VERTICAL    0  
  20.   
  21. class CharSegment{  
  22. public:  
  23.     CharSegment();  
  24.     CharSegment(Mat i, Rect p);  
  25.     Mat img;  
  26.     Rect pos;  
  27. };  
  28.   
  29. class OCR{  
  30.     public:  
  31.         bool DEBUG;  
  32.         bool saveSegments;  
  33.         string filename;  
  34.         static const int numCharacters;//字符个数  
  35.         static const char strCharacters[];//字符数组  
  36.         OCR(string trainFile);  
  37.         OCR();  
  38.         string run(Plate *input);//识别车牌  
  39.         int charSize;  
  40.         Mat preprocessChar(Mat in);//将字符图片调整为正方形  
  41.         int classify(Mat f);//根据特征识别出每个字符图片的字符  
  42.         void train(Mat trainData, Mat trainClasses, int nlayers);//训练分类器  
  43.         int classifyKnn(Mat f);//扩展的Knn分类器  
  44.         void trainKnn(Mat trainSamples, Mat trainClasses, int k);  
  45.         Mat features(Mat input, int size);//提取每幅字符图片的特征  
  46.   
  47.     private:  
  48.         bool trained;  
  49.         vector<CharSegment> segment(Plate input);//分割车片图片  
  50.         Mat Preprocess(Mat in, int newSize);//缩放为正方形         
  51.         Mat getVisualHistogram(Mat *hist, int type);//生成视觉直方图  
  52.         void drawVisualFeatures(Mat character, Mat hhist, Mat vhist, Mat lowData);//绘制视觉直方图  
  53.         Mat ProjectedHistogram(Mat img, int t);//计算累计直方图  
  54.         bool verifySizes(Mat r);//判断字符图像大小是否合适  
  55.         CvANN_MLP  ann;//神经网络分类器  
  56.         CvKNearest knnClassifier;//扩展的k邻域分类器  
  57.         int K;  
  58. };  
  59.   
  60. #endif  
[cpp] view plain copy
  1. #include "stdafx.h"  
  2. #include "OCR.h"  
  3.   
  4. const char OCR::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'};  
  5. const int OCR::numCharacters=30;  
  6.   
  7. CharSegment::CharSegment(){}  
  8. CharSegment::CharSegment(Mat i, Rect p){  
  9.     img=i;  
  10.     pos=p;  
  11. }  
  12.   
  13. OCR::OCR(){  
  14.     DEBUG=false;  
  15.     trained=false;  
  16.     saveSegments=false;  
  17.     charSize=20;  
  18. }  
  19. OCR::OCR(string trainFile){  
  20.     DEBUG=false;  
  21.     trained=false;  
  22.     saveSegments=false;  
  23.     charSize=20;  
  24.   
  25.     //Read file storage.  
  26.     FileStorage fs;  
  27.     fs.open("OCR.xml", FileStorage::READ);  
  28.     Mat TrainingData;  
  29.     Mat Classes;  
  30.     fs["TrainingDataF15"] >> TrainingData;  
  31.     fs["classes"] >> Classes;  
  32.   
  33.     train(TrainingData, Classes, 10);  
  34.   
  35. }  
  36. //将单个字符图像变成正方形  
  37. Mat OCR::preprocessChar(Mat in){  
  38.     int h=in.rows;  
  39.     int w=in.cols;  
  40.     Mat transformMat=Mat::eye(2,3,CV_32F);//缩放矩阵  
  41.     int m=max(w,h);  
  42.     transformMat.at<float>(0,2)=m/2 - w/2;  
  43.     transformMat.at<float>(1,2)=m/2 - h/2;  
  44.   
  45.     Mat warpImage(m,m, in.type());  
  46.     warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0) );  
  47.   
  48.     Mat out;  
  49.     resize(warpImage, out, Size(charSize, charSize) );   
  50.   
  51.     return out;  
  52. }  
  53.   
  54. //判断字符图像长宽是否符合要求  
  55. bool OCR::verifySizes(Mat r){  
  56.     //Char sizes 45x77  
  57.     float aspect=45.0f/77.0f;  
  58.     float charAspect= (float)r.cols/(float)r.rows;  
  59.     float error=0.35;  
  60.     float minHeight=15;  
  61.     float maxHeight=28;  
  62.     //We have a different aspect ratio for number 1, and it can be ~0.2  
  63.     float minAspect=0.2;  
  64.     float maxAspect=aspect+aspect*error;  
  65.     //area of pixels  
  66.     float area=countNonZero(r);  
  67.     //bb area  
  68.     float bbArea=r.cols*r.rows;  
  69.     //% of pixel in area  
  70.     float percPixels=area/bbArea;  
  71.   
  72.     if(DEBUG)  
  73.         cout << "Aspect: "<< aspect << " ["<< minAspect << "," << maxAspect << "] "  << "Area "<< percPixels <<" Char aspect " << charAspect  << " Height char "<< r.rows << "\n";  
  74.     if(percPixels < 0.8 && charAspect > minAspect && charAspect < maxAspect && r.rows >= minHeight && r.rows < maxHeight)  
  75.         return true;  
  76.     else  
  77.         return false;  
  78.   
  79. }  
  80.   
  81. //将车牌图像进一步分割成单个字符图片  
  82. vector<CharSegment> OCR::segment(Plate plate){  
  83.     Mat input=plate.plateImg;  
  84.     vector<CharSegment> output;  
  85.     //将输入图像二值化  
  86.     Mat img_threshold;  
  87.     threshold(input, img_threshold, 60, 255, CV_THRESH_BINARY_INV);  
  88.     if(DEBUG)  
  89.         imshow("Threshold plate", img_threshold);  
  90.     Mat img_contours;  
  91.     img_threshold.copyTo(img_contours);  
  92.     //查找字符轮廓  
  93.     vector< vector< Point> > contours;  
  94.     findContours(img_contours,  
  95.             contours, // 轮廓  
  96.             CV_RETR_EXTERNAL, //去除内环  
  97.             CV_CHAIN_APPROX_NONE); // 轮廓所有像素  
  98.       
  99.     //将轮廓绘制到车牌图  
  100.     cv::Mat result;  
  101.     img_threshold.copyTo(result);  
  102.     cvtColor(result, result, CV_GRAY2RGB);  
  103.     cv::drawContours(result,contours,-1,cv::Scalar(255,0,0),1);   
  104.   
  105.     vector<vector<Point> >::iterator itc= contours.begin();  
  106.       
  107.     //筛选符合条件的闭环     
  108.     while (itc!=contours.end()) {  
  109.           
  110.         //创建一个包围矩形  
  111.         Rect mr= boundingRect(Mat(*itc));  
  112.         rectangle(result, mr, Scalar(0,255,0));  
  113.           
  114.         Mat auxRoi(img_threshold, mr);  
  115.         if(verifySizes(auxRoi)){//判断长宽是否满足  
  116.             auxRoi=preprocessChar(auxRoi);//缩放成正方形  
  117.             output.push_back(CharSegment(auxRoi, mr));//保存字符图像及位置  
  118.             rectangle(result, mr, Scalar(0,125,255));  
  119.         }  
  120.         ++itc;  
  121.     }  
  122.     if(DEBUG)  
  123.         cout << "Num chars: " << output.size() << "\n";  
  124.     if(DEBUG)  
  125.         imshow("SEgmented Chars", result);  
  126.     return output;  
  127. }  
  128. //计算累计直方图,统计每列或行的非0像素个数  
  129. Mat OCR::ProjectedHistogram(Mat img,int t)  
  130. {  
  131.     int sz=(t)?img.rows:img.cols;  
  132.     Mat mhist=Mat::zeros(1,sz,CV_32F);  
  133.   
  134.     for(int j=0; j<sz; j++){  
  135.         Mat data=(t)?img.row(j):img.col(j);  
  136.         mhist.at<float>(j)=countNonZero(data);//  
  137.     }  
  138.   
  139.     //直方图归1化  
  140.     double min, max;  
  141.     minMaxLoc(mhist, &min, &max);  
  142.       
  143.     if(max>0)  
  144.         mhist.convertTo(mhist,-1 , 1.0f/max, 0);  
  145.   
  146.     return mhist;  
  147. }  
  148.   
  149. //得到直方图图像  
  150. Mat OCR::getVisualHistogram(Mat *hist, int type)  
  151. {  
  152.   
  153.     int size=100;  
  154.     Mat imHist;  
  155.   
  156.   
  157.     if(type==HORIZONTAL){  
  158.         imHist.create(Size(size,hist->cols), CV_8UC3);  
  159.     }else{  
  160.         imHist.create(Size(hist->cols, size), CV_8UC3);  
  161.     }  
  162.   
  163.     imHist=Scalar(55,55,55);  
  164.   
  165.     for(int i=0;i<hist->cols;i++){  
  166.         float value=hist->at<float>(i);  
  167.         int maxval=(int)(value*size);  
  168.   
  169.         Point pt1;  
  170.         Point pt2, pt3, pt4;  
  171.   
  172.         if(type==HORIZONTAL){  
  173.             pt1.x=pt3.x=0;  
  174.             pt2.x=pt4.x=maxval;  
  175.             pt1.y=pt2.y=i;  
  176.             pt3.y=pt4.y=i+1;  
  177.   
  178.             line(imHist, pt1, pt2, CV_RGB(220,220,220),1,8,0);  
  179.             line(imHist, pt3, pt4, CV_RGB(34,34,34),1,8,0);  
  180.   
  181.             pt3.y=pt4.y=i+2;  
  182.             line(imHist, pt3, pt4, CV_RGB(44,44,44),1,8,0);  
  183.             pt3.y=pt4.y=i+3;  
  184.             line(imHist, pt3, pt4, CV_RGB(50,50,50),1,8,0);  
  185.         }else{  
  186.   
  187.                         pt1.x=pt2.x=i;  
  188.                         pt3.x=pt4.x=i+1;  
  189.                         pt1.y=pt3.y=100;  
  190.                         pt2.y=pt4.y=100-maxval;  
  191.   
  192.   
  193.             line(imHist, pt1, pt2, CV_RGB(220,220,220),1,8,0);  
  194.             line(imHist, pt3, pt4, CV_RGB(34,34,34),1,8,0);  
  195.   
  196.             pt3.x=pt4.x=i+2;  
  197.             line(imHist, pt3, pt4, CV_RGB(44,44,44),1,8,0);  
  198.             pt3.x=pt4.x=i+3;  
  199.             line(imHist, pt3, pt4, CV_RGB(50,50,50),1,8,0);  
  200.         }  
  201.     }  
  202.     return imHist ;  
  203. }  
  204.   
  205. void OCR::drawVisualFeatures(Mat character, Mat hhist, Mat vhist, Mat lowData){  
  206.     Mat img(121, 121, CV_8UC3, Scalar(0,0,0));  
  207.     Mat ch;  
  208.     Mat ld;  
  209.       
  210.     cvtColor(character, ch, CV_GRAY2RGB);  
  211.   
  212.     resize(lowData, ld, Size(100, 100), 0, 0, INTER_NEAREST );  
  213.     cvtColor(ld,ld,CV_GRAY2RGB);  
  214.   
  215.     Mat hh=getVisualHistogram(&hhist, HORIZONTAL);  
  216.     Mat hv=getVisualHistogram(&vhist, VERTICAL);  
  217.   
  218.     Mat subImg=img(Rect(0,101,20,20));  
  219.     ch.copyTo(subImg);  
  220.   
  221.     subImg=img(Rect(21,101,100,20));  
  222.     hh.copyTo(subImg);  
  223.   
  224.     subImg=img(Rect(0,0,20,100));  
  225.     hv.copyTo(subImg);  
  226.   
  227.     subImg=img(Rect(21,0,100,100));  
  228.     ld.copyTo(subImg);  
  229.   
  230.     line(img, Point(0,100), Point(121,100), Scalar(0,0,255));  
  231.     line(img, Point(20,0), Point(20,121), Scalar(0,0,255));  
  232.   
  233.     imshow("Visual Features", img);  
  234.   
  235.     cvWaitKey(0);  
  236. }  
  237.   
  238. Mat OCR::features(Mat in, int sizeData){  
  239.     //分别获取垂直和水平直方图信息  
  240.     Mat vhist=ProjectedHistogram(in,VERTICAL);  
  241.     Mat hhist=ProjectedHistogram(in,HORIZONTAL);  
  242.       
  243.     //低分辨率图像  
  244.     Mat lowData;  
  245.     resize(in, lowData, Size(sizeData, sizeData) );//15x15  
  246.   
  247.     if(DEBUG)  
  248.         drawVisualFeatures(in, hhist, vhist, lowData);  
  249.       
  250.     //整合低分辨路图像信息和直方图统计信息,  
  251.     int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;  
  252.       
  253.     Mat out=Mat::zeros(1,numCols,CV_32F);  
  254.     //保存特征信息  
  255.     int j=0;  
  256.     for(int i=0; i<vhist.cols; i++)  
  257.     {  
  258.         out.at<float>(j)=vhist.at<float>(i);  
  259.         j++;  
  260.     }  
  261.     for(int i=0; i<hhist.cols; i++)  
  262.     {  
  263.         out.at<float>(j)=hhist.at<float>(i);  
  264.         j++;  
  265.     }  
  266.     for(int x=0; x<lowData.cols; x++)  
  267.     {  
  268.         for(int y=0; y<lowData.rows; y++){  
  269.             out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);  
  270.             j++;  
  271.         }  
  272.     }  
  273.     if(DEBUG)  
  274.         cout << out << "\n===========================================\n";  
  275.     return out;  
  276. }  
  277.   
  278. //训练        //训练样本数据//每条数据对应的字母下标//深度  
  279. void OCR::train(Mat TrainData, Mat classes, int nlayers){  
  280.     Mat layers(1,3,CV_32SC1);  
  281.     layers.at<int>(0)= TrainData.cols;//每个样本宽度  
  282.     layers.at<int>(1)= nlayers;//深度  
  283.     layers.at<int>(2)= numCharacters;//结果个数  
  284.     ann.create(layers, CvANN_MLP::SIGMOID_SYM, 1, 1);  
  285.   
  286.     Mat trainClasses;  
  287.     trainClasses.create( TrainData.rows, numCharacters, CV_32FC1 );//每一条样本都对应着numCharacters个可能结果,但是只有一个结果是正确的,  
  288.     forint i = 0; i <  trainClasses.rows; i++ )  
  289.     {  
  290.         forint k = 0; k < trainClasses.cols; k++ )  
  291.         {  
  292.             //将该条训练数据对应的字符下标位置赋值为1,其他赋值为0  
  293.             if( k == classes.at<int>(i) )  
  294.                 trainClasses.at<float>(i,k) = 1;  
  295.             else  
  296.                 trainClasses.at<float>(i,k) = 0;  
  297.         }  
  298.     }  
  299.     Mat weights( 1, TrainData.rows, CV_32FC1, Scalar::all(1) );  
  300.     //开始训练学习  
  301.     ann.train( TrainData, trainClasses, weights );  
  302.     trained=true;  
  303. }  
  304. //识别字符  
  305. int OCR::classify(Mat f){  
  306.     int result=-1;  
  307.     Mat output(1, numCharacters, CV_32FC1);  
  308.     ann.predict(f, output);  
  309.     Point maxLoc;  
  310.     double maxVal;  
  311.     minMaxLoc(output, 0, &maxVal, 0, &maxLoc);//求最大值以及下标位置,这里没有打印出来最大值  
  312.     return maxLoc.x;  
  313. }  
  314.   
  315. int OCR::classifyKnn(Mat f){  
  316.     int response = (int)knnClassifier.find_nearest( f, K );  
  317.     return response;  
  318. }  
  319. void OCR::trainKnn(Mat trainSamples, Mat trainClasses, int k){  
  320.     K=k;  
  321.     // learn classifier  
  322.     knnClassifier.train( trainSamples, trainClasses, Mat(), false, K );  
  323. }  
  324.   
  325. string OCR::run(Plate *input){  
  326.       
  327.     //分割车牌中每个字符  
  328.     vector<CharSegment> segments=segment(*input);  
  329.   
  330.     for(int i=0; i<segments.size(); i++){  
  331.         //统一所有字符图像大小  
  332.         Mat ch=preprocessChar(segments[i].img);  
  333.         if(saveSegments){  
  334.             stringstream ss(stringstream::in | stringstream::out);  
  335.             ss << "tmpChars/" << filename << "_" << i << ".jpg";  
  336.             imwrite(ss.str(),ch);  
  337.         }  
  338.         //提取每个字符图像特征  
  339.         Mat f=features(ch,15);  
  340.         //For each segment feature Classify  
  341.         int character=classify(f);  
  342.         input->chars.push_back(strCharacters[character]);  
  343.         input->charsPos.push_back(segments[i].pos);  
  344.     }  
  345.     return "-";//input->str();  
  346. }  
                                                                    OCR类代码
[cpp] view plain copy
  1. #include "stdafx.h"  
  2. #include <cv.h>  
  3. #include <highgui.h>  
  4. #include <cvaux.h>  
  5. #include <ml.h>  
  6.   
  7. #include <iostream>  
  8. #include <vector>  
  9.   
  10. #include "DetectRegions.h"  
  11. #include "OCR.h"  
  12.   
  13. using namespace std;  
  14. using namespace cv;  
  15.   
  16. string getFilename(string s) {  
  17.   
  18.     char sep = '/';  
  19.     char sepExt = '.';  
  20.   
  21. #ifdef _WIN32  
  22.     sep = '\\';  
  23. #endif  
  24.   
  25.     size_t i = s.rfind(sep, s.length());  
  26.     if (i != string::npos) {  
  27.         string fn = (s.substr(i + 1, s.length() - i));  
  28.         size_t j = fn.rfind(sepExt, fn.length());  
  29.         if (i != string::npos) {  
  30.             return fn.substr(0, j);  
  31.         }  
  32.         else{  
  33.             return fn;  
  34.         }  
  35.     }  
  36.     else{  
  37.         return "";  
  38.     }  
  39. }  
  40.   
  41. int main(int argc, char** argv)  
  42. {  
  43.     char* filename;  
  44.     Mat input_image;//必须为灰度图像  
  45.   
  46.     //有输入图片才继续  
  47.     if (argc >= 2)  
  48.     {  
  49.         filename = argv[1];  
  50.         input_image = imread(filename, 1);  
  51.     }  
  52.     else{  
  53.         printf("Use:\n\t%s image\n", argv[0]);  
  54.         return 0;  
  55.     }  
  56.   
  57.     string filename_whithoutExt = getFilename(filename);//得到去除后缀部分  
  58.     OCR ocr("OCR.xml");//参数为保存了自己训练数据的xml文件  
  59.     ocr.saveSegments = true;  
  60.     ocr.DEBUG = true;  
  61.     ocr.filename = filename_whithoutExt;  
  62.     Plate plate;  
  63.     plate.plateImg = input_image;  
  64.     plate.position = Rect(50, 100, input_image.cols, input_image.rows);//车牌是从大图中抠图出来的,这里说明车牌的位置和大小  
  65.     imwrite("plateImg.jpg", plate.plateImg);  
  66.     string plateNumber = ocr.run(&plate);  
  67.     string licensePlate = plate.str();  
  68.     cout << "================================================\n";  
  69.     cout << "License plate number: " << licensePlate << "\n";  
  70.     cout << "================================================\n";  
  71.     rectangle(input_image, plate.position, Scalar(0, 0, 200));  
  72.     putText(input_image, licensePlate, Point(plate.position.x, plate.position.y), CV_FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 200), 2);  
  73.     if (false){  
  74.         imshow("Plate Detected seg", plate.plateImg);  
  75.         cvWaitKey(0);  
  76.     }  
  77.   
  78.     imshow("Plate Detected", input_image);  
  79.     for (;;)  
  80.     {  
  81.         int c;  
  82.         c = cvWaitKey(10);  
  83.         if ((char)c == 27)  
  84.             break;  
  85.     }  
  86.     return 0;  
  87. }  
                                                                      main函数代码

有关ANN神经网络分类器的原理及训练请参考我的另一篇文章:http://blog.csdn.NET/xukaiwen_2016/article/details/53293465

最后:例子中没有实现对中文的识别,其实原理都是一样的,大家可以自己寻找中文车牌的图片进行分类器训练即可,代码几乎不用修改。



原文地址:http://blog.csdn.net/xukaiwen_2016/article/details/53525988

0 0
原创粉丝点击