前言

本文介绍了 HCF-Net 中的多膨胀通道优化模块——MDCR,用于增强红外小目标检测中的细粒度特征表达能力。该模块将输入特征沿通道划分为多个分支,并分别采用不同膨胀率的深度可分离卷积,以捕获多尺度感受野下的空间上下文信息;随后通过通道交错重组与点卷积融合,实现轻量化的跨分支特征聚合,从而更好地区分小目标与复杂背景。我们将 MDCR 融入 YOLOv8 的 C2f 结构,构建 C2f_MDCR 模块,用于替换 Neck 部分的特征融合单元,提升多尺度特征提取与目标定位能力,尤其适用于红外小目标等弱纹理、小尺寸目标检测场景。

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

专栏链接: YOLOv8改进专栏

介绍

image-20240701160512143

摘要——红外小目标检测是计算机视觉中的一项重要任务,涉及识别和定位红外图像中的微小目标,这些目标通常只有几个像素。然而,由于目标体积小且红外图像背景通常复杂,这项任务面临诸多挑战。本文提出了一种深度学习方法HCF-Net,通过多个实用模块显著提升红外小目标检测性能。具体而言,该方法包括并行化的感知补丁注意力(PPA)模块、维度感知选择性融合(DASI)模块和多膨胀通道优化(MDCR)模块。PPA模块使用多分支特征提取策略来捕捉不同尺度和层次的特征信息。DASI模块实现了自适应的通道选择和融合。MDCR模块通过多层深度可分离卷积捕捉不同感受野范围的空间特征。大量实验结果表明,在SIRST红外单帧图像数据集上,所提出的HCF-Net表现优异,超越了其他传统和深度学习模型。代码可在https://github.com/zhengshuchen/HCFNet获取。

文章链接

论文地址:论文地址

代码地址:代码地址

基本原理

