资讯详情

MATLAB车牌识别课程设计:灰度定位、投影分割与模板匹配

📅 2026/10/28 4:52:02 | 华诺云谱 👁 阅读
MATLAB车牌识别课程设计:灰度定位、投影分割与模板匹配
简介面向高校信息类、自动化类专业学生的Matlab车牌识别课程设计报告配套源代码可用于课程设计、毕业设计入门与图像处理练手。压缩包共1个docx文件约1.37MB主体为报告正文源代码随文档给出便于边读原理边对照实现。目前已有116人学习。报告按设计目的、设计原理、详细设计步骤、结果分析、总结体会和参考文献展开覆盖车辆检测、图像采集与牌照识别流程并具体讨论灰度化、背景开运算、图像增强、二值化阈值、边缘提取、车牌定位、垂直投影字符分割以及模板匹配和人工神经网络两类字符识别方法同时涉及不同光照、牌照质量与车辆速度下的测试以及多牌照、假牌照等挑战和识别率优化思路。适合需要理解车牌识别算法链路并完成Matlab课程设计实践的读者。1. 从一份课程设计任务书说起MATLAB 车牌识别要交付到什么程度选题会上A 同学拿到的是「车牌识别系统设计」要求交一份 MATLAB 课程设计报告并附源代码两周后验收现场跑图、现场讲。多数人的第一反应是上深度学习可答辩时老师往往只追问一句这一步输出什么图参数为什么取这个值。所以这套方案反着来——灰度跳变定位、形态学开闭运算、垂直投影分割、模板库匹配识别全流程用脚本串起来每一步都能落一张中间图错了能定位到具体环节。适合要在两三周内跑通一套可演示系统、又必须讲清每个环节依据的人代码不追求短追求改一个参数就知道哪里会变。2. 总体方案三段链路与三个必须跑通的软件步骤2.1 把任务书拆成三段链路再塌缩成三个软件步骤任务书里的完整系统由车辆检测、图像采集、牌照识别三部分组成车辆检测部分判断车到了触发采集单元抓下当前帧识别单元再对这张图做处理定位牌照、切出字符、识别并输出号码。这是一条硬件参与的实时链路摄像机、补光、触发线圈都在里面。课程设计现场没有这些设备通常的做法是拿一张静态车辆图代替「采集到的当前帧」把三段链路塌缩成软件上的三个连续步骤牌照定位、字符分割、字符识别。塌缩不等于删减因为定位的输入质量仍然由「采集」决定——图糊了、逆光了、车牌只露一半后面全部白搭这一点在写报告的需求分析里必须交代清楚否则整篇会写成一个纯算法练习。三个步骤的顺序不能调换也不建议合并。定位粗一层分割细一层识别只对已经摆正的单个字符负责。把职责切开的好处是排错成本低识别结果错先看切出来的字符图对不对字符图歪了缺边回头看定位框是不是压住了边框。这种分层是整套方案最值钱的地方后面所有参数调整都建立在它上面。牌照颜色识别在报告里一般和号码识别互相配合、互相验证先按颜色判断是不是蓝底白字牌照缩小候选区范围再走号码识别号码识别置信度低时反查颜色。课程设计只做蓝底白字也能交但报告里要把这条互相验证的思路写出来答辩时是加分项。2.2 主流程脚本与运行环境工程别写成一坨。我一般按「一个主入口 三个函数 一个样本库目录」来组织主入口只负责串联和打印结果出问题直接看是哪一段返回的空值。% run_lpr.m —— 车牌识别主流程串起定位、分割、识别三段 clc; clear; close all; imgPath fullfile(data, car_03.jpg); % 原始车辆图像换成自己的图 Scolor imread(imgPath); if size(Scolor, 3) 3 Sgray rgb2gray(Scolor); % 彩色转灰度后续全部在灰度域处理 else Sgray Scolor; % 已经是灰度图就别再转一次 end [plateBw, plateGray, box] locatePlate(Sgray); % 第一步定位返回车牌二值子图 chars segmentChars(plateBw, plateGray); % 第二步分割返回 1x7 字符图 result recognizeChars(chars, sam); % 第三步识别样本库目录为 sam fprintf(定位框[%d %d %d %d]\n, round(box)); fprintf(识别结果%s\n, result);主入口里没有任何算法细节locatePlate、segmentChars、recognizeChars三个函数各自独立可测。这样拆分之后改定位参数不会碰到识别代码调识别样本也不会影响定位结果回退版本时能一段一段退。box用的是[x y width height]四元组和regionprops的BoundingBox保持一致省掉一次坐标换算换算多一次就多一次错的机会。工程文件的分工建议明确到表格里写报告时直接抄这一节文件作用关键输出run_lpr.m主入口串联三段并打印识别字符串locatePlate.m灰度化、背景估计、二值化、边缘、形态学、连通域筛选车牌二值子图、定位框segmentChars.m投影分析、倾斜校正、按列切割、尺寸归一化1×7 字符图像元胞recognizeChars.m读样本库、逐张比对、取误差最小字符类标字符串sam/样本库目录文件名首字符即类标22×14 的二值样本图运行环境没有特殊要求基础 MATLAB 加 Image Processing Toolbox 就够strel、edge、bwlabel、regionprops、imrotate、imresize都在里面。原文里用到的imfeature是老版本函数新版本已经并入regionprops这一点必须在报告里说明否则换台电脑跑不起来。曲线拟合部分原文用的fit(..., poly1)依赖 Curve Fitting Toolbox用polyfit可以绕开答辩现场少一个装工具箱的风险。提示所有路径用fullfile拼不要写成F:\MATLAB\work\sam\image1.jpg这种绝对路径换机器必崩。2.3 进度安排把四周拆成四个可验收的节点任务书给的进度是 19 周熟悉环境与基础、19 周选题与题目分析、20 周编程实现、20 周验收答辩。实际执行时这个粒度太粗容易前松后紧。我一般会把它翻译成带自检标准的节点每个节点都有能拿出来给人看的东西节点主要工作交付物自检标准第 1 段MATLAB 环境熟悉跑通imread、imshow、rgb2gray、edge几个基础调用一段能显示原图与灰度图的脚本换三张图不报错第 2 段题目分析确定定位、分割、识别三段的技术路线一页流程图 参数初值能口述每步输入输出第 3 段编程实现逐个模块打通并保存中间图四个函数 中间结果图集单张图端到端出结果第 4 段用 10 张以上不同光照、不同角度的图做回归成功率统计表定位成功率与识别正确率分开统计第 3 段最容易失控因为定位一崩后面全崩人会本能地去调识别参数。正确的顺序是先把定位稳定到 10 张图里至少 8 张框对再往下走定位不稳的情况下调识别等于在流沙上盖楼。第 4 段的统计一定要分开算定位成功率、分割正确率、单字符识别正确率三个数分开看才知道该修哪一段混成一个「准确率」等于自己把黑匣子关上。3. 预处理到定位五步把车牌从车身背景里抠出来3.1 灰度化与形态学背景估计自然环境下拍到的车辆图背景复杂、光照不均直接二值化会把车身反光、地面阴影一起抬成前景。通常的做法是先估计一张「背景亮度图」再用原图减掉它把缓慢变化的光照压平把突变的字符笔画留下。彩色图先转灰度这一步不只是省内存。三个通道在识别环节没有额外信息反而会让阈值在三个通道上各调一遍调参成本翻三倍。转灰度之后像素动态范围压缩到 0 到 255后续阈值的物理意义才清楚。% step_preprocess.m —— 灰度化 形态学背景估计 差分增强 Scolor imread(data/car_03.jpg); Sgray rgb2gray(Scolor); % 三通道按权值合成单通道 se strel(disk, 13); % 半径 13 的圆盘尺寸要大于字符笔画宽度 Bgray imopen(Sgray, se); % 开运算抹掉字符剩下的就是背景亮度 Egray imsubtract(Sgray, Bgray); % 原图减背景字符被抬亮光照被压平 figure, imshow(Egray), title(增强后的灰度图);strel(disk, 13)里的 13 是这套方案里最需要解释的参数。开运算会抹掉比结构元素小的亮结构半径取小了抹不掉字符背景图里还残留笔画取大了会把整块车牌当成背景一起抹掉差分之后车牌区域变黑。经验值是取字符笔画宽度的 1.5 到 2 倍普通蓝牌在常见分辨率下笔画大概 6 到 10 像素13 落在合理区间。换分辨率就要重算这个数这一点写进报告里比写一堆公式有用。imsubtract和直接用减号的区别在于它会截断到 0不会出现负值导致uint8回绕成 255 的亮斑。这个细节不注意图上会冒出几块莫名其妙的白色区域排查半天以为是定位算法的问题。3.2 阈值选取与二值化二值化的目标是把字符和背景彻底分开同时保住字符的形状不缺口、不多出空洞。全局阈值在这个场景里其实够用因为差分已经把光照压得比较平了。fmax1 double(max(Egray(:))); % 增强图的最大灰度 fmin1 double(min(Egray(:))); % 增强图的最小灰度 level (fmax1 - (fmax1 - fmin1) / 3) / 255; % 取动态范围上 1/3 处作为归一化阈值 bw im2bw(Egray, level); % 灰度转二值字符为 1 bw double(bw); % 转 double方便后面按列求和做投影阈值取的是动态范围上 1/3 处不是中点。原因是差分之后车牌字符的灰度普遍高于背景取偏高一点的位置能把字符留住、把残余噪声挡在外面。取中点会让车身反光混进来取太高则笔画断裂后面形态学闭合要花更大力气去补。im2bw接受的是 0 到 1 的归一化阈值所以这里除以 255很多同学忘掉这一步结果阈值恒大于 1二值图整片全白排查起来很费时间。二值化的输出转成double是为了后面sum投影的方便。logical类型也能求和但和浮点运算混在一起时容易出现类型不匹配的报错统一成double省事。3.3 Canny 边缘与矩形结构元素的开闭运算二值图里字符笔画是断的尤其汉字部分笔画之间有间隙。直接找连通域会把一个字拆成三四个小块所以要先做边缘提取再连接。grd edge(bw, canny); % Canny 提取笔画边缘 bg1 imclose(grd, strel(rectangle, [5, 19])); % 闭运算横向连接断开的笔画 bg3 imopen(bg1, strel(rectangle, [5, 19])); % 开运算去掉细小噪点 bg2 imopen(bg3, strel(rectangle, [19, 1])); % 纵向长条开运算断开车牌与车身粘连 figure, imshow(bg1), title(闭运算 [5,19]); figure, imshow(bg2), title(开运算 [19,1]);结构元素用矩形而不是圆盘是因为车牌是横长方形字符沿水平方向排列。[5, 19]表示高 5 宽 19横向拉长闭运算能把同一行上相邻的笔画连成一整块纵向只连 5 个像素不会把上下两行比如上下两排字符的双层牌糊在一起。[19, 1]是纵向长条开运算会抹掉宽度小于 1 个像素、高度小于 19 个像素的竖直细条。车牌和保险杠、车灯之间常有细窄的连接这一步就是专门断它的。参数别乱改改大了车牌本身被啃掉一块改小了粘连断不开定位框会一路延伸到车头。这三步的顺序有讲究先闭后开闭是为了连开是为了断。反过来先开笔画还没连上就被开运算当成噪点清掉了。3.4 连通域特征筛选与车牌裁剪到这里图上还剩好几个候选块车牌、车标、散热格栅、部分车灯。需要用形状特征把它们分开。[L, num] bwlabel(bg2, 8); % 8 邻域标记连通域 stats regionprops(L, BoundingBox, Area); % 新版用 regionprops旧版对应 imfeature ratio zeros(1, num); for k 1:num bb stats(k).BoundingBox; % [x y width height] ratio(k) bb(3) / bb(4); % 候选区宽高比 end cand find(ratio 2.2 ratio 3.8 [stats.Area] 800); % 宽高比 面积双条件 [~, idx] max([stats(cand).Area]); % 面积最大的合格候选作为车牌 box stats(cand(idx)).BoundingBox; plateBw imcrop(bg2, box); % 裁出车牌二值子图 plateGray imcrop(Sgray, box); % 同步裁灰度子图分割时要用标准蓝牌尺寸 440×140宽高比约 3.14实际拍摄有透视变形所以取 2.2 到 3.8 这个区间而不是精确值。面积下限 800 是为了过滤车标和小灯这个数跟图像分辨率强相关报告里应该给出你所用图片的分辨率并说明这个阈值怎么定的别直接抄。两个条件缺一不可。只用宽高比格栅条也可能命中只用面积车身大块区域会被选中。两个条件同时成立再取面积最大者鲁棒性才够。imcrop要裁两次二值图和灰度图必须用同一个box。分割阶段要在灰度图上找字符边界、在二值图上做投影两者尺寸不一致会直接导致下标越界。4. 校正、分割与识别从倾斜车牌到七位字符输出4.1 水平投影峰谷分析定上下边框裁出来的车牌子图通常还带着上下边框和铆钉。投影分析的做法是把二值图按行求和字符行求和值高边框行求和值低峰谷一对比就能找到字符区的上下界。histrow sum(plateBw, 2); % 按行求和得到水平投影 thr mean(histrow) * 0.5; % 均值折半作为参考阈值 mark histrow thr; % 高于阈值的行视为字符行 d diff([0 mark 0]); rowTop find(d 1, 1, first); % 第一个上升点字符区上界 rowBot find(d -1, 1, last) - 1; % 最后一个下降点字符区下界 plateBw2 plateBw(rowTop:rowBot, :); % 去上下边框后的二值子图 plateGr2 plateGray(rowTop:rowBot, :); charH rowBot - rowTop 1; % 字符高度后面归一化要用阈值用均值的 0.5 倍是经验值。取值偏高字符顶部笔画细的地方被判成背景字符被削头取值偏低边框残留进来字符高度算大归一化后字符变扁。改这个参数时盯着两个数charH是否落在合理区间常见分辨率下 20 到 40 像素以及去掉边框后第一行和最后一行是否还有大片连通白块。这一步失败最典型的症状是字符被上下削掉一块识别时「0」和「8」、「B」和「8」互相串。看到这类混淆先怀疑rowTop/rowBot别急着换识别算法。4.2 线性拟合求倾斜角并旋转车牌倾斜时投影峰谷会变糊字符中心距算不准切割位置整体偏移。倾斜校正的做法是取车牌上边框的一组前景点做直线拟合斜率转角度再反向旋转。% step_rotate.m —— 用上边框点集线性拟合唱出倾斜角 [rowIdx, colIdx] find(plateBw2 1); % 所有前景点坐标 topRow min(rowIdx); sel find(rowIdx topRow 3); % 最上面几行作为上边框点集 p polyfit(colIdx(sel), rowIdx(sel), 1); % 一次拟合 y p(1)*x p(2) angle atan(p(1)) * 180 / pi; % 斜率转角度度 plateRot imrotate(plateBw2, angle, bilinear, crop); % 反向旋转校正 grayRot imrotate(plateGr2, angle, bilinear, crop);polyfit的第三个参数 1 表示一次多项式等价于原文里的poly1好处是不依赖 Curve Fitting Toolbox。atan得到弧度乘180/pi转成度imrotate接受的就是度这一步单位搞错会转出几百度图直接转没了。imrotate的第三个参数bilinear是双线性插值比最近邻平滑字符边缘不容易出现锯齿第四个参数crop保持输出尺寸和输入一致不加这个参数图会变大后面按下标切割全部错位。角度绝对值超过 15 度就要警惕。真实的车牌倾斜一般不超过 10 度算出二十几度基本说明拟合点被字符笔画污染了这时应该只取最上面一两行、并限制横向范围在车牌中部别把左右两端也纳进来。4.3 垂直投影切字符与 22×14 归一化校正完之后按列求和字符列投影值高间隙列投影值低据此切出七个字符。% step_segment.m —— 垂直投影切字符 histcol sum(plateRot, 1); % 按列求和得到垂直投影 thr mean(histcol) * 0.6; % 均值折半偏上作为间隙阈值 above histcol thr; % 高于阈值的列标记为字符列 d diff([0 above 0]); segS find(d 1); % 每段字符的起始列 segE find(d -1) - 1; % 每段字符的结束列 segW segE - segS 1; keep segW 5 segW 40; % 过滤噪点和整块粘连 segS segS(keep); segE segE(keep); if numel(segS) 7 segS segS(1:7); segE segE(1:7); % 蓝牌固定七位多出来的截断 end chars cell(1, numel(segS)); for k 1:numel(segS) seg plateRot(:, segS(k):segE(k)); seg imresize(seg, [22 14]); % 统一到 22 行 14 列与样本库同尺寸 chars{k} seg; imwrite(seg, fullfile(sam, sprintf(tmp_%02d.jpg, k))); % 落盘便于肉眼核对 end宽度过滤的两个阈值 5 和 40 是按经验给的小于 5 的段基本是铆钉或噪声大于 40 的是两个字符粘在一起。粘字是这套方法最常见的失败模式原因通常是二值化阈值偏低导致间隙被填满回头调level比在这里硬切更有效。归一化到 22×14 不是为了好看是为了让待识别字符和样本库里的模板尺寸完全一致后面做逐像素比对才有意义。宽高比 14:22 大致对应真实字符的窄高形状缩放时不要用crop直接imresize拉伸即可变形在模板匹配里是可接受的。落盘那一行别省。切割对不对看一眼tmp_01.jpg到tmp_07.jpg就知道比在内存里翻变量快十倍。4.4 模板库匹配与神经网络备选路线识别环节原文给了两条路模板匹配和人工神经网络。课程设计用前者更稳因为可解释、可调试、不需要训练时间后者适合作为报告里的对比方案写一段。模板匹配的流程是把待识别字符和样本库中每张模板逐像素相减误差最小的那张图的类标就是结果。% step_recognize.m —— 与样本库逐张比对误差最小者胜出 function label recognizeChars(chars, libDir) files dir(fullfile(libDir, *.jpg)); % 样本文件名形如 A_01.jpg label blanks(numel(chars)); for i 1:numel(chars) q im2double(imresize(chars{i}, [22 14])); % 统一尺寸并转浮点 best inf; bestName ?; for j 1:numel(files) t im2double(imread(fullfile(libDir, files(j).name))); if ~isequal(size(t), size(q)), continue; end % 尺寸不符直接跳过 err sum(abs(q(:) - t(:))); % 逐像素绝对差之和 if err best best err; bestName files(j).name; end end label(i) bestName(1); % 文件名首字符即类标 end end误差用绝对差之和而不是欧氏距离计算量小对二值图的效果差别不大写报告时两种都算一遍做对比也行。bestName(1)直接取文件名首字符当类标这是命名约定带来的便利样本库按A_01.jpg、B_02.jpg这样命名读代码的人一眼能懂。样本库是这套方案的命门。原文那张失败样例里G 被认成 B、0 被认成 8、K 被认成 A问题几乎都出在样本不足或样本质量差上——同一个字符只有一两张模板字形稍有差异就匹配到别的类。我一般要求每个字符至少 5 张模板覆盖不同粗细和轻微倾斜。样本库的字段建议在报告里列清楚字段说明取值示例文件名首字符为类标后接序号A_03.jpg尺寸与分割输出一致22×14类型二值图白底黑字或黑底白字统一二值类标集蓝牌需要的字符集合31 个省份汉字 24 个字母 10 个数字每类样本数至少 5 张510神经网络路线在报告里这么写就够一种做法是先对待识别字符提取特征用特征训练分类器另一种是把字符图直接送进网络由网络自己学特征。难点不在网络结构在样本量——几十张图训练出来的网络在测试集上不一定打得过模板匹配这一点老实在报告里承认比硬吹效果好。5. 避坑与排查五个最容易在验收现场翻车的地方5.1 定位阶段的三个翻车点现象一定位框套在车标或散热格栅上。原因通常是宽高比区间开得太宽或者面积下限设得太小让格栅条这种横向条纹块混进了候选集。解决办法是把宽高比收窄到 2.5 到 3.5同时把「面积最大」改成「面积与宽高比加权打分最高」权重按你的测试集调。改完之后一定要拿原来失败的图回归一遍别修好一张坏掉三张。现象二定位框一直延伸到车头或保险杠。这是形态学参数没配对导致的粘连。纵向开运算strel(rectangle, [19, 1])的高度不够车牌和车身之间的细连接断不开或者横向闭运算宽度过大车牌和旁边的格栅连成一片。排查方法是依次显示grd、bg1、bg3、bg2四张中间图看哪一步开始粘连就在那一步收参数。现象三换了张图直接报错「索引超出矩阵维度」。八成是候选集为空——cand是空数组cand(idx)必然越界。根因可能是这张图里根本没有蓝牌或者二值化阈值过高把车牌整体判成背景。加一行判空再往下走比让程序崩在半路强if isempty(cand) warning(未找到符合特征的候选区域检查阈值或换图); plateBw []; plateGray []; box [0 0 0 0]; return; end5.2 分割与识别阶段的坑现象四切出八个字符或者把边框、铆钉当成字符。原因在垂直投影的阈值上mean(histcol) * 0.6这个系数在不同图上表现不一样图偏暗时均值低阈值跟着低边框列也被判成字符列。解决办法是先做左右边框剥离——把投影最左和最右各若干列直接裁掉再走分段的逻辑同时把宽度过滤的下限从 5 提到 7 左右。还有一个隐蔽原因是倾斜校正没做好字符中心距算出来整体偏移七个字符被切成八段。现象五G 认成 B、0 认成 8、K 认成 A。这类形近字混淆在模板匹配里几乎是必然的根因有三个样本库对应类别的模板太少或字形单一字符分割时上下被削0 和 8 的闭合区域差别被抹掉二值化后字符笔画粘连。排查顺序是固定的——先看tmp_0x.jpg有没有缺边再看样本库里 B 和 8 各有几张最后才动距离度量。很多同学一上来就把绝对差换成相关系数结果只是把错误从一类挪到另一类。5.3 工程与版本兼容的坑最典型的一个坑是路径写死。原文示例里出现了F:\MATLAB\work\sam\image1.jpg这种绝对路径换台电脑、换个盘符整个识别环节直接失效。统一改成fullfile(sam, sprintf(image%d.jpg, k))所有资源跟着工程目录走压缩包发出去别人也能直接跑。第二个是函数版本差异。imfeature在较新版本里已经被regionprops取代直接照抄老代码会报未定义函数im2bw也建议换成imbinarize虽然前者目前还能用但会给出警告信息答辩投影时跳一堆黄色警告不好看。写报告时在环境说明里把版本和替代关系写清楚这本身就是工程素养的体现。第三个是中间图没有保存。全程只在最后显示一张识别结果出错了根本不知道错在哪一步。我一般强制在locatePlate和segmentChars里各存四到六张中间图到debug/目录跑完一轮翻一遍图比盯着代码猜快得多。6. 验收与答辩用中间结果图讲清每一步为什么这么做验收现场的时间通常只有五到八分钟老师不会让你从头讲代码但一定会问「为什么」。有效的做法是准备一页中间结果图序列原图、灰度图、增强图、二值图、边缘图、闭运算图、开运算图、定位框、去边框后的车牌、七个字符小图、最终识别串。每张图下面标一行参数被问到就指图回答。答辩自检表可以按这个结构准备检查项合格标准常见扣分点现场跑通换一张新图能在 10 秒内出结果只对固定那张图有效参数可解释每个关键参数能说出取值依据「试出来的」失败可定位能指出失败发生在定位/分割/识别哪一段只会说「识别率不高」边界说清说明只做蓝底白字其他颜色需调整声称全部支持版本兼容说明regionprops与imfeature的替代关系换机器报错汇报顺序建议倒过来讲先给三张测试图的最终结果让人看到系统能跑再回头讲链路讲定位为什么用灰度跳变而不是颜色分割讲形态学结构元素为什么是[5, 19]和[19, 1]讲字符为什么归一化到 22×14。倒序讲的好处是老师带着「结果是对的」这个前提听过程追问会更聚焦在方法选择上而不是纠缠系统能不能用。失败案例别藏。原文里那张把 G 认成 B、0 认成 8 的样例反而是最好的答辩素材——你能说清是样本库太薄、是分割削了字符顶部、还是二值化阈值偏低就说明你真的理解这套链路而不是跑通了一个黑匣子。诚实交代边界条件比硬撑着说准确率百分之九十九可信得多。从那以后我每次带课程设计都强制要求先跑一遍十张图的回归把定位成功率、分割正确率、单字符正确率分开记录下来再谈优化。没有这三个数任何调参都是在赌。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