MATLAB车牌识别课设全流程:图像预处理、定位分割与识别

发布时间:2026/10/24 20:37:55
MATLAB车牌识别课设全流程:图像预处理、定位分割与识别

简介:一份基于Matlab的车牌识别课程设计报告,附源代码,面向高校信息科学与工程专业学生,用于完成车牌识别课程设计或毕业设计,也适合对图像处理与模式识别感兴趣的入门者。资源包仅含1个docx文档,大小1.37MB,报告将设计说明与核心Matlab程序相结合,便于阅读、修改和运行调试,已有116人学习。报告内容覆盖课程设计全流程:先说明设计目的、系统组成与进度安排,明确车辆检测、图像采集、牌照识别三部分的功能关系;随后重点讲解车牌定位、字符分割、字符识别等关键算法,包括大范围搜索确定候选区域、垂直投影法切割字符、模板匹配与BP神经网络识别等方法。同时给出车牌图像灰度化、背景差分、二值化、边缘提取等Matlab实现代码,并配有设计结果展示、问题分析与总结体会。通过这份文档,读者既能获取可直接复现的源码和调试思路,又能学习如何撰写结构完整的课程设计报告,对完成类似课题或参加竞赛也有参考价值。

1. 车牌识别课设怎么做:先用MATLAB经典图像处理链路跑通一版再说

很多人一提到车牌识别就想到深度学习、YOLO、卷积神经网络,但在课程设计和本科毕设的场景下,最稳的路线反而是MATLAB经典图像处理管线:灰度化、背景估计、增强、二值化、边缘检测、形态学开闭运算、候选区域筛选、投影分割、模板匹配。这套方案每一步都能用一幅中间结果图讲清楚,答辩时老师问「这一步为什么这么做」,你直接指着图说,比端到端的黑匣子模型好讲得多。本文拆解的就是一份完整的Matlab车牌识别课程设计报告:从图像读取到车牌定位、字符分割、字符识别全流程跑通,并给出容易翻车的位置和验收方法。适合正在做车牌识别课设/毕设的学生,也适合要快速交付一版可演示识别系统的开发者参考。

2. 总体设计:车牌识别的完整链路与图像预处理

2.1 系统组成与设计原理

一个完整的牌照识别系统,硬件侧通常包含触发设备、摄像设备、照明设备、图像采集设备和识别处理机;软件侧核心是三条算法链路:车牌定位算法、车牌字符分割算法、光学字符识别算法。整体工作流程是:车辆检测部分检测到车辆到达,触发图像采集单元抓取当前视频帧;牌照识别单元对图像做处理,先定位出牌照位置,再把牌照中的字符分割出来,逐个识别后组成牌照号码输出。

这里有一个容易被忽略的设计点:识别率的下限不是由识别算法决定的,而是由前面的图像质量决定的。车牌本身可能生锈、污损、油漆剥落、字体褪色、被遮挡、倾斜、高亮反光,拍摄时又受环境亮度、车速影响。所以课程设计里通常的做法是:把预处理链路做厚,把定位和分割做稳,识别算法哪怕是朴素的模板匹配也能跑出不错的效果。反之,如果预处理偷工减料,后面用什么高级分类器都救不回来。

整个软件流程可以概括为:读图 → 灰度化 → 开操作估计背景 → 背景相减增强 → 自适应阈值二值化 → Canny边缘检测 → 形态学开闭运算 → 连通域标记 → 宽高比筛选 → 水平投影峰谷分析 → 水平校正 → 去边框 → 垂直投影分割字符 → 归一化 → 样本匹配识别。下面按这个顺序逐步实现。

2.2 图像读取、灰度化与背景估计

第一步是读取待处理的彩色图像并做灰度化。代码很简单,但两个细节值得注意:一是imread直接读入的如果是彩色图,后续存储开销和处理耗时都会翻倍;二是灰度化之后像素动态范围会发生变化,这直接决定后面阈值取值的形态。

clear; close all; % Step1 读取彩色图像并显示原始图像 Scolor = imread('3.jpg'); figure, imshow(Scolor), title('原始图像'); % Step2 彩色图像转灰度,加快后续处理速度 Sgray = rgb2gray(Scolor); figure, imshow(Sgray), title('原始黑白图像'); % Step3 开操作估计图像背景 s = strel('disk', 13); % 圆盘结构元素,半径13 Bgray = imopen(Sgray, s); figure, imshow(Bgray), title('背景图像');

