1. 程式人生 > >車牌識別系統 opencv

車牌識別系統 opencv

這個車牌識別系統(opencv定位,切割+BP神經網路識別字符)我已經在本科畢業的時候順利完成了,相關的文件可以給大家一個連結~祝順。http://wenku.baidu.com/view/0d69765dbed5b9f3f90f1cd5.html 
1.影象預處理
車牌識別系統的第一個步驟為影象預處理。簡單的說,為了方便計算,減少計算量,我們通常將獲取的圖片灰度化。所謂灰度化就是讓圖片每個畫素點在0-255之間。灰度化後圖片可以方便我們操作而且不影響我們對車牌的操作。這裡先說下,我們獲取的照片因為光照的不同,或者車牌本身有汙染等原因,會讓我們的照片存在干擾訊號。將圖片傅立葉變換之後,這些干擾信後又通常為高頻訊號,所以我們需要做一個濾波器,將高頻濾掉。對,就是一個低通。推薦使用均值濾波哈,方便我們後面的幾個操作!

注意一下,這裡的0代表的是強行將圖片轉化為灰度圖,再讀入記憶體。當然,你完全可以用另一個函式:
	cvCvtColor( SRC_PICTURE	, DES_PICTURE	, CV_BGR2GRAY );

       
	img0 = cvLoadImage( names2[i], 0 ); 
	

        if( !img0 )  
        {  
            printf("Couldn't load %s/n", names[i] );  
            continue;  
        } 

        img=cvCreateImage(cvSize(400,300),8,1);  
	imgsource=cvCreateImage(cvSize(400,300),8,1);  
        
	/*目標圖片img*/
        cvResize(img0,img);  


        /*顯示目標檔案img*/
        cvNamedWindow("input",1);  
        cvShowImage("input",img);  

	/*均值濾波*/
        cvSmooth(img,img,CV_MEDIAN);  



到此為止,我們就做好了圖片預處理50%啦!接下來的一個操作是將圖片進行sobel變換。簡單的說,sobel變化是一種邊緣檢測,它可以幫我們檢測出一張圖片的邊緣。而它到底又是怎麼樣的原理呢?我幫大家推薦一篇文章,應該可以解決大家大部分的疑惑!點選開啟連結而sobel在opencv裡面對應的函式為:void cvSobel( const CvArr* src, CvArr* dst, int xorder, int yorder, int aperture_size=3 );根據大量的實驗,觀察,我們發現車牌是縱向紋理的,所以我們可以通過sobel函式將車牌的紋理提取出來!下面附一張效果圖

大家可以清楚地看到,此時,車牌的縱向紋理就被很好的提取出來。現在到了最關鍵的一步,就是二值化。所謂二值化就是在圖片中設定一個閥值T,當某點的畫素小於T時,就將此點畫素置為0,否則置為255;而關鍵卻在於如何尋找合適閥值?
閥值的需找有很多種,我選擇了大律法。因為據說這種演算法十分經典。下面給出C原始碼:
#define GrayScale 256

int otsu(const IplImage *frame) //大津法求閾值
{
 //frame灰度級
int width=frame->width;
int	height=frame->height;
 int pixelCount[GrayScale]={0};
 float pixelPro[GrayScale]={0};
 int i, j, pixelSum = width * height, threshold = 0;
 uchar* data = (uchar*)frame->imageData;
 float w0, w1, u0tmp, u1tmp, u0, u1, deltaTmp, deltaMax = 0;
 //統計每個灰度級中畫素的個數
 for(i = 0; i < height; i++)
 {
  for(j = 0;j < width;j++)
  {
   pixelCount[(int)data[i * width + j]]++;
  }
 }
 //計算每個灰度級的畫素數目佔整幅影象的比例
 for(i = 0; i < GrayScale; i++)
 {
  pixelPro[i] = (float)pixelCount[i] / pixelSum;
 }
 //遍歷灰度級[0,255],尋找合適的threshold
 for(i = 0; i < GrayScale; i++)
 {
  w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = 0;
  for(j = 0; j < GrayScale; j++)
  {
   if(j <= i)   //背景部分
   {
    w0 += pixelPro[j];
    u0tmp += j * pixelPro[j];
   }
   else   //前景部分
   {
    w1 += pixelPro[j];
    u1tmp += j * pixelPro[j];
   }
  }
  u0 = u0tmp / w0;
  u1 = u1tmp / w1;
  deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2)) ;
  if(deltaTmp > deltaMax)
  {
   deltaMax = deltaTmp;
   threshold = i;
  }
 }
 return threshold;
}

