YOLO26改进 – 特征融合 EFC增强层间特征相关性,通过多尺度特征交互减少冗余信息丢失即插即用



前言

本文介绍了基于增强层间特征相关性(EFC)的轻量级融合策略及其在 YOLO26中的结合。检测无人机图像小物体具有挑战性,传统多尺度特征融合方法存在不足。EFC 模块通过引入分组的特征聚焦单元(GFF)增强特征关联性,利用多级特征重构模块(MFR)进行特征重构,减少冗余特征生成,且具有通用性和适应性。我们将 EFC 模块集成进 YOLO26,替换部分特征融合策略。该方法即插即用,有望提升 YOLO26在小物体检测任务中的性能。

文章目录: YOLO26改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总

专栏链接: YOLO26改进专栏

介绍

image-20250414210941192

摘要

鉴于无人机图像存在低分辨率以及背景混合的情况,检测其中的小物体颇具挑战性,这会造成特征信息有限。多尺度特征融合能够通过获取不同尺度上的信息来增强检测能力,然而传统方法存在一定缺陷。简单的连接或加法运算无法充分发挥多尺度融合的优势,致使特征之间的相关性不足。这一缺陷对小物体的检测造成了阻碍,尤其是在复杂背景和人口稠密地区。为解决该问题并有效利用有限的计算资源,本文提出一种基于增强层间特征相关性(EFC)的轻量级融合策略,用以替代传统特征金字塔网络(FPN)中的特征融合策略。特征金字塔中不同图层的语义表达存在不一致性。在EFC策略里,分组的特征聚焦单元(GFF)通过聚焦不同特征的上下文信息来增强各层的特征相关性。多级特征重构模块(MFR)对金字塔中各层的强弱信息进行有效的重构和变换,减少冗余的特征融合,从而保留更多的小目标信息。值得一提的是,所提出的方法具有即插即用的特性,能够广泛应用于各种基础网络。

文章链接

论文地址:论文地址

代码地址:代码地址

基本原理

EFC (Enhanced Inter-layer Feature Correlation) 模块是一种轻量级的特征融合策略,旨在提升小物体检测的性能,尤其是在复杂的背景中。以下是对 EFC 模块各组成部分的详细介绍:

  1. 特征关联增强

​ EFC 模块通过引入 Grouped Feature Focusing Unit (GFF) 来强化不同层之间的特征关联性。GFF 通过关注特征的空间上下文信息,聚合邻近层的特征,使得特征之间的语义表示更加一致和丰富。这种对上下文信息的聚焦有助于解决小物体特征的不确定性问题,使模型能够更好地定位小物体。

  1. 特征重构

​ EFC 中的 Multi-Level Feature Reconstruction Module (MFR) 关键在于有效利用各层的特征,而不是仅依赖于简单的卷积操作。MFR 模块能够分离强弱特征信息,通过对特征进行重构,来最大程度地保留对于小物体检测至关重要的细节信息,降低语义偏差。这使得模型在不同层次的特征融合中,能够更精确地理解小物体的空间位置和语义内容。

  1. 冗余特征的减少

​ EFC 特别设计时考虑到了减少冗余特征生成的方法。传统的特征融合常常使用大型卷积核,导致冗余信息的产生,而 EFC 通过采用小型卷积或者轻量级卷积设计,降低了计算复杂度,并在不损失信息的前提下,使得输出特征更加精炼和有用。

  1. 通用性和适应性

​ EFC 模块设计为“插件式”,可以广泛应用于多种特征金字塔网络(FPN)框架,提供了更灵活的检测模型适应能力。这种普遍适用性使得 EFC 可以与不同基础网络结合,进一步提升各种目标检测任务中的表现。

通过上述原理,EFC 模块能够有效应对小物体检测中的挑战,尤其是在多尺度和复杂背景下,增强了特征表达能力,最终实现了更高的检测精度和效率

核心代码

 import torch.nn as nn
import torch.nn.functional as F
from mmcv.cnn import ConvModule
from mmcv.runner import BaseModule, auto_fp16

from ..builder import NECKS

