简介:这份完整车牌识别系统项目包,以车辆检测、图像采集、预处理、车牌定位、字符分割与识别为主线,覆盖从触发采集到车牌号码输出的全流程,适合计算机、人工智能、自动化等专业学生用于毕设、课设或项目初期演示,也适合初学者对照源码学习图像处理与模式识别实战。包内共133个文件,其中jpg、png、bmp等图像样本用于测试不同场景下的定位与分割效果,wav音频文件可用于模拟车辆到达的采集触发环节,另有m脚本、fig图表、doc文档和课题介绍,配套说明文档对系统结构和各模块实现做了详细解析,便于快速上手和二次开发。压缩包约6.16MB,轻量易下载,目前已有57人学习。资源附带完整测试通过的源码和文档说明,下载后可按README或文档指引运行,若遇环境或运行问题,作者提供私聊远程教学支持。整体内容紧凑、结构清晰,是一份兼顾教学演示与实际部署参考的车牌识别入门到进阶资料。
1. 车牌识别不是一个我会建议你直接套用深度学习模板的任务
很多刚接触车牌识别的同学会把注意力放在字符识别上,实际上真正决定项目能否交付的是车辆检测和车牌定位。这套资源里包含车1.bmp、车2.bmp两张现场图像,以及chepai.fig、车牌识别文档详细解析.doc和一组Sam*.jpg字符模板,正好覆盖一个完整链路:从图像采集触发开始,经过预处理、定位、分割,最后输出车牌号码。它没有用任何深度学习框架,全部基于MATLAB图像处理工具完成,适合课程设计、毕设,也适合想重新复习传统图像处理流程的工程师。接下来的章节我会按图像数据流动顺序,把这几个单元拆开讲,并给出可以直接复现的代码和参数经验值。
2. 图像采集与预处理:从车1.bmp到可定位候选区的关键步骤
2.1 图像采集触发单元的数据来源
图像采集在真实路侧系统中通常由地感线圈或补光灯触发,车辆检测单元会先做帧差法判断目标是否进入画面。这套资源没有提供视频流,而是直接给出了已经触发成功的车1.bmp和车2.bmp,所以离线处理时可以直接跳过动态检测,把它们当作采集结果读入。我的处理顺序是:先把彩色图转为灰度图,再消除噪声,最后强化边缘,这样车牌区域才能在颜色阈值和形态学处理中形成明显候选块。预处理参数直接决定定位是否稳定,这一节里的数值大多可以在车牌识别文档详细解析.doc里找到出处。
2.2 灰度化、去噪与边缘增强的MATLAB实现
我常用的一组MATLAB代码如下:
% 读取现场图像 img = imread('车1.bmp'); % 灰度化:消除颜色冗余,为后续边缘检测做准备 gray = rgb2gray(img); % 中值滤波去噪:3x3窗口对椒盐噪声和传感器噪声有较好抑制 gray = medfilt2(gray, [3 3]); % 边缘增强:Sobel算子计算梯度幅值,0.08是经验阈值 edges = edge(gray, 'sobel', 0.08);这里每一步都有取舍。rgb2gray直接采用YIQ亮度通道,符合人眼感知,比简单平均更接近车牌图像在黑白传感器下的表现。中值滤波用3x3而不是5x5,是因为车牌字符笔画宽度通常只有几个像素,窗口太大会把字符边缘抹平;如果图像中有明显粒状噪声,我会临时改成5x5,代价是后续字符分割时需要做一次形态学膨胀来恢复笔画宽度。Sobel阈值0.08是一个偏低的值,这样做的目的是宁可多留下一些阴影或路面纹理,也不能让车牌边框在边缘图上断裂。
预处理阶段我一般不会做透视校正,因为倾斜角较小时,后续形态学闭运算可以容忍;若倾斜角超过15度,再在车牌定位后单独做旋转矫正。对车2.bmp这种偏暗的图像,我会额外在灰度图上做一次直方图均衡化,注意不要在HSV的V通道上做太强拉伸,否则颜色饱和度会发生变化,导致后续HSV阈值漂移。
2.3 预处理参数对照表
下面这张表是我在调不同样本时总结的参数范围,比单独给一个固定值更有用:
| 处理步骤 | 常用方法 | 参数经验值 | 参数失控时的表现 |
|---|---|---|---|
| 灰度化 | rgb2gray | 无 | 颜色信息在后续HSV定位中会进一步被利用,此处不可用自定义强度权重 |
| 去噪 | medfilt2 | [3 3] 或 [5 5] | 窗口过大导致字符笔画粘连,窗口过小噪声残留 |
| 边缘检测 | sobel | 0.05 ~ 0.12 | 阈值过高,边框断裂;阈值过低,路面纹理被误认为边缘 |
| 亮度归一化 | histeq | 无参数 | 过度拉伸会让蓝色区域偏移,影响HSV阈值范围 |
实际项目里,最容易被忽视的是图像割裂现象。车1.bmp和车2.bmp如果直接跑同一个参数,通常会出现候选区域面积差异极大的情况。我建议先跑整个数据集,统计边缘图上非零像素占比,用sum(edges(:)) / numel(edges)输出,把目标比例控制在0.05到0.2之间。如果某个样本低于0.05,说明边缘过弱,需要降低Sobel阈值;高于0.2,则噪声过多,应优先加强中值滤波而不是继续调阈值。
3. 车牌定位:HSV颜色阈值与形态学闭运算的工程组合
3.1 为什么使用HSV而不是RGB进行粗定位
车牌的背景色通常是蓝色或黄色,RGB空间下三个通道互相耦合,受光线影响很大,同样的蓝色在阴影中可能R和G分量升高,导致阈值难以设定。HSV空间把色相单独分出来,我可以只关心H通道范围,同时用S和V排除白色和暗色区域。MATLAB的rgb2hsv返回H、S、V都是0到1的浮点数,蓝底车牌对应的H范围大概在0.55到0.75之间,这个区间在不同相机下会有小幅偏移,我会先用滑块工具手动确认一下当前数据集的色相分布,再写入固定阈值。
3.2 形态学闭运算、连通域筛选与区域坐标还原
一种常见做法是先对HSV掩码做闭运算,把断裂的车牌字符和边框连接成一体,再用连通域分析提取候选矩形。示例代码如下:
% 转换颜色空间 hsv = rgb2hsv(img); % 蓝色车牌的HSV阈值,S下限和V下限用于排除灰色区域 mask = (hsv(:,:,1) > 0.55) & (hsv(:,:,1) < 0.75) & ... (hsv(:,:,2) > 0.3) & (hsv(:,:,3) > 0.15); % 闭运算:先膨胀再腐蚀,弥合字符间隙 se = strel('rectangle', [15, 4]); mask = imclose(mask, se); % 连通域分析 cc = bwconncomp(mask); stats = regionprops(cc, 'BoundingBox', 'Area'); % 根据车牌宽高比和面积筛选 candidate = []; for k = 1:length(stats) w = stats(k).BoundingBox(3); h = stats(k).BoundingBox(4); areaRatio = stats(k).Area / (size(img,1) * size(img,2)); if w/h > 2.5 && w/h < 5.5 && areaRatio > 0.01 && areaRatio < 0.2 candidate = stats(k).BoundingBox; end end % 裁剪出车牌区域 plateImg = imcrop(img, candidate);闭运算的结构元素我用一个宽15像素、高4像素的矩形,这个比例接近一个字符的笔画密度,能很好地把相邻字符之间的狭长间隔连起来。如果结构元素太短,连接效果差,多个字符块会分成两个候选区域;太长又容易把车灯或保险杠上的横向纹理也连进来。宽高比范围选择2.5到5.5,因为中国车牌的标准尺寸是440mmx140mm,近似3.14,但样本倾斜或透视会拉宽,所以上下各留了余量。面积比控制在0.01到0.2,是为了排除远处小车和近处车头整体区域。
这里有一个工程细节:regionprops默认返回的BoundingBox是轴对齐的,如果车牌有轻微旋转,宽高比会被放大。此时可以额外读取Orientation属性,用最小外接矩形重新计算长度和宽度。下面是一组我会在代码里同时使用的筛选范围:
| 筛选参数 | 取值范围 | 场景说明 |
|---|---|---|
| 宽高比 w/h | 2.5 ~ 5.5 | 标准车牌约3.14,倾斜时放宽到5.5 |
| 面积占比 | 0.01 ~ 0.2 | 小于0.01是远距离小目标,大于0.2可能是整块车体 |
| 角度 | -15° ~ 15° | 超过后需要先做旋转校正,否则重算w/h |
3.3 两张现场图的定位差异和chepai.fig带来的排错信息
我实际运行车1.bmp和车2.bmp时发现一个典型差异:车1.bmp的蓝色HSV掩码比较干净,经过闭运算后只有一个大连通域;车2.bmp因为车辆处于侧向位置,牌照产生透视,掩码区域被拉得很扁,宽高比接近6,直接越过筛选阈值。此时不能只调整宽高比上限,而是先对候选区域做一次极小角度旋转矫正,或者改用上面提到的最小外接矩形,再重新计算宽高比。
资源里的chepai.fig是MATLAB的figure文件,用openfig('chepai.fig')打开后,能看到当初记录下来的定位中间结果,包括HSV掩码层、闭运算后和最终矩形框三个图。这个文件对排错很有帮助,能直接对比你的参数和原始设计者的参数在哪个环节分叉。如果手头没有MATLAB,也可以用Octave的load打开部分兼容数据,但figure交互功能会弱一些。
4. 字符分割与识别:把Sam渝.jpg变成可输出号码
4.1 水平投影与垂直投影的分割原理
车牌定位完成后,得到的基本是一张包含蓝色边框和白色字符的彩色图。字符分割前需要先做二值化,把字符变成1,背景变成0。我倾向使用OTSU全局阈值,因为车牌区域的对比度通常稳定,局部自适应阈值反而会把边框噪声引入字符区域。接下来执行水平投影和垂直投影,投影就是逐行或逐列对二值图像求和,连续的非零区间对应字符行或列。代码可以写成一个函数:
function charCells = splitChars(bwPlate) % 水平投影:定位字符区域上下边界 hProj = sum(bwPlate, 2); hRows = find(hProj > 0.1 * max(hProj)); bwPlate = bwPlate(min(hRows):max(hRows), :); % 垂直投影:定位每个字符左右边界 vProj = sum(bwPlate, 1); vProj = vProj > 0.1 * max(vProj); % 找连续非零段的起止索引 diffVec = diff([0, vProj, 0]); startIdx = find(diffVec == 1); endIdx = find(diffVec == -1) - 1; % 切分字符 charCells = cell(1, length(startIdx)); for i = 1:length(startIdx) charCells{i} = bwPlate(:, startIdx(i):endIdx(i)); end end投影阈值的0.1倍max是为了自适应不同光照,避免第二行字符或边框噪声破坏分割。如果字符内部笔画较细,最大值可能来自一条横线,此时0.1倍可能导致分割线断裂。我会在二值化之后先调用一次imclose(bwPlate, [1 5]),做小尺度的水平连接,把同一个字符内断裂的笔画合起来。
4.2 基于模板匹配和特征比对的字符识别
识别阶段,资源里的SamO.jpg、Sam0.jpg、Sam8.jpg、Sam渝.jpg、SamG.jpg、SamC.jpg就是模板库。这组文件名混合了数字、字母和汉字,正好覆盖了容易混淆的类别:O与0的外观几乎一致,G与C的区别只在缺口位置。传统模板匹配代码很直接:
% 统一字符尺寸为40x20像素 templates = {'SamO.jpg', 'Sam0.jpg', 'Sam8.jpg', 'Sam渝.jpg', ... 'SamG.jpg', 'SamC.jpg'}; for i = 1:length(charCells) charImg = imresize(charCells{i}, [40 20]); bestScore = Inf; bestChar = ''; for j = 1:length(templates) tpl = imread(templates{j}); tpl = imresize(tpl, [40 20]); score = sum(sum(abs(double(charImg) - double(tpl)))) / numel(tpl); if score < bestScore bestScore = score; bestChar = templates{j}; end end result(i) = bestChar; end % 输出车牌号 fprintf('识别结果: %s\n', result);绝对差匹配逻辑足够应付规格统一的正常车牌。它的缺陷在于对笔画粗细和字体变化很敏感,所以我会在匹配后加一道二次校验:统计连通域数量。数字8和字母B都有两个孔洞,保护号码中第2个字符是字母,假设第2位为字母,则当候选字符的孔洞数为两个时,优先分配B或8;第1位是汉字,只能用模板库中的有限汉字集合匹配。下表是我在处理常见混淆对时使用的方法:
| 混淆对 | 区分方法 |
|---|---|
| 0与O | 结合车牌位置先验:第2位是字母时选O,第3位及以后选0 |
| 8与B | 孔洞数都是2,但B右侧有一条垂直边,8中心孔洞位置偏高 |
| 渝与G | 对汉字区域单独放大模板权重,避免被外边框干扰 |
4.3 识别结果组装与输出
识别结果不能直接拼接模板文件名,需要去掉扩展名,再按中国车牌编码规则装配。常见输出格式是省份汉字+字母+5位数字或字母组合,但在课程设计里只需要将字符数组连接并过滤杂质。如果分割阶段误把车牌中的小圆点算成一个字符,要提前设置宽度过滤:字符宽度占整个车牌宽度的比例低于3%时直接丢弃。这个尺寸过滤通常放在splitChars函数返回值之后,可以节省大量调参时间。我还会把每个字符的匹配得分保存下来,识别器输出时附带一个置信度,方便在毕设答辩时解释哪里识别错了。
5. 课程设计与毕设工程化的几个验证技巧
5.1 用文档说明和README.md还原完整流程
下载资源后不要急着改代码,先打开车牌识别文档详细解析.doc,里面通常会列出参数表和流程图。如果有README.md,第一步先把运行顺序记录下来:读取图片、预处理、HSV定位、闭合运算、连通域筛选、字符分割、模板匹配。我习惯把每个中间变量在MATLAB工作区保存为.mat文件,这样后续可以用load直接对比。chepai.fig里保存的定位中间结果可以当作标准答案,验证你的定位框是否和原始设计者一致。
5.2 没有MATLAB环境时如何快速验证算法思路
如果只有一台没有MATLAB的电脑,可以用Octave运行部分脚本。但chepai.fig和模板匹配代码中部分工具箱函数不兼容,这时可以用Python OpenCV写一个最小验证版本:cv2.imread后先用cv2.cvtColor转到HSV,再走一遍同样的阈值和morphologyEx闭运算,会发现能复现相同的定位结果。这个迁移过程也能帮你确认算法依赖到底在哪里。对已经入行的工程师,这种传统图像处理方案的价值在于无依赖、可解释,适合那些不允许安装深度学习运行时的嵌入式环境。
5.3 从传统方案过渡到更现代的检测流程
现在商业车牌识别产品早已用上深度学习,常见做法是用YOLOv5或YOLOv8直接检测车牌区域,替代HSV定位和形态学连接。你在这套资源中学到的字符分割和模板匹配,可以保留作为识别后处理。验证时建议用同一批图片分别跑传统流程和深度学习流程,记录定位框的IoU和整体识别准确率。一个实用的技巧是:将chepai.fig中的结果与YOLOv5输出叠加到同一张图上,可以直观看到传统方法漏检的模糊、倾斜样本,这些样本才会成为改进深度模型的训练集。如果在STM32这类边缘端做车辆检测,处理流程会进一步重构成灰度和轻量卷积结构,而这块资源的预处理与IoU评估逻辑依然可以直接复用。
本文还有配套的精品资源,点击获取