YOLO11 改进 – 检测头 _ DetectDeepDBB 基于深度多样分支块的检测头:优化特征提取流程,改善多尺度目标检测
前言
本文介绍了 DeepDBB 技术在 YOLOv11 中的结合应用。DeepDBB 是改进的网络模块,继承 WDBB 思想,通过多分支和多样化卷积设计提升特征提取能力。其核心是多样化的多分支卷积结构,包括常规 KxK 卷积、1x1 卷积、深度可分离卷积和非对称卷积,能处理不同特征模式。还进行多尺度特征融合,增强模型对不同尺寸目标的处理能力。训练时用多分支结构,推理时合并卷积核,减少计算负担。我们将 DeepDBB 集成进 YOLOv11 的检测头,替换原有模块。实验表明,改进后的 YOLOv11 在目标检测任务中表现出色。
文章目录: YOLOv11改进大全:卷积层、轻量化、注意力机制、损失函数、Backbone、SPPF、Neck、检测头全方位优化汇总
专栏链接: YOLOv11改进专栏
文章目录
[TOC]
介绍
摘要
本文针对灰度图像中目标检测的挑战,提出了一种增强型目标检测网络YOLO-MIF,该网络整合了多种多信息融合策略,以改进YOLOv8网络。文章首先介绍了一种技术,用于创建伪多通道灰度图像,增加网络的通道信息,并减轻潜在的图像噪声和虚焦模糊问题。随后,采用网络结构重新参数化技术,提升网络的检测性能而不增加推断时间。另外,引入了一种新颖的解耦式检测头,增强了模型在处理灰度图像时的表现力。文章还对该算法在两个开源灰度图像检测数据集(NEU-DET和FLIR-ADAS)上进行了评估。结果表明,在相同速度下,该算法在平衡检测效率和有效性方面优于YOLOv8 2.1%,优于Faster R-CNN 4.8%,取得了更好的性能表现。
创新点
DeepDBB (Deep Diverse Branch Block)是一种改进的网络模块,设计用于提升卷积神经网络的特征提取能力,特别是对于复杂的目标检测和分类任务。DeepDBB 继承了 WDBB (Wide Diverse Branch Block) 的思想,通过多分支和多样化的卷积设计,实现更为丰富的特征表达。其核心在于结合多个不同类型的卷积操作,在增强网络的特征提取能力的同时减少推理阶段的计算负担。以下是 DeepDBB 的技术原理和实现细节:
1. 多样化的多分支卷积结构
DeepDBB 模块的设计核心是通过多个卷积分支来丰富特征空间,不同的卷积分支负责不同的特征提取任务,从而形成更加多样的特征表征。典型的分支结构包括:
-
常规 KxK 卷积 :用于标准的特征提取。
-
1x1 卷积 :用于降低维度,通常被用作瓶颈层来减少计算量,并增加网络的非线性表达能力。
-
深度可分离卷积 :通过深度卷积和逐点卷积的组合来提取更细粒度的特征,并显著降低计算量。
-
非对称卷积(如 1xK 和 Kx1) :通过在特定方向上扩展感受野,使网络能够捕捉到更广泛的特征。
这些分支结构能够处理不同的特征模式,例如大尺度特征、边缘特征和方向性特征,从而实现特征的多样性表达。
2. 多尺度特征融合
DeepDBB 通过不同分支卷积的组合,在不同的尺度上提取特征,并将这些特征进行融合。这种多尺度特征融合使得模型可以处理不同尺寸的目标,尤其是在目标检测任务中表现出色。多尺度融合的方式可以有效增强网络对小目标和大目标的识别能力。
3. 卷积核重构和身份转换
在训练阶段,DeepDBB 使用多分支结构增加模型的学习能力,但在推理阶段会将多分支卷积核进行合并,转换为等效的单一 KxK 卷积核。这个过程可以分为以下步骤:
-
分支扩展 :将各个分支的卷积核进行扩展,例如将 1x1 卷积核通过零填充扩展为 KxK 卷积核。
-
卷积核融合 :对不同分支的卷积核进行线性组合或加权融合,生成一个单一的 KxK 卷积核。这一融合过程确保了模型在推理阶段可以简化为单一的 KxK 卷积,从而减少计算量。
4. 训练与推理阶段的分离优化
DeepDBB 利用了“训练-推理分离优化”的思想,在训练阶段采用多分支和多样化卷积设计来提升特征学习能力,而在推理阶段通过合并分支简化网络结构。这种设计使得模型在推理时更加轻量化和高效,同时保留了训练阶段的丰富特征表达能力。
5. 深度卷积与残差连接的结合
在 DeepDBB 中,深度卷积操作通常与残差连接结合使用,以增强梯度传播和特征复用。这种结构能够缓解深层网络中梯度消失的问题,使得网络可以更深,从而学习更复杂的特征。此外,残差连接能够促进不同层次特征的融合,进一步增强特征提取能力。
文章链接
论文地址: 论文地址
代码地址: 代码地址
核心代码
class DetectDeepDBB(nn.Module):
dynamic = False # force grid reconstruction
export = False # export mode
shape = None
anchors = torch.empty(0) # init
strides = torch.empty(0) # init
def __init__(self, nc=80, ch=()): # detection layer
super().__init__()
self.nc = nc # number of classes
self.nl = len(ch) # number of detection layers
self.reg_max = 16 # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
self.no = nc + self.reg_max * 4 # number of outputs per anchor
self.stride = torch.zeros(self.nl) # strides computed during build
c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], self.nc) # channels
self.cv2 = nn.ModuleList(
nn.Sequential(DeepDiverseBranchBlock(x, c2, 3), DeepDiverseBranchBlock(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
self.cv3 = nn.ModuleList(nn.Sequential(DeepDiverseBranchBlock(x, c3, 3), DeepDiverseBranchBlock(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()
def forward(self, x):
"""Concatenates and returns predicted bounding boxes and class probabilities."""
shape = x[0].shape # BCHW
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:
return x
elif self.dynamic or self.shape != shape:
self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
self.shape = shape
x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
if self.export and self.format in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs'): # avoid TF FlexSplitV ops
box = x_cat[:, :self.reg_max * 4]
cls = x_cat[:, self.reg_max * 4:]
else:
box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)
dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
y = torch.cat((dbox, cls.sigmoid()), 1)
return y if self.export else (y, x)
def bias_init(self):
"""Initialize Detect() biases, WARNING: requires stride availability."""
m = self # self.model[-1] # Detect() module
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
# ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # nominal class frequency
for a, b, s in zip(m.cv2, m.cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[:m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)
实验
脚本
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
# 修改为自己的配置文件地址
model = YOLO('/root/ultralytics-main/ultralytics/cfg/models/11/yolov11-DetectDeepDBB.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='DetectDeepDBB',
)