人行道场景检测这个方向,做智慧城市项目的人应该都不陌生。我在实际项目里最常被问到的就是:市面上开源数据集要么是通用COCO那种,对行人、车辆大而全但缺场景针对性;要么是学术数据集,采集地点单一、天气光照条件理想,模型一上真实道路就露馅。所以当时决定从零攒一套人行道场景的YOLO数据集,前后迭代了几个版本,最终沉淀出10300张可用样本。这篇就把整个构建思路、标注细节、训练验证和踩过的坑完整梳理一遍。
1. 人行道场景检测到底在解决什么问题
1.1 从智慧城市需求反推数据要求
智慧城市项目里,人行道场景检测不是“识别出人”这么简单。实际接到的需求往往是一套组合逻辑:路口是否有人闯红灯、非机动车是否骑上人行道、盲道是否被占用、是否有商贩违规摆摊、环卫清扫是否及时。这些上层应用都依赖一个底层能力——对固定摄像头视野内的人行道区域做准确的目标定位和分类。
我把需求拆解成五类核心目标:行人(pedestrian)、非机动车(包括自行车和电动自行车)、机动车(误入人行道的汽车,比如抢道或违停)、路面障碍物(石墩、锥桶、遗撒物、违规堆物)、以及盲道区域标记。前四类是通用目标检测的范畴,最后一类即使模型不做像素级分割,至少也要能给出一个有效区域框,方便上层逻辑判断是否被遮挡。
从这个需求反推数据集的构建标准:不是说找一堆行人照片标注一下就算完成,而是要覆盖真实的城市道路多样性——不同时段的光照、不同季节的地面状态、不同城市的设施风格、不同摄像头安装角度。同时,类别之间的关联关系要保留,比如盲道障碍物和普通障碍物在视觉特征上容易混淆,需要在标注规范上做明确区分。
1.2 10300张这个规模是怎么定下来的
训练一个YOLO系列的检测模型,规模选择是个平衡问题。太多会浪费采集和标注成本,太少则模型泛化和鲁棒性不足。我按实际经验做了一个估算:5类目标,平均每类目标在不同场景下至少需要1200到1500个有效实例;考虑到单张图中通常有多个目标,一张图能贡献多个实例,那么8000到9000张图就已经能达到每个类别实例数的基本阈值。
实际最终定为10300张,是因为在采集过程中把终端设备的分辨率、真实部署场景的相机型号都纳入了筛选标准,同时兼顾了数据的冗余度。我特意保留了大概10%左右的“难例”——远距离小目标、部分遮挡目标、夜间低照度目标。这些样本在初筛阶段看起来会影响训练指标,但对最终部署效果帮助很大。数据集的规模不是拍脑袋定的,前提是搞清楚你的目标类别数、每类需要多少实例、以及难例占比控制在什么范围。
1.3 项目涉及的核心技术与工具链
整个项目的主线是数据工程,辅线才是模型训练。数据侧涉及采集方案设计、去重与质量筛选、标注规范制定、格式统一化;模型侧涉及YOLO系列结构和训练超参数调整,以及最后的mAP评估。工具链上我用的主要是:Python的OpenCV脚本做视频抽帧;LabelImg做初期标注,后来切到了LabelStudio做团队协同;模型训练用的YOLOv8,具体来说是从yolov8n起步快速迭代,稳定后换yolov8s微调。
选YOLO而不是其他检测框架,理由很直接:智慧城市边缘设备上推理算力有限,YOLO的推理速度优势明显;社区生态成熟,部署转换到TensorRT或者OpenVINO都有现成路径;训练流程对数据集格式的要求规范,YOLO格式的txt标签可以快速转换到COCO或VOC。对这份数据集的目标来说,YOLO系列是最合适的落点。
2. 数据集构建与标注的完整过程
2.1 数据采集的地点和时段分布设计
采集要解决的核心问题是多样性。我按照三个维度做了交叉覆盖:地理区域上,涵盖了商业步行街、老旧小区周边、学校门口、地铁站出入口、工业区主干道、公园内部道路六类典型场景;时间维度上,按白天强光照、傍晚逆光、夜间路灯照明、清晨薄雾四类时段划分;天气维度上,覆盖晴天、阴天、小雨、雨后积水四种情况。
这样的设计最终对模型泛化能力的提升是很关键的。比如夜间低照度下,行人反光衣物和深色衣物在图像上的特征差异巨大,如果训练集缺少夜间样本,模型在夜间部署时召回率会直接崩掉。我在每个场景节点上都保持至少300张以上的图片量,保证单一维度变化时其他维度能够保持稳定,进而能在训练阶段更好地学习目标本身的结构特征。
实际采集用的是固定摄像头录像加人工移动采集结合的方式。固定摄像头位置高出地面3米左右,模拟真实的城市监控角度;移动采集则使用手持设备沿人行道行走,获得俯视和水平视角之间的中间态视角。两种方式的比例控制在7比3,以固定视角为主,因为部署场景大多是固定机位,移动采集的视角是为了提升模型的视角鲁棒性。
2.2 图像清洗和去重的筛选标准
采集得到的原始素材数量远超10300张,直接进入标注会造成大量无效成本。清洗环节我定了几条硬性标准:单张图像中存在至少一个目标类别且目标的可辨识度满足人工标注的基本前提;图像分辨率不低于1280乘720;模糊程度通过拉普拉斯方差值筛选,低于设定阈值的直接剔除;过曝或欠曝导致目标细节完全丢失的图像剔除。
去重环节容易被忽视但特别重要。连续视频抽帧会产出大量高度相似的相邻帧,直接训练会导致模型对重复样本过拟合,测试指标虚高。我用了感知哈希加直方图对比的双重去重策略:先计算图像的感知哈希值,去掉完全相同的帧;再对剩余图像做直方图相似度对比,相似度超过0.95的只保留一帧。这步操作让有效样本从抽帧得到的数万张压缩到合理规模。
去重后还要做一次类间均衡检查。我统计每张图中各类目标的数量分布,发现“行人”类实例数量远高于“路面障碍物”类。如果不干预,模型会对常见类别过拟合,对稀有类别的召回率很低。处理方式是:对路面障碍物类偏少的图像做数据增强,包括水平翻转、亮度扰动、随机裁剪和Mosaic增强;对行人过多的场景,则通过采样控制在每张图中不超过一定数量。
2.3 标注规范和YOLO格式的转换细节
标注是最容易出质量问题且影响最大的环节。我制定的标注规范有几条核心规则:被遮挡面积超过50%的目标不标;目标边缘模糊到人眼无法确认的不标;非机动车类别统一包含自行车、电动自行车、三轮车,不往下拆子类——因为实际部署场景里上层应用并不需要区分车型,拆得太细反而导致同类目标的标注框一致性变差。
类别命名上我用了纯英文小写,避免后续转训练配置时出现编码问题。标注框遵循YOLO格式:每行一个目标,内容为类别ID加上归一化后的中心点x坐标、y坐标、框宽度、框高度。这里有一个容易踩的坑——标注软件导出时,有些工具输出的是左上角加宽高的坐标格式,需要写脚本做转换。我写了一个Python转换函数,确保所有标注文件统一成YOLO格式。
def voc_to_yolo(size, box): dw = 1.0 / size[0] dh = 1.0 / size[1] x = (box[0] + box[2]) / 2.0 y = (box[1] + box[3]) / 2.0 w = box[2] - box[0] h = box[3] - box[1] return x * dw, y * dh, w * dw, h * dh这个转换逻辑不复杂,但它决定了整个数据集能否被YOLO训练流程直接读取。转换完成后我做了全面的校验脚本,逐行检查坐标数值是否在0到1之间、框宽高是否为正数、类别ID是否在合法范围内。发现异常会标注出具体图片编号再回头人工修正。
2.4 数据集划分:训练、验证、测试的分配策略
划分策略直接关系到最终评估结果的可信度。我用的是7比2比1的比例:7210张训练,2060张验证,1030张测试。这里的验证集和测试集是严格分开的,因为验证集用于训练过程中的模型选择和超参数调优,测试集则只在最终评估时用一次,确保测试结果不失真。
划分时还做了一个关键操作:按视频源分组。同一个摄像头采集的连续帧,即使去重后,仍然存在内容相关性。如果按图片随机划分,训练集和测试集可能出现画面高度相似的样本,导致测试成绩虚高。我按照原始视频片段ID进行分组,确保同一条录像只出现在一个分集中。经过验证,这种划分方式比纯随机划分的测试成绩低了2到3个百分点,但那才是真实水平。
数据集目录结构按YOLO标准组织:images下分train、val、test三个子目录存放图片;labels同构存放对应的txt标签文件;另附一个data.yaml,包含路径、类别数量和类别名称列表。这个data.yaml可以直接被YOLO训练脚本引用,不需要额外写加载逻辑。
3. 基于YOLO的训练与验证全流程
3.1 环境配置和模型选型
训练环境我用了单卡GPU,YOLOv8对显存的占用控制得不错。CUDA和PyTorch版本对应关系需要提前确认好,否则训练到一半会因为libcudnn版本不匹配崩溃。我习惯用conda管理虚拟环境,把torch、ultralytics包版本固定下来,确保项目可复现。
模型选型按yolov8n、yolov8s、yolov8m的路线各跑了一轮做对比。实际上对于人行道场景检测,最后部署到边缘设备上时,yolov8n和yolov8s的体积和推理速度更有竞争力。yolov8m的精确度更高,但推理耗时几乎翻倍。我的建议是:先用nano级跑通全流程确认数据和标注没有问题,再用small级做正式训练。直接上大模型会让你在排错时浪费大量时间,因为数据问题在复杂模型下更难定位。
模型结构层面,YOLOv8的C2f模块加强了梯度流动,检测头做了解耦,分类和回归分支分离。这些改进对小目标检测有正向作用,而人行道场景中远距离目标恰恰是小目标为主。这也是我选YOLOv8而没有选更早版本的原因之一。
3.2 训练参数配置和超参数调优思路
训练参数上,输入分辨率、批量大小、初始学习率和训练轮数是最关键的四个超参数。我最终使用的参数组合如下:输入分辨率640乘640;批量大小16;初始学习率0.01,使用余弦退火调度;训练轮数120轮。之后又用这批最优权重继续微调了60轮。
为什么输入分辨率选640而不选更高?原因是部署端的输入尺寸通常会被限制在640左右,训练和推理尺寸保持一致有助于减少尺度失配带来的精度损失。更高分辨率如1280能提升小目标检测效果,但推理耗时和显存占用都会增加,对于智慧城市的实际设备来说性价比不高。
批量大小和输入分辨率共同决定了单卡的显存占用。如果训练过程中出现CUDA out of memory,优先策略是减小批量大小而不是降低输入分辨率。批量大小对BN层统计量的稳定性有影响,过低会导致训练不收敛,所以我保持在16这个平衡点。学习率上,用Cosine Annealing比固定学习率更稳,可以在训练后期平滑收敛,避免在最优解附近震荡。
损失函数部分,YOLOv8默认的CIoU损失对边框回归的收敛效果比较好。我没有额外修改损失函数,但通过类别权重做了调整:对“路面障碍物”和“盲道区域”这两个困难类别,将类别损失权重提升到1.2,因为这两类样本数量少且形状高度不规则,不加权重的话模型容易忽略它们。
3.3 训练过程监测和中间结果诊断
训练过程中不要只看最终mAP,中间指标的变化趋势很关键。我重点盯几个输出指标:box_loss、cls_loss、dfl_loss三条曲线是否同步下降;验证集上的mAP50和mAP50-95是否持续上升;precision和recall两条曲线的相对关系是否在合理区间。
我最常遇到的问题是训练集损失持续下降,但验证集mAP上不去。这个现象几乎都是过拟合或者训练集分布偏斜导致的。处理方式包括:增加Mosaic和MixUp增强强度、增加权重衰减系数、以及回到数据层面检查类别分布。
训练中期还会出现验证集mAP波动明显的情况。这时先不要动超参数,而是确认验证集是否划得干净。我之前遇到过验证集中相邻帧过多的情况——去重时阈值设置得不够严格,验证集和训练集之间的重复度偏高。重新做了严格去重后,验证集的评估结果就恢复了稳定。这个经验说明:数据清理工作做得越扎实,训练阶段省下的时间就越多。
训练的日志我用TensorBoard记录,每个epoch结束时的各项指标都在一个界面上纵向对比。想要判断模型是否还有训练空间,可以看最后的20个epoch里mAP50-95的斜率:如果斜率趋于平缓且波动幅度很小,说明已经收敛,再增加轮数收益很小;如果最后阶段还有持续上升趋势,果断加轮数。
3.4 测试集评估和模型部署前的验证方法
测试集上的评估是整个项目最关键的环节。我的做法分两步:第一步看定量指标,mAP50和mAP50-95要分别记录,因为mAP50对小目标不敏感,mAP50-95才能反映定位质量;第二步是误差分析——把测试集中所有误检和漏检的样本都提取出来,按类别和场景维度统计,搞清楚模型的能力短板到底集中在哪里。
从我这版模型的测试结果看:行人类别的mAP50-95达到了0.87,表现最好;非机动车类别达到0.81,中等水平;路面障碍物类别只有0.64,是最弱的环节。原因很清晰:障碍物的外形极度多样——石墩、锥桶、纸箱、共享单车乱堆,任意两种之间没有统一的视觉模式。这类目标与其让检测模型硬学,不如在部署时通过视角固定区域的背景差分算法做兜底。
部署前的验证要带上实际摄像头画面,而不是拿测试集图片跑一遍就完事。我用部署环境的摄像头实时采集了20分钟视频作为额外的验证素材。把模型输出的结果叠加到视频流上,人工观察误检和漏检模式。这一步能发现静态图片测试发现不了的问题:比如画面抖动导致的误检、逆光过曝区域的漏检、以及多目标交错时ID切换混乱(如果叠加了跟踪逻辑)。
4. 实际项目中踩过的坑和解决办法
4.1 标注不一致导致模型收敛困难
标注不一致是最隐蔽的问题。不同标注员对同一目标的判定尺度不同——有人把远处一个模糊人影标出来了,有人选择不标;有人把电动车的整个车身框进去,有人只框了车身主体不包含后视镜。这些看似微小的差异,累积到上千张就会成为训练时的噪声信号。
我采用的解决办法是:标注规范里附上正例和反例的参考图,对模糊目标标注“不确定”标签后来人工复核;阶段性地从已标注数据中随机抽取5%做二次标注,计算标注一致性。一致性指标低于90%就组织标注员统一校准,校准后再继续。这个流程会多花一些时间,但效果立竿见影,后续训练收敛速度快很多。
4.2 小目标漏检严重:从数据增强和损失加权两个方向解
人行道场景的典型特征是目标尺度差异大——近处的人可能占图像高度的一半,远处的人只有不到30个像素。在640分辨率下,小于32乘32的目标检测难度会急剧上升。初期版本模型对远距离小目标漏检率较高。
数据层面的解法是Mosaic增强和Copy-Paste增强。Mosaic把四张图拼接成一张,等于变相缩小了目标尺度,让模型在训练阶段看到更多小目标;Copy-Paste能把少见类别(比如障碍物)的小实例复制到其他图中,增加该类的小目标样本数量。模型层面的解法是损失加权加检测头多尺度融合。我在训练配置中提高了P3小目标检测头的权重,让梯度更侧重于小尺寸目标的回归。两个方向叠加后,小目标召回率提升了大概9个百分点。
4.3 雨雾天气性能骤降的应对思路
晴天样本训练的模型在雨天表现大幅下降,这个坑在智慧城市项目里特别突出。原因很好理解:雨水会在镜头上形成光斑和雨丝,地面积水产生倒影,玻璃和反光路面上的目标轮廓变得模糊。秋季测试模型在雨天的mAP50比晴天低了15个百分点。
短期解法是采集一批雨天和雨后积水的数据加入训练集,我用前期采集时留下的雨天样本替换了部分晴天样本,重新训练后雨天性能恢复了10个百分点。长期解法是建议部署端搭配去雨算法或图像增强策略,在模型推理前对图像做预处理。从项目交付的角度看,数据采集时就考虑到部署地气候特征是很有必要的,我在初版采集阶段对天气因子的考虑还不够充分。
4.4 数据集后续扩展的规划建议
人行道场景检测数据集的价值在于可扩展性。我目前这版数据集聚焦在目标检测层,但后续完全可以做两件事:一是增加实例分割标注,把当前的目标框精细化到像素级,这样上层应用可以做盲道占用面积评估、路面障碍物体积估算等更精细的功能;二是增加多相机视角的数据,不同高度和角度的相机在零售、安防、交通管理中都有各自的应用场景。
另一个扩展方向是增加类别。比如宠物、儿童推车、轮椅这类在人行道场景中有意义的目标。每增加一个类别,不需要重新采集全部数据,只需要在现有数据基础上增量标注新类别,然后调整data.yaml中的类别配置即可。这对实际项目来说,是用最小成本扩展模型能力的好方法。
5. 从项目复盘看数据工程的心得
整条流程走下来,最大的体会是数据工程在整个检测项目中的占比重超过预期。模型结构、训练技巧固然重要,但数据质量、标注规范、场景覆盖才是决定模型真实水平的基石。很多人把精力全放在调参上,实际跑了大量对比后发现,把数据层面挖掘透带来的提升往往比调三个超参数带来的提升更显著。
在标注规范上的投入是非常值得的。如果重新做一遍这个数据集,我会在前期花更多时间完善标注说明和参考图库——标注员换人最怕的就是标准漂移。另外在划分数据时重视同类视频分组的操作也帮了大忙,它确保了模型的评估结果没有被相邻帧信息污染,这是每个做数据集的人都应该重视的事。
数据集构建过程中,我每天会做一次质量巡检:随机抽取当天标注的样本,在标注工具上人工过一遍框的位置和类别。这个动作不费太多时间,但能及时发现标注软件出问题、标注员理解偏差等早期异常。这个习惯让我避免了最后统一复核时的大规模返工。
另一个值得分享的经验是:训练前先跑一个几十张图的小样本测试,只训练几个epoch,确认数据加载、模型配置、损失函数都在正常工作,然后再全量训练。这个操作看起来多此一举,但事实上帮我发现过两次data.yaml类别顺序写错的严重问题,避免了浪费一整次全量训练的算力。
如果你准备做类似的场景数据集,我的建议是:先把场景定义写清楚,再把类别定义写清楚,然后才是动手采集和标注。场景和类别定义不清晰,后面每一步都会受影响。以人行道这个场景为例,共享单车乱堆放到底算障碍物还是非机动车,这个定义不明确,标注员和模型都会糊涂。定义清晰后,标注、训练、评估都会顺畅很多。