YOLOv3的源代码精度理解(四) YoloBody

263 阅读8分钟

代码主要是参考bubbliiing的github YOLOv3的代码:github.com/bubbliiiing…

对于源代码的解读

预测部分

nets\yolo.py文件

网络的构建主要是按照这张图进行左侧方框的部分就是我们的主干网络darknet53,右侧中我们能看到的5L和convD 3x3 + conv2D 1x1,实际上是一个7层的结构,就是我们的make_last_layers中使用Sequential构造的序列结构

主干特征提取网络:

  • 功能:提取特征
  • 输入是一个416 * 416 * 3 进行下采样,高宽不断的被压缩,通道数不断的加深--->我们可以获得一堆的特征层,可以表示输入进来的图片的特征。

13,13,1024 经过5次卷积变成 13,13,512 这个部分分成了两条路:

  • 一条路是经过两层卷积,将通道数变成13 * 13 * 75 --- 分解为13 * 13 * 3 * 25 (20(voc20分类信息) + 1(置信度) + 4(位置信息))
  • 另外一条路就是通过上采样变成26 * 26 * 256的维度的大小,和上面的卷继层得到数据26 * 26 * 512进行concat,拼接成26 * 26 * 768的数据

26 * 26 * 512 和下层13 * 13 * 512 计算之后的数据进行concat得到26 * 26 * 768之后,经过5层卷积网络之后变成26 * 26 * 256,这个部分分成了两条路:

  • 一条路是经过两层卷积,将通道数变成26 * 26 * 75 --- 分解为26 * 26 * 3 * 25 (20(voc20分类信息) + 1(置信度) + 4(位置信息))
  • 另外一条路就是通过上采样变成52 * 52 * 128的维度的大小,和上面的卷继层得到数据52 * 52 * 256进行concat,拼接成52 * 52 * 384的数据

52 * 52 * 256 和下层26 * 26 * 128 计算之后的数据进行concat得到52 * 52 * 384之后,经过5层卷积网络之后变成52 * 52 * 128,这个部分直接通过两层卷积将通道数变成52 * 52 * 75 --- 分解为52 * 52 * 3 * 25 (20(voc20分类信息) + 1(置信度) + 4(位置信息))

利用特征金字塔可以进行多尺度特征融合,提取到更加有效的特征

image.png

class YoloBody(nn.Module):
    def __init__(self, anchors_mask, num_classes, pretrained = False):
        super(YoloBody, self).__init__()
        #---------------------------------------------------#   
        #   生成darknet53的主干模型
        #   获得三个有效特征层,他们的shape分别是:
        #   52,52,256
        #   26,26,512
        #   13,13,1024
        #---------------------------------------------------#
        self.backbone = darknet53()
        if pretrained:
            self.backbone.load_state_dict(torch.load("model_data/darknet53_backbone_weights.pth"))

        #---------------------------------------------------#
        #   out_filters : [64, 128, 256, 512, 1024]
        #---------------------------------------------------#
        out_filters = self.backbone.layers_out_filters

        #------------------------------------------------------------------------#
        #   计算yolo_head的输出通道数,对于voc数据集而言
        #   final_out_filter0 = final_out_filter1 = final_out_filter2 = 75
        #------------------------------------------------------------------------#
        self.last_layer0            = make_last_layers([512, 1024], out_filters[-1], len(anchors_mask[0]) * (num_classes + 5))
        self.last_layer1_conv       = conv2d(512, 256, 1)
        self.last_layer1_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
        
        self.last_layer1            = make_last_layers([256, 512], out_filters[-2] + 256, len(anchors_mask[1]) * (num_classes + 5))

        self.last_layer2_conv       = conv2d(256, 128, 1)
        self.last_layer2_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
        self.last_layer2            = make_last_layers([128, 256], out_filters[-3] + 128, len(anchors_mask[2]) * (num_classes + 5))

    def forward(self, x):
        #---------------------------------------------------#   
        #   获得三个有效特征层,他们的shape分别是:
        #   52,52,256;26,26,512;13,13,1024
        #---------------------------------------------------#
        x2, x1, x0 = self.backbone(x)

        #---------------------------------------------------#
        #   第一个特征层
        #   out0 = (batch_size,255,13,13)
        #---------------------------------------------------#
        # 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512
        out0_branch = self.last_layer0[:5](x0)
        out0        = self.last_layer0[5:](out0_branch)

        # 13,13,512 -> 13,13,256 -> 26,26,256
        x1_in = self.last_layer1_conv(out0_branch)
        x1_in = self.last_layer1_upsample(x1_in)

        # 26,26,256 + 26,26,512 -> 26,26,768
        x1_in = torch.cat([x1_in, x1], 1)
        #---------------------------------------------------#
        #   第二个特征层
        #   out1 = (batch_size,255,26,26)
        #---------------------------------------------------#
        # 26,26,768 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256
        out1_branch = self.last_layer1[:5](x1_in)
        out1        = self.last_layer1[5:](out1_branch)

        # 26,26,256 -> 26,26,128 -> 52,52,128
        x2_in = self.last_layer2_conv(out1_branch)
        x2_in = self.last_layer2_upsample(x2_in)

        # 52,52,128 + 52,52,256 -> 52,52,384
        x2_in = torch.cat([x2_in, x2], 1)
        #---------------------------------------------------#
        #   第一个特征层
        #   out3 = (batch_size,255,52,52)
        #---------------------------------------------------#
        # 52,52,384 -> 52,52,128 -> 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128
        out2 = self.last_layer2(x2_in)
        return out0, out1, out2

针对与上面的代码进行详细的解读:

  • 初始化部分的代码
 def __init__(self, anchors_mask, num_classes, pretrained = False):
    super(YoloBody, self).__init__()
    #---------------------------------------------------#   
    #   生成darknet53的主干模型
    #   获得三个有效特征层,他们的shape分别是:
    #   52,52,256
    #   26,26,512
    #   13,13,1024
    #---------------------------------------------------#
    
    # 首先先加载darknet53作为我们的主干网络
    self.backbone = darknet53()
    
    # 要是设置了预训练的的话,我们从指定路径读取预训练参数,将其加载到我们的模型中
    if pretrained:
        self.backbone.load_state_dict(torch.load("model_data/darknet53_backbone_weights.pth"))

    #---------------------------------------------------#
    #   out_filters : [64, 128, 256, 512, 1024]
    #---------------------------------------------------#
    
    # 我们获得输出的特征层的数量的列表,
    out_filters = self.backbone.layers_out_filters

    #------------------------------------------------------------------------#
    #   计算yolo_head的输出通道数,对于voc数据集而言
    #   final_out_filter0 = final_out_filter1 = final_out_filter2 = 75
    #------------------------------------------------------------------------#
    
    # 以此为例我们将我们特征提取主干网络的特征进行5层 + 2层的变化
    self.last_layer0            = make_last_layers([512, 1024], out_filters[-1], len(anchors_mask[0]) * (num_classes + 5))
    
    # 对来自下层经过5次卷积的特征图将其通道数进行通道变化
    self.last_layer1_conv       = conv2d(512, 256, 1)
    # 对来自下层经过5次卷积的特征图进行上采样
    
    # 和上面的地方如出一辙,不在解析
    self.last_layer1_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
    self.last_layer1            = make_last_layers([256, 512], out_filters[-2] + 256, len(anchors_mask[1]) * (num_classes + 5))

    self.last_layer2_conv       = conv2d(256, 128, 1)
    self.last_layer2_upsample   = nn.Upsample(scale_factor=2, mode='nearest')
    self.last_layer2            = make_last_layers([128, 256], out_filters[-3] + 128, len(anchors_mask[2]) * (num_classes + 5))

  • 调用部分解析

    • darknet53()

    • 上面调用到 make_last_layers

      实际就是这个部分

      • 前5层还是转化通道,提取特征,将提取出来的特征有两个去向
        • 经过上采样和上层特征进行concat,实际上就是我们说的特征融合
        • 进入剩下的两层继续进行卷积,然后得到相对应尺寸的特征图 image.png
      # anchors_mask:[[6, 7, 8], [3, 4, 5], [0, 1, 2]],
      # 我们以第一个为代表进行分析([512, 1024], out_filters[-1]=1024,3 * (20 +5))
      def make_last_layers(filters_list, in_filters, out_filter):
          m = nn.Sequential(
      
              # 前五个卷积是提取特征
              # 首先先使用1 * 1卷积对1024通道调整成512
              conv2d(in_filters, filters_list[0], 1),
              # 在使用3 * 3卷积 将512通道数调整成1024
              conv2d(filters_list[0], filters_list[1], 3),
              # 在使用1 * 1卷积对1024通道调整成512
              conv2d(filters_list[1], filters_list[0], 1),
              # 在使用3 * 3卷积 将512通道数调整成1024
              conv2d(filters_list[0], filters_list[1], 3),
              # 在使用1 * 1卷积对1024通道调整成512
              conv2d(filters_list[1], filters_list[0], 1),
      
              # 后面两个卷积是预测结果
              # 在使用3 * 3卷积对512通道调整成1024
              conv2d(filters_list[0], filters_list[1], 3),
              # 我们使用1 * 1卷积将1024通道调整成75
              nn.Conv2d(filters_list[1], out_filter, kernel_size=1, stride=1, padding=0, bias=True)
      
              # 总结:就是我们将输入的 batch_size,13,13,1024的数据变成batch,13,13,75
          )
          return m
      

      对于conv2d的调用

      # 比较简单,构建 卷积-BN-Relu 层,不做解析
      def conv2d(filter_in, filter_out, kernel_size):
          pad = (kernel_size - 1) // 2 if kernel_size else 0
          return nn.Sequential(OrderedDict([
              ("conv", nn.Conv2d(filter_in, filter_out, kernel_size=kernel_size, stride=1, padding=pad, bias=False)),
              ("bn", nn.BatchNorm2d(filter_out)),
              ("relu", nn.LeakyReLU(0.1)),
          ]))
      
  • forward进行解读:

def forward(self, x):
    #---------------------------------------------------#   
    #   获得三个有效特征层,他们的shape分别是:
    #   52,52,256;26,26,512;13,13,1024
    #---------------------------------------------------#

    # 我们从darknet53中中获取到out3 = x2 = [52,52,256],out4 = x3 = [26,26,512],out5 = x4 = [13,13,1024]
    x2, x1, x0 = self.backbone(x)

    #---------------------------------------------------#
    #   第一个特征层
    #   out0 = (batch_size,255,13,13)
    #---------------------------------------------------#
    # 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512 -> 13,13,1024 -> 13,13,512

    # 我们使用前五层进行特征提取,通道被调整成512
    out0_branch = self.last_layer0[:5](x0)
    # 然后我们使用后两层进行预测,得到第一个的输出 batch_size,13,13,75
    out0        = self.last_layer0[5:](out0_branch)

    # 13,13,512 -> 13,13,256 -> 26,26,256

    # 我们将通道进行调整512降到256
    x1_in = self.last_layer1_conv(out0_branch)
    # 在对图片进行上采样 13 * 13 * 256 -> 26 * 26 * 256
    x1_in = self.last_layer1_upsample(x1_in)

    # 26,26,256 + 26,26,512 -> 26,26,768

    # 我们将x1[26,26,512] + x1_in[26,26,256] = [26,26,768]
    x1_in = torch.cat([x1_in, x1], 1)
    #---------------------------------------------------#
    #   第二个特征层
    #   out1 = (batch_size,255,26,26)
    #---------------------------------------------------#
    # 26,26,768 -> 26,26,256 -> 26,26,512 -> 26,26,256 -> 26,26,512 -> 26,26,256

    # 和上面的过程一样,我们拿到前五层的特征,通道数变成256,输出out1 = batch_size,26,26,75
    out1_branch = self.last_layer1[:5](x1_in)
    out1        = self.last_layer1[5:](out1_branch)

    # 26,26,256 -> 26,26,128 -> 52,52,128
    # 和上面一样通过卷积降通道,上采样,输入的[52,52,384]
    x2_in = self.last_layer2_conv(out1_branch)
    x2_in = self.last_layer2_upsample(x2_in)

    # 52,52,128 + 52,52,256 -> 52,52,384
    x2_in = torch.cat([x2_in, x2], 1)
    #---------------------------------------------------#
    #   第一个特征层
    #   out3 = (batch_size,255,52,52)
    #---------------------------------------------------#
    # 52,52,384 -> 52,52,128 -> 52,52,256 -> 52,52,128 -> 52,52,256 -> 52,52,128

    # 最后经过7层网络直接得到预测的结果 out2 = batch_size,52,52,75 
    out2 = self.last_layer2(x2_in)

    # 最后将结果返回
    return out0, out1, out2

总结

我们的输入是一张图片,最终经过网络之后得到的是三个输出[[batch_size,13,13,75],[batch_size,26,26,75],[batch_size,52,52,75]]