YOLOV8の改良 - 動的スネークコnvolutionの導入

01 動的スネークコnvolutionの概要

主に管状構造のセグメンテーションに使用されるコnvolutionで、血管や道路などの細長い連続的な構造を対象としています。スネークコnvolutionは高精度なセグメンテーションを可能にしますが、その課題は細かい局所的な構造特徴と複雑で変動する全体的な形状特徴からの学習です。したがって、モデルは特徴を学ぶ際にコnvolutionカーネルの形状を変化させることで、管状構造の核となる特徴に注目します。管状構造が全体の中での割合が少ないため、モデルはそれらの構造を認識することを避けがちです。コnvolutionカーネルが目標から完全に逸脱することもあります。そこで、管状構造の特性に基づいて特定のネットワーク構造を設計し、モデルが重要な特徴に注目するようにすることが目的となります。

この記事では、管状構造の細長い連続的なトポロジカル特性を利用して、以下の3つの段階で感知を強化します:特徴抽出、特徴融合、損失制約。それぞれ動的スネークコnvolution、マルチビューアプローチの特徴融合戦略、連続性トポロジカル損失を提案します。さらに2Dおよび3Dの手法も提案し、実験結果によると、この論文で提案したDSCNetは管状構造のセグメンテーションタスクにおいて高い精度と連続性を提供しています。

論文へのリンク: https://arxiv.org/abs/2307.08388

02 DySnakeConv - 動的スネークコnvolutionのコード

以下のコードには以下のものが含まれています:

DySnakeConv - 単一のスネークコnvolution
DSConv - スネークコnvolutionで使用されるオフセットコnvolution
DSC - オフセットコnvolutionで使用されるモジュール
class FlexibleSnakeConv(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size=3, activation=True) -> None:
        super().__init__()

        self.conv_initial = BasicConv(in_channels, out_channels, kernel_size, activation=activation)
        self.conv_x = OffsetConv(in_channels, out_channels, direction=0, kernel_size=kernel_size)
        self.conv_y = OffsetConv(in_channels, out_channels, direction=1, kernel_size=kernel_size)
        self.conv_final = BasicConv(out_channels * 3, out_channels, 1, activation=activation)

    def forward(self, x):
        combined_features = torch.cat([self.conv_initial(x), self.conv_x(x), self.conv_y(x)], dim=1)
        return self.conv_final(combined_features)

class OffsetConv(nn.Module):
    def __init__(self, in_ch, out_ch, direction, kernel_size=3, apply_deformation=True, deformation_range=1):
        """
        動的スネークコnvolution
        :param in_ch: 入力チャネル数
        :param out_ch: 出力チャネル数
        :param kernel_size: カーネルサイズ
        :param deformation_range: 变形範囲(このメソッドではデフォルトで1)
        :param direction: コnvolutionカーネルの形態は主にx軸(0)とy軸(1)の2つに分類される
        :param apply_deformation: 変形が必要かどうか、Falseの場合標準的なコnvolutionカーネルとなる
        """
        super(OffsetConv, self).__init__()
        # <offset_conv>を使用して可変形オフセットを学習
        self.offset_conv = nn.Conv2d(in_ch, 2 * kernel_size, 3, padding=1)
        self.bn = nn.BatchNorm2d(2 * kernel_size)
        self.kernel_size = kernel_size

        # 2種類のOffsetConv(x軸方向とy軸方向)
        self.snake_conv_x = nn.Conv2d(
            in_ch,
            out_ch,
            kernel_size=(kernel_size, 1),
            stride=(kernel_size, 1),
            padding=0,
        )
        self.snake_conv_y = nn.Conv2d(
            in_ch,
            out_ch,
            kernel_size=(1, kernel_size),
            stride=(1, kernel_size),
            padding=0,
        )

        self.gn = nn.GroupNorm(out_ch // 4, out_ch)
        self.act = BasicConv.default_activation

        self.deformation_range = deformation_range
        self.direction = direction
        self.apply_deformation = apply_deformation

    def forward(self, feature_map):
        offset = self.offset_conv(feature_map)
        offset = self.bn(offset)
        # スネークの振れ幅を模倣するために-1から1までの範囲が必要
        offset = torch.tanh(offset)
        input_shape = feature_map.shape
        snake_module = SnakeModule(input_shape, self.kernel_size, self.deformation_range, self.direction)
        deformed_feature = snake_module.deformable_conv(feature_map, offset, self.apply_deformation)
        if self.direction == 0:
            result = self.snake_conv_x(deformed_feature.type(feature_map.dtype))
            result = self.gn(result)
            result = self.act(result)
            return result
        else:
            result = self.snake_conv_y(deformed_feature.type(feature_map.dtype))
            result = self.gn(result)
            result = self.act(result)
            return result

# 核心コード、理解しやすくするために各コードの入力と出力の次元を示す
class SnakeModule(object):
    def __init__(self, input_shape, kernel_size, deformation_range, direction):
        self.num_points = kernel_size
        self.width = input_shape[2]
        self.height = input_shape[3]
        self.direction = direction
        self.deformation_range = deformation_range  # offset (-1 ~ 1) * deformation_range

        # 特徴マップの形状を定義
        """
        B: バッチサイズ  C: チャネル  W: 幅  H: 高さ
        """
        self.batch_size = input_shape[0]
        self.channels = input_shape[1]

    """
    input: offset [B,2*K,W,H]  K: カーネルサイズ (2*K: 2D画像、変形には<x_offset>と<y_offset>が含まれる)
    output_x: [B,1,W,K*H]   座標マップ
    output_y: [B,1,K*W,H]   座標マップ
    """

    def _create_coordinate_map(self, offset, apply_deformation):
        device = offset.device
        # offset
        y_offset, x_offset = torch.split(offset, self.num_points, dim=1)

        y_center = torch.arange(0, self.width).repeat([self.height])
        y_center = y_center.reshape(self.height, self.width)
        y_center = y_center.permute(1, 0)
        y_center = y_center.reshape([-1, self.width, self.height])
        y_center = y_center.repeat([self.num_points, 1, 1]).float()
        y_center = y_center.unsqueeze(0)

        x_center = torch.arange(0, self.height).repeat([self.width])
        x_center = x_center.reshape(self.width, self.height)
        x_center = x_center.permute(0, 1)
        x_center = x_center.reshape([-1, self.width, self.height])
        x_center = x_center.repeat([self.num_points, 1, 1]).float()
        x_center = x_center.unsqueeze(0)

        if self.direction == 0:
            """
            カーネルを初期化し、カーネルをフラット化する
                y: 0のみ必要
                x: -num_points//2 ~ num_points//2 (カーネルサイズによって決定)
                !!! 関連のPPTは後日提出予定で、PPTには各ステップの変化が含まれます
            """
            y = torch.linspace(0, 0, 1)
            x = torch.linspace(
                -int(self.num_points // 2),
                int(self.num_points // 2),
                int(self.num_points),
            )

            y, x = torch.meshgrid(y, x)
            y_spread = y.reshape(-1, 1)
            x_spread = x.reshape(-1, 1)

            y_grid = y_spread.repeat([1, self.width * self.height])
            y_grid = y_grid.reshape([self.num_points, self.width, self.height])
            y_grid = y_grid.unsqueeze(0)  # [B*K*K, W,H]

            x_grid = x_spread.repeat([1, self.width * self.height])
            x_grid = x_grid.reshape([self.num_points, self.width, self.height])
            x_grid = x_grid.unsqueeze(0)  # [B*K*K, W,H]

            y_new = y_center + y_grid
            x_new = x_center + x_grid

            y_new = y_new.repeat(self.batch_size, 1, 1, 1).to(device)
            x_new = x_new.repeat(self.batch_size, 1, 1, 1).to(device)

            y_offset_new = y_offset.detach().clone()

            if apply_deformation:
                y_offset = y_offset.permute(1, 0, 2, 3)
                y_offset_new = y_offset_new.permute(1, 0, 2, 3)
                center = int(self.num_points // 2)

                # 中央位置は変化せず、他の位置から揺れ始めます
                # この部分は非常にシンプルで、主なアイデアは「オフセットは反復過程である」ことです
                y_offset_new[center] = 0
                for index in range(1, center):
                    y_offset_new[center + index] = (y_offset_new[center + index - 1] + y_offset[center + index])
                    y_offset_new[center - index] = (y_offset_new[center - index + 1] + y_offset[center - index])
                y_offset_new = y_offset_new.permute(1, 0, 2, 3).to(device)
                y_new = y_new.add(y_offset_new.mul(self.deformation_range))

            y_new = y_new.reshape(
                [self.batch_size, self.num_points, 1, self.width, self.height])
            y_new = y_new.permute(0, 3, 1, 4, 2)
            y_new = y_new.reshape([
                self.batch_size, self.num_points * self.width, 1 * self.height
            ])
            x_new = x_new.reshape(
                [self.batch_size, self.num_points, 1, self.width, self.height])
            x_new = x_new.permute(0, 3, 1, 4, 2)
            x_new = x_new.reshape([
                self.batch_size, self.num_points * self.width, 1 * self.height
            ])
            return y_new, x_new

        else:
            """
            カーネルを初期化し、カーネルをフラット化する
                y: -num_points//2 ~ num_points//2 (カーネルサイズによって決定)
                x: 0のみ必要
            """
            y = torch.linspace(
                -int(self.num_points // 2),
                int(self.num_points // 2),
                int(self.num_points),
            )
            x = torch.linspace(0, 0, 1)

            y, x = torch.meshgrid(y, x)
            y_spread = y.reshape(-1, 1)
            x_spread = x.reshape(-1, 1)

            y_grid = y_spread.repeat([1, self.width * self.height])
            y_grid = y_grid.reshape([self.num_points, self.width, self.height])
            y_grid = y_grid.unsqueeze(0)

            x_grid = x_spread.repeat([1, self.width * self.height])
            x_grid = x_grid.reshape([self.num_points, self.width, self.height])
            x_grid = x_grid.unsqueeze(0)

            y_new = y_center + y_grid
            x_new = x_center + x_grid

            y_new = y_new.repeat(self.batch_size, 1, 1, 1)
            x_new = x_new.repeat(self.batch_size, 1, 1, 1)

            y_new = y_new.to(device)
            x_new = x_new.to(device)
            x_offset_new = x_offset.detach().clone()

            if apply_deformation:
                x_offset = x_offset.permute(1, 0, 2, 3)
                x_offset_new = x_offset_new.permute(1, 0, 2, 3)
                center = int(self.num_points // 2)
                x_offset_new[center] = 0
                for index in range(1, center):
                    x_offset_new[center + index] = (x_offset_new[center + index - 1] + x_offset[center + index])
                    x_offset_new[center - index] = (x_offset_new[center - index + 1] + x_offset[center - index])
                x_offset_new = x_offset_new.permute(1, 0, 2, 3).to(device)
                x_new = x_new.add(x_offset_new.mul(self.deformation_range))

            y_new = y_new.reshape(
                [self.batch_size, 1, self.num_points, self.width, self.height])
            y_new = y_new.permute(0, 3, 1, 4, 2)
            y_new = y_new.reshape([
                self.batch_size, 1 * self.width, self.num_points * self.height
            ])
            x_new = x_new.reshape(
                [self.batch_size, 1, self.num_points, self.width, self.height])
            x_new = x_new.permute(0, 3, 1, 4, 2)
            x_new = x_new.reshape([
                self.batch_size, 1 * self.width, self.num_points * self.height
            ])
            return y_new, x_new

    """
    input: 入力特徴マップ [N,C,D,W,H]; 座標マップ [N,K*D,K*W,K*H] 
    output: [N,1,K*D,K*W,K*H]  変形された特徴マップ
    """

    def _bilinear_interpolation(self, input_feature, y, x):
        device = input_feature.device
        y = y.reshape([-1]).float()
        x = x.reshape([-1]).float()

        zero = torch.zeros([]).int()
        max_y = self.width - 1
        max_x = self.height - 1

        # 8つのグリッド位置を見つける
        y0 = torch.floor(y).int()
        y1 = y0 + 1
        x0 = torch.floor(x).int()
        x1 = x0 + 1

        # 特徴マップのボリュームを超える座標をクリッピング
        y0 = torch.clamp(y0, zero, max_y)
        y1 = torch.clamp(y1, zero, max_y)
        x0 = torch.clamp(x0, zero, max_x)
        x1 = torch.clamp(x1, zero, max_x)

        input_feature_flat = input_feature.flatten()
        input_feature_flat = input_feature_flat.reshape(
            self.batch_size, self.channels, self.width, self.height)
        input_feature_flat = input_feature_flat.permute(0, 2, 3, 1)
        input_feature_flat = input_feature_flat.reshape(-1, self.channels)
        dimension = self.height * self.width

        base = torch.arange(self.batch_size) * dimension
        base = base.reshape([-1, 1]).float()

        repeat = torch.ones([self.num_points * self.width * self.height
                             ]).unsqueeze(0)
        repeat = repeat.float()

        base = torch.matmul(base, repeat)
        base = base.reshape([-1])

        base = base.to(device)

        base_y0 = base + y0 * self.height
        base_y1 = base + y1 * self.height

        # 近傍ボリュームの上部の長方形
        index_a0 = base_y0 - base + x0
        index_c0 = base_y0 - base + x1

        # 近傍ボリュームの下部の長方形
        index_a1 = base_y1 - base + x0
        index_c1 = base_y1 - base + x1

        # 8つのグリッド値を取得
        value_a0 = input_feature_flat[index_a0.type(torch.int64)].to(device)
        value_c0 = input_feature_flat[index_c0.type(torch.int64)].to(device)
        value_a1 = input_feature_flat[index_a1.type(torch.int64)].to(device)
        value_c1 = input_feature_flat[index_c1.type(torch.int64)].to(device)

        # 8つのグリッド位置を見つける
        y0 = torch.floor(y).int()
        y1 = y0 + 1
        x0 = torch.floor(x).int()
        x1 = x0 + 1

        # 特徴マップのボリュームを超える座標をクリッピング
        y0 = torch.clamp(y0, zero, max_y + 1)
        y1 = torch.clamp(y1, zero, max_y + 1)
        x0 = torch.clamp(x0, zero, max_x + 1)
        x1 = torch.clamp(x1, zero, max_x + 1)

        x0_float = x0.float()
        x1_float = x1.float()
        y0_float = y0.float()
        y1_float = y1.float()

        vol_a0 = ((y1_float - y) * (x1_float - x)).unsqueeze(-1).to(device)
        vol_c0 = ((y1_float - y) * (x - x0_float)).unsqueeze(-1).to(device)
        vol_a1 = ((y - y0_float) * (x1_float - x)).unsqueeze(-1).to(device)
        vol_c1 = ((y - y0_float) * (x - x0_float)).unsqueeze(-1).to(device)

        outputs = (value_a0 * vol_a0 + value_c0 * vol_c0 + value_a1 * vol_a1 +
                   value_c1 * vol_c1)

        if self.direction == 0:
            outputs = outputs.reshape([
                self.batch_size,
                self.num_points * self.width,
                1 * self.height,
                self.channels,
            ])
            outputs = outputs.permute(0, 3, 1, 2)
        else:
            outputs = outputs.reshape([
                self.batch_size,
                1 * self.width,
                self.num_points * self.height,
                self.channels,
            ])
            outputs = outputs.permute(0, 3, 1, 2)
        return outputs

    def deformable_conv(self, input, offset, apply_deformation):
        y, x = self._create_coordinate_map(offset, apply_deformation)
        deformed_feature = self._bilinear_interpolation(input, y, x)
        return deformed_feature

03 コードの使用方法

FlexibleSnakeConvはコnvolutionの一種であり、完全なコードをコnvolutionのパスに配置します。私のパスはultralytics/nn/modules/conv.pyです。

01 __all__ にエクスポート名を追加

Pythonコードにおいて、__all__ は特別な変数で、モジュールからどのオブジェクト(関数、クラス、変数など)をエクスポートするか(つまり、from モジュール名 import * を通じてインポートできるか)を指定します。

__all__ = [
    'Conv', 'LightConv', 'DWConv', 'DWConvTranspose2d', 'ConvTranspose', 'Focus', 'GhostConv', 'ChannelAttention',
    'SpatialAttention', 'CBAM', 'Concat', 'RepConv',
    'DCNv2','DiverseBranchBlock','FlexibleSnakeConv']

01 FlexibleSnakeConvを直接使用する

01 convモジュールをインポートする

パスultralytics/nn/modules/__init__.pyにFlexibleSnakeConvを追加します。

from .conv import (CBAM, ChannelAttention, Concat, Conv, ConvTranspose, DWConv, DWConvTranspose2d, Focus, GhostConv,
                   LightConv, RepConv, SpatialAttention,
                   DCNv2,FlexibleSnakeConv,
                   ) 

下方の__all__にもFlexibleSnakeConvを追加します。

__all__ = [
    'Conv', 'LightConv', 'RepConv', 'DWConv', 'DWConvTranspose2d', 'ConvTranspose', 'Focus', 'GhostConv','FlexibleSnakeConv',
    'ChannelAttention', 'SpatialAttention', 'CBAM', 'Concat', 'TransformerLayer', 'TransformerBlock', 'MLPBlock',
    'LayerNorm2d', 'DFL', 'HGBlock', 'HGStem', 'SPP', 'SPPF', 'C1', 'C2', 'C3', 'C2f', 'C3x', 'C3TR', 'C3Ghost',
    'GhostBottleneck', 'Bottleneck', 'BottleneckCSP', 'Proto', 'Detect', 'Segment', 'Pose', 'Classify',
    'TransformerEncoderLayer', 'RepC3', 'RTDETRDecoder', 'AIFI', 'DeformableTransformerDecoder',
    'DeformableTransformerDecoderLayer', 'MSDeformAttn', 'MLP','deattn',
    'DCNv2','ODConv_3rd',
    'C2f_DCN','C2f_DSConv','C2f_Biformer','DiverseBranchBlock','Bottleneck_DBB','C2f_DBB','EVCBlock','C2f_EMA','C2f_ODConv']

02 タスクスクリプトに追加する

パスはultralytics/nn/tasks.pyで、FlexibleSnakeConvのインポート名を追加します。

from ultralytics.nn.modules import (AIFI, C1, C2, C3, C3TR, SPP, SPPF, Bottleneck, BottleneckCSP, C2f, C3Ghost, C3x,
                                    Classify, Concat, Conv, ConvTranspose, Detect, DWConv, DWConvTranspose2d, Focus,
                                    GhostBottleneck, GhostConv, HGBlock, HGStem, Pose, RepC3, RepConv, RTDETRDecoder,
                                    DCNv2,
                                    FlexibleSnakeConv,
                                    Segment,CBAM,C2f_DCN,C2f_DSConv,C2f_Biformer,C2f_DBB, C2f_EMA,EVCBlock,
                                    ODConv_3rd)

03 パラメータのパースに追加する

def parse_model(d, ch, verbose=True): 内でFlexibleSnakeConvを探して追加します。

        if m in (Classify, Conv, ConvTranspose, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, Focus,DCNv2,FlexibleSnakeConv,
                 BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x, RepC3,BasicRFB_a,BasicRFB,
                 C2f_DCN,C2f_DSConv,C2f_DBB,EVCBlock,BoT3, C2f_EMA,ODConv_3rd,C2f_Biformer):

04 yamlに組み込む

FlexibleSnakeConvは画像サイズを変更せずに動作するため、ストライド2のプロセスはありません。したがって、レイヤーを追加し、チャンネル数を入力と同じに設定し、カーネルサイズは3にします。YOLOv8のyamlには画像サイズを変更しないconvが存在しないため、置き換えることはできません。

  - [-1, 1, FlexibleSnakeConv, [512, 3]]
head:
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]      # 40*40*512 w*r
  - [[-1, 6], 1, Concat, [1]]  # cat backbone P4 11   40*40*512 *(w+wr)
  - [-1, 3, C2f, [512]]  # 12                       # 40*40*512 *w

  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]      # 80*80*256 *w
  - [[-1, 4], 1, Concat, [1]]  # cat backbone P3      80*80*512 *w
  - [-1, 3, C2f, [256]]  # 15 (P3/8-small)            80*80*256 *w       --Detect

  - [-1, 1, Conv, [256, 3, 2]]                      # 40*40*256 *w
  - [-1, 1, FlexibleSnakeConv, [256, 3]]
  - [[-1, 12], 1, Concat, [1]]  # cat head P4       # 40*40*512 *w
  - [-1, 3, C2f, [512]]  # 19 (P4/16-medium)        # 40*40*512 *w       --Detect

  - [-1, 1, Conv, [512, 3, 2]]                      # 20*20*512 *w
  - [[-1, 9], 1, Concat, [1]]  # cat head P5        # 20*20*512 *(w+wr)
  - [-1, 3, C2f, [1024]]  # 22 (P5/32-large)        # 20*20*512 *w       --Detect

  - [[15, 19, 22], 1, Detect, [nc]]  # Detect(P3=80*80*256 *w, P4=40*40*512 *w, P5=20*20*512)

02 FlexibleSnakeConvをモジュールに組み込む

FlexibleSnakeConvをBottleneckに統合してBottleneck_FlexibleSnakeConvを作成し、それをC2FのモデルとしてC2F_FlexibleSnakeConvを作成します。

01 FlexibleSnakeConvをインポートする

他のコnvolutionと同様に、モデルのサブモジュールとしてインポートします。

from .conv import Conv, DWConv, GhostConv, LightConv, RepConv,DCNv2,FlexibleSnakeConv

02 FlexibleSnakeConvをBottleneckに追加する

class Bottleneck_FlexibleSnakeConv(nn.Module):
    # 標準ボトルネックにDCNを含む
    def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):  # 入力チャネル, 出力チャネル, ショートカット, グループ, カーネル, 拡張
        super().__init__()
        c_ = int(c2 * e)  # 隠れチャネル
        if k[0] == 3:
            self.cv1 = FlexibleSnakeConv(c1, c_, k[0], 1)
        else:
            self.cv1 = Conv(c1, c_, k[0], 1) #self.cv2 = FlexibleSnakeConv(c_, c2, 3)
        if k[1] == 3:
            self.cv2 = FlexibleSnakeConv(c_, c2, k[1])
        else:
            self.cv2 = Conv(c_, c2, k[1], 1, g=g)
        self.add = shortcut and c1 == c2 #もしショートカットかつチャネル数が同じ場合

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

03 FlexibleSnakeConvをC2Fに追加する

class C2f_FlexibleSnakeConv(nn.Module):
    # CSPボトルネックに2つのコnvolutionを使用する
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):  # 入力チャネル, 出力チャネル, 数, ショートカット, グループ, 拡張
        super().__init__()
        self.c = int(c2 * e)  # 隠れチャネル
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # オプション act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck_FlexibleSnakeConv(self.c, self.c, shortcut, g, k=(3, 3), e=1.0) for _ in range(n)) #Bottleneck

    def forward(self, x):
        y = list(self.cv1(x).split((self.c, self.c), 1)) #まずコnvolutionを行い、その後分割する
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))

04 C2f_FlexibleSnakeConvをallに書き込む

allのパスは同じスクリプト内

__all__ = [
    'DFL', 'HGBlock', 'HGStem', 'SPP', 'SPPF', 'C1', 'C2', 'C3', 'C2f', 'C3x', 'C3TR', 'C3Ghost', 'GhostBottleneck',
    'Bottleneck', 'BottleneckCSP', 'Proto', 'RepC3',
    'C2f_DCN','C2f_DSConv','C2f_Biformer','Bottleneck_DBB','C2f_DBB','EVCBlock','C2f_EMA','ODConv_3rd','C2f_FlexibleSnakeConv']

05 C2f_FlexibleSnakeConvをinitに書き込む

        C2f_FlexibleSnakeConvはモジュールであり、block内に存在する
from .block import (C1, C2, C3, C3TR, DFL, SPP, SPPF, Bottleneck, BottleneckCSP, C2f, C3Ghost, C3x, GhostBottleneck,
                    HGBlock, HGStem, Proto, RepC3,
                    C2f_DCN,C2f_DSConv,C2f_Biformer,C2f_DBB,Bottleneck_DBB,EVCBlock, C2f_EMA,ODConv_3rd,C2f_FlexibleSnakeConv)#追加したもの

下方のallにも追加

__all__ = [
    'Conv', 'LightConv', 'RepConv', 'DWConv', 'DWConvTranspose2d', 'ConvTranspose', 'Focus', 'GhostConv','FlexibleSnakeConv',
    'ChannelAttention', 'SpatialAttention', 'CBAM', 'Concat', 'TransformerLayer', 'TransformerBlock', 'MLPBlock',
    'LayerNorm2d', 'DFL', 'HGBlock', 'HGStem', 'SPP', 'SPPF', 'C1', 'C2', 'C3', 'C2f', 'C3x', 'C3TR', 'C3Ghost',
    'GhostBottleneck', 'Bottleneck', 'BottleneckCSP', 'Proto', 'Detect', 'Segment', 'Pose', 'Classify',
    'TransformerEncoderLayer', 'RepC3', 'RTDETRDecoder', 'AIFI', 'DeformableTransformerDecoder',
    'DeformableTransformerDecoderLayer', 'MSDeformAttn', 'MLP','deattn',
    'DCNv2','ODConv_3rd',
    'C2f_DCN','C2f_DSConv','C2f_Biformer','DiverseBranchBlock','Bottleneck_DBB','C2f_DBB','EVCBlock','C2f_EMA','C2f_ODConv','C2f_FlexibleSnakeConv']

06 task.pyに追加する

パスはultralytics/nn/tasks.py。

モジュールからC2f_FlexibleSnakeConvをインポートする

from ultralytics.nn.modules import (AIFI, C1, C2, C3, C3TR, SPP, SPPF, Bottleneck, BottleneckCSP, C2f, C3Ghost, C3x,
                                    Classify, Concat, Conv, ConvTranspose, Detect, DWConv, DWConvTranspose2d, Focus,
                                    GhostBottleneck, GhostConv, HGBlock, HGStem, Pose, RepC3, RepConv, RTDETRDecoder,
                                    DCNv2,
                                    FlexibleSnakeConv,C2f_FlexibleSnakeConv,
                                    Segment,CBAM,C2f_DCN,C2f_DSConv,C2f_Biformer,C2f_DBB, C2f_EMA,EVCBlock,
                                    ODConv_3rd)

パラメータを解析する-def parse_model(d, ch, verbose=True):で追加する

if m in (Classify, Conv, ConvTranspose, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, Focus,DCNv2,FlexibleSnakeConv,
                 BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x, RepC3,BasicRFB_a,BasicRFB,
                 C2f_DCN,C2f_DSConv,C2f_DBB,EVCBlock,BoT3, C2f_EMA,ODConv_3rd,C2f_Biformer,C2f_FlexibleSnakeConv):

C2f_FlexibleSnakeConvが複数回使用されるため、以下にも追加

if m in (BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, C3x, RepC3,
                     C2f_DCN,C2f_DSConv,C2f_DBB,BoT3,C2f_EMA,C2f_Biformer,C2f_FlexibleSnakeConv):#ブロック追加

タグ: YOLOv8 DynamicSnakeConvolution DeepLearning ComputerVision NeuralNetworks

7月13日 18:52 投稿