
简介面向目标检测学习与遥感图像分析场景这份YOLO卫星遥感多类别检测数据集内置1000张真实场景下的高质量遥感图片图片由LabelImg工具完成标注框体质量较高标签同时提供VOCxml、COCOjson和YOLOtxt三种格式能够直接导入YOLO系列及其他常见检测框架使用。资源包共2000个文件主体为1000个xml标注文件与990个txt标签文件另有6个HTML教程文档、3个Python划分脚本和1个YAML配置压缩包整体114.64MB目录里图片与各格式标签分文件夹存放便于调用。额外附赠YOLO环境搭建含Linux/Windows、训练案例教程以及可自定义的划分脚本能按需求生成训练集、验证集、测试集避免手动整理标签的麻烦。目前已有635人学习下载尤其适合遥感目标识别入门者、高校课程设计或需要快速启动算法实验的研究人员。1. 卫星遥感多类别检测这份数据集到底能帮你少走多少弯路卫星遥感里做目标检测最烦的不是模型而是数据。公开遥感数据集要么只有单一类别要么标注格式只有一种想统一到 YOLO 训练链路上得自己写格式转换脚本和划分脚本光这一套下来一两天就进去了。当看到这个标题——“YOLO卫星遥感多类别检测数据集(含1000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar”我第一反应是它把最耗时的部分提前封装好了。1000张图不算多但配合三种格式标签和训练教程足够用来做算法预研、迁移学习也适合刚接触遥感目标检测的人快速跑通流程。这篇就按我拿到这类数据集后的实际做法从格式验证、标签转换、数据划分到训练踩坑完整过一遍。2. 拿到压缩包先别解压训练三分钟核对文件、标签与坐标系很多人拿到 rar 第一件事就是解压、扔进训练脚本、开跑然后翻车。这里我先花十分钟做数据核对顺序是看目录结构、检查图片与标签对应关系、抽查 VO C和 COCO 标签的坐标体系。别小看这几步能挡掉后面 80% 的“训练 loss 不降”“mAP 为 0”的玄学问题。2.1 解压后先看目录树三种格式的典型组织方式解压前先确认有没有 Linux 下的 unrar没有就用7z它同时处理 rar 和中文文件名。解压后不要急着跑代码先用两层目录树看清结构。mkdir -p satellite_dataset 7z x YOLO卫星遥感多类别检测数据集.rar -osatellite_dataset cd satellite_dataset find . -maxdepth 2 -type d逻辑说明7z x的-o指定输出目录Linux 下比 unrar 更不容易在中文文件名上出错。find -maxdepth 2只看两层目录足以判断是 images/labels 平铺还是 Imageset、Annotations 分级。常见三种格式的组织方式如下VOC 是 JPEGImages Annotations ImageSetsCOCO 是 images annotations/instances_xxx.jsonYOLO 是 images labels 两个平铺目录。这个包说“对应 voc、coco 和 yolo 三种格式标签”很可能解压后出现多个标签目录先确认哪个对应哪个避免后面拿错路径。2.2 校验 YOLO 标签每行五个数坐标必须落在 01YOLO 训练只吃纯文本的 txt 标签每行格式是class_id x_center y_center width height全部归一化到 01。我一般直接用 Python 脚本把整份 YOLO 标签扫一遍比肉眼靠谱。import os from PIL import Image img_dir images lab_dir labels class_names [aircraft, ship, vehicle] # 按数据集提供的 classes.txt 修改 bad_files [] for fname in os.listdir(img_dir): stem os.path.splitext(fname)[0] img_path os.path.join(img_dir, fname) lab_path os.path.join(lab_dir, stem .txt) if not os.path.exists(lab_path): bad_files.append(f{fname} - 缺少标签) continue try: im Image.open(img_path) im.verify() except Exception as e: bad_files.append(f{fname} - 图像损坏:{e}) continue with open(lab_path) as f: for line in f: parts line.split() if len(parts) ! 5: bad_files.append(f{lab_path} - 行格式不对) break cls int(parts[0]) x, y, w, h map(float, parts[1:]) if cls len(class_names): bad_files.append(f{lab_path} - 类别ID {cls} 超出范围) break if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_files.append(f{lab_path} - 归一化坐标越界) break print(问题文件:, bad_files if bad_files else 全部通过)逻辑说明Image.verify()只解析文件头不加载像素速度很快几百张图压测没问题。校验项目就四样标签是否缺失、每行是否五个数、类别 ID 是否越界、归一化坐标是否在 01。其中“归一化坐标越界”最常见原因多半是格式转换时除错了宽高比如把 xmin 除成了 height导致算出来的 x 大于 1。2.3 抽查 VOC 和 COCO 标签单位系统一致才敢互转VOC 的 XML 里是xmin/ymin/xmax/ymax像素坐标COCO 的 JSON 里是bbox: [x, y, width, height]像素坐标YOLO 是归一化坐标。三者单位混着看很容易出幻觉。我的做法是挑同一张图把一个目标在三种格式里的坐标打印出来手动比对。import xml.etree.ElementTree as ET import json # 打印同一目标在 VOC 中的坐标 xml_file Annotations/0001.xml tree ET.parse(xml_file) obj tree.getroot().find(object) bnd obj.find(bndbox) print(VOC:, bnd.find(xmin).text, bnd.find(ymin).text, bnd.find(xmax).text, bnd.find(ymax).text) # 打印 COCO 中的第一个 bbox coco_file annotations/instances_train.json with open(coco_file) as f: data json.load(f) ann data[annotations][0] print(COCO bbox:, ann[bbox]) # YOLO 标签直接看 txt with open(labels/0001.txt) as f: print(YOLO:, f.readline())逻辑说明VOC 里 xmax 减 xmin 就是框宽度COCO 的 bbox 第三位直接给宽度。比对时重点看同一个目标换算后的数值是否对得上比如 YOLO 的 x_center 乘回图片宽度应该等于 VOC 里 (xminxmax)/2。如果差了一个像素还能忍如果系统性偏移几个像素说明标注工具里坐标从 1 开始而转换脚本按 0 处理这种偏移对小目标影响非常大。提示这个核对脚本在任何格式转换前后都值得跑一遍。我见过一份数据集 YOLO 标签看起来正常实际统一少了一个类别位导致整个训练过程类别错乱白跑 100 轮。3. 三种格式标签互转VOC、COCO、YOLO 的转换脚本与四个边界坑这个包虽然三种格式都给全了但你不一定总拿到这么齐的。更多时候手里的数据集只有一种标注或者需要用 mmdetection 读 COCO、用 YOLO 训练就必须自己写转换脚本。记住一个核心坐标单位统一为归一化类别 ID 统一从 0 开始先把这条路走通后面换任何框架都不慌。3.1 VOC 转 YOLO解析 XML中心点归一化VOC XML 记录的是绝对像素框转 YOLO 要先把左上右下转为中心点加宽高再分别除以图片宽高。注意 xmin 这类值在不少标注工具里从 1 开始强烈建议先减 1 再算。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): 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) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: continue cls_id class_map[name] bndbox obj.find(bndbox) # 统一减 1转成 0 基坐标 xmin float(bndbox.find(xmin).text) - 1 ymin float(bndbox.find(ymin).text) - 1 xmax float(bndbox.find(xmax).text) - 1 ymax float(bndbox.find(ymax).text) - 1 x (xmin xmax) / 2 / img_w y (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x:.6f} {y:.6f} {w:.6f} {h:.6f}) yolo_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, yolo_name), w) as f: f.write(\n.join(lines) \n)逻辑说明class_map是一个从目标名到整数 ID 的字典比如{aircraft: 0, ship: 1}。这里最容易踩的坑是括号位置写错把 w 写成(xmax - xmin 1) / img_w。有些实现为了补偿边界会加一但如果你的标注严格闭合加一反而造成 1 像素偏差。我的建议是统一不加一先转出来再与原始框叠图对比。3.2 COCO 转 YOLO跳过 iscrowd用实际图片尺寸COCO JSON 的 bbox 是[x, y, width, height]左上角是像素坐标转 YOLO 的逻辑和 VOC 类似但有两个额外的坑iscrowd的标注要跳过还有 JSON 里 images 字段的 width/height 不一定和图片文件一致转的时候最好直接读图片实际尺寸。import json import os from PIL import Image def coco_to_yolo(json_path, img_dir, out_dir, class_map): with open(json_path) as f: data json.load(f) img_info {im[id]: im[file_name] for im in data[images]} img_labels {im[id]: [] for im in data[images]} for ann in data[annotations]: if ann.get(iscrowd, 0) 1: continue img_id ann[image_id] cat_id ann[category_id] if cat_id not in class_map: continue x, y, w, h ann[bbox] file_name img_info[img_id] img_path os.path.join(img_dir, file_name) img_w, img_h Image.open(img_path).size # 以实际文件尺寸为准 cx (x w / 2) / img_w cy (y h / 2) / img_h nw w / img_w nh h / img_h img_labels[img_id].append(f{class_map[cat_id]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) for img_id, lines in img_labels.items(): stem os.path.splitext(img_info[img_id])[0] with open(os.path.join(out_dir, stem .txt), w) as f: f.write(\n.join(lines) \n)逻辑说明这里没有用 JSON 里的 image width/height而是用Image.open拿真实尺寸。原因是我遇到过一份 COCO 数据集缩略图和原图混在一起JSON 里记录的是缩略图尺寸直接按它归一化转出的坐标全部偏掉。这个脚本只处理矩形框没有考虑 polygon 分割纯检测任务够用。如果你的数据集里有多边形标签且目标是不规则形状需要先算最小外接矩形再转。3.3 YOLO 转 VOC/COCO反向转换时浮点数精度怎么处理虽然这个包三种格式齐了但自己会反向转换才能把训练后的预测结果落回 COCO 评测。YOLO 的归一化坐标转回像素框时需要乘回图片宽高切记四舍五入成整数不然 XML 里的浮点会污染后续工具。def yolo_to_voc_line(line, img_w, img_h, class_names): cls_id, x_norm, y_norm, w_norm, h_norm line.split() cx float(x_norm) * img_w cy float(y_norm) * img_h w float(w_norm) * img_w h float(h_norm) * img_h xmin int(round(cx - w / 2)) ymin int(round(cy - h / 2)) xmax int(round(cx w / 2)) ymax int(round(cy h / 2)) return class_names[int(cls_id)], xmin, ymin, xmax, ymax逻辑说明round的银行家舍入规则在 Python 里有点反直觉比如round(2.5)得 2所以如果你的坐标恰好碰在 .5建议用int(x 0.5)代替。另外反向转换后必须做边界裁剪xmin 小于 0 或 xmax 大于图片宽度时直接 clip否则 VOC 评测工具会报错。这条我吃过亏转换脚本一次能转几千个框个别越界很难一眼看出来。3.4 四个边界坑类别 ID 错位、坐标基、单位、图片尺寸第一个坑是类别 ID 错位。YOLO 的 ID 从 0 开始COCO 不一定比如 COCO 的原始类别 ID 从 1 开始且不连续转换时必须做映射否则船变成飞机的闹剧就来了。第二个坑是坐标基0 基还是 1 基必须统一。第三个坑是单位VOC 和 COCO 的像素坐标都要除以图片宽高不能混除。第四个坑是图片尺寸最保险的是转之前从图片文件读取真实宽高而不是信任 JSON 或 XML 里的 size 字段。这四个坑对应到代码里就是三处除法、一个加一、一个映射表检查这些点就能避免 90% 的转换问题。4. 划分脚本怎么写7:2:1 背后还有场景泄漏这层坑数据集划分看似简单三行代码随机切一下就行但遥感数据偏偏容易在划分上翻车。同一个大图切出来的两个小 patch在内容上高度重叠如果一个进了训练集一个进了验证集验证指标会虚高得离谱。这个标题里带的“划分脚本”正好切中这个需求我这里给出一套我常用的稳健方案。4.1 先定比例和随机种子8:1:1 还是 7:2:1比例选择取决于数据量。1000 张图做多类别检测建议 7:2:1 或 8:1:1。验证集至少要有 100 张以上太少了评估结果抖动大。另一个关键点是随机种子。有人图省事不设种子每次划分结果不同实验对比就失去意义。我习惯在脚本最前面固定seed 42保证任何人重新跑都能得到同一份划分。import os import random from sklearn.model_selection import train_test_split img_dir images seed 42 random.seed(seed) images [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png, .jpeg))] images.sort() train_val, test train_test_split(images, test_size0.1, random_stateseed) train, val train_test_split(train_val, test_size1/9, random_stateseed) def write_list(data, path): with open(path, w) as f: for name in data: f.write(f{name}\n) write_list(train, train.txt) write_list(val, val.txt) write_list(test, test.txt)逻辑说明test_size1/9表示在 train_val 的总量里留 1/9 做验证这样 val 占整体 10%train 占 80%test 占 10%。注意train_test_split默认会打乱数据random_state保证打乱顺序一致images.sort()是为了在有多个标注版本的时候保持文件顺序稳定。写出的 txt 里只用文件名实际路径在训练时拼接这样迁移到别的目录结构不至于整套重来。4.2 分层采样确保每个类别都出现在三个集合里随机划分最大的隐患是小类别某张图恰好没被分进训练集导致模型完全没学过那个类别。解决办法是分层采样按每条样本里最稀有的类别作为层字段。import os import numpy as np from sklearn.model_selection import StratifiedShuffleSplit def image_rare_class(img_name, labels_dir): stem os.path.splitext(img_name)[0] lab_path os.path.join(labels_dir, stem .txt) if not os.path.exists(lab_path): return None classes_seen set() with open(lab_path) as f: for line in f: classes_seen.add(int(line.split()[0])) return min(classes_seen) if classes_seen else None files os.listdir(images) labels_dir labels strata [image_rare_class(f, labels_dir) for f in files] sss StratifiedShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(sss.split(files, groupsstrata))逻辑说明这段用“当前图片中最小的类别 ID”作为分层键整体效果是让含有稀有类别的图片在各集合中占比接近。更严格的做法是计算每张图片所有类别向量用多标签分层采样比如iterative-stratification库但对 1000 张的规模优先保障稀有类别就够了。如果某个类别整包只有 5 张图分层也救不了后续要靠类别加权或再加数据。4.3 场景级划分遥感切片的防泄漏方案遥感检测数据集的经典坑一张 10000x10000 的大图切了 400 个小 patch如果所有 patch 都丢进去随机划分那训练集和验证集里一定有大量来自同一张大图的 patch。这些 patch 重叠区域大、光照和背景一致验证时模型相当于“开卷考试”。正确做法是先给每个 patch 记一个 scene_id再按 scene 分组划分。from sklearn.model_selection import GroupShuffleSplit # scene_dict 形如 {patch_001.jpg: scene_04, patch_002.jpg: scene_04} groups [scene_dict[name] for name in files] gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(files, groupsgroups))逻辑说明GroupShuffleSplit保证同一个 scene_id 的所以图片只会落在同一个集合里。如果你的数据集没有提供 scene_id可以根据图片文件名前缀推断比如LC81180442014116_1000_1000_xxx.jpg前面的经纬度或编号通常就是场景标识。这一条不做后续训练出来的 val mAP 即使再高也不可信。4.4 划分脚本的常见错误自查第一忘设 random_state导致实验不可复现。第二划分只动了 images没动 labels结果训练时发现labels/train里没有对应 txt。第三有人用split命令按行切 txt把同一张图的 image 和 label 切到了不同位置这种低级错误跑出来的训练结果毫无意义。我的自查顺序是写列表后先wc -l看三个集合数量再随机抽三张图确认它们在 images 和 labels 下都存在且内容非空。提示划分脚本和格式转换脚本一样要放进项目仓库不要只在命令行里跑一次。以后数据增删、类别变化重跑划分时能保持同样的随机种子和逻辑。5. YOLO 训练教程从 data.yaml 到最佳权重参数这样调前面数据准备好了训练步骤反而是最“模板化”的。但参数怎么设遥感场景和普通街景差别很大。这里我以 YOLOv8 为例讲训练流程因为当前生态里它最通用命令行和 Python API 都成熟。训练完你得到的是能直接做遥感推理的权重而不是一个只能在特定框架里跑的模型。5.1 安装环境与模型选型pip install ultralytics新版 ultralytics 会同时装好 PyTorch 和依赖在带 NVIDIA GPU 的机器上训练前先确认 CUDA 可用python -c import torch; print(torch.cuda.is_available())输出True再往下走。模型型号选择上遥感多类别小目标检测最少从yolov8s起步显存够就上m。n参数量小但小目标召回率明显偏低l和x对 1000 张这种量级容易过拟合。如果没有 GPU也可以先跑n走通流程但最终部署还是要回到 GPU 或 TensorRT。5.2 目录重组与 data.yaml 编写训练前先把数据整理成 YOLO 默认的目录结构。如果你已经有images和labels平铺文件夹就按下面的方式重组# 进入数据集根目录 mkdir -p images/train images/val labels/train labels/val # 用之前划分的 train.txt 挪文件 xargs -a train.txt -I{} mv images/{} images/train/ xargs -a val.txt -I{} mv labels/{} labels/val/实际上更常见的是用划分脚本直接生成目标路径列表。data.yaml 是关键配置注意names列表顺序必须和标签里的 class_id 完全对应。path: /workspace/satellite_dataset train: images/train val: images/val test: images/test nc: 10 names: 0: aircraft 1: ship 2: vehicle 3: harbor 4: oil_tank 5: bridge 6: storage_tank 7: basketball_court 8: overpass 9: airplane逻辑说明nc是类别总数names索引对应标签 ID。如果这里写错训练过程不会立刻爆错但验证结果会出现类别错位。别问我为什么知道我第一次把第 1 类和第 2 类写反跑了十几个小时才发现。还有path建议写绝对路径避免相对路径在不同终端下解析不一致。5.3 训练命令关键参数按遥感场景调整yolo detect train \ datadata.yaml \ modelyolov8m.pt \ epochs100 \ imgsz1280 \ batch-1 \ workers4 \ patience20 \ saveTrue \ projectrun_satellite \ nameexp1参数说明imgsz是四个参数里最影响遥感效果的一个。640 对小目标来说太粗很多目标在 640 分辨率下只有 8 到 12 像素建议 1280。代价是显存占用翻倍batch-1让程序根据显存自动选最大 batch避免 OOM。patience20表示验证指标 20 个 epoch 不提升就早停防止过拟合。epochs100对 1000 张图完全够我甚至见过 60 轮就收敛的案例。首次训练不建议直接关数据增强但如果你发现小目标被 mosaic 切碎可以在训练中后段把增强逐步关掉yolo detect train ... mosaic0.0 copy_paste0.0逻辑说明mosaic在最后几个 epoch 关闭是社区常见的 trick目的是让模型在正常构图下微调边界框精度。遥感目标大多小且密集mosaic 前中期能提高泛化后期反而引入定位偏差。5.4 训练过程中盯什么别只盯 train loss训练开始后终端日志和run_satellite/exp1/results.png是核心观察对象。重点关注val/box_loss、val/cls_loss不要上涨太早以及metrics/mAP50(B)有没有稳定爬升。train loss 下降到 1 附近甚至更低不代表 val 好过拟合是遥感小数据集最容易出现的问题。另一个参数lr0默认 0.01如果你从自己的预训练权重继续训练而不是官方权重建议调低到 0.001否则前期容易把已有权重破坏。5.5 训练完的产物best.pt 和 last.pt 怎么用训练结束会生成best.pt和last.pt前者按验证集指标选最优后者是最后一轮权重。部署优先用best.pt。验证时把best.pt拖进yolo predict快速看一眼效果yolo predict modelrun_satellite/exp1/weights/best.pt sourceimages/test saveTrue如果发现大量漏检先看推理阈值。conf0.25是默认检测置信度遥感小目标置信度普遍偏低调成conf0.1试试。还要注意推理尺寸和训练尺寸一致训练用 1280推理就别用 640否则目标尺度失配。6. 避坑与验证遥感数据集训练最常见的 5 个问题训练到这一步流程已经通了但真正拉开差距的是验证和排错。这里把我在类似数据集上踩过的坑列出来每一条都是现象、原因、解决三段式直接照着排查。6.1 验证集 mAP 很高测试集一塌糊涂现象val mAP 0.85test mAP 只有 0.3。原因划分时没按场景隔离同一个大图切片同时进了训练集和验证集等于开卷考试。解决用GroupShuffleSplit按 scene_id 重划分重训练。6.2 小目标完全漏检现象车辆、油罐这类小目标在预测框里一个都没有。原因默认推理尺寸 640 下小目标在深层特征图上只剩个把像素特征信息耗尽。解决训练和推理都上imgsz1280还不行就做切片推理——把大图切成 512 或 640 的小图分别预测再拼回坐标这一步能把小目标召回拉回一大截。6.3 训练时提示 corrupt label或跑着跑着精度中断现象log 里出现corrupt label警告训练进程大幅变慢。原因标签坐标越界比如 x w 超过了 1.05。解决跑第二章的校验脚本把越界行要么 clip 到[0,1]要么直接删掉这行。优先级是宁丢一框不炸全局。6.4 类别错乱船被识别成飞机现象预测框位置对但类别全乱。原因data.yaml的names顺序和转换脚本的class_map不一致也可能是 COCO 原始类别 ID 不连续转换时没做映射。解决用一份classes.txt作为唯一真源格式转换脚本、data.yaml、划分脚本都从它读取类别顺序不让任何人手写列表。6.5 中文路径导致读不到文件现象Windows 解压时好好的传到 Linux 服务器训练直接 file not found。原因压缩包里中文文件名在 Linux 下解压后编码变成乱码训练脚本拿着乱码路径去读当然失败。解决解压后第一时间统一重命名为英文比如rename s/[\x80-\xff]//g *或者干脆解压后find一遍看有没有乱码字符。这五条里我踩得最深的是第一条和第四条。现在每次拿到数据集第一件事跑校验第二件事按 scene 划分布局第三件事核对类别表最后才敢启动训练。流程走顺之后1000 张图的数据集足够跑出可用的基线后面再往模型结构或数据增强上做改进。这一套验证习惯帮你省下的不只是时间还有反复翻车之后对数据的信心。希望帮到你。本文还有配套的精品资源点击获取