文章目录

00 写在前面0 参考链接1 摘要2 基本原理3 代码(前面是对比介绍,最后是直接可用的代码)3.1 SPDConv源码中的space_to_depth代码3.2 SPDConv源码中是怎么使用spd的呢?3.3 直接可用的代码3.3.1 定义SPDConv模块:将源码space_to_depth和Conv直接融合在一起3.3.2 在yolo.py中修改3.3.3 网络结构:YOLOv7-tiny-SPDConv.yaml

4 一些疑问4.1 整体思想,跟YOLOv5 v5.0版本里的Focus一摸一样哇4.2 为什么论文和代码不一致?

00 写在前面

蓝色括号里面带了os的,是我自己的内心所想,仅供参考最后面有一些疑问,欢迎各位大佬解答

0 参考链接

代码参考–B站视频:YOLOV8改进(三),下采样Conv替换为更细粒度的SPDConv,亲测小目标长点!概念参考–知乎文章:YOLOv5、v7改进之四十一:引入SPD-Conv处理低分辨率图像和小对象问题⭐重点参考的简明版CSDN博文:针对低分辨率或小目标的卷积-SPDConv,有论文、源码地址

论文地址:No More Strided Convolutions or Pooling: A New CNN Building Block for Low-Resolution Images and Small Objects仓库地址:https://gitcode.com/mirrors/labsaint/spd-conv/tree/main,重点查看YOLOv5-SPD文件夹,里面的网络结构YOLOv5-SPD/models/space_depth_s.yaml定义了SPDConv应用在YOLOv5s里面的网络结构,核心代码是YOLOv5-SPD/models/common.py里面的函数space_to_depth本文的基本原理部分的图片来自这篇博文

1 摘要

解决问题:用于图像处理中低分辨率图像和小对象难以检测的问题

在本文中,我们指出,这根源于现有CNN架构中存在的一个有缺陷但常见的设计,即使用strided convolution或pooling layers,这导致细粒度信息的丢失和不够有效的特征表示的学习。为此,我们提出了一个新的CNN构建快, 称为SPD-Conv,取代每个strided convolution层和每个pool层(因为完全消除他们)。SPD-Conv由一个space-to-depth(SPD)层和一个non-strided convolution层组成,可以应用于大多数CNN架构。

2 基本原理

为了解决这一问题,我们提出了一种CNN的新构建快,称为SPD-Conv 完全替代了下采样和池化(os:特征图高宽将缩小为原始的1/2)。SPD-Conv是一个空间到深度层,仅跟随一个非步幅卷积层。SPD层对特征图X进行将采样,当保留了通道维度中的所有信息,因此没有信息损失,我们受到了图像转换技术的启发,该技术在将原始图像馈送到神经网络之前对其进行重新缩放,但我们将其广泛推广到网络内部和整个网络中的特征图的将采样,此外,我们在每个SPD之后添加了一卷积操作,使用科学系的参数减少通道数量,我们提出的方法即通用又统一,即SPD可以应用于大多数CNN架构,并且以相同的方式替代了步幅卷积和池化。

3 代码(前面是对比介绍,最后是直接可用的代码)

就是将YOLOv7-tiny里面的Conv(3,2)替换为SPDConv(3,1),共更改了4处

Conv(3,2)是简写,表示为使用卷积核大小为3×3,步长为2,按照计算公式,特征图尺寸将缩小为原始的一半SPDConv(3,1)也是简写,表示为使用卷积核大小为3×3,步长为1,按照计算公式,特征图尺寸将保持不变

计算公式:输出图像尺寸 = (输入图像尺寸 - 卷积核尺寸 + 2 * 填充)/ 步长 + 1

(os:如果参考文章上面写的那个B站视频,使用大小为1×1的、步长为1的卷积核,计算量和参数量会减少一些,但是可能效果没有那么好(看评论好像效果不大行),然后参考SPDConv的源码,它们也是使用的3×3卷积核,所以本文用到的SPDConv是3×3大小的,实验结果mAP50能提升1.2个百分点)

3.1 SPDConv源码中的space_to_depth代码

仓库地址:https://gitcode.com/mirrors/labsaint/spd-conv/tree/main,重点查看YOLOv5-SPD文件夹,里面的网络结构YOLOv5-SPD/models/space_depth_s.yaml定义了SPDConv应用在YOLOv5s里面的网络结构,核心代码是YOLOv5-SPD/models/common.py里面的函数space_to_depth下列代码是将函数space_to_depth截取出来后,导包和写main函数,使得可以打印出经过spd后的特征图尺寸

对比output和input可以发现,经过spd之后,特征图的通道数成为了原来的4倍,高宽变为了原来的1/2

import torch

import torch.nn as nn

class space_to_depth(nn.Module):

# Changing the dimension of the Tensor

def __init__(self, dimension=1):

super().__init__()

self.d = dimension

def forward(self, x):

return torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)

if __name__ == '__main__':

input = torch.randn(1, 128, 8, 8) # bachsize, c, h, w

spd = space_to_depth()

output = spd(input)

print(output.shape) # torch.Size([1, 512, 4, 4])

来自chatgpt的解释:

在前向传播过程中,torch.cat函数将输入张量 x 按照以下方式进行拼接:

