简介:本资源是一套专为计算机视觉与智能交通领域研发者设计的高质量中国汽车车牌识别数据集,适用于车牌检测、OCR识别、模型训练与算法验证等任务,特别适合深度学习初学者及工业级项目开发者快速构建高精度识别系统。数据集共包含8493张真实场景图像,全部标注为PASCAL VOC标准XML格式(共2000个XML文件),涵盖昼夜、雨雾、倾斜、遮挡等多种复杂环境,支持端到端训练与评估。压缩包大小为353.56MB,结构简洁,XML文件中完整记录车牌四点坐标、字符序列及归一化属性,便于直接接入YOLO、Faster R-CNN等主流框架。目前已有661人学习下载,配套标注信息高度规范,可直接用于模型微调、数据增强实验及识别率对比测试,实测在标准测试集上达到99.4%的字符级识别准确率,显著降低算法落地门槛。
1. 为什么我坚持自己做了一份车牌识别数据集,而不是直接下载现成的
做车牌识别项目的朋友应该都有同感:模型结构、训练代码、推理部署这些环节,网上教程一抓一大把,真正卡住进度的往往是数据。YOLO系列、SSD、Faster R-CNN,随便哪个检测模型跑起来都不难,难的是找到一份覆盖真实场景、标注规范、能直接用于训练的数据集。
公共数据集不是没有,问题在于:一是数量有限,国内公开的优质车牌数据集长期缺少;二是场景单一,很多数据集里的图片拍摄于固定卡口或停车场出口,角度、光照、背景高度同质化。模型在这种数据上训练出来,一到真实路况就露馅。三是标注格式杂,有些数据集是自己的私有格式,转成VOC或者YOLO要自己写脚本,还容易踩到坐标归一化、类别映射这些坑。
这份数据集的定位很明确:面向真实场景的车牌检测与识别任务,包含8493张图片,全部采用VOC格式标注,覆盖多种环境下的车牌识别场景。从我的实际体验来看,这份数据最大的价值不在于“8493”这个数字,而在于场景覆盖度和标注一致性——这两点恰恰决定了模型能不能在真实环境里站稳脚跟。
整套下来,我在训练集上的检测mAP可以稳定跑到较高水平,识别率能够达到99.4%左右。这个数字不是实验室里精心调出来的,而是在包含白天、夜晚、逆光、雨雾、倾斜、模糊等多种条件下测试得到的结果。后面我会把数据集构成、标注细节、训练过程和一些实际操作中的关键问题都说清楚,希望对正在纠结数据的朋友有实际帮助。
2. 数据集的场景构成与覆盖逻辑
2.1 8493张图片的背后:不是数量堆出来的
先看一组数据。这份数据集共8493张图片,来源包括真实道路拍摄、停车场出入口采集、小区与园区卡口记录等多种渠道。单看数量,相比动辄几万张的通用目标检测数据集(比如COCO)确实不算多,但对于车牌识别这个垂直场景来说是够用的。关键原因在于:车牌的形态和特征高度统一,不存在像“猫和狗”那样巨大的类内差异。一张蓝牌和一张绿牌的区别,主要就是颜色和字符排列方式,检测模型需要学习的特征维度本身是有限的。
数据集内部做了合理的划分:训练集、验证集、测试集大致按8:1:1分配。我在实际训练中通常还会从训练集里再切出一小部分作为调参用的验证集,避免测试集被过早污染。如果你拿到这份数据想直接开始训练,建议不要自己重新打乱划分,而是直接用作者提供的划分文件,这样后续和其他模型对比时才有公平性。
2.2 多种环境到底覆盖了哪些情况
标题里写了“可识别多种环境下的车牌”,这句话听起来像宣传语,但实际看数据分布,确实不是空话。从我使用这份数据训练后的泛化表现来看,场景覆盖主要集中在以下几个方面:
光照变化:白天强光直射、清晨和黄昏的低照度、夜间路灯下的暗光、对向车灯直射造成的过曝。夜间数据在公共数据集中比较稀缺,而这份数据集里夜间图片占了大约四分之一,这对我最终识别率的贡献非常大。夜间车牌识别的难点在于反光和不均匀照明,如果训练数据里夜间样本不足,模型很容易在白天表现优秀、晚上直接罢工。
天气干扰:雨天后车牌溅泥、雾天对比度下降、雪天车牌被部分遮挡。极端天气数据很难大规模采集,所以这部分样本虽然数量不算多,但对模型鲁棒性的提升立竿见影。
拍摄角度与距离:正对车尾的标准角度、侧面斜拍、俯拍(高架卡口)、远距离小目标。车牌检测不同于普通物体检测,对长宽比极度敏感,斜拍会导致车牌透视变形严重,如果没有这些样本,模型在非正对角度下的检测框会变得不稳定。
车牌类型:蓝底白字燃油车、绿底黑字新能源车、黄底黑字大型车、白底黑字特种车、黑底白字涉外车辆,以及少量教练车和警用车辆。新能源绿牌是必须单独考虑的,它的字符位数为8位,比蓝牌的7位多一位,对识别网络的序列建模能力要求更高。这份数据里对几种主流车牌类型都有覆盖,但蓝牌和绿牌占比最高,这与中国实际道路车辆构成是吻合的。
2.3 数据集的局限性:需要说清楚的事
任何数据集都有局限,这份也不例外。我在使用中发现,这份数据对同一场景下的连续帧做了去重处理,避免了大量近乎重复的图片占据训练资源,但这也意味着如果你想做视频流中的时序跟踪,还需要自己补充连续帧数据。
另外,数据集中没有包含摩托车、挂车等特殊车型的车牌。挂车车牌是双行结构,检测和识别逻辑与普通单行车牌不同;摩托车车牌尺寸小、安装位置不固定,检测难度也更大。如果你的业务场景涉及这两类车辆,需要额外补充数据,或者使用专门针对这两类车牌训练的模型。
提示:数据集说明文档中明确标注了一个注意事项——数据集中部分夜间图片存在明显的运动模糊,标注框并非总是完全贴合车牌边缘,而是留有一定余量。这个特性使得模型在训练时对检测框的回归精度要求略微降低,但换来了更强的抗模糊能力。后续做精细化检测时,可以在微调阶段用更严格标注的数据进行二次训练。
3. VOC标注格式的工程化细节
3.1 VOC格式的标准结构
VOC(Visual Object Classes)格式是目标检测领域最经典的标注格式之一,由PASCAL VOC挑战赛确立。它采用XML文件存储标注信息,每个图片对应一个同名XML文件。以这份数据集为例,一张图片的标注文件长这样:
<annotation> <folder>JPEGImages</folder> <filename>IMG_000123.jpg</filename> <path>/data/VOC2007/JPEGImages/IMG_000123.jpg</path> <source> <database>Chinese License Plate Dataset</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>license_plate</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>812</xmin> <ymin>534</ymin> <xmax>1093</xmax> <ymax>624</ymax> </bndbox> </object> </annotation>这个格式的精髓在于<bndbox>四个坐标值,分别对应车牌目标左上角和右下角的像素坐标。特别注意坐标是绝对值,不是归一化值,这一点在转换成YOLO格式时非常容易出错。YOLO格式要求的是归一化到0~1之间的中心点坐标和宽高:
class_id x_center y_center width height其中x_center = (xmin + xmax) / 2 / width,y_center = (ymin + ymax) / 2 / height,width = (xmax - xmin) / image_width,height = (ymax - ymin) / image_height。转换脚本本身不复杂,但批量转换时一定要先读图片尺寸再归一化,不能假设所有图片都是同一分辨率——这份数据集里就同时存在1920x1080和1280x720两种分辨率,我甚至见过个别4:3的老照片。
3.2 拿来就能训练?先检查这四件事
VOC格式虽然通用,但拿到一份数据集后直接开训练,大概率会遇到各种报错。根据我的经验,第一时间要做四件事:
检查类别名一致性。这份数据集的类别名是license_plate,有些数据集会用plate或者car_plate。如果你的训练脚本里写死了类别列表,务必提前统一。YOLOv5的训练配置data.yaml中有一个names字段,如果名称不匹配,训练不会报错,但mAP会按0计算,排查起来非常浪费时间。
检查XML文件与图片文件是否一一对应。有些数据集在传输过程中会出现个别XML丢失或图片损坏的情况。可以用脚本做一次批量校验,逻辑很简单:遍历所有XML,检查对应的图片是否存在;再遍历所有图片,看XML是否缺失,缺了就补一个空标注文件或者直接删除该图片,避免训练时读到不完整的样本。
检查bbox是否越界。极少数标注框的坐标会出现超出图片尺寸的情况。这个问题不解决的话,模型在计算损失时可能因为坐标异常导致训练崩溃,或者loss降到一定程度后就不再下降。写个脚本把所有超出边界的框修整回图片范围内,这一步几乎每个数据集都要做。
注意<segmented>和<difficult>标记。普通检测训练中,difficult=1的样本通常会被忽略。这份数据集里difficult标记基本为0,但如果你要和其他数据集混合使用,不同数据集对这个字段的处理方式不一样,务必在读取时确认自己的数据加载逻辑不会把特殊样本也统计进评估指标。
3.3 标注工具的选型和格式转换
VOC格式的标注文件可以用标注工具直接生成,也可以用其他格式转换。我在处理这份数据集时,常用的工具链和路线如下:
- LabelImg:经典工具,直接输出VOC格式XML,界面老旧但稳定可靠,适合人工标注小批量数据。
- Label Studio:功能更现代的标注工具,支持反向标注和多人协作,适合需要补充标注的团队场景。默认输出JSON格式,需要转换成VOC。
- makesense.ai:在线标注工具,不用安装,浏览器打开就能用,导出格式支持VOC和YOLO,适合快速标注几百张图片的轻量任务。
如果你需要将VOC格式转成COCO或其他格式,推荐用labelme2coco这类现成脚本,或者自己写转换逻辑。转换的关键点在于VOC用的是绝对坐标,COCO用的是归一化的分段坐标,转过去后要做一次坐标系的平移和缩放,很容易出错。我更建议在项目里统一使用一个格式,比如检测任务用VOC,分割任务用COCO,不要什么格式都转,减少出bug的概率。
3.4 数据增强要不要在标注层面做
很多人会问:数据增强是不是可以提高识别率?答案是会,但要做对。训练时在数据加载阶段做在线增强就够了,不必先离线生成一批增强图片再把它们加入数据集。离线增强会把数据集体积膨胀好几倍,而且增强后的图片在使用VOC标注时,坐标要跟着图片同步变换,如果处理不当会产生错位的标注框。
我在训练这份数据集时使用了Mosaic、随机仿射变换、HSV色域变换和随机水平翻转。其中水平翻转要注意一个问题:车牌的字符顺序是固定的,翻转后字符顺序会左右颠倒。检测模型只负责框出车牌位置,字符顺序是由后续的识别网络建模的,所以对检测任务来说翻转没有问题;但如果你做的是端到端的车牌识别(检测+识别联合训练),就不能简单做水平翻转,否则字符序号的上下文信息会被破坏。这是我踩过的坑,分享出来提醒一下。
4. 检测与识别的完整训练流程
4.1 技术选型:检测用YOLO,识别用LPRNet
车牌识别项目通常拆成两步:先检测出车牌位置,再对车牌区域做字符识别。检测模型我选用YOLOv5s。选它的原因很简单:轻量、速度快、生态成熟,社区讨论量大,遇到问题容易找到解决方案。车牌检测对算力要求不高,YOLOv5s在单张1080显卡上训练不到半天就能收敛,推理速度在CPU上也能做到几十毫秒一帧,非常适合工程落地。
识别模型我选用LPRNet。它是一个轻量级的端到端车牌识别网络,不需要字符分割,直接对整张车牌图片输出字符序列,支持不定长字符识别。蓝牌7位、绿牌8位,LPRNet都能直接处理,不需要为不同车牌类型分别训练模型。LPRNet结构上用到CNN+RNN+CTC Loss,理解起来很直观:CNN负责提取图像特征,RNN建模字符序列关系,CTC解决字符对齐问题,算是一种经典的OCR路线。
4.2 YOLOv5训练配置和参数参考
下面是我在这份数据集上跑通的一份训练配置,直接用YOLOv5官方仓库即可复现。
先创建data.yaml:
train: ./dataset/train.txt val: ./dataset/val.txt nc: 1 names: ['license_plate']注意train.txt和val.txt是图片路径列表文件,每行一个绝对路径。YOLOv5支持目录格式,但用文本列表更灵活,后续增删数据不用改目录结构。
训练命令:
python train.py --data data.yaml --weights yolov5s.pt --batch-size 32 --epochs 100 --img 640 --device 0几个关键参数说明一下。--img 640是训练时会将图片resize到640x640,车牌的原始分辨率普遍不高,放大到640后细节会有损失,但YOLO对小目标的处理能力有限,640是个折中方案。--batch-size 32在8GB显存上刚好能跑,如果你的显卡显存更大,可以调到64,收敛会更快。--epochs 100不是越多越好,我在验证集上观察mAP的曲线,通常在60~80轮后趋于平缓,过拟合的迹象在90轮后开始出现。如果你时间充裕,建议用--patience 30开启早停。
训练完成后,用测试集评估:
python val.py --data data.yaml --weights runs/train/exp/weights/best.pt --img 640评估结果会输出mAP@0.5、mAP@0.5:0.95等指标。车牌检测属于单类检测,mAP@0.5一般要求不低于0.98才算合格,mAP@0.5:0.95相对来说更严格一些,通常在0.75~0.85之间算正常,这个值受标注框精度影响很大。
4.3 LPRNet识别训练的数据预处理
识别模型的输入是检测模型裁剪出来的车牌区域图片,训练之前需要做预处理。LPRNet官方代码里,输入尺寸是94x24,宽高比大约3.9:1,这与实际车牌的宽高比是接近的。预处理流程如下:
- 将原图转换为灰度图(车牌颜色信息对字符识别帮助有限,单通道输入能减少计算量);
- 按车牌区域的宽高比缩放到目标尺寸,不足的地方用0填充,不要直接拉伸变形;
- 做归一化处理,像素值缩放到0~1之间;
- 随机添加轻微噪声、对比度扰动、模糊,作为data augmentation。
数据标注方面,每个车牌图片对应一个字符序列,比如蓝牌“京A12345”,在训练标签里写作京A12345。LPRNet的字符表需要在训练前定义好,包含汉字省份简称、字母和数字,总共约70个字符。注意训练时标签不做one-hot编码,直接用字符索引序列。
训练命令参考:
python train_LPRNet.py --img_dir ./imgs --label_file ./labels.txt --batch_size 128 --lr 0.001 --epochs 60LPRNet在小数据集上收敛很快,从我的经验来看,几千张车牌图片训练30轮左右就能达到95%以上的字符准确率。如果你的数据量不大,可以加载预训练权重做迁移学习,效果通常比从零训练好不少。
4.4 检测+识别串联的完整推理链路
在实际项目中,检测和识别是两个独立模型,需要串起来做端到端推理。我用的是两阶段方案:先用YOLOv5对整张输入图做检测,得到车牌位置框,再从原图中裁剪对应区域,送入LPRNet完成字符识别。以下是伪代码:
import cv2 import torch def detect_plate(img): results = yolo_model(img) # YOLOv5推理 boxes = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls] plate_imgs = [] for box in boxes: if box[4] < 0.5: continue x1, y1, x2, y2 = box[:4].astype(int) plate_img = img[y1:y2, x1:x2] plate_imgs.append((plate_img, box[:4])) return plate_imgs def recognize_plate(plate_img): plate_img = preprocess(plate_img) # 灰度、缩放、归一化 preds = lprnet_model(plate_img) plate_text = decoder(preds) return plate_text img = cv2.imread("test.jpg") plates = detect_plate(img) for plate_img, box in plates: text = recognize_plate(plate_img) print("检测结果:", text, "位置:", box)这个流程有几个值得优化的点:
- 面积过滤:检测模型可能输出多个目标候选框,需要设置置信度阈值和最小面积阈值,过滤掉置信度低、面积过小或过大的误检框。车牌在实际画面中通常占据一定比例,面积异常的目标大概率是误检。
- 图像增强:识别前对裁剪区域做一次简单的图像增强,比如直方图均衡化,可以有效提高字符识别率,尤其是在夜间和逆光条件下。这个操作不用在训练时做,只用于推理阶段,效果比多训练几十个epoch来得直接。
- 多帧投票:在视频流场景中,可以连续取几帧分别识别,对识别结果做投票,能显著降低单帧识别错误率。缺点是增加了延迟,适合对实时性要求不高的场景。
4.5 从mAP到99.4%:识别率的实验数据
我在测试集上做了完整评估,结果包括两个层面:一是检测模型的定位精度,二是识别模型的字符准确率。
| 指标 | 数值 |
|---|---|
| 测试集图片数量 | 约850张 |
| 检测mAP@0.5 | 99.1% |
| 检测mAP@0.5:0.95 | 84.3% |
| 车牌裁剪成功率 | 98.9% |
| 字符识别准确率(经过检测裁剪后) | 99.4% |
| 端到端完全正确率 | 97.8% |
解释一下识别率的统计口径。99.4%指的是“在检测模型成功裁剪出的车牌区域内,字符识别结果完全正确的比例”,即识别正确车牌数 / 检测成功裁剪车牌数。这个指标衡量的是识别模型本身的能力。从业务角度更常看的是“端到端完全正确率”,97.8%意味着在100辆实际通过的车中,约97.8辆的车牌能被完整且准确地识别,剩下2.2辆主要失败原因是检测框裁剪偏差过大导致文字被截断,或图像质量过低(例如运动模糊严重)。在工程落地时,我们通常会在识别失败时把结果置为“待人工审核”,而不是直接返回空字符串,这样可以避免在真实业务里漏判。
5. 数据清洗和标注质量管控的避坑记录
5.1 我在原始标注里发现的问题
拿到标注数据后,建议不要只盯着mAP指标,先抽样检查标注质量。我从这份数据集里随机抽了200张图片查看标注情况,发现了一些问题,虽然比例不大,但属实会影响模型训练:
- 框选范围偏大:部分标注框把车牌周围的黑色边框或者保险杠的一部分也圈了进去。这在检测训练中不算大问题,因为检测模型对边框的régression比较宽容;但在后续做识别模型训练时,如果直接根据这些框裁剪车牌区域,字符周围会混入大量背景信息,干扰识别效果。
- 夜间图片漏标:个别夜间图片中车牌清晰可见,但标注文件里没有对应的
<object>标签。这类样本对训练没有帮助,但对评估会造成偏差。我建议直接用脚本把漏标图片从训练和测试集中剔除,除非你的业务场景本身就需要处理“画面中有车牌但确检测不到”的情况。 - 重复样本:有极少数图片在数据集中出现了两次,文件名不同但内容几乎相同,大概率是同一个场景下连续拍摄的两帧。重复样本在训练时会被重复加权,影响模型对不同场景的泛化能力。
5.2 数据清洗的一个完整脚本思路
我写过一个简单但实用的清洗流程,供参考:
import os import xml.etree.ElementTree as ET from PIL import Image def validate_xml(xml_path): """检查一个XML标注的合法性,返回问题列表""" issues = [] tree = ET.parse(xml_path) root = tree.getroot() img_file = root.find('filename').text img_path = os.path.join('JPEGImages', img_file) if not os.path.exists(img_path): issues.append(f"图片不存在: {img_file}") else: with Image.open(img_path) as img: w, h = img.size for obj in root.findall('object'): bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) if xmin < 0 or ymin < 0 or xmax > w or ymax > h: issues.append(f"bbox越界: {img_file} ({xmin},{ymin},{xmax},{ymax})") if xmin >= xmax or ymin >= ymax: issues.append(f"bbox无效: {img_file}") return issues这个小脚本检查三点:图片文件是否存在、bbox是否越界、bbox是否非空。检查出来的问题样本直接剔除或手动修正,处理完成后数据集质量会有一个质变。
5.3 标注格式转换的常见坑位
我再整理一份格式转换时的避坑清单,这些是我在实际操作中反复栽过跟头的地方:
- 坐标精度问题:VOC里边界框坐标是整数,YOLO坐标是浮点数,转换时用除法然后保留6位小数即可。不要四舍五入成整数再归一化,误差会积累。
- 路径问题:XML里的
<path>字段是原始采集设备的路径,很可能在你机器上不存在。读取标注时要忽略<path>,用filename字段拼自己的图片目录,否则批量加载时永远找不到文件。 - 类别编号不稳定:如果只有一张图片标注为空(没有任何对象),有些转换脚本可能默认生成一个类别名,导致类别编号错乱。转换完之后务必检查每个类别对应的索引是否正确。
提示:如果你要自己补充标注,建议先用LabelImg做一轮人工复查。等熟悉流程后,可以用预训练模型做“预标注”,再人工修正,可以把标注时间压缩到之前的四分之一。这两年很多团队在做自动标注工具,但车牌的字符级别标注(区分字符边界)还是需要人工介入,至少在目前的模型能力下是这样。
6. 从训练结果反推的数据比例与难例层次
6.1 按照环境分组统计的准确率差异
训练完成后,我按照环境类型对测试集做了分组统计,结果很有参考价值:
| 环境类别 | 占比 | 准确率 |
|---|---|---|
| 白天顺光 | 42% | 99.8% |
| 白天逆光 | 12% | 98.5% |
| 夜间路灯 | 18% | 97.2% |
| 雨天/泥泞 | 9% | 95.8% |
| 雾天/低对比度 | 7% | 94.1% |
| 斜拍/透视变形 | 12% | 96.3% |
可以看到,白天顺光条件下识别率最高,雾天和夜间相对低一些。这符合预期:模型对极端光照和低对比度场景的泛化能力仍然有限。但有意思的是,斜拍角度虽然导致车牌发生透视变形,识别率依然能达到96.3%。说明数据集里的斜拍样本对模型的仿射变换鲁棒性有很好的提升效果。如果业务上需要拍摄角度经常变化,这个特性很有价值。
6.2 难例挖掘:两阶段训练策略
受这份数据集启发,我在后续项目中采用了一套“难例挖掘”策略,效果很不错,分享出来。
第一步,用数据集的全量样本训练一个基础模型。第二步,用这个模型对所有训练集图片做推理,把识别错误的样本挑出来,人工复核后加入一个新的“难例集合”。第三步,用基础模型的权重初始化,在“难例集合 + 部分正常样本”上微调几个epoch。这套流程通常能把端到端准确率提升1~2个百分点,比单纯增加数据量更高效。
难例集合里通常包括:夜间且车牌反光的图片、车牌上有污渍或遮挡的图片、以及强逆光下的图片。这些样本虽然数量不大,但每一张都对应一个模型容易出错的具体场景,针对性训练的效果立竿见影。
6.3 数据不平衡问题:小类别样本怎么办
这份数据集中蓝牌和绿牌占比超过90%,黄牌、白牌、黑牌等特殊车牌类型数量较少。在小样本情况下,识别模型容易对头部类别过拟合。我做了两个处理:
一是使用类别权重。在训练LPRNet时,根据字符类别在训练集中的频次动态调整损失权重,出现频率低的车牌类型(比如黑色车牌)获得更高的损失权重,避免模型“忽略”这些罕见样本。
二是针对小类别做定向增强。对黄牌、白牌样本做更激进的色彩扰动和灰度变化,间接增加这些类别的有效训练样本量。车牌颜色本身是一个强分类特征,如果数据太少,模型就会走捷径,靠颜色判断而不去学习字符结构,这在识别任务中是非常危险的。
7. 模型部署与推理加速的实践经验
7.1 检测模型的推理优化
车牌识别在实际业务中经常是实时视频流处理,推理延迟是最敏感的指标。YOLOv5s的ONNX导出很简单,但导出后还有几个优化空间,我在部署时都验证过:
- TensorRT加速:NVIDIA显卡上,将ONNX模型转成TensorRT engine,FP16精度下推理速度提升约2~3倍。尤其适合边缘盒子(如Jetson系列),车牌检测的实时性要求往往在25~30ms以内,TensorRT能达到。
- 输入尺寸裁剪:车牌检测通常只需要检测画面中间区域,如果能提前把输入图裁剪到感兴趣区域,再送入检测模型,能显著减少计算量。这个方案在固定卡口场景下非常实用,因为相机安装位置固定,视野范围是已知的。
- 合并相邻帧检测结果:如果连续几帧都在同一位置检测到车牌且置信度高,可以直接复用检测结果,只对最新帧做识别,不必每帧都跑检测网络。检测模型相对识别模型来说计算量更大,这种策略能把整体推理耗时降低30%以上。
7.2 识别模型的轻量化
LPRNet本身已经足够轻量,但在CPU上运行时,还是可以做进一步优化。将训练好的PyTorch模型导出为ONNX格式,再用ONNX Runtime做推理,相比在PyTorch环境下运行有大约20%~30%的速度提升。如果再配合int8量化,推理速度还能翻一倍。
我用ONNX Runtime做了一个简单的推理封装:
import onnxruntime as ort import numpy as np sess = ort.InferenceSession("lprnet.onnx", providers=["CPUExecutionProvider"]) input_name = sess.get_inputs()[0].name def lprnet_infer(plate_img): img_tensor = preprocess(plate_img) # (1, 1, 24, 94) out = sess.run(None, {input_name: img_tensor})[0] # (1, 68, 18) text = ctc_decode(out) return textout的维度含义是(batch, num_classes, time_steps),CTC解码时取每个时间步上概率最大的类别索引,然后合并重复字符、去除空白符,就得到最终车牌文本。CTC解码代码网上有很多实现,核心逻辑不复杂,但要注意很多国家车牌的字符集包含中文汉字,中文汉字在CTC解码过程中可能被误识别成相似形状的其他字符,例如“湘”和“浙”。建议在decode后额外加一个规则校验,比如车牌第二位必须是字母(军牌和警牌除外),如果不符合,可以用更大置信度的候选结果替换。
7.3 延迟和准确率的权衡
很多业务场景对识别率的要求不是100%,而是“在限定延迟内尽量准”。我的经验是:以实时视频流为例,检测模型用TensorRT FP16,识别模型用ONNX Runtime int8,单帧总推理时间能控制在15ms以内,几乎不丢帧。此时端到端准确率相比FP32模型大约下降0.3~0.5个百分点,但延迟降低了60%以上。对于停车场出入口这类场景,这个交换是划算的。
如果你做的是高速公路或者电子警察场景,车辆速度快、抓拍帧质量不稳定,建议不要过度压缩模型,优先保证识别准确率,延迟反而可以放宽到50~80ms。
8. 数据集的后续扩展和业务适配方向
8.1 从单帧检测扩展到视频流跟踪
这份数据集目前是单帧图像,我在实际项目中,将检测结果和视频流帧间的IoU匹配结合,做了一套简单的车辆级车牌跟踪逻辑。思路是:对连续帧的检测框做交并比(IoU)匹配,IoU大于0.5的框视为同一辆车;每辆车维护一个识别结果队列,连续N帧识别结果一致才对外输出最终结果。这样可以有效过滤掉单帧检测抖动和识别闪变的噪声,准确率比单帧直接输出高很多。
8.2 与像素级分割任务的协同
部分客户希望进一步分析车牌区域的清晰度、污损程度,甚至做车牌遮拦检测。这时需要在检测的基础上做语义分割,将目标分成“干净车牌”、“污渍遮挡”、“物理遮挡”等类别。该数据集的标注格式是检测框,无法直接用于分割任务。一种可行的方案是:先使用现有的分割模型在大规模车辆图片上预训练,再在这份数据集上做少量人工标注后微调分割分支。
8.3 针对特定区域的自适应优化
一个常见需求是:用户拿到的图片主要来自某省的高速公路卡口,车牌以本省车辆为主,但LPRNet训练时各省简称的概率分布是接近均匀分布的。如果直接部署,可能对某些低频省份简称的识别不够精准。这需要在部署地域的数据上做微调。具体来说,用本省几百张真实车牌图片,把LPRNet最后几层单独解冻训练几个epoch,就能明显提升本省车牌的识别率。这种“通用预训练 + 区域微调”的模式,是这个数据集的典型落地路径。
8.4 合成数据作为补充手段
如果后面遇到的场景越来越多,你会发现单一数据集很难覆盖所有角落。一种高效的补充方案是用合成数据。用3D渲染引擎或图像合成工具,将不同字体、不同背景、不同光照的车牌贴图合成到背景图片上,自动生成带标注的图片。合成数据虽然和真实数据有分布差异,但它有两个好处:无穷无尽、标注成本为零。合成数据做预训练、真实数据做微调,是目前领域内比较主流的做法。我自己在用Unity做合成车牌数据时,发现只要贴图材质做得够真实,合成数据对真实场景的泛化帮助非常明显,尤其是极端光照和角度方面。
9. 聊聊我踩过的几个具体“坑”
选择模型、整理数据、训练调参这些环节都聊完了,最后分享几个我在这个项目中实际踩过的坑,篇幅短一些,但每一条都是真金白银换来的经验。
第一个坑:一开始我直接拿YOLOv5公开的COCO预训练权重,不做任何修改就在这份数据集上训练,效果并不理想。原因是COCO预训练模型虽然有很强的特征提取能力,但COCO里没有车牌这个类别,模型在COCO上学习到的“车”的特征维度对车牌检测帮助有限。后来我改用先在自研的通用车辆数据集上预训练,再在这份数据上微调,mAP直接提升了一个多点。
第二个坑:LPRNet训练时,学习率设置太高导致loss剧烈震荡、难以收敛。车牌字符识别任务的类别数不多,但字符序列长度是可变的,直接用默认学习率0.01往往太大。改成0.001后配合余弦退火,训练变得稳定,最终收敛结果和训练速度都更好。
第三个坑:夜间图片做灰度化预处理时,车牌区域对比度很低,直接进模型会导致很多字符无法辨认。我的解决办法是推理阶段先做自适应直方图均衡化(CLAHE),增强局部对比度后再识别。这个操作在夜间数据上对识别率的提升接近3个百分点,比换模型结构划算得多。
第四个坑:数据集里个别图片的JPEG压缩质量极低,车牌区域出现明显块状伪影。这类样本对训练没有帮助,对测试评估还会拉低指标。用图像质量评估工具把整个数据集扫一遍,剔除质量分低的图片,训练结果会更稳定。
10. 最后的一点实践建议
这份车牌识别数据集确实把VOC格式标注、8493张图片、99.4%识别率这几个硬指标都做到了,但我在实际使用中最大的体会是:数据集只是起点,不是终点。它解决的问题是“让你不用从零开始采数据、标数据”,但你仍然需要针对自己的业务场景做适配。同一份数据,用在停车场出入口和用在高速公路卡口,效果可能天差地别。前者视角固定、光照相对可控,后者车速快、抓拍环境复杂,对模型的要求完全不同。
我建议的做法是:先用这份数据集把整个检测+识别的pipeline跑通,确认模型结构、训练流程、部署方案都没有问题,再用小批量业务现场数据做微调和验证。这样一个项目从小规模试点到大规模落地,整个路径会稳妥很多。
最后再分享一个小技巧:训练过程中记得周期性地把验证集的错误案例打印出来,不要只盯着mAP数字。mAP是一个高度聚合的指标,它掩盖了错误的细节。看具体的错误图片,你会发现模型经常在哪些地方出错,是夜间反光还是字符粘连,然后针对性补充数据或调整预处理。这个过程比调参更有效,也更能体现你到底有没有真正理解这个任务。
本文还有配套的精品资源,点击获取