
简介基于BP神经网络的车牌识别MATLAB实现面向图像处理与模式识别入门者、高校学生及智能交通方向工程师提供从车牌图像采集、预处理到字符识别输出的完整技术流程解决复杂环境下人工设计特征不稳定、识别率不高的问题。资源包大小11.42MB已有1103人学习下载可结合实际课题反复研读。方案详细介绍了灰度化、二值化、边缘检测、轮廓提取等预处理方法并指导将车牌几何形状、颜色分布、字符结构等特征编码为BP网络的输入向量。在网络构建上阐明了输入层、隐藏层、输出层节点数的确定原则以及学习率、动量项、训练次数等关键参数的调节策略。通过有监督训练与多次迭代网络逐步拟合图像与车牌号码的映射并在测试集上验证泛化能力。内容还讨论了光照变化、倾斜、遮挡等实际场景中的难点并提出以卷积神经网络替代BP网络的改进方向帮助读者系统掌握基于神经网络的智能车牌识别方法与MATLAB工程实现要点。1. BP网络车牌识别为什么一套老模型在Matlab里还值得跑通BP网络车牌识别Matlab这两个词放到一起搜索的人要么是课程设计要做车牌识别系统要么是工作中遇到了现场相机拍回来的图片质量太差、正规车牌识别SDK认不出来想自己补一个字符级别的小分类器。车牌识别可以拆成三个独立步骤车牌定位、字符分割、字符识别。前两步用传统图像处理方法就能做掉大部分工作真正需要神经网络出场的只有最后一步——告诉系统这个分割好的小图像是“京”“A”“6”还是“0”。BP网络恰好是这一步里门槛最低的选择不需要GPU不依赖深度学习工具箱几千张字符样本在CPU上几分钟就能跑完一轮训练而且每一步都能在Matlab工作区里看到中间变量出了问题可以直接点开查看这对理解网络原理和排查训练失败都非常方便。这篇博文把从图像预处理到BP网络训练、再到识别结果映射的完整链路走一遍不必下载别人的整包源码跟着步骤自己就能把识别器搭起来。2. 车牌识别前置处理从HSV定位到网格特征的Matlab实现车牌识别的效果上限往往不在网络而在输入到网络的图片质量。BP网络对笔画位置、粗细、断连都很敏感同一个字符“6”在训练集里居中对齐测试时偏了半个像素输出向量可能就乱了。所以把一张完整车牌照片变成一组组干净字符图是整个流程中必须认真对待的一步。2.1 先做车牌定位边缘检测与形态学闭运算定位车牌最稳妥的做法是先用颜色信息做粗筛选再用边缘和形状做确认。Matlab里可以用rgb2gray把彩色图转灰度再用edge提边缘然后imclose把断开的边缘连起来最后用regionprops按宽高比筛出车牌区域。% locate_plate.m img imread(car.jpg); gray rgb2gray(img); % 用Sobel算子提取边缘阈值0.2对常见夜间照片效果较好 bwEdge edge(gray, sobel, 0.2); % 先膨胀再腐蚀把字符笔画连接到一起形成连通域 se strel(rectangle, [15, 15]); bwClose imclose(bwEdge, se); % 填充内部空洞让车牌区域变成实心块 bwFill imfill(bwClose, holes); % 按面积和宽高比筛选候选区域 stats regionprops(bwFill, BoundingBox, Area); candidates []; for k 1:length(stats) w stats(k).BoundingBox(3); h stats(k).BoundingBox(4); % 标准车牌宽高比在2.5到4.5之间蓝底车牌白天拍摄时面积一般占全图1%以上 if h 0 w/h 2.5 w/h 4.5 stats(k).Area size(img,1)*size(img,2)*0.005 candidates [candidates; stats(k).BoundingBox]; %#okAGROW end endedge的阈值会影响边缘密度建议先从 0.15 开始调夜间照片适当降低imclose的结构元素尺寸要和图像分辨率匹配1080p 图像用 15×15 比较合适如果是手机拍的大图可以放大到 25×25。regionprops筛选按宽高比筛一次能去掉不少路牌、广告牌干扰。如果只想先验证BP网络定位环节可以用imcrop手动框出车牌把精力集中在后续步骤上。定位算法对BP识别率没有直接影响但它决定了你能不能在整图场景里把系统串起来。2.2 字符分割垂直投影法切出七个位置车牌字符之间有固定间隔蓝色底上白色字符二值化后同一行字符的垂直投影会呈现明显的波峰波谷。最常见的做法是把车牌区域灰度图先二值化然后统计每一列白色像素的数量投影值接近0的列就是字符间隙。% split_chars.m plateGray rgb2gray(cropImg); % cropImg为定位后的车牌子图像 bw plateGray 128; % 白字为1 % 统计每列白色像素数量 colSum sum(bw, 1); % 把连续的非零列段切出来每一段就是一个候选字符 inChar false; startIdx 0; charCols []; for c 1:length(colSum) if colSum(c) 3 ~inChar inChar true; startIdx c; elseif colSum(c) 3 inChar inChar false; charCols [charCols; startIdx, c]; %#okAGROW end end % 过滤掉高度不足的碎片保留最宽的7个段colSum(c) 3这个阈值用来去除细小的噪声列如果图像噪点多可以提高到5或8。过滤完还会遇到两个问题第一个字符是省份汉字笔画比较散投影时可能会断成两段第二个字符后面有一个小圆点那个点的高度不够会在筛选时被去掉。处理方式是按段的高度和相邻距离合并汉字断开的段之间间隔通常很小把间隔小于字符高度1/3的相邻段合并成一个字符。这一阶段如果发现分割结果不稳定先不要急着调BP网络优先检查二值化阈值。用graythresh自适应阈值比固定128更可靠尤其高速公路拍摄的图片光照不均时能明显减少字符粘连。2.3 特征提取字符归一化尺寸与网格特征设计分割出来的字符宽高不一样不能直接喂给网络。常见做法是把每个字符缩放到统一尺寸再提取网格特征。车牌字符大多有窄高特征归一化到 32×16 较合适既保留了笔画细节特征维度又不至于太高。% feature_extract.m function feat charFeature(charImg) % 统一尺寸双线性插值避免出现明显锯齿 resized imresize(charImg, [32, 16], bilinear); % 二值化一次统一前景值为1 resized resized 128; % 8x8网格特征把32x16图像切成8行4列每格4x4像素 feat zeros(1, 32); idx 1; for row 1:8 for col 1:4 block resized((row-1)*41:row*4, (col-1)*41:col*4); feat(idx) sum(block(:)) / 16; idx idx 1; end end end32维特征对应8×4网格每格统计4×4像素块里前景像素的占比这样弱化了笔画粗细的影响偏一点位置还能保持相近特征分布。也可以直接把32×16512维灰度值展开当作特征效果通常更好但网络输入节点会从32变成512隐层也要跟着加宽训练数据量不够时反而容易过拟合。网格特征另一个优势是能直观地叠加可视化把提取后的特征向量还原成热图就能看出哪些格子的像素对分类有贡献方便后续调优。参数项推荐值说明字符归一化尺寸32×16依据车牌字符宽高比设定网格划分8×4每个格子统计像素占比特征维度32维度过高需要更多样本二值化方法自适应阈值光照不均时优先调整3. 用Matlab搭BP神经网络的结构与训练参数从结构图到feedforwardnet数据准备完下一步就是构建网络。Matlab里封装了BP神经网络的标准库函数但对新手来说最容易踩坑的是不知道输入矩阵该怎么排、输出标签用什么格式。这一章先把训练数据组织方式讲清楚再给网络参数选型。3.1 训练样本的组织方式目录到矩阵训练目录按类别建文件夹每张图对应一个字符。读取时把所有样本的特征向量堆成一个矩阵X每一列是一个样本对应的标签用ind2vec转成类别向量矩阵T。% build_dataset.m classes {0,1,2,3,4,5,6,7,8,9,A,B, ... C,D,E,F,G,H,J,K,L,M,N,P, ... Q,R,S,T,U,V,W,X,Y,Z}; % 注意这里去掉了O和I因为与0和1容易混淆 X []; T []; for i 1:length(classes) folder fullfile(train_chars, classes{i}); files dir(fullfile(folder, *.png)); for j 1:length(files) img imread(fullfile(folder, files(j).name)); feat charFeature(img); X [X; feat]; %#okAGROW 每行一个样本 T [T; i]; %#okAGROW end end % 转置为列向量格式并生成单位矩阵作为目标 X X; T ind2vec(T);样本矩阵的维度在调用训练函数时可以直接影响速度数据量大时建议把[X; feat]改成预分配矩阵否则每加一个样本都重开内存几千张图也能感觉到卡顿。ind2vec输出的矩阵行数等于类别数列数等于样本数每个样本只有当前类别那一行是1其余是0。3.2 feedforwardnet与newff两种写法的参数对照Matlab从R2010b开始推荐feedforwardnet老教程里大量出现的newff依然可用但调用方式在新版本中略有变化。feedforwardnet(hiddenSizes)会直接生成一个可训练的前馈网络隐藏层默认激活函数是tansig输出层是purelin分类任务里输出层用logsig更合适因为输出被压缩到0到1之间可以和类别概率对照。% build_net.m net feedforwardnet([20, 10]); % 两层隐层节点数20和10 % 分类场景把输出层激活函数改成logsig net.layers{end}.transferFcn logsig; % 训练函数用自适应学习率动量法样本量不大时比trainlm更稳 net.trainFcn traingdx; % 训练参数设置 net.trainParam.epochs 1000; net.trainParam.goal 0.001; net.trainParam.lr 0.05; net.trainParam.max_fail 20;feedforwardnet的括号里传入向量每个元素是一个隐层的节点数。实际车牌字符识别任务中两层隐层的效果往往不会比一层明显更好反而增加训练时间因为字符特征相对简单输入只有32维。如果发现训练集准确率上去了、测试集上不去首先要怀疑隐层节点数太多导致过拟合。newff老写法里还能直接指定各层激活函数和训练函数适合想完全复现论文结构的人net newff(minmax(X), [20, size(T,1)], ... {tansig, logsig}, traingdx);newff的minmax(X)会自动计算输入范围不需要手动归一化但生成的网络初始化权重随机性较大最好在训练前用net init(net)重置一次保证每次跑的结果可以复现。3.3 隐层节点数与训练函数的选型逻辑隐层节点数没有一个绝对公式工业上常用的经验范围是输入维度与输出类别的几何平均值附近或者按sqrt(输入维度 输出类别) 1~10来取。对于32维输入、34个类别几何平均值大概是33所以常见做法是在这个值附近尝试一组候选值。% grid_search_hidden.m hiddenList [16, 24, 32, 40, 48]; bestAcc 0; for h hiddenList net feedforwardnet(h); net.layers{end}.transferFcn logsig; net.trainFcn traingdx; net.trainParam.epochs 500; net.trainParam.lr 0.05; [net, ~] train(net, X_train, T_train); Y_test net(X_test); acc mean(sum(round(Y_test) T_test, 1) size(T_test,1)); fprintf(隐层节点 %d, 测试准确率 %.2f%%\n, h, acc*100); end训练函数的选择也值得单独提一下trainlmLevenberg-Marquardt收敛很快但在分类输出层配合logsig时容易陷入局部最优且矩阵求逆计算量随节点数快速上涨traingdx是traingd加动量和自适应学习率训练慢一点但稳定适合课程设计和入门场景。如果你的Matlab装了优化工具箱默认训练函数可能被改成TrainBFG一类拟牛顿算法这时查看net.trainFcn确认一下很必要。如果需要对比不同参数组合的效果建议把训练集和验证集固定下来随机划分会导致每次结果不可比调参时很难判断是参数变化带来的提升还是数据划分的偶然性。4. 从BP网络输出到车牌字符串识别结果的组装流程训练收敛后网络拿到的只是一个输出向量。这一步要把网络输出转换成真正的人类可读车牌字符串同时应对汉字、字母、数字混杂的情况。这个组装环节在很多现成教程里被一笔带过但实际写代码时最容易被卡住。4.1 保存与加载网络Matlab里保存网络非常轻量直接save整个net结构体到.mat文件即可。save(plate_net.mat, net); % 其他项目使用时 load(plate_net.mat, net);注意在不同Matlab版本之间迁移时net结构体基本兼容但如果用了较新版本的trainNetwork或dlnetwork旧版本可能打不开。BP网络不存在这个问题这也是它长期被选作教学项目的原因之一。保存时还可以把类别列表一起存进去避免推理时重新手动定义映射关系。save(plate_net.mat, net, classes);4.2 车牌图像到输出字符串的流水线单独运行时把第2章的定位、分割、特征提取接到一起就是一条完整流水线。% recognize_plate.m function outputStr recognizePlate(imgPath) img imread(imgPath); % 手动裁剪时传入车牌区域坐标自动定位时调用locate_plate plateCrop locatePlate(img); charImgs splitChars(plateCrop); % 规范化到7个字符位置 load(plate_net.mat, net, classes); if length(charImgs) 7 warning(分割出的字符数不足7个实际为%d, length(charImgs)); end charStr ; for i 1:min(length(charImgs), 7) feat charFeature(charImgs{i}); % 新版本Matlab推荐用net(x)替代sim(net,x) out net(feat); % 计算出预测类别下标 [~, idx] max(out); charStr [charStr, classes{idx}]; %#okAGROW end outputStr charStr; end这个函数里feat是32维行向量net要求列向量输入所以用feat转置。如果省略转置Matlab会报维度不匹配错误这也是新手最常见的问题之一。max(out)取输出向量中最大值的下标代表网络认为该字符属于第几个类别。4.3 省份汉字映射与O/I冲突处理车牌第一位是省份汉字这部分不能靠随机输入图片训练通常业内把31个省份简称收集齐后单独训练。实际做项目时训练数据来源可以自己用字体渲染生成、从公共数据集抽取字符图两个途径得到的样本风格差异大需要在特征提取时统一灰度方向汉字白底黑字和黑底白字会导致前景像素统计完全反转。provices {京,津,沪,渝,冀,豫,云,辽,黑,湘,皖,鲁,新, ... 苏,浙,赣,鄂,桂,甘,晋,蒙,陕,吉,闽,贵,粤, ... 青,藏,川,宁,琼};第二个字符是发牌机关代号固定是字母后面五位才是字母数字混合。做识别映射时可以把训练集类别按“省份汉字、城市字母、车牌字母数字”三组拆分训练每个网络负责一个位置。这样O与0、I与1这两对易混淆字符可以只在第三组里出现输出端做后处理时再根据车牌编码规则去重。输出位置候选集合训练网络第1位省份汉字BP-1第2位英文字母BP-2第3-7位字母数字BP-3如果只用单一网络识别所有字符输出层类别就在汉字、字母、数字三者里混着选省份汉字里有些字形和数字接近比如“川”和“0”在低分辨率下相似很容易把错误扩散到后面的校验逻辑。拆开训练还有一个好处每个网络的任务更简单样本量需求变小类别数从50多降到30多隐层节点也可以相应缩小。实际调用时还可以用Matlab的代码生成工具把训练好的网络转成C代码嵌入到工业相机或者边缘计算盒子里不需要运行时安装完整版Matlab环境。社区里也有人讨论过让Codex这类AI编程助手直接操作Matlab任务本质上都是把网络结构配置文件和推理代码分开维护便于自动化调用。5. BP网络车牌识别的混淆矩阵调优与样本增强技巧模型跑通不代表识别率达标真正干活要面对的是车牌照片里倾斜、反光、模糊、字符断裂等各种情况。调优的关键不是盲目增加隐层节点而是让样本分布匹配真实场景。5.1 用混淆矩阵定位易混淆字符对Matlab的confusionmat可以直接输出分类结果的混淆矩阵比单看准确率有用得多。% evaluate_net.m Y_test net(X_test); [~, predIdx] max(Y_test, [], 1); predLabel classes(predIdx); % trueLabel 是测试集真实类别字符串数组 cm confusionmat(trueLabel, predLabel); % 找出错误最多的样本 errors find(predLabel ~ trueLabel); fprintf(测试集共%d个样本错%d个\n, length(trueLabel), length(errors));混淆矩阵按类别排列后通常能看到典型的聚集现象字符“0”和“D”互相混淆“8”和“B”互相混淆这说明笔画在网格特征下信息丢失太多单纯增加隐层节点或者拉长训练时间不会有明显改善。此时优先查看错例图片看是训练样本字体太单一还是分割后字符上下边缘被切掉。5.2 从混淆矩阵出发的样本增强策略如果混淆集中在“0”和“D”提示训练集没有覆盖这类笔画粗细变化。增加训练数据时不建议用网上随便下载的字体库刷大量样本更好做法是模拟真实车牌摄像头的物理变化让原始字符图做1到2像素的随机平移、3度以内随机旋转、加少量高斯噪声再把扩增样本加入训练集重新训练。% augment_char.m function augImg augmentChar(img) if size(img, 3) 3 img rgb2gray(img); end rot randn * 3; % 正负3度以内的随机旋转 img imrotate(img, rot, bilinear, crop); dx randi([-2, 2]); dy randi([-2, 2]); img imtranslate(img, [dx, dy]); % 微光噪声模拟夜间拍摄 augImg imnoise(img, gaussian, 0, 0.01); augImg augImg 128; end扩增后的样本不要全部扔进训练集应该把原始测试集保留不动避免扩增过强导致网络对噪声本身过拟合。字符旋转只需要控制在3度以内超过5度以后BP网络的特征向量会发生较大偏移识别率反而下降这时优先改进定位阶段的透视校正而不是继续增加旋转角度。5.3 什么时候该把BP换成CNNBP网络在车牌识别里的边界比较清晰字符分割准确、单字符图像干净它完全可以胜任一旦遇到整图端到端识别、夜间低照度、字符粘连严重BP就明显力不从心。现在用PyTorch实现BP回归或者CNN分类的方案很多但Matlab里的深度学习工具箱也提供图像分类器迁移训练也方便。如果只是想快速出结果先用BP把流程验证通确认瓶颈是在字符识别还是在分割再决定是否引入CNN。深度学习模型在分割和识别混合任务上更好不过代价是训练数据要多一个数量级调参难度也更大。最后一个实用技巧把识别错误的样本按字符类别重新放回训练集给每类设一个最小样本量不足的下次训练前先做增强网络对高频出现的错误会自然产生更加明显的响应抑制。这样训练三轮后通常能稳定提升2到3个百分点比在任意调整隐层节点更有效。本文还有配套的精品资源点击获取