☰
泥石流滑坡目标检测数据集:YOLO+VOC双格式解析与YOLOv8训练避坑指南
2026/10/11 20:37:07 网站建设 项目流程

简介:目标检测数据集聚焦泥石流与滑坡两类地质灾害场景,面向需要训练YOLO、Faster R-CNN等检测模型的算法工程师、研究生及防灾减灾研究人员。数据集以VOC与YOLO双格式组织,JPEGImages、Annotations、labels三个文件夹一一对应,共含2262张清晰现场图像,标注框总数为6508个,其中landslide类6079个、Debris-flow类429个,适合滑坡隐患识别、灾后快速评估等任务模型训练与精度验证。压缩包共2000个文件,文件主体为XML标注文件,另附一个说明性txt,整体约166MB,便于下载与离线使用;目前已有653人学习浏览。数据集未做增强处理,全部矩形框标签经人工核对,解压后可直接接入YOLO或VOC流程,也可借助脚本快速转换为其他检测框架格式,尤其适合中高级算法工程师动手实践,对需要快速搭建滑坡监测识别流程的团队可显著减少数据整理耗时。

1. 泥石流滑坡目标检测:2262 张真实样本、YOLO+VOC 双格式,拿到手先别急着开训

泥石流和滑坡检测在目标检测里属于典型的小众场景:主流开源数据集几乎都围绕 COCO 那 80 类,人和车占了绝大多数。真要做地质灾害预警,常用的做法只能是自己去现场或遥感图里找样本,再手动标注,一张图来回折腾半小时很正常。这个「泥石流滑坡数据集 2262 张 YOLO+VOC 格式」的价值在于:它把 2262 张真实泥石流、滑坡场景图片和对应标注框打包好了,并且同时给出 YOLO 的 txt 和 VOC 的 XML 两套标签,省去最枯燥的格式转换环节。适合正在做地质灾害监测的算法工程师、滑坡预警系统开发,或者只是想要一个非 COCO 场景练手目标检测的研究生。我的建议是别拿到手直接训练,先把两套标签的格式和目录对应关系核对清楚,这个步骤能避免后面换工具链时的大量返工。

2. 数据格式解剖:YOLO 的 txt 与 VOC 的 XML 如何描述同一个目标框

这个数据集最容易被忽略、也最值得先看的地方,就是双格式标注。YOLO 系列训练工具需要 txt,而很多标注软件、开源标注平台和论文复现代码里用的是 VOC XML。你只有在训练前把这两套标签的对应关系吃透,之后做迁移训练、转 COCO、接 TensorRT 部署时才不会两眼一抹黑。

2.1 目录结构与同名映射:先把三份文件对齐

解压后常见的目录结构是这样的,你可以把它当作默认参考:

泥石流滑坡数据集2262张YOLO+VOC格式/ ├── images/ # 2262 张 JPEG 图片 │ ├── img_0001.jpg │ ├── img_0002.jpg │ └── ... ├── labels/ # YOLO 格式 txt │ ├── img_0001.txt │ ├── img_0002.txt │ └── ... ├── annotations/ # VOC 格式 XML │ ├── img_0001.xml │ ├── img_0002.xml │ └── ... ├── classes.txt # 类别清单 └── data.yaml # YOLO 训练配置

我一般拿到压缩包后不会直接解压训练,而是先跑一个最简单的同名映射检查:images 下的每张 jpg,必须能在 labels 里找到同名 txt,在 annotations 里找到同名 xml。文件名不要求完全相同后缀,但主体名必须一致,比如img_001.jpg对应img_001.txt和img_001.xml。这一步看起来多余,实际很关键,因为后面训练时 YOLO 是根据图片路径去推断标签路径,文件名对不上,标签就会被静默跳过,表现为“训练正常但模型什么都没学到”。

如果解压出来的目录命名不完全一致,不用慌。你只需要弄清楚谁和谁配对,然后写个脚本做软链接或重命名。我遇到过标注平台导出时给图片加了前缀、标签没加的情况,花十几分钟写个批量改名脚本,比后面排查一个空的验证集损失要划算得多。

2.2 YOLO 标注 txt 的解析:类别、中心点、宽高

YOLO 的 txt 标注格式不长,每行表示一个目标框,标准结构是五列:类别 ID、中心点 x、中心点 y、宽度 w、高度 h。注意,后面的四个数值全部是相对图片宽高的比值,范围应该在 0 到 1 之间。拿头部几个文件看一眼:

import os label_dir = "labels" for filename in sorted(os.listdir(label_dir))[:5]: path = os.path.join(label_dir, filename) with open(path, "r", encoding="utf-8") as f: lines = f.read().strip().splitlines() print(f"{filename} -> {len(lines)} 个目标") for line in lines: parts = line.split() cls = int(parts[0]) # 类别 ID,从 0 开始 cx, cy, w, h = map(float, parts[1:5]) print(f" class={cls} center=({cx:.3f}, {cy:.3f}) size=({w:.3f}, {h:.3f})")

这段代码逻辑很简单,但价值在于快速确认两点:第一,类别 ID 是不是从 0 开始且连续;第二,坐标是否真的落在 [0,1] 区间。如果出现某个 w 或 h 大于 1,说明标注导出时图片尺寸算错了,这类坏标签进训练集会拉低整个模型的框回归质量。

这个数据集如果只有一个滑坡类别,那所有 txt 的第一列应该都是 0。如果出现 0 和 1 混用,就需要对照 classes.txt 确认到底是泥石流、滑坡分开标,还是只标了一个总类。不要在代码里猜类别数量,训练前打开 classes.txt 看一眼是最稳的。

2.3 VOC XML 标注:绝对坐标和图片尺寸是对账凭证

VOC 的 XML 里把信息拆成了 filename、size、object 三部分。size 里记录图片真实宽高,object 里每个目标有一个 bndbox,用 xmin、ymin、xmax、ymax 四个绝对像素坐标表示框的位置。解析方式很固定:

import xml.etree.ElementTree as ET xml_path = "annotations/img_0001.xml" tree = ET.parse(xml_path) root = tree.getroot() width = int(root.find("size/width").text) height = int(root.find("size/height").text) print(f"图片尺寸: {width} x {height}") for obj in root.findall("object"): name = obj.find("name").text 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) print(f"类别: {name}, 框: ({xmin:.1f}, {ymin:.1f}, {xmax:.1f}, {ymax:.1f})")

从 VOC 转 YOLO 时,核心就三步:中心点 x 等于 (xmin + xmax) 除以 2 再除以图片宽度,中心点 y 同理除以高度,框宽等于 (xmax - xmin) 除以宽度,框高除以高度。反过来,从 YOLO 转 VOC 就是做乘法并还原成整数坐标。

我拿到双格式数据集,一定会用 XML 反推一遍 YOLO 坐标,再和同名的 txt 做对比,误差超过 0.01 就标记出来。因为有些数据集打包时,图片被统一缩放或裁剪过,但 XML 里的尺寸节点还是旧值,即便肉眼看不出来,训练后会表现为检测框整体偏移。这个“对账”动作十分钟能做完,却能排除掉最隐蔽的标签不一致问题。

3. 开始训练:8:1:1 划分数据并在 YOLOv8 里跑通完整链路

确认标签格式没问题之后,下一步就是把 2262 张图片划分成训练集、验证集、测试集,然后接进 YOLOv8 训练。这里我用的划分比例是 8:1:1,背后考虑是:泥石流场景图片之间可能来自同一条沟、同一片坡,相似背景较多,验证集如果太小,评估指标的波动会非常大。8:1:1 属于中性偏保守的比例,既保证训练样本量,又能让 val 的统计方差小一点。

3.1 用脚本划分数据集,固定随机种子

