详解车牌识别Matlab算法
首先是项目配置:
第一行:functionvarargout = LicenseRecognition(varargin)
分别表示主函数的输出、函数名称、函数输入
在% Begin initialization code - DO NOT EDIT
% End initialization code - DO NOT EDIT
这两行注释符之前的代码是编辑菜单时自动生成的,不需要修改
functionLicenseRecognition_OpeningFcn(hObject, eventdata, handles, varargin)
%系统自动生成
handles.output = hObject;%系统自动生成
——————————————————————————————————
%以下代码为手工添加,表示使用handles结构体来保存图形界面中各种对象的句柄,或者中间结果,这些句柄或者中间结果在创建图形对象或运算中产生,需要在以后的回调函数中多次用到,所以这里的作用类似于全局变量的作用,用来进行数据的传递。
handles.imgIn = [];
handles.imgOut = [];
handles.flag = 0;%判断标志
handles.pos = [];%图形对象所在位置
handles.lef = 1;
handles.top = 1;
handles.wid = 0;
handles.hig = 0;
——————————————————————————————————
% Update handles structure 自动生成,用来将上面定义的数据
guidata(hObject, handles);
%自动生成,定义输出
% — Outputs from this function are returned to the command line.
functionvarargout = LicenseRecognition_OutputFcn(hObject, eventdata, handles)
varargout{1} = handles.output;
下面再讲解下车牌识别算法的每个步骤
-
打开读取文件
functionMenu_File_Open_Callback(hObject, eventdata, handles)%该行为自动生成
[fname,pname] = uigetfile({‘.bmp;.jpg;.tif’,'All IMAGE Files (.bmp, *.jpg, *.tif)’},‘Open an input image’);%弹出打开文件的对话框
strPath = [pname fname];%设置文件名和路径名
if~isempty(strPath)
[img,cmap] = imread(strPath);%读取文件数据
[hig,wid,page] = size(img);%获得图像大小
ifpage == 3 %假设图像是RGB彩色
imgIn = rgb2gray(img);%转换为灰度图象
else
imgIn = img;
end
handles.imgIn = imgIn;%将结果赋给全局变量,以便交给下一个函数处理
handles.flag = 1 %标志为1表示数据已经读取
% pos = get(handles.mainFrm,‘Position’);
figure(handles.mainFrm);%创建图形界面,用来显示图像
set(handles.mainFrm,‘Position’,[400,300,wid,hig]);%设置该图形界面的显示位置
image(imgIn);%显示图像,其和imshow功能类似,但带有默认的调色板
colormap(gray(256));%将其默认调色板更改为256级灰度
handles.pos = [400,300,wid,hig];%将图形界面的位置保存到hdl结构体中
guidata(hObject,handles);%保存图形界面数据
set(handles.Menu_EdgeDetect,‘Enable’,‘On’);%设置下一个菜单为可点击的亮色
end -
完成菜单退出功能
functionMenu_File_Exit_Callback(hObject, eventdata, handles)
ifhandles.flag == 1
str = {‘Are you sure about exiting the program?’,‘If so, the data will be lost!’};
ret = questdlg(str,‘Warning’);
switchret
case’Yes’
delete(gcf);
case’No’,‘Cancel’
return;
end
end -
边缘检测功能实现
functionMenu_EdgeDetect_Callback(hObject, eventdata, handles)
if~isempty(handles.imgIn) %检查图像数据是否非空
imgIn = double(handles.imgIn); %在处理之前将图像数据类型转换为double型
% BW = uint8(255 * edge(handles.imgIn,‘roberts’));
% handles.imgOut = BW;
% guidata(hObject,handles);
% set(handles.mainFrm,‘Position’,handles.pos);
% image(BW);
% colormap(gray(256));
filt = fspecial(‘sobel’); %构建soble算子的模板
horz = conv2(imgIn,filt,‘same’);%进行卷积运算
% Cs = conv2(A,B,‘same’) Cs is the same size as A: 3-by-3
vert = conv2(imgIn,filt’,‘same’);
imgOut = uint8(max(horz,vert));% 取水平和垂直方向运算结果的最大值
% imgOut = uint8(sqrt(horz.^2 + vert.^2));
handles.imgOut = imgOut;
handles.flag = 1;
guidata(hObject,handles);
figure(handles.mainFrm);
set(handles.mainFrm,‘Position’,handles.pos);
image(imgOut);
colormap(gray(256));
set(handles.Menu_RoughLocate,‘Enable’,‘On’);
set(handles.Menu_FineLocate,‘Enable’,‘Off’);
set(handles.Menu_CharacterCutout,‘Enable’,‘Off’);
set(handles.Menu_LicenseRecognition,‘Enable’,‘Off’);
end -
车牌粗定位
% 车牌粗定位:根据车牌区域在水平方向灰度值具有明显频繁的跳变,可求边缘提取后水平方向的差分,然后进行水平方向上的投影,也即沿水平方向进行相邻像素差分值的累加,绘制的投影图横轴为图像高度,原点为左上角,纵轴即为沿水平方向的差分值累加和。
functionMenu_RoughLocate_Callback(hObject, eventdata, handles)
if~isempty(handles.imgOut)
imgIn = double(handles.imgOut);
[hig,wid] = size(imgIn);
diff_horz = zeros(hig,wid);%构建一个与图像大小一样的全为0的矩阵,存储水平方向相邻像素的差值
diff_horz = abs(imgIn(:,1:wid-1) - imgIn(:,2:wid));%类似第1列像素减第2列像素,第2列减第3列,依次减下去
cum_horz = sum(diff_horz’);%对图像矩阵先转置,再投影
figure;%绘制投影图形,横轴为1到图像高,纵轴为水平方向差分值累计和
bar(1:hig,cum_horz,‘r’);
title(‘horizontal projection’);
% diff_vert = zeros(hig,wid);
% diff_vert = abs(imgIn(1:hig-1,:) - imgIn(2:hig,:));
% cum_vert = sum(diff_vert);
% figure;
% bar(1:wid,cum_vert,‘b’);
% title(‘vertical projection’);
%通过观察投影图,给出车牌的大概位置是,左上角坐标为(85,225),宽度为60,高度为20。
lef = 85; top = 225; wid = 60; hig = 20;
handles.lef = lef; %将检测结果传递给hdl结构体,以传递到下一个微定位的函数
handles.top = top;
handles.wid = wid;
handles.hig = hig;
imgOut = uint8(imgIn(top:top+hig-1,lef:lef+wid-1));%按刚才给的位置大小提取出车牌,转换为无符号整型,保存到imgOut中
handles.imgOut = imgOut;
handles.flag = 1;
guidata(hObject,handles);
figure(handles.mainFrm);
set(handles.mainFrm,‘Position’,handles.pos);
image(imgOut);
colormap(gray(256));
axisoff;
set(handles.Menu_FineLocate,‘Enable’,‘On’);
set(handles.Menu_RoughLocate,‘Enable’,‘Off’);
set(handles.Menu_CharacterCutout,‘Enable’,‘Off’);
set(handles.Menu_LicenseRecognition,‘Enable’,‘Off’);
end -
车牌微定位
在粗定位结果的基础上,还需要把红色边框外的图像去掉,以进一步确定字符范围,缩减车牌的左右上下边界,以便后续字符处理。
functionMenu_FineLocate_Callback(hObject, eventdata, handles)
if~isempty(handles.imgOut)
imgIn = double(handles.imgOut);%传入粗定位的结果图像
[hig,wid] = size(imgIn);% 取图像大小
lef_tem = wid * ones(hig,1);% 构建一个hig行,1列的值全为1的矩阵,与wid相乘,矩阵值全部为wid
rig_tem = zeros(hig,1);%构建一个hig*1的0矩阵
fori = 1:hig
forj = 1:wid-1%从左到右扫描,遇到相邻像素的灰度值差值大于60时,停止扫描,记下列号,说明此列是车牌左边界
tem = imgIn(i,j+1) - imgIn(i,j);
iftem >= 60
lef_tem(i) = j+1;
break;
end
end
%从右向左扫描,遇到相邻像素的灰度值差值大于60时,停止扫描,记下列号,说明此列是车牌右边界
forj = wid : -1 : 2
tem = imgIn(i,j-1) - imgIn(i,j);
iftem >= 60
rig_tem(i) = j-1;
break;
end
end
end4
lef = min(lef_tem);%每一行都可以扫描得到一个左边界,取其中最小的
rig = max(rig_tem);%每一行都可以扫描得到一个右边界,取其中最大的
%这也是lef_tem、rig_tem在初始化定义的时候分别为ones全为1的矩阵和全为zeros 0的矩阵的原因
top_tem = hig * ones(wid,1);
bot_tem = zeros(wid,1);
forj = lef:rig
fori = 1:hig-1
tem = imgIn(i+1,j) - imgIn(i,j);
iftem >= 60
top_tem(j) = i+1;
break;
end
end
fori = hig : -1 : 2
tem = imgIn(i-1,j) - imgIn(i,j);
iftem >= 60
bot_tem(j) = i-1;
break;
end
end
end
top = min(top_tem); %按同样的方法找到上下边界,注意图像左上角为原点
bot = max(bot_tem);
handles.lef = handles.lef + lef - 1; %在原始图像上定位微定位后车牌的位置
handles.top = handles.top + top - 1;
handles.wid = rig - lef + 1;
handles.hig = bot - top + 1;
%将微定位后的图像数据取出来
imgOut = uint8(imgIn(top:bot,lef:rig));
handles.imgOut = imgOut;
handles.flag = 1;
guidata(hObject,handles);
figure(handles.mainFrm);
set(handles.mainFrm,‘Position’,handles.pos);
image(imgOut);
colormap(gray(256));
axisoff;
set(handles.Menu_CharacterCutout,‘Enable’,‘On’);
set(handles.Menu_RoughLocate,‘Enable’,‘Off’);
set(handles.Menu_FineLocate,‘Enable’,‘Off’);
set(handles.Menu_LicenseRecognition,‘Enable’,‘Off’);
end -
字符分割
functionMenu_CharacterCutout_Callback(hObject, eventdata, handles)
ifhandles.wid ~= 0
lef = handles.lef;%注意:这里是微定位后车牌在原始图像上的位置
top = handles.top;
wid = handles.wid;
hig = handles.hig;
%此处imgIn是在原始图像上取微定位后车牌位置区域的数据产生的
imgIn = double(handles.imgIn(top:top+hig-1,lef:lef+wid-1));
% maxvalue = max(imgIn());
% minvalue = min(imgIn());
% imgOut = ~(imgIn >= (maxvalue + minvalue+30)/2);
imgOut = ~JudgeAnalysis(imgIn); %对图像进行二值化,然后反转
[label,num] = bwlabel(imgOut); %对二值图像进行连通成分的标记
%对于以上两个中间变量的结果,大家可以在Matlab的workspace中查看其结果变化,理解
k = 0;
fori = 1:num %以下代码为找到像素总数小于10的连通成分,剔除,这里6个字符,但有7个连通成分,剔除其中较少像素的连通成分
tem = (label == i); %把所有label为i的像素统计出来
ifsum(tem()) <= 10
imgOut(find(label == i)) = 0;
k = k+1;
end
end
num = num - k; %减去剔除的连通成分的个数
proj_vert = sum(imgOut); % 对图像每一列求和,也即垂直投影
ifproj_vert(1) ~= 0 %如果第一列的投影不为0,说明第一列的像素中有字符
coordx(1) = 1;
k = 1;
else
k = 0;
end
%以下代码说明:如果第后一列像素为0,而前一列的像素不为0,则说明此处是字符的右边界
%如果前一列像素为0,后一列不为0,说明此处是字符的左边界
forj = 2:wid-1
tem1 = proj_vert(j-1) - proj_vert(j);
tem2 = proj_vert(j+1) - proj_vert(j);
if(proj_vert(j) == 0) & (tem1 >= 1)
k = k+1;
coordx(k) = j-1;
elseif(proj_vert(j) == 0) & (tem2 >= 1)
k = k+1;
coordx(k) = j+1;
end
end
%如果倒数第二列不为0,倒数第1列为0,则说明倒数第2列为边界
if(proj_vert(wid-1) ~= 0) & (proj_vert(wid) == 0)
k = k+1;
coordx(k) = wid-1;
end
%k代表总共有k条边界
k = length(coordx);
set(handles.mainFrm,‘Position’,handles.pos);
image(uint8(255*(~imgOut))); %将其反转回来在0-255之间显示
colormap(gray(256));
axisoff;
holdon;
forj = 1:k
x = coordx(j) * ones(1,hig);%将x的数目变成和y一样多,组成(x,y)坐标点对
y = 1:hig;
plot(x,y,‘b’);
holdon;
end
holdoff;
%根据上面所找边界线,对字符进行实际的分割
forj = 1:num
tem_wid(j) = coordx(2j) - coordx(2j-1) + 1; %每个字符由一对边界线分割而成,由右边边界线减去左边边界线的位置,得到的是此字符的宽度
tem = imgOut(:,coordx(2j-1):coordx(2j));%取出每一字符的实际数据
proj_horz = sum(tem’);%对该字符的行求累加值
fork = 1:hig %从上往下扫描,找出第j个字符的上边界
ifproj_horz(k) ~= 0
tem1 = k;
coordy(j,1) = tem1;
break;
end
end
fork = hig-1:1 %从下往上扫描,找出第j个字符的下边界
ifproj_horz(k) ~= 0
tem2 = k;
coordy(j,2) = tem2;
break;
end
end
tem_hig(j) = tem2 - tem1 + 1; %找出每一字符的高度
end
%以最大的宽和高来统一每个字符的大小
maxwid = max(tem_wid); %总共有6个字符,找出其中最宽的
maxhig = max(tem_hig); %总共有6个字符,找出其中最高的
fork = 1:num
lef = coordx(2k-1); rig = coordx(2k);
top = coordy(k,1); bot = coordy(k,2);
wid = rig - lef + 1; hig = bot - top + 1;
Norm_Char(1:maxhig,1:maxwid,k) = ones(maxhig,maxwid);%初始化为大小统一
Norm_Char(1:hig,1:wid,k) = ~imgOut(top:bot,lef:rig);%取出每一字符数据
end
handles.Norm_Char = Norm_Char;
handles.flag = 1;
guidata(hObject,handles);
% for k = 1:num
% figure;
% image(uint8(255*Norm_Char(:,:,k)));
% colormap(gray(256));
% axis off;
% end
set(handles.Menu_LicenseRecognition,‘Enable’,‘On’);
set(handles.Menu_RoughLocate,‘Enable’,‘Off’);
set(handles.Menu_FineLocate,‘Enable’,‘Off’);
set(handles.Menu_CharacterCutout,‘Enable’,‘Off’);
end -
字符识别
%读取事先定义好的模板,将分割后的字符与模板进行匹配,取相关系数最大的。
functionMenu_LicenseRecognition_Callback(hObject, eventdata, handles)
if~isempty(handles.Norm_Char)
Norm_Char = handles.Norm_Char;
template(:,:,1) = double(imread(‘FY10_0.bmp’));
template(:,:,2) = double(imread(‘FY10_1.bmp’));
template(:,:,3) = double(imread(‘FY10_3.bmp’));
template(:,:,4) = double(imread(‘FY10_F.bmp’));
template(:,:,5) = double(imread(‘FY10_M.bmp’));
template(:,:,6) = double(imread(‘FY10_P.bmp’));
num = size(Norm_Char,3); %返回第3维的个数
result = [];
fork = 1:num
str = recognition(Norm_Char(:,:,k),template);
result = [result str’ '];
end
msgbox({‘The recognition result is:’,result},‘Recognition of the license’);
end
%模板比对函数
functioncout = recognition(cin,template)
d=zeros(6,1);
fori = 1:6
d(i)=corr2(cin,template(:,:,i));%求相关系数
end
e = find(d == max(d));
switche
case1
cout =‘0’;
case2
cout =‘1’;
case3
cout =‘3’;
case4
cout =‘F’;
case5
cout =‘M’;
case6
cout =‘P’;
end