☰
飞机卫星图3数据集:从YOLO小目标训练到遥感检测实战
2026/10/7 13:50:50 网站建设 项目流程

简介:这份资源是面向目标检测与遥感图像分析的人工智能数据集,以卫星图中的飞机为唯一检测目标,类别单一且图像分辨率高,适合初学者熟悉单类别检测流程,也适合研究者验证模型在遥感场景下的识别能力。压缩包内共2000余个文件,含1000张1024×1024像素JPG彩色图像、1000个XML标注文件及1个info.txt元数据说明,整体约291.76MB;XML标注集中在annotations文件夹,可直接配合YOLO、SSD、Faster R-CNN等主流检测框架使用。目前已有634人学习,数据中的真实边界框覆盖阴影、反光、遮挡等复杂遥感环境,可支撑从数据预处理、模型调参到精度评估的完整实验流程。可重点考察模型对小型目标及复杂背景的判别能力,为无人机巡检、智慧交通等场景中的飞机检测提供训练基础,也可用于算法对比与改进。

1. 人工智能目标检测数据集里的“飞机卫星图3”:一份能直接用来训遥感小目标的资源

很多人第一次接触人工智能目标检测数据集,都是从行人、车辆这类日常场景入手的;等到真正要做飞机卫星图检测时才发现,之前那套经验几乎全废。卫星俯视下的飞机又小又密、朝向随机,停机坪上的目标可能只有几十个像素,直接把 COCO 预训练模型搬上去,漏检漏到怀疑人生。“飞机卫星图3”就是为这种场景准备的数据集:一批以卫星遥感图为主、以飞机为标注对象的高分辨率图像,外加配套的标注文件。它适合三类人:搞遥感或机场业务的工程师、目标检测刚入门想做项目的新手,以及拿它做毕设或大作业的学生。这篇笔记不绕概念,只讲怎么把这份数据变成能用的检测模型,以及途中一定会踩的坑。

2. 卫星图飞机检测为什么不能拿普通数据集硬顶:小目标、朝向和背景的三个差异

2.1 先算清楚:飞机在卫星图里到底占几个像素

拿到任何遥感数据集,我第一件事不是看标注,而是算目标尺寸。飞机不是大目标。常见民航客机机身长度在 40~70 米,通航小飞机 10~20 米;商用卫星图像分辨率通常在 0.3~1 米每像素。这样折算下来,一架波音 737 在一张 1 米分辨率图里大约 40 像素长,在 0.5 米分辨率图里大约 120 像素长,而翼展方向上往往只有机身长度的一半左右。如果再把图像 resize 到 YOLO 常用的 640×640,这些飞机在输入图上会进一步缩到十几到几十像素。

目标检测界有一个约定俗成的划分:小于 32×32 的框算小目标,小于 16×16 算极小目标。卫星图的飞机大量落在 16~64 像素这个区间,是典型的“小目标密集场景”。小目标之所以难,是因为模型 backbone 每下采样一次,特征图分辨率就减半;一个 30 像素的目标经过 5 次下采样后只剩不到 1 个像素,深层特征里基本没有它的位置。这也是为什么遥感项目里“imgsz=640 跑得动但检不出来”的现象极其常见。

目标类型实际尺寸0.3m/px 下的像素1m/px 下的像素
通航小飞机10~20m33~66 px10~20 px
中型客机30~50m100~166 px30~50 px
干线客机50~70m166~233 px50~70 px

通用数据集如 COCO 里的飞机是俯拍视角下较大的目标,背景简单、尺度单一;卫星图里的飞机是顶视、小尺度、成群停放在停机坪上,旁边还有廊桥、车辆、建筑物阴影干扰。数据分布差异这么大,直接用通用预训练权重做迁移效果自然很差。这也是“飞机卫星图3”这类数据集存在的价值:把分布本身换掉。

2.2 水平框还是旋转框:看到标注先别急着训练

飞机标注方案上有一个绕不开的分岔:用水平矩形框(HBB)还是旋转框(OBB)。这是决定后续训练策略的第一道选择题。

水平框标注成本低、工具多、YOLO 系列开箱即用,但飞机朝向后很麻烦。一架斜 45° 的客机,水平框会框进去一大片停机坪背景;相邻两架飞机如果靠得近,两个水平框大量重叠,NMS 阶段会互相抑制,后处理的漏检率一下就上去了。旋转框能贴合机身方向,框内背景少、目标特征更纯,但标注和训练工具链都要额外搭,比如 mmrotate 以及 DOTA 数据生态那一套。

