简介:目标检测是计算机视觉的基础任务,但在遥感图像中,由于俯视视角、目标尺寸极小且背景复杂,通用检测模型往往失效。YOLOv8作为主流单阶段检测器,在自然图像上表现优异,但直接迁移到遥感场景,小目标的特征在深层网络中极易丢失,导致漏检。针对这一问题,通常需要从数据预处理入手,例如对DOTA等遥感数据集进行切片、标签对齐和尺度适配,再通过调整输入分辨率、数据增强策略以及引入注意力机制等手段提升小目标检测精度。这项技术在卫星遥感、无人机巡检、智慧交通等领域具有重要应用价值,能够有效从高空影像中识别车辆、船舶等小目标。基于实际工程经验,完整梳理了基于YOLOv8的遥感小目标检测流程,覆盖数据转换、训练参数调优和踩坑排查,为相关开发者提供一条可复用的实践路径。 提到YOLOv8做遥感小目标检测,很多人第一反应是“直接用官方预训练权重跑到遥感图上试试”。我第一次在DOTA数据集上干这事时,结果惨不忍睹——人眼能数出来的停车场车辆,模型一个框都没给,mAP基本可以忽略不计。不是YOLOv8不行,而是遥感图像和COCO自然图像之间的领域差异比你想象的大得多。这篇就把我在NWPU VHR-10和DOTA上做小目标检测的完整思路、数据预处理、训练调参和踩坑过程写透,给准备入门遥感小目标检测的朋友一条可以直接复用的路线。
1. 为什么官方权重在遥感图上“失效了”
1.1 领域鸿沟:俯视图与自然图像的差异
YOLOv8在COCO数据集上的表现大家有目共睹,但COCO数据集是自然场景照片,平视视角、光照丰富、目标尺度相对稳定。遥感图像来自卫星或航拍,是典型的俯视图,目标特征完全不一样:车辆变成一堆像素点、储油罐是个圆形色块、立交桥的结构从上方看是交错的线条。模型在COCO上学到的纹理、形状、上下文信息,搬到遥感图像上很多都用不上。
还有一个更致命的问题:尺度差异。一张DOTA原始图像动辄4000×4000,里面的小汽车可能只占20×20像素甚至更小。如果用YOLOv8官方默认的640×640推理,整张图缩放到640后,一个20像素的目标就只剩3像素左右,几乎等于消失。这是纯粹的物理信息丢失,不是模型能力问题。
1.2 小目标检测为什么一直难做
小目标检测难,根本原因有三个。第一,特征太少,浅层的边缘纹理信息到深层特征图基本被卷积池化抹掉了;第二,正样本匹配难,目标占的面积比例太小,在标签分配阶段容易匹配不到正样本,导致网络“想学也学不到”;第三,边界框回归的精度要求高,几个像素的偏差对IoU影响极大。
在YOLOv8里,默认的检测头输出是80×80、40×40、20×20三张特征图,分别负责大、中、小目标。理论上20×20特征图对应的是大目标,80×80对应小目标,但遥感图像里大量目标在80×80特征图上仍然太小。这个矛盾不是YOLOv8独有,而是所有单阶段检测器在遥感场景下的通病,所以数据处理阶段就要想办法“让目标变大”。
2. 先摸清两个数据集的家底:NWPU VHR-10与DOTA差异
动手之前先花点时间了解数据集,这个步骤不亏。两个数据集的标注格式、图像尺寸、类别体系都不一样,处理方式也不同。
2.1 NWPU VHR-10:入门练手级的遥感数据集
NWPU VHR-10是西北工业大学发布的遥感目标检测数据集,一共800张图像,其中650张正样本(图像中包含目标)、150张负样本(背景图,不包含目标),来源是Google Earth和一部分航空影像。类别有10类:飞机、船舶、储油罐、棒球场、网球场、篮球场、田径场、港口、立交桥、车辆。
这个数据集的优势是规模小、标注干净、图像尺寸相对统一,大部分图像在600×800到900×900这个量级,不需要切图就能直接训练。劣势也很明显:样本量偏少,做深度模型训练容易过拟合。它的价值在于快速验证整个pipeline是否跑通——数据格式转换、训练脚本、评估流程都调通之后,再上DOTA就从容多了。
NWPU VHR-10的标注默认格式是TXT文件,每行内容大意是“类别ID 左上角x 左上角y 右下角x 右下角y”,类别ID从1开始计数,需要注意转换成YOLO格式时类别要从0开始。
2.2 DOTA:大而全、也大而“难”的遥感基准
DOTA(Dataset of Object deTection in Aerial images)是目前遥感目标检测领域使用最广泛的公开数据集之一。DOTA v1.0包含2806张遥感图像,约18.8万个标注实例,类别覆盖飞机、船舶、储油罐、棒球场、网球场、篮球场、田径场、港口、桥梁、大型车辆、小型车辆、直升机、环形交叉路口、足球场、游泳池共15类。
DOTA和NWPU VHR-10最大的区别在两点。第一是图像尺寸跨度大,小到800×800,大到4000×4000,直接缩放到训练尺寸会严重损失小目标信息;第二是标注格式,DOTA使用旋转四边形标注,每个目标用四个顶点坐标表示,而不是简单的水平框。这意味着做YOLOv8水平框检测时,还需要额外一步把旋转框转成水平外接矩形。
这里提一个很多人会问的点:DOTA既然标注了旋转框,是不是应该用mmrotate或者YOLOv8的OBB模式来做旋转框检测?确实可以,但旋转检测的复杂度和坑都比水平检测高不少。本文以水平框检测为主线,先保证小目标检测的整体流程跑通,旋转框作为后续进阶方向。
2.3 两个数据集在项目中的定位
我的建议是:NWPU VHR-10作为快速验证集,DOTA作为正式训练和评估集。先用NWPU把数据转换脚本写对、把训练命令跑通,再投入到DOTA的切图、转换和大规模训练中,能省掉很多重复排查的时间。两个数据集的差异可以整理成一张表,方便对比:
| 特性 | NWPU VHR-10 | DOTA v1.0 |
|---|---|---|
| 图像数量 | 800张(正样本650,负样本150) | 2806张 |
| 类别数 | 10类 | 15类 |
| 标注方式 | 水平框 | 旋转四点多边形 |
| 图像尺寸 | 约600×800~900 | 800×800~4000×4000 |
| 实例规模 | 数千量级 | 约18.8万 |
| 主要用途 | 快速验证流程 | 模型训练与评估 |
3. 硬件与环境的现实边界:6G显存怎么安排
3.1 环境安装与版本兼容性
环境配置本身不难,但版本坑不少。我以GTX 1660 Ti 6GB显存为例来说明,这也是很多人跑YOLOv8的起步配置。Python建议3.8到3.10,PyTorch版本建议2.0以上,Ultralytics用8.x版本。
conda create -n yolo python=3.10 conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics安装完成后先跑一个快速验证,确认环境没问题:
yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg能看到推理结果说明环境OK。有人问PyTorch最新版本是否支持YOLOv8,这不用担心,Ultralytics的依赖里会自动适配PyTorch版本,只要CUDA和PyTorch匹配就行。真正需要留意的是Ultralytics版本变动导致的配置项变化,比如增强参数名、OBB模式支持等,建议锁定一个大版本使用。
3.2 显存不够时先改数据,不要硬扛
6G显存训练YOLOv8,直接拿DOTA原始图片肯定是想都不用想。一张4000×4000的图像如果直接送进网络,光图像张量就占掉大量显存,batch稍微大一点就直接OOM。
但这里有个反直觉的结论:显存不够,优先用“数据切块”解决,而不是换显卡或者把batch降到1。原因在于,遥感图像切成1024×1024的patch后,每张patch的信息量依然完整,小目标的尺寸占patch的比例比占原图的比例大得多,模型能学到的东西更充分。batch=4或batch=8完全可以在6G显存上运行,代价只是训练速度慢一点。
3.3 硬件配置的预期管理
1660 Ti跑yolov8n,imgsz=1024,batch=8,训练DOTA切图后的几千张patch,一个epoch大概要几分钟到十几分钟,300个epoch可能要跑一两天甚至更久,要有心理预期。模型选择上不用死磕yolov8l/x,yolov8s在小目标检测上已经能打,yolov8m效果更好但显存和速度压力都上来。先跑小模型验证方案,再逐步放大是务实的路线。
4. 数据预处理是胜负手:切片、旋转框转水平框、标签对齐
4.1 为什么要切图:一次缩放就丢了98%的信息
DOTA的4000×4000图像直接resize到1024,面积缩小到原来的约1/16,一个20×20像素的小汽车在缩放后就剩5×5像素,基本失去检测价值了。图像缩放到1024,相当于所有目标等比例缩小4倍,小目标检测的难度直接放大一个数量级。所以切图不是可选项,是必选项。
滑窗切图是遥感检测的标准做法。patch尺寸选1024×1024,重叠区建议设置200像素左右。为什么要有重叠?目标如果恰好被切在图块边缘,会被截断导致标签混乱,重叠区可以保证同一个目标至少在某一个patch里是完整的。
切图脚本用OpenCV就能实现:
import cv2 import numpy as np import os from tqdm import tqdm def sliding_window_crop(image_path, label_path, save_img_dir, save_label_dir, patch_size=1024, overlap=200): img = cv2.imread(image_path) h, w = img.shape[:2] step = patch_size - overlap patch_id = 0 labels = [] with open(label_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue # DOTA原始格式: x1 y1 x2 y2 x3 y3 x4 y4 category difficult coords = list(map(float, parts[:8])) category = parts[8] labels.append((coords, category)) for y in range(0, h - patch_size + 1, step): for x in range(0, w - patch_size + 1, step): crop = img[y:y+patch_size, x:x+patch_size] # 切图后的标签过滤和坐标转换在下面处理 patch_id += 1 patch_name = f"{os.path.splitext(os.path.basename(image_path))[0]}_{patch_id}" cv2.imwrite(os.path.join(save_img_dir, patch_name + ".jpg"), crop)这个脚本只展示了切图主循环,实际使用中还需要在右下边缘做对齐处理,确保切出来的patch都满足patch_size,最后一列和最后一行可能不足1024时要做边界填充。
4.2 DOTA旋转框转水平框
DOTA标注的8个坐标点构成一个旋转矩形,而YOLO水平框检测需要的是轴对齐矩形。最简单的转换方法是取四个点的最小横坐标、最大横坐标、最小纵坐标、最大纵坐标,直接算出水平外接矩形,代码很短:
import cv2 def poly_to_xyxy(coords): xs = coords[0::2] ys = coords[1::2] x1, y1 = min(xs), min(ys) x2, y2 = max(xs), max(ys) return x1, y1, x2, y2用这种方式转出来的框会包含一些背景区域,比如一个旋转45度的车辆,水平外接框里会有不小的非目标区域。这也是做水平框检测的天然劣势,可以接受,在后续优化阶段再考虑旋转框方案。
4.3 切图后的标签对齐与过滤
切图后最麻烦的环节是标签处理。每个patch里面只保留落在patch范围内的目标,坐标要做相对patch的偏移计算,然后用patch的宽高做归一化。还有一类目标要特别小心:被切在patch边缘、大部分身体在patch外面的目标,强行保留会导致这个patch出现“残缺目标”,模型学到错误特征。
我的过滤策略是计算目标框与patch的交集面积与目标原面积的比值,保留IoU大于0.5的目标,其余丢弃。这个阈值可以根据实际情况调整,太严格会丢掉大量正样本,太松则引入过多噪声。
转换标签时,DOTA的四边形顶点坐标是原图坐标系,需要先减去patch左上角的偏移量,再除以patch_size完成归一化。对应的YOLO格式是“类别 cx cy w h”,其中cx、cy是归一化中心点坐标,w、h是归一化宽高。类别ID记得从0开始,DOTA的15类分别映射为0到14。
4.4 NWPU VHR-10的数据转换
NWPU VHR-10不用切图,处理起来简单很多。读取原始TXT标注,每行的类别ID减1,然后从x1 y1 x2 y2转换为cx cy w h并除以图像宽高归一化,写出TXT到labels目录即可。训练时用data.yaml指定类别名称和路径。
4.5 数据划分与目录结构
不论哪个数据集,最终统一成这样的目录结构:
datasets/ dota_1024/ images/ train/ val/ labels/ train/ val/对于DOTA,建议把官方train和val合并,再按9:1或8:2重新划分。因为DOTA官方的划分不完全适合YOLO训练习惯,重新划分能保证训练集和验证集分布更稳定。NWPU VHR-10同理,把150张负样本剔除后参与训练,正样本按8:2划分。
5. 训练参数逐项拆解:每个数值背后都有取舍
5.1 模型选择:先小后大,验证思路优先
对于NWPU VHR-10这种小数据集,yolov8n或者yolov8s都够用,数据量太小,上大模型很快就过拟合。对于DOTA,建议从yolov8s开始,跑通整个流程后再考虑yolov8m。用COCO的预训练权重做初始化,不是让模型直接迁移遥感特征,而是让backbone有一个合理的初始状态,微调起来收敛更快、更稳定。
5.2 imgsz:小目标检测最关键的参数
imgsz对小目标检测的影响比模型选择还大。同样的模型,imgsz=640和imgsz=1024,小目标的AP可以差出十几个百分点。因为特征图分辨率越高,小目标在特征图上对应的像素越多,网络能获取的信息越充分。
6G显存跑yolov8s在imgsz=1024时需要把batch控制在8左右;如果显存更紧张,把模型换成yolov8n,或者把imgsz降到768到896之间,不要直接降到640。
5.3 训练命令与参数清单
训练命令如下:
yolo detect train data=data.yaml model=yolov8s.pt imgsz=1024 batch=8 epochs=300 patience=50 optimizer=AdamW lr0=0.001 weight_decay=0.0005关键参数的选择可以参考下表:
| 参数 | 建议值 | 原因 |
|---|---|---|
| imgsz | 1024 | 保留小目标信息,在显存允许范围内尽量大 |
| batch | 4-8 | 6G显存下以OOM为上限,不要硬塞 |
| epochs | 200-300 | 遥感数据收敛慢,太短学不到位 |
| patience | 50-100 | 留出足够空间让mAP继续爬升 |
| optimizer | AdamW | 收敛稳定,配合lr0=0.001效果好 |
| mosaic | 1.0(可尝试降低到0.5) | 增强多样性,但小目标会被进一步缩小 |
| scale | 0.5 | 缩放增强幅度不宜过大,避免目标缩得更小 |
| pretrained | yolov8s.pt | 迁移学习加速收敛 |
5.4 数据增强参数的两个坑
第一个坑是mosaic。YOLOv8默认开启mosaic,它把四张图拼成一张,确实能提升泛化能力。但对小目标检测来说,mosaic会让目标尺寸变得更加迷你,很多目标在拼接后只有几个像素大小,正样本匹配难度反而增加。我实际测试下来,在NWPU VHR-10上mosaic开1.0和0.5的区别不大,但在DOTA上mosaic太大时小目标AP会掉。可以尝试前10个epoch关闭mosaic,让模型先学会基础特征,之后再开启。
第二个坑是scale。YOLOv8的scale增强默认是0.5,表示随机缩放范围较大。在小目标数据集上建议调得更保守,比如scale=0.3,减少目标被缩得更小的概率。这些增强参数在ultralytics的训练配置里可以通过augment相关参数调整。
6. 训练曲线读法:指标不是只看mAP
6.1 训练日志里到底有什么
训练完成后,Ultralytics会在runs/detect/目录下生成results.png,里面包含损失曲线和指标曲线。很多人只看mAP,其实下面这几个曲线更有诊断价值:
- box_loss:边界框回归损失。这个值下降慢或不下降,说明边界框回归没学好,可能是标签质量差或正样本太少。
- cls_loss:分类损失。如果这个值降到很低但mAP不涨,可能是类别学习没问题但定位有问题。
- dfl_loss:分布焦点损失,反映边界框质量分布。数值不会特别低,但曲线应该是平滑下降的。
- mAP50和mAP50-95:mAP50反映的是“粗定位”能力,mAP50-95反映的是“精确定位”能力。遥感小目标检测里,两者差距大是常态,因为小目标的框稍微偏几个像素,IoU就跌破0.5了。
6.2 常见的虚假训练信号
遇到过一种情况:训练loss一路下降,看起来非常完美,但val mAP纹丝不动。这通常是过拟合或者标签分布有问题。检查方法很简单,把训练集和验证集分开画曲线对比,如果train loss降而val loss不降,加数据增强或加大正则化。另一种情况是loss一直震荡不降,先看学习率,可能太高或者太低;再看数据增强,如果mosaic加mixup开满,小数据集上震荡很正常。
6.3 小目标检测要重点看AP_small
Ultralytics在验证时会对不同尺度的目标分别统计AP,日志里可以看到metrics/small、metrics/medium、metrics/large。小目标检测项目里,AP_small才是核心指标,AP_medium和AP_large只能作为参考。如果AP_small低而AP_medium高,说明小目标信息在特征提取阶段丢失严重,需要从数据切块尺寸和模型结构入手,而不是死磕训练epoch。
我在DOTA切图数据集上跑yolov8s,最终mAP50能达到一个可用的水平,但AP_small明显比AP_medium低15个百分点以上,这是正常现象。理想情况下,小目标的框需要预测得足够准确,否则IoU计算时很容易被判定为负样本。
7. 小目标检测的进阶优化思路
7.1 更高的输入分辨率与多尺度训练
当基础流程跑通后,提高分辨率是收益最直接的优化。把imgsz从1024提升到1280甚至1536,小目标的AP还能再涨。代价是训练速度明显变慢、显存压力增大。多尺度训练也很实用,训练时每隔几个epoch在预定义尺度范围里随机切换输入尺寸,相当于免费的数据增强,提升了模型对不同尺度目标的适应能力。
7.2 注意力机制:以ECA为例子
热词里有人提到YOLOv8加ECA(Efficient Channel Attention),这是小目标检测里很常见的改进方式。ECA本质上是通道注意力模块,核心思路是让网络知道哪些特征通道对当前目标更重要,类似给每个通道打个“重要性分数”,然后加权。对小目标来说,有用的特征通常集中在某些特定通道,ECA能把网络注意力引导到这些通道上。
在Ultralytics中自定义注意力模块,通常需要修改模型的yaml配置文件,在backbone或者neck的适当位置插入ECA层。这类改动没有标准答案,需要结合自己的数据反复实验。我的经验是:加了注意力模块不代表一定涨点,要在验证集上对比实验才能判断。如果数据量少,注意力机制很容易过拟合,效果甚至可能变差。
7.3 P2检测头:让浅层特征参与小目标检测
YOLOv8默认从P3开始检测,也就是8倍下采样特征图。P2检测头是指额外增加一个4倍下采样特征图的检测分支,这个位置的特征图分辨率更高,保留了更多小目标的细节。代价是计算量增大、显存占用升高,但小目标AP通常有明显提升。
7.4 SAHI切片推理:从推理侧解决小目标问题
如果把训练时的切图思路延续到推理阶段,就是SAHI(Slicing Aided Hyper Inference)的基本思想。训练时切图可以保证目标在patch里足够大,推理时如果直接对整张大图推理,小目标照样会丢。SAHI在推理阶段把大图切成小块分别推理,再合并结果,与训练阶段的数据分布保持一致。这个方案对遥感推理部署很有价值,可以在不重新训练的情况下直接提升小目标检测效果。
7.5 部署与工程化
训练好的模型导出成ONNX或TensorRT,可以部署到嵌入式设备或边缘设备上。热词里有人问“YOLOv8训练好的模型怎么部署到嵌入式设备”,一般流程是:导出ONNX → 用ONNX Runtime或TensorRT做推理 → 按目标平台做量化压缩。小目标检测场景下,模型压缩要谨慎,因为量化容易丢精度,尤其对小目标更敏感。
8. 踩坑记录与完整排查链路
8.1 坑:标签文件全是错的但训练loss正常下降
这个问题困扰了我很久。现象是训练loss正常下降、mAP50从一开始就是0或者非常低,怎么调参都救不回来。一开始我怀疑是模型问题、学习率问题、数据增强问题,全部排查一轮都没用。后来把训练集的标签可视化到原图上,发现框的位置和目标完全对不上——有的框跑到了图像右上角,而目标明明在中心区域。
排查过程重新捋了一遍数据转换脚本,终于定位到根因:NWPU VHR-10的坐标原点在图像左上角,但当时写转换脚本不小心把坐标和宽高搞混了,生成的归一化中心点全部偏移。修复方法很简单,重新转换一遍标签,可视化确认没问题后重新训练,mAP马上就上来了。
这里总结一个排查套路:mAP为0先看标签可视化,不要急着调模型。用Ultralytics自带的plot功能或者写个简单的OpenCV画框脚本,把标签画在图上检查,坐标是否对齐、类别是否对、归一化是否正确,一眼就能看出来。
8.2 坑:显存频繁OOM,batch降到2也没用
有段时间训练总在某个epoch中途挂掉,提示CUDA out of memory。把batch从8降到4、再降到2,还是随机会OOM。这让我怀疑不是batch大小的问题。后来排查发现两个原因。
第一个是数据加载器的问题。数据集的workers数量和prefetch_factor设置太大,多个worker同时预取图片到内存,虽然显存没爆,但系统内存和GPU内存交界处的压力很大,也会触发CUDA错误。把workers降到2到4,prefetch_factor设为2,OOM频率明显下降。
第二个是输入图像尺寸的隐患。DOTA原始图像被切图后,虽然大部分patch是1024×1024,但边缘patch可能在处理时被填充成了不一致尺寸,导致训练时数据加载到GPU后临时缩放,显存峰值暴涨。解决方案是切图时强制所有patch输出尺寸一致,并且在Dataset里检查图像尺寸是否严格等于设定值。
8.3 坑:DOTA验证集mAP不稳定,每个epoch波动大
DOTA类别多、目标尺度差异大,验证集mAP波动大是很正常的。但波动太大就要检查验证集划分是否合理:如果验证集里碰巧某个类别的目标特别多,或者某个大图的切割块在验证集里占了很高比例,指标就会不稳定。重新做划分时,尽量把同一张原始图像的切图块放在同一个集合里,避免同一张图的补丁同时出现在训练集和验证集,造成信息泄漏。
8.4 最后再分享一个小技巧
我在DOTA上做完一版训练后,习惯把验证集上漏检的图像集中看一遍。Ultralytics在训练结束后会保存部分验证图像的可视化结果,但默认数量有限。我会单独写一个脚本,用训练好的模型跑验证集,把漏检目标(GT存在但模型没框出来)的图像挑出来排列对比。你会发现漏检的目标大多集中在尺寸极小、对比度低、被遮挡的场景,这些情况对应到数据增强和推理策略上都非常有针对性。这种“按图索骥”的做法,比盲目堆模型和调参高效得多。
本文还有配套的精品资源,点击获取