语义分割专栏(二)复习FCN的编解码结构

发布时间 2023-04-26 19:53:34作者: CV技术指南(公众号)
前言 在这一期中,我们先简要复习一遍FCN网络,随后进入今天的重点——编码器-解码器架构。

本教程禁止转载。同时,本教程来自知识星球【CV技术指南】更多技术教程,可加入星球学习。

欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技术跟踪、经典论文解读、CV招聘信息。

CV各大方向专栏与各个部署框架最全教程整理

【CV技术指南】CV全栈指导班、基础入门班、论文指导班 全面上线!!

FCN结构

FCN基于传统的卷积神经网络 (CNN),但做了一些特定的改进,使其可以用于像素级别的语义分割任务。

也许大家还记得,在上一期中我们提到过FCN的网络结构:

网络结构
在蓝色箭头部分,实际上对图像进行了“卷积-BN-非线性”的集合操作,并在池化后让图像尺寸变小;

在红色线部分,执行的是上采样操作(在pytorch官方实现中采用的是双线性插值);

在绿色矩形框部分,通过元素相加的方式对不同位置的特征图进行了融合;

通俗来说,我们先经过一系列的操作,对输入图像进行压缩,再经过另一系列操作对图像进行解压。

于是,压缩图像的那一系列结构我们就称之为编码器,而解压图像的另一系列结构我们称之为解码器。

网络结构

通过上图可以看到,最后我们可以得到1/32尺寸的heatmap,1/16尺寸的featuremap和1/8尺寸的featuremap,将1/32尺寸的heatmap进行上采样到原始尺寸,这种模型叫做FCN-32s。这种简单粗暴的方法还原了conv5中的特征,但是其中一些细节是无法恢复的,所以FCN-32s精度很差,不能够很好地还原图像原来的特征。

基于上述原因,所以自然而然的就想到将浅层网络提取的特征和深层特征相融合,这样或许能够更好地恢复其中的细节信息。于是FCN把conv4中的特征对conv7进行2倍上采样之后的特征图进行融合,然后这时候特征图的尺寸为原始图像的1/16,所以再上采样16倍就可以得到原始图像大小的特征图,这种模型叫做FCN-16s。

为了进一步恢复特征细节信息,就重复以上操作。于是乎就把pool3后的特征图对conv7上采样4倍后的特征图和对pool4进行上采样2倍的特征图进行融合,此时的特征图的大小为原始图像的1/8。融合之后再上采样8倍,就可以得到原始图像大小的特征图了,这种模型叫做FCN-8s。

代码实现

  1. backbone部分
class VGG(nn.Module):
    def __init__(self, pretrained=True):
        super(VGG, self).__init__()

        # conv1 1/2
        self.conv1_1 = nn.Conv2d(3, 64, kernel_size=3, padding=1)
        self.relu1_1 = nn.ReLU(inplace=True)
        self.conv1_2 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
        self.relu1_2 = nn.ReLU(inplace=True)
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)

        # conv2 1/4
        self.conv2_1 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.relu2_1 = nn.ReLU(inplace=True)
        self.conv2_2 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
        self.relu2_2 = nn.ReLU(inplace=True)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)

        # conv3 1/8
        self.conv3_1 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
        self.relu3_1 = nn.ReLU(inplace=True)
        self.conv3_2 = nn.Conv2d(256, 256, kernel_size=3, padding=1)
        self.relu3_2 = nn.ReLU(inplace=True)
        self.conv3_3 = nn.Conv2d(256, 256, kernel_size=3, padding=1)
        self.relu3_3 = nn.ReLU(inplace=True)
        self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)

        # conv4 1/16
        self.conv4_1 = nn.Conv2d(256, 512, kernel_size=3, padding=1)
        self.relu4_1 = nn.ReLU(inplace=True)
        self.conv4_2 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
        self.relu4_2 = nn.ReLU(inplace=True)
        self.conv4_3 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
        self.relu4_3 = nn.ReLU(inplace=True)
        self.pool4 = nn.MaxPool2d(kernel_size=2, stride=2)

        # conv5 1/32
        self.conv5_1 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
        self.relu5_1 = nn.ReLU(inplace=True)
        self.conv5_2 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
        self.relu5_2 = nn.ReLU(inplace=True)
        self.conv5_3 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
        self.relu5_3 = nn.ReLU(inplace=True)
        self.pool5 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # load pretrained params from torchvision.models.vgg16(pretrained=True)
        if pretrained:
            pretrained_model = vgg16(pretrained=pretrained)
            pretrained_params = pretrained_model.state_dict()
            keys = list(pretrained_params.keys())
            new_dict = {}
            for index, key in enumerate(self.state_dict().keys()):
                new_dict[key] = pretrained_params[keys[index]]
            self.load_state_dict(new_dict)

    def forward(self, x):
        x = self.relu1_1(self.conv1_1(x))
        x = self.relu1_2(self.conv1_2(x))
        x = self.pool1(x)
        pool1 = x

        x = self.relu2_1(self.conv2_1(x))
        x = self.relu2_2(self.conv2_2(x))
        x = self.pool2(x)
        pool2 = x

        x = self.relu3_1(self.conv3_1(x))
        x = self.relu3_2(self.conv3_2(x))
        x = self.relu3_3(self.conv3_3(x))
        x = self.pool3(x)
        pool3 = x

        x = self.relu4_1(self.conv4_1(x))
        x = self.relu4_2(self.conv4_2(x))
        x = self.relu4_3(self.conv4_3(x))
        x = self.pool4(x)
        pool4 = x

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

        return pool1, pool2, pool3, pool4, pool5
  1. FCN-8s部分