我的习惯是分场景决定。如果数据集里是稀疏的滑行道、维修机位,飞机之间间隔远,水平框完全够用;如果是密集停放的机坪,飞机头尾相接、翼尖相碰,水平框会非常难受,这时候要么换旋转框方案,要么靠后处理和增强补偿。常见的“飞机卫星图”类数据集,标注多为水平框,因为标注成本决定了数据规模;这份“飞机卫星图3”也不例外。拿到手先看 labels 里是四元组还是五元组坐标:四元组就是普通水平框,按常规 YOLO 流程走;五元组带角度,需要转用 OBB 训练方式,普通 detect 命令不认。

另外留意一下数据格式是否参照 HRSC2016 或 DOTA 的组织方式。很多遥感数据集用的是旋转框 XML 或 Poly 格式,即使画出来是水平框,内部存储也可能带角度字段。做格式转换前先把标注样例打开看一遍,这一步能省后面至少半天排错时间。

3. 拿到“飞机卫星图3”先做数据体检:目录确认、格式转换与可视化复核

3.1 先别急着训练,把目录结构和标注格式确认清楚

数据集从网盘或本地拷下来后,第一步是看目录长什么样。常见组织方式有三种:YOLO 风格的images/+labels/,VOC 风格的JPEGImages/+Annotations/,以及 COCO 风格的images/+annotations.json。用一条命令就能摸清结构:

# 列出两层以内的目录结构,并统计图片与标注文件数量 find . -maxdepth 2 -type d | sort echo "--- images ---" find . -name "*.jpg" -o -name "*.png" | wc -l echo "--- labels ---" find . -name "*.txt" -o -name "*.xml" -o -name "*.json" | wc -l

这条命令的输出能立刻暴露几个大坑。第一,图片数不等于标注文件数,很常见的原因是部分空图没有标注,或者转换时漏了文件;第二,label 分散在多个子目录里,说明数据集可能被分成了好几批,需要合并时就要格外注意重名覆盖;第三,如果存在classes.txt或README一类的文件,优先打开看,里面通常写了类别顺序和标注规范,这直接决定后面 class id 怎么映射。

还需要确认一件事:数据集的划分是否已经给定。很多卫星图数据集是按“图”切的 patch,同一张大图切成若干小图后,如果随机分 train/val,很容易出现同一架飞机在训练集和验证集里各出现一次的情况,这就是数据泄漏。验证指标会虚高,部署时立刻现原形。常见做法是自己按“原始大图”或“机场场景”做划分,而不是按 patch 文件随机分。如果这份数据没给官方划分,我一般会按文件名前缀或目录批次手动分,保证 val 里的场景 train 里没见过。

3.2 VOC/COCO 到 YOLO 格式的转换脚本

如果标注是 VOC XML,需要转成 YOLO 的 txt。这一步看着简单,但坐标归一化、类别映射、越界处理三个地方最容易出错。下面是我常用的转换脚本,逻辑上做了防呆处理:

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, classes): root = ET.parse(xml_path).getroot() filename = root.find("filename").text # 图片宽高以 xml 里的 size 为准,不要用 PIL 现读, # 否则 xml 和实际图片尺寸不一致时坐标全错。 size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in classes: continue cls_id = classes.index(name) box = obj.find("bndbox") x1 = max(float(box.find("xmin").text), 0) y1 = max(float(box.find("ymin").text), 0) x2 = min(float(box.find("xmax").text), w) y2 = min(float(box.find("ymax").text), h) # 标注越界会导致宽高为负,这种框直接丢掉 if x2 <= x1 or y2 <= y1: continue # YOLO 格式要求:中心点坐标和宽高,全部除以图像宽高做归一化 xc = (x1 + x2) / 2 / w yc = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join( out_dir, os.path.splitext(os.path.basename(xml_path))[0] + ".txt" ) with open(out_path, "w") as f: f.write("\n".join(lines))

这段脚本里有三个细节值得注意。一是图像尺寸从 XML 的 size 节点读,而不是读图片本身,原因在于标注工具在某些情况下会记录 resize 后的尺寸,直接读图反而对不上。二是越界 clip 和x2 <= x1的过滤,卫星图标注里框出图像边缘是常态,不处理会产生负数宽高,训练时 loss 计算直接异常。三是 classes 列表顺序,转换前必须先确认类别名跟训练配置里 names 的顺序一致,否则所有框的类别都会错位。

