黄牌车牌检测数据集:COCO标注转YOLO训练与避坑实践 简介面向车牌识别与车牌检测算法训练的数据集资源聚焦典型黄色车牌检测场景。整套数据包含9324个文件压缩包约257.79MB其中4627张jpg与35张jpeg图片对应4662个json标注文件标签采用polygon多边形目标框每个车牌由四个顶点贴合定位能较好适配倾斜、透视等复杂拍摄角度为车牌字符识别与目标检测提供可直接使用的标注信息。图片经过人工逐张筛选已剔除不清晰样本并处理有歧义区域质量较统一无需二次清洗即可用于模型训练。作者还提供标签格式转换支持如需要rectangle矩形目标框的json可私信沟通便于接入不同检测框架。目前已有885人学习下载适合计算机视觉初学者、算法工程师以及智慧交通、停车场管理等项目开发者直接复用。1. 车牌检测数据集黄牌样本为什么比蓝牌更容易翻车做车牌检测的人大概率都经历过这种场景蓝牌车跑得稳稳当当一到黄牌就漏检、误检轮着来。黄牌多挂在货车、客车和工程车辆上车牌颜色和车身漆面、车厢反光、泥污混在一起检测框经常画偏甚至直接丢掉目标。这份车牌检测数据集以黄牌为主体图像覆盖白天、傍晚、阴天等多种光照每张图都配好 JSON 标签类别、边框、图像尺寸齐全拿过来不用重新标注直接能跑通数据加载 → 可视化 → 训练 → 评估的完整链路。适合正在做交通监控、停车场出入口识别、车型分类预处理的开发者也适合想用现成数据快速验证 YOLO 系列模型、跑通图像识别与车牌检测完整流程的从业者。2. 数据集结构与 JSON 标注格式先搞懂标签再动手2.1 目录组织训练集、验证集与标注文件的对应关系拿到压缩包解压之后目录组织比较直白图像和标注分离训练集与验证集各自独立。常见做法是dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ └── val/ │ ├── img_0100.jpg │ └── ... └── annotations/ ├── train.json └── val.jsonimages 目录下是按 train / val 拆好的原始图像annotations 目录里放的是与之一一对应的 JSON 标注文件。train.json 只描述 images/train 下的图像val.json 同理二者不会交叉引用。这种组织方式和 COCO 数据集的惯例一致后续接 Detectron2、MMDetection 或者自己写数据加载器都比较省事。如果打算用 YOLO 系列训练只需要把 images 目录原样保留再把 JSON 转成同名 txt 标签丢进 labels 目录即可具体转换脚本在下一章。我第一次拿到时踩过一个坑直接用 glob 去扫 images 目录结果把 val 的图像也扫进来了训练集和验证集出现重叠训练时 loss 一震荡就开始怀疑模型最后查了两小时才发现是数据加载的问题。后来我改成以 JSON 里的 file_name 为准来构建图像路径映射而不是反向扫描目录问题就消失了。具体做法是给 JSON 里的 file_name 拼接上对应的 train 或 val 前缀再检查文件是否存在。提示以 JSON 的 file_name 为准构建图像路径映射训练前先检查 train 和 val 的图像集合是否有交集。2.2 JSON 字段逐项解析images、annotations、categories 三块各管什么标注文件用了 COCO 风格的字段顶层分成三块images 记录图像元信息annotations 记录每个车牌框categories 定义类别。打开一个典型的 train.json结构是这样的{ images: [ { id: 1, file_name: img_0001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [862, 415, 210, 68], area: 14280, iscrowd: 0 } ], categories: [ { id: 1, name: yellow_plate } ] }三个块各管各的事别混。images 里的 id 在全局唯一file_name 是相对于 images 目录的文件名width 和 height 是标注时记录的原始分辨率。后续做归一化坐标换算必须用这两个记录值不能用脚本里读到的实际尺寸代替因为个别图像可能被预处理改过尺寸标注框是基于原分辨率画的混用会造成少量框偏移。annotations 里的 image_id 关联到 images.idcategory_id 关联到 categories.id本数据集目前只有 yellow_plate 一个类别bbox 是 [x, y, width, height] 四个数值表示车牌外接矩形左上角坐标和宽高单位是像素area 等于 width 乘 height一些框架用它做小目标筛选和评估加权iscrowd 为 0 表示普通目标为 1 表示群体区域转换格式时容易忽略这个字段后面避坑章节专门讲。2.3 坐标系换算COCO 绝对坐标与 YOLO 归一化坐标的互转COCO 的 bbox 是绝对像素坐标原点在图像左上角x 向右、y 向下。YOLO 系列训练时需要的是归一化坐标目标中心点 (cx, cy) 和宽高 (w, h) 都除以图像宽高取值在 0 到 1 之间。换算公式不复杂但方向搞反的话训练出来的模型框中心点全在图像外面损失函数根本收敛不了。def coco_to_yolo(bbox, img_w, img_h): x, y, w, h bbox cx x w / 2.0 cy y h / 2.0 return cx / img_w, cy / img_h, w / img_w, h / img_h参数含义bbox 是 COCO 格式的 [左上角 x, 左上角 y, 宽, 高]img_w 和 img_h 来自 JSON 里 images 块的 width / height。返回的是归一化后的中心点 x、中心点 y、归一化宽、归一化高。注意 x 和 w / 2 的单位都是像素要先加出中心点再除以图像宽不能先分别归一化再相加那样算出来的中心点坐标是错的。反过来从 YOLO 转 COCO 时用中心点减半宽得到左上角def yolo_to_coco(cx_norm, cy_norm, w_norm, h_norm, img_w, img_h): x (cx_norm * img_w) - (w_norm * img_w) / 2.0 y (cy_norm * img_h) - (h_norm * img_h) / 2.0 w w_norm * img_w h h_norm * img_h return [int(v) for v in (x, y, w, h)]yolo_to_coco 最后转 int 是因为画框时需要整数像素坐标而训练标签文件里一般保留 6 位小数不需要取整。这两段函数建议直接放进一个 utils.py后面做格式转换和可视化都要反复用到。如果数据集里有旋转标注或者多边形标注这套矩形换算就不够用了需要额外处理角度不过本数据集的 JSON 里只有轴对齐矩形框用这两个函数足够。3. 把 JSON 标签用起来统计、可视化与格式转换3.1 标注统计先摸清数据量和分布再决定训练策略拿到数据集先别急着开训练花两分钟跑个统计脚本确认图像数量、标注数量、每张图平均车牌数以及 bbox 宽高比的分布。这些数字直接决定输入分辨率、锚框设置和是否要做特殊增强。import json from collections import Counter with open(annotations/train.json, r, encodingutf-8) as f: data json.load(f) print(图像数量:, len(data[images])) print(标注数量:, len(data[annotations])) per_image Counter() wh_ratios [] for a in data[annotations]: per_image[a[image_id]] 1 x, y, w, h a[bbox] wh_ratios.append(round(w / h, 2)) print(单图最多车牌数:, max(per_image.values())) print(宽高比均值:, sum(wh_ratios) / len(wh_ratios)) print(宽高比范围:, min(wh_ratios), -, max(wh_ratios))这段脚本从 JSON 里读出标注列表per_image 用 image_id 做键统计每张图的车牌数量wh_ratios 收集所有框的宽高比。黄牌的标准宽高比接近 3:1如果统计出来大量框的比值明显偏离这个范围比如小于 2 或者大于 5说明标注边界可能有问题或者图像里存在非标准尺寸的特殊车牌需要回看原始图确认。宽高比分布还会影响后面的训练配置YOLOv5 训练时有一个自动锚框的步骤k-means 聚类结果直接受 bbox 宽高分布影响换 YOLOv8 这类 anchor-free 模型则不用关心锚框聚类但同样要从输入分辨率上解决小目标问题。3.2 可视化检测框检查标签是否真的框住了车牌统计只能发现数值层面的异常标签是否真正贴着车牌边缘必须把框画出来看。用 OpenCV 叠加标注框是检查数据质量最快的方式随机抽一部分图出来看即可。import cv2 import json data json.load(open(annotations/val.json, encodingutf-8)) for img_info in data[images][:10]: img cv2.imread(fimages/val/{img_info[file_name]}) annos [a for a in data[annotations] if a[image_id] img_info[id]] for a in annos: x, y, w, h [int(v) for v in a[bbox]] cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 255), 2) cv2.putText(img, plate, (x, y - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2) cv2.imwrite(fcheck_{img_info[file_name]}, img)cv2.rectangle 的四个参数分别是左上角坐标和右下角坐标注意不是中心点加宽高这里用 bbox 的 x、y 和 x w、y h 拼出右下角。颜色用了 BGR 的 (0, 255, 255)也就是黄色和黄牌本身颜色一致如果觉得看不清可以换成红色 (0, 0, 255)。我一般会抽 20 到 30 张图重点看三种情况框是否紧贴车牌边缘、是否把车灯或车身装饰条也框了进去、车牌字符区域是否完整包含在框内。抽样最好覆盖不同光线条件因为黄牌在逆光和夜间反光时的视觉特征差别很大白天看着正常的标签到了强反光场景可能就偏了。3.3 转 YOLO 格式生成 txt 标签并整理训练目录YOLO 系列训练需要每个图像对应一个同名 txt 文件每行格式是类别ID 中心点x 中心点y 宽 高全部归一化。JSON 转 txt 的脚本可以一次性处理训练集和验证集import json import os def convert_json_to_yolo(json_path, out_dir): data json.load(open(json_path, encodingutf-8)) os.makedirs(out_dir, exist_okTrue) for img_info in data[images]: name os.path.splitext(img_info[file_name])[0] img_w, img_h img_info[width], img_info[height] lines [] for a in data[annotations]: if a[image_id] ! img_info[id]: continue if a.get(iscrowd, 0): continue cx, cy, w, h coco_to_yolo(a[bbox], img_w, img_h) lines.append(f0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, name .txt) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines) \n) convert_json_to_yolo(annotations/train.json, labels/train) convert_json_to_yolo(annotations/val.json, labels/val)参数说明json_path 是输入的 COCO 风格标注文件out_dir 是输出的 txt 目录脚本会自动创建。类别 ID 直接写死为 0因为当前数据集只有 yellow_plate 一个类别如果后续自己扩展成多类别必须建一个类别名到 ID 的映射字典用 name 查 ID不能继续写死。这里显式跳过 iscrowd 为 1 的标注避免群体区域被当成单个车牌来训练。写文件时每行末尾加了换行符YOLO 训练不受影响但用其他数据检查脚本按行读取时更省事。没有标注的图片会生成空 txt 文件YOLO 训练会把它当作背景样本跳过可以保留也可以删除取决于你需不需要负样本。4. 黄牌检测训练与评估四个避坑记录和排查方法4.1 现象远距离黄牌漏检严重模型只捡近处车牌模型在验证集上跑出来的 mAP 看起来不错但实际监控画面里远处货车的小车牌基本检测不到。原因是两层一是输入分辨率不够YOLO 默认 640 输入下一个 30 像素宽的小车牌经过几次下采样后特征图里只剩一两个像素分类头很难给出置信度二是黄牌在远处本身对比度低和灰色路面、车厢阴影混在一起特征更弱。解决思路是先把训练输入分辨率提到 960 甚至 1280同时开启多尺度训练让模型见过不同尺寸的目标如果框架支持按 bbox 面积做采样加权可以把小目标样本的权重调高。注意分辨率提高后显存占用明显上涨batch size 要相应调小我试过从 640 换到 960batch 从 64 降到 32 才没爆显存。4.2 现象黄色误检到车灯、车身贴纸和道路标线训练完可视化 bad case 发现模型经常把货车车灯、车身黄色贴纸、甚至路面黄色标线框成车牌。原因在于黄牌的特征主要是颜色加矩形纹理而 HSV 空间里车灯反光和黄色贴纸与车牌颜色非常接近仅靠颜色分支很难区分。解决分三步训练阶段做 HSV 颜色抖动增强让模型不只依赖颜色这一个维度数据层面把包含车灯的难例挑出来加入训练集或者做复制粘贴增强把这些区域作为正样本让模型学会区分推理阶段可以配合后处理规则过滤掉宽高比不在合理范围、置信度低于阈值的框。如果用的检测框架支持设置忽略区域也可以把车灯位置标成 ignore 区域让模型在这些位置不计算损失实测对误检率下降很明显。4.3 现象mAP 虚高但实际框偏标注框边界不齐有一次训练出来的模型 mAP0.5 到了 0.9 左右看起来很好但部署到现场发现检测框总是往左偏半个字符的位置。后来查了几张验证集图像的标注发现不少框的左边沿压到了字符边缘甚至切进字符里右边却多留了一个字符的空白。原因是标注阶段不同的人对车牌边界理解不一致有的以字符区域为准有的以底板边缘为准。解决方法是先统一标注规范黄牌框必须包含完整底板左右留 2 到 3 像素边距上下留 1 到 2 像素然后写脚本统计全部 bbox 的宽高比把偏离均值超过 30% 的框列出来人工复查。从那以后我每次拿到数据集都会先做一遍 bbox 宽高比分布统计再决定要不要修标注。4.4 现象训练正常但验证时 loss 震荡JSON 和 txt 标签不一致训练日志里 loss curve 在某个 epoch 后开始周期性震荡排查后发现是因为数据加载顺序是随机的但有的 txt 标签和图像文件不是一一对应。具体原因是前面提到的扫描目录时把 val 图像也扫进了训练集导致同一张图一会儿用训练标签、一会儿用验证标签标签内容不一致loss 自然震荡。解决方法是严格以 JSON 的 file_name 为准构建数据集禁止单独扫描目录同时训练前写一个校验脚本检查每个 txt 标注文件对应的图像是否存在于训练目录、train 和 val 两个集合是否完全不相交。这个校验脚本十几行就能写完但能避免后面浪费好几个小时的排错时间。另外一个容易忽略的点是如果 JSON 里存在 iscrowd1 的标注部分框架会忽略部分框架不处理转换时统一过滤掉才能保证结果一致。5. 进阶针对黄牌的数据增强与难例筛选把召回率再往上拉5.1 针对性增强别用通用增强按黄牌物理特征来设计通用增强里的随机旋转 90 度对车牌检测是灾难因为真实场景里车牌不会倒着出现。我一般用 Albumentations 做一组针对性增强核心是模拟光线变化和运动模糊import albumentations as A transform A.Compose([ A.HueSaturationValue(hue_shift_limit10, sat_shift_limit30, val_shift_limit20, p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.5), A.MotionBlur(blur_limit5, p0.3), A.RandomSizedBBoxSafeCrop(width960, height960, p0.5) ], bbox_paramsA.BboxParams(formatcoco, label_fields[labels]))参数说明HueSaturationValue 的 val_shift_limit 控制亮度偏移模拟不同时间段的光照sat_shift_limit 控制饱和度抖动对应晴天和阴天的颜色差异MotionBlur 模拟车辆行驶和相机抖动产生的模糊RandomSizedBBoxSafeCrop 会在裁剪时保持 bbox 完整width 和 height 设为 960对应训练分辨率。注意 hue 偏移不要超过 15否则车牌颜色会从黄色漂移到绿色或橙色反而制造错误样本。回传增强参数时bbox_params 的 format 必须写 coco和原始标注格式一致如果用 yolo 需要提前做归一化。5.2 难例筛选用验证集错误样本反哺训练集增强做到位之后下一步是难例挖掘。把训练好的模型在验证集上推理收集所有漏检和误检的样本按置信度排序人工挑出典型的难例回填到训练集。这一步不需要全部重新标注常见做法是只挑 50 到 100 张配合复制粘贴增强把难例复制到多张图里模型很快就能学会这些特征。验证时不仅看整体 mAP还要单独统计黄牌类别的召回率把验证集里所有包含黄牌的图像挑出来按类别算 recall这个数字比整体 mAP 更能反映实际问题有没有解决。如果召回率上去了但误检也变多再回头看 PR 曲线的平衡点调整置信度阈值和 NMS 参数。我从那次踩坑之后养成了一个习惯每次拿到新数据集训练前强制走一遍标注可视化复查 bbox 宽高比统计 训练验证集合交集检查三步训练后再加一次黄牌类别召回率统计才敢部署。这套流程帮我拦下了不少数据集层面而非模型层面的问题。这份数据集配合前面的脚本从 JSON 解析、可视化到 YOLO 格式转换整条链路可以直接复现希望帮到你。本文还有配套的精品资源点击获取