简介:航拍滑坡泥石流检测数据集为地灾目标检测任务提供5619张432×432的航拍图像,覆盖滑坡与泥石流两类目标,合计17715个矩形标注框(landslide 16199框、Debris-flow 1516框),适合用于遥感影像识别、山地灾害监测等场景的模型训练与算法验证。数据以Pascal VOC和YOLO两种格式存放,每张图片均有对应xml与txt标注,另有说明txt;标注采用labelImg完成,已做数据增强,但未划分训练/验证/测试集,需使用者自行划分。压缩包体积约183.46MB,文件总数2000,其中1999个为XML标注文件、1个为说明TXT,便于按需挑选使用。已有63人学习/下载,作为带类别框数统计的公开数据集,可帮助快速开展滑坡/泥石流检测模型的训练与评测,但对训练所得模型精度不作保证。
1. 先弄清这个数据集能干什么:5619张航拍图,两种标注格式,拿它做检测的起点
做地质灾害监测的同行应该都经历过这种状态:跑一趟野外或者从无人机平作业里凑到一批影像,却花了一周时间在标注上。所以当看到“航拍滑坡泥石流检测数据集5619张VOC+YOLO格式.zip”这个包时,我的第一反应是兴奋,第二反应是冷静。5619张航拍图,意味着数据量足够启动一个目标检测项目的初期训练;VOC和YOLO两种标注格式并存,意味着不管你是准备跑YOLOv5、YOLOv8还是先做格式对照分析,都不用再从零转格式,但前提是你得先搞懂这个包里到底有什么,以及两种格式之间的坐标系统差异。
它解决的核心问题,是把“航拍大场景里的滑坡、泥石流区域框出来”。这类任务和通用目标检测最大的区别是:目标尺度小、背景复杂、标注框往往不规整。这套数据适合三类人:想把检测模型落地到地质灾害巡检场景的算法工程师;需要一份相对干净的数据集做遥感图像检测对比实验的研究者;以及刚接触YOLO、需要真实数据练手的初学者。但我要把丑话放在前面:这套数据没有统一固定的目录模板,不同版本包里文件组织方式差别很大,拿到后直接解压训练大概率翻车。我们先把包装拆开,把标注格式和对齐关系搞清楚。
2. 把zip里的东西吃透:VOC和YOLO标注格式的对应关系与解压检查
2.1 VOC那边的XML到底写了什么:object框和difficult标记
VOC格式在物体检测数据集里是流传最广的老牌格式。它的核心是一个XML文件,和每一张图像同名。打开这个XML,你会看到结构化信息:文件名、图像尺寸、以及若干个object节点。每个object里包含类别名和bndbox边界框,也就是xmin、ymin、xmax、ymax四个整数坐标,单位是像素。
这里有一个容易忽略的字段叫difficult。标注人员会用它标记“很难分辨”的样本。很多转换脚本会默认跳过difficult为1的框。航拍滑坡数据里,边界若被云层阴影或植被遮挡,标注员就会把这个框标记为difficult。如果训练时直接跳过这些框,相当于白白丢掉了一部分困难样本,模型的鲁棒性会打折扣。我一般建议把difficult框也保留,只是给它一个较低的权重,或者至少在验证时单独统计它对指标的影响。
import xml.etree.ElementTree as ET from pathlib import Path xml_path = Path("annotations/sample.xml") tree = ET.parse(xml_path) root = tree.getroot() print("filename:", root.find("filename").text) size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) print("image size:", w, "x", h) for obj in root.findall("object"): name = obj.find("name").text difficult = int(obj.find("difficult").text) if obj.find("difficult") is not None else 0 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"class={name}, difficult={difficult}, bbox=({xmin:.0f},{ymin:.0f},{xmax:.0f},{ymax:.0f})")这段脚本只做读取,不修改文件。逻辑说明:ET.parse解析XML,root.findall("object")拿到所有标注框。之所以在打印时保留浮点数,是因为有些工具生成的VOC格式坐标不是整数,直接转成int会丢失精度。参数上说,一般VOC的width和height要在这里记录,后续转YOLO格式时需要它做归一化分母。注意difficult字段在一部分标注里根本没写,所以要用if obj.find("difficult") is not None做防空判断,否则脚本会崩。
2.2 YOLO那边的txt为什么是“归一化坐标”:类别ID与中心点宽高的换算
YOLO格式和VOC最大的区别是坐标表达方式。YOLO的每个txt文件放在labels目录下,每一行对应一个目标框,格式是五个字段:class_id x_center y_center width height。这四个坐标值全部做了归一化,取值范围在0到1之间,除以的是图像自身的宽度和高度。
这里的“归一化”正是很多新手踩坑的地方。有人直接把VOC里的xmin和xmax除以图像宽,得到的是两个独立坐标,而YOLO要求的是中心点坐标x_center = (xmin + xmax) / 2 / image_width,宽度是width = (xmax - xmin) / image_width。同理,y方向用height做分母。另外,class_id是一个非负整数,它要对应一个单独的classes清单,通常是classes.txt或data.yaml里的类别顺序。滑坡、泥石流在这套数据里一般会作为两个类别出现,但具体是哪一行对应哪一类,必须以你解压后看到的类别顺序为准。
cat labels/sample.txtfrom pathlib import Path label_path = Path("labels/sample.txt") img_width = 1920 img_height = 1080 for line in label_path.read_text().strip().splitlines(): class_id, x_center, y_center, w, h = map(float, line.split()) x_min = (x_center - w / 2) * img_width y_min = (y_center - h / 2) * img_height x_max = (x_center + w / 2) * img_width y_max = (y_center + h / 2) * img_height print(f"class_id={int(class_id)}, pixel_bbox=({x_min:.1f},{y_min:.1f},{x_max:.1f},{y_max:.1f})")参数说明:读取txt每一行,把归一化中心点坐标反推回像素坐标。为什么要单独写这段?因为判断一个txt文件是否正确,最直观的方式就是把它还原成像素坐标后,与原图叠加或与原图尺寸对照。如果反推出来的x_max大于图像宽度,或者y_max大于图像高度,就说明标注越界,这类样本必须处理,否则训练时loss会异常。
2.3 解压和目录检查:先摸清图像数量、标注数量和类别分布
拿到zip先别急着解压后直接训练。我习惯先做一次完整性检查,因为数据集传输过程中zip包损坏是常见问题。其次,航拍数据集的目录结构五花八门,有的把VOC和YOLO分成两个文件夹,有的把images和labels分开,有的还带着一个无用的README。先摸清结构,再谈训练。
unzip -t 航拍滑坡泥石流检测数据集5619张VOC+YOLO格式.zip unzip 航拍滑坡泥石流检测数据集5619张VOC+YOLO格式.zip -d dataset cd dataset find . -type f | awk -F. '{ ext = ($NF in a ? $NF : $NF) a[ext]++ } END {for (e in a) print e, a[e]}'第一条命令unzip -t是测试zip文件是否完整,它会逐个文件做CRC校验。如果看到bad CRC或者missing entry,说明文件下载不完整,建议重新下载。第二条命令解压到dataset目录。第三条命令统计所有文件的扩展名分布。这里会看到大概率的文件类型有几类:jpg、png、xml、txt,可能还有json或yaml。拿到这个统计结果后,才能判断数据格式和标题描述是否一致。
参数说明:统计扩展名的awk脚本里,$NF是文件名最后一截,即扩展名。它把每个扩展名计数到数组a中,最后打印。真正需要人工核对的是:图像数量、xml数量、txt数量是否接近5619。如果图像是5619张,但xml只有5400多,那就有200多张图没有VOC标注。差出来这一部分,要么是空样本,要么是对齐文件名时出了遗漏,后续校验必须处理。
3. 把数据集改造成可训练的YOLO格式:校验标注、转换坐标、划分训练集和验证集
3.1 一份可跑的转换脚本:VOC转YOLO,注意类别名与ID的映射表
如果你拿到的是VOC格式的xml,想跑YOLO训练,最稳妥的办法是写一个转换脚本。这里的关键是类别名与ID的映射表必须固定。比如类名列表是["landslide", "debris_flow"],那么landslide的ID就是0,debris_flow就是1。这个顺序一旦确定,后续生成的所有txt都要严格遵守。常见做法是把这份映射写进一个classes.txt文件,长期保留。
我提供一个经过了多次调试的转换函数,它会把指定目录下的所有VOC xml转换成YOLO txt。它在源头上过滤了difficult为0或1两种选项,默认保留difficult框,因为航拍负样本本身难分,保留困难框对后续提高召回率有帮助。
import xml.etree.ElementTree as ET from pathlib import Path CLASSES = ["landslide", "debris_flow"] # 请对照数据包里的classes.txt修改 def voc_to_yolo(xml_path, out_dir, keep_difficult=True): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) text_lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASSES: continue difficult = int(obj.find("difficult").text) if obj.find("difficult") is not None else 0 if difficult and not keep_difficult: continue class_id = CLASSES.index(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) 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 text_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_dir.mkdir(parents=True, exist_ok=True) out_path = out_dir / (xml_path.stem + ".txt") out_path.write_text("\n".join(text_lines)) # 用法示例:把VOC目录下的所有xml转成labels目录下的txt voc_dir = Path("VOC/Annotations") out_dir = Path("YOLO/labels") for xml_file in voc_dir.glob("*.xml"): voc_to_yolo(xml_file, out_dir)逻辑说明:先把XML里的像素坐标算成归一化中心点和宽高,输出为一行文本。参数说明:CLASSES列表里的顺序就是后续训练的类别顺序,一旦数据包里的class顺序和你预想的顺序不一致,会在后面表现为mAP曲线异常、类别标签错位。所以转换结束后,立刻抽几张图检查txt内容,并把classes.txt保存在数据集根目录里。输出的小数点保留6位,精度足够。如果某张图没有目标,这个函数会生成一个空txt,YOLO系列训练默认会跳过空标签文件,但空文件也可能触发警告,建议在生成后统一清理空文件。
3.2 没有VOC直读YOLO?校验txt标注的合法性:边界、空框、重复标注
有些数据包的VOC格式可能不完整,或者你拿到的就是纯YOLO格式。这时不能跳过校验直接训练。我一般会写一个扫描脚本,把labels目录下所有txt逐行检查,判断四件事:类别ID是否越界、中心点坐标是否在0到1之间、宽高是否为负或为0、是否存在完全相同的重复标注行。这个脚本在几十秒内能扫描完五千多张图的标注,收益很高。
from pathlib import Path label_dir = Path("YOLO/labels") num_classes = len(CLASSES) errors = [] for txt_path in label_dir.glob("*.txt"): lines = txt_path.read_text().strip().splitlines() for line_no, line in enumerate(lines, 1): parts = line.split() if len(parts) != 5: errors.append(f"{txt_path.name}:{line_no}: field count != 5") continue class_id, x_c, y_c, w, h = map(float, parts) if class_id < 0 or class_id >= num_classes: errors.append(f"{txt_path.name}:{line_no}: class_id out of range") if not (0 <= x_c <= 1 and 0 <= y_c <= 1): errors.append(f"{txt_path.name}:{line_no}: center out of [0,1]") if w <= 0 or h <= 0: errors.append(f"{txt_path.name}:{line_no}: non-positive w/h") # 检查重复行 from collections import Counter for txt_path in label_dir.glob("*.txt"): lines = txt_path.read_text().strip().splitlines() if not lines: errors.append(f"{txt_path.name}: empty label file") dupes = [line for line, cnt in Counter(lines).items() if cnt > 1] if dupes: errors.append(f"{txt_path.name}: duplicate lines: {dupes[:3]}") print("errors:", len(errors)) for e in errors[:30]: print(e)这段校验脚本输出三类典型问题。参数说明:num_classes必须和转换脚本里的CLASSES长度一致。为什么要单独检查重复行?因为有些半自动标注工具会在导出时把同一个框导出两次,训练时这个框会被重复计数,相当于变相给该样本加权重,干扰模型收敛。如果发现空标签文件比较多,需要回去看是不是图像里确实没有目标。航拍滑坡场景里确实存在一定比例的纯背景图,这些图对训练是有价值的负样本,但需要单独建一个负样本目录,不要让空的txt干扰正常标签读取。
3.3 划分train/val并生成路径清单:目录结构与随机种子
数据集下载后,划分训练集与验证集是标准动作。常见做法是8比2或者9比1划分。对于5619张图的规模,8比2足够,也就是约4495张训练,1124张验证。要特别注意的是随机种子,否则每次复跑数据划分都不同,后续对比实验就失去意义。
我习惯直接把图片和标签统一归档到images/train、images/val、labels/train、labels/val四个目录。如果原包是VOC和YOLO两个格式混放,那么这个统一结构会让后面的数据配置变得非常简单。
import random import shutil from pathlib import Path image_root = Path("images/all") # 所有图片 label_root = Path("labels/all") # 所有txt train_ratio = 0.8 random.seed(42) image_files = sorted(list(image_root.glob("*.jpg")) + list(image_root.glob("*.png"))) random.shuffle(image_files) train_files = image_files[:int(len(image_files) * train_ratio)] val_files = image_files[int(len(image_files) * train_ratio):] for subset, file_list in [("train", train_files), ("val", val_files)]: (Path("images") / subset).mkdir(parents=True, exist_ok=True) (Path("labels") / subset).mkdir(parents=True, exist_ok=True) for img_path in file_list: label_path = label_root / (img_path.stem + ".txt") if not label_path.exists(): continue shutil.copy2(img_path, Path("images") / subset / img_path.name) shutil.copy2(label_path, Path("labels") / subset / label_path.name) print("train:", len(train_files)) print("val:", len(val_files))逻辑说明:先混洗图片列表,再按比例切片。复制而不是移动,这样可以保留原始数据。参数说明:random.seed(42)是固定随机种子,保证每次运行结果一致。这里有个很现实的坑:如果某张图没有对应txt,脚本会跳过它。这会导致train和val里实际有效的图片数量小于5619。航拍数据里这类“无标注图”可能是背景负样本,也可能是标注遗漏。如果是背景负样本,建议单独放一个background目录,并在data.yaml里配置bg标注为空,不要在训练集中强制丢弃。
4. 单卡跑通YOLO训练:数据YAML编写、预训练权重选择与关键训练参数
4.1 写一个data.yaml:路径、类别名与类别数对不上时的Sanity Check
YOLO系列训练需要一份数据描述文件,通常叫data.yaml。它包含三个核心字段:train路径、val路径、类别名称列表。最常见翻车点是类别名称列表顺序与前面生成txt时用的类别ID不一致。如果你转换脚本里CLASSES = ["landslide", "debris_flow"],那么data.yaml里的names必须是["landslide", "debris_flow"],先后顺序不能变。
path: /home/user/dataset train: images/train val: images/val names: 0: landslide 1: debris_flow参数说明:path是数据集根目录的绝对路径。使用相对路径时,yolo程序会以当前工作目录拼接,容易出错,我建议这里写绝对路径。names键可以写成列表形式["landslide", "debris_flow"],两种写法等价,但顺序必须和训练时读取的类别ID完全一致。写完之后做一个Sanity Check:用下面的Python代码打印每个类别的样本数。
from pathlib import Path label_root = Path("labels/train") class_counts = {0: 0, 1: 0} for txt_path in label_root.glob("*.txt"): for line in txt_path.read_text().strip().splitlines(): class_id = int(line.split()[0]) if class_id in class_counts: class_counts[class_id] += 1 print(class_counts)这里类别统计的意义在于:如果滑坡样本有8000个框,泥石流只有2000个框,训练时会发现模型倾向于学会滑坡而忽略泥石流。类别不平衡是滑坡泥石流数据的常态,因为泥石流地表特征更复杂、标注成本更高。预先统计好比例,后面才能决定是否启用类别权重。
4.2 选择预训练模型和anchors:小目标航拍场景的尺寸考虑
YOLO系列里,模型配置从n、s、m、l到x,参数量递增。针对航拍滑坡泥石流检测,我一般不会一上来就上YOLOv8x,因为数据集只有5619张,参数量过大会迅速过拟合。常见做法是先用YOLOv8n或YOLOv5s做一次基线训练,拿到baseline之后再换更大模型对比。
这个场景下真正影响结果的是输入图像尺寸imgsz。航拍图像通常是1920x1080甚至更大,而滑坡体、泥石流沟道在画面里往往只占几十到几百像素。如果imgsz设成640,小目标会被压缩到十几个像素,检测器很难学会。常见做法是设成1024或1280。代价是显存占用成倍增加,V100 16G上跑1280的batch只能调到8到16。如果你只有消费级显卡,可以先640验证流程,再抽查放大到1280的收益。
pip install ultralytics yolo detect train data=data.yaml model=yolov8n.pt imgsz=1024 batch=16 epochs=100 patience=20逻辑说明:model=yolov8n.pt会从官方拉起yolo预训练模型下载,作为初始权重。如果数据集类别数不等于80,它会自动替换检测头,但骨干网络参数保留。参数说明:imgsz=1024让网格能更细致覆盖小目标;patience=20表示连续20个epoch验证集指标不提升就早停。航拍数据噪声大,早停太严格容易欠拟合,20左右是一个比较合理的值。如果你打算跑YOLOv5,命令变成python train.py --data data.yaml --weights yolov5s.pt --img 1024 --batch 16,逻辑是一样的。
4.3 训练命令与最小可复现配置:关闭数据增强会影响什么
YOLO默认开启马赛克增强、随机翻转、颜色变换等。这些增强对通用目标检测很有效,但对航拍地灾图像不一定全适用。比如随机翻转会把滑坡沟道的走向左右颠倒,语义上问题不大;但马赛克增强会把四张图拼在一起,导致滑坡目标被切割成碎片,这会让小目标变成了碎目标。跑基线时我倾向于保留马赛克,但把它的关闭阈值调低,或者直接在训练到后半程时关闭。
yolo detect train data=data.yaml model=yolov8n.pt imgsz=1024 batch=16 epochs=100 patience=20 close_mosaic=10close_mosaic=10表示最后一个epoch从第10个epoch开始关闭马赛克增强。这是一种常见的训练策略:前期用马赛克拉大样本多样性,后期关掉它让模型适应真实分布。如果你发现训练loss下降很慢,先不要盲目增大学习率,先检查是不是数据标注本身有大量接近图像边缘的框被裁剪了,导致目标只露出局部。
5. 训练流程中的5个必踩坑:从zip解压到模型评估的血泪经验
5.1 解压后发现图片数量不对,或者“明明5619张,却只有5500张能训练”
现象:解压后统计发现jpg有5600多张,但xml或txt只有5400多张,模型训练到一半报错找不到对应标签。 原因:数据集传输时zip损坏,或者部分标注文件因为文件名特殊字符被解压工具跳过;另一种可能是原包本身就有一部分没有目标,属于背景样本,并没有生成标注文件。 解决:先做一次文件名对齐脚本,找出哪些图片缺标注。如果大量缺失且原包没有说明,把这部分图单独放背景目录,不进入训练。如果只是个别缺失,直接把缺失样本移出训练集,不要硬补一个空txt,否则验证集统计会混乱。
5.2 训练刚跑几轮,loss直接飙升或变成NaN
现象:前5个epoch的box_loss和cls_loss快速下降,到第10个epoch突然跳到无穷大。 原因:最常见的是标注框坐标越界,比如某个x_center是1.05,或者width为负数。这类脏数据在划分时没被校验脚本拦住,导致目标框回归目标异常。 解决:返回第3.2节的校验脚本,重点检查center out of [0,1]和non-positive w/h两类报错。我在实际项目里遇到过某张图存在一个标注框宽为0的情况,那行的来源是标注软件在鼠标双击时误写入了一个空框。删除这些可疑的txt行后,重新训练即恢复正常。
5.3 验证集mAP很高,但实际推理时把大片裸土误检成滑坡
现象:在测试集上mAP50能到0.8,但拿到一张全是裸岩、没有滑坡的航拍图,模型输出了四五个高分框。 原因:滑坡和泥石流数据里,背景样本的多样性不足。很多背景图是草地或建筑区,模型没有见过大片裸露岩土。验证集里恰好没有类似背景,所以评估指标没有体现出这个问题。 解决:从原始数据里专门挑出“裸土、山体阴影、道路边坡、采石场”这类难背景图,整理成背景验证集。训练时开启负样本采样,或者在后处理阶段增加置信度阈值。另一个有效手段是给这些误检区域打上标注为背景的小标签数据,重新微调。这个坑属于航拍地灾检测特有,靠通用数据增强解决不了。
5.4 模型对小规模滑坡体漏检率居高不下,尤其是图像边角区域
现象:目标在图像中央时能检出,一旦目标偏离画面中心或贴近边缘,召回率明显下降。 原因:航拍图像尺寸大,目标尺寸小,YOLO在训练时会随机裁剪和缩放。边角目标经过随机裁剪后可能只剩下一半,增强样本和目标边缘被截断的比例过高;另外大多数预训练模型是在自然图像上训练的,对遥感的中心偏置不敏感。 解决:训练时禁用自动裁剪,或者把rect=True设为true,使用矩形推理让整图参与训练。还有一招是瓦片切分:把1920x1080的航拍图切成若干个640x640的小块,重叠50%并复制贴边图块,再用切分后的数据训练。这能直接提升边角小目标的召回率。这个过程会在第6章详细展开。
5.5 使用YOLO预训练模型下载后权重文件却加载失败
现象:运行训练命令时提示下载预训练模型失败,或下载完成后权重文件无法加载,报unexpected key in state_dict。 原因:网络波动导致权重文件不完整,或者当前使用的YOLO版本与预训练权重版本不一致。比如用YOLOv8的代码加载了YOLOv5格式的权重,就会报state_dict不一致。 解决:检查权重文件的大小,yolov8n.pt通常在6MB左右,如果只有几KB说明下载失败。先删除本地缓存,重新下载。如果网络受限,可以挂一个稳定的下载源,或者直接用随机初始化的权重训练。从零训练对5619张的数据集来说难度较大,但也不是不行,损失收敛会慢,最终效果会差一些。调换权重版本后,务必重新生成data.yaml,避免缓存冲突。
6. 验证你的结果:mAP、混淆矩阵和针对航拍数据的进阶调整
6.1 用验证集做一次精确评估:推理脚本和指标解释
训练结束后,第一件事是在验证集上跑一次评估,而不是直接看训练过程的图表。因为训练过程中显示的mAP是验证集在某个epoch的快照,未必代表最终权重表现。
yolo detect val model=runs/detect/train/weights/best.pt data=data.yaml imgsz=1024这条命令会输出mAP50、mAP50-95、precision、recall四个主要指标。只看mAP50是不够的,滑坡检测里我更关注mAP50-95,它对手框位置精度更敏感。泥石流这类目标边界不规则,如果mAP50-95明显低于mAP50,说明预测框位置偏移较大,后处理阶段要考虑用更大iou阈值做非极大值抑制,或者调整anchor尺寸。
6.2 航拍大图适用的瓦片检测与结果合并:把1920x1080切出9个可重叠小图
前文已经多次提到瓦片切分,这是航拍大图检测的进阶技巧。核心思路很简单:检测时把大图切成若干小图,分别推理,再把框合并回原图坐标。
python tools/tile_predict.py --source large_image/ --model runs/detect/train/weights/best.pt --tile-size 640 --overlap 0.2不要纠结于具体脚本文件名,重点是合并时的坐标换算。如果切分时每个瓦片在水平方向重叠20%,那么同一个目标很可能在两个瓦片里同时出现并各产生一个框。合并时先用非极大值抑制把重叠状态的框按置信度过滤,再输出最终结果。
我的习惯是:先在原分辨率大图上直接推理一次,再对瓦片推理一次,对比两者的漏检情况。通常在雅典娜图像上直接推理,模型能出框但不精准;瓦片推理会明显提升小目标召回,而且不会漏掉边角目标。代价是推理时间变长。
数据标注和训练的复杂度不会让模型自动学会一切。滑坡泥石流检测的难点不在网络结构,而在数据质量与大图尺度的矛盾。如果你拿到的数据集里image尺寸差异很大——有些是无人机正射图,有些是倾斜摄影截图——建议先按尺寸分桶,分别做训练和验证,否则模型会被不同尺度间的统计分布干扰。另一个习惯是每个epoch后存一次验证集预测图,而不是只看指标。图片会比数字更快暴露问题。做这类模型,我学到最深一课就是:数据集的“真实感”远比数量重要,五千张干净标注的航拍图能带给你的收益高于两万张随意标注的图。这一版流程跑通后,建议你保留转换脚本、数据划分、data.yaml完整副本,下一次换数据集时能直接复用。希望帮到你。
本文还有配套的精品资源,点击获取