简介:本资源为面向YOLO系列目标检测算法的航空卫星图像数据集,适用于遥感场景下的地物分类与目标检测任务,可支撑森林、公路、作物、河流、住宅、工业、果园、牧场及贫瘠土地等多类别识别模型的训练与验证。数据集已按训练与测试需求划分完毕,并附带data.yaml配置文件,兼容YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10及YOLO11等主流版本,适合算法入门者快速跑通流程,也便于进阶用户开展对比实验。压缩包共2000个文件,其中1230个xml文件与770个txt文件,分别对应VOC格式与YOLO格式标注,YOLO格式采用归一化中心点与宽高坐标,可直接用于模型训练;包体约60.51MB,结构清晰便于检索。目前已有42人学习下载,读者可借此省去数据采集与标注成本,直接投入模型调参与效果验证。
1. 航空卫星图像里的八类地物:这套 1366 张带标签数据集到底能干什么
拿到一份标注好的航空卫星图像数据集,第一反应往往不是兴奋,而是怀疑:1366 张够不够训一个能用的 YOLO 模型?八类地物——森林、公路、作物、河流、住宅、工业、果园、牧场、贫瘠土地——听着像遥感课的作业清单,实际做起来却是一堆边界模糊的语义难题。比如河流和公路在灰度图上都是细长条带,作物和果园在低分辨率下几乎同色,工业区和住宅区只差一个屋顶密度。这套数据集的价值不在于规模,而在于它把遥感场景里最容易混淆的几类地物凑到了一起,逼着你在 YOLO 的框架下认真处理类间相似性和尺度差异。适合谁?做遥感目标检测的算法工程师、想拿真实卫星图练手 YOLO 训练部署的学生、以及需要快速验证某个改进模块在遥感域是否有效的研究者。1366 张不算多,但带标签意味着你可以直接跑通训练、验证、推理全流程,把精力花在调参和改结构上,而不是从零标注。接下来我会按实际落地顺序,把数据检查、格式转换、训练配置、避坑和进阶技巧讲清楚。
2. 从压缩包到 YOLO 可读格式:数据检查与标签转换的完整路径
2.1 先别急着训练,把 1366 张图和标签对齐检查一遍
拿到yolo算法-航空卫星图像数据集-1366张图像带标签-森林-公路-作物-河流-住宅的-工业-果园-牧场-贫瘠的土地.zip这种命名很长的压缩包,第一步不是解压完就train.py,而是做一次数据完整性检查。遥感数据集常见的问题包括:图像和标签文件数量不一致、标签里出现超出图像边界的坐标、某些类别样本极少导致训练时梯度被多数类主导。我一般会先写一个脚本统计每个类别的实例数和图像数,顺便检查标签格式是 YOLO 的归一化xywh还是 VOC 的xmin ymin xmax ymax。
import os from collections import Counter from pathlib import Path # 假设解压后目录结构为 images/ 和 labels/,标签为 YOLO 格式 txt img_dir = Path("dataset/images") lbl_dir = Path("dataset/labels") class_names = ["forest", "road", "crop", "river", "residential", "industrial", "orchard", "pasture", "barren"] class_counter = Counter() img_with_label = 0 missing_label = [] for img_path in img_dir.glob("*.jpg"): lbl_path = lbl_dir / (img_path.stem + ".txt") if not lbl_path.exists(): missing_label.append(img_path.name) continue img_with_label += 1 with open(lbl_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls_id = int(parts[0]) class_counter[class_names[cls_id]] += 1 # 检查坐标是否归一化在 0-1 之间 coords = list(map(float, parts[1:5])) if any(c < 0 or c > 1 for c in coords): print(f"坐标越界: {lbl_path.name} -> {coords}") print(f"有标签的图像数: {img_with_label}") print(f"缺失标签的图像: {len(missing_label)}") for name, cnt in class_counter.most_common(): print(f"{name}: {cnt} 个实例")这段脚本的逻辑很直接:遍历图像目录,找同名 txt 标签,统计每个类别的实例数,同时检查 YOLO 格式要求的归一化坐标是否在 0 到 1 之间。参数上,class_names的顺序必须和标签里的类别 ID 严格对应,遥感数据集经常出现类别 ID 从 0 开始但顺序和你想的不一样的情况。如果发现某个类别实例数不到总数的 1%,比如“贫瘠的土地”可能只有几十个实例,那训练时就要考虑用类别权重或者重采样。缺失标签的图像要么补标,要么直接从训练集剔除,别留着让模型学一堆背景。
2.2 标签格式转换:VOC 转 YOLO 的脚本与四个边界坑
很多航空卫星图像数据集原始标注是 VOC 的 XML 格式,每个目标用xmin, ymin, xmax, ymax表示。YOLO 需要的是归一化的中心点坐标和宽高,转换公式看起来简单,但实际做的时候有四个坑:图像尺寸读取错误、坐标越界截断、类别名到 ID 的映射不一致、以及空标签文件处理。下面这个转换脚本把 VOC 转成 YOLO,同时处理了这些边界情况。
import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image voc_dir = Path("dataset/voc_annotations") out_lbl_dir = Path("dataset/labels") out_lbl_dir.mkdir(parents=True, exist_ok=True) class_map = {"forest": 0, "road": 1, "crop": 2, "river": 3, "residential": 4, "industrial": 5, "orchard": 6, "pasture": 7, "barren": 8} for xml_file in voc_dir.glob("*.xml"): tree = ET.parse(xml_file) root = tree.getroot() img_name = root.find("filename").text img_path = Path("dataset/images") / img_name if not img_path.exists(): print(f"图像不存在: {img_name}") continue with Image.open(img_path) as im: w, h = im.size # 注意 PIL 返回的是 (宽, 高) lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text.strip().lower() if cls_name not in class_map: continue bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 坑1:坐标越界,必须截断到图像范围内 xmin = max(0, min(xmin, w - 1)) xmax = max(0, min(xmax, w - 1)) ymin = max(0, min(ymin, h - 1)) ymax = max(0, min(ymax, h - 1)) if xmax <= xmin or ymax <= ymin: continue # 坑2:无效框直接丢弃 # 坑3:归一化时用浮点除法,别用整除 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h # 坑4:确保归一化后仍在 0-1 之间 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) bw = min(max(bw, 0.0), 1.0) bh = min(max(bh, 0.0), 1.0) lines.append(f"{class_map[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_file = out_lbl_dir / (Path(img_name).stem + ".txt") with open(out_file, "w") as f: f.write("\n".join(lines))逻辑说明:先读 XML 里的图像文件名,用 PIL 打开原图拿到真实宽高,然后对每个目标框做截断、有效性检查、归一化。参数上,class_map必须和训练时的data.yaml里的names顺序完全一致,否则模型学到的类别会错位。四个坑里最隐蔽的是坐标越界——遥感图像标注时经常有目标被图像边缘截断,标注员可能直接标了超出边界的框,不截断的话 YOLO 训练时会出现损失突然变成 NaN 的情况。另外空标签文件要保留,YOLO 会把没有目标的图像当作纯背景样本,这对减少误检有帮助。
3. 用 YOLOv8 在本地跑通航空卫星图像训练:配置、参数与显存控制
3.1 环境搭建与 data.yaml 的五个必填字段
训练之前先把环境弄干净。我一般用 conda 建一个独立环境,装 PyTorch 和 ultralytics,避免和系统里的其他包打架。显卡方面,1366 张 640 分辨率的图,YOLOv8n 或 YOLOv8s 在 8GB 显存的卡上就能跑,batch size 设 16 或 8 都行。如果用的是 V100 或者显存更大的卡,可以上 YOLOv8m,但遥感图像里小目标多,模型太大反而容易过拟合,因为 1366 张的规模撑不起太复杂的网络。
conda create -n yolo_sat python=3.10 -y conda activate yolo_sat pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python pillow装完之后,在数据集根目录建一个data.yaml,这是 YOLO 训练的数据入口。五个必填字段:path是数据集根目录,train和val是训练集和验证集的图像目录(相对 path),nc是类别数,names是类别名列表。遥感数据集常见的问题是验证集和训练集混在一起,导致验证指标虚高。我一般按 8:2 随机划分,并且确保同一区域的不同切片不会同时出现在训练和验证集里,否则模型会“记住”那片区域而不是学会识别地物。
path: /home/user/sat_dataset train: images/train val: images/val nc: 9 names: ["forest", "road", "crop", "river", "residential", "industrial", "orchard", "pasture", "barren"]参数说明:nc必须等于names的长度,多一个少一个都会在训练启动时报错。path用绝对路径最稳,相对路径在不同工作目录下容易翻车。如果数据集里某些类别样本极少,可以在names里保留但训练时用cls_pw参数给类别加权,不过 YOLOv8 默认的分类损失已经有一定的平衡能力,先跑一轮看混淆矩阵再决定要不要调。
3.2 训练命令与关键参数:imgsz、batch、lr0 怎么设
YOLOv8 的训练入口是yolo detect train,一行命令就能跑,但参数设不对就是浪费显卡。下面这条命令是我在 1366 张航空卫星图像上跑通的配置,用的是 YOLOv8s 预训练权重,输入分辨率 640,batch size 16,初始学习率 0.01。
yolo detect train \ model=yolov8s.pt \ data=/home/user/sat_dataset/data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ optimizer=SGD \ patience=30 \ device=0 \ project=runs/sat \ name=yolov8s_1366逻辑说明:model=yolov8s.pt加载 COCO 预训练权重,遥感域和自然图像域差异大,但底层边缘和纹理特征仍然可迁移,比从零训收敛快很多。imgsz=640是 YOLO 的默认值,航空卫星图像里小目标多,如果显存够可以提到 1024,但 1366 张的规模下 640 已经能覆盖大部分地物。batch=16在 8GB 显存上比较稳,如果报 OOM 就降到 8。lr0=0.01配合 SGD 是 YOLO 的经典组合,lrf=0.01让学习率在训练末期降到初始值的 1%,避免最后震荡。patience=30表示验证指标 30 轮不提升就早停,遥感数据集容易过拟合,早停能省不少时间。
训练启动后重点看三个输出:box_loss和cls_loss是否稳定下降、mAP50在第几轮开始 plateau、以及混淆矩阵里哪些类别互相误判。如果cls_loss一直不降,大概率是标签格式有问题或者类别 ID 映射错了。如果mAP50在 50 轮左右就卡住,可以试试降低学习率或者加数据增强。
3.3 显存不够时的三个降级方案
不是每个人都有 V100,8GB 甚至 6GB 显存跑 YOLOv8 训练很常见。显存不够的报错通常是CUDA out of memory,这时候有三个降级方案,按优先级排:先降 batch size,再降 imgsz,最后换更小的模型。batch 从 16 降到 8 通常能省一半显存,但 batch 太小会让 BN 层的统计量不稳定,训练时 loss 波动大。imgsz 从 640 降到 512 或 416 也能省显存,但小目标会变得更难检测,遥感图像里的公路和河流本身就是细长条,降分辨率要谨慎。换模型是最彻底的方案,YOLOv8n 的参数量只有 YOLOv8s 的三分之一左右,在 1366 张数据上精度差距可能只有 2-3 个点,但速度翻倍。
# 方案一:降 batch yolo detect train model=yolov8s.pt data=data.yaml batch=8 imgsz=640 ... # 方案二:降 imgsz yolo detect train model=yolov8s.pt data=data.yaml batch=16 imgsz=512 ... # 方案三:换 nano 模型 yolo detect train model=yolov8n.pt data=data.yaml batch=16 imgsz=640 ...还有一个容易被忽略的点:workers参数。默认是 8,在 Windows 上或者磁盘 IO 慢的机器上设太高会导致数据加载卡住,训练速度反而下降。我一般设workers=4,如果看到 GPU 利用率忽高忽低,再往下调。
4. 遥感 YOLO 训练避坑:从 BN 崩溃到混淆矩阵不唯一的排查记录
4.1 训练中 BN 层崩溃导致 loss 变 NaN
现象:训练到第 20 多轮,box_loss突然从 0.8 跳到 NaN,之后所有输出都是 NaN,模型直接废掉。原因:遥感图像里有些切片几乎全是同色地物,比如大片森林或者贫瘠土地,一个 batch 里如果连续几张都是低纹理图像,BN 层算出来的方差接近零,除零之后就炸了。解决:把batch调大一点让统计量更稳定,或者在data.yaml里开启rect=True让同一 batch 内的图像尺寸一致,减少 padding 带来的干扰。更彻底的办法是换optimizer=AdamW并加warmup_epochs=5,让前几轮学习率慢慢升上去,给 BN 层时间适应。
4.2 混淆矩阵总合不唯一:验证集标签有重复框
现象:跑完验证,混淆矩阵里某些类别的总数和实际标签数对不上,比如“公路”标签有 300 个,但矩阵里只统计到 280 个。原因:验证集的标签文件里有重复框,同一个目标被标了两次,YOLO 在计算混淆矩阵时会做 NMS,重复框被合并了,导致统计数偏少。解决:写个脚本检查标签文件里是否有完全相同的行,有的话去重。另外检查是否有类别 ID 超出nc范围的情况,这种标签会被直接忽略。
4.3 河流和公路互相误检:类间相似性的处理
现象:训练完看推理结果,模型把一些细长的公路段标成了河流,或者反过来。原因:在低分辨率卫星图上,公路和河流都是细长条带,颜色也接近,模型很难从纹理上区分。解决:一是在数据增强里加hsv_h和hsv_s的扰动,让模型对颜色变化更鲁棒;二是在训练时对这两类加更高的分类损失权重;三是如果允许改结构,可以在 backbone 后面加一个轻量的注意力模块,让模型更关注形状而不是颜色。我试过最有效的办法其实是后处理:对“河流”和“公路”的检测框做一次形状分析,河流的宽长比通常更小且边缘更不规则。
4.4 小目标漏检严重:果园和住宅的尺度问题
现象:mAP50整体有 0.7 左右,但“果园”和“住宅”的召回率明显偏低,很多小目标没检出来。原因:果园在卫星图上是密集的小树冠,住宅是密集的小屋顶,两者在 640 分辨率下都只有十几个像素宽,YOLO 的 P3 特征图下采样 8 倍后,这些小目标的信息几乎丢光了。解决:把imgsz提到 1024,或者用 YOLOv8 的p2版本(如果有的话),让浅层特征图参与检测。另一个办法是切片推理:把大图切成 512x512 的小块分别检测,再把结果拼回去,但这样会增加推理时间。
4.5 验证集 mAP 虚高:训练集和验证集来自同一区域
现象:验证集mAP50到了 0.85,但拿另一片区域的卫星图测试时掉到 0.5。原因:划分数据集时按随机切分,同一片区域的相邻切片同时进了训练集和验证集,模型记住了那片区域的地物分布,换一片区域就失效。解决:按地理区域划分,比如用经纬度网格,确保验证集的区域在训练集里完全没出现过。如果数据集本身没有地理信息,至少按图像文件名里的区域编号分组划分。
5. 让 1366 张数据发挥更大价值:切片推理与类别加权的实战技巧
1366 张带标签的航空卫星图像,说多不多,说少也不少。如果直接整图训练,小目标漏检和类间混淆是两个绕不过去的坎。我自己的习惯是先把大图切成重叠的小块,用切片推理的方式训练和验证,这样每个小目标在切片里占的像素比例更大,模型更容易学到判别性特征。具体做法:用 512x512 的窗口,步长 256,对每张原图做切片,同时把标签框也映射到切片坐标系里。切完之后训练样本数能翻好几倍,但要注意切片边缘的目标会被截断,需要在映射时做裁剪和有效性检查。
import cv2 import numpy as np from pathlib import Path def slice_image(img, labels, slice_size=512, stride=256): """将大图切成重叠小块,返回切片和对应的 YOLO 标签""" h, w = img.shape[:2] slices = [] for y in range(0, h, stride): for x in range(0, w, stride): x2 = min(x + slice_size, w) y2 = min(y + slice_size, h) x1 = max(0, x2 - slice_size) y1 = max(0, y2 - slice_size) patch = img[y1:y2, x1:x2] patch_labels = [] for cls, cx, cy, bw, bh in labels: # 将归一化坐标转回原图绝对坐标 abs_cx = cx * w abs_cy = cy * h abs_bw = bw * w abs_bh = bh * h # 检查目标中心是否在切片内 if x1 <= abs_cx < x2 and y1 <= abs_cy < y2: new_cx = (abs_cx - x1) / (x2 - x1) new_cy = (abs_cy - y1) / (y2 - y1) new_bw = abs_bw / (x2 - x1) new_bh = abs_bh / (y2 - y1) # 裁剪到 0-1 new_cx = min(max(new_cx, 0), 1) new_cy = min(max(new_cy, 0), 1) new_bw = min(max(new_bw, 0), 1) new_bh = min(max(new_bh, 0), 1) patch_labels.append((cls, new_cx, new_cy, new_bw, new_bh)) if patch_labels: slices.append((patch, patch_labels)) return slices这段代码的逻辑是:按步长滑动窗口,对每个切片检查原图标签框的中心点是否落在切片内,如果在就重新计算归一化坐标。参数上,slice_size和stride需要根据目标大小调,果园和住宅这种小目标多的话,slice_size可以降到 320,stride设成slice_size的一半保证重叠。切片之后训练样本数会增加,但要注意去重,同一个目标可能出现在多个切片里,训练时会被重复学习,可以用 NMS 在标签层面做一次合并。
另一个技巧是类别加权。遥感数据集里“贫瘠的土地”和“牧场”通常样本很少,训练时模型会偏向多数类。YOLOv8 的cls_pw参数可以给每个类别的分类损失加权重,值越大权重越高。我一般先跑一轮默认配置,看混淆矩阵里哪些类别的召回率明显低,然后给那些类别设 1.5 到 2.0 的权重。但别设太高,否则模型会疯狂预测少数类,误检率飙升。
最后说一个验证技巧:不要只看mAP50,遥感场景下mAP50-95更能反映模型对框位置的精度。如果mAP50高但mAP50-95低,说明模型能找到目标但框不准,这时候可以调box损失的权重或者检查标签框的质量。我自己的习惯是每跑完一轮训练,把验证集的预测结果可视化出来,随机抽 20 张图看误检和漏检的模式,比盯着数字有用得多。希望帮到你。
本文还有配套的精品资源,点击获取