灰度化的意义在于去掉颜色维度,只保留亮度梯度信息,车牌区域和背景的区分主要靠灰度跳变,不靠颜色本身。第三步用strel('disk',13)生成圆盘形结构元素做开操作,效果是得到一张「没有字符、只有光照背景」的估计图。半径13这个参数在640×480左右的图像上表现稳定;如果图像更大或更小,需要等比调整,这个在后面避坑部分会专门说。

2.3 背景相减增强、自适应阈值二值化与Canny边缘检测

拿到背景估计图之后,用原灰度图减去背景,相当于做了一次光照不均匀校正——背景中平缓的亮度变化被抹掉,车牌的字符和边框这些高频细节被保留下来。之后再取增强图的动态范围,按「最大值回退三分之一」的方式求阈值做二值化,把灰度图变成黑白图。

% Step4 灰度图与背景图相减,增强前景目标 Egray = imsubtract(Sgray, Bgray); figure, imshow(Egray), title('增强黑白图像'); % Step5 根据最大最小值计算阈值并二值化 fmax1 = double(max(max(Egray))); % 增强图最大值 fmin1 = double(min(min(Egray))); % 增强图最小值 level = (fmax1 - (fmax1 - fmin1) / 3) / 255; bw22 = im2bw(Egray, level); bw2 = double(bw22); figure, imshow(bw2), title('图像二值化'); % Step6 用Canny算子提取边缘 grd = edge(bw2, 'canny'); figure, imshow(grd), title('图像边缘提取');

阈值公式fmax1 - (fmax1 - fmin1)/3的意思是:从最大灰度值向下退三分之一动态范围作为分割点。这样取阈值偏保守,能保留较暗的字符笔画,避免把车牌字符在二值化阶段就切碎。乘1/255是因为im2bw期望输入是 [0,1] 范围的 double 类型。二值化之后直接做Canny边缘检测,把字符和边框的轮廓线提取出来。Canny对弱边缘保留好,但也会产生大量碎边缘点,所以下一步必须用形态学开闭运算做清理,否则后面区域标记会得到几十个碎片区域。

3. 车牌定位:形态学滤波、候选区筛选与倾斜矫正

3.1 开闭运算:用矩形结构元素把车牌「焊」成连通域

Canny边缘提取之后的图像是细碎线条,车牌字符的边缘虽然已经显现,但字符内部和字符之间并不连通,直接做连通域标记会把同一个车牌拆成几十个小块。这里的关键操作用矩形结构元素先闭后开:闭运算把断裂的轮廓线连接起来,开运算削掉细小的孤立噪声。

% 闭运算:连接断裂轮廓,消除小孔洞 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,1]的竖向矩形,作用是把车身散热格栅、车窗边缘这类竖长条噪声削掉。先闭后开的顺序不能反——先开会把细碎的边缘点先消掉,闭运算就失去连接对象了。

3.2 区域标记与宽高比筛选

形态学操作做完,图像里剩下的连通区域数量已经大幅减少,但仍可能包含车灯、保险杠装饰条、文字广告等干扰区域。这时用bwlabel标记每个连通域,再用imfeature计算区域面积和最小包含矩形,最后根据车牌的先验知识做筛选。

% 标记二进制图像中的连通区域 [L, num] = bwlabel(bg2, 8); Feastats = imfeature(L, 'basic'); % 新版本MATLAB建议改用regionprops Area = [Feastats.Area]; % 各区域面积 BoundingBox = [Feastats.BoundingBox]; % [x y width height] % 根据先验知识筛选候选车牌区域 % 普通蓝底白字车牌宽高比约3:1,考虑倾斜和透视后放宽到2.5~5.5 for i = 1:num w = BoundingBox(i, 3); h = BoundingBox(i, 4); ratio = w / h; if ratio > 2.5 && ratio < 5.5 && Area(i) > 800 % 记录该区域,截取灰度子图和二值子图备用 end end

宽高比范围不能卡死在3:1,因为车牌在画面里可能有小角度倾斜,拍摄视角也不是完全正对,取2.5到5.5更稳。面积下限800的作用是过滤掉远距离的小块噪声。这里我用的是示例循环体,实际课设代码里通常会直接选出宽高比最接近标准车牌比例的那一个区域,并记录其行列范围,为后面的投影分析做准备。筛选完再做一次label2rgb假彩色标记,能很直观地在答辩PPT里展示每个候选区域的样子。

