YOLOv5中的Focus层

发布时间 2023-07-07 10:37:47作者: 海_纳百川

 


一、背景介绍

Focus层是在YOLOv5中被提出来的。感觉像是一种特殊的下采样的方式。

1.下采样

下采样就是一种缩小图像的手法,用来降低特征的维度并保留有效信息,一定程度上避免过拟合,都是以牺牲部分信息为代价,换取数据量的减少。下采样就是池化操作。但是池化的目的不仅如此,还需要考虑旋转、平移、伸缩不变形等。采样有最大值采样,平均值采样,随机区域采样等,对应池化:比如最大值池化,平均值池化,随机池化等。

2.常见下采样做法

平均值池化:对邻域内特征点只求平均,有点像平滑滤波,根据滑窗的尺寸控制下采样的力度,尺寸越大,它的采样率越高,但边缘信息损失越大。

最大值池化:对邻域内特征点取最大,类似锐化,突出滑窗内的细节点(特殊点)。

具体如下图所示:

 


二、前身:YOLOv2中的PassThrough层

PassThrough层和YOLOv5中的Focus层很像(前身?感觉好像作用一样啊,有没有知道的大佬可以给我讲讲)。他是将相邻的特征堆积在不同的通道中,这样可以将大尺度特征图下采样后与小尺度特征图进行融合,进而增加了小目标检测的精确度。

原理如下:

 


三、Focus层

1.原理

Focus层原理和PassThrough层很类似。它采用切片操作把高分辨率的图片(特征图)拆分成多个低分辨率的图片/特征图,即隔列采样+拼接。原理图如下:

 

原始的640 × 640 × 3的图像输入Focus结构,采用切片(slice)操作,先变成320 × 320 × 12的特征图,拼接(Concat)后,再经过一次卷积(CBL(后期改为SiLU,即为CBS))操作,最终变成320 × 320 × 64的特征图。

 

Focus层将w-h平面上的信息转换到通道维度,再通过3*3卷积的方式提取不同特征。采用这种方式可以减少下采样带来的信息损失 。

2.代码分析

Focus层及其相关代码如下(models/common.py)

(YOLOv5源码:GitHub - ultralytics/yolov5: YOLOv5 ? in PyTorch > ONNX > CoreML > TFLite:)

def autopad(k, p=None):  # kernel, padding自动填充的设计,更加灵活多变
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  
# auto-pad自动填充,通过自动设置填充数p
        #如果k是整数,p为k与2整除后向下取整;如果k是列表等,p对应的是列表中每个元素整除2。
    return p
 
class Conv(nn.Module):
    # 这里对应结构图部分的CBL,CBL = conv+BN+Leaky ReLU,后来改成了SiLU(CBS)
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2) 
#将其变为均值为0,方差为1的正态分布,通道数为c2
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
#其中nn.Identity()是网络中的占位符,并没有实际操作,在增减网络过程中,可以使得整个网络层数据不变,便于迁移权重数据;nn.SiLU()一种激活函数(S形加权线性单元)。
 
    def forward(self, x):#正态分布型的前向传播
        return self.act(self.bn(self.conv(x)))
 
    def forward_fuse(self, x):#普通前向传播
        return self.act(self.conv(x))
 
class Focus(nn.Module):
    # Focus wh information into c-space
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        # self.contract = Contract(gain=2)
 
    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) 
        #图片被分为4块。x[..., ::2, ::2]即行按2叠加取,列也是,对应上面原理图的“1”块块), x[..., 1::2, ::2]对应“3”块块,x[..., ::2, 1::2]指“2”块块,x[..., 1::2, 1::2]指“4”块块。都是每隔一个采样(采奇数列)。用cat连接这些采样图,生成通道数为12的特征图
        # return self.conv(self.contract(x))

 

先采取切片操作(x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2] )把图片分成1,3,2,4共4块(如上面的原理图)

然后进行一个连接

然后再来一次卷积,这里的卷积是自定义卷积:先进行一次卷积,然后变化成正态分布,最后来个SiLU激活,完成。