HCF-Net(Hierarchical Context Fusion Network)是一种用于红外小目标检测的深度学习模型,旨在提高对红外图像中微小目标的识别和定位能力。

  1. 网络架构:HCF-Net采用了一种升级版的U-Net架构,主要由三个关键模块组成:Parallelized Patch-Aware Attention(PPA)模块、Dimension-Aware Selective Integration(DASI)模块和Multi-Dilated Channel Refiner(MDCR)模块。这些模块在不同层级上解决了红外小目标检测中的挑战 。

  2. PPA模块

    • Hierarchical Feature Fusion:PPA模块利用分层特征融合和注意力机制,以在多次下采样过程中保持和增强小目标的表示,确保关键信息在整个网络中得以保留[T1]。
    • Multi-Branch Feature Extraction:PPA采用多分支特征提取策略,以捕获不同尺度和级别的特征信息,从而提高小目标检测的准确性 。
  3. DASI模块

    • Adaptive Feature Fusion:DASI模块增强了U-Net中的跳跃连接,专注于高低维特征的自适应选择和精细融合,以增强小目标的显著性 。
  4. MDCR模块

    • Spatial Feature Refinement:MDCR模块通过多个深度可分离卷积层捕获不同感受野范围的空间特征,更细致地建模目标和背景之间的差异,提高了定位小目标的能力 。

    image-20240701161043152

    Multi-Dilated Channel Refiner(MDCR)模块是HCF-Net中的关键组成部分,旨在增强网络对红外小目标检测的特征表示和区分能力。

    1. MDCR模块的目的:MDCR模块旨在通过采用多种扩张率的深度可分离卷积层,捕获不同感受野大小的空间特征,从而实现对目标和背景之间差异的详细建模,提升网络有效区分小目标的能力 。

    2. MDCR模块的结构

      • 输入特征的划分:MDCR模块将输入特征F_a沿通道维度划分为四个独立的头部,生成对应的(a_i),其中i从1到4。每个头部对应特定的特征集合 。
      • 深度可分离扩张卷积:每个头部经历独立的深度可分离扩张卷积,具有不同的扩张率,最终生成(a’_i),其中i从1到4。这些卷积的扩张率分别标记为d1、d2、d3和d4 。
      • 特征增强:通过对各个头部应用深度可分离扩张卷积,MDCR模块增强了特征表示和网络的区分能力 。
    3. MDCR模块的有效性

      • 捕获空间特征:MDCR模块在不同感受野范围内捕获空间特征,使网络更好地理解红外图像中目标的上下文和细节 。
      • 区分小目标:通过利用具有不同扩张率的多个深度可分离卷积层,MDCR模块增强了网络区分小目标和背景的能力,提高了检测性能 。

    如下图所示MDCR沿着通道维度将输入特征 F a ∈ R H × W × C \mathbf{F}_{a}\in\mathbb{R}^{H\times W\times C} FaRH×W×C分成四个不同的头,生成 ( a i ) i = 1 4 ∈ R H × W × C 4 (\mathbf{a}_i)_{i=1}^4\in\mathbb{R}^{H\times W\times\frac{C}{4}} (ai)i=14RH×W×4C。然后,每个头部以不同的扩张率分别进行深度可分离的扩张卷积,得到 ( a i ′ ) i = 1 4 ∈ R H × W × C 4 (\mathbf{a}^{\prime}_i)_{i=1}^4\in\mathbb{R}^{H\times W\times\frac{C}{4}} (ai)i=14RH×W×4C。将卷积扩张率分别命名为 d 1 、 d 2 、 d 3 d1、d2、d3 d1d2d3 d 4 d4 d4

    a i ′ = D D W C o n v ( a i ) , \mathbf{a}_i^{\prime}=DDWConv(\mathbf{a}_i), ai=DDWConv(ai),

    其中, a i ′ \mathbf{a}_i^{\prime} ai表示对第 i i i个头部进行深度可分离扩张卷积后获得的特征。操作 D D W C o n v ( ) DDWConv() DDWConv()表示深度可分离扩张卷积, i i i取值为1、2、3、4。

    MDCR通过通道分割和重组来增强特征表示。具体来说,我们将 a ′ i \mathbf{a'}_i ai分割成单个通道,从而得到每个头部的 ( a i j ) j = 1 C 4 ∈ R H × W × 1 (\mathbf{a}^{j}_{i})_{j=1}^{\frac{C}{4}}\in\mathbb{R}^{H\times W\times1} (aij)j=14CRH×W×1。然后,我们将这些通道交错排列,形成 ( h j ) j = 1 C 4 ∈ R H × W × 4 (\mathbf{h}_j)_{j=1}^{\frac{C}{4}}\in\mathbb{R}^{H\times W\times4} (hj)j=14CRH×W×4,从而增强多尺度特征的多样性。随后,我们使用点式卷积法进行组间和跨组信息融合,得到输出 F o ∈ R H × W × C \mathbf{F}_{o}\in\mathbb{R}^{H\times W\times C} FoRH×W×C,实现轻量高效的聚合效果。

    h j = W i n n e r ( [ a 1 j , a 2 j , a 3 j , a 4 j ] ) , F o = δ ( B ( W o u t e r ( [ h 1 , h 2 , … , h j ] ) ) ) \mathbf{h}_j=Winner([\mathbf{a}^j_1, \mathbf{a}^j_2, \mathbf{a}^j_3, \mathbf{a}^j_4]), \mathbf{F}_{o}=\delta(B(W_{outer}([\mathbf{h}_1, \mathbf{h}_2, \ldots, \mathbf{h}_j]))) hj=Winner([a1j,a2j,a3j,a4j]),Fo=δ(B(Wouter([h1,h2,,hj])))

    其中, W i n n e r W_{inner} Winner W o u t e r W_{outer} Wouter是用于点卷积的权重矩阵。这里, a i j \mathbf{a}^j_i aij表示第 i i i个头的第 j j j个通道,而 h j \mathbf{h}_j hj表示第 j j j组特征。 i ∈ { 1 , 2 , 3 , 4 } i \in \{1, 2, 3, 4\} i{1,2,3,4} j ∈ { 1 , 2 , … , C 4 } j \in \{1, 2, \ldots, \frac{C}{4}\} j{1,2,,4C}。函数 δ ( ) \delta() δ() B ( ) \mathcal{B}() B()分别对应于整流线性单元(ReLU)和批量归一化(BN)。


    image-20240701161122511

核心代码

class MDCR(nn.Module):
    def __init__(self, in_features, out_features, norm_type='bn', activation=True, rate=[1, 6, 12, 18]):
        super().__init__()

        # 定义四个卷积块,每个块使用不同的扩张率进行深度可分离卷积
        self.block1 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[0],
            dilation=rate[0],
            norm_type=norm_type,
            activation=activation,
            groups=128
        )
        self.block2 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[1],
            dilation=rate[1],
            norm_type=norm_type,
            activation=activation,
            groups=128
        )
        self.block3 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[2],
            dilation=rate[2],
            norm_type=norm_type,
            activation=activation,
            groups=128
        )
        self.block4 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[3],
            dilation=rate[3],
            norm_type=norm_type,
            activation=activation,
            groups=128
        )
        
        # 定义输出的尺寸调整卷积块和最终输出卷积块
        self.out_s = conv_block(
            in_features=4,
            out_features=4,
            kernel_size=(1, 1),
            padding=(0, 0),
            norm_type=norm_type,
            activation=activation,
        )
        self.out = conv_block(
            in_features=out_features,
            out_features=out_features,
            kernel_size=(1, 1),
            padding=(0, 0),
            norm_type=norm_type,
            activation=activation,
        )

    def forward(self, x):
        split_tensors = []
        x = torch.chunk(x, 4, dim=1)  # 沿通道维度将输入张量分成四个部分
        x1 = self.block1(x[0])
        x2 = self.block2(x[1])
        x3 = self.block3(x[2])
        x4 = self.block4(x[3])
        
        # 对每个通道进行操作,拼接和处理
        for channel in range(x1.size(1)):
            channel_tensors = [tensor[:, channel:channel + 1, :, :] for tensor in [x1, x2, x3, x4]]
            concatenated_channel = self.out_s(torch.cat(channel_tensors, dim=1))  # 在通道维度上拼接
            split_tensors.append(concatenated_channel)
        
        x = torch.cat(split_tensors, dim=1)  # 沿通道维度拼接
        x = self.out(x)  # 最终输出
        return x