COCO JSON 转 YOLO 的思路完全一样,只是解析结构不同:读取images的width、height,再读annotations里的bbox字段;COCO 的 bbox 是[x, y, w, h]左上角格式,转 YOLO 时中心点要自己加半宽半高。转换完随手抽查几个文件,看看数字是否都在 0 到 1 之间。

3.3 可视化复核:检查框偏移和标注错误

格式转换和目录准备做完,还没到训练这一步。先做可视化复核,把标注框画到原图上肉眼过一遍。这一步能拦住大量“训练半天 loss 不降”的问题:

import cv2 img = cv2.imread(image_path) h, w = img.shape[:2] labels = ["airplane"] # 按 classes.txt 的顺序写 with open(label_path) as f: for line in f: parts = line.strip().split() cls = int(parts[0]) xc, yc, bw, bh = map(float, parts[1:]) # 归一化坐标换算回像素 x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, labels[cls], (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite("check.jpg", img)

这一步常见的问题有三类。第一,框的位置整体偏移一半或出现在图像四角,多半是归一化基准错了,比如拿原图尺寸归一化却存在 xml 的缩略图尺寸,或者 xc 和 yc 写反;第二,框明显小于飞机轮廓,通常是标注是从另一个分辨率图像上画的,转换时尺寸对不上;第三,类别编号在 labels 长度之外,训练时直接 IndexError。跑一遍可视化,这些基本能一次看清。

我习惯在可视化脚本里顺手统计每个 label 文件的框数量和坐标范围,输出异常值。框总数很少但图片很多,说明标注缺失率高;坐标范围出现负数或大于 1 的值,说明转换脚本里有 bug 或原始标注越界。这一步属于“后悔药”,花 20 分钟检查,能省掉后面几小时的无效训练。

4. 用 YOLO 系模型训练飞机检测:环境配置、数据配置与关键参数

4.1 环境与数据配置

训练阶段我默认用 ultralytics 的 YOLOv8/YOLO11 系列,理由很直接:对遥感小目标有足够多的调参手段,命令行入口统一,好复现。环境配置不需要折腾,装一个包就能启动:

pip install ultralytics python -c "import torch; print(torch.cuda.is_available())"

注意这里检查的是 CUDA 可用性,不是 torch 有没有装。没有 GPU 也能训练,但 1024 分辨率的小目标场景 CPU 训练会慢到让人失去耐心。我一般要求显存至少 8G,这决定了后面 batch 和 imgsz 能开到多少。

数据配置文件是训练前最后一道手续。新建一个aircraft3.yaml,内容指向刚才准备好的目录:

# aircraft3.yaml path: /data/aircraft3 train: images/train val: images/val nc: 1 names: 0: airplane

这里最容易踩的坑是路径。path建议写绝对路径,且整条路径不要包含中文和空格;train和val是相对path的目录。ultralytics 在解析时会自动拼 path + train,如果目录写错,通常会报 FileNotFoundError,但有时候会静默跳过空数据集,表现为训练 loss 不变、mAP 为 0,这个后面避坑章会细说。

类别名这里只写了一个airplane。如果你的数据集里区分了客机、战斗机、直升机等多个类别,就把 nc 和 names 按 classes.txt 的实际内容填。

4.2 训练命令与参数选择

数据准备好了,训练本身只需要一条命令:

cd /data/aircraft3 yolo detect train \ data=aircraft3.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=1024 \ batch=8 \ patience=30 \ mosaic=0.5 \ workers=4 \ device=0

这套参数是我跑卫星图飞机数据集的起步配置。逐个说为什么。

imgsz=1024是最关键的一项。上一章算过,飞机在 1 米分辨率下只有 30~50 像素,如果用 640 输入,实际喂给模型的飞机只有十几个像素,深层特征图里基本消失。把输入调到 1024,相当于给模型更多像素去分辨机翼和机身。显存不够时不要贸然降到 640,而是先减 batch。

batch=8在 1024 分辨率下大概占 10~12G 显存,如果用的是 yolov8s 会更宽松一些。显存小就把 batch 降到 4,甚至 2,同时接受训练时间变长。epochs=100起步,patience=30意味着验证指标连续 30 轮不涨就早停,不会白等。mosaic=0.5是把马赛克增强概率从默认的 1.0 降下来——遥感大图里 mosaic 会把飞机切碎或拼出大量残缺目标,增强过头反而伤小目标。workers=4是数据加载线程数,Windows 上偶尔会卡,改成 0 可以排查问题。

