2026/11/2 8:11:59

MATLAB车牌识别课程设计全流程:定位、分割、识别与避坑参数表

MATLAB车牌识别课程设计全流程:定位、分割、识别与避坑参数表 简介《基于Matlab车牌识别课程设计报告附源代码》面向电子信息、自动化及计算机相关专业学生与课程设计指导教师围绕车牌定位、字符分割与字符识别三大核心环节提供一份可参考的完整设计文档。压缩包共1个docx文件约1.37MB正文含设计目的、总体方案、详细实现步骤、结果分析、总结体会与参考文献并附Matlab源代码及关键处理片段。内容具体展开图像灰度化、背景估计与增强、二值化阈值选取、基于垂直投影的字符切分以及模板匹配和人工神经网络两类识别思路同时涉及不同光照、车牌质量与拍摄速度下的测试和识别率优化讨论。已有116人学习适合需要完成课程设计、快速搭建Matlab实验流程或对照报告整理算法与代码的读者。1. 一块车牌跑通全流程这套 MATLAB 课程设计报告值不值得照着做课程设计季一到问得最多的就是那套 MATLAB 车牌识别代码现在还能不能跑。我手上这份课程设计报告加源代码走的是最朴素也最耐查的路线灰度化、开运算背景建模、背景差分、阈值二值化、Canny 边缘、形态学开闭运算、长宽比筛候选框再靠投影切字符、模板库做匹配。答辩最容易追问的定位、分割、识别三段它都留了中间图能讲清每一步为什么这么干而不是只交一个能跑的结果。适合正在做图像处理课程设计、想照着复现一遍流程的人也适合带课的老师拿去当示例改。代码是旧版 MATLAB 写法直接丢进新版本会翻车后面把版本适配和参数标定的坑一并讲透。2. 车牌定位链路拆解从灰度化到候选框筛出唯一区域这一章把定位段拆成四步灰度与背景估计、背景差分与二值化、边缘与形态学、连通域筛选。每一步都给了可运行的片段代码按 R2016b 之后的语法整理旧函数替换的地方单独标注。跑通这四步后面字符分割才有稳定的输入否则投影图全是毛刺再怎么调参数也是白费。2.1 灰度化与形态学背景估计strel(disk,13)到底在干什么自然场景里车牌的最大干扰不是噪声是光照不均。车顶反光、树荫、地库顶灯都会让同一块车牌上半部分和下半部分的灰度差出几十级。这份代码没有上来就全局阈值而是先用开运算估一个“背景照明图”再把它减掉。开运算的本质是先腐蚀后膨胀结构元尺寸大于字符笔画宽度时笔画被腐蚀没了剩下的就是缓慢变化的背景。这一步在照明均匀的图上增益不明显但在逆光或局部阴影下差分后的字符对比度能拉出一大截后面二值化才不至于把整块车牌吞掉。%% 2.1 灰度化 形态学背景估计 背景差分 clear; close all; clc; imgPath fullfile(pwd, plate_samples, 3.jpg); % 相对路径换机器不用改 Scolor imread(imgPath); if size(Scolor, 3) 3 Sgray rgb2gray(Scolor); % 彩色转灰度加快后续运算 else Sgray Scolor; % 已经是灰度图就直接用 end seBack strel(disk, 13); % 背景估计结构元半径略大于字符笔画宽度 Bgray imopen(Sgray, seBack); % 开运算抹掉细笔画保留照明背景 Egray imsubtract(Sgray, Bgray); % 背景差分字符被拉出来噪声同时被放大 figure; subplot(1,3,1); imshow(Sgray); title(原始灰度); subplot(1,3,2); imshow(Bgray); title(背景估计); subplot(1,3,3); imshow(Egray, []); title(差分增强);strel(disk,13)里的 13 是半径不是直径。它对标定最敏感原图缩到 640 宽还沿用 13背景估计会把车牌边框一起抹平差分图里车牌区域反而变暗原图放到 1920 宽还用 13笔画残留背景图上能看到一排排字符的残影。我一般按图像宽度取800 宽左右的图用 8 到 13 比较稳先看背景图笔画干净了再往下走。imsubtract遇到负值会截断到 0所以差分结果整体偏暗是正常的别急着调亮度。2.2 阈值落在动态范围的上三分之一一行level的连锁反应fmax1 double(max(Egray(:))); % 用 (:) 展平别写成 max(max()) fmin1 double(min(Egray(:))); level (fmax1 - (fmax1 - fmin1)/3) / 255; % 阈值放在动态范围上 1/3 处 bw im2bw(Egray, level); % im2bw 的阈值参数是 0~1这行level等价于把阈值放在离最大值三分之一动态范围的位置再归一化到 0 到 1 给im2bw用。偏保守倾向多留白像素好处是字符不容易断坏处是白车身、地面高光、车道线都会被一起留下。所以这一步不是“调准”是“调到一个后面能靠形态学收拾掉的程度”。如果二值图里车牌字符已经粘连成一块把系数从 1/3 改到 1/4 试阈值往上抬一点如果字符大面积断裂改到 1/2。别小看这零点几的差别一张逆光图能差出整块车牌的连通性。注意im2bw的第二个参数必须落在 0 到 1 之间。有人把level再乘 255 传进去MATLAB 不报错但结果是全黑或全白排查半天以为是图像问题。2.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; subplot(2,2,1); imshow(grd); title(Canny 边缘); subplot(2,2,2); imshow(bg1); title(闭运算 [5 19]); subplot(2,2,3); imshow(bg3); title(开运算 [5 19]); subplot(2,2,4); imshow(bg2); title(纵向开运算 [19 1]);结构元尺寸是这套流程里第二个敏感参数。[5 19]的意思是竖向 5 像素、横向 19 像素为什么要各向异性车牌是扁的字符之间的间隙是竖向细缝横向是长条用一个方形结构元要么连不上条要么把整幅图连成一片。第一次闭运算把字符横向连成候选条第二次开运算把粘连的小点擦掉最后一次[19 1]用竖向长条把“高度超标”的连通域削掉——车牌高的宽比摆在那竖直方向太高的区域基本是护栏、立柱或者保险杠。这里的尺寸和分辨率强绑定。800 宽图上[5 19]表现不错1920 宽图上就得放大到[12 46]左右。判断标准看中间图闭运算后车牌区域应该是一条完整的、边缘略毛的长条如果字符还是一颗颗分开的横向尺寸不够如果整块图糊成一团横向尺寸过大。2.4bwlabel与长宽比筛选把候选压到唯一一个[L, num] bwlabel(bg2, 8); % 8 连通斜向粘连也能标到一起 stats regionprops(L, Area, BoundingBox); % 旧报告的 imfeature 已废弃 if isempty(stats) error(没有找到任何连通域回到 2.2 检查二值化); end area [stats.Area]; bbox cat(1, stats.BoundingBox); % [x y w h] 左上角坐标加宽高 ratio bbox(:,3) ./ bbox(:,4); % 宽高比 keep find(area 400 ratio 2.2 ratio 4.5); if isempty(keep) error(长宽比筛完没有候选放宽 ratio 区间或降低面积下限); end [~, idx] max(area(keep)); % 多个候选时取面积最大的 plateBox bbox(keep(idx), :); plateImg imcrop(Scolor, plateBox); % 从原彩图裁保留颜色信息中国单行车牌的字符区域宽高比大致在 3 比 1 附近含边框会比这个宽一点。区间取 2.2 到 4.5 是留了倾斜和透视的余量太窄会漏掉斜拍的牌太宽会把保险杠上的长条一起收进来。面积下限 400 是 800 宽图的经验值分辨率翻倍后要乘四。取面积最大的候选是个偷懒但有效的策略多车牌场景下会失效那种情况得按位置排序再逐个识别。到这一步plateImg就是后面所有分割和识别的输入。建议每张中间图都imwrite存盘答辩时拼成一张流程对比图比讲十句原理都直观。3. 字符分割水平投影定行、垂直投影切字的两轮校正定位只解决“车牌在哪”分割解决“第几个字在哪”。这一段的输入是定位后的车牌二值子图输出是一组统一尺寸的字符小图。报告里的顺序是先做倾斜校正再用水平投影去掉上下边框锁定字符行最后用垂直投影切出七个字符并归一化到 22 行 14 列。顺序不能乱倾斜没校正就直接投影峰谷会糊成一片阈值怎么调都切不准。3.1 倾斜校正小角度扫描比直接拟合更稳%% 3.1 角度粗估小角度扫描取水平投影方差最大的角度 bestAngle 0; bestScore -inf; for a -8:1:8 tmp imrotate(sbw0, a, bilinear, crop); % sbw0 为定位后的车牌二值子图 hp sum(tmp, 2); % 每行白像素数 score var(hp); % 投影越尖锐说明行越对齐 if score bestScore bestScore score; bestAngle a; end end sbw imrotate(sbw0, bestAngle, bilinear, crop);报告里用线性拟合求上下边缘的斜率再算角度思路没错但对边缘提取的质量很敏感——边缘断成几段时拟合出来的斜率能偏好几度。我一般先跑一遍 -8 到 8 度的一度步长扫描用水平投影的方差当评分方差最大的角度就是让字符行最“平”的角度。这招在倾斜小于 15 度时很稳代价是九到十七次旋转单张图耗时可以忽略。扫描出的整数角度再交给拟合做精修精度和鲁棒性都能兼顾。imrotate的正角度是逆时针crop参数保持输出尺寸不变边缘会补黑。如果校正后车牌反而更歪多半是拟合时取点顺序反了导致角度符号相反先用扫描法兜底再回头查拟合。3.2 水平投影去上下边框把字符行夹出来hp sum(sbw, 2); % 水平投影每行白像素数 hp hp / max(hp eps); % 归一化消除分辨率差异 hpS movmean(hp, 3); % 三行滑动平均压掉单行噪声 th mean(hpS) * 0.6; % 经验阈值字符行显著高于边框行 rowFlag hpS th; d diff([0; rowFlag; 0]); % 找连续段的起止 rowStart find(d 1); rowEnd find(d -1) - 1; [~, mainIdx] max(rowEnd - rowStart); % 最长的连续段就是字符主体 rowTop rowStart(mainIdx); rowBot rowEnd(mainIdx); sbwChar sbw(rowTop:rowBot, :);车牌二值子图的上边和下边各有一条水平边框投影上表现为两个窄峰中间隔一段低谷字符行的峰又宽又高占车牌高度的六到八成。用“最长连续段”挑字符主体比报告里“取最大峰再找相邻峰”更抗噪。阈值系数 0.6 是经验值光照均匀的图 0.5 到 0.7 都能用背景残留多的图要往上提。这一步失败最典型的表现是切出来的字符上下被削掉一截或者带进一条黑边。前者是rowBot取小了把阈值系数降到 0.5 再试后者是rowTop取大了检查一下闭运算的竖向尺寸是不是过大把边框和字符连到了一起。3.3 垂直投影切字符与 22×14 归一化vp sum(sbwChar, 1); % 垂直投影每列白像素数 vp vp / max(vp eps); vS movmean(vp, 3); isGap vS 0.15; % 谷区判定0.15 是经验阈值 d diff([0, isGap, 0]); gapStart find(d 1); gapEnd find(d -1) - 1; % 两个谷区之间的高投影段就是一个字符 charStart [1, gapEnd(1:end-1) 1]; charEnd [gapStart(2:end) - 1, size(sbwChar, 2)]; segs cell(1, numel(charStart)); for k 1:numel(charStart) cleft max(1, charStart(k)); cright min(size(sbwChar, 2), charEnd(k)); seg sbwChar(:, cleft:cright); segs{k} imresize(seg, [22 14]); % 统一到 22 行 14 列供模板匹配 end垂直投影切字符有两个先天问题。第一汉字和后面的字母之间间隔大字母之间间隔小用同一个谷区阈值会导致汉字被切成两半或者字母被合并。第二车牌上的铆钉、污渍会在投影里造出假谷多切出一个碎片。实际处理时加两条约束切出来的字符宽度和平均宽度的偏差不超过 40%总字符数不满足 7 个就按宽度排序取前七个。这不是万能的但能挡掉大部分低级错误。归一化到 22×14 是报告里的设定我建议改成 32×16。22×14 一共才 308 个像素0 和 8、G 和 6 的差异被压得几乎看不见后面模板匹配误判大多出在这里。尺寸加一倍匹配耗时会涨但对一张图七个字符的量级完全感觉不到。3.4 样本库落盘命名规则决定后面的识别上限outDir fullfile(pwd, sam, trained); if ~exist(outDir, dir) mkdir(outDir); end for k 1:numel(segs) fname fullfile(outDir, sprintf(char_%02d.jpg, k)); imwrite(segs{k}, fname, jpg); end报告里用image1.jpg到image7.jpg这种纯序号命名识别时靠循环下标去对字符表程序能跑可一旦中间某一刀切歪后面所有字符全部错位而且很难查。我习惯在文件名里带标签比如A_01.jpg、0_03.jpg或者另存一个labels.mat存字符数组。多花十行代码排查时能省一晚上。落盘还有个好处样本库可以离线增广旋转一两度、加一点高斯噪声同一类字符扩出三到五份模板匹配的容错会明显变好。4. 字符识别两条路模板匹配够快神经网络够泛分割之后是识别。报告里写了两条路模板匹配和 BP 神经网络。前者是把分割出来的字符缩放后与字符库逐像素比对取误差最小的当结果后者是提取特征训练网络或者直接把图像送进网络。对课程设计这个体量模板匹配当天就能出结果神经网络光样本采集就可能卡一周。但模板匹配的上限很低光照、字体、污损一变就掉点两条路的取舍要提前想清楚。4.1 模板匹配平均绝对误差加拒识阈值%% 4.1 单字符模板匹配 % tmpl : N×(22*14) 矩阵每行是一个展平的模板 % labels : 长度 N 的 cell对应每个模板的字符 segVec double(seg(:)); % 1×308 行向量 err mean(abs(tmpl - repmat(segVec, size(tmpl,1), 1)), 2); [minErr, idx] min(err); if minErr 0.32 predChar ?; % 拒识宁可返回问号也不硬猜 else predChar labels{idx}; end误差度量用平均绝对误差而不是欧氏距离原因是欧氏距离对个别离群像素过于敏感车牌字符上的一个污点就能把距离拉偏一个数量级。0.32这个拒识阈值不是拍脑袋来的拿一批测试图把正确识别的误差和错误识别的误差分别统计出来中间会有一段明显的间隔阈值放在间隔中点附近。没有拒识机制的模板匹配最坑的地方是“永远给你一个答案”错的也给你答辩演示时翻车就翻在这。匹配前还有个容易漏的细节分割出来的字符是 logical 类型模板如果是 double 类型abs运算前必须先统一否则差值计算会出莫名其妙的结果。上面代码里两边都double了别省这一步。4.2 样本库怎么建才不拖后腿字符集一共 65 类左右31 个省简称汉字、24 个字母去掉 I 和 O、10 个数字。每类至少五份样本就是三百多张图手工一张张切不现实。可行的做法是拿一批已定位好的车牌跑一遍批量分割自动落盘再人工把切错的挑出来删掉剩下的按标签改名。这个过程枯燥但样本库的质量直接决定识别率的上限报告里那句“数字库的建立很重要”是实话。样本库的另一个坑是尺寸一致性。imresize到 22×14 之后同一字符在不同光照下二值化出来的笔画粗细能差一两个像素模板取平均会把笔画糊掉。我的做法是每类保留三份模板正常光照、偏亮、偏暗匹配时取三份里的最小误差。模板数量翻三倍匹配耗时还是毫秒级但跨光照的稳定性提升明显。4.3 换成 BP 神经网络特征维度和样本量的现实差距报告里的方案是四个子网络分别识别汉字、字母、字母数字、数字这种拆分思路在工程上是对的能缩小每张网络的输出维度。但课程设计环境下真正卡人的不是网络结构是样本量。308 维输入、40 个隐层节点、65 类输出每类没有二三十个样本根本训不出稳定的权值而手工采集三百个样本的工作量已经超过写代码本身。如果确实想走这条路建议把输入从原始像素换成投影特征字符的水平和垂直投影各取 16 个 bin加上宽高比和笔画密度一共三十多维样本需求量能降一个量级训练也快。代价是特征工程要自己做投影 bin 的划分方式得试几轮。对答辩来说模板匹配能讲清楚误差度量和拒识逻辑比硬套一个训练不收敛的网络更有说服力。5. 避坑与排查五个把识别率拉回及格线的现场记录这一章的五个问题是我照着报告复现时真实撞上的每一个都能让流程在某个环节静默失败——不报错但结果就是不对。按“现象、原因、解决”记下来照着对一遍能省不少时间。5.1 现象imfeature报未定义函数原因imfeature在 R2013a 之后就被移除了报告成文时间早于这个版本代码本身没错是版本不兼容。解决换成regionprops注意返回的是结构体数组而不是矩阵取字段要写[stats.Area]、cat(1, stats.BoundingBox)。另外regionprops的返回顺序和bwlabel的标签一一对应中间如果做过删除连通域的操作索引会错位要么重新标记要么保留原标签数组做映射。5.2 现象定位框把整个车头都框进来了原因闭运算结构元[5 19]是按某张特定分辨率的图调出来的换一张宽度翻倍的图19 像素的横向结构元只够连半个字符候选区域碎成一片面积最大的那块反而是保险杠。解决进流程前先统一缩放把图imresize到宽度 800所有结构元尺寸按这个基准调或者按round(w/800)的比例动态缩放结构元。缩放会损失一点细节但换来的是参数可复用批量跑图时不用一张张改。5.3 现象倾斜校正后车牌歪得更厉害原因拟合直线的取点顺序和图像坐标系方向相反算出来的角度符号是反的imrotate正角度逆时针结果越转越歪。解决别信拟合先用 -8 到 8 度的一度步长扫描取水平投影方差最大的角度这个判据和符号无关。扫描出来的角度和拟合结果差两度以上说明边缘点取错了回去查边缘提取那一环。5.4 现象0 识别成 8G 识别成 B原因归一化尺寸 22×14 太小0 和 8 的内圈差异只剩几个像素二值化后几乎一样G 和 B 的右侧竖笔在缩放时被插值糊掉。报告里自己也记录了 G 误识为 B、K 误识为 A、0 误识为 8这不是偶然。解决归一化尺寸提到 32×16每类字符保留多份模板按最小误差匹配再加拒识阈值兜底。如果这三招都上了还错说明样本库里这一类字符的样本本身就偏少去补样本别在算法上继续折腾。5.5 现象imwrite报目录不存在换台电脑就崩原因保存路径写死成F:\MATLAB\work\sam\image%d.jpg换台电脑没有 F 盘直接报错退出。解决全部换成fullfile(pwd, ...)拼相对路径写之前用exist(outDir, dir)判断、mkdir补建。路径问题看起来低级但它是“在我电脑上能跑”类事故的头号来源交付前一定查一遍所有硬编码路径。6. 从能跑到能交付一套验收集和一张参数速查表单张图跑通不算数验收集至少要覆盖三种光照正面顺光、逆光、夜间或地库。每类三到五张分辨率统一到同一宽度跑完把中间图和最终识别结果拼成一张对比图存盘。这么做有两个目的答辩时说明算法在不同条件下的边界自己排查时也能一眼看出是哪一步失效。参数速查表按 800 宽图给基准值其他分辨率按比例缩放。参数建议值800 宽基准失效表现调整方向背景开运算半径813小了残笔画大了吃边框看背景图笔画抹净为止二值化阈值位置动态范围上 1/3偏小白斑多偏大字符断±0.05 微调闭运算结构元[5 19]连不上条或糊成一片横向尺寸按字高比例纵向开运算结构元[19 1]竖高干扰剥不掉按车牌高宽比推算候选面积下限400 像素小框漏检按分辨率平方缩放宽高比区间2.24.5窄了漏斜牌宽了进保险杠单行车牌实测字符归一化尺寸32×16 优先0/8、G/B 混淆不超过原尺寸一倍拒识阈值平均绝对误差 0.32低了误识高了全返问号按测试集统计间隔取中点批量验证用一个循环把验收集跑一遍结果打到命令行比一张张点开看快得多%% 6.1 验收集批量跑结果落表 imgList {front_01.jpg, backlight_02.jpg, garage_03.jpg}; resCell cell(numel(imgList), 2); for i 1:numel(imgList) resCell{i,1} imgList{i}; resCell{i,2} runPlatePipeline(fullfile(pwd, plate_samples, imgList{i})); fprintf(%-18s - %s\n, resCell{i,1}, resCell{i,2}); endrunPlatePipeline是把前面定位、分割、识别各步封成的函数返回七位字符串。封装的时候记得给每一步留一个debug开关打开就把中间图imwrite到debug/目录下关掉就只返回结果。验收集里只要有一张识别不对打开开关重跑这一张中间图按顺序排开问题卡在哪一步一目了然。从那以后我每次交付图像处理类的课程设计代码都强制先跑一遍三光照验收集把中间图拼成一张长图塞进附录再对着参数表逐项核一遍硬编码。血泪经验是算法本身很少翻车翻车的都是路径、版本、分辨率这类看着不起眼的细节。把这份报告当骨架先把中间图跑出来再按参数表一项项压能省掉不少试错。希望帮到你。本文还有配套的精品资源点击获取