Enregistrement de l'expérience de propagation directe YOLO v5 (pendant la démo)

Table des matières

1. Traitement de l'image d'entrée

2. Processus de propagation vers l'avant (pendant la démo)

1. Interprétation de la figure ci-dessus en combinant le code et 3. Structure du réseau, afin de mieux comprendre l'ensemble du réseau 

2. La définition et la fonction de chaque module dans le code

(1) Module de conversion

 (2) module C3

 (3) Module SSPF

 (4)Suréchantillonner

 (5) Module de détection

3. Structure du réseau


Notez que le traitement des entrées est différent entre la démo et la formation, il s'agit du processus de raisonnement en avant du processus de démonstration

1. Traitement de l'image d'entrée

# detect.py - 108
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)

Il est principalement géré par cette classe, puis la fonction clé est la

# dataloaders.py - 311       
im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0]  # padded resize  调用的函数
im = im.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB
im = np.ascontiguousarray(im)  # contiguous

La fonction boîte aux lettres, le processus de traitement de l'image est

1、new_shape = (640, 640),其作用为保证输入到网络中的img的h w 最大为640
2、计算 640 和 原img的h w 和  的比例并取最小的比例。注意,这里是 640 / 原img的hw 取最小。也就是以
原img的h w 最大的边为基准,从而保证原img的内容不丢失
3、按照最小的比例 缩小 或 放大,即原img的h w 乘上 比例
4、为了保证 输入到model中的img h w 为stride的整数倍,需要对其进行填充。如下:
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
if auto:  # minimum rectangle
    dw, dh = np.mod(dw, stride), np.mod(dh, stride)  # wh padding  np.mod 取模运算

得到填充量大小,然后除以2为单边填充量大小

dw /= 2  # divide padding into 2 sides
dh /= 2
5、填充,即得输入到 model 中的 img

Par exemple, les côtés supérieur et inférieur sont remplis après le traitement ci-dessus. Étant donné que ce qui suit est déjà un multiple entier de la foulée après le remodelage, aucun rembourrage n'est requis.

Par conséquent, en résumé,  la taille de l'entrée img n'est pas fixée à 640X640, mais pour s'assurer que le plus grand côté est de 640 et que l'autre côté est un multiple entier de la foulée.

2. Processus de propagation vers l'avant (pendant la démo)

 Figure 1

1. Interprétation de la figure ci-dessus en combinant le code et 3. Structure du réseau, afin de mieux comprendre l'ensemble du réseau 

Empruntez les images sur Internet et marquez-les (comme illustré à la figure 1), où les numéros de série correspondent aux modules du modèle, voir les trois structures de réseau suivantes pour le modèle. (Notez que j'ai l'impression que les 8 et 9 dans l'image ci-dessus sont inversés quand je la regarde personnellement.)

 Le processus de propagation vers l'avant est principalement complété par la fonction suivante

yolo.py ---114

    def _forward_once(self, x, profile=False, visualize=False):
        y, dt = [], []  # outputs
        # print('================')
        for m in self.model:  # m 与 model <c-8>
            # print('====================')
            # print("i is {}".format(m.i))
            # print('====================')
            if m.f != -1:  # if not from previous layer
                # print("when i is {},f is {}".format(m.i, m.f))
                x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layers
            if profile:
                self._profile_one_layer(m, x, dt)
            x = m(x)  # run
            y.append(x if m.i in self.save else None)  # save output  这里注意 m.i 这个属性
            if visualize:
                feature_visualization(x, m.type, m.i, save_dir=visualize)
        return x

La chose la plus importante est la variable m, qui sort les modules du modèle en boucle, et imprime quand mf ne sera pas -1 pendant l'expérience, où mi est le numéro de série du module, et l'information imprimée est comme suit 

when i is 12,f is [-1, 6]
when i is 16,f is [-1, 4]
when i is 19,f is [-1, 14]
when i is 22,f is [-1, 10]
when i is 24,f is [17, 20, 23]

On peut voir que la condition si est déclenchée lorsque i = 12, 16, 19, 22, 24, combiné avec la figure 1 et les trois suivantes, il n'est pas difficile de trouver qu'il s'agit du numéro de série correspondant au module Contact ( sauf le dernier, qui est le module Detect , qui correspond également à la figure 1), c'est-à-dire qu'en atteignant la couche de contact, la sortie précédente est requise pour l'épissage du tenseur (torch.cat), et faites attention à la valeur de f, -1 est la sortie du module précédent, et l'autre est la sortie précédente. À ce stade, le y dans le code est utilisé, et après le débogage, on constate que self.save est le suivant

 On peut trouver que correspond et inclut complètement la sortie de la couche de contact en f. Ainsi, le rôle de y est reflété, la sortie de la position correspondante requise par le module de contact est réservée, et le reste de y est None (comme indiqué dans le code, sinon None).

Combiné avec la figure 1 et le contenu de 'quand i est , f est ', le résumé est le suivant :

1. Épissage du module de contact ⑫ ⑪ et ⑥

2. Épissage du module de contact ⑯ ⑮ et ④

3. ⑲ épissure du module de contact ⑱ et ⑭

4. ㉒ épissure du module de contact ㉑ et ⑩

5. ㉔ La tête de détection reçoit de ⑰, ⑳ et ㉓, donc la sortie finale des trois échelles

2. La définition et la fonction de chaque module dans le code

Les définitions de module sont dans common.py 

Note : Comme ces modules héritent tous de la classe mère nn.Model, la fonction forward est appelée lors de l'exécution. Lorsque le modèle est chargé, cela se passe comme ci-dessous

# detect.py ---85
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)

跳转到

# common.py ---339        
if pt:  # PyTorch
     model = attempt_load(weights if isinstance(weights, list) else w, device=device, inplace=True, fuse=fuse)  # 搭建的网络,来自权重文件

 L'instance du modèle a été créée lorsque le modèle est chargé, donc chaque module a été initialisé à ce moment, c'est-à-dire que l'attribut self dans __init__ a été transmis en fonction des paramètres transmis dans le fichier de pondération (tels que input et canaux de sortie c1, c2, raccourci, etc.) est créé.

(1) Module de conversion

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):
        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):  # 有 bn 层执行这个
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):  # 无bn层 执行这个
        return self.act(self.conv(x))

 Modules de base. Comme le montre le troisième, il n'y a pas de couche bn, donc convolution directe + fonction d'activation, où la fonction d'activation SiLU est la suivante

La dérivée est (voir : 23 types de fonctions d'activation )

 (2) module C3

class C3(nn.Module):
    # CSP Bottleneck with 3 convolutions
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

    def forward(self, x):
        return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))

class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 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))

 Le goulot d'étranglement et le C3 sont réunis ici. C3 exécute d'abord self.cv1(x)), self.cv2(x), qui sont tous deux des modules Conv, puis exécute le module Bottleneck, qui contient toujours le module Conv, exécute le module Conv deux fois de suite, puis ajoute la sortie et l'entrée (en référence à ce bloc de la figure 1), la sommation n'est pas effectuée lorsque les canaux d'entrée et de sortie ne sont pas égaux.

 (3) Module SSPF

class SPPF(nn.Module):
    # Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
    def __init__(self, c1, c2, k=5):  # equivalent to SPP(k=(5, 9, 13))
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning
            y1 = self.m(x)
            y2 = self.m(y1)
            return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))  # 拼接 四个 ,然后 再卷积

Toujours combiné avec le module de la figure 1. Il exécute d'abord le module Conv, puis effectue trois regroupements maximum consécutifs (un de plus avant le retour), conserve les résultats de ces trois temps  et les assemble avec l'entrée, puis exécute le module Conv.

 (4)Suréchantillonner

Il appelle directement la classe de suréchantillonnage Upsample dans la bibliothèque pytorch via le hook

def forward(self, input: Tensor) -> Tensor:
    return F.interpolate(input, self.size, self.scale_factor, self.mode, self.align_corners,
                             recompute_scale_factor=self.recompute_scale_factor)

Le suréchantillonnage est réalisé en agrandissant la taille de la carte des caractéristiques par interpolation bilinéaire.

 (5) Module de détection

La tête de détection, il unifie les canaux de carte de fonctionnalités de différentes échelles à 255.

class Detect(nn.Module):
    # YOLOv5 Detect head for detection models
    stride = None  # strides computed during build
    dynamic = False  # force grid reconstruction
    export = False  # export mode

    def __init__(self, nc=80, anchors=(), ch=(), inplace=True):  # detection layer
        super().__init__()
        # self.anchors = anchors
        self.nc = nc  # number of classes  80
        self.no = nc + 5  # number of outputs per anchor  85
        self.nl = len(anchors)  # number of detection layers  3  anchors 为设置的锚框的参数,shape为(3,3,2),表示各层的特征图每个位置设置的锚框数量
        self.na = len(anchors[0]) // 2  # number of anchors  3
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)

    def forward(self, x):  # 举例 x {list:3}  Tensor:(1,128,80,80), Tensor:(1,256,40,40), Tensor:(1,512,20,20)
        z = []  # inference output
        for i in range(self.nl):  # 举例 i:0  分通道处理
            x[i] = self.m[i](x[i])  # conv  举例 x {list:3}  Tensor:(1,255,80,80), Tensor:(1,256,40,40), Tensor:(1,512,20,20)
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)  举例 bs:1, _ : 255, ny:80, nx:80
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()  # 举例 x {list:3}  Tensor:(1,3,80,80,85), Tensor:(1,256,40,40), Tensor:(1,512,20,20)

            if not self.training:  # inference
                if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:  # 换输入后重新 设定锚框
                    self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)  # 举例 grid {list:3}  Tensor:(1,3,80,80,2),Tensor:(1,3,42,28,2),Tensor:(1,3,21,14,2)
                                                                                # anchor_grid {list:3}  Tensor:(1,3,80,80,2),Tensor:(1,3,42,28,2),Tensor:(1,3,21,14,2)
                                                                                # 也是按通道处理,只改变当前的,之后的还是原来的还没做改变呢
                                                                                # 其中 grid 为特征图的坐标, anchor_grid为原图的点坐标
                    xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4)  #
                    xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i]  # xy  结合着锚框的标签设定,逆运算求取 预测 的 xy
                    wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i]  # wh   同上,逆运算 求取 wh
                    y = torch.cat((xy, wh, conf.sigmoid(), mask), 4)  # 最终的预测,这只是一个尺度下的
                else:  # Detect (boxes only)
                    xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
                    xy = (xy * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf), 4)
                z.append(y.view(bs, self.na * nx * ny, self.no))  # 全部尺度下的, 整成相应输出的形状

        return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)  

    def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, '1.10.0')):
        d = self.anchors[i].device
        t = self.anchors[i].dtype
        shape = 1, self.na, ny, nx, 2  # grid shape
        y, x = torch.arange(ny, device=d, dtype=t), torch.arange(nx, device=d, dtype=t)
        yv, xv = torch.meshgrid(y, x, indexing='ij') if torch_1_10 else torch.meshgrid(y, x)  # torch>=0.7 compatibility
        grid = torch.stack((xv, yv), 2).expand(shape) - 0.5  # add grid offset, i.e. y = 2.0 * x - 0.5
        anchor_grid = (self.anchors[i] * self.stride[i]).view((1, self.na, 1, 1, 2)).expand(shape)  # 乘上stride,反射回原图
        return grid, anchor_grid

 Faites attention à la mise en place de la boîte d'ancrage. Il sera rétabli si l'entrée change.

3. Structure du réseau

C'est la structure dans le modèle, voir yolo.py --- 116

for m in self.model:

Le m qu'il contient est la couche qu'il contient, ce qui montre qu'il n'inclut pas la couche bn. 

Sequential(
  (0): Conv(
    (conv): Conv2d(3, 32, kernel_size=(6, 6), stride=(2, 2), padding=(2, 2))
    (act): SiLU(inplace=True)
  )
  (1): Conv(
    (conv): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (act): SiLU(inplace=True)
  )
  (2): C3(
    (cv1): Conv(
      (conv): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (3): Conv(
    (conv): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (act): SiLU(inplace=True)
  )
  (4): C3(
    (cv1): Conv(
      (conv): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
      (1): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (5): Conv(
    (conv): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (act): SiLU(inplace=True)
  )
  (6): C3(
    (cv1): Conv(
      (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
      (1): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
      (2): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (7): Conv(
    (conv): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (act): SiLU(inplace=True)
  )
  (8): C3(
    (cv1): Conv(
      (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(512, 512, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (9): SPPF(
    (cv1): Conv(
      (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): MaxPool2d(kernel_size=5, stride=1, padding=2, dilation=1, ceil_mode=False)
  )
  (10): Conv(
    (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
    (act): SiLU(inplace=True)
  )
  (11): Upsample(scale_factor=2.0, mode=nearest)
  (12): Concat()
  (13): C3(
    (cv1): Conv(
      (conv): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (14): Conv(
    (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
    (act): SiLU(inplace=True)
  )
  (15): Upsample(scale_factor=2.0, mode=nearest)
  (16): Concat()
  (17): C3(
    (cv1): Conv(
      (conv): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (18): Conv(
    (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (act): SiLU(inplace=True)
  )
  (19): Concat()
  (20): C3(
    (cv1): Conv(
      (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (21): Conv(
    (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (act): SiLU(inplace=True)
  )
  (22): Concat()
  (23): C3(
    (cv1): Conv(
      (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv2): Conv(
      (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (cv3): Conv(
      (conv): Conv2d(512, 512, kernel_size=(1, 1), stride=(1, 1))
      (act): SiLU(inplace=True)
    )
    (m): Sequential(
      (0): Bottleneck(
        (cv1): Conv(
          (conv): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1))
          (act): SiLU(inplace=True)
        )
        (cv2): Conv(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (act): SiLU(inplace=True)
        )
      )
    )
  )
  (24): Detect(
    (m): ModuleList(
      (0): Conv2d(128, 255, kernel_size=(1, 1), stride=(1, 1))
      (1): Conv2d(256, 255, kernel_size=(1, 1), stride=(1, 1))
      (2): Conv2d(512, 255, kernel_size=(1, 1), stride=(1, 1))
    )
  )
)

Je suppose que tu aimes

Origine blog.csdn.net/allrubots/article/details/127385048
conseillé
Classement