资讯动态

YOLOv8+LPRNet车牌识别两阶段方案实战与调优

发布时间:2026/10/29 22:25:04 来源:尧图企业网站定制
1. 先聊聊这套两阶段方案到底是怎么想的车牌识别这套东西说穿了就两步先把车牌从整张图里框出来再把框里的字符读出来。前者是目标检测后者是序列识别。我见过太多同学一上来就想搞个端到端网络输入一张图直接吐出一串车牌号最后卡在数据量和收敛上出不来。所以这篇就从YOLOv8 检测 LPRNet 识别这条最稳的技术路线讲起把源码里那些注释没写清楚、文档里也不会提的细节掰开揉碎讲一遍。适合谁看如果你是做毕设的学生手里有一份类似的源码但跑不通、精度上不去、答辩讲不清原理那这篇基本能覆盖你的问题。如果你是从业者想快速搭个车牌识别的原型验证业务这套方案的工程成本也是最低的。整套东西依赖 PyTorch检测侧用 YOLOv8识别侧用 LPRNet一个很轻的 CTC 序列识别网络单张 1080P 图片在普通消费级显卡上跑完整个流程大概几十毫秒显存占用也很低笔记本上都能跑得动。我先给你一个整体认知这是个分工明确的架构。检测器不需要认识字符它只负责车牌在哪识别器不需要管车牌在图的什么位置它只负责这块区域里是什么字。两个模型各自训练、各自调优、各自替换互不干扰。这个解耦带来的好处在后期的调试和迭代里会体现得非常明显。1.1 为什么是检测 识别而不是端到端一把梭很多人会问现在不是有那种直接输出字符串的模型吗确实有但你要想清楚代价。端到端方案要么依赖超大规模标注数据每张图都要标完整字符串且要求图里只有一块车牌要么依赖复杂的注意力机制和更大的骨干网络训练周期和调参难度直接翻倍。而两阶段方案里检测任务和识别任务各自都是成熟问题网上有大量预训练权重可用数据标注也更灵活——检测只需要画框识别只需要裁好的小图。更现实的一点是车牌场景存在大量图里有多辆车车牌倾斜严重光照极端的情况。端到端模型在这种复杂场景下很容易漏检或者串行输出错误而你用检测器先把每块车牌单独裁出来再逐块送进识别器相当于把复杂问题拆成了若干个简单问题。这种分而治之的思路在工程上永远是优先选项因为出错的时候你能定位到底是哪一步出了问题而不是面对一个黑盒输出干瞪眼。当然两阶段也有代价流程变长累积误差。检测框稍微偏一点识别就可能把边上的字符切掉检测框里混进了背景识别网络可能把噪点当成字符。这就是为什么后面我要花很大篇幅讲检测框后处理和裁剪扩边这两个看似不起眼但极其影响最终精度的环节。1.2 YOLOv8 与 LPRNet 的分工边界具体到这两个模型各自的职责边界要划清楚。YOLOv8 负责输出车牌的边界框坐标还可以带旋转角度它的评价指标是 mAP、召回率LPRNet 负责把一张 94×24 左右的彩色小图映射成字符序列它的评价指标是整牌准确率整串全对的比例和字符准确率。这里有个新手常犯的认知错误以为检测器 mAP 高最终车牌识别率就一定高。实际上不是。检测 mAP 0.98 的模型如果框的贴合度不好框比车牌大一圈或者小一圈识别率可能掉十几个点。我实测下来检测框与真实车牌区域的 IoU 从 0.85 提到 0.92整牌准确率能涨 5 到 8 个百分点。所以后面讲后处理的时候我会专门讲怎么根据检测框做适度外扩。LPRNet 这边它的核心是用 CTCConnectionist Temporal Classification损失来处理不定长序列。简单类比你给模型看一条横着排的字符带它逐列扫描每列猜一个字符最后把连续重复的合并、把空白符去掉就得到最终字符串。这个机制的好处是不需要事先把每个字符的位置标出来只要给整串标签就行标注成本极低。代价是解码阶段有点绕CTC 的 blank 机制和重复字符合并规则必须吃透否则训练看起来在收敛实际输出全是错的。1.3 技术栈版本选型的几个坑版本这东西坑比想象中多。我在不同机器上复现过同一份代码结果差异巨大最后定位到是 ultralytics 库版本和 PyTorch 版本不匹配导致的。给个我的经验组合PyTorch 2.x 系列配 ultralytics 8.x 系列CUDA 版本跟着显卡驱动走。LPRNet 部分用的是纯 PyTorch 手写网络反而不太挑版本但要注意torch.nn.CTCLoss在不同版本里zero_infinity参数的默认行为有过变化训练时如果出现 loss 变成 inf八成是这里的问题。另外提醒一句别迷信最新版本。毕设场景下稳定复现比尝鲜重要得多。我一般会先在虚拟环境里把依赖锁死导出 requirements 文件确保换台机器也能一把跑通。这个习惯在答辩前那几天会救你的命。2. 数据这块不搞定后面全是白费深度学习项目里数据质量决定上限模型和调参只决定你离上限有多近。车牌识别尤其如此因为它的数据分布非常长尾——不同省份简称、不同底色蓝牌、黄牌、绿牌、白牌、不同光照、不同拍摄角度任何一个维度没覆盖到模型在真实场景里就会翻车。2.1 车牌数据的来源与标注规范公开的车牌数据集有不少但普遍存在两个问题一是场景单一大多是正脸清晰拍摄二是标注格式五花八门。我的做法是公开数据集打底 自己采集补充。打底部分用来快速让模型收敛补充部分针对你实际要部署的场景。比如你的应用是停车场出入口那就去拍几百张出入口的图包含夜间补光、雨天反光、车辆斜向驶入这些情况。标注规范必须提前定死否则几千张图标完发现格式不统一返工成本极高。检测标注用 YOLO 格式class x_center y_center width height全部归一化到 0-1只有车牌一个类别所以 class 恒为 0。识别标注更简单一个文件名对应一串车牌号比如img_0001.jpg对应京A12345。注意标注的字符串必须和字符集严格对应不能一会儿写京一会儿写北京也不能把字母 O 和数字 0 混着标。这里有个非常容易被忽略的点车牌号的字符集要显式定义成一个列表训练、推理、评估三处必须引用同一份。我见过太多项目因为训练时字符集是 65 类、推理时手写了个 66 类导致索引整体错位输出全是乱码。2.2 数据增强的分寸感数据增强是低成本提升泛化的手段但车牌任务有个特殊性不能乱增强。比如随机裁剪如果裁掉了车牌的边框字符标签就对不上了模型会学歪。所以裁剪类增强要么禁用要么配合标签重算。我常用的增强组合是这样的增强类型参数建议说明随机亮度/对比度0.2~0.3模拟昼夜、逆光幅度别太大随机饱和度0.2蓝牌绿牌色彩差异适度扰动有帮助随机旋转±10 度模拟斜向驶入超过 15 度建议用旋转框随机缩放0.5~1.5模拟远近注意别缩到车牌不可辨认马赛克增强开启提升小目标检测YOLOv8 自带随机擦除小概率模拟遮挡、污损但别盖住关键字符有两点要特别注意。第一HSV 扰动不要做得太狠。蓝牌和白牌在色相上有明显区别你把色相抖动拉到 0.5模型就分不清蓝牌白牌了反而有害。第二识别侧的数据增强要和检测侧分开做因为识别网络看到的是已经裁好的车牌小图这时候做随机透视变换、轻微模糊、加噪声是合理的做随机裁剪就有风险。2.3 训练集、验证集、测试集的划分逻辑比例上常规是 8:1:1但车牌任务我建议按场景划分而不是纯随机划分。什么意思如果你把所有夜间图随机打散分到三个集合那验证集里的夜间样本和训练集高度相似评估结果会虚高。正确做法是把白天入口夜间出口地下车库这些场景整体分到不同集合这样验证集才能真正反映泛化能力。还有个小技巧验证集里至少保留 50 到 100 张困难样本包括严重倾斜、部分遮挡、字符磨损的车牌。这些样本的准确率单独统计能帮你在答辩时讲清楚模型的边界在哪里比只报一个总体准确率有说服力得多。3. YOLOv8 车牌检测从配置到收敛检测这一环说实话是整套方案里最省心的部分因为 YOLOv8 的工程化做得太好了开箱即用。但能跑和跑得好之间还是有差距尤其是车牌这种小目标、密集目标场景。3.1 模型规模怎么选YOLOv8 有 n/s/m/l/x 五个尺度。车牌的检测难度不高目标特征明显纹理规律所以我的建议是从 yolov8n 或 yolov8s 起步。我实测过在 5000 张图的数据集上yolov8n 训练 100 轮mAP50 能到 0.97 以上速度却是 yolov8m 的三倍多。毕设场景完全够用甚至有点过剩。什么情况下才需要上更大的模型两种情况一是你的图分辨率特别高比如 4K 卡口图车牌在原图里占比很小这时候大模型的特征提取能力有优势二是场景极其复杂比如密集停车场俯拍一辆车挨着一辆小模型容易框串。这两种情况下我建议先降输入分辨率试试或者用切片推理把大图切成小块分别检测往往比换大模型更划算。3.2 训练参数与损失曲线解读关键参数我给一组我反复用过的配置输入尺寸 640batch size 取你显存能扛的最大值16 或 32epochs 100 到 150初始学习率 0.01用 SGD 优化器配余弦退火最后 10 轮关闭马赛克增强。这套配置背后是有道理的马赛克增强能让小目标学得更好但训练末期必须关掉否则模型学到的目标尺度和真实推理时不一致精度会掉一截。损失曲线怎么看YOLOv8 会输出三个损失分量box_loss、cls_loss、dfl_loss。正常收敛的曲线应该是前 20 轮快速下降之后缓慢平滑下降。如果 box_loss 震荡剧烈通常是学习率太大或者 batch 太小如果 cls_loss 早早降到接近 0 而 box_loss 还在高位说明数据里正样本太少或者标注有问题。我遇到过一次 cls_loss 一直不动最后发现是标注文件的路径写错了模型其实一直在学空标签这种低级错误排查起来最费时间。提示训练前一定先用model.val()在少量图上跑一遍确认标注能被正确读入、类别数对得上。这一步能省掉你几个小时的无效训练。3.3 检测框后处理与车牌矫正模型输出的框是最小外接矩形但车牌是有角度的。如果车辆斜向驶入外接矩形里会混入大量背景直接裁出来送进识别网络字符被拉伸变形识别率必然崩。所以后处理这块必须做两件事适度外扩和透视矫正。外扩的逻辑是这样的YOLO 的框通常贴得比较紧甚至会把车牌边缘的铆钉、边框切掉一点而字符往往贴着边框。我一般按框宽高的 5% 到 8% 向外扩实测这个比例下整牌准确率提升最明显。扩太多又会引入车体背景反而干扰所以这是个需要实验确定的参数不是越大越好。矫正的话有两条路。如果你用的是普通检测头可以取框内的图像做边缘检测用霍夫变换找车牌的上下边界拟合直线再算旋转角做仿射变换。这条路实现成本低但对模糊图不友好。更省事的方案是用 YOLOv8-OBB旋转框检测直接输出带角度的框然后用四个角点做透视变换一次到位。我现在的项目基本都用后者虽然标注成本高一点要标旋转框但省掉了大量后处理调试。透视变换的目标尺寸要固定比如统一拉到 94×24这是 LPRNet 的标准输入。这里提醒一下变换时保持宽高比很重要如果硬拉伸字符的笔画比例就变了识别网络会不适应。我一般先按比例缩放再补边补边的颜色用灰色128,128,128比纯黑效果好因为纯黑容易被网络误认为字符间隙。4. LPRNet 字符识别小而快的 CTC 方案识别这一环是整套系统的灵魂。LPRNet 这个名字听起来唬人实际上结构非常简洁几个卷积块做特征提取加几个轻量的 inception 结构最后接一个全连接把通道数映射到字符集大小输出一条时间序列交给 CTC 解码。整个网络参数量只有几十万级别比一般的分类网络还小。4.1 网络结构与输入尺寸输入尺寸通常定成 94×24宽高比大概 4:1正好契合单行车牌的形态。为什么是这个尺寸因为车牌 7 个字符横向排列每个字符大致占 94/7 约 13 个像素宽这个分辨率下汉字和字母的笔画还能分辨。你要是压到 48×12七拼八凑也能认但汉字尤其是赣藏这种笔画多的准确率会明显掉。网络结构上有几个细节值得说。第一LPRNet 在中间层用了 max pooling 但控制了下采样倍数最终输出的序列长度大约是输入宽度的 1/4也就是 23 左右这个长度要大于最大车牌字符数新能源 8 位否则 CTC 没法对齐。第二最后那个全连接层输出维度是字符集大小 1多出来的 1 就是 CTC 的 blank。这个 1 千万别漏漏了训练直接报错或者永远收敛不了。4.2 字符集与标签编码字符集我一般这么构造31 个省份简称汉字 24 个大写字母去掉 I 和 O避免和 1、0 混淆 10 个数字 65 类。加上 blank 就是 66。有些实现还会加使领学警这类特殊字符看你实际场景需不需要。标签编码就是把字符串京A12345转成索引列表[0, 10, 1, 2, 3, 4, 5]假设京在字符集第 0 位。这一步看起来简单但线上事故经常出在这。我的建议是写一个encode()和一个decode()函数训练前先自测一遍随机生成字符串编码再解码看能不能还原。这个自测能在几分钟内发现字符集定义错误比训练三轮后发现强太多。注意如果你只做单行车牌模型就按单行设计如果场景里还有双层黄牌大型车要么单独训练一个双行模型要么先把双行切开成两行再识别别指望一个模型通吃结构对不上怎么调都没用。4.3 CTC Loss 与解码的实操细节CTC 的训练过程是这样的网络输出形状(T, B, C)T 是时间步B 是 batchC 是类别数。CTCLoss 需要传入 log_softmax 后的结果、目标标签、输入长度、目标长度。四个参数缺一不可输入长度一般固定填 T目标长度是每个样本标签的实际长度。这里有个坑把不同长度的标签拼成一个 batch 时需要 padding 到最长长度并把 log_probs 按时间优先排列。PyTorch 的 CTCLoss 默认是(T, N, C)格式如果你按(N, T, C)传进去loss 不会报错但会算错表现为训练 loss 一直在某个值附近抖动不下来。我第一次踩这个坑的时候调了两天最后逐行对比官方文档才发现是维度顺序的问题。解码阶段用贪心解码就行每个时间步取 argmax然后去掉连续的重复字符再去掉 blank。比如输出序列是京京-A-11-2-3-4-5-横线代表 blank去重去 blank 后就是京A12345。想要更准的话可以上 beam search但车牌场景下贪心解码已经够用收益不大速度还快。4.4 训练技巧与收敛判断LPRNet 的训练比检测简单得多一般 50 到 80 轮就能收敛。学习率用 0.001 配 Adambatch size 128 到 256因为图小显存压力不大。数据增强可以做轻微的随机模糊、亮度扰动、小角度旋转±5 度但不要做水平翻转翻转后的车牌是镜像的字符全反了这属于错误样本。怎么判断训练到位了看两个指标整牌准确率整串完全匹配的比例和字符准确率单个字符匹配的比例。正常情况下字符准确率应该稳定在 99% 以上整牌准确率 95% 左右。如果字符准确率很高但整牌准确率低说明模型在大多数字符上都对只是偶尔某个位置出错这时候去翻看错误样本往往是特定字符比如 D 和 0、B 和 8混淆可以考虑针对性地补数据。如果字符准确率本身就上不去那就是网络结构或者输入分辨率的问题先检查输入图有没有被拉伸变形。5. 端到端串联从一张图到一行车牌号两个模型各自训练好之后要把它们串起来。这一步看起来就是检测→裁剪→识别三行代码的事但实际串联的时候各种边界情况会集中爆发。5.1 推理流水线的组织完整流程大概是这么几步读图 → 预处理letterbox 到 640→ YOLOv8 推理 → NMS → 取框 → 外扩 → 透视矫正 → resize 到 94×24 → LPRNet 推理 → CTC 解码 → 车牌号 置信度。每一步我都建议写成一个独立函数中间结果可以存下来可视化调试的时候能一眼看出是哪一步出的问题。性能上这套流程单张图在 GPU 上大概 20 到 40 毫秒其中检测占大头识别几乎不耗时LPRNet 太轻了。如果要处理视频流建议把读图、预处理放到单独线程做成生产者-消费者模式GPU 利用率能提上去不少。我做过一个 25 帧的实时视频测试这套异步结构比同步处理快了三成左右。5.2 车牌号的规则校验这一步是提升看起来准的关键。国内车牌有明确的编码规则第一位是省份简称第二位是发牌机关代号字母后面五位是字母数字组合新能源是六位。你可以写个正则做初步校验把明显不合规的结果标记出来。更有价值的是校验位的逻辑约束普通车牌里字母 I 和 O 基本不出现数字 0 和字母 O 容易混淆。如果识别结果里出现了 O而该位置按规则应该是数字那就可以按置信度把它纠正成 0。这类规则纠错能把整牌准确率再提一到两个点而且逻辑清晰答辩时也容易讲。我一般会给每个字符保留置信度如果整串里有某个字符置信度特别低比如低于 0.6就在输出里标一个待确认的标记。这个设计在实际业务里很有用等于告诉使用者这个结果可能需要人工复核比盲目给一个高置信度结果要负责得多。5.3 可视化与结果输出毕设答辩的时候可视化做得好不好直接影响观感。我建议输出三样东西原图上画检测框和识别结果、矫正后的车牌小图、每个字符的置信度热力条。这三张图放一起说明力很强一眼就能看出系统是怎么工作的。代码上存结果我推荐用 JSON 或者 CSV字段包括图片名、车牌号、检测置信度、识别置信度、处理耗时。批量跑测试集的时候把这个表拉出来可以很直观地算出总体准确率、平均耗时、错误样本清单。别小看这个表我做优化的时候基本都是靠它定位问题——按耗时排序看慢在哪按置信度排序看哪些样本最不确定。6. 常见问题排查实录下面这部分是我踩过的坑和帮别人排查过的典型问题按现象、原因、解法整理成表遇到问题先对着查一遍能省不少时间。6.1 检测阶段典型问题现象可能原因排查与解法训练 loss 正常但 mAP 极低标注格式错、路径错、类别数不匹配用model.val()打印混淆矩阵检查标签文件坐标是否归一化小目标车牌大量漏检输入分辨率不足、马赛克增强过度提高 imgsz 到 960或启用切片推理密集场景框互相重叠NMS IoU 阈值不合适把 iou 从 0.7 调到 0.5 到 0.6 之间试检测框明显偏大训练集标注框本身就偏大抽 20 张图人工核对标注尺寸重新标推理速度远慢于预期图片预处理耗时、模型未转 half检查预处理是否用了 PIL 逐像素操作改 numpy6.2 识别阶段典型问题最常见的三个现象。第一个是 loss 不下降八成是 CTC 的维度顺序或者 blank 索引搞错了建议用 batch1、固定长度标签先验证一遍。第二个是训练 loss 很低但推理全错这通常是训练和推理的预处理不一致——训练时做了归一化推理时忘了做或者均值方差用错了值。第三个是特定字符总认错比如湘和鄂、D 和 0这个靠调参解决不了只能补数据或者上更强的数据增强针对这些字符做局部形变。还有一个隐蔽的问题如果你的测试集准确率比验证集高很多别高兴太早大概率是两者数据分布重复了比如同一辆车不同帧的图被分到了两个集合。这种情况在视频抽帧的数据集里特别普遍一定要按车辆 ID 或者按视频来源划分集合。6.3 联调阶段典型问题联调阶段的问题最琐碎也最考验耐心。典型的比如检测明明框对了识别结果是空字符串。这一般是裁剪区域不对要么坐标算错xyxy 和 xywh 混用要么透视变换后图是全黑的变换矩阵参数错了。我建议串起来之前先把中间的车牌小图批量存下来肉眼看一遍有没有框偏、图黑、图糊的情况一眼就能定位。另一个高频问题是批量推理时结果和单张推理不一致。这九成和 batch 处理有关比如把不同尺寸的图硬拼成一个 batch 时没做 padding或者 CTC 解码时按固定长度切分错了。建议联调时先用 batch1 跑通全流程确认逻辑正确后再加 batch出问题也容易定位。提示调试期把torch.no_grad()和model.eval()都加上不然 dropout 和 BN 层的行为会随机变化同一张图两次结果不一样排查起来很痛苦。7. 精度与速度的最后一公里优化模型跑通之后你会发现距离好用还差一段。这一段我分成两个方向速度不够就优化推理精度不够就做后处理和规则约束。7.1 推理加速的几个实操手段最直接的是模型导出。PyTorch 原生推理有额外开销导出成 ONNX 再跑速度通常能提升 20% 到 30%。导出时注意动态 batch 和动态尺寸的选择如果你只处理固定分辨率建议用静态尺寸速度更快。再进一步可以用 TensorRT 或者 ONNX Runtime 的优化版本我实测在同一个显卡上YOLOv8n 从 PyTorch 的 12 毫秒降到 5 毫秒左右提升相当可观。第二个手段是输入尺寸。640 不一定是最优解如果你的图片里车牌普遍较大降到 480 甚至 416速度能翻倍精度损失很小。这个需要用你的实际数据做对比实验别照抄别人的参数。第三个是把两个模型合并到同一个进程、同一个 GPU 上下文里避免反复创建上下文带来的开销这个在小批量推理时效果明显。7.2 精度提升的最后一公里精度这块模型本身的空间其实不大了剩下的提升基本都在后处理上。我总结了几条实测有效的做法。多尺度裁剪投票对同一个检测框取 95%、100%、105% 三种外扩比例各识别一次取置信度最高的结果能减少边框切字带来的错误。字符级置信度过滤整串置信度低于阈值的结果标记为不可信宁可漏报不误报在很多业务场景下更实用。规则纠错结合车牌编码规则做约束把不可能出现的字符组合筛掉。还有一条容易被忽略的训练集和实际场景的分布对齐。如果你的部署场景是夜间补光那就专门采一批夜间图微调识别模型冻结前面的卷积层只训练最后几层收敛快、效果立竿见影。这种小成本微调往往比调一堆超参数管用得多。7.3 部署与演示环境准备毕设演示环节稳定性比性能重要。我的建议是准备两个环境一个 GPU 环境跑正式流程一个 CPU 环境做备用。LPRNet 在 CPU 上跑单张图大概几十毫秒YOLOv8n 用 ONNX 在 CPU 上也能做到 100 毫秒级别应付演示完全够。提前把依赖打包好导出成离线可用的环境别现场装包——我见过太多演示现场因为网络问题装不上库的惨案。演示脚本也建议提前写好支持三种输入单张图片、文件夹批量、摄像头实时。摄像头那块要加个降级逻辑帧率跟不上就跳帧处理保证界面不卡死。最后留个结果导出按钮把识别记录存成表格答辩的时候可以直接展示批量测试的统计数据比嘴上说准确率很高有说服力。这套系统我从头搭过几遍最大的体会是模型训练反而是最省心的环节真正耗时间的是数据清洗、框的后处理、以及各种边界情况的联调。我个人的经验是把 60% 的精力放在数据和后处理上30% 放在模型微调上剩下 10% 留给部署这个配比基本不会跑偏。另外提醒一句源码拿到手先别急着跑花半小时把数据加载、字符集定义、CTC 维度这几处关键代码读一遍比盲目调参高效得多——很多所谓的跑不通其实就是某一处维度或者路径对不上理清楚了后面一路顺畅。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