基於Matlab的車牌字元識別
來自專欄機器學習的打怪之路
前言:
- 此文是在學習機器學習過程中對實踐項目的一點總結,若有錯漏之處,請指正,敬請諒解
- 使用工具:Matlab,libsvm3.2.2
本文主要通過以下幾個方面進行介紹:
- 數據預處理
- 特徵提取
- 模型訓練與測試
- 模型優化
本案例是通過SVM分類器對樣本進行訓練與測試,達到識別車牌字母、數字及漢字的目的。關於SVM的原理這裡就不多贅述了,想了解的同學可以看下陳老師的SVM講解,寫的細緻且易懂。
耳東陳:零基礎學SVM—Support Vector Machine(一)
數據集是已經分割好的車牌字元,共有1000張車牌字元圖片,大小均為47*92,兩個txt文本文件分別包含所有字元和需要手工校正的字元圖片的名字及對應的類別。
- 數據預處理
將字元圖像進行二值化操作,將圖像上的像素點的灰度值設置為0或255,也就是將整個圖像呈現出明顯的黑白效果的過程,而在Matlab中,一幅二值圖像是一個取值只有0和1的邏輯數組。通常做法是先把彩色圖像轉化為灰度圖像,再轉化為呈現黑白的二值圖像,此處我是直接將彩色RGB圖轉化為二值圖,因為與後面的手工校正相關聯。當轉化完你會發現,大部分字元圖像變為黑底白字,但是還有小部分為白底黑字,所以還需將此部分的圖像進行反轉處理,代碼如下:
%讀取文件[input1, input2, input3] = textread(Char_Index.txt,%d %d %s,1000, headerlines,1);indexFileName = input3;for k=1:1000 A=imread(strcat(Char_Image/,char(indexFileName(k,1)))); t=graythresh(A);%設置閾值 B=im2bw(A,t);%將灰度圖轉化為二值圖%以圖像左上角為原點,向下為x軸,向右為y軸,四個角的值大於等於2,即有2及以上為白點的,進行反轉 [a,b]=size(B); if(B(1,1)+B(1,b)+B(a,1)+B(a,b)>=2) for i=1:a for j=1:b B(i,j)=1-B(i,j); end end end imwrite(B,strcat(Char_Image_Binary/,char(indexFileName(k,1))));end
經過上述步驟(二值化,反轉),大部分字元已經轉為黑底白字,但仍有小部分頑固字元寧死不屈,這裡便進行人工校正。你可能會認為人工參與成本高,僅限於小數據量樣本,並且項目上線後會不斷地產生同樣問題,所以這並完美,那麼設想下,如果能實現全自動不就解決此問題了嗎?該採取什麼方法?這裡先賣個關子,後面優化部分會跟大家分享。下圖為人工篩選出的白底黑字部分字元名稱。
這裡要做的,僅僅是將這14張字元進行圖片反轉即可,最終得到全部的黑底白字的車牌字,代碼如下:
[input] = textread(Char_Index_Err.txt,%s,14);indexFileName = input;for k=1:14A=imread(strcat(Char_Image_Binary/,char(indexFileName(k,1))));t=graythresh(A); B=im2bw(A,t);[a,b]=size(B);for i=1:a for j=1:b B(i,j)=1-B(i,j); endendimwrite(B,strcat(Char_Image_Binary/,char(indexFileName(k,1))));end
2.特徵提取
2.1 每一行和每一列的白點數
讀取1000張圖片,並將每一行和每一列的白點計數存於featrue1.txt中,代碼如下:
[input1, input2, input3] = textread(Char_Index_kuochong.txt,%d %d %s,1000, headerlines,1);indexFileName = input3;fid=fopen(feature1.txt,w+);for k=1:1000 A=imread(strcat(Char_Image_Binary/,char(indexFileName(k,1)))); t=graythresh(A); B=im2bw(A,t); [a,b]=size(B); C=zeros(1,a+b); for i=1:a for j=1:b if(B(i,j)==1) C(1,i)=C(1,i)+1; end end end for j=1:b for i=1:a if(B(i,j)==1) C(1,a+j)=C(1,a+j)+1; end end endendfclose(fid);
得到的feature1.txt部分內容如下圖:
2.2 區域密度,大小為8*8
顧名思義,將圖像分割成n塊區域,每塊區域的大小為8*8。注意n的取值,因為原圖像大小為47*92,會有除不盡的情況,為了保證圖像所有元素均被取到,故要採取進一法,代碼如下:
[input1, input2, input3] = textread(Char_Index.txt,%d %d %s,1000, headerlines,1);indexFileName = input3;fid=fopen(feature2.txt,w+);for k=1:1000 A=imread(strcat(Char_Image_Binary/,char(indexFileName(k,1)))); t=graythresh(A); B=im2bw(A,t); [a,b]=size(B); C=zeros(1,6*12); l=1; for i=1:8:a for j=1:8:b for m=i:min(i+7,a) for n=j:min(j+7,b) if(B(m,n)==1) C(1,l)=C(1,l)+1; end end end l=l+1; end endendfclose(fid);
2.3 字元左右上下與邊界的距離
從邊界開始走,當碰到字元時則返回所走過的距離(步數),假如某行或某列沒有字元元素,則返回最大值,代碼如下:
[input1, input2, input3] = textread(Char_Index.txt,%d %d %s,1000, headerlines,1);indexFileName = input3;fid=fopen(feature3.txt,w+);for k=1:1000 A=imread(strcat(Char_Image_Binary/,char(indexFileName(k,1)))); t=graythresh(A); B=im2bw(A,t); [a,b]=size(B); C=zeros(1,a*2+b*2); for i=1:a for j=1:b if(B(i,j)==1) C(1,i)=j-1; break; end if(j==b && B(i,j)==0) C(1,i)=b; end end end for i=1:a for j=b:-1:1 if(B(i,j)==1) C(1,a+i)=b-j; break; end if(j==1 && B(i,j)==0) C(1,a+i)=b; end end end for j=1:b for i=1:a if(B(i,j)==1) C(1,a*2+j)=i-1; break; end if(i==a && B(i,j)==0) C(1,a*2+j)=a; end end end for j=1:b for i=a:-1:1 if(B(i,j)==1) C(1,a*2+b+j)=a-i; break; end if(i==1 && B(i,j)==0) C(1,a*2+b+j)=a; end end endendfclose(fid);
2.4 每一行和每一列的線段數目
此處的線段數目指的是白色線段,注意判斷條件為第n步元素顏色與第n+1步元素不同且由黑轉白,代碼如下:
[input1, input2, input3] = textread(Char_Index.txt,%d %d %s,1000, headerlines,1);indexFileName = input3;fid=fopen(feature4.txt,w+);for k=1:1000 A=imread(strcat(Char_Image_Binary/,char(indexFileName(k,1)))); t=graythresh(A); B=im2bw(A,t); [a,b]=size(B); C=zeros(1,a+b); for i=1:a for j=1:b-1 if(B(i,j)~=B(i,j+1) && B(i,j+1)==1) C(1,i)=C(1,i)+1; end end end for j=1:b for i=1:a-1 if(B(i,j)~=B(i+1,j) && B(i+1,j)==1) C(1,a+j)=C(1,a+j)+1; end end end end fclose(fid);
2.5 區域密度,大小為4*4/6*6
原理同2.2,只不過大小改為4*4和6*6,代碼就不上了,感興趣的同學可以自己動手寫下,有問題留言,大家一起討論。
3.訓練模型與測試
這裡用的是SVM演算法,而且還要用到台灣大學林智仁(Lin Chih-Jen)教授等開發設計的一個簡單、易於使用和快速有效的SVM模式識別與回歸的libsvm3.2.2軟體包。該軟體包的安裝及用法我就不多做介紹了,利用好Google可以自己解決。然後讀取之前提取的任一種特徵作為數據集,隨機選取800個數據為訓練集,剩下的200個數據為測試集。注意,當選完訓練集和測試集後,記得存儲,以便保證後面調參的有效性。
接下來選取核函數,當我們不知道那種核函數效果好的時候,通常採用交叉驗證法,來試用不同的核函數,誤差最小的即為效果最好的核函數。我分別測試了多項式核函數、高斯徑向基核函數、sigmoid核函數三種,最終結果是多項式核函數效果最好,代碼如下:
feature = textread(feature2.txt,%d,delimiter, ,); feature = reshape(feature,[73 1000]);feature = feature;feature = feature(:,2:end);classification_num = 13;allclass = [10 11 12 20 22 25 26 28 30 31 32 33 34];indexInfo = [京 渝 鄂 0 2 5 6 8 A B C D Q];[~, class, name] = textread(Char_Index.txt,%d %d %s,1000, headerlines,1);train_num = 800;selection_index = (randperm (1000) <= train_num);save selection_index.mat selection_index;model = svmtrain( class(selection_index,:),feature(selection_index,:),-t 1 -d 3 -g 0.01 -r 2);[predict_label, accuracy, dec_values] = svmpredict(class(~selection_index,:),feature(~selection_index,:), model);
4.模型優化
當測試完6個特徵後發現準確率最高為98.5%,此時你會心想到底能否達到100%?該採取什麼方法來優化模型?這裡便引入特徵融合的概念,本案例用的也是其中最簡單的一種,即將前面提取的6種特徵拼接在一起,實現特徵互補,降低單一特徵固有缺陷的影響。實驗證明,該方法是有效果的,最後識別準確率100%。
前文還提到人工校正能否改為全自動的問題,其實思路很簡單,人工校正的目的就是將車牌字元全部成為黑底白字,便於模型識別正確分類,那如果將模型稍微改進下,能將黑底白字和白底黑字的字元都能識別並正確分類不就解決了?感興趣的同學可以自己試試。
5.總結
- 前期的數據處理和特徵工程很重要,套用一句廣泛流傳的名言:數據和特徵決定了機器學習的上限,而模型和演算法只是逼近這個上限而已
- 本案例中使用的是SVM處理多分類問題中的1對1,即在任意兩個樣本之間設計一個SVM,最後通過投票選擇最終結果
- 特徵融合可以有效解決單一特徵固有缺陷的影響,提高準確率
- 本案例還需改善,例如對數據歸一化,這對無論ML還是DL都是很重要的思想
附上完整代碼鏈接
lpdsdx/License-plate-character-Recognition
註:轉載、翻譯請直接私聊本人,經本人同意後方可進行轉載。
推薦閱讀:
※Tensorflow object detection API之InvalidArgumentError: image_size must contain 3 elements[4]
※視覺追蹤之meanshift
※DenseNet:比ResNet更優的CNN模型
※四軸飛行器結合OpenMV和OpenCV實現定點、循線尋線、跟蹤
※Python徒手實現識別手寫數字—圖像識別演算法(K最近鄰)