做卫星图像目标检测久了,你会发现一件特别拧巴的事:目标在图像里明明是斜着停的,普通检测网络却非要给它套一个正矩形框。前阵子我在处理船舶和储油罐识别任务时,把YOLOv11强行拉上旋转目标检测的战线,折腾了几天后,最大的感受是——YOLOv11本身并不难改,真正容易翻车的地方,全都在“特殊坐标系”和“旋转框怎么表示、怎么算、怎么回归”这些细节上。
这篇文章不聊虚的,直接把我对旋转目标检测在卫星影像里的理解、YOLOv11的改造思路、框表示方法的完整约定,以及实际训练中踩过的坑全部写下来。适合手里正好有遥感数据集、想用YOLOv11做旋转检测的人参考,也适合还没见过旋转框的新手快速上手。
1. 为什么卫星图像目标检测绕不开旋转框
先说一个最基础的认知:遥感图像和普通自然图像最大的区别,不是分辨率更高,而是拍摄视角是俯视的。俯视意味着目标的朝向完全自由,船头朝哪、飞机停成什么角度、储油罐怎么排列,都和地面上的“水平世界”没有必然关系。这时候如果还用传统的水平框,问题就来了。
1.1 水平框在遥感场景中的三个硬伤
水平框,也就是Axis-Aligned Bounding Box,始终假设目标的长宽方向与图像坐标轴平行。在遥感图里,这个假设基本不成立,后果体现在三个层面:
第一个硬伤是目标框严重冗余。一艘斜着的货轮,长边可能有200多米,短边只有20多米,水平框为了框住斜着的轮廓,不得不在四个角落各留出一大片空白。这些空白区域在训练时会被当作目标内容参与特征提取,实际上却混进了大量海面纹理、波浪噪声,导致特征不干净。
第二个硬伤是重叠框导致NMS失效。卫星图里停靠的船只往往并排靠得很近,水平框计算IoU时会把本来没有交叠的目标算成高度重叠,后处理阶段很容易把两条并排的船压成一个检测结果,严重拉低召回率。逻辑上很简单:两张斜着交错的长条目标,水平框的重叠面积可能比它们真实相交的区域大好几倍。
第三个硬伤是mAP评估失真。评测指标里IoU阈值一卡,水平框和真值旋转框的匹配度天然偏低,模型哪怕定位很准,最后的AP也提不上去。换句话说,用水平框做遥感检测,是在用错误的评测语言去衡量模型的真实能力。
1.2 旋转框到底能带来多少收益
旋转框,也叫Oriented Bounding Box,本质是在水平框的基础上多了一个角度维度,让框能够贴合目标的真实朝向。它的收益不是锦上添花,而是实打实的指标提升。
我试过一个中等规模的船舶识别场景,把原来的水平框检测头直接改成旋转框检测头,其余数据增强策略保持基本一致。同样的训练轮数验证下来,mAP50 涨了4个点左右,mAP75 涨了接近10个点。mAP75提升更明显的原因很好理解:高IoU区间对定位精度敏感,水平框的长边误差在小目标上非常致命,而旋转框只要角度回归得准,定位精度就会大幅度提高。
此外,旋转框在后续的量测、统计和轨迹关联任务里也有直接价值。比如计算船舶长度、估算油罐容量,都需要知道真实朝向和长边尺寸,水平框算出来的物理尺寸是错的。
1.3 为什么是在YOLOv11上改造而不是直接用专用框架
旋转目标检测领域其实已经有专门的算法,比如基于经典两阶段检测器的旋转分支方案,或者基于Transformer的端到端方案。但我在实际项目里还是选择了YOLOv11,原因是多方面的。
首先是工程生态。YOLOv11的推理效率在边缘设备和服务器端都经过大量验证,导出、量化、部署的链路很成熟。卫星影像项目后期往往要跑大批量数据,推理速度直接决定处理耗时,这一点YOLOv系列有明显优势。
其次是改造难度。YOLOv11的检测头本质上还是anchor-free的解耦头,输出分类和框回归两部分。旋转检测只不过是把回归分支从4个参数改成5个参数,网络结构层面的改动非常小,真正的复杂度集中在坐标系定义、损失函数和后处理这三块,而这三块恰好是我们可以单独控制、逐步验证的。
当然,如果项目只看重精度、完全不关心耗时,专用旋转检测框架也值得考虑。但像我这种既要快速验证又要能部署上线的场景,YOLOv11就是更舒服的底座。
2. 旋转框的特殊坐标系:从数学坐标系到图像坐标系的约定
标题里说“特殊坐标系”,这个词听起来玄乎,但它的本质非常简单:旋转框需要一个额外的角度来描述朝向,而角度的定义方式、取值区间、旋转方向,以及和像素坐标系的配合,必须在整个训练链路里严格统一。任何一个环节的约定不一致,模型就会学到完全错误的东西。
2.1 像素坐标系的真实含义
做自然图像检测时,很多人不会去刻意关注坐标系,因为水平框的表示不依赖角度,x和y自然就是图像里的行列位置。但旋转框一出现,坐标系就必须拿到台面上来。
图像坐标系里,原点通常在左上角,x轴向右,y轴向下。这和数学课本里的笛卡尔坐标系唯一的区别就是y轴方向是反的。看似一个小区别,实际影响非常大。
假设你在数学坐标系里定义“逆时针为正角度”,那么在图像坐标系里,同样的视觉旋转方向,用数学公式算出来的角度符号可能正好相反。更麻烦的是,paddle、OpenCV、某些数据标注工具对角度方向的约定都不一样。有的约定以x轴正方向为起点,角度沿顺时针为正;有的则相反。如果你从网上巴拉了一个标注好的旋转框数据集,结果没检查它的角度约定,训练出来的模型在推理阶段就会全线翻车。
我统一采用的约定是:旋转角度 θ 表示从x轴正方向起,按照图像坐标系的顺时针方向转到目标长边方向的角度,取值范围固定为 [-90°, 90°)。这个约定在工程实现上比较常见,也容易换算。
2.2 五参数表示法与八参数四点法的取舍
旋转框在工程上主要有两种表示方法,第一种是五参数法,也叫长边法,用 (cx, cy, w, h, θ) 表示。cx和cy是中心点坐标,w是长边长度,h是短边长度,θ是长边的朝向角。
第二种是八参数法,直接存储四个角点的坐标 (x1, y1, x2, y2, x3, y3, x4, y4)。两者的对比如下面这张表:
| 对比项 | 五参数法 | 八参数四点法 |
|---|---|---|
| 参数数量 | 5个 | 8个 |
| 角度周期性问题 | 存在,需特殊处理 | 天然回避 |
| 回归难度 | 较低,可加角度分支 | 较高,不便于设定回归目标 |
| 标注格式转换 | 方便 | 方便 |
| 损失函数选择 | 灵活,可用旋转IoU | 多用L1或smooth-L1四点回归 |
| 后处理复杂度 | 低 | 稍高 |
实际训练下来,我的建议是回归分支用五参数法,因为网络直接回归角度比回归四个角点更符合人对旋转框的理解,也方便做角度周期性的修正。但数据集标注文件里的原始格式,大概率是八参数法,也就是标注软件导出四个角点坐标,需要在加载数据时做一个格式转换。
2.3 角点标注与五参数格式的转换代码
这里给出一个可以直接用的角点转五参数的核心逻辑。输入是四个角点坐标,输出是 (cx, cy, w, h, θ)。思路是先通过外接矩形求中心,再找出最长边,然后计算长边与x轴的夹角。
import math def vertices_to_obb_params(points): xs = [p[0] for p in points] ys = [p[1] for p in points] cx = sum(xs) / 4 cy = sum(ys) / 4 edges = [] for i in range(4): x1, y1 = points[i] x2, y2 = points[(i + 1) % 4] length = math.hypot(x2 - x1, y2 - y1) edges.append((length, (x1, y1), (x2, y2))) edge = max(edges, key=lambda e: e[0]) (x1, y1), (x2, y2) = edge[1], edge[2] dx = x2 - x1 dy = y2 - y1 theta = math.degrees(math.atan2(dy, dx)) # 统一到 [-90, 90) if theta >= 90: theta -= 180 elif theta < -90: theta += 180 # 长边与短边谁长,需要根据角度修正后的方向重新判断 w = edge[0] h = math.sqrt((points[0][0] - points[3][0]) ** 2 + (points[0][1] - points[3][1]) ** 2) return cx, cy, w, h, theta这段代码里最值得注意的是最后一轮角度归一化。目标的长边方向是不定的,如果直接用atan2算出来的角度在90度附近,模型回归时会遇到角度跳变。统一到[-90, 90)之后,遇到边接近垂直的情况,长边和短边也要相应交换,否则w和h的语义就反了。实际操作中,这个“角度归一化+长边校正”的逻辑建议封装在数据集类里,在训练前把所有标注统一过一遍,后续不需要再去想角度约定的事。
3. YOLOv11适配旋转检测的关键改造
YOLOv11的模型结构本身不用大改,网络骨架继续用原来的,特征金字塔也保持不变,真正需要动手术的是三个地方:检测头的输出通道、损失函数的计算方式、后处理NMS的IoU逻辑。这三块改完,一个可用的旋转检测YOLOv11就立起来了。
3.1 检测头的输出通道改造
YOLOv11的检测头是解耦的,分类分支和回归分支分开输出。对于普通水平检测,回归分支每个位置输出4个值:中心点偏移、宽和高。旋转检测只需要在这个基础上把4改成5,多加一个角度输出。
具体来说,以某个特征层为例,假设类别数是C,anchor-free设置下每个anchor位置需要输出的通道数为:
- 分类分支:C
- 回归分支:5
也就是把最终的卷积层输出通道改成 C + 5,不需要额外的注意力模块或者复杂结构来“容纳”角度信息,直接让网络在特征上回归 θ 的数值就行。之所以可以这么简单,是因为YOLOv11检测头本身有足够的表达能力,旋转目标在特征图上的差异主要靠足够强的监督信号来约束。
当然,有些项目追求极致性能时会加一个deg分支专门预测角度,让角度回归的权重独立可调。这也是可行的,只是结构稍微复杂一点。我的建议是先把基础5参数回归跑通,再根据结果决定是否拆分支。
3.2 损失函数为什么不能继续用CIoU
YOLOv11原来的回归损失用的是IoU系列损失,在水平框上表现非常稳定,CIoU更是综合了重叠度、中心点距离和纵横比。但到了旋转框,这套东西不能直接套,原因很直接:
CIoU里的IoU计算基础是水平矩形的交集面积,旋转框的交集是两个旋转多边形的交集,求解过程复杂,而且不是处处可导。你让网络回传梯度时,遇到多边形重叠面积的导数计算,几乎全是精度损失和训练不稳定。
现阶段工程上用的比较多的是KLD和GWD这一类基于高斯分布的损失。它们的核心思想是:把旋转框映射成二维高斯分布,用分布之间的散度或距离来描述框的差异,从而绕开多边形交并比不可导的问题。
以KLD为例,旋转框的五参数中心点、长边、短边、角度,会被转换成一个二维高斯分布的均值向量和协方差矩阵。两个框的匹配程度,用Kullback-Leibler散度来度量,散度越小说明框越接近。这个损失的好处是角度参与到了一整条计算链路里,梯度能够顺畅地回流到角度回归分支。
在YOLOv11里接入rotation_loss并不复杂,但要注意和分类损失、置信度损失的权重搭配。我常用的组合是分类损失用BCE,回归损失用KLD,两者比例最初按1:1跑,如果回归项收敛太慢,再把KLD的权重适当调大。
3.3 后处理阶段用旋转IoU替代普通IoU
哪怕回归损失算得分毫不差,如果后处理阶段还用普通水平IoU做NMS,前面所有努力都会白费。因为NMS要根据预测框之间的重叠程度删掉冗余检测,旋转框在图像里是斜的,水平IoU根本不能反映它们的真实重叠关系。
旋转IoU的计算比水平IoU复杂不少,核心思路是把两个旋转多边形做区域分解,计算交集多边形,再求交并比。工程实现不需要自己从头写,直接使用某个第三方实现的旋转IoU轮子即可,但要确认API输入是五参数法还是角点法,务必和训练时的格式保持一致。
换上旋转NMS之后,并排停靠船只的漏检问题会明显缓解。我实测过一个场景,某处港口有七八条船并排,水平NMS直接压掉了一半,旋转NMS则能保留绝大部分。
4. 实操复现:从数据集准备到模型收敛
理论讲再多,不动手永远学不会。下面把这个项目的完整实操流程拆开,从数据准备到训练配置,再到大图推理,每一步都给出可以直接照用的经验和参数。
4.1 数据准备与训练集构造
卫星影像和普通图像在数据准备上的最大区别是:影像通常非常大,动辄几千乘几千像素,而目标的尺寸相对很小。如果直接把整张图喂给网络,目标在缩略图里几乎不可见,检测效果会非常差。
我采用的做法是先做切片预处理。把原始大图按照设定步长切成固定大小的patch,比如1024×1024,然后只在包含目标的patch上做训练。步长不能等于切片大小,否则目标会被切碎,一般用重叠步长,比如1024大小、512步长,保证目标跨切片时至少有一个完整版本落在patch里。
数据集格式上,我给每个patch存储一个标签文件,内容格式是 class_index cx cy w h angle。读取时直接解析成五参数,再通过数据增强模块做随机旋转、翻转和亮度调整。有一点要特别提醒:随机旋转增强时,目标的角度标注也必须跟着旋转,如果忘了同步,模型会在角度上学习到大量矛盾信息。
4.2 训练参数与收敛技巧
训练配置我推荐直接仿照下面这一组,作为第一版跑通基线:
- 输入分辨率:1024×1024
- 优化器:AdamW,初始学习率 0.001
- 权重衰减:0.0005
- 批大小:8(根据显存调整,最小也要4)
- 训练轮数:200轮
- 学习率调度:前5轮warmup,后接cosine退火
- 数据增强:Mosaic、随机亮度对比度、随机旋转 45度倍数、随机翻转
这组参数在中等规模数据集上通常能稳定收敛。值得单独强调的是学习率。旋转框的角度分支比水平框回归更敏感,学习率太大会出现振荡,表现就是loss曲线在某个区间来回抖动。遇到这种情况,把初始学习率降到0.0005,或者把角度分支的loss权重暂时调小,效果立刻就能改善。
另一个容易忽略的点是标签平滑。遥感场景中目标密集、小目标多,分类分支过拟合的风险高,我给分类损失加上0.01的标签平滑之后,验证集mAP有小幅稳定提升。
4.3 推理阶段的大图切片与TTA
训练时吃的是1024切片,推理时面对的是原始大图,不能直接把整张图喂进去。要采用滑动窗口推理,窗口大小和训练一致,步长适当调大一点,比如1024窗口、768步长,每个切片独立推理,再根据目标中心点坐标把结果映射回原图坐标系。
映射完成之后还缺一步:距离边界太近的目标可能被切掉一半,所以我会对全图做一次TTA,也就是把整张大图翻转一次再推理一轮,然后把两轮结果合并,再做旋转NMS。这种方式能有效提升边界目标的召回率,代价只是推理时间翻倍。卫星图像项目一般对离线处理的时间容忍度比较高,所以这个代价值得付。
实测下来,加入TTA后,模型的mAP大约能再提升1到2个点,尤其是细长目标,改善比较明显。
5. 常见问题排查与调参实录
这部分是我最想写的,也是我觉得对这些项目最有价值的一部分。因为我见过的很多问题,不是理论搞不懂,而是训练时遇到了奇怪现象,却不知道去哪个方向排查。
5.1 角度回归震荡、损失不下降
症状是loss在训练前期下降正常,跑了二三十轮之后开始原地抖动,甚至周期性反弹。最常见的诱因是角度周期性没有处理好。
角度预测按[-90, 90)取值,但目标真值可能接近90度或-90度的边界。比如真值是89度,预测值跑到-89度,数值上差了178度,但实际框的朝向只差1度。这种巨大的数值差会让损失函数爆炸,梯度更新方向混乱,表现为loss抖动。
解决方法是把角度损失做周期化处理。在计算角度差时先做归一化,把差值映射回[-90, 90)的范围内,再参与损失计算。这一点在所有旋转目标检测模型里都是必修课。KLD等高斯分布损失天然带周期容错能力,但如果用的是普通L1损失,必须手动处理。
5.2 细长目标翻转与长边歧义
这个问题的典型表现是:模型预测的框位置很准,但长边和短边经常对调,也就是角度预测恰好差了90度。比如一条水平放置的船,有时候预测成垂直方向,然后长边变成原来的短边。
根本原因在于五参数法对长边语义的依赖。如果目标的真实长边和短边长度接近,或者标注格式在转换时没有统一长边方向,网络就学到的信息是矛盾的。我在对比实验里发现,同样是五参数法,如果训练数据里长边总是保持长边语义,收敛速度会明显快于长边短边混乱的数据。
一个务实做法是检查数据预处理里的最小外接矩形计算,确保转出来的w总大于h。如果某个目标的长边和短边本来就接近,这里可以再加一点点判断,把角度归一化到你定义的区间。不要小看这个细节,它直接影响角度分支的收敛难度。
5.3 小目标漏检与误检
卫星图像里的小目标太常见了,几十米长的储油罐在1024分辨率的patch里可能只有几十个像素。Mosaic增强虽然能增加小目标的多样性,但对分辨率没有帮助。
我的调试经验是三层递进:先检查标签文件,看小目标在标注里是不是被切碎或者漏标;其次把训练切片的分辨率从1024提到1280,网络对小目标的感知会强不少;最后再考虑加小目标专用数据增强,比如在训练时对小目标区域做随机裁剪放大后贴回原图。实测下来,单纯把分辨率提升这一步,对小目标AP的提升就已经非常可观。
5.4 几个值得长期保留的调试经验
最后把踩坑过程中总结出的调试经验整理成一张速查表,方便后续项目直接检索。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| mAP正常但角度全偏 | 推理时没有做角度归一化 | 检查预测输出的角度是否越界 |
| 训练loss波动大 | 学习率过大或角度周期处理缺失 | 降低学习率,检查角度差归一化 |
| 并排目标被吞 | 后处理还在用水平IoU | 切换旋转NMS |
| 小目标几乎检不到 | 输入分辨率不够 | 提升切片分辨率,用步长重叠切片 |
| 预测框长宽互换 | 数据标签长边语义不统一 | 统一w>h,修正角度范围 |
| 高校验证集偏低 | 切片时目标被切碎 | 缩小切片步长或扩大重叠区域 |
说到这,我想把最后一个小技巧单独强调一下,因为我在后续几个项目里都在用。调旋转框模型时,训练日志里除了记录loss,建议额外记录一个指标:预测框和真值框之间的角度绝对误差。这个误差可以帮你迅速区分问题出在回归分支还是后处理环节。如果loss很低但角度误差很大,基本可以判断是角度周期归一化出了问题;如果角度误差很小但mAP低,大概率是NMS阶段或者评测阶段前后处理不一致。加了这个小指标之后,模型定位问题的速度会快很多,这也是我个人最受益的一个调试习惯。