☰
黄牌车牌检测数据集JSON转YOLOv8训练实战指南
2026/9/29 18:42:48 网站建设 项目流程

简介:面向车牌识别与检测任务的高质量黄牌数据集,已人工逐张筛选并完成精细标注,适合目标检测模型训练、车牌字符识别等场景,可直接用于算法验证或项目落地。压缩包共9324个文件,包括4662个json标签、4627张jpg图片和35张jpeg图片,整体约257.79MB。标签采用polygon多边形目标框,四个点分别贴合车牌四角,能够适配不同拍摄角度下的车牌轮廓;同时已剔除模糊图像并处理有歧义区域,极大减少数据清洗时间,训练时无需额外整理即可直接开展字符识别。目前已有883人学习下载,标注质量较受认可。对于需要矩形目标框或转换其他标签格式的开发者,也可联系作者进一步处理,资源灵活性较高,适合从事智慧交通、停车场管理等方向的人员使用。

1. 车牌检测数据集(黄牌)+json标签:能直接训练还是得先洗一遍

做车牌识别项目的人,多半都经历过“找数据集两小时、清洗数据一整天”的折磨。这份“车牌检测数据集(黄牌)+json标签”,从文件名看就是奔着黄牌场景去的——黄牌在高速卡口、物流园区、大货车入城登记这些场景里出现频率极高,但公开数据集里大量混着蓝牌、绿牌,纯黄牌标注的反而不多。它带的json标签是标准目标检测标注格式,理论上解压出来就能喂给检测模型,但实际能不能直接用,取决于你的训练框架对json的兼容程度,以及标注框的坐标约定。这篇笔记会把这套数据集从头拆到尾:json结构怎么读、怎么转成YOLO能用的txt、训练参数怎么设、以及最容易翻车的几个标注坑。

适合谁看:准备用YOLOv5/YOLOv8训练车牌检测模型的开发者,或者刚接触json格式标注、想复现一个完整检测流程的入门者。看完你应该能判断这份数据值不值得下,以及怎么花最少的时间把它跑起来。

2. 数据集内部结构与json格式:先搞清标注怎么组织

2.1 json文件里的关键字段与坐标约定

不管用Labelme、LabelImg还是CVAT导出的json,目标检测标注的核心信息就三块:图片路径、目标类别、目标位置。这份黄牌数据集的json我按常见COCO风格来拆,就算实际字段名有出入,思路也通用。

先说最容易踩的坑:bbox的坐标格式。COCO风格是[x, y, width, height],其中x和y是框左上角坐标;而YOLO训练用的是归一化的中心点坐标[cx, cy, w, h]。如果你直接把json里的数值填进YOLO的label文件,模型会学出一堆错位框。先确认坐标格式再动手转换,这个顺序别反了。

{ "images": [ { "id": 1, "file_name": "IMG_0001.jpg", "width": 1920, "height": 1080 } ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [x, y, width, height], "area": width * height } ], "categories": [ { "id": 1, "name": "yellow_plate" } ] }

上面是COCO格式最精简的结构。注意categories里只有yellow_plate一个类别,说明这份数据集是单类检测——只负责把黄牌从画面里框出来,不区分省份汉字、不识别车牌号码。如果你后续要做的是端到端车牌识别(检测加字符识别),那这份数据只能作为检测环节的输入,字符识别还得另找数据集。json里面每一张图的id和标注里的image_id是一一对应的,这个对应关系在转换时要保持好,一旦错位会导致某张图有框、另一张图空标注,训练出来的模型会在部分图片上漏检。

2.2 json转YOLO txt:一份能直接用脚本与参数说明

既然这份数据给的是json,而绝大多数开源车牌检测项目基于YOLO系列训练,第一步就是把json转成YOLO的txt标注。下面这段转换脚本是我平时处理类似json数据集的固定套路,直接在项目根目录跑。

