【免费公开】yolo11添加ASFF检测头

该文章已生成可运行项目,

  1. 在ultralytics\nn\modules目录下新建ASFFHead.py文件,将ASFF代码写入ASFFHead.py文件,具体代码如下:
    import torch
    import torch.nn as nn
    from ultralytics.utils.tal import dist2bbox, make_anchors
    import math
    import torch.nn.functional as F
    
    __all__ = ['Detect_ASFF']
    
    
    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
    
    
    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))
    
    
    class DFL(nn.Module):
        """
        Integral module of Distribution Focal Loss (DFL).
        Proposed in Generalized Focal Loss https://ieeexplore.ieee.org/document/9792391
        """
    
        def __init__(self, c1=16):
            """Initialize a convolutional layer with a given number of input channels."""
            super().__init__()
            self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
            x = torch.arange(c1, dtype=torch.float)
            self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
            self.c1 = c1
    
        def forward(self, x):
            """Applies a transformer layer on input tensor 'x' and returns a tensor."""
            b, c, a = x.shape  # batch, channels, anchors
            return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)
            # return self.conv(x.view(b, self.c1, 4, a).softmax(1)).view(b, 4, a)
    
    
    class ASFFV5(nn.Module):
        def __init__(self, level, ch, multiplier=1, rfb=False, vis=False, act_cfg=True):
            """
            ASFF version for YoloV5 .
            different than YoloV3
            multiplier should be 1, 0.5 which means, the channel of ASFF can be
            512, 256, 128 -> multiplier=1
            256, 128, 64 -> multiplier=0.5
            For even smaller, you need change code manually.
            """
            super(ASFFV5, self).__init__()
            self.level = level
            self.dim = [int(ch[2] * multiplier), int(ch[1] * multiplier),
                        int(ch[0] * multiplier)]
            # print(self.dim)
    
            self.inter_dim = self.dim[self.level]
            if level == 0:
                self.stride_level_1 = Conv(int(ch[1] * multiplier), self.inter_dim, 3, 2)
    
                self.stride_level_2 = Conv(int(ch[0] * multiplier), self.inter_dim, 3, 2)
    
                self.expand = Conv(self.inter_dim, int(
                    ch[2] * multiplier), 3, 1)
            elif level == 1:
                self.compress_level_0 = Conv(
                    int(ch[2] * multiplier), self.inter_dim, 1, 1)
                self.stride_level_2 = Conv(
                    int(ch[0] * multiplier), self.inter_dim, 3, 2)
                self.expand = Conv(self.inter_dim, int(ch[1] * multiplier), 3, 1)
            elif level == 2:
                self.compress_level_0 = Conv(
                    int(ch[2] * multiplier), self.inter_dim, 1, 1)
                self.compress_level_1 = Conv(
                    int(ch[1] * multiplier), self.inter_dim, 1, 1)
                self.expand = Conv(self.inter_dim, int(
                    ch[0] * multiplier), 3, 1)
    
            # when adding rfb, we use half number of channels to save memory
            compress_c = 8 if rfb else 16
            self.weight_level_0 = Conv(
                self.inter_dim, compress_c, 1, 1)
            self.weight_level_1 = Conv(
                self.inter_dim, compress_c, 1, 1)
            self.weight_level_2 = Conv(
                self.inter_dim, compress_c, 1, 1)
    
            self.weight_levels = Conv(
                compress_c * 3, 3, 1, 1)
            self.vis = vis
    
        def forward(self, x):  # l,m,s
            """
            # 128, 256, 512
            512, 256, 128
            from small -> large
            """
            x_level_0 = x[2]  # l
            x_level_1 = x[1]  # m
            x_level_2 = x[0]  # s
            # print('x_level_0: ', x_level_0.shape)
            # print('x_level_1: ', x_level_1.shape)
            # print('x_level_2: ', x_level_2.shape)
            if self.level == 0:
                level_0_resized = x_level_0
                level_1_resized = self.stride_level_1(x_level_1)
                level_2_downsampled_inter = F.max_pool2d(
                    x_level_2, 3, stride=2, padding=1)
                level_2_resized = self.stride_level_2(level_2_downsampled_inter)
            elif self.level == 1:
                level_0_compressed = self.compress_level_0(x_level_0)
                level_0_resized = F.interpolate(
                    level_0_compressed, scale_factor=2, mode='nearest')
                level_1_resized = x_level_1
                level_2_resized = self.stride_level_2(x_level_2)
            elif self.level == 2:
                level_0_compressed = self.compress_level_0(x_level_0)
                level_0_resized = F.interpolate(
                    level_0_compressed, scale_factor=4, mode='nearest')
                x_level_1_compressed = self.compress_level_1(x_level_1)
                level_1_resized = F.interpolate(
                    x_level_1_compressed, scale_factor=2, mode='nearest')
                level_2_resized = x_level_2
    
            # print('level: {}, l1_resized: {}, l2_resized: {}'.format(self.level,
            #      level_1_resized.shape, level_2_resized.shape))
            level_0_weight_v = self.weight_level_0(level_0_resized)
            level_1_weight_v = self.weight_level_1(level_1_resized)
            level_2_weight_v = self.weight_level_2(level_2_resized)
            # print('level_0_weight_v: ', level_0_weight_v.shape)
            # print('level_1_weight_v: ', level_1_weight_v.shape)
            # print('level_2_weight_v: ', level_2_weight_v.shape)
    
            levels_weight_v = torch.cat(
                (level_0_weight_v, level_1_weight_v, level_2_weight_v), 1)
            levels_weight = self.weight_levels(levels_weight_v)
            levels_weight = F.softmax(levels_weight, dim=1)
    
            fused_out_reduced = level_0_resized * levels_weight[:, 0:1, :, :] + \
                                level_1_resized * levels_weight[:, 1:2, :, :] + \
                                level_2_resized * levels_weight[:, 2:, :, :]
    
            out = self.expand(fused_out_reduced)
    
            if self.vis:
                return out, levels_weight, fused_out_reduced.sum(dim=1)
            else:
                return out
    
    
    class Detect_ASFF(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=(), multiplier=1, rfb=False):
            """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()
            self.l0_fusion = ASFFV5(level=0, ch=ch, multiplier=multiplier, rfb=rfb)
            self.l1_fusion = ASFFV5(level=1, ch=ch, multiplier=multiplier, rfb=rfb)
            self.l2_fusion = ASFFV5(level=2, ch=ch, multiplier=multiplier, rfb=rfb)
    
        def forward(self, x):
            """Concatenates and returns predicted bounding boxes and class probabilities."""
            x1 = self.l0_fusion(x)
            x2 = self.l1_fusion(x)
            x3 = self.l2_fusion(x)
            x = [x3, x2, x1]
            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)
    
    
    if __name__ == "__main__":
        # Generating Sample image
        image1 = (1, 64, 32, 32)
        image2 = (1, 128, 16, 16)
        image3 = (1, 256, 8, 8)
    
        image1 = torch.rand(image1)
        image2 = torch.rand(image2)
        image3 = torch.rand(image3)
        image = [image1, image2, image3]
        channel = (64, 128, 256)
        # Model
        mobilenet_v1 = Detect_ASFF(nc=80, ch=channel)
    
        out = mobilenet_v1(image)
        print(out)
  2. 在ultralytics\nn\modules目录下找到__init__.py文件,将Detect_ASFF类导入其中并进行注册

    在__all__ = ()中进行注册,如下图

  3. 在ultralytics\nn\tasks.py中导入ASFFhead类,并找到def parse_model函数,把ASFFHead添加到其中:

    找到

    from ultralytics.nn.modules import 

    并导入Detect_ASFF类:

    把Detect_ASFF添加到parse_model函数中:

  4. 编写添加了ASFFHead检测头的模型结构文件:
    在ultralytics\cfg\models\11目录下新建yolo11-ASFF.yaml,并将模型配置写入其中:

    模型配置如下:

    # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
    
    # Ultralytics YOLO11 object detection model with P3/8 - P5/32 outputs
    # Model docs: https://docs.ultralytics.com/models/yolo11
    # Task docs: https://docs.ultralytics.com/tasks/detect
    
    # Parameters
    nc: 80 # number of classes
    scales: # model compound scaling constants, i.e. 'model=yolo11n.yaml' will call yolo11.yaml with scale 'n'
      # [depth, width, max_channels]
      n: [0.50, 0.25, 1024] # summary: 319 layers, 2624080 parameters, 2624064 gradients, 6.6 GFLOPs
      s: [0.50, 0.50, 1024] # summary: 319 layers, 9458752 parameters, 9458736 gradients, 21.7 GFLOPs
      m: [0.50, 1.00, 512] # summary: 409 layers, 20114688 parameters, 20114672 gradients, 68.5 GFLOPs
      l: [1.00, 1.00, 512] # summary: 631 layers, 25372160 parameters, 25372144 gradients, 87.6 GFLOPs
      x: [1.00, 1.50, 512] # summary: 631 layers, 56966176 parameters, 56966160 gradients, 196.0 GFLOPs
    
    # YOLO11n backbone
    backbone:
      # [from, repeats, module, args]
      - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
      - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
      - [-1, 2, C3k2, [256, False, 0.25]]
      - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
      - [-1, 2, C3k2, [512, False, 0.25]]
      - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
      - [-1, 2, C3k2, [512, True]]
      - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
      - [-1, 2, C3k2, [1024, True]]
      - [-1, 1, SPPF, [1024, 5]] # 9
      - [-1, 2, C2PSA, [1024]] # 10
    
    # YOLO11n head
    head:
      - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
      - [[-1, 6], 1, Concat, [1]] # cat backbone P4
      - [-1, 2, C3k2, [512, False]] # 13
    
      - [-1, 1, nn.Upsample, [None, 2, "nearest"]]
      - [[-1, 4], 1, Concat, [1]] # cat backbone P3
      - [-1, 2, C3k2, [256, False]] # 16 (P3/8-small)
    
      - [-1, 1, Conv, [256, 3, 2]]
      - [[-1, 13], 1, Concat, [1]] # cat head P4
      - [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium)
    
      - [-1, 1, Conv, [512, 3, 2]]
      - [[-1, 10], 1, Concat, [1]] # cat head P5
      - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large)
    
      - [[16, 19, 22], 1, Detect_ASFF, [nc]] # Detect(P3, P4, P5)
    
  5. 修改训练文件,配置模型文件为yolo11-ASFF.yaml文件并开始训练:

本文章已经生成可运行项目
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值