尧图精选

YOLOv8+LPRNet车牌识别系统:从训练到部署的完整实战指南

🕒 发布时间:2026/10/1 4:18:00 📁 来源:尧图网络
简介这是一套面向计算机、电子信息等专业学生与算法初学者的车牌识别实战项目源码采用 YOLOv8 完成车牌检测、LPRNet 负责字符识别可直接用于课程设计、期末大作业或毕业设计参考。压缩包共 60 个文件约 36.15MB包含 13 个 Python 脚本、3 个 pt 与 1 个 pth 模型权重、3 个 Vue 前端文件及 json、yaml、md 等配置与说明文档覆盖数据生成、标注、训练、推理到 Flask 可视化界面的完整链路。资源已吸引 1171 人学习下载目录中划分了 YOLOv8 训练、LPRNet 识别、数据集处理与 Web 展示等模块读者可据此理解两阶段车牌识别的工程组织方式并在此基础上调试、替换数据集或扩展功能。1. 车牌识别系统从零落地YOLOv8 加 LPRNet 这套组合拳到底能不能打停车场出入口的相机拍到一张车头照你得在几百毫秒内告诉闸机“这辆车是谁”这就是车牌识别系统要干的活。基于 YOLOv8 和 LPRNet 的车牌识别系统 python 源码加模型这套方案本质是把任务拆成两段YOLOv8 负责在整张图里把车牌框出来LPRNet 负责把框出来的车牌小图逐字符读出来。它适合谁适合手上有 GPU 或 RK3588 这类边缘板、想自己训一套能识别蓝牌绿牌黄牌的团队也适合刚学完 YOLOv8 训练自己数据集、想找个完整下游任务练手的开发者。这套组合不是玄学检测和识别解耦之后每一段都能单独调、单独换工程上比端到端大模型好维护得多。下面我按“先跑通、再调参、最后避坑”的顺序把这条链路拆开讲清楚。2. 为什么是 YOLOv8 做检测、LPRNet 做识别选型理由与数据流拆解2.1 检测和识别为什么要拆成两阶段车牌识别看着是一个任务实际包含两个性质完全不同的子问题。检测是“在哪里”输出的是坐标框对位置敏感、对内容不敏感识别是“是什么”输出的是字符序列对内容敏感、对位置不敏感。把这两件事塞进一个网络里训练时梯度会互相拉扯改一个字符的损失可能把框的位置带偏。拆成两阶段之后YOLOv8 只管框LPRNet 只管读各自的损失函数、数据增强、评估指标都能独立设计。从工程角度看拆开还有个隐性好处车牌检测模型可以复用。你今天做蓝牌明天要加新能源绿牌检测模型基本不用大改只需要在识别阶段扩充字符集重新训 LPRNet。反过来如果现场相机角度变了导致框不准你也只需要重训检测识别模型不动。这种可替换性是端到端方案给不了的。常见做法是检测输出车牌四点的透视变换后的小图再送进识别网络。这里有个细节YOLOv8 默认输出的是水平矩形框但车牌在画面里往往有倾斜直接用水平框裁剪会带进大量背景识别率会掉。所以实际落地时要么在 YOLOv8 里加关键点分支输出四角点要么用检测框做一次粗略裁剪后再做仿射校正。源码包里如果只给了水平框版本倾斜场景就是第一个要自己补的地方。2.2 YOLOv8 检测分支的输入输出约定YOLOv8 的检测头输出格式是[batch, 4nc, num_anchors]其中 4 是cx, cy, w, hnc 是类别数。车牌检测通常只有 1 类所以输出通道是 5。训练时标签用 YOLO 格式的 txt每行class cx cy w h全部归一化到 0 到 1。这里最容易翻车的是归一化基准cx、cy 除以图宽图高w、h 也除以图宽图高不是除以 640。很多人第一次训自己数据集框全飘到左上角就是这一步搞错了。推理阶段YOLOv8 会先做 letterbox 把原图缩放到 640×640 并补灰边输出框再映射回原图坐标。映射时要减掉 padding 再除以缩放比例顺序反了框就会整体偏移。源码里如果直接调model.predict()这些它帮你做了但如果你要把检测和识别串成流水线最好自己写后处理把 letterbox 参数记下来不然后面裁剪车牌小图会对不上。2.3 LPRNet 的序列建模思路LPRNet 是一个轻量级的字符识别网络结构上是 CNN 加 CTC 损失。它不需要先做字符分割直接对整张车牌图输出字符序列这对中文车牌特别友好因为中文省份简称和后面的字母数字混排分割很容易切错。LPRNet 的主干是几个卷积块加最大池化最后接一个全连接把特征图压成序列再用 CTC 解码。CTC 的关键是引入空白符让网络自己学对齐。训练时标签是字符序列比如“京A12345”网络输出的是每个时间步的字符概率CTC 负责把重复字符和空白符合并成最终序列。这里有个参数必须注意字符集里要包含空白符通常放在索引 0。如果字符集顺序和训练时不一致推理结果会全乱这是血泪经验。LPRNet 的输入尺寸一般是 94×24 或 96×32宽高比接近真实车牌。如果你的检测框裁剪出来的图比例差太多要先 resize 再送进去否则卷积核感受野对不上识别率会明显下降。源码包里如果给了预训练模型先拿它跑通推理再决定要不要自己重训。3. 把源码跑起来环境配置、数据准备和训练命令3.1 环境搭建CPU 版和 GPU 版怎么选先明确一点训练必须用 GPU推理可以 CPU。如果你只是想把源码跑通看效果CPU 版够用如果要训自己的数据集至少一张 8G 显存的卡GTX1660Ti 这个级别跑 YOLOv8n 加 LPRNet 是够的。Ubuntu 20.04 上搭 YOLOv8 环境最稳的方式是 conda 建虚拟环境别在系统 Python 里装。conda create -n plate python3.9 -y conda activate plate # 装 PyTorchCPU 版用下面这行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # GPU 版按你的 CUDA 版本去 PyTorch 官网找对应命令别直接 pip install torch pip install ultralytics opencv-python numpy装完先验证python -c from ultralytics import YOLO; print(ok)如果报libGL相关错误是 opencv 缺系统库apt install libgl1解决。这个坑在服务器上很常见因为服务器默认不装图形库。3.2 车牌检测数据集准备标注、转换和划分YOLOv8 训练需要 YOLO 格式的标签。如果你用 labelme 标的是 JSON要转成 txt。转换脚本核心逻辑是读 JSON 里的shapes取矩形框的左上右下坐标算中心点和宽高再除以图宽图高。import json, os def labelme_to_yolo(json_path, out_path, img_w, img_h): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: if shape[shape_type] ! rectangle: continue (x1, y1), (x2, y2) shape[points] cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w abs(x2 - x1) / img_w h abs(y2 - y1) / img_h lines.append(f0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))参数说明img_w、img_h是原图尺寸必须从对应图片读出来不能写死 640。shape_type过滤掉非矩形标注避免多边形混进来。类别 id 这里写死 0因为车牌只有一类。数据集划分按 8:1:1 分 train、val、test图片和标签同名放对应目录。然后写一个plate.yamlpath: /data/plate train: images/train val: images/val nc: 1 names: [plate]path是数据集根目录train和val是相对路径。这个文件放哪都行训练时用绝对路径指过去。3.3 YOLOv8 训练命令和关键参数yolo detect train dataplate.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0modelyolov8n.pt是拿预训练权重做迁移学习别从零训收敛慢还容易过拟合。imgsz640是输入尺寸车牌在画面里占比小的话可以提到 960但显存翻倍。batch16根据显存调8G 卡跑 640 一般能到 16。device0指定第一张 GPUCPU 训练去掉这个参数但会很慢。训练完看runs/detect/train/weights/best.pt这是检测模型。验证时重点看 mAP50 和 mAP50-95车牌检测 mAP50 到 0.95 以上才算能用。如果 recall 低说明漏检多检查标注是不是有漏标如果 precision 低说明误检多看看是不是把相似纹理误判成车牌。3.4 LPRNet 训练字符集定义和 CTC 损失LPRNet 训练数据是裁剪好的车牌小图加对应字符标签。字符集要覆盖你的业务场景蓝牌是省份简称加字母数字绿牌多一位黄牌是字母数字。字符集定义成一个列表索引 0 留给 CTC 空白符。CHARS [blank, 京, 沪, 粤, A, B, C, 1, 2, 3] # 实际要补全所有省份简称和字母数字 char_to_idx {c: i for i, c in enumerate(CHARS)}训练时标签转成索引序列损失用nn.CTCLoss()。注意 CTC 要求输入序列长度大于等于标签长度所以车牌图不能裁得太窄。LPRNet 输入 94×24 时时间步大概是 24 左右足够放 7 到 8 个字符。import torch.nn as nn ctc_loss nn.CTCLoss(blank0, zero_infinityTrue) # log_probs 形状 [T, N, C]targets 是拼接后的索引input_lengths 和 target_lengths 是各自长度 loss ctc_loss(log_probs, targets, input_lengths, target_lengths)blank0对应字符集里的空白符索引。zero_infinityTrue防止某些样本算不出对齐导致 loss 为 inf这个参数在训练不稳定时很有用。4. 检测和识别怎么串成流水线裁剪、校正和批量推理4.1 从检测框到识别输入的裁剪逻辑检测输出框之后不能直接img[y1:y2, x1:x2]就送识别。原因有两个一是框可能带背景二是车牌有倾斜。常见做法是先按框裁剪再做一次透视变换把车牌摆正。如果检测模型只给了水平框可以用框的宽高比判断倾斜程度倾斜大的话用 OpenCV 的minAreaRect在框内找车牌轮廓再校正。import cv2 import numpy as np def crop_plate(img, box): x1, y1, x2, y2 map(int, box) # 外扩一点避免框太紧切掉字符边缘 pad 2 x1 max(0, x1 - pad) y1 max(0, y1 - pad) x2 min(img.shape[1], x2 pad) y2 min(img.shape[0], y2 pad) crop img[y1:y2, x1:x2] # resize 到 LPRNet 输入尺寸 crop cv2.resize(crop, (94, 24)) return croppad是外扩像素车牌字符贴边时外扩能救回来。resize 直接用cv2.resize会拉伸变形如果宽高比差太多建议先按比例缩放再补边保持字符不变形。4.2 批量推理和耗时优化实际部署时一帧图可能有多辆车检测输出多个框识别要批量跑。把裁剪出的小图堆成一个 batch 送 LPRNet比逐张跑快很多。GPU 上 batch 16 和 batch 1 的吞吐能差好几倍。batch np.stack(crops, axis0) # [N, 24, 94, 3] batch torch.from_numpy(batch).permute(0, 3, 1, 2).float() / 255.0 with torch.no_grad(): logits lprnet(batch) preds logits.argmax(dim2) # 贪心解码实际用 CTC 解码更稳permute把 HWC 转成 CHW/255.0归一化。贪心解码对简单场景够用但字符重复时容易出错比如“AA”可能被解成一个“A”。生产环境建议用 CTC beam search 解码源码包里如果只给了贪心解码这是第二个要自己补的地方。4.3 RK3588 等边缘板部署的注意点RK3588 部署 YOLOv8 一般走 RKNN 工具链把 pt 转 onnx 再转 rknn。转换时要注意算子支持情况YOLOv8 的某些激活函数在旧版 RKNN 上不支持需要替换。LPRNet 结构简单转换一般没问题但 CTC 解码要在 CPU 上做因为板端 NPU 不直接支持变长序列输出。量化是另一个坑。INT8 量化能提速但车牌字符细量化误差可能导致识别率掉几个点。建议检测用 INT8识别用 FP16或者识别也量化但用真实车牌图做校准集别用随机图。5. 避坑与排查车牌识别系统落地时最容易翻车的五件事5.1 检测框准但识别全错字符集顺序不一致现象检测框位置很准但识别出来的字符全是乱码或者固定几个字符。原因训练 LPRNet 时的字符集顺序和推理时不一致索引对不上。解决把训练时的字符集列表存成 json推理时加载同一个文件别手写两遍。这个坑我踩过排查了一下午才发现是省份简称的顺序换了。5.2 训练 loss 不降CTC 输入长度不够现象LPRNet 训练 loss 卡在某个值不动或者直接报input_lengths相关的错。原因车牌图裁得太窄卷积下采样后时间步少于标签长度CTC 无法对齐。解决检查输入尺寸94×24 对应的时间步要大于等于最大车牌字符数。如果车牌有 8 个字符时间步至少 16。可以加大输入宽度或者减少池化层。5.3 推理速度慢没做 batch 也没用半精度现象单张图推理要几百毫秒达不到实时。原因逐张跑且用 FP32。解决检测和识别都做 batchGPU 上用model.half()转半精度速度能翻倍。边缘板上确认 NPU 是否真正启用有些转换后的模型实际跑在 CPU 上速度差十倍。5.4 夜间和逆光识别率暴跌训练数据没覆盖现象白天识别率 98%晚上掉到 70%。原因训练集全是白天图模型没见过夜间车牌的反光和低对比度。解决采集夜间、逆光、雨天的图补充训练集检测阶段可以加一个简单的图像增强比如 CLAHE 做对比度均衡。别指望模型自己泛化到没见过的光照。5.5 新能源绿牌识别错位字符集没加新字符现象蓝牌识别正常绿牌最后一位总是错。原因绿牌比蓝牌多一位且字母数字组合规则不同字符集没覆盖或者 CTC 时间步不够。解决把绿牌样本单独统计确认字符集包含所有出现的字符必要时加宽 LPRNet 输入让时间步多几个。绿牌和蓝牌可以共用一个模型但训练集里绿牌比例不能太低。6. 把识别率从 95% 推到 99%几个我实际用过的调优技巧第一个技巧是检测阶段加关键点回归。YOLOv8 的 pose 分支可以输出车牌四角点用四角点做透视变换比水平框裁剪干净得多识别率能直接涨一两个点。代价是标注要从矩形框改成四点标注工作量增加但值得。如果不想重标可以用水平框内的边缘检测找车牌轮廓再拟合四边形效果差一些但不用改标注。第二个技巧是 LPRNet 输入做灰度化加直方图均衡。车牌识别对颜色不敏感灰度图能减少计算量直方图均衡能提升低对比度场景的字符清晰度。实测在夜间图上这一步能让识别率涨三到五个点。注意训练时也要用同样的预处理别训练用彩色推理用灰度。第三个技巧是 CTC 解码用 beam search 替代贪心。贪心解码在字符重复时容易合并错beam search 保留多个候选路径最后选概率最高的。beam width 设 5 到 10 就够再大收益递减。这个改动在代码上就是换一个解码函数但效果立竿见影尤其是车牌里有连续相同字符时。第四个技巧是难例挖掘。跑一遍验证集把识别错的图挑出来人工看是检测框不准还是字符看不清。检测框不准的重新标字符看不清的补充类似样本进训练集。迭代两三轮识别率能从 95% 推到 99%。这个过程没有捷径就是体力活但比调网络结构管用。最后一个习惯每次改完参数固定用同一批测试图跑一遍记录识别率和耗时。别凭感觉说“好像变好了”数据不会骗人。我一般会留一个benchmark.py加载模型跑一百张图输出准确率和平均耗时改完就跑一次。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →