YOLOv11改进 – C3k2融合 _ C3k2融合EBlock(Encoder Block):低光增强编码器块,利用傅里叶信息增强低光图像
# 前言
本文介绍了用于多任务低光图像恢复的DarkIR模型中的EBlock在YOLOv11中的结合应用。EBlock由空间注意力模块(SpAM)和频域前馈网络(Fre - MLP)组成,通过处理图像的傅里叶域信息提升亮度,具有高效、针对性强和轻量等优势。我们将EBlock集成到YOLOv11的C3k2模块中,构建了C3k2_EBlock。该模块可增强模型在低光环境下的特征提取能力。实验证明,YOLOv11 - C3k2_EBlock在相关任务中表现良好。
文章目录: YOLOv11改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总
专栏链接: YOLOv11改进专栏
介绍
摘要
在夜间或昏暗环境下进行照片拍摄时,因光线条件不佳且常采用长曝光方式,照片往往会出现噪点、亮度不足以及模糊等问题。尽管在此类情形下,去模糊与低光图像增强(LLIE)这两项任务存在一定关联,但多数图像恢复方法仍将二者分开处理。
本文提出了一种高效且稳健的神经网络,用于多任务低光图像恢复。与当前流行的基于Transformer的模型不同,本研究设计了新型注意力机制,以增强高效卷积神经网络(CNNs)的感受野。相较于以往方法,该方法在参数和乘加运算(MAC)方面降低了计算成本。所提出的模型DarkIR在常用的LOLBlur、LOLv2和Real - LOLBlur数据集上取得了新的最优结果,且能较好地适用于真实世界中的夜间及昏暗环境图像。
文章链接
论文地址:论文地址
代码地址:代码地址
基本原理
在文章所提出的DarkIR模型中,EBlock(Encoder Block,编码器块)作为实现低光图像增强的核心组件,其主要功能是借助傅里叶域信息改善图像的光照条件,同时为后续的解码器提供经光照增强后的特征。该组件的设计遵循Metaformer和NAFBlock的结构,具体情况如下:
EBlock的核心功能
EBlock专为低光增强任务而设计,通过处理图像的傅里叶域信息来提升图像亮度,同时提取有效的空间特征。其核心目标如下:
- 增强图像在傅里叶域的幅度信息(该信息与光照条件高度相关);
- 生成低分辨率的光照校正图像,为解码器提供基础;
- 在保证增强效果的同时,降低计算成本。
EBlock的结构组成
EBlock由两个关键模块构成,二者协同完成低光增强任务。
1. 空间注意力模块(SpAM)
- 作用:提取图像的空间特征,为后续的频域增强提供辅助。
- 结构:
- 基于NAFBlock的反向残差块(Inverted Residual Block),在减少参数数量的同时保留重要特征;
- 采用简化的通道注意力(SCA),通过对通道维度进行权重分配,突出关键区域(如高光或暗光区域);
- 运用门控机制(而非传统激活函数),更灵活地控制特征流动,避免因过度激活而导致的信息丢失。
- 功能:聚焦于图像中对光照敏感的空间区域,为频域处理提供具有针对性的空间信息。
2. 频域前馈网络(Fre - MLP)
- 作用:直接在傅里叶域进行操作,增强与光照相关的幅度信息。
- 原理:
- 图像的光照条件主要与傅里叶域的幅度相关(相位信息更多地影响图像结构),因此仅需增强幅度即可改善图像亮度;
- 通过快速傅里叶变换(FFT)将图像从空间域转换至频域,分离出幅度和相位;
- 对幅度进行增强处理(如调整对比度、抑制噪声),再通过逆快速傅里叶变换(IFFT)将其转换回空间域。
- 优势:相较于空间域操作(如通道MLP),频域处理能够更高效地对光照进行全局优化,且对低分辨率图像同样有效。
EBlock的工作流程
- 输入处理:接收低光图像的特征图(该特征图来自前一层或原始输入);
- 空间特征提取:通过SpAM提取空间注意力特征,突出光照敏感区域;
- 频域增强:将特征图转换至傅里叶域,通过Fre - MLP增强幅度信息,再转换回空间域;
- 下采样与特征传递:通过步长卷积(Strided Convolution)对特征图进行下采样(分辨率减半),以减少深层网络的计算量;
- 生成低分辨率图像:将编码后的深层特征通过卷积层进行线性组合,生成分辨率为原始图像1/8的低光增强图像($\hat{x}_{↓8}$),用于模型正则化(确保光照增强效果)。
EBlock的设计优势
- 高效性:通过下采样减少深层网络的运算量,同时频域操作避免了空间域的冗余计算;
- 针对性:专注于傅里叶域的幅度增强,直接解决低光图像的核心问题——光照不足;
- 轻量性:与Transformer模型相比,EBlock基于CNN结构,参数数量更少,适合在移动端进行部署。
核心代码
class EBlock(nn.Module):
'''
Change this block using Branch
'''
def __init__(self, c, DW_Expand=2, dilations = [1], extra_depth_wise = False):
super().__init__()
#we define the 2 branches
self.dw_channel = DW_Expand * c
self.extra_conv = nn.Conv2d(c, c, kernel_size=3, padding=1, stride=1, groups=c, bias=True, dilation=1) if extra_depth_wise else nn.Identity() #optional extra dw
self.conv1 = nn.Conv2d(in_channels=c, out_channels=self.dw_channel, kernel_size=1, padding=0, stride=1, groups=1, bias=True, dilation = 1)
self.branches = nn.ModuleList()
for dilation in dilations:
self.branches.append(Branch(c, DW_Expand, dilation = dilation))
assert len(dilations) == len(self.branches)
self.dw_channel = DW_Expand * c
self.sca = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(in_channels=self.dw_channel // 2, out_channels=self.dw_channel // 2, kernel_size=1, padding=0, stride=1,
groups=1, bias=True, dilation = 1),
)
self.sg1 = SimpleGate()
self.conv3 = nn.Conv2d(in_channels=self.dw_channel // 2, out_channels=c, kernel_size=1, padding=0, stride=1, groups=1, bias=True, dilation = 1)
# second step
self.norm1 = LayerNorm2d(c)
self.norm2 = LayerNorm2d(c)
self.freq = FreMLP(nc = c, expand=2)
self.gamma = nn.Parameter(torch.zeros((1, c, 1, 1)), requires_grad=True)
self.beta = nn.Parameter(torch.zeros((1, c, 1, 1)), requires_grad=True)
# self.adapter = Adapter(c, ffn_channel=None)
# self.use_adapters = False
# def set_use_adapters(self, use_adapters):
# self.use_adapters = use_adapters
def forward(self, inp):
y = inp
x = self.norm1(inp)
x = self.conv1(self.extra_conv(x))
z = 0
for branch in self.branches:
z += branch(x)
z = self.sg1(z)
x = self.sca(z) * z
x = self.conv3(x)
y = inp + self.beta * x
#second step
x_step2 = self.norm2(y) # size [B, 2*C, H, W]
x_freq = self.freq(x_step2) # size [B, C, H, W]
x = y * x_freq
x = y + x * self.gamma
# if self.use_adapters:
# return self.adapter(x)
# else:
return x
实验
脚本
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
# 修改为自己的配置文件地址
model = YOLO('/root/ultralytics-main/ultralytics/cfg/models/11/yolov11-C3k2_EBlock.yaml')
# 修改为自己的数据集地址
model.train(data='/root/ultralytics-main/ultralytics/cfg/datasets/coco8.yaml',
cache=False,
imgsz=640,
epochs=10,
single_cls=False, # 是否是单类别检测
batch=8,
close_mosaic=10,
workers=0,
optimizer='SGD',
amp=True,
project='runs/train',
name='C3k2_EBlock',
)