ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

人行道场景目标检测数据集:YOLO格式智慧城市长尾问题实战

人行道场景目标检测数据集:YOLO格式智慧城市长尾问题实战

人行道场景检测,听起来不像自动驾驶那种大热门赛道,但它恰恰是智慧城市落地最频繁、最琐碎、也最容易被低估的一环。我平时做项目就深有体会:算法效果不好,十有八九不是模型的问题,而是数据集跟实际场景压根对不上。最近我整理了一套专门面向人行道场景的数据集,10300张图,全部跑通YOLO格式,说句实在话,这套东西的定位从一开始就不是“通用目标检测数据集”,而是冲着智慧城市里的长尾场景去的——盲道占用、共享单车乱停、小商贩超范围经营、路面破损这些真实痛点。

不管你是刚入门YOLO、想训练自己的第一个检测模型,还是已经跑过不少公开数据集、正在为落地场景的数据发愁,这篇文章都值得你看完。我会把数据集的设计思路、标注规范、目录结构、训练参数这些核心细节全部拆开讲,也会把我踩过的坑、排查问题的方法一并交代清楚。

1. 内容整体设计与思路拆解

1.1 人行道场景为什么难做——长尾分布是最大敌人

智能交通、智慧城管这些项目,表面上是“把所有东西都检测出来”,实际上核心难点在长尾分布。什么叫长尾分布?简单说,人行道场景里常见的、高频出现的目标就那么几类——行人、非机动车、红绿灯,关于这些类别的公开数据集一抓一大把。但真正让项目无法交付的,往往是那些出现频率低、但一旦出问题就会引发投诉的目标:占用盲道停放的共享单车、摆到路面上的经营摊位、破损翘起的地砖、违规驶入人行道的电瓶车。

我用一个生活化的例子来解释这个问题的严重性。假设你训练一个模型用来识别“人行道上乱停的共享单车”,你用通用数据集训练,模型可能表现还不错。但如果这个通用数据集的来源是欧洲城市、美国郊区,那里的共享单车长什么样?跟国内城市随处可见的黄色、蓝色、绿色单车完全不是一回事。通用数据集里这种类别的样本可能只有几十张,而你落地场景里每天要处理成千上万张实时抓拍。你拿一个“见过世面但没见过你家门口单车”的模型去部署,结果可想而知——误报率、漏报率都高到没法用。

这套数据的出发点就是解决这个核心矛盾:不追求类别多到吓人,而是追求每一个类别在你的真实场景里“够用”“扛得住”。10300张图,数量不算夸张,但每张图都经过筛选,保证目标密度适中、场景覆盖到位,并且每张图都带YOLO格式的标注文件,拿来就能直接进训练流程。

1.2 数据集方案选型背后的考量

关于目标检测数据集,业内比较大的公开资源像COCO、BDD100K,确实覆盖面广,类别也丰富。我在很多项目里也用过它们做预训练。但落到人行道这种高度垂直的场景,有四个绕不开的问题:

第一,地域差异大。BDD100K是欧美路况为主,街景风格、车辆型号、行人衣着、道路设施跟国内城市差异明显,直接拿来做智慧城市项目,domain shift带来的性能下降很直接。

第二,类别粒度过粗。COCO里有“person”和“bicycle”,但没有“盲道”“井盖缺失”“占道经营”这种城管和交管关心的对象。换句话说,就算你拿COCO预训练,最后还是要自己标注大量私有类别,迁移成本并不低。

第三,场景单一。很多公开数据集都是在天气良好、光线充足的条件下采集的,而人行道检测项目在实际部署时,雨天、夜晚、逆光、树荫遮挡是常态。没有覆盖这些条件的训练数据,模型一到恶劣天气就“罢工”。

第四,标注颗粒度不统一。公开数据集的标注框往往比较松,有的连被遮挡很严重的行人都框进去,这对训练质量是有负面影响的。

所以我在设计这套数据集时,权衡下来放弃了“大而全”的路子,转向“小而精”:场景聚焦人行道,类别聚焦智慧城市真实项目需求,质量优先,每张图的标注都做过审核。对做落地项目的团队来说,这种数据比“拿一堆通用数据来凑”要实用得多。

2. 核心细节解析与实操要点

2.1 10300张图意味着什么——数据规模与质量的关系

很多人一听到“10300张”第一反应是“够不够?”我的回答是:取决于你的任务复杂度。如果做的是COCO那80类通用检测,一万张确实偏少;但人行道场景如果聚焦到8到12个核心类别,一万多张训练图完全够用了。关键在于类别分布和场景多样性,而不是盲目堆数量。

