做智慧城市项目最烦的不是模型选型,而是数据场景不匹配。我之前拿通用检测模型直接跑人行道视频,白天看着还行,一到傍晚、树影、逆光、蹲推婴儿车的人群,误报漏报齐上阵,连"两个并肩行人只出一个框"这种低级失误都频繁出现。换成人行道场景检测数据集(10300张)配合YOLO重新训练后,效果提升非常明显。这篇内容就围绕"人行道场景检测数据集 + YOLO + 智慧城市落地"展开,讲清楚数据集里到底有什么、训练前要做哪些体检、部署到边缘设备又要避哪些坑。无论你是做算法工程的、做毕业设计的、还是正在评估数据集质量的甲方,这篇都适合。
1. 人行道场景为什么是目标检测的"硬骨头"
1.1 通用模型部署到人行道会怎样
先说一个反常识的事:在COCO上训练的YOLO模型,在普通照片、行车记录仪画面上跑得很稳,但一接到人行道高位摄像头的画面,性能立刻缩水。这不是模型算法退化了,而是训练数据里的目标形态和实际场景差太远。
人行道和高架路口的画面有几个自带难点的特征。第一是视角:通用数据集里的人物大多是平视或轻微俯视的全貌,而人行道球机是从3到6米高的杆件上往下看,拍出来的是大面积头顶、肩部,全身框的形态比通用数据集少得多。第二是目标密度:早晚高峰的人行横道,一个画面里塞十几个人是常态,相互遮挡严重,通用数据集里那种"三五个目标、彼此分离清晰"的样本根本不够用。第三是光照:树荫、逆光、夜间路灯、雨后地面反光,这些复杂光照会让模型把树影误判成人影。第四是形态变体:婴儿车、轮椅、拐杖、共享单车、外卖电动车、宠物狗,这些在通用数据集里都没有专门的类别覆盖。
所以直接拿一个"看上去很强"的通用模型去跑人行道场景,结果基本就是误报多、漏检多、框不稳定。真正解决问题的第一步,不是换更强的模型,而是换一套贴合场景的数据集。
1.2 人行道检测为什么要选YOLO架构
如果你让我推荐智慧城市人行道场景的基线模型,我优先选YOLO系列,尤其YOLOv8和之后的版本。原因很实际:部署链路短、训练门槛低、社区资料全。YOLO从PyTorch导出ONNX再到TensorRT引擎,半天就能跑通;Faster R-CNN、Cascade R-CNN这类两阶段模型精度虽高,但实时视频流多路分析时,GPU占用和延迟都扛不住。
人行道检测任务核心需求是"在监控视频里尽可能快地找到人、车、设施",YOLO的单阶段设计天然适合。而真正决定上线效果的,是数据集的质量和你对场景的理解。用一套准确标注、场景覆盖充分的数据集训练YOLO,比硬堆一个大模型效果更明显。这也是为什么我一直强调:这个项目里"数据集"三个字,和"YOLO"三个字同等重要。
2. 读懂数据集:10300张图里到底有什么
2.1 拍摄场景、分辨率与数据来源分布
拿到一套人行道场景检测数据集,第一步不是急着训练,而是先弄清它的来源结构。以我手上这套10300张的数据为例,它的大致构成是这样的:约八成图像来自城市杆件上的高位固定摄像头,拍摄位置集中在主干道人行道、路口、商业街区、学校门口、公园步道和老旧小区内部道路;两成左右是低机位的补充素材,包括手持拍摄、车载记录仪画面,用来增加视角多样性。这种高低机位搭配非常重要,因为只靠一套高位视角训练出来的模型,换个近景机位就容易失效。
分辨率分布上,绝大多数是1920×1080,有一定比例2K、4K的高清素材。YOLO训练时会统一缩放到640或1280分辨率,所以原图大不大不是关键,关键是小目标在缩放后还剩多少像素。另外要特别检查场景重复度:如果10300张图其实是同一批摄像头在不同时刻的连续抓帧,那数据多样性会大打折扣。我的判断标准是,每隔10张抽一张缩略图拼成大图,快速扫一眼,看看背景是不是总在几个固定位置来回切。重复度过高的数据集,训练出来的模型容易过拟合到具体路口,换个路口就崩。
2.2 十类标注体系与标注规范
这套数据集的标注体系,我整理下来是十类目标,大概覆盖了人行道场景里最常出现的所有检测对象。
| class_id | 类别 | 典型目标 | 注意点 |
|---|---|---|---|
| 0 | person | 走路的人、等红绿灯的人、蹲坐的人、环卫工人 | 遮挡目标可见范围大于约30%才标注 |
| 1 | rider | 骑共享单车、电动车、摩托车的人 | 人和车是整体,人在车上就归此类 |
| 2 | bicycle | 停放的自行车、共享单车,推行中的空车 | 有人骑在上面则整框标rider |
| 3 | motorcycle | 电动自行车、摩托车、外卖配送车 | 和rider配合使用,人车整体 |
| 4 | vehicle | 汽车、SUV、面包车等小型机动车 | 人行道周边的机动车道目标 |
| 5 | bus | 公交车、大巴车 | 单独设类,避免和vehicle混淆 |
| 6 | traffic_light | 红绿灯灯组 | 近距离灯组和远处灯组都要标 |
| 7 | traffic_sign | 交通标志牌、指示牌 | 小目标居多,容易漏标 |
| 8 | crosswalk | 人行横道、斑马线区域 | 框住整个人行横道区域 |
| 9 | barrier | 路障、防撞柱、隔离桩 | 静态目标,误报相对少 |
这套类别的设计思路是"人车优先、设施兜底"。行人、骑行人员、机动车三大类解决了人行道主要的动态目标问题;红绿灯、交通标志、人行横道、路障四类解决的是静态设施检测需求。很多智慧城市项目最终要做的功能,比如"路口人流量统计""人行横道被障碍物占用识别""盲道周边障碍物警示",都能从这套类别里找到依据。
当然,不同卖家或不同机构提供的同类数据集,类别定义会有些差异。有的会把person和rider合在一起,有的会把truck单独拆出来。所以拿到数据集后第一件事永远是看classes.txt或者data.yaml里的类别列表,别靠标题猜。
关于标注规范,这套数据集处理遮挡目标的策略是"看得见的才标,不臆想完整轮廓"。具体来说:目标被遮挡后如果可见部分还能明显辨认,就按可见范围把框画到位;如果可见程度过低,宁可放弃不标。这种做法比"凭经验补全被遮挡部分"的标注更可控,避免引入高噪声框。在实际使用时,如果你的业务确实需要检测被严重遮挡的人,则需要理解这套数据集在这方面的覆盖上限,后续针对性地做补充标注。
2.3 训练、验证、测试划分与目标密度
按照8:1:1的比例,10300张图可以划分为训练集8240张、验证集1030张、测试集1030张。我建议不要自己重新随机划分,因为数据集中如果有连续帧和同一机位素材,随机划分会造成"数据泄露"——模型在验证集里见过训练集的相似画面,指标虚高,一部署就露馅。稳妥的办法是要求提供方按拍摄点位或时间片段分组划分,确保同一个场景的数据只出现在一个集合里。
从目标密度看,这套数据集单张平均大约有13到15个标注框,高峰期的路口图甚至能达到四五十个框,整体标注实例数量超过14万个。其中面积小于32×32像素的小目标占比接近四成,主要是远处的人头、红绿灯灯组和小型交通标志。这个特征直接决定了后续训练策略——你如果拿YOLO的默认参数硬训,小目标漏检率会非常难看。
另外,光照时段分布也值得留意。白天的样本占主体,黄昏和夜间加起来大致在两到三成,雨雪天样本更少。这个比例在国内城市视觉项目里很常见,但也意味着如果你的上线场景以夜间为主,就必须把这套数据集的夜间不足当成主要风险去处理。
3. 训练前的数据体检:格式转换、标签清洗与目录规划
3.1 先别急着训练:可视化抽样五百个框
数据集的README写得再漂亮,都不如自己亲眼看一下标注。我拿到任何数据集,都先做个可视化抽样:随机抽几十张图片,把对应的标签框叠加画出来,存成一张张带框的对比图,然后快速浏览。重点看三件事:框是否紧贴目标、骑行者是否标到人车整体、严重遮挡的小目标到底标了没有。
这个步骤看起来简单,但能规避很多后期麻烦。比如我见过一些数据集把"推着共享单车的人"标注成rider而不是person,导致模型后期把推车行人也框成骑行人员,业务上完全不可用。另有数据集把停在路边的汽车漏标了一大批,训练出来的模型在停车场附近突然大量漏检。花半天时间做抽样检查,远比你后面花几天调模型参数要划算。
可视化代码不用写太复杂,用opencv遍历图片和对应的txt标签,画矩形框保存即可。关键是注意YOLO标注是归一化坐标,画图前要乘回图像宽高。如果你拿到的是COCO JSON或VOC XML格式,建议先转成YOLO格式再统一处理。
3.2 YOLO标签格式转换与校验脚本
YOLO格式的标签文件是每个txt对应一张图片,每行五个数值:类别ID、归一化中心点x、归一化中心点y、归一化框宽、归一化框高。而COCO的标注是左上角坐标加宽高,单位是像素。两者换算其实就一步:中心点等于左上角加半宽半高,再除以图片宽高。
如果你拿到的数据集是COCO格式,需要一个转换脚本。下面这段我常用的代码可以直接改着用:
import json from pathlib import Path def coco_to_yolo(coco_path, out_dir): with open(coco_path, 'r', encoding='utf-8') as f: data = json.load(f) # 类别ID可能不是从0开始,需要重映射 categories = {cat['id']: idx for idx, cat in enumerate(data['categories'])} images = {img['id']: img for img in data['images']} Path(out_dir).mkdir(parents=True, exist_ok=True) for ann in data['annotations']: img = images[ann['image_id']] txt_path = Path(out_dir) / (Path(img['file_name']).stem + '.txt') w, h = img['width'], img['height'] x, y, bw, bh = ann['bbox'] xc = (x + bw / 2) / w yc = (y + bh / 2) / h nw, nh = bw / w, bh / h with open(txt_path, 'a', encoding='utf-8') as t: t.write(f"{categories[ann['category_id']]} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}\n")转换完之后,必须跑一遍标签异常检查。下面这个脚本重点查空文件、字段数量错误、坐标越界和疑似极小框:
from pathlib import Path label_dirs = [Path('labels/train'), Path('labels/val')] for label_dir in label_dirs: for txt in label_dir.glob('*.txt'): lines = txt.read_text().strip().splitlines() if not lines: print(f'[空标签] {txt}') continue for line in lines: parts = line.split() if len(parts) != 5: print(f'[字段数量异常] {txt}: {line}') continue try: _, xc, yc, w, h = map(float, parts) except ValueError: print(f'[数值解析失败] {txt}: {line}') continue if not (0 <= xc <= 1 and 0 <= yc <= 1 and 0 < w <= 1 and 0 < h <= 1): print(f'[坐标越界] {txt}: {line}') if w * h < 0.0001: print(f'[疑似极小框] {txt}: {line}')空标签文件不一定是问题,如果图片里确实没有目标,那就是一张合理的背景负样本。但如果你发现大量图片都是空txt,就要警惕是不是漏标了。坐标越界一般来自格式转换精度问题,直接把数值clip到[0,1]区间即可。极小框则是小目标标注精度的问题,这类框多了会干扰训练,建议结合人工判断决定保留还是清除。
目录结构我建议固定成YOLO约定俗成的样子,后面无论用ultralytics还是其他框架,都能直接吃:
dataset/ images/ train/ val/ test/ labels/ train/ val/ test/ data.yaml3.3 针对密集人群的增强策略选择
人行道数据集的增强策略,不能照搬一般目标检测项目。我自己的经验是三个要点。
第一,Mosaic增强打开,Mixup可以适量开一点。密集人群场景里目标互相遮挡严重,Mixup把两张拥挤的图合成一张,能让模型提前适应"目标贴目标、框压框"的情况。YOLOv8默认mixup是0,我会调成0.1到0.2。
第二,上下翻转必须关闭。人行道监控画面里,人物永远是头朝上脚朝下,你做一个上下翻转,等于让模型去学习"倒立的人",这不符合物理规律,只会增加无效学习。
第三,不要指望HSV增强能解决夜间样本不足。HSV调色只是改变颜色空间的亮度、饱和度和色相,它能模拟黄昏与暗光的一部分效果,但模拟不出夜间路灯造成的反光、光晕和长曝光噪点。真要解决夜间场景,只能老老实实补数据或者用带夜景图像的预训练权重做迁移。
还有一个容易被忽略的点:训练前统计一下标注框的宽高比分布。人行道场景里行人的框通常是瘦高型,骑行人员的框接近横向,车辆框是宽扁型。如果你的增强配置里scale过大,会把本来瘦高的人形目标拉伸变形,反而降低检测精度。我一般将scale保持在0.5以内,translate设小一点,保证目标形态不被打乱。
4. YOLO训练参数与性能实测:从mAP到误报排查
4.1 预训练权重、输入分辨率与训练轮次的选择
用YOLOv8训练人行道数据集,我的起步配置是:yolov8s预训练权重、640输入分辨率、300轮训练。之所以用预训练权重而不是从零训练,是因为COCO上的通用特征对行人、车辆这类目标仍然有效,预训练可以显著缩短收敛时间,尤其适合样本量在万级左右的数据集。
输入分辨率的选择需要结合小目标占比来定。这套数据集中小目标占比接近四成,如果算力允许,可以尝试960甚至1280分辨率训练,小目标召回率会有明显提升。但要注意,分辨率从640提高到1280,显存占用和训练耗时大约翻四倍,很多时候不划算。更实用的折中方案是先用640训练出基线,然后对小目标漏检严重的类别,用SAHI切片推理去解决,这个后面专门讲。
训练轮次我建议至少300轮。人行道这种密集场景,模型收敛比普通目标检测慢,我在多个项目上都是跑到200轮以后mAP才稳定下来。如果你发现150轮时指标还在涨,别急着停,继续跑,让模型充分拟合小目标和遮挡样本。
启动训练的命令很简单,几条关键参数直接传给yolo即可:
yolo detect train data=sidewalk.yaml model=yolov8s.pt \ epochs=300 imgsz=640 batch=16 \ mosaic=1.0 mixup=0.1 fliplr=0.5 close_mosaic=10batch大小主要看显存,我一般以"显卡能跑满且不OOM"为准,16、32都行。close_mosaic=10指的是最后10轮关闭Mosaic增强,让模型从"拼接图模式"切回真实图片分布,这个细节对最终精度影响挺大,别省。
4.2 损失曲线怎么读,调参信号在哪里
训练过程中,YOLO输出的box_loss、cls_loss、dfl_loss三条曲线,重点看验证集上的表现,不要死盯训练集。训练loss降得再低,只要val loss不降甚至反弹,就说明过拟合了。
我在人行道项目上总结的经验是:val/box_loss如果持续下降但val/cls_loss还在高位波动,大概率是类别混淆问题,尤其是person和rider之间的边界模糊。这时候去查标签噪声比调学习率更有效。反过来,val/cls_loss下降正常但mAP50迟迟不突破,可能是训练集本身存在大量遮挡导致正样本质量低,可以试试提高置信度阈值或者重新审查标注框是否裁掉了目标的关键部位。
训练结束后,ultralytics会同时保存best.pt和last.pt,我建议默认用best.pt。它对应验证集mAP最优的权重,实际部署效果通常也更稳。但注意,best.pt是在验证集上选的,如果数据划分有泄漏,这个best会虚高,这也是前面反复强调按场景分组划分的原因。
4.3 只看mAP不够:夜间、遮挡、小目标的分类别评测
模型训练完,跑验证集拿到整体mAP,先别高兴。人行道项目必须多看几层指标,我的习惯是把验证集按光照和场景切成几份,分别跑一遍。下面这组数字来自我自己项目上的实测,不同数据集和设备会有差异,但能代表这类场景的普遍规律:
| 评估切分 | YOLOv8s mAP50 | 备注 |
|---|---|---|
| 白天顺光 | 0.87 | 整体表现最好的切片 |
| 黄昏逆光 | 0.72 | 行人边缘变模糊,框位置抖动 |
| 夜间灯光 | 0.63 | 路灯反光造成目标误检 |
| 雨天夜间 | 0.51 | 镜头水渍干扰大,漏检明显 |
同一个模型,白天和雨天夜间的mAP能差出30个百分点。如果你只看整体的mAP,这些隐患会被掩盖掉。另一个重要输出是混淆矩阵图,重点看person和rider之间的交叉错误。还有一类我在多个项目里反复遇到的误报源:广告牌上的真人大小人像。模型会把它们当成真实行人来检测。这种问题在整体mAP上几乎看不出来,必须跑一批实际街景素材才能暴露。
误报排查的方法也不复杂:把验证集预测结果保存成带框图片,按置信度从低到高排序,人工扫一遍低置信度的FP样本,记录误报类型。树影、井盖、垃圾桶、广告人像、车辆玻璃反光,这些都是人行道场景的典型误报来源。弄清楚你的模型误报集中在哪几类,才能决定是用数据清洗、后处理还是规则过滤来压误报。
5. 从PyTorch到边缘盒子:部署优化与视频流处理
5.1 导出ONNX与TensorRT,精度和速度要一起盯
训练好的模型要落地到智慧城市边缘设备,不能直接跑PyTorch动态图。我统一的流程是先导出ONNX,再转TensorRT引擎。用ultralytics自带的导出命令可以一步到位:
yolo export model=best.pt format=onnx opset=11 yolo export model=best.pt format=engine half=True device=0TensorRT这一步在GPU机器上做,导出后的.engine文件可以直接拷贝到Jetson等边缘设备上用。需要注意三点。
第一,输入尺寸在导出时要固定,不要在推理时动态改分辨率,TensorRT对固定shape优化最充分。第二,转模型时的精度模式要匹配部署需求:FP16是最常用且稳妥的,INT8虽然能进一步提升速度,但校准集选不好很容易让夜间场景的精度崩掉。我见过的INT8翻车案例,几乎都是校准集里全是白天清晰样本,导致模型对夜景的分布估计完全失衡。校准集至少要有300张图,并且覆盖白天、夜间、雨天、逆光等多种光照。第三,TensorRT版本要匹配,Jetson上的TensorRT版本和你PC上的必须一致,否则引擎加载直接报错。
5.2 视频流接入:抽帧、跟踪与业务逻辑的配合
边缘设备的推理只是中间环节,真正上线时你要面对的是多路RTSP视频流。我的做法是每路视频开一个独立线程拉流,解码后的帧放进带时间戳的队列,再由检测线程批量取帧处理。千万别用单线程逐帧同步推理,否则一路视频卡顿就会拖垮其他路数。
抽帧频率要合理。30fps的视频流如果每帧都检测,边缘设备基本吃不消;但如果降到每秒1帧,又会出现行人横穿人行道时漏检。人行道行人的运动速度不算快,一般10fps检测频率配合跟踪算法,已经能保持稳定的目标轨迹。我在实际项目里常用方案是"10fps检测 + ByteTrack跟踪",中间帧用跟踪结果补齐,既能稳住检测负载,又能给业务层提供连续的目标ID。
具体到某个报警功能,比如"有人长时间停留在盲道区域",就不能只看单帧检测结果,因为单帧误报会造成高频误触。通过跟踪拿到目标ID和停留时间,再叠加区域规则,误报率能压下来一大截。这类经验通常不会写在模型的文档里,但上线前你要是没做,现场调试会让你欲哭无泪。
5.3 边缘设备的实测帧率与性能取舍
不同边缘设备的推理性能差异很大。我给一个自己在测试平台上的实测参考,注意这只是单路640输入、纯模型推理耗时折算的帧率,实际部署会因解码、前后处理、系统负载而有所下降:
| 设备 | 模型与加速方式 | mAP50参考 | 单路推理帧率 |
|---|---|---|---|
| Jetson Orin NX 16GB | YOLOv8s FP16 TensorRT | 0.83 | 约80 FPS |
| Jetson Orin Nano | YOLOv8n FP16 TensorRT | 0.78 | 约60 FPS |
| RK3588 | YOLOv8s RKNN | 0.82 | 约40 FPS |
| RTX 4090桌面卡 | YOLOv8s FP16 TensorRT | 0.84 | 300 FPS以上 |
如果你的项目要跑四路以上视频,Jetson Orin NX跑YOLOv8s每路抽10fps是够用的。如果设备更弱,就只能在模型大小和抽帧率之间做取舍。我个人的建议是,优先保证晚间场景的精度,而不是盲目追求帧率。
6. 这个数据集最常见的翻车点与补救方案
6.1 遮挡目标的标注歧义与误报清洗
人行道数据集中最大的标注噪声来源,就是遮挡场景下"标不标""怎么标"的尺度不一致。有的标注员习惯只标完整可见的人,半个身子被电线杆档住就不标;有的习惯只要看到头肩就标。两种风格混在同一个数据集里,模型就会无所适从。
我拿到这类数据集后会做一个漏标率抽查:随便抽10张人群密集的图,人肉数一遍画面里实际有多少个"可辨认的人",再和标注框数量对比。如果漏标率超过两三成,说明这套数据的标注质量对你的业务场景不够用,需要重新补标或者至少补标你想重点检测的类别。
遮挡目标还有一个常见问题:行人互相挨得太近,YOLO输出多个重叠框。这时不要急着骂模型,先看后处理里NMS的IoU阈值。密集场景可以把iou阈值适当调低,比如从默认0.7调到0.5,让高重叠的同类框更容易被合并,减少重复计数。
6.2 小目标漏检:SAHI切片推理与P2头的取舍
前面提过这套数据集里小目标占比近四成,那YOLO对它漏检的根源在哪?YOLOv8的主干网络有多层下采样,输出特征图上每个网格对应的原图区域很大,一个16×16像素的行人头在深层特征图上可能只剩一两个响应点。想要改善,有三个层面可以动手。
最直接的方案是提高推理分辨率,把imgsz提到1280,小目标在特征图中的响应面积变大,召回率明显提升。代价是推理耗时翻几倍,需要评估边缘设备扛不扛得住。第二个方案是SAHI切片推理,把大图切成640的切片,每个切片独立检测再合并结果。它专门解决"小目标在大图中占比太小"的问题,我实测在密集人群场景能把小目标AP拉高好几个点。缺点是1080p画面切成4到8个切片,每路视频的检测次数会翻好几倍,通常用于离线处理或低路数场景。第三个方案是用带P2头的小目标优化模型结构,训练成本更高,但对特定场景的收益也会更持久。
我不建议一上来就三条路同时上,先做一个简单实验:用1280分辨率跑一次验证集,如果小目标AP提升不到两个点,说明问题主要不在分辨率而在数据本身,那就优先去补小目标样本。
6.3 换城市失效的迁移问题与增量训练
很多人在一个城市调好的模型,换到另一个城市就明显衰减,人行道砖颜色、路边绿植、护栏样式、路灯高度全变了,模型对旧场景的"熟悉感"全部失效。这不是模型玄学,是场景分布漂移。
我的处理方式是先做一次最小成本的迁移验证:从目标城市采集100张带标注的画面,直接跑当前模型,看分类别AP掉到哪。如果只是轻微下降,说明原有特征还有效,不需要大动干戈;如果person的AP掉了十几个点,就要走增量训练了。增量训练不要重头来,用原best.pt做预训练,加入目标城市的几百张新图,配合原始数据集一起微调20到30轮,学习率降到0.001到0.002,防止新样本把老权重冲垮。
一个数据集的寿命不取决于它初始有多大,而取决于你有没有数据回流机制。真正上线后,我会从生产环境按小时抽帧,跑一遍模型,把置信度在0.3到0.7之间的"模糊样本"自动截取出来,每周人工复核一次,挑出有价值的补标后塞回训练集。这个流程跑一个月,模型对新场景的适应能力会明显强于初始版本。
我在几个项目里的体感是:人行道场景检测,七分靠数据,三分靠模型。10300张这个体量做基线足够,但真正决定落地上限的,是你对数据集的体检做得够不够细、对场景切分的理解深不深。拿到任何数据集,先花一两天做可视化检查、标签清洗和场景统计,这笔时间花得永远值得。尤其是person和rider的边界定义、夜间样本占比、小目标覆盖度这三件事,提前确认清楚,能帮你少走好几周的弯路。