简介这套交通信号灯数据集面向计算机视觉、目标检测和自动驾驶领域的开发者与研究人员聚焦红、绿、黄三类信号灯的识别任务。压缩包共包含2000个文件其中1995张jpg图片为真实道路场景中的交通灯拍摄样本覆盖不同距离、角度、光照和天气条件3个json文件采用COCO格式标注记录了每个信号灯的类别标签、边界框坐标等关键信息可直接对接主流检测框架另有2个txt文件可用于数据集的训练/验证划分或类别映射说明。整体压缩包大小约223.94MB目录清晰、导入方便。目前已有594人浏览学习对需要快速获得现成标注数据、节省采集和标注成本的读者来说这套资源能直接支持模型训练与效果验证。借助COCO格式的标注读者可使用YOLO、Faster R-CNN等算法快速搭建交通灯识别模型用于智能驾驶辅助、车路协同或交通监控等场景。1. 交通信号灯数据集zip 里的 COCO 标记到底能干什么先弄明白再动手拿到一个名为交通信号灯数据集可识别红绿黄三种颜色并使用coco格式标记.zip的资源最要紧的不是急着解压开训而是先想清楚你准备拿它解决什么问题。信号灯检测在自动驾驶数据集里属于典型的强语义小目标任务目标只有红、黄、绿三类但在原图上往往只有二三十个像素夜间和白天的颜色表现差异极大标注稍有偏差后续训练出来的模型在真实路口就会漏检、误检甚至把红灯当绿灯。COCO 格式作为目标检测常用的标注标准把图像路径、目标类别、包围盒坐标统一放在 JSON 里换工具、换框架都方便。这篇文章按我自己的落地方案拆解怎么验收这套数据、怎么把 COCO 标注转成 YOLO 能用的标签、训练参数怎么设、坑在哪。适合准备做交通信号灯检测的工程师也适合想把手里数据集整理成 COCO 规范的从业者。2. 先读懂 COCO 标记文件JSON 结构与红黄绿的类别设计2.1 images、annotations、categories 三个顶层字段怎么验收COCO 标记文件的核心是一份 JSON完整版包含 info、licenses、images、annotations、categories 五个顶层字段。交通信号灯数据集为了省事经常会砍掉 info 和 licenses只保留后面三个这并不影响训练但你要知道该查哪些字段。images 数组里每条记录至少要有 id、file_name、width、heightannotations 数组里每条记录至少要有 id、image_id、category_id、bbox、area、iscrowdcategories 数组里则是 id 和 name 的对应关系。很多人在网上下载的 COCO 数据集里 category_id 是乱序的比如 red 的 id 是 3、yellow 是 1、green 是 2这在 COCO 里合法但直接转成 YOLO 就会出大问题。bbox 字段的标准格式是 [x, y, width, height]x、y 是左上角坐标不是中心点也不要求整数。area 字段表示目标面积对检测任务来说最合理的写法是 width 乘 height 的近似值你也可以用多边形精确面积但对矩形框标注来说两者差距很小。iscrowd 表示这个目标是不是密集人群或重叠目标信号灯很少用不过夜间多灯组遮挡时可以拿来标注成 crowd让评估时忽略这部分避免过度惩罚。拿到 JSON 之后我一般先跑一遍快速校验确认类别和 bbox 边界import json from PIL import Image def validate_coco(coco_path, image_dir): data json.load(open(coco_path, encodingutf-8)) # 1. 顶层字段检查 need {images, annotations, categories} missing need - set(data.keys()) assert not missing, f缺少字段: {missing} # 2. 类别映射打印 cat_map {c[id]: c[name] for c in data[categories]} print(类别映射:, cat_map) # 3. 图片分辨率一致性检查 bad 0 for img in data[images]: img_path f{image_dir}/{img[file_name]} try: w, h Image.open(img_path).size if w ! img[width] or h ! img[height]: bad 1 print(f分辨率不一致: {img[file_name]}) except FileNotFoundError: bad 1 print(f图片不存在: {img[file_name]}) print(f异常图片数量: {bad}) # 4. bbox 越界检查 wh {im[id]: (im[width], im[height]) for im in data[images]} overflow 0 for ann in data[annotations]: w, h wh[ann[image_id]] x, y, bw, bh ann[bbox] if x 0 or y 0 or x bw w or y bh h: overflow 1 print(f越界 bbox 数量: {overflow}) validate_coco(annotations/instances.json, images)这段脚本做了四件事检查顶层字段在不在、打印类别映射、核对 JSON 里记录的图片宽高是否与真实图片一致、检查 bbox 是否越界。图片宽高不一致这条最容易踩因为很多人用爬虫或视频抽帧工具生成图片图片尺寸变了但 JSON 没更新YOLO 归一化坐标时会按错误的宽高计算全部标签偏移。越界框检查同样重要COCO 允许 bbox 略微出界但训练时会造成损失函数异常波动最好提前过滤或裁切。2.2 红黄绿三类怎么定直接三分还是单类加属性分支交通信号灯数据集的类别设计看似简单实际有两种主流做法。第一种是按 red、yellow、green 直接分三个检测类别这也是标题里采用的方式推理结果直接就得颜色部署最简单yolov8训练自己的数据集时配置文件写起来也直观适合大多数工程项目。第二种是检测部分只输出一个 signal_light 类把颜色判断交给分类头或后处理颜色阈值适合灯体特别小、颜色受白平衡影响大的场景先框出灯再判断颜色但推理链路多一层代码复杂度更高。我在项目里更推荐折中方案检测头输出三类但把灭灯、状态模糊、闪烁中间状态的灯剔除出训练集只在图里标确定的颜色。原因很直接如果你把灭灯也标记成一个类模型会在三个类别间摇摆不定测试集上就可能把红灯报成绿灯这在信号灯场景里是绝对不允许的误报。类别方案优点缺点适用场景red/yellow/green 三分部署简单推理直接出颜色夜间颜色畸变时分类不稳定常规路口、行车道检测单类颜色属性分支框检测稳定颜色交给后级多一个分类模型调试成本高极端小目标、低照度场景三分状态剔除保留三分部署优势脏标签少需要过滤规则样本量打折扣工程落地首选类别 id 建议固定为 red1、yellow2、green3并且把这个映射写到训练配置文件的 names 里。很多人拿到 COCO 数据集习惯手动重排 category_id转 YOLO 时又忘了同步改 YAML导致训练日志里类别名和真实类别对不上。血的教训是类别映射只维护一处其他位置引用不要复制粘贴。2.3 为什么信号灯数据集更适合用 COCO 而不是 VOCVOC 格式用 XML 也能存三类框而且转换成本很低但 COCO 最大的优势在 annotations 里多出的 area 字段和 iscrowd 字段。area 字段用于按目标大小分层评估COCO 官方指标里的 AP-small、AP-medium、AP-large 就靠它计算。信号灯在原图上经常小于 32x32 像素属于绝对的小目标如果没有 area 字段你就无法批量筛选小目标样本做测试或数据增强。iscrowd 字段在通用数据集里用于人群遮挡在信号灯场景里则可以标记多灯组重叠区域让评估时不计入这部分避免单帧里大量重复灯组把 AP 拉低。从训练管线角度来说COCO JSON 是更好的母版。VOC 的 XML 是每张图一个文件检查类别分布、修改标注都更麻烦COCO JSON 是单文件用一段脚本就能完成统计、筛选、抽样可视化。哪怕你最终训练用的是 YOLO 的 txt 标签也应该把 COCO JSON 当作唯一真源所有 txt 都由脚本从 JSON 生成而不是手工编辑 txt。这样可以保证你在快速迭代时不怕改坏某一类标注重新生成一份即可有后悔药可吃。3. 自己攒一套交通信号灯数据集采集、标注与 COCO 导出的完整流程3.1 场景覆盖与样本去重数据分布比数量更影响 mAP如果手里的数据集有偏科问题补充数据的优先级应该是场景分布大于样本数量。交通信号灯检测的特殊性在于颜色随环境光照变化极大同一个红灯在白天顺光、逆光、夜间、雨天四种条件下呈现出的色值完全不同。我一般把采集分成四个 bucket白天顺光、白天逆光、夜间、雨雾比例控制在 4:3:2:1其中夜间占比不能太低因为夜间信号灯自发光容易被 HDR 合成效果干扰产生红色黄边或绿色白芯的现象。采集来源没有太多玄学行车记录仪原帧、自动驾驶公开数据集、自己拿手机在路口固定机位拍都可以。但要特别注意帧去重。连续视频帧之间相似度太高如果直接全量放入训练集验证集会泄漏训练信息造成 mAP 虚高上线即翻车。简单做法是按帧差去重相邻两帧像素差小于阈值就丢弃一帧或者每隔 N 帧抽一帧。更稳妥的办法是给每张图记录 video_id 和 frame_index后续做数据集划分时按 video_id 分保证同一段视频不会同时出现在 train 和 val 里。3.2 标注规范与工具选型三条红线必须提前约定标注工具方面单人快速产出用 labelme 或 X-AnyLabeling团队协作和在线管理用 CVAT。X-AnyLabeling 界面友好支持矩形和多边形能直接导出多种格式适合标注新手labelme 虽然是老工具但 JSON 结构简单我习惯用它标注再用脚本转换可控性最强。无论选哪个工具标注规范必须先定三条红线。第一条只标亮着的灯体不标整个灯壳。灯壳包含物理结构会让模型学会把熄灭的红灯也检测成红色目标。第二条箭头灯状态明确时按箭头颜色标状态模糊就舍弃不要勉强给一个标签。第三条同一帧里多个灯组存在时一个灯组只标一个有效框不要为了凑样本把同一个灯重复标三次。这些规范不提前写后面模型训练出来后面对复杂路口的误检率会高到让你怀疑人生。3.3 从标注文件批量导出 COCO JSON转换脚本与三个边界坑标注完成后需要汇总成 COCO 格式。下面是一段 labelme JSON 转 COCO JSON 的脚本每张图的标注文件是独立的 labelme JSON输出一个统一格式的 COCO JSONimport json, glob, os from PIL import Image CATEGORIES {red: 1, yellow: 2, green: 3} def labelme_to_coco(image_dir, label_dir, output_path, cat_map): images [] annotations [] ann_id 1 for img_id, label_path in enumerate(sorted(glob.glob(os.path.join(label_dir, *.json)))): with open(label_path, encodingutf-8) as f: label json.load(f) img_name label[imagePath] img_path os.path.join(image_dir, img_name) with Image.open(img_path) as im: w, h im.size images.append({ id: img_id, file_name: img_name, width: w, height: h }) for shape in label[shapes]: label_name shape[label] if label_name not in cat_map: continue pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] x_min, y_min min(xs), min(ys) box_w max(xs) - x_min box_h max(ys) - y_min if box_w 2 or box_h 2: continue # 过滤错误点击产生的极窄框 annotations.append({ id: ann_id, image_id: img_id, category_id: cat_map[label_name], bbox: [round(x_min, 2), round(y_min, 2), round(box_w, 2), round(box_h, 2)], area: round(box_w * box_h, 2), iscrowd: 0 }) ann_id 1 coco { images: images, annotations: annotations, categories: [{id: v, name: k} for k, v in cat_map.items()] } json.dump(coco, open(output_path, w, encodingutf-8), ensure_asciiFalse, indent1) labelme_to_coco(images, labels, instances_train.json, CATEGORIES)这里有个容易翻车的点labelme 的 imagePath 只存文件名如果你的图片和 JSON 同时存在子目录里必须先统一路径规则。脚本里我用 sorted 对 label 文件排序再按顺序分配 image_id但这要求 label 文件名和图片文件名一一对应中间不能有缺失。一旦某张图的 label JSON 缺失后面的 image_id 会整体错位。稳妥做法是先扫图片目录用文件名做 key 建立映射再遍历对应 label 文件保证 image_id 和文件一一对应。第二个坑是 labelme 的 points 是 float 列表转成 bbox 时 x_min、y_min 直接取最小值没有问题但标注员手抖时可能画出交叉的矩形即 points 顺序不是左上到右下。取 min 和 max 的方式可以规避交叉问题但如果出现了宽度或高度小于 2 像素的框一定是误操作直接丢弃比保留更健康。第三个坑是 bbox 坐标的 round 精度2 位小数对训练足够了千万不要四舍五入到整数否则小目标的真实框会被偏移好几个像素。4. 把 COCO 标注喂给 YOLOv8格式转换、训练参数与红黄绿评估4.1 从 COCO JSON 到 YOLO txt归一化坐标脚本和数据集 YAMLYOLOv8 训练自己的数据集时可以直接使用官方数据加载器读 COCO JSON但我在实际项目中更习惯先把 COCO 转换成 YOLO txt 标签。原因是 txt 标签加载速度快且能直接配合 labelImg 做人工复核出现问题容易定位。每一行对应一个目标格式是 class_id x_center y_center width height注意中心点和宽高都要按图片宽高做归一化。转换脚本如下import json, os def coco_to_yolo(coco_path, output_dir): data json.load(open(coco_path, encodingutf-8)) img_info {im[id]: im for im in data[images]} anns_by_img {} for ann in data[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) os.makedirs(output_dir, exist_okTrue) for img_id, anns in anns_by_img.items(): img img_info[img_id] w, h img[width], img[height] stem os.path.splitext(os.path.basename(img[file_name]))[0] out_path os.path.join(output_dir, stem .txt) with open(out_path, w) as f: for ann in anns: x, y, bw, bh ann[bbox] cx x bw / 2 cy y bh / 2 # 防止越界框在归一化后出现负数 cx min(max(cx, 0), w) cy min(max(cy, 0), h) bw min(bw, w - x) bh min(bh, h - y) f.write(f{ann[category_id]} {cx/w:.6f} {cy/h:.6f} f{bw/w:.6f} {bh/h:.6f}\n) coco_to_yolo(annotations/instances.json, labels/train)我写了一段防止越界的 clamp 逻辑虽然 COCO JSON 里越界框不常见但真出问题时会导致 txt 中出现大于 1 的坐标YOLO 训练时虽然不报错却会让模型学坏。参数说明category_id 必须和你后续 YAML 里的类别顺序对应例如 COCO 里 red3、yellow1、green2那么输出 txt 的 class_id 也要保持一致不要自作主张重排。转换完成后写数据集 YAMLpath: datasets/traffic_light train: images/train val: images/val nc: 3 names: 0: red 1: yellow 2: green这里 path 填数据集根目录train 和 val 填相对 path 的路径nc 和 names 必须和 txt 里的 class_id 顺序完全一致。容易犯的错是 train 里混入了 val 的图片或者 path 写了绝对路径又换了机器导致 YOLO 找不到文件直接报错。推荐做法是固定一种目录布局例如根目录下 images/train、images/val、labels/train、labels/val镜像到哪台机器都能跑。4.2 训练命令与关键超参数imgsz、mosaic 和 close_mosaic 对小目标的影响数据准备好了训练命令如下yolo detect train \ datatraffic_light.yaml \ modelyolov8n.pt \ epochs100 \ imgsz1280 \ batch16 \ device0 \ lr00.005 \ optimizerSGD \ close_mosaic15imgsz 是交通信号灯数据集里最重要的参数。默认 640 对 32x32 的小目标太不友好下采样五次后目标在特征图上只剩一个点。信号灯推荐 1280如果显存不够就 960底线是 800。batch 根据显存调整8G 显卡在 imgsz1280 下最多跑到 8 到 16 之间再大就 OOM。close_mosaic15 是指在最后 15 个 epoch 关闭马赛克增强这一步很关键mosaic 增强会把四张图拼在一起小目标经常被切到边缘甚至切碎最后阶段关掉让模型回归到原始尺度做精细收敛。loss 曲线震荡有一个特征值得注意如果前 20 个 epoch 就出现 mAP50 快速上升但后面基本不动那大概率不是模型不行而是训练集和验证集有重叠帧或者标注噪声大。这种时候先回数据不要急着换模型。SGD 作为优化器在小数据集上比 AdamW 收敛更稳关键是要把 lr0 控制在 0.005 以下过高的学习率会让小目标框回归发散。4.3 用 COCO 指标评估红黄绿三类mAP50 和 mAP50-95 分开看训练结束后评估也是 COCO 体系下最理想的方式from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datatraffic_light.yaml, imgsz1280, splitval) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map) print(每个类别的 AP50:) print(metrics.box.ap50)metrics.box.map 是 COCO 标准 mAP50-95对框的位置精度要求很高map50 是只要交并比超过 0.5 就算检测成功的宽松指标。对交通信号灯来说如果 mAP50 在 0.9 以上但 mAP50-95 只有 0.5说明模型能找出灯的大致位置但边缘定位不准。边缘不准在小目标上会导致下游轨迹规划误判所以不能只看 mAP50。打印每个类别的 ap50 时顺序对应 names 的顺序。最常见的异常是 yellow 的 AP 明显低于 red 和 green这几乎总是黄灯样本太少而不是模型能力问题。信号灯数据集里黄灯持续时间短自然采集量少哪怕你做了过采样标注噪声也比红绿灯大。这时候优先回归数据去采集更多黄灯样本其他技巧都是杯水车薪。5. COCO 标记与训练的高频坑从 JSON 校验到验证集划分的排查顺序5.1 image_id 错位训练 loss 正常推理结果却对不上图现象训练过程一点异常都没有loss 降得很漂亮但验证集上模型输出和原图明显不符换一张真实图片测试检测框也乱飘。原因转换脚本里用 enumerate 生成 image_id但同一批数据在不同操作系统或不同文件扫描顺序下文件名排序不一样image_id 就串位了。COCO JSON 的 images 数组顺序本身没有业务含义标注的对应关系完全靠 annotations 里的 image_id 与 images 里的 id 建立一旦错位图片 A 的标签就跑到了图片 B 上。排查随机抽十张图把 COCO JSON 里的 bbox 画到对应 file_name 图片上肉眼比对框位置和目标类别。如果八张以上对得上剩下的对不上基本就是 image_id 错位。解决方法是转换脚本里用 file_name 作为 key先生成 filename 到 id 的映射表再处理 annotations彻底放弃按遍历顺序推断 image_id 的做法。5.2 bbox 越界和负数坐标归一化后静默污染训练集现象训练不报错但 loss 前期下降极慢后期 mAP50 卡在一个低水平换小学习率也上不去。原因COCO JSON 里的 bbox 越界比如 x width 大于图片宽度甚至 width 是负数。转换到 YOLO 时中心点变成负数或大于 1训练代码不会检测这种问题标签会静默参与损失计算。信号灯数据里常见来源是标注工具拖框时超出画布边界或者半自动标注算法生成的坐标存在端点错误。排查在转换脚本里加断言和统计输出越界框数量。修复策略是过滤而非裁切宽度或高度小于 2 像素的框直接丢弃越界部分裁切到图片边界后重新计算宽高和中心点。要注意裁切后如果目标只剩一小条实际已经不可辨认所以裁切完还要再判断一次最小尺寸。5.3 类别 id 不连续YAML 和 JSON 映射错位现象训练日志里类别名字和实际类别对不上比如显示 red实际框住的是绿灯混淆矩阵里某个类完全没有样本。原因COCO categories 数组的使用者没有做连续性检查。比如 JSON 里 red1、yellow3、green4转换脚本直接用了 category_idYAML 里 names 又是从 0 到 2 的列表于是 yellow 和 green 整体错位。COCO 允许 category_id 不连续但 YOLO 训练要求从 0 开始的连续整数。排查打印 COCO 的类别映射和 YAML 的 names逐行比对。最稳妥的做法是转换脚本里维护一个重映射表把 COCO 的 category_id 映射成 0、1、2 三个连续编号同时生成一份 json记录映射关系后续评估时再映射回去。这样哪怕原始数据集类别 id 再乱训练管线也不会受影响。5.4 黄灯样本不足三分类模型被少数类拖垮现象red 和 green 的 AP50 都在 0.9 以上yellow 只有 0.5 左右无论怎么加训练轮数都上不去。原因黄灯实际亮起时间短采集时天然少标注占比可能不到 5%。三分类检测模型在极端类别不均衡下会倾向于把不确定的目标分类到大类黄灯就被 red 或 green 吞掉了。另一个隐藏原因是黄灯颜色介于红绿之间人工标注时本身就有视角差异。排查统计三个类别的样本数和 bbox 面积分布如果黄灯占比低于 10%先做数据层面处理不要急着调损失函数权重。我先尝试对黄灯图片做轻度过采样配合随机亮度、饱和度扰动能小幅提升如果效果不够再考虑引入 focal loss 或给黄灯类别提高权重但要注意避免在验证集上过拟合。5.5 验证集和训练集串帧mAP 虚高上线翻车现象验证集 mAP50 有 0.95看起来非常完美但部署到真实路口漏检率却高得离谱。原因图像来自同一段视频的连续帧随机划分时相邻帧被同时分配到 train 和 val。模型在训练时见过的画面和验证时几乎一样相当于开卷考试分数毫无参考价值。这是自动驾驶数据集里最典型的 mAP 虚高问题。排查划分数据集前必须按视频片段分组确保同一视频的所有帧只在 train 或 val 其中之一。脚本里维护 video_id 和 frame_index 两个字段按 video_id 做分组分层采样。宁可验证集少一点也要保证跨视频验证。如果手里的数据来源庞杂、没有视频信息至少做一次图像相似度去重把感知哈希相近的图片归到同一边。6. 把 COCO 数据集用好小目标增强、状态扩展与最后一步验证习惯6.1 在 COCO annotations 中扩展 state 字段兼顾颜色与方向语义标题数据集只有 red、yellow、green 三个类别但实际路口还大量存在箭头灯左转箭头、直行箭头、右转箭头。如果只保留颜色类别模型输出的 green 语义不足以支撑下游决策。我通常会在 COCO JSON 的 annotations 里额外加一个 state 字段记录灯的状态是常亮、闪烁还是箭头JSON 解析时用 get 方法读取完全向后兼容不影响标准检测训练。箭头灯的采样可以在主模型训练基础上单独抽取一个子集再训练一个轻量分类头专门识别箭头方向这样主检测模型不用重来又能拿到方向语义。6.2 小目标增强的三项实测技巧区域裁剪、贴图复制与尺度扰动交通信号灯大多在 32x32 像素以下哪怕 imgsz 拉到 1280特征图上的有效感受野依然有限。我常用的三招是第一用小目标区域的裁剪作为第二训练流把包含灯组的局部放大后输入模型相当于让模型既看全局路况又看灯体细节第二对灯体区域做贴图复制增强把某个红灯上下轻微扰动后复制到同图的合理灰度位置上增加小目标样本数量第三训练时开启多尺度扰动让 imgsz 在 800 到 1280 之间浮动模拟不同摄像头安装高度带来的尺度差异。这三招组合后小目标类别的 AP50 通常会有 3 到 5 个百分点的提升。最后一环是验证习惯。每次拿到一份新的 COCO 数据集不管对方怎么保证质量我都会先跑一遍第三节的字段校验脚本再随机抽取五十张图把 bbox 可视化叠加在原图上最后按视频分组划分一次 train/val。这三步做完才允许自己把时间投入训练。这些血泪经验让我避免了很多表面指标漂亮、实际不可用的数据集把这套流程当作铁律执行后模型的真实鲁棒性明显上了一个台阶。希望帮到你。本文还有配套的精品资源点击获取
