Matlab车牌识别课程设计:传统图像处理定位分割识别全流程解析
简介:一份基于Matlab实现的车牌识别课程设计报告,面向信息科学与工程类专业学生,可作为图像处理、模式识别课程设计或毕业设计的参考资料。报告围绕车牌定位、字符分割、字符识别三个核心环节,系统介绍了车辆检测与图像采集触发机制、牌照区域搜索与候选区分析、垂直投影分割方法,以及模板匹配和人工神经网络两类字符识别算法;同时给出完整Matlab实现代码,涵盖图像灰度化、背景差分、二值化、边缘提取、水平垂直校正等关键步骤。资源包仅含1个docx文档,约1.37MB,文档内置课程设计封面、进度安排、设计原理、详细步骤、结果展示、总结体会与参考文献,结构完整,便于对照学习。已有116人浏览学习。读者可参考其中算法流程和源代码快速搭建车牌识别原型,也能了解多牌照、假牌照、光照不均等实际工程挑战及优化思路,为交通管理、高速公路收费等应用打下基础。
1. Matlab 车牌识别课程设计:一份能跑通的定位、分割、识别全流程
车牌识别这事儿,听着像是工业停车场和高速收费口的专属系统,离课程设计很远。但真打开这份 Matlab 车牌识别课程设计报告,你会发现整个识别链路并没有那么高不可攀:它没有引入深度学习,完全靠图像处理经典套路——灰度化、边缘提取、形态学滤波、投影分析和模板匹配,就把蓝底白字车牌从一张自然背景的 3.jpg 里抠出来,切割成单个字符,再逐个比对出车牌号码。适合图像处理刚入门、需要做课设或者想补一遍传统 CV 基本功的人。报告里配了完整可运行的 Matlab 源码和详细步骤图,照着敲一遍,比看十遍教科书都管用。
2. 系统设计与模块划分:五个环节串起从原始图像到车牌号码
2.1 牌照识别系统的完整链路
整个系统遵循一个非常经典的视频触发式车牌识别流程:车辆检测部分感知到车辆到达后,触发图像采集单元抓取当前帧,牌照识别单元对该帧做处理,定位出牌照区域,再把牌照中的字符分割出来识别,最后组成牌照号码输出。落到这份报告里,车辆检测和图像采集是外部硬件的事,软件部分也就是你能拿到的代码,集中在牌照识别这一块,拆开看是五个环节:
- 图像预处理:把读入的彩色图像转灰度、做背景估计、差影增强,把车牌区域从复杂背景里凸显出来。
- 二值化与边缘提取:求最佳阈值把灰度图变成二值图,再用 Canny 算子提取边缘。
- 形态学滤波与区域筛选:通过闭运算、开运算的组合消除小噪声,再用连通域标记和长宽比先验知识定位车牌矩形。
- 倾斜校正与边框去除:对水平投影做峰谷分析,检测出上下边框位置,必要时用线性拟合计算倾斜角度并旋转图像。
- 字符分割与识别:用垂直投影找到每个字符的中心位置,切割出单个字符,统一缩放成 22 行 14 列的标准子图,再与样本库比对误差,输出车号。
2.2 目录结构与关键文件
拿到这份资源,先别急着点运行,把目录结构摸清楚。报告自带完整源码,典型组织方式如下:
plate_recognition/ ├── 车牌识别课程设计报告.docx # 完整设计文档,含步骤图和结果分析 ├── code/ │ ├── main.m # 主程序入口,按步骤调用各处理函数 │ ├── 3.jpg # 测试原图,蓝底白字车牌 │ ├── sam/ # 样本库目录,存放缩放后的字符模板 │ │ ├── image1.jpg │ │ ├── image2.jpg │ │ └── ... │ └── util/ # 可选的自定义函数目录主程序 main.m 直白到让人舒服,一句一句从上往下执行,没有花哨的封装。这种写法不优雅,但作为课设非常合理:每一行对应报告里的一个设计步骤,老师验收时对着文档就能指出代码位置,答辩也好讲。
2.3 主流程代码骨架
clear; close all; % Step1 获取图像:读取待处理的彩色图像 Scolor = imread('3.jpg'); % imread 读取图像文件 figure, imshow(Scolor), title('原始图像'); % Step2 灰度化:彩色转灰度,降低计算量 Sgray = rgb2gray(Scolor); figure, imshow(Sgray), title('原始黑白图像'); % Step3 背景估计:用半径为13的圆形结构元素做开运算提取背景 s = strel('disk', 13); Bgray = imopen(Sgray, s); % Step4 图像增强:原图减去背景,突出前景目标 Egray = imsubtract(Sgray, Bgray); % Step5 二值化:动态阈值转二值图 fmax1 = double(max(max(Egray))); fmin1 = double(min(min(Egray))); level = (fmax1 - (fmax1 - fmin1) / 3) / 255; bw22 = im2bw(Egray, level); bw2 = double(bw22); % Step6 边缘检测:Canny 算子提取强边缘 grd = edge(bw2, 'canny');这段代码就是后面一切操作的基石。注意 Step3 里strel('disk', 13)的 13 是结构元素半径,选大了背景被磨平,选小了背景里还残留纹理;Step5 的阈值公式是经验值,意思是取灰度范围上三分之一处作为二值化阈值,背景亮、车牌字符暗时效果很好,但光照极端时这个固定比例会失效,后面避坑章节我会细说。
2.4 边缘之后:形态学滤波与候选区域分析
边缘图直接用来定位是不可靠的,车牌周围的噪声、车灯轮廓、路面标线都会产生响应。报告的思路是用形态学开闭运算滤掉杂散边缘,再连通域分析找到候选区域。
% 先闭运算再两次开运算,分别用不同尺寸的矩形结构元素 bg1 = imclose(grd, strel('rectangle', [5, 19])); bg3 = imopen(bg1, strel('rectangle', [5, 19])); bg2 = imopen(bg3, strel('rectangle', [19, 1])); % 标记连通域并计算区域特征 [L, num] = bwlabel(bg2, 8); Feastats = regionprops(L, 'basic'); Area = [Feastats.Area]; BoundingBox = [Feastats.BoundingBox]; % 每行 [x y width height]这里[5, 19]的矩形结构元素是照着车牌字符的宽高比例设计的——字符窄高,横向跨度大,所以闭运算能把离散的字符边缘连成一片;[19, 1]的竖向结构元素再把竖向细小噪声抹掉。选这两个参数不是拍脑袋,是观察了车牌字符尺寸和笔画宽度之后定出来的,改到别的数据集上需要重调。regionprops计算每个连通域的外接矩形和面积,后面就用宽高比去匹配真实车牌的先验。
到这里,整个软件架构就清楚了:环境依赖只有 MATLAB 和图像处理工具箱,不涉及额外 SDK;算法链路全部是经典图像处理,没有神经网络权重文件要管理;样本库就是 sam 目录下的一组 22x14 的二值字符图,靠相减求误差来识别,逻辑一眼能看懂。这种设计对学习和答辩都非常友好。
3. 核心算法拆解:灰度化、边缘检测与投影分析的关键实现
3.1 车牌定位:从二值图到候选矩形
如果说整个识别系统是一个漏斗,车牌定位就是最宽的那一层。报告采用的方法是先大范围搜索符合车牌特征的候选区域,再逐步筛选。具体到代码里,靠的是连通域的外接矩形宽高比。
真实蓝底白字车牌的宽高比大约是 3.14 比 1(不含汉字间隔等特殊差异),所以拿到每个连通域的 BoundingBox 后,计算 width/height,越接近这个比值,越可能是车牌。报告没有直接贴出筛选那几行,而是通过彩色标记图可视化展示不同区域:
RGB = label2rgb(L, 'spring', 'k', 'shuffle'); figure, imshow(RGB); title('图像彩色标记');这一步的原理不复杂:背景被形态学滤波清理后,车牌字符的边缘会连成一个横向的长条形区域,其他噪声区域要么太大(车身),要么太小(树叶、文字),要么长宽比不对(路牌、车灯)。代码里用bwlabel给每个连通域编号,regionprops算出每个域的参数,再人工检查或加一段筛选逻辑把宽高比接近 3 的区域挑出来。实际操作里这个环节是整车牌识别准确率的分水岭,选错了区域后面全白搭。
3.2 投影分析与峰谷检测:找到字符的每一行
车牌区域提取出来后,还不能直接分割字符,因为图像里可能带着上下边框、铆钉和车牌背景的干扰。报告的处理方式是投影分析——把二值图像按行和列分别累加像素值,形成水平投影和垂直投影,再在投影曲线上找峰谷。
histcol1 = sum(sbw1); % 垂直投影:每列像素和 histrow = sum(sbw1'); % 水平投影:每行像素和 figure, subplot(2,1,1), bar(histcol1); title('垂直投影(含边框)'); subplot(2,1,2), bar(histrow); title('水平投影(含边框)');sumbw1是车牌区域的二值子图,sum(sbw1)沿列求和得到垂直投影,sum(sbw1')沿行求和得到水平投影。车牌上边框和下边框在水平投影上表现为两个高而窄的峰,字符区域则是一个宽而平的峰。通过对投影曲线做峰谷检测,找到峰上升点和下降点,就能精确确定字符区域的行范围,把上下边框切掉。
这一步最容易翻车的点是投影曲线毛刺太多。原因往往前面就埋下了:二值化阈值偏了,或者形态学结构元素尺寸不对,导致字符边缘断裂或粘连,投影峰谷边界模糊。报告中专门保留了完整投影图,就是为了让使用者能肉眼对照检查峰谷位置的判断是否合理。
3.3 倾斜校正:线性拟合算旋转角
实际拍照时车牌很少有完全水平的,尤其从侧面拍,倾斜个三五度很常见。倾斜直接破坏字符分割的准确性,所以报告在分割前设计了校正环节。校正方法很朴素也很实用——拟合车牌边缘那条直线,算出与水平轴的夹角,然后旋转。
% 提取车牌上边缘或下边缘像素值为1的点的坐标 % xdata, ydata 为边缘点的横纵坐标集合 fresult = fit(xdata', ydata', 'poly1'); % 一阶多项式拟合,即线性拟合 p1 = fresult.p1; % 斜率 angle = atan(p1) * 180 / pi; % 斜率转角度 % 按计算出的角度旋转车牌图像 subcol = imrotate(subcol1, angle, 'bilinear', 'crop'); sbw = imrotate(sbw1, angle, 'bilinear', 'crop');fit函数来自曲线拟合工具箱,poly1表示拟合 y = p1*x + p2 的一次多项式,p1就是斜率,atan取反正切得到弧度,再转成度。imrotate的第三个参数bilinear是双线性插值,旋转后图像更平滑;crop表示旋转后裁剪到与原图相同尺寸。旋转角度是正数就逆时针转,是负数就顺时针转,完全由拟合直线的斜率决定。
我一般会建议在这里加一个角度阈值判断:如果算出的角度绝对值小于 0.5 度,干脆跳过旋转,省一次插值带来的画质损失。报告里没有做这个判断,直接旋转了——在课设这种简单场景下问题不大,但放到真实数据集里,多余旋转会让字符边缘变糊,影响后面识别准确率。
3.4 字符分割:垂直投影找中心,缩放成统一尺寸
去完边框、校完倾斜,就进入分割环节。这里的关键是计算每个字符的上升点、下降点和中心位置,用垂直投影的谷值把字符切开。
% 计算字符中心位置 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); markcol6(findmax) = 0; % 按找到的字符位置逐个切割,并统一缩放到22x14 for k = findmax-1:findmax+5 cleft = markcol5(k) - maxwidth/2; cright = markcol5(k) + maxwidth/2 - 2; SegBw1 = sbw(rowtop:rowbot, cleft:cright); SegBw2 = imresize(SegBw1, [22 14]); imwrite(SegBw2, strcat('sam\image', int2str(k), '.jpg'), 'jpg'); end注意findmax那一段逻辑:车牌字符中间通常有一个小圆点分隔符,投影上表现为一个异常宽的间距,直接用它把特征点剔除,避免把分隔符当成字符。imresize把切割出来的字符统一缩放成 22x14,这是样本库的标准尺寸,保证后面字符比对时维度一致。strcat拼出文件名,把分割好的字符图写入 sam 目录当作样本,这套代码还自带建库功能,想加新样本非常方便。
3.5 字符识别:样本库相减找最小误差
如果把前面的字符串替换成 7 个 22x14 的二值小图,识别就变成了一个最笨但最有效的办法——和样本库里每张图相减,误差最小的就是答案。
% 伪代码逻辑,报告中的识别实现 for each char_image in 分割出的字符: min_error = Inf result_char = '' for each sample in 样本库: error = sum(sum(abs(char_image - sample))); if error < min_error min_error = error result_char = sample.name end output result_char这是基于模板匹配的思想。样本库里预先存放了汉字、字母、数字的 22x14 二值模板,识别时逐个相减,差值越小说明越相似。优点是零训练成本、代码量小、可解释性强;缺点也明显:对字体、粗细、噪声极其敏感,轻微断笔或者笔画粘连就会让误差最小的模板变成错误答案。报告后面的失败案例——G 误识别成 B、K 误识别成 A、0 误识别成 8——就是这种敏感性最直观的体现。想提升识别率,后文二次开发部分我会给出具体改造方向。
4. 常见问题避坑:从 imfeature 报错到识别翻车的排查记录
4.1 imfeature 函数报错:老版本函数已被移除
- 现象:代码运行到
Feastats = imfeature(L, 'basic')时,MATLAB 直接报错“未定义函数或变量 'imfeature'”,新版 R2023b 里根本没这个函数。 - 原因:imfeature 是老版本图像处理工具箱里的区域特征计算函数,R2014b 之后被 regionprops 取代,文档里直接标注了 deprecated。
- 解决:把
imfeature(L, 'basic')改成regionprops(L, 'basic'),返回的 Feastats 结构体字段名兼容,Area 和 BoundingBox 的取法不用动。改完这一行,整段连通域分析代码就能跑通。
4.2 阈值公式在强光照下失效:整张图二值化后全是黑的
- 现象:把 3.jpg 换成正午强光下拍的照片,
im2bw之后的二值图像车牌区域和背景糊成一团,边缘图里车牌轮廓看不清。 - 原因:阈值
(fmax1 - (fmax1 - fmin1) / 3) / 255是经验比例,假设了灰度直方图大致呈单峰分布,强光照、逆光、阴影场景下直方图形状完全变了,固定比例不再适用。 - 解决:换成 Otsu 全局阈值,一行代码
level = graythresh(Egray),让算法自动找分割阈值。如果光照不均匀更严重,就上自适应阈值adaptthresh,按局部亮度动态计算阈值。
4.3 样本库路径写死:换电脑直接找不到图片
- 现象:代码里保存样本用到
strcat('F:\MATLAB\work\sam\image', int2str(k), '.jpg'),拷贝到别人电脑上运行,保存或读取样本时提示路径不存在。 - 原因:绝对路径写死了原作者机器的盘符和目录结构,换环境必然失效。
- 解决:用
mfilename('fullpath')获取当前脚本所在目录,再用fullfile拼接相对路径,例如save_dir = fullfile(fileparts(mfilename('fullpath')), 'sam')。这样整个项目随便挪到哪个目录都能跑,也是所有 Matlab 小项目最值得养成的习惯。
4.4 字符识别翻车:G 识别成 B、K 识别成 A、0 识别成 8
- 现象:报告结果展示里明确记录了一次失败识别,G 误判为 B,K 误判为 A,0 误判为 8。这几种错误在模板匹配算法里非常典型。
- 原因:模板匹配相减的误差只衡量像素差异,没有考虑形状结构。G 和 B 都带竖画和半圆,像素覆盖区域高度重合;0 和 8 更是只差中间一横——二值化稍微断笔,误差最小的样本就变成另一个字符。
- 解决:把纯像素相减改成带权重的比对,例如先计算字符的宽高比、笔画密度、孔洞数作为粗筛特征,再用像素误差做精匹配。更彻底的做法是换成报告里提到的 BP 神经网络分类器,训练一个 22x14 输入、34 类输出的全连接网络,对断笔和粘连的鲁棒性会好很多。
4.5 非蓝色车牌定位失败:整个候选区域筛选逻辑崩溃
- 现象:把测试图换成黄底黑字或白底黑字的车牌,前面定位环节直接找不到候选区域,或者找到的区域根本不是车牌。
- 原因:报告从预处理到形态学滤波乃至宽高比筛选,整套参数都是针对蓝底白字车牌设计的。蓝底在灰度图上表现为中灰色,和白色字符的对比度方向与其他底色车牌不同,Canny 边缘响应和形态学连接结果都会改变。
- 解决:如果必须支持其他底色,先在彩色空间做颜色判断:统计 HSV 色相分布,蓝色、黄色、白色各走一套预处理参数。黑底白字按报告原话,整个算法原则上可以直接适用;白底黑字和黄底黑字则需要把图像反转(0 变 1、1 变 0),让字符变成亮色,后续分割算法依然有效。
5. 二次开发:把静态识别改成视频流与样本库增强
这一章我不讲概念,直接给三条可以落地的改造路径,每条都是对这个报告源码动小手术就能完成的。
第一,把单张图片识别改成视频流识别。报告代码入口是imread('3.jpg'),改成视频只需要一个循环:用VideoReader读视频文件,或webcam对象读摄像头实时画面,每帧都调用现有的imread之后的处理链路。关键是要加一个车辆检测触发逻辑——报告开头提到车辆检测部分是系统组成之一,但软件里没有实现。最简单的方案是帧差法:取连续两帧做差,如果差异像素超过阈值,就认为有车辆进入视野,才开始跑车牌识别链路。这样避免了每一帧都做全流程的算力浪费,也贴合报告里“当车辆检测部分检测到车辆到达时触发图像采集单元”的设计叙述。
第二,升级字符识别的比对策略。报告里的模板匹配是把 22x14 二值图逐像素相减,这个方法太脆。我的改造做法是先用形态学细化提取字符骨架,然后对骨架图计算 HOG 特征(方向梯度直方图),再用一个简单的多层感知器分类。不用深度学习框架,MATLAB 自带的feedforwardnet就能搭一个 128 维输入、34 输出的网络,训练样本不够就每个字符模板做轻微平移、旋转、加噪声来扩充,三倍数据量起步。训练完成后,把原来相减比对的代码块换成net(test_feature),识别率提升是很明显的。
第三,加一个实时可视化面板。课设答辩时,评委最喜欢看你现场改参数看结果。用 MATLAB 的uifigure和UIAxes组件做一个简易界面,左边放原始图,右边分四块依次显示灰度图、边缘图、二值图和识别结果,外加一个阈值 slider 控件实时调节二值化level参数,代码不到五十行。参数调节的效果立刻反映在四块画布上,答辩效果比贴静态图强太多。
给你一个实际验证过的小技巧:改完任何一步,不要只拿 3.jpg 一张图测。收集十张不同角度、不同光照、不同距离的车牌照片(手机拍就行),统一缩放到 640x480 后跑一遍全流程,统计每张图的定位成功率和字符识别正确率。报告里识别成功和失败的两组案例已经说明了问题——一个系统在测试集上的表现永远比单张样例更值得关注。
这套代码我前后拆过一遍,最深的感触是:传统图像处理方法的定位、分割、识别链路虽然每个环节都有明显的痛点,但它每一步都是可观测、可调试、可解释的,不像神经网络那样像个黑匣子。从那以后我每次拿 Matlab 做图像处理,都会先看一眼输入图像的直方图分布和结构元素尺寸,再决定要不要沿用报告的阈值公式,而不是无脑跑完整段脚本。希望这份拆解笔记能帮你少走几个弯路。
本文还有配套的精品资源,点击获取