简介:这份资源面向具备一定MATLAB基础、希望入门深度学习图像识别的学习者,聚焦于用卷积神经网络完成车牌识别这一典型任务。项目依托MATLAB深度学习工具箱,将车牌识别拆解为定位、字符分割、数据集处理、CNN训练与模型应用等环节,并配有标签转换、训练评估等配套脚本,帮助读者理解从图像预处理到预测输出的完整链路。压缩包为zip格式,整体约56.62MB,内含源码、数据集与说明文档,文件类型以m脚本、mat数据文件和文档为主,分别承担算法实现、数据存储与教程讲解等用途。目前已有520人学习下载,适合作为课程设计或自学练手项目。通过复现该案例,读者可掌握CNN网络构建、参数设置与训练流程,并借助文档与教程视频理清车牌识别各模块的衔接方式,积累图像识别项目的实战经验。
1. 从一张倾斜的车牌照片说起:matlab卷积神经网络车牌识别到底难在哪
地下车库出口的相机拍下一张车牌,角度偏了 15 度,光线一半在阴影里,字符边缘还带着运动模糊。传统做法是灰度化、边缘检测、形态学闭运算、连通域筛选,再套模板匹配或 SVM 分类。这套流程我调过不下十次,白天晴天准确率能到 95%,一到傍晚逆光或者车牌有泥点,字符分割就开始翻车,一个字符切歪,后面全错。问题不在某一步参数没调好,而在于「分割」这个动作本身把误差累积放大了。
matlab卷积神经网络车牌识别要解决的就是这个累积误差。它把字符识别从「先切再认」变成「整块区域直接认」,用 CNN 在特征层面自己学出对倾斜、模糊、光照变化的容忍度。适合谁做?手里有几百到几千张车牌图、会一点 MATLAB 基础、不想从零搭 C++ 推理框架的人。MATLAB 的 Deep Learning Toolbox 把数据增强、网络搭建、训练、量化部署串成一条链,配合 Image Processing Toolbox 做预处理,整个闭环能在一台带中端显卡的机器上跑完。这一篇就按我实际做过的顺序,从数据准备讲到训练参数、避坑和验证,把能抄的代码和会踩的坑都摆出来。
2. 数据从哪来、怎么标:车牌数据集的构建与增强策略
2.1 车牌数据的两个来源和标注格式选择
做车牌识别,数据永远是第一道坎。常见做法有两类:一是公开数据集,国内常用的有 CCPD(Chinese City Parking Dataset)系列,包含蓝牌、绿牌、黄牌多种场景,标注信息里带车牌框和字符;二是自己用手机或监控截图攒,几百张起步就能跑通流程。我一般会两者混用,公开数据集打底,自己拍的补场景短板,比如地库弱光、雨天反光。
标注格式上,如果你只做「整牌识别」(不定位、直接认整张裁剪好的车牌图),那标注就是「图片文件名 → 车牌字符串」,一个文件夹放图,一个 txt 或 csv 存标签,最省事。如果你要做「检测 + 识别」两阶段,那还得有车牌框的坐标,格式用 [x, y, w, h] 或 [x1, y1, x2, y2] 都行,关键是全流程统一。新手建议先做整牌识别,把 CNN 分类这条链跑通,再回头加检测。
这里有个容易忽略的点:车牌字符集是有限的。蓝牌常见 65 个字符左右(省份汉字 + 字母 + 数字),你可以先统计一遍数据集里出现过的所有字符,建一个字符到索引的映射表,存成 mat 文件。后面做标签编码、解码、算准确率都靠它。别小看这一步,我见过有人训练完发现验证集里有个字符训练集压根没有,模型只能瞎猜。
2.2 用 MATLAB 做批量预处理和在线增强
预处理的核心目标是把输入尺寸统一、把对比度拉到一个稳定区间。车牌图原始尺寸五花八门,CNN 输入层要求固定大小,常见选 32×128 或 48×192(高×宽,车牌是扁长的)。直接 resize 会拉伸变形,更好的做法是先按比例缩放再居中 padding。下面这段是我常用的批量预处理脚本:
% 批量读取车牌图,统一到 48x192,灰度化并做直方图均衡 imgDir = 'plates_raw'; outDir = 'plates_proc'; if ~exist(outDir, 'dir'); mkdir(outDir); end files = dir(fullfile(imgDir, '*.jpg')); targetSize = [48, 192]; for i = 1:numel(files) img = imread(fullfile(imgDir, files(i).name)); if size(img, 3) == 3 img = rgb2gray(img); % 车牌识别灰度足够,省一半计算量 end img = imresize(img, targetSize); % 简单场景直接 resize,形变可接受 img = adapthisteq(img); % 自适应直方图均衡,抗光照不均 imwrite(img, fullfile(outDir, files(i).name)); end逻辑说明:rgb2gray把三通道压成单通道,车牌识别对颜色依赖低(绿牌黄牌靠颜色区分的话另说),灰度能显著降显存和训练时间。imresize直接缩放到目标尺寸,简单但有形变;如果车牌长宽比差异大,改成先算缩放比再 pad。adapthisteq是自适应直方图均衡,比全局histeq更适合局部光照不均的车牌,参数'ClipLimit'默认 0.01,光照特别极端的场景可以调到 0.02~0.03,但调太高会放大噪声。
在线增强比离线增强更划算,因为每个 epoch 看到的图都不一样。MATLAB 里用imageDataAugmenter配置:
aug = imageDataAugmenter( ... 'RandRotation', [-8, 8], ... % 车牌倾斜一般不超过 8 度 'RandXTranslation', [-5, 5], ... % 像素级平移 'RandYTranslation', [-3, 3], ... 'RandScale', [0.95, 1.05], ... % 轻微缩放 'RandXReflection', false); % 车牌不能水平翻转!翻转后字符无意义参数说明:RandRotation范围别开太大,车牌本身倾斜有限,开到 ±15 度以上模型会学到无意义的旋转不变性,反而降低正常样本精度。RandXReflection必须设 false,这是血泪经验——有人图省事开了水平翻转,训练 loss 降得挺好看,实测时把「京」认成镜像的怪字符。垂直翻转同理,车牌不会倒着出现。
提示:增强只对训练集做,验证集和测试集保持原始分布,否则你评估出来的准确率是虚高的。
3. 网络怎么搭:从 LeNet 改到适合车牌的 CNN 结构
3.1 为什么车牌识别不需要 ResNet 那么深
车牌识别的本质是「短字符串分类」,字符集几十个,字符间没有复杂语义关系,不像 ImageNet 一千类那样需要极深的网络提取抽象特征。我试过 ResNet-50 迁移学习,准确率比自建浅层网络高不到 1 个百分点,但训练时间翻了三倍,显存占用翻倍,部署时推理延迟也上去了。对绝大多数车牌场景,5~8 层卷积加 2~3 层全连接就够了。
结构设计上有几个针对车牌的点:第一,车牌是扁长的,卷积核用 3×3 但池化窗口在宽度方向可以更激进,因为字符在水平方向排列,垂直方向信息密度低。第二,全连接层之前用全局平均池化替代 flatten,能大幅减少参数量,降低过拟合。第三,输出层神经元数等于字符集大小,用 softmax 做多分类。
3.2 用 MATLAB 搭一个可训练的车牌 CNN
下面是我实际用过的网络定义,输入 48×192 灰度图,输出字符类别数:
function lgraph = buildPlateCNN(numClasses) layers = [ imageInputLayer([48 192 1], 'Name', 'input', 'Normalization', 'zscore') convolution2dLayer(3, 32, 'Padding', 'same', 'Name', 'conv1') batchNormalizationLayer('Name', 'bn1') reluLayer('Name', 'relu1') maxPooling2dLayer([2 2], 'Stride', [2 2], 'Name', 'pool1') convolution2dLayer(3, 64, 'Padding', 'same', 'Name', 'conv2') batchNormalizationLayer('Name', 'bn2') reluLayer('Name', 'relu2') maxPooling2dLayer([2 2], 'Stride', [2 2], 'Name', 'pool2') convolution2dLayer(3, 128, 'Padding', 'same', 'Name', 'conv3') batchNormalizationLayer('Name', 'bn3') reluLayer('Name', 'relu3') convolution2dLayer(3, 128, 'Padding', 'same', 'Name', 'conv4') batchNormalizationLayer('Name', 'bn4') reluLayer('Name', 'relu4') maxPooling2dLayer([3 3], 'Stride', [3 3], 'Name', 'pool3') dropoutLayer(0.5, 'Name', 'drop') fullyConnectedLayer(256, 'Name', 'fc1') reluLayer('Name', 'relu5') fullyConnectedLayer(numClasses, 'Name', 'fc2') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'output') ]; lgraph = layerGraph(layers); end逻辑说明:imageInputLayer的Normalization设成zscore,让网络自己按通道做零均值单位方差,省得你手动归一化。每个卷积后接batchNormalizationLayer是标配,能加速收敛、缓解梯度问题,但要注意 batch size 太小时 BN 统计量不稳,建议 batch 不低于 32。dropoutLayer(0.5)放在全连接前,是防过拟合最有效的一招,如果训练集小于 2000 张,可以加到 0.6。
参数怎么改:卷积核数量从 32 起步,数据量翻倍可以加到 64、128。池化窗口[3 3]那层是为了把 48×192 快速降到 6×24 再降,如果你的输入尺寸不同,要保证进全连接前特征图尺寸算得对,否则会报维度错误。全连接 256 是个经验值,字符集大(比如上百类)可以加到 512。
3.3 训练参数设置与迁移学习取舍
训练用trainingOptions配置,下面这套是我在单卡 8G 显存上跑得比较稳的组合:
options = trainingOptions('adam', ... 'InitialLearnRate', 1e-3, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.5, ... 'LearnRateDropPeriod', 10, ... 'MaxEpochs', 60, ... 'MiniBatchSize', 64, ... 'Shuffle', 'every-epoch', ... 'ValidationData', augimdsVal, ... 'ValidationFrequency', 30, ... 'L2Regularization', 1e-4, ... 'ExecutionEnvironment', 'auto', ... 'Plots', 'training-progress');参数说明:adam优化器对大多数 CNN 都够用,初始学习率 1e-3 是安全起点,如果 loss 震荡就降到 5e-4。piecewise每 10 个 epoch 降一半,让后期收敛更细。MiniBatchSize64 是显存和稳定性的平衡点,显存不够降到 32,但要相应把学习率也降一点。L2Regularization1e-4 是轻量正则,配合 dropout 用。ExecutionEnvironment设auto会自动选 GPU,没 GPU 就 CPU 慢慢跑,但 CPU 训这个网络一个 epoch 要几分钟,不推荐。
迁移学习要不要用?如果你数据只有几百张,可以拿预训练的 SqueezeNet 或 MobileNet 改输入层和输出层来微调,收敛快很多。但车牌是灰度、扁长、字符结构特殊,ImageNet 预训练特征的迁移收益有限,我实测下来自建网络加增强,1000 张图就能到 97% 以上验证准确率,没必要非上迁移。
4. 训练完怎么用:推理、后处理与准确率验证
4.1 单张和批量推理的代码写法
训练完得到trainedNet,推理时要注意输入预处理必须和训练时完全一致,否则精度断崖式下跌。这是最常见的翻车点之一:
function plateStr = recognizePlate(trainedNet, charMap, imgPath) img = imread(imgPath); if size(img, 3) == 3 img = rgb2gray(img); end img = imresize(img, [48 192]); img = adapthisteq(img); % 关键:网络输入是 4D,单张图要补成 [H W C N] input = reshape(img, [48 192 1 1]); input = single(input) / 255; % 若训练时用了 zscore 层,这里可省 scores = predict(trainedNet, input); [~, idx] = max(scores); plateStr = charMap(idx); end逻辑说明:reshape那一步是新手最容易漏的,predict要求输入是 4 维[H W C N],单张图 N=1。single转单精度是因为网络权重是 single,double 输入会报类型不匹配。如果训练时imageInputLayer设了zscore,推理时不用手动除 255,网络内部会处理;如果没设,就要手动归一化到 [0,1]。这两者必须和训练配置对齐。
批量推理用minibatchpredict或循环predict,几百张图循环也就几秒。注意classify和predict的区别:classify直接返回类别标签,predict返回各类概率,做置信度过滤时用predict更灵活。
4.2 准确率怎么算才不骗自己
准确率不能只看整体数字。车牌识别里,一个字符错整牌就错,所以「整牌准确率」和「字符准确率」要分开算。整牌准确率是预测字符串和真值完全一致的比例,字符准确率是逐字符比对。我一般两个都报,整牌准确率才是业务指标。
验证集要独立于训练集和增强,最好按场景分层:白天、夜间、倾斜、模糊各留一部分。如果验证集全是清晰正脸图,准确率 99% 也没意义。混淆矩阵用confusionchart画出来,重点看哪些字符互相混淆,比如「0」和「O」、「1」和「I」、「8」和「B」,这些是车牌字符集的天然难点,必要时针对性补样本。
注意:测试集准确率比验证集低 2~3 个百分点是正常的,如果低太多,说明验证集泄漏或者增强过度导致分布偏移。
5. 避坑与排查:车牌 CNN 训练里最容易翻车的 5 个地方
现象一:训练 loss 一直不降,准确率卡在随机水平。原因通常是标签编码错了,比如字符映射表索引从 0 开始但 MATLAB 分类标签要求从 1 开始,或者图片和标签没对齐。解决:先拿 20 张图做小规模过拟合测试,如果网络连 20 张都记不住,一定是数据管道问题,不是网络问题。
现象二:训练准确率 99%,测试准确率 60%。典型过拟合。原因可能是数据量太小、增强不够、dropout 没开或太小。解决:先加增强(旋转、平移、亮度抖动),再把 dropout 提到 0.5~0.6,加 L2 正则,还不行就减网络容量,砍掉一层卷积或全连接。
现象三:推理时准确率比验证时低一大截。原因几乎都是预处理不一致,训练用了zscore推理没归一化,或者 resize 的插值方法不同。解决:把预处理写成一个函数,训练和推理都调它,杜绝两套代码。
现象四:某些字符永远认错。原因是训练集里这些字符样本太少,类别不平衡。解决:统计每个字符的样本数,对少的字符做定向增强或复制采样,MATLAB 里可以用oversample思路手动构造平衡的 datastore。
现象五:GPU 显存溢出,batch 调小后 BN 层效果变差。原因:batch 太小 BN 统计不准。解决:改用groupNormalizationLayer替代 BN,或者用梯度累积模拟大 batch。MATLAB 里梯度累积要自己写训练循环,稍麻烦,优先还是想办法把 batch 保持在 32 以上。
6. 把整牌识别拆成字符序列:CTC 思路与一个可验证的进阶技巧
整牌识别有个天花板:车牌长度不固定,蓝牌 7 位、新能源 8 位,纯分类网络只能处理固定长度。要突破这个限制,得引入序列建模。MATLAB 的 Deep Learning Toolbox 支持sequenceInputLayer和 CTC 损失,可以搭一个 CRNN(CNN + RNN + CTC)结构,让网络直接输出变长字符序列,不用预先分割。
思路是这样:CNN 部分照旧提特征,但输出的是宽度方向的特征序列,每一列对应原图一个水平位置;接一层双向 LSTM 学字符间的上下文;最后接ctcLayer做损失。这样网络自己学会对齐,输入任意长度的车牌图都能输出对应长度的字符串。下面是一个简化的 CRNN 骨架:
layers = [ imageInputLayer([48 192 1], 'Name', 'input') convolution2dLayer(3, 32, 'Padding', 'same') reluLayer maxPooling2dLayer([2 2], 'Stride', [2 2]) % 48x192 -> 24x96 convolution2dLayer(3, 64, 'Padding', 'same') reluLayer maxPooling2dLayer([2 2], 'Stride', [2 2]) % 24x96 -> 12x48 convolution2dLayer(3, 128, 'Padding', 'same') reluLayer maxPooling2dLayer([2 1], 'Stride', [2 1]) % 只降高不降宽:12x48 -> 6x48 % 把 [6 x 48 x 128] 转成序列 [48 x 768] functionLayer(@(x) permute(squeeze(x), [2 1 3]), 'Formattable', true) % 实际工程里用 reshape + permute 组合,这里示意 bilstmLayer(128, 'OutputMode', 'sequence') fullyConnectedLayer(numClasses + 1) % +1 是 CTC blank softmaxLayer ctcLayer('Name', 'ctc') ];逻辑说明:关键在池化窗口[2 1],高度方向继续降,宽度方向保留,这样特征序列的长度才够表达 7~8 个字符。bilstmLayer学上下文,比如「京」后面大概率跟字母。numClasses + 1里的 1 是 CTC 的 blank 标签,用于处理字符间空白。CTC 训练比普通分类慢,收敛也慢,但换来的是变长识别能力,值得。
验证方法上,我习惯做一个「最小可复现测试」:拿 50 张训练集里没出现过的车牌图,人工核对每张的预测结果,把错的挑出来单独看。如果错误集中在某类场景(比如全是夜间),说明数据分布有偏;如果错误随机分布,说明模型容量或训练还不够。这个习惯帮我省了很多盲目调参的时间。
最后说个我自己的教训:别一上来就追求 99% 准确率,先把「数据管道 → 训练 → 推理 → 评估」这条链跑通,哪怕准确率只有 80%。链路通了,后面每一步优化都有反馈;链路不通,调什么参数都是玄学。我早期就是卡在预处理不一致上折腾了两天,后来把预处理写成统一函数,问题立刻消失。希望帮到你。
本文还有配套的精品资源,点击获取