3.3 水平投影峰谷分析与倾斜角拟合

车牌被定位出来之后,还需要解决两个问题:一是车牌在图像里可能带几度倾斜,导致后续垂直投影的峰谷不明显;二是要精确去掉上下边框,才能得到干净的字符区域。这里先重新计算二值子图的水平投影,分析峰谷位置,再用线性拟合求倾斜角。

% 计算水平投影:每一行像素值的总和 histrow = sum(sbw1'); figure, bar(histrow); title('水平投影(含边框)'); % 对水平投影做峰谷分析,得到上边框峰、字符峰、下边框峰的位置 % 字符区的峰宽最大,上下边框的峰较窄 % 取字符边缘点做线性拟合,计算倾斜角度 fresult = fit(xdata', ydata', 'poly1'); % 一阶拟合 Y = p1*x + p2 p1 = fresult.p1; angle = atan(p1) * 180 / pi; % 斜率转角度 % 旋转车牌图像做水平校正 subcol = imrotate(subcol1, angle, 'bilinear', 'crop'); sbw = imrotate(sbw1, angle, 'bilinear', 'crop'); figure, imshow(sbw); title(['车牌旋转角:', num2str(angle), '度'], 'Color', 'r');

水平投影的物理意义很直观:把二值图像每一行像素求和,车牌区域的字符行会产生高峰,上边框和下边框产生较窄的峰。峰谷分析就是找到这些峰的起点、终点、峰宽,从而确定字符行所在的上下边界。倾斜矫正的核心思路是:取车牌上边缘或下边缘灰度值为1的点集做线性拟合,拟合直线的斜率就是倾斜角的正切值。旋转时选bilinear双线性插值是为了保留灰度过渡信息,crop参数保证旋转后图像尺寸不变。这一步做完,车牌在图像里基本是水平的,字符分割才能稳定。

4. 字符分割与识别:垂直投影定位字符与样本库匹配

4.1 去掉上下边框,精准获取字符高度

倾斜矫正之后,车牌图像仍然包含上下边框。字符分割的第一步是把边框去掉,拿到只含字符像素的区域。做法是对旋转后的二值图重新做水平投影,找到峰宽最大的那一段——那一段就是字符主体。

% 重新计算水平投影 histrow = sum(sbw'); % 从投影结果中找最大峰宽,对应字符区域 maxhight = max(markrow2); % 字符峰的最大宽度 findc = find(markrow2 == maxhight); % 找到该峰的索引 rowtop = markrow(findc); % 字符区域上边界 rowbot = markrow(findc + 1) - markrow1(findc + 1); % 下边界 % 截取只含字符的横向区域 sbw2 = sbw(rowtop:rowbot, :); maxhight = rowbot - rowtop + 1; % 实际字符高度(像素)

这里有一个容易踩的细节:字符高度不能用固定值,因为摄像头安装距离不同,车牌在画面中的像素高度是变量。取投影峰宽最大的区域作为字符区,是自适应做法,即使图像缩放也能对得上。去掉上下边框后,再用垂直投影做列方向的分析,定位每个字符的左右边界。

4.2 垂直投影峰谷分析与字符中心计算

字符区域的垂直投影是按列求和,每一列的像素和形成波形:字符笔画经过的列投影值高,字符间隙和字符内谷底的列投影值低。通过峰谷分析可以找到每个字符的上升点和下降点,进而算出每个字符的宽度和中心位置。

% 计算垂直投影 histcol = sum(sbw2); figure, bar(histcol); title('垂直投影(去水平边框后)'); % 对垂直投影做峰谷分析 % markcol 记录每个字符的上升点(字符左边界) % markcol1 记录每个字符的下降点(字符右边界) for k = 1:n1 markcol3(k) = markcol(k + 1) - markcol1(k + 1); % 字符下降点修正 markcol4(k) = markcol3(k) - markcol(k); % 字符宽度 markcol5(k) = markcol3(k) - uint16(markcol4(k) / 2); % 字符中心位置 end % 计算相邻字符中心距离,最大值对应第二、三字符之间的分隔点 markcol6 = diff(markcol5); maxs = max(markcol6); % 最大间距 findmax = find(markcol6 == maxs); % 定位分隔符位置

为什么字符中心距离的最大值出现在第二和第三个字符之间?因为国内车牌第二个字符和第三个字符之间有一个分隔圆点,这个间隔显著大于普通字符间距。找到这个位置后,从findmax-1到findmax+5正好覆盖7个字符,分割起点就固定下来了。计算字符宽度时用uint16取整是防止后面索引计算出现小数,这个细节在答辩时提一句,老师会觉得你考虑得比较细。

4.3 分割字符、归一化22×14并建立样本库

定位到每个字符的中心位置后,以中心为基准、以最大字符宽度为半宽向左右扩展,就能把单个字符完整切出来。切出来的字符大小不一,直接送进识别模块之前必须统一尺寸。这里统一缩放为22行14列的标准子图——22对应字符高度,14对应字符宽度,比例接近真实字符,能最大限度保留笔画形状。

l = 1; [m2, n2] = size(subcol); % 子图实际尺寸 for k = findmax - 1 : findmax + 5 cleft = markcol5(k) - maxwidth / 2; % 左边界 cright = markcol5(k) + maxwidth / 2 - 2; % 右边界,预留2像素 % 边界越界保护 if cleft < 1 cleft = 1; cright = maxwidth; end if cright > n2 cright = n2; cleft = n2 - maxwidth; end % 截取字符灰度图和二值图 SegGray = sbw(rowtop:rowbot, cleft:cright); SegBw1 = sbw(rowtop:rowbot, cleft:cright); % 统一缩放为22行14列标准子图 SegBw2 = imresize(SegBw1, [22 14]); % 保存到样本库,用于后续识别匹配 fname = strcat('F:\MATLAB\work\sam\image', int2str(k), '.jpg'); imwrite(SegBw2, fname, 'jpg'); l = l + 1; end

识别阶段有两种主流做法。基于模板匹配的做法是:把分割好的字符二值图与样本库中的每一张模板图做逐像素相减,误差最小的模板对应的字符就是识别结果;基于BP神经网络的做法是:用汉字、字母、字母数字、数字四类样本分别训练四个子网络,识别时把字符特征送入对应网络分类。这份课程设计实际走的是模板匹配路线,代码里保留了神经网络训练的流程说明作为扩展方案。要注意的是样本库质量直接决定识别率,建立样本库时每个字符至少要收集多张不同粗细、不同倾斜程度的样本,只放一张模板的话,稍微有点形变的字符就识别不了。

5. 避坑指南:误识别、阈值与算子兼容性的常见问题

5.1 现象:G被识别成B、K被识别成A、0被识别成8

课程设计报告里有一组失败样例:一张车牌把G误识别成B,K误识别成A,0误识别成8。这类错误几乎都出在字符分割阶段——字符被切得过宽或过窄,归一化到22×14时笔画发生了形变。比如G的右下角开口如果被边框残留填满,长得就像B;0的中间如果有细小噪声点,二值化后中间出现横线,就会被匹配成8。

原因通常是垂直投影的谷底检测阈值设得太浅,或者去边框时残留了上下边框的像素,导致投影波形粘连。解决的办法是先加大形态学开运算的力度,把边框残留彻底清掉;再检查垂直投影的谷底判定条件,多个连续低投影值的列只能算一个字符间隙,不能拆成两个。另外,识别阶段对误差最小的结果可以加一个置信度判断:如果最小误差和第二小误差非常接近,说明这个字符识别不可靠,宁可标记为待确认,也不要硬输出一个错误结果。

5.2 现象:强光下二值化结果全黑或全白

预处理链路里的阈值公式是fmax1 - (fmax1 - fmin1)/3,这个公式在正常光线下表现不错,但遇到车牌被强光直射、大面积反光时,增强图的动态范围会被极端值拉满,按三分之一回退算出的阈值可能把整个车牌区域全部判成背景,二值化之后车牌字符消失。

原因是阈值只依赖全局最大最小值,对局部光照变化不敏感。解决思路有两个:一是分块自适应阈值,把图像切成若干小块分别计算阈值;二是先对增强图做直方图均衡化再二值化,把灰度分布拉开。课设时间有限的话,至少要做到把阈值计算改成中位数加标准差的方式,中位数对极端值不敏感,比最大最小值稳定得多。

5.3 现象:候选区域筛选把真车牌漏掉了

宽高比范围2.5到5.5、面积下限800这套参数不是普适的。如果测试图像里车牌离摄像头远,像素面积只有四五百,面积下限直接把它滤掉了;如果拍摄角度侧一点,车牌在图像里是斜平行四边形,宽高比会明显偏离正常范围。

原因是筛选参数和成像条件绑得太死。我一般会做一层多尺度兜底:先用宽泛的宽高比范围(比如1.5到7.0)粗筛一轮,再用形态学的矩形度、字符密度做第二轮打分,而不是单一条件硬过滤。课程设计里可以在答辩时说清楚这个思路,然后现场演示放宽参数后候选区数量从1个变成5个,再说明为什么最终选定的是车牌区域,这比直接展示一张完美识别图更可信。

5.4 现象:新版MATLAB运行报错,imfeature不存在

原报告代码里用了imfeature(L,'basic'),这个函数在老版本MATLAB的图像处理工具箱里能用,但新版本已经被regionprops取代。直接复制代码跑新版MATLAB会在这一步报错,很多同学第一反应是代码有问题,其实是API变更。

解决方法是做一行替换:Feastats = regionprops(L, 'Area', 'BoundingBox'),然后访问方式从[Feastats.Area]改成[Feastats.Area]依然成立,BoundingBox的字段结构也兼容,改动成本很低。另外strel的组合写法在新版里有更简洁的offsetstrel,但不影响运行,不必强改。遇到这种兼容性问题不要慌,优先查对应函数的官方文档,通常只是函数换名。

5.5 现象:倾斜矫正拟合出的角度明显偏大或偏小

用边缘点线性拟合求倾斜角时,如果参与拟合的点集里混入了字符竖线或者边框拐角,拟合直线的斜率会被带偏。比如车牌左边第一个字符的竖线很长,这些点的x坐标集中在一小段范围,却贡献了大量高y值,拟合结果会比真实倾斜角大好几度。

原因是拟合前没有对点集做提纯。通常的做法是只取车牌上边缘或下边缘附近两三个像素宽度内的点参与拟合,远离边缘的字符点剔除后再做poly1拟合。另一个细节是拟合之后要检查旋转方向:atan(p1)得到的是弧度制的正切角,转成角度后如果是正值说明逆时针倾斜,旋转时需要取负号;方向搞反会让车牌更歪,这一步出错时投影峰会变得比校正前更平。

6. 验收与答辩:用测试表和识别率数据证明方案可靠

课程设计答辩和项目验收,核心就一句话:用数据说话,而不是用一两张成功的图说话。我建议在验收前做一张测试记录表,准备至少10张不同类型的测试图,覆盖正常光照、强反光、倾斜车牌、远距离小尺寸车牌、有边框铆钉干扰的车牌,每一张记录定位是否成功、分割是否成功、识别结果是否正确,最后算出三个数字:定位成功率、字符分割成功率、整牌识别率。

演示顺序也很关键。先跑一张标准测试图,展示完整链路和成功结果;再跑一张失败案例,主动说出失败原因,对应第5章里的字符混淆问题;最后用统计表说明整体识别率。答辩老师最反感的是「我的程序只能识别这一张图」,所以一定要准备一张没在开发阶段反复调试过的真实验证图像,当场跑一次。表格式的测试记录可以直接留在报告附录里,评审老师翻到会认为你做测试做得很扎实。

测试图编号场景描述定位结果分割结果识别结果备注
01正常光照,蓝底白字成功成功正确完整链路
02强光反光失败——阈值需自适应
03倾斜约3度成功成功正确拟合角2.8度
04远距离小尺寸失败——面积筛选漏检

还有一个答辩技巧:把中间结果图串成一条流水线展示。每一张预处理图下面标注「这一步解决了什么问题」,比如灰度化解决计算量、背景相减解决光照不均、闭运算解决边缘断裂、宽高比筛选解决候选区干扰、投影峰谷解决字符定位、归一化解决尺寸不一致。这会让老师觉得你不是在跑现成代码,而是真的理解整条链路。这份方案对应的资源里包含完整的课程设计报告和可直接运行的MATLAB源代码,报告里的流程图、结果分析、参考文献列表都齐全,拿去做课设提交或者在此基础上扩展识别算法都很方便。

做这个车牌识别课设,我最大的教训是:不要一开始就追求「所有图都能识别对」,而是先把一条链路完整跑通,再拿失败样本去反推哪个环节顶不住。从那以后我每次做图像类的课程设计验收,都强制自己先跑失败案例再跑成功案例,识别率统计表随身带着,答辩基本没慌过。希望帮到你。

本文还有配套的精品资源,点击获取