ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

地面障碍物检测数据集与YOLO实战:从COCO转换到训练避坑

地面障碍物检测数据集与YOLO实战:从COCO转换到训练避坑 简介地面障碍物检测数据集面向自动驾驶、机器人导航、智能监控与计算机视觉研究等场景提供栅栏、地面障碍物、岩石、树木、汽车、人物共6个类别的目标检测样本可用于YOLO等主流框架的模型训练与算法验证。全量数据共626张JPEG原图及对应的YOLO格式TXT标注文件另附类别配置YAML与说明文档压缩包内共1254个文件整体约35.98MB目录结构清晰便于直接读取。数据集图片来自多样真实环境覆盖不同光照与背景条件标注边界框定位准确有助于提升模型在复杂地形中的泛化能力与鲁棒性。已有127人学习浏览适合需要多类别地面障碍物检测训练数据的研究者、竞赛选手及智能系统开发人员使用。1. 地面障碍物检测数据集巡检与AGV项目里的第一道坎做厂区巡检和AGV导航的人大概率被同一个问题卡过摄像头把地面拍得很清楚但模型就是认不出施工锥桶、散落木箱、水马和坑洞。公开数据集偏重交通场景换到车间、园区、工地里迁移效果明显缩水。这份“地面障碍物检测数据集”就是用来补齐这个空白的标注按多类别目标检测组织同时兼容实例分割和关键点检测的扩展需求可以直接喂给YOLO训练检测模型也能拿去跑分割和关键点任务。适合正在做智慧巡检、移动机器人避障的算法工程师拿到手就能开始清洗、转换并训练第一版模型。转换脚本和训练配置我会在后面逐步展开按着走一遍就能出可用的权重。2. 拆目录与标注格式先摸清每类障碍物标了什么2.1 目录结构与文件规模下载后先摸一遍再动手我拿到这类数据集的第一件事不是解压就开训而是先把目录结构摸清楚。常见做法是解压之后用tree命令把顶层结构打出来确认图片和标注放在哪里再决定后面的转换路径。unzip 地面障碍物检测数据集_20251123_025931.zip -d ground_obstacle_data cd ground_obstacle_data tree -L 2 -d解压到ground_obstacle_data之后主要看顶层有没有train、val这类划分目录或者是否统一把图片放在images目录下、标注集中放annotations。tree -L 2 -d只列目录不列文件能快速确认布局。此时不要急着看图片先确认数据组织方式——不同组织方式对应完全不同的转换脚本看错一步后面全得返工。统计图片数量和尺寸分布是第二步。地面障碍物数据通常来自固定机位或移动平台采集如果图像尺寸不统一训练阶段就需要额外做resize策略这会影响后续YOLO输入分辨率的设定。find images -type f | wc -l find annotations -type f | wc -l file images/*.jpg | sed s/.*: // | sort | uniq -c第一行统计图片总数第二行统计标注文件数第三行快速看每个文件的格式和位深。如果annotations下是COCO格式大概率是一个或几个JSON文件如果每张图对应一个txt或json那就是单文件标注。判断清楚这两点才能决定转换脚本怎么写。我一般还会顺手用python批量读一遍图像尺寸确认是否存在混合分辨率的情况因为这会直接影响第3章坐标归一化时的分母选择。还有一个值得养成的习惯数据下载后先做一次“快照”。把目录结构、文件数量、类别清单、图像尺寸记在一个README里后面训练和复用的时候随时能查。不要相信自己的记忆地面障碍物数据集经常用着用着就忘了初始状态特别是多轮增补之后原始标注和转换后标注很容易混在一起。2.2 标注格式与类别定义检测、分割、关键点各看什么地面障碍物数据集在标注格式上有个显著特点同一份数据往往同时提供检测框、分割掩码、关键点三种标注形态对应不同的下游需求。AGV避障只需要知道“前方两米有一个木箱”一个矩形框就够但要做精密绕行评估则需要实例分割的像素级掩码而坑洞、裂缝这类目标需要关键点来定位端点或测量宽度。下面这张表是我整理类似数据集时常用的对照表也可以用来快速确认手上这份数据的标注覆盖范围。不同数据集的类别命名可能有差异例如“barricade”也可能叫“water_horse”或“fence”看JSON里的类别清单为准。目标类别检测框可用分割掩码可用关键点可用下游用法锥桶 cone是是可选避障、移除水马/护栏 barricade是是否路径规划绕行木箱/大物件 crate是是否避障、搬运石块/散落物 obstacle是是否避障坑洞 pothole是是是边缘关键点路况评估、修补裂缝 crack是是是端点关键点长度与宽度估算人员 person是是是骨架关键点安全预警表格里有一个容易被忽略的点同一目标在不同标注格式下类别编号必须一致。比如约定“锥桶0”那COCO JSON里的category_id、YOLO txt里的第一个数字、分割掩码对应的类别必须是同一个编号。这个对应关系最好单独存一个category_map.json或者直接写死在转换脚本顶部避免后续反复核对翻车。另外要确认JSON里有没有segmentation字段。如果这份数据集面向实例分割annotations里除了bbox还会有polygon或RLE编码的多边形如果只做目标检测yaml里不涉及分割格式即可。这类多类别目标检测数据集最常见的坑是“一张图里同时出现七八个类别但某个类别的样本只有几十张”类别不平衡会直接体现在mAP曲线上后面第5章会细讲。2.3 关键点标注约定可见性标志别忽略如果数据集包含关键点标注首先要搞清楚可见性标志visibility的约定。COCO格式里每个关键点通常带一个vis值0表示未标注、1表示被遮挡但位置可推断、2表示完全可见。这份数据如果面向坑洞和裂缝标注坑洞边缘关键点会有大量被阴影或杂物遮挡的情况。转换时直接无视vis标志YOLO关键点损失就会在不可见点上反复震荡loss曲线看着很焦虑指标却始终上不去。我一般会在转换脚本里把vis0的样本直接过滤掉或者把所有关键点统一按vis1处理并由损失函数权重承担。前者更干净适合大多数场景后者适合标注本身比较稀疏的情况。第5.4节会给出具体的现象和排查方式这里先记住一件事关键点不是“有坐标就行”可见性标志决定损失计算是否合理。3. 把COCO标注转成YOLO格式坐标归一化与三类坑3.1 COCO转YOLO检测框与分割掩码的转换脚本COCO的bbox是[x, y, width, height]绝对像素值以图像左上角为原点YOLO的bbox是[x_center, y_center, width, height]全部除以图像宽高得到0到1之间的相对坐标。看起来只是数学变换但真正落地时至少有三个坑坐标原点约定搞错、分割坐标没有归一化、类别编号和data.yaml对不上。最常见做法是写一个转换脚本一次性把检测框和分割掩码都转好。import json import os CATEGORY_MAP {cone: 0, barricade: 1, crate: 2, obstacle: 3, pothole: 4, crack: 5, person: 6} def convert_coco(coco_json, img_dir, out_label_dir): with open(coco_json) as f: data json.load(f) os.makedirs(out_label_dir, exist_okTrue) img_id_to_info {img[id]: img for img in data[images]} cat_id_to_new {cat[id]: CATEGORY_MAP[cat[name]] for cat in data[categories]} for ann in data[annotations]: img img_id_to_info[ann[image_id]] w, h img[width], img[height] if w 0 or h 0: continue x, y, bw, bh ann[bbox] cx (x bw / 2) / w cy (y bh / 2) / h bw_n bw / w bh_n bh / h cls cat_id_to_new[ann[category_id]] line f{cls} {cx:.6f} {cy:.6f} {bw_n:.6f} {bh_n:.6f}\n txt_path os.path.join(out_label_dir, img[file_name].replace(.jpg, .txt)) with open(txt_path, a) as out: out.write(line) convert_coco(annotations/instances.json, ground_obstacle_data/images, ground_obstacle_data/labels)脚本的逻辑不复杂但参数值得留意。cx、cy、bw_n、bh_n计算时全部除以w和h这是YOLO格式的核心要求类别编号通过CATEGORY_MAP映射保证和后面data.yaml完全对齐。如果某一类在标注里叫obstacle_rock而转换脚本把它映射成了3那训练出来的模型永远把石块识别成第3类后面排查时非常头疼。instances.json里除了检测框还可能有segmentation字段。只训练检测模型时直接忽略seg即可如果要跑实例分割则需把COCO的多边形坐标归一化后写入YOLO分割格式。YOLO分割格式每一行首字符同样是类别编号后面跟的是多边形顶点坐标对注意顶点之间用空格分隔坐标同样归一化到0到1。还有一类地面障碍物数据是视频抽帧采集分割掩码很多用RLE压缩存储。转polygon需要先解RLE再转坐标用pycocotools的segmentationToPolygon可以完成。这一段的工作量比检测框大但原理仍然是“绝对像素转相对坐标”没有本质差异。3.2 训练集与验证集划分按采集序列切分而不是随机打散转换完成之后另一个容易出问题的步骤是train/val划分。很多人习惯直接random.shuffle然后按比例切但地面障碍物数据常常来自同一场景的连续视频帧随机划分会让模型在验证阶段“见过”相邻帧导致验证指标虚高实际部署时立刻露出原形。我一般会按采集来源或视频序列来划分确保同一镜头的连续帧只出现在同一分区。import os import random import shutil base_dir ground_obstacle_data sequences [d for d in os.listdir(base_dir) if os.path.isdir(os.path.join(base_dir, d))] random.seed(42) random.shuffle(sequences) split int(len(sequences) * 0.8) train_seqs sequences[:split] val_seqs sequences[split:] train_img_dir os.path.join(base_dir, train, images) train_lbl_dir os.path.join(base_dir, train, labels) val_img_dir os.path.join(base_dir, val, images) val_lbl_dir os.path.join(base_dir, val, labels) for d in (train_img_dir, train_lbl_dir, val_img_dir, val_lbl_dir): os.makedirs(d, exist_okTrue) for seq in train_seqs: for sub in (images, labels): src os.path.join(base_dir, seq, sub) dst train_img_dir if sub images else train_lbl_dir for name in os.listdir(src): shutil.move(os.path.join(src, name), os.path.join(dst, name))这段代码的核心不是shutil而是按序列切分、让同镜头的连续帧不进不同分区。random.shuffle前先固定seed保证每次划分结果一致这也是一个容易忽略的细节。如果数据集没有显式的seq目录名可以按文件名前缀分组比如巡检车一天跑下来文件名常有日期前缀按前缀分组等效于按采集时段分组效果类似。划分完毕后记得再做一次统计确认train和val里每个类别都有样本。如果验证集中某个类别只有一两张mAP会忽高忽低这就是另一种“数据总量充足但指标不可信”的陷阱。常见做法是把划分后的两个目录分别跑一遍cat统计类别分布写在README里。3.3 转换完成后先自查坐标范围与类别编号一次验完转换结束后强烈建议做一次自动化自检先看坐标是否越界再看类别编号是否和yaml对应。不要省略这一步坐标问题发现是小修等训练完发现类别错位就是大改。我吃过一次亏转换脚本里高度变量写错所有y坐标都除以了宽度坐标严重越界但YOLO训练时只给出Warning不报错整个训练过程看上去一切正常mAP却一直停在0附近。import glob label_files glob.glob(ground_obstacle_data/labels/*.txt) cat_set set() for lf in label_files: with open(lf) as f: for line in f: parts line.strip().split() if len(parts) 5: print(坐标缺失:, lf) continue cat_set.add(parts[0]) vals [float(v) for v in parts[1:]] assert min(vals) 0, lf assert max(vals) 1, lf print(类别编号:, sorted(cat_set, keyint)) print(坐标范围检查通过)这里assert了所有坐标在0到1之间一旦转换脚本里混入未归一化坐标训练时YOLO只会给出警告但训练完mAP往往异常。提前拦截省得后面花两小时Debug。类别编号的打印也有用——如果发现输出里出现了预期之外的数字比如6之外出现了7那就是映射表写错了。4. 训练配置与调参从yaml到loss曲线的落地4.1 数据yaml与模型选择小目标与多类别是主要矛盾转换脚本跑完第一步是把data.yaml写好。Ultralytics YOLO的data.yaml只需要三块信息训练图片路径、验证图片路径、类别名字。下面给一个常见写法。path: /home/user/datasets/ground_obstacle train: images/train val: images/val nc: 7 names: [cone, barricade, crate, obstacle, pothole, crack, person]path用绝对路径不要用相对路径。YOLO在不同操作系统下对相对路径的解析结果不一致跑训练命令时最容易在这里翻车。nc是类别数names对应转换脚本里的CATEGORY_MAP两者顺序必须一字不差否则就是典型的“模型能训练、指标很好看、部署全错”场景。names顺序错了预测出来的类别名和实际目标对不上可视化检查时会发现所有框的标签都偏了一位。模型选择方面地面障碍物兼具“目标小”和“多类别”两个特点。目标是锥桶、坑洞这类小物体时不建议一上来就选最小的YOLOv8n模型容量太小对极小目标召回率偏低如果显存有限我宁可选YOLOv8s加高分辨率输入也不选YOLOv8n加低分辨率。常见做法是先用YOLOv8m输入分辨率640起步跑通链路后再针对小目标做增强。4.2 训练命令与参数说明batch、imgsz和早停怎么设yaml就绪后直接跑训练命令。以Ultralytics框架为例命令如下。yolo train \ modelyolov8m.pt \ datadata.yaml \ imgsz640 \ batch16 \ epochs200 \ patience50 \ workers4 \ device0 \ projectruns \ nameobstacle_exp1参数里值得说道的是batch和patience。batch16是在普通单卡下的折中值显存紧张时可以降到8但降低batch后初始学习率最好同步下调否则前几个epoch的loss会非常“暴躁”。patience50意味着验证指标连续50个epoch不提升就自动停止地面障碍物这类类别差异大的数据集早停能省下大量训练时间。workers4表示开4个数据加载进程要注意Windows下workers大于0偶尔会触发子进程问题这时改成workers0最省事。训练过程中重点盯两条曲线box_loss和cls_loss。box_loss持续下降说明定位在收敛cls_loss在epoch 30之后还在反复横跳常见原因不是模型菜而是类别本身难分。比如水马和锥桶在远处长得像或者坑洞和阴影的纹理相似这类情况再提高epoch数也不会改善需要回到数据层面补样本或者做针对性数据增强。另一个常见场景是loss稳定在0.02附近不动了但验证集的mAP50还停留在0.4左右。这时优先检查两件事一是val集是否混进了标注错误的数据可以随机抽几十张验证图人工看一眼二是确认输入图像里目标是否太小。我在处理小目标时有个习惯在不爆显存的前提下把imgsz从640拉到1280配合cacheTrue对地面小物体的召回往往比加任何增强都直接。YOLO在1280输入下会把原图切块推理对小目标的检测提升尤为明显代价是前向时间拉长部署前要重新评估推理速度。4.3 关键点训练分支kpt_shape与损失观察如果这份数据集的坑洞和裂缝标注带关键点需要额外配置关键点分支。Ultralytics的pose模型在data.yaml里多两行配置kpt_shape和flip_idx。kpt_shape形如[4, 3]表示4个关键点、每个点3个值x、y、vis。针对坑洞边缘关键点真正需要的是点之间的相对位置约束flip_idx可以不设但kpt_shape一定要写对。关键点训练时损失除了box_loss和cls_loss还会多一个关键点损失。观察它的时候要看是否持续下降而不是只看mAP。如果关键点loss在第20个epoch之后开始反弹优先怀疑vis标志处理不对或者真值坐标有错位。这里有个先后的经验先解决关键点坐标本身的问题再调学习率否则你会在错误的坐标上反复调参纯属浪费生命。5. 训练避坑记录五个真实翻车现场与排查方式5.1 现象loss正常收敛但mAP一直为0有一次训练结束box_loss和cls_loss都正常下降验证曲线的mAP50停在0附近。排查了大半天最后发现是转换脚本里坐标归一化只用了图像宽度高度变量写错所有y坐标越界。YOLO遇到坐标越界时不会直接报错只会给个警告然后继续训练于是整个训练过程看上去一切正常实际上模型学到的坐标信息全是错的。解决方式就是第3.3节那个坐标范围检查脚本转换完先跑一遍所有坐标在0到1之间才算通过。从那以后我再也没有绕过这一步无论数据集多小多简单坐标自查都是必备流程。血泪经验说一句坐标越界这类问题在loss曲线上几乎看不出痕迹你的第一道防线永远是转换脚本的自查逻辑。5.2 现象验证mAP很高部署后连续漏检这是连续帧数据最常见的坑。数据集来自一段连续视频随机划分train和val后相邻帧大量同时出现在两边。模型在val上看到了几乎一样的画面mAP自然高到接近0.85但部署到新场景、新角度模型没见过的画面一多召回率立刻掉下来锥桶和坑洞接连漏检。解决方式就是按采集序列切分而不是按图像切分。如果文件名本身不带序列信息用采集时间戳作为分组依据。这一点建议在转换阶段就处理好不要等模型训练完再去补验证集那是把简单问题复杂化。检测类任务吃的就是数据分布差异训练集和验证集分布越接近验证指标就越有欺骗性。5.3 现象大目标全中、小目标全丢mAP被拉低同一个模型里木箱和人员的检测效果尚可但锥桶和小石块几乎全部漏掉。原因是地面环境中目标尺寸差距太大一个大木箱占图像百分之二十的面积一个锥桶在远处只占几十个像素。YOLO在640输入下对占屏比小于1%的目标召回率天然偏低这不是调参能解决的问题。解决思路有两个一是微调数据增强把Cutout、Mosaic里的小目标增强强度提高让模型多“看到”小目标二是用切片推理训练时imgsz先用640验证推理时切块看局部细节。我后来更倾向于直接imgsz1280训练小目标版本虽然训练时间快翻倍但地面障碍物检测的mAP50回升非常明显。如果显存不够切片推理几乎是唯一选择。5.4 现象关键点loss不降反升坑洞关键点检测训练时loss在第20个epoch后开始反弹看起来很不稳定。排查后发现是转换脚本没有过滤vis0的点大量坑洞边缘被阴影遮挡标注点根本没有有效坐标模型被迫去预测那些未标注的位置梯度自然难以收敛。关键点head会对所有关键点同时计算损失不管这个点是否可见。解决方式是转换时过滤vis0的样本只保留有坐标且可见的点。如果数据集本身标注稀疏可以把vis1和vis2合并处理但vis0一定要扔掉或者单独给一组权重。这个坑在普通目标检测里不会触发只有做实例分割或关键点检测时才会遇到处理过一次之后每次拿到新数据集我都会先查vis分布。5.5 现象训练到一半显存OOM训练到epoch 43时突然显存OOM前面的box_loss一直正常没有任何预兆。原因是batch和imgsz同时拉得比较高PyTorch在训练中期会缓存更多激活值峰值显存超过显卡容量时直接崩溃。直觉上小事一桩但训练中途OOM意味着前面所有epoch全部白跑时间成本很高。解决方式有两个一是让Ultralytics自动探测batch把batch减半通常会降到能跑的水平二是用梯度累积模拟更大的batch显存占用不增加但训练步数要相应调整。显存不够时不要死磕batch梯度累积是更体面的方案代价只是训练时间变长一点。另外可在训练脚本里加一句export CUDA_VISIBLE_DEVICES0避免环境变量混乱导致的无关OOM。6. 验证与单图推理把指标落回画面再收工6.1 用val脚本确认检测与分割指标训练完成后常规验证是用val脚本输出指标与训练日志里的曲线核对。val脚本默认会输出mAP50和mAP50-95两种指标前者看总体命中率后者看框质量。区分使用场景AGV避障更看重mAP50因为只要框位置大致正确就能绕行而精密对接或面积估算场景mAP50-95更值得关注。yolo detect val \ modelruns/obstacle_exp1/weights/best.pt \ datadata.yaml \ imgsz640这个命令跑完会生成一个results.csv和若干验证可视化图。我一般会顺手看一眼混淆矩阵重点看哪些类别互相混淆。水马被识别成木箱、坑洞被识别成阴影这类错误在混淆矩阵里一目了然比单纯看mAP有信息量得多。6.2 单图推理与逐类可视化检查指标落完我从不直接收工会拿几张训练时没见过的现场图跑单图推理确认模型在真实场景下的表现。yolo detect predict modelruns/obstacle_exp1/weights/best.pt \ sourcesamples/field_0921.jpg \ conf0.35 \ saveTrue \ save_confTrueconf0.35是经验值太低会冒出大量误检太高会把远处的锥桶过滤掉。地面障碍物场景调研时我一般用0.3到0.4之间找一个平衡点。保存出来的图片要放大逐类看重点确认三类问题哪类目标被频繁误检、远处小目标是否完全无框、水马和锥桶的标签是否混用。每次训练完我都会强制自己先跑一遍最难的五张样本再谈任何调参动作。这个习惯帮我省下的时间远比多花的多——数据集的最终价值永远体现在实际画面里而不只是指标文件里。希望帮到你。本文还有配套的精品资源点击获取
返回列表