简介本资源为基于 YOLOv8 与 LPRNet 的车牌识别系统完整项目包面向计算机、人工智能、电子信息等相关专业学生及企业开发者可用于毕业设计、课程设计、大作业或初期项目立项演示兼顾小白实战练习与进阶学习借鉴。压缩包共 60 个文件约 36.15MB包含 13 个 Python 源码文件、3 个 PyTorch 模型权重pt/pth、22 张 jpg 图片样本以及 Vue 前端页面、JSON 配置、YAML 训练参数和 Markdown 说明文档等覆盖数据生成、标注、训练与推理全流程。项目采用 Flask 搭建后端服务集成 YOLOv8 检测与 LPRNet 字符识别配套数据集划分、批量图像缩放、标注生成等工具脚本目录结构清晰便于按模块阅读与二次开发。目前已有 1146 人学习下载代码经测试可正常运行适合作为车牌识别方向的入门与实战参考。1. 车牌识别系统从零落地YOLOv8 加 LPRNet 这套组合拳到底能不能打车牌识别系统在停车场、园区闸机、高速卡口这些场景里几乎是刚需但真正自己动手搭一套能跑通的很多人卡在第一步检测和识别到底怎么串。YOLOv8 负责把车牌从整张图里框出来LPRNet 负责把框里的字符读出来这套组合是目前 Python 源码层面最容易复现的方案之一。你拿到一份「YOLOv8 LPRNet 车牌识别系统 python 源码 模型」的压缩包里面通常包含检测权重、识别权重、推理脚本和少量测试图。这篇文章不假设你手里有某份特定源码而是按这个技术方向最常见的工程做法把环境搭建、数据准备、检测训练、识别训练、推理串联、部署优化整条链路讲清楚。适合有 Python 基础、想跑通一个完整 CV 项目但不想从论文啃起的工程师也适合已经跑过 YOLOv8 检测、想加一个字符识别头的新手。下面从环境配置开始一步步来。2. 环境搭建与数据准备把 YOLOv8 和 LPRNet 的依赖先理顺2.1 用 conda 隔离环境避开 CUDA 版本玄学车牌识别项目同时依赖 ultralytics、PyTorch、OpenCV、NumPy 这几个库版本冲突是新手翻车最多的地方。我一般用 conda 建一个独立环境Python 版本锁在 3.9 或 3.10这两个版本对 ultralytics 和 PyTorch 的兼容性最稳。如果你只有 CPU也能跑只是训练慢推理一张图大概几百毫秒做 demo 够用。conda create -n lpr python3.10 -y conda activate lpr # 安装 PyTorchCPU 版本用下面这行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 如果有 NVIDIA 显卡换成对应 CUDA 版本的命令比如 cu118 # pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python numpy pillow pyyaml tqdm这段命令的逻辑是先建环境再装 PyTorch最后装 ultralytics 和其他工具库。参数上唯一需要你改的是 PyTorch 的 index-urlCPU 版本和 CUDA 版本不能混装混装之后torch.cuda.is_available()会返回 False但代码不报错只是默默用 CPU 跑这是最常见的「以为用了 GPU 其实没有」的坑。装完之后用下面这段代码验证import torch from ultralytics import YOLO print(torch:, torch.__version__) print(cuda available:, torch.cuda.is_available()) print(device count:, torch.cuda.device_count())如果cuda available是 False 而你有显卡先检查驱动版本再检查 PyTorch 是不是装成了 CPU 版。这一步不通过后面训练会慢到让你怀疑人生。2.2 车牌检测数据集标注格式和目录结构YOLOv8 检测部分需要的数据集是「图片 YOLO 格式标签」。YOLO 格式每行是class_id x_center y_center width height全部归一化到 0 到 1 之间。车牌检测通常只有一个类别class_id 就是 0。目录结构按 ultralytics 的约定来datasets/ plate_det/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml内容如下path: ./datasets/plate_det train: images/train val: images/val nc: 1 names: [plate]标注工具用 labelme 或 labelImg 都行导出时选 YOLO 格式。这里有个血泪经验中文路径和空格路径会让 ultralytics 在部分版本下读不到文件数据集路径全部用英文和下划线。另外训练集和验证集的比例建议 8:2如果样本少于 500 张验证集至少留 50 张否则验证指标波动很大看不出模型到底有没有收敛。2.3 车牌识别数据集LPRNet 需要的是字符序列标签LPRNet 不检测位置它输入的是已经裁剪好的车牌小图输出是字符序列。所以识别数据集要单独准备把每张车牌图裁剪出来文件名或配套的 txt 里写清楚车牌号。常见做法是建一个train.txt每行格式为图片路径 车牌号例如images/plate_001.jpg 京A12345 images/plate_002.jpg 沪B67890字符集需要你自己统计中国车牌一般包含省份简称、字母、数字和少量特殊字符。把字符集写成一个列表后面做编码映射用。注意车牌里的「I」和「1」、「O」和「0」容易混训练前最好统一规则否则模型学出来的结果在业务上不可用。识别数据集的图片建议统一缩放到94x24这是 LPRNet 原文常用的输入尺寸宽高比接近真实车牌缩放后字符不会严重变形。3. YOLOv8 检测训练参数怎么设、损失曲线怎么看3.1 从预训练权重起步别从零训YOLOv8 提供 n、s、m、l、x 几个尺寸车牌检测属于单类目标检测用yolov8n.pt或yolov8s.pt就够了。n 版模型小、推理快适合边缘设备s 版精度略高适合服务器端。训练命令如下yolo detect train \ modelyolov8n.pt \ datadatasets/plate_det/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ projectruns/plate_det \ nameexp1参数说明epochs100是上限patience20表示 20 轮验证指标不提升就早停避免过拟合imgsz640是输入尺寸车牌在图中占比小的话可以提到 960但显存占用会明显上升batch16根据显存调显存不够就降到 8 或 4lr00.01是初始学习率YOLOv8 默认用 SGD 时这个值比较稳如果你换成 AdamW建议降到 0.001。device0表示用第一块 GPUCPU 训练改成devicecpu。训练过程中重点看两个东西box_loss和mAP50。box_loss持续下降说明定位在收敛mAP50上升到 0.9 以上基本可用。如果box_loss震荡不降先检查学习率是不是太大再检查标注框有没有越界或宽高为 0 的脏数据。YOLOv8 训练结束后会在runs/plate_det/exp1/weights/下生成best.pt和last.pt推理用best.pt。3.2 用验证集跑一遍确认检测框不飘训练完不要直接上业务图先用验证集跑一遍看检测框是不是稳定套在车牌上。命令如下yolo detect val \ modelruns/plate_det/exp1/weights/best.pt \ datadatasets/plate_det/data.yaml \ imgsz640 \ batch16输出里会给出 Precision、Recall、mAP50、mAP50-95。车牌检测场景下Recall 比 Precision 更重要因为漏检一张车牌比多检一个框更影响后续识别。如果 Recall 偏低优先补充小目标、模糊、逆光场景的训练样本而不是盲目加 epoch。验证时还可以加saveTrue把预测结果图存下来肉眼过一遍比只看数字靠谱。3.3 检测模型导出ONNX 和 TensorRT 怎么选训练完的.pt权重适合 Python 推理但如果要部署到 C 或边缘设备通常导出 ONNX 或 TensorRT。导出命令yolo export modelruns/plate_det/exp1/weights/best.pt formatonnx imgsz640 yolo export modelruns/plate_det/exp1/weights/best.pt formatengine imgsz640 halfTrueONNX 通用性好TensorRT 在 NVIDIA 设备上速度快但绑定硬件。halfTrue表示 FP16 量化精度损失很小速度提升明显。注意导出时的imgsz必须和训练时一致否则检测框坐标会错位。导出后建议用onnxruntime跑一张图对比 PyTorch 输出确认数值差异在可接受范围内。4. LPRNet 识别训练字符序列模型的关键参数与坑4.1 LPRNet 的网络结构和输入输出LPRNet 是一个轻量级字符识别网络核心思路是用 CNN 提取特征再接一个 CTC 损失做序列解码不需要字符分割。输入是94x24的 RGB 车牌图输出是每个时间步的字符概率分布最后用 CTC 贪心解码或 beam search 得到车牌号。它的优势是模型小、速度快适合和 YOLOv8 串联做实时识别。网络结构上前面是几个卷积和池化层后面接全连接和 reshape整体参数量在百万级别CPU 上也能跑到几十毫秒一张。训练 LPRNet 的损失函数用 CTC LossPyTorch 里是torch.nn.CTCLoss。字符集需要预留一个 blank 标签通常放在索引 0。下面是一个简化的训练循环骨架import torch import torch.nn as nn from torch.utils.data import DataLoader # charset 示例blank 省份简称 字母 数字 charset [blank, 京, 沪, 粤, A, B, C, 1, 2, 3] char_to_idx {c: i for i, c in enumerate(charset)} ctc_loss nn.CTCLoss(blank0, zero_infinityTrue) optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(100): model.train() for imgs, labels, label_lens in train_loader: logits model(imgs) # shape: [batch, T, num_classes] log_probs logits.log_softmax(2).permute(1, 0, 2) input_lens torch.full((imgs.size(0),), log_probs.size(0), dtypetorch.long) loss ctc_loss(log_probs, labels, input_lens, label_lens) optimizer.zero_grad() loss.backward() optimizer.step()逻辑说明logits的时间步维度要和 CTC 要求的输入长度对应zero_infinityTrue防止某些样本因长度不匹配导致 loss 变成 inflabel_lens是每个车牌号的实际字符数。参数上学习率 1e-3 是 Adam 的常用起点如果 loss 下降慢可以降到 5e-4如果震荡就再降。batch size 根据显存调LPRNet 模型小batch 32 或 64 都可以。4.2 字符集设计和数据增强的边界字符集设计直接决定识别上限。中国车牌常见字符包括 31 个省份简称、26 个字母I 和 O 通常不用、10 个数字加上 blank 一共不到 70 类。如果你的业务只涉及某个省份可以把字符集缩小模型更容易收敛。数据增强方面LPRNet 对颜色和亮度变化比较敏感训练时可以加随机亮度、对比度、轻微旋转和仿射变换但不要加水平翻转因为车牌翻转后字符顺序会反CTC 解码会乱。另外模糊和运动模糊可以加这能提升实际卡口场景的鲁棒性。4.3 识别模型评估整牌准确率和字符准确率识别模型不能只看 loss要看整牌准确率。评估代码如下def evaluate(model, dataloader, char_to_idx, idx_to_char): model.eval() total, correct 0, 0 with torch.no_grad(): for imgs, labels, _ in dataloader: logits model(imgs) preds logits.argmax(2) # 贪心解码 for pred, label in zip(preds, labels): pred_str decode(pred, idx_to_char) label_str decode(label, idx_to_char) total 1 if pred_str label_str: correct 1 return correct / total整牌准确率低于 90% 时先看是哪些字符错常见的是相似字符混淆比如「京」和「津」、「B」和「8」。解决办法是补充这些易混字符的训练样本或者在解码时加语言模型约束。字符准确率可以用编辑距离算但业务上整牌准确率更有意义因为车牌错一个字符就等于识别失败。5. 检测与识别串联从一张图到车牌号的完整推理链路5.1 推理流程拆解裁剪、缩放、解码完整推理链路是读图 → YOLOv8 检测车牌框 → 按框裁剪 → 缩放到94x24→ 送入 LPRNet → CTC 解码 → 输出车牌号。下面是一个可运行的串联脚本import cv2 import torch import numpy as np from ultralytics import YOLO det_model YOLO(runs/plate_det/exp1/weights/best.pt) rec_model torch.jit.load(lprnet_best.pt) rec_model.eval() def recognize_plate(img_path): img cv2.imread(img_path) results det_model(img, imgsz640, conf0.5)[0] plates [] for box in results.boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) crop img[y1:y2, x1:x2] if crop.size 0: continue crop cv2.resize(crop, (94, 24)) crop crop.astype(np.float32) / 255.0 crop np.transpose(crop, (2, 0, 1))[None, ...] tensor torch.from_numpy(crop) with torch.no_grad(): logits rec_model(tensor) pred logits.argmax(2)[0].tolist() plate_str decode(pred, idx_to_char) plates.append((plate_str, (x1, y1, x2, y2))) return plates逻辑说明conf0.5是检测置信度阈值低于这个值的框丢弃裁剪后如果crop.size 0说明框越界直接跳过缩放和归一化必须和训练时一致否则识别率会大幅下降。decode函数做 CTC 贪心解码把重复字符和 blank 去掉。参数上conf可以按业务调追求召回就降到 0.3追求准确就提到 0.6。5.2 多车牌和倾斜车牌的處理一张图里有多辆车时YOLOv8 会输出多个框逐个裁剪识别即可。倾斜车牌是难点检测框可能是旋转的但 YOLOv8 默认输出水平框裁剪出来的车牌会带背景。常见做法是先用检测框做透视变换矫正再送识别。如果业务场景倾斜严重可以考虑用 YOLOv8-OBB 旋转框检测但那是另一个模型需要重新标注和训练。实际项目中我一般先看倾斜角度小于 15 度直接裁剪识别影响不大大于 15 度才上矫正。5.3 推理速度优化批处理和半精度如果要做视频流实时识别单张推理可能不够快。优化手段有三个一是把检测和识别都导出成 ONNX 或 TensorRT用onnxruntime或tensorrt推理二是识别模型用 FP16 半精度三是多帧批处理把几帧的车牌裁剪图攒成一个 batch 送 LPRNet。实测在 GTX 1660 Ti 上YOLOv8n LPRNet 的 FP16 推理单帧端到端可以做到 20 毫秒以内满足 25 FPS 视频流。CPU 上大概 200 到 300 毫秒只能做离线或低频抓拍。6. 避坑与排查车牌识别系统最常见的 5 个翻车现场6.1 检测框正常但识别结果全是乱码现象YOLOv8 框得很准但 LPRNet 输出的车牌号完全不对。原因通常是预处理不一致训练时用的归一化方式、通道顺序、缩放插值方法和推理时不一样。解决把训练时的预处理代码单独抽成一个函数推理时直接调用同一个函数不要凭记忆重写。重点检查cv2.resize的插值方式训练用INTER_LINEAR推理也要用INTER_LINEAR。6.2 训练 loss 不降CTC 输出全是 blank现象LPRNet 训练几轮后 loss 卡住解码结果全是 blank。原因一般是字符集索引和标签编码对不上或者label_lens传错。解决打印一个 batch 的标签和对应长度确认每个标签的索引都在字符集范围内且长度等于实际字符数。另外检查 CTC 的blank索引是不是 0如果字符集里 blank 不在 0要显式传参。6.3 验证集准确率高实际场景一塌糊涂现象验证集整牌准确率 95%但拿真实卡口图测试只有 60%。原因是训练数据分布和实际场景差异大比如训练集都是正面清晰图实际有逆光、模糊、大角度。解决补充实际场景的困难样本重新训练或者在训练时加更强的数据增强比如随机模糊、随机亮度、随机遮挡。不要只看验证集数字要拿业务图做测试集。6.4 导出 ONNX 后检测框坐标偏移现象PyTorch 推理正常导出 ONNX 后框的位置整体偏移或缩放。原因是导出时的imgsz和推理时不一致或者预处理里的 letterbox 填充参数没对齐。解决导出和推理用同一个imgsz并且把 letterbox 的缩放比例和填充量记录下来后处理时按同样参数还原坐标。ONNX 推理建议用 ultralytics 自带的YOLO类加载它已经处理好了这些细节。6.5 多线程推理时结果错乱现象单线程推理正常开多线程后车牌号串了。原因是 PyTorch 模型不是线程安全的多个线程同时调用同一个模型实例会出问题。解决每个线程单独加载一个模型实例或者用锁串行化推理。更推荐的做法是用一个推理队列单线程消费避免并发问题。如果一定要多线程用torch.jit.load加载 TorchScript 模型它对并发的支持比原生 PyTorch 好一些。7. 进阶技巧用置信度过滤和字符级后处理把准确率再拉一截整套系统跑通之后真正决定业务可用性的是后处理。LPRNet 的 CTC 解码输出是一串字符概率贪心解码只取最大概率但有些位置的最大概率也不高。我一般会加两层过滤第一层是检测置信度低于 0.4 的框直接丢第二层是识别置信度把每个字符的最大概率相乘或取平均低于阈值的整牌结果标记为「不确定」交给人工复核或二次抓拍。这个阈值需要按业务调停车场场景可以放宽到 0.6高速卡口建议 0.8 以上。字符级后处理也能救回不少错。中国车牌有固定格式第一个字符是省份简称第二个是字母后面是字母数字组合新能源车牌还有固定的位数规则。写一个简单的规则校验函数把不符合格式的结果按概率次优解重新解码往往能把「京A12345」从「京A1234S」纠正回来。下面是一个规则校验的示例def validate_plate(plate_str): if len(plate_str) not in (7, 8): return False if plate_str[0] not in PROVINCES: return False if not plate_str[1].isalpha(): return False if not plate_str[2:].isalnum(): return False return True def decode_with_rules(logits, idx_to_char): pred logits.argmax(2)[0].tolist() plate_str decode(pred, idx_to_char) if validate_plate(plate_str): return plate_str # 不符合规则时取 top2 重新组合选第一个通过校验的 top2 logits.topk(2, dim2).indices[0].tolist() for i in range(len(top2)): for candidate in top2[i]: trial pred[:i] [candidate] pred[i1:] trial_str decode(trial, idx_to_char) if validate_plate(trial_str): return trial_str return plate_str这段代码的逻辑是先用贪心解码出一个结果如果不符合车牌格式就在每个位置尝试第二候选字符找到第一个符合格式的组合。参数上top2可以扩展到top3但计算量会增加实际用top2就能覆盖大部分情况。注意这个后处理只适用于规则明确的中国车牌如果业务涉及其他地区车牌规则要相应调整。还有一个容易被忽略的点是模型更新。车牌识别系统上线后遇到识别错的样本不要只改后处理规则要把这些样本收集起来定期重新训练。我一般每积累 500 张困难样本就做一次增量训练学习率调小到 1e-4训练 10 到 20 轮既能吸收新样本又不会把原有能力冲掉。这个习惯坚持下来系统的准确率会随着业务运行逐步提升而不是越用越差。最后说一个我自己的教训不要一上来就追求大模型和高精度。YOLOv8n LPRNet 这套轻量组合在大部分车牌识别场景下已经够用先把整条链路跑通把数据闭环建起来再考虑换更大的模型或加更复杂的后处理。很多项目死在第一步环境没配好而不是模型不够强。希望帮到你。本文还有配套的精品资源点击获取