做遥感目标检测的朋友应该都感受过那种无力感一张几千乘几千的影像里目标就占了几十个像素人眼不放大都看不清却指望模型能精准框出来。我在项目里摸索了大半年试过各种改网络结构的方案最后发现瓶颈往往不在骨架而在训练喂进去的数据质量和模型对“小目标”的处理方式。今天想分享的是一个我近期做得比较顺手的方案一个以 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 图像复杂度量化信号熵与梯度密度结合动态增强的第一步是给输入图像“打分”。我实现了一个轻量级的复杂度评估模块不需要额外训练直接在数据加载阶段计算即可。复杂度分数由两部分组成信息熵和梯度密度。信息熵反映了图像中灰度分布的丰富程度如果一张图的直方图集中在少数几个灰度级上说明它的信息量少可能是大片均匀的水面或裸地这时候目标往往比较稀疏。梯度密度则反映了局部纹理的变化程度遥感影像里的复杂背景例如城市建筑区通常具有很高的梯度密度但高梯度密度也会干扰小目标检测。具体的计算流程是将图像转为灰度图计算其直方图并归一化得到概率分布。用香农熵公式计算信息熵 HH 越高表示灰度分布越均匀信息越丰富。计算 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, ksize3) grad_y cv2.Sobel(img_gray, cv2.CV_32F, 0, 1, ksize3) 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_threshold0.85, max_paste10): # 提取小目标面积 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, anglenp.random.uniform(-20, 20), scalenp.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 Size16受显存限制8卡并行初始学习率0.0032线性预热 3 epochs权重衰减0.0005防止过拟合优化器SGD Momentum0.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.3CIoU Loss 会给很大的梯度导致训练震荡。后来我换成了 Wise-IoU Loss这是一个关注样本难易度的 IoU 变体。它引入了动态的梯度增益因子让简单样本贡献较少的梯度让困难样本也就是那些 IoU 很低但真实存在的目标贡献更多的梯度。这个特性对遥感小目标很友好因为小目标天然就是困难样本它们需要更多的“关注”。# Wise-IoU 的核心计算思路简化版 def wise_iou_loss(pred, target, beta1.0): iou bbox_iou(pred, target, xywhFalse) # 离群度 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 定量结果小目标指标是衡量系统的唯一标准实验结果如下模型变体mAP0.5mAP0.5:0.95AP_smallAR_smallYOLOv5s baseline52.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 提升这是非常可观的。而且mAP0.5:0.95 的提升幅度与 AP_small 基本同步说明提升不是靠过拟合某个特定目标类别而是整体检测能力的提升。固定增强策略虽然也有提升但幅度明显小于动态增强说明“均匀强增强”的做法在遥感场景下确实存在瓶颈。复杂背景图被过度增强后会引入更多噪声而对简单背景图的增强强度又不够模型对小目标的判别力得不到有效锻炼。4.3 定性分析什么情形下动态增强发挥最大作用除了量化指标我也在验证集上做了大量的主观检查。发现动态增强机制在两类场景下的提升最明显第一类是低对比度的阴影像区。传统增强在阴影区域容易把目标细节压没而动态增强对高复杂度图像保留了大量原始细节配合小目标复制粘贴模型能够在阴影中保持对目标的敏感度。第二类是目标密度极低的场景。比如空旷的农田中夹杂着几辆收割机原来的模型经常漏检。原因就是训练数据中这种“稀疏小目标”的样例太少。复制粘贴模块大量生成了稀疏目标样本让模型学到了“在空旷区域找孤立目标”的能力。4.4 推理性能有没有退化这是很多人关心的问题。动态增强机制发生在训练阶段推理阶段完全移除。超分辨率分支同样在推理时被剔除所以推理流程和原始 YOLOv5 完全一样。我在单张 640x640 输入、单卡 V100 环境下的实测结果是推理速度为 78 FPSbaseline 为 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 动态调整以及探索在特征层面做动态增强而不仅限于图像像素层面。目前这套系统最值得借鉴的还是“分析图像-决策增强-针对优化”的思路它帮助我把遥感和通用目标检测之间的那条裂缝填补了不少。如果读这篇文章的朋友也在做类似方向我最真诚的建议是不要一开始就改模型结构先把训练数据里的目标分布统计清楚再针对性地设计增强策略你会发现投入产出比高得多。