class EFC(BaseModule):
    def __init__(self,
                 c1, c2
                 ):
        super().__init__()
        self.conv1 = nn.Conv2d(c1, c2, kernel_size=1, stride=1)
        self.conv2 = nn.Conv2d(c2, c2, kernel_size=1, stride=1)
        self.conv4 = nn.Conv2d(c2, c2, kernel_size=1, stride=1)
        self.bn = nn.BatchNorm2d(c2)
        self.sigomid = nn.Sigmoid()
        self.group_num = 16
        self.eps = 1e-10
        self.gamma = nn.Parameter(torch.randn(c2, 1, 1))
        self.beta = nn.Parameter(torch.zeros(c2, 1, 1))
        self.gate_genator = nn.Sequential(
            nn.AdaptiveAvgPool2d((1, 1)),
            nn.Conv2d(c2, c2, 1, 1),
            nn.ReLU(True),
            nn.Softmax(dim=1),
        )
        self.dwconv = nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1, groups=c2)
        self.conv3 = nn.Conv2d(c2, c2, kernel_size=1, stride=1)
        self.Apt = nn.AdaptiveAvgPool2d(1)
        self.one = c2
        self.two = c2
        self.conv4_gobal = nn.Conv2d(c2, 1, kernel_size=1, stride=1)
        for group_id in range(0, 4):
            self.interact = nn.Conv2d(c2 // 4, c2 // 4, 1, 1, )

    def forward(self, x):
        x1, x2 = x
        global_conv1 = self.conv1(x1)
        bn_x = self.bn(global_conv1)
        weight_1 = self.sigomid(bn_x)
        global_conv2 = self.conv2(x2)
        bn_x2 = self.bn(global_conv2)
        weight_2 = self.sigomid(bn_x2)
        X_GOBAL = global_conv1 + global_conv2
        x_conv4 = self.conv4_gobal(X_GOBAL)
        X_4_sigmoid = self.sigomid(x_conv4)
        X_ = X_4_sigmoid * X_GOBAL
        X_ = X_.chunk(4, dim=1)
        out = []
        for group_id in range(0, 4):
            out_1 = self.interact(X_[group_id])
            N, C, H, W = out_1.size()
            x_1_map = out_1.reshape(N, 1, -1)
            mean_1 = x_1_map.mean(dim=2, keepdim=True)
            x_1_av = x_1_map / mean_1
            x_2_2 = F.softmax(x_1_av, dim=-1)
            x1 = x_2_2.reshape(N, C, H, W)
            x1 = X_[group_id] * x1
            out.append(x1)
        out = torch.cat([out[0], out[1], out[2], out[3]], dim=1)
        N, C, H, W = out.size()
        x_add_1 = out.reshape(N, self.group_num, -1)
        N, C, H, W = X_GOBAL.size()
        x_shape_1 = X_GOBAL.reshape(N, self.group_num, -1)
        mean_1 = x_shape_1.mean(dim=2, keepdim=True)
        std_1 = x_shape_1.std(dim=2, keepdim=True)
        x_guiyi = (x_add_1 - mean_1) / (std_1 + self.eps)
        x_guiyi_1 = x_guiyi.reshape(N, C, H, W)
        x_gui = (x_guiyi_1 * self.gamma + self.beta)
        weight_x3 = self.Apt(X_GOBAL)
        reweights = self.sigomid(weight_x3)
        x_up_1 = reweights >= weight_1
        x_low_1 = reweights < weight_1
        x_up_2 = reweights >= weight_2
        x_low_2 = reweights < weight_2
        x_up = x_up_1 * X_GOBAL + x_up_2 * X_GOBAL
        x_low = x_low_1 * X_GOBAL + x_low_2 * X_GOBAL
        x11_up_dwc = self.dwconv(x_low)
        x11_up_dwc = self.conv3(x11_up_dwc)
        x_so = self.gate_genator(x_low)
        x11_up_dwc = x11_up_dwc * x_so
        x22_low_pw = self.conv4(x_up)
        xL = x11_up_dwc + x22_low_pw
        xL = xL + x_gui

        return xL

实验

脚本

import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO

if __name__ == '__main__':
#     修改为自己的配置文件地址
    model = YOLO('./ultralytics/cfg/models/26/yolo26-EFC.yaml')
#     修改为自己的数据集地址
    model.train(data='./ultralytics/cfg/datasets/coco8.yaml',
                cache=False,
                imgsz=640,
                epochs=10,
                single_cls=False,  # 是否是单类别检测
                batch=8,
                close_mosaic=10,
                workers=0,
                optimizer='MuSGD',  
                # optimizer='SGD',
                amp=False,
                project='runs/train',
                name='yolo26-EFC',
                )

结果

image-20260125184428057

THE END