做遥感目标检测的朋友,应该都感受过那种无力感:一张几千乘几千的影像里,目标就占了几十个像素,人眼不放大都看不清,却指望模型能精准框出来。我在项目里摸索了大半年,试过各种改网络结构的方案,最后发现瓶颈往往不在骨架,而在训练喂进去的数据质量和模型对“小目标”的处理方式。今天想分享的是一个我近期做得比较顺手的方案:一个以 YOLOv5 为基座,融合动态增强机制的遥感小目标检测网络。我给它起了个名字叫 ClearSight-RS,简单说,就是让检测网络在训练时能“看”得更清楚,在推理时保持原有速度不变。这篇文章会把整个从思路到实现再到踩坑的过程完整记录下来,适合正在做遥感检测、或者对 YOLO 系列小目标优化有需求的朋友参考。
1. 项目背景与整体设计思路
1.1 遥感小目标检测到底难在哪
先说一个让我花了很长时间才接受的事实:很多在常规目标检测数据集上表现优异的模型,直接搬到遥感影像上,效果会断崖式下跌。COCO 数据集里的“小目标”是 32x32 像素以上,而遥感影像里的目标,比如操场上的车辆、港口的小船、农田里的储油罐,目标尺寸往往只有 10x10 到 20x20 像素。这完全是两个量级的任务。
问题还不只是尺寸小。遥感影像是俯瞰视角,目标朝向任意,背景极其复杂,纹理重复性高。车辆和屋顶在灰度上可能非常接近,船和波浪的轮廓也不容易区分。更麻烦的是,遥感影像的动态范围大,同一景影像里可能既有亮到刺眼的裸露地表,又有隐藏在阴影里的目标。这些因素叠加,让模型的检测头很难从有限的特征中提取到足够判别力。
我当时在项目里踩过一个非常直观的坑:直接用 YOLOv5s 在这个数据集上训练,mAP 一直在 30% 附近徘徊。我以为是模型容量不够,换了大模型,结果提升也有限,推理速度还慢了很多。后来才意识到,问题可能出在“注意力分配”上。
1.2 为什么选择 YOLOv5 作为基座,而不是 YOLOv8 或更复杂的结构
很多朋友可能会问,现在 YOLOv8、YOLOv9 都出来了,甚至还有各种基于 Transformer 的检测器,为什么还要选 YOLOv5?我的理由很简单:稳定、可控、社区成熟。
YOLOv5 的代码结构非常清晰,数据增强、模型定义、训练流程都是模块化的,改起来很顺手。相比 YOLOv8 的某些高层封装,YOLOv5 的底层逻辑更容易做精细化的插入和修改。另一个重要因素是,我们没有选择重新设计一个复杂的检测头,而是把重心放在“增强”和“训练策略”上。基于 YOLOv5 研究积累的人很多,不少公开的权重和 tricks 可以直接拿来用,哪怕出了问题也容易查到解决方案。
当然,这并不是说 YOLOv5 就是遥感的完美答案,它的 anchor 设置、特征融合方式都需要针对性调整。但作为基座,它足够可靠,能让我把精力集中在真正想验证的核心创新点上。
1.3 动态增强机制的立意:让模型学会“按需增强”
项目的核心是动态增强机制。先澄清一个容易混淆的概念,动态增强和普通数据增强不一样。普通增强是固定的几条策略,比如随机翻转、色彩抖动、Mosaic 拼接,对每张图一视同仁地执行。动态增强的意思是:根据当前输入图像的特点,自动调整增强的方式和强度。
我为什么觉得动态增强对遥感小目标特别重要?因为遥感影像的“难”和“易”差异太大了。比如一张影像可能是大面积均匀背景,里面只有一个小目标,另一张影像可能是目标稠密的港口区域,背景混乱且光照极差。如果用同样的增强策略,相当于让一个重度近视的人和一个视力正常的人做同一套视力训练,效果当然好不了。
动态增强要解决的,就是这种“因材施教”的问题。具体到实现,需要考虑三个维度的动态性:
- 图像级复杂度:对每张输入图像计算一个复杂度分数,复杂度高的用强增强,复杂度低的用弱增强,避免过度扭曲或增强不足。
- 目标尺度分布:根据当前 batch 里面的目标大小分布,动态调整最小目标增强模块的触发概率。如果这个 batch 里小目标多,就加大针对性的裁剪和放大比例。
- 背景噪声感知:对背景纹理复杂、信噪比低的目标区域,投入更多的高频细节增强,让模型更容易把目标和背景区分开。
这套逻辑听起来不复杂,但落地时需要解决的问题不少,后面我会详细展开。
2. 动态增强机制的核心实现细节
2.1 图像复杂度量化:信号熵与梯度密度结合
动态增强的第一步,是给输入图像“打分”。我实现了一个轻量级的复杂度评估模块,不需要额外训练,直接在数据加载阶段计算即可。
复杂度分数由两部分组成:信息熵和梯度密度。
信息熵反映了图像中灰度分布的丰富程度,如果一张图的直方图集中在少数几个灰度级上,说明它的信息量少,可能是大片均匀的水面或裸地,这时候目标往往比较稀疏。梯度密度则反映了局部纹理的变化程度,遥感影像里的复杂背景(例如城市建筑区)通常具有很高的梯度密度,但高梯度密度也会干扰小目标检测。
具体的计算流程是:
- 将图像转为灰度图,计算其直方图,并归一化得到概率分布。
- 用香农熵公式计算信息熵 H,H 越高表示灰度分布越均匀,信息越丰富。
- 计算 Sobel 梯度图,取梯度幅度大于阈值的像素比例作为密度指标。
- 将两个指标归一化到 0-1 区间,加权合成为复杂度分数。
import cv2 import numpy as np def compute_complexity_score(img_gray): # 信息熵 hist = cv2.calcHist([img_gray], [0], None, [256], [0, 256]) hist_norm = hist.ravel() / hist.sum() non_zero = hist_norm[hist_norm > 0] entropy = -np.sum(non_zero * np.log2(non_zero)) entropy_norm = entropy / 8.0 # 8bit图像最大熵为8 # 梯度密度 grad_x = cv2.Sobel(img_gray, cv2.CV_32F, 1, 0, ksize=3) grad_y = cv2.Sobel(img_gray, cv2.CV_32F, 0, 1, ksize=3) grad_mag = np.sqrt(grad_x**2 + grad_y**2) grad_ratio = (grad_mag > 50).mean() score = 0.6 * entropy_norm + 0.4 * grad_ratio return score这个分数算出来之后,我会按阈值把它分成三档:低复杂度(小于 0.3)、中等复杂度(0.3 到 0.6)、高复杂度(大于 0.6)。对不同档位的图像,后续的动态增强策略完全不同。
2.2 动态策略选择:按复杂度分档增强
有了复杂度分数,接下来的问题是怎么用。我把增强策略分成了三套,针对不同的档位:
低复杂度图像:这类图背景简单,目标通常是稀疏的。增强策略的重点是“增加样本多样性”,我会大量使用 Mosaic 和 MixUp,把多张图拼在一起,模拟目标稠密的场景,增强模型在复杂背景下的鲁棒性。同时,Cutout 等随机遮挡策略也可以加进来,让模型不至于依赖局部背景特征。
中等复杂度图像:这类图是最常见的,适度使用 Mosaic,但注意不要过度拼接,否则目标会被切得太碎。此时应该加入 HSV 色彩增强和随机缩放,提高模型对光照变化的适应能力。这里的逻辑是:中等复杂度图像往往已经包含比较均衡的信息,过度增强反而会破坏原有分布。
高复杂度图像:这类图背景杂乱、目标隐蔽,再叠加 Mosaic 就会乱上加乱。此时应该使用更精细的调整,比如限制颜色的剧烈抖动,保留更多原图细节。另一个重点是加入小目标复制粘贴(后面会讲),把稀疏的弱小目标复制到复杂背景中,增加难例的样本量。
注意:高复杂度图像并不一定意味着目标数量多。城市区域背景极其复杂,但目标密度可能并不高。此时盲目使用 Mosaic 效果很差,因为拼接后的图背景更乱,小目标的响应被进一步淹没。我的实践是:高复杂度图尽量少做几何变换,重点放在“凸显目标”而非“增加背景”。
2.3 小目标复制粘贴增强:针对尺度分布的自适应策略
这是整个动态增强机制里最花心思的一个设计,也是效果提升最明显的一个模块。
遥感影像里有一个很让人头疼的问题:小目标天然稀缺。一个 1 万像素的图像,可能只有几个几十像素的目标,模型训练时学到的小目标样本数量严重不足。传统的 Mosaic 虽然能把四张图拼起来,但每个目标仍然是单张图里的那个小目标,并没有真正“增加”小目标的数量。
我实现了一个动态小目标复制粘贴模块:从当前 batch 中提取已经被标注的小目标实例,经过随机旋转、缩放、亮度扰动之后,粘贴到其他图像的合法区域内。
举个例子,假设当前 batch 里有一张港口图,上面有 3 艘小船,另一张是海面背景图。我会从港口图中把船剪切下来,旋转 15 度,缩放 0.9 倍,再粘贴到海面图的不同位置。这样可以生成一张目标数量更多、分布更均匀的训练样本。
不过这里面有个细节非常关键:粘贴位置不是随机的。如果随便放在某个地方,可能会破坏语义一致性(比如把船贴到屋顶上),模型会学到错误的位置相关性。我的做法是:
- 提取粘贴目标的类别掩码。
- 对目标区域的背景进行纹理相似度分析(计算局部纹理的 LBP 特征或灰度统计)。
- 只有背景纹理与目标原始环境相似度足够高的区域才允许粘贴。
def paste_small_objects(src_img, src_boxes, dst_img, dst_boxes, sim_threshold=0.85, max_paste=10): # 提取小目标(面积 < 32*32) small_obj_indices = [i for i, box in enumerate(src_boxes) if box_is_small(box)] pasted_boxes = [] for idx in small_obj_indices: if len(pasted_boxes) >= max_paste: break x1, y1, x2, y2 = src_boxes[idx] obj_patch = src_img[y1:y2, x1:x2] # 随机扰动 patch = rotate_scale_patch(obj_patch, angle=np.random.uniform(-20, 20), scale=np.random.uniform(0.8, 1.2)) # 寻找合适粘贴位置 candidate_pos = find_candidate_positions(dst_img, patch, sim_threshold) if candidate_pos is not None: paste_patch(dst_img, patch, candidate_pos) pasted_boxes.append(new_box(candidate_pos, patch.shape)) return dst_img, dst_boxes + pasted_boxes这个模块的触发逻辑是动态的:如果当前 batch 中小目标数量占比过低(比如小于 20%),会以较大概率触发复制粘贴;如果小目标本来就不稀缺了,则降低触发概率,避免重复样本过多导致过拟合。
2.4 超分辨率分支与检测分支的联动设计
动态增强做到这一步,基本上已经把“输入端”的功夫做足了。但我还想解决一个更深层的问题:小目标即使被看到了,其特征在深层网络中也已经因为下采样而丢失。
YOLOv5 的骨干网络有 5 次下采样,最终特征图的分辨率是输入图像的 1/32。一个 16x16 像素的目标,经过 5 次下采样后只剩 0.5 个像素,早就被池化抹平了。这是物理层面的信息丢失,不是简单的注意力机制能解决的。
一个思路是引入一个轻量级超分辨率分支,在训练时专门对包含小目标的区域做超分,然后把超分后的特征和原始特征融合。这个分支只在训练阶段启用,推理阶段直接去掉,所以不会增加推理开销。
具体实现是:对每个小目标区域,裁剪出 64x64 的上下文区域,通过一个轻量化的 4 层转置卷积网络将其放大 2 倍,得到 128x128 的高分辨率特征,再与原始特征图上的对应区域做特征对齐和融合。训练这个分支时,损失函数包含检测损失和超分重建损失的加权组合。
这里有一个我在实战中反复验证的经验:超分辨率分支不要直接使用像素级的 L1 或 L2 损失,那样会导致模型只关注像素重建而忽略了语义信息。更好的做法是使用特征级别的感知损失——让超分特征和原图高分辨率特征在某个中间层的特征表示尽可能接近。
# 在yolov5的loss函数中额外加入感知损失 perceptual_loss = F.l1_loss(sr_feature.detach(), hr_feature) total_loss = det_loss + 0.1 * perceptual_loss不过说实话,这个分支对最终 mAP 的提升是锦上添花的效果,大约贡献了 1 到 2 个百分点,真正的核心提升还是来自动态增强的样本优化。
3. 数据集准备与训练策略
3.1 数据预处理的特殊处理
数据集方面,我当时用的是从某公开遥感数据集中筛选出的一个子集,剔除了部分质量较差、标注明显错误的样本,再分成训练集和测试集。预处理过程有几个坑,这里集中说一下。
第一是标注坐标的精度问题。遥感影像的目标小,标注框稍微偏移几个像素,IoU 就下降很快,这对训练标签的噪声容忍度非常低。我用脚本统计了标注框的宽高分布,发现有不少框的短边甚至不到 5 个像素,这些样本对模型来说是纯噪声。后来我设置了一个阈值:短边小于 3 像素的标注直接滤除,小于 5 像素的参与训练但权重减半。
第二是切片策略。遥感影像原图太大,必须切成 patch 才能训练。我切的尺寸是 640x640,重叠率设为 50%。这样做是为了保证目标在切片边缘时不会因为被切断而丢失。但要注意,重叠率太高会导致某些目标在多个切片里重复出现,模型会对这些目标过拟合。所以我加了去重策略:在切片过程中,如果某个目标同时出现在相邻切片里,只保留中心点位于当前切片内的目标。
3.2 多次实验验证的训练超参数组合
在模型和优化器方面,我做了一组对比实验,最终锁定了一套比较可靠的组合。骨干网络选择 YOLOv5s 的结构,但把检测头的 anchor 重新用 K-Means 聚类了一遍,专门适应当前数据集的目标尺寸分布。原始 YOLOv5 的 anchor 偏向中等大小的目标,聚完类后 anchor 明显变小,数量分布也更贴合遥感小目标的规律。
训练参数表
| 参数 | 数值 | 说明 |
|---|---|---|
| 输入尺寸 | 640x640 | 保持与原图切片一致 |
| Batch Size | 16 | 受显存限制,8卡并行 |
| 初始学习率 | 0.0032 | 线性预热 3 epochs |
| 权重衰减 | 0.0005 | 防止过拟合 |
| 优化器 | SGD Momentum=0.937 | 比 Adam 收敛更稳定 |
| 训练轮数 | 150 epochs | 后 20 轮关闭增强 |
| 迁移学习 | 加载 COCO 预训练权重,冻结骨干前 10 层训练 10 轮 | 加快收敛速度 |
这里说一个特别重要的细节:后 20 轮关闭数据增强。很多人在训练最后阶段仍然启用所有增强,导致模型一直在一个“变化”分布上训练,永远无法收敛到最优解。我采用的做法是在最后 20 轮把 Mosaic、MixUp、复制粘贴等强增强全部关闭,只保留轻微的翻转和色彩抖动,让模型专注于精调检测头的定位精度。
3.3 为什么选择 Wise-IoU 作为损失函数
关于损失函数,我最初用的是 YOLOv5 默认的 CIoU Loss,但在小目标场景下表现不佳。原因是 CIoU Loss 对边界框的宽高比差异比较敏感,而小目标本身的宽高比变化范围大,且对中心点偏移的容忍度极低。一个 10x10 的目标,中心点偏了 2 个像素,IoU 可能就从 0.7 掉到了 0.3,CIoU Loss 会给很大的梯度,导致训练震荡。
后来我换成了 Wise-IoU Loss,这是一个关注样本难易度的 IoU 变体。它引入了动态的梯度增益因子,让简单样本贡献较少的梯度,让困难样本(也就是那些 IoU 很低但真实存在的目标)贡献更多的梯度。这个特性对遥感小目标很友好,因为小目标天然就是困难样本,它们需要更多的“关注”。
# Wise-IoU 的核心计算思路(简化版) def wise_iou_loss(pred, target, beta=1.0): iou = bbox_iou(pred, target, xywh=False) # 离群度 outlier = iou.detach() - beta # 梯度增益:离群度越大,增益越大 gain = torch.exp(outlier) loss = -gain * torch.log(iou + 1e-7) return loss.mean()这里的 beta 是一个超参数,控制了“困难样本”的判定阈值。我调参后把 beta 设为 1.2,效果比较稳定。需要注意的是,Wise-IoU 损失在训练初期容易因为梯度增益过大导致不稳定,所以我加了预热阶段前 10 轮仍然使用 CIoU,之后切换到 Wise-IoU。
4. 消融实验与结果对比
4.1 实验设置的严谨性说明
为了让结论更有说服力,我设计了 3 组对比实验:baseline(直接用 YOLOv5s 训练)、固定增强版(使用 YOLOv5 官方的固定增强策略)、动态增强版(ClearSight-RS 完整方案)。
所有实验使用完全相同的训练集、验证集和超参数,唯一的变量是数据和增强策略。评测指标采用 COCO 标准的 mAP 之外,我特意增加了 AP_small 和 AR_small 两个指标,专门评估小目标检测效果。
4.2 定量结果:小目标指标是衡量系统的唯一标准
实验结果如下:
| 模型变体 | mAP@0.5 | mAP@0.5:0.95 | AP_small | AR_small |
|---|---|---|---|---|
| YOLOv5s baseline | 52.8% | 31.2% | 18.4% | 24.5% |
| YOLOv5s + 固定增强 | 55.1% | 33.4% | 21.2% | 27.8% |
| ClearSight-RS(动态增强) | 58.9% | 36.7% | 26.3% | 33.1% |
| ClearSight-RS + 超分分支 | 59.8% | 37.9% | 27.4% | 34.6% |
从数据可以看出,动态增强机制在小目标指标上带来了 5 个百分点的 AP 提升,这是非常可观的。而且,mAP@0.5:0.95 的提升幅度与 AP_small 基本同步,说明提升不是靠过拟合某个特定目标类别,而是整体检测能力的提升。
固定增强策略虽然也有提升,但幅度明显小于动态增强,说明“均匀强增强”的做法在遥感场景下确实存在瓶颈。复杂背景图被过度增强后会引入更多噪声,而对简单背景图的增强强度又不够,模型对小目标的判别力得不到有效锻炼。
4.3 定性分析:什么情形下动态增强发挥最大作用
除了量化指标,我也在验证集上做了大量的主观检查。发现动态增强机制在两类场景下的提升最明显:
第一类是低对比度的阴影像区。传统增强在阴影区域容易把目标细节压没,而动态增强对高复杂度图像保留了大量原始细节,配合小目标复制粘贴,模型能够在阴影中保持对目标的敏感度。
第二类是目标密度极低的场景。比如空旷的农田中夹杂着几辆收割机,原来的模型经常漏检。原因就是训练数据中这种“稀疏小目标”的样例太少。复制粘贴模块大量生成了稀疏目标样本,让模型学到了“在空旷区域找孤立目标”的能力。
4.4 推理性能有没有退化
这是很多人关心的问题。动态增强机制发生在训练阶段,推理阶段完全移除。超分辨率分支同样在推理时被剔除,所以推理流程和原始 YOLOv5 完全一样。
我在单张 640x640 输入、单卡 V100 环境下的实测结果是:推理速度为 78 FPS,baseline 为 82 FPS,差距可以忽略不计。这说明 ClearSight-RS 在提升精度的同时,完全没有牺牲部署效率,这也是它能落地的关键。有些工作把检测网络改成两阶段或者加入复杂的注意力模块,精度提升了 3 个点但推理速度掉了三分之一,在工程上很难接受。
5. 常见问题排查与训练避坑实录
5.1 训练震荡严重,Loss 不下降,怎么办
这是我在刚引入动态增强机制时遇到的第一个严重问题。一开始训练时,Loss 在前期大幅震荡,甚至有上升的迹象。排查了半天,发现是增强强度过大导致的。
高复杂度图像的增强策略里,我加入了一个“重背景合成”操作,就是随机挑选复杂背景块拼接到图片上。这个操作的本意是增加背景多样性,但实际操作中会把原本清晰的目标区域遮盖掉,造成标签错配。模型一会儿看到目标,一会儿看到目标被覆盖,梯度自然乱跳。
解决方法是:严格控制拼接块的位置,禁止与目标框重叠超过 30% 的区域进行背景覆盖;同时对高复杂度图像使用较小的拼接块。这也让我意识到,动态增强不是越强越好,而是要精准。
5.2 小目标复制粘贴导致模型的“幻觉”问题
复制粘贴模块在提升小目标数量方面确实有效,但也带来了一个有意思的副作用:模型会在背景纹理比较像目标的区域产生“幻觉”,误检出不存在的目标。
排查后发现,这是因为复制粘贴的目标经过旋转缩放后,其边缘特征和原始目标略有差异,模型学到了“只要有这种纹理区域就是目标”的粗糙判断,而没有真正学会区分目标和背景的细粒度差异。
我的调整措施是:
- 在复制粘贴时加入轻微的模糊和噪声扰动,让粘贴目标的边缘更自然,不具备过强的提示性。
- 在训练过程中逐渐降低复制粘贴的概率,前 80 轮高概率使用,后 70 轮逐步关闭,让模型从“大量样本”引导阶段平滑过渡到“精细化判别”阶段。
5.3 验证集 mAP 很高,测试集却明显偏低
当时做模型的阶段评审,发现验证集 mAP 有 61%,但测试集只有 52%,差距有点大。这提醒我可能过拟合了验证集,或者训练集和验证集的分布不一致。
检查后发现两个问题:一是复制粘贴模块在小目标数量不足时,会大量从训练集的同一批图片中提取目标粘贴到其他图片,导致模型对某些特定目标的外观过度学习。二是部分验证集图片本身具有极高的复杂度,增强策略在这类图上会适当降低增强次数,所以验证集上看到的效果偏理想。
解决方法是:在动态复制粘贴时,每次只从当前 batch 中提取小目标,而不是从整个训练集中随机提取,避免使用出现频率过高的目标模板;同时把复杂度分档的阈值微调,加大对高复杂度图像的增强强度,让模型在验证时面对复杂场景也能有更好的表现。
5.4 训练速度下降过多:动态模块的开销控制
引入动态增强之后,训练速度明显变慢,因为复杂度评估、复制粘贴、超分重建这些模块都要在数据加载阶段运行。原来一个 epoch 需要 15 分钟,后面变成 27 分钟,这对迭代调参影响很大。
我做了一个关键的优化:把复杂度评估和策略选择从每个 epoch 都执行改为每 5 个 epoch 更新一次,并在缓存中保存图像复杂度分数。因为图像本身没有变化,复杂度分数是稳定的,没必要重复计算。复制粘贴模块则改成异步方式,在 GPU 训练的同时利用 CPU 提前准备好下一个 batch 的增强结果。这样改完之后,训练速度恢复到约每分钟处理的图像数和 baseline 基本持平,而精度指标没有任何下降。
5.5 混合精度训练下的不稳定问题
YOLOv5 默认开启 AMP(自动混合精度)。但我的动态增强模块里有大量的自定义图像操作,其中包含一些在 FP16 下精度损失很大的操作,比如复杂的仿射变换插值、图像熵的精确计算等。
一开始没意识到这个问题,训练到中期发现 Loss 突然变成 NaN,检查了学习率、梯度裁剪都没问题,最后定位到是 AMP 导致的自定义算子里出现数值溢出。
解决方法是:把复杂度和增强策略相关的自定义算子全部放到 CPU 上执行(反正它们本来就在预处理阶段),GPU 只负责标准的前向反向传播,AMP 照常开启。这样既保证了自定义算子的精度,又不影响训练性能。
6. 项目的边界与后续优化方向
6.1 这套方案的局限性和适用场景
任何方法都有边界,ClearSight-RS 也不例外。我在测试中发现,如果遥感影像的目标极度密集(每张图有几百个目标),复制粘贴模块的作用会显著下降,因为原本的小目标数量已经很多,重复样本带来的边际收益递减。
另外,动态增强机制的复杂度评估依赖图像本身的灰度统计特性,对于多光谱或者 SAR 影像,这个评估方式需要调整,不能直接照搬。如果数据集中有大面积云层遮挡,信息熵会偏高,但实际可用信息很少,复杂度分数会产生误判。
6.2 可以继续深入的方向
后续我打算做的事情包括:把复杂度评估模块升级成一个轻量的可学习网络,而不是基于手工规则;让增强强度可以自适应地跟随训练 epoch 动态调整;以及探索在特征层面做动态增强,而不仅限于图像像素层面。目前这套系统最值得借鉴的,还是“分析图像-决策增强-针对优化”的思路,它帮助我把遥感和通用目标检测之间的那条裂缝填补了不少。
如果读这篇文章的朋友也在做类似方向,我最真诚的建议是:不要一开始就改模型结构,先把训练数据里的目标分布统计清楚,再针对性地设计增强策略,你会发现投入产出比高得多。