import os import shutil import random random.seed(42) images = sorted(os.listdir("images")) random.shuffle(images) total = len(images) train_cnt = int(total * 0.8) val_cnt = int(total * 0.1) splits = { "train": images[:train_cnt], "val": images[train_cnt:train_cnt + val_cnt], "test": images[train_cnt + val_cnt:], } for split_name, split_images in splits.items(): img_dir = os.path.join(split_name, "images") lab_dir = os.path.join(split_name, "labels") os.makedirs(img_dir, exist_ok=True) os.makedirs(lab_dir, exist_ok=True) for img_name in split_images: shutil.copy(os.path.join("images", img_name), os.path.join(img_dir, img_name)) label_name = os.path.splitext(img_name)[0] + ".txt" src_label = os.path.join("labels", label_name) if os.path.exists(src_label): shutil.copy(src_label, os.path.join(lab_dir, label_name)) else: print(f"警告: 缺失标签 {label_name}")

脚本里有三个点值得说明。第一,random.seed(42)保证每次划分结果一致,这样你在调整超参数时,val 和 test 的组成不会变化,实验对比才有意义。第二,train_cnt用 int 取整,2262 张按 0.8 算出来是 1809,剩下 453 张再对半分给 val 和 test,总样本覆盖比较均匀。第三,缺失标签时打印警告而不是直接跳过,是为了让问题暴露在训练之前。

划分完成后再做一件小事:分别统计 train、val、test 三个子目录下的 jpg 数量和 txt 数量,必须完全一致。这一步能挡住“图片进了训练集、标签留在原目录”的低级失误。

3.2 编写 data.yaml 并选择训练超参数

YOLOv8 的训练入口会读一个 YAML 配置文件。这个数据集如果是单类别,data.yaml 内容大致是:

path: /home/user/landslide_dataset train: train/images val: val/images test: test/images nc: 1 names: 0: debris_flow

path建议写成绝对路径,避免 YOLO 相对路径解析的歧义;train和val指向划分后图片所在目录,YOLO 会自动在相同父目录下找 labels,也就是说 train 的同级 labels 目录必须存在。names里的 0 对应 txt 中第一列的类别 ID,顺序不能乱写,nc必须和 names 数量一致。

训练命令我通常这样起:

yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs_detect \ name=debris_flow_01

选择yolov8s.pt而不是 nano 或 large,是平衡时间和精度的结果。泥石流滑坡的框通常比较大且边缘模糊,s 模型的感受野和参数量足够;n 模型跑得快但边框回归精度会差一点,l 模型对显存要求高,单卡训练一轮的时间翻倍。imgsz=640是默认值,如果你的显卡显存大于 16G,建议提到 960,对滑坡这类背景纹理复杂的场景有小幅精度增益。batch=16对应 8G 显存左右的单卡,显存不够就先降到 8 或 4,不要硬撑。

训练过程中重点盯两个输出:第一个是BoxLoss和ClsLoss,它们应该在前面几个 epoch 快速下降,后面慢慢收敛;第二个是每个 epoch 末尾的验证指标,如果 val 的 mAP50 在某个点开始停滞,说明模型容量或者数据增强已经到极限,续训不会有太大变化。YOLOv8 默认保留best.pt和last.pt,分别按验证集最优和最后一步保存。

3.3 用 best.pt 做测试集评估

训练结束后,先不要急着接视频流,在测试集上验证一轮:

from ultralytics import YOLO model = YOLO("runs_detect/debris_flow_01/weights/best.pt") metrics = model.val(data="data.yaml", split="test") print("mAP50:", metrics.box.map50) print("mAP50-95:", metrics.box.map)

这段代码加载训练好的权重,在划分时单独的 test 目录上跑验证。metrics.box.map50是 IoU 阈值 0.5 时的平均精度,metrics.box.map是 0.5 到 0.95 的平均值。对泥石流检测来说,前者低于 0.75 基本不可用,后者受标注框边缘模糊影响,低一些可以接受。

也可以直接出可视化结果,用统一的 conf 阈值跑一遍测试集推理:

yolo predict \ model=runs_detect/debris_flow_01/weights/best.pt \ source=test/images \ conf=0.3 \ save=True

conf=0.3是起步值。泥石流检测场景里误报和漏报的代价完全不同,后续选阈值应该结合 val 的 Precision-Recall 曲线,如果业务偏向预警宁可误报,就把 conf 降到 0.2;偏向精准告警就升到 0.45。

4. 避坑:泥石流检测数据集从加载到训练,防不胜防的五个坑

这类真实灾害数据集的坑比标准数据集多很多,不是代码写错,而是数据本身带着历史包袱。下面五条是我复现数据集时踩过或见过别人踩的典型问题,每一条都可以直接用来自查。

4.1 空 txt 导致 loss 异常、mAP 全程为零

现象:训练正常启动,没有报错,但每个 epoch 的 loss 都很低,验证时检测目标数为 0,mAP 始终是 0。

原因:labels 目录里有空文件,或者图片名和标签名不匹配,数据加载器把这类图片当成无目标样本。模型把所有区域都预测成背景,但训练 loss 照样能下降,因为背景预测对了。

解决:训练前扫描一次空文件。

import os empty_count = 0 for root, _, files in os.walk("labels"): for f in files: path = os.path.join(root, f) if os.path.getsize(path) == 0: print("空标签:", path) empty_count += 1 print("空标签总数:", empty_count)

空文件对应的图片要么删掉,要么人工补标。千万不要只删 txt 不删图片,那会让模型把该区域当背景负样本,后续再补标训练时模型特别难“回心转意”。

4.2 data.yaml 的类别数量写错,训练直接罢工

现象:训练日志里出现IndexError: index 0 is out of bounds for axis 0 with size 0,或者类别 loss 一直是 NaN。

原因:data.yaml 里写了nc: 2,names 也列了两个名字,但标签文件里所有目标第一列都是 0,也就是实际只有一个类别。模型检测头初始化了两个类别分支,却拿不到第二个类别的正样本,反向传播异常。

解决:先cat classes.txt,确认数据集到底几个类别,然后让 yaml 跟它严格同步。比如 classes.txt 里只有debris_flow一行,就写成nc: 1。换预训练权重时不用担心类别数变化,YOLO 会自动重新初始化检测头。

4.3 YOLO txt 和 VOC XML 坐标不一致,框整体偏移

现象:同一张图片,用模型推理出来的框和原标注位置差一大截,但数值上又没报错。

原因:数据集在制作时图片被统一缩放或裁剪过,但 XML 里的 size 节点还保留原始尺寸;或者 YOLO txt 生成时除以了错误的宽高。这种错误在 mAP 里会被大幅惩罚,但我见过因为移位方向一致,mAP 只掉了零点零几的情况,更要命的是一换到别的数据集精度的伪装。

解决:用第 2 章里的 XML 解析脚本反推 YOLO 坐标,和 txt 逐行比较,误差超过 0.01 就重新生成。同时检查 XML 的 size 是否和实际图片的像素宽高一致,不一致就是当时打包出问题的最直接证据。

4.4 图片尺寸差异过大,显存溢出或训练中断

现象:训练到某个 epoch 突然报 CUDA OOM,但 batch 和 imgsz 都是常规设置。

原因:真实灾害数据里混了无人机航拍的大图和手机拍的竖图,有的长边超过 4000 像素,有的只有 640。YOLOv8 在训练时会把一个 batch 的图片 pad 到统一尺寸,超大图会把显存峰值瞬间顶满。

解决:先用 PIL 扫描全部图片的长宽分布,再决定 imgsz 和 batch。

from PIL import Image import os max_w = 0 max_h = 0 for img_name in os.listdir("images"): img = Image.open(os.path.join("images", img_name)) w, h = img.size max_w = max(max_w, w) max_h = max(max_h, h) print("最大宽:", max_w, "最大高:", max_h)

