简介:遥感目标检测是计算机视觉的重要方向,其核心挑战在于高空视角下目标尺寸小、排列密集。RSOD作为经典遥感目标检测数据集,以轻量级、格式规范著称,非常适合快速验证检测算法。基于VOC格式的标注可便捷转换为YOLO格式,结合YOLOv8框架能快速完成训练与评估。从数据集结构解析到格式转换,再到YOLOv8训练参数调优,系统梳理了遥感目标检测的完整技术链路,并针对小目标检测与类别不平衡问题给出SAHI切片推理等实用方案,为目标检测入门及工程实践提供了可复用的参考。 做目标检测这些年,跑过的数据集不少,COCO、VOC、Open Images、DOTA都折腾过,但如果有人问我“想快速验证一个检测器在遥感图像上的效果,拿什么数据集最合适”,我大概率会推荐RSOD。这话可能有点武断,但我见过太多人被大型数据集预处理劝退,而RSOD在整个流程上实在太友好了。它足够小,下载、解压、转格式、跑训练,一个下午就能走完全流程;它的目标类别又都是遥感场景里的高频地物——飞机、操场、立交桥、船舶,问题足够真实,不是那种只适合表演的玩具数据集。
这篇文章我会从RSOD数据集的背景、结构讲起,再到下载、格式转换、YOLOv8训练、推理评估,最后聊聊我实际操作中遇到的坑和应对思路。适合刚入门目标检测、想用遥感数据练手的朋友,也适合已经在用YOLO、想了解小目标检测和遥感数据特性的人。很多人第一次接触RSOD是在论文的对比实验里,但论文通常只写一句“we evaluate on RSOD dataset”,很少讲清楚数据内部长什么样,所以这篇文章换个角度,以RSOD为样本,把遥感目标检测的数据准备和训练细节一次说透。
1. RSOD是什么:遥感目标检测里绕不开的经典benchmark
1.1 为什么遥感检测需要专属数据集
遥感目标检测和普通目标检测最大的区别在于视角和尺度。普通数据集里一张图可能就一两个目标,目标在画面中占很大面积;遥感图像则是高空俯拍,地面目标在画幅里通常很小,而且排列密集。比如一个机场的遥感影像里可能有几十架飞机,每一架只占几十个像素。用自然场景训练的模型迁移过来,经常会漏检小目标,因为模型学到的特征尺度完全不匹配。所以遥感领域必须单独准备数据集来驱动模型迭代,RSOD、DOTA这些数据集就是干这个的。
很多人以为目标检测的算法是通用的,换个场景无非是换个数据重新训练。这句话对了一半。算法框架确实通用,但数据分布决定了模型的强项和弱项。RSOD这种遥感数据,第一个要教会模型的就是“从高空往下看,目标可以很小、很密、角度很随意”。你拿COCO预训练模型直接往遥感图上砸,效果往往惨不忍睹,只有在这个场景下重新训练,模型才开始真正适应遥感图像的纹理、背景和目标尺寸。
1.2 RSOD的四个类别与大致规模
RSOD全称是Remote Sensing Object Detection Dataset,一套面向遥感图像目标检测的开源数据集。根据网上公开资料显示,它由国内高校团队整理发布,图像主要采集自Google Earth等遥感影像,覆盖机场、港口、城区、立交桥、运动场等典型场景。类别一共四类:aircraft(飞机)、playground(操场)、overpass(立交桥)、ship(船舶)。
光看这四类,“操场”和“立交桥”似乎不太像遥感领域的高频目标,但恰恰是这两类目标尺度大、结构复杂,和飞机、船舶的小尺度目标形成对照,训练出来的模型能同时兼顾大小目标,这比单纯全是小目标的数据集更有锻炼价值。从公开资料看,各类目标实例数量大致如下:
| 类别 | 实例数量(约) | 典型场景 |
|---|---|---|
| aircraft 飞机 | 4000+ | 机场停机坪、跑道,密集群目标 |
| playground 操场 | 150左右 | 学校、体育场,目标大,数量少 |
| overpass 立交桥 | 170左右 | 城市道路交叉口,形状复杂 |
| ship 船舶 | 4000+ | 港口海面,分布不均匀 |
合计大概有上千张遥感图像、接近一万个标注框。不同渠道下载的版本,统计数据可能略有差异,具体以你实际解压出来的压缩包为准。总之,这是一个非常典型的小规模数据集:单张图像几百KB到几MB,一张普通显卡几分钟就能跑完一个epoch。训练成本低,这是它作为demo和基准测试数据集的最大优势。
1.3 RSOD和DOTA、DIOR、NWPU VHR-10的差异
可能有人会问:既然有DOTA这种大而全的遥感数据集,为什么还要用RSOD?
DOTA确实更大、类别更多、带旋转框,但带来的问题是下载慢、预处理复杂、对显存要求高。对刚开始做遥感目标检测的人来说,一下子上DOTA很容易被数据预处理淹没。NWPU VHR-10和DIOR也是经典遥感数据集,类别更多,但相对RSOD来说,标注格式和文件组织方式没有这么“轻量”。
RSOD的定位更像一块敲门砖:数据量小、标注规范、格式是大家最熟悉的VOC格式,几乎不用花太多时间在数据整理上。你可以在它上面快速跑通一个检测流程、验证算法方向的可行性,然后再切换到DOTA、DIOR这种大规模数据集做正式实验。我自己有个习惯:新模型先用RSOD检测一通,如果模型连RSOD都做不好,那拿到DOTA上大概率也不会好到哪去。
2. 数据集细节:那些官方文档不会细讲的坑
2.1 图像尺寸不统一,输入resize要谨慎
RSOD原始图像尺寸很不统一,有的只有几百像素,有的到几千像素。用YOLO训练时,网络会固定输入尺寸,比如640x640。直接把这批图resize到640,很多小目标信息会被压缩掉。举个例子,一张2000x1500的机场图里,飞机宽度可能只有40个像素,缩放5倍后变成8个像素,模型能看到的特征就非常模糊。
所以训练之前,建议先做一次数据分析:统计训练集图像的分辨率分布、目标宽高比、目标占全图的比例。如果发现目标普遍小于32x32,就要考虑提高输入分辨率,或者在推理时用SAHI这种切片推理方案。RSOD最大的价值之一,就是让你在训练早期就意识到这个问题的存在,而不是等到业务数据上出了大问题才回头补课。
2.2 类别不平衡:操场和立交桥很容易被“无视”
飞机和船舶各有4000多实例,操场和立交桥只有150、170左右,这个悬殊程度在目标检测数据集里算是比较极端的。模型训练的时候,损失函数会被实例数量多的类别主导,操场、立交桥的AP往往低得可怜。这不是模型有问题,而是数据分布决定的。
处理办法通常有几个方向:
- 图像级重采样:在数据加载时提高含操场、立交桥图像的采样权重,让每个epoch里少样本类被看到更多次;
- 复制粘贴增强:把少样本类目标从原图裁剪出来,随机粘贴到其他图像上,并同步生成标注,这个策略在遥感检测里效果不错;
- 类别平衡损失:给少样本类更高的loss权重。
这些都属于锦上添花。我的建议是,先不调任何技巧,直接训练一个baseline,看看各类别AP到底差多少,再来决定要不要处理。别一上来就上复杂方案,否则你根本不知道哪个环节真正起了作用。
2.3 标签质量:训练前一定要做一次体检
RSOD整体标注质量算是不错的,但公开数据集传到手里,谁也不能保证中间没被改过。我接过一次网上下载的RSOD,解压后发现有几张图片和XML文件名对不上,还有个别XML里类名大小写混用(“Ship”和“ship”),甚至有一些标注框坐标超出图像尺寸。这种问题不提前排查,训练过程就会出现莫名其妙的报错,loss曲线也会乱跳。
我建议先写一个校验脚本,检查下面几项:
- 图片文件与XML文件是否一一对应;
- XML里类别名是否都在预期列表内;
- bndbox坐标是否在图片尺寸范围内;
- 是否出现宽度或高度为0的无效框。
这里给出一个简单的Python校验脚本,可以直接根据自己的目录结构改:
import os import xml.etree.ElementTree as ET from PIL import Image annot_path = "annotations" img_path = "images" expected_classes = {"aircraft", "playground", "overpass", "ship"} for xml_file in os.listdir(annot_path): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(annot_path, xml_file)) root = tree.getroot() filename = root.findtext("filename") img_file = os.path.join(img_path, filename) if not os.path.exists(img_file): print(f"[MISSING IMAGE] {filename}") continue with Image.open(img_file) as img: w, h = img.size for obj in root.iter("object"): name = obj.findtext("name") if name not in expected_classes: print(f"[BAD CLASS] {xml_file}: {name}") box = obj.find("bndbox") xmin = int(box.findtext("xmin")) ymin = int(box.findtext("ymin")) xmax = int(box.findtext("xmax")) ymax = int(box.findtext("ymax")) if xmin < 0 or ymin < 0 or xmax > w or ymax > h: print(f"[BAD BOX] {xml_file}: {name} ({xmin},{ymin},{xmax},{ymax})") print("check done")这段代码虽然简单,但能把最典型的三个问题筛查出来。推荐训练前跑一遍,几秒钟的成本,能省掉后面排查问题的一天时间。
2.4 目标密集且多尺度,这正好是遥感检测核心难点
RSOD里飞机密集场景特别典型,停机坪上十几架甚至几十架飞机挨着停,有的还有角度倾斜;船舶场景里,港口区域船挨着船,边界容易重合。这些特点让小目标检测、NMS阈值选择、标注框匹配都比自然图像麻烦不少。换个角度想,这也是RSOD适合练手的原因——它在某种程度上反复提醒你:遥感检测不是“缩小的自然图像检测”,它有自己的独立问题域。模型在这个数据集上学到的,不只是四个类别,还有密集排列下的区分能力。
3. 下载与格式转换:从VOC到YOLO
3.1 下载渠道和注意事项
RSOD数据集在网络上公开渠道不少。GitHub上有不少仓库做过镜像,Kaggle也能找到打包好的版本,国内一些平台也有直接分享的下载链接,搜“RSOD Dataset Download”就能看到。由于链接变化比较快,这里不写死某个具体地址,只提醒两点:优先选择包含原始图像和XML标注的完整版本;下载后先核对文件结构,确认图像和标注都齐全。
如果在Kaggle下载,你通常会看到别人整理好的CSV格式标注,虽然方便,但字段名可能各不相同,我建议还是以VOC XML为准,自己写脚本转格式最稳。别嫌麻烦,格式转换这步自己亲手做一遍,后面遇到任何标注问题你都能更快定位。
3.2 目录结构怎么组织
下载下来的原始包经常是按类别拆开的,比如“aircraft”文件夹里既有图也有XML,“ship”文件夹里也是。直接这样训练也可以,但建议把四类合并到同一套目录下,后面管理起来更省心:
RSOD/ ├── images/ │ ├── 001.jpg │ ├── 002.jpg │ └── ... ├── annotations/ │ ├── 001.xml │ ├── 002.xml │ └── ...如果你的压缩包是每个类别一个子目录,可以用一个简单的脚本把所有jpg和xml复制到同一个目录,重名时加上类别前缀,避免覆盖。处理完之后记得再跑一遍前面的校验脚本,确认文件一一对应。
3.3 VOC转YOLO格式的完整脚本
YOLO训练要求每个图像对应一个txt标注文件,每行基本格式是:class cx cy w h,其中cx、cy是归一化后的中心点坐标,w、h是归一化后的宽高。RSOD的XML是标准VOC格式,转换逻辑很直接:
import os import xml.etree.ElementTree as ET class_names = ["aircraft", "playground", "overpass", "ship"] class2idx = {name: i for i, name in enumerate(class_names)} annot_dir = "annotations" label_dir = "labels" os.makedirs(label_dir, exist_ok=True) for xml_file in os.listdir(annot_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(annot_dir, xml_file)) root = tree.getroot() img_w = int(root.findtext("size/width")) img_h = int(root.findtext("size/height")) lines = [] for obj in root.iter("object"): name = obj.findtext("name") if name not in class2idx: print(f"skip unknown class: {name}") continue cls_id = class2idx[name] box = obj.find("bndbox") xmin = float(box.findtext("xmin")) ymin = float(box.findtext("ymin")) xmax = float(box.findtext("xmax")) ymax = float(box.findtext("ymax")) cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") txt_file = os.path.join(label_dir, xml_file.replace(".xml", ".txt")) with open(txt_file, "w") as f: f.write("\n".join(lines))这里有三个细节要注意。第一,类名统一用小写,映射表里也一律小写,避免“Ship”和“ship”这种大小写问题。第二,有些XML里没有size字段或字段缺失,那就用PIL读取图片宽高来兜底。第三,cx、cy、w、h理论上应该在0到1之间,个别越界框可以通过clip强制夹紧,但最好还是人工看一眼,因为越界往往意味着原始标注有问题。
3.4 训练集/验证集划分的逻辑
分割建议按8:1:1划分为train/val/test,但不要单纯按文件名排序切。RSOD里飞机、船舶的图片多,操场、立交桥的图片少,简单切分很可能让验证集里压根没有操场和立交桥。更合理的做法是:按图片是否包含少样本类来分层抽样,保证验证集和训练集的类别分布大致一致。
实际操作中,可以把全部图片路径按“是否包含操场或立交桥”分成两个子集合,两个子集合各自按比例切分,再合并成最终的train/val。这样虽然不能保证完全均衡,但至少不会出现少样本类完全没进验证集的极端情况。分层切分代码不复杂,用sklearn的train_test_split再带上stratify参数就能做。
4. YOLOv8训练RSOD数据集的完整流程:命令、参数与评估
4.1 安装与数据准备
推荐Python 3.9以上,用conda建一个干净环境,避免和系统Python环境互相污染:
conda create -n rsod python=3.10 conda activate rsod pip install ultralytics pip install sahiYOLOv8用Ultralytics库统一管理,训练、验证、推理都方便。接下来把图片放到images/train、images/val,把txt标注放到对应labels/train、labels/val,注意目录结构和名字不能错。Ultralytics默认会自动去找与图片同名的label文件,如果找不到,训练时会报“unable to find label”之类的错,经验就是目录名千万别自己发明。
建议目录结构:
RSOD/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ └── val/ │ ├── 002.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── ... │ └── val/ │ ├── 002.txt │ └── ...4.2 data.yaml配置
Ultralytics训练时需要一份数据集配置文件,内容很简单:
path: /你的绝对路径/RSOD train: images/train val: images/val names: 0: aircraft 1: playground 2: overpass 3: ship注意path最好写绝对路径。新手最容易在这里翻车,写了相对路径后训练时提示图片路径找不到,实际上就是运行目录和data.yaml里的路径对不上。naval还有一个坑:val列表里不要写测试集,测试集留到最终评估时单独指定或用脚本计算。
4.3 训练参数怎么选
一条基线训练命令:
yolo detect train data=RSOD/data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0几个参数的考虑:
- model=yolov8s.pt:用COCO预训练的YOLOv8s作为初始权重。虽然COCO类别和RSOD完全不同,但预训练权重提供的是底层通用特征,迁移效果远好于从零训练。RSOD这种小数据集从零训练很容易过拟合。
- imgsz=640:比较均衡的输入尺寸。跑通后再试1280,小目标AP一般会明显提升,但显存占用和训练时间也会明显增加。我在RSOD上实测过,1280输入下飞机类AP提升明显,尤其对小目标。
- batch=16:根据显存调整,普通6GB显存跑YOLOv8s没问题,显存小就降到8或4。
- epochs=100:小数据集上100轮足够,再长容易过拟合,训练完可以看曲线决定是否早停。
4.4 看评估指标,而不是只看mAP
训练结束后执行:
yolo detect val model=runs/detect/train/weights/best.pt data=RSOD/data.yaml会输出mAP50、mAP50-95,以及每个类别的AP。这时候重点看类别AP表格。如果aircraft、ship的AP很高但playground、overpass的AP很低,那就是类别不平衡问题暴露了。mAP50被数量多的类拉高,代表不了真实水平。
我实际跑下来的体验是:YOLOv8s在imgsz=640下,RSOD的mAP50通常能到0.75到0.85,不同版本标签数据会有浮动,但playground和overpass的AP可能只有0.3到0.5。想提升这两类,就要针对性做数据增强或者重采样。别看到mAP50有0.8就觉得万事大吉,分开看才知道模型短板在哪。
4.5 推理可视化
训练完可以做一次推理看看效果:
yolo detect predict model=runs/detect/train/weights/best.pt source=test_images/ save=True可视化结果默认保存到runs/detect/predict。如果发现大量漏检,先尝试把置信度阈值调低,默认0.25在密集小目标场景下可能偏保守;如果调阈值还是不行,就需要考虑更大输入尺寸或者切片推理了。另外,推理时可以加一句show_labels=True看类别标签是否有明显错乱,一些类别定义问题在可视化时一眼就能看出来。
5. 遥感检测踩坑实录:小目标、类别不平衡与数据校验
5.1 小目标为什么那么难
RSOD里飞机和船舶很多都是小目标。YOLOv8输出特征图分三层(P3/P4/P5),小目标主要靠P3这种高分辨率特征图来检测,但即使P3层stride为8,原图上一个8x8区域在特征图上也只占1个像素。对于只有十几个像素的小飞机,特征基本是模糊的。增大输入尺寸是最直接的缓解手段,另一个思路是SAHI切片推理,把大图切块分别检测,再合并结果。
5.2 SAHI切片推理的实际效果
SAHI(Slicing Aided Hyper Inference)在做超清遥感图像推理时非常有用。用法不复杂,Ultralytics模型可以被SAHI直接加载:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model = AutoDetectionModel.from_pretrained( model_type="ultralytics", model_path="runs/detect/train/weights/best.pt", confidence_threshold=0.25, image_size=640, ) result = get_sliced_prediction( image="test_images/airport.jpg", detection_model=detection_model, slice_height=512, slice_width=512, overlap_height_ratio=0.2, overlap_width_ratio=0.2, ) result.export_visuals(export_dir="output/")我实测下来的感受是,在目标密集区域,切片推理的召回率比整图推理高不少,代价是推理时间增加。简单任务整图推理就够了,但面对几千像素的大图、或者小目标密集成群的场景,SAHI的提升相当明显。切片重叠率建议设在0.2左右,太低会导致切缝处目标被斩断,太高浪费算力。
5.3 类别不平衡的正确打开方式
前面一直提到操场和立交桥AP低,那你到底该怎么处理?
- 如果目的是做
本文还有配套的精品资源,点击获取