1. 系统整体设计与思路拆解先想清楚再做1.1 车牌识别到底在识别什么车牌识别这件事听起来像一个“智能”光环很重的功能但拆开来看本质就是图像处理里几个经典步骤的组合图像采集、车牌定位、字符分割、字符识别。用MATLAB做智能车牌识别系统最大的好处是算法验证链路极短不需要从零搭建图像数据结构也不用过早纠结编译部署能把精力集中在“识别率怎么提上去”这件事上。一套完整的车牌识别系统输入是一张包含车辆的图片或视频帧输出是车牌号码的文本结果。中间要做的事情可以粗分为两类一类是“找到车牌在哪”一类是“把车牌上的字符认出来”。第一类问题靠边缘检测、形态学处理、区域筛选这些传统图像处理手段就能解决大部分第二类问题则可以走模板匹配也可以走OCR或深度学习分类器。如果你只是做毕业设计、项目Demo、算法预研MATLAB都是很合适的载体。这里有个容易踩的认知误区很多人以为车牌识别很难一上来就想用深度学习目标检测标注数据、训练模型、调参折腾一个月还没跑通一个完整流程。实际上在固定场景、固定相机角度、光照相对可控的情况下传统图像处理方法用半天就能搭出一个能跑通的版本。智能车牌识别系统重点在于“系统”二字——每个环节都要能稳定输出让整条链路跑起来而不是某一个环节炫技。1.2 为什么用MATLAB做原型而不是直接上深度学习框架不少人在前期选型时纠结既然语音识别、图像识别都在用深度学习车牌识别是不是也应该直接用Python加PyTorch我个人的建议是如果你需要快速验证算法、需要可视化中间结果、需要写报告出图表MATLAB的优势非常明显。MATLAB在图像处理领域的积累相当厚实。图像读取、灰度化、边缘检测、形态学操作、连通域分析、区域属性提取这些全部有现成函数而且中间结果可以直接用imshow、imtool可视化。写代码时你可以一行一行执行随时看每步处理后的图长什么样这在调试车牌定位算法时极其重要。你用其他语言写往往需要额外写一堆显示代码调格式、转类型非常影响思路。另一个实际考虑是MATLAB的Image Processing Toolbox和Computer Vision Toolbox对新手非常友好。比如形态学闭运算只需要imclose(I, se)一行连通域筛选regionprops能一次性输出面积、外接矩形、离心率等十几个属性。后面我会详细展开这些函数的用法但核心结论是车牌识别本质是工程问题不是算法研究问题用最顺手工具快速实现是正解。1.3 整体流程从图像采集到字符输出的五个阶段整个系统我习惯拆成五步图像预处理把彩色图转灰度图必要时做增强、去噪、光照校正。车牌定位用边缘、颜色或连通域特征从整车图中锁定车牌区域。车牌矫正对倾斜的车牌做仿射变换转成正视角。字符分割把车牌区域中的每个字符单独切出来。字符识别对每个字符图做分类输出最终字符串。这五个阶段里最耗时间的是字符分割和字符识别两个环节因为它们直接决定识别结果。车牌定位虽然看起来复杂但在图像质量尚可的情况下传统方法成功率很高。文章后面的部分我会按照这个流程逐步拆解每一环节的坑和技巧。2. 核心细节解析与实操要点定位、分割、识别三个关键环节2.1 车牌定位的关键边缘检测与形态学处理车牌定位最常见的思路是利用车牌区域的纹理特征。车牌上有多个字符字符与底色之间有强烈的边缘信息所以整个车牌区域会呈现“横向密集边缘”的特征。我们可以先做边缘检测再用形态学操作把这些边缘连成一个整体块最后用“宽高比、面积、矩形度”等几何特征筛选出车牌候选区域。具体操作顺序是读入图像转灰度做Sobel或Canny边缘检测。直接对灰度图做边缘检测时需要注意车牌底色和车身颜色的对比度。国内常见的蓝底白字车牌字符边缘在灰度图中通常很清晰但如果遇到白色车身加白色车牌边框边缘响应就会弱一些。这时可以结合HSV颜色空间提取蓝色像素的掩膜再用形态学方法连通区域两种方法互为补充。边缘检测之后字符边缘是断开的需要使用闭运算把邻近边缘连接起来。这里有个参数经验闭运算的结构元素宽度要接近字符笔画宽度长度要能覆盖车牌字符间的空隙一般推荐strel(rectangle, [15, 5])。如果车牌子区域较小可以把尺寸等比缩小。闭运算做完之后再用imfill填充孔洞让车牌变成一个实心矩形。区域筛选时我一般用regionprops提取BoundingBox和Area然后设置至少三个条件面积不能太小过滤掉远处的小噪声宽高比在2到5之间车牌本身是宽扁矩形矩形度要大于某个阈值。需要注意的是车头散热器格栅、前保险杠装饰条也会形成横向纹理容易被误判为候选区域所以最后往往会有多个候选框需要根据位置和分数排序。2.2 字符分割的细节投影法与连通域分析定位到车牌区域之后下一步是把车牌里的字符逐个切出来。标准车牌一般有7个字符第一位是省份汉字第二位是字母后面是字母和数字的组合。字符之间有小间隔但汉字部分可能存在左右结构中间会有空隙如果直接用垂直投影切割会把“京”这种汉字切开这是最容易踩的坑。解决思路有两种一种是先做连通域分析把每个独立连通块找出来再根据字符高度和间距进行合并另一种是做垂直投影统计每一列的前景像素数量投影值为0的列就是字符间隔。投影法实现简单但汉字结构复杂时容易切碎。我实际项目里更推荐“投影为主、连通域校验为辅”的组合方式。先对二值化后的车牌区域做水平投影找到车牌字符的上下边界去除上下边框和铆钉干扰。再做垂直投影切出一个个字符块。切完后检查每个字符块的高度是否合理如果某个字符块的高度明显小于字符平均高度说明可能是汉字左右结构的一部分需要和邻近块合并。这个处理逻辑写成规则并不复杂但能明显提升汉字的切割正确率。分割完成后建议对每个字符块做尺寸归一化统一缩放到比如40×20像素再送入识别模块。归一化之前要保留字符的宽高比信息否则某些窄字符如数字1和宽字符如字母W在模板匹配时会产生歧义。这里的小技巧是先将字符图像按比例缩放再在四周补零而不是直接拉伸到目标尺寸。2.3 字符识别的两种路线模板匹配与CNN字符识别的实现路线直接决定了系统的复杂度和识别率。最简单的做法是模板匹配把每个字符和预先准备好的标准模板做归一化相关计算相关系数最高的模板作为识别结果。这种方法在字体统一、字符清晰、二值化干净的情况下准确率很高而且运行速度快适合嵌入式或实时性要求高的场景。另一种做法是用MATLAB内置的OCR函数ocr(plateImg)可以直接识别文本。内置OCR对印刷体英文字母和数字识别效果不错但对中文车牌的汉字支持要看版本和语言模型而且对字符切分有自己的逻辑不一定符合车牌的固定格式。我的建议是如果车牌只有字母数字可以试试内置OCR如果包含汉字还是用模板匹配或自训练分类器更可控。这里还要多说一句模板匹配的识别率上限取决于模板库的质量。你需要准备至少三类模板汉字模板省份简称、字母模板、数字模板。字体要和实际车牌字体尽量接近。做项目时如果手头没有现成模板可以从网上下载车牌字体图片或者直接用车牌识别公开数据集里切好的字符来生成模板。模板图片和待识别字符图片必须做同样的预处理这个细节很多人忽略导致相关系数偏低。3. 完整实现过程与关键代码讲解一个能跑通的流程3.1 环境准备与图像预处理我用的是MATLAB R2021a版本需要安装Image Processing Toolbox。如果你的版本较老Sobel边缘检测、形态学操作这些基础函数也都支持不用担心。图像获取可以使用imread读取静态图片也可以使用VideoReader读取视频帧。下面的代码以静态图片为输入。% 读取并显示图像 img imread(car.jpg); imshow(img); title(原始图像);预处理阶段的目标是让车牌区域更突出。第一步是转灰度这一步没什么可说的。第二步是增强对比度推荐使用adapthisteq自适应直方图均衡化它对光照不均匀的车牌图像尤其有效能把字符边缘从背景中分离出来。第三步是滤波去噪可以选用中值滤波既能平滑噪声又能保持边缘信息。% 灰度化 grayImg rgb2gray(img); % 自适应直方图均衡化增强局部对比度 enhancedImg adapthisteq(grayImg); % 中值滤波去噪窗口别设太大3x3足够 filteredImg medfilt2(enhancedImg, [3 3]);预处理做完后用imshow对比一下处理前后的图像。你会发现车牌区域的字符边界明显更锐利。这里有一个实操经验如果原图整体偏亮或偏暗先不要急着调各种参数先试试adapthisteq绝大多数情况下它比全局直方图均衡化histeq效果好因为它考虑的是局部区域对比度不会把高光区域过曝。3.2 车牌定位与字符分割的MATLAB实现这一步是整个系统的核心。我直接贴出定位部分的代码并对每一段做说明。% 边缘检测Sobel对横向边缘响应较强适合车牌字符 edges edge(filteredImg, Sobel); % 形态学闭运算连接字符间的断裂边缘 se strel(rectangle, [15, 5]); closedImg imclose(edges, se); % 填充孔洞让车牌变成实心区域 filledImg imfill(closedImg, holes); % 提取连通域属性 cc bwconncomp(filledImg); stats regionprops(cc, BoundingBox, Area); % 筛选车牌候选区域 plateBoxes []; for k 1:length(stats) area stats(k).Area; bbox stats(k).BoundingBox; width bbox(3); height bbox(4); ratio width / height; % 面积、宽高比、矩形度条件 if area 1000 ratio 2 ratio 5 plateBoxes [plateBoxes; bbox]; %#okAGROW end end这段代码里strel(rectangle, [15, 5])是最需要调的参数。15是结构元素的宽度表示在水平方向上能连接多少像素5是高度。如果定位不到车牌优先增大宽度如果多个噪声区域连成片优先减小宽度或增大面积阈值。筛选条件里的面积阈值1000不是固定的图片分辨率越高阈值应该按比例增大。我建议在调试时用imshow把候选框画出来看看而不是直接闷头调参。定位到候选区域后从原图中裁剪出车牌区域然后转二值图准备字符分割。% 裁剪车牌区域取第一个候选框实际项目中按得分排序 plateImg imcrop(img, plateBoxes(1,:)); plateGray rgb2gray(plateImg); % 二值化Otsu方法自动确定阈值 bwPlate imbinarize(plateGray, adaptive, ForegroundPolarity, dark, Sensitivity, 0.4);imbinarize的adaptive参数很有用它能根据局部邻域亮度动态调整阈值对车牌区域光线不均匀的情况效果较好。注意ForegroundPolarity要设置为dark因为车牌字符相对背景是暗色蓝底白字倒过来看白字是亮色黑底白字才是暗色实际取决于你的图像类型。如果字符变白底黑字则设置成light。字符分割的垂直投影代码如下% 水平投影找到字符的上下边界 horizontalProj sum(bwPlate, 2); rows find(horizontalProj 0); topRow rows(1); bottomRow rows(end); % 裁剪出字符区域去掉上下边缘 charRegion bwPlate(topRow:bottomRow, :); % 垂直投影找到每个字符的左右边界 verticalProj sum(charRegion, 1); isCharCol verticalProj 0; % 找到跳变点确定字符起始和结束列 diffCol diff([0 isCharCol 0]); startCol find(diffCol 1); endCol find(diffCol -1) - 1;拿到startCol和endCol之后就可以循环切出每个字符。切出来的字符宽度如果太窄小于平均宽度的0.5倍多半是噪声或字符的一部分需要忽略或合并。具体合并逻辑你可以写一个while循环也可以直接用连通域分析处理。我的经验是先把所有字符块存到一个cell数组里再按高度、宽度筛选掉杂质块最后再考虑相邻块的合并。3.3 模板匹配识别与结果输出字符切好之后尺寸归一化是必须做的一步。我写了一个简单函数先按比例缩放到目标高度再在左右两侧补零到目标宽度。function normChar normalizeChar(charImg, targetH, targetW) % 按比例缩放高度 scale targetH / size(charImg, 1); resized imresize(charImg, scale); % 如果缩放后宽度超过目标宽度则按宽度缩放 if size(resized, 2) targetW scale2 targetW / size(resized, 2); resized imresize(resized, scale2); end % 补零到目标尺寸 normChar false(targetH, targetW); h size(resized, 1); w size(resized, 2); rowStart floor((targetH - h) / 2) 1; colStart floor((targetW - w) / 2) 1; normChar(rowStart:rowStarth-1, colStart:colStartw-1) resized; end识别阶段我用corr2计算模板和待识别字符的相关系数遍历所有模板取最大值对应的字符作为结果。% 加载模板库结构体数组包含字符和图像 templateSet load(plateTemplates.mat); % 假设charCell是分割好的字符二值图列表 resultStr ; for i 1:length(charCell) normChar normalizeChar(charCell{i}, 40, 20); maxCorr 0; recognizedChar ; for j 1:length(templateSet.templates) tpl templateSet.templates(j).img; tpl double(tpl(:)); test double(normChar(:)); % 相关系数计算 c corr2(tpl, test); if c maxCorr maxCorr c; recognizedChar templateSet.templates(j).char; end end resultStr [resultStr recognizedChar]; %#okAGROW end disp([识别结果: , resultStr]);这段代码的核心就是把每个字符和所有模板做相关性比较取最高者。实际会遇到一个问题某些字符形状相似比如数字0和字母O、数字1和字母I、字母B和数字8模板匹配很容易混淆。解决方法是在识别后加一个规则校验比如中国车牌第二位只能是字母其余位置字母数字混排根据位置先排除掉不可能的候选。4. 常见问题与排查技巧实录我踩过的坑4.1 定位失败光照不均匀怎么处理最典型的定位失败场景是强光直射下车牌反光或者树荫下车牌一半暗一半亮边缘检测出来的边缘断成一截一截。我遇到这种情况第一步不是改边缘算法而是先看灰度图的直方图。如果灰度分布集中在亮部或暗部说明预处理环节没有做好。这时候优先使用adapthisteq做局部对比度增强再尝试边缘检测。如果车牌区域和车身颜色接近边缘检测效果很差可以考虑用颜色分割辅助定位。以蓝底车牌为例先把RGB图像转到HSV色彩空间然后提取蓝色像素的掩膜再用形态学闭运算把掩膜区域连成块这样定位会比纯边缘检测稳定得多。颜色分割代码也很简单hsvImg rgb2hsv(img); hue hsvImg(:,:,1); sat hsvImg(:,:,2); blueMask (hue 0.5 hue 0.7) (sat 0.3);需要注意的是不同光照条件下蓝色的色调范围会有偏移所以阈值不能定死建议用colorThresholder这个交互式工具自动调整范围它会实时显示掩膜效果比手写阈值快很多。4.2 字符粘连与断裂怎么办字符粘连通常发生在车牌图像分辨率低、二值化后字符之间的缝隙被噪声填满。分割时垂直投影看不到零值列所有字符连成一个长条。处理办法是先用形态学开运算把细小的连接点断开用的结构元素一般是strel(line, 5, 90)表示垂直方向5像素长的线能断开狭窄的水平连接。相反的情况是字符断裂比如汉字“苏”的笔画较多二值化后部分笔画断开水平投影会把它看成上下两截。这时候不要急着做形态学闭运算连接先用连通域分析找到所有字符块根据字符高度分布判断哪些是断裂碎片然后把这些碎片合并到最近的完整字符块上。合并的判定逻辑是两个连通块的重叠列数要足够多且纵向距离很近。另外还有一个常见干扰源车牌固定螺丝和边框。螺丝在车牌中间如果不提前去掉会干扰字符分割。我的做法是先用水平投影找到字符区域的上下边界然后水平裁剪时把上下各裁掉几个像素这样能避开大部分边框。螺丝的位置一般靠近车牌中心左右两侧且高度覆盖整个字符区域分割后得到的“字符块”如果宽度非常宽且内部有规则镂空基本就是螺丝区域直接丢弃。4.3 模板匹配误识别的根源与对策模板匹配系统识别率上限受三个因素影响模板和实际字符的字体差异、二值化后字符的笔画粗细差异、以及相似字符的混淆。字体差异很难处理因为车牌字符字体是专用的网上能找到的模板不一定完全一致。一个变通办法是收集一批真实车牌切好的字符图自己做模板。比如从几十张车牌图片中切出所有字符对同一个字符的多张图做平均得到更接近实际分布的模板。这个方法提升效果非常明显远比你在模板库中多加几个字体文件有效。笔画粗细差异可以通过细化处理来消除。待识别字符和模板都做一次骨架化也就是用bwmorph(im, thin, Inf)提取字符骨架再做匹配。骨架化之后笔画粗细的影响被消除匹配更关注字形结构对相似字符的区分力更强。缺点是骨架化会放大噪声使用前需要先做简单去噪。相似字符混淆是模板匹配的老问题。推荐在匹配完成后增加一个后验规则库0/O、1/I、8/B、2/Z等组合如果相关系数相差很小就根据字符在车牌中的位置约束做最终决策。例如车牌第二位不可能是数字那么如果第二位匹配结果是数字和字母之间的分数接近优先取字母。4.4 性能瓶颈处理速度太慢如何优化MATLAB在处理循环和大量图像数据时如果代码写得不好性能会非常感人。我见过有人对每一个字符都调用imresize和corr2模板库有100个模板7个字符就要做700次相关性计算每帧图像跑出来要好几百毫秒。优化空间主要有三块。第一块是减少模板匹配次数。实际车牌字符集并没有那么大省份汉字最多31个字母24个不含I和O数字10个全部大约100个模板。但你可以根据车牌规则缩小范围第一位是汉字第二位是字母后面是字母和数字混合。分割完成后按位置加载不同的模板子集匹配量能减少一半以上。第二块是向量化计算。corr2虽然是内置函数但循环100次仍然有开销。可以把所有模板图像拉成行向量组成一个大矩阵用一次矩阵运算计算出待识别字符与所有模板的相关系数向量从而把循环去掉。这个优化在MATLAB里效果很显著。第三块是图像预处理阶段的加速。adapthisteq和medfilt2是耗时大户如果实时性要求高可以先把输入图像缩小一半处理完定位后再在原图对应区域精确分割。小图定位大图裁剪是工程里常用的分层处理思路。5. 工具选型与扩展方向从原型到落地5.1 MATLAB图像处理工具箱与Computer Vision Toolbox的分工做车牌识别系统主要用到两套工具箱。Image Processing Toolbox负责底层图像操作灰度化、滤波、形态学、二值化、连通域分析。Computer Vision Toolbox则偏高层视觉任务目标检测、特征提取、几何变换、视频IO。两者搭配使用效率很高。实际项目里我建议定位阶段可以先用Computer Vision Toolbox的detectMSERFeatures或detectSURFFeatures做候选区域提取再用几何规则筛选。字符识别阶段如果不想写模板匹配Computer Vision Toolbox的ocr函数可以直接调用配合insertObjectAnnotation还能方便地画出识别结果框。工具箱之间函数名没有冲突可以混合使用。需要提醒的是不要一上来就装一堆工具箱。在命令行输入ver查看已安装工具箱只需要Image Processing Toolbox和Computer Vision Toolbox就够用了。如果都没有先装前者也能完成大部分流程。5.2 从传统方法到深度学习更进一步的扩展方向传统图像处理方法在固定场景下够用但一旦遇到复杂背景、极端光照、多角度拍摄识别率会明显下降。这时候可以考虑引入深度学习目标检测器来做车牌定位。MATLAB的Deep Learning Toolbox支持导入训练好的模型也支持用trainYOLOv4ObjectDetector等接口训练检测器但训练数据集的准备是个大工程。我的建议是分阶段演进第一阶段用传统方法跑通整个系统理解每一环节的瓶颈第二阶段针对瓶颈环节替换成深度模型。比如定位部分稳定性差就只训练一个车牌检测模型替换掉边缘检测和形态学定位字符识别部分汉字识别率不够就用分类网络替换模板匹配。这样做的好处是每个改动都有对比基准效果好坏一眼就能看出来。如果之后要部署到嵌入式设备或实时视频流MATLAB有代码生成能力可以把算法转成C/C代码或者用MATLAB Coder生成独立可执行程序。不过我个人经验是真正落地时还是会把MATLAB里验证过的算法逻辑用Python或C重写一遍因为工程环境里模型部署、跨语言调用、并发处理这些需求MATLAB并不是最优解。但用MATLAB做算法验证、调参和效果演示性价比依然很高。5.3 工程化时要注意的细节如果这个系统的目标是交作业、参赛或快速Demo做到前面那一步已经足够。但如果要部署到实际场景比如停车场出入口还有几个细节必须提前考虑。第一个细节是输入图像质量。实际现场图像可能来自模拟摄像头、网络摄像头或手机拍摄分辨率、帧率、畸变都不一样。建议在预处理阶段加入相机标定环节至少要做一次去畸变处理。第二个细节是识别结果的置信度输出。不要只输出一个识别字符串还应该输出每个字符的匹配分数。分数低于阈值的字符要标记为“可疑”交给人工复核或二次识别而不是强行给出错误结果。第三个细节是车牌类型。不同地区的车牌底色、字符排列不完全相同新能源车牌是8位字符比传统车牌多一位而且第二位是字母D或F代表纯电动或混合动力。如果系统只做固定7位字符假设遇到新能源车牌会直接分割失败。所以工程化之前务必明确系统要支持的车牌类型范围。第四个细节是实时性。视频流处理时每一帧都做全图定位会非常浪费计算资源。工程上常用跟踪策略第一帧检测并定位车牌后续帧根据前一帧的位置在一个扩大后的搜索窗口内做局部检测做完识别后再用卡尔曼滤波或简单的IOU匹配更新位置。这样整体帧率能提升好几倍。最后再分享一个实用技巧做模板库的时候你手头可能没有足够的真实车牌字符素材但又不想用别人整理好的现成模板。一个替代思路是用MATLAB自带的光学字符识别结果来自动生成弱标注先用内置ocr函数批量识别一批车牌图片把识别置信度高的字符区域裁剪下来作为模板候选再人工过滤一遍。这个方法能快速积累一套“半真实”的模板库比纯用字体渲染的模板更贴近实际图像退化过程。另外调试时建议把所有中间结果都保存成一个mat文件方便回溯。比如定位框坐标、分割后的字符图、每个字符的相关系数矩阵都存下来。后期调算法时不用反复跑整个流程直接从mat文件读中间结果做实验效率会高很多。踩过几次坑之后你就会发现车牌识别系统里最花时间的不是写算法而是调参数。而调参数的基础就是你能快速看到每一步的中间状态。
阅读完成 · 觉得有帮助?