YOLOv5+LPRNet车牌识别:从CCPD训练到部署全流程
2026/9/11 17:34:31 网站建设 项目流程

简介:基于YOLOv5与LPRNet的车牌检测识别项目包,面向毕业设计、课程设计及软件工程实践场景,解决车牌定位与字符识别的完整流程问题,适合需要快速搭建可演示系统的学习者与开发者。压缩包共59个文件,大小16.72MB,以22个Python源码文件为核心,涵盖数据集划分、CCPD格式转换、YOLOv5检测模型训练、LPRNet识别模型训练以及测试推理等脚本,并提供训练好的pt与pth模型权重,下载解压后即可直接运行验证。配套的jpg与png示例图、yaml超参配置、txt依赖说明及md教程文档,进一步降低了复现门槛,便于按模块学习与二次开发。教程从环境准备、数据预处理到训练调参与结果可视化均有详细说明,目录结构清晰,可帮助读者快速掌握车牌检测识别项目的完整技术链路。目前已有432人学习下载,适合作为课程设计或毕业设计的参考实现。

1. 车牌检测与识别流水线:从CCPD数据集到YOLOv5+LPRNet的完整落地

一辆车从画面中出现到车牌号被读出来,中间隔着两个问题:车牌在哪,车牌是什么。前者是目标检测,后者是字符识别,两个问题用不同的模型解决才合理。YOLOv5负责在整张图中框出车牌区域,LPRNet则把裁剪出的车牌图像直接映射成字符串,中间不需要字符分割。这套组合在CCPD数据集上训练,能覆盖大部分真实场景下的中国车牌识别需求,尤其适合毕设、工程原型和边缘设备推理场景。本文会从数据集处理、检测模型训练、识别模型训练到两条链路串联,给出可以直接照着跑的参数和命令,并把常见的坑标注出来。

2. CCPD数据集:车牌检测与识别的数据底盘

2.1 CCPD数据集结构与关键字段

CCPD(Chinese City Parking Dataset)是中科大收集的大型停车场真实场景车牌数据集,图像来自不同天气、光照和角度下的实拍,比合成车牌数据更接近实际部署环境。整个数据集按省份简称、字母、数字和车牌类型做了信息冗余标注,文件名本身就携带了车牌字符和角点坐标。CCPD2019包含约25万张图像,CCPD2020补充了更多恶劣天气和模糊样本。文件名格式类似:

025-95_113-154&383_386&473-386&473_177&454_154&383_363&402-0_0_22_27_27_33_16-37-15.jpg

各段含义分别是:车牌宽度占比、车牌高度占比、车牌左上和右下角坐标、车牌的四个角点坐标、车牌字符编码、亮度评分、模糊评分。文件名中的154&383_386&473就是检测框的左上角(154, 383)和右下角(386, 473)386&473_177&454_154&383_363&402是车牌的四个角点,用于透视校正。

下载后建议先解压,并确认目录结构是否符合YOLOv5的预期。常见做法是将数据按train/val划分,每个子目录下放imageslabels两个文件夹,YOLOv5训练时会自动匹配相同前缀的.txt标注文件和.jpg图像文件。CCPD原始数据没有VOC格式的XML标注,需要从文件名解析坐标后自行生成YOLOv5的txt标注。

2.2 用脚本将CCPD转换为YOLOv5训练格式

YOLOv5的标注格式是归一化后的class x_center y_center width height,类别只有一类(车牌)。下面这个脚本会遍历CCPD图像文件名,提取角点坐标中的左上与右下点,再归一化写入txt。需要注意坐标原点是图像左上角,CCPD文件名里的坐标已经是像素值,不需要额外偏移。

