车牌识别项目实战全记录:CNN模型设计、训练与推理优化详解
发布时间:2026/10/22 4:58:28 锦皓数字建站

做过车牌识别这个项目后我想先把话放在前面车牌识别看起来是深度学习入门必做的“老三样”之一但真正把它从论文复现变成能稳定跑在真实场景里的系统中间的坑远比你想象的多。无论是数据集怎么标、网络结构怎么选还是训练完发现晴天识别率不错、阴天就崩盘每一步都在逼你重新审视自己的方案。这篇文章我把整个实践过程拆开来讲从设计思路、技术选型、动手实现到问题排查把我踩过的坑和验证过的做法都整理出来给正在做或者准备做类似项目的同学一个能直接参考的完整路线。1. 项目整体设计与技术选型1.1 核心需求拆解车牌识别这个项目表面上是“给一张图输出车牌号”但真正动手之后才发现它其实是三个子问题叠在一起车牌在哪、车牌上的字符有哪些、这些字符按什么顺序排列。任何一个环节出错整个识别结果就废了。我当时拿到的需求场景是面向露天停车场出入口的固定摄像头抓拍图像图像分辨率在720p到1080p之间车牌区域占整个画面的比例不大存在白天强光、傍晚逆光、夜间辅助照明等不同光照条件。这个场景定义非常重要它直接决定了后续所有的技术选型。我把需求拆成了四个模块车牌定位模块从整张图中找出车牌区域输出车牌的位置框字符分割模块把定位到的车牌区域按字符切分出来字符识别模块对每个切割后的字符做分类输出对应的文字和数字后处理模块根据车牌排列规则对识别结果做校正和校验这个拆法看起来很传统甚至有点“过时”——现在很多论文都是直接端到端识别了。但我选这个路线是有原因的一是这个项目的数据量和算力有限分步方案每一步都可控、好调试二是车牌字符排列有严格的规则后处理能有效纠错三是边缘部署时分步方案的内存占用更好预估。1.2 技术路线比对先说说备选方案大概有四种第一种是传统图像处理方案通过灰度化、二值化、形态学操作找到车牌轮廓再用模板匹配识别字符。优点是简单、快缺点是鲁棒性极差换个光照、换个倾斜角度就废了。第二种是目标检测网络加分类网络用目标检测模型定位车牌区域再用分类模型识别字符。这是经典的“检测加识别”路线。第三种是端到端的车牌识别网络输入整张图直接输出字符串内部用CNN加序列建模网络处理。思路很先进但对数据量要求高而且字符长度是动态的工程实现上复杂度不低。第四种是近年比较流行的端到端检测识别一体网络把车牌检测和文字识别放进同一个网络里同时训练。效果好但调参难度会远高于分步方案。结合项目本身的定位——这是一个偏设计实践和验证的课题我选择了第二种路线的变体定位模块用轻量级目标检测网络识别模块则自己搭建一个卷积神经网络分类器。字符分割这个环节我并没有单独做传统算法而是利用检测网络的输出框信息和固定比例的先验知识来做粗分割再用形态学方法细校准。这样做的好处是每个环节都有可解释性出现问题能立刻定位到是哪一步出了问题。对于时间有限的实践项目来说这点极其重要。2. 数据集构建与预处理全流程2.1 图像采集与标注策略数据是车牌识别项目的地基。我一开始犯过一个错误直接在网上找了一些公开的车牌数据集拿过来就训练结果模型在自己的测试集上表现还行一到实际摄像头拍摄的画面上就一塌糊涂。原因很简单——数据集和真实场景的分布不一致公开数据集里的图像大多是在固定角度、固定光照下拍的而实际场景中的图像是低角度、远近变化、光照复杂的。后来我调整了策略把数据分成三个来源公开的国内车牌数据集用于预训练和基础特征学习自己在不同时间段早中晚拍摄的车辆图像大概补充了800多张通过图像增强手段对已有数据做扩充这一步后面细说标注工作我用了常用的标注工具来画车牌边界框格式转为XML文件。这里有两个建议一个是要保证边界框贴合车牌边缘不要有太多余量否则后面分割字符的时候误差会被放大另一个是每张图要记录拍摄环境和光照条件我后来调鲁棒性的时候这些标注信息帮了大忙。关于数据规模的体会这个项目我最终用了约12000张图其中正样本画面里确实有车牌的有8000多张。对于字符分类任务每个字符的样本量我确保至少在一千张以上因为字符分类网络虽然简单但相似字符比如数字1和字母I之间的区分需要足够多的样本。2.2 图像预处理流水线预处理环节我分成了两大类一类是用于训练目标检测模型的一类是用于训练字符识别模型的两类的处理方式是不一样的这里要特别注意。训练车牌定位模型时预处理步骤是统一缩放至统一尺寸保持宽高比多余部分用灰色填充做随机亮度扰动模拟早晚光照差异做随机水平平移和缩放模拟车牌在画面中的位置变化不做过度的图像增强否则会丢失边缘信息训练字符识别模型时字符图像来自于“从车牌区域中切割出来的单个字符”所以预处理重点在于归一化把字符图像缩放至统一尺寸并且要保留字符本身的笔画结构。这里我踩了一个早期的坑字符图像直接用双线性插值缩放到小尺寸结果字符笔画出现了非常严重的锯齿状网络性能大幅下降。后来改成了保持管道细节的方式即先按长边缩放再居中填充效果才恢复正常。还有一个关键细节是字符图像的二值化操作。虽然是训练CNN而不是传统分类器但适当做自适应阈值二值化反而有助于去除光照不均的影响。我一开始跳过这步结果模型对反光和阴影特别敏感后来把二值化加入流水线意外地发现鲁棒性提升了不少。3. CNN模型架构设计与参数推导3.1 网络结构选择的思路字符识别模块的CNN结构我经过了几次迭代最终的骨干结构是这样的输入层以灰度图输入避免颜色信息干扰同时减少计算量第一卷积块两个卷积层加一个最大池化层卷积核大小3x3输出通道32第二卷积块两个卷积层加一个最大池化层输出通道64第三卷积块两个卷积层加一个最大池化层输出通道128全连接层一256维加ReLU激活和随机失活全连接层二输出维度等于字符类别数这个结构放在今天看起来很常规但我选它是有理由的。首先字符类别数是有限的我这里是31个类别数字加部分字母类别数不多用不着很深很大的网络其次字符图像尺寸被统一放到了64x32这种分辨率下人眼都可以辨识大网络反而容易过拟合第三这个结构的推理延迟在GPU上不到5毫秒在CPU上也能跑到20毫秒以内对边缘部署非常友好。3.2 关键层参数的计算逻辑这里我详细说一下参数是怎么推导出来的因为很多同学在做设计的时候是“抄结构”却不清楚每个参数为什么会是这个值。输入图像64x32x1宽64高32单通道第一卷积块的计算过程第一个卷积层卷积核3x3输入通道1输出通道32stride为1padding为1。输出特征图尺寸保持64x32x32激活函数是ReLU不改变尺寸第二个卷积层卷积核3x3输入通道32输出通道32stride为1padding为1。输出仍然是64x32x32最大池化层核大小2x2stride为2。输出特征图变为32x16x32第二卷积块卷积层3x3输入32输出64padding为1。输出32x16x64第二个卷积层3x3输入64输出64。输出32x16x64最大池化2x2stride为2。输出16x8x64第三卷积块卷积层3x3输入64输出128。输出16x8x128第二个卷积层3x3输入128输出64这里我做了一次通道降维控制全连接层的参数量。输出16x8x64最大池化2x2stride为2。输出8x4x64到这里特征图是8x4x64展平后变成2048个特征这个维度对于全连接层来说完全可控。如果不做通道降维会变成4096维全连接层的参数量直接翻倍而识别精度几乎没有提升。参数量的估算可以简单算一下第一个卷积层参数量为3x3x1x32288个权重加上32个偏置整体模型参数量大概在百万级这个规模在嵌入式平台上完全跑得动。3.3 损失函数与优化器配置字符识别本质上是一个多分类问题我用的损失函数是交叉熵损失。这里有个容易被忽略的点不同类别的样本数量如果严重不均衡交叉熵会让网络偏向样本多的类别。比如数字“0”和字母“O”在车牌中都被归到“0”这一类但汉字“京”和“沪”这类省市区简称的样本量少得多如果不做处理模型对汉字的识别率会明显偏低。我的处理方式是在损失函数中加入类别权重权重与样本数量的倒数成正比。同时在数据加载时做了类别均衡采样每次迭代确保每个类别都有样本被抽到。优化器选的是带动量的随机梯度下降初始学习率0.01动量0.9权重衰减设了0.0001。很多同学看到现在流行Adam就直接用但在这个中等规模的数据集上带动量随机梯度下降的收敛效果更稳定最终精度也比Adam高了两个百分点左右。学习率调度我用了阶梯式衰减每训练20个epoch衰减为原来的0.1倍。4. 实操过程与核心环节实现4.1 车牌定位模型的训练与调优车牌定位我用了轻量级目标检测网络输入尺寸设置的是320x320。训练之前有个很重要的步骤叫做锚框聚类分析——我统计了标注框中车牌框的真实宽高分布发现车牌框的长宽比非常集中基本上都在2:1到4:1之间于是我把锚框的长宽比设为1.5、2.5、4.0三组匹配度比默认值提升了很多。训练时的数据加载我开启了在线数据增强包括随机裁剪、颜色抖动、Mosaic增强将多张图拼接成一张进行训练。Mosaic增强带来的效果最明显因为车牌在画面中的尺度有大有小Mosaic能让网络见过更多不同尺度的车牌。训练过程我自己记录了一组比较有参考价值的数据拿出来给大家对照训练迭代到第20轮时验证集上的定位精度约70%看起来好像很低原因是前期网络还在学习图像的整体特征还没有聚焦到车牌区域第40轮时定位精度提升到了88%第60轮时到达93%继续到80轮时精度上升趋缓。最终模型在验证集上的定位精度为94.5%召回率为95.2%。整个训练过程在GPU服务器上大概跑了3个小时。对于这个体量的项目三个小时的训练时长是比较合理的——太短说明模型容量不够或数据太少太长则说明模型结构有问题。4.2 字符分割的边界处理字符分割看似简单实际上线后最大的问题就出在它的边界条件上。常规做法是拿到车牌定位框后依据中国车牌字符的排列规则最左边是汉字然后是字母之后是5位数字或字母组合结合字符区域的连通域分析来切割字符。但在实际场景中我遇到这些问题铆钉干扰车牌上的铆钉会在二值化后形成噪声区域与字符粘连边框干扰车牌的白色或黄色边框跟字符连通导致分割区域变大字符粘连在低分辨率照片里相邻字符的笔画会连在一起无法区分我的解决方案是组合策略先用固定比例加投影法做预分割再对分割结果做连通域检查如果发现某个区域的宽度比例超过正常字符宽度的1.5倍就按投影谷底二次切开。这一步在项目实践里被反复验证每个车牌的不规则二值化过程不同但组合策略的容错率更高。字符分割完之后保存每个字符图块再做一次尺寸归一化处理统一到64x32分辨率转成灰度图送到字符识别网络。4.3 推理流程的完整实现整个系统的推理流程我用代码描述一下核心逻辑def plate_recognition_pipeline(image): # 第一步车牌定位 det_boxes detect_plate(image) # 返回车牌边界框列表 if not det_boxes: return None results [] for box in det_boxes: # 第二步按边界框裁剪车牌区域 plate_img crop_plate(image, box) # 第三步透视校正处理倾斜车牌 plate_img perspective_correction(plate_img) # 第四步字符分割 char_images segment_characters(plate_img) # 第五步字符识别 chars [] for char_img in char_images: char_img preprocess_char(char_img) pred cnn_model.predict(char_img) chars.append(pred) # 第六步后处理按车牌规则校验 plate_number postprocess(chars) results.append(plate_number) return results推理流程中有一个特别值得说说的地方透视校正。真实场景中摄像头不是正对着车牌的所以拍到的车牌是一个平行四边形甚至梯形。如果不做透视校正直接切割字符最左边和最右边的字符宽度会不一致识别准确率明显下降。我用的透视校正方式是先通过边缘检测找到车牌的四条边然后通过求解透视变换矩阵把车牌区域映射到正面视角。这个步骤在CPU上的耗时约为3毫秒换来的是识别准确率5个百分点的提升性价比极高。4.4 后处理规则引擎后处理是整个系统中“隐形的功臣”很多人忽略了这一步。中国车牌有固定的编码规则第一位是省份汉字简称如京、沪、粤等第二位是发牌机关代号字母如A、B、C第三到第七位是5个字符数字或字母。根据这个规则我做了一个检测器对于第一位字符限定在特定的汉字集合内过滤如果模型输出“甲”说明大概率是模型识别错了对于第二位字符限定为英文字母如果输出数字则按字母相似度映射比如1映射为I0映射为O第三到第七位需要结合特殊字符规则不能出现字母I和O因为这两个字母与数字1和0极易混淆这个后处理看起来简单实际效果非常显著。我统计过加上规则引擎之后整牌识别准确率从86%提升到了91%。这说明模型单独把每个字符都对的可能性没那么高但加上规则约束把不可能的字符组合排除掉整体准确率就有了质的飞跃。5. 常见问题与排查技巧实录5.1 模型过拟合的典型表现与应对我在训练字符识别模型时遇到了一个非常典型的过拟合信号训练集准确率在第30轮就达到了98%但验证集准确率只徘徊在82%左右而且随着训练继续验证集精度不升反降。这个问题的主要原因在于模型容量相对于数据量来说过大了。我当时的字符数据集每个类别的样本只有几百张而网络的第三卷积块输出通道有128个全连接层特征维度有2048个参数空间足够“记住”所有训练样本。针对这个情况我做了三个调整效果立竿见影数据增强强度加大对字符图像做随机旋转正负15度、随机缩放0.9到1.1倍、随机平移正则化加强随机失活概率从0.5提升到0.75权重衰减从0.0001调整为0.0005模型简化将第三卷积块的输出通道从128降为64全连接维度从2048降为512调整之后验证集准确率从84%上升到了92%训练集和验证集的差距也明显缩小了。5.2 光照变化引起的鲁棒性问题车牌识别在实际场景中最大的敌人就是光照。露天停车场早晨和傍晚的光线角度低车牌反光严重中午顶光会在车牌上方形成阴影夜间虽然有辅助照明但画面暗部噪声大。我的定位模型在强光场景下表现还可以但在逆光场景下经常漏检。排查后发现逆光时车牌区域的对比度极低边缘信息微弱检测网络的浅层特征提取不出来。我的解决思路不是换模型而是从数据层面做针对性增强。我编写了一个模拟逆光的预处理函数把图像的亮度整体降低然后在局部区域加强暗角效果同时适当降低对比度。用这批增强数据重新训练后逆光场景的漏检率降低了近四成。另外还试过用图像增强技术调整色温和对比度但效果不稳定最终保留了上述简单处理。5.3 字符识别容易混淆的几组字符字符识别中有些字符对是天然的“冤家”我在实际项目里总结出了最容易出错的几组混淆字符对是数字1和字母I、数字0和字母O、数字8和字母B、数字5和字母S、数字2和字母Z。这些相似字符在小分辨率下几乎无法从视觉上区分只能靠规则。我的处理策略分三步第一步模型训练时保留这些相似字符的类别不直接合并。 第二步后处理阶段利用车牌规则做过滤比如第二位必须是字母所以如果模型输出数字1可以映射为字母I第三到第七位不允许字母和O所以如果模型输出字母I就映射为数字1。 第三步对模型预测的置信度做统计分析如果两个相似字符的置信度差距小于0.05则根据其在车牌中的位置做规则修正。5.4 推理阶段的性能优化车牌识别系统要做到实时候用推理性能必须达标。我在主流CPU平台上做了性能测试完整的单张图像推理流程定位加识别初始耗时是310毫秒。我做了一系列优化后降到了180毫秒左右。优化的几个重点方向将图像缩放使用内存连续格式避免离散内存访问定位模型使用推理框架加速推理核函数合并字符识别模型在输入尺寸不变的情况下将浮点模型转换为半精度模型TensorRT格式后精度几乎不损失推理提速约50%对视频流场景做跳帧策略每3帧执行一次完整推理中间帧使用上一帧结果节省大量计算资源这里我特别想强调一下模型推理优化不要只看网络结构工程层面的优化往往更有效、更直接。6. 项目复盘与后续扩展思路整个项目做下来我最深刻的体会是车牌识别系统的难点不在任何单一环节而在各个环节的衔接和数据分布的把控。目标检测网络、字符识别网络单独拿出来都不难但把它们组装成一个稳定的系统中间涉及的数据处理、规则设计、性能调优才是真正耗时间的地方。我对整个项目的核心指标做了一个记录最终达到了这样的水平在测试集上车牌定位准确率94.5%字符识别准确率96.2%整牌识别准确率91.3%单张图像平均推理时间180毫秒CPU。这个结果对于一个基于卷积神经网络的实践项目来说我认为达到了预期。后续如果想继续扩展有两个方向值得尝试一是把定位和识别合并成端到端模型减少中间环节的误差累积二是做轻量化模型剪枝让系统能够在更低功耗的嵌入式设备上运行。我自己后续在做的是第二个方向因为实际部署场景中算力资源往往比想象中更紧张。最后分享一个实用的小技巧在所有中间环节定位、分割、识别都加上日志记录包括每张图的运行时间、中间结果、置信度。这个日志在调优的时候非常有用我就是靠着日志才定位到了逆光场景下的漏检问题。有了它整个系统就不再是黑盒每一个决策都有迹可循。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。