
简介基于Matlab的车牌识别课程设计报告以docx文档形式提供内容覆盖车牌识别系统的完整设计流程从系统组成、车辆检测与图像采集到车牌定位、字符分割与字符识别算法并配有详细设计步骤、流程图和Matlab源代码片段适合信息科学与工程专业学生完成课程设计或进行图像处理、模式识别实践。报告重点阐述了垂直投影法字符分割、模板匹配与神经网络识别等核心方法同时包含开运算背景分离、二值化等预处理实现可帮助读者理解车牌识别从图像输入到号码输出的完整链路。资源共1个docx文件压缩包大小约1.37MB属于轻量级文档型资料已有116人学习下载在同主题课程设计报告中有一定参考价值。读者可用于对照自己的设计方案、补充算法原理说明也可借鉴其中的Matlab代码快速搭建车牌识别原型系统。1. 这份Matlab车牌识别课程设计定位、分割、识别一条龙车牌识别是图像处理课设里的常青树很多学生第一次接触“图像处理到底怎么落地”就是从这个题目开始的。这套Matlab车牌识别课程设计报告从一张普通照片出发把灰度化、背景相减、二值化、Canny边缘检测、形态学开闭运算、区域宽高比筛选、倾斜校正、投影分割、模板匹配这些环节完整走了一遍报告文档和源代码都是齐的。它的价值不在于识别率有多高而在于把“定位-分割-识别”这条链路讲得可以一步步复现。适合两类人一是刚拿到课设题目、想快速跑通全流程的学生二是工作中需要处理图像定位和字符分割、想借一套现成思路的从业者。下面我把这条链路拆开讲重点落在参数怎么设、哪几步容易翻车。2. 图像预处理与二值化灰度化、背景相减和阈值公式拆解报告从imread(3.jpg)读入图像开始但真正决定后续成败的是预处理阶段三个操作灰度化、开运算背景估计、减法增强。这三个操作合起来做了一件事把一张光照复杂、背景杂乱的照片变成“亮字符、暗背景”的理想二值图。2.1 灰度化与背景估计为什么要先做一次开运算彩色图像三通道信息量大直接处理会拖慢速度更重要的是车牌识别本身依赖的是亮度和形状特征而不是颜色。rgb2gray是Matlab标准灰度化接口内部按亮度加权公式0.299R 0.587G 0.114B对三通道加权求和。这一步没有玄学就是降维提速。Scolor imread(3.jpg); % 读入彩色原图 Sgray rgb2gray(Scolor); % RGB转灰度后续所有处理都在单通道上进行 figure, imshow(Sgray), title(原始黑白图像); s strel(disk, 13); % 圆盘结构元素半径13 Bgray imopen(Sgray, s); % 开运算估计背景亮度 figure, imshow(Bgray), title(背景图像);imopen是形态学开运算先腐蚀后膨胀。腐蚀把细小的亮目标抹掉膨胀再把大尺度的背景结构恢复回来。disk半径13的意思是直径26像素以下的亮细节会被腐蚀掉只留下缓慢变化的背景亮度场。这个13是经验值和图像分辨率强相关。如果原图是千万像素级别的大图13可能偏小背景里会残留车体轮廓如果是三四百像素的小图13可能偏大连车牌底板都被“背景化”了。Egray imsubtract(Sgray, Bgray); % 原图减背景突出比背景亮的目标 figure, imshow(Egray), title(增强黑白图像);imsubtract把原图灰度值和背景估计值逐像素相减。实际效果是车身、路面这类缓变区域被压暗而白色车牌字符、车牌底板边缘这类比局部背景亮的目标被保留下来。报告把这步叫“图像增强”本质是高通滤波的形态学版本。2.2 背景相减与阈值二值化level 那行代码的来历增强之后就要二值化。报告里这段代码是很多人看不懂的地方fmax1 double(max(max(Egray))); % 增强图的最大灰度值 fmin1 double(min(min(Egray))); % 增强图的最小灰度值 level (fmax1 - (fmax1 - fmin1)/3) / 255; % 阈值落在动态范围高端1/3处 bw22 im2bw(Egray, level); % 大于level置1小于level置0 bw2 double(bw22); % 转成double方便后续计算 figure, imshow(bw2), title(图像二值化);把 level 的公式展开level (max - (max-min)/3) / 255也就是(min 2*(max-min)/3) / 255。意思是阈值取在灰度动态范围从低到高的 2/3 处换句话说只保留灰度最高的那 1/3 区域。为什么这么设因为在增强图里车牌字符是整幅图最亮的目标取高端阈值能最大限度把字符和亮噪点分开。除以 255 是因为im2bw的 level 参数必须是 [0,1] 区间的归一化值。这个公式是典型的工程经验值不是统计意义上的最优阈值。它隐含一个假设字符比背景亮而且亮得足够多。光照条件一变这个阈值就会飘。比如强逆光下车身反光max 被高光点拉高阈值跟着升高真字符可能被误杀阴天整体灰度偏低动态范围被压缩阈值可能过低路面纹理全变成前景。所以复现时第一件事是打印fmax1、fmin1和level三个值看二值图里车牌字符是不是完整白色。2.3 Canny 边缘检测与形态学滤波让车牌区域连成块二值图里字符是实心的白色块直接做区域标记会出现一个字符被算成多个连通域的情况。报告的处理是先对二值图做边缘检测把实心块变成轮廓再用形态学操作把轮廓重新焊接成车牌形状。grd edge(bw2, canny); % Canny边缘检测提取二值图轮廓 figure, imshow(grd), title(图像边缘提取); bg1 imclose(grd, strel(rectangle,[5,19])); % 闭运算焊接口字断裂 figure, imshow(bg1), title(图像闭运算[5,19]); bg3 imopen(bg1, strel(rectangle,[5,19])); % 开运算去掉小噪点 figure, imshow(bg3), title(图像开运算[5,19]); bg2 imopen(bg3, strel(rectangle,[19,1])); % 竖直细长条开运算按高度筛 figure, imshow(bg2), title(图像开运算[19,1]);这里三个结构元素各有分工。第一个是[5,19]矩形宽19高5横向长纵向短。闭运算先把字符轮廓内部断裂和字符间的小缺口焊起来形成横向条带接着同样的矩形做开运算把比这个尺寸小的孤立噪点清掉。第三个[19,1]是宽1高19的竖直细长条开运算要求图中结构能完整容纳一个19像素高的竖条才保留于是细横线、短划痕这类噪声被剔除而车牌字符行因为高度接近19像素被保留。这组5和19的数字不是随便拍的它们对应的是“字符在小分辨率图像里的线宽量级”。如果你的图像采集距离远、车牌在画面里占比小19这个数可能要缩到12甚至8如果车牌占据画面很大19会不够用字符行会被误删。改参数的标准只有一个让处理后只剩车牌区域这个连通块车身其他纹理全部消失。3. 车牌候选区域筛选区域标记、宽高比与先验知识形态学做完图像里剩下的白色连通块通常不止车牌一个——车灯、保险杠上的反光条、车身文字都可能残留。这一步要把真正的车牌区域从这些候选块里挑出来靠的是两个先验连通域面积和宽高比。3.1 bwlabel 与区域特征统计老接口 imfeature 的替代方案报告里用的是bwlabel加imfeature前者标记连通域后者计算区域特征。问题是imfeature在Matlab新版本里已经被移除了直接跑会报“未定义函数”。必须换成regionprops字段名和返回结构基本一致。[L, num] bwlabel(bg2, 8); % 8连通标记num是连通域个数 RGB label2rgb(L, spring, k, shuffle); % 伪彩色显示每个连通域 figure, imshow(RGB), title(图像彩色标记); stats regionprops(L, Area, BoundingBox); Area [stats.Area]; % 每个区域的像素面积 BoundingBox reshape([stats.BoundingBox], 4, num); % 每行[x y width height]bwlabel的8表示8邻域连通即上下左右加四个对角方向都算连通。对车牌字符和边框来说8连通容易把断开的笔画重新接上适合后续整体提取。regionprops返回的BoundingBox格式是[x, y, width, height]x和y是区域最小外接矩形的左上角坐标width和height是宽和高单位是像素。label2rgb是纯可视化辅助把每个连通域染成不同颜色方便肉眼确认“到底哪些块被标记出来了”。这一步对调试非常有用尤其当筛选结果不对时先看彩色标记图能立刻判断是形态学参数的问题还是筛选阈值的问题。3.2 宽高比筛选把 3.14:1 用进 BoundingBox车牌筛选最核心的规则是宽高比。我国蓝底白字车牌标准尺寸是440mm×140mm宽高比约3.14:1。加上车牌边框、铆钉和字符区域的实际边界二值化后提取的候选块宽高比通常会落在2.5到4.5之间。报告里这部分只画了流程图没有给出具体阈值实际操作时我一般用下面这组条件candidates []; for k 1:num w BoundingBox(k,3); h BoundingBox(k,4); ratio w / h; % 车牌宽高比约3.14放宽到2.5~4.5面积下限防小噪点 if ratio 2.5 ratio 4.5 Area(k) 500 candidates [candidates; k, BoundingBox(k,:)]; end end注意Area的单位是像素个数不是物理面积所以下限500这个值完全取决于图像分辨率。更稳的做法是先把所有连通域的Area打印出来看车牌区域和其他噪点的面积分布取一个能明显分开两者的值。宽高比的上下限同样要结合图片里的车牌倾斜程度如果车牌倾斜超过10度外接矩形的宽高比会被拉大上限4.5可能不够建议打印所有候选块的宽高比再定阈值。筛选逻辑还有一个隐藏点如果二值化做得不好车牌字符和底板没有被形态学焊成一个连通块而是碎成几块那么每一块的宽高比都不可能是3.14。这种时候不要急着调筛选参数回头查第2章的形态学结构元素尺寸。3.3 灰度子图与二值子图为投影分析准备的输入找到候选区域后要用BoundingBox从原灰度图和二值图中分别裁剪出子图。报告里灰度子图叫subcol二值子图叫sbw1后面所有投影和分割都在二值子图上进行。% 假设筛选出的最佳候选区为box [x y w h] box candidates(1, 2:5); % 取第一个候选区实际应比较所有候选 subcol imcrop(Sgray, box); % 从灰度图裁出车牌区域 sbw1 imcrop(bw2, box); % 从二值图裁出对应区域 figure, subplot(1,2,1), imshow(subcol), title(灰度子图); subplot(1,2,2), imshow(sbw1), title(二值子图);这里有个容易被忽略的细节裁剪必须用同一个box同时作用于灰度图和二值图保证两个子图严格对齐。灰度子图是给人看的用来确认“这确实是车牌”二值子图是给后续算法用的投影、拟合、分割全部基于它。如果二值子图里字符连成一条黑带或者碎成麻点不要继续往下走先回头调整二值化阈值。到这一步图像已经从“一张照片”变成了“一块像车牌的二值区域”。接下来最考验代码功力的是投影分析和倾斜校正这也是报告里代码最乱、变量最容易对不上的部分。4. 倾斜校正与字符分割投影峰谷分析的完整链路投影分析的核心思想很朴素把二值图像按行或按列累加白点数量字符区域会在投影曲线上形成峰字符间隙形成谷。问题是报告里markrow、markcol这一堆变量的赋值过程被省略了直接照跑必然报错。下面这套是我按相同逻辑重写的等价实现能直接跑通。4.1 水平投影与峰谷分析markrow 系列的现代写法% sbw1为第3章得到的车牌二值子图 histrow sum(sbw1, 2); % 水平投影每一行白点数量求和 figure, bar(histrow), title(水平投影含边框); % 投影序列转0/1超过峰值20%的行算字符行 projMask histrow max(histrow) * 0.2; d diff([0; projMask; 0]); % 前后差分找跳变沿 rowTop find(d 1); % 上升沿字符带起始行 rowBot find(d -1) - 1; % 下降沿字符带结束行sum(sbw1, 2)对每行求和得到的是行号到白点数的映射。一个标准车牌的投影曲线从上到下应该是上边框峰、字符峰群、下边框峰。max(histrow)*0.2这个阈值把投影曲线压成0/1序列再用diff找前后状态变化的位置——从0变1是行带开始从1变0是行带结束。rowTop和rowBot就是报告里markrow和markrow1想表达的东西。如果这段代码跑出来rowTop和rowBot长度一大串说明二值图里残留的噪声行太多往下走之前先把第2章的形态学参数重新过一遍。4.2 车牌旋转校正poly1 拟合、弧度转角度与 imrotate水平投影曲线如果峰谷不明显比如字符行整片糊成一个宽峰最常见的原因是车牌倾斜。倾斜车牌在水平投影上各字符行错位叠加峰被抹平。报告的解法是线性拟合车牌边缘算出倾角再把图像转回来。% 逐行找第一个白点坐标拟合车牌上边缘直线 xdata zeros(size(sbw1,1),1); ydata (1:size(sbw1,1)); for r 1:size(sbw1,1) c find(sbw1(r,:) 1); if ~isempty(c) xdata(r) c(1); % 该行第一个白点的列号 end end valid xdata 0; % 剔除全黑行 fresult fit(xdata(valid), ydata(valid), poly1); % 一阶多项式拟合 angle atan(fresult.p1) * 180 / pi; % 斜率转角度弧度换度 sbw imrotate(sbw1, angle, bilinear, crop); % 旋转校正 figure, imshow(sbw), title([车牌旋转角: , num2str(angle), 度]);fit是曲线拟合工具箱的函数poly1表示拟合y p1*x p2这种一次直线p1就是斜率也就是tan(theta)。atan(p1)得到弧度乘以180/pi转成角度。imrotate三参数分别是旋转角度、插值方式、是否裁剪。bilinear双线性插值比默认最近邻更能避免边缘锯齿crop让输出图像和原图同尺寸。这里有两个坑。第一fit要求输入是列向量xdata和ydata维度必须一致所以要先剔除全黑行。第二拟合出来的角度正负取决于车牌往哪边歪不要手动把角度改成绝对值直接交给imrotate就行它会按反方向转正。4.3 去边框与垂直投影字符中心、字符宽度怎么算旋转校正后重新做水平投影这次的目标是精确去掉上下边框拿到纯字符带。histrow2 sum(sbw, 2); projMask2 histrow2 max(histrow2) * 0.2; d2 diff([0; projMask2; 0]); rowtop find(d2 1); rowbot find(d2 -1) - 1; maxhight max(rowbot - rowtop 1); % 字符行高度 % 取最宽的字符带来裁剪 [~, idx] max(rowbot - rowtop 1); sbw2 sbw(rowtop(idx):rowbot(idx), :); % 去掉上下边框的字符带 histcol sum(sbw2, 1); % 垂直投影每列白点数量 figure, bar(histcol), title(垂直投影去水平边框后);垂直投影的目标是找到每个字符的左右边界。字符列白点多形成峰字符间隙形成谷。同样用跳变检测projCol histcol max(histcol) * 0.15; dCol diff([0, projCol, 0]); colTop find(dCol 1); % 每个字符带的起始列 colBot find(dCol -1) - 1; % 每个字符带的结束列 width colBot - colTop 1; center colTop width / 2; % 字符中心位置 gap diff(center); % 相邻字符中心距离 [~, maxGapIdx] max(gap); % 最大间距通常出现在第一个字符之后max(gap)找的是相邻字符中心距离最大的位置。因为车牌第一个字符是汉字宽度通常比后续字母数字窄它和第二个字符之间的中心距会被拉大。报告里for k findmax-1:findmax5就是这个逻辑从最大间距的前一个字符开始连续取7个正好覆盖整个车牌的7个字符位。如果你的图片里汉字本身很宽或者有间隔符干扰这个假设会失效到时候打印center和gap人工核对。4.4 切字符并归一化22×14 标准子图的由来拿到每个字符中心和最大字符宽度后就可以切割了maxwidth max(width); % 最大字符宽度 l 1; for k (maxGapIdx - 1) : (maxGapIdx 5) cleft center(k) - maxwidth / 2; % 切割左边界 cright center(k) maxwidth / 2 - 2; % 右边界减2是避免重叠 cleft max(cleft, 1); cright min(cright, size(sbw2, 2)); SegBw1 sbw2(:, cleft:cright); % 原始尺寸字符块 SegBw2 imresize(SegBw1, [22 14]); % 归一化为22行14列标准子图 figure, imshow(SegBw2); l l 1; endmaxwidth是垂直投影里最宽字符的宽度用它作为切割窗口的参考宽度能保证每个字符都被完整框住。cleft和cright用字符中心加减半个最大宽度得到最后用max、min把边界限制在图像范围内防止越界。22×14 是报告定的标准子图尺寸22对应字符高度14对应字符宽度。不同图像里车牌大小差异很大直接拿原始尺寸去匹配模板没有任何意义必须统一缩放。imresize默认最近邻插值在缩放比例不是整数时容易产生锯齿建议改成imresize(SegBw1, [22 14], bilinear)。5. 字符识别模板匹配、神经网络与失败案例复盘字符分割完成最后一步是识别。报告同时提到了模板匹配和BP神经网络两条路线并给出了一个完整失败案例。这一章把两条路线的原理和取舍讲清楚。5.1 模板匹配流程与样本库组织模板匹配的思路是准备一个样本库每个样本是已知字符的22×14二值图然后把待识别字符和库中所有样本逐像素比较误差最小的就是结果。报告里的实现是“样本与数据库中的图片相减找误差最小的图片”对应代码如下function ch matchChar(cutImg, sampleDir) cutImg imresize(cutImg, [22 14]); % 统一尺寸 cutImg im2double(cutImg); files dir(fullfile(sampleDir, *.jpg)); bestErr inf; ch ; for i 1:length(files) t imread(fullfile(sampleDir, files(i).name)); t imresize(t, [22 14]); t im2double(t); err sum(sum(abs(cutImg - t))); % 绝对差求和SAD误差 if err bestErr bestErr err; ch files(i).name(1); % 样本文件名首字符即类别 end end end误差度量用绝对差求和SAD还是平方差求和SSD都可以SSD对差异更敏感匹配结果更尖锐但受离群像素影响更大。样本库文件名直接编码字符类别比如首字符是“0”的样本就命名为0_1.jpg、0_2.jpg匹配结束后取文件名首字符作为识别结果。报告的imwrite里用了F:\MATLAB\work\sam\image这种绝对路径复现时建议改成相对路径加fullfile否则换电脑必崩。模板匹配最怕两件事样本量不足和样本与待识别字符风格不一致。同一个字符不同字体、不同笔画粗细、不同车牌材质反光在22×14的归一化图像上可能差出几百个像素的SAD误差。样板库每个字符最好放10张以上从不同车牌上切下来的真实样本。5.2 神经网络替代方案四个子网分开训练报告提到两种神经网络做法一种是先提取特征再训练分类器另一种是直接把图像像素送入网络让网络自己学特征。报告中用的是后者更适用的方案字符图像直接拉成向量输入BP网络。% 示意把22x14二值图拉成308维向量输入BP网络 trainX reshape(sampleImgs, 308, []); % 每列一个样本共308维 net feedforwardnet(60); % 单隐含层60个神经元 net train(net, trainX, trainY); % trainY是类别标签的独热编码注意报告把样本分成汉字、字母、字母数字、数字四组分别训练四个子网。原因是汉字类别多且结构复杂字母和数字存在大量相似形比如0和O、1和I混在一起训练会让网络难以收敛。拆成子网后每个子网的类别数减少类间距离更大识别率能明显提升。直接用像素向量训练的好处是不用手工设计特征坏处是需要的样本量很大。课设级别的样本量每类几十张下效果未必比模板匹配好。实际复现时我建议先用模板匹配跑通全流程再视识别率决定要不要上神经网络。5.3 失败案例复盘G→B、K→A、0→8 的根因报告里有一个很有价值的失败案例某次识别把“G”误识成“B”“K”误识成“A”“0”识别成“8”。这三类错误代表了模板匹配的三种典型失败模式。G和B在22×14分辨率下实在太像右侧都是竖线加弧线唯一明显区别是B中间有一横隔出两个孔G没有。如果模板匹配只看整体像素差这两个字符的SAD误差会非常接近。K和A同理左侧竖线加右侧斜线的骨架几乎重叠区别只在K的斜线更低、A有一横。0识别成8则通常是分割阶段的问题——字符0内部残留了噪点二值化后噪点把0的中间连了一条横杠0就变成了8。这三类错误提示一个关键原则识别错了先别急着换识别算法把二值化后的字符图打出来看。如果字符本身是脏的、断的、粘连的再强的识别器也救不回来。匹配前加一步重心对齐把归一化字符的白色像素重心平移到图像中心能缓解笔画粗细不均带来的整体偏移误差再用bweuler计算字符的欧拉数连通域数减孔洞数把B、8这类双孔字符和G、0这类少孔字符先分开再做模板匹配能显著压低相似字符的误判。6. 换个底板就翻车避坑清单与再工程验证这套报告默认处理蓝底白字车牌。实际复现时代码里还有几处“时代印记”和“环境依赖”直接把报告代码跑起来大概率会断。下面是照着复现时最值得注意的四类问题。6.1 四条常见问题现象1新版Matlab运行到imfeature直接报错。原因imfeature是远古接口在新版本中已被regionprops完全替代。解决把Feastats imfeature(L,basic)改为stats regionprops(L,Area,BoundingBox)字段名Area和BoundingBox不变取值方式从[Feastats.Area]改为[stats.Area]。现象2二值化后字符变成黑色背景变成白色。原因level公式假设字符比背景亮。遇到车身亮、车牌在阴影里时字符灰度低于底板二值化结果整体反转。解决先打印增强图Egray确认字符和底板的灰度关系。如果反转了在二值化后加一行bw2 1 - bw2。现象3旋转校正后车牌上下边框被裁掉字符缺一半。原因imrotate(..., crop)在旋转角度大时会把四角空白裁掉同时把有效字符行也切了。解决旋转前先用padarray向四周扩一圈转完再按投影结果取有效行。sbw_pad padarray(sbw1, [20 20], 0, both); sbw imrotate(sbw_pad, angle, bilinear, crop); % 旋转后重新做水平投影取rowtop/rowbot不要沿用原来的值现象4识别结果反复把0输出成8把G输出成B。原因模板库样本太少、没有做字符重心对齐、相似字符缺少粗分类。解决匹配前把字符白色像素重心平移到中心再用欧拉数分桶双孔字符单独建库单孔和无孔字符分两组匹配。这一步对0/8、G/B这类组合的改善非常明显。6.2 换底板颜色与完整验证流程报告总结里写了一句很关键但容易被忽略的话算法对黑底白字车牌原则上可直接适用对白底黑字、黄底黑字车牌需要调整定位算法并将图像反转0变1、1变0。实操时白底黑字只需在二值化前后把图反过来就行bw2 ~bw2。黄底黑字除了反色定位阶段还要把基于“车牌区域相对暗”的假设改成基于边缘密度因为黄色底板在灰度图上亮度很高和车身反光区域难区分。验证流程建议做成一个固定的检查顺序测试场景预期结果关键检查点正对、光照均匀直接识别成功二值图字符完整水平倾斜5~10度校正后分割成功拟合角度正负正确阴影遮挡一半车牌增强后仍能定位阈值level是否被拉高字符污损或褪色分割存在断笔模板匹配误差偏大我的习惯是把每张测试图的中间结果全部落盘level值、候选区宽高比、旋转角度、每个字符的匹配误差前三名。这样识别错了翻日志就能定位是哪一步挂的而不是对着最终结果瞎猜。从那以后我每次跑这种定位-分割-识别流程都强制走一遍“先把中间结果全部落盘、再谈识别率”的流程。很多识别错误不是算法不行是前面某一步的参数被环境带偏了打印出中间图十分钟就能找到断点。这套Matlab车牌识别课程设计的代码和报告适合作为你第一个图像识别全流程的练手项目希望帮到你。本文还有配套的精品资源点击获取