
简介面向车牌检测与识别的YOLO格式图像数据集适合目标检测研究者、计算机视觉开发者以及智能交通算法工程师使用可直接用于车牌定位、车牌识别等任务的模型训练、效果评估与算法比较。资源压缩包共2000个文件主要包含jpg原始图像、txt格式的YOLO标注文件以及yaml类别配置文件整体大小约147.47MB。数据集内含1695张高质量车牌图像样本覆盖城市道路、高速路口、停车场等多种场景标注信息采用YOLO文本格式提供类别标签与归一化边界框坐标可配合主流YOLO检测框架直接使用兼顾不同光照与拍摄角度有助于提升模型的泛化能力。当前已有132人学习适合需要真实车牌数据支撑深度学习项目的研究者与开发者可广泛应用于高速公路监控、停车场收费、城市交通管理及安防侦查等场景中的车牌检测、识别与精准定位。1. 先搞懂这个车牌图像数据集1695张图到底能训练出什么做车牌图像识别的开发者应该都有体会模型结构好解决数据才是真正的门槛。手头这份车牌图像数据集一共 1695 张图片标注采用 YOLO 格式每张图对应一个 txt 标签文件里面写的是类别 id 和归一化后的边界框坐标。它的定位很直接省掉你打开 labelimg 逐张框选的时间拿来就能训练对象检测模型。适合两种人刚入门 YOLO、想用现成标注数据把整个训练流程走通的新手已经在做智能交通、停车场、安防监控需要快速补充训练数据的同学。注意它解决的是“车牌在哪里”的检测问题不是“车牌号码是什么”的识别问题后者还要接一层 OCR。2. YOLO格式标注拆解从标签文件到坐标校验脚本YOLO 格式能流行是因为它足够简单每个 txt 文件按行存储目标一行一个框格式固定为class x_center y_center width height。四个坐标值全部归一化到 [0,1]也就是用像素坐标除以图片宽高后得到的小数。这样做的好处是训练时不用关心原始分辨率是 1920 还是 1280模型读到的都是统一尺度。这份数据集的 1695 张图来自多个视频抽帧和户外拍摄典型结构是一张 jpg 对应一个相同主文件名的 txt。有的目录把图片和标签放一起有的分成 images 和 labels 两个目录训练框架按文件名前缀自动找对应标签。无论哪种组织方式先确认配对关系永远比直接开训省时间。2.1 标记文件与图片的对应关系拿到资源后第一步是确认 images 和 labels 里的文件能不能一一对上。图片名类似video11_2180.jpg标签文件就是video11_2180.txt。如果目录里混进没有标签的图片训练时 Ultralytics 会报警告但不会告诉你具体是哪一张排查起来很烦。ls -1 images | head -5 ls -1 labels | head -5先看两个目录前几个文件名确认命名风格一致。这只是肉眼检查更稳妥的方式是用脚本算差集。import os from pathlib import Path img_dir Path(images) lab_dir Path(labels) img_names {p.stem for p in img_dir.glob(*.jpg)} lab_names {p.stem for p in lab_dir.glob(*.txt)} print(只有图片没有标签:, len(img_names - lab_names)) print(只有标签没有图片:, len(lab_names - img_names))代码用集合差集找出配对失败的文件。p.stem取出不带后缀的文件名所以video11_2180.jpg变成video11_2180。差集结果为 0 说明配对完整大于 0 就要检查是不是有图片漏标或者 labelimg 导出时生成了空 txt。这套检查我每次拿到新数据集都会跑一遍别看简单真能拦住后面训练脚本莫名其妙的崩溃。2.2 统计类别与边界框分布第一时间发现脏数据YOLO 格式数据虽然简单但不代表它干净。视频帧抽出来的图片经常出现空标签、坐标越界、类别 id 不一致。直接喂给训练框架轻则指标异常重则训练直接中断。所以第二步是扫描所有标签文件统计类别和坐标范围。import numpy as np from pathlib import Path label_dir Path(labels) all_boxes [] empty_files [] for txt in label_dir.glob(*.txt): lines txt.read_text().strip().splitlines() if not lines: empty_files.append(txt.name) continue for line in lines: parts line.split() if len(parts) ! 5: continue cls, xc, yc, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) all_boxes.append((cls, xc, yc, w, h)) arr np.array(all_boxes) print(总标注框数:, len(arr)) print(类别 id 分布:, np.unique(arr[:, 0], return_countsTrue)) print(空标签文件数:, len(empty_files)) print(坐标范围检查: min{:.3f} max{:.3f}.format(arr[:, 1:5].min(), arr[:, 1:5].max()))这段逻辑是读每个 txt 后按空格切分成 5 个字段类别 id、中心点 x、中心点 y、宽度、高度。全部标签堆成二维数组后第一列查类别分布后四列查坐标范围。坐标小于 0 或大于 1说明有越界框空标签文件数大于 0就要把对应图片一起过滤掉。这个数据集大概率是单类别 0但你不能假设所有 txt 都干净跑一遍统计才能放心。这里有个容易踩的细节有些工具导出的 YOLO 标签不是纯空格分隔而是0,0.5,0.5,0.3,0.3这种带逗号的形式跟标准格式不兼容。遇到这种情况把分隔符统一替换成空格再喂给训练脚本。2.3 坐标转换把 VOC/COCO 标注转成 YOLO 的通用脚本这份数据集本身已经是 YOLO 格式但训练时经常要混入其他来源的数据比如网上开源检测数据集多为 COCO JSON 或 VOC XML。遇到这种情况就需要写转换脚本。VOC XML 里一个目标的坐标是xmin, ymin, xmax, ymax转成 YOLO 中心点坐标的公式很固定def voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h return x_center, y_center, width, height这个函数把绝对像素坐标转成归一化中心点和宽高。/ img_w和/ img_h是关键不除以宽高的话训练框架会把坐标当成 0 到 1 之间的值去处理框的位置会整个错乱。合并多个数据集前还要检查每个数据集的类别 id 是否一致。常见坑是 A 数据集的 0 是 carB 数据集的 0 是 plate合并后类别语义错乱。我的习惯是合并前先打印两份数据的类别分布统一归类后再转格式免得训练完发现模型输出的结果没法解释。3. 用YOLOv8把数据集跑起来划分、配置与训练参数实测要训练先想清楚“如何训练”。从这份数据集的组织方式看比较省事的方式是用 Ultralytics YOLOv8 作为训练框架它直接吃 YOLO 格式标注不需要额外转换。下面按数据划分、YAML 配置、训练命令三步走。1695 张这个量级不算大但足够支撑一个能用的车牌检测模型前提是得用好预训练权重和增强策略。如果从零开始随机初始化训练这个数据量容易欠拟合用 COCO 上训好的 yolov8n.pt 做迁移学习几十轮就能收敛到可用水平。这也是现在大多数目标检测项目的主流做法。3.1 train/val 划分脚本按视频来源分别按文件名随机分数据划分是最容易被忽略、又最影响结果真实性的一步。1695 张图如果直接random_split极大概率让同一视频的相邻帧同时出现在训练集和验证集里导致验证指标虚高。我见过 mAP50 0.95 的模型换到现场视频直接掉到 0.7原因就是训练集和验证集长得太像模型等于“背题”了。常见做法是按图片前缀分组video9、video11、video5这些前缀来自不同视频片段的抽帧划分时保证同一视频的所有帧只落在训练集或验证集一侧。import random from pathlib import Path random.seed(42) img_dir Path(images) val_ratio 0.15 img_paths sorted(img_dir.glob(*.jpg)) groups {} for p in img_paths: prefix p.stem.split(_)[0] groups.setdefault(prefix, []).append(p.stem) train_ids, val_ids [], [] for prefix, stems in groups.items(): random.shuffle(stems) cut max(1, int(len(stems) * val_ratio)) val_ids.extend(stems[:cut]) train_ids.extend(stems[cut:]) print(训练集:, len(train_ids), 验证集:, len(val_ids))这段逻辑按文件名下划线切分出视频前缀把同一视频帧聚到一组再在组内按 15% 抽验证样本。random.seed(42)固定随机种子保证每次划分结果一致方便复现实验。max(1, ...)避免某个视频片段只有一帧时被抽空验证集至少要留一张。划分完成后需要把文件名列表写入 txt再按清单把图片和标签复制到训练目录。复制文件的好处是训练时路径干净缺点是占双倍磁盘。如果磁盘紧张也可以在 data.yaml 里分别指定源图片目录和标签目录Ultralytics 默认会在图片同目录下找同前缀标签保持目录结构一致就行。mkdir -p dataset/train/images dataset/train/labels mkdir -p dataset/val/images dataset/val/labels while read name; do cp images/${name}.jpg dataset/train/images/ cp labels/${name}.txt dataset/train/labels/ done train.txt这段 shell 的主力是while read循环逐行读取train.txt里的文件名主干同时复制图片和标签到训练集目录。注意train.txt每行只能写文件名不带后缀否则拼出来的路径会错。验证集同理把val_ids写入val.txt再跑一遍。我这里只写了 train 的例子实际操作时别漏了 val。3.2 写 data.yaml 时容易忽略的点Ultralytics YOLOv8 需要一个 YAML 描述数据集结构。文件内容长这样path: /your/absolute/path/dataset train: train/images val: val/images names: 0: platepath指向数据集根目录train和val是相对path的图片目录。names字典里 0 对应车牌。如果这份数据集的类别 id 不是 0 而是 1那这里也要跟着改并且标签文件里的第一列必须是同一个 id。我拿到陌生数据集时第一件事就是看标签文件第一列是几而不是想当然地写 0。还有一个小坑path不要写相对路径。训练脚本的当前工作目录一变相对路径就解析失败报AssertionError: train dataset not found。所以这里我直接用/your/absolute/path/dataset占位实际使用时替换成你本机的绝对路径。3.3 训练命令与关键超参数目录配好后直接跑yolo detect train datadataset.yaml modelyolov8n.pt epochs100 imgsz640 batch16 lr00.01 device0yolo命令是 Ultralytics 提供的入口detect train表示跑目标检测训练。modelyolov8n.pt是官方预训练权重从 COCO 上迁移过来对收敛很有帮助。imgsz640是模型输入尺寸车牌属于中小目标如果原图分辨率普遍高于 1280可以尝试 800 或 960但显存占用会明显上涨。batch要参考显存16GB 显卡跑 yolov8n 通常能开到 32视频帧大图容易爆显存时降到 16 更稳。lr0是初始学习率0.01 是默认值数据量小的时候不要加大不然前几轮 loss 就直接飞了。如果显存只有 6GB 左右别硬上 yolov8m先用 n 或 s。yolov8s 的参数量是 n 的两倍多精度提升并没有想象中大训练时间却明显变长。数据量 1695 这个规模yolov8n 往往比大模型更稳不容易过拟合。我习惯的做法是先用 n 跑通流程确认数据没问题再换 s 跑最终版本。训练过程关注两个指标val/box_loss下降曲线和metrics/mAP50-95。车牌检测只要框得准mAP50 更重要它衡量 IoU 大于 0.5 时召回和精度的综合表现mAP50-95 对框的精度要求更严。训练到约 60 轮时指标通常会进入平台期可以关掉不用盲堆 300 轮。还可以在命令里加close_mosaic10意思是最后 10 轮关闭 Mosaic 增强避免模型在训练后期被拼图增强干扰这个设置在连续帧抽帧的数据上效果更明显。4. 避坑指南车牌检测训练中五个高频翻车点数据量不大、场景杂车牌检测训练踩坑几乎是必然的。这一章写的五条都是我实际碰过的翻车点按现象、原因、解决的顺序列出来方便你对照排查。4.1 验证指标虚高模型背了训练集的答案现象训练完 mAP50 到 0.98换到另一个停车场视频推理漏检率突然变高完全不像验证时那么强。原因随机划分导致同一视频的连续帧同时出现在训练集和验证集。视频相邻帧之间往往只有车牌位置几像素的差别模型很容易“记住”而不是真正学会泛化。这个问题在抽帧数据集上特别隐蔽因为每张图都不一样肉眼看不出来两个集合有重叠。解决按文件名前缀分组划分让同一视频片段只进训练集或验证集一侧脚本参考第 3.1 节的 groups 逻辑。划分完成后还可以再打印两个集合的视频前缀分布确认没有交叉。4.2 EXIF 旋转导致框整体偏移现象大多数图片推理正常但某个 JPG 的车牌框整体偏移到右下角或左下角框和车牌位置差了一个身位。原因手机或相机拍摄的 JPG 会带 EXIF Orientation 信息OpenCV 的cv2.imread不自动旋转读取出来的像素其实是旋转前的原始方向。YOLO 训练时用 OpenCV 读图标注坐标却是按人眼看到的方向标定的两者不一致框自然就歪了。解决训练前统一用 PIL 按 EXIF 方向重写图片让像素方向和图能对上。from PIL import Image, ImageOps from pathlib import Path for img_path in Path(images).glob(*.jpg): im Image.open(img_path) im ImageOps.exif_transpose(im) im.save(img_path, JPEG, quality95)ImageOps.exif_transpose读取 EXIF Orientation 并旋转像素再覆盖保存。注意要放在数据集划分之前做否则重写图片后标签匹配会出现错位。如果图片数量大这一步会稍微耗时但值得做。4.3 空标签文件不过滤导致训练中断现象训练日志里出现WARNING: ignoring corrupt img或者某个 epoch 训练直接报错退出。原因labelimg 生成过空 txt或者视频某帧确实没有车牌但脚本还是创建了标签文件。Ultralytics 读到空 txt 时行为不稳定轻则跳过重则抛异常。解决训练前扫出空标签把对应图片一起移出数据集。find labels -name *.txt -empty -delete find images -name *.jpg -size 0 -delete这里我先列出命令但实际执行时不要直接-delete更稳妥的做法是把空文件名导出再逐一手工处理。我吃过一次亏删了 labels 里的空 txt却忘了对应图片还在训练集里训练时label缺失警告刷了一屏。正确顺序是先找空 txt再根据文件名删除对应 jpg。4.4 标注框里混入非车牌目标现象训练出的模型把车灯、车标、车身反光条当车牌误检率偏高。翻看训练集时发现部分标注框圈进去的根本不是车牌。原因人工标注或半自动标注时粗心把形状相似的矩形目标框进来了。视频抽帧数据里运动模糊和反光最容易让标注员看走眼。解决用可视化脚本把所有标注画到图片上逐张抽查重点看视频开头和结尾帧。那几帧车牌常处于半遮挡状态最容易出现误框。如果发现有误标要改标签然后重新训练不要试图靠后处理把错误学回来。4.5 坐标小数位截断导致框精度不足现象模型训练出来 mAP 不低但实际推理时预测框总比车牌大一圈或者边缘不贴合。原因标签文件里坐标小数位只有两位比如0.50而不是0.500123。在 1280 分辨率下0.01 的归一化误差就是 12 像素对车牌这种小目标来说非常致命。解决检查标签文件坐标位数。不足 6 位时用原始像素坐标重新归一化计算。常见做法是把 YOLO 坐标还原成像素坐标核对无误后再转回归一化格式。LabelImg 手动打标时默认保留较多小数位但某些批量标注工具会截断这类数据必须重新导出。5. 标注质量自查与数据增强别让坏标注拖垮模型模型是骡子是马很大程度取决于喂进去的标注质量。1695 张图如果全部靠人工检查工作量有点大但看完这一章你可以用脚本快速定位问题再针对性地做增强。5.1 画框脚本检查标注偏移先画框再训练永远比训练后看坏例图再回头排查高效。用 OpenCV 把 YOLO 归一化坐标转回像素坐标画出来抽查几十张就能发现问题。import cv2 from pathlib import Path img_dir Path(images) label_dir Path(labels) out_dir Path(visual) out_dir.mkdir(exist_okTrue) for img_path in sorted(img_dir.glob(*.jpg))[:50]: img cv2.imread(str(img_path)) h, w img.shape[:2] txt_path label_dir / (img_path.stem .txt) if not txt_path.exists(): continue for line in txt_path.read_text().strip().splitlines(): cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(str(out_dir / img_path.name), img)代码把归一化中心点加减半宽半高还原成左上角和右下角像素坐标再乘以原始宽高。h, w img.shape[:2]是 OpenCV 的 HWC 顺序先取高再取宽别写反。画完的图输出到 visual 目录肉眼检查框是否贴边、是否框住整个车牌、是否出现明显的偏移或错位。抽查时不需要 1695 张全看挑每个视频前缀的第一帧、中间帧、最后一帧再加上标注比较密集的图基本能覆盖 80% 的问题。重点看倾斜车牌、夜间闪光灯、严重过曝这三类图它们最容易出现框偏移。如果发现某张图的框整体偏了优先怀疑 EXIF 旋转回头处理。5.2 数据增强让 1695 张图发挥出更多效果数据量小增强就特别重要。Ultralytics 训练时默认开启 Mosaic、HSV 扰动、随机翻转等增强不需要额外加库你要控制的是增强强度。yolo detect train datadataset.yaml modelyolov8n.pt epochs100 hsv_h0.015 hsv_s0.7 hsv_v0.4 fliplr0.5hsv_h、hsv_s、hsv_v分别控制色相、饱和度、亮度的随机扰动范围。别调太大车牌蓝色区域如果被改成绿色模型会学到错误颜色特征。fliplr0.5是水平翻转概率但车牌字符左右翻转后顺序是反的如果后面要接 OCRfliplr建议改成 0否则检测模型虽然能框住下游识别却会很别扭。尺度方面如果原图分辨率偏低再叠加随机裁剪容易让车牌目标小于 16×16 像素检测效果很差。有需要时可以把 Mosaic 概率降到 0.5或者用scale0.3限制尺度扰动范围。另一种做法是离线增强把雨雾、暗光、模糊样本主动加进去。imgaug 是常见的图像增强库用起来很直接import imgaug.augmenters as iaa import cv2, glob aug iaa.Sequential([ iaa.AdditiveGaussianNoise(scale(0, 0.05*255)), iaa.Multiply((0.6, 1.4)), iaa.GaussianBlur(sigma(0.0, 1.5)) ]) for img_file in glob.glob(images/*.jpg)[:300]: img cv2.imread(img_file) new aug(imageimg) name img_file.split(/)[-1] cv2.imwrite(faug_images/{name}, new) # 同步复制对应 label 文件AdditiveGaussianNoise模拟夜间噪点Multiply模拟亮度变化GaussianBlur模拟运动模糊。关键点是这些增强只改变像素不改变目标位置所以标签文件直接复制同名 txt 就行。但要注意如果用了水平翻转或缩放就必须同步更新坐标不要偷懒复制。离线增强有个容易忽略的问题增强后的图片和原图如果同时进训练集又会造成训练验证分布重叠。所以增强图只能放训练集验证集必须保持纯净。我一般把增强图和原图放在同一个训练目录增强图数量控制在原图的 20% 到 30%太多会让模型偏向增强后的分布。6. 进阶技巧拿检测结果接车牌识别前处理验证模型真的能用训练完的模型不能只看 mAP它最终要服务下游任务。这里说一个我常用的验证流程YOLO 检测出车牌框把框裁剪出来再做灰度化和二值化交给 OCR 识别。这能直接反映检测框质量是不是真的够好。import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) img cv2.imread(test.jpg) results model(img, conf0.5)[0] for box in results.boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 map(int, box[:4]) plate img[y1:y2, x1:x2] gray cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) cv2.imwrite(plate_bin.png, binary)代码把检测框坐标转成整数后裁剪出车牌区域转灰度再 Otsu 二值化。conf0.5是置信度阈值车牌检测场景可以降到 0.3避免低置信度但正确的框被过滤掉。二值化输出如果边缘区域全是背景噪声说明检测框偏大或者车牌本身角度太大。想验证模型对倾斜车牌的鲁棒性可以统计验证集上预测框与标注框的 IoU 分布重点看 IoU 低于 0.6 的样本长什么样。我习惯把失败样本抽出来拼成一张大图观察是不是都集中在夜间、远距离或遮挡场景这比只看 mAP 数值有用得多。从那以后我每次训完车牌检测模型都会强制走一遍“裁剪—二值化—拼接坏例”的流程不然总觉得模型是个黑匣子不敢直接拿到现场用。希望这些实测细节能帮到你少走几趟我走过的弯路。本文还有配套的精品资源点击获取