news 2026/9/28 18:39:40

车牌识别数据集+YOLOv11训练全攻略:从标注格式到避坑实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
车牌识别数据集+YOLOv11训练全攻略:从标注格式到避坑实践

简介:面向计算机视觉车辆检测场景的中国车牌号识别数据集,覆盖车牌数字与字母的常见组合,标注格式已适配YOLOv11,可直接用于目标检测模型的训练、验证与迁移学习,适合正在做智慧交通、车辆识别或毕业设计的开发者与学习者。压缩包共2000个文件,约17.18MB,其中542张JPG车牌图像配有1457个TXT格式的YOLO标注文件,另含1个YAML配置文件,可直接写入YOLOv11项目读取类别与路径信息,整体目录结构紧凑清晰。该数据集已有950人学习使用,数据规模对于快速验证模型和算法调优比较合适。下载后即可获得已对齐的图像与标注对,免去自行采集样本、手工标注和格式转换的繁琐工作,方便直接开展车牌识别训练、评估与二次标注,尤其适合需要尽快跑通YOLOv11流程的中级开发者。

1. 车牌识别数据集:1458张YOLOv11标注图,能解决什么问题

做车牌识别,最耗精力的往往不是模型训练,而是数据本身。这个数据集把最脏的活提前干完了:1458张中国车辆车牌图片,全部配好YOLOv11格式的txt标签,识别对象覆盖数字0-9和字母A-Z,共36个类别。解压后改一个data.yaml,yolo detect train就能跑起来,是速度最快的一条验证路径。适合三类人:被标注工具折磨的车牌识别开发者、用YOLO系做目标检测的毕设课设项目、想在YOLOv11上快速验证训练流程的算法工程师。注意边界:数据集不含省简称汉字,完整输出“粤B12345”这种结果,还需要后续补汉字标注或后处理,这一点先记住,后面会展开讲。下面按数据格式、训练配置、踩坑、验证增强四块拆透,保证拿到手当晚上手。

2. 数据格式与目录结构:1458张图是怎么组织的

2.1 图片文件名、标签文件与对应关系

数据集里的图片文件名长这样:

1481_jpg.rf.14d0c1c37bb508dc94e22790b98dfb24.jpg 1477_jpg.rf.bf08c1582a9527d951c6f56484c3f57e.jpg

带rf后缀是从Roboflow平台导出的典型特征,jpg是原图格式,后面那串是防止重名的uuid。文件名本身不含标注信息,不能从名字猜车牌内容。每张jpg对应一个同名txt,txt里存着YOLO格式的标注行。标准的一行长这样:

8 0.4862 0.5513 0.1854 0.0652

五个字段分别是:类别id、框中心x坐标、框中心y坐标、框宽、框高。x、y、w、h全部是归一化到0-1之间的相对值,和图片实际像素无关。这个设计是为了适配不同分辨率输入,YOLO训练时统一缩放到imgsz指定尺寸,标注不会变形。

下载解压后第一件事,是确认图片和标签是不是一一对应。我一般先跑一段快速统计脚本:

import os from pathlib import Path img_dir = Path("images") label_dir = Path("labels") imgs = list(img_dir.glob("*.jpg")) labels = list(label_dir.glob("*.txt")) print(f"图片数量: {len(imgs)}") print(f"标签数量: {len(labels)}") missing = [] for img in imgs: txt = label_dir / (img.stem + ".txt") if not txt.exists(): missing.append(img.name) print(f"缺标签的图片: {len(missing)}") for name in missing[:10]: print(name)

脚本逻辑很简单:遍历images目录下所有jpg,用img.stem取出去掉扩展名的主文件名,再去labels目录找同名txt。打印前三行是典型的rf前缀文件名,从uuid前面那几位能看出原始命名规律。参数上只有img_dir和label_dir两个路径需要改成你实际解压的目录。缺标签数量如果大于0,先别急着训练,后面第4章有对应处理方案。

2.2 车牌识别里的类别设计:为什么是36类

中国车牌由汉字省简称、字母、数字三部分组成,但这份数据集的标注范围收敛在数字和字母上:数字0-9共10类,字母A-Z共26类,加起来正好36类。省简称不在标注方案里,这是这个资源最重要的一个边界。

类别id怎么分配?常见做法是数字0-9对应id 0-9,字母A-Z对应id 10-35。这样一张“京A·12345”的车牌,检测目标会被拆成“A”“1”“2”“3”“4”“5”六个独立检测框,而不是一个整体车牌框。这种按单字符检测的方案在车牌识别里很常见,好处是字符类别固定、样本均衡,坏处是后续还得把单字符结果拼回完整车牌。

为了训练时能看懂类别,建议先把映射表存一份。命令行的类别显示和data.yaml里的names都靠它:

类别id内容类别id内容
0数字018字母I
1数字119字母J
2-9数字2-920-25字母K-P
10字母A26-35字母Q-Z
11-17字母B-H--

注意字母I和数字1、字母O和数字0在视觉上高度相似,这是后面训练时最容易翻车的混淆点,第4章会专门说怎么处理。

2.3 解压后先理顺目录结构

下载的压缩包打开后,图片可能全堆在一个文件夹里,旁边还带着.classes、_darknet.labels这类配置文件。直接拿去训练不行,YOLO对目录结构有硬性要求。先整理成标准结构:

dataset/ ├── data.yaml ├── train/ │ ├── images/ # 训练图片 │ └── labels/ # 训练标签 ├── valid/ │ ├── images/ # 验证图片 │ └── labels/ # 验证标签 └── test/ ├── images/ # 测试图片 └── labels/ # 测试标签

如果原始图片都在一个目录,写个简单脚本按比例拆分。这里把原图放在all_images、标签放在all_labels:

import random import shutil from pathlib import Path src_imgs = Path("all_images") src_labels = Path("all_labels") out = Path("dataset") for sub in ["train", "valid"]: (out / sub / "images").mkdir(parents=True, exist_ok=True) (out / sub / "labels").mkdir(parents=True, exist_ok=True) imgs = sorted(src_imgs.glob("*.jpg")) random.seed(42) random.shuffle(imgs) split = int(len(imgs) * 0.9) train_imgs = imgs[:split] valid_imgs = imgs[split:] for img in train_imgs: shutil.copy2(img, out / "train" / "images" / img.name) lbl = src_labels / (img.stem + ".txt") if lbl.exists(): shutil.copy2(lbl, out / "train" / "labels" / lbl.name) for img in valid_imgs: shutil.copy2(img, out / "valid" / "images" / img.name) lbl = src_labels / (img.stem + ".txt") if lbl.exists(): shutil.copy2(lbl, out / "valid" / "labels" / lbl.name)

random.seed(42)固定随机种子,保证每次划分结果一致,方便复现。split = int(len(imgs) * 0.9)是训练集占比,图片多可以提到0.95,1458张图用0.9比较稳,验证集还能剩下140多张。标签文件缺失的图片我会直接跳过而不是报错,因为YOLO反正也读不到对应标注。

2.4 标签自检:坐标越界与类别越界

标注文件是从Roboflow导出的,理论上格式干净,但保不齐有个别文件损坏或者被二次编辑过。训练前扫一遍标签,能省掉半夜训练报错的麻烦:

from pathlib import Path def check_labels(label_dir: Path, nc: int = 36): bad_lines = 0 for txt in sorted(label_dir.glob("*.txt")): for line_no, line in enumerate(txt.read_text().splitlines(), 1): parts = line.split() if len(parts) != 5: print(f"{txt.name}:{line_no} 字段数异常: {line}") bad_lines += 1 continue cls = int(parts[0]) coords = [float(v) for v in parts[1:]] if cls >= nc: print(f"{txt.name}:{line_no} 类别id越界: {cls}") bad_lines += 1 if any(c < 0.0 or c > 1.0 for c in coords): print(f"{txt.name}:{line_no} 坐标越界: {line}") bad_lines += 1 print(f"扫描完成, 异常行数: {bad_lines}") check_labels(Path("labels"))

字段数必须等于5,多一个空格拆出空串都不行;类别id必须小于nc,越界说明混入了另外一套标注体系的类别;归一化坐标必须在0-1之间,越界训练时会被当成非法框丢掉。这三个检查覆盖了90%以上的数据格式问题。

3. 用YOLOv11把模型训起来:从配置到推理

3.1 环境准备

训练基于Ultralytics框架,装一个包就行:

pip install ultralytics

版本上建议直接用最新的,Python环境最好3.10以上。装好后先验证CUDA对GPU是不是可用:

import torch print(torch.cuda.is_available())

输出True走GPU,False也没关系,1458张图的数据量用CPU跑起来就是慢一些,一个epoch可能要几分钟到十几分钟,选yolo11n.pt这种nano模型还能忍。训练前确认下磁盘剩余空间,中间权重和日志大概占几百MB,这个体量不吃紧。

3.2 编写data.yaml

YOLOv11沿用Ultralytics的配置方式,data.yaml是唯一必须手写的文件。按照第2章整理的目录结构来写:

path: ../dataset train: train/images val: valid/images nc: 36 names: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

path是数据集的根目录,用相对路径时以当前工作目录为基准,我习惯把data.yaml放在dataset外面一层,所以写成../dataset。train和val的值是相对于path的目录,YOLO会自动去对应目录下找images和labels子文件夹。nc: 36和names列表顺序必须严格对应,names里的索引就是类别id,第0个元素对应id0,第35个元素对应id35。如果把字母Z放错位置,训练出来的模型会把所有的Z都认成倒数第二个字母。

