ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

黄牌检测数据集构建全攻略:json标签转YOLO格式与避坑指南

黄牌检测数据集构建全攻略:json标签转YOLO格式与避坑指南 简介一份面向车牌检测与车牌识别任务的高质量图像数据集素材以黄牌车辆为主涵盖不同拍摄角度与场景适合目标检测模型训练、车牌角度适配及后续字符识别等应用。压缩包内共9324个文件其中包含4662个json标注文件、4627张jpg图片及35张jpeg图片总体积约257.79MB。标注采用polygon多边形格式每个车牌用四个顶点精准对应四角能贴合不同拍摄角度下的车牌形变相比普通矩形框更具几何适应性。所有图片均经过人工逐张筛选已剔除模糊、遮挡或存在歧义的样本数据干净一致可直接输入检测网络进行训练和字符识别也可用于目标检测、车牌定位、字符分割等典型任务显著节省数据清洗与二次标注的时间。当前已有883人学习下载适合需要规整车牌标注数据的开发者、算法工程师及科研人员。若需转为rectangle矩形框或其他标签格式可私信作者进行转换。1. 车牌检测数据集黄牌json标签先弄清它和蓝牌到底差在哪做车辆视觉落地的人基本都有同感车牌检测看着门槛低真正训起来才发现数据全是脏活。尤其黄牌货车、拖挂、农用机械上最常见车牌区域大、字符间距宽、反光严重网上现成的 json 标签数据集又以蓝牌为主拿来做黄牌检测几乎等于重新攒一份。这篇不绕弯子就讲清楚怎么从零搞出一份能直接喂给 YOLO 类模型的“黄牌检测数据集 json 标签”原始数据从哪来、json 标签文件怎么拆、如何转成训练格式以及标注清洗里那些必须提前知道的坑。适合正在用 YOLOv8 或 YOLOv5 训练自己的数据集、但被数据质量卡住的人。2. 黄牌原始数据从哪来采集场景、开源现状与视频抽帧命令2.1 黄牌车的真实分布场景先定场景再拍别眉毛胡子一把抓黄牌车和蓝牌车的分布逻辑完全不同。蓝牌私家车在马路上随处都是随手截几张图就能开训黄牌车集中在物流园区、高速收费站、港口闸口、建筑工地、农用车辆检测站这几类场景。如果你打算做通用黄牌检测我的建议是每个场景至少覆盖三类角度车头正对、30 度左右斜向、以及上下坡带来的俯仰角度。这直接决定模型能不能在真实项目里扛住。我一般会在拍摄前先列一个采集清单把场景分布写清楚避免数据全堆在同一个角度上。给一个可参考的采集表场景建议数量张质量要求物流园区出入口800 以上车牌宽度占画面 1/8 以上包含完整边框高速收费站500 以上避免前车遮挡尽量正对或小角度港口/工地门口400 以上注意扬尘和泥污导致的局部遮挡农用车辆停放区300 以上允许空旷背景但不要拍成远景过小拍摄时手机、行车记录仪、监控录像都可以唯一硬指标是车牌最短边不要少于 40 像素。低于这个值后面标注员自己都看不清楚字符边界标注质量很难保证。另外黄牌比蓝牌长一截标准的 440mmx140mm 尺寸放在蓝牌旁边视觉上就大不少这会让模型在后续推断时对目标尺寸的预设产生偏差数据里必须有意识地混入不同距离下的车牌样本。2.2 开源数据集里黄牌占比很低别指望拿来就能用市面上能找到的车牌数据集比如常见的城市车牌数据集 CCPD绝大多数样本是蓝牌黄牌在其中的比例相当低。另一个常被提到的 UA-DETRAC 是交通监控场景本身定位是车辆目标检测和跟踪车牌区域太小直接拿来做车牌检测需要大量裁剪和二次标注工作量并不比自采小。检查音频、视频、图片、日志、压缩包、文档等各类文件集成病毒检测引擎提供扫描报告与威胁级别判定可对检测文件采取隔离或删除等处置动作提示安全产品若缺少对压缩包内文件的深度检测常常让攻击载荷借壳潜入这是文件检测落地时必须补齐的一环。我的做法是拿开源数据做预标注的种子而不是直接作为训练集。比如先用已有的蓝牌检测模型去对黄牌图片做初筛框再人工修正边界。这样省掉的只有“画框”这一步标注员仍然得逐张看黄牌的字符排列、双层结构、污损反光这些细节必须人来确认。指望一个 json 标签从网上下载下来就能匹配自己的场景基本不现实。2.3 从视频抽帧ffmpeg 命令与去重逻辑如果你能搞到监控视频或行车记录仪片段抽帧是最高效的来源方式。我常用的命令是ffmpeg -i input.mp4 -vf fps1,scale1920:1080 -q:v 2 -fps_mode passthrough frame_%04d.jpg这里fps1表示每秒抽一帧对车牌检测来说足够密了太快反而会让相邻帧高度相似相当于数据翻倍但信息没翻倍。-q:v 2控制 JPEG 质量2 是高质量档车牌上的字符边缘在压缩后仍然清晰。-fps_mode passthrough让输出帧率跟随过滤链避免 ffmpeg 对帧做重采样引入多余处理。抽完帧之后必须做相似度去重。连续两帧的画面如果只有车牌位置差几个像素训练时容易被模型当同一条样本反复记忆导致验证集指标虚高。我一般先抽完帧用感知哈希算法粗筛两张图的汉明距离小于 5 就删掉一张然后再人工扫一遍把镜头快速切换时产生的花帧、运动模糊帧、车辆只露出一半的帧清掉。这个步骤看着琐碎但直接影响后面标注环节的返工率。# 用 ImageMagick 比较相邻帧的结构相似度 # 大于 0.95 的帧删除 ffmpeg -i input.mp4 -vf selectgt(scene,0.05) -vsync vfr frame_%04d.jpg用selectgt(scene,0.05)可以直接让 ffmpeg 只保留画面突变超过阈值的帧配合人工复查比先抽帧再删除省一半时间。注意阈值不要设得太大黄牌车进出闸口时车速慢画面变化本来就不大设 0.05 左右比较稳妥。3. json 标签文件拆开看LabelMe 风格、COCO 风格和校验脚本3.1 车牌检测用 LabelMe 风格就够了polygon 反而是负担车牌检测数据集的 json 标签最常见的两种格式是 LabelMe 风格和 COCO 风格。LabelMe 风格的每个文件对应一张图片顶层包含imagePath、imageWidth、imageHeight、shapes四个关键字段COCO 风格则是把所有图片的标注汇总到一个大 json 里包含images、annotations、categories三个数组。我的建议是如果是从标注工具导出选 LabelMe 风格更省事。车牌是刚性目标矩形框足够表达但 LabelMe 默认会让你标 polygon标注员很容易在车牌四角上多点几下。polygon 看起来精细训练时反而有害——模型目标检测的锚框回归学习的是矩形框参数polygon 边界上一点点的抖动都会被放大成 bbox 抖动。所以我统一要求标注员用 rectangle 模式四个点最多六个点拖拽一个对角再微调另一边即可。3.2 一个标准的黄牌 json 文件长什么样下面这个例子是单张图片、单个车牌框的 LabelMe 风格标签{ version: 5.2.1, flags: {}, shapes: [ { label: yellow_plate, points: [ [128, 256], [512, 384] ], group_id: null, shape_type: rectangle, flags: {} } ], imagePath: frame_0001.jpg, imageData: null, imageWidth: 1920, imageHeight: 1080 }这里points只有两个点分别是矩形左上角和右下角坐标单位是像素以图片左上角为原点。label是类别名建议统一成yellow_plate不要一会写yellow_plate一会写yellowplate后面转格式时踩坑还得回头改。imagePath是相对路径注意它必须和实际文件名完全一致包括大小写和后缀。如果你拿到的数据是 COCO 风格对应的标注字段是bbox: [x, y, width, height]area是面积category_id是类目编号。COCO 的 bbox 用左上角坐标加宽高表达和 YOLO 训练需要的中心点坐标加宽高不是一回事转换时公式很容易搞反后面第四章会给出具体换算方式。3.3 训练前先跑一遍标签校验脚本及时发现问题不管 json 标签来自标注平台还是网上开源训练前都必须做一次完整性扫描。常见问题包括json 文件缺失、图片路径指向不存在的文件、同一张图重复标注、类别名不一致、坐标超出图片边界。下面这个脚本可以一次性扫出这些问题import json, os from glob import glob img_dir images json_dir labels_json for json_path in glob(os.path.join(json_dir, *.json)): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_name data[imagePath] img_path os.path.join(img_dir, os.path.basename(img_name)) if not os.path.exists(img_path): print(f[缺图] {json_path} - {img_name}) continue w, h data[imageWidth], data[imageHeight] for shape in data[shapes]: pts shape[points] for x, y in pts: if x 0 or y 0 or x w or y h: print(f[越界] {json_path}: 点({x},{y})超出 {w}x{h}) break labels set(s[label] for s in data[shapes]) if len(labels) 1: print(f[多类别] {json_path}: {labels})脚本做了三件事查图片是否存在、查坐标是否越界、查类别是否统一。输出会逐条列出问题文件路径照着改就行。这里有个容易被忽略的细节——imagePath里如果写的是绝对路径或含子目录直接用os.path.basename取文件名去匹配图片避免路径层级不一致导致的误报。坐标越界这个检查尤其重要很多标注工具允许你拖出画布边缘标签照样保存但模型训练时 loss 直接飘到 NaN。4. 标签转 YOLO 格式json 转 txt 的归一化细节与目录划分4.1 坐标归一化先加后除方向搞反就全废YOLO 训练用的 txt 标签文件每一行是class_id x_center y_center width height四个坐标值全部归一化到 0 到 1 之间。这里的 x_center、y_center 是 bbox 中心点相对整张图片的比例width、height 是 bbox 宽高相对图片宽高的比例。从 LabelMe 的矩形框左上角点(x1, y1)和右下角点(x2, y2)换算公式如下x_center ((x1 x2) / 2) / imageWidthy_center ((y1 y2) / 2) / imageHeightwidth (x2 - x1) / imageWidthheight (y2 - y1) / imageHeight注意先求中心点像素坐标再除以图片尺寸顺序不要颠倒。有人图省事先各自归一化再加得到的结果差之毫厘谬以千里。另外YOLO 格式不接受负数坐标越界的标注必须在转换时就裁掉或者直接扔掉这张图不要想着让模型去学习越界目标。黄牌特有的一个问题是双层车牌。大货车挂的双层牌上层小下层大标注策略必须先统一是上下两层合起来框一个整体还是各自分开框两个框。我统一建议分开框因为检测到两层后后续识别字符时天然知道哪一层是号牌主体合框会把两层中间的缝隙噪声带入模型。做转换脚本时遇到同一个车牌区域出现两个分离的框不要去合并。4.2 转换脚本直接可用附详细参数说明下面这个脚本把 LabelMe 风格 json 转成 YOLO txt同时处理了双层牌、路径分隔符和类别映射import json, os from glob import glob CLASS_MAP {yellow_plate: 0} img_dir images json_dir labels_json out_dir labels_txt os.makedirs(out_dir, exist_okTrue) for json_path in glob(os.path.join(json_dir, *.json)): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] img_name data[imagePath].replace(\\, /) base_name os.path.splitext(os.path.basename(img_name))[0] txt_path os.path.join(out_dir, base_name .txt) with open(txt_path, w, encodingutf-8) as out_f: for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue pts shape[points] x_coords [p[0] for p in pts] y_coords [p[1] for p in pts] x1, x2 min(x_coords), max(x_coords) y1, y2 min(y_coords), max(y_coords) # 去掉越界部分避免归一化出现负数 x1 max(0, x1) y1 max(0, y1) x2 min(img_w, x2) y2 min(img_h, y2) box_w x2 - x1 box_h y2 - y1 if box_w 2 or box_h 2: continue # 面积太小的框视为废标注 x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h norm_w box_w / img_w norm_h box_h / img_h out_f.write(f{CLASS_MAP[label]} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}\n)我写脚本时特别注意了两点。第一是data[imagePath].replace(\\, /)Windows 上标注导出的路径经常带反斜杠Python 读字符串时\f、\t这类会被转义成特殊字符导致文件名匹配失败。第二是小型过滤box_w 2 or box_h 2把面积小于 2 像素的框丢掉这类框基本是标注员手误点出来的噪点。如果你的图片里有特殊字符类名比如新能源绿牌要叫green_plate直接在CLASS_MAP里加一行即可。4.3 目录划分和训练文件固定随机种子验证集别混入重复帧转换完 txt 标签后目录结构我一般组织成dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── data.yaml划分时用随机种子固定顺序否则每次跑训练数据分布都变实验结果没法对比。下面这段脚本把图片列表打乱后按 9:1 分到 train 和 valimport os, random from glob import glob random.seed(42) imgs glob(images/*.jpg) random.shuffle(imgs) val_count max(1, int(len(imgs) * 0.1)) for i, img in enumerate(imgs): dest val if i val_count else train os.makedirs(fimages/{dest}, exist_okTrue) os.makedirs(flabels/{dest}, exist_okTrue) base os.path.basename(img) os.rename(img, fimages/{dest}/{base}) txt flabels_txt/{base[:-4]}.txt if os.path.exists(txt): os.rename(txt, flabels/{dest}/{base[:-4]}.txt)这里 val 占比 10% 对车牌检测够用因为黄牌场景单一不需要像自动驾驶数据集那样留大量验证场景。划分后打开data.yaml写下路径path: /absolute/path/to/dataset train: images/train val: images/val names: 0: yellow_platepath建议写绝对路径YOLO 对相对路径的处理在不同版本之间有差异写绝对路径少踩一个坑。names 里的编号必须和转换脚本里 CLASS_MAP 定义一致这是最容易被忽略的对应关系。5. 黄牌数据集避坑5 个真实翻车点每一条我都付出过时间代价5.1 绿牌被标成黄牌颜色信息是最后的防线现象训练完模型后在新能源车多的路段连续误检绿色车牌被识别成黄牌。打开标注数据检查发现大量绿牌样本的 label 写的是 yellow_plate。原因标注员在灰度模式下看图绿牌和黄牌在灰度图上色调接近肉眼很难区分一批次标完就全错了。解决在标注规范里强制要求彩色模式看图同时在转换脚本里加一道颜色校验。这里可以写一个小函数对标注框内的图像区域统计 HSV 色相均值绿色的色相在 35 到 85 之间黄色在 20 到 35 之间如果框内绿像素比例超过 30% 但标签写的是 yellow就打印警告。这个校验不能完全替代人工复核但能过滤掉八成以上的低级错误。5.2 拖挂车的双层车牌只标了一半现象模型在高速收费站对拖挂车漏检而且漏检的集中在车尾。检查标注文件发现这些车牌的框高度只有正常车牌的三分之一。原因部分标注工具默认区域是单目标标注员看到双层车牌后习惯性只框下半部分大号牌上半层小号牌完全没标。模型训练时学到的黄牌特征变成“一块窄条形区域”对完整双层车牌响应低。解决规格定义阶段就写明双层牌的标注规则。如果上下两层间距小于上层高度的两倍合并成一个整体框间距再大分两个框标类别名保持一致。更重要的是在转换脚本里统计同一个 json 文件里同类目标的框高度分布如果一批数据中出现大量高度明显偏小的框立刻回溯检查原始标注。5.3 反光导致标注员“脑补”边框现象验证集 loss 一直正常但实际使用中逆光场景车牌检测框偏移严重框边界贴着字符而不是车牌边缘。原因户外逆光下黄牌表面反光标注员看不到真实的金属边框只能凭经验猜一个边界。不同人猜的偏差不同有的贴着字符有的把旁边车灯也框进来了。模型看到的是边界不一致的目标回归自然学不好。解决采集阶段尽量选顺光时段拍摄或者用行车记录仪循环录像时注意遮阳板角度。如果必须用逆光素材标注时打开图像增强辅助工具把亮度临时拉高看清边框再标。建议在标注界面设置一条规则边框模糊看不清的一律先标一个粗框后面专门人复查而不是让标注员自由发挥。数据集质量烂在标注环节后面对话模型再优秀也救不回。5.4 ffmpeg 抽帧产生大量异常帧花屏帧和运动模糊帧现象训练到一半 loss 突然跳高检查数据集发现里面混进了几百张只有半截车身、车牌像被拉伸过的图片。原因用fps1抽帧时如果视频编码有 B 帧某些关键帧之间的插值帧会被 ffmpeg 直接输出画面呈现撕裂快速变道时运动模糊帧也混进去了。人工筛片很难注意这种异常帧它们和正常帧通常只有细微差别。解决抽帧后跑一遍清晰度筛选。我一般用 OpenCV 的拉普拉斯方差判断模糊程度方差低于阈值的帧直接删除。同时把抽帧后相邻两帧做一次结构相似度比较相似度大于 0.9 的删掉。这样处理后数据集整体质量明显提高loss 曲线的毛刺也少了很多。5.5 json 文件名和图片名不同步导出工具的绝对路径陷阱现象转换脚本跑完后统计发现只有六成图片有对应 txt其余图片的标签文件是空的。原因标注平台或本地工具导出的 json 里imagePath有时记录的是标注当时文件的绝对路径比如/home/user/2024/05/frame_001.jpg但实际图片被移动到了images/frame_001.jpg。转换脚本按 basename 匹配时部分文件名中有重复数字或空格导致匹配失败。解决转换之前先做一次全量文件名检查用脚本把 json 里的 imagePath 和 images 目录下的实际文件名做交集比对列出差集再人工处理。不要试图在转换脚本里用模糊匹配去猜猜出来的结果迟早会在训练时翻车。另一个技巧是收集 json 时直接要求标注工具输出相对路径大多数工具在导出设置里可以勾选这一步能在源头上省掉后面的对齐工作。6. 用 YOLOv8 快速验证数据集质量20 轮训练与混淆矩阵查账6.1 最小训练命令数据质量验证不需要大模型只验证标签质量时不要直接上 YOLOv8x 或 YOLOv5m用最小的模型跑 20 轮就够。小模型对数据噪声更敏感数据有问题它会直接体现在指标上大模型反而可能在参数里“消化”掉部分错误。命令如下yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs20 \ imgsz640 \ batch16 \ patience10patience10表示验证集指标连续 10 轮不提升就提前停止质量差的数据集往往十几轮后就开始震荡不会出现虚假的“越训越好”。imgsz640对黄牌这种大目标足够不要用更高的分辨率测试否则会把换行符的输入尺寸差异和标签质量问题混在一起。跑完后看两处results.csv里的验证集 mAP50 和混淆矩阵图。6.2 训练曲线和混淆矩阵要这么读指标高不代表数据干净如果 mAP50 在 0.9 以上看起来很美但打开混淆矩阵发现黄牌和蓝牌类别之间有 15% 的互相误判说明类别之间的区分度有问题。出现这种组合的概率很高——单纯看 mAP 不会暴露具体错在哪里要真正排查标签噪声混淆矩阵是最直观的账本。我还会额外看一眼训练集和验证集的 loss 曲线。训练集 loss 下降而验证集 loss 不降甚至上升通常不是过拟合而是验证集里混入了标错的样本导致验证集本身不可信。这时候做的不是调模型结构而是回去重新清洗数据。一天晚上我调了一个模型的参数怎么都压不住验证集 loss最后发现是验证集里混了几十张标注框偏了大半的车牌图。从那以后我养成了习惯每份数据集训完都留一份可视化验证结果随机抽 100 张验证集图片把预测框和标签框画出来快速过一遍。这个过程 20 分钟能省掉后面正式部署时反复排查问题的几小时。数据集的脏东西不会自动消失只会等你在最不想遇到的时候冒出来希望这篇梳理能帮你把前面的坑填得平一点。本文还有配套的精品资源点击获取
返回列表