第一个拼接项:x[…, ::2, ::2],表示取输入张量 x 在高度和宽度上步长为2的偶数位置的子图像。 第二个拼接项:x[…, 1::2, ::2],表示取输入张量 x 在高度上步长为2的奇数位置、宽度上步长为2的偶数位置的子图像。 第三个拼接项:x[…, ::2, 1::2],表示取输入张量 x 在高度上步长为2的偶数位置、宽度上步长为2的奇数位置的子图像。 第四个拼接项:x[…, 1::2, 1::2],表示取输入张量 x 在高度和宽度上步长为2的奇数位置的子图像。

3.2 SPDConv源码中是怎么使用spd的呢?

查看源码,发现没有直接可用的SPDConv模块,作者是在网络结构space_depth_s.yaml中定义成了这种以15行的[-1,1,space_to_depth,[1]], 为例,最后面的[1]代表的是space_to_depth中需要的dimension参数,定义从哪个维度进行操作,默认为1

3.3 直接可用的代码

3.3.1 定义SPDConv模块:将源码space_to_depth和Conv直接融合在一起

在yolov7-main-biyebase/models中新建一个名为SPDConv.py的文件,将以下代码复制进去

import torch.nn as nn

import torch

from models.common import Conv

class SPDConv(nn.Module):

# Changing the dimension of the Tensor

def __init__(self, inc=64, outc=128, dimension=1):

super(SPDConv, self).__init__()

self.d = dimension

self.conv = Conv(4*inc, outc, 3, 1, None, 1, nn.LeakyReLU(0.1))

def forward(self, x):

return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], self.d))

if __name__ == '__main__':

input = torch.randn(1, 128, 8, 8)

spdconv = SPDConv(128)

output = spdconv(input)

print(output.shape)

3.3.2 在yolo.py中修改

导入包

from models.SPDConv import SPDConv

搜索n = max(round(n * gd), 1) if n > 1 else n # depth gain,然后在下图位置添加SPDConv

3.3.3 网络结构:YOLOv7-tiny-SPDConv.yaml

# parameters

nc: 80 # number of classes

depth_multiple: 1.0 # model depth multiple

width_multiple: 1.0 # layer channel multiple

# anchors

anchors:

- [10,13, 16,30, 33,23] # P3/8

- [30,61, 62,45, 59,119] # P4/16

- [116,90, 156,198, 373,326] # P5/32

# yolov7-tiny backbone

backbone:

# [from, number, module, args] c2, k=1, s=1, p=None, g=1, act=True

[[-1, 1, SPDConv, [32]], # 0-P1/2

[-1, 1, SPDConv, [64]], # 1-P2/4

[-1, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ---------------ELAN Backbone-1

[-2, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 7 ------------ELAN Backbone-1 end

[-1, 1, MP, []], # 8-P3/8

[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ---------------ELAN Backbone-2

[-2, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 14 ----------ELAN Backbone-2 end

[-1, 1, MP, []], # 15-P4/16

[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # --------------ELAN Backbone-3

[-2, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 21 ----------ELAN Backbone-3 end

[-1, 1, MP, []], # 22-P5/32

[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # --------------ELAN Backbone-4

[-2, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [256, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [256, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [512, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 28 ----------ELAN Backbone-4 end

]

# yolov7-tiny head

head:

[[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ---------------------SPP

[-2, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, SP, [5]],

[-2, 1, SP, [9]],

[-3, 1, SP, [13]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -7], 1, Concat, [1]],

[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 37 -----------------SPP end

[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, nn.Upsample, [None, 2, 'nearest']],

[21, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # route backbone P4

[[-1, -2], 1, Concat, [1]],

[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ----------------------ELAN FPN

[-2, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 47 -----------------ELAN FPN end

[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, nn.Upsample, [None, 2, 'nearest']],

[14, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # route backbone P3

[[-1, -2], 1, Concat, [1]], # ---------------------------FPN end---------------

[-1, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ---------------------ELAN PAN-1

[-2, 1, Conv, [32, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [32, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 57 -----------------ELAN PAN-1 end

[-1, 1, SPDConv, [128]],

[[-1, 47], 1, Concat, [1]],

[-1, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ---------------------ELAN PAN-2

[-2, 1, Conv, [64, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [64, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 65 -----------------ELAN PAN-2 end

[-1, 1, SPDConv, [256]],

[[-1, 37], 1, Concat, [1]],

[-1, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # ---------------------ELAN PAN-3

[-2, 1, Conv, [128, 1, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[-1, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[-1, -2, -3, -4], 1, Concat, [1]],

[-1, 1, Conv, [256, 1, 1, None, 1, nn.LeakyReLU(0.1)]], # 73 -----------------ELAN PAN-3 end

[57, 1, Conv, [128, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[65, 1, Conv, [256, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[73, 1, Conv, [512, 3, 1, None, 1, nn.LeakyReLU(0.1)]],

[[74,75,76], 1, IDetect, [nc, anchors]], # Detect(P3, P4, P5)

]

参数量/M计算量/GFLOPsYOLOv7-tiny6.2313.9YOLOv7-tiny-SPDConv7.39(+1.16)18.6(+4.7)

4 一些疑问

4.1 整体思想,跟YOLOv5 v5.0版本里的Focus一摸一样哇

采用的同样的cat拼接内容并且顺序也是spd->Conv

4.2 为什么论文和代码不一致?

论文里面:先spd,再Conv(可见本文的1 摘要)

代码里面:先Conv,再spd(可见本文的3.2 SPDConv源码中是怎么使用spd的呢?)

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: