1. 项目背景:为什么普通目标检测思路在遥感图像上行不通
做遥感图像目标检测之前,我默认会先把 YOLOv8 跑通一套通用流程——下数据集、配置类别、训练、验证、调参。这套流程在自然图像上非常顺滑,比如 COCO、VOC 这类常规目标检测数据集。但换成遥感图像后,同样一套流程立刻变得非常"别扭"。
先说最直观的感受:遥感图像和手机拍的照片、监控摄像头截帧完全是两个物种。一张标准的遥感影像动辄 5000×5000 像素以上,常见的有 1024×1024 瓦片拼接出来的大面积场景。拿 YOLOv8 默认的 640×640 输入分辨率去推理这种大图,本质上是把信息量压缩了几十倍。小目标比如船只、车辆、油罐,在缩放之后可能只剩下几个像素,哪怕是 YOLOv8 的 C2f 特征提取能力也扛不住这种信息丢失。
再说多尺度问题。遥感图像里的目标尺寸跨度非常夸张:一整片机场里的飞机可能占几百个像素,公路上的车辆可能只有 20×20 像素,而大型油罐、储煤场这类目标动辄占图上几千像素。同一个模型要同时检测这么宽的目标尺度范围,仅仅依靠 YOLOv8 默认的 P3/P4/P5 三个检测头是不够的。
最后是方向多变的问题。自然图像里的物体大体是"头朝上"的,检测框可以近似成水平矩形。但遥感影像是俯视视角,什么方向的船都有,飞机头朝向任意角度,车辆在道路上随意停靠。普通 YOLOv8 的水平框检测头在处理这类目标时会有大量冗余区域,一个 45 度斜放的细长船体,水平框里可能一半以上是背景。
这篇文章我会把我在实际项目中踩过的一些坑和用过的解法完整梳理一遍,包括数据预处理、切片推理策略、网络结构调整、方向目标处理这几块内容。适合的人群是已经能跑通 YOLOv8 基础流程,想转向遥感图像检测场景的开发者。
2. 问题拆解:遥感图像的三大特性分别影响模型的哪些模块
2.1 大尺寸图像对显存、推理效率和上下文信息的挑战
遥感图像的大尺寸带来的直接问题是显存不够用。一张 5000×5000 的 3 通道 RGB 图像,如果直接输入 YOLOv8,光是把图像本身加载到 GPU 显存就需要 5000×5000×3×4 字节约 300MB 的显存,FP16 推理时代这个数字会减半但依然不容乐观。底层特征图更大、中间层的特征计算量呈指数级增长,一张大图跑一次前向传播的时间会长到无法接受。
更隐蔽的问题是上下文信息的割裂。遥感图像里目标之间的空间关系非常重要,一个目标是否属于某个类别,有时候需要看它周围的环境。比如一艘船,停靠在港口和航行在开阔海域,对应的检测置信度应该有所区分;一个停车场的车辆簇和散落在野外的车辆,检测策略也不同。直接缩放整张大图虽然保留了全局上下文,但目标太小,局部细节全丢了。
我实测下来,对这种大尺寸输入最有效的方案是"切块推理"(Sliding Window Inference)。把大图按固定尺寸切分成若干瓦片(Tile),每个瓦片独立过模型,最后把预测框映射回原图坐标再合并去重。这样既能保证输入分辨率足够让模型看清目标细节,又不会撑爆显存,后处理阶段再考虑跨瓦片的上下文信息。
2.2 多尺度目标与 YOLOv8 默认锚框/检测头的匹配偏差
YOLOv8 是 anchor-free 的检测器,官方预训练模型在 COCO 上表现很好,默认的三个检测头分别在 stride 8、16、32 的特征图上做预测,对应感受野大致覆盖中小、中大、大目标。但遥感图像的目标尺度分布和 COCO 差异很大。
我用一个实际的例子说明这个问题。在某个遥感数据集上,骨干道路上的车辆平均尺寸在 28×28 像素左右,而大型储油罐平均尺寸在 300×300 像素以上。用 YOLOv8 默认配置训练,小目标(车辆)几乎全靠 stride 8 的特征图(P3)来检测,但 P3 特征图在输入 640×640 时只有 80×80 的网格,分配到每个小目标上的特征点其实很少,特征表达容易被背景干扰。
多尺度问题还有一个容易忽略的维度:模型训练时的尺度扰动策略(Mosaic、随机缩放)对遥感图像并不完全适用。自然图像里目标本身尺度就有先验分布,随机缩放是增强模型尺度鲁棒性的常规手段。但遥感目标的尺度由传感器分辨率决定,随机缩放反而会让模型学习到错误的尺度先验。遥感数据里目标的尺度分布不是连续的,而是由传感器分辨率对应的地面采样距离(GSD, Ground Sample Distance)决定的多模态分布。
2.3 方向任意性:水平框的先天缺陷
方向多变是遥感图像最独特的挑战。一辆车停在路边,在俯视视角下它的最小外接矩形是一个和道路方向对齐的斜框;一艘船靠岸,船头和船尾连线可能与图像水平轴成任意角度。
普通 YOLOv8 输出的是 axis-aligned 水平框(HBB, Horizontal Bounding Box),每个目标用一个 (x_center, y_center, width, height) 四元组表示。这种表示方式对斜向目标非常不友好。举个例子:
一个长 80 像素、宽 20 像素的船,以 60 度角斜放在图像里。它的水平外包框大约是 70×45 像素,而真实目标只占这个框的不到 40% 区域。这意味着检测头的分类分支需要从大量背景像素中分辨出目标类别,回归分支则需要处理加宽后的框,特征对齐自然变差。如果已经有旋转标注数据(OBB, Oriented Bounding Box),直接用 YOLOv8 训练还会因为标注格式的根本不同而无法使用,必须要做针对性处理。
我在项目里用了两条路线并行处理方向问题。一条是推理侧优化,通过数据增强让模型在水平框下也能适应多种方向;另一条是改用支持旋转框检测的 YOLOv8-OBB 变体。两者各有优劣,我会在后面展开讲。
3. 数据准备与预处理策略
3.1 数据标注格式的选择:从 HBB 到 OBB
遥感目标检测数据的标注格式,常见有两种。第一种是水平框 (xmin, ymin, xmax, ymax),纯四个坐标值;第二种是旋转框,一般用 (cx, cy, width, height, angle) 表示,angle 是被检测目标长边与水平轴的夹角。还有数据集用四角点 (x1, y1, x2, y2, x3, y3, x4, y4) 表示,但最终都会归一化为旋转框五参数格式。
如果你拿到的数据是 OBB 标注,但只是想用普通 YOLOv8 快速验证一下,可以把旋转框转换为水平框。转换逻辑很简单:取旋转框四个角点的最小外包矩形。但要注意这个转换是有代价的。目标倾斜角度越大,水平框里的背景比例越高,检测难度随之上升。我统计过一个遥感数据集的情况,当目标倾斜角超过 30 度时,水平框会被撑大 30% 以上,如果不做额外处理,最终精度下降非常明显。
所以数据准备阶段的核心判断是:你的任务需不需要精细的方向信息。如果只需要知道"这里有一架飞机"不关心机头朝向,HBB 够用。如果需要输出目标的航向角、需要精确的形状轮廓用于后续 GIS 分析,那就必须用 OBB。
3.2 大尺寸遥感影像的切片与标注同步
准备训练数据时,遥感大图不能直接整图塞给模型训练,常规做法是先把大图切成瓦片,再对瓦片做标注。通常切片的策略有两种:固定网格切片和滑窗切片。
固定网格切片是把一张大图均匀分成不重叠的若干块。这种方式实现简单,但目标恰好被切在边界上时会丢失标注。滑窗切片则让连续窗口之间有重叠,保证目标不会因为恰好落在边界而左右为难。训练阶段的常用参数是窗口 1024×1024、重叠 200 像素左右。窗口太小会让上下文不足,窗口太大又会在推理时增加计算压力,1024×1024 在训练和推理之间是个不错的平衡点。
切片后的标注信息需要同步转换。如果是 HBB 标注,且目标框完全落在某个切块内,直接保留对应坐标;如果目标框横跨多个切块,原样切割会导致目标残缺。常规做法是丢弃跨块目标,或者保留其落在当前窗口的部分并重新计算坐标。在实际工程中我建议丢弃跨块目标,因为训练时丢几个边界目标无伤大雅,而保留残缺目标反而会污染训练集。
标注同步的代码逻辑不复杂,但细节容易出错。核心点在于坐标偏移计算:切块的左上角在原图中的坐标,以及标注框的坐标减去这个偏移量之后的映射关系。
3.3 像素级预处理:全色锐化、直方图均衡与归一化
遥感图像和普通相机照片的光谱特性差异很大。很多遥感数据集是 4 通道多光谱数据(红、绿、蓝、近红外),或者全色波段加多光谱波段的组合。直接用三通道模型训练需要提前做通道融合,最常见的做法是使用 RGB 三个波段直接输入,或者做全色锐化(Pansharpening,用高分辨率全色波段增强多光谱波段的空间分辨率)。我在几个项目里试过这两种方案,PAN 锐化对视觉小目标确实有提升,但提升幅度不大,属于锦上添花。
归一化策略我建议用全局统计量而不是 ImageNet 预训练用的均值方差。遥感图像的像素分布和自然图像差异明显,植被覆盖多的区域绿色通道均值偏高,水体多的区域蓝色通道均值偏高。用 ImageNet 上统计的 mean=[0.485, 0.456, 0.406] 去归一化遥感图像,会存在分布偏移。实操中可以先计算数据集的整体均值方差,再用于归一化。
一个小技巧:如果目标对比度低、图像整体偏灰,可以先用直方图均衡拉伸后再输入模型。遥感图像动态范围往往很宽,有些区域过曝、有些区域过暗,不做预处理直接训练,模型会花费大量容量去拟合亮度变化。我用 CLAHE(限制对比度自适应直方图均衡)处理过一批低照度图像,mAP 大约有 2~3 个百分点的提升。
3.4 训练时数据增强策略的特殊调整
YOLOv8 默认的训练增强管线包含 Mosaic 拼接、随机仿射变换、HSV 扰动、水平翻转等。在自然图像上这些增强效果很好,但搬到遥感数据上需要小心。
遥感图像是垂直俯视视角,目标方向是任意的,所以水平翻转增强不会像自然图像那样带来额外的方向多样性。更适合的方向增强是旋转增强,包括 90 度整数倍旋转和任意角旋转。90 度倍数的旋转不会改变水平框标注的真实语义,但 45 度旋转后水平框会被撑大,需要重新计算外包框尺寸。
我建议在训练里加入 0-180 度范围内的随机小角度旋转,角度范围控制在正负 30 度以内,配合同步旋转的标注框。角度过大时,框的坐标变换会引入较多数值误差,而且目标物本身在俯视视角下的纹理特征也发生变化。
Mosaic 增强需要调低强度乃至关闭。原因是遥感图像本身已是大尺寸场景,Mosaic 把四张图拼到一起会造成小目标进一步缩小,小目标信息本来就稀缺,再压缩特征就更难提取。实测下来,在遥感数据集上关掉 Mosaic 或者只用 Mosaic4 的轻度版(融合尺度更小),比默认的 Mosaic9 更有收益。
4. 切片推理与全图预测结果合并全流程
4.1 切片推理的优势与场景分析
遥感图像检测不能整图进模型,根本原因是显存和计算量限制以及小目标信息损失。但切片推理也不只是把大图"切小了喂进去"这么简单,关键在于切片之间的重叠度设定和预测结果合并策略。
切片推理的场景分两种。一种是离线推理,比如对已有的遥感影像做目标提取,对实时性没有要求,这时可以牺牲推理速度换取精度上限。另一种是接近实时的推理,常见于无人机平台的在线检测,这时切片窗口设定需要考虑算力约束。
离线推理里,我通常把窗口大小设置为 1024×1024,重叠 200 像素。重叠的意义在于:模型对图像中心区域的目标检测置信度通常高于边缘区域,这是卷积网络天然的中心偏好导致的。如果一个目标恰好落在瓦片边缘,漏检概率会明显增大。重叠 200 像素让边缘区域的目标在相邻瓦片里处于更靠近中心的位置,这样合并时可以采用置信度加权的策略,提高边缘目标召回率。
在线推理场景,计算开销需要控制,通常窗口降低到 640×640 甚至 512×512,重叠度降到 0.15 以内。这样每张瓦片的推理延迟在单张显卡上可以做到显著降低,但边缘漏检率会略有上升。
4.2 基于 Sliding Window 的推理代码实现
切片推理的核心实现并不复杂,我给出可以直接参考的代码框架。关键点在于三个部分:切片循环、坐标映射、结果合并。
import numpy as np import torch from ultralytics import YOLO def sliding_window_inference( model: YOLO, large_image: np.ndarray, window_size: int = 1024, overlap: float = 0.2, conf_thres: float = 0.25, iou_thres: float = 0.5, ): """ 对任意尺寸的遥感大图执行滑窗推理 large_image: HWC 格式,BGR 颜色通道符合 YOLOv8 输入习惯 返回全图坐标系的检测结果列表 """ H, W = large_image.shape[:2] step = int(window_size * (1.0 - overlap)) stride = window_size # 模型输入尺寸 all_predictions = [] # 存放 (x1, y1, x2, y2, score, cls) # 构建行方向的滑动起点 ys = list(range(0, H - window_size, step)) if ys[-1] + window_size < H: ys.append(H - window_size) xs = list(range(0, W - window_size, step)) if xs[-1] + window_size < W: xs.append(W - window_size) for y in ys: for x in xs: window = large_image[y:y + window_size, x:x + window_size] # 推理单窗口 results = model.predict(source=window, conf=conf_thres, iou=iou_thres, verbose=False) boxes = results[0].boxes if boxes is None or len(boxes) == 0: continue # 映射回原图坐标 for box in boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().tolist() score = box.conf[0].cpu().item() cls = int(box.cls[0].cpu().item()) all_predictions.append( [x1 + x, y1 + y, x2 + x, y2 + y, score, cls] ) return np.array(all_predictions)这段代码里需要注意几个细节。
步长计算是关键。重叠度 overlap 和步长的关系是 step = window_size * (1 - overlap),窗口大小 1024、重叠度 0.2 时步长约 819 像素。这样每个窗口重叠的部分会作为边缘目标的兜底检测。
窗口起始点的处理。如果大图尺寸不能被窗口大小整除,尾部剩余部分需要单独补一个窗口,否则图像右下角区域会被漏掉。我看过不少开源实现忽略了这个边界条件。
调用 predict 时的 verbose=False 是必要的,否则每一张瓦片都会往控制台刷一堆日志,几百张瓦片刷下来非常影响可读性。
4.3 多窗口检测结果的 NMS 合并策略
滑窗推理会产出一个问题:同一个目标出现在多个窗口里,会被重复检测。因为重叠区的目标在相邻窗口里都会输出一个框,框的坐标虽然相近但不完全一致,需要对这些重复框做合并去重。
常用的合并策略是全局 NMS(非极大值抑制)。把所有窗口产生的框统一放到同一个坐标系下,按类别分组,对每个类别执行标准 NMS。NMS 的核心逻辑是:按置信度降序排列,取最高置信度的框,抑制掉和它 IoU 超过阈值的所有框;重复执行直到处理完所有候选框。
合并时的 IoU 阈值建议比单图推理时略高一些。单图推理时 NMS 阈值一般设 0.5 左右,但跨窗口的重复框因为窗口裁剪位置不同,坐标会有轻微偏移,IoU 可能在 0.7 以下。我在项目中把跨窗口 NMS 阈值设为 0.55,在去重和保留真实目标之间取了平衡。
还有一个更细致的处理方式:把同一位置的多个框的坐标做加权平均,以置信度为权重。这种方式比单纯丢弃低置信度框更平滑,对目标边缘定位精度有好处。我实际对比过加权融合和普通 NMS,加权融合在 mAP 上大约有 0.5 个百分点的提升。
4.4 切片窗口大小与重叠度的参数选择
窗口大小和重叠度的选择直接影响推理速度和检测精度。我做了几组对照实验,以 5000×5000 的典型遥感大图为样例,结果如下:
| 窗口大小 | 重叠度 | 单图推理时间(秒) | 边缘目标漏检率(相对%) | 小目标召回率(相对%) |
|---|---|---|---|---|
| 512 | 0.2 | 约 18 | 约 25 | 高 |
| 640 | 0.2 | 约 14 | 约 18 | 高 |
| 1024 | 0.2 | 约 9 | 约 8 | 中 |
| 1024 | 0.5 | 约 15 | 约 3 | 高 |
| 2048 | 0.2 | 约 6 | 约 2 | 低 |
小目标召回率的差异来自窗口内目标的绝对大小。512×512 窗口里,一个 40×40 像素的目标占窗口面积的比例高,卷积特征层可以提供更丰富的细节。但窗口小带来的问题是上下文缺失,比如一艘船周边的港口信息无法看到,部分模糊目标的分类置信度下降。
综合来看,如果对精度要求较高,建议 1024 窗口加 0.2 重叠度,再配合后续的 TTA 多尺度增强来弥补小目标问题。如果推理效率更重要,2048 窗口加 0.1 重叠度更划算。
5. YOLOv8 网络结构调整与训练参数优化
5.1 添加 P2 检测头:提升小目标检测能力的结构改动
YOLOv8 默认的检测头有三个,分别基于骨干网络的 P3、P4、P5 特征层,对应 stride 8、16、32。遥感图像中的小目标大量存在,默认的最小 stride 8 特征图在输入 640×640 时,一张 80×80 的特征图对应的原图网格为 8×8 像素。这意味着小于 8×8 像素的目标无法在这层有效检测。
一个直接的解决办法是给模型添加 P2 检测头,对应 stride 4 的特征层。P2 层来自骨干早期,分辨率更高,空间信息更丰富,对小目标非常友好。
我在 YOLOv8 上加入 P2 头的配置如下,修改模型 yaml 文件即可:
# YOLOv8n-p2.yaml 核心骨架 backbone: - [-1, 1, Conv, [64, 3, 2]] # P1/2 - [-1, 1, Conv, [128, 3, 2]] # P2/4 - [-1, 3, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] # P3/8 - [-1, 6, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] # P4/16 - [-1, 6, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # P5/32 - [-1, 3, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 6], 1, Concat, [1]] # P5上采样后与P4融合 - [-1, 3, C2f, [512]] - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 4], 1, Concat, [1]] # 与P3融合 - [-1, 3, C2f, [256]] - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 2], 1, Concat, [1]] # 额外上采样一层,与P2融合 - [-1, 3, C2f, [128]] - [-1, 1, Detect, [nc, [128, 256, 512, 1024]]] # 四个检测头注意检测头配置从三个变成了四个,对应四个特征层 [P2, P3, P4, P5],输出尺寸分别是 stride 4、8、16、32。
加了 P2 头之后,计算量会显著增加,因为 P2 特征图尺寸大,检测头部分的 FLOPs 会成倍增长。我实测在 640×640 输入下,YOLOv8n 加 P2 后单次推理耗时从约 12ms 增加到约 30ms,但小目标召回率提升了大约 10%。这个权衡在多数遥感场景是值得的。
5.2 多尺度训练与推断(Multi-Scale Training/TTA)
遥感图像的尺度变化范围广,训练时固定输入 640×640 会让模型只适应特定范围的绝对目标尺寸。多尺度训练是提升尺度鲁棒性的有效手段。
YOLOv8 的本地训练接口支持 --rect 参数做矩形训练,但遥感场景更常用的是多尺度训练。训练时将输入图像缩放到多个固定尺寸,比如 512、640、768、896、1024,每个 epoch 随机选一个尺度。这种方法关键是确保目标不因缩放过度而丢失——一批输入尺寸中,最小尺寸不宜低于 512,否则部分小目标在特征图上只剩 1~2 个像素。
推理阶段可以配合 TTA(Test-Time Augmentation)。YOLOv8 原生支持 --augment 推理参数,内置了多尺度翻转增强。在遥感大图上用 TTA,会把算力开销放大数倍,我建议只在窗口推理时把整张图的尺度固定在一个值,对不同瓦片之间不独立做尺度变化。
5.3 损失函数细节与正负样本策略调整
YOLOv8 的损失函数由分类损失(BCE)、回归损失(CIoU)和 DFL 损失三者组成。在遥感目标检测里,类别不平衡问题非常突出,背景区域占绝对主导,目标类别间数量也可能相差数十倍。
我遇到的一个典型问题是车辆类别样本几万个,但油罐类别只有几百个。这时模型训练后期 [分类损失 + 回归损失] 中分类损失被车辆类主导,油罐类几乎学不到。解决方案有两个方向:
一个是调整 loss 权重。YOLOv8 的损失权重可以通过hpy文件或代码传入,多类别严重不平衡时,把分类损失的权重适度调低,让回归分支更多关注目标位置。另一个是给稀疏类别做欠采样或者给密集类别做降采样。最直接的做法是按类别添加 loss 权重,但这需要改训练代码。
正负样本分配方面,YOLOv8 默认使用 Task-Aligned Assigner(TAL)策略,动态匹配每个目标的样本数量。遥感小目标密度高,一个特征点周围可能有多个 GT 框,默认策略可能漏掉部分目标。对于小目标密集的场景,调大 topk 参数可以有效增加正样本覆盖率。我在实验中把 topk 从 13 提升到 20,小目标的召回率提升了约 4%,代价是训练时间略微增加。
5.4 实验对比:不同配置在遥感数据集上的效果
我选取一个典型的遥感数据集(包含船只、车辆、飞机、油罐、桥梁五类),在统一训练条件下对比几种配置:
| 配置 | 输入尺寸 | 检测头 | 数据增强策略 | mAP@0.5 | 小目标(面积<32²像素)AP |
|---|---|---|---|---|---|
| YOLOv8n 基线 | 640×640 | P3/P4/P5 | 默认(含Mosaic) | 0.78 | 0.41 |
| YOLOv8n + 切片训练 | 1024×1024 | P3/P4/P5 | 关闭Mosaic,加旋转增强 | 0.85 | 0.53 |
| YOLOv8n + P2头 + 切片训练 | 1024×1024 | P2/P3/P4/P5 | 关闭Mosaic,加旋转增强 | 0.87 | 0.61 |
| YOLOv8s + P2头 + TTA | 1024×1024 | P2/P3/P4/P5 | 关闭Mosaic,加旋转增强 | 0.88 | 0.63 |
从数据里可以看到三个结论:一是直接放大训练输入尺寸带来的收益最明显,mAP 从 0.78 涨到 0.85;二是在高分辨率输入基础上加 P2 头,小目标 AP 从 0.53 涨到 0.61,说明两者是互补的;三是模型规模从 n 换到 s 对整体精度提升有限,说明瓶颈更多在输入分辨率和结构适配,而非模型容量。
6. 方向多变场景的进阶处理
6.1 数据增强模拟多个旋转方向
如果是普通 YOLOv8(HBB),方向多变问题最直接的攻法思路,是让训练数据覆盖更多可能的角度。具体实现可以有三种做法。
第一种是 90 度整数倍旋转增强。把每张训练图旋转 0 度、90 度、180 度、270 度,标注框对应做旋转更新。这种做法不会影响水平框标注的准确性,因为旋转 90 度倍数后,宽和高的关系被准确映射。
第二种是随机小角度旋转,比如正负 30 度范围内随机旋转。这样能模拟更多中间角度,但旋转后水平框会变大,坐标要重新计算。实现时要注意用 OpenCV 的getRotationMatrix2D和warpAffine做图像旋转,同时把边界框坐标也用同一个变换矩阵做投影。
第三种是全角度旋转增强。理论上是覆盖 0-180 度任意角,但水平框表示下,全角度旋转会让每一个目标的外包框显著大于目标本身,导致回归目标变差。实际项目中,我建议小目标(面积小于 32×32)不做全角旋转,只做 90 度倍数增强,这样小目标细长特征不容易被破坏。
6.2 旋转框检测:YOLOv8-OBB 的原理与配置
如果任务需要精确输出目标朝向,模型必须输出旋转框。Ultralytics 在 YOLOv8 官方仓库中提供了 OBB 支持,基于旋转框的检测头输出五参数 (cx, cy, w, h, angle)。
YOLOv8-OBB 的标注格式是八个坐标点(四角点),对应一个旋转框。数据准备阶段需要把标注从 HBB 或斜框格式统一转换成八个角点坐标保存。OBB 检测头的输出层比普通 HBB 多了一个角度预测分支,损失函数也在回归分支里加入角度损失的考量。
使用 OBB 的代价是推理和 NMS 都会变复杂。普通水平框 NMS 用 IoU 计算很方便,旋转框的 IoU 计算则要计算两个旋转多边形的交集面积。Ultralytics 内部实现了旋转框的 IoU 计算逻辑,但其速度比水平框慢不少。我在 CPU 上测试过一个 80×80 的特征图对应旋转框 NMS 的时间开销,大约比水平框慢 6 到 8 倍。
在实际项目中,我通常根据需求决定走哪条路线:
| 需求 | 推荐路线 | 理由 |
|---|---|---|
| 只需要知道目标位置/类别 | HBB + YOLOv8 | 实现简单,推理快,标注更容易获取 |
| 需要知道目标朝向/精确轮廓 | OBB + YOLOv8-OBB | 角度信息是硬需求,OBB 才能输出 |
| 目标细长、倾斜角度对检测影响大 | OBB 或 HBB+大量旋转增强 | 具体看标注数据是否支持 |
6.3 旋转框的评估指标与可视化注意点
OBB 模型的评估指标比 HBB 多了一个角度误差维度。除了常用的 mAP@0.5,我在项目中还会单独统计角度误差(Angle Error,预测框角度和 GT 框角度的平均绝对差)。角度误差能直接反映方向预测的准确性,mAP 只看框重叠率,对角度不敏感,因为角度差 20 度但有足够重叠时 IoU 依然可能超过 0.5。
可视化时,OBB 框不能直接用 OpenCV 的rectangle画,要用polylines画四边形轮廓。调试阶段我习惯同时画预测框的角点标记和中轴线,这样能快速发现角度预测的系统性偏移,比如某种朝向的框角度普遍偏大了 5 度。
7. 常见问题与排查实录
7.1 切块推理时边缘目标漏检率高
症状:窗口推理的结果里,靠近窗口边界的目标漏检比例明显偏高,尤其在大目标旁边的小目标。
原因:模型训练时的标签分配机制倾向于特征图中心位置的目标。窗口边缘区域,卷积感受野不足,特征被截断,模型预测置信度下降,部分目标被 NMS 抑制掉。
排查步骤:先可视化单窗口推理的输出框,观察是否目标置信度明显低于中心区域目标;再检查 NMS 阈值是否设置过高,跨窗口合并时可能导致边缘低置信度框被抑制。
解决办法:第一优先调高窗口重叠度,把重叠从 0.1 提到 0.3 甚至 0.5;第二是对重叠区域做置信度加权,当一个目标同时出现在多个窗口时,取置信度最高的框作为最终结果,而不是简单 NMS;第三是后处理阶段针对概率在置信度阈值以下但接近阈值的候选框做二次确认,利用相邻窗口的重复检测互相投票。
7.2 小目标检测效果差但大目标表现正常
症状:模型对大型油罐、桥梁、机场等大目标检测精度高,但车辆、小船等小目标漏检严重。
原因:最根本的问题是输入分辨率和特征层下采样次数。小目标在默认 640×640 输入下经过 5 次下采样,在 P3 特征图上只剩很小区域;再加上锚框(anchor-free 的中心点分配)对小目标的匹配策略不如大目标稳定。
解决办法:把训练和推理输入分辨率提高到 1024×1024 或 1280×1280(前提是显存足够);添加 P2 检测头,增加 stride 4 的特征层用于小目标预测;关闭过强的 Mosaic 增强,避免小目标被进一步缩小;正负样本分配时调大 topk 参数,让每个小目标覆盖更多候选正样本。
7.3 训练时损失震荡不收敛
症状:训练曲线前期快速下降后迅速进入平台期,损失在局部范围内来回震荡,验证集 mAP 基本不再上升。
原因:遥感数据里的类别极不均衡,且图像尺度差异过大。个别 epoch 随机采样到的高尺度图让模型参数大步调整,下一 epoch 又遇到低尺度图,模型来回适应,造成震荡。
解决办法:一是使用余弦退火学习率调度器,同时在训练中期冻结骨干网络参数,只微调检测头;二是对训练图像按照目标尺度和类别分布做均匀采样,保证每个 epoch 的数据分布稳定;三是调低初始学习率,我在遥感场景通常把 YOLOv8 默认 lr0=0.01 降到 0.003。
7.4 推理时 GPU 显存不足
症状:1080Ti 8G 显存跑 1024×1024 窗口推理时 OOM。
解决办法:降低窗口大小到 640×640;开启半精度 FP16 推理;分批推理而非单张连续推;如果 CPU 内存足够,图形过大时先用 Python 端切块,再逐块送 GPU。
7.5 数据量不足导致 OBB 模型过拟合
症状:OBB 模型在训练集上接近满分,验证集 mAP 低 10 到 15 个点。
解决办法:OBB 模型的参数空间比 HBB 更大,但表现在角度预测分支,更容易过拟合。针对方向问题,可以做固定角度扰动增强,在标注角度上随机加减 5 到 10 度,强迫模型学习角度不敏感特征;也可以把骨干网络换成更深的预训练模型进行迁移学习,而不是随机初始化从头训练。
8. 实操心得与工程优化建议
8.1 工程上值得优先投入的方向
在遥感目标检测项目里,投入产出比最高的环节是数据后处理(切片推理与合并)和输入分辨率调整。这两项通常能让 mAP 提升 5 个点以上,而代价只是推理时间增加。相比之下,更换更大的骨干网络或者堆更复杂的注意力模块,收益往往没有前两项大。
从实际工程角度看,遥感目标检测的部署和常规检测的部署差别很大。通常需要同步考虑瓦片坐标系映射、地理坐标转换、结果导出到 GIS 软件,这些工程环节虽然不属于模型训练本身,但直接影响用户是否认可你的检测结果。
8.2 开源工具与代码库的推荐
除了 Ultralytics YOLOv8 官方仓库之外,建议关注遥感目标检测领域常用的辅助工具。SAHI 是一个优秀的切片推理库,专为大尺寸图像设计,提供了切图、推理、合并的完整流水线,还集成了多种后处理策略,直接pip install sahi就能用。
数据可视化上,用 Ultralytics 自带的预测结果绘制函数很快,但对大规模瓦片级推理,建议写一个自定义的批量可视化脚本,用地理信息库或普通 PIL 把多个瓦片的检测框拼回完整大图,更有全局观。
8.3 后续可能的扩展方向
这个项目后续可以做的方向包括:把检测头和遥感图像金字塔结合起来做尺度自适应推理,而不是固定窗口大小;基于 YOLOv8-OBB 扩展做目标的航向角感知跟踪,用于视频遥感目标检测;引入半监督学习,利用大量没有标注的遥感影像做自监督预训练,缓解标注稀缺的问题。
我个人更推荐先把 P2 检测头和滑窗推理这一套流程在多个不同传感器(光学、SAR)的数据上验证一通,因为不同传感器的尺度分布和噪声特性差异很大,能逼你把流程做得更通用。再往下做,如果对实时性有要求,可以考虑把滑窗推理改成流式处理,边接收影像边检测,工程复杂度会上升不少,但完成后很有成就感。