這個是去年完成一個課程大作業時利用課餘時間完成的matlab車牌識别程式,雖然大學階段也接觸過《數字圖像處理》這門課,但是當時的學習也停留在了比較淺的階段,又一直對這個課題挺感興趣的,于是送出題目時就報了車牌識别。
很早之前的東西了,相對于現在的智能算法以及計算機視覺相關方面的研究,肯定是不夠看的,不過這樣接觸一下也是挺有意思的。
文章目錄
-
- 正文
-
- 基本步驟
- 具體内容
-
- 1.準備工作
- 2.根據HSV色彩定位法定位車牌
- 3.根據Radon變換理論調整傾斜角度
- 4.圖像預處理
- 5.字元分割
- 6.字元識别
- 7. 輸出結果實作編号
- 結論
- 結尾
- 歡迎通路我的個人網站 [http://www.josu.top/](http://www.josu.top/)
正文
什麼背景了意義了就不多說了,網上一大堆,這裡直接進入正題:
首先,當初一開始也沒想從頭開始,先在網上找了相關的資料,其中也參考了csdn上的以下資源:
- https://download.csdn.net/download/j_m_chen/10572194
- https://download.csdn.net/download/mrlambert/10192471
- https://download.csdn.net/download/weixin_45062596/11387713
- https://www.cnblogs.com/asks/p/4372736.html
以及一個開源的中文車牌識别系統 EasyPR,一開始的時候想嘗試來着,車牌搜集資料時,用了接近兩天的時間,嘗試在64維WIN10以及Visual Studio 2019 –OpenCV 3.48環境下,調試前面第一個具有學習網絡的中文車牌識别系統。
從配置OpenCV程式設計環境→用小程式測試→下載下傳開源項目→學習手冊→載入項目→配置參數→消錯補缺→回溯OpenCV版本→配置調試→回溯VS版本→交叉變量調試→陷入調試死循環。其間不斷查資料調bug,不斷地嘗試,最後還是放棄深入學習了。不過作者本身的意思路是沒有問題的,在matlab上也可以實作準确率較高的測試。
基本步驟
大體上,車牌識别可以分為以下幾個基本環節:
- 圖像預處理
- 車牌初步定位
- 圖像水準、垂直傾斜角度調整
- 字元分割及歸一化
- 字元識别顯示結果
具體内容
要是需要上面相關文字叙述的話,也可以在網上找到很多例子,這裡直接講我覺得還比較實用的算法思想,完整的代碼會在最後給出。
1.準備工作
搜集整理了不同樣式共50張車牌圖檔,統一編号存放待用:
在程式中實作手動選擇測試樣本,使得在程式運作後,能夠主動打開視窗要求選取需要測試的圖檔,并且生成對應的展示視窗,具體的語句不再較長的描述,原本的注釋加上我後來學習時的看懂問題應該不大
%景象比對與目辨別别大作業程式
%2019年12月13日
%Jonny Su
%根據hsv色彩定位法定位車牌,将圖像轉為HSV模型,擷取每個像素點的HSV值,判斷是否為藍色像素點,統計每行每列藍色像素點的個數,進而擷取車牌位置
%根據該位置對原始彩色圖像進行剪裁,并進行二值化,濾波、去點處理,為字元分割做準備
%按順序切割出7個字元,并儲存為7張圖檔
%分别提取圖檔和模闆進行比較,将比較結果最接近的确定為對應模闆,并最終顯示車牌識别結果
cd D:\AEEEE\Course\圖像處理&景象比對\大作業\program\景象比對\測試圖檔 %轉換到自己的路徑下
% path(path,'測試圖檔') %修改檔案的搜尋路徑
[pictureName] = uigetfile('*.jpg', '請選擇圖檔'); %手動選取,更直覺
tic; %開始計時
I=imread(pictureName); %讀入車牌照片
A=isstrprop(pictureName,'digit'); %存儲具有編号資訊的位置
B=pictureName(A); %提取編号資訊
C=str2double(B); %将編号轉換為字元串
figure(1),imshow(I),title(['這是',num2str(C),'号樣本的原圖']); %繪圖,并且标注是幾号測試對象
代碼效果:
2.根據HSV色彩定位法定位車牌
将圖像轉為HSV模型,擷取每個像素點的HSV值,判斷是否為藍色像素點,統計每行每列藍色像素點的個數,進而擷取車牌位置。
- 函數imread支援多數流行的圖像/圖形格式,包括JPEG、JPEG2000和TIFF。使用函數imread可将圖像讀入MATLAB環境,該函數的基本文法為
imread(‘filename’)
- 使用im2double函數可将圖像資料格式轉為double類。對應的值會歸一化到範圍[0,1]。
%% %%%%根據hsv色彩定位法定位車牌%%%%
cd D:\AEEEE\Course\圖像處理&景象比對\大作業\program\景象比對\字元模闆
Image=im2double(I); %把圖像資料類型轉換為double類型
Image=rgb2hsv(Image); %将RGB圖像轉化為hsv模型,H色相,S飽和度,V亮度
[y,x,~]=size(Image);
Blue_y = zeros(y, 1);
p=[0.56 0.71 0.4 1 0.3 1 0]; %藍色點範圍
for i = 1 : y
for j = 1 : x
hij = Image(i, j, 1); %取出每個像素點的H、S、V值
sij = Image(i, j, 2);
vij = Image(i, j, 3);
if (hij>=p(1) && hij<=p(2)) &&( sij >=p(3)&& sij<=p(4))&&(vij>=p(5)&&vij<=p(6)) %若像素點的HSV處于藍色的HSV範圍
Blue_y(i, 1) = Blue_y(i, 1) + 1;%統計每行的藍色像素點數目
end
end
end
[~, MaxY] = max(Blue_y);%擷取藍色像素點最多的行号
運作結果:
3.根據Radon變換理論調整傾斜角度
Radon變換的本質是将原來的函數做了一個空間轉換,即,将原來的XY平面内的點映射到AB平面上,那麼原來在XY平面上的一條直線的所有的點在AB平面上都位于同一點。記錄AB平面上的點的積累厚度,便可知XY平面上的線的存在性。
詳細的數學理論不再贅述可以參考這篇文章, 總結一句來說,randon變換就是在其不傾斜的時候水準軸上的各點的線積分。
- **水準傾斜角度的調整:**先将圖像逆時針旋轉90°,當對于傾斜的圖像進行radon變換之後,變換之後的結果在其傾斜角度的位置表現出最大的落差。是以,可以通過一階微分函數對其進行求導然後求出其絕對值的累加和,尋找得到最大值的角度就是索要求得角度,再通過旋轉變換imrotate得到修正後的圖形
- **豎直傾斜角度的調整:**對于豎直角度,我們采用和水準角度一樣的方式,求radon變換,然後求其一階導數絕對值的累加和,最大值就是我們所要求的傾斜角度,不過和水準不一樣的是,豎直方向其實是同一行的元素之間的錯位偏移
%% %%%%車牌傾斜角度的調整%%%%
% pictureOut = rotate(I2)
% I2= rgb2gray(I2);
%水準方向調整
T=affine2d([0 1 0;1 0 0;0 0 1]);
pictureTr=imwarp(I2,T); % 圖像轉置,順時針旋轉90°調整水準方向
theta = -20 : 20; %設定傾斜角度的範圍
r1 = radon(pictureTr, theta); %radon變換确定傾斜角
result1 = sum(abs(diff(r1)), 1); %求出行倒數絕對值的累加和,最大的對應傾斜角
rot1 = find(result1==max(result1))-21;
pictureRo = imrotate(I2, rot1);
% figure, imshow(pictureRo), title('調整水準角度之後的圖像');
運作結果:
4.圖像預處理
- 根據如下圖所示的實際車牌比例,來去除邊框。在程式中設定的數值則依據圖中所标記的标準數值
- 使用rgb2gray函數可将圖像轉換為灰階圖。
- 使用bwareaopen函數,可以删除二值圖像中面積小于一個定值的對象,預設情況下使用8鄰域。
程式框圖:
邊界校正:
%% %%%%字元分割前的圖像處理%%%%
%邊界校正
% [y,x,z]=size(pictureLo);%I2有y行x列
[y,x,z]=size(I2);%I2有y行x列
PX1=round(x*5/440); %根據實際車牌比例,将邊框部分去除 經驗值
PX2=x-round(x*5/440);
PY1=round(y*16/140);
PY2=y-round(y*16/140);
fprintf('校正後 左邊界=%d、右邊界=%d、上邊界=%d、下邊界=%d',PX1,PX2,PY1,PY2);
%彩色圖像車牌部分截取
dw=I1(PY1:PY2,PX1:PX2,:);%對邊界進行截取
% dw= pictureLo;
figure(4),imshow(dw),title(['對',num2str(C),'号進行邊界校正結果']);
得到灰階圖:
imwrite(dw,'dw.jpg');%把截取後的彩色圖像新建立一張圖檔,命名為dw
a=imread('dw.jpg');
b=rgb2gray(a); %灰階圖像
% b=a; %灰階圖像
imwrite(b,'1.車牌灰階圖像.jpg');
得到二值圖像:
g_max=double(max(max(b)));%得出剪裁後灰階圖矩陣的最大值,并變成雙精度浮點型
g_min=double(min(min(b)));%得出剪裁後灰階圖矩陣的最小值,并變成雙精度浮點型
T=round(g_max-(g_max-g_min)/2); % T為二值化的門檻值,round用于取整
d=(double(b)>=T); % d:二值圖像
imwrite(d,'2.車牌二值圖像.jpg');
figure(5),subplot(3,1,1),imshow(d),title([' ',num2str(C),'号車牌的二值圖像']);
均值濾波:
% 濾波
h=fspecial('average',3);%建立一個二維濾波器,average是類型,3是參數
d=im2bw(round(filter2(h,d)));%filter2進行濾波處理,im2bw使用門檻值變換法把灰階圖像轉換成二值圖像
imwrite(d,'4.均值濾波後.jpg');
去除原點:
%去點處理
d=cut(d); %子程式内
[m,n]=size(d);
d(:,round(n*122/430):round(n*137/430))=0;%去除中間的點
d=bwareaopen(d,65);%用于删除二值圖像中面積小于一個定值(此處為65)的對象,預設情況下使用8鄰域
figure(5),subplot(3,1,2),imshow(d),title([' ',num2str(C),'号車牌的去點處理']);
邊緣剪切:
%上下邊框處理,找到上下邊界處像素值小于20的行,并将整行設為零
s=zeros(1,m);
i=1;
while i<=m
s(i)=sum(d(i,:));
i=i+1;
end
j=0;c=zeros(1,m);%c矩陣用于記錄下邊框處像素值小于20的行,用于确定邊框與字元間空隙的位置
while j<m
while s(j+1)>20&&j<m-1
j=j+1;
end
if j<round(m*12/140) %如果是上邊框
d((1:j),:)=0;
else j>round(m*115/140)%如果是下邊框
c(j)=j;%将下邊界進行中像素值小于20的行記錄下來
d(j,:)=0;
end
j=j+1;
end
jj=round(m/2);%這裡是為了找到下邊界進行中,被記錄下來的最小行(即邊框和字元間空隙的上沿),是以從中間開始往下找,直到找到的第一個非零數
while c(jj)==0
jj=jj+1;
end
d((jj:m),:)=0;%将這一行以下皆設為0
d=cut(d);
figure(5),subplot(3,1,3),imshow(d),title([' ',num2str(C),'号車牌的上下邊框處理']);
imwrite(d,'5.切割前.jpg');
運作結果:
5.字元分割
第一個M函數cut()函數:
第二個子函數extract()函數:
剩餘字元的分割,直接依次調用extract函數即可。
部分程式:
%% %%%%字元分割%%%%
[~,n]=size(d);
% 切割出 7 個字元
% 第一個字元,
y1=n*15/440;y2=0.25;flag=0;word1=[];
while flag==0
[m,~]=size(d);
left=1;wide=0;
while sum(d(:,wide+1))~=0 % 找到像素和為零的列
wide=wide+1;
end
if wide<y1 % 若這一列的位置在圖像左側前10列之内則認為是左側幹擾
d(:,(1:wide))=0;%把左側幹擾全部變為0,并将其切割掉
d=cut(d);
else
temp=cut(imcrop(d,[1 1 wide m]));%将一個字元剪切下來
[m,~]=size(temp);
all=sum(sum(temp));%第一個字元的像素值總和
two_thirds=sum(sum(temp((round(m/3):2*round(m/3)),:)));%單個字元中段1/3圖像的像素值總和
if two_thirds/all>y2%驗證是否為字元
flag=1;word1=temp; %擷取第一個字元word1
end
d(:,(1:wide))=0;d=cut(d);%當提取第一個字元結束或者不滿足上述條件時,将該區域變為0,并切除
end
end
% 分割出第二個字元
[word2,d]=extract(d);
運作結果:
6.字元識别
最常用的就是模闆比對法,肯定沒有EasyPR那樣的準确度和智能,但是好處是不需要太多的樣本
- 首先建立标準的字元模闆庫(共65個字元模闆),字元尺寸統一使用 。字元模闆統一命名為“字元模闆”+“字元代碼”,如字元模闆1、字元模闆鄂、字元模闆A等.
- 建立自動識别字元的代碼表,采用字元類數組char。
- 在進行比對前必須先進行字元圖像歸一化,使圖像字元大小跟模闆圖像大小一緻。使用imresize函數可對圖像進行縮放處理。此處縮放與字元模闆保持一緻,統一使用 。
- 使用corr2函數計算分割出的每個字元與字元模闆庫中的每個字元的相關系數大小,并儲存在一個數組中。
- 使用find函數找出相關系數最大值所在的位置,進而按照順序,在自動識别字元代碼表中找到對應的字元,并輸出便實作了字元的識别。
字元模闆:
程式框圖:
建立初始字元串變量:
%% %%%%字元識别%%%%
%建立自動識别字元代碼表
liccode=char(['0':'9' 'A':'H' 'J':'N' 'P':'Z' '藏川鄂甘贛桂貴黑滬吉冀津晉京遼魯蒙閩甯青瓊陝蘇皖湘新渝豫粵雲浙']); %建立自動識别字元代碼表
SubBw2=zeros(40,20);
運作結果:
7. 輸出結果實作編号
在這個程式中,因為樣本本身比較多,雖然樣本自己帶有固定格式的編号,一開始也是手動選取的,但是難免會有程式跑了一會然後發現忘了自己在測那張圖的時候,而且在寫報告展示的時候如果能直接從結果中看到測試的編号,會省事不少,其實在這個程式的修改中自己也有穿插這方面的實作代碼。
在第一開始的準備工作中,調用的測試樣本不同格式的資訊也被存儲到變量A ,B ,C 當中:
[pictureName] = uigetfile('*.jpg', '請選擇圖檔'); %手動選取,更直覺
tic; %開始計時
I=imread(pictureName); %讀入車牌照片
A=isstrprop(pictureName,'digit'); %存儲具有編号資訊的位置
B=pictureName(A); %提取編号資訊
C=str2double(B); %将編号轉換為字元串
之後在繪圖的過程中,隻需要在title指令裡加入特定的語句,就可以實作每個測試樣本都有帶相應編号的标題,友善測試:
figure(1)
imshow(I);
title(['這是',num2str(C),'号樣本的原圖']); %繪圖,并且标注是幾号測試對象
不過這裡需要注意title後面的格式比平時用的多了一個括号[],以及num2str©以外的内容都用單引号包覆再用逗号分開。
運作結果:
以上就是程式中比較關鍵的内容
結論
車牌識别系統的關鍵技術是車牌區域定位,字元分割和字元識别。
運用基于藍色象素點統計特性的方法對車牌是藍色的車牌進行定位,對車牌定位準确率較高。
本程式所采用的二次水準投影分析和門檻值技術有效檢測了車牌圖像的上下左右邊框、旋轉角度,準确實作的車牌字元的分割,對多個車牌進行實驗,均有較高的正确率。
模闆比對法雖然識别率低,但實作簡單,計算量小,隻需計算模闆字元與待識别字元的相關系數的大小進行比較即可,而且車牌字元是有阿拉伯數字,英文大寫字母,還有部分漢字,雖有字庫量不大,字元較規整時對字元圖像的缺損、污迹幹擾适應力強且識别率較高。
本次組對50幅包含車牌的圖像進行了程式驗證,其中完全識别無誤的有28幅,大部分能識别的有8幅,不能識别的有14幅。識别率在72%左右,運作結果較理想。不過有條件的話,可以進一步改進,提高識别率。
程式之間的對比,其中前面的幾個就是從網上找的其他版本的車牌識别程式,在文章一開頭也做了聲明,都調通後大緻分了下類然後做了對比:
結尾
學習心得:
- 本身因為專業和導師,做控制方向比較多,但是此次的經曆讓我對圖像處理和計算機視覺方面也有了一定的了解;
- 在程式設計過程中,重要的是心中的邏輯性、符合計算機語言處理的思考,以及平日裡真正的操作和積累;
- 在網絡上有很多值得學習和讨論的新的算法和資源,如何去将自己的時間抓住,利用好能夠得到的資源提升自身的能力,跟緊前沿的技術和算法的進步,是值得學習的點。
寫在最後:程式有借鑒也有自己思考的東西,僅供參考,如果前面聲明還不夠的話參考程式的作者可以私聊溝通。主要目的是為了互相學習共同進步,也激發新的思考。