资讯详情

基于Matlab的车牌识别实战:BP神经网络与图像处理全流程解析

📅 2026/10/17 5:39:03 | 华诺云谱 👁 阅读
基于Matlab的车牌识别实战:BP神经网络与图像处理全流程解析
把一张随手拍的车牌照片变成一行干净的字符文本中间要跨过好几道坎先得从复杂的画面背景里把车牌区域找出来再把车牌上的字符一个个切开最后让算法认出来。这三步里最后一步的主角通常就是BP神经网络。我最初完整体验这个项目用的就是Matlab整套代码不算长却把图像处理、特征提取、人工神经网络、模式识别这些知识全部串了起来特别适合第一次接触机器视觉和神经网络的人。这篇不是文档搬运是我实际调试、反复踩坑后的记录定位、分割、训练、识别各个环节都尽量讲透要做快速原型或者拿来交项目作业的朋友可以直接照着走。1. 整体设计思路与方案选型1.1 一条完整的识别链路应该怎么拆分车牌识别听起来是个整体落到工程上必须拆成几个独立模块不然根本没法调试。最常见的拆法是把流程分成五段图像输入、车牌定位、字符分割、字符归一化、BP网络识别。前两段解决“车牌子在哪”中间两段解决“牌子上有哪些字”最后一段解决“每个字是什么”。这种拆法最大的好处是问题隔离比如识别率低的时候你可以先确认是字符切歪了还是网络训练得不好而不是一锅粥乱查。我在实际测试中还发现一个容易被忽略的点定位和分割的质量直接决定了BP网络能发挥多少实力。网络本身再强喂进去的字符图是歪的、带边框的、二值化方向反的准确率也上不去。所以整套系统的权重分配我大概会划成图像预处理占五成字符分割占三成BP训练占两成。很多初学者上来就盯着网络结构调参其实前面两步才是真正的瓶颈。1.2 为什么选BP神经网络而不是模板匹配早期车牌识别系统很多用模板匹配思路是把字符图和标准模板逐像素对比算相似度取最大值。这个方案实现简单但有两个致命弱点一是对字符的变形、噪声、光照变化特别敏感稍微有点倾斜笔画变粗相似度就崩掉二是每来一种新字体或者新样式就得重新做一套模板维护成本很高。说白了模板匹配就像配钥匙钥匙齿形差一点也不行。BP神经网络是另一种思路它不直接比对像素而是通过多层神经元从训练样本里自动学习“哪些像素组合构成哪个字符”。一开始网络权重是随机的训练过程中根据预测错误不断调整最后相当于自己总结出一套字符的判别规则。这个过程更像“学写字”见过足够多样式之后看到一个没完全见过但风格一致的字符也能认出来。这种泛化能力就是它在车牌识别里胜出模板匹配的核心原因。当然现在更先进的方案是卷积神经网络CNN识别率确实更高但需要更多样本、更长的训练时间而且框架部署复杂度也上来了。对一个几百行Matlab代码就能跑通的入门级项目来说BP神经网络的性价比是最合适的它保留了神经网络学习机制的本质又不会把过多的注意力分散到调模型结构上。1.3 训练样本与标签规划BP网络是监督学习训练前必须准备两样东西字符图片数据以及每张图片对应的正确标签。标签处理方式我推荐用one-hot编码假设字符集一共65类省份简称、24个英文字母去掉I和O、10个数字那么每个样本的标签就是一个长度为65的向量所属类别位置为1其余为0。样本来源有两种主流做法。第一种是用字体渲染生成比如在Matlab里用不同字体、不同字号画出字符图片再做缩放、旋转、加噪声优势是省事能快速攒出一批训练数据第二种是从真实车牌图片上裁剪字符手工标注优势是数据分布和实际场景一致训练出来的模型更有实用价值。我的建议是两种结合先靠合成数据把网络训到一个能用的基线再用真实字符数据做微调。只靠合成数据的模型放到实拍图片上识别率通常会掉一截这一点你在测试时会深有体会。2. 车牌定位与字符分割实操2.1 用边缘检测和形态学把车牌框出来车牌定位是整个系统里出错率最高的环节也是最需要耐心调参数的地方。我常用的方案是“边缘检测形态学处理连通域筛选”的组合。第一步把彩色图像读进来转成灰度图然后用Sobel算子提取垂直边缘。为什么单独提垂直方向因为车牌字符是竖直排列的细笔画在垂直方向上会产生密集的边缘响应而车身的横向边缘比如保险杠的线条会被过滤掉很大一部分。这一步做好了后续定位难度直接降一半。第二步是形态学处理。对边缘图做一次膨胀操作把断裂的字符边缘连起来再做闭运算填补字符内部的空洞。这一步的关键是结构元素大小经验值是用边长15到20像素的正方形结构元素。太小连不起来太大容易把整片车头区域糊成一个连通块。这个参数和图像分辨率强相关实际使用时应该根据你的图像尺寸做等比调整。第三步是连通域筛选。遍历所有连通区域计算每个区域的宽高比和面积。常见车牌宽高比约为3.14比1实际判断时可以放宽到2.5到4.5面积阈值则要根据图像大小确定比如1000乘800的图车牌区域面积通常在2万像素以上。筛选完之后如果还剩下多个候选区域可以用一个额外的技巧彩色图像转HSV空间统计候选区域中蓝色像素色调在0.55到0.75区间的比例蓝色车牌在这一步能直接过滤掉大量误检。这里有一个我踩过的坑只靠宽高比筛选会把车灯、车标、散热格栅上的反光区域误判成车牌。加入蓝色像素校验之后误检率大幅度下降。如果你的识别对象不是蓝牌这个颜色判断逻辑也要跟着调整为对应的目标颜色。2.2 字符分割用垂直投影把字符逐个切开定位到车牌区域之后接下来要把车牌上的字符一个个切出来。这个环节的预处理顺序很讲究我一般按这样来先灰度化再二值化然后去除边框和铆钉最后做垂直投影分割。二值化方向是个容易翻车的细节。车牌字符是深色底色是浅色二值化之后应该是字符为黑色、背景为白色还是反过来很多资料没有明确说明导致字符图送入网络后明暗方向不一致识别率忽高忽低。我的建议是统一使用“字符为白色、背景为黑色”的表示方式后续所有处理都基于这个约定执行。原因很简单字符区域内白色像素的数量和分布是分割、归一化的主要依据白色字符在视觉检查和调试时也更直观。去除铆钉和边框推荐用连通域面积筛选。铆钉是车牌的固定螺丝面积很小靠近车牌边界会在投影图上产生一个很窄的干扰峰边框则是四周一圈连续的白色矩形会对字符产生粘连。实际操作中可以先向内收缩切割矩形上下各收缩约5%再用连通域分析把面积明显小于字符的噪声块删掉。这一步做完投影图会干净很多。垂直投影分割的核心算法不复杂统计每一列上的白色像素数得到一条一维曲线字符间隔处会出现低谷。我的实现思路是先扫描投影曲线找到所有连续的非零区间每个区间对应一个字符的左右边界。但这里有个坑——如果字符笔画内部有空隙比如汉字的某些结构投影曲线中间可能出现断裂导致一个字符被切成两段。解决办法是设置一个最小宽度阈值一般取字符标准宽度的三分之一如果某个区间的宽度小于这个阈值就把它合并到相邻区间里去。2.3 字符归一化网络输入的最后一公里字符切割完成之后每张字符子图的大小都不一样有的是90乘180像素有的是73乘159像素。BP网络是固定输入维度的所以必须先做尺寸归一化和数值归一化这一步直接影响识别准确率。尺寸归一化我习惯把字符图统一缩放到16乘32像素宽16、高32这也是常见车牌字符的宽高比例。要注意的一点是直接等比缩放和直接拉伸缩放的结果不一样等比缩放会在边角产生黑边和多余空白直接拉伸会改变字符的长宽比例。我的经验是直接拉伸更符合神经网络的输入习惯因为网络只关心像素位置的统计模式不关心原图的比例关系只要训练和识别用同一套缩放方式就行。缩放用imresize函数的双三次插值即可效果比最近邻插值平滑很多。数值归一化容易被忽略。原始灰度图的值域是0到255如果直接作为神经网络输入大的数值会让神经元输出很早进入饱和区梯度消失训练很难收敛。正确做法是把所有像素值映射到[-1,1]区间img_norm double(img) / 255 * 2 - 1。这个细节我在第一次训练时没注意误差一直停在0.5左右不下降加上之后情况立刻好转。还有一个细节训练样本和待识别样本必须走完全一致的预处理流程。先缩放后二值化还是先二值化后缩放都会产生差异一旦训练和识别的流程不一致识别率会明显下降。最稳妥的做法是把预处理封装成一个函数训练和识别都调用同一个入口。3. BP神经网络结构、参数与训练细节3.1 网络结构怎么定BP网络结构核心是三个数字输入层节点数、隐藏层节点数、输出层节点数。输入层节点数由字符归一化尺寸决定。16乘32的字符展开成列向量就是512维所以输入节点数是512。输出层节点数由字符类别总数决定我做的是数字和字母加省份简称的混合识别共65类所以输出节点数是65。识别时取输出向量中数值最大的那个下标作为预测类别。隐藏层节点数是最需要调的部分。没有万能公式但有一个经验范围可以参考隐藏层节点数通常是输入节点数的十分之一到五分之一同时不小于输出类别数。512维输入时可以取60到120。我实测下来80个隐藏节点在这个任务上的表现比较均衡节点太少网络拟合能力不足识别率上不去节点太多训练时间变长而且容易过拟合在训练集上表现很好一换到测试图片就出错。你可以在这个基础上做几组对比实验记录每组在验证集上的识别率取最高点。3.2 训练函数、学习率与收敛目标训练函数的选择直接影响收敛速度和稳定性。Matlab的神经网络工具箱里traingdx梯度下降动量法加自适应学习率和trainlmLevenberg-Marquardt算法是最常用的两个选择。我的实际体验是trainlm收敛很快但容易陷入局部最优内存占用也偏大在小规模数据集上表现不稳定traingdx虽然收敛慢一点但胜在稳健字符识别这类中小规模的分类任务推荐优先尝试。学习率方面初始值我习惯设为0.05到0.1之间。设置太大误差会在最优值附近震荡不收敛设置太小训练时间被拉长。动量系数设为0.9可以帮助梯度跳出局部极小点让误差曲线更平滑。目标误差goal设为0.001到0.01之间即可太小的目标会让网络过度拟合训练数据不见得提升真实识别率。最大迭代次数我一般设置2000到3000轮。这里有个省时间的技巧在训练过程中打印误差变化曲线如果误差在500轮左右就达到目标可以提前停止训练。如果2000轮误差还在高位徘徊说明网络结构或数据预处理有问题单纯增加迭代次数解决不了根本问题。3.3 数据增强与样本配比训练样本数量和质量是BP网络性能的上限来源。每类字符至少准备50到100个样本总样本量达到几千张时效果才够稳定。如果某个类别样本太少网络对这类的判别能力会明显偏弱。数据增强是解决样本不足的利器。对已有的字符图做小幅旋转正负5度、平移几个像素、缩放0.8到1.2倍、加高斯噪声都能生成新的训练样本。这样不仅扩充了数量还提高了网络对轻微形变的容忍度和真实场景下字符的成像波动很契合。样本配比也值得一提。如果某类字符样本量特别大而另一类特别少网络会把精力都放在拟合多数类上少数类的识别率会很差。我的做法是先统计各类样本数量对样本少的类别多做一些增强变换尽量让各类数量处于同一量级。4. Matlab关键代码与运行记录4.1 车牌定位代码实现这一节给出可以直接运行的Matlab代码完整流程包含读图、灰度化、边缘检测、形态学处理、连通域筛选和裁剪。% 车牌定位边缘检测 形态学 连通域筛选 img imread(test_car.jpg); gray rgb2gray(img); % Sobel垂直边缘提取 bwEdge edge(gray, sobel, vertical); % 形态学膨胀连接字符边缘闭运算填充空洞 se strel(square, 17); bwClose imclose(bwEdge, se); bwClose imdilate(bwClose, se); bwClose imfill(bwClose, holes); % 删除面积过小的噪声连通域 bwClean bwareaopen(bwClose, 2000); % 连通域分析 stats regionprops(bwClean, BoundingBox, Area); plateBox []; for k 1:length(stats) w stats(k).BoundingBox(3); h stats(k).BoundingBox(4); ratio w / h; area stats(k).Area; % 宽高比和面积双重筛选 if ratio 2.5 ratio 4.5 area 5000 plateBox stats(k).BoundingBox; break; end end % 裁剪车牌 if ~isempty(plateBox) plateImg imcrop(img, plateBox); imshow(plateImg); end代码里的几个参数不是拍脑袋写的。结构元素尺寸17对应图像宽度800到1000像素的图片可以让字符边缘连成片又不至于糊掉面积阈值2000用来去掉散碎噪声5000是最小车牌候选面积。如果你的图像直接来自手机拍摄分辨率更高这些阈值要等比例放大。4.2 字符分割与归一化代码实现定位到车牌后进入字符分割流程。下面的代码包含灰度化、二值化、去除上下边框、垂直投影分割和尺寸归一化。% 字符分割与归一化 plateGray rgb2gray(plateImg); plateBin imbinarize(plateGray); % 统一为白字符黑背景 if mean(plateBin(:)) 0.5 plateBin ~plateBin; end % 去除上下边框取行投影找到字符内容主体区域 rowProj sum(plateBin, 2); rowIdx find(rowProj 1); if isempty(rowIdx) error(未检测到字符区域); end rowTop rowIdx(1); rowBottom rowIdx(end); plateBin plateBin(rowTop:rowBottom, :); % 去除左右边框向内收缩固定比例 [ph, pw] size(plateBin); marginW round(pw * 0.02); plateBin plateBin(:, marginW1:end-marginW); % 垂直投影分割 colProj sum(plateBin, 1); charRegions []; inChar false; startCol 0; minCharW round(size(plateBin, 2) / 10); for c 1:length(colProj) if colProj(c) 0 ~inChar inChar true; startCol c; elseif colProj(c) 0 inChar inChar false; if c - startCol minCharW charRegions [charRegions; startCol, c]; end end end % 按照找到的区间切割并统一归一化到 16x32 chars {}; for i 1:size(charRegions, 1) seg plateBin(:, charRegions(i,1):charRegions(i,2)); seg imresize(seg, [32, 16]); chars{end1} seg; end figure; for i 1:numel(chars) subplot(1, numel(chars), i); imshow(chars{i}); end分割代码的关键逻辑在垂直投影的扫描循环里。minCharW取整个车牌宽度的十分之一可以防止字符内部的笔画断裂被误判为字符边界。我在测试中发现字符“川”和“京”这类笔画复杂的汉字投影曲线波动特别大如果不加这个最小宽度阈值一个汉字会被切成两段BP网络根本没法识别。4.3 BP网络训练与识别代码实现训练部分的代码分三步整理输入输出数据、创建网络、训练并测试。下面给出核心实现。% 构建训练集 % trainData: 512 x N 矩阵每列是一个归一化字符向量 % trainLabel: 65 x N 矩阵每列是one-hot标签 inputs trainData; targets trainLabel; % 创建BP网络 hiddenSize 80; net newff(minmax(inputs), [hiddenSize, size(targets, 1)], ... {tansig, purelin}, traingdx); % 训练参数设置 net.trainParam.epochs 3000; net.trainParam.goal 0.001; net.trainParam.lr 0.1; net.trainParam.mc 0.9; net.trainParam.show 50; % 训练 [net, tr] train(net, inputs, targets); % 测试识别 output sim(net, testData); [~, pred] max(output, [], 1); % 计算准确率 acc sum(pred testLabelIdx) / length(testLabelIdx); fprintf(测试准确率: %.2f%%\n, acc * 100);这里有几个容易出错的地方。minmax(inputs)生成输入范围矩阵新版Matlab里newff依然可用但官方推荐用feedforwardnet替代。如果你用的是较新版本可以用下面这几行替换网络创建部分。net feedforwardnet([hiddenSize]); net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn purelin; net.trainFcn traingdx;为什么隐藏层用tansig、输出层用purelin因为tansig能把输出压缩到[-1,1]适合做隐藏层的非线性变换输出层用线性激活函数可以让网络输出的数值范围不受限制配合max取最大值下标得到分类结果这种组合在分类任务里很经典。4.4 一次完整的实测记录我在某个测试数据集上完整跑过一次这套流程数据是现场采集的几十张车辆图片分辨率1024乘768。定位阶段成功率约九成失败的主要场景是车身颜色和车牌底色接近或者车牌在画面里占比太小。定位成功之后字符分割的成功率在八到九成之间最常出问题的是字符间距不均匀和边缘阴影。分割质量过关的字符送入BP网络单个字符识别准确率在95%以上数字和字母的识别率普遍比汉字高。整牌识别率最终稳定在八成左右。这个数字看起来不是很高但如果你实际做一个项目就会知道大多数丢分不是网络笨而是前面定位或者分割环节把字符图切坏了。每次识别出错的时候把出错的字符图打印出来看一眼原因基本都一目了然。5. 常见问题与排查技巧实录5.1 定位环节的典型问题问题一定位框选到了车灯或车标区域反而把真正的车牌漏掉了。这个大概率是单纯依赖宽高比筛选导致。解决办法是加入颜色特征约束在HSV空间中统计蓝色像素比例候选区域必须有足够比例的蓝色像素才保留。如果是新能源绿色车牌色调阈值要相应调整。问题二车牌区域边缘断裂严重形态学处理后无法形成完整连通域。排查方向是结构元素尺寸太小或者Sobel边缘检测的阈值设置过高。可以适当增大结构元素尺寸或者把边缘检测阈值下调让更多的弱边缘参与连接。问题三图像中车牌占比很小连通域面积阈值写死导致误删。解决思路是面积阈值不要写死而是根据图像总像素数做一个比例换算比如取总像素的0.5%到3%作为有效车牌区域的面积范围这样在不同分辨率下都能通用。5.2 分割环节的典型问题问题一字符之间粘连严重垂直投影找不到低谷。常见原因是二值化阈值不合理或者车牌本身有污渍。可以先对车牌区域做一次中值滤波再用自适应阈值方法如adaptthresh代替全局阈值粘连问题会明显改善。问题二汉字字符被切成两段。这个我前面提到过多半是最小字符宽度阈值没设置。汉字内部笔画结构复杂投影曲线会有多个低谷必须靠最小宽度阈值过滤掉无效低谷。我在实际中把这个值设为车牌宽度的十分之一效果比较稳定。问题三分割出的字符图里有残留的铆钉或边框。铆钉靠近车牌边缘边框包裹在整个车牌四周都会污染字符区域。除了前面代码里的行投影收缩和边缘裁剪还可以做一个连通域面积过滤把面积小于字符平均面积三分之一的连通域直接删除。5.3 训练和识别环节的典型问题问题一训练误差一直不下降停在0.5附近。这是我在BP网络新手期最常遇到的问题。原因是输入数据没有做数值归一化256的像素值进入tansig激活函数后直接饱和。把输入数据映射到[-1,1]误差立刻开始下降。问题二训练集准确率很高但测试集准确率很低。典型的过拟合现象。可以从三方面排查训练样本太少隐藏层节点过多或者训练轮数太长。优先增加样本数量和做数据增强同时把隐藏层节点数往低调试试看。问题三某些特定字符总是混淆比如数字0和字母O数字1和字母I。这类问题在字符集设计阶段就应该规避车牌字符集本身就不包含容易混淆的字母I和O因此网络输出类别里也不应该有这两类。如果你的项目里还有类似的混淆对检查训练样本里有没有混入错误标签而不是盲调网络。5.4 问题速查表常见问题可能原因排查思路解决方案车牌定位失败宽高比阈值不合适打印候选连通域检查结合HSV颜色过滤辅助判断目标候选框过多形态学结构元素过小观察边缘图连接情况增大结构元素尺寸字符粘连二值化阈值偏差查看二值化中间结果改用自适应阈值方法汉字切成两段未设置最小字符宽度检查投影低谷位置增加最小宽度合并逻辑训练误差停滞输入未归一化检查输入数据值域映射到[-1,1]测试集准确率低过拟合对比训练集与测试集差距增加样本、减少隐层节点特定字符总混淆样本标签错误抽查混淆类别的样本清洗数据集重新标注我在实际操作中的体会是这套系统最花时间的环节不是BP网络本身而是图像预处理和字符分割的参数调节。网络结构只要按照经验值设定误差曲线基本都能顺利收敛反而定位和分割因为面对的是真实拍摄条件中的复杂变化需要你反复调整参数、观察中间结果才能稳定下来。建议调试时把边缘图、形态学结果图、投影图都显示出来一步步定位问题出在哪而不是等到最后看识别率再去猜原因。最后再分享一个小技巧调通整套流程之后可以把预处理函数和网络模型打包成两个独立脚本识别时只调用接口不用每次从头训练。后续想扩展到其他字符集也只需要更换训练数据网络结构和预处理流程可以原样复用。这个扩展思路能让你的系统快速适配更多应用场景而不必重新造轮子。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