引入代码

在根目录下的ultralytics/nn/目录,新建一个attention 目录,然后新建一个以MDCR为文件名的py文件, 把代码拷贝进去。

import math
import torch
import torch.nn as nn
import torch.nn.functional as F

def autopad(k, p=None, d=1):  # kernel, padding, dilation
    """Pad to 'same' shape outputs."""
    if d > 1:
        k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k]  # actual kernel-size
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p


class Conv(nn.Module):
    """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""

    default_act = nn.SiLU()  # default activation

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        """Initialize Conv layer with given arguments including activation."""
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        """Apply convolution, batch normalization and activation to input tensor."""
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        """Perform transposed convolution of 2D data."""
        return self.act(self.conv(x))



class conv_block(nn.Module):
    def __init__(self,
                 in_features,
                 out_features,
                 kernel_size=(3, 3),
                 stride=(1, 1),
                 padding=(1, 1),
                 dilation=(1, 1),
                 norm_type='bn',
                 activation=True,
                 use_bias=True,
                 groups=1
                 ):
        super().__init__()

        self.conv = nn.Conv2d(in_channels=in_features,
                              out_channels=out_features,
                              kernel_size=kernel_size,
                              stride=stride,
                              padding=padding,
                              dilation=dilation,
                              bias=use_bias,
                              groups=groups)


        self.norm_type = norm_type
        self.act = activation

        if self.norm_type == 'gn':
            self.norm = nn.GroupNorm(32 if out_features >= 32 else out_features, out_features)
        if self.norm_type == 'bn':
            self.norm = nn.BatchNorm2d(out_features)
        if self.act:
            # self.relu = nn.GELU()
            self.relu = nn.ReLU(inplace=False)

    def forward(self, x):
        x = self.conv(x)
        if self.norm_type is not None:
            x = self.norm(x)

        if self.act:
            x = self.relu(x)
        return x

class MDCR(nn.Module):
    def __init__(self, in_features, out_features, norm_type='bn', activation=True, rate=[1, 2, 4, 8]):
        super().__init__()

        self.block1 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[0],
            dilation=rate[0],
            norm_type=norm_type,
            activation=activation,
            groups=8
            )
        self.block2 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[1],
            dilation=rate[1],
            norm_type=norm_type,
            activation=activation,
            groups=8
            )
        self.block3 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[2],
            dilation=rate[2],
            norm_type=norm_type,
            activation=activation,
            groups=8
            )
        self.block4 = conv_block(
            in_features=in_features//4,
            out_features=out_features//4,
            padding=rate[3],
            dilation=rate[3],
            norm_type=norm_type,
            activation=activation,
            groups=8
            )
        self.out_s = conv_block(
            in_features=4,
            out_features=4,
            kernel_size=(1, 1),
            padding=(0, 0),
            norm_type=norm_type,
            activation=activation,
        )
        self.out = conv_block(
            in_features=out_features,
            out_features=out_features,
            kernel_size=(1, 1),
            padding=(0, 0),
            norm_type=norm_type,
            activation=activation,
            )

    def forward(self, x):
        split_tensors = []
        x = torch.chunk(x, 4, dim=1)
        x1 = self.block1(x[0])
        x2 = self.block2(x[1])
        x3 = self.block3(x[2])
        x4 = self.block4(x[3])
        for channel in range(x1.size(1)):
            channel_tensors = [tensor[:, channel:channel + 1, :, :] for tensor in [x1, x2, x3, x4]]
            concatenated_channel = self.out_s(torch.cat(channel_tensors, dim=1))  # 拼接在 batch_size 维度上
            split_tensors.append(concatenated_channel)
        x = torch.cat(split_tensors, dim=1)
        x = self.out(x)
        return x

class C2f_MDCR(nn.Module):
    """Faster Implementation of CSP Bottleneck with 2 convolutions and MDCR blocks."""

    def __init__(self, c1, c2, n=1, k=7, shortcut=False, g=1, e=0.5):
        """Initialize C2f_MDCR layer with CSP bottleneck and MDCR blocks."""
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(MDCR(self.c, self.c) for _ in range(n))

    def forward(self, x):
        """Forward pass through C2f_MDCR layer."""
        y = list(self.cv1(x).chunk(2, 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))

    def forward_split(self, x):
        """Forward pass using split() instead of chunk()."""
        y = list(self.cv1(x).split((self.c, self.c), 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))

tasks注册

ultralytics/nn/tasks.py中进行如下操作:

步骤1:

from ultralytics.nn.C2f.C2f_MDCR import C2f_MDCR

步骤2

修改def parse_model(d, ch, verbose=True):

C2f_MDCR

image-20260712215932092

配置yolov8-C2f_MDCR.yaml

# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect

# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
  # [depth, width, max_channels]
  n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers,  3157200 parameters,  3157184 gradients,   8.9 GFLOPs
  s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients,  28.8 GFLOPs
  m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients,  79.3 GFLOPs
  l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
  x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs

# YOLOv8.0n backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
  - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
  - [-1, 3, C2f, [128, True]]
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 6, C2f, [256, True]]
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 6, C2f, [512, True]]
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 3, C2f, [1024, True]]
  - [-1, 1, SPPF, [1024, 5]] # 9

# YOLOv8.0n head
head:
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 6], 1, Concat, [1]] # cat backbone P4
  - [-1, 3, C2f_MDCR, [512]] # 12

  - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
  - [[-1, 4], 1, Concat, [1]] # cat backbone P3
  - [-1, 3, C2f_MDCR, [256]] # 15 (P3/8-small)

  - [-1, 1, Conv, [256, 3, 2]]
  - [[-1, 12], 1, Concat, [1]] # cat head P4
  - [-1, 3, C2f_MDCR, [512]] # 18 (P4/16-medium)

  - [-1, 1, Conv, [512, 3, 2]]
  - [[-1, 9], 1, Concat, [1]] # cat head P5
  - [-1, 3, C2f_MDCR, [1024]] # 21 (P5/32-large)

  - [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)

实验

脚本


    
import os
from ultralytics import YOLO
 
yaml = 'ultralytics/cfg/models/v8/yolov8-C2f_MDCR.yaml'

 
model = YOLO(yaml) 
 
model.info()
if __name__ == "__main__":
   
    results = model.train(data='./ultralytics/cfg/datasets/coco8.yaml',
                          name='C2f_CascadedGroupAttention',
                          epochs=10,
                          amp=False,
                          workers=8, 
                          batch=1,
                         device="cpu")


结果

image-20260712220100159

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