3.3 训练命令与关键参数

最简训练命令是这样:

yolo detect train model=yolo11n.pt data=data.yaml epochs=100 imgsz=640 batch=16 device=0

第一次运行会自动下载yolo11n.pt预训练权重,网络慢的话可以先手动下载放到当前目录。epochs=100对1458张图的数据集是合理起点,车牌字符检测任务不算复杂,一般60-80轮mAP就稳定了,100轮是为了看后期是否过拟合。imgsz=640是输入分辨率,车牌字符属于小目标,后面避坑章节会讲怎么调。device=0指定GPU,没有GPU就改成device=cpu。

显存不够就降batch,8G显存跑batch=16加imgsz=640有点紧张,降到batch=8或者imgsz=512都能缓解。注意batch太小时BN层统计不稳定,一般不低于4。

训练结束后,结果在runs/detect/train/目录下:

runs/detect/train/ ├── weights/ │ ├── best.pt # 验证集mAP最高的权重 │ └── last.pt # 最后一轮权重 ├── args.yaml # 本次训练的全部参数 ├── results.csv # 每轮指标 └── confusion_matrix.png

best.pt是后续推理和导出要用的,last.pt通常是训练中断后恢复用的。

3.4 训练过程看什么

CLI界面每轮会输出一串指标:box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。对车牌识别来说,重点盯cls_loss和mAP50。如果cls_loss在某一轮突然升高,大概率是学习率骤降或某些难样本在起作用,不一定是模型坏了。我习惯看训练结束后的results.png曲线,确认mAP50在最后几十轮是平缓上升而不是振荡。

best.pt按mAP50选择,不是按总损失选,这一点很多人忽略。训练集准确率再高也没用,验证集上的mAP才是真实水平的估计。

3.5 用训练好的模型推理验证

训练完拿几张没参与训练的车牌图验证一下:

yolo detect predict model=runs/detect/train/weights/best.pt source=test/images save=True

save=True会把带预测框的结果图存到runs/detect/predict/。打开图片看三件事:一是框的位置是不是刚好包住单个字符,二是类别标签是不是对的,三是置信度分数。如果置信度普遍低于0.5,说明训练还不充分或者数据有干扰。

后续要部署的话,先导出ONNX:

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640

ONNX格式可以转TensorRT或ONNX Runtime,这一步对边缘设备部署是标准流程。imgsz要和训练时保持一致,否则尺寸变化会影响精度。

4. 避坑手册:训练车牌识别时的五个常见问题

4.1 训练时提示No labels found

现象:启动训练后日志出现WARNING No labels found in .../train,训练过程不报错但loss始终不降。

原因:YOLO找标签的路径是train目录下与images平级的labels文件夹,如果标签直接在train下或者放在images里,就会扫不到。另一个常见原因是标签扩展名写成了.txt但实际文件是.txt带空格。

解决:先检查目录结构是否符合第2.3节的规范,再用第2.1节的脚本核对图片与标签数量。两者都正常,就用find train -name "*.txt" | head看下标签文件是不是真的在train/labels下。目录结构没问题最后检查data.yaml的path相对路径是不是写对了,../dataset少一个点就找不到目录。

4.2 标签类别id越界导致训练崩溃

现象:训练开始不久报错IndexError: index 36 is out of bounds for axis 0 with size 36,或者loss直接变成nan。

原因:数据集中混入了标注时多余类别,比如某个字符被标成了id36或更大,超出nc范围。Roboflow导出数据偶尔会把背景类或者冗余类也带进来。

解决:用第2.4节的标签自检脚本全量扫一遍,把所有cls >= nc的标签行找出来。处理方式两种:如果多余类别数量很少直接删掉这些行,如果数量较多说明类别体系对不上,要重新梳理映射关系。我遇到过一次标签里混了汉字省简称的标注,id直接是36,删掉后发现是标注工具残留。

4.3 检测框框住整个车而不是单个字符

现象:推理结果里矩形框包住了整块车牌,甚至框住了半个车头,字符之间没有独立框。

原因:这是标注粒度问题。训练数据里如果一部分框比较大、包住了几个相邻字符,模型学到的框尺度就不稳定。YOLO的anchor会根据标注框的宽高分布自动调整,但训练标签里大小尺度差距太大时,小字符目标会被大的候选框盖过去。

解决:统计标签框的宽高比,把明显异常的大框过滤掉重标。检查方法是写脚本统计每个框的w和h分布,正常字符框的宽高比集中在0.4到0.8之间,如果出现w超过0.3(归一化后)的框,大概率是标注时框选了多个字符。对于少量异常样本,可以直接删掉这些图片,让模型学剩下的干净数据。

4.4 数字0和字母O、数字1和字母I识别混淆