关于预训练权重,我用yolov8s.pt而不是yolov8n.pt起步。n 模型轻、训练快,但小目标检测能力明显弱;s 模型在显存和精度之间比较平衡。如果你的显存超过 16G,直接上yolov8m.pt也不会后悔。预训练权重自带 COCO 特征,虽然分布不匹配,但底层边缘纹理特征依然有迁移价值,比从零训练收敛快得多。

4.3 训练过程里看什么

训练启动后不要干等,日志和 results.csv 里值得盯的指标有几个。第一是train/box_loss和val/box_loss是否同步下降,如果 train loss 在降但 val loss 横盘,说明过拟合,需要加强数据增强或提前早停;第二是metrics/mAP50和metrics/mAP50-95,前者看框的召回质量,后者更严格地惩罚框偏移,对遥感小目标来说 mAP50 上了 0.8 之后,mAP50-95 通常会低 15~20 个点,这正常,因为飞机框小,IoU 稍微偏一点就掉分。

ultralytics 会自动在runs/detect/trainXX/目录下生成混淆矩阵、PR 曲线和验证集的预测样例图。我特别建议打开val_batch0_pred.jpg看小图:如果验证图上飞机旁边画了一堆置信度 0.3 左右的碎框,说明 NMS 阈值或置信度阈值要调;如果完全没框,说明模型还没收敛或标注有严重问题。

训练结束后的产物是best.pt和last.pt,best.pt按验证指标保存,是部署首选。不要习惯性地拿last.pt,因为它可能是过拟合后的模型。

5. 遥感飞机检测的五个高频翻车现场与排查记录

5.1 训练 loss 不降,mAP 一直是 0

这个现象我在处理数据集类项目时遇到最多:训练日志显示box_loss和cls_loss完全不动,mAP从头到尾是 0。原因绝大多数不是模型问题,而是数据没喂进去。最常见的是标注文件为空或类别编号越界,ultralytics 对无法匹配的标签会静默忽略,模型每个 batch 看到的全是背景图。

解决步骤很固定:随机抽三个 train 标签文件,用cat确认里面有内容;再跑一遍 3.3 的可视化脚本,确认框能画出来且类别号正确。如果 label 文件是空的,回去查转换脚本里是否有x2 <= x1的过滤条件,或者源标注文件本身缺框。空 label 文件在 YOLO 体系里是合法的(表示该图没有目标),但全数据集如果大面积为空,说明转换过程出了大问题。

5.2 训练指标不错,单独抽一张测试图却什么都检不出来

最气人的故障就是这个:results.csv 里 mAP50 有 0.8 以上,拿一张没见过的卫星图一跑,输出一个目标都没有。先别怀疑模型装神弄鬼,十次里有九次是尺度不匹配。训练用的是切好的 1024×1024 patch,推理时直接喂整张大图,原始大图可能 5000×5000,resize 到 640 后飞机只有几个像素,检不出来太正常了。

另一个隐蔽原因是推理置信度阈值。训练时验证集的置信度阈值在 0.001 量级,推理 CLI 默认用 0.25,对本来就低置信度的小目标,这一下就筛没了。排查时把 conf 降到 0.05 再跑一遍,如果目标出来了,问题就是阈值。这个问题的根本解法在下一章:推理时对大图做滑窗切图,让推理输入尺度落在训练分布里。

5.3 把白色屋顶、机库、车辆误检成飞机

卫星俯视图里,白色厂房顶、机库、大巴车顶和飞机的俯视轮廓高度相似,模型分不清很正常。这类误检是遥感目标检测的“职业病人”,不是模型坏了。

原因有两层:一是标注的正样本全集中在停机坪区域,背景负样本不够;二是水平框框进去的背景占了很大比例,模型学到的不只是飞机本身,还包括了周围停机坪的纹理。这就导致它见到任何“亮色矩形物体+平整背景”的组合都会兴奋。解决手段按性价比排序:先在推理后处理里加尺寸过滤,飞机的真实像素尺寸范围是已知的,长宽比和面积都有边界,超出范围的检测框直接丢;再考虑补充难负样本,专门收集一批包含屋顶、车辆的图像标注为 background 或在背景图上做负样本训练;最后才是上旋转框,从特征上把方向信息加进来,让模型学会区分“有机头有机翼”和“只是个方盒子”。

5.4 CUDA out of memory,训练到一半崩掉

1024 分辨率 + batch 8 + 数据加载线程 4,很容易把 8G 显存打到溢出。崩溃前没有任何征兆,RuntimeError: CUDA out of memory直接中断。遇到这个不要硬调显存,按顺序降参数:先把 batch 减半到 4,不行再把 imgsz 从 1024 降到 896 或 768,最后才考虑换更小的模型。

