当前位置:   article > 正文

图像分割-FCN全卷积神经网络(完整代码详解)_图像分割神经网络算法

图像分割神经网络算法

目录

FCN全卷积神经网络

实现过程

全卷积

反卷积

FCN的三点创新

code

FCN全卷积神经网络

        FCN为深度学习在语义分割领域的开山之作,提出使用卷积层代替CNN中的全连接操作,生成热力图heat map而不是类别。

实现过程

图1  FCN网络结构

        包括全卷积过程以及反卷积过程。

        全卷积:使用经典的CNN网络作为骨架网络,例如:Vgg ResNet AlexNet等。本文使用Vgg16作为骨架网络,提取feature map。

        反卷积:将feature map上采样回去(通过转置卷积等上采样方式),恢复原图大小。

        然后,将预测结果和真实label的像素一一对应分类,也称为像素级分类。从而,将分割问题转化为分类问题。

全卷积

        蓝色指卷积操作,绿色为池化操作(图像宽高减半)。因此,按照图1,网络结构为:conv1(2层卷积)、pool1、conv2(2层卷积)、pool2、conv3(3层卷积)、pool3(向下输出预测的第一个分支)、conv4(3层卷积)、pool4(向下输出预测的第二个分支)、conv5(3层卷积)、pool5、conv6、conv7(向下输出的最后一个分支)。提取得到pool3 pool4 conv7,用于后面的特征融合以及反卷积操作。

反卷积

        FCN分为FCN-32S,FCN-16s,FCN-8s三种网络结构。

        FCN-8s获取过程:conv7特征进行2倍上采样,与pool4融合,将融合后的进行2倍上采样,与pool3融合,最后进行8倍上采样得到原图大小的特征图。FCN-32s获取过程:conv7直接32倍上采样得到原图大小的特征图。

        由于FCN-8s综合较多层的特征,因此效果最好;而FCN-32s只使用了最后一层conv7上采样32倍进行预测,特征图较小,丢失了很多信息。

         注意:FCN-8s  (conv7 2倍上采样 + pool4) 2倍上采样 + pool3 -> 8倍上采样

        卷积过程中,特征经过pool操作,h w为奇数时,池化后的特征图的h1 w1不一定是原来h w的1/2,因此转置卷积2倍上采样后的shape与原来的h w有区别,因此需先通过插值方式torch.nn.functional.interpolate方式调整特征图大小,确保可以和上一层的特征融合。

   

FCN的三点创新

(1)全卷积:将传统CNN最后的全连接层转化为卷积层,实现分类器变为稠密预测dense prediction(即分割)。

具体操作:把原来CNN操作中的全连接变成卷积操作(见图1中conv6、conv7),此时图像的featureMap数量改变但是图像大小依然为原图的1/32,图像不再叫featureMap而是叫heatMap。

(2)上采样:由于骨架网络提取特征的过程采取了一系列下采样(池化操作),使得特征图大小减小,为了得到和原图大小一致的预测层,采用上采样(如转置卷积操作)

(3)跳跃结构:类似于ResNet,将不同层的feature map进行融合,在分类预测时可以综合多层信息。

code

