简介:本资源为面向YOLO系列算法学习者的卫星遥感图像目标检测数据集,适合从事遥感图像识别、地理信息分析及深度学习目标检测的开发者与研究人员使用,可直接用于模型训练、验证与测试。压缩包共2000个文件,包含1280个VOC格式xml标注文件与720个YOLO格式txt标注文件,整体约59.44MB,并附带data.yaml配置文件,已划分好训练与验证集,兼容yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流版本。YOLO格式采用类别索引与归一化中心点、宽高坐标,便于直接读取训练;VOC格式则保留原始XML结构,方便转换与复核。两种标注分别存放于独立文件夹,文件名末尾标注部分类别名称,便于按类别检索与管理。目前已有110人学习下载,适合希望快速上手遥感目标检测任务、验证算法效果或进行二次开发的读者参考使用。
1. 遥感图像目标检测为什么总在“小目标”上翻车:1825 张带标签数据集的真实价值
拿到一份卫星遥感图像物体检测数据集,很多人第一反应是直接套 YOLOv8 训练脚本跑一遍,然后看着 mAP 曲线沾沾自喜。但真正做过遥感检测的人都知道,卫星图像和自然图像根本不是一个物种——目标尺度差异极大、方向任意、背景极其复杂,一张 1024×1024 的图里可能同时存在几十米长的飞机和几米宽的汽车。这份 1825 张图像带标签的 YOLO 格式数据集,核心价值不在于“数量大”,而在于它把遥感场景下最典型的几类目标(飞机、舰船、储油罐、车辆等)用统一的 YOLO 标注格式整理好了,省去了从原始影像到可训练数据之间最耗时的清洗和转换环节。如果你正在做遥感目标检测的落地验证,或者想找一个真实场景练手 YOLO 训练全流程,这份数据集能让你把精力集中在模型调优而不是数据整理上。但前提是,你得先搞清楚遥感图像和常规图像在训练策略上的本质差异,否则再好的数据集也救不了你的 mAP。
2. 从压缩包到可训练:YOLO 格式遥感数据集的目录结构与校验方法
2.1 遥感数据集的 YOLO 标注格式到底长什么样
YOLO 格式的标注文件是每张图片对应一个.txt文件,每行代表一个目标,格式为class_id x_center y_center width height,其中坐标全部是相对于图像宽高的归一化值(0~1 之间)。遥感图像的特殊之处在于,目标通常很小,归一化后的宽高可能只有 0.01 甚至更小,这就导致在训练时正样本匹配非常困难。拿到这份 1825 张图像的数据集后,第一件事不是急着训练,而是先确认标注文件和图像文件是否一一对应、类别 ID 是否从 0 开始连续编号、有没有空标注文件混在里面。
常见的目录结构是这样的:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml但很多从公开渠道拿到的遥感数据集并不会预先划分好 train/val/test,而是所有图像和标注混在一起。这时候你需要自己写脚本做划分,同时保证图像和标注同步移动。
2.2 用 Python 脚本完成数据集完整性校验与划分
下面这段脚本做三件事:检查图像和标注是否配对、统计每个类别的目标数量、按 8:1:1 划分训练集验证集测试集。
import os import random import shutil from pathlib import Path from collections import Counter # 数据集根目录,根据实际情况修改 ROOT = Path("dataset") IMG_DIR = ROOT / "images" LBL_DIR = ROOT / "labels" # 收集所有图像文件(常见遥感图像格式) img_exts = {".jpg", ".jpeg", ".png", ".tif", ".tiff"} images = [p for p in IMG_DIR.rglob("*") if p.suffix.lower() in img_exts] paired, missing_label, empty_label = [], [], [] class_counter = Counter() for img_path in images: # 标注文件与图像同名,只是后缀换成 .txt lbl_path = LBL_DIR / img_path.relative_to(IMG_DIR).with_suffix(".txt") if not lbl_path.exists(): missing_label.append(str(img_path)) continue lines = lbl_path.read_text().strip().splitlines() if len(lines) == 0: empty_label.append(str(img_path)) continue for line in lines: cls_id = int(line.split()[0]) class_counter[cls_id] += 1 paired.append((img_path, lbl_path)) print(f"配对成功: {len(paired)}") print(f"缺失标注: {len(missing_label)}") print(f"空标注文件: {len(empty_label)}") print(f"类别分布: {dict(sorted(class_counter.items()))}") # 按 8:1:1 划分 random.seed(42) random.shuffle(paired) n = len(paired) n_train = int(n * 0.8) n_val = int(n * 0.1) splits = { "train": paired[:n_train], "val": paired[n_train:n_train + n_val], "test": paired[n_train + n_val:] } for split_name, items in splits.items(): img_out = ROOT / "split" / "images" / split_name lbl_out = ROOT / "split" / "labels" / split_name img_out.mkdir(parents=True, exist_ok=True) lbl_out.mkdir(parents=True, exist_ok=True) for img_path, lbl_path in items: shutil.copy2(img_path, img_out / img_path.name) shutil.copy2(lbl_path, lbl_out / lbl_path.name) print(f"{split_name}: {len(items)} 张")这段脚本的关键逻辑在于:先做配对检查再划分,避免把没有标注的图像混进训练集导致训练时找不到标签而报错。random.seed(42)保证每次划分结果一致,方便复现。类别统计那一步能帮你快速判断数据集是否存在严重的类别不平衡——遥感数据集里“背景类”往往占据绝对多数,如果某个类别的目标数少于 50 个,训练时基本学不到有效特征,需要考虑过采样或数据增强。
参数方面,ROOT指向解压后的数据集根目录,img_exts集合覆盖了遥感领域常见的 GeoTIFF 和普通图像格式。如果你的数据集里图像和标注不在同一级目录下,需要根据实际结构调整IMG_DIR和LBL_DIR的相对路径。
2.3 data.yaml 的写法与类别名映射
划分完成后,需要生成 YOLO 训练所需的data.yaml:
path: ./split train: images/train val: images/val test: images/test names: 0: plane 1: ship 2: storage_tank 3: vehicle 4: bridge这里names的键必须从 0 开始连续,且与标注文件里的class_id严格对应。遥感数据集常见的坑是类别名用了中文或者包含空格,YOLO 训练时读取 yaml 会直接报编码错误。另外path建议用相对路径,方便整个数据集目录迁移到不同机器上。
3. 用 YOLOv8 在遥感数据集上跑通训练:环境配置与关键参数
3.1 Anaconda 环境配置与 ultralytics 安装
遥感图像训练对显存的要求比常规图像高,因为输入分辨率通常要设得更大(1024 或 1280),否则小目标在特征图上只剩几个像素,检测头根本抓不住。我一般用 Anaconda 建一个独立环境,避免和系统里的其他 CUDA 版本打架。
conda create -n yolo_remote python=3.10 -y conda activate yolo_remote # 安装 PyTorch,根据你的 CUDA 版本选择对应命令 # CUDA 11.8 的情况 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics # 验证安装 yolo checksyolo checks会输出当前环境检测到的 CUDA 版本、GPU 型号和 ultralytics 版本号。如果这里显示 CPU only,说明 PyTorch 装成了 CPU 版本,需要卸载重装。遥感训练没有 GPU 基本跑不动,1024 分辨率下 batch size 设为 8 就需要至少 8GB 显存。
3.2 训练命令与遥感场景下的参数调整
yolo detect train \ data=./split/data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=1024 \ batch=8 \ device=0 \ workers=4 \ patience=30 \ lr0=0.001 \ lrf=0.01 \ mosaic=0.5 \ scale=0.3 \ degrees=0.0 \ fliplr=0.5 \ flipud=0.5 \ project=runs/remote \ name=exp1逐项说明这些参数为什么这样设。imgsz=1024是遥感检测的底线,低于这个值小目标基本消失。batch=8是在 8GB 显存下的保守值,显存够可以加到 16。patience=30表示 30 个 epoch 没有提升就早停,遥感数据集通常 100~150 epoch 就能收敛。lr0=0.001比默认的 0.01 小一个数量级,因为遥感图像预训练权重和实际数据分布差异大,学习率太大会导致损失震荡。
数据增强部分需要特别注意:degrees=0.0关掉旋转增强,因为遥感目标本身就有任意方向,再随机旋转会让模型对方向的建模更加混乱;flipud=0.5打开上下翻转,遥感图像从卫星俯拍,上下翻转不改变目标的语义;mosaic=0.5比默认的 1.0 低,因为 mosaic 四图拼接会让本就很小的目标变得更小,过度使用反而有害。
3.3 训练过程监控与 mAP 曲线解读
训练启动后,runs/remote/exp1/目录下会生成results.csv和一系列可视化图表。重点看三个指标:metrics/mAP50、metrics/mAP50-95和train/box_loss。遥感检测的 mAP50 能到 0.7 以上就算不错,mAP50-95 通常只有 0.4~0.5,因为小目标的 IoU 很难做高。
如果 box_loss 在前 20 个 epoch 下降很慢,说明学习率可能还是偏大或者数据增强太强。如果 mAP 曲线在某个 epoch 突然断崖式下跌,大概率是遇到了标注质量差的样本,需要回头检查那批数据。results.csv可以直接用 pandas 读出来画图:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/remote/exp1/results.csv") df.columns = df.columns.str.strip() fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(df["epoch"], df["train/box_loss"], label="box_loss") axes[0].set_xlabel("epoch") axes[0].set_ylabel("loss") axes[0].legend() axes[1].plot(df["epoch"], df["metrics/mAP50"], label="mAP50") axes[1].plot(df["epoch"], df["metrics/mAP50-95"], label="mAP50-95") axes[1].set_xlabel("epoch") axes[1].set_ylabel("mAP") axes[1].legend() plt.tight_layout() plt.savefig("training_curve.png", dpi=150)这段代码把训练日志里的损失和精度曲线画在一起,方便判断模型是否过拟合。如果 train/box_loss 持续下降但 val/box_loss 开始上升,就是过拟合的典型信号,需要增加数据增强或者减少模型参数量。
4. 遥感检测避坑指南:从标注质量到推理部署的五个血泪教训
4.1 坑一:标注框比目标大一圈,模型学了个寂寞
现象:训练 loss 正常下降,但推理时检测框明显偏大,小目标尤其严重,经常把背景一起框进去。
原因:遥感图像标注时,标注人员习惯性地把目标周围的地物也框进去,导致标注框比实际目标大 20%~30%。YOLO 的回归损失直接学这个框,模型自然输出偏大的检测结果。
解决:用脚本统计所有标注框的宽高分布,如果某个类别的平均宽高明显大于该类别在图像中的实际像素尺寸,就需要重新审视标注。对于已经标注好的数据,可以用labelImg或CVAT逐类抽查,把明显偏大的框收紧。更彻底的做法是换用带旋转框的标注格式(如 DOTA 格式),但那就需要换检测头了。
4.2 坑二:类别 ID 不连续导致训练时索引越界
现象:训练启动时报IndexError: index 5 is out of bounds for dimension 0 with size 5。
原因:标注文件里的 class_id 是 0、1、2、3、5,跳过了 4,但data.yaml里只定义了 5 个类别(0~4)。YOLO 在构建分类头时按 names 的长度分配输出维度,遇到 id=5 直接越界。
解决:写脚本扫描所有标注文件,收集实际出现的 class_id 集合,然后重新映射为从 0 开始的连续整数。映射关系要同步更新到data.yaml的 names 里。这个坑在合并多个来源的遥感数据集时特别常见,因为不同数据集的类别编号体系不一样。
4.3 坑三:图像格式是 16 位 TIFF,YOLO 读进去全是噪声
现象:训练时 loss 一直是 nan,或者可视化出来的训练图像一片灰白,完全看不出目标。
原因:遥感原始影像很多是 16 位或 32 位浮点的 GeoTIFF,像素值范围是 0~65535 甚至更大。YOLO 的数据加载器默认按 8 位图像读取,不做归一化的话像素值直接溢出,做归一化又因为位深不同导致对比度异常。
解决:在训练前统一转成 8 位 PNG 或 JPEG。转换时用 2%~98% 的百分位做线性拉伸,而不是简单除以 255。下面这段代码批量处理:
import numpy as np import cv2 from pathlib import Path def convert_to_8bit(src_path, dst_path): img = cv2.imread(str(src_path), cv2.IMREAD_UNCHANGED) if img is None: return False if img.dtype == np.uint16: # 百分位拉伸,保留主要动态范围 p2, p98 = np.percentile(img, (2, 98)) img = np.clip((img - p2) / (p98 - p2 + 1e-6) * 255, 0, 255).astype(np.uint8) elif img.dtype == np.float32: img = np.clip(img * 255, 0, 255).astype(np.uint8) # 如果是多波段,只取前三个波段 if len(img.shape) == 3 and img.shape[2] > 3: img = img[:, :, :3] cv2.imwrite(str(dst_path), img) return True src_dir = Path("raw_images") dst_dir = Path("dataset/images") dst_dir.mkdir(parents=True, exist_ok=True) for p in src_dir.glob("*.tif"): convert_to_8bit(p, dst_dir / (p.stem + ".png"))百分位拉伸比直接除以最大值更稳,因为遥感图像里经常有极亮的云层或极暗的水体,直接归一化会把有效目标的对比度压没。
4.4 坑四:验证集 mAP 很高但实际推理一塌糊涂
现象:results.csv里 mAP50 到了 0.85,但拿几张新图推理,检测结果惨不忍睹。
原因:验证集和训练集来自同一批图像划分,分布完全一致。如果数据集本身场景单一(比如全是港口区域),模型只是记住了这个场景的背景特征,换一个场景就失效。
解决:划分验证集时按场景或地理区域划分,而不是随机划分。比如按图像来源的卫星或区域分组,确保验证集里的场景在训练集中没出现过。如果数据集本身场景多样性不够,就需要额外收集不同区域的图像做外部验证。
4.5 坑五:推理时置信度阈值设 0.25,小目标全被过滤掉
现象:训练指标正常,推理时大目标能检测到,小目标一个都出不来。
原因:YOLO 默认的置信度阈值是 0.25,但遥感小目标的分类置信度普遍偏低,很多真实目标的置信度在 0.1~0.2 之间。阈值一卡,全没了。
解决:推理时把conf降到 0.05~0.1,同时把iou设为 0.3~0.4 来抑制重叠框。更好的做法是在验证集上画 precision-recall 曲线,找到 F1 分数最大的阈值点,而不是拍脑袋定一个值。
yolo detect predict \ model=runs/remote/exp1/weights/best.pt \ source=test_images/ \ conf=0.08 \ iou=0.35 \ imgsz=1024 \ save=True5. 把遥感检测模型推到实用线:切片推理与类别平衡的进阶技巧
训练出一个 mAP 好看的模型只是第一步,真正落地时你会发现两个绕不开的问题:大图推理时小目标被缩放没了,以及某些类别样本太少导致漏检严重。这两个问题在 1825 张规模的数据集上尤其突出,因为数据量本身不足以让模型学到所有类别的鲁棒特征。
先说切片推理。遥感图像的实际尺寸往往是 4000×4000 甚至更大,直接缩放到 1024 输入模型,小目标在缩放后只剩不到 5 个像素,检测头根本无力回天。我一般用 SAHI(Slicing Aided Hyper Inference)的思路,把大图切成有重叠的小块,每块单独推理后再把结果合并回原图坐标。核心代码如下:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model = AutoDetectionModel.from_pretrained( model_type="yolov8", model_path="runs/remote/exp1/weights/best.pt", confidence_threshold=0.08, device="cuda:0" ) result = get_sliced_prediction( "large_image.tif", detection_model, slice_height=1024, slice_width=1024, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) result.export_visuals(export_dir="sliced_output/")overlap_height_ratio=0.2表示相邻切片有 20% 的重叠区域,防止目标正好落在切片边界上被截断。切片推理的代价是推理时间成倍增加,但小目标的召回率通常能提升 15~25 个百分点,对于遥感场景来说这个 trade-off 完全值得。
再说类别平衡。1825 张图像里,如果某个类别只有几十个目标,模型对这个类别的学习会严重不足。我习惯用两种手段缓解:一是对稀有类别做过采样,在训练时通过copy_paste增强把稀有类别的目标粘贴到其他图像上;二是调整损失函数里分类损失的权重,让稀有类别的分类错误惩罚更大。YOLOv8 本身不直接支持类别权重,但可以通过自定义 dataset 类在__getitem__里对稀有类别样本做重复采样。
最后一个实用技巧是模型集成。用同一份数据训练 3 个不同初始化的 YOLOv8s,推理时把三组检测框做加权框融合(WBF),mAP 通常能再涨 2~3 个点。代价是推理时间三倍,适合对精度要求高、对延迟不敏感的场景。
我自己踩过最深的坑是迷信 mAP 数字,在验证集上刷到 0.9 就以为可以交付了,结果实际部署时发现模型对某个类别的漏检率超过 40%。后来养成习惯:每次训练完必须拿一批完全没参与训练的新图做人工抽检,看每个类别的实际检出情况,而不是只看汇总指标。遥感检测这件事,数据质量比模型结构重要,场景覆盖比数据数量重要,实际抽检比验证集 mAP 重要。希望帮到你。
本文还有配套的精品资源,点击获取