还有一个容易被忽略的选项:cache=True会把数据提前缓存在内存里,卫星图数据集如果单张 2000×2000,内存占用会非常夸张,反而拖垮训练。显存不够时优先关掉缓存,把workers从 4 调到 2,也能减少因为数据加载占用的临时显存。实在不行就在命令行加amp混精训练,默认就是开启的,不要手动关。

5.5 验证指标好、部署效果差:切图方式和训练时不统一

这个坑我翻车过一次:训练时用 1024 的 patch,验证时也按 1024 切,指标很漂亮;部署推理时图省事直接把整张 6000×6000 的原图塞给模型,结果目标几乎全丢。整个过程跟模型无关,纯粹是推理输入尺度跟训练分布不一致。

解决思路只有一个:部署推理必须复刻训练时的预处理链路。训练时怎么切图、多大尺寸、有没有 overlap,推理时就按同一套参数跑。不要相信模型能“泛化到任意尺度”,目标检测模型的尺度鲁棒性远没有想象中好。这也是下一章把滑窗推理作为必做动作的原因。

6. 让飞机检测在真实卫星图上不虚:切图推理与验证技巧

6.1 滑窗切图推理:把大图剪成训练时的尺度

上一章反复提到切图,这里给出可直接用的实现。核心思路是把大图切成固定大小的 patch,逐个推理后再把检测框映射回原图坐标,最后做一次全图 NMS 合并重叠框:

import cv2 import numpy as np def iou(b1, b2): x1 = max(b1[0], b2[0]); y1 = max(b1[1], b2[1]) x2 = min(b1[2], b2[2]); y2 = min(b1[3], b2[3]) inter = max(0, x2 - x1) * max(0, y2 - y1) area1 = (b1[2] - b1[0]) * (b1[3] - b1[1]) area2 = (b2[2] - b2[0]) * (b2[3] - b2[1]) return inter / (area1 + area2 - inter + 1e-6) def nms(boxes, thr=0.45): boxes = sorted(boxes, key=lambda x: x[4], reverse=True) keep = [] while boxes: best = boxes.pop(0) keep.append(best) boxes = [b for b in boxes if iou(best, b) < thr] return keep def slide_predict(model, img, tile_size=1024, overlap=128, conf=0.15): h, w = img.shape[:2] boxes = [] step = tile_size - overlap for y in range(0, h, step): for x in range(0, w, step): x1, y1 = x, y x2 = min(x + tile_size, w) y2 = min(y + tile_size, h) patch = img[y1:y2, x1:x2] res = model(patch, conf=conf, verbose=False)[0] for b in res.boxes: bx1 = b.xyxy[0][0].item() + x1 by1 = b.xyxy[0][1].item() + y1 bx2 = b.xyxy[0][2].item() + x1 by2 = b.xyxy[0][3].item() + y1 boxes.append([bx1, by1, bx2, by2, float(b.conf), int(b.cls)]) return nms(boxes, thr=0.45)

tile_size必须等于训练时的imgsz,这是硬性原则。overlap=128是经验和折中:飞机被切在 patch 边缘时,框会被截断导致检测不到,重叠区域能让同一目标在相邻 patch 里各出现一次;overlap 太小漏检,太大会产生大量重复框,128 在 1024 的 tile 下是合适的起点。推理置信度 0.15 比训练默认的 0.25 低,因为滑窗本身会制造大量低置信度碎片框,这些靠后面的全图 NMS 合并,而不是靠提高阈值硬杀。

6.2 验证技巧:数飞机而不是只看 mAP

mAP 是训练指标,部署验证我更推荐一个土办法:从验证集里挑一张典型停机坪大图,人工数一遍飞机数量,然后跑上面的滑窗推理,比较“人工计数 vs 模型计数 + 漏检数 + 误检数”。这个数字比任何曲线都直观。卫星图飞机检测的最终业务往往是“这个机场停了多少架飞机”,计数误差率才是老板关心的指标。模型 mAP50 从 0.85 提到 0.9 可能只反映框的细微偏移,而计数误差率能直接把漏检和误检折算成你能感知的业务损失。

进阶习惯是把误检样本攒下来做增量训练。推理阶段每次发现新的误检模式——比如雾天把机库顶当成飞机——就把那张图连同修正后的标注加入训练集,重新跑一轮。这样模型在真实场景里会越用越准。有一次我就是没按统一尺度切图,直接拿原图去推理,结果一架飞机都没检出来,后来老老实实把推理切图参数对齐训练参数,肉眼可见地恢复了。这个教训我现在留着,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询