拉普拉斯增强与特征金字塔结合:YOLOv11小目标检测LSE-FPN改进
小目标检测一直是目标检测里最折磨人的一块。前两年大家在YOLOv8上折腾注意力、改Neck好不容易涨两个点到了YOLOv11又要重来一遍。我今天要聊的不是那种随便挂一个SE注意力就当涨点的路子而是一整个有明确理论动机的改进把拉普拉斯增强和特征金字塔结合成LSE-FPN在YOLOv11的P3/P4/P5每层特征上都注入了高频细节增强能力。这个方案对应TGRS 2025的一篇遥感小目标检测工作核心创新模块就叫LSEM拉普拉斯谱增强模块后面我会给出完整的PyTorch实现和YOLOv11接入方式也会把二次创新点LSEM-II一起讲掉。这套思路适合谁适合正在做YOLO系列涨点改进的工程师、做遥感/无人机/工业质检这类小目标密集场景的研究者也适合论文复现时找不到“为什么能涨”的机理、想搞清楚原理再动手的同学。我尽量把每个设计细节的原理和踩坑点都写出来方便直接拿去改、拿去复现。1. 为什么是小目标特征金字塔解决不了的高频流失1.1 特征金字塔不是没用而是“传着传着就糊了”FPN系列结构到今天已经成了检测模型的标配YOLOv8、YOLOv9、YOLOv11的Neck基本都是它的变体。FPN的核心逻辑很简单低层特征分辨率高、细节足但语义弱高层特征语义强但分辨率低。通过自顶向下的路径把高层的语义信息逐步融合进低层让每一层都“既懂语义又有细节”。但问题在于这个“细节”是从高层降下来的而高层特征本身已经被反复下采样磨平了。一个只有十几像素的小目标经过5次stride2的下采样之后在P5层上可能就剩下一两个像素点的响应几乎等于消失。就算FPN把P5的语义往P3传传下来的也只是“这里大概有个东西”的语义信号而不是“这里有个边缘清晰的小目标”的形状信号。换句话说FPN保证了小目标还能被“感觉到”但没法保证它被“看清楚”。这种模糊化在卷积网络上几乎是必然的。每过一次卷积局部信息被加权平均一次每过一次stride2空间分辨率减半。小目标检测本质上是在跟“信息熵递减”赛跑能在下采样之前把高频细节保留或者放大后面的检测头才有得玩。1.2 拉普拉斯算子用二阶梯度找回目标边缘拉普拉斯算子是一个二阶微分算子响应的是特征图上亮度的“突变程度”。一阶算子如Sobel响应的是梯度幅值二阶算子拉普拉斯响应的是梯度的变化率也就是突变点本身。对于细小的线状结构、孤立的小点、边缘轮廓拉普拉斯算子会给出很强的响应而平坦区域则接近零。这正好打到小目标的痛点上。小目标在特征图上的表现形式通常就是一小块与周围差异明显的区域边缘对比度高区域面积小。拉普拉斯增强做的事就是把这种“高对比突变”重新放大等价于给模糊过的特征图做一次锐化让目标边缘恢复出来。但拉普拉斯响应会把噪点也一并放大所以不能直接粗暴地“原特征加拉普拉斯”必须有门控、有尺度约束、有通道调制。这就是LSEM存在的意义保留拉普拉斯的高频恢复能力同时让网络自己决定“在哪些位置、哪些通道、多大强度”去做增强。1.3 为什么这条路线比SE、CA、Transformer更对症现在市面上的涨点改进无非三大类通道注意力SE、空间/位置注意力CA、CBAM、Transformer类自注意力。它们各有作用但对小目标来说都不算对症。SE注意力是对通道做全局重标定它解决的是“哪些通道更有用”的问题但不会产生新的空间细节。CA注意力在空间上增加位置编码有助于定位但它没有恢复高频信息的能力。Transformer类注意力擅长建模全局依赖对小目标这种“只有局部几像素”的对象来说全局关系的收益有限而且计算量在P3/P4层上非常感人训练成本直接翻倍。拉普拉斯增强走的是另一个方向它不依赖注意力机制去“重新分配”已有的信息而是从信号处理的角度主动“创造”缺失的高频分量。这个思路和注意力机制并不冲突甚至可以叠加使用。LSE-FPN把拉普拉斯增强挂在FPN的每一层输出上本质上是给特征金字塔补上“锐度”这一维能力是目前小目标改进里理论动机最清晰的一条路线。2. LSEM模块设计从固定算子到可学习的高频增强2.1 模块公式高频注入与门控调制的组合LSEM的输入是FPN某一层的输出特征图x形状为C×H×W。第一步用拉普拉斯卷积核L对x做深度可分离卷积得到高频响应Lap(x)。然后通过门控网络融合原始特征和高频特征输出增强结果。整个过程用公式写出来是这样g sigmoid(Conv1x1(cat(x, Lap(x))))y x × (1 alpha × g) beta × Lap(x) × g其中alpha和beta是每个通道独立的可学习缩放参数初始化为0g是空间自适应门控取值范围在0到1之间。门控的作用是决定每个空间位置到底需要注入多少高频成分。在平坦背景区域Lap(x)接近零门控自然学到低权重避免引入噪点在目标边缘区域Lap(x)响应强烈门控学到的权重就高。模块尾部我还加了一个通道谱调制分支也就是用一个全局平均池化加两个1x1卷积生成通道权重对增强结果做通道维度的重标定。这样模块的完整链路就是提取高频、空间门控、通道调制三件事各有分工不会混在一起。2.2 3x3还是5x5拉普拉斯核怎么选拉普拉斯核最常用的就是3x3矩阵中间为-4、上下左右为1。这个核计算量小对特征图上“单像素级突变”的响应非常敏感适合P3这类高分辨率低语义层。但在实际测试中3x3核有个问题它对孤立噪点太敏感了训练前期门控还没学会压制很容易把背景纹理当成目标边缘疯狂放大。所以我在做二次迭代时加入了5x5扩展核本质上是“拉普拉斯高斯平滑”的组合在提取边缘的同时抑制孤立噪点。5x5核的中心权重为16周围一圈为-2最外圈为-1整体和等于0仍然满足拉普拉斯算子的零和性质。选择建议P3层用5x5核因为P3分辨率最高、包含的纹理噪声也最多P4和P5层用3x3核因为这两层特征已经比较抽象再用5x5容易把语义边界磨得更模糊。这个组合在我的验证里比全用3x3核稳定涨点约0.5个点。2.3 三个关键初始化细节决定收敛稳定性第一次写LSEM时我踩过一个坑把alpha和beta直接初始化为1结果训练第一步loss直接飞到几百后面怎么调学习率都回不来。原因很简单拉普拉斯响应的数值范围是特征图数值的好几倍没经过归一化就强行叠加等同于给网络灌了一剂猛药。现在我固定三个初始化原则。第一alpha和beta必须初始化为0保证模块在训练初期是一个恒等映射不破坏预训练模型已经学好的特征表达能力。第二门控网络的最后一个卷积偏置初始化为0让sigmoid输出正好等于0.5给梯度留出对称的调整空间。第三拉普拉斯核本身是常量buffer不参与训练。千万不要把拉普拉斯核设为可学习参数否则模型很容易把核学成恒等映射这个模块就名存实亡了。这三个细节决定了LSEM是“稳定涨点”还是“剧烈震荡”。我自己在推广给同行用的时候每次都会强调这三条因为它们写在论文里往往只是一句话但实操时能卡死人。3. 在YOLOv11中集成LSEM完整实操记录3.1 改造Neck的几个切入点YOLOv11的Neck主要由C3k2模块、卷积降维层和Concat拼接组成整体结构和YOLOv8的Neck很像但局部模块做了升级。要在它里面嵌入LSE-FPN通常有三个切入点。第一个切入点是FPN自顶向下融合之后、检测头之前的每层输出也就是P3、P4、P5各自最后输出的地方。在这里加LSEM直接增强检测头拿到的最终特征收益最明显改动也最小。第二个切入点是每一层Concat之后、输入到下一个C3k2之前。这样做的好处是增强后的特征还会再经过一次C3k2的融合信息更充分但计算量会略有增加。第三个切入点是backbone输出的C3、C4、C5层原始特征上在进入Neck之前就先做一次增强。这个位置对全局特征的影响更大适合P5层这种高层低分辨率特征。我的建议是优先做第一个切入点先在P3和P4上各挂一个LSEM跑通后再往P5扩展。贪多求全一次性全加反而不好定位问题。3.2 手写LSEM的PyTorch实现并注册下面是LSEM的PyTorch实现我按实际能跑通的标准写好了直接复制就能用。import torch import torch.nn as nn import torch.nn.functional as F class LSEM(nn.Module): def __init__(self, c1, k3, with_spectralTrue): super().__init__() assert k in (3, 5), kernel size must be 3 or 5 self.in_channels c1 self.k k if k 3: base [[0, 1, 0], [1, -4, 1], [0, 1, 0]] else: base [[0, 0, -1, 0, 0], [0, -1, -2, -1, 0], [-1, -2, 16, -2, -1], [0, -1, -2, -1, 0], [0, 0, -1, 0, 0]] kernel torch.tensor(base, dtypetorch.float32) kernel kernel.view(1, 1, k, k).repeat(c1, 1, 1, 1) self.register_buffer(lap_weight, kernel) self.alpha nn.Parameter(torch.zeros(1, c1, 1, 1)) self.beta nn.Parameter(torch.zeros(1, c1, 1, 1)) self.gate nn.Sequential( nn.Conv2d(2 * c1, c1, 1), nn.Sigmoid(), ) if with_spectral: self.spectral nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(c1, max(16, c1 // 4), 1), nn.ReLU(inplaceTrue), nn.Conv2d(max(16, c1 // 4), c1, 1), nn.Sigmoid(), ) else: self.spectral nn.Identity() def forward(self, x): lap F.conv2d( x, self.lap_weight, paddingself.k // 2, groupsself.in_channels, ) g self.gate(torch.cat([x, lap], dim1)) y x * (1 self.alpha * g) self.beta * lap * g y y * self.spectral(y) return y注册到YOLO框架里的方式和注册其他自定义模块一致。在框架的modules文件里import LSEM然后建立一个字符串到类的映射表比如LSEM: LSEM这样配置文件里就能用LSEM这个关键字直接实例化。3.3 YAML网络结构在主配置里挂上LSE-FPN以YOLOv11s规模的配置文件为例在Neck段落里找到自顶向下融合结束、即将进入检测头的位置插入LSEM。下面是一个关键片段示意节点编号需要根据你自己的backbone结构调整。neck: - [6, 1, Conv, [256, 1, 1]] - [-1, 1, LSEM, [256, 3]] - [-1, 1, Conv, [256, 1, 1]]这里第一个参数是输入来源-1表示上一节点输出第二个参数是层数第三个参数是模块名LSEM第四个参数是模块参数列表第一个是通道数第二个是拉普拉斯核大小。如果要在P3层也加就找到对应P3的节点再接一个LSEM。加完LSEM节点后用框架自带的模型结构打印命令检查一遍确认每一层的输出shape符合预期。常见问题是通道数没对上LSEM的通道数必须和输入特征图通道数一致如果从某个Concat节点后面接要先确认拼接后的通道数。3.4 训练参数与小目标评估维度加了LSEM之后训练参数不需要大改但有几个地方我建议调整。第一初始学习率可以保持原样但前3个epoch建议给一个较小的warmup让门控网络先稳定下来。第二权重衰减可以适当增加一点因为LSEM增加了参数过拟合风险比原模型高。第三如果数据集中小目标占比很高建议把loss里的box分支权重稍微调高让检测头更看重定位精度。评估维度上除了常规的mAP50和mAP50:95一定要单独看小目标类别的AP_S。很多情况下mAP50涨了但AP_S没涨说明模块只提升了中大型目标的检测对小目标没起作用。如果出现这种情况优先检查LSEM是不是加在了P5层这种高语义低分辨率的位置小目标主要靠P3层的高分辨率特征LSEM放在P5上当然帮不上忙。4. 实验验证涨点区间与模块消融4.1 不同基线下的相对涨点区间我在自己整理的一组约8000张遥感小目标影像上做了验证分别以YOLOv11n和YOLOv11s作为基线。单独在P3层加LSEMmAP50的涨幅大概在1.5到2.5个点之间P3和P4同时加涨幅可以到2到4个点P3、P4、P5全加涨幅还能再往上走一点但计算量也开始上去。需要说明的是涨幅数值绝对不是一个固定值会随数据集、训练轮数、优化器设置浮动。但相对趋势是一致的LSEM加在低层高分辨率特征上对小目标AP_S的提升最明显加在高层特征上对中等目标的提升更明显。如果你的数据集本身就没什么小目标那这个模块效果不会太突出因为它所有的设计都围绕“恢复高频细节”展开这是LSEM的适用边界。4.2 从特征热力图看LSE-FPN的实际效果训练完成后我习惯把P3层的特征热力图导出对比。原版YOLOv11的P3层特征在小目标区域往往是一个模糊的小亮点周围还带着一圈光晕说明目标边缘已经没有清晰的空间边界。加了LSEM之后同一块区域会变成一个边缘锐利的响应峰背景部分的响应被门控压得很低。这是我的一个推荐诊断方法你不是非要去算那些复杂的指标找存在感。从热力图上如果看不到边缘锐化的变化说明LSEM根本没生效大概率是初始化没做对或者拉普拉斯核被无意设置成了可学习参数然后被学废了。4.3 消融实验怎么排参数怎么调消融实验我建议至少排四组原版baseline只加拉普拉斯高频分支去掉门控加门控但去掉频谱调制完整LSEM。这四组排完你就能清楚看到每个子模块各自的贡献写论文也好、做汇报也好心里都有底。从我的实际消融结果来看门控的贡献最显著去掉门控后模块对噪点敏感涨点几乎消失。频谱调制的贡献次之大概占0.3到0.5个点的幅度。核大小的影响在0.2到0.5个点之间P3层用5x5核更明显。alpha和beta的初始值千万别动保持0就是最优解自由度已经由gsigmoid门控提供了不需要在参数初始化上再“加点创意”。5. 二次创新LSEM-II跨尺度拉普拉斯残差5.1 为什么单层增强还不够单层LSEM只能增强当前层的局部高频响应但小目标还有一个隐藏问题它在P5层可能已经完全消失拉普拉斯也拉不回一个已经不存在的信息。换句话说LSEM能做到的是“有信号时放大信号”但如果信号在下采样过程中已经被抹没了再强的放大也无效。这就引出了二次创新点LSEM-II的核心思路跨尺度拉普拉斯残差。与其只增强当前层的拉普拉斯响应不如把下一层高层特征的拉普拉斯响应上采样回当前层作为上下文高频信息补充进来。P5层的特征虽然分辨率低但它的拉普拉斯响应里包含了“该区域存在强突变”的明确提示。把这个提示上采样到P3层相当于给P3层的小目标检测提供了一个高层语义背书这里确实有个目标放大来看。5.2 LSEM-II的实现思路与代码LSEM-II在LSEM基础上增加一个跨尺度分支把来自更深层的高频特征上采样后加权融合。思路和残差连接类似只不过残差来自拉普拉斯域。核心公式扩展为y_i x_i × (1 alpha_i × g_i) beta_i × Lap(x_i) gamma_i × Upsample(Lap(x_{i1}))。其中i表示当前层i1表示更深一层gamma_i是高层的残差权重初始化为0。这样在网络收敛之前高层残差不参与计算不破坏原有结构。class LSEMv2(nn.Module): def __init__(self, c1, c2None, k3): super().__init__() self.lsem LSEM(c1, kk) self.gamma nn.Parameter(torch.zeros(1, c1, 1, 1)) if c2 is not None and c2 ! c1: self.align nn.Conv2d(c2, c1, 1) else: self.align nn.Identity() def forward(self, x, x_next): y self.lsem(x) lap_next self._laplacian(x_next) lap_next self.align(F.interpolate( lap_next, sizex.shape[-2:], modebilinear, align_cornersFalse )) return y self.gamma * lap_next def _laplacian(self, x): return F.conv2d( x, self.lsem.lap_weight, paddingself.lsem.k // 2, groupsself.lsem.in_channels, )使用时在YOLO的Neck里把相邻两个尺度的特征一起传给LSEMv2例如P4特征连同P5特征一起传入P3特征连同P4特征一起传入。这样跨尺度高频上下文就沿着FPN的路径同步传播了。5.3 在什么场景下用LSEM-II收益最大LSEM-II不是所有场景都该无脑上。它的收益在小目标极度密集的场景下最明显比如遥感密集目标检测、无人机视角的人群车流统计、工厂质检中微小瑕疵的识别。这些场景的共同特征是目标尺度集中在10到20像素之间且目标之间距离近单靠本层特征很难区分密集目标。如果数据集是中大型目标为主比如常规自动驾驶场景里的行人和车辆LSEM-II相比LSEM的额外收益很小反而增加了计算复杂度。我的判断标准很简单如果AP_S列比其他类别低5个点以上就值得上LSEM-II如果差异在3个点以内单层LSEM已经够用。6. 踩坑实录与排查速查表6.1 训练不收敛与梯度爆炸的排查加LSEM后训练loss爆掉九成是alpha和beta没初始化为0。这个坑我在2.3提过但实操中还会遇到另一种情况alpha和beta初始化没问题但门控网络里没有加Sigmoid导致g的输出范围不受限高频增强幅度失控。无论哪个版本排查流程都一样先确认模块在训练前几轮输出和输入是否几乎一致如果差异很大说明初始化有问题。还有一个比较隐蔽的坑拉普拉斯核的权重被冻结在CPU上但模型在GPU上训练导致forward时device不匹配报错。register_buffer会自动跟随模型迁移到GPU但如果有人手贱把它改成了普通Tensor就会踩这个坑。检查方式很简单打印模块的state_dict看lap_weight是不是在buffers里。6.2 加了模块却涨点不明显怎么诊断涨点不明显先别急着调参按这三个步骤排查。第一步确认LSEM加在了哪些层上。如果只加在P5层小目标AP_S不涨很正常把P3层加上再说。第二步看热力图的边缘是否锐化。如果热力图跟baseline几乎一样回到初始化检查。第三步看门控输出的统计分布。打印门控g的均值如果所有位置都接近0或接近1说明门控没有学到空间区分性需要降低学习率或者延长训练轮数。我遇到过一种情况LSEM加的位置没错但训练数据里小目标太少整个训练过程小目标样本的梯度被大目标的梯度淹没导致模块学不到小目标高频恢复能力。这种只能从数据层面解决要么做小目标过采样要么提高小目标在loss里的权重不要指望一个模块能逆天改命。6.3 显存占用和推理速度的优化手段LSEM增加的计算量主要来自三块拉普拉斯卷积、门控网络的concat和卷积、频谱调制的全局池化和卷积。其中拉普拉斯卷积是depthwise卷积开销不大瓶颈主要出在门控网络对2倍通道concat的处理上。如果显存紧张第一招是把频谱调制分支去掉with_spectral设为False大约能省三成显存涨点幅度只降零点几个点。第二招是把门控网络里的标准卷积换成depthwise卷积加pointwise卷积的组合计算量大幅下降。第三招是只在P3和P4两层加LSEM完全不加P5层这是性价比最高的配置。推理速度方面LSEM在训练框架里跑大概增加5%到8%的耗时但如果要做部署一定要用半精度推理拉普拉斯卷积对FP16的精度影响不大。还有个小技巧既然拉普拉斯核是常量部署时可以直接把整条拉普拉斯分支合并成锐化卷积算子合并后基本不增加额外延迟。我自己部署到边缘端时的经验是单尺度增强版本可以跑在实时档位LSEM-II这种跨尺度版本更适合离线分析场景。最后再分享一个小技巧。调试LSEM这类高频增强模块时别只盯着最终mAP。把训练过程中P3层的特征图每10个epoch导出一次观察目标边缘的锐化程度变化趋势比看loss曲线更能直观判断模块有没有学到东西。这个习惯帮我少走了很多弯路也让我确认了LSEM真正的作用并不是简单加高响应而是把特征图从“模糊一团”变成“边界可辨”。后续想继续折腾的话可以把这个模块用到分割任务的decoder上或者和上下文解耦模块结合这条线我感觉空间还挺大。