YOLOv8白皮书-第Y9周:重要模块解读

文件路径:yolov8\ultralytics-main\ultralytics\nn\modules\***

1.conv.py

包含卷积层相关的实现。

import math

inport numpy as np
import torch
import torch.nn as nn

__all__ = ('Conv', 'Conv2', 'LightConv', 'DWConv', 'DWConvTranspose2d', 'ConvTranspose', 'Focus', 'GhostConv',
           'ChannelAttention', 'SpatialAttention', 'CBAM', 'Concat', 'RepConv')

开头列举了该文件中定义的所有模型,如果需要新加一个模块,就需要在文件开头将其名称加入。(这是YOLOv8新增的一个类似声明的内容)

1.1 autopad

def autopad(k, p=None, d=1):  # kernel, padding, dilation
    """Pad to 'same' shape outputs."""
    if d > 1:
        k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k]  # actual kernel-size
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p

功能: 返回pad的大小,使得padding后输出张量的大小不变。
参数:
k : 卷积核(kernel)的大小。类型可能是一个int , 也可能是一个序列 。
p : 填充(padding)的大小。默认为None 。
d : 扩张率(dilation rate)的大小,默认为1 。普通卷积的扩张率为1,空洞卷积的扩张率大于1。

通常用于自动计算卷积层所需的填充(padding),以保持特征图(feature map)的空间维度或者使其符合某种设计意图,如保留边界信息或者使得卷积操作的输出大小等同于输入大小(即所谓的 ‘same’ padding)。

这个函数的作用是,给定一个卷积核尺寸 k、填充 p 和扩张率 d,它会返回一个计算出来的填充大小 p。这样做的目的在于简化网络设计过程,并使得特征图的大小在卷积运算后保持不变,或者是依照设计者的其他特定需求。

1.2  conv

