前言
本文介绍了将双坐标注意力特征提取(DCAFE)模块与YOLOv11相结合的方法。DCAFE模块采用“并行坐标注意力+双池化融合”设计,通过平均池化和最大池化并行支路捕获特征,经通道自适应调整生成注意力权重,增强特征表达。我们将DCAFE模块引入YOLOv11,对相关代码进行修改和注册,并配置了yolov11 - DCAFE.yaml文件。
文章目录: YOLOv11改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总
专栏链接: YOLOv11改进专栏
介绍

摘要
精准的药用花卉分类是其在医疗保健、制药以及食品和化妆品行业中发挥重要作用的前提。此外,了解药用花卉物种也是保护和维护生物多样性的必要条件。然而,由于较大的类内差异、较小的类间差异以及与其他树木类别的视觉相似性,野外药用花卉分类是一项具有挑战性的任务。如今,众多深度学习方法(尤其是卷积神经网络)已被应用于多个基于图像的目标分类任务中。但现有方法难以捕捉花瓣纹理、花卉结构变异等复杂特征图。本文提出一种新型 “Flora-NET” 方法,首次将坐标注意力与内卷神经网络相结合,应用于花卉行业的分类任务。该方法实现了全新的特征细化模块,即双坐标注意力特征提取(DCAFE)和内卷基特征细化(Inv-FR)。DCAFE 模块采用平均池化与最大池化相结合的并行坐标注意力方法;Inv-FR 模块通过串行内卷层和自适应核,进一步增强空间信息。研究人员在两个公开可用的药用花卉数据集上对 Flora-NET 方法进行了训练和测试。大量实验表明,该方法在两个基准数据集上的准确率分别比现有的深度神经分类方法高出约 6.50% 和 5.59%。超参数调优、k 折交叉验证和消融实验也验证了 Flora-NET 方法的有效性。相关代码和数据集可通过以下链接获取:https://github.com/ersachingupta11/Flora-NET。
文章链接
论文地址:论文地址
代码地址:代码地址
基本原理
核心设计原理
DCAFE模块的核心是“并行坐标注意力+双池化融合”,突破了传统坐标注意力(CA)仅用平均池化的局限,具体逻辑如下:
- 坐标注意力基础:通过水平(X轴)和垂直(Y轴)两个方向的特征编码,捕获位置敏感信息,让模型精准定位花卉区域。
- 双池化并行设计:同时使用平均池化(捕获全局通用特征,抑制背景噪声)和最大池化(保留局部关键特征,突出花卉纹理/边缘),两条支路独立运算后融合,避免单一池化导致的信息丢失。
- 通道自适应调整:通过1×1卷积实现通道降维与恢复,平衡模型复杂度和特征表达能力。
详细结构与流程
假设输入特征图为 ( F \in H×W×C )(H=高度、W=宽度、C=通道数),DCAFE模块的处理步骤如下:
1. 特征方向编码(双池化并行支路)
- 平均池化支路:对输入特征图分别进行水平(核尺寸 (H,1))和垂直(核尺寸 (1,W))平均池化,得到 ( X{AvgPool} \in H×1×C ) 和 ( Y{AvgPool} \in 1×W×C ),捕获全局平滑特征。
- 最大池化支路:同理,进行水平和垂直最大池化,得到 ( X{MaxPool} \in H×1×C ) 和 ( Y{MaxPool} \in 1×W×C ),保留局部尖锐特征(如花瓣边缘、花蕊轮廓)。
2. 特征拼接与降维
- 每条支路中,将水平和垂直编码后的特征向量拼接,得到 ( (H+W)×1×C ) 的特征向量(如64×64×96的输入,拼接后为128×1×96)。
- 通过1×1卷积进行通道降维,下采样率 ( D_r=32 ),将通道数从C压缩至 ( C/D_r ),减少计算量,得到中间特征向量 ( f^a \in (H+W)×1×(C/D_r) )。
3. 注意力权重生成
- 将降维后的特征向量沿空间维度拆分为水平(( f_h^a \in H×1×(C/D_r) ))和垂直(( f_w^a \in W×1×(C/D_r) ))两个张量。
- 对两个张量分别应用1×1卷积恢复通道数至C,并通过sigmoid激活函数生成注意力权重矩阵 ( s_h \in H×1×C )(水平权重)和 ( s_w \in 1×W×C )(垂直权重)。
4. 特征加权与融合
- 每条支路(平均/最大池化)的权重矩阵与原始输入特征图 ( F ) 相乘,得到加权后的特征图 ( Y^a )(平均池化支路输出)和 ( Y^m )(最大池化支路输出)。
- 将两条支路的输出特征图拼接,最终生成1260个增强通道的特征图,传递给后续Inv-FR模块。
核心代码
class CoordAttMeanMax(nn.Module):
def __init__(self, inp, oup, groups=32):
super(CoordAttMeanMax, self).__init__()
self.pool_h_mean = nn.AdaptiveAvgPool2d((None, 1))
self.pool_w_mean = nn.AdaptiveAvgPool2d((1, None))
self.pool_h_max = nn.AdaptiveMaxPool2d((None, 1))
self.pool_w_max = nn.AdaptiveMaxPool2d((1, None))
mip = max(8, inp // groups)
self.conv1_mean = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
self.bn1_mean = nn.BatchNorm2d(mip)
self.conv2_mean = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
self.conv1_max = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
self.bn1_max = nn.BatchNorm2d(mip)
self.conv2_max = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
identity = x
n, c, h, w = x.size()
# Mean pooling branch
x_h_mean = self.pool_h_mean(x)
x_w_mean = self.pool_w_mean(x).permute(0, 1, 3, 2)
y_mean = torch.cat([x_h_mean, x_w_mean], dim=2)
y_mean = self.conv1_mean(y_mean)
y_mean = self.bn1_mean(y_mean)
y_mean = self.relu(y_mean)
x_h_mean, x_w_mean = torch.split(y_mean, [h, w], dim=2)
x_w_mean = x_w_mean.permute(0, 1, 3, 2)
# Max pooling branch
x_h_max = self.pool_h_max(x)
x_w_max = self.pool_w_max(x).permute(0, 1, 3, 2)
y_max = torch.cat([x_h_max, x_w_max], dim=2)
y_max = self.conv1_max(y_max)
y_max = self.bn1_max(y_max)
y_max = self.relu(y_max)
x_h_max, x_w_max = torch.split(y_max, [h, w], dim=2)
x_w_max = x_w_max.permute(0, 1, 3, 2)
# Apply attention
x_h_mean = self.conv2_mean(x_h_mean).sigmoid()
x_w_mean = self.conv2_mean(x_w_mean).sigmoid()
x_h_max = self.conv2_max(x_h_max).sigmoid()
x_w_max = self.conv2_max(x_w_max).sigmoid()
# Expand to original shape
x_h_mean = x_h_mean.expand(-1, -1, h, w)
x_w_mean = x_w_mean.expand(-1, -1, h, w)
x_h_max = x_h_max.expand(-1, -1, h, w)
x_w_max = x_w_max.expand(-1, -1, h, w)
# Combine outputs
attention_mean = identity * x_w_mean * x_h_mean
attention_max = identity * x_w_max * x_h_max
# Sum the attention outputs
return attention_mean + attention_max
class DCAFE(nn.Module):
def __init__(self, in_channels,out_channels):
super(DCAFE, self).__init__()
self.coord_att = CoordAttMeanMax(in_channels, out_channels)
def forward(self, x):
return self.coord_att(x)