import os import cv2 src_img_dir = "CCPD2019" out_img_dir = "dataset/images" out_lbl_dir = "dataset/labels" os.makedirs(out_img_dir, exist_ok=True) os.makedirs(out_lbl_dir, exist_ok=True) for fname in os.listdir(src_img_dir): if not fname.endswith(".jpg"): continue parts = fname.split("-") # 第四段是左上角与右下角坐标,格式如 154&383_386&473 box_part = parts[2] left_top, right_bottom = box_part.split("_") x1, y1 = map(int, left_top.split("&")) x2, y2 = map(int, right_bottom.split("&")) img = cv2.imread(os.path.join(src_img_dir, fname)) h, w = img.shape[:2] x_center = ((x1 + x2) / 2) / w y_center = ((y1 + y2) / 2) / h box_w = (x2 - x1) / w box_h = (y2 - y1) / h label_name = fname.replace(".jpg", ".txt") with open(os.path.join(out_lbl_dir, label_name), "w") as f: f.write(f"0 {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n") # 顺便拷贝图片到目标目录 os.system(f"cp {os.path.join(src_img_dir, fname)} {os.path.join(out_img_dir, fname)}")

脚本的逻辑很直接:按-切分文件名后取第三段,再按_&拆出两个点坐标。这里没有做train/val划分,建议先随机抽取10%的图像作为验证集,避免同一停车场的相近角度图像全部落进训练集导致过拟合。更严谨的做法是按采集时间或地理位置划分,但CCPD文件名里没有这些信息,随机划分是常见妥协方案。转换完成后,还需要在dataset/下创建CCPD.yaml

train: dataset/images/train val: dataset/images/val nc: 1 names: ['plate']

nc: 1表示只有一个类别,也就是车牌。如果后续想同时检测车脸或车型,需要修改这里并重新生成标注。

2.3 数据增强与样本分布检查

转换完格式后,先别急着训练。用下面命令统计一下标注框的尺寸分布,这直接决定YOLOv5的锚框参数怎么设置。

python -c " import os import numpy as np sizes = [] for f in os.listdir('dataset/labels/train'): with open(os.path.join('dataset/labels/train', f)) as fh: for line in fh: _, x, y, w, h = map(float, line.split()) sizes.append((w, h)) arr = np.array(sizes) print('mean w,h:', arr.mean(axis=0)) print('max w,h:', arr.max(axis=0)) print('min w,h:', arr.min(axis=0)) "

车牌在图像中通常是小目标,尤其是停车场整车的画面,车牌宽高往往只有图像的十分之一甚至更小。如果平均框宽小于0.15,训练时建议开启YOLOv5的--multi-scale参数,让模型在不同输入尺度上看到目标,增强小目标检测能力。同时,CCPD中相当一部分图像有透视形变,检测框是倾斜的,但YOLOv5输出的是水平框,会引入一定的背景噪声,这在识别阶段需要靠LPRNet的鲁棒性来消化。

3. YOLOv5车牌检测:模型选择、超参数与训练命令

3.1 为什么检测端选YOLOv5而不是传统图像处理

传统车牌定位靠边缘检测、颜色阈值和形态学闭运算,在固定角度、固定光照的卡口场景下效果尚可,但一旦遇到逆光、阴影、车身颜色与车牌底色接近的情况,阈值参数就会失效。YOLOv5将车牌定位转化为回归问题,直接预测边界框和置信度,天然适配多角度、多尺度场景。相比于Faster R-CNN,YOLOv5在推理速度和工程易用性上有明显优势;相比YOLOv8等新版本,YOLOv5的生态更成熟,网上能找到大量训练自己的数据集的现成配置和踩坑记录,这也是毕设和企业原型项目选它的主要原因。

YOLOv5的网络结构延续了CSPDarknet的设计思路。骨干网络用CSPNet降低冗余梯度计算,颈部用PANet做多尺度特征融合,检测头在三个不同尺度的特征图上输出预测框。车牌识别场景下,输入分辨率不需要太高,--img 416通常就够用,过高的分辨率只会增加推理延迟,对小目标的提升有限。实际训练时,预训练权重选择yolov5s.ptyolov5m.pt,s版本参数量小、训练快,m版本精度更高但推理稍慢。车牌只有一个类别,s版本已经足够。

3.2 训练自己的车牌检测模型的完整命令

环境配置是第一个坑。YOLOv5官方代码基于PyTorch,环境配置建议直接按官方README执行:

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

PyTorch版本建议不低于1.8,CUDA版本与显卡驱动要匹配。如果机器没有NVIDIA显卡,训练会非常慢,可以尝试Google Colab或云GPU。训练命令如下:

python train.py \ --data ../dataset/CCPD.yaml \ --weights yolov5s.pt \ --img 416 \ --batch 16 \ --epochs 100 \ --workers 4 \ --cache \ --multi-scale

参数说明如下表,这些参数在车牌这类单一类别小目标任务中需要重点调整:

参数建议值说明
--img416输入图像边长,越小训练越快,但车牌过小容易漏检
--batch16取决于显存,6GB显存建议8,12GB以上可以到32
--epochs100单类别任务100轮基本收敛,超过150轮容易过拟合
--multi-scale开启每10个batch随机缩放输入,增强小目标鲁棒性
--patience20验证集指标连续20轮不提升就早停,节省时间
--cache开启提前把图像缓存到内存,加速数据读取,注意显存消耗
--weightsyolov5s.pt建议使用COCO预训练权重做迁移学习,不要随机初始化

训练过程会输出mAP、Precision、Recall等指标。车牌检测的mAP@0.5一般能到98%以上,但如果出现mAP很高但实际测试漏检的情况,多半是验证集分布与训练集太接近。另一个要注意的是--hyp超参数文件,YOLOv5默认的hyp.scratch-low.yaml里包含hsv_hhsv_sdegrees等增强参数。车牌颜色是重要的先验信息,默认的色调增强幅度较大,可能把蓝牌增强成绿牌,建议把hsv_h从默认值调低到0.01,degrees旋转角度设到5-10度之间,因为实际车牌不会出现90度旋转。

3.3 检测效果排查:漏检和误检分别看哪里

训练完成后如果发现漏检,优先看测试图像中车牌的真实尺寸。如果车牌像素宽度小于20个像素,YOLOv5s的特征图下采样32倍后只剩下不到1个像素的信息,这是结构性漏检,调参无法解决,只能提高输入分辨率或使用更大模型。误检则通常把车灯、车标、深色区域当成车牌,原因是背景与车牌颜色相似度高。这时需要增加负样本,可以从CCPD测试集里截取不含车牌的图像加入训练集,并在CCPD.yaml中设定background目录。YOLOv5本身不区分正负样本目录,需要把所有背景图放到images/下,并生成空的标签文件。

如果推理时检测框抖动明显,可以开启TTA(Test Time Augmentation)或多帧平滑,但这些都是后处理技巧,根本还是要保证检测框紧贴车牌边缘。训练结束后的最佳权重在runs/train/exp*/weights/best.pt,后续识别阶段的输入就依赖这个模型输出的坐标框。

4. LPRNet车牌识别:端到端字符识别与CTC解码

4.1 LPRNet网络结构与CTC原理

LPRNet是面向车牌识别的轻量级卷积循环神经网络,核心思想是不做字符分割,直接输入整张车牌图像,输出变长的字符序列。网络主干是一系列卷积层和最大池化层,用来提取视觉特征,然后接入循环层(通常是双向LSTM或GRU)建模字符之间的时序依赖,最后通过CTC损失函数对齐输入序列与输出标签。中国车牌固定为7个字符(省份汉字+字母+字母/数字组合,新能源车牌为8位),但LPRNet的设计允许输出变长序列,因此也适用于其他国家的车牌格式。

为什么不用传统的字符分割方案?传统方法先找字符间隙,再用单个字符分类器逐个识别,问题是车牌铆钉、污渍、边框和倾斜透视都会干扰分割结果。LPRNet把字符识别建模为一个序列标注问题,CTC在训练时自动学习特征帧与字符之间的对齐关系,不要求每一帧都有明确的字符边界,这大大提升了对模糊车牌的容错能力。车牌底色差异(蓝、绿、黄)不影响LPRNet的特征提取,因为字符区域的颜色信息在网络深层已经弱化,网络更关注字符形状。