class FCNs(nn.Module):
    def __init__(self, num_classes, backbone="vgg"):
        super(FCNs, self).__init__()
        self.num_classes = num_classes
        if backbone == "vgg":
            self.features = VGG()

        # deconv1 1/16
        self.deconv1 = nn.ConvTranspose2d(512, 512, kernel_size=3, stride=2, padding=1, output_padding=1)
        self.bn1 = nn.BatchNorm2d(512)
        self.relu1 = nn.ReLU()

        # deconv1 1/8
        self.deconv2 = nn.ConvTranspose2d(512, 256, kernel_size=3, stride=2, padding=1, output_padding=1)
        self.bn2 = nn.BatchNorm2d(256)
        self.relu2 = nn.ReLU()

        # deconv1 1/4
        self.deconv3 = nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, output_padding=1)
        self.bn3 = nn.BatchNorm2d(128)
        self.relu3 = nn.ReLU()

        # deconv1 1/2
        self.deconv4 = nn.ConvTranspose2d(128, 64, kernel_size=3, stride=2, padding=1, output_padding=1)
        self.bn4 = nn.BatchNorm2d(64)
        self.relu4 = nn.ReLU()

        # deconv1 1/1
        self.deconv5 = nn.ConvTranspose2d(64, 32, kernel_size=3, stride=2, padding=1, output_padding=1)
        self.bn5 = nn.BatchNorm2d(32)
        self.relu5 = nn.ReLU()

        self.classifier = nn.Conv2d(32, num_classes, kernel_size=1)

    def forward(self, x):
        features = self.features(x)

        y = self.bn1(self.relu1(self.deconv1(features[4])) + features[3])

        y = self.bn2(self.relu2(self.deconv2(y)) + features[2])

        y = self.bn3(self.relu3(self.deconv3(y)))

        y = self.bn4(self.relu4(self.deconv4(y)))

        y = self.bn5(self.relu5(self.deconv5(y)))

        y = self.classifier(y)

        return y

FCN的编码器解码器结构

在 FCN 中,编码器是由多个卷积层构成的,用来提取输入图像的特征信息。由于卷积层会逐渐减小图像的大小,因此经过多次卷积之后,得到的特征图大小会变小,也就是下采样。这个过程可以看做是对图像信息的压缩和抽象,类似于将图像转换为一些更高层次的特征表示。

接下来,解码器负责将编码器输出的低分辨率特征图还原为原始图像大小的高分辨率特征图。解码器通过一系列反卷积(也称为上采样)层进行图像的放大,逐步恢复出原始图像中的更细节的信息。这个过程可以看做是将抽象的特征还原为原始图像的过程。

现在,你已经大概知道什么是__编码器解码器__结构了。

编码器-解码器

它是计算机视觉领域中的一种基本网络结构,这种先压缩,再解压的思想,在后续的很多结构中都会出现,在语义分割领域,这就是祖师爷般的存在~ 再提一句,一些很优秀的网络(如 U-Net、SegNet 等),都是在在编码器-解码器结构的基础上进行了更细致的设计和改进得到的

