资讯动态

深度学习人行道检测实战:数据标注、模型训练与调优全流程

发布时间:2026/10/21 9:24:39 来源:尧图企业网站定制
简介面向自动驾驶、智慧城市与行人安全场景的深度学习人行道检测资料包适合计算机视觉初学者及希望接触目标检测迁移学习的开发者。资源围绕SidewalkDetection-master项目展开覆盖卷积神经网络结构、预训练模型VGG/ResNet/YOLO/Faster R-CNN、数据集标注与训练评估流程等关键内容。包内共32个文件以Python源码6个py、XML标注文件8个xml、PDF论文7篇为主另含README与配置文件压缩包约33.95MB目录结构清晰便于按模块学习。7篇PDF覆盖人行道检测、盲道识别、自动驾驶障碍物检测等研究方向可作为理论补充Python源码与XML标注则支持从数据准备、模型训练到评估调优的完整实践。目前已有923人学习下载适合需要系统掌握人行道检测项目完整链路的中级开发者。1. 人行道检测用深度学习把“哪里能走”这件事交给模型人行道检测在辅助驾驶、盲人导航和城市巡检里一直是个“看着简单、落地闹心”的活。它不像车道线那样有明确的几何先验人行道的外观随材质、光照、磨损程度变化极大一块带条纹的砖地、一段被落叶盖住的坡道都可能让模型翻车。我最早在一套 Python 视觉巡检项目里接触这个任务前后换了三个方案才把误检压到可接受范围内。这套《人行道检测深度学习中的人行道检测》就是当时沉淀下来的完整思路从数据标注到模型训练再到后处理适合正在做毕设、准备部署边缘设备的工程师也适合想把手头检测Demo升级成能扛住真实场景的开发者。下面按我实际拆项目的顺序来写每一步都有可复现的代码和参数。2. 数据准备先过三关标注格式转换、类不平衡、按城市划分人行道检测的第一步不是选模型而是把数据收拾到能让模型好好学习的程度。现实中拿到的数据集五花八门常见的是 COCO 格式的 JSON 或者 VOC 格式的 XML而训练端侧模型时最常用的还是 YOLO 系列数据格式得统一。2.1 VOC 格式转 YOLO 格式转换脚本与四个边界坑我经常遇到的情况是数据集里是 VOC 的 XML 标注每个文件对应一张图框的坐标是xmin, ymin, xmax, ymax而 YOLO 需要的是归一化后的中心点坐标和宽高。转换脚本本身不难难的是坐标算错和边界处理。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_map): 将单张VOC XML标注转为YOLO txt标注 class_map: {sidewalk: 0} 之类的类别名到id的映射 tree ET.parse(xml_path) root tree.getroot() img_w float(root.find(size).find(width).text) img_h float(root.find(size).find(height).text) yolo_lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 关键防御框不能超出图像边界 xmin max(0.0, min(xmin, img_w - 1)) ymin max(0.0, min(ymin, img_h - 1)) xmax max(0.0, min(xmax, img_w - 1)) ymax max(0.0, min(ymax, img_h - 1)) # 过滤空框 if xmax xmin or ymax ymin: continue # 转中心点宽高并归一化 x_center ((xmin xmax) / 2.0) / img_w y_center ((ymin ymax) / 2.0) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 四舍五入到6位避免txt文件里出现浮点噪声 yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not yolo_lines: return False out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(yolo_lines)) return True这段脚本的逻辑要点第一XML 里的宽高必须真实读取不能用固定分辨率否则归一化全错第二框边界做了 clamp 防止标注不小心超出图幅后模型学到奇怪的偏移第三过滤掉宽高为 0 或反了的框这类脏标注在数据集中并不少见。参数上class_map如果检测目标只有人行道一个类就写成{sidewalk: 0}即可如果以后要加盲道、斑马线就按类别顺序递增。输出精度保留 6 位小数对 YOLO 训练足够还能防止文件里出现1e-18这类极端值干扰读取。2.2 类不平衡人行道占图面积太小模型学不到东西人行道检测的典型问题是“物体大面积背景化”。在一张城市街景图里人行道往往只占图像的下半部分且视角越朝前它的面积占比越小。我试过直接用原始数据训练 YOLOv8smAP 只有 0.6 出头漏检集中在远处区域。这个问题的解决思路分三层第一层是损失函数层面。YOLO 系列用 BCEWithLogits 做分类损失对正负样本不均衡相对不敏感问题更多出现在回归分支对小目标的梯度贡献不足。常见做法是提高输入分辨率让一个小目标占据更多像素。第二层是数据层面。把包含人行道的图像区域单独切出来做增广或者通过滑窗把远处人行道放大相当于人为制造更多“近处样本”。我用一张 640x640 的图切成四张 320x320 的子图训练小目标的 mAP 能提升 3 到 5 个百分点。第三层是后处理层面。做人行道检测时很多场景是“一对一”关系——上一帧检测到、下一帧却没检测到。这时引入时序平滑用上一帧存在的框去辅助当前帧的 NMS 流程能缓解单帧漏检。2.3 按城市划分数据集别随机 shuffle这个坑我踩得比较深。最早的训练是随机划分 train/val结果在本地验证集上 mAP 到了 0.85但在另一座城市的路测数据上掉到了 0.5 不到。原因在于同一座城市的人行道砖纹、马路牙子颜色高度相似随机划分让验证集和训练集的“城市风格”重叠模型学到的是城市特征而不是人行道通用特征。正确做法是按采集城市划分数据集划分方式train 城市数val 城市数说明随机划分同城混合同城混合本地指标虚高跨城市泛化差按城市划分82模拟真实跨区部署指标更有参考价值真实场景里训练集至少要覆盖 68 个不同城市的街景验证集单独留出 12 个城市的数据这样才能评估模型换城市后的鲁棒性。我在做跨城市评估时还会特意加入夜间和雨天样本这类样本对人行道检测的影响很大。3. 模型选型与配置检测框还是分割掩膜先想清楚人行道检测有一个绕不开的选型问题用目标检测的框还是用语义分割的掩膜网上很多项目直接把 YOLO 拿来改类别数就开始训练但人行道的实际形状往往是不规则的长条形区域用一个矩形框去框它框内有大量背景后处理做边界线提取时会引入噪声。3.1 什么时候用检测框什么时候用分割掩膜我处理过两种数据来源如果数据集只有 BBox 标注比如从某些公开数据集里拉的人行道类那只能先做检测。此时建议把检测框的下边缘当作人行道与路面的分界线来用上边缘舍弃因为上边缘往往是建筑或天空用处不大。如果数据集提供了 Mask 标注分割是更合适的选择。人行道作为连通域分割模型能直接输出每个像素是否属于人行道后续做边缘提取、面积计算都非常直接。两种方案的技术栈也不一样。检测路线首选 YOLOv8s 或更轻的 YOLOv5s输入 640 分辨率时单帧推理在嵌入式设备上能做到 20~30 FPS。分割路线可选 DeepLabV3 这类经典模型输入分辨率 512 或 640 都行但推理速度会慢一截需权衡。3.2 YOLOv8s 训练配置从配置文件到超参数的完整参数表如果走检测路线我一般用 YOLOv8 官方代码配置文件长这样# sidewalk.yaml names: 0: sidewalk path: ../datasets/sidewalk train: images/train val: images/val这个配置文件里没有写尺寸和 epoch因为它们属于训练参数放在命令行里反而更好调。实际训练命令yolo detect train \ modelyolov8s.pt \ datasidewalk.yaml \ imgsz640 \ epochs120 \ batch16 \ lr00.01 \ lrf0.01 \ mosaic0.8 \ fliplr0.5 \ scale0.4 \ close_mosaic10参数这里逐个说明imgsz640是输入分辨率对人行道来说低于 512 会导致远处目标几乎不可见高于 768 会增加显存占用但收益递减640 是精度和速度的平衡点。lr00.01是初始学习率用 COCO 预训练权重继续训练时 0.01 是安全值如果从头训练调低到 0.005 更稳。mosaic0.8表示 80% 的概率使用马赛克增广这个概率不能设成 1否则模型会依赖拼接图的上下文真实单图场景反而掉点。close_mosaic10是最后 10 轮关闭马赛克让模型适应无增广的输入分布这是很多人忽略但很关键的一步。fliplr0.5水平翻转概率人行道左右对称性较强0.5 没问题但如果是带盲道方向性的标注建议改成 0。这里有个值得强调的点预训练权重选择。YOLOv8s 的 COCO 预训练权重包含 80 类物体其中没有“人行道”所以最后一层分类头会被替换重建。但前几层特征提取仍有效尤其是边缘和纹理特征对砖缝识别很有帮助不要从头训练。3.3 分割路线的模型结构DeepLabV3 的 backbone 选择分割路线我参考了常见做法比如用 DeepLabV3 搭配 MobileNetV3 作为 backbone参数量在 5M 以内适合部署。它通过空洞卷积扩大感受野对人行道这种大面积连通区域的分割效果不错而且不会像检测框那样产生大量无效区域。以某跨平台系统的分割实现为例完整的训练流程是先冻结 backbone 训头部 30 轮再解冻全模型微调 60 轮。冻结阶段的代码如下import torch import torch.nn as nn # 冻结backbone前50个epoch只训练分类头 for name, param in model.named_parameters(): if backbone in name: param.requires_grad False逻辑说明冻结 backbone 是为了让随机初始化的分类头快速收敛避免在训练初期梯度同时更新过深的网络导致震荡。参数上解冻时机设在总 epoch 的 1/3 处比较常见如果数据集很小冻结时间可以延长到 60%。这个阶段我会格外注意 loss 曲线的形态如果冻结阶段分类头 loss 已经降平但验证集性能不升说明 backbone 的特征尺度可能不对这时需要检查输入图像的分辨率是否过低。4. 训练实战参数调节、验证策略与效果评估模型选好了训练过程里真正影响效果的是几个细节数据划分的方式、增广强度的把握、loss 权重的配比、以及验证集的评估方式。这一节按训练流程从数据加载讲到最后指标。4.1 数据加载与增广策略不要让增广改变人行道语义人行道的语义是“可行走的硬质地面”增广操作必须保持这个语义。颜色抖动HSV 调整是安全的因为真实场景中光照变化本来就大模型需要学的是“无论颜色偏黄还是偏灰它都是人行道”。但几何形变要谨慎大的随机旋转会让“地面”变成“墙面”导致模型学到错误的竖直纹理。我的实际增广配置经验是增广方式强度说明HSV 颜色抖动h0.015, s0.5, v0.4模拟晨昏、灯光下的颜色偏移随机翻转p0.5对左右对称场景安全缩放0.4~1.2模拟不同远近视角但要小于 2 倍旋转±15 度超过 15 度会让行人道变成“立面”马赛克p0.8增加上下文变化但最后 10 轮关闭数据增强这块最常见的失误是把旋转角度调得过大。我曾在一张俯瞰视角的巡检图上做了 30 度旋转增广结果模型学会了把竖直墙体误判为人行道因为在那个旋转角度下墙面纹理和地面纹理几乎不可区分。从那以后我旋转角度从不超过 15 度。4.2 Loss 曲线怎么看val_loss 掉头向上就是过拟合训练过程中我会同时盯两类曲线训练 loss 和验证 loss。它们揭示了几个典型问题训练 loss 下降但验证 loss 不动学习率太低或模型容量不够先调学习率再看模型是否太小。训练 loss 不降大概率是标注数据有严重错误或者类别映射写错了比如把sidewalk映射成1而标注文件里写的是0。训练 loss 降、验证 loss 掉头向上典型的过拟合。这时先降低增广强度增加weight_decay如果还是不行减少 epoch 或减小模型宽度。另外检测任务还要观察 NMS 后的 inference 阶段指标。有的框架里训练时的 loss 和验证时的 mAP 趋势不一致原因是训练阶段存在大比例增广模型在增广分布上优化而验证时用的是原始分布。所以最后 10 轮关闭马赛克后mAP 往往会出现一个明显的回升。4.3 验证指标的选择mAP 之外还要看中心点距离误差对行人道检测而言mAP 只是及格线真正决定落地效果的是“检测框与真实边界的贴合度”。我用一个额外指标来评估计算检测框的下边线与人工标注的马路牙子线之间的平均像素距离。这个指标在行人道检测里比 mAP 更能反映“可通行区域”的边界精度。评估代码如下import numpy as np def edge_error(pred_boxes, gt_boxes, img_width, img_height): 计算预测框下边缘与GT框下边缘的平均绝对误差归一化坐标 errors [] for pb, gb in zip(pred_boxes, gt_boxes): pred_bottom pb[3] / img_height gt_bottom gb[3] / img_height errors.append(abs(pred_bottom - gt_bottom)) return np.mean(errors) if errors else -1这段代码的思路是把框的下边缘当作“人行道与车道分界”的近似位置归一化后计算误差。误差小于 0.03约 19 个像素 640说明边界贴合度可用超过 0.05 就会明显影响后续的可行驶区域判断。5. 避坑与排查我在人行道检测项目里翻过的五个车这一节把我实际踩过的坑整理成“现象→原因→解决”的形式。如果你训练时遇到类似问题直接按对应条目排查。5.1 换了城市之后 mAP 暴跌现象在本地验证集上 mAP 0.85换到一个新城市的路测数据上 mAP 掉到 0.5 以下。原因训练集和验证集按文件随机划分导致同城的图像同时出现在两边。模型记住的是该城市的砖纹和光照特征而不是人行道的一般特征。解决按城市划分数据保证验证集城市完全不出现在训练集里。至少收集 6 个城市以上的街景否则这一条无法根治。5.2 误检马路的斑马线被当成行人道现象模型对绘有白色条纹的路面区域输出高置信度检测框但这些区域实际是斑马线不是行人道。原因斑马线的白色条纹纹理与某些人行道砖缝纹理高度相似模型在浅层学到了“条纹人行道”的错误关联。解决增加负样本把斑马线、导流带、带条纹的墙面都截成负样本图单独放一个目录训练。如果用的是 YOLO可以在数据集中额外建一个吊打类“noise”把容易误检的这类图全部标注进去模型会学到“这些背景纹理不是目标”的判别边界。5.3 夜间和雨天漏检严重现象白天场景 mAP 0.75夜间只有 0.3。原因训练数据大部分是白天采集夜间图像亮度低、对比度差、还有路灯阴影和汽车灯光干扰模型没学到这类分布。解决引入更多夜间样本是最直接的手段如果没有夜图用 HSV 增强和 Gamma 校正模拟低光场景。import cv2 import numpy as np def low_light_augment(image, gamma1.8): 模拟暗光环境Gamma校正压低整体亮度并压缩对比度 inv 1.0 / gamma table np.array([(i / 255.0) ** inv * 255 for i in range(256)]).astype(uint8) return cv2.LUT(image, table)gamma1.8会把整体亮度压低、暗部对比度更弱适合模拟夜间路灯下的灯光衰减。注意这个增广不能对所有训练图使用否则模型会对过暗图像产生错误认知我一般控制在 20% 的概率。5.4 远处小目标几乎全部漏检现象近处人行道检测正常图像上方离摄像头 20 米外的人行道完全没框。原因输入分辨率 640 时远处人行道可能只有 20x60 像素对于 YOLOv8 的检测头来说太小了。另一个原因是训练数据中近距离样本占绝大多数模型对小目标特征不敏感。解决把训练图像分成多个尺度做多尺度训练或在训练时套用滑窗切图策略。我用 512 和 640 两种分辨率交替训练小目标召回提升明显。另外调低置信度阈值从 0.5 到 0.35 能保留更多低置信度但位置正确的框后期用 NMS 过滤即可。5.5 训练 loss 一直下降但 mAP 不动现象训练 80 个 epoch分类和回归 loss 都降了但验证 mAP 几乎没有上升。原因分类 loss 与回归 loss 的权重不平衡模型过度优化了分类分支而对 box 位置的回归不够重视。也可能是 anchor 尺寸和数据集目标尺度不匹配。解决先检查数据集中目标的宽高比分布人行道通常是长条如果 anchor 都是正方形式就不合理。YOLOv8 是 anchor-free 的这个问题会小一些如果还出现类似情况把 loss 权重往 box 和 dfl 方向倾斜设置box7.5、cls0.5。6. 进阶技巧把检测结果变成真正的可通行区域信息检测模型输出的是框和置信度但实际应用里更需要的是“图像里哪些像素可以踩”。这部分我做了一个后处理流程能把检测框转为边界线信息帮助后续决策模块使用。6.1 形态学后处理去除孤立噪点并保留主干区域当获得分割掩膜或检测框内掩膜后先用形态学开运算去除孤立噪点再用闭运算填补空洞并使用线性结构元素保留长条形区域。对人行道这种长条目标结构元素尺寸要按输入分辨率缩放512 分辨率下 5x5 合适640 分辨率下 7x7 合适。import cv2 import numpy as np def postprocess_mask(mask, kernel_size7): 形态学后处理先开运算去噪再闭运算补洞 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size)) opened cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations2) closed cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel, iterations2) return closed代码逻辑MORPH_OPEN先腐蚀后膨胀能去掉小于结构元素尺寸的孤立白色像素比如远处的树影、车灯反光点MORPH_CLOSE先膨胀后腐蚀能把人行道内部的裂缝、井盖等小洞填平。iterations2我试过是性价比比较高的次数再大会让边缘变得过于圆滑。6.2 单目视觉下的人行道距离估计前视摄像头下的人行道检测还需要解决“这条人行道离我多远”的问题。常见做法是先标定相机内参和安装高度然后把检测框下边缘的中点投影到地面平面估算距离。import numpy as np def bottom_to_distance(bottom_y, cam_height, pitch, f_pixel): 根据相机几何估算框下边缘到相机的距离 bottom_y: 归一化图像坐标中的底边位置[0,1] cam_height: 相机安装高度单位m pitch: 俯仰角单位rad f_pixel: 焦距像素值 alpha pitch np.arctan2((bottom_y - 0.5) * 2 * 0.5, f_pixel) distance cam_height * np.tan(alpha) if alpha 0 else -1 return distance这个估算公式假设地面是平面适用于城市道路的平整场景。参数上相机安装高度通常在 1.2~1.5m俯仰角按实际标定调整一般为 0.05~0.1 rad。我用这段逻辑配合检测框下边缘误差在 3 米以内大约 10%足够做提前减速和避障的初步判断了。6.3 与地图先验结合GPS 轨迹的时序过滤单帧检测总是有抖动的尤其是在摄像头过坎或车速较快时。我的做法是把检测置信度与 GPS 位置关联起来当检测框遇到雪天识别临时失败时直接由上几帧的轨迹外推出下一帧人行道位置。这个方法来自我曾经做过的模拟项目X简单而有效用车辆转向角和车速线性推算 200 毫秒内框的位置变化可以扛住摄像头短暂被遮住的场景。6.4 视频输入时的跳帧策略处理车载视频时不需要每帧都跑完整模型。常见做法是每 3 帧跑一次模型中间 2 帧用光流法或直接复制上一帧的掩膜。人行道在连续帧之间的变化很小间 隔一帧复制掩膜几乎不影响最终效果还能省下近 60% 算力。有一次在具体设备上调优用这个策略把功耗降了四成帧率反而从 12 提升到 28从那以后我每次做视频推理优化都强制走一遍“跳帧掩膜复用”流程再去做模型压缩。这套方案整体下来检测、分割、距离估算和后处理都有了直接拿去复现的时候注意按城市划分数据、保持合理的增广强度、最后 10 轮关闭马赛克。希望帮到你这套资源里包含了训练脚本、标注转换工具和后处理模块直接用会少走很多弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