車牌識別系統 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);
}
}
最近還要忙字元識別啊,歡迎大家交流啊,我也是小菜一個