import json import os def coco_json_to_yolo_txt(json_path, output_dir): # 读取标注文件 with open(json_path, 'r', encoding='utf-8') as f: coco_data = json.load(f) # 建立图片id到文件名的映射 image_id_to_name = {} for img in coco_data['images']: image_id_to_name[img['id']] = img['file_name'] # 建立类别id到序号的映射,这里强制从0开始顺序编号 category_id_to_index = {} for idx, cat in enumerate(coco_data['categories']): category_id_to_index[cat['id']] = idx os.makedirs(output_dir, exist_ok=True) # 按图片聚合标注框 annotations_grouped = {} for ann in coco_data['annotations']: image_id = ann['image_id'] if image_id not in annotations_grouped: annotations_grouped[image_id] = [] annotations_grouped[image_id].append(ann) # 逐张图片写txt标注 for image_id, anns in annotations_grouped.items(): file_name = image_id_to_name[image_id] img_width = 0 img_height = 0 for img in coco_data['images']: if img['id'] == image_id: img_width = img['width'] img_height = img['height'] break txt_path = os.path.join(output_dir, file_name.replace('.jpg', '.txt')) with open(txt_path, 'w', encoding='utf-8') as f: for ann in anns: x, y, w, h = ann['bbox'] # COCO的x,y是左上角坐标,先转成中心点再归一化 center_x = (x + w / 2) / img_width center_y = (y + h / 2) / img_height norm_w = w / img_width norm_h = h / img_height # 第一个数字是类别id,本数据集只有一个类所以恒为0 f.write(f"0 {center_x:.6f} {center_y:.6f} {norm_w:.6f} {norm_h:.6f}\n") coco_json_to_yolo_txt('annotations.json', 'labels')

这段脚本的核心逻辑分三步:先建立image_id到文件名、category_id到类序号的映射,再把同属一张图片的所有标注框聚合到一起,最后逐张图片把COCO的左上角宽高坐标换算成YOLO的归一化中心点坐标。换算公式就是center_x = (x + w/2) / img_width,看不懂的话直接记住“先加半宽再除以整图宽度”这个口诀。

脚本有两个地方需要根据实际数据微调。第一个是file_name.replace('.jpg', '.txt'),如果你的图片是.png或者.jpeg,替换后缀那行要对应改。第二个是json里的图片宽度和高度字段,正规数据集都有,如果是第三方爬虫整理的野数据集,这两个字段可能是空值,那就要在转换时用cv2或PIL读取实际图片尺寸,否则归一化计算会除零报错或者出全零值。

2.3 转换后的文件组织:训练前必须做的一次性整理

json转成txt只是第一步,之后的目录结构直接决定训练时会不会报“找不到图片”的错误。常见做法是把图片和标注分开放,但保持同名对应。我习惯这样组织:

dataset/ ├── images/ │ ├── train/ │ │ ├── IMG_0001.jpg │ │ └── IMG_0002.jpg │ └── val/ │ ├── IMG_0100.jpg │ └── IMG_0101.jpg ├── labels/ │ ├── train/ │ │ ├── IMG_0001.txt │ │ └── IMG_0002.txt │ └── val/ │ ├── IMG_0100.txt │ └── IMG_0101.txt └── data.yaml

注意labels目录下不能再嵌套二级文件夹,YOLO训练时标签路径是直接拼接的:image_path的images换成labels、后缀换成txt。如果你原始样本量不大(比如只有几百张),按9:1划分训练集和验证集就够用;如果图里有大量同一辆车从不同角度拍的近重复样本,建议按车辆ID去重后再划分,不然验证集会混进训练集的近似副本,mAP会虚高,真上了新场景立刻露馅。这个去重逻辑没有现成脚本,一般是根据文件名前缀或者拍摄时间戳手工分批,属于脏活累活,但省不掉。

3. 用这份黄牌数据集训练YOLOv8:配置、参数与验证

3.1 数据配置yaml与类别数:一个数字错了整个训练白费

YOLOv8的数据配置文件是一个yaml,里面最重要的是path、train、val和nc。这份数据只有一类黄牌,所以nc=1。一个典型配置长这样:

# data.yaml path: D:/plate_dataset # 数据集根目录,建议写绝对路径 train: images/train # 训练图片目录,相对根目录 val: images/val # 验证图片目录,相对根目录 nc: 1 # 类别数 names: ['yellow_plate'] # 类别名称列表

很多人在这一步翻车,不是nc写错成2(把背景也算进去了),就是names列表里写了中文“车牌”,导致训练时报错无法编码。names里的字符串只用于日志展示和最终结果可视化,不影响模型学习,但建议用纯英文,后续部署时统一映射。yaml文件保存时注意编码,Windows下用记事本另存为UTF-8,别用默认的ANSI。

