ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

斑马线行人交通灯联合检测数据集构建与YOLOv8训练实践

斑马线行人交通灯联合检测数据集构建与YOLOv8训练实践 简介面向软件开发与智能交通领域研究者的斑马线行人交通灯数据集源码包适用于基于YoLo系列模型的目标检测训练与算法验证可解决城市道路中斑马线、行人、交通灯多目标检测及光照变化影响等问题。压缩包共6个文件包含txt说明、md文档、html页面、gitignore、inscode及license整体约9KB适合轻量快速获取与二次开发。已有53人学习浏览。资源内提供数据集下载指引、说明文档及项目配置入口配合12554张实拍图像、83546个精细标注实例覆盖交通灯13826个、斑马线10706个、行人59014个以YoLoV5 m6权重训练300轮后mAP0.5为0.956mAP0.5~0.95为0.7299可直接作为训练参考基线便于复现实验、对比算法效果也适用于城市安全监控、交通流量分析等场景扩展。 去年做路侧智能感知项目客户提了一个很直接的需求把路口“谁在过马路、什么时候可以过、过马路的位置在哪”一次性识别出来。拆开看就是斑马线、行人、交通灯三类目标的联合检测当时市面上找不到直接可用的斑马线行人交通灯数据集我只能从采集、标注到训练源码全链路自己搭一套。这套方案的源码和数据组织方式最后整理成了一个完整可开源的项目今天这篇就把里面的关键细节一次性讲清楚给正在做智能交通、辅助驾驶、路侧感知的同学一个可以直接参考的样本。1. 为什么一个路口场景需要“三位一体”的数据集1.1 斑马线、行人、信号灯在算法上的强关联性我最初把问题想简单了认为三个目标各检各的就行。但真实路口场景里这三个目标不是独立个体而是互相提供上下文信息的要素。信号灯的状态决定行人是否合法过街斑马线决定行人应该出现在哪条空间通道上行人当前的位置又能反向修正对灯态的判断。比如一个行人在斑马线上快速移动但信号灯是红灯那这个行为本身就是一个需要关注的冲突事件。如果三个模型各自独立预测再把结果简单叠加那么当行人检测漏检、交通灯误检时下游事件判断也会跟着出错。把三类目标放进同一份数据、同一个模型框架下联合学习模型在特征层面就能学到这种空间关联。实测下来联合训练相比三个模型分开跑在有遮挡的小目标上召回率能提高5到8个百分点。原因也直观模型在训练时反复看到“红绿灯在上方、行人在斑马线上”的固定空间结构相当于多了一个隐式的先验约束遇到模糊目标时不容易判断错。1.2 直接拿通用数据集拼装的三大问题市面上不是没有行人数据集和交通灯数据集但直接拿来拼装会遇到三个很实际的问题。第一个是视角不匹配很多自动驾驶数据集都是车顶摄像头视角信号灯在画面正上方行人出现在车头前方而路侧监控是高位俯视角目标和镜头的距离、遮挡关系完全不同模型迁移过去要重新适应很长时间。第二个是缺少“人行横道线”这个类别COCO这种公开数据集里有人、有交通灯但几乎没有把斑马线单独标出来的类别更别说把行人信号灯和机动车信号灯分开。第三个是场景上下文缺失公开数据集里的交通灯图片多半是从远处拍的单个灯头没有行人和斑马线的交互状态模型学到的是“看到灯”而不是“理解路口”。所以我才决定自己建数据。数据量不需要对标千万级但要保证场景真实、目标关系完整、标注语义清晰。这也是整个项目从零开始的核心原因。2. 数据采集与标注最难的不是画框而是定规则2.1 采集地点、时段和相机的选择采集原则只有一条先覆盖场景复杂度再追求总量。同一个机位蹲一天得到的1000张图信息量可能不如不同路口、不同时段的200张图。建议按路口类型、时段、天气三个维度组合覆盖。路口类型要覆盖主干道十字路口、丁字路口、学校门口、商圈附近时段要覆盖早高峰、晚高峰、平峰、夜间天气要覆盖晴天、阴天、雨天、逆光、夜间低照度。相机建议用路侧杆位常见的枪型摄像机分辨率不低于1920乘1080固定机位拍摄。固定机位的好处是后续做跟踪、跨帧统计时不用重复标注缺点是容易拍出大量相似画面所以抽帧间隔不能太密建议1到3秒抽一帧同一段视频最多保留150帧避免连续帧高度冗余。我这份数据集累计采集了7个路口、3种天气、覆盖早期到夜间的视频抽帧后保留约6000张有效图片。2.2 三类目标的框选边界哪些算、哪些不算标注规范直接决定模型上限这块必须以文档形式固定下来否则多人协作时框的风格会五花八门。行人统一用外接矩形框包含完整的头脚遮挡超过一半的行人标为忽略区域不参与训练多个人紧挨时不允许合并框必须逐个标注。交通灯拆成“机动车信号灯”和“行人信号灯”两类框体包含灯头外壳如果灯头在画面里小于20乘20像素就不标因为这种尺寸下标注带来的噪声可能超过信息量。斑马线是标注里最容易产生分歧的类别。我采用的做法是标斑马线的可见条纹区域整体外接矩形一组连续的斑马线条纹只标一个框如果一截被车辆完全遮挡、看不到条纹纹理就把遮挡处断开分别标两个框。这里有个小技巧斑马线横穿画面时直接外接矩形会框进大量路面背景这对检测任务影响不大但会影响后续基于掩码的精细分析所以我会在JSON里把矩形框的四角点坐标和中心点一起存下来为后面做多边形转换留余地。2.3 标注工具与质量检查标注工具推荐用CVAT或labelme两者都支持多人协作导出格式也成熟。我实际用的是labelme因为它的JSON是单文件粒度方便脚本逐张处理。质量检查分两层底层检查看标签和框是否匹配比如斑马线框里是不是真的有条纹、行人框里是不是完整的一个人上层检查看类别分布比如某张图有大量行人却没有交通灯就要回去确认是不是标漏了。人工全面复查成本太高我一般只全量复查“交通灯”这类实例少的类别因为一个漏标灯头的代价比漏标一个行人高得多。信号灯一旦漏标训练时这个位置就会被当作背景模型很容易学到“这种区域不该有灯”的错误信号后面再怎么调参都拉不回来。3. 源码工程从原始标注到YOLO格式的一站式转换3.1 目录结构设计整个源码工程我按“数据不动、脚本可重跑”的原则组织。目录结构大致是下面这样的zebra-crosswalk-traffic-light-dataset/ ├── data/ │ ├── annotations/ │ │ └── labelme/ │ ├── images/ │ ├── labels_yolo/ │ └── splits/ ├── scripts/ │ ├── labelme2yolo.py │ ├── split_dataset.py │ ├── visualize_bbox.py │ └── dataset_stats.py ├── configs/ │ ├── dataset.yaml │ └── classes.json └── README.md设计上刻意把原始标注、中间产物、训练配置分开因为原始标注是资产中间产物随时可以删除重建。这样即使后续YOLO格式有调整只要原始JSON还在一条命令就能重新生成全部产物不用动原始数据。很多项目做到一半就乱了都是因为把所有文件堆在一个目录里清洗脚本跑过之后原数据被覆盖想回溯都无从下手。3.2 防数据泄漏的划分策略这个问题最容易忽略但也最容易毁掉整个评估体系。如果直接把所有图片随机打乱然后按7比1比2划分训练、验证、测试集同一段视频里相邻帧的画面高度相似会造成严重的数据泄漏模型相当于提前见过测试集的近乎原图验证指标虚高得离谱换个路口就原形毕露。正确的做法是按“视频片段”分组。同一段视频的全部帧只能完整地进入训练、验证或测试其中之一绝不跨集合。脚本里我实现了一个函数输入sample_id到video_id的映射表先对video_id分组再在组级别做随机划分。这个逻辑写起来不复杂但效果差别很大分组划分后的评测分数通常比随机划分低几个点那才是真实水平。3.3 格式转换与校验脚本把labelme的JSON转成YOLO格式是源码里最核心的一段。Labelme的points是按xy坐标列表存的需要先算出外接矩形再归一化到0到1。核心逻辑大致是def labelme_to_yolo(points, img_w, img_h): xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) x_center (x_min x_max) / 2 / img_w y_center (y_min y_max) / 2 / img_h width (x_max - x_min) / img_w height (y_max - y_min) / img_h return x_center, y_center, width, height这里有个容易踩的坑归一化后的宽高必须用原图宽高做分母不能用归一化后的坐标差值直接当宽高。两者数学上等价但很多人在手写脚本时会把img_w和img_h写反导致所有框的宽高比例错误。转换完必跑可视化脚本把框画回到图上人工抽查这一步我强烈建议不要省它能发现坐标转换写错、类别映射错位、坐标越界一类的低级问题。4. 用YOLOv8训练参数怎么定、结果怎么看4.1 数据配置文件与训练启动数据集就绪之后训练直接用YOLOv8版本跑起来。数据配置文件放在configs/dataset.yaml大概是这样的path: /path/to/zebra-crosswalk-traffic-light-dataset/ train: data/splits/train.txt val: data/splits/val.txt test: data/splits/test.txt names: 0: pedestrian 1: vehicle_traffic_light 2: pedestrian_traffic_light 3: crosswalk我把行人信号灯和机动车信号灯拆开是为了让下游逻辑能直接区分“车看什么灯”和“人看什么灯”训练阶段也能根据这两个类的召回率差异做针对性优化。训练命令我用的是yolo detect train \ dataconfigs/dataset.yaml \ modelyolov8s.pt \ imgsz1280 \ epochs150 \ batch16 \ device0这里最关键的参数是imgsz我特意用1280而不是默认的640。原因是路侧画面视野大远处的信号灯和行人往往只有几十像素640输入下小目标信息几乎全丢1280或1536输入能明显改善小目标召回。当然分辨率上去之后显存占用也会涨batch从32降到16是把8G显存和训练速度平衡之后的选择。如果显存不够优先保证分辨率其次才是batch。4.2 指标解读与典型失败模式训练完先看mAP50和mAP50-95但我更在意的是每个类别的单独曲线因为类别间mAP差异能直接暴露问题。第一次训练跑完行人mAP50在0.82左右机动车信号灯0.78斑马线0.66行人信号灯只有0.51。斑马线和行人信号灯明显拖后腿。打开PR曲线和混淆矩阵后看得更清楚斑马线漏检集中在远处和被车辆遮挡的区域行人信号灯则大量漏检在夜间低照度场景还有一部分红色车辆误检成红灯、绿色植被误检成绿灯。这说明数据集本身够用但训练配置和标注策略还有优化空间不能把问题一股脑归结到数据量不够。5. 迭代优化把模型从“能跑”调到“能用”5.1 类别不平衡的解法斑马线和行人信号灯的实例数量远少于行人导致模型对稀疏类别学不充分。我采用的第一个手段是给稀疏类样本过采样让每个epoch里这三类的样本占比不低于20%。做法是在DataLoader里维护一个类别统计表当某个batch中稀疏类样本不足时从全局样本池里重复采样这些类别的图片。另一个更有效的思路是复制粘贴增强把标注好的斑马线小图随机粘贴到其他白天场景里只要注意粘贴区域的透视角度不要明显违和就行。这两个组合起来斑马线mAP50从0.66提升到0.73提升幅度比单纯加数据明显得多。复制粘贴增强特别适合斑马线这种结构纹理固定、对空间位置不敏感的目标但对行人这种姿态多变的类别效果就比较差所以增强策略也要按类别定制不能一套打天下。5.2 小目标和发光目标怎么处理夜间和远距离是最顽固的两个问题。小目标靠提高输入分辨率只能解决一部分更有效的是在训练时加大mosaic增强比例因为mosaic随机组合4张图天然生成了大量缩小后的目标模型能被迫学到更小的特征。推理阶段还可以用TTA或切片推理代价是速度下降适合离线分析场景。信号灯发光的特性是另一个难点。夜间灯体过曝成一团白色颜色信息丢失严重模型很容易把一个白色亮斑当成任意颜色。我处理的办法是在标注时把发光核心和灯壳一起框进去训练时对信号灯类别单独做亮度扰动、光晕模拟让模型学会看灯壳形状而不是只看颜色。这个 trick 的思路很简单既然颜色不可靠就逼模型找更稳定的结构特征。5.3 误检案例分析红车与红灯、绿树与绿灯红灯和红色车辆、绿灯和绿色植被这对误检本质是模型过于依赖颜色特征没有充分利用形状、位置和上下文。单靠标注层面很难彻底解决我最后从两个方向下手。一是给模型加位置先验信号灯通常出现在画面上半部而车辆出现在下半部训练时对位置置信度做加权二是在业务层面对检测结果做时序过滤连续多帧判断灯态单帧的偶发误检会被滤波掉。这套组合拳打完误检样本从每百帧十几处降到每百帧两处以内模型才算真正到了“能用”的状态。调优到这一步回头再看整个项目真正的瓶颈早就不是模型结构了而是数据定义和训练策略的细节。构建这个斑马线行人交通灯数据集的过程让我对“数据集即算法上限”这句话理解更深。数据量当然重要但更关键的是每个类别的定义、划分策略、标注边界这些看似琐碎的决策这些决策在训练阶段全都变成了模型的先验直接影响最终效果。如果有条件后续我会在这个基础上补充帧序列标注让模型可以利用时间维度的信息同时把多边形标注版本也放出来方便直接对接分割类模型。整套源码和使用说明都在项目仓库里欢迎拿去跑一版遇到问题照着文中这些坑回头看大概率能少走很多弯路。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进