尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于MATLAB的车牌识别系统:从图像预处理到字符识别完整实现

基于MATLAB的车牌识别系统:从图像预处理到字符识别完整实现 做车牌识别项目十个人里有八个会先想到MATLAB原因很简单图像处理工具箱成熟几行代码就能完成滤波、边缘检测这些底层操作可以把精力集中在算法逻辑本身。这套“基于MATLAB的车牌识别含子程序”整体结构分四层主程序负责调用流程子程序分别处理图像预处理、车牌定位、字符分割和字符识别。整套代码在Matlab R2020b以上版本均能直接运行只依赖Image Processing Toolbox不需要额外装深度学习工具箱对绝大多数学生和嵌入式入门工程师来说是门槛最低的一套方案。如果你的目标是毕业设计、课程大作业或者想快速跑通一套完整的OCR流程这套结构非常适合。它天然拆分成独立子程序任何一个环节都能单独调试也方便替换成你自己的想法比如把模板匹配换成BP神经网络或者版CNN只需要改一个子程序的内部实现其余代码不用动。1. 系统整体设计与识别流程1.1 车牌识别的核心链路车牌识别这件事听起来高大上拆开看就是四步图像预处理、车牌定位、字符分割、字符识别。整套系统按这个链路组织成四个子程序主程序只负责按顺序调用并显示中间结果。先说说为什么这样拆。把功能拆成子程序不是单纯为了代码好看。车牌识别最大的痛点是场景变化太多光照不一样、车牌角度不一样、图片清晰度不一样。如果你把所有逻辑写在一个脚本里一旦定位效果不好你要在几百行代码里找原因。拆成子程序以后每个模块独立测试比如定位子程序输入是原始图像输出是一个裁剪好的车牌区域你单独拿十张不同的图测这个函数定位不准就只调这个函数效果好了再往下走。整个识别流程描述如下读取原始图像转成灰度图做中值滤波去噪用边缘检测和形态学闭运算把车牌区域从背景中提取出来对定位到的车牌区域做倾斜校正、二值化然后按投影法把每个字符切分出来归一化每个字符的尺寸用模板库做匹配输出最终车牌号码这个流程是传统图像处理路线的经典组合虽然现在的深度学习方法端到端识别已经很强了但对于学习图像处理基础和算法原理来说这套流程仍然是最好的入门路径。每一步的中间结果都可视化输出你能清楚看见图像在每一层发生了什么变化。1.2 为什么用MATLAB实现而不是直接用现成库如果你只是想要一个能用的车牌识别结果OpenCV加一个现成的模型半小时就能搞定没必要用MATLAB。但这个项目的目的不是“用”而是“懂”。MATLAB在图像处理教学和算法验证上的优势是它的中间过程可视化能力极强。你可以在每个子程序里随时暂停看当前图像长什么样、阈值取多少合理、某个形态学操作改变了什么。另外MATLAB的矩阵操作让图像处理代码极度紧凑。比如rgb2gray一句转换灰度edge一句做边缘检测bwareaopen一句去掉小面积连通域。同样的功能用C写光图像读取和内存管理就得几十行。对于验证算法思路来说MATLAB的调试成本是最低的。客观来说MATLAB的缺点也很明显运行速度比C慢部署不方便商业授权贵。所以这套代码定位是教学验证和毕设原型如果你想上生产环境后续可以把算法移植到C或者Python但算法原理和参数是你在这套代码里调出来的这个积累是通用的。2. 核心子程序设计定位、分割、识别2.1 车牌定位子程序从整幅图中找车牌车牌定位是整个系统里最影响成败的一步。车牌没找对后面做得再好都白费。常用的方法是用车牌区域的边缘特征和颜色特征结合。这里采用的方案是先把图像转灰度做中值滤波去除噪声然后用Sobel算子做边缘检测。因为车牌区域字符密集会产生大量边缘加上车牌边框也是明显的矩形边缘所以车牌区域在边缘图上呈现为一个高密度高亮的矩形块。再用imclose做形态学闭运算把字符间的空隙连接起来形成连通域通过计算每个连通域的长宽比例和面积筛掉明显不是车牌的区域。核心代码如下function plateImg locatePlate(grayImg) % 车牌定位子程序 % 输入灰度图像 % 输出裁剪并校正后的车牌图像 % 中值滤波去除噪声 denoisedImg medfilt2(grayImg, [3 3]); % Sobel边缘检测 edgeImg edge(denoisedImg, sobel); % 形态学闭运算连接字符区域 se strel(rectangle, [17, 3]); closeImg imclose(edgeImg, se); % 填充空洞增强区域连通性 filledImg imfill(closeImg, holes); % 提取连通域并筛选候选区域 cc bwlabel(filledImg); stats regionprops(cc, BoundingBox, Area); % 车牌长宽比一般在2.5到4.5之间 % 面积也需大于一定阈值 candidate []; for i 1:length(stats) w stats(i).BoundingBox(3); h stats(i).BoundingBox(4); area stats(i).Area; ratio w / h; if ratio 2.5 ratio 4.5 area 1000 candidate floor(stats(i).BoundingBox); break; end end if isempty(candidate) error(未定位到车牌区域请调整滤波参数或边缘检测阈值); else plateImg imcrop(grayImg, candidate); end end这段代码里的几个参数值得细说。Sobel边缘检测比Canny更适合这里因为Canny产生的边缘太细碎闭运算连接后容易形成很多小碎块干扰候选区域筛选。结构元素se选择[17, 3]意思是横向17像素、纵向3像素的矩形刚好够把字符边缘连成块又不会把相邻字符块合并得太大。实际调试时可以根据图像分辨率按比例缩放图像越大会话结构元素尺寸也要相应放大。2.2 字符分割子程序投影法切割定位到车牌区域以后第二步是字符分割。车牌的每个字符之间有一段空白间隔这个特征让投影法成为最常用的分割方法。原理很简单把二值化后的车牌图像在垂直方向做投影也就是逐列统计白色像素个数字符区域的投影值高字符间隔处投影值为零或接近零。找到这些“波谷”位置就能把字符一个个切开。实际处理时分割前要做两个关键操作。一是倾斜校正车牌拍摄角度稍有偏斜垂直投影的波谷就会被字符边缘污染导致切割位置偏移。这里用Hough变换检测车牌上下边框的直线倾斜角度然后把图像旋转回来。二是二值化这里用Otsu自适应阈值因为不同环境下车牌图像的灰度分布差异很大固定阈值完全不靠谱。function chars segmentChars(plateImg) % 字符分割子程序 % 输入定位后的车牌灰度图 % 输出切割好的字符图像cell数组 % 二值化 level graythresh(plateImg); bwImg imbinarize(plateImg, level); % 去除上下边框干扰统计水平投影去掉上下边缘区域 horiProj sum(bwImg, 2); rowIdx find(horiProj size(bwImg, 2) * 0.5); if ~isempty(rowIdx) bwImg(rowIdx(1):rowIdx(end), :) 1; bwImg ~bwImg; % 车牌字符为白底黑字反转 end % 垂直投影 vertProj sum(bwImg, 1); % 找字符区域的边界投影值由0转正处为起点由正转0处为终点 inChar false; charBounds []; for col 1:length(vertProj) if vertProj(col) 0 ~inChar startCol col; inChar true; elseif (vertProj(col) 0 || col length(vertProj)) inChar endCol col; inChar false; if endCol - startCol 5 % 排除单个噪点 charBounds [charBounds; startCol, endCol]; end end end % 依宽度排序去除异常区域 % 实际车牌字符宽度接近可筛选掉过窄和过宽的区间 chars cell(1, size(charBounds, 1)); for i 1:size(charBounds, 1) chars{i} bwImg(:, charBounds(i, 1):charBounds(i, 2)); end end字符分割有一个细节容易忽略车牌上有边框和铆钉如果不去除投影时会多出几个“伪字符”。上面代码通过水平投影把包含大面积横向连续白色像素的行先统一处理掉这一步能在很大程度上消除边框干扰。铆钉位置通常和字符在同一行面积小通过设定endCol - startCol 5就能把宽度不足的噪声区域过滤掉。2.3 字符识别子程序模板匹配法字符识别是整个流程的最后一步这里用模板匹配实现。思路是先准备一套0到9和A到Z的字符模板把分割出来的字符和每个模板做相似度计算选择最相似的那个作为识别结果。模板匹配的核心是归一化分割出来的字符尺寸不一样必须统一缩放到和模板一样的尺寸再计算相似度。常用的相似度度量有绝对差平均Mean Absolute Difference和归一化互相关Normalized Cross-Correlation。推荐用归一化互相关因为对照明显更敏感抗噪能力强一点。function result recognizeChar(charImg, templateSet) % 字符识别子程序 % 输入分割后的字符图像模板库 % 输出识别字符 % 统一缩放到42*24 charImg imresize(charImg, [42, 24]); charImg charImg(:); bestScore -1; bestChar ; for i 1:length(templateSet) tpl templateSet(i).img; tpl tpl(:); % 归一化互相关 score corr2(double(charImg), double(tpl)); if score bestScore bestScore score; bestChar templateSet(i).char; end end result bestChar; end这里模板库的结构体templateSet每个元素包含两个字段char是字符本身img是对应的二值化模板图像。制作模板时需要注意取样的图片最好用和你实际摄像头同类型的车牌图像字体越接近识别率越高。模板数量一般35个左右数字10个字母24个排除I和O的歧义情况省字特殊汉字如“京”“苏”“鲁”等需要单独加。3. 主程序调用流程与运行效果3.1 完整主程序与子程序接口约定主程序的核心工作是把定位、分割、识别三个子程序串起来并在每个节点显示中间结果便于观察算法表现。% 主程序车牌识别系统调用示例 clear; close all; clc; % 读取图像 img imread(car.jpg); % 步骤1灰度化 定位子程序 if size(img, 3) 3 grayImg rgb2gray(img); else grayImg img; end plateImg locatePlate(grayImg); % 步骤2字符分割子程序 chars segmentChars(plateImg); % 步骤3加载模板库并逐个识别 load(template_set.mat); % 变量名为templateSet plateResult ; for i 1:length(chars) c recognizeChar(chars{i}, templateSet); plateResult [plateResult, c]; end % 显示结果 figure; subplot(2, 2, 1); imshow(img); title(原始图像); subplot(2, 2, 2); imshow(plateImg); title(定位到的车牌); subplot(2, 2, 3); imshow(cell2mat(imresize(chars{1}, [42, 24]))); title(分割效果示意); subplot(2, 2, 4); text(0.3, 0.5, plateResult, FontSize, 24); title(识别结果); disp([识别结果: , plateResult]);子程序之间通过两个隐式约定解耦。第一locatePlate的输出必须是灰度图有彩色信息在里面会干扰后续二值化所以主程序在调用前强制转灰度。第二segmentChars输出的chars是一个cell数组每个元素是一张二值化后的字符图recognizeChar对输入图像是二值还是灰度并不敏感因为内部会通过imbinarize再处理一次不过建议在分割阶段就输出二值图减少后续转换环节。这种接口设计的好处非常明显你想换掉识别算法不需要动前两个子程序只要把recognizeChar内部的模板匹配改成神经网络推理即可。同理你想换定位策略比如用颜色分割法定位蓝底车牌也只需要改locatePlate一个函数。3.2 运行效果与参数调优观察以一张典型的高清小区入口监控图为例图像尺寸1920x1080车牌区域约占图幅的4%左右整套流程在MATLAB里的运行时间约为2到3秒其中定位子程序最耗时因为要对全图做边缘检测和连通域分析。识别结果在“车牌区域清晰、无强反光、车牌无大幅倾斜”的条件下能跑到95%以上的准确率。实际调试时我最常动的三个参数是定位中的连通域面积阈值、分割中的Otsu二值化结果、以及形态学结构元素尺寸。图像分辨率越高最小面积阈值和结构元素尺寸要等比放大。比如原图是640x480时area 1000效果不错换到1920x1080时往往需要调到area 4000以上。还有一个调试思路不要直接把参数改到一个固定值而是把中间结果显示出来观察。比如定位不出来就单独显示边缘检测图、闭运算图、连通域标记图看车牌到底在哪一步消失了。这样定位问题基本几分钟就能完成。4. 踩坑记录与常见问题排查4.1 常见问题速查表问题现象可能原因解决方案定位不到车牌区域边缘检测阈值过高/结构元素太小调低edge阈值放大结构元素尺寸检查车牌在图中占比定位到多个候选区域背景中有大量规则矩形物增加长宽比和面积约束条件增加颜色特征校验字符分割多切或少切车牌子边框未去除干净/图像倾斜增强水平投影去边框处理先做倾斜校正识别出的字符错乱模板库样本与实际字体风格不一致重新收集与测试图同字体风格的模板图像整个流程崩溃报错子程序输入输出维度不匹配逐一检查imresize和imcrop后图像维度接口处加断言判断第一行的定位不到问题是最多人卡住的地方。很多人直接把边缘检测图的阈值设为默认值0.2结果图像细节少或光照暗时完全检测不到车牌边缘。建议先用edge(denoisedImg, sobel);后用imshow观察边缘图如果车牌区域在边缘图上还没有明显的密集白点说明你的滤波太狠或者阈值设置不当。4.2 提升鲁棒性的三个实践技巧首先车牌识别系统的性能上限不是由识别算法决定而是由定位的鲁棒性决定。定位准了后面的分割和识别都是顺水推舟的事。想让定位更稳定可以在形态学处理前增加一个全局Otsu阈值化的步骤把亮度比较暗的车牌从背景中剥出来再和边缘图做交集双通道验证能大幅减少定位失败的概率。其次二值化的时机很重要。传统流程是定位后在车牌区域内二值化但如果你用的是蓝色车牌在灰度图上字符和底色的灰度差往往不大二值化很容易把字符和底色混在一起。更好的做法是在定位前用颜色特征提取蓝色区域作为候选然后转灰度再二值化。这样一来即使图像在逆光环境下整体偏暗蓝色区域依然是稳定的。第三字符分割后别忘了对每个字符做尺寸归一化。模板匹配对尺寸非常敏感我在调试时遇到过一个案例数字“1”和字母“I”最容易被混淆就是因为“1”的宽度远小于模板而“I”的宽度接近模板匹配分接近后就会错判。解决方案是识别前统一缩放高度但宽度保持原始比例再根据字符宽度的先验知识排除明显不合理的结果。4.3 从传统方法升级到深度学习的思路这套代码虽然基于传统图像处理但它给深度学习方案留下了清晰的扩展空间。字符识别子程序recognizeChar是一个非常干净的函数级接口输入一张字符图输出一个字符。你已经把分割做完了剩下的就是把模板匹配换成已经训练好的分类器。在MATLAB里可以用fitcecoc训练一个多分类SVM或者用Deep Learning Toolbox里的trainNetwork跑到简单的CNN输入输出接口完全不用改。同样端到端的深度学习车牌识别模型在公开数据集上已经能拿到99%以上的准确率但代价是你需要大量标注数据和GPU资源。对这个毕设或者课程设计来说用传统方法做一遍理解图像处理每一步的物理意义然后在此基础上再对比深度学习方案你会真正明白有哪些问题传统方法解决不了以及深度学习是怎么解决这些问题的。这种“先通传统再学深度”的路线对个人成长的价值比直接调用一个现成API高得多。5. 扩展从单张图片到实时视频流5.1 视频流处理的帧间隔优化很多做完图片版车牌识别的人下一步就想把它接到摄像头或视频文件上。这里有个现实问题MATLAB直接对视频流做全帧识别每帧2到3秒的速度完全无法做到实时。实际工程中常用的策略是抽帧处理典型做法是每秒只需要对2到3帧做完整识别中间帧只检测车牌是否还在画面里。在MATLAB里用VideoReader读取视频文件很简单但注意readFrame读取的是压缩后的帧颜色空间和静态图片可能有细微差异建议从第一帧建立背景模型后后续每帧先计算帧差只有帧差超过阈值才做全流程识别。监控场景下大多数时间画面是静止的这个优化能把平均CPU占用降一个数量级。5.2 识别结果后处理提升连续帧稳定性单帧识别偶尔会出错但连续帧不会在同一位置连续错。工程上可以做一个滑动窗口投票连续识别5帧每个字符位置选出现次数最多的结果作为最终输出。比如某字符5帧结果分别是“3”、“8”、“3”、“3”、“3”那最终结果就是“3”。这个方法朴素但非常有效尤其适合进出口闸机、停车场道闸这类车辆静止或慢速通过的场景。注意投票帧数不要太多否则车辆快速通过时帧间车牌位置变化太大字符分割的结果反而不稳定。5到7帧是一个比较平衡的经验值。5.3 项目中进一步扩展的可能方向这套代码的下一步扩展方向可以参考以下几个方面。一是增加车牌颜色识别子程序把蓝牌、黄牌、绿牌新能源和白色车牌区分开。这一步在定位子程序之前完成根据像素颜色分布直接圈定候选区域能显著提升定位的鲁棒性也能为后续的车辆分类提供输入。二是增加字符模板库的覆盖范围。中国车牌包含汉字、字母和数字汉字和字母的字体风格有细微差别模板匹配如果不管字体风格差异识别率会掉得厉害。一个更稳妥的方案是对每个字符保存多套模板匹配时取最高得分。三是将识别结果与车牌归属地信息结合。比如汉字部分识别以后可以根据前2位字符判断归属地作为车辆行程管理和区域统计的输入。严格来说这一步已经不涉及图像算法了更多是业务应用层的设计但在毕设里加上会显得整个系统完整度更高。6. 实操心得与调试建议整套项目做下来我的感受是一个字磨。跟参数较劲的时间远多于写代码的时间但这也是最有收获的部分。印象最深的一次是调试定位子程序连续三天定位准确率只有60%。刚开始怀疑是边缘检测阈值问题调了几天没起色。后来把每一步中间结果都打出来看才发现问题是输入的测试图尺寸跨度太大有高清的相机图片也有低分辨率的监控截图。用同一套面积阈值大图上的车牌面积轻松超过阈值小图上的车牌面积连阈值的一半都不到。后来改成先归一化图像尺寸再统一参数准确率一下就上去了。这让我意识到参数不是越调到某个固定值越好而是要根据输入特性做自适应匹配。如果你也在做类似项目我的建议是先把代码从头到尾跑通再用10张不同场景的图去测统计失败案例从失败案例里找共性原因。不要一开始就追求极高的识别率先把整个流程打通再逐步提高每个环节的鲁棒性。车牌识别这个项目最大价值在于它几乎是图像处理知识体系的一个微缩样本滤波、边缘检测、形态学、投影、特征匹配全用上了每一块知识都能找到直观的落地点。把这个项目认真啃下来你再来回看图像处理的课本会觉得所有概念都变成了实实在在的像素操作。
返回列表