另外一个隐形问题是图片格式。如果json里的file_name是IMG_0001.jpg,但实际图片是png,训练时YOLO会根据后缀找同名文件,找不到就跳过这张图,而且不报错,只是在日志里输出一条warning。你可能训练了十几个小时才发现有效训练图片少了三分之一。先跑一个统计脚本确认图片数量和标注数量一致,再开始训练,这个习惯能省下半天时间。

3.2 训练命令与关键超参数:跑通和跑好是两个层次

训练命令本身不长,但参数填不对,要么OOM(显存溢出),要么学不动。以一个常规的1080Ti/3060 12G显卡为例,batch size填8到16,imgsz填640,训练100个epoch:

yolo detect train data=plate.yaml model=yolov8s.pt epochs=100 batch=16 imgsz=640 device=0 project=plate_experiment name=run1

逐个参数说:data指定yaml路径;model用yolov8s.pt预训练权重,s是small版本,在速度和精度之间比较平衡,第一次跑先别上yolov8x,训练时间长而且数据量小容易过拟合;batch=16在12G显存下刚好到头,再大就爆显存;imgsz=640是通用输入尺寸,如果原图里车牌很小(比如超过100米距离拍摄),建议改成1280做多尺度训练,但要接受训练时间翻倍;device=0指定第一块GPU。

关于epochs,100轮在几百张数据的小数据集上已经足够,因为预训练权重里已经有大量通用视觉特征,我们只是微调检测头。训练时观察两个指标:val/box_loss在30轮左右开始平缓下降就说明在正常收敛,如果loss在曲线图上反复震荡不下降,先检查学习率,默认lr0=0.01对微调场景通常偏大,改到0.001往往能救回来。

3.3 训练完成后看什么:mAP50和mAP50-95怎么解读

训练结束会生成weights/best.pt和last.pt,best是验证集上mAP最高的权重,直接拿它做后续推理。验证指标里最常看的是mAP50和mAP50-95。

mAP50是IoU阈值0.5下的平均精度,mAP50-95是0.5到0.95每隔0.05取一个阈值的平均。对车牌检测来说,mAP50能到0.95以上才算合格,mAP50-95一般比mAP50低10到20个点都正常。如果mAP50很高、但mAP50-95上不去,说明模型定位精度不够,框虽然落在车牌附近但不够贴边,这时候优先考虑把imgsz调大,而不是盲目加训练轮数。

另外一个重要动作是看混淆矩阵和F1曲线。YOLO训练日志里会输出confusion_matrix.png和F1_curve.png,前者能看到模型把背景误判为黄牌的比例,后者告诉你置信度阈值设多少最合理。如果背景误判多,推理时confidence阈值从默认0.25提高到0.4能过滤掉大部分假阳性框。这部分细节在训练完成后的results.png里都有,别只盯着loss曲线看。

4. 黄牌数据集常见问题排查:json标注里容易翻车的四个坑

4.1 现象:转出来的txt全是0或者负数

转换脚本正常运行,但生成的txt文件里坐标值大量是0.000000,甚至出现负值。打开json原始标注一看,bbox字段里存的是[x1, y1, x2, y2],不是[x, y, width, height]。

原因:数据集的标注规范不是COCO标准,而是Pascal VOC风格,json里存的是右下角坐标。转换脚本按COCO的width、height去算,算出来自然是一堆负数或者无意义数值。

解决:先人工看一条标注,如果x2 > x1且y2 > y1,说明是VOC风格,转换时把width = x2 - x1、height = y2 - y1算回宽高,再套归一化公式。这个判断只需要花一分钟抽查三条标注,但能避免整个数据集白白转换一遍。

4.2 现象:训练时提示“found 0 annotated images”

图片和txt文件都在,目录结构也对,但训练日志里提示图片标注数量为0。进入labels目录发现txt文件是空的或者只有换行符。

原因:json解析出来的annotation字段是空的,但图片列表正常。可能是json里用了一个我没在2.1节提到的“ignore”字段,被标注人员标记为忽略的框实际没有写入annotations数组,或者标注软件把空标注图也导出成了一条记录。

解决:在转换脚本里加一个过滤逻辑,annotations数组为空的那张图直接跳过,不生成txt文件。同时把这张图从训练集里排除掉,否则YOLO训练时图片存在但没有对应标签,会在loss计算时报错或者把梯度切成NaN。

4.3 现象:推理时黄牌全部漏检

训练指标mAP50显示0.9以上,但放到实际路口视频里,远处来的大货车黄牌一个都检测不到,近距离的倒能框住。

