基于MATLAB和阈值分割的车牌定位识别实战:从图像处理到字符识别全流程 简介本资源是一套面向图像处理初学者与MATLAB实践者的车牌识别教学案例聚焦基于阈值分割的车牌定位与字符识别全流程实现。资源完整覆盖图像预处理、二值化阈值选取、连通域分析定位车牌区域、字符分割及模板匹配识别等核心环节适用于课程设计、课程实验或智能交通系统入门项目开发。压缩包共69个文件含59幅标准车牌BMP样本图涵盖皖、京、粤、沪等31个省市及0–9、A–Z字符、8个关键功能MATLAB脚本如Main_Process.m、Segmation.m、Pattern_Recognition.m、1个GUI界面文件Gui_Main.fig及1张示例原图car.jpg总大小仅287KB结构清晰、模块解耦便于逐层调试与原理验证。已有1156人学习下载配套代码可直接运行提供从原始图像到识别结果的端到端可复现流程特别适合理解阈值分割在实际车牌识别任务中的应用边界与优化思路。 第一次做车牌识别课设的时候我犯了个典型错误——还没搞懂图像处理基础就直接去搜YOLO车牌识别代码。折腾一周模型没训练起来反而把整个流程的耐心耗光了。后来老老实实从阈值分割开始用MATLAB一步步做基于阈值分割的车牌定位识别才真正把图像处理这条链路打通。今天这篇就聊聊这个经典项目怎么做、为什么这么做、有哪些坑。它适合三类人刚接触图像处理的学生、准备课设或毕设又不想无脑抄代码的人以及想搞明白传统CV和深度学习到底差在哪的爱好者。整个项目不依赖任何训练数据只要一张含有车牌的清晰照片MATLAB就能在几十行代码之内把车牌号码读出来。虽然现在工业级车牌识别基本都转向深度学习但阈值分割这套思路依然是理解图像处理全流程最好的教科书没有之一。1. 项目整体设计与技术选型1.1 这个项目到底在解决什么问题从功能上看输入是一张包含车辆的照片输出是车牌号码字符串。但拆开来看里面藏了两个完全不同的子问题车牌在哪里以及车牌上的字是什么。前者叫定位后者叫识别。定位最难。车身有大量干扰信息车灯、进气格栅、后视镜、车窗反光甚至车身广告上的文字在图像里看起来都像车牌。阈值分割能做的是先把图像变成一张黑白分明的前景背景二值图让车牌区域和周围环境在灰度层面拉开差距。但这只是第一步真正把车牌框出来还要靠形态学、连通域分析和一系列几何规则。为什么传统方法在这个场景可行因为车牌本身有极强的先验特征标准蓝色车牌的宽高比大约是440比140也就是3.14左右非常固定字符区域纹理密集笔画和背景交替频繁车牌底色和车身颜色通常有较大灰度差。这些特征叠加起来就让“阈值分割几何筛选”成为一套天然适配的解决方案。相比之下直接上深度学习虽然鲁棒性更好但需要标注数据、训练环境和显卡对一个入门项目来说成本偏高。1.2 为什么是阈值分割加MATLAB而不是直接上Python很多人一上来就想用Python和OpenCV我不是说不行但用在入门和验证算法这个阶段MATLAB确实更顺手。先说数学这块。MATLAB的矩阵运算几乎是零成本的图像本来就是一个二维矩阵灰度化、二值化、膨胀腐蚀这些操作在MATLAB里就是一行函数的事。图像处理工具箱里的graythresh、regionprops、bwlabel都是经过工业级验证的成熟实现不需要自己造轮子也不容易踩到OpenCV里各种版本兼容的坑。调试体验也香。MATLAB的工作区可以直接看到每个变量双击变量就能看到矩阵数值imshow随手一画就能看到中间结果。做图像处理中间结果可视化比什么都重要。你调参数的时候如果每改一次都要重新编译或者写一堆显示代码耐心很快就没了。当然如果你打算以后做工程部署OpenCV和C才是归宿。我用一个表来对比一下对比维度MATLABPython OpenCVC OpenCV算法验证速度最快函数全、语法短快但版本兼容偶尔折腾慢编译时间长调试可视化原生支持体验极好需要配合matplotlib需要额外GUI库工程部署难度较高依赖运行环境中等可以打包最适合嵌入式学习成本低脚本语言思维中低高说句实在话验证阈值分割这类传统算法MATLAB的效率比另外两个高一个量级。等你把整个流程跑通、算法思路完全吃透再迁移到OpenCV去工程化那个过程会非常顺畅。1.3 完整处理流程拆解整个项目的处理链路可以拆成七步建议按顺序一个个调通图像预处理彩色图转灰度图中值滤波去噪。阈值分割把灰度图变成二值图让车牌区域和背景分离。形态学处理用闭运算把字符间隙填上让车牌连成一个整体块。候选区域筛选用连通域分析和几何特征从二值图里锁出车牌位置。车牌精确定位裁剪出车牌区域再做一次局部二值化。字符分割用垂直投影把每个字符切出来。字符识别用模板匹配识别每个字符拼接成最终车牌号码。一句话概括先让车牌在二值图里变成一个稳定的亮块再做几何筛选取出车牌最后逐字符识别。每一步的算法选择都服务于这个总目标。很多人误以为调用graythresh就能识别车牌实际操作后你会发现真正决定成败的反而是形态学参数和筛选规则。2. 图像预处理与阈值分割的关键细节2.1 灰度化与去噪别小看这一步车牌识别的输入是彩色照片但阈值分割是基于单通道灰度值做的所以第一步一定是rgb2gray。MATLAB这一行代码背后是标准的加权公式Y 0.299R 0.587G 0.114B。人眼对绿色最敏感对蓝色最不敏感这个权重系数就是按照人眼感知特性定的不需要自己改。灰度化之后紧接着去噪。车牌图像里最常见的噪声是椒盐噪声就是那种随机出现的黑白杂点。我建议用中值滤波MATLAB里是medfilt2(gray, [3 3])。中值滤波的原理很简单把每个像素周围的9个值排序取中间值作为当前像素的值。这样做的效果是孤立的噪点会被邻居“投票”顶掉但真正的边缘比如字符笔画边缘不会被抹平。这里有个小知识点为什么不用均值滤波均值滤波会把噪点平均到周围结果就是整个画面变糊边缘过渡带变宽。车牌字符边缘一旦模糊后续阈值分割出来的笔画就会变粗、粘连字符分割时非常痛苦。我试过在同样一组图上对比中值和均值用中值滤波后字符识别率大概能高出8到10个百分点。所以这个选择不是玄学是实打实影响结果的。顺便提一句如果原图整体偏暗可以先做一次histeq直方图均衡化来拉大对比度。但要注意直方图均衡化对过暗图像效果好对正常光照图像反而可能让背景噪声也变强属于“非必要不加”的操作。2.2 阈值怎么选从固定阈值到Otsu拿到灰度图之后核心问题来了灰度的分界线划在哪里才能把车牌区域和背景干脆利落地切开最直觉的做法是固定阈值比如imbinarize(gray, 0.5)像素值大于0.5的变成白色否则变黑。但实战里这么干基本翻车。白天强光下白色车身反光灰度值可能整体飙到0.8傍晚光线不足蓝色车牌的灰度值又可能掉到0.3。固定阈值就像穿着一双固定尺码的鞋走过所有地形走两步就得崴脚。正确的打开方式是Otsu算法也就是MATLAB的graythresh。它的核心思想是遍历所有可能的阈值把像素分成前景和背景两类然后计算这两类之间的类间方差。类间方差越大说明两类越“分开”这个阈值就越靠谱。公式表达就是σ² ω₀ω₁(μ₀ - μ₁)²其中ω是两类像素占比μ是两类灰度均值。算法就在所有灰度级别上最大化这个值。核心代码长这样gray rgb2gray(I); gray medfilt2(gray, [3 3]); level graythresh(gray); bw imbinarize(gray, level);车牌场景为什么适合Otsu因为蓝底白字或者黄底黑字正常情况下前景和背景灰度差很明显直方图呈双峰分布Otsu能稳定找到两峰之间的谷底。注意老版本MATLAB里常用im2bw(gray, level)但R2016b之后官方推荐用imbinarize两者功能一样新函数兼容性更好不会报过时警告。Otsu也不是万能的。如果车身上有大片阴影或者阳光在引擎盖上打出一道高光灰度直方图就变成了多峰Otsu选的阈值可能偏向某一峰导致车牌区域被错误分割。这种情况下我会在阈值分割之前先叠加边缘信息这就是下一节要说的内容。2.3 边缘信息叠加让车牌“显形”我调试的时候遇到过一个很头疼的案例一辆白色轿车白色车身和车牌白色字符几乎融为一体。灰度阈值无论怎么选车身背景和车牌前景都切不干净。这时候如果只盯着灰度值一条路走到黑很容易陷入死胡同。换个思路车身虽然亮但表面是光滑的边缘稀少而车牌区域是字符密集区笔画边缘横竖交错比车身密集得多。于是可以用边缘检测把“纹理密集”这个特征强化出来。MATLAB里用edge(gray, sobel)就能得到二值边缘图。但边缘图是细线条和阈值二值图直接相加效果不好。实操中我一般先用小结构元素膨胀一下边缘让边缘线条变粗然后再用imclose和阈值二值图融合edgeBW edge(gray, sobel); seDilate strel(square, 3); edgeDil imdilate(edgeBW, seDilate); bw imbinarize(gray, graythresh(gray)); bwFuse imclose(edgeDil | bw, strel(rectangle, [25 25]));这里用到的逻辑是“或”操作只要某像素在阈值二值图里是前景或者在边缘图里是边缘就保留下来。因为车牌区域阈值分割和边缘检测两方面特征都强叠加之后会变成一个非常密集的亮块而车身表面虽然也可能被阈值分割误判为前景区但边缘稀疏叠加后不会有车牌那种“填满”的效果。这个小技巧让整个系统的容错率高了很多。哪怕阈值分割偶发不理想只要边缘特征在车牌区域依然能靠后面的形态学步骤救回来。3. 车牌区域定位与字符分割实战3.1 形态学操作把车牌“拼”成完整块阈值分割和边缘叠加做完二值图里车牌区域大概率是“条形码”状态字符笔画是独立的白色小条中间被黑色间隙隔开。这种状态没法做连通域筛选因为一个车牌会被拆成几十个白色碎片没有哪个碎片能代表完整的车牌区域。解决办法是闭运算。闭运算就是先膨胀后腐蚀它的效果很形象膨胀把白色区域向外扩张字符之间的间隙被填上腐蚀再把整个区域的轮廓收缩回接近原来的大小。你可以想象用胶带把一排整齐的零件盖住胶带把它们连成一个整体撕开之后轮廓依然整齐。结构元素的选择是精髓。我刚开始图省事写死了一个[15 15]的矩形结果换了一张分辨率高一点的图字符间隙被撑得特别大闭运算完全焊不上。后来改成动态计算h round(size(bw, 1) / 15); w round(size(bw, 2) / 15); se strel(rectangle, [h w]); bwMorph imclose(bwFuse, se); bwMorph bwareaopen(bwMorph, round(numel(bw) / 800));结构元素的尺寸大概取图像高度和宽度的十五分之一这样能保证不同分辨率下的适应性。bwareaopen的作用是删除面积小于阈值的连通块把车窗反光、树叶阴影这些小噪点清掉。这个阈值我用的是图像总像素数的八百分之一实际需要根据图像内容微调。闭运算有个副作用如果结构元素太大整个车身区域可能被连成一大块白色把车牌区域也淹没了。所以结构元素尺寸要克制刚够把字符间隙填上就行宁可偏小靠后面筛选也不要偏大造成区域合并。3.2 用连通域分析和几何特征锁定车牌形态学处理之后二值图里会出现若干个白色连通块。车牌区域大概率是其中一块现在要做的是从这些块里挑出真正是车牌的那个。我用bwlabel给每个连通块编号然后用regionprops提取几何属性。车牌区域有三条硬指标面积不能太大也不能太小一般占图像总面积的0.2%到15%。宽高比在2.0到4.5之间标准车牌是3.14左右但拍照视角不同会有透视变形。填充率Extent区域像素数与包围盒面积的比值大概在0.25到0.9之间。代码逻辑L bwlabel(bwMorph); stats regionprops(L, Area, BoundingBox, Extent); plateBox []; for k 1:length(stats) area stats(k).Area; box stats(k).BoundingBox; ratio box(3) / box(4); extent stats(k).Extent; if area numel(bwMorph) * 0.002 area numel(bwMorph) * 0.15 ... ratio 2 ratio 4.5 ... extent 0.25 extent 0.9 plateBox box; break; end endregionprops返回的BoundingBox是[x, y, width, height]格式x对应列y对应行画矩形框的时候顺序搞反是常见错误。这一点我踩过坑在坐标索引时记住MATLAB是“先行后列”的思维模式就没事。如果筛选后同时出现多个候选框比如车身贴了广告文字、车灯形状诡异我会额外加一个“垂直边缘密度”的评分统计候选区域内黑色到白色的跳变次数车牌字符区的跳变次数明显高于普通车身区域选出跳变最多的那个框。如果这时候还不放心就进入HSV颜色空间做核对蓝色车牌对应H通道大约0.55到0.75的范围直接过滤出蓝色像素比例最高的候选框。这套组合规则在传统方法里算得上够用。3.3 字符分割把每个字符干净切出来候选框锁定之后从原图上裁剪出车牌区域然后重新做一次局部阈值分割。为什么不在全局二值图上直接切因为车牌区域的光照是局部的裁剪之后再求graythresh阈值更贴合这块区域的实际亮度出来的字符更干净。这是我调参过程中很重要的一个心得局部阈值永远比全局阈值精准。切割之后还有一个烦人的问题车牌边框和铆钉。车牌有一圈白色边框在二值化后会和字符连成一片直接影响后续投影分割。我的做法是先把车牌区域四周各裁掉约5%的像素再用一个小的矩形结构元素做腐蚀把残余的边框细线断掉。铆钉通常在车牌上下边沿中间位置面积小、位置固定用面积过滤就能去掉。接下来的字符分割最经典也最实用的方法是垂直投影plateGray imcrop(gray, plateBox); levelPlate graythresh(plateGray); bwPlate imbinarize(plateGray, levelPlate); % 蓝底白字车牌直接可用黄底黑字需要 bwPlate ~bwPlate; proj sum(bwPlate, 1); isBlank proj 0; d diff([isBlank(1), isBlank]); starts find(d -1); ends find(d 1) - 1;这段代码的思路是在每一列上统计白色像素的个数。字符列有笔画投影值大于0字符间隙是空白投影值为0。从空白进入字符的列就是字符起始点从字符回到空白的列就是结束点。切出来的每一段就是一个候选字符。但这里有一个非常典型的坑汉字和字母“H”“U”这类字符内部有空白列垂直投影会把它们切碎。比如“鄂”字左右结构鲜明中间可能有明显的窄空白直接按投影切会拆成两半。对策是对切出来的连续段做一次宽度过滤和聚类如果某段的宽度明显小于车牌字符正常宽度的一半就把它和相邻段合并。更省事的办法是先用先验知识判断标准车牌除第一个汉字外后面六个字符宽度相对均匀可以用中位数宽度作为参考把过窄的段合并到左右邻近段。这样一来“鄂”这类左右结构汉字也能完整保留下来。4. 字符识别与系统串联效果4.1 基于模板匹配的字符识别字符分割完成之后每个字符都是一张黑白小图接下来要做的就是从模板库里找到最像的那一个。这个场景特别适合模板匹配原因是车牌字符不是手写体而是规范印刷字体每个字符的形态高度一致。省份简称汉字总共就三十来个字母数字加起来三十多个模板库的构建成本很低。模板匹配的原理说白了就是算相似度。把待识别字符和每个模板都缩放到相同尺寸然后计算相关系数相关系数最大的模板就是识别结果。MATLAB里用corr2就好charResized imresize(charImg, [40 20]); scores zeros(size(templates)); for t 1:length(templates) scores(t) corr2(charResized, templates{t}); end [~, idx] max(scores); charResult charLabels(idx);这里有两个细节值得注意。第一模板和待识别字符必须是相同的“极性”要么都是白字黑底要么都是黑字白底否则相关系数完全失效。我会在预处理时统一成白字黑底。第二第一个字符是省份简称只能和汉字模板库匹配后面几个字符不会出现汉字用字母数字模板库匹配。分开匹配不仅能提高速度还能避免汉字和数字之间的误判。中国车牌不使用字母I和O这也是一个天然的约束识别结果里如果出现这两个字母可以直接排除。4.2 完整识别流程代码串联把上面的模块串起来主流程函数结构是这样的function result plateRecognition(imgPath) I imread(imgPath); gray preprocess(I); bw thresholdSegment(gray); box locatePlate(bw); if isempty(box) result ; return; end chars segmentChars(gray, box); result recognizeChars(chars); fprintf(识别结果: %s\n, result); end每个子函数的职责要清晰preprocess负责灰度化、滤波和可选的直方图均衡thresholdSegment负责Otsu阈值分割和边缘叠加locatePlate负责形态学连接和连通域筛选segmentChars负责裁剪车牌、局部二值化、去边框和垂直投影切分recognizeChars负责模板匹配和结果拼接。调试的时候我强烈建议在locatePlate返回之后用下面两行代码把框画出来确认figure; imshow(I); rectangle(Position, box, EdgeColor, r, LineWidth, 2);这一步能帮你快速确认算法的每一步有没有按预期工作。很多人一上来直接跑完整流程中间某个环节出了问题也看不出来最后发现识别结果不对根本不知道从哪查起。4.3 识别率表现与优化方向在光照正常、车牌清晰、没有大角度倾斜的照片上这套方案实测下来定位成功率95%以上字符识别准确率90%到95%整体端到端准确率大概在85%到90%。如果场景换成阴天、晚上或者强逆光整体准确率会掉到70%到80%。说实话这个水平在真实停车场场景里不够用但对课设和入门学习来说完全达标了。如果你想进一步提升最值得投入的优化有三条路。第一条是颜色先验辅助定位在HSV空间先筛出蓝色或黄色像素区域把阈值分割局限在这个区域内进行能滤掉大量背景干扰鲁棒性提升非常明显。第二条是把字符识别从模板匹配换成MATLAB自带的ocr函数对标准印刷字符的识别能力更强但需要安装Computer Vision Toolbox而且汉字的识别需要语言包支持。第三条是整体换用深度学习方案比如YOLO检测车牌、LPRNet识别字符端到端准确率可以做到99%以上但需要准备训练集和GPU环境。不过我的看法是如果你想真正理解车牌识别这条技术链路一定要先把传统方法亲手调通一遍。深度学习模型虽然强但它是个黑盒你很难直观感受到“阈值偏了半级”“结构元素大了两个像素”会产生什么连锁反应。这些手感是调传统算法练出来的。5. 常见问题排查与工程化建议5.1 典型问题速查表实际调试中下面这几个问题我几乎每次都遇到直接整理成表给你参考现象可能原因解决方案完全定位不到车牌阈值分割失败车牌与背景灰度太接近改用边缘增强或者在HSV颜色空间先提取蓝色区域定位到车身广告或车灯候选区域过滤规则太宽松收紧宽高比范围增加垂直边缘密度排序条件车牌区域连本文还有配套的精品资源点击获取