LPRNet的输入尺寸一般设为宽94像素、高24像素。这个比例接近真实车牌的宽高比。车速很快时图像存在运动模糊,网络需要一定的数据增强来模拟模糊效果,常见做法是训练时随机添加高斯模糊、运动模糊、对比度扰动和仿射变换。

4.2 LPRNet训练参数与数据准备

先定义字符集。中国车牌字符集合包括省份简称汉字、24个字母(除去I和O)、10个数字,再加上新能源车牌的特殊要求,总共约70个类别。

provinces = "京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼" letters = "ABCDEFGHJKLMNPQRSTUVWXYZ" digits = "0123456789" charset = provinces + letters + digits

训练LPRNet前,需要把检测模型输出的车牌裁剪图作为输入。CCPD文件名第七段能直接提供真实车牌字符,但需要自己写映射函数将编码转为字符串。训练时推荐使用Adam优化器,初始学习率1e-3,batch size设为128或256,输入统一resize到(94, 24)。损失函数使用torch.nn.CTCLoss,需要传入预测序列、目标序列、输入长度和目标长度。

import torch import torch.nn as nn criterion = nn.CTCLoss(blank=0, zero_infinity=True) # log_probs: (T, N, C),T为序列长度,N为batch,C为类别数 # targets: (N, S),S为目标序列长度 # input_lengths: (N,) 每个样本的输入序列长度 # target_lengths: (N,) 每个样本的目标字符数 loss = criterion(log_probs, targets, input_lengths, target_lengths)

blank=0必须与网络输出层中空白字符对应的通道索引一致。LPRNet输出层维度是字符集大小+1,多出的1个通道是CTC的blank标记。训练时要对车牌图像做适度形变增强,例如随机旋转±5度、随机亮度调整、随机添加噪声,这能让模型更好地应对检测框没贴合车牌边缘的情况。另外,CCPD中的车牌字符分布并不均匀,某些省份简称出现次数很少,训练时可以对低频字符做加权采样,否则模型会对高频字符过拟合,低频省份字符识别率偏低。

4.3 字符表与解码顺序

推理阶段需要把网络输出的概率序列转换为最终车牌字符串。CTC解码常见做法是贪心解码:每一步取最大概率的类别,然后合并相邻相同类别,最后删除blank。这个策略在车牌识别场景中已经够用,因为车牌长度固定且字符间隔清晰,不需要beam search。

def ctc_greedy_decode(pred): # pred: (T, C) 概率矩阵 last = None result = [] for t in range(pred.shape[0]): char_idx = pred[t].argmax().item() # 跳过blank if char_idx != 0 and char_idx != last: result.append(char_idx) last = char_idx return result

解码后得到的索引列表需要映射回字符集,注意charset中索引0对应第一个汉字,但CTC的blank占用了通道0,映射时要错位。实际项目中经常出现的问题是省份汉字被识别成字母,这是因为汉字的笔画复杂、类内差异大,而训练数据中汉字样本相对较少。缓解方法是在数据增强时对字符区域做随机裁剪,人为引入一定的字符形变,让网络看到更多汉字细节。LPRNet本身也支持使用额外的分类头对车牌类型(蓝牌、绿牌、黄牌)进行辅助识别,这个多任务学习技巧能提高整体识别稳定性,但会略微增加模型复杂度。

5. 检测与识别串联推理:坐标裁剪、故障排查与性能优化

5.1 从检测框到识别输入的坐标裁剪细节

串联推理时最容易出错的是坐标尺度不一致。YOLOv5的detect.py输出的是原始图像像素坐标,而LPRNet要求输入是车牌区域的裁剪图。直接使用detect.py的坐标裁剪时,要把检测框向外扩10%到20%的余量,因为YOLOv5预测的水平框往往紧贴车牌边缘,裁剪后可能会截掉字符的上下边界,导致识别准确率下降。扩边操作在推理代码中实现。

