FCN-8s源码理解

        FCN网络用于对图像进行分割,由于是全卷积网络,所以对输入图像的分辨率没有要求。本文重点对fcn8s.py中图像降采样和上采样后图像分辨率的变换进行理解。

相关知识

       为准确理解图像分辨率的变换,对网络结构中影响图像分辨率变换的几个函数进行简单回顾

  • nn.Conv2d的参数详见这里,其输入和输出之间的关系如下,其中dilation默认为1.
     
  • nn.MaxPool2d的参数详见这里,其输入和输出之间的关系如下:
  • nn.ConvTranspose2d的参数详见这里,其输入和输出之间的关系如下,其中dilation默认为1

网络模型中特征图
  • 前两次上采样均是针对中间特征图进行的,故计算过程一样,只是在特征合并时偏移量不同,这主要是因为前向传播时经历的网络层不一样。此处重点以第一次上采样为例子,介绍偏移量的由来:第一次上采样后进行合并时5个像素的偏移量是如何来的。首先该部分模型代码和网络层参数如下:
A.网络结构
        pool4 = h  # 1/16

        h = self.relu5_1(self.conv5_1(h))
        h = self.relu5_2(self.conv5_2(h))
        h = self.relu5_3(self.conv5_3(h))
        h = self.pool5(h)

        h = self.relu6(self.fc6(h))
        h = self.drop6(h)

        h = self.relu7(self.fc7(h))
        h = self.drop7(h)

        h = self.score_fr(h)
        h = self.upscore2(h)
        upscore2 = h  # 1/16

        h = self.score_pool4(pool4)
        h = h[:, :, 5:5 + upscore2.size()[2], 5:5 + upscore2.size()[3]]
B.不同网络层的参数
        # conv5
        self.conv5_1 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu5_1 = nn.ReLU(inplace=True)
        self.conv5_2 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu5_2 = nn.ReLU(inplace=True)
        self.conv5_3 = nn.Conv2d(512, 512, 3, padding=1)
        self.relu5_3 = nn.ReLU(inplace=True)
        self.pool5 = nn.MaxPool2d(2, stride=2, ceil_mode=True)  # 1/32

        # fc6
        self.fc6 = nn.Conv2d(512, 4096, 7)
        self.relu6 = nn.ReLU(inplace=True)
        self.drop6 = nn.Dropout2d()

        # fc7
        self.fc7 = nn.Conv2d(4096, 4096, 1)
        self.relu7 = nn.ReLU(inplace=True)
        self.drop7 = nn.Dropout2d()

        self.score_fr = nn.Conv2d(4096, n_class, 1)
        self.score_pool3 = nn.Conv2d(256, n_class, 1)
        self.score_pool4 = nn.Conv2d(512, n_class, 1)

        self.upscore2 = nn.ConvTranspose2d(
            n_class, n_class, 4, stride=2, bias=False)

结合相关知识中,模型中仅如下三行代码对特征图分辨率有影响。

        #假设特征图分辨率中宽高一致,均为I
        h = self.pool5(h)  #池化后特征图分辨率变为I/2

        h = self.relu6(self.fc6(h))  #根据fc6中卷积参数,得到卷积后特征图分辨率为(I/2+2*0-7)/1+1=I/2-6

        …………

        h = self.upscore2(h) #根据upscore2上采样的参数,得到等号左侧特征图分辨率为(I/2-6-1)*2+1*(4-1)+1=I-10

从如上变换可以看到,最后上采样后的特征图分辨率相对于pool4特征图的小了10个像素,所以在进行特征合并时,需要进行偏移10/2=5个像素,即如下代码中参数的由来。

h = h[:, :, 5:5 + upscore2.size()[2], 5:5 + upscore2.size()[3]]
  • 输出时的偏移计算方法如下。
        #由于相对于输入X经conv1_1卷积后得到的I而言,pool4之后输出的特征图分辨率变为I/16
        h = self.upscore2(h)   #(I/16/2-6-1)*2+4=I/16-10  ps:I/16之后还有pool5,故又减小一半
        upscore2 = h  # 1/16

        h = self.score_pool4(pool4)
        h = h[:, :, 5:5 + upscore2.size()[2], 5:5 + upscore2.size()[3]]
        score_pool4c = h  # 1/16

        h = upscore2 + score_pool4c  # 1/16
        h = self.upscore_pool4(h)    #(I/16-10-1)*2+4=I/8-18
        upscore_pool4 = h  # 1/8

        h = self.score_pool3(pool3)
        h = h[:, :,
              9:9 + upscore_pool4.size()[2],
              9:9 + upscore_pool4.size()[3]]
        score_pool3c = h  # 1/8

        h = upscore_pool4 + score_pool3c  # 1/8

        h = self.upscore8(h)    #(I/8-18-1)*8+16=I-136

通过上采样后输出图像的分辨率较conv1_1的减小136个像素,由于conv1_1进行了填充(padding=100)故输入x与卷积后I的关系如下。

self.conv1_1 = nn.Conv2d(3, 64, 3, padding=100) #(x+2*100)-3+1=x+198=I

由于在输入x进行了198像素的填充,然后经网络输出后像素减少136个,所以最后网络上采样得到的结果相对于输入x还是增加了(198-136)=62个像素,故而得到最终结果是。

h = h[:, :, 31:31 + x.size()[2], 31:31 + x.size()[3]].contiguous()

此时最终输出的特征图分辨率与输入x的分辨率保持一致,且网络中与浅层特征进行混合时充分考虑了实际的偏移。

参考文献:

  1. pytorch-fcn
  2. Conv2d — PyTorch 2.1 documentation
  3. MaxPool2d — PyTorch 2.1 documentation
  4. ConvTranspose2d — PyTorch 2.1 documentation

你可能感兴趣的:(深度学习,人工智能)