简介:地面障碍物检测数据集面向自动驾驶、机器人导航、智能监控与计算机视觉研究等场景,提供栅栏、地面障碍物、岩石、树木、汽车、人物共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曲线看着很焦虑,指标却始终上不去。
我一般会在转换脚本里把vis=0的样本直接过滤掉,或者把所有关键点统一按vis=1处理并由损失函数权重承担。前者更干净,适合大多数场景;后者适合标注本身比较稀疏的情况。第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_ok=True) 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_ok=True) 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, key=int)) 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 \ model=yolov8m.pt \ data=data.yaml \ imgsz=640 \ batch=16 \ epochs=200 \ patience=50 \ workers=4 \ device=0 \ project=runs \ name=obstacle_exp1参数里值得说道的是batch和patience。batch=16是在普通单卡下的折中值,显存紧张时可以降到8,但降低batch后初始学习率最好同步下调,否则前几个epoch的loss会非常“暴躁”。patience=50意味着验证指标连续50个epoch不提升就自动停止,地面障碍物这类类别差异大的数据集,早停能省下大量训练时间。workers=4表示开4个数据加载进程,要注意Windows下workers大于0偶尔会触发子进程问题,这时改成workers=0最省事。
训练过程中重点盯两条曲线:box_loss和cls_loss。box_loss持续下降,说明定位在收敛;cls_loss在epoch 30之后还在反复横跳,常见原因不是模型菜,而是类别本身难分。比如水马和锥桶在远处长得像,或者坑洞和阴影的纹理相似,这类情况再提高epoch数也不会改善,需要回到数据层面补样本或者做针对性数据增强。
另一个常见场景是loss稳定在0.02附近不动了,但验证集的mAP50还停留在0.4左右。这时优先检查两件事:一是val集是否混进了标注错误的数据,可以随机抽几十张验证图人工看一眼;二是确认输入图像里目标是否太小。我在处理小目标时有个习惯:在不爆显存的前提下把imgsz从640拉到1280,配合cache=True,对地面小物体的召回往往比加任何增强都直接。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,验证推理时切块看局部细节。我后来更倾向于直接imgsz=1280训练小目标版本,虽然训练时间快翻倍,但地面障碍物检测的mAP50回升非常明显。如果显存不够,切片推理几乎是唯一选择。
5.4 现象:关键点loss不降反升
坑洞关键点检测训练时,loss在第20个epoch后开始反弹,看起来很不稳定。排查后发现是转换脚本没有过滤vis=0的点,大量坑洞边缘被阴影遮挡,标注点根本没有有效坐标,模型被迫去预测那些未标注的位置,梯度自然难以收敛。关键点head会对所有关键点同时计算损失,不管这个点是否可见。
解决方式是转换时过滤vis=0的样本,只保留有坐标且可见的点。如果数据集本身标注稀疏,可以把vis=1和vis=2合并处理,但vis=0一定要扔掉或者单独给一组权重。这个坑在普通目标检测里不会触发,只有做实例分割或关键点检测时才会遇到,处理过一次之后,每次拿到新数据集我都会先查vis分布。
5.5 现象:训练到一半显存OOM
训练到epoch 43时突然显存OOM,前面的box_loss一直正常,没有任何预兆。原因是batch和imgsz同时拉得比较高,PyTorch在训练中期会缓存更多激活值,峰值显存超过显卡容量时直接崩溃。直觉上小事一桩,但训练中途OOM意味着前面所有epoch全部白跑,时间成本很高。
解决方式有两个:一是让Ultralytics自动探测batch,把batch减半,通常会降到能跑的水平;二是用梯度累积模拟更大的batch,显存占用不增加,但训练步数要相应调整。显存不够时不要死磕batch,梯度累积是更体面的方案,代价只是训练时间变长一点。另外可在训练脚本里加一句export CUDA_VISIBLE_DEVICES="0",避免环境变量混乱导致的无关OOM。
6. 验证与单图推理:把指标落回画面再收工
6.1 用val脚本确认检测与分割指标
训练完成后,常规验证是用val脚本输出指标,与训练日志里的曲线核对。val脚本默认会输出mAP50和mAP50-95两种指标,前者看总体命中率,后者看框质量。区分使用场景:AGV避障更看重mAP50,因为只要框位置大致正确就能绕行;而精密对接或面积估算场景,mAP50-95更值得关注。
yolo detect val \ model=runs/obstacle_exp1/weights/best.pt \ data=data.yaml \ imgsz=640这个命令跑完,会生成一个results.csv和若干验证可视化图。我一般会顺手看一眼混淆矩阵,重点看哪些类别互相混淆。水马被识别成木箱、坑洞被识别成阴影,这类错误在混淆矩阵里一目了然,比单纯看mAP有信息量得多。
6.2 单图推理与逐类可视化检查
指标落完,我从不直接收工,会拿几张训练时没见过的现场图跑单图推理,确认模型在真实场景下的表现。
yolo detect predict model=runs/obstacle_exp1/weights/best.pt \ source=samples/field_0921.jpg \ conf=0.35 \ save=True \ save_conf=Trueconf=0.35是经验值,太低会冒出大量误检,太高会把远处的锥桶过滤掉。地面障碍物场景调研时,我一般用0.3到0.4之间找一个平衡点。保存出来的图片要放大逐类看,重点确认三类问题:哪类目标被频繁误检、远处小目标是否完全无框、水马和锥桶的标签是否混用。每次训练完,我都会强制自己先跑一遍最难的五张样本,再谈任何调参动作。这个习惯帮我省下的时间远比多花的多——数据集的最终价值,永远体现在实际画面里而不只是指标文件里。希望帮到你。
本文还有配套的精品资源,点击获取