ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5+LPRNet车牌识别实战:CCPD数据集训练与避坑指南

YOLOv5+LPRNet车牌识别实战:CCPD数据集训练与避坑指南 简介这份资源面向计算机视觉入门与进阶开发者提供一套基于YOLOv5与LPRNet的车牌检测与识别完整实现解决从图像中定位车牌并识别字符的工程问题适用于交通监控、停车管理等场景的学习与二次开发。压缩包共59个文件约16.73MB包含22个Python脚本训练、测试、数据转换与推理入口、27张jpg示例图、3个yaml配置、3张png结果图以及lprnet_best.pth与yolov5_best.pt两个预训练权重和说明文档覆盖数据准备到模型部署的主要环节。已有816人学习下载。资源以CCPD数据集为训练与测试来源代码中提供ccpd2yolov5、ccpd2lpr等转换脚本并附带检测与识别结果图便于读者快速复现YOLOv5检测加LPRNet识别的两阶段流程理解边界框输出、车牌裁剪预处理与字符识别网络的衔接方式同时可参考权重文件直接验证效果适合作为课程设计或项目原型的起步模板。1. 从一张停车场抓拍图说起YOLOv5LPRNet 车牌检测识别到底怎么落地前阵子帮朋友处理一批停车场抓拍图两千多张角度歪、光照乱、车牌还带各种蓝绿渐变。他原本想用传统 OpenCV 轮廓法硬怼结果一半以上车牌定位都飘。后来换成 YOLOv5 做检测、LPRNet 做识别这套组合才把整条链路跑通。这套方案的核心思路很清晰YOLOv5 负责在整张图里框出车牌位置LPRNet 负责把裁出来的车牌小图转成字符序列。两者解耦各自训练、各自优化比端到端模型好调试得多。CCPD 数据集是国内车牌场景里绕不开的公开数据源标注格式和真实抓拍接近适合拿来练手或做基线。如果你手头有车牌识别需求又不想从零造轮子这套组合值得花时间拆一遍。下面按「数据怎么准备 → 模型怎么训 → 坑在哪 → 怎么验证」的顺序把能复现的细节都摊开。2. 数据管线CCPD 数据集解析与 YOLO 格式转换2.1 CCPD 的目录结构与标注逻辑CCPD 数据集的文件名本身就是标注这一点和很多数据集不同。以025-95_113-226469_448520-444522_454542_454542_454542-0_0_22_27_27_33_16-66-88.jpg为例按-切分后各字段含义如下字段序号含义示例值0区域编号0251倾斜角度95_1132边界框坐标226469_4485203四角点坐标444522_454542_...4车牌字符索引0_0_22_27_27_33_165亮度666模糊度88其中字段 2 的226469_448520表示左上角(226,469)、右下角(448,520)这就是 YOLO 训练需要的检测框。字段 4 的七个数字对应车牌字符在省份、字母、数字字典里的索引LPRNet 训练时要用。常见做法是先把 CCPD 的标注转成 YOLO 的 txt 格式每行class x_center y_center width height坐标归一化到 0~1。转换脚本我一般这么写import os import cv2 # CCPD 字符字典按实际数据集版本调整 provinces [皖, 沪, 津, 渝, 冀, 晋, 蒙, 辽, 吉, 黑, 苏, 浙, 京, 闽, 赣, 鲁, 豫, 鄂, 湘, 粤, 桂, 琼, 川, 贵, 云, 藏, 陕, 甘, 青, 宁, 新] alphabets [A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,U,V,W,X,Y,Z] ads [0,1,2,3,4,5,6,7,8,9] def ccpd_to_yolo(img_dir, label_dir): os.makedirs(label_dir, exist_okTrue) for fname in os.listdir(img_dir): if not fname.endswith(.jpg): continue parts fname.split(-) if len(parts) 7: continue # 解析边界框 bbox parts[2].split(_) x1, y1 map(int, bbox[0].split()) x2, y2 map(int, bbox[1].split()) img_path os.path.join(img_dir, fname) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] # 归一化并转为中心点宽高 xc (x1 x2) / 2.0 / w yc (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h # 类别 0 表示车牌 line f0 {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}\n txt_name os.path.splitext(fname)[0] .txt with open(os.path.join(label_dir, txt_name), w) as f: f.write(line) if __name__ __main__: ccpd_to_yolo(./ccpd/images, ./ccpd/labels)这段脚本的关键点有三个一是parts[2]的解析必须严格按和_切CCPD 不同子集base、blur、tilt 等字段顺序一致但个别文件名可能缺字段所以加了len(parts) 7的过滤二是归一化用的是原图宽高不是 640×640YOLOv5 训练时会自己 resize三是类别统一写 0因为只检测车牌一类。跑完以后建议抽查几张用labelImg或自己写个可视化脚本叠框看一眼确认没有坐标翻转。2.2 训练集/验证集划分与 LPRNet 标签生成YOLO 那边只需要图片和 txtLPRNet 这边需要裁出来的车牌小图和对应字符标签。我一般先用训练好的 YOLO 权重跑一遍检测把车牌区域裁出来存成rec_images/同时生成rec_labels.txt每行图片名 字符序列。字符序列的生成逻辑是把字段 4 的七个索引分别映射到省份、字母、数字字典拼成字符串。注意 CCPD 里字母字典没有I和O和国内车牌规则一致映射时别用错字典。划分比例上CCPD 官方建议按 8:1:1 分训练、验证、测试。但实际做的时候如果只做检测YOLO 的验证集可以小一点如果检测和识别一起调识别模型的验证集要单独留不能和检测混用否则评估结果会虚高。我通常固定随机种子按文件名哈希取模来分保证每次复现划分一致。提示CCPD 里有些图片车牌区域极小或严重模糊转换后建议按框面积过滤掉宽或高小于 20 像素的样本否则 YOLO 训练时这些小目标会拉低召回。3. YOLOv5 车牌检测环境配置、训练参数与推理后处理3.1 环境配置与 conda 隔离YOLOv5 对环境比较敏感尤其是 PyTorch 和 CUDA 版本。我习惯用 conda 建独立环境避免和系统里的其他项目打架conda create -n yolov5-lpr python3.8 -y conda activate yolov5-lpr # 按自己显卡驱动选对应 CUDA 版本的 PyTorch pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt装完以后跑python detect.py --source data/images --weights yolov5s.pt验证环境。如果报CUDA out of memory先把--device cpu加上确认代码没问题再回头查显卡占用。常见坑是 conda 环境里装了 CPU 版 PyTorchtorch.cuda.is_available()返回 False这时候要卸掉重装对应 CUDA 版本。3.2 训练参数怎么设从 yolov5s 起步车牌检测属于单类目标检测数据量几千到几万张用yolov5s就够没必要上 l 或 x。我一般这么起训python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data ccpd.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name ccpd_yolov5sccpd.yaml里写清楚train、val路径和nc: 1、names: [plate]。--img 640是默认值如果车牌在图中占比很小可以提到 1280但显存翻倍batch 要相应降到 8 或 4。--hyp用 low 增强版本因为车牌颜色和纹理变化大Mosaic、HSV 增强能明显提升泛化。训练过程中重点看mAP0.5和mAP0.5:0.95车牌检测一般 mAP0.5 能到 0.95 以上如果卡在 0.8 左右多半是标注框有偏移或漏标。3.3 推理与后处理NMS 阈值和裁剪边距训练完拿best.pt推理YOLOv5 的detect.py默认 NMS IoU 阈值 0.45、置信度 0.25。车牌场景下如果一张图里只有一块车牌这两个值不用大改如果有多车牌或误检多把置信度提到 0.5、NMS 降到 0.4 试试。裁车牌时别贴着框裁四周留 5~10 像素边距LPRNet 对边缘敏感裁太紧会把字符切掉。import torch from PIL import Image model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) img Image.open(test.jpg) results model(img) # 取第一个检测框留边距裁剪 for *box, conf, cls in results.xyxy[0].tolist(): x1, y1, x2, y2 map(int, box) pad 8 crop img.crop((max(0, x1-pad), max(0, y1-pad), min(img.width, x2pad), min(img.height, y2pad))) crop.save(plate_crop.jpg)这段代码里results.xyxy[0]是检测结果张量每行x1,y1,x2,y2,conf,cls。pad设 8 是经验值CCPD 里车牌框通常比较准留太多会引入背景干扰。裁完的图统一 resize 到 LPRNet 需要的尺寸一般是 94×24保持宽高比做 padding别直接拉伸。4. LPRNet 识别字符字典、CTC 损失与训练细节4.1 LPRNet 结构为什么适合车牌LPRNet 全称 License Plate Recognition Network核心是不用 RNN、不用分割直接 CNN CTC 输出字符序列。车牌字符长度固定国内蓝牌 7 位新能源 8 位但 CTC 允许输入不定长所以同一套网络能兼容不同长度车牌。它的 backbone 是轻量 CNN参数量小推理快适合部署到边缘设备。相比 CRNNCTCLPRNet 少了循环层训练更稳收敛更快在 CCPD 上通常几十个 epoch 就能到不错的准确率。4.2 字符字典与 CTC 标签对齐字符字典要覆盖省份简称、字母、数字外加 CTC 的 blank 符。我一般把 blank 放在索引 0省份从 1 开始字母和数字依次排。训练时标签是字符索引序列CTC 会自动做对齐不需要逐字符标注位置。这里有个容易翻车的点字典顺序必须和生成标签时的映射一致否则训练 loss 降不下去识别出来全是乱码。建议把字典单独存成plate_dict.py训练和推理都 import 同一份。# plate_dict.py blank _ provinces [皖, 沪, 津, 渝, 冀, 晋, 蒙, 辽, 吉, 黑, 苏, 浙, 京, 闽, 赣, 鲁, 豫, 鄂, 湘, 粤, 桂, 琼, 川, 贵, 云, 藏, 陕, 甘, 青, 宁, 新] alphabets [A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,U,V,W,X,Y,Z] ads [0,1,2,3,4,5,6,7,8,9] chars [blank] provinces alphabets ads char_to_idx {c: i for i, c in enumerate(chars)} idx_to_char {i: c for c, i in char_to_idx.items()}4.3 训练参数与常见收敛问题LPRNet 训练用 CTC Loss优化器选 Adam学习率 1e-3batch 32 左右。输入尺寸 94×24灰度或 RGB 都行CCPD 是彩色图我一般转灰度减少计算量。训练时重点看 CTC loss 是否稳定下降如果 loss 震荡大把学习率降到 5e-4 或加梯度裁剪。另一个常见问题是过拟合CCPD 里某些省份车牌特别多模型会偏向这些省份解决办法是在采样时做类别平衡或者对稀有省份做数据增强。import torch import torch.nn as nn ctc_loss nn.CTCLoss(blank0, reductionmean) optimizer torch.optim.Adam(model.parameters(), lr1e-3) for imgs, labels, label_lens in dataloader: logits model(imgs) # shape: [batch, T, num_classes] log_probs logits.log_softmax(2).permute(1, 0, 2) # CTC 需要 [T, batch, classes] 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() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step()这段里blank0必须和字典里 blank 的索引一致log_softmax(2)是在类别维度做permute把时间维换到最前。clip_grad_norm_阈值 5.0 是防止梯度爆炸的常用值。如果 loss 降到 0.1 以下还在降但验证集准确率不涨就是过拟合了早点停。5. 避坑与排查从数据到推理的五个血泪经验5.1 现象YOLO 训练 loss 正常但 mAP 极低原因通常是标注格式不对。CCPD 转 YOLO 时如果坐标没归一化或者 x1x2、y1y2 没交换YOLO 读进去框全是乱的。解决方法是转换后随机抽 20 张用labelImg打开看确认框贴合车牌。另外检查ccpd.yaml里nc和names数量是否一致不一致会直接报错或静默忽略类别。5.2 现象LPRNet 识别结果全是同一个字符这是 CTC 训练里最经典的翻车。原因一般是字典映射错了或者标签里混入了不在字典里的字符。排查时先把idx_to_char打印出来再拿几条训练标签手动解码看能不能还原成正确车牌。如果标签里有空格或换行没 strip 掉也会导致映射失败。解决就是统一在生成标签时做strip()和字符校验。5.3 现象推理时检测框对但识别错位车牌有倾斜时YOLO 给的矩形框会包含较多背景LPRNet 对字符位置敏感容易把边缘噪声识别成字符。常见做法是先做透视变换把车牌摆正再送 LPRNet。CCPD 字段 3 有四角点坐标训练识别模型时可以用它做矫正推理时如果没有角点就用最小外接矩形加仿射变换近似。5.4 现象conda 环境里 YOLOv5 跑着跑着报 CUDA error多半是 PyTorch 版本和显卡驱动不匹配或者显存被其他进程占了。先nvidia-smi看显存占用再确认torch.version.cuda和驱动支持的 CUDA 版本一致。如果 batch 设太大也会在训练中途 OOM把 batch 减半或开--amp混合精度能缓解。5.5 现象CCPD 某些子集图片读入为 NoneCCPD 里有个别文件损坏或路径含特殊字符cv2.imread返回 None。转换脚本里必须加if img is None: continue否则后续img.shape直接崩。另外 Windows 下路径分隔符和 Linux 不同用os.path.join拼路径别手写斜杠。6. 验证与进阶用测试集量化评估再谈端到端串联技巧训练完不能只看 loss得用留出的测试集算指标。检测这边算 mAP0.5 和召回率识别这边算整牌准确率和字符准确率。整牌准确率要求七位全对字符准确率按位算后者通常比前者高 5~10 个百分点。我一般写个评估脚本把检测和识别串起来跑一遍测试集correct_plate 0 total 0 for img_path, gt_plate in test_list: img Image.open(img_path) det model(img) if len(det.xyxy[0]) 0: continue x1, y1, x2, y2 map(int, det.xyxy[0][0][:4].tolist()) crop img.crop((x1, y1, x2, y2)).convert(L).resize((94, 24)) pred lprnet_infer(crop) # 返回解码后的字符串 total 1 if pred gt_plate: correct_plate 1 print(f整牌准确率: {correct_plate / total:.4f})这段代码里lprnet_infer封装了预处理、前向、CTC 贪心解码。贪心解码实现简单但遇到重复字符可能出错比如皖A88888里连续 8 容易被 CTC 合并这时候可以用 beam search 解码或者在后处理里加规则校验。实际部署时如果检测和识别分开跑中间裁剪和 resize 的耗时可能比模型本身还大我一般把预处理写成 batch 操作一次处理多张裁图。另一个进阶技巧是联合微调先用 CCPD 分别训好检测和识别再拿一小批真实场景数据固定检测模型只微调 LPRNet 的最后几层让识别模型适应自己场景的字体和光照。这样比从头训省时间效果也比直接用 CCPD 模型好。从那以后我每次上新场景都强制先跑一遍测试集评估再决定要不要微调绝不凭感觉直接上线。希望帮到你。本文还有配套的精品资源点击获取
返回列表