【语义分割】FCN

概述

FCN算是图像分割的开篇之作,在它之前,分割任务就是当做分类去做的。
最简单的分类就是通过一系列的卷积操作进行特征提取,最后加上几个全连接层,通过softmax得到分类的结果。
最初的分割方式,就是通过划窗之类的策略,提取到一个个的patch,将这个patch作为当前像素的上下文,输入到分类网络中,然后得到当前像素的类别,最终所有像素的分类结果就是我们的分割结果了。
这个做法显然有很多的问题,如
1、滑窗带来的计算量大、计算效率低(相邻的像素块基本上是重复的)。
2、patch相对于全图而言会比较小,只能提取局部的特征,使得最后的效果也没那么好。
3、这不是端到端的,需要有预处理,后处理的环节。

而FCN做的就是在分类模型的基础上,稍加变动,直接得到分割网络,因此是端到端的,新的网络称为全卷积网络(就是没有全连接层的网络)

细节

FCN的结构

就像概述中提到的,FCN做的改变其实不多,主要是两个部分,一个是将全连接层改成卷积操作,另一个就是将最后加了一个反卷积操作(上采样)用于还原回全分辨率的分割图,而全连接之前的特征提取部分,不做变动。
在这里插入图片描述

全连接层改成卷积操作

以VGG这个分类网络的改造为例,以下是VGG16的结构:
在这里插入图片描述

在我们得到7x7x512的特征图之后,VGG会将它展平成一维向量,然后做一次全连接得到4096维得到数据,然后做一次维度不变的全连接,最后再做一次全连接,得到目标类别的维数,简略版的实现如下:

self.fc1=nn.Linear(512*7*7, 4096)
self.fc2=nn.Linear(4096, 4096)
self.fc3=nn.Linear(4096, num_classes)

然后我们要把三层全连接改成卷积操作,并且保证参数相同,如下:

self.fc1=nn.Conv2D(512,4096,7,1,0)
self.fc2=nn.Conv2D(4096,4096,1,1,0)
self.fc3=nn.Conv2D(4096,self.num_classes,1,1,0)

相应的参数计算:

全连接
fc1:(512*7*7)*4096
fc2:4096*4096
fc3:4096*num_classes
卷积
fc1:7*7*512*4096
fc2:1*1*4096*4096
fc3:1*1*4096*num_classes

上述过程的话,我们完成了从全连接操作到卷积操作的变换,最终得到的输出是 1 ∗ 1 ∗ n u m − c l a s s e s 1*1*num-classes 11numclasses,接着我们只需要使用上采样操作,如反卷积,将特诊图的尺寸变为输入尺寸 224 ∗ 224 224*224 224224即可。

新的问题出现了:很明显,从 1 ∗ 1 直接采样到 224 ∗ 224 1*1直接采样到224*224 11直接采样到224224,得到的结果肯定很差。
也就是最终的输出尺寸需要变大一点,我们将第一个卷积操作的卷积核从7改到1(之前卷积核取7是为了实现和全连接层一样的效果),最终可以得到 7 ∗ 7 ∗ n u m − c l a s s e s 7*7*num-classes 77numclasses的输出,然后继续上述操作,得到一个更好一点的分割结果。

skip-connection

继续,下一步怎么提升分割效果呢?得到更大的输出尺寸吗?如果是这么操作,就需要改变特征提取网络了,这个不是希望的。因此,另一个思路出现就是skip-connection。简而言之就是在不断上采样的过程中使用已有信息弥补上采样带来的损失,具体点就是 7 ∗ 7 ∗ n u m − c l a s s e s 7*7*num-classes 77numclasses的输出,不直接上采样到全分辨率,而是上采样两倍,和特征提取网络对应尺度的输出融合,然后持续这个过程。在这里插入图片描述
具体一点的过程如下:
FCN-8s的得到过程是没有skip-connection的,直接将 7 ∗ 7 ∗ n u m − c l a s s e s 7*7*num-classes 77numclasses的输出上采样
FCN-16s的得到过程
在这里插入图片描述
FCN-8s的得到过程
在这里插入图片描述

几个模型的效果:
在这里插入图片描述

基于AGG的FCN实现

import paddle
import paddle.nn as nn

