我把大部分时间压在了一场算法挑战赛上,赛题是大规模SAR图像多类别有向目标检测。说白了,就是给模型一张超大的雷达遥感图,让它把船、飞机、储油罐、车辆这类目标都找出来,而且每个框都得带旋转角度,不能像普通检测那样只给一个水平矩形。跟常规的检测比赛相比,这个赛题在数据理解、模型选型和训练细节上完全是另一种玩法,我前期踩了不少坑,后面把整套流程梳理顺了,成绩才慢慢提上来。这篇复盘主要讲我自己的比赛打法,从数据、模型到训练细节都有涉及,适合已经有一点目标检测基础、但还没碰过SAR和旋转框的同学,也适合准备参加类似赛事的人提前避避雷。
1. 赛题拆解与核心难点
1.1 SAR图像到底特殊在哪
SAR全称是合成孔径雷达,它通过主动发射微波脉冲并接收地面回波来成像。跟光学相机完全不同,SAR不依赖太阳光照,也不怕云层遮挡,所以理论上可以全天时、全天候工作。这个特点让SAR在遥感、海事监测、灾害评估这些场景里特别吃香。
但代价就是图像质量比较“反直觉”。SAR图像里到处都是颗粒状的斑点噪声,这是相干成像引起的固有现象;目标边缘往往很模糊,还伴随一定的几何畸变,比如强反射目标旁边会出现十字亮线或阴影。这就导致同一个目标在SAR图上和光学图上的视觉特征差异极大。你如果直接把在光学数据上预训练好的模型拿过来用,特征分布完全对不上,效果会非常差。
我第一次看到训练集的时候甚至怀疑数据是不是坏了。后来习惯了才发现,SAR图里一个船可能就是一个亮斑加一点拖尾,一个储油罐通常是圆环加中心亮点,跟自然图像里的视觉语义完全是两码事。所以做这个赛题的人,第一步不是调模型,而是先让自己“看懂”SAR图。
1.2 多类别和有向检测的难点
这次赛题全称里有两个关键词:多类别、有向目标检测。多类别意味着数据集中有若干类目标,而且类别之间数量往往很不均衡。有的类可能有几万个框,有的类只有几百个框。如果不管这个长尾分布,模型很容易把稀有类别全部学成背景。
有向检测又比普通检测难一档。普通检测的标签是[x1, y1, x2, y2],只回归四个值;有向检测常用五参数定义[x, y, w, h, theta],多了一个角度。这个角度不是随便预测的,它决定了框怎么贴目标。比如一条斜着停靠的船,水平框会框进大量水面背景,旋转框就能严丝合缝地把船身包住,这对后续的分类和定位精度提升非常明显。
角度回归的难点在于,它是一个周期变量。学术一点的说法就是theta和theta + 180是同一个朝向,但数值差了180,如果用普通的L1损失去算,模型会被错误的大loss拉偏。这是很多新手最容易崩溃的地方,后面我会专门讲怎么处理。
整体来看,这个赛题的难点可以总结成三句话:图像噪声大,目标边界难定;类别分布长尾,小目标密集;角度回归存在周期性和边界跳变,损失函数和编解码都要专门适配。
2. 数据准备与预处理
2.1 先做数据侦察,千万不要上来就训练
比赛拿到数据后,我第一件事不是写模型,而是做数据侦察。这个环节看着不起眼,但对后续所有决策都有决定性影响。
具体来说,我会写几个简单的统计脚本,把下面这些信息全列出来:
- 每个类别的目标总数、每个类别有多少张图包含目标;
- 所有标注框的面积分布,特别是中位数和最小值;
- 所有标注框的宽高比分布,以及角度分布是否均匀;
- 每张图的尺寸范围,目标大多在什么尺度区间;
- 训练集和验证集之间是否存在目标类别偏差。
这些统计结果直接决定了你的预处理策略。比如我发现数据集里的“车辆”类别数量极少,只有几百个框,而“船舶”有上万框。这种情况下如果不做类别重采样,模型在验证集上对车辆类的AP会非常难看。另外角度分布统计也很重要,如果数据里大多数框的角度集中在某个区间,比如都接近水平,那么模型对斜目标的泛化就会很差。
我习惯把统计结果画成图表,类别分布用柱状图,角度分布用极坐标玫瑰图,目标尺度用散点图。这个过程不用很复杂,Matplotlib几行代码的事,但能让你对数据有一个全局认识。很多同学拿到数据直接就开训,训完发现某类别AP是0,回头查才看到那个类别总共就出现过几次,纯属浪费时间。
2.2 数据清洗与标注修正
竞赛数据集的标注质量通常比真实业务数据好一些,但依然有大量“脏”标注。SAR图像里目标轮廓模糊,标注员看着费劲,就会出现下面这些典型问题:
- 框的某个顶点坐标超出图像边界;
- 框的宽或高为零,或者宽高比极其离谱;
- 角度定义不统一,有的框朝左,有的框朝右,实际是同一个目标;
- 严重漏标的小目标,特别是在密集的港口区域。
我的建议是写一个离线检查脚本,把这些异常都筛出来,能修的自动修,不能修的直接删掉,别让它们进训练集。比如我遇到过一批标注框的长边和短边标反了,导致角度刚好差90度的情况。后来统一按长边为w、角度为与x轴夹角去重写所有标签,模型才稳定下来。
注意:处理SAR标注框时,一定要先搞清楚数据集的角度定义。常见的有OpenCV定义角度范围
[-90°, 0)、DOTA定义[0°, 90°)或[-180°, 180°),不同定义转换规则完全不同。这个不搞清楚,后面所有数据增强和模型输出都可能在边界处出问题。
2.3 切图与数据增强:旋转增强必须连标签一起转
由于原始SAR图像往往非常大,动辄一万像素见方,直接整图送进GPU显存肯定爆掉。所以标准做法是切图:把大图切成若干个固定大小的patch,再按patch去训练和推理。
切图策略我踩过不少坑,最后常用的方案是:
- 训练时用
1024×1024的patch,步长设置为768,重叠区域25%左右; - 推理时用
1024×1024或1536×1536的patch,步长设为patch的一半,最后再做重叠区域的去重合并; - 切图后如果某个目标被patch边界切断,除非它的可见面积超过完整面积的70%,否则我训练时直接丢弃这个样本;推理时则保留所有切碎的目标,靠后处理合并。
数据增强方面,除了常规的随机翻转、多尺度缩放,旋转增强对SAR目标特别有用。因为SAR目标的方向分布往往不均匀,旋转增强可以让模型看过各个方向的同类目标。但切记,旋转增强的时候,标注框的四个角和角度都要同步变换,而且旋转90、180、270度时,宽高和角度的变换公式都不一样。我见过有人直接对整图做90度旋转,标签只改了角度没换宽高,结果模型一直学错。
针对SAR图像,还可以加一点斑点噪声模拟增强。用乘性噪声模型,给训练图乘一个带随机的噪声矩阵,能让模型对真实SAR噪声更鲁棒。不过这种增强强度不能太大,否则会破坏细节。
3. 模型选型与结构设计
3.1 有向检测框架怎么选
有向目标检测的框架现在其实已经比较成熟了,不用自己从头实现旋转框IoU或者旋转框编解码。主流的选择无非这几类:
- 两阶段派:
Oriented R-CNN、RoI Transformer,精度高,但速度慢; - 一阶段派:
YOLOv8-OBB、RTMDet-R、S2ANet,速度快,精度已经能追上甚至超过两阶段; - Anchor-free派:
FCOS改的旋转版本,比如基于中心点和角点回归,适合密集小目标。
比赛场景里我优先推荐先用一个能快速出baseline的框架,比如MMRotate里的Oriented R-CNN或者RTMDet-R。如果你对工程部署不敏感,直接用MMRotate就行,它把数据增强、损失函数、评估指标都给你集成好了,省去大量底层实现时间。我们在比赛中大部分时间都花在分析badcase和调参上,而不是造轮子。
如果你更熟悉YOLOv8-OBB,也可以直接用来做baseline。它的推理速度极快,方便快速验证一些预处理和增强策略是否有效。我自己的习惯是:用YOLOv8-OBB快速跑通流程,再用MMRotate里的高精度模型做最终提交。
3.2 Backbone与Neck的选择
Backbone方面,我看很多人上来就换大模型,比如Swin-L、ConvNeXt-L。但SAR图像的数据量和自然图像不一样,比赛数据往往只有几千张甚至几百张大图,直接上超大模型很容易过拟合。更稳妥的做法是先试试ResNet50、ResNet101这类经典Backbone,把训练流程跑顺,再根据验证集差距决定要不要换更大的模型。
Neck部分用FPN是底线,后续可以升级到PAFPN或者BiFPN。SAR图像里多尺度的目标共存情况很明显,比如一大片港口里既有大型货轮,也有密密麻麻的小渔船。多尺度特征融合的质量几乎直接决定小目标AP。
如果想让模型更适应SAR的纹理特性,可以在Backbone前面加一个轻量的SAR特征适配模块,比如用几个卷积层把单通道或者三通道伪彩色图像映射到合适的高维特征空间。这个模块不需要太深,主要作用是让预训练模型的特征分布迁移到SAR域。实测在预训练权重的基础上微调时,这个小改动能让收敛速度明显加快。
3.3 检测头与角度回归
有向检测的检测头通常输出分类分数,加上旋转框参数。常见的有向框表示有两种:五参数(x, y, w, h, theta)和八参数(四点坐标)。八参数能表达任意四边形,但对长条目标不友好;五参数结构简单,更常用。
五参数中最关键的还是角度回归。为什么难?举例:两个朝向几乎一样的细长矩形,一个角度是-89.5°,另一个是89.5°,按长边定义它们其实只差1度,但数值上差了179度。如果你直接用SmoothL1损失,模型会收到一个巨大的错误梯度,训练直接抖起来。解决思路有三种:
- 对角度做周期变换,比如
sin/cos监督,或者把角度拆成两个量; - 使用旋转框专用的可导损失,比如
GWD、KLD、ProbIoU,它们本身就对角度周期性问题做了平滑处理; - 在训练时对角度误差做最小差值修正,比如计算损失之前先把角度差wrap到
[-90, 90)区间。
我个人的选择是优先用GWD或KLD这类损失,省心很多。如果必须用SmoothL1,也要做角度wrap,不能裸着上。你可以把角度回归想象成时钟上的指针:11点和1点看起来差很多,但其实只差两小时。直接比数字大小是怎么都不对的。
4. 训练策略与调参实战
4.1 损失函数与样本均衡
有向检测的损失一般由三部分组成:分类损失、回归损失,以及可能的物体性损失或角度专用损失。对于多类别长尾分布,分类损失单独用交叉熵往往不够,我建议用Focal Loss或者对类别采样概率做调整。
具体做法可以是:
- 计算每个类别的样本数量,按中位数或者某个阈值把稀有类别的采样概率上调,把高频类别的采样概率下调;
- 或者简单一点,在损失函数里给稀有类别加一个权重,权重跟样本数的倒数有关;
- 如果你用的是
MMRotate,可以在数据集里配置ClassBalancedDataset,它会自动帮你重采样。
回归损失部分,如果用GWD,它的核心思想是把旋转框建模成二维高斯分布,然后计算两个高斯分布之间的Wasserstein距离。因为角度边界被高斯分布的协方差“圆滑”了,所以对边界跳变不敏感。如果你是第一次接触,可以先直接用现成实现,理解它为什么能工作之后,再去调里面的超参数,比如tau。
4.2 学习率、训练轮数与EMA
训练策略是比赛里最容易被忽视但提升巨大的部分。我常用的配置是:
- 优化器:
SGD或者AdamW,前者在数据量大的时候更稳,后者收敛更快; - 初始学习率:
1e-4(AdamW)或0.01(SGD),然后按批量大小做线性缩放,比如8卡batch size 64时lr设0.01,单卡batch size 8时lr可以降到0.00125; - 学习率衰减:
CosineAnnealing,总轮数设24到36轮之间,前3轮做warmup; - Batch size:尽量往大了给,至少8个patch以上,太小的话BN统计量会非常不稳;
- EMA:指数移动平均,权重衰减系数设0.0001,能明显稳定验证集波动。
一个让我印象很深的问题是:同样的模型和超参,用两卡和单卡训练,验证集AP能差2个点。后来发现是Batch size小了以后,BN的统计量跟推理时不一致。解决方法是多卡时开SyncBN,或者增大batch size。如果显存不够,可以减小patch尺寸而不是直接砍batch size。
4.3 后处理与推理加速
比赛提交的最终结果是整张大图上的目标框,而我们是分patch推理的,所以后处理很关键。
我的标准流程是这样:
- 把大图切patch,每个patch独立推理;
- 将所有patch的预测结果映射回原图坐标;
- 用跨patch的旋转NMS,把重叠区域重复检测的框合并;
- 如果有必要,做一个全局置信度阈值调整,一般选验证集上最优的
score_thr。
这里有个小技巧:如果目标被切在patch边缘,同一个目标会在相邻两个patch里各出现一次,但置信度都会下降。如果单用NMS,可能会留下两个不完整的框。我后来会在切patch时给每个patch加一个边缘留白,比如推理时实际输入1024×1024,但只保留中心896×896区域的预测结果,这样每个目标最多被一个patch完整看到,重复检测大幅减少。
推理加速方面,我强烈建议最后阶段做FP16混合精度推理。SAR图像很大,如果一张张用FP32推理,时间成本高到难以迭代。FP16在部分硬件上可能损失一点点精度,但速度能快一倍以上,完全值得。
5. 常见问题与排查技巧实录
5.1 模型loss突然变成NaN或者Inf
这个问题我至少遇到三次。排查顺序是:先看数据里有没有异常标签,比如坐标是NaN或者角度超出定义范围;再看损失函数的输入,包括预测框和目标框,有没有出现零面积框;最后看学习率是不是太大。
有一个隐藏问题:旋转框回归时,如果模型的预测宽高变成负数,在计算GWD时会因为协方差矩阵不合法而出现NaN。解决办法是在回归头上强制w和h用exp激活,保证输出恒为正数。用现成框架一般没问题,但自己改模型时一定要检查。
5.2 小目标漏检严重
SAR图像里小目标是真的小,可能只有十几个像素。如果大图切成1024×1024patch,小目标占比会进一步降低。这个问题我靠三招解决:
- 增加推理时的patch分辨率,比如训练用1024,推理用1536,配合多尺度TTA;
- 在FPN的浅层加更多监督,让P2层参与输出,专门负责小尺度的目标;
- 针对稀有和小目标做离线过采样,把它们所在的小patch多复制几份进训练集。
不过要注意,不能盲目增大输入尺寸,不然显存和推理时间都会爆炸。比赛中期我试过把输入直接拉到2048,结果精度没涨多少,每个epoch时间翻倍,完全不划算。合理的做法是先确认模型对小目标的瓶颈到底是特征分辨率不够,还是样本太少,否则做再多措施都是白费。
5.3 旋转框评估结果和可视化结果不一致
还有一次让我很困惑:可视化出来的框看起来贴合目标,但验证集的AP很低。后来发现原因是评估脚本和数据增强里的角度定义不一致。训练时我按[0, 90)的角度范围做了数据增强,但验证代码默认用的是[-90, 0),导致模型输出的角度在评估时全部错位。
这种情况在比赛里非常常见。建议是:从第一天起就固定一套角度定义,并且写一个最小的单元测试,验证模型输出经过解码后和标签能对齐。不要在训练中途更改角度约定,这是血泪教训。
5.4 一个实用的效率策略:先小规模试跑再全量训练
比赛时间紧,GPU资源有限,我习惯先用一个小的随机子集跑20%的轮数,确认整个流程没有bug,并且验证集指标能随训练上升,再启动全量训练。这个习惯帮我避免了好几次大半夜跑完发现数据处理错误、白白浪费十几个小时的悲剧。
另外,如果预算允许,我会同时开两个实验:一个用RTMDet-R快速迭代找增益点,一个用Oriented R-CNN做精度冲刺。低延迟模型用来验证“某个增强有没有用”,高精度模型在验证有效后再训练,这样时间利用率最高。
这次打下来我最大的感觉是,SAR和有向检测组合在一起确实硬核,但也不是无迹可寻。核心还是先把数据琢磨透,再选一个能快速上手的框架,最后把损失函数和训练细节吃透。比赛过程中有几次我甚至想放弃,觉得模型怎么调都上不去,结果回头发现只是角度定义搞错了。如果你也在准备类似的赛题,建议先花两天时间把数据可视化、标签统计和角度约定这三件事做到位,后面绝对会省出更多时间。最后再分享一个小技巧:每次跑完一个实验,把配置文件、训练日志和验证结果都留好,这个实验记录在最后写报告和复现时,比你的记忆可靠一百倍。