ARTICLE DETAIL

资讯详情

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

信号灯COCO数据集与MMDetection训练指南:小目标检测避坑全解析

信号灯COCO数据集与MMDetection训练指南:小目标检测避坑全解析 简介这份交通信号灯数据集面向计算机视觉与智能交通领域开发者提供红、绿、黄三色信号灯的原始图像样本适用于目标检测模型训练、自动驾驶感知验证、城市交通视频分析等场景。压缩包共2000个文件其中1995张jpg图片构成核心图像库覆盖不同角度、光线与路口环境下的信号灯样本3个json文件提供COCO格式的目标标注标注框对应红、绿、黄灯位置另有2个txt文件用于类别列表或训练/验证划分说明整体容量223.94MB。已有594人学习/浏览该资源。使用者可直接基于COCO标注接入主流检测框架如YOLO、MMDetection等免去手动标注成本图片命名以traffic-light前缀统一编号便于按批次管理、数据增强与样本筛选。该数据集适合作为算法实验、毕业设计或智能交通项目前期数据支撑也可帮助初学者快速上手目标检测全流程。1. 交通信号灯数据集与 COCO 标注为什么目标检测的起点往往是一份 JSON如果你正在做车路协同、自动驾驶感知或者路口视频分析大概率会先下载一份“交通信号灯数据集可识别红绿黄三种颜色并使用coco格式标记.zip”。解压后里面通常就是路口图片、一个标注 JSON 和类别说明图里有红灯、黄灯、绿灯三种目标每个目标都给了矩形框和颜色类别。COCO 格式的价值在于它不是把类别写在文件名里而是用结构化的 JSON 把图片、标注、类别串成一张关系表主流检测训练框架读取后可以直接算损失、出评测不用你手工解析。这篇笔记按“读标注、跑训练、做体检、排问题”的顺序展开适合刚拿到数据集的初学者也适合在信号灯小目标上反复漏检的工程师参考。2. 读懂 COCO 格式的信号灯标注从 json 字段到三色类别划分2.1 一张图里的三段结构images、annotations、categories 各管什么COCO 格式的标注文件是一个大 JSON最外层的键有info、images、annotations、categories四部分。images数组里每条记录代表一张图常见字段是id、file_name、width、heightannotations数组里每条记录代表一个目标字段是id、image_id、category_id、bbox、area有时带segmentationcategories数组把类别 ID 映射到类别名。这三段不是平级的三份数据而是通过id和image_id串起来的图片是父表目标是子表类别是字典。信号灯数据集里一张图往往有多个灯组一个灯组里可能左右排列着红、黄、绿三颗灯珠这种父表子表结构比 VOC 的 XML 更贴近真实路口。import json with open(annotations/instances_train.json, r, encodingutf-8) as f: coco json.load(f) img coco[images][0] ann coco[annotations][0] print(image 字段:, img) print(annotation 字段:, ann) img_id_to_file {item[id]: item[file_name] for item in coco[images]} dup_ids len(coco[images]) - len(img_id_to_file) print(重复 image id 数量:, dup_ids)用字典推导把 image id 映射到文件名顺手检测重复 ID。有些标注工具导出的包images里同一张图被写了两遍训练时不一定会报错但会重复计算 loss让训练曲线变平滑验证却偏低。另一点要注意file_name是相对于data_prefix的路径如果数据集把图片放在train2017/下file_name通常写成train2017/xxx.jpg训练配置里就不能再加一次文件夹名否则路径会变成train2017/train2017/xxx.jpg。bbox是[x, y, width, height]原点在图像左上角area一般等于width * height但在带segmentation的多边形标注里更严格的定义是多边形面积。信号灯通常没有复杂轮廓矩形框足够所以大部分包只提供 bbox 不提供掩膜这对检测任务没有影响。2.2 红黄绿在 categories 里怎么编号类别 ID 稳定比名称更重要类别 ID 决定了训练时 loss 是怎么分配颜色的。先写一个统计脚本确认包里到底有几个类别、每类多少框再决定要不要调整。import json from collections import Counter with open(annotations/instances_train.json, r, encodingutf-8) as f: coco json.load(f) id_to_name {c[id]: c[name] for c in coco[categories]} print(categories:, id_to_name) counts Counter() for ann in coco[annotations]: counts[id_to_name.get(ann[category_id], unknown)] 1 print(类别统计:, dict(counts))这段逻辑很简单遍历所有标注按category_id翻译成名字再计数。输出里如果出现unknown说明标注里写了 categories 之外的 ID这是不合规包最常见的错误之一。类别名本身不重要重要的是category_id在训练配置、模型输出、评测脚本三处保持一致。如果你自己合并过两个数据集A 包红灯 ID1B 包红灯 ID0合并后的 JSON 必须统一我习惯用{red: 1, yellow: 2, green: 3}这样的规则重新映射而不是依赖原始文档里的文字描述。颜色名称也会有坑有人标注成red_light、green_light、yellow_light有人标注成红色、绿灯同一份 JSON 里出现两种写法模型不会报错但会把它们当成两个类别导致黄灯被拆成“黄灯”和“yellow”两类各自的样本量都不够。拿到包的第一天就统一类别词表能省掉后面所有调试时间。2.3 信号灯是典型小目标bbox 与 area 的换算关系会直接改变评估结果COCO 官方评测把目标按面积分三档小目标面积小于32*321024像素中目标小于96*969216像素以上算大目标。这个阈值不是拍脑袋定的它直接影响mAP_small、mAP_medium的划分。信号灯很有趣在 1920x1080 的图片里一颗 40x40 像素的灯面积是 1600 像素按阈值属于中目标但如果把图片缩放到 640x640灯变成约 18x18 像素面积不到 400 像素又变成了小目标。import numpy as np areas [ann[area] for ann in coco[annotations] if ann[area] 0] if areas: print(面积分位(px):, np.percentile(areas, [10, 50, 90]).round(1))用分位数看目标尺寸分布比看平均值可靠。如果 50 分位面积只有 500 像素说明大量灯珠在图里非常小训练时应该用原图或高分辨率输入千万不要为了省显存把图缩到 512。如果面积分布里冒出好几个面积几万像素的框多半是标注把整个信号灯箱体框进去了而不是单颗灯珠这类样本会让模型学到“灯箱就是灯”部署时反而在远距离漏检。COCO 档位面积阈值信号灯常见表现small 1024 px远距离路口灯占大头medium 9216 px近景灯组模型最容易学习large 9216 px灯箱特写通常不是单灯这个表和 bbox 换算关系决定了你后面看 mAP 时应该关注哪一档。如果模型mAP_medium高、mAP_small低这是信号灯任务的常态不是模型坏了。3. 用 MMDetection 跑通这份 COCO 信号灯数据集最小配置与三个必调参数3.1 为什么优先选 MMDetection而不是自己写训练循环拿到 COCO 格式的数据最省事的路线是直接上 MMDetection。它把数据加载、模型、训练策略全部配置化改几行就能训练而且评测输出直接包含mAP_small、mAP_medium这些档位指标。这套生态里不止有检测像 mmrotate 训练 DOTA 旋转框数据集、MMSegmentation 做道路分割用的都是同一套数据接口你在这里踩过的 COCO 坑换到旋转框、语义分割任务依然适用。如果之后想用 YOLOv8 训练自己的数据集做部署也是先把 COCO 转成 YOLO txt 再喂进去不建议直接从 COCO 绕开检测框架手写训练循环。Detectron2 也支持 COCO但配置风格和社区资源这两年明显不如 MMDetection 密集信号灯这种小目标任务需要频繁调img_scale和增强MMDetection 的配置覆盖更直接。我的习惯是先用 MMDetection 把基线跑出来确认红黄绿三色可分再谈部署优化否则直接上部署框架黄灯翻车时很难分清是数据问题还是模型问题。3.2 最小配置改 metainfo、数据根目录与 annotation 路径先把数据整理成标准的 COCO2017 风格目录训练集和验证集分开mkdir -p data/traffic_light/train2017 mkdir -p data/traffic_light/val2017 mkdir -p data/traffic_light/annotations然后写一份基于 Faster R-CNN 的配置。下面这份配置只改数据部分模型结构保持 COCO 预训练权重初始化_base_ ../faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py data_root data/traffic_light/ metainfo dict( classes(red, yellow, green), ) train_dataloader dict( batch_size4, num_workers4, datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/instances_train.json, data_prefixdict(imgtrain2017/), ), ) val_dataloader dict( datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/instances_val.json, data_prefixdict(imgval2017/), ), ) val_evaluator dict( ann_filedata_root annotations/instances_val.json, ) default_hooks dict( checkpointdict(interval3, save_bestcoco/bbox_mAP), ) train_cfg dict(max_epochs40)这段配置做了三件事用metainfo覆盖类别名称把训练和验证的数据路径指到正确位置把验证集标注文件单独传给val_evaluator。data_prefix只写train2017/因为file_name本身可能已经包含train2017/前缀如果两边重复路径会拼接错误。训练命令python tools/train.py configs/traffic_light/faster-rcnn_r50_fpn_1x_tl.py --work-dir work_dirs/traffic_light如果你的 JSON 没有单独的验证集可以用tools/misc/split_coco.py从训练集里按比例切出一份保持类别的比例一致尤其要保证黄灯在验证集里也存在。3.3 三个必调参数img_scale、batch_size、eval 间隔第一个参数是img_scale。信号灯是小目标全图缩放会让灯珠变得更小所以分辨率不能一味求低。默认(1333, 800)是 COCO 预训练常见的输入但如果你手上的原图是 1920x1080建议测试推理时用(1920, 1080)或者至少在验证时开多尺度。代价是显存上升因此下一步调 batch。第二个参数是batch_size。交通灯数据集通常只有几千张BN 层的统计量需要足够多样本才稳定。按显存调节显存batch_size备注6GB2配梯度累积凑到 1611GB4常见起步值24GB8可以开更强的增强第三个参数是eval间隔。上面的配置已经把checkpoint.interval改为 3save_best设为coco/bbox_mAP。默认 12 epoch 存一次权重对信号灯数据集太稀疏黄灯曲线要到很晚才出现你等于在盲跑。每 3 个 epoch 验证一次能提前看到mAP_small是否在涨也方便判断是否要提前停车。这三个参数之外初始学习率lr0.001对 batch_size16 是合理的如果你的总 batch 小于 16学习率要相应下调到 0.0005 左右否则训练前期 loss 不降是常态。4. 训练前先做数据体检COCO 标注质量与信号灯增强策略4.1 检查空洞、重复与越界一个一跑就懂的小脚本数据集的坑多半不在模型而在标注。首要查的是空洞图里有但没有任何目标。信号灯数据里经常混入无灯路口、隧道进出帧、天色全黑的无效图如果这类图占了三成模型会把“没有灯”学成“不输出”验证集因为空图不参与计算而毫无察觉。import json with open(annotations/instances_train.json, r, encodingutf-8) as f: coco json.load(f) img_ids {img[id] for img in coco[images]} ann_img_ids {ann[image_id] for ann in coco[annotations]} empty_ids img_ids - ann_img_ids print(f无标注图片数: {len(empty_ids)}) for img in coco[images]: for ann in coco[annotations]: if ann[image_id] ! img[id]: continue x, y, w, h ann[bbox] if x 0 or y 0 or x w img[width] or y h img[height]: print(f越界框: {img[file_name]}, bbox{ann[bbox]})脚本用 set 差集找空图再遍历所有图片和标注判断 bbox 是否越界。越界框常见于标注工具的坐标系错乱从 PASCAL VOC 转 COCO 时xmax和width含义混用右边多算一个像素。MMDetection 训练时会忽略部分越界样本但导出的模型会在图像边缘漏检训练前修掉最省事。还需要查重复标注 ID。annotation的id是全局唯一键如果两份数据拼接时没重新编号训练时可能出现同一个目标被当成两个目标的情况loss 计算不平滑。4.2 模糊、过曝与夜间样本该删的要删该留的要多留信号灯本身的颜色特征很强但真实路口的车灯炫光、雨夜反光、逆光都会让灯珠在照片里接近白色。这类图片不是噪声而是部署时一定会遇到的情况。我的处理方式是模糊到人眼都判断不了颜色的先删过曝但位置可辨的保留并在增强阶段加入颜色扰动。可以写一个脚本统计每张图标注框内的亮度均值和模糊程度输出成 CSV 让人工抽样检查import cv2 import json import numpy as np with open(annotations/instances_train.json, r, encodingutf-8) as f: coco json.load(f) img_id_to_info {img[id]: img for img in coco[images]} rows [] for ann in coco[annotations]: img img_id_to_info[ann[image_id]] path data/traffic_light/ img[file_name] image cv2.imread(path) if image is None: continue x, y, w, h [int(v) for v in ann[bbox]] crop image[max(y,0):yh, max(x,0):xw] gray cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) laplacian cv2.Laplacian(gray, cv2.CV_64F).var() rows.append((path, laplacian, float(gray.mean()), ann[category_id])) print(模糊度低于 50 的样本数:, sum(1 for r in rows if r[1] 50)) print(亮度高于 240 的框数:, sum(1 for r in rows if r[2] 240))Laplacian方差低于 50 通常意味着画面模糊高于 240 的亮度均值说明框内几乎纯白。这些数字不是绝对标准但可以帮你从几千张图里快速筛出重点要看的几十张。纯白灯珠框对红灯影响小对黄灯影响大因为黄灯褪色后和红灯、白灯高度相似。4.3 针对小目标的增强mosaic、多尺度与复制粘贴哪个对灯真有效信号灯不建议一上来就把 Mosaic 开到最大。Mosaic 把四张图拼在一起整体缩小目标尺寸对本来就小的灯珠不友好。如果你的图片是 1080p我更推荐先调多尺度随机缩放在 0.5~1.5 倍之间随机改变输入尺寸模拟相机安装角度的远近变化。train_pipeline [ dict(typeLoadImageFromFile), dict(typeLoadAnnotations, with_bboxTrue), dict( typeRandomResize, scale(1333, 800), ratio_range(0.5, 1.5), keep_ratioTrue, ), dict(typeRandomFlip, prob0.5), dict(typePackDetInputs), ]这里的ratio_range(0.5, 1.5)表示每次随机把图片缩放到原图的 0.5 到 1.5 倍。对信号灯来说0.5 倍会大幅缩小灯珠因此我不建议把下限放到 0.3。RandomFlip需要注意如果数据里包含了“红灯在左、绿灯在右”的箭头灯随机翻转会让灯组左右关系混乱箭头灯数据集一般关掉翻转或只在单方向做。对黄灯这种样本不足的类别复制粘贴增强比过采样更有效。把黄灯灯珠从原图抠出随机贴到没有灯的背景上注意避开已有灯组区域重新计算 bbox。灯的语义很小、边缘清晰粘贴后不违和这是信号灯任务里少有的、可以直接放心用的 copy-paste 场景。5. 信号灯 COCO 数据集的避坑清单五个真实现象与排查方法下面这五条是实际项目中翻车频率最高的排序也基本是按出现次数来的。5.1 红绿都准黄灯永远召不回现象验证集上红灯 mAP 0.85绿灯 0.82黄灯只有 0.1。 原因黄灯样本太少且黄灯在远距离和红灯亮度接近训练时 loss 被红灯主导。 解决先看第 2 章的类别统计若黄灯占比低于 5%先做复制粘贴增强把黄灯数量拉到红灯的一半再把黄灯类别的分类 loss 权重调高在loss_cls的class_weight里给黄灯更高权重如果权重拉满仍无效说明原始标注里黄灯本身质量差需要回看标注框。5.2 验证 mAP 高实车或路口视频却疯狂漏检现象COCO eval 的 bbox_mAP 超过 0.7但把一段路口视频逐帧喂进去远距离灯一个都检不出来。 原因COCO eval 的结果是高分辨率图片上的指标视频帧往往被播放器或采集卡缩放过灯进一步变小另外视频帧的过曝和运动模糊在训练集里比例不足。 解决用原图分辨率做测试检查img_scale是否小于原图评测时单独打印mAP_small如果它只有 0.2问题主要在小目标策略同时在训练集里加入 2 倍降采样样本让模型提前见过更小的灯。5.3 COCO eval 全为 0类别 ID 从 1 开始还是从 0 开始现象训练 loss 正常下降每个 epoch 都正常保存权重但验证输出里 bbox_mAP 一直是 0。 原因数据集的 categories ID 可能是0,1,2而框架默认类别从 1 开始或者验证集 categories 与ann_file不匹配导致 gt 全部匹配失败。 解决先打印model.dataset_meta或coco.cats看实际读到的 ID再把metainfo的类别顺序与 JSON 里categories的 ID 对齐。一个稳定做法是把categories写成[{id: 1, name: red}, {id: 2, name: yellow}, {id: 3, name: green}]训练配置里classes顺序保持一致不要依赖名称去猜。5.4 箭头灯和圆盘灯混标绿灯类别内部打架现象绿灯 mAP 不低但输出里箭头右转灯和圆盘灯的置信度经常抖动时高时低。 原因标注时没有区分灯组类型同一类别里既有圆盘灯、箭头灯还有倒计时数字区域模型不知道“到底该学哪种”。 解决如果业务只需要红黄绿三种颜色先按灯组拆分标注文件把箭头灯单独留作困难样本如果下游业务需要方向就把 categories 扩成red_left、red_straight、green_left、green_straight再训练。这一步必须在标注阶段决定后面改类别代价很高。5.5 过曝把黄灯拍成白色颜色特征失效现象白天夜间都正常傍晚逆光和雨夜黄灯被识别成红灯或直接漏掉。 原因标注框内平均亮度极高RGB 三通道都接近 255黄色特征完全丢失。 解决在增强里加入亮度偏移和模拟过曝把正常黄灯图的部分区域拉成白色再让模型同时学习“灯珠位置”和“颜色类别”。如果数据集本身包含过曝样本不要删把它们单独归到验证集用来观测真实场景下的鲁棒性。6. 进阶用红黄绿互斥规则做后处理顺便给数据集补上验证基线拿到一份“红黄绿 COCO”的数据集除了训练还有一个常被忽略的用法用它验证路口灯组的互斥性质。正常交通信号灯组在同一时刻只会亮一种颜色红、黄、绿三者互相排斥。如果模型同时输出“红灯 0.6”和“绿灯 0.5”大概率是模型问题也可能标注里混了黄灯闪烁的中间帧。把互斥约束加进后处理能直接砍掉一部分明显错误的输出这个方向也和你后续要做信号灯控制算法比如强化学习做配时天然衔接因为感知输出必须符合灯组逻辑。一个简单的做法是把检测框按位置聚类成灯组每个灯组内部选最高置信度的颜色作为最终结果再用逻辑判断过滤掉“同一灯组同时出现红绿”的输出。def same_group(box_a, box_b, dist_thr80): ax, ay, aw, ah box_a bx, by, bw, bh box_b # 两个框中心距离足够近视为同一灯组 cx (ax aw / 2) - (bx bw / 2) cy (ay ah / 2) - (by bh / 2) return abs(cx) dist_thr and abs(cy) dist_thr def filter_conflict(dets): dets sorted(dets, keylambda d: d[score], reverseTrue) keep [] for det in dets: conflict False for k in keep: if same_group(det[bbox], k[bbox]) and det[category_id] ! k[category_id]: conflict True break if not conflict: keep.append(det) return keep这段代码按置信度从高到低保留检测框如果新框和已保留框距离很近且颜色不同就认为是一次红绿互斥冲突直接丢弃。dist_thr80是经验值需要根据图片里灯组的像素间距调整它并不完美但足以在部署阶段把红色和绿色同时出现的明显误检压下去。跑完验证集后分别记录加规则前后的 mAP只要提升超过 1 个点规则就值得固化成部署代码。往里再走一步你可以用这份 COCO 标注做坏灯筛查如果模型的绿灯高置信度输出持续一整段视频而同灯组的红灯从不输出很可能是灯组损坏也可能是标注本身就是错的。这类应用和自动驾驶感知是反过来的它需要的是稳定输出而不是追逐极限 mAP。做到这一步一份数据集的边际价值就远远不止训练模型了。我自己每次拿到新数据集都会先跑一遍体检脚本再训练这个习惯已经帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表