简介:交通信号灯数据集专注于红绿黄三种灯色的目标检测,采用COCO格式标注,适合计算机视觉学习者、自动驾驶及智能交通项目开发者使用。整个压缩包共两千个文件,其中一千九百九十五张JPG图片覆盖不同光照、天气、角度与距离下的真实城市交通场景,有助于提升模型在复杂环境中的泛化能力;三个JSON文件按COCO标准记录类别与边界框信息,可直接加载进MMDetection、YOLOv8等常见训练框架;另有二个TXT文件用于类别映射或数据划分说明。压缩包整体约二百二十四MB,结构清晰,便于快速下载与部署。目前已有五百九十四人学习下载,数据量适中,既可用于完整模型训练,也可作为目标检测入门与算法验证的基准数据。借助这套数据,使用者能快速构造红绿灯识别模型,减少自行采集、清洗与标注的时间成本,专注算法调优与落地验证。
1. 交通信号灯数据集:一个直接能用的三分类 COCO 目标检测原料包
做辅助驾驶、车路协同或者城市交通感知,红绿灯检测是绕不开的一环。我之前找数据时翻过车,GitHub 上要么是视频流没标注,要么是国外路口风格和国内差异太大,整理半天心情全无。这个交通信号灯数据集的吸引力在于:它直接给了 COCO 格式的标记,图片按 traffic-light-编号 这种规律命名,目标就是红、绿、黄三种颜色,省掉了从零标注的体力活。对刚接触目标检测的人来说,它可以用来完整走一遍“数据→训练→验证”流程;对老手来说,它适合做预训练底材,或者用来验证检测算法的改动。下面我从标注格式、训练参数到踩坑记录,按实际落地顺序过一遍,顺便给一些能直接抄的脚本。
2. COCO 格式盘一盘:标注 JSON 结构与三个关键字段
2.1 为什么选 COCO 而不是 YOLO txt 或 VOC XML
COCO 格式是目前目标检测领域最通用的标注载体,做检测的人总会在某个环节碰到 COCO JSON、Labelme 输出或者 Roboflow 导出包。这个数据集直接用 COCO 格式封装,意味着你拿到手不用再写 VOC XML 转 COCO 的转换器,少一道人工检查。COCO 的核心结构是三个顶层数组:images、annotations 和 categories。images 里存每张图的 id、宽度、高度、文件名;annotations 里存每个目标框的 image_id、category_id、bbox 和 area;categories 里存类别 id 与类别名。红绿灯检测的类别就是 red、green、yellow,对应的 category_id 常见是 1、2、3,但有些工具会从 0 开始,这是需要确认的第一个坑。
下面是我常用的一张对比表,方便你决定要不要转格式。
| 格式 | 标注结构 | 主要使用场景 | 转换成本 |
|---|---|---|---|
| COCO JSON | images + annotations + categories,bbox 为像素坐标 | Detectron2、MMDetection、Roboflow | 低 |
| YOLO txt | 每张图一个 txt,类别 + 归一化中心点和宽高 | YOLOv5/YOLOv8 | 低 |
| VOC XML | 每张图一个 xml,bbox 为像素坐标 | 早期 PASCAL VOC、SSD | 中 |
虽然 YOLOv8 官方训练支持读取 COCO 格式,但我在实际项目里更倾向于先把它转成 YOLO 的 txt。原因很简单:YOLO 的归一化坐标在训练时少一步求解缩放,batch 读取更快;而且 YOLO 的 data.yaml 配置起来也直观。如果你用 MMDetection,直接喂 COCO JSON 反而更顺,因为 MMDetection 的 CocoDataset 就是为这个设计的。所以不要急着转,先想清楚你后面要跑哪个框架。
2.2 从文件名到标注:先看清目录和类别编号
压缩包打开后别急着开训练脚本,先花两分钟确认目录结构。文件名类似 traffic-light-534-_jpg.rf.f4725a2994734785934e0c77592c2e40.jpg,其中 rf 是 Roboflow 导出时常见的后缀,编号 534、546、549 是原始样本编号。这种命名有一个好处:样本编号是连续的,方便你按区间切训练集、验证集和测试集,而不是靠随机 shuffle。Roboflow 导出的 COCO 包通常包含一个 _annotations.json 文件,里面就是全部标注数据。
拿到手后我第一件事是写个脚本读一遍 categories 和 images 总数,确认类别名称和 id 的对应关系。下面这段代码只做信息检查,不画图。
import json with open("annotations/_annotations.json", "r") as f: coco = json.load(f) print("categories:") for c in coco["categories"]: print(f" id={c['id']}, name={c['name']}") print("image count:", len(coco["images"])) # 粗略统计每个类别的标注数量 from collections import Counter cat_counter = Counter() for a in coco["annotations"]: cat_counter[a["category_id"]] += 1 print("annotation count per category:", dict(cat_counter))逻辑说明:这段代码先读 JSON 顶层 categories,列出类别的 id 和名称,再统计 annotations 里每个 category_id 出现的次数。用途有两个:一是确认是不是红绿黄三类的 id 顺序,二是看类别是否均衡。红绿灯数据里红色和绿色的数量通常偏高,黄色偏少,这是后面训练时要重点处理的类别不平衡问题。
参数说明:_annotations.json 的路径要根据你解压的实际目录调整,有些包会把 JSON 放在根目录,有些放在 annotations 子目录。如果统计出来的类目 id 是 0、1、2,那么后面转 YOLO 格式时不用改;如果 id 是 1、2、3,转换为 YOLO 时记得减一,因为 YOLO 类别 id 默认从 0 开始。
2.3 先画 bbox 再动手训练:把标注可视化出来
检查完数量和类别,先别直接开训练,建议做一次可视化抽查。COCO bbox 的格式是 [x, y, width, height],x、y 是框左上角坐标,width、height 是宽高。常见翻车点是:有人把中心点坐标当成了左上角,或者把归一化坐标当成了像素坐标,导致画出来的框偏移。还有一类问题是标注框超出了图片边界,图片本身是 640×640,但框的 x + width 超过了 640,这在训练时轻则裁切掉信息,重则数据加载报错。
下面这段代码将标注框叠加到原图上,抽查前 5 张,方便你肉眼确认。
import json import cv2 import matplotlib.pyplot as plt ann_path = "annotations/_annotations.json" img_dir = "images/" with open(ann_path, "r") as f: coco = json.load(f) cats = {c["id"]: c["name"] for c in coco["categories"]} # 假设 1=red, 2=green, 3=yellow,可按实际输出调整 color_map = {1: (0, 0, 255), 2: (0, 255, 0), 3: (0, 255, 255)} for img in coco["images"]: img_path = img_dir + img["file_name"] image = cv2.imread(img_path) if image is None: print(f"missing: {img_path}") continue annos = [a for a in coco["annotations"] if a["image_id"] == img["id"]] for a in annos: x, y, w, h = [int(v) for v in a["bbox"]] color = color_map.get(a["category_id"], (255, 255, 255)) cv2.rectangle(image, (x, y), (x + w, y + h), color, 2) cv2.putText(image, cats.get(a["category_id"], "?"), (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) plt.show() # 先抽查 5 张,别全量弹窗 if len(coco["images"]) <= 5: break这段代码首先按 image_id 筛选出当前图片的 annotations,然后用 cv2.rectangle 画框。需要特别注意的是 bbox 里的值是 Python 数字,cv2.rectangle 要求 int,所以先做一次 int() 强转。color_map 里默认按 category_id = 1 红、2 绿、3 黄分配颜色,如果你的类别 id 是 0 开头,需要同步修改。如果你跑的机器没有显示器,把 plt.show() 换成 cv2.imwrite("viz.jpg", image),把结果输出到文件检查,这更适合服务器环境。
我一般会在这个环节顺手统计一下每张图标注框的宽高比。红绿灯的形状大多数是竖长条,宽高比明显小于 1。如果统计结果里出现了大量接近正方形的框,那说明标注可能把灯和灯杆、背景框一起圈进去了,后续训练会让模型学到错误特征。
3. 拿它训练 YOLOv8:从 COCO 转 YOLO 格式到出模型
3.1 转换脚本:把 COCO bbox 转成 YOLO 归一化坐标
我平时跑得最多的是 YOLOv8,因为它开箱即用,对小目标的检测调参相对省事。红绿灯在画面里往往是小目标,所以我会先转成 YOLO txt。转换逻辑很直接:COCO 的 bbox 是 [x, y, w, h],YOLO 需要的是 [class_id, center_x, center_y, norm_w, norm_h],其中 center_x 是目标中心相对于图片宽度的比例,norm_w 是目标宽度除以图片宽度。类别 id 需要从 COCO 的 category_id 减一,因为 COCO 通常从 1 开始,YOLO 从 0 开始。
下面是我常用的转换脚本,处理整个目录。
import json import os ann_path = "annotations/_annotations.json" img_dir = "images" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) with open(ann_path, "r") as f: coco = json.load(f) img_id_to_info = {img["id"]: img for img in coco["images"]} # 按 image_id 聚合标注 from collections import defaultdict annos_by_img = defaultdict(list) for a in coco["annotations"]: annos_by_img[a["image_id"]].append(a) for img_id, annos in annos_by_img.items(): img_info = img_id_to_info[img_id] width = img_info["width"] height = img_info["height"] img_name = img_info["file_name"] txt_name = os.path.splitext(img_name)[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w") as f: for a in annos: x, y, w, h = a["bbox"] cx = x + w / 2 cy = y + h / 2 norm_cx = cx / width norm_cy = cy / height norm_w = w / width norm_h = h / height cls_id = a["category_id"] - 1 # 若 COCO id 从 0 开始则不要减 f.write(f"{cls_id} {norm_cx:.6f} {norm_cy:.6f} {norm_w:.6f} {norm_h:.6f}\n")逻辑说明:脚本先把 COCO 的 images 按 id 建立索引,再把 annotations 按 image_id 聚合,这样每个图片只写一个 txt。宽度和高度直接取自 JSON 的 images 字段,如果这里的数据和真实图片不一致,后面训练会出现标签错位。写入 txt 时每一行是一个目标,五个值分别是类别 id、中心点 x 百分比、中心点 y 百分比、宽百分比、高百分比。
参数说明:norm_cx 等归一化数值保留 6 位小数就够了。cls_id = category_id - 1 这行很关键,如果你的 JSON 是从 Labelme 转出来的,category_id 本身可能已经从 0 开始,那就不要减。判断方法很简单:回到 2.2 的检查脚本,看 categories 里第一个 id 是 1 还是 0。转换完以后,labels 目录下每个 txt 和 images 目录下的图片一一对应,没有标注的图片是不生成 txt 的,这一点要特别留意。
3.2 配置 dataset.yaml 并启动训练
YOLOv8 训练时需要一份 data.yaml,指明训练集、验证集路径和类别名称。这里需要把之前切片好的 train 子目录和 val 子目录路径写正确。常见目录结构是:
traffic-light-dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml 内容如下:
path: /absolute/path/to/traffic-light-dataset train: images/train val: images/val names: 0: red 1: green 2: yellow逻辑说明:path 是数据集根目录的绝对路径,train 和 val 是相对 path 的图片目录路径。YOLOv8 在训练时会自动去 labels 目录下找对应的 txt,也就是把 images/train 替换成 labels/train。names 列表的索引必须和 txt 里的类别 id 对应,顺序不能变。
参数说明:如果训练和验证时提示找不到 labels,多半是 txt 文件没有放进 labels/train 和 labels/val 这两个目录。注意 YOLOv8 要求 txt 文件名必须和对应图片文件名完全一致,包括后缀前的那一段。比如 traffic-light-534-_jpg.rf....jpg 对应的 txt 名就是 traffic-light-534-_jpg.rf....txt,一个字节都不能差。
启动训练命令我一般这么写:
yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=80 \ imgsz=640 \ batch=16 \ project=traffic_light_run \ name=exp1参数说明:model 用 yolov8s.pt 起步,比 nano 精度高,比 medium 训练快;imgsz 选 640 是因为原图大多接近这个尺寸;batch 要按显卡显存调整,8G 显存建议 batch=16,如果报 CUDA out of memory,减半。epochs 我一般先跑 80 到 100,红绿灯类别少,收敛不会太慢。
3.3 训练完成以后看什么:mAP50 和类别分布
训练结束后不要只看 total loss,YOLOv8 会输出 results.png 和混淆矩阵。我习惯先看三个指标:mAP50、mAP50-95、per-class AP。红绿灯这种小目标场景,mAP50 能到 0.9 以上算是可用的模型;mAP50-95 低于 0.5 也不稀奇,因为红绿灯太小,真实框的 IoU 稍微偏移一点就掉分。重点看 yellow 类别的 AP,如果明显低于 red 和 green,那基本可以断定是样本数量太少或者标注框太紧。
如果你用的是 MMDetection,训练命令则变成这样:
python tools/train.py configs/yolox/yolox_s_8x8_300e_coco.py这里不做深讲,因为 YOLO 路线更轻量。关键是:不管用哪个框架,都要把 validation 的结果单独保存成图片。YOLOv8 会在 run/detect/val 目录里生成 val_batch0_pred.jpg,我一般会再写个脚本把它单独挑出来看。如果发现绿色灯被误标成红色,先别急着加训练集,回头检查标注框是不是把相邻的灯也圈进去了。
4. 避坑与常见问题:红绿灯数据训练中的常见翻车点
4.1 训练阶段两个高频报错
现象一:训练时立刻报错AssertionError: Label class ... does not exist。原因是 txt 里的类别 id 超出了 data.yaml 里定义的类别数量。比如 data.yaml 只定义了 3 类,但 txt 里出现了 id=3,而 id 编号是从 0 到 2 的。解决方法是重新跑 2.2 的类别统计脚本,看原始 JSON 里 categories id 是否包括 4 或 5;如果有,就把多余的类过滤掉。我也遇到过类别 id 为 3 但三个类别其实是 0、1、2,这一步纯粹是 COCO 转 YOLO 时减一减错了。
现象二:训练能跑但 loss 不降,验证集 mAP 一直很低。常见原因是训练集和验证集划分时出现了同源图片串扰。Roboflow 导出包的图片文件名带哈希后缀,如果你按文件名前缀切分,比如取前五个字符作为划分条件,很容易让同一个场景的不同帧同时进出训练集和验证集,造成“看起来指标高、实际泛化差”的假象。解决方法是按文件名中的原始编号进行分层采样,比如编号 mod 10 小于 8 的进训练集,其余进验证集,保证编号交叉分布。
4.2 数据标注里的三个隐蔽坑
坑一:bbox 坐标单位不对。有的标注工具导出的是归一化坐标,比如 0.5、0.5 这种,但 COCO JSON 里写的是像素坐标。直接用它去画框会发现框全部挤在图片中心区域。验证方法很简单,把 bbox 的值乘以图片宽高,再看是否落在真实灯体附近。我一般会在 2.3 的可视化脚本里加一行打印,抽查几个框的像素范围是否都在图片尺寸之内。
坑二:黄色灯和红色灯分类边界模糊。红绿灯数据里 yellow 往往最少,而且部分图片中黄色灯在强光下发白,标注人员容易把它标成 red 或 green。解决方法是做一次类别统计,如果 yellow 数量不到 red 的 20%,我会在训练时给 yellow 类别加权重,或者在数据增强时提高亮度扰动。YOLOv8 里可以直接在损失权重层面做调整,但更省事的办法是手动复制黄色灯样本做轻微翻转和旋转,做成简单过采样。
坑三:夜间和逆光样本不足。红绿灯检测在白天容易跑出好指标,一到晚上就翻车。这个资源里如果夜间图占比低,需要额外补充。常见做法是用本地视频抽帧,再用这个模型做半自动标注:先让模型给一个置信度较高的预测框,人工只纠正错框,这样能把标注工作量压到最低。记住不要用模型预测结果直接当标注入库,否则错误会自我强化。
5. 最后的动手技巧:用测试视频验证模型,并快速扩充黄灯样本
训练完模型后,我习惯先用图片测试,再跑一段视频,因为红绿灯是动态场景,前一帧和后一帧的状态变化能帮你发现漏检。你可以用下面这段 OpenCV 脚本,对一段路口视频跑 YOLOv8 的检测结果并输出到新视频。
import cv2 from ultralytics import YOLO model = YOLO("runs/detect/exp1/weights/best.pt") cap = cv2.VideoCapture("intersection.mp4") writer = cv2.VideoWriter("out.mp4", cv2.VideoWriter_fourcc(*"mp4v"), 25, (1280, 720)) while True: ret, frame = cap.read() if not ret: break results = model(frame, conf=0.35, imgsz=640, verbose=False) annotated = results[0].plot() writer.write(annotated) cap.release() writer.release()逻辑说明:模型加载 best.pt 后逐帧检测,conf=0.35 表示只有置信度大于 0.35 的框才保留。红绿灯背景复杂,conf 设太高容易漏掉小目标,设太低会有误检,我一般从 0.35 开始调。imgsz=640 保持和训练一致,视频帧可能被缩放。输出文件我用 mp4v 编码,兼容性问题最少。
参数说明:如果你的测试视频是 1920×1080,但写入分辨率固定 1280×720,画面会被拉伸。建议先用 cap.get(cv2.CAP_PROP_FRAME_WIDTH) 读取原始宽高,再传给 VideoWriter。还有一点:不要把检测结果直接输出到屏幕,如果要看实时效果,用 cv2.imshow 加 break 处理按键退出。
从这以后,我每次拿到新的信号灯数据集,都会强制先做三件事:统计类别分布、可视化抽查 bbox、确认 id 偏移。不做这三步,后面任何训练脚本都可能被同样的问题卡住。这个交通信号灯数据集本身是三分类 COCO 格式,底子干净,省了最耗时的标注环节,但你仍然需要把格式细节和自己用的框架做对齐。希望我的这些踩坑记录和脚本,能帮你在红绿灯检测这条路上少走一次弯路。
本文还有配套的精品资源,点击获取