现象:训练指标看起来不错,推理时数字0偶尔被识别成字母O,数字1被识别成字母I。这类混淆在单车牌多字符场景下特别影响整牌拼接结果。

原因:视觉上O和0几乎一样,1和I只有一个衬线区别,模型的特征提取本身就容易混淆。加上数据集中0和O、1和I的出现频率不平衡,模型对少数类别的决策边界就偏向多数类。

解决:常规做法是在后处理阶段做映射,比如识别结果为O时输出为0,识别结果为I时输出为1。中国车牌字符集中实际上不使用字母I和O,所以可以放心把这两类的预测结果都映射回数字。另一个方向是训练时加大cls_loss权重,但实测效果不如后处理来得直接。如果数据量够,把字母O和I的样本单独挑出来做数据增强,也能缓解部分混淆。

4.5 关闭翻转增强:车牌镜像问题

现象:训练时mAP上升缓慢,验证集指标始终比训练集低一大截,而且推理时经常把字符方向弄反。

原因:YOLOv11默认开启左右翻转增强,fliplr=0.5意味着每两张图里有一张会被水平镜像。车牌上的字符翻转后变成反写形态,“B”翻转后视觉上像左右颠倒的B,模型被迫同时学习正写和反写两种特征,等于把任务难度翻倍。

解决:训练时显式关闭翻转增强:

yolo detect train model=yolo11n.pt data=data.yaml epochs=100 imgsz=640 batch=16 fliplr=0.0

fliplr=0.0关闭水平翻转,对车牌识别几乎是必设参数。这一点在文档里不容易察觉,默认行为也不会在日志里提示,是典型的黑匣子坑。从那以后我每次训练车牌数据都强制检查参数配置里有没有关闭fliplr。

5. 验证与增强:让模型在真实场景更稳

5.1 从混淆矩阵和PR曲线读结论

训练生成的runs/detect/val/confusion_matrix.png是判断模型真实水平的第一手材料。横纵轴都是36个类别,对角线上的值越高越好。重点看非对角线区域的集中点:如果大部分错误集中在小范围,比如0和O之间,后处理映射就能解决;如果错误分散在多个字符间,说明数据量不足或者图像质量太差,靠后处理兜不住,需要补数据。PR曲线主要看拐点位置,拐点越靠近右上方说明模型在低置信度下也能保持较高准确率,适合部署时把置信度阈值调低来提升召回率。

5.2 车牌场景的数据增强建议

除了关闭fliplr,YOLOv11的数据增强参数还有几个值得动。hsv_h和hsv_s控制色调和饱和度扰动,默认值0.015和0.7基本不用改,车牌颜色在蓝底白字、黄底黑字之间变化,这点扰动有助于泛化。mosaic默认开启,四张图拼一张,对检测小目标有帮助,但如果车牌图里部分字符被切到拼接边缘,反而会引入无效标注。显存够的话保持默认,显存紧张就设mosaic=0.5降低概率。translate和scale可以稍微开大点,模拟摄像头在不同距离拍到的车牌尺寸变化。

从效果验证来讲,最终看的不是训练集loss,而是拿一批真实拍摄的、没有参与训练的车牌图跑一遍,统计单字符识别准确率和整牌拼接成功率。单字符准确率达到95%以上,整牌正确率一般能到90%左右,剩下的误差基本发生在模糊图和极端角度图上。

这个数据集作为起步资源完全够用,但真实场景里遇到新能源绿牌、双层车牌、夜间反光,还是要自行扩充数据。回到开头说的边界:汉字省简称这个缺口是肉眼可见的。如果项目要求输出“粤B12345”这样的完整车牌,第一步先在这个数据集上把36类字符模型训稳,第二步单独收集省份汉字样本重训一个汉字分类模型,第三步在后处理里按位置拼接。我自己的习惯是先把单字符检测做到稳定,再考虑端到端的车牌识别方案,因为单字符方案的每个环节都能独立验证,出问题也好定位。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 18:38:58

Zotero与Word交叉引用实战:从引文域到书签的完整指南

用Zotero管文献写了五年论文&#xff0c;最常被同事和学生追问的一个问题就是&#xff1a;为什么我在Word里插入的Zotero引用&#xff0c;不能像正文里的图、表、章节那样做交差引用&#xff1f;点了“交叉引用”菜单半天没反应&#xff0c;好不容易插进去格式又乱成一团。今天…

作者头像 李华
网站建设 2026/9/28 18:37:51

1458张车牌数据集+YOLOv11训练全流程:从标签检查到部署避坑

简介&#xff1a;中国车辆车牌号识别数据集面向计算机视觉目标检测项目开发者与学习车牌识别的初学者&#xff0c;可用于训练YOLOv11等模型&#xff0c;重点覆盖中国车牌数字与字母的识别任务。资源包共2000个文件&#xff0c;核心构成为1457个txt标注文件和542张jpg图片&#…

作者头像 李华