优缺点

其实说到上面,核心内容已经讲完啦~ 这个结构的基本思想就是先压缩,再解压,其他的内容就在其他网络中慢慢领悟吧~ 下面来看一下非常非常简单的优缺点分析:

优点:

能够提取高层次的特征信息:编码器通过卷积层等操作,能够有效地提取图像的高层次特征信息,这些信息对于图像分类、目标检测、语义分割等任务非常重要。
能够还原图像细节信息:解码器通过上采样、反卷积等操作,能够将编码器输出的低分辨率特征图还原为原始图像大小的高分辨率特征图,从而恢复出原始图像中的细节信息。
结构简单:编码器-解码器结构是一种相对简单的网络结构,易于理解和实现,并且具有较好的可解释性。

缺点:

由于多次卷积和池化操作,编码器-解码器结构会使得图像信息逐渐丢失,导致一些细节信息无法恢复,例如边缘和纹理等。
在解码器中使用反卷积和上采样等操作,容易引起信息的混叠和失真,导致图像质量下降。
编码器-解码器结构的训练需要大量的数据和计算资源,模型参数较多,训练过程较为困难。

编码器-解码器总结

编码器-解码器(encoder-decoder)是语义分割领域中最重要的一种结构,它的核心思想就是先压缩,再解压,后续提到的绝大部分网络采用的都是这种原始的结构。在此基础之上,我们可以改变上采样方式,在论文中常常叫做微调编码器;也可以改变上采样方式,在论文中常常叫做微调解码器。通过这两种方式,我们就能创造出很多很多的种样式的神经网络~

 

欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技术跟踪、经典论文解读、CV招聘信息。

计算机视觉入门1v3辅导班

【技术文档】《从零搭建pytorch模型教程》122页PDF下载

QQ交流群:470899183。群内有大佬负责解答大家的日常学习、科研、代码问题。

其它文章

CUDA 教程(三)CUDA C 编程简介

目标跟踪(二)单、多目标跟踪的基本概念与常用数据集

【CV技术指南】咱们自己的CV全栈指导班、基础入门班、论文指导班 全面上线!!

即插即用模块 | RFAConv助力YOLOv8再涨2个点

CVPR 2023|21 篇数据集工作汇总(附打包下载链接)

CVPR 2023|两行代码高效缓解视觉Transformer过拟合,美图&国科大联合提出正则化方法DropKey

LargeKernel3D:在3D稀疏CNN中使用大卷积核

ViT-Adapter:用于密集预测任务的视觉 Transformer Adapter

CodeGeeX 130亿参数大模型的调优笔记:比FasterTransformer更快的解决方案

分割一切还不够,还要检测一切、生成一切,SAM二创开始了

CVPR 2023 深挖无标签数据价值!SOLIDER:用于以人为中心的视觉

SegGPT:在上下文中分割一切

上线一天,4k star | Facebook:Segment Anything

Efficient-HRNet | EfficientNet思想+HRNet技术会不会更强更快呢?

实践教程|GPU 利用率低常见原因分析及优化

ICLR 2023 | SoftMatch: 实现半监督学习中伪标签的质量和数量的trade-off

目标检测创新:一种基于区域的半监督方法,部分标签即可(附原论文下载)

CNN的反击!InceptionNeXt: 当 Inception 遇上 ConvNeXt

神经网络的可解释性分析:14种归因算法

无痛涨点:目标检测优化的实用Trick

详解PyTorch编译并调用自定义CUDA算子的三种方式

深度学习训练模型时,GPU显存不够怎么办?

deepInsight:一种将非图像数据转换图像的方法

ICLR2023|基于数据增广和知识蒸馏的单一样本训练算法

拯救脂肪肝第一步!自主诊断脂肪肝:3D医疗影像分割方案MedicalSeg

AI最全资料汇总 | 基础入门、技术前沿、工业应用、部署框架、实战教程学习

改变几行代码,PyTorch炼丹速度狂飙、模型优化时间大减

AAAI 2023 | 轻量级语义分割新范式: Head-Free 的线性 Transformer 结构

计算机视觉入门1v3辅导班

计算机视觉交流群

聊聊计算机视觉入门