
简介本资源为面向车牌检测与车牌识别任务的人工精标数据集适用于计算机视觉方向的算法训练、模型验证与字符识别预处理尤其适合需要处理倾斜、多角度车牌的检测场景。包内共9324个文件包含4627张jpg、35张jpeg图像以及4662个json标签文件压缩包约257.79MB图像与标签一一对应便于直接加载训练。标签采用polygon多边形目标框格式四个点分别对应车牌四角能贴合不同倾斜角度的车牌区域且已逐张人工过滤不清晰图片并处理歧义区域可直接用于字符识别流程。若需rectangle矩形框json或其他格式标签可联系作者转换。目前已有885人学习下载适合从事车牌识别、目标检测的中高级开发者与研究者参考使用。1. 黄牌车牌检测数据集与 json 标签从标注格式到训练落地的完整链路做车牌检测的同行大多有过这种经历手里拿到一批黄牌车图片标注文件却是 json打开一看结构五花八门既不是 COCO 也不是 LabelMe直接喂给 YOLO 训练脚本就报错。黄牌车牌检测数据集加 json 标签这个组合本质上解决的是「数据从哪来、标签怎么读、模型怎么训」这条链路里最容易被低估的一环——标注格式的解析与转换。黄牌车牌大型车、营运车辆常见在尺寸、长宽比、颜色分布上和蓝牌差异明显直接套用蓝牌模型往往召回率掉一截所以单独针对黄牌做数据集和标签处理是有实际价值的。这篇文章面向的是已经拿到或准备自建黄牌车牌数据集的工程师从 json 标签结构讲起一路走到 YOLO 格式转换、训练参数设置和踩坑排查目标是让你拿到一份 json 标注后能独立跑通整条流程而不是卡在格式转换那一步反复试错。2. 黄牌车牌 json 标签的结构解析与格式选型2.1 黄牌车牌标注为什么常用 json 而不是 xml车牌检测数据集的标注格式选择取决于标注工具和后续训练框架。早期车牌数据集多用 PASCAL VOC 的 xml因为 LabelImg 默认输出 xml。但近几年标注工具生态变了LabelMe、CVAT、以及各类自研标注平台普遍以 json 作为中间格式原因是 json 天然支持嵌套结构能同时存多边形点集、矩形框、属性字段比如车牌颜色、是否遮挡而 xml 表达这些要绕很多弯。黄牌车牌数据集用 json 标签通常有两种典型结构。第一种是 LabelMe 风格每个图片对应一个同名 json核心字段是shapes数组每个 shape 里有label、points、shape_type。第二种是平台导出的聚合 json所有图片的标注放在一个大文件里用images和annotations两个数组关联接近 COCO 的结构。这两种在转换脚本写法上差别很大拿到数据第一件事就是确认属于哪种。提示不要假设 json 标签的坐标系是哪种。有的工具存的是左上角加宽高有的存的是左上角加右下角两个点还有的存归一化后的 0 到 1 浮点数。转换前先打印几条样本确认。黄牌车牌本身有个特点值得注意它的长宽比通常在 2:1 到 3.5:1 之间比蓝牌略宽而且黄底黑字的对比度在逆光、夜间场景下衰减很快。这意味着标注框如果贴得太紧训练时数据增强裁剪容易把边缘字符切掉反而影响检测框回归。我一般建议标注时框比车牌实际边缘外扩 2 到 4 个像素给增强留余量。2.2 三种常见 json 结构的字段对照拿到一份黄牌车牌 json 标签先做结构识别。下面这张表是我处理过的主要几种结构对照你可以直接拿去比对字段名。结构类型顶层字段单框坐标字段坐标含义典型来源LabelMe 单文件shapespoints两点或四点像素坐标LabelMe 标注COCO 聚合images/annotationsbbox左上角 x,y 加宽高CVAT 导出自研平台objectsxmin/ymin/xmax/ymax左上右下像素坐标内部平台归一化聚合datacx/cy/w/h中心点加宽高0-1部分开源数据集识别方法很简单写几行 Python 把 json 读进来打印顶层 key 和第一条标注的字段名基本就能归类。这一步花五分钟能省掉后面半小时的报错排查。import json # 读取单个 json 标签识别结构类型 with open(sample.json, r, encodingutf-8) as f: data json.load(f) print(顶层字段:, list(data.keys())) # 情况一LabelMe 风格 if shapes in data: print(LabelMe 风格框数量:, len(data[shapes])) if data[shapes]: print(首框字段:, list(data[shapes][0].keys())) print(首框坐标:, data[shapes][0][points]) # 情况二COCO 聚合风格 if annotations in data: print(聚合风格标注数:, len(data[annotations])) if data[annotations]: print(首条标注字段:, list(data[annotations][0].keys()))这段代码的逻辑是先看顶层有没有shapes或annotations这两个 key 基本能区分单文件和多文件聚合。参数上没什么可调的重点是打印出来的字段名要和你预期的转换脚本对上。如果points里是四个点而不是两个点说明标注用的是多边形模式转矩形框时需要取 min/max 计算外接矩形不能直接取前两个点。2.3 选型理由为什么最终都往 YOLO 格式收敛不管原始 json 是哪种结构做车牌检测训练时绝大多数人最终会转成 YOLO 的 txt 格式。原因有三个一是 YOLO 系列在车牌这种小目标密集场景下性价比高部署链路成熟二是 txt 格式每行一个框解析快训练时不用反复读大 json三是归一化坐标让图片缩放增强不会破坏标签。YOLO txt 每行格式是class_id cx cy w h全部归一化到 0 到 1。黄牌车牌如果只做单类检测class_id 恒为 0如果要区分黄牌和蓝牌就设 0 和 1。我一般建议先做单类把检测框质量跑稳再考虑加颜色分类否则两个目标混在一起早期调试很难判断是框的问题还是分类的问题。3. 从 json 到 YOLO txt转换脚本与四个边界坑3.1 写一个能处理两种结构的转换脚本转换脚本的核心是把不同结构的坐标统一成cx cy w h归一化格式。下面这个脚本同时兼容 LabelMe 单文件和 COCO 聚合两种输入你只需要改输入路径和类别映射。import json import os from pathlib import Path def labelme_to_yolo(json_path, img_w, img_h, out_txt): LabelMe 单文件 json 转 YOLO txt with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data.get(shapes, []): pts shape[points] # 多边形取外接矩形矩形取两点 xs [p[0] for p in pts] ys [p[1] for p in pts] xmin, xmax min(xs), max(xs) ymin, ymax min(ys), max(ys) # 归一化为中心点加宽高 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 越界裁剪防止增强后坐标溢出 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) w, h min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) def coco_to_yolo(json_path, out_dir): COCO 聚合 json 转 YOLO txt按图片拆分 with open(json_path, r, encodingutf-8) as f: data json.load(f) # 建立 image_id 到文件信息的映射 img_map {img[id]: img for img in data[images]} # 按 image_id 聚合标注 ann_by_img {} for ann in data[annotations]: ann_by_img.setdefault(ann[image_id], []).append(ann) for img_id, anns in ann_by_img.items(): img img_map[img_id] w, h img[width], img[height] lines [] for ann in anns: x, y, bw, bh ann[bbox] # COCO bbox 是左上角加宽高 cx (x bw / 2) / w cy (y bh / 2) / h lines.append(f0 {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}) stem Path(img[file_name]).stem with open(os.path.join(out_dir, stem .txt), w) as f: f.write(\n.join(lines))逻辑说明labelme_to_yolo里对多边形做了外接矩形处理这是黄牌车牌标注常见的情况因为有人习惯沿车牌四角点标注。coco_to_yolo里注意 COCO 的 bbox 是左上角加宽高不是右下角这个点每年都有人搞错。参数上归一化保留 6 位小数足够类别 id 这里写死 0多类时改成从类别映射表读取。3.2 图片尺寸必须和标签同步否则框全偏转换时最容易翻车的地方是图片尺寸对不上。json 里记录的宽高和实际图片文件的宽高如果不一致归一化坐标就会整体偏移。常见原因是标注时用的是压缩预览图而训练用的是原图。我一般会在转换脚本里加一道校验用 PIL 读实际图片尺寸和 json 里记录的对比不一致就打印警告。from PIL import Image def check_size(json_path, img_path): with open(json_path, r, encodingutf-8) as f: data json.load(f) with Image.open(img_path) as im: real_w, real_h im.size json_w data.get(imageWidth) or data.get(width) json_h data.get(imageHeight) or data.get(height) if (json_w, json_h) ! (real_w, real_h): print(f尺寸不一致: json{json_w}x{json_h}, 实际{real_w}x{real_h}) return False return True这个校验函数不复杂但能拦住大部分「训练 loss 不降、框位置诡异」的问题。黄牌车牌数据集如果来自多个采集批次尺寸不一致的概率更高务必逐张校验。3.3 空标签和纯背景图的处理黄牌车牌数据集里经常混入没有车牌的负样本图或者标注漏了的图。转换后如果 txt 是空的YOLO 训练时默认会把它当纯背景图这本身是好事能降低误检。但要注意两点一是空 txt 文件必须存在不能缺失否则训练脚本可能报找不到标签二是负样本比例别太高我一般控制在正样本的 10% 到 20%太多会让模型偏向预测背景。注意有些转换脚本遇到空 shapes 会直接跳过不生成 txt导致图片和标签数量对不上。训练前用ls images | wc -l和ls labels | wc -l对一下数量差太多就要查。3.4 坐标越界与极小框的过滤黄牌车牌在远距离场景下可能只有十几个像素宽转换后 w 或 h 归一化后小于 0.005这种框训练价值很低还会拉低回归精度。我一般设一个阈值宽或高小于 8 像素的框直接丢弃。同时坐标越界小于 0 或大于 1的框要裁剪不能直接写进 txt否则训练时数据增强会报错。MIN_PIXEL 8 # 最小框边长按数据集分辨率调整 def filter_box(xmin, ymin, xmax, ymax): if (xmax - xmin) MIN_PIXEL or (ymax - ymin) MIN_PIXEL: return None # 丢弃过小框 # 裁剪到图像范围内 return max(xmin, 0), max(ymin, 0), xmax, ymax阈值 8 不是固定的如果你的图片分辨率普遍在 1920 以上可以提到 12 到 16。判断依据是车牌字符高度至少要 8 像素才能被检测网络有效学习低于这个值标注本身也不可靠。4. 用 YOLOv8 训练黄牌车牌检测配置、参数与验证4.1 数据集目录组织与 data.yaml 写法YOLO 训练对目录结构有约定转换完的图片和 txt 要按下面这样放dataset/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml里写清路径和类别path: /home/user/dataset train: images/train val: images/val nc: 1 names: [plate]黄牌车牌如果只做单类nc就是 1。如果后续要区分颜色names改成[yellow_plate, blue_plate]同时转换脚本里的 class_id 要对应改。路径建议用绝对路径相对路径在不同工作目录下启动训练时容易找不到。4.2 训练参数怎么设从输入尺寸到学习率黄牌车牌属于小目标输入尺寸不能太小。我一般用imgsz960或1280比默认的 640 大原因是车牌在原图里占比小放大输入能让特征更清晰。代价是显存占用上升8G 显存跑 960 大概 batch 能到 8跑 1280 只能到 4。yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ imgsz960 \ epochs150 \ batch8 \ lr00.01 \ lrf0.01 \ mosaic1.0 \ degrees5.0 \ translate0.1 \ scale0.5 \ fliplr0.5 \ patience30 \ projectruns/plate \ nameyellow_v1参数说明lr0初始学习率 0.01 是 YOLOv8 的常用值数据量小于 5000 张时可以降到 0.005 更稳。mosaic1.0开启马赛克增强对小目标检测帮助明显但最后 10 个 epoch 建议关掉让模型适应真实分布。degrees5.0旋转角度别开太大车牌是水平文本旋转超过 10 度反而引入噪声。patience30是早停验证指标 30 轮不升就停省时间。4.3 训练过程看什么指标什么时候该停训练日志里重点看三个box_loss、cls_loss、mAP50。黄牌车牌单类检测cls_loss应该很快降到很低如果一直不降多半是标签类别写错了。box_loss下降到 0.5 以下算正常具体值跟数据难度有关。mAP50在验证集上能到 0.9 以上说明框质量不错低于 0.7 就要回头查标签。如果训练到 50 轮mAP50还在 0.3 附近晃常见原因是标签坐标整体偏移尺寸没对齐、类别 id 不匹配、或者学习率太大导致震荡。这时候别急着加轮数先把验证集的可视化结果导出来看用yolo detect predict跑几张验证图框偏在哪一眼就能看出来。4.4 验证阶段的可视化与阈值调整训练完在验证集上跑预测重点看两类错误漏检和误检。黄牌车牌漏检多发生在逆光、车牌倾斜、部分遮挡的场景误检多发生在车身反光、广告牌文字区域。调整置信度阈值能缓解一部分但根本还是数据覆盖问题。yolo detect predict \ modelruns/plate/yellow_v1/weights/best.pt \ sourcedataset/images/val \ conf0.25 \ iou0.45 \ saveTrue \ projectruns/plate_valconf0.25是默认值如果漏检多可以降到 0.15 看召回如果误检多就提到 0.4。iou0.45控制 NMS 合并阈值车牌密集时别设太高否则相邻车牌会被合并成一个框。5. 黄牌车牌数据集训练的避坑与排查清单5.1 训练 loss 不降框位置整体偏移现象训练几十轮后box_loss卡在 1.0 以上不降预测框全部偏到图像某个角落。原因json 标签里的图片尺寸和实际训练图片尺寸不一致归一化坐标基于错误的分母计算导致所有框系统性偏移。解决用 3.2 节的check_size函数逐张校验把不一致的图片重新按实际尺寸转换标签。如果数据集来自多个批次建议统一缩放到同一分辨率后再标注。5.2 验证集 mAP 很高但实际推理漏检严重现象验证集mAP50到 0.95但拿真实场景图片推理黄牌车牌漏检一半以上。原因验证集和训练集同分布且可能来自同一批采集模型过拟合了这批数据的拍摄角度和光照。真实场景的逆光、夜间、远距离没有覆盖。解决验证集要单独从不同时间、不同设备采集的图片里划比例至少 15%。训练时加强hsv_v和hsv_s增强模拟光照变化。黄牌车牌在夜间偏暗可以专门补一批夜间样本。5.3 空标签文件缺失导致训练报错现象训练启动时报No labels found或图片标签数量不匹配。原因转换脚本遇到没有标注的图片直接跳过没有生成空 txt导致 images 和 labels 数量不一致。解决转换时对每张图片都生成对应 txt没有框就写空文件。训练前用脚本核对两边文件名集合是否一致。5.4 小框太多拉低回归精度现象box_loss能降但降不到很低验证时小目标框抖动明显。原因数据集中混入大量远距离、十几像素宽的车牌框这些框的回归目标本身噪声大。解决按 3.4 节设最小框阈值过滤同时可以在训练时用box损失权重调整但更根本的是把远距离样本单独评估必要时降低其在训练集中的比例。5.5 类别 id 从 0 开始还是从 1 开始搞混现象训练不报错但cls_loss异常或者预测结果类别全错。原因不同标注工具导出的类别 id 起始值不同有的从 0 有的从 1转换时没统一。解决转换脚本里显式做类别映射把原始标签名映射到 0 起始的连续整数。YOLO 要求 class_id 从 0 开始且连续中间不能跳号。6. 黄牌车牌数据集的增量迭代与标签质量自检技巧数据集做完第一版不是终点黄牌车牌检测的难点在于场景漂移——新采集的批次光照、角度、车型分布都会变。我一般会建一个「难例回流」机制每次模型上线后把置信度在 0.3 到 0.6 之间的预测框对应的图片捞出来人工复核确认是漏标还是误检然后补进训练集。这个区间是模型最不确定的地方增量价值最高。标签质量自检有个实用技巧把标注框画回原图生成缩略图网格一眼扫过去看有没有明显偏移或漏标。代码不长但比逐张看效率高得多。import cv2 import numpy as np from pathlib import Path def draw_grid(img_dir, label_dir, out_path, cols6, thumb320): 把标签框画回图片拼成网格图便于快速自检 img_paths sorted(Path(img_dir).glob(*.jpg))[:36] tiles [] for ip in img_paths: img cv2.imread(str(ip)) h, w img.shape[:2] lp Path(label_dir) / (ip.stem .txt) if lp.exists(): for line in lp.read_text().strip().split(\n): if not line: continue _, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) # 缩放到统一尺寸 scale thumb / max(h, w) img cv2.resize(img, (int(w * scale), int(h * scale))) tiles.append(img) # 补齐网格 while len(tiles) % cols ! 0: tiles.append(np.zeros_like(tiles[0])) rows [np.hstack(tiles[i:icols]) for i in range(0, len(tiles), cols)] grid np.vstack(rows) cv2.imwrite(out_path, grid) print(f网格图已保存: {out_path})这段代码把前 36 张图的标签框画出来拼成一张大图cols控制每行几张thumb控制单张缩略图宽度。跑一次几秒钟能快速发现框整体偏移、漏标、类别错乱这几类问题。我每次数据集更新后都会跑一遍比写一堆校验脚本更直观。另一个习惯是给数据集打版本号每次增删样本都记录变更。黄牌车牌数据集从 v1 到 v2 可能只加了 200 张夜间图但模型在夜间的召回可能从 0.6 跳到 0.85这种收益要能追溯否则后面调参时根本不知道是哪批数据起了作用。json 标签在迭代时建议保留原始文件转换脚本也版本化这样任何一版标签出问题都能回滚重转不至于从头标注。最后说个我踩过的坑早期图省事把黄牌和蓝牌混在一个数据集里训单类检测结果模型对黄牌的召回一直上不去查了很久才发现是蓝牌样本占了七成模型偏向蓝牌特征。后来按颜色分层采样黄牌单独保证足够比例问题才解决。数据集的类别平衡在车牌这种颜色差异明显的场景里比调参重要得多。希望帮到你。本文还有配套的精品资源点击获取