import cv2 import torch def crop_plate(image, box, expand_ratio=0.15): x1, y1, x2, y2 = box w, h = x2 - x1, y2 - y1 dx, dy = int(w * expand_ratio), int(h * expand_ratio) x1 = max(0, x1 - dx) y1 = max(0, y1 - dy) x2 = min(image.shape[1], x2 + dx) y2 = min(image.shape[0], y2 + dy) plate = image[y1:y2, x1:x2] plate = cv2.resize(plate, (94, 24)) plate = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) plate = torch.from_numpy(plate).float().unsqueeze(0).unsqueeze(0) / 255.0 return plate

灰度化是可选的。LPRNet输入是三通道或单通道都能训练,但灰度图能减少光照颜色干扰,蓝牌和绿牌的底色差异在经过灰度转换后依然保留亮度差,不影响字符形状识别。注意cv2.resize的插值方式,缩小车牌图像时用cv2.INTER_AREA可以保留更多边缘细节,默认的双线性插值会导致字符边缘发虚。另外,检测模型输出的是多个候选框,需要先按置信度过滤(conf > 0.5),再按坐标排序。如果一帧图像中出现多辆车,要对所有检测框依次裁剪识别,最后按车牌字符串去重或按跟踪ID关联。

5.2 识别结果中的常见错误模式与对策

实际运行中识别错误主要集中在三种情况。第一是颜色车牌的误识别,新能源绿色车牌和黄色教练车牌字符颜色不同,LPRNet在灰度输入下要依靠字符亮度区分,如果光线太强或反光严重,字符与底色对比度下降,模型会把数字5识别成6或3。对策是在训练数据中对亮度通道做更大的随机扰动,或者把输入从灰度改回RGB,让网络利用颜色信息。第二是检测框偏移引发的截断错误,字符第一个字被裁掉一半,此时LPRNet往往会输出长度不足7的结果。在解码后加一个长度校验,长度为6或7时正常输出,长度小于6时用更大expand_ratio重新裁剪识别。第三是蓝色车牌与绿色车牌在灰度图中难以区分字符边界,典型表现是把字母P识别成D,或者把0识别成O。这里有一个实用技巧:后处理阶段可以约束字符位置类型,第1位必须是省份汉字,第2位必须是字母,第3到7位允许字母或数字,在第2位解码到数字时用聚类算法修正。

错误现象根因对策
省份汉字识别错误汉字样本少、笔画复杂低频字符过采样,增加裁剪增强
数字5/6/3混淆低对比度、字符模糊亮度扰动增强,改用RGB输入
识别结果长度不足7检测框截断字符扩大裁剪比例,二次识别
字母O与数字0混淆字符形状高度相似后处理按位置规则约束

5.3 从CPU推理到TensorRT的加速路径

车牌识别的实际部署环境差异很大,可能是停车场闸机旁的Jetson设备,也可能是x86服务器。纯CPU推理YOLOv5s+LPRNet组合,单帧处理时间大约150到300毫秒,其中检测占大头。如果使用TensorRT在Jetson上部署,可以把检测模型转换为FP16精度,推理时间缩短到10毫秒级别。转换过程使用trtexec工具或torch2trt库,转换前确保算子和动态shape设置正确。

trtexec --onnx=yolov5s.onnx \ --saveEngine=yolov5s_fp16.engine \ --fp16 \ --minShapes=images:1x3x416x416 \ --optShapes=images:1x3x416x416 \ --maxShapes=images:8x3x416x416

trtexec参数中的minShapes/optShapes/maxShapes要匹配部署时的batch需求,如果实际推理batch=1,动态shape范围可以设得更窄,TensorRT优化效果更好。LPRNet结构简单,参数量不到10MB,在CPU上单帧识别只需10-20毫秒,一般不需要单独做TensorRT转换。但要注意检测模型输出和LPRNet输入之间的数据拷贝开销,推理时尽可能让两个模型使用同一块内存区域,避免反复拷贝。如果只用CPU且对速度要求不高,可以考虑将YOLOv5替换为--img 320输入并开启--half精度,在精度损失小于1个百分点的情况下获得近一倍的加速。把输入分辨率、推理精度、裁剪余量三个参数按实际场景调一遍,通常能找到速度和准确率的平衡点。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询