尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

YOLOv5s+LPRNet车牌识别实战:从CCPD数据到两阶段推理

发布时间:2026/9/28 18:09:45

资讯中心
01
ARTICLE

YOLOv5s+LPRNet车牌识别实战:从CCPD数据到两阶段推理

YOLOv5s+LPRNet车牌识别实战:从CCPD数据到两阶段推理
简介本资源面向计算机、电子信息、数学等专业的大学生及算法初学者提供一套基于YOLOv5s与LPRNet的车牌检测与识别完整项目可用于课程设计、期末大作业或毕业设计参考。项目以CCPD数据集为基础采用YOLOv5s完成车牌定位、LPRNet完成字符识别目前支持蓝牌与绿牌等中国车牌后续可通过添加数据微调以扩展场景与车牌类型。压缩包共61个文件约16.75MB包含22个Python脚本训练、测试、数据转换与推理、27张jpg示例图、3个yaml配置、3张png结果图以及pth与pt权重文件、pptx与docx说明文档和README覆盖从数据准备到模型部署的完整流程。已有482人学习下载。读者可获取可运行的检测识别代码、预训练权重、CCPD转YOLO与LPRNet格式的脚本及测试样例便于快速复现实验、理解两阶段车牌识别架构并在此基础上二次开发。1. 从一张 CCPD 蓝牌图说起YOLOv5s 加 LPRNet 到底怎么把车牌读出来随手拍一张带蓝牌的街景图丢进这个项目它会先框出车牌位置再把框里的字符一个个读出来——检测和识别是两段独立模型接力完成的。检测端用 YOLOv5s识别端用 LPRNet训练数据是 CCPD 数据集。整套代码打包在YOLOv5-LPRNet-Licence-Recognition-master里权重文件yolov5_best.pt和lprnet_best.pth已经随包给出main.py是推理入口train_yolov5.py和train_lprnet.py分别负责两段训练。它适合做课程设计、期末大作业或毕业设计的参考实现也适合想跑通「检测识别」两阶段流水线的从业者。目前只覆盖蓝牌和绿牌新能源换场景需要自己补数据微调。2. 两阶段流水线的选型逻辑为什么检测和识别要拆开做2.1 检测与识别分离而不是端到端 OCR车牌识别这件事常见做法有两种一种是端到端 OCR直接把整张图映射成字符串另一种是先检测再识别拆成两个模型。这个项目选了后者理由很实际。端到端模型对数据量和场景覆盖要求高CCPD 里车牌在画面中的占比、角度、光照变化都很大端到端模型容易在字符级对齐上翻车。拆成两段之后检测模型只关心「车牌在哪」识别模型只关心「框里是什么字」两个任务的输入分布被解耦各自训练收敛都更快。YOLOv5s 是 YOLOv5 系列里最小的模型之一参数量小、推理快适合做轻量级检测。LPRNet 本身是为车牌识别设计的轻量网络不需要字符分割直接输出字符序列对车牌这种定长字符串场景很合适。两者组合整体算力需求不高普通带显卡的机器就能跑。2.2 数据格式转换CCPD 原始标注要拆成两套CCPD 数据集的原始文件名里编码了车牌位置和字符信息但 YOLOv5 和 LPRNet 各自需要不同格式的标注。项目里ccpd2yolov5.py和ccpd2lpr.py就是干这个的。ccpd2yolov5.py把 CCPD 文件名里的边界框坐标解析出来转成 YOLO 格式的class x_center y_center width height归一化到 0~1每张图对应一个.txt。ccpd2lpr.py则把车牌区域裁剪出来按字符顺序生成识别标签。# ccpd2yolov5.py 核心逻辑示意 import os import cv2 def parse_ccpd_filename(filename): # CCPD 文件名格式区域-倾斜度-边界框-四个角点-车牌号-亮度-模糊度 parts filename.split(-) bbox parts[2].split(_) # 格式为 x_miny_min_x_maxy_max x_min, y_min map(int, bbox[0].split()) x_max, y_max map(int, bbox[1].split()) return x_min, y_min, x_max, y_max def convert_to_yolo(img_path, save_dir): img cv2.imread(img_path) h, w img.shape[:2] x_min, y_min, x_max, y_max parse_ccpd_filename(os.path.basename(img_path)) # 归一化 x_center (x_min x_max) / 2.0 / w y_center (y_min y_max) / 2.0 / h bw (x_max - x_min) / w bh (y_max - y_min) / h # 写入 YOLO 格式标签类别 0 表示车牌 with open(os.path.join(save_dir, os.path.basename(img_path).replace(.jpg, .txt)), w) as f: f.write(f0 {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}\n)这段代码的关键在parse_ccpd_filenameCCPD 文件名用-分隔字段第三个字段是边界框内部再用_分隔左上角和右下角坐标。转换时注意归一化用的是图像实际宽高不是固定值。如果转换后训练时检测框全偏了先回头查这一步的宽高有没有取反。2.3 数据集划分split_dataset.py 的用法转换完格式用split_dataset.py把数据分成训练集和验证集。常见做法是按 8:2 或 9:1 切脚本里一般会接收原始目录和输出目录两个参数。python split_dataset.py --source ./ccpd_yolo --train ./datasets/train --val ./datasets/val --ratio 0.8--source是转换后的 YOLO 格式数据目录--train和--val是输出路径--ratio控制训练集比例。跑完之后检查一下两个目录里的图片和标签数量是否一一对应少标签或多标签都会让训练中途报错。3. 训练两段模型YOLOv5 检测器和 LPRNet 识别器分别怎么跑3.1 YOLOv5 检测训练配置文件与超参数检测训练入口是train_yolov5.py数据配置文件在data/ccpd.yaml超参数在data/hyp.scratch.yaml。ccpd.yaml里要写清楚训练集、验证集路径、类别数和类别名。# data/ccpd.yaml train: ./datasets/train/images val: ./datasets/val/images nc: 1 names: [plate]nc: 1表示只有一类目标车牌names里写类别名。如果后面要加其他类型车牌改这里就行但检测模型本身不区分蓝绿牌颜色分类是识别阶段的事。训练命令常见写法python train_yolov5.py --img 640 --batch 16 --epochs 100 --data data/ccpd.yaml --weights yolov5s.pt --cfg models/yolov5s.yaml--img 640是输入尺寸--batch 16看显存调--weights yolov5s.pt用预训练权重做迁移学习--cfg指定模型结构。如果显存不够先把 batch 降到 8 或 4别硬撑OOM 报错不会给你后悔药。训练过程中关注mAP0.5和mAP0.5:0.95两个指标前者到 0.95 以上基本可用后者反映框的精度。如果 loss 一直不降检查标签格式是不是写错了YOLO 对标签格式很敏感多一个空格都可能让某个 batch 直接跳过。3.2 LPRNet 识别训练数据加载与字符集识别训练入口是train_lprnet.py数据加载逻辑在utils/load_lpr_data.py。LPRNet 的输入是裁剪后的车牌图输出是字符序列。字符集一般包含省份简称、字母、数字和特殊字符蓝牌 7 位绿牌 8 位。# utils/load_lpr_data.py 关键部分示意 class LPRDataset(Dataset): def __init__(self, data_dir, transformNone): self.data_dir data_dir self.transform transform self.img_paths [] self.labels [] # 遍历目录读取图片路径和对应标签 for img_name in os.listdir(data_dir): if img_name.endswith(.jpg): self.img_paths.append(os.path.join(data_dir, img_name)) # 标签从文件名或单独标注文件读取 self.labels.append(img_name.split(_)[0]) def __getitem__(self, idx): img cv2.imread(self.img_paths[idx]) img cv2.resize(img, (94, 24)) # LPRNet 常用输入尺寸 label self.labels[idx] if self.transform: img self.transform(img) return img, labelLPRNet 的输入尺寸常见是 94×24宽高比接近车牌实际比例。__getitem__里把图片 resize 到这个尺寸标签按字符逐个编码成索引序列。训练时 CTC loss 负责对齐不需要手动分割字符。训练命令python train_lprnet.py --data ./datasets/lpr_train --val ./datasets/lpr_val --epochs 100 --batch 32--data和--val指向裁剪后的车牌图目录。如果识别结果出现重复字符或漏字符先看 CTC 的 blank 标签有没有设对再检查字符集映射表是否和训练时一致。3.3 两段模型的衔接检测框裁剪后送识别训练完两个模型推理时main.py负责串联。流程是YOLOv5 检测出车牌框按框坐标从原图裁剪出车牌区域resize 到 LPRNet 输入尺寸送识别模型输出字符序列。# main.py 推理流程示意 import torch import cv2 from models.LPRNet import LPRNet def detect_and_recognize(img_path, yolo_model, lpr_model, device): img cv2.imread(img_path) # 第一步YOLOv5 检测 detections yolo_model(img) # 返回框坐标和置信度 results [] for det in detections: x1, y1, x2, y2, conf, cls det if conf 0.5: continue # 第二步裁剪车牌区域 plate_img img[int(y1):int(y2), int(x1):int(x2)] plate_img cv2.resize(plate_img, (94, 24)) # 第三步LPRNet 识别 plate_tensor torch.from_numpy(plate_img).float().unsqueeze(0).to(device) preds lpr_model(plate_tensor) plate_text decode(preds) # CTC 解码 results.append((plate_text, conf)) return results裁剪时注意坐标取整和边界检查x1、y1可能为负或超出图像范围直接切片会报错或裁出空图。置信度阈值 0.5 是常见起点漏检多就降到 0.3误检多就升到 0.6。4. 推理与验证test_yolov5.py、test_lprnet.py 和 detect_yolov5.py 怎么用4.1 单独验证检测模型test_yolov5.py用来在验证集上跑检测指标输出 mAP、precision、recall。python test_yolov5.py --data data/ccpd.yaml --weights weights/yolov5_best.pt --img 640 --batch 16跑完看输出里的mAP0.5如果低于 0.9先查验证集标签有没有漏转再查训练轮数够不够。检测模型欠拟合时框会飘识别再准也没用。4.2 单独验证识别模型test_lprnet.py在验证集上算识别准确率。python test_lprnet.py --weights weights/lprnet_best.pth --data ./datasets/lpr_val输出一般是逐字符准确率和整牌准确率。整牌准确率比逐字符低是正常的因为一个字符错整牌就算错。如果逐字符高但整牌低说明模型在某些位置容易混淆比如0和O、1和I需要补这类样本。4.3 端到端推理detect_yolov5.py是检测推理脚本main.py是完整流水线。跑一张图python main.py --source ./demo/test.jpg --yolo weights/yolov5_best.pt --lpr weights/lprnet_best.pth结果会保存到demo/det_result和demo/rec_result前者是画了检测框的图后者是识别出的车牌号文本。如果检测框画出来了但识别结果是乱码先确认裁剪区域是不是反了x 和 y 搞混再确认 LPRNet 的字符集和训练时一致。5. 避坑与排查训练和推理里最容易翻车的五个点5.1 现象训练 loss 不降mAP 一直是 0原因标签格式不对。YOLO 要求class x_center y_center width height全部归一化到 0~1且 class 从 0 开始。CCPD 转换时如果坐标没除以宽高或者 class 写成了 1训练直接学不到东西。解决打开一个标签文件确认五个值都在 0~1 之间class 是 0。用ccpd2yolov5.py重新转一遍转完抽查十张图的可视化框是否贴合车牌。5.2 现象识别结果全是同一个字符或空原因CTC 解码配置错了或者字符集映射表对不上。LPRNet 输出的是每个时间步的字符概率解码时需要 blank 标签参与。如果 blank 索引和字符集冲突解码会全错。解决检查LPRNet.py里 num_classes 是否等于字符集大小加一blank解码函数里 blank 索引是否和训练时一致。用test_lprnet.py单独跑几张训练集图片如果训练集也错说明是解码问题不是模型问题。5.3 现象检测框位置对但识别结果字符顺序反了原因裁剪出的车牌图方向不对。CCPD 里有些车牌是倾斜或倒置的YOLOv5 检测框是正的但框内图像可能旋转了 180 度。解决在裁剪后加一个方向判断常见做法是用车牌颜色或字符分布做简单判断或者直接对裁剪图做一次小角度旋转校正。如果数据里倒置样本多训练 LPRNet 时把倒置图也加进去做增强。5.4 现象显存不够batch 降到 1 还是 OOM原因输入尺寸太大或模型没释放中间变量。YOLOv5 训练时--img 640在 8G 显存上 batch 16 一般够用但如果同时开了其他进程或者用了--img 1280显存会爆。解决先把--img降到 416 或 320 试再降 batch。推理时用torch.no_grad()包住前向别让梯度占显存。如果还不行换小一号的模型配置yolov5s.yaml已经是最小的了检查是不是误用了yolov5l.yaml。5.5 现象绿牌识别率明显低于蓝牌原因CCPD 里蓝牌样本远多于绿牌绿牌只有 8 位字符且颜色和蓝牌差异大模型对绿牌欠拟合。解决统计训练集里蓝绿牌比例如果绿牌少于 20%要么补绿牌数据要么在 loss 里给绿牌样本加权。另一个常见做法是先把蓝绿牌分类做出来再分别送对应的识别模型但这个项目目前是统一识别的加分类头需要改结构。6. 进阶技巧用 ccpd2lpr.py 做数据增强和字符集扩展ccpd2lpr.py除了做格式转换还可以顺手做数据增强。我一般会在裁剪车牌图的时候加几步随机亮度调整、小角度旋转、轻微透视变换。这三样对车牌识别提升明显尤其是光照变化大的场景。# ccpd2lpr.py 里加数据增强的示意 import cv2 import numpy as np import random def augment_plate(plate_img): # 随机亮度 if random.random() 0.5: factor random.uniform(0.7, 1.3) plate_img np.clip(plate_img * factor, 0, 255).astype(np.uint8) # 小角度旋转 if random.random() 0.3: angle random.uniform(-5, 5) h, w plate_img.shape[:2] M cv2.getRotationMatrix2D((w/2, h/2), angle, 1.0) plate_img cv2.warpAffine(plate_img, M, (w, h)) # 轻微透视 if random.random() 0.2: h, w plate_img.shape[:2] pts1 np.float32([[0,0],[w,0],[0,h],[w,h]]) offset 2 pts2 np.float32([[random.randint(0,offset), random.randint(0,offset)], [w-random.randint(0,offset), random.randint(0,offset)], [random.randint(0,offset), h-random.randint(0,offset)], [w-random.randint(0,offset), h-random.randint(0,offset)]]) M cv2.getPerspectiveTransform(pts1, pts2) plate_img cv2.warpPerspective(plate_img, M, (w, h)) return plate_img亮度因子控制在 0.7~1.3 之间再大就可能把蓝色洗成白色或压成黑色。旋转角度 ±5 度足够车牌本身不会倾斜太多。透视偏移 2 个像素是保守值想更激进可以加到 4但别超过 5否则字符变形太厉害反而伤模型。字符集扩展是另一个进阶点。目前项目只支持蓝牌和绿牌如果要加黄牌或白牌需要改三处ccpd2lpr.py里的字符映射表、LPRNet.py里的 num_classes、以及训练数据里对应类型的车牌图。改完 num_classes 之后之前的权重不能直接加载需要重新训练或者只加载骨干网络部分。验证增强有没有效果最直接的办法是跑test_lprnet.py对比增强前后的整牌准确率。我一般会留一个固定的验证集每次改完数据增强都跑一遍涨了就保留跌了就回退。这个习惯帮我省了很多次「改了反而更差」的后悔药。从那以后我每次动数据管道之前都会先把当前验证集准确率记下来改完再跑一遍对比不靠感觉判断。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。