paddle.set_device("cpu")
class FCN(nn.Layer):
    def __init__(self, arch,num_classes=2):
        super().__init__()
        self.num_classes=num_classes
        self.in_channels=3
        self.conv3_64=self.make_layer(64,arch[0])
        self.conv3_128=self.make_layer(128,arch[1])
        self.conv3_256=self.make_layer(256,arch[2])
        self.conv3_512a=self.make_layer(512,arch[3])
        self.conv3_512b=self.make_layer(512,arch[4])
        
        self.pool=nn.MaxPool2D(2)
        
        # 以下是变动
        self.fc1=nn.Sequential(
            nn.Conv2D(512,4096,7,1,0),
            nn.BatchNorm2D(4096),
            nn.ReLU()
        )
        self.fc2=nn.Sequential(
            nn.Conv2D(4096,4096,1,1,0),
            nn.BatchNorm2D(4096),
            nn.ReLU()
        )
        self.up=nn.UpsamplingBilinear2D(scale_factor=2)
        
        self.fc3_conv=nn.Conv2D(4096,self.num_classes,1,1,0)
        self.s32=nn.UpsamplingBilinear2D(scale_factor=32)
        
        
        self.down4_conv=nn.Conv2D(512,self.num_classes,1,1,0)
        self.s16=nn.UpsamplingBilinear2D(scale_factor=16)
        
        self.down3_conv=nn.Conv2D(256,self.num_classes,1,1,0)
        self.s8=nn.UpsamplingBilinear2D(scale_factor=8)
        
    def make_layer(self,channels,nums):
        layers=[]
        for i in range(nums):
            layers.append(nn.Conv2D(self.in_channels,channels,3,1,1))
            layers.append(nn.BatchNorm2D(channels))
            layers.append(nn.ReLU())
            self.in_channels=channels
        return nn.Sequential(*layers)
       
    def forward(self,x):
        # --------------
        # 骨干网络
        # --------------
        
        # x:[n,3,224,224]
        x=self.conv3_64(x) # x:[n,3,224,224]->[n,64,224,224]
        down1=self.pool(x)
        x=self.conv3_128(down1) # x:[n,64,224,224]->[n,128,112,112]
        down2=self.pool(x)
        x=self.conv3_256(down2) # x:[n,128,112,112]->[n,256,56,56]
        down3=self.pool(x)
        x=self.conv3_512a(down3) # x:[n,256,56,56]->[n,512,28,28]
        down4=self.pool(x)
        x=self.conv3_512b(down4) # x:[n,512,28,28]->[n,512,14,14]
        down5=self.pool(x) # x:[n,512,14,14]->[n,512,7,7]
        
        
        # --------------
        # FCN-32s
        # --------------
        down5=self.fc1(down5)
        down5=self.fc2(down5)
        down5=self.fc3_conv(down5)
        s32=self.s32(down5)
        
        
        # --------------
        # FCN-16s
        # --------------
        down4=self.down4_conv(down4)
        down4=down4+self.up(down5)
        s16=self.s16(down4)
        
        # --------------
        # FCN-8s
        # --------------
        down3=self.down3_conv(down3)
        down3=down3+self.up(down4)
        s8=self.s8(down3)
        
        
        
        
        return s32,s16,s8
      

def main():
    x=paddle.randn(shape=[1,3,224,224])
    fcn=FCN([2, 2, 3, 3, 3])
    s32,s16,s8=fcn(x)
    print(s32.shape,s16.shape,s8.shape)

if __name__ == '__main__':
    main()
    
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
fcn语义分割是一种基于全卷积神经网络的图像分割方法,可以对图像中的每个像素进行分类,从而实现对整张图像的语义分割。以下是fcn语义分割的pytorch实现步骤: 1. 定义模型:使用pytorch定义全卷积神经网络模型,可以使用已经训练好的预训练模型,如VGG16等。 2. 加载数据集:加载训练集和测试集,并对数据进行预处理,如归一化、裁剪等。 3. 训练模型:使用训练集对模型进行训练,并在验证集上进行验证,可以使用交叉熵损失函数和随机梯度下降等优化算法。 4. 测试模型:使用测试集对训练好的模型进行测试,并计算模型的准确率、召回率、F1值等指标。 5. 可视化结果:将模型输出的分割结果可视化,可以使用matplotlib等库进行可视化。 以下是一个简单的fcn语义分割pytorch实现示例: ```python import torch import torch.nn as nn import torch.optim as optim from torchvision import models # 定义fcn模型 class FCN(nn.Module): def __init__(self, num_classes): super(FCN, self).__init__() self.features = models.vgg16(pretrained=True).features self.conv1 = nn.Conv2d(512, num_classes, kernel_size=1) self.conv2 = nn.Conv2d(256, num_classes, kernel_size=1) self.conv3 = nn.Conv2d(128, num_classes, kernel_size=1) def forward(self, x): x = self.features(x) x1 = self.conv1(x) x = nn.functional.upsample_bilinear(x1, scale_factor=32) x2 = self.conv2(x) x = nn.functional.upsample_bilinear(x2, scale_factor=16) x3 = self.conv3(x) x = nn.functional.upsample_bilinear(x3, scale_factor=8) return x # 加载数据集 train_dataset = ... test_dataset = ... # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # 训练模型 for epoch in range(num_epochs): for i, (inputs, labels) in enumerate(train_dataset): optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # 测试模型 for i, (inputs, labels) in enumerate(test_dataset): outputs = model(inputs) # 计算指标 # 可视化结果 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值