FCN-8s实现过程,其他网络结构可直接修改FCN类中的forward函数实现。

  1. import torch
  2. from torch import nn
  3. from torchvision.models import vgg16
  4. import torch.nn.functional as F
  5. def vgg_block(num_convs, in_channels, out_channels):
  6. """
  7. vgg block: Conv2d ReLU MaxPool2d
  8. """
  9. blk = []
  10. for i in range(num_convs):
  11. if i == 0:
  12. blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=(3, 3), padding=1))
  13. else:
  14. blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=(3, 3), padding=1))
  15. blk.append(nn.ReLU(inplace=True))
  16. blk.append(nn.MaxPool2d(kernel_size=(2, 2), stride=2))
  17. return blk
  18. class VGG16(nn.Module):
  19. def __init__(self, pretrained=True):
  20. super(VGG16, self).__init__()
  21. features = []
  22. features.extend(vgg_block(2, 3, 64))
  23. features.extend(vgg_block(2, 64, 128))
  24. features.extend(vgg_block(3, 128, 256))
  25. self.index_pool3 = len(features) # pool3
  26. features.extend(vgg_block(3, 256, 512))
  27. self.index_pool4 = len(features) # pool4
  28. features.extend(vgg_block(3, 512, 512)) # pool5
  29. self.features = nn.Sequential(*features) # 模型容器,有state_dict参数(字典类型)
  30. """ 将传统CNN中的全连接操作,变成卷积操作conv6 conv7 此时不进行pool操作,图像大小不变,此时图像不叫feature map而是heatmap"""
  31. self.conv6 = nn.Conv2d(512, 4096, kernel_size=1) # conv6
  32. self.relu = nn.ReLU(inplace=True)
  33. self.conv7 = nn.Conv2d(4096, 4096, kernel_size=1) # conv7
  34. # load pretrained params from torchvision.models.vgg16(pretrained=True)
  35. if pretrained:
  36. pretrained_model = vgg16(pretrained=pretrained)
  37. pretrained_params = pretrained_model.state_dict() # state_dict()存放训练过程中需要学习的权重和偏置系数,字典类型
  38. keys = list(pretrained_params.keys())
  39. new_dict = {}
  40. for index, key in enumerate(self.features.state_dict().keys()):
  41. new_dict[key] = pretrained_params[keys[index]]
  42. self.features.load_state_dict(new_dict) # load_state_dict必须传入字典对象,将预训练的参数权重加载到features中
  43. def forward(self, x):
  44. pool3 = self.features[:self.index_pool3](x) # 图像大小为原来的1/8
  45. pool4 = self.features[self.index_pool3:self.index_pool4](pool3) # 图像大小为原来的1/16
  46. # pool4 = self.features[:self.index_pool4](x) # pool4的第二种写法,较浪费时间(从头开始)
  47. pool5 = self.features[self.index_pool4:](pool4) # 图像大小为原来的1/32
  48. conv6 = self.relu(self.conv6(pool5)) # 图像大小为原来的1/32
  49. conv7 = self.relu(self.conv7(conv6)) # 图像大小为原来的1/32
  50. return pool3, pool4, conv7
  51. class FCN(nn.Module):
  52. def __init__(self, num_classes, backbone='vgg'):
  53. """
  54. Args:
  55. num_classes: 分类数目
  56. backbone: 骨干网络 VGG
  57. """
  58. super(FCN, self).__init__()
  59. if backbone == 'vgg':
  60. self.features = VGG16() # 参数初始化
  61. # 1*1卷积,将通道数映射为类别数
  62. self.scores1 = nn.Conv2d(4096, num_classes, kernel_size=1) # 对conv7操作
  63. self.relu = nn.ReLU(inplace=True)
  64. self.scores2 = nn.Conv2d(512, num_classes, kernel_size=1) # 对pool4操作
  65. self.scores3 = nn.Conv2d(256, num_classes, kernel_size=1) # 对pool3操作
  66. self.upsample_8x = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=8, stride=8) # 转置卷积
  67. self.upsample_2x = nn.ConvTranspose2d(num_classes, num_classes, kernel_size=2, stride=2)
  68. def forward(self, x):
  69. b, c, h, w = x.shape
  70. pool3, pool4, conv7 = self.features(x)
  71. conv7 = self.relu(self.scores1(conv7))
  72. pool4 = self.relu(self.scores2(pool4))
  73. pool3 = self.relu(self.scores3(pool3))
  74. # 融合之前调整一下h w
  75. conv7_2x = F.interpolate(self.upsample_2x(conv7), size=(pool4.size(2), pool4.size(3))) # conv7 2倍上采样,调整到pool4的大小
  76. s=conv7_2x+pool4 # conv7 2倍上采样与pool4融合
  77. s=F.interpolate(self.upsample_2x(s),size=(pool3.size(2),pool3.size(3))) # 融合后的特征2倍上采样,调整到pool3的大小
  78. s = pool3 + s # 融合后的特征与pool3融合
  79. out_8s=F.interpolate(self.upsample_8x(s) ,size=(h,w)) # 8倍上采样得到 FCN-8s,得到和原特征x一样大小的特征
  80. return out_8s
  81. if __name__=='__main__':
  82. model = FCN(num_classes=12)
  83. fake_img=torch.randn((4,3,360,480)) # B C H W
  84. output_8s=model(fake_img)
  85. print(output_8s.shape)

输出:

torch.Size([4, 12, 360, 480])

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/72356
推荐阅读
相关标签
  

闽ICP备14008679号