class Conv(nn.Module):
    """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""
    default_act = nn.SiLU()  # default activation

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        """Initialize Conv layer with given arguments including activation."""
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        """Apply convolution, batch normalization and activation to input tensor."""
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        """Perform transposed convolution of 2D data."""
        return self.act(self.conv(x))
  • 功能: 标准卷积模块。
  • 参数:
    • 输入通道数(c1
    • 输出通道数(c2
    • 卷积核大小(k ,默认是1)
    • 步长(s , 默认是1)
    • 填充(p,默认为None)
    • 组(g,默认为1)
    • 扩张率(d,默认为1)
    • 是否采用激活函数(act,默认为True,且采用SiLU为激活函数)

        在前向传播方法forward中,首先对输入张量x进行卷积操作self.conv(x),然后对卷积结果进行批归一化self.bn,最后使用激活函数self.act进行激活,并返回结果。

​         forward_fuse方法,用于执行转置卷积操作。他对输入张量x执行卷积操作self.conv(x),然后使用激活函数self.act进行激活,并返回结果。

1.3  Focus

Focus是原作者自己设计出来,为了减少浮点数和提高速度,而不是增加feature map的,本质就是将图像进行切片,类似于下采样取值,将员图像的宽高信息切分,聚合到channel通道中。结构如下所示:

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(输出通道数),卷积核大小,步长,填充,分组数
        super().__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)
        # self.contract = Contract(gain=2)  # 降维模块(可选)

    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2) (输入x的维度为:批大小,通道数,宽度,高度;输入y的维度为:批大小,4倍通道数,宽度的一半,高度的一半)

#[开始索引:结束索引:步长] x[..., ::2, ::2]最后两个维度,::2表示从0开始,隔两个取一个,即取偶数
        return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))
        # 通过将输入张量x沿通道维度进行拼接四次得到新的输入,然后经过卷积层self.conv处理
        
        # 如果启用降维模块:
        # return self.conv(self.contract(x))

2.block.py 

包含神经网络中的基础块,例如C3,SPPF等。

2.1. C2f

C2f模块就是参考了C3模块以及ELAN的思想进行的设计,让YOLOv8可以在保证轻量化的同时获得更加丰富的梯度流信息。

C2f模块全称为Cross Stage Partial Network Fusion模块,其作用是在不同层次的特征图之间进行信息融合。具体来说,C2F模块主要包括两个部分:SPP(Spatial Pyramid Pooling)和PAN(Path Aggregation Network)。

首先是SPP,它通过构建具有不同尺度池化层的金字塔结构,实现了对不同尺寸目标的有效特征提取。这样能够使得网络具备更好的感知能力,能够识别不同尺寸的物体。

其次是PAN,它主要解决不同尺度特征图之间信息融合的问题。PAN模块采用了多个跨舞台部分网络融合(CSP)模块,将来自浅层和深层特征图的信息进行融合。这样可以提高网络的感知范围,提高目标检测的准确性。

通过使用C2F模块,YOLOv8能够在保持高检测精度的情况下,提高目标检测的速度和效率。C2F模块的引入使得网络具备更好的感知能力和更强的信息融合能力,提高了目标检测的准确性和鲁棒性。

class C2f(nn.Module):
    """C2F层,快速实现带有2个卷积的CSP Bottleneck"""

    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in为输入通道数,ch_out为输出通道数,n表示重复次数,shortcut表示是否使用快捷连接,g表示分组数,e表示扩展比例
        super().__init__()
        self.c = int(c2 * e)  # 隐藏通道数
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)  # 第一个卷积层,用于将输入通道转换为2倍的隐藏通道数
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # 第二个卷积层,用于将隐藏通道数转换为输出通道数;也可选使用激活函数FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))  # 创建包含n个Bottleneck块的ModuleList

    def forward(self, x):
        """通过C2f层进行前向传播"""
        y = list(self.cv1(x).chunk(2, 1))  # 将cv1的输出通道分为两部分
        y.extend(m(y[-1]) for m in self.m) # 将y的最后一个部分输入n个Bottleneck块中,得到一系列的输出
        return self.cv2(torch.cat(y, 1))   # 将这些输出连接起来并输入到cv2中进行最终的输出

    def forward_split(self, x):
#前向传播执行这个操作的时候,会提前声明,例如m.forward=m.forward_split,这个时候就按照这个定义的顺序执行操作。
        """使用split()而不是chunk()进行前向传播"""
        y = list(self.cv1(x).split((self.c, self.c), 1))  # 使用split函数将cv1的输出按指定大小分割
        y.extend(m(y[-1]) for m in self.m)  # 将y的最后一个部分输入n个Bottleneck块中,得到一系列的输出
        return self.cv2(torch.cat(y, 1))  # 将这些输出连接起来并输入到cv2中进行最终的输出

3.head.py

yolov8中的head.py文件主要负责检测模型的头部结构,包括处理检测结果的后处理工作。在这个文件中,首先会定义检测的头部结构,例如在yolov8中常用的YOLOv3检测头部结构。通过定义检测头部的结构,可以更好地处理检测结果,包括筛选出符合条件的目标物体以及对目标物体进行位置和类别的标记。

同时,head.py文件也包括了一些后处理的操作,比如对检测结果进行筛选,去除重复的目标物体或者对目标物体进行位置的微调。此外,head.py文件还会对检测结果进行类别的识别,将目标物体的类别信息进行标记,并将结果输出为可读的格式。

除此之外,head.py文件还会根据检测结果生成对应的边界框,并且根据边界框的位置和大小将目标物体在图像中进行标记。这些操作都是在head.py文件中完成的。

class Detect(nn.Module):
    """YOLOv8 Detect head for detection models."""
    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=()):
        """Initializes the YOLOv8 detection layer with specified number of classes and channels."""
        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], min(self.nc, 100))  # channels
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
        self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(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

        if self.export and self.format in ('tflite', 'edgetpu'):
            # Normalize xywh with image size to mitigate quantization error of TFLite integer models as done in YOLOv5:
            # https://github.com/ultralytics/yolov5/blob/0c8de3fca4a702f8ff5c435e67f378d1fce70243/models/tf.py#L307-L309
            # See this PR for details: https://github.com/ultralytics/ultralytics/pull/1695
            img_h = shape[2] * self.stride[0]
            img_w = shape[3] * self.stride[0]
            img_size = torch.tensor([img_w, img_h, img_w, img_h], device=dbox.device).reshape(1, 4, 1)
            dbox /= img_size

        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)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

房FF房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值