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的分辨率保持一致,且网络中与浅层特征进行混合时充分考虑了实际的偏移。
参考文献:
- pytorch-fcn
- Conv2d — PyTorch 2.1 documentation
- MaxPool2d — PyTorch 2.1 documentation
-
ConvTranspose2d — PyTorch 2.1 documentation