原因:训练图片里的车牌在画面中占比都很大,平均宽度占整张图的20%以上,模型没有见过小目标场景。黄牌检测的难点恰恰是远距离小目标,这部分特性在数据集里没有体现。

解决:训练时开启多尺度训练,YOLOv8参数里加scale=0.5表示每次迭代随机缩放范围是原图的0.5倍到1.5倍,能让模型适应多种目标尺寸。如果数据集图片本身分辨率高(比如1080p以上),先降采样到1280再切patch训练,比硬塞进640效果更好。这个属于数据分布问题,调参只能缓解,根治得补一批远距离样本。

4.4 现象:json里明明有“黄牌”类别,转出来却全是“0”

上面2.2节脚本里category_id_to_index是从0开始编号的,如果json里categories的id预先定义成了1、3、7这种不连续编号,脚本依然会按出现顺序映射为0、1、2,但类别名称对不上号。

原因:json导出时categories数组里的id不是训练框架要求的连续整数,有些标注工具默认从1开始,有些从0开始,还有的按创建顺序乱序排列。

解决:转换后抽几张图,把YOLO格式的txt打印出来看一眼类别数字,再对应回json里categories的原始id和name字段。如果数据集只有一个类,一般不会出问题;一旦以后合并多类数据,这个映射表必须显式维护,别依赖字典遍历顺序。这个坑最隐蔽,也最容易被忽略——因为单类数据集永远不会报错,多类数据集一上来就乱套。

5. 把这份数据集当预标注用:用脚本做质量抽检与二次清洗

跑通训练只是第一步,真正落地还要确认标注质量。很多人拿到数据集直接训练,训练完遇到bad case也不知道是模型问题还是标注问题,最后把锅甩给模型——实际上数据集里可能有错框、漏框、边界残缺三类问题。下面提供一个可视化抽检脚本,把标注框画到图片上人工过一遍,比直接看txt数字直观得多。

import cv2 import json import random def visualize_annotations(json_path, image_dir, sample_count=50): with open(json_path, 'r', encoding='utf-8') as f: coco_data = json.load(f) image_id_to_name = {} for img in coco_data['images']: image_id_to_name[img['id']] = img['file_name'] annotations_grouped = {} for ann in coco_data['annotations']: image_id = ann['image_id'] if image_id not in annotations_grouped: annotations_grouped[image_id] = [] annotations_grouped[image_id].append(ann) random.seed(42) sample_ids = random.sample(list(annotations_grouped.keys()), min(sample_count, len(annotations_grouped))) for image_id in sample_ids: img_path = os.path.join(image_dir, image_id_to_name[image_id]) img = cv2.imread(img_path) if img is None: print(f"图片读取失败: {img_path}") continue for ann in annotations_grouped[image_id]: x, y, w, h = [int(v) for v in ann['bbox']] cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 3) cv2.imshow('check', img) if cv2.waitKey(0) & 0xFF == ord('q'): break cv2.destroyAllWindows()

这个脚本的核心是随机抽样,不按顺序看全部图片,而是用固定随机种子抽50张,保证每次运行抽到同一批样本,方便对比前后清洗效果。按q键跳过当前图,按任意其他键继续下一张。标注框用红色画,宽度3像素,在1080p图上足够醒目。

抽检时重点看三类问题:一是框是否完全包住黄牌,有没有把车牌边缘的一部分切掉;二是json标注里有没有重复框——同一个车牌被标了两次,训练时模型会对同一目标输出两个预测框,NMS阶段才合并,但训练过程loss会收到重复监督信号,导致收敛变差;三是黄牌被遮挡时框怎么标——有的标注员只框可见部分,有的把遮挡区域也框进去,这种不一致会在测试时让模型无所适从。如果抽检发现超过10%的图片有标注问题,别犹豫,直接清洗而不是带病训练。宁可拿清洗后的小数据集跑,也不拿脏数据集硬训。

从那以后我每次拿到新数据集,第一反应不是开训练脚本,而是先跑一遍可视化脚本抽检20张图,再决定这个数据能不能用、要不要返工。数据集本身没有原罪,用错了方式才是问题。这份黄牌加json标签的资源,本质上是一个质量还不错的检测基础集,适合做方案验证和流程跑通,真要在生产环境用,补拍真实场景数据那一步谁也省不掉。希望这份拆解能帮你少踩几个坑。

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

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

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

立即咨询