如果最大长边超过 1600,我一般会写脚本把长边统一重缩放到 1280 或 1600,短边按比例缩放,缩放之后重新核对标签。直接调低 batch 也能临时解决,但超大图送入网络后下采样信息损失比较严重,还是统一尺寸更稳。

4.5 标注框过小或宽高比极端,指标好看但实际漏检

现象:mAP50 有 0.8 以上,但把模型接到实际视频里,大片滑动面漏检,只框出来一小块碎石区。

原因:部分标注框只覆盖了滑坡滑动带的局部,或者框是特别窄的长条,宽高比到 1:10 甚至更大。YOLOv8 在 640 分辨率下经过 32 倍下采样,只有十几个像素宽的框特征几乎被抹掉,训练时模型没机会学到完整形态。

解决:先统计框的宽高分布。用 Python 读所有 txt,计算每个框的面积和宽高比,凡是 w/h 或 h/w 超过 8 的框,返回原图人工确认是否框得太局部。同时可以考虑训练时把 imgsz 提到 960,让窄长目标保留更多像素。真实部署时,也可以配合滑窗或 SAHI 这类切图推理,用重叠窗口把大图切成多个小块分别检测,小目标漏检率会明显下降。

5. 进阶:把泥石流检测从单张图片扩展到视频帧,并做伪标签增量学习

训练出 best.pt 只是第一步,真实项目里输入往往是无人机航拍视频或者固定点位监控流。把单张推理改成连续视频推理,再加一层时序平滑和伪标签回灌,才算把模型变成一个可用的预警模块。

5.1 用 OpenCV 逐帧读取并推理

最常见做法是用 OpenCV 读视频帧,逐帧喂给模型,再写回视频。

import cv2 from ultralytics import YOLO model = YOLO("runs_detect/debris_flow_01/weights/best.pt") cap = cv2.VideoCapture("field_video.mp4") fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) out = cv2.VideoWriter("field_video_out.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height)) while cap.isOpened(): ret, frame = cap.read() if not ret: break result = model.predict(frame, conf=0.35, imgsz=640)[0] out.write(result.plot()) cap.release() out.release()

conf=0.35在视频场景下会比测试集高一些,因为单帧误报可以被时序平滑兜住,不用为了召回拼命压低阈值。

5.2 用指数平滑做时序去抖

视频里真正的泥石流不会只出现一帧,而是一段时间内连续出现。用指数移动平均对置信度做平滑,能过滤掉相机抖动、飞鸟、临时碎石造成的单帧假阳。

ema = 0.0 alpha = 0.3 hit_frames = 0 for result in frame_results: if len(result.boxes) == 0: ema *= 0.7 hit_frames = max(0, hit_frames - 1) continue conf = float(result.boxes.conf.max()) ema = alpha * conf + (1 - alpha) * ema hit_frames += 1 if ema > 0.4 and hit_frames > 5: print("连续检测到泥石流,触发预警")

alpha越大,模型对当前帧越敏感;越小,平滑越强。地质灾害预警宁可慢半拍也不能疯报警,我会偏向alpha=0.25。

5.3 用新场景图片做伪标签增量学习

真实场景的多样性永远超过数据集。我会用 best.pt 对新采集的图片批量生成伪标签,人工抽查后再并入训练集。

yolo predict \ model=runs_detect/debris_flow_01/weights/best.pt \ source=new_scenes \ conf=0.45 \ save_txt=True \ save_conf=True

生成的 txt 在runs_detect/predict/labels里,人工审核时重点看 conf 在 0.45 到 0.6 之间的样本,这些最可能是难例;conf 高于 0.7 的可以放心采用。伪标签只能做数据扩充,不能全自动落库,我一般至少抽查 30%。

从那以后,我每次拿到新的灾害类数据集,都强制把标签合法性校验放到第一步:空 txt 扫描、同名映射检查、XML 反推坐标对账、图片尺寸分布统计,四件事全过一遍才允许开训。这个小习惯帮我少走了很多弯路,希望也能帮到你避开同样的坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询