閥值找到後,我們可以用opencv的庫函式將圖片閥值話了!原型為:void cvThreshold( const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type );讓我們看一看閥值後的效果:
到此為止,圖片預處理已經完成了,我貼下這部分的原始碼:

	IplImage* imgS=cvCreateImage(cvGetSize(img),IPL_DEPTH_16S,1);  
        IplImage* imgTh=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
        IplImage* temp=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);  
        cvSobel(img,imgS,2,0,3);  
        cvNormalize(imgS,imgTh,255,0,CV_MINMAX);  
        cvNamedWindow("Sobel",1);  
        cvShowImage("Sobel",imgTh);  

	cvReleaseImage(&imgS); 
	cvReleaseImage(&temp); 
		
	/*閥值化*/
	printf("threshold=%d",otsu(imgTh));
        cvThreshold( imgTh, imgTh, otsu(imgTh), 255, CV_THRESH_BINARY );
車牌識別的第二部分為車牌定位,我所用的方法是基於數學形態變化。將上面得到的圖片不斷地進行開操作,閉操作,從而可以讓車牌部分連成一個區域,大家可以看下效

果圖:

大家可以很清楚得看到我們已經得到了車牌明顯的區域。在進行開閉操作的時候有兩點需要注意:

1.Opencv的開閉操作函式不是很好,因為它對計算過後的核沒有進行進步一步操作。(大致意思)所以我查看了幾位牛人的部落格,得到了他們無私奉獻的開源函式。(我是小菜啊,剛大3,接觸cv不到1個月,以前一直玩微控制器的。)現在公佈如下哈:

void lhMorpROpen(const IplImage* src, IplImage* dst, IplConvKernel* se, int iterations)
{
assert(src != NULL  && dst != NULL && src != dst );

IplImage*  temp = cvCreateImage(cvGetSize(src), 8, 1);

cvErode(src, temp, se, iterations);

lhMorpRDilate(temp, src, dst, se, -1);

cvReleaseImage(&temp);

}
void lhMorpRClose(const IplImage* src, IplImage* dst, IplConvKernel* se , int iterations)

{

       assert(src != NULL  && dst != NULL && src != dst );

       IplImage*  temp = cvCreateImage(cvGetSize(src), 8, 1);

       cvDilate(src, temp, se, iterations);

       lhMorpRErode(temp, src, dst, se, -1);

       cvReleaseImage(&temp);

} 

介於篇幅,lhMorpRErode等函式大家可以百度之,或者聯絡我哈~

2.關於開閉操作的核的問題。膨脹,腐蝕都是岩石形態學裡面的東西,我國慶節利用時間看了一下這方面的只是,也問我數學系的哥們,都感覺比較難。它卷積的計算方法應該是自有一套體系吧,懂的大神幫助下我~我再去告訴其他人嘛!我看了幾個論文,最終試出了比較好的核。1*3,中心0,1.在下面的程式碼中大家會看到哈!

接下來就是找到車牌區域了。在opencv裡面有專門的尋找輪廓的函式,簡直是所向披靡。 int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,

int header_size=sizeof(CvContour), int mode=CV_RETR_LIST,int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) );它的返回值是找到的輪廓條數。找到的輪廓被這個函式放在了佇列裡面儲存,(opencv裡面採用佇列儲存)並用一根指標指向了這個佇列。CvSeq** first_contour,就是這根指標哈。具體的函式flag的含義大家google,百度很方便的,我也就不多嘴了。下面給出效果圖和原始碼

cvFindContours( src, storage, &contours, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );  
	
	
     for( ; contours != 0; contours = contours->h_next)  
            {  
				
				
               CvRect aRect = cvBoundingRect( contours, 1 );  //使用邊界框的方式 
		
		int tmparea=aRect.width*aRect.height; 
		float fact=float(float(aRect.width)/float(aRect.height));
		printf("Are0=%d,Width=%d,Hei=%d,fact=%f\n",tmparea,aRect.width,aRect.height,fact);

	if (fact>=3.2  && fact<=8.0&& tmparea>=1000&&tmparea<=9500)  
	{  
					
	cvRectangle(dst,cvPoint(aRect.x,aRect.y),cvPoint(aRect.x+aRect.width ,aRect.y+aRect.height),color,2);
	printf("Are1=%d,Width=%d,Hei=%d,fact=%f\n",tmparea,aRect.width,aRect.height,fact);
					
	CarID_Picture_Rec(aRect);	
	cvNamedWindow("carID",1);  
	cvShowImage("carID",pImg8uROI); 
						
					
						
					
	}
		
  }  


最近還要忙字元識別啊,歡迎大家交流啊,我也是小菜一個