在我國汽車工業迅猛發展的今天,汽車保有量的逐年大幅增加,對傳統的車 輛管理和交通運行方式面臨著巨大的壓力和挑戰。為了解決上述問題,車輛牌照自動識別技術(AVI)應運而生,并發展成為現代智能交通系統中的重要組成部分之一。它以計算機視覺處理、數字圖像處理、模式識別等技術為基礎,對攝像機所 拍攝的車輛圖像或者視頻圖像進行處理分析,得到每輛車的車牌號碼,從而完成識別過程。通過一些后續處理技術其可以實現停車場出入口收費管理、盜搶車輛管理、高速公路超速自動化管理、闖紅燈電子警察、公路收費管理等等功能。對于維護交通安全和城市治安,防止交通堵塞,實現交通全自動化管理有著現實的意義。
設圖像在像素點(x,y)處的灰度值為f(x,y),考慮以像素點(x,y)為中心的(2w+1)*(2w+1)窗口(w為窗口寬度),則局部自適應二值化算法可以描述如下:
a.計算圖像中各點(x,y)的閾值w(x,y)
W(x,y)=0.5*(max f(x+m,y+n)+min f(x+m,y+n))
b.如果f(x,y)>w(x,y),則二值化結果為1,代表字符區域的目標點;否則二值化結果為0,代表背景區域的目標點。
?
Canny邊緣檢測算子的方向性質保證了很好的邊緣強度估計,而且能同時產生邊緣梯度方向和強度兩個信息,即能在一定程度上抗噪聲又能保持弱邊緣,因此采用以canny算子做邊緣檢測。
任何邊緣檢測算法都不可能在未經處理的原始數據上很好地處理,所以第一步是對原始數據與高斯 mask 作卷積,得到的圖像與原始圖像相比有些輕微的模糊(blurred)。這樣,單獨的一個像素雜訊在經過高斯平滑的圖像上變得幾乎沒有影響。
(2)用一階偏導的有限差分來計算梯度的幅值和方向。
(3)對梯度幅值進行非極大值抑制。
僅僅得到全局的梯度并不足以確定邊緣,因此為確定邊緣,必須保留局部梯度最大的點,而抑制非極大值。
非極大值抑制產生的二值灰度矩陣的潛在點中按照高閾值尋找邊緣,并以所找到的點為中心尋找鄰域內滿足低閾值的點,從而形成一個閉合的輪廓。然后對于不滿足條件的點直接刪除掉。
1.在一個不大的區域內密集包含有多個字符;
2.車牌字符與車牌底色形成強烈對比;
3.車牌區域大小相對固定,區域長度和寬度成固定比例。
根據以上特征,車牌區域所在行相鄰像素之間0 到1和1到0 的的變化會很頻繁,變化總數會大于一個臨界值,這可以作為尋找車牌區域的一個依據。 因此根據跳變次數與設定的閾值比較,就可以確定出車牌的水平區域。
由于車牌一般懸掛在車輛下部,所以采用從上到下,從左到右的方式對圖像進行掃描。車牌的字符部分由7個字符數與兩個豎直邊框組成,則車牌區域內任一行的跳變次數至少為(7+2)*2=18次。從圖像的底部開始向頂部進行掃描,則第一組連續數行且每行的跳變次數都大于跳變閾值,同時滿足連續行數大于某個閾值。
在車牌的水平區域中,最高行與最低行的差值即為車牌在圖像中的高度。我國車牌區域為矩形,寬高比約為3.14,取3.14*H作為車牌的寬度。在水平區域內選擇任意一行,用L長的窗口由左至右移動,統計窗口中相鄰像素0,1的跳變次數并存入數組中。若窗口移動到車牌的垂直區域時,窗口內的跳變次數應該最大。因此在數組中找到最大值,其對應的區域即為車牌的垂直區域。
?
基于BP神經網絡的字符識別
車牌字符識別是字符識別的重要組成部分。車牌字符識別的最終目的就是將圖像中的車牌字符轉化成文本字符,車牌字符的識別屬于印刷體識別范疇。
字符識別的基本思想是匹配判別。抽取待識別字符特征按照字符識別的原理和預先存儲在計算機中的標準字符模式表達形式的集合逐一進行匹配,找出最接近輸入字符模式的表達形式,該表達形式對應的字就是識別結果。字符識別的原理如下:
?
圖像的預處理,加載圖像、并灰度化、高斯濾波
[cpp] view plain copy
void CMyDialog::OnLoadimage() ??
{ ?
????// TODO: Add your control notification handler code here ?
????src = NULL ; ?
????CString filePath; ?
????CFileDialog dlg(TRUE, _T("*.bmp"),"",OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,"image files (*.bmp; *.jpg) |*.bmp;*.jpg|All Files (*.*)|*.*||",NULL); ?
????char title[]= {"Open Image"}; ?
????dlg.m_ofn.lpstrTitle= title; ?
????if (dlg.DoModal() == IDOK) { ?
????????filePath= dlg.GetPathName(); ?
????????src=cvLoadImage(filePath); ?
????????DrawPicToHDC(src,IDC_IMAGESRC); ?
????} ????
????pImgCanny=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1); ?
??????
????cvCvtColor(src,pImgCanny,CV_RGB2GRAY); ?
????cvSmooth(pImgCanny,pImgCanny,CV_GAUSSIAN,3,0,0); ??//平滑高斯濾波 ?濾波后的圖片保存在 pImgCanny ?????????????
} ?
?
自適應閾值法定義閾值
[cpp] view plain copy
int CMyDialog::AdaptiveThreshold(int t, IplImage *Image) ?
{ ?
????int t1=0,t2=0,tnew=0,i=0,j=0; ?
????int Allt1=0,Allt2=0,accountt1=0,accountt2=0; ?
????for(j=0;jheight;j++) //根據現有t,將圖像分為兩部分,分別求兩部分像素的平均值t1、t2 ?
????{ ????
????????for(i=0;iwidth;i++) ?
????????{ ?
????????????if(CV_IMAGE_ELEM(Image,uchar,j,i)
????????????{ ?
????????????????Allt1+=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????????????accountt1++; ?
????????????} ?
????????????else ??
????????????{ ?
????????????????Allt2+=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????????????accountt2++; ?
????????????} ?
????????} ?
????} ?
????t1=Allt1/accountt1; ?
????t2=Allt2/accountt2; ?
????tnew=0.5*(t1+t2); ?
????//若t1、t2的平均值和t相等,則閾值確定 ?
????if(tnew==t) ??
????????return tnew; ?
????//若不等,則以t1、t2的平均值為新閾值迭代 ?
????else ??
????????AdaptiveThreshold(tnew,Image); ??
} ?
??
二值化圖像
[cpp] view plain copy
void CMyDialog::Threshold(IplImage *Image, IplImage *Image_O) ?
{ ?
????int thresMax=0,thresMin=255,i=0,j=0,t=0; ?
????//循環得到圖片的最大灰度值和最小灰度值 ?
????for(j=0;jheight;j++) ?
????????for(i=0;iwidth;i++) ?
????????{ ?
????????????if(CV_IMAGE_ELEM(Image,uchar,j,i)>thresMax) ??
????????????????thresMax=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????????else if(CV_IMAGE_ELEM(Image,uchar,j,i)
????????????????thresMin=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????} ?
??
????//小閾值用來控制邊緣連接 ?大閾值用來控制強邊緣的初始化分割 ?
????cvCanny(Image,Image_O,AdaptiveThreshold((thresMax+thresMin)*0.5,Image),thresMax*0.7,3); ?
??
} ?
車牌定位
[cpp] view plain copy
int CMyDialog::PlateAreaSearch(IplImage *pImg_Image) ?
{ ?
????// 檢測是否有值 ?
????if (pImg_Image==NULL) ????
????{ ??
????????return 0; ????
????} ?
??????
????IplImage* imgTest =0; ??
????int i=0, j=0,k=0,m=0; ?
????bool flag=0; ?
????int plate_n=0; ?//上邊界 ?
????int plate_s=0; ?//下邊界 ?
????int plate_e=0; ?//右邊界 ?
????int plate_w=0; ?//左邊界 ?
??????????
????int *num_h=new int[MAX(pImg_Image->width,pImg_Image->height)]; ??????
????if ( num_h==NULL ) ???
????{ ?
????????MessageBox("memory exhausted!"); ?
????????return 0; ????
????} ??
??
????//初始化分配的空間 ?
????for(i=0;iwidth;i++) ?
????{ ?
????????num_h[i]=0; ?
????} ???
??
????imgTest = cvCreateImage(cvSize(pImg_Image->width,pImg_Image->height),IPL_DEPTH_8U,1); ?
????cvCopy(pImg_Image, imgTest); ??
??
????//--水平輪廓細化 ?
????for(j=0; jheight; j++) ?
????{ ?
????????for(i=0;iwidth-1;i++) ?
???????{ ?
????????????CV_IMAGE_ELEM(imgTest,uchar,j,i)=CV_IMAGE_ELEM(imgTest,uchar,j,i+1)-CV_IMAGE_ELEM(imgTest,uchar,j,i); ?
????????????//記錄每一行的像素值 ?
????????????num_h[j]+=CV_IMAGE_ELEM(imgTest,uchar,j,i)/250; ???????
???????} ?????????
????} ??
???
????int temp_1 = 0; ?//統計20行中最大的每行數據量 ?
????int temp_max = 0; ?//20行最大的數據量 ??
????int temp_i = 0; ?//最大數據量的行 ?
??
????for(j=0; jheight-20; j++) ???
????{ ?
??????temp_1=0; ?
??????for(i=0;i<20;i++) ???
????????temp_1 += ?num_h[i+j]; ?
??????if(temp_1>=temp_max) ?
??????{ ?
??????????temp_max=temp_1; ?
??????????temp_i = j; ?
??????} ?
????} ?
??
????//找出上行邊界行 ?
????k=temp_i; ?
????while ( ((num_h[k +1]>POINT_X )||(num_h[k +2]>POINT_X )||(num_h[k]>POINT_X )) && k ) k--; ?
????plate_n=k+1; ??
??
????//找出下邊界行 ?
????k=temp_i+10; ?
????while (((num_h[k -1]>POINT_X )||(num_h[k-2]>POINT_X )||(num_h[k]>POINT_X ))&&(kheight)) k++; ??
????plate_s=k; ?
??
????//沒找到水平分割線,設置為默認值 ?
????if ( !(plate_n && plate_s ?
????????&& (plate_nwidth*(1-WITH_X)))) ?
????{ ?
????????MessageBox("水平分割失敗!"); ?
????????return 0; ?
????} ?
????//找到水平線 ??
????else ??
????{ ?
????????int ?max_count = 0; ?
????????int ?plate_length = (imgTest->width-(plate_s-plate_n)*HIGH_WITH_CAR); ?
????????plate_w=imgTest->width*WITH_X-1;//車牌寬度 默認 ?
??????
????????//--垂直方向 輪廓細化 ?
????????for(i=0;iwidth;i++) ?
????????????for(j=0;jheight-1;j++) ??
???????????{ ?
????????????????CV_IMAGE_ELEM(imgTest,uchar,j,i)=CV_IMAGE_ELEM(imgTest,uchar,j+1,i)-CV_IMAGE_ELEM(imgTest,uchar,j,i); ?
???????????} ?
??
????????for(k=0;k
????????{ ??
????????????for(i=0; i<(int)((plate_s-plate_n)*HIGH_WITH_CAR); i++) ?
????????????????for (j=plate_n;j
????????????????{ ?
????????????????????num_h[k] =num_h[k]+ CV_IMAGE_ELEM(imgTest,uchar,j,(i+k))/250; ?
????????????????} ?
????????????if (num_h[k]>max_count) ?
????????????{ ?
????????????????max_count = num_h[k]; ?
????????????????plate_w = k; ?
????????????} ?
??????????
????????} ?
????????????
??
????????CvRect ROI_rect; ????????????????//獲得圖片感興趣區域 ?
????????ROI_rect.x=plate_w; ?
????????ROI_rect.y=plate_n; ?
????????ROI_rect.width=(plate_s-plate_n)*HIGH_WITH_CAR; ?
????????ROI_rect.height=plate_s-plate_n; ?
????????????????
????????if ((ROI_rect.width+ROI_rect.x)> pImg_Image->width) ?
????????{ ?
????????????ROI_rect.width=pImg_Image->width-ROI_rect.x; ????
????????????MessageBox("垂直方向分割失敗!"); ?
????????????return 0; ?
????????} ?
??
????????else ?
????????{ ?
????????????IplImage *pImg8uROI=NULL; ????????//感興趣的圖片 ???
????????????pImg8uROI=cvCreateImage(cvSize(ROI_rect.width,ROI_rect.height), src->depth,src->nChannels); ?
???????????????
????????????IplImage *pImg8u11=NULL; ???????//車牌區域灰度圖 ?
????????????pImg8u11=cvCreateImage(cvSize(40*HIGH_WITH_CAR,40),pImg8uROI->depth,pImg8uROI->nChannels); ?
??
????????????cvSetImageROI(src,ROI_rect); ?
????????????cvCopy(src,pImg8uROI,NULL); ?
????????????cvResetImageROI(src); ?
?????
????????????pImgResize=cvCreateImage(cvSize(40*HIGH_WITH_CAR,40),IPL_DEPTH_8U,1); ????
????????????cvResize(pImg8uROI,pImg8u11,CV_INTER_LINEAR); //線性插值 ?
??
????????????cvCvtColor(pImg8u11,pImgResize,CV_RGB2GRAY); ?
????????????Threshold(pImgResize,pImgResize); ?
??????????????
????????????cvReleaseImage(&pImg8uROI); ?
????????????cvReleaseImage(&pImg8u11); ?
????????????cvReleaseImage(&imgTest); ?
????????} ??
????} ??
???????????
????// 釋放內存 ?
????delete []num_h; ???
????num_h=NULL; ???
????return 1; ?
} ?
?
字符分割
[cpp] view plain copy
int CMyDialog::SegmentPlate() ?
{ ?
????// 沒有切割成功,直接彈出 ?
????if (pImgResize==NULL) ???
????{ ??
????????return 0; ??
????} ??
??
????int *num_h=new int[MAX(pImgResize->width,pImgResize->height)]; ?
????if ( num_h==NULL ) ??
????{ ????
????????MessageBox("字符分割memory exhausted"); ?
????????return 0; ????
????} ?
??
????int i=0,j=0,k=0;//循環變量 12 ?
????int ?letter[14]={0,20,23,43,55,75,78,98,101,121,124,127,147,167}; // 默認分割 ?
????bool flag1=0; ???
??????
????// 垂直投影 ?
????for(i=0;i<40*HIGH_WITH_CAR;i++) ?
????{ ????
?????????num_h[i]=0; // 初始化指針 ?
?????????for(j=0;j<17;j++) ?// 0-16 ?
?????????{ ?
?????????????num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45; ?
?????????} ???
?????????for(j=24;j<40;j++) ?// 24-39 ?
?????????{ ?
?????????????num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45; ?
?????????} ?
????} ?
????// 初定位,定位點 第二個字符末端, ?
????int max_count=0; ?
????int flag=0; ??
????for(i=30;i<40*HIGH_WITH_CAR;i++) ?
????{ ?
????????if(num_h[i]
????????{ ?
????????????max_count++; ?
????????????if(max_count==11) ?
????????????{ ?
????????????????letter[3]=i-11;//第二字符的結束位置 ?
????????????????while( (num_h[i]
????????????????letter[4]=i-1;//第三個字符的開始位置 ?
????????????????break; ?
????????????} ?
????????} ?
????????else ?
????????{ ?
????????????max_count=0; ?
????????} ?
????} ?
??
????// 精定位 ?
????for(i=0;i<40*HIGH_WITH_CAR;i++) ?
????{ ?
????????for(j=17;j<=24;j++) ?
????????{ ?
????????????num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45; ?
????????} ?
????} ?
??????
????for(j=letter[3];j>0;j--)//找第一個和第二個字符起始位置 ?
????{ ?
????????if((num_h[j]
????????{ ?
????????????letter[2]=j; ??????????????????????//第二個字符的開始位置 ?
????????????letter[1]=(j>=23)?j-3:letter[1]; ??//第一個字符的結束位置 ?
????????????letter[0]=(j>=23)?j-23:letter[0]; ?//第一個字符的起始位置 ?
????????????break; ?
????????} ?
????} ?
??
????j=2; ?flag=0;flag1=0;//兩個標記 ?
????for(i=letter[4];i<40*HIGH_WITH_CAR;i++) ?//從第三個字符的開始位置算起 ?
????{ ?
????????if((num_h[i]>POINT_Y)&&(num_h[i-1]>POINT_Y) && !flag ) ?
????????{ ?
????????????flag=1; ?
????????????flag1=0; ?
????????????letter[2*j]=i-1; //這里 只記錄字符的開始位置 ?
????????????if(j==6) ?//判斷 最后一個字符的結束位置 是否越界 超出界限,如果沒有,則letter[13]=letter[12]+20 ?
????????????{ ?
????????????????letter[2*j+1]=((letter[2*j]+20)>40*HIGH_WITH_CAR-1)?40*HIGH_WITH_CAR-1:letter[2*j]+20; ?
????????????????break; ?
????????????} ?
????????} ?
????????else if((num_h[i]
????????{ ?
????????????flag=0; ?
????????????flag1=1; ?
????????????letter[2*j+1]=i-1; ?
????????????j++; ?
????????} ???
????} ????
????// 刪除角點 ???
????for(i=0;i<40*HIGH_WITH_CAR-1;i++) ?
????{ ?
????????for(j=0;j<39;j++) ?
????????{ ?
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&&CV_IMAGE_ELEM(pImgResize,uchar,j,i+1)&&CV_IMAGE_ELEM(pImgResize,uchar,j+1,i)) // ?01 ?
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????// ??1 ?
??????????????
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&& ?CV_IMAGE_ELEM(pImgResize,uchar,j,i-1) &&CV_IMAGE_ELEM(pImgResize,uchar,j+1,i)) ????// ?10 ?
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????// ???1 ?????????????????????
??????????????????????????
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&&CV_IMAGE_ELEM(pImgResize,uchar,j,i-1) &&CV_IMAGE_ELEM(pImgResize,uchar,j-1,i)) ????????// ??1 ???????
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ???????????????????????????????????????????????????????????????????????????????????// 10 ?
??
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&&CV_IMAGE_ELEM(pImgResize,uchar,j,i+1) &&CV_IMAGE_ELEM(pImgResize,uchar,j-1,i)) ??????????// 1 ???????
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ????????????????????????????????????????????????????????????????????????????????// 01 ?
????????} ?
????} ????
??
??????
????// 分割出字符圖片 ?
????pImgCharOne=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharTwo=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharThree=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharFour=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharFive=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharSix=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharSeven=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
??
????????????CvRect ROI_rect1; ?
????????????ROI_rect1.x=0.5*(letter[1]+letter[0])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharOne,NULL); //獲取第1個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[3]+letter[2])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharTwo,NULL); //獲取第2個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[5]+letter[4])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharThree,NULL); //獲取第3個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[7]+letter[6])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharFour,NULL); //獲取第4個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[9]+letter[8])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharFive,NULL); //獲取第5個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[11]+letter[10])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharSix,NULL); //獲取第6個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[13]+letter[12])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharSeven,NULL); //獲取第7個字符 ?
????????????cvResetImageROI(pImgResize); ?
??
??
??
????????????// 釋放內存 ?
????????????delete []num_h; ???
????????????num_h=NULL; ?
??
} ?
?
字符識別
[cpp] view plain copy
int CMyDialog::CodeRecognize(IplImage *imgTest, int num, int char_num) ?
{ ?
????if (imgTest==NULL) ?
????{ ??
????????return 0; ?
????} ?
??????
????int i=0,j=0,k=0,t=0; ?
????int ?char_start=0,char_end=0; ?
????int num_t[CHARACTER ]={0}; ?
??????
??????
?????switch(num)//這里這樣分 可以提高效率,并且提高了識別率 ?
?????{ ?
????????case 0: ?char_start =0; ????????// 數字 ?
?????????????????????????char_end ?= 9; ?
??????????????????????????break; ?
????????case 1: ?char_start =10; ???????// 英文 ?
?????????????????????????char_end ?= 35; ?
??????????????????????????break; ?
????????case 2: ?char_start =0; ??????// 英文和數字 ?
?????????????????????????char_end ?= 35; ?
??????????????????????????break; ???
????????case 3: ?char_start =36; ??????// 中文 ?
?????????????????????????char_end ?= TEMPLETENUM-1; ?
??????????????????????????break; ?
????????????default: break; ??????????????????
?????} ?
??
????// 提取前8個特征 ?
????for(k=0; k<8; k++) ?
????{ ??
????????for(j=int(k/2)*10; j
????????{ ?
????????????for(i=(k%2)*10;i<(k%2+1)*10;i++) ?
????????????{ ????????
???????????????num_t[k]+=CV_IMAGE_ELEM(imgTest,uchar,j,i)/255 ; ?
????????????} ????????
????????} ?
??
????????// 第9個特征 前8個特征的和作為第9個特征值 ?
????????num_t[8]+= num_t[k]; ???
????} ?
????
????for(i=0;i<20;i++) ????
????????num_t[9]+=CV_IMAGE_ELEM(imgTest,uchar,10,i)/255 ; ?
????for(i=0;i<20;i++) ?
????????num_t[10]+=CV_IMAGE_ELEM(imgTest,uchar,20,i)/255 ; ?
????for(i=0;i<20;i++) ?
????????num_t[11]+=CV_IMAGE_ELEM(imgTest,uchar,30,i)/255 ; ?
????
????for(j=0;j<40;j++) ?
????????num_t[12]+=CV_IMAGE_ELEM(imgTest,uchar,j,7)/255; ?
????for(j=0;j<40;j++) ?
????????num_t[13]+=CV_IMAGE_ELEM(imgTest,uchar,j,10)/255 ; ?
????for(j=0;j<40;j++) ?
????????num_t[14]+=CV_IMAGE_ELEM(imgTest,uchar,j,13)/255 ; ?
??
????int num_tt[CHARACTER]={0}; ?
????int matchnum=0; ?//可以說是 匹配度或 相似度 ?
????int matchnum_max=0; ???
????int matchcode = 0; ????????// 匹配號 ?
??
????j=0; ?
??
????for(k=char_start;k<=char_end;k++) ?
????{ ?
????????matchnum=0; ?
????
????????for(i=0;i<8;i++) //區域的匹配 ?
????????{ ?
????????????if (abs(num_t[i]-Num_Templete[k][i])<=2)//與模板里的相應值進行匹配 ?
????????????????matchnum++; ?
????????} ?
??????????
???????if(Num_Templete[k][i]-abs(num_t[i])<=8)//對第9個特征進行匹配 ??
?????????????matchnum+=2; ????
???????for(i=9;i
????????{ ?
???????????if (Num_Templete[k][i]>=5) ?//特征值 大于5 ??
???????????{ ?
????????????????if(abs(num_t[i]-Num_Templete[k][i])<=1) ?
????????????????matchnum+=2; ?
???????????} ?
???????????else if( num_t[i]==Num_Templete[k][i]) ?
???????????{ ??
????????????????matchnum+=2; ?
???????????} ?
????????} ?
????????if(matchnum>matchnum_max) ?
????????{ ?
??????????????matchnum_max=matchnum; ?//保留最大的 匹配 ??
??????????????matchcode= k; ?//記錄 識別的字符的 索引 ??
????????} ????
????} ?
????//識別輸出 ?存放輸出結果 ?
????G_PlateChar[char_num]=PlateCode[matchcode]; //保存下該字符 ?
} ?
?
網上分享的的車牌字符模板特征值,并不是很全但對于初步學習已經足夠了
const int Num_Templete[TEMPLETENUM][CHARACTER]= ?
{ ?
??{16,19,10,12,10,10,15,18,110,3,2,2,3,3,3}, ????//0 ?
??{9,11,10,10,10,10,9,10,79,2,2,2,0,2,12}, ??????//1 ?
??{18,19,3,18,10,10,23,22,123,4,2,2,7,6,8}, ?????//2 ?
??{19,21,11,14,4,20,18,22,129,2,2,4,6,6,7}, ?????//3 ?
??{2,18,11,22,20,21,11,18,123,2,4,2,6,7,5}, ?????//4 ?
??{23,19,20,12,9,20,18,22,143,2,4,4,6,6,6}, ?????//5 ?
??{6,13,17,8,15,20,18,20,117,2,2,4,5,7,6}, ??????//6 ?
??{21,21,0,20,8,12,9,11,102,2,2,2,2,8,15}, ??????//7 ?
??{17,18,18,19,14,20,17,20,143,4,2,4,6,6,6}, ????//8 ?
??{16,18,15,21,7,19,13,7,116,3,2,2,6,6,5}, ??????//9 ?
??{10,10,16,16,20,20,18,19,129,2,4,2,8,3,6}, ????//A ?
??{24,20,20,19,22,22,24,20,171,4,8,4,6,6,6}, ????//B ?
??{18,19,20,4,20,8,17,21,127,3,2,4,4,4,4}, ??????//C ?
??{23,19,11,20,12,20,22,21,148,3,3,3,4,4,4}, ????//D ?
??{23,19,21,9,22,8,23,23,148,2,2,2,6,6,6}, ??????//E ?
??{25,17,20,9,22,8,19,0,120,2,2,2,4,4,4}, ???????//F ?
??{17,18,22,14,12,24,18,21,146,4,7,4,4,6,6}, ????//G ?
??{14,20,18,22,17,22,16,20,149,4,1,4,2,2,2}, ????//H ?
??{0,17,0,20,3,20,18,22,100,2,2,4,2,2,2}, ???????//J ?
??{19,20,26,10,20,20,20,22,157,4,4,4,3,5,11}, ???//K ?
??{20,0,20,0,20,0,25,20,105,2,2,2,2,2,2}, ???????//L ?
??{20,10,27,17,20,10,22,14,140,1,3,3,4,1,5}, ????//M ?
??{21,12,25,17,26,12,18,18,149,3,5,3,5,5,6}, ????//N ??
??{23,19,18,20,21,8,22,0,131,3,3,2,4,4,4}, ??????//P ?
??{18,19,20,10,26,15,18,21,147,3,3,4,5,7,5}, ????//Q ?
??{26,19,21,18,21,17,20,21,163,4,3,4,4,6,5}, ????//R ?
??{18,18,18,10,8,17,17,22,128,4,3,4,6,6,6}, ?????//S ?
??{22,18,10,10,10,10,10,10,100,2,2,2,33,2,2}, ???//T ?
??{18,12,20,10,20,10,19,21,130,3,3,3,2,2,2}, ????//U ?
??{20,19,20,20,15,14,9,10,127,4,4,2,9,1,8}, ?????//V ?
??{21,25,26,28,16,16,21,19,172,6,2,4,13,0,7}, ???//W ?
??{21,21,13,13,12,11,22,21,134,4,2,4,8,0,10}, ???//X ?
??{21,20,10,11,10,10,10,11,103,3,2,2,5,2,6}, ????//Y ?
??{21,23,5,15,15,5,24,20,128,2,2,2,8,8,7}, ??????//Z ?
??{13,14,10,10,10,10,13,13,93,2,2,2,29,2,29}, ???//I ?
??{20,20,13,20,19,12,17,20,141,3,3,4,4,4,4}, ????//O ???????????
??{14,15,17,17,16,10,25,24,138,0,2,4,12,8,9}, ???//云 ?????????
??{17,20,17,12,33,28,23,20,170,3,4,7,13,6,4}, ???//蘇 ?
??{21,21,23,24,24,25,31,27,196,0,9,6,8,6,7}, ????//京 ?
??{19,27,20,34,19,36,24,37,216,4,4,7,13,28,3}, ??//湘 ?
??{17,14,23,27,36,40,26,27,210,4,13,4,16,14,14}, //魯 ?
??{24,24,32,38,34,32,17,22,223,9,6,10,11,12,9}, // 粵 ?
??{22,20,33,37,25,24,24,25,210,13,3,6,12,8,7} ????//蒙 ?
}; ?
1框架流程
?
2、平滑處理
?
車牌定位的第一步為圖像預處理。為了方便計算,系統通常將獲取的圖片灰度化。將彩色圖像轉化成為灰度圖像的過程就稱為圖像的灰度化處理。彩色圖像中R、G、B三個分量的值決定了具體的像素點。一個像素點可以有上千萬種顏色。而灰度圖像是一種彩色圖像,但是它的特點在于R、G、B三個分量具體的值是一致的。灰度圖中每個像素點的變化區間是0到255,由于方便計算,所以在實際工程處理中會先將各種格式的圖像轉變成灰度圖像。在保留圖像輪廓和特征的基礎上,灰度圖仍然能夠反映整幅圖像輪廓和紋理。在Opencv里面有實現圖像灰度化的接口。調用OpenCV中的cvSmooth函數進行中值濾波處理,以去除細小毛刺。3、二值化
局部自適應二值化是針對灰度圖像中的每一個像素逐點進行閾值計算,它的閾值是由像素周圍點局部灰度特性和像素灰度值來確定的。局部閾值法是逐個計算圖像的每個像素灰度級,保存了圖像的細節信息,非均勻光照條件等情況雖然影響整個圖像的灰度分布缺不影響局部的圖像性質,但也存在缺點和問題,相比全局閾值法來說,它的計算時間較長,但適用于多變的環境。設圖像在像素點(x,y)處的灰度值為f(x,y),考慮以像素點(x,y)為中心的(2w+1)*(2w+1)窗口(w為窗口寬度),則局部自適應二值化算法可以描述如下:
a.計算圖像中各點(x,y)的閾值w(x,y)
W(x,y)=0.5*(max f(x+m,y+n)+min f(x+m,y+n))
b.如果f(x,y)>w(x,y),則二值化結果為1,代表字符區域的目標點;否則二值化結果為0,代表背景區域的目標點。
4、車牌定位
?
??
Canny邊緣檢測算子的方向性質保證了很好的邊緣強度估計,而且能同時產生邊緣梯度方向和強度兩個信息,即能在一定程度上抗噪聲又能保持弱邊緣,因此采用以canny算子做邊緣檢測。
Canny算法步驟:
(1)去噪任何邊緣檢測算法都不可能在未經處理的原始數據上很好地處理,所以第一步是對原始數據與高斯 mask 作卷積,得到的圖像與原始圖像相比有些輕微的模糊(blurred)。這樣,單獨的一個像素雜訊在經過高斯平滑的圖像上變得幾乎沒有影響。
(2)用一階偏導的有限差分來計算梯度的幅值和方向。
(3)對梯度幅值進行非極大值抑制。
僅僅得到全局的梯度并不足以確定邊緣,因此為確定邊緣,必須保留局部梯度最大的點,而抑制非極大值。
非極大值抑制產生的二值灰度矩陣的潛在點中按照高閾值尋找邊緣,并以所找到的點為中心尋找鄰域內滿足低閾值的點,從而形成一個閉合的輪廓。然后對于不滿足條件的點直接刪除掉。
行掃描定位
車牌細定位的目的是為下一步字符的分割做,就是要進一步去掉車牌冗余的部分。在一幅經過適當二值化處理 含有車牌的圖像中,車牌區域具有以下三個基本特征:1.在一個不大的區域內密集包含有多個字符;
2.車牌字符與車牌底色形成強烈對比;
3.車牌區域大小相對固定,區域長度和寬度成固定比例。
根據以上特征,車牌區域所在行相鄰像素之間0 到1和1到0 的的變化會很頻繁,變化總數會大于一個臨界值,這可以作為尋找車牌區域的一個依據。 因此根據跳變次數與設定的閾值比較,就可以確定出車牌的水平區域。
由于車牌一般懸掛在車輛下部,所以采用從上到下,從左到右的方式對圖像進行掃描。車牌的字符部分由7個字符數與兩個豎直邊框組成,則車牌區域內任一行的跳變次數至少為(7+2)*2=18次。從圖像的底部開始向頂部進行掃描,則第一組連續數行且每行的跳變次數都大于跳變閾值,同時滿足連續行數大于某個閾值。
在車牌的水平區域中,最高行與最低行的差值即為車牌在圖像中的高度。我國車牌區域為矩形,寬高比約為3.14,取3.14*H作為車牌的寬度。在水平區域內選擇任意一行,用L長的窗口由左至右移動,統計窗口中相鄰像素0,1的跳變次數并存入數組中。若窗口移動到車牌的垂直區域時,窗口內的跳變次數應該最大。因此在數組中找到最大值,其對應的區域即為車牌的垂直區域。
5、模板匹配
??
基于BP神經網絡的字符識別
車牌字符識別是字符識別的重要組成部分。車牌字符識別的最終目的就是將圖像中的車牌字符轉化成文本字符,車牌字符的識別屬于印刷體識別范疇。
字符識別的基本思想是匹配判別。抽取待識別字符特征按照字符識別的原理和預先存儲在計算機中的標準字符模式表達形式的集合逐一進行匹配,找出最接近輸入字符模式的表達形式,該表達形式對應的字就是識別結果。字符識別的原理如下:
?
根據我國的車牌牌照標準,車牌第一位字符一般為漢字,車牌第二位英文大寫字母,第三位至第七位為英文大寫字母或數字。考慮到神經網絡對小類別字符集有較高的識別率,因此在車牌字符識別系統中,分別設計三個神經網絡:漢字網絡、字母網絡、字母數字網絡 實現對字符的分類識別。
?
以字母0為例,提取字符特征的步驟為:將分割好的圖片再分割成8*4的特征向量,形成一個32維的向量。最終字符特征提取的結果如下:
?
最終效果:
?
?
主要函數代碼:
圖像的預處理,加載圖像、并灰度化、高斯濾波[cpp] view plain copy
void CMyDialog::OnLoadimage() ??
{ ?
????// TODO: Add your control notification handler code here ?
????src = NULL ; ?
????CString filePath; ?
????CFileDialog dlg(TRUE, _T("*.bmp"),"",OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,"image files (*.bmp; *.jpg) |*.bmp;*.jpg|All Files (*.*)|*.*||",NULL); ?
????char title[]= {"Open Image"}; ?
????dlg.m_ofn.lpstrTitle= title; ?
????if (dlg.DoModal() == IDOK) { ?
????????filePath= dlg.GetPathName(); ?
????????src=cvLoadImage(filePath); ?
????????DrawPicToHDC(src,IDC_IMAGESRC); ?
????} ????
????pImgCanny=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1); ?
??????
????cvCvtColor(src,pImgCanny,CV_RGB2GRAY); ?
????cvSmooth(pImgCanny,pImgCanny,CV_GAUSSIAN,3,0,0); ??//平滑高斯濾波 ?濾波后的圖片保存在 pImgCanny ?????????????
} ?
?
自適應閾值法定義閾值
[cpp] view plain copy
int CMyDialog::AdaptiveThreshold(int t, IplImage *Image) ?
{ ?
????int t1=0,t2=0,tnew=0,i=0,j=0; ?
????int Allt1=0,Allt2=0,accountt1=0,accountt2=0; ?
????for(j=0;j
????{ ????
????????for(i=0;i
????????{ ?
????????????if(CV_IMAGE_ELEM(Image,uchar,j,i)
????????????????Allt1+=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????????????accountt1++; ?
????????????} ?
????????????else ??
????????????{ ?
????????????????Allt2+=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????????????accountt2++; ?
????????????} ?
????????} ?
????} ?
????t1=Allt1/accountt1; ?
????t2=Allt2/accountt2; ?
????tnew=0.5*(t1+t2); ?
????//若t1、t2的平均值和t相等,則閾值確定 ?
????if(tnew==t) ??
????????return tnew; ?
????//若不等,則以t1、t2的平均值為新閾值迭代 ?
????else ??
????????AdaptiveThreshold(tnew,Image); ??
} ?
??
二值化圖像
[cpp] view plain copy
void CMyDialog::Threshold(IplImage *Image, IplImage *Image_O) ?
{ ?
????int thresMax=0,thresMin=255,i=0,j=0,t=0; ?
????//循環得到圖片的最大灰度值和最小灰度值 ?
????for(j=0;j
????????for(i=0;i
????????{ ?
????????????if(CV_IMAGE_ELEM(Image,uchar,j,i)>thresMax) ??
????????????????thresMax=CV_IMAGE_ELEM(Image,uchar,j,i); ?
????????????else if(CV_IMAGE_ELEM(Image,uchar,j,i)
????????} ?
??
????//小閾值用來控制邊緣連接 ?大閾值用來控制強邊緣的初始化分割 ?
????cvCanny(Image,Image_O,AdaptiveThreshold((thresMax+thresMin)*0.5,Image),thresMax*0.7,3); ?
??
} ?
車牌定位
[cpp] view plain copy
int CMyDialog::PlateAreaSearch(IplImage *pImg_Image) ?
{ ?
????// 檢測是否有值 ?
????if (pImg_Image==NULL) ????
????{ ??
????????return 0; ????
????} ?
??????
????IplImage* imgTest =0; ??
????int i=0, j=0,k=0,m=0; ?
????bool flag=0; ?
????int plate_n=0; ?//上邊界 ?
????int plate_s=0; ?//下邊界 ?
????int plate_e=0; ?//右邊界 ?
????int plate_w=0; ?//左邊界 ?
??????????
????int *num_h=new int[MAX(pImg_Image->width,pImg_Image->height)]; ??????
????if ( num_h==NULL ) ???
????{ ?
????????MessageBox("memory exhausted!"); ?
????????return 0; ????
????} ??
??
????//初始化分配的空間 ?
????for(i=0;i
????{ ?
????????num_h[i]=0; ?
????} ???
??
????imgTest = cvCreateImage(cvSize(pImg_Image->width,pImg_Image->height),IPL_DEPTH_8U,1); ?
????cvCopy(pImg_Image, imgTest); ??
??
????//--水平輪廓細化 ?
????for(j=0; j
????{ ?
????????for(i=0;i
???????{ ?
????????????CV_IMAGE_ELEM(imgTest,uchar,j,i)=CV_IMAGE_ELEM(imgTest,uchar,j,i+1)-CV_IMAGE_ELEM(imgTest,uchar,j,i); ?
????????????//記錄每一行的像素值 ?
????????????num_h[j]+=CV_IMAGE_ELEM(imgTest,uchar,j,i)/250; ???????
???????} ?????????
????} ??
???
????int temp_1 = 0; ?//統計20行中最大的每行數據量 ?
????int temp_max = 0; ?//20行最大的數據量 ??
????int temp_i = 0; ?//最大數據量的行 ?
??
????for(j=0; j
????{ ?
??????temp_1=0; ?
??????for(i=0;i<20;i++) ???
????????temp_1 += ?num_h[i+j]; ?
??????if(temp_1>=temp_max) ?
??????{ ?
??????????temp_max=temp_1; ?
??????????temp_i = j; ?
??????} ?
????} ?
??
????//找出上行邊界行 ?
????k=temp_i; ?
????while ( ((num_h[k +1]>POINT_X )||(num_h[k +2]>POINT_X )||(num_h[k]>POINT_X )) && k ) k--; ?
????plate_n=k+1; ??
??
????//找出下邊界行 ?
????k=temp_i+10; ?
????while (((num_h[k -1]>POINT_X )||(num_h[k-2]>POINT_X )||(num_h[k]>POINT_X ))&&(k
????plate_s=k; ?
??
????//沒找到水平分割線,設置為默認值 ?
????if ( !(plate_n && plate_s ?
????????&& (plate_n
????{ ?
????????MessageBox("水平分割失敗!"); ?
????????return 0; ?
????} ?
????//找到水平線 ??
????else ??
????{ ?
????????int ?max_count = 0; ?
????????int ?plate_length = (imgTest->width-(plate_s-plate_n)*HIGH_WITH_CAR); ?
????????plate_w=imgTest->width*WITH_X-1;//車牌寬度 默認 ?
??????
????????//--垂直方向 輪廓細化 ?
????????for(i=0;i
????????????for(j=0;j
???????????{ ?
????????????????CV_IMAGE_ELEM(imgTest,uchar,j,i)=CV_IMAGE_ELEM(imgTest,uchar,j+1,i)-CV_IMAGE_ELEM(imgTest,uchar,j,i); ?
???????????} ?
??
????????for(k=0;k
????????????for(i=0; i<(int)((plate_s-plate_n)*HIGH_WITH_CAR); i++) ?
????????????????for (j=plate_n;j
????????????????????num_h[k] =num_h[k]+ CV_IMAGE_ELEM(imgTest,uchar,j,(i+k))/250; ?
????????????????} ?
????????????if (num_h[k]>max_count) ?
????????????{ ?
????????????????max_count = num_h[k]; ?
????????????????plate_w = k; ?
????????????} ?
??????????
????????} ?
????????????
??
????????CvRect ROI_rect; ????????????????//獲得圖片感興趣區域 ?
????????ROI_rect.x=plate_w; ?
????????ROI_rect.y=plate_n; ?
????????ROI_rect.width=(plate_s-plate_n)*HIGH_WITH_CAR; ?
????????ROI_rect.height=plate_s-plate_n; ?
????????????????
????????if ((ROI_rect.width+ROI_rect.x)> pImg_Image->width) ?
????????{ ?
????????????ROI_rect.width=pImg_Image->width-ROI_rect.x; ????
????????????MessageBox("垂直方向分割失敗!"); ?
????????????return 0; ?
????????} ?
??
????????else ?
????????{ ?
????????????IplImage *pImg8uROI=NULL; ????????//感興趣的圖片 ???
????????????pImg8uROI=cvCreateImage(cvSize(ROI_rect.width,ROI_rect.height), src->depth,src->nChannels); ?
???????????????
????????????IplImage *pImg8u11=NULL; ???????//車牌區域灰度圖 ?
????????????pImg8u11=cvCreateImage(cvSize(40*HIGH_WITH_CAR,40),pImg8uROI->depth,pImg8uROI->nChannels); ?
??
????????????cvSetImageROI(src,ROI_rect); ?
????????????cvCopy(src,pImg8uROI,NULL); ?
????????????cvResetImageROI(src); ?
?????
????????????pImgResize=cvCreateImage(cvSize(40*HIGH_WITH_CAR,40),IPL_DEPTH_8U,1); ????
????????????cvResize(pImg8uROI,pImg8u11,CV_INTER_LINEAR); //線性插值 ?
??
????????????cvCvtColor(pImg8u11,pImgResize,CV_RGB2GRAY); ?
????????????Threshold(pImgResize,pImgResize); ?
??????????????
????????????cvReleaseImage(&pImg8uROI); ?
????????????cvReleaseImage(&pImg8u11); ?
????????????cvReleaseImage(&imgTest); ?
????????} ??
????} ??
???????????
????// 釋放內存 ?
????delete []num_h; ???
????num_h=NULL; ???
????return 1; ?
} ?
?
字符分割
[cpp] view plain copy
int CMyDialog::SegmentPlate() ?
{ ?
????// 沒有切割成功,直接彈出 ?
????if (pImgResize==NULL) ???
????{ ??
????????return 0; ??
????} ??
??
????int *num_h=new int[MAX(pImgResize->width,pImgResize->height)]; ?
????if ( num_h==NULL ) ??
????{ ????
????????MessageBox("字符分割memory exhausted"); ?
????????return 0; ????
????} ?
??
????int i=0,j=0,k=0;//循環變量 12 ?
????int ?letter[14]={0,20,23,43,55,75,78,98,101,121,124,127,147,167}; // 默認分割 ?
????bool flag1=0; ???
??????
????// 垂直投影 ?
????for(i=0;i<40*HIGH_WITH_CAR;i++) ?
????{ ????
?????????num_h[i]=0; // 初始化指針 ?
?????????for(j=0;j<17;j++) ?// 0-16 ?
?????????{ ?
?????????????num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45; ?
?????????} ???
?????????for(j=24;j<40;j++) ?// 24-39 ?
?????????{ ?
?????????????num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45; ?
?????????} ?
????} ?
????// 初定位,定位點 第二個字符末端, ?
????int max_count=0; ?
????int flag=0; ??
????for(i=30;i<40*HIGH_WITH_CAR;i++) ?
????{ ?
????????if(num_h[i]
????????????max_count++; ?
????????????if(max_count==11) ?
????????????{ ?
????????????????letter[3]=i-11;//第二字符的結束位置 ?
????????????????while( (num_h[i]
????????????????break; ?
????????????} ?
????????} ?
????????else ?
????????{ ?
????????????max_count=0; ?
????????} ?
????} ?
??
????// 精定位 ?
????for(i=0;i<40*HIGH_WITH_CAR;i++) ?
????{ ?
????????for(j=17;j<=24;j++) ?
????????{ ?
????????????num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45; ?
????????} ?
????} ?
??????
????for(j=letter[3];j>0;j--)//找第一個和第二個字符起始位置 ?
????{ ?
????????if((num_h[j]
????????????letter[2]=j; ??????????????????????//第二個字符的開始位置 ?
????????????letter[1]=(j>=23)?j-3:letter[1]; ??//第一個字符的結束位置 ?
????????????letter[0]=(j>=23)?j-23:letter[0]; ?//第一個字符的起始位置 ?
????????????break; ?
????????} ?
????} ?
??
????j=2; ?flag=0;flag1=0;//兩個標記 ?
????for(i=letter[4];i<40*HIGH_WITH_CAR;i++) ?//從第三個字符的開始位置算起 ?
????{ ?
????????if((num_h[i]>POINT_Y)&&(num_h[i-1]>POINT_Y) && !flag ) ?
????????{ ?
????????????flag=1; ?
????????????flag1=0; ?
????????????letter[2*j]=i-1; //這里 只記錄字符的開始位置 ?
????????????if(j==6) ?//判斷 最后一個字符的結束位置 是否越界 超出界限,如果沒有,則letter[13]=letter[12]+20 ?
????????????{ ?
????????????????letter[2*j+1]=((letter[2*j]+20)>40*HIGH_WITH_CAR-1)?40*HIGH_WITH_CAR-1:letter[2*j]+20; ?
????????????????break; ?
????????????} ?
????????} ?
????????else if((num_h[i]
????????????flag=0; ?
????????????flag1=1; ?
????????????letter[2*j+1]=i-1; ?
????????????j++; ?
????????} ???
????} ????
????// 刪除角點 ???
????for(i=0;i<40*HIGH_WITH_CAR-1;i++) ?
????{ ?
????????for(j=0;j<39;j++) ?
????????{ ?
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&&CV_IMAGE_ELEM(pImgResize,uchar,j,i+1)&&CV_IMAGE_ELEM(pImgResize,uchar,j+1,i)) // ?01 ?
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????// ??1 ?
??????????????
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&& ?CV_IMAGE_ELEM(pImgResize,uchar,j,i-1) &&CV_IMAGE_ELEM(pImgResize,uchar,j+1,i)) ????// ?10 ?
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????// ???1 ?????????????????????
??????????????????????????
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&&CV_IMAGE_ELEM(pImgResize,uchar,j,i-1) &&CV_IMAGE_ELEM(pImgResize,uchar,j-1,i)) ????????// ??1 ???????
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ???????????????????????????????????????????????????????????????????????????????????// 10 ?
??
????????????if(CV_IMAGE_ELEM(pImgResize,uchar,j,i)&&CV_IMAGE_ELEM(pImgResize,uchar,j,i+1) &&CV_IMAGE_ELEM(pImgResize,uchar,j-1,i)) ??????????// 1 ???????
????????????????CV_IMAGE_ELEM(pImgResize,uchar,j,i)=0; ????????????????????????????????????????????????????????????????????????????????// 01 ?
????????} ?
????} ????
??
??????
????// 分割出字符圖片 ?
????pImgCharOne=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharTwo=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharThree=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharFour=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharFive=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharSix=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
????pImgCharSeven=cvCreateImage(cvSize(20,40),IPL_DEPTH_8U,1); ?
??
????????????CvRect ROI_rect1; ?
????????????ROI_rect1.x=0.5*(letter[1]+letter[0])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharOne,NULL); //獲取第1個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[3]+letter[2])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharTwo,NULL); //獲取第2個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[5]+letter[4])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharThree,NULL); //獲取第3個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[7]+letter[6])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharFour,NULL); //獲取第4個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[9]+letter[8])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharFive,NULL); //獲取第5個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[11]+letter[10])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharSix,NULL); //獲取第6個字符 ?
????????????cvResetImageROI(pImgResize); ?
??????????????
????????????ROI_rect1.x=0.5*(letter[13]+letter[12])-10; ?
????????????ROI_rect1.y=0; ?
????????????ROI_rect1.width=20; ?
????????????ROI_rect1.height=40; ?
????????????cvSetImageROI(pImgResize,ROI_rect1); ?
????????????cvCopy(pImgResize,pImgCharSeven,NULL); //獲取第7個字符 ?
????????????cvResetImageROI(pImgResize); ?
??
??
??
????????????// 釋放內存 ?
????????????delete []num_h; ???
????????????num_h=NULL; ?
??
} ?
?
字符識別
[cpp] view plain copy
int CMyDialog::CodeRecognize(IplImage *imgTest, int num, int char_num) ?
{ ?
????if (imgTest==NULL) ?
????{ ??
????????return 0; ?
????} ?
??????
????int i=0,j=0,k=0,t=0; ?
????int ?char_start=0,char_end=0; ?
????int num_t[CHARACTER ]={0}; ?
??????
??????
?????switch(num)//這里這樣分 可以提高效率,并且提高了識別率 ?
?????{ ?
????????case 0: ?char_start =0; ????????// 數字 ?
?????????????????????????char_end ?= 9; ?
??????????????????????????break; ?
????????case 1: ?char_start =10; ???????// 英文 ?
?????????????????????????char_end ?= 35; ?
??????????????????????????break; ?
????????case 2: ?char_start =0; ??????// 英文和數字 ?
?????????????????????????char_end ?= 35; ?
??????????????????????????break; ???
????????case 3: ?char_start =36; ??????// 中文 ?
?????????????????????????char_end ?= TEMPLETENUM-1; ?
??????????????????????????break; ?
????????????default: break; ??????????????????
?????} ?
??
????// 提取前8個特征 ?
????for(k=0; k<8; k++) ?
????{ ??
????????for(j=int(k/2)*10; j
????????????for(i=(k%2)*10;i<(k%2+1)*10;i++) ?
????????????{ ????????
???????????????num_t[k]+=CV_IMAGE_ELEM(imgTest,uchar,j,i)/255 ; ?
????????????} ????????
????????} ?
??
????????// 第9個特征 前8個特征的和作為第9個特征值 ?
????????num_t[8]+= num_t[k]; ???
????} ?
????
????for(i=0;i<20;i++) ????
????????num_t[9]+=CV_IMAGE_ELEM(imgTest,uchar,10,i)/255 ; ?
????for(i=0;i<20;i++) ?
????????num_t[10]+=CV_IMAGE_ELEM(imgTest,uchar,20,i)/255 ; ?
????for(i=0;i<20;i++) ?
????????num_t[11]+=CV_IMAGE_ELEM(imgTest,uchar,30,i)/255 ; ?
????
????for(j=0;j<40;j++) ?
????????num_t[12]+=CV_IMAGE_ELEM(imgTest,uchar,j,7)/255; ?
????for(j=0;j<40;j++) ?
????????num_t[13]+=CV_IMAGE_ELEM(imgTest,uchar,j,10)/255 ; ?
????for(j=0;j<40;j++) ?
????????num_t[14]+=CV_IMAGE_ELEM(imgTest,uchar,j,13)/255 ; ?
??
????int num_tt[CHARACTER]={0}; ?
????int matchnum=0; ?//可以說是 匹配度或 相似度 ?
????int matchnum_max=0; ???
????int matchcode = 0; ????????// 匹配號 ?
??
????j=0; ?
??
????for(k=char_start;k<=char_end;k++) ?
????{ ?
????????matchnum=0; ?
????
????????for(i=0;i<8;i++) //區域的匹配 ?
????????{ ?
????????????if (abs(num_t[i]-Num_Templete[k][i])<=2)//與模板里的相應值進行匹配 ?
????????????????matchnum++; ?
????????} ?
??????????
???????if(Num_Templete[k][i]-abs(num_t[i])<=8)//對第9個特征進行匹配 ??
?????????????matchnum+=2; ????
???????for(i=9;i
???????????if (Num_Templete[k][i]>=5) ?//特征值 大于5 ??
???????????{ ?
????????????????if(abs(num_t[i]-Num_Templete[k][i])<=1) ?
????????????????matchnum+=2; ?
???????????} ?
???????????else if( num_t[i]==Num_Templete[k][i]) ?
???????????{ ??
????????????????matchnum+=2; ?
???????????} ?
????????} ?
????????if(matchnum>matchnum_max) ?
????????{ ?
??????????????matchnum_max=matchnum; ?//保留最大的 匹配 ??
??????????????matchcode= k; ?//記錄 識別的字符的 索引 ??
????????} ????
????} ?
????//識別輸出 ?存放輸出結果 ?
????G_PlateChar[char_num]=PlateCode[matchcode]; //保存下該字符 ?
} ?
?
網上分享的的車牌字符模板特征值,并不是很全但對于初步學習已經足夠了
const int Num_Templete[TEMPLETENUM][CHARACTER]= ?
{ ?
??{16,19,10,12,10,10,15,18,110,3,2,2,3,3,3}, ????//0 ?
??{9,11,10,10,10,10,9,10,79,2,2,2,0,2,12}, ??????//1 ?
??{18,19,3,18,10,10,23,22,123,4,2,2,7,6,8}, ?????//2 ?
??{19,21,11,14,4,20,18,22,129,2,2,4,6,6,7}, ?????//3 ?
??{2,18,11,22,20,21,11,18,123,2,4,2,6,7,5}, ?????//4 ?
??{23,19,20,12,9,20,18,22,143,2,4,4,6,6,6}, ?????//5 ?
??{6,13,17,8,15,20,18,20,117,2,2,4,5,7,6}, ??????//6 ?
??{21,21,0,20,8,12,9,11,102,2,2,2,2,8,15}, ??????//7 ?
??{17,18,18,19,14,20,17,20,143,4,2,4,6,6,6}, ????//8 ?
??{16,18,15,21,7,19,13,7,116,3,2,2,6,6,5}, ??????//9 ?
??{10,10,16,16,20,20,18,19,129,2,4,2,8,3,6}, ????//A ?
??{24,20,20,19,22,22,24,20,171,4,8,4,6,6,6}, ????//B ?
??{18,19,20,4,20,8,17,21,127,3,2,4,4,4,4}, ??????//C ?
??{23,19,11,20,12,20,22,21,148,3,3,3,4,4,4}, ????//D ?
??{23,19,21,9,22,8,23,23,148,2,2,2,6,6,6}, ??????//E ?
??{25,17,20,9,22,8,19,0,120,2,2,2,4,4,4}, ???????//F ?
??{17,18,22,14,12,24,18,21,146,4,7,4,4,6,6}, ????//G ?
??{14,20,18,22,17,22,16,20,149,4,1,4,2,2,2}, ????//H ?
??{0,17,0,20,3,20,18,22,100,2,2,4,2,2,2}, ???????//J ?
??{19,20,26,10,20,20,20,22,157,4,4,4,3,5,11}, ???//K ?
??{20,0,20,0,20,0,25,20,105,2,2,2,2,2,2}, ???????//L ?
??{20,10,27,17,20,10,22,14,140,1,3,3,4,1,5}, ????//M ?
??{21,12,25,17,26,12,18,18,149,3,5,3,5,5,6}, ????//N ??
??{23,19,18,20,21,8,22,0,131,3,3,2,4,4,4}, ??????//P ?
??{18,19,20,10,26,15,18,21,147,3,3,4,5,7,5}, ????//Q ?
??{26,19,21,18,21,17,20,21,163,4,3,4,4,6,5}, ????//R ?
??{18,18,18,10,8,17,17,22,128,4,3,4,6,6,6}, ?????//S ?
??{22,18,10,10,10,10,10,10,100,2,2,2,33,2,2}, ???//T ?
??{18,12,20,10,20,10,19,21,130,3,3,3,2,2,2}, ????//U ?
??{20,19,20,20,15,14,9,10,127,4,4,2,9,1,8}, ?????//V ?
??{21,25,26,28,16,16,21,19,172,6,2,4,13,0,7}, ???//W ?
??{21,21,13,13,12,11,22,21,134,4,2,4,8,0,10}, ???//X ?
??{21,20,10,11,10,10,10,11,103,3,2,2,5,2,6}, ????//Y ?
??{21,23,5,15,15,5,24,20,128,2,2,2,8,8,7}, ??????//Z ?
??{13,14,10,10,10,10,13,13,93,2,2,2,29,2,29}, ???//I ?
??{20,20,13,20,19,12,17,20,141,3,3,4,4,4,4}, ????//O ???????????
??{14,15,17,17,16,10,25,24,138,0,2,4,12,8,9}, ???//云 ?????????
??{17,20,17,12,33,28,23,20,170,3,4,7,13,6,4}, ???//蘇 ?
??{21,21,23,24,24,25,31,27,196,0,9,6,8,6,7}, ????//京 ?
??{19,27,20,34,19,36,24,37,216,4,4,7,13,28,3}, ??//湘 ?
??{17,14,23,27,36,40,26,27,210,4,13,4,16,14,14}, //魯 ?
??{24,24,32,38,34,32,17,22,223,9,6,10,11,12,9}, // 粵 ?
??{22,20,33,37,25,24,24,25,210,13,3,6,12,8,7} ????//蒙 ?
}; ?
評論
查看更多