这套数据的构成大致是:训练集7000张左右,验证集1500张左右,测试集1800张左右。我特意没有把验证集和测试集合在一起,因为很多初学者习惯只分train和val,然后反复用val调参,调着调着就过拟合到验证集上了。留一个独立的测试集,相当于给模型做一次“期末考试”,心里更有底。

从场景覆盖来看,这些图像涉及主干道两侧人行道、背街小巷、商业街区、学校门口、地铁站出入口、公园步道等多种子场景,时间跨上午、下午、傍晚、夜间,天气覆盖晴天、阴天、雨天。之所以强调这些,是因为行人检测和车辆检测在本质上吃的是“场景先验”——一个只在晴天白天训练过的模型,放到夜雨里基本是废的。

2.2 标注规范与类别体系——“检测什么”比“怎么检测”更重要

在开始标注之前,最需要花时间想清楚的,是类别体系。我见过太多项目,标注到一半才发现类别定义含糊,有的框把共享单车和私人单车混在一起,有的把盲道区域的静物也算作“障碍物”,最后模型输出一团糟。这些问题的根源就是没有建立清晰的标注规范。

这套数据的类别体系大概分为几个层级:

交通参与者类,包括行人、非机动车(电瓶车、自行车)、机动车(违规驶入人行道的车辆)。这几个是基础中的基础,几乎没有哪个行人场景项目能绕开。

道路设施类,包括盲道、红绿灯、路灯杆、井盖。盲道是智慧城市项目的重点关注对象——是否被占用、是否破损中断,都是城管部门的考核指标。

违规行为对象类,包括占道经营(摊位)、违规停车(机动车占用人行道)。这类是典型的城管执法需求,通用数据集里基本找不到对应类别。

路面状态类,包括路面破损、积水。这类检测对图像分辨率要求高,但对模型结构要求反而常规,属于“数据到位就能出效果”的类别。

标注规范里还规定了几条硬性规则:目标被遮挡超过70%不标;目标像素宽度小于20像素不标;类别含糊的宁可放弃也不硬标。我在标注时严格执行这几点,因为YOLO系列的标注质量直接影响训练收敛速度和最终mAP——一张标注错误的图,十个epoch都未必能纠正回来。

用YOLO格式的朋友注意了,每张图对应的txt文件,每行格式是这样的:

class_id x_center y_center width height

其中x_center、y_center、width、height都是归一化到[0,1]区间的相对坐标。比如一张1920x1080的图,一个框的左上角是(480, 540),右下角是(720, 810),换算下来就是:

3 0.3125 0.625 0.125 0.25

这里的3如果是类别编号,就代表这个框属于第四个类别。我之前遇到不少人直接拿像素坐标写进txt,结果训练时loss直接飞掉,查了半天才发现是归一化的问题。

2.3 YOLO格式适配与目录结构

拿到数据集以后,第一件事就是确认目录结构是否符合YOLO的训练习惯。这套数据的组织方式如下:

dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── classes.txt

images和labels一一对应,同名文件分别放在train、val、test各自的子目录里。data.yaml里面配置了类别名、训练/验证/测试路径,直接就能喂给YOLOv5、YOLOv8或者YOLOv11使用。

关于data.yaml,有一个小细节经常有人栽跟头——路径配置。如果你用的是相对路径,务必确认训练时的工作目录在哪;如果你用的是绝对路径,那换机器之后一定要同步修改。我通常建议直接改成绝对路径,省事,尤其团队协作时,每个人克隆仓库后只需要改一下data.yaml里的路径就能跑。

3. 实操过程与核心环节实现

3.1 基于YOLOv8的训练配置与参数调整

我之前用YOLOv8s在这套数据上做了完整训练实验,整个流程算是检验数据集质量最直接的方式。硬件环境就是普通的单卡RTX 3090或者4090,没有上多卡分布式。

训练启动命令参考:

yolo train data=/path/to/data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0

这里的几个参数我在实际调的时候有自己的偏好。imgsz=640是速度和精度的折中;如果你主要检测路面破损这种小目标,可以考虑提到768甚至896,但代价是显存占用和训练时长都翻倍。batch=16在24G显存下比较稳,如果你的卡只有12G显存,建议降到8,不然很容易OOM。epochs=100对一万张图的数据集来说比较充裕,我实测大约在60到70个epoch时mAP就趋于平台期了。

关于加载预训练权重这件事,我的态度是:除非你有充分理由,否则强烈建议用yolov8s.pt而不是从零训练。预训练模型在COCO上学到的通用特征——边缘、纹理、形状——对这些垂直场景的迁移非常有帮助。从零训练不是不行,但通常需要多花一倍以上的epoch才能追上。

3.2 关键训练指标解读与调优方向

训练过程中,我习惯重点盯三个指标:box_loss、cls_loss和mAP50-95。

box_loss反映的是框的回归质量。如果你发现box_loss降得很慢,或者反复震荡,先检查标注框是不是有大量不对称或者压根没紧贴目标的情况。cls_loss则体现分类置信度,如果训练后期cls_loss还明显高于初期,多半是某些困难样本在“拖后腿”。

mAP50-95这个指标被人吐槽很多,因为它太严格了——IoU从0.5一路算到0.95取平均。但我依然建议保留这个指标作为验收依据,因为人行道场景里小目标多,如果只盯着mAP50,很可能你看到的结果是“看起来不错”,部署之后才发现小目标的定位精度完全不够用。

我在实验里有一组对照:用YOLOv8s在这套数据上训练100个epoch,mAP50大约在0.62到0.65区间,mAP50-95大约0.38到0.42。说实话,这个成绩单看不惊艳,但放到真实场景里,行人、非机动车、盲道占用这几类核心目标的检测效果是够用的。如果你追求更高的精度,建议把模型换成yolov8m或者yolov8l,显存够的话直接上rticle是yolov8l,mAP能再涨四五个点。

3.3 从训练到部署:ONNX导出与推理验证

训练完的模型不能一直躺在PyTorch权重文件里,落地部署时通常要转成ONNX或者TensorRT格式。导出ONNX的命令很简单:

yolo export model=/path/to/best.pt format=onnx imgsz=640 opset=12

但这里有几个容易踩的坑,我逐个说。第一,opset版本不能太低,否则一些算子不支持,导出会报错。第二,如果你在推理端要用GPU加速,建议导出时加上dynamic=True,让输入尺寸不固定,方便后续接不同分辨率的视频流。第三,导出后务必用onnxruntime或者TensorRT做一次完整推理验证,不要认为“能导出就一定能正确运行”。

我遇到过一种情况:模型在PyTorch里推理一切正常,导出ONNX后,某些小类别完全检测不到。排查下来发现是导出时imgsz设置跟训练时不一致,导致输入张量的缩放逻辑变化,小目标特征丢失了。这种问题很隐蔽,如果不是逐类别对比推理结果,真的很难发现。

4. 常见问题与排查技巧实录

4.1 小目标漏检严重怎么办

人行道场景里有一个天然难点——小目标特别多。一个站在50米外的行人,在1080P画面里可能只有40x80像素,对于yolov8s这种下采样倍数较大的模型,小目标的特征图经过多次缩放后几乎剩不下多少信息。

我处理这个问题的优先级顺序是:先加大输入分辨率,从640升到768或896,这一步通常能带来3到5个点的mAP提升;再加一个浅层检测头,YOLOv8本身是P3、P4、P5三层输出,如果你用小目标专用的yolov8-p2版本,会多一个P2层,专门负责小目标检测;最后才是调anchor,实际上YOLOv8已经用anchor-free了,这一步不需要操作。

如果做完这三步依然漏检,那问题大概率出在数据本身——你训练集中小目标样本的数量是不是太少了?我建议统计一下所有标注框中宽度小于32像素的占比,如果低于10%,数据增强层面可以适当加一些随机裁剪放大,人为“制造”更多小目标样本。

4.2 雨天和夜间效果差怎么破

很多同学拿到数据集,训练完晴天白天的效果很满意,一到雨夜就翻车。这个问题简单来说就是训练数据的“域覆盖度”不够。我这里专门留了大约15%的阴雨夜间图像,就是让大家不用一上来就慌。

如果效果依然不理想,优先检查图像增强策略。YOLOv8自带的增强参数里,hsv_h、hsv_s、hsv_v控制色调饱和度明度偏移,适当调高可以让模型对光照变化更鲁棒。我一般习惯把hsv_h调到0.02左右,hsv_s调到0.7左右,hsv_v调到0.5左右。另外,随机擦除(随机遮挡一部分区域)对夜间遮挡场景也有帮助。

如果实在不想动训练参数,另一个思路是直接在图像预处理阶段做自适应直方图均衡化(CLAHE),把夜间图像的对比度拉起来再送进模型推理。这个方法不增加任何训练成本,实测在很多夜间场景下能明显减少漏检。

4.3 类别混淆与误检的定位方法

误检——把人行道上的垃圾桶认成“占道经营”,把路灯杆的影子认成“路面破损”——这类问题在智慧城市项目里特别常见。排查的时候,先不要急着加数据或者调参数,第一步应该分析混淆矩阵。YOLO训练结束后会生成confusion_matrix.png,这张图能直观展示哪些类别互相“打架”。

我举一个真实例子:有一次模型频繁把“盲道占用”的共享单车框错到“占道经营”上,看混淆矩阵果然这两类交叉严重。仔细琢磨了一下,发现标注阶段这两类在某些角度下的视觉特征确实接近——一辆停在盲道上没放稳的单车,看起来跟一个占地很小的水果摊,在人行道的纹理背景下,从俯视角度确实容易混。我的处理办法是:在标注规范里加了更明确的判定边界——共享单车只算“盲道占用”,摊位必须有明显经营物特征(货架、遮阳伞、秤盘)才算“占道经营”。重新清洗了一轮数据后,这个问题基本消失。

这个案例想说的是:数据集标注的一致性,在某种程度上比模型结构的选择还重要。很多看似“模型不行”的问题,根源都在数据标签的模糊地带。

4.4 训练loss不收敛的常见原因排查

训练loss不收敛,也是高频问题。我把常见原因和排查顺序列一个表,方便你对照操作:

现象可能原因排查与解决
loss一开始就飙到很大标注坐标未归一化检查txt文件坐标是否在0-1之间
loss震荡剧烈不下降学习率过高把lr0从0.01降到0.001
训练集loss下降但验证集loss不降过拟合增强数据、加大dropout、提前早停
mAP50很高但mAP50-95上不去标注框边界不紧重新校准目标边缘
验证集上某些类别AP为0该类别样本过少检查每个类别的样本数量分布

还有一个特别容易被忽视的坑:类别编号不连续。比如classes.txt里面你定义了0、1、2、3、4,但标注文件里出现了类别5,训练时这部分的loss会一直偏高,模型被“带偏”。我的建议是训练前先跑一个脚本统计所有标注文件的类别范围,确认没有越界:

python -c " import os labels_dir = 'labels/train' ids = set() for f in os.listdir(labels_dir): with open(os.path.join(labels_dir, f)) as fh: for line in fh: ids.add(int(line.split()[0])) print(sorted(ids)) "

如果输出结果跟classes.txt对不上,那数据准备阶段一定有疏漏。

5. 训练与业务结合的扩展玩法

5.1 结合智慧城市的实际项目场景

我打算把数据集持续迭代扩充。目前这批图像以城市常规道路为主,后续打算加入更多特殊场景,比如学校门口上下学高峰时段、景区周边、大型活动集会之后的人行道状态,这些场景对城市管理来说才是真正的硬骨头。另外也可以把无人机俯拍视角纳进来,跟常规平视视角形成互补,这样对城管巡逻、社区安防这类需求会更有价值。

数据集本身也可以做成一个小工具链,比如配合简单的统计脚本,能按类别、按时长、按区域输出人流量热力图、非机动车违停高发路段分布图,这类可视化数据对城管部门来说比单纯的检测框更有说服力。

5.2 数据增强策略的进阶思路

对这套数据集来说,常规的马赛克增强、随机翻转、随机缩放已经够用。但如果想让模型泛化性更强,有两个方向可以试试:第一个是MixUp增强,把两张图按一定比例混合,让模型学会在“混乱”背景里识别目标;第二个是随机光照模拟,在训练时随机叠加亮度曲线,模拟清晨、正午、黄昏的不同光照——这个对夜间检测的提升特别明显。

我实测下来的经验是,增强策略不要一次性全开,一次加一个策略,每加一次都跑短epoch的对照实验。这样可以明确知道是哪个策略在起作用、哪个策略反而让效果变差。盲目把所有增强都拉满,很多时候是负优化,因为太强的增强会让模型学到“假纹理”。

最后再分享一个小技巧:当你拿到一套新数据集,不管来源是哪,第一件事别急着开训练,先花半小时随机抽几张图,打开标注框过一遍。看看框是不是紧贴目标、有没有漏标错标、类别分布是不是合理。这套人工抽检动作,比之后追查三天loss异常值要省时间得多。数据集的质量是模型性能的上限,模型结构只是在逼近这个上限。把这行的功夫做到位,后面的事都会顺很多。

RELATED READING

延伸阅读

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