【FCN】端到端式语义分割的开篇之作! 从中窥探后续语义分割网络的核心模块(一)

简介: 【FCN】端到端式语义分割的开篇之作! 从中窥探后续语义分割网络的核心模块(一)

前言

  《Fully Convolutional Networks for Semantic Segmentation》(后续将以《FCN》替代)是由Jonathan Long、Evan Shelhamer和Trevor Darrell等人在2015年发表的论文。该论文提出了一种基于全卷积神经网络(FCN)的语义分割方法,将传统的卷积神经网络中的全连接层替换为卷积层,从而实现了端到端的像素级别的语义分割,这是第一个端到端训练fns进行像素预测和进行超级预训练的工作。现有网络的全卷积版本可以从任意大小的输入预测密集输出。

论文背景和意义

  Semantic Segmentation 是计算机视觉领域中的一个重要研究方向,它通过将图像分割成不同的类别,实现对图像中物体的识别和分割。在过去的几年中,深度学习在计算机视觉领域取得了巨大的进展,其中卷积神经网络 (Convolutional Neural Network,CNN) 已经成为了 Semantic Segmentation 最常用的方法。

  然而,传统的 CNN 方法需要大量的人工标注数据,而标注数据的成本非常高。因此,对于某些场景,如夜间驾驶、医学影像等,传统 CNN 方法难以应用。而 Fully Convolutional Networks(FCN) 的出现,为解决这一问题提供了新的思路。

全卷积网络可以有效地学习对每像素任务(如语义分段定位)进行密集预测

image.png

主要内容和贡献

  《FCN》的主要贡献在于提出了一种全卷积神经网络(Fully Convolutional Network,FCN)的架构,用于解决语义分割问题。传统的卷积神经网络(CNN)通常被用于图像分类问题,即将整张图片分为不同的类别。但是对于像素级别的语义分割问题,传统的CNN架构并不适用,因为它们的输出通常是一个固定大小的向量,无法处理不同大小的输入图像。

  FCN的主要思想是将传统的卷积层和池化层替换为全卷积层,使得网络的输出可以是一个与输入图像大小相同的特征图。这样,每个像素都可以被预测为属于哪个类别,从而实现像素级别的语义分割。同时,FCN还使用了上采样和跳跃连接等技术,帮助网络更好地捕捉不同尺度的特征信息,提高了分割的准确性。

代码

在这里我们以FCN8为例子展示:

ini

复制代码

import numpy as np
import torch
import torch.nn as nn
def get_upsample_filter(size):
    factor=(size+1)//2
    if size%2==1:
        center=factor-1
    else:
        center=factor-0.5
    og=np.ogrid[:size,:size]
    filter=(1-abs(og[0]-center)/factor)*(1-abs(og[1]-center)/factor)
    return torch.from_numpy(filter).float()
class FCN(nn.Module):
    def __init__(self, n_class=22):
        super(FCN, self).__init__()
        self.features_123 = nn.Sequential(
            # conv1
            nn.Conv2d(3, 64, 3, padding=100),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True),  # 1/2
            # conv2
            nn.Conv2d(64, 128, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True),  # 1/4
            # conv3
            nn.Conv2d(128, 256, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True),  # 1/8
        )
        self.features_4 = nn.Sequential(
            # conv4
            nn.Conv2d(256, 512, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True),  # 1/16
        )
        self.features_5 = nn.Sequential(
            # conv5 features
            nn.Conv2d(512, 512, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True),  # 1/32
        )
        self.classifier = nn.Sequential(
            # fc6
            nn.Conv2d(512, 4096, 7),
            nn.ReLU(inplace=True),
            nn.Dropout2d(),
            # fc7
            nn.Conv2d(4096, 4096, 1),
            nn.ReLU(inplace=True),
            nn.Dropout2d(),
            # score_fr
            nn.Conv2d(4096, n_class, 1),
        )
        self.score_feat3 = nn.Conv2d(256, n_class, 1)
        self.score_feat4 = nn.Conv2d(512, n_class, 1)
        self.upscore = nn.ConvTranspose2d(n_class, n_class, 16, stride=8,
                                              bias=False)
        self.upscore_4 = nn.ConvTranspose2d(n_class, n_class, 4, stride=2,
                                              bias=False)
        self.upscore_5 = nn.ConvTranspose2d(n_class, n_class, 4, stride=2,
                                              bias=False)
    def forward(self, x):
        feat3 = self.features_123(x)  #1/8
        feat4 = self.features_4(feat3)  #1/16
        feat5 = self.features_5(feat4)  #1/32
        score5 = self.classifier(feat5)
        upscore5 = self.upscore_5(score5)
        score4 = self.score_feat4(feat4)
        score4 = score4[:, :, 5:5+upscore5.size()[2], 5:5+upscore5.size()[3]].contiguous()
        score4 += upscore5
        score3 = self.score_feat3(feat3)
        upscore4 = self.upscore_4(score4)
        score3 = score3[:, :, 9:9+upscore4.size()[2], 9:9+upscore4.size()[3]].contiguous()
        score3 += upscore4
        h = self.upscore(score3)
        h = h[:, :, 28:28+x.size()[2], 28:28+x.size()[3]].contiguous()
        return h
    def init_vgg16(self, vgg16, copy_fc8=True, init_upscore=True):
        for l1, l2 in zip(vgg16.features, [self.features_123,self.features_4,self.features_5]):
            if (isinstance(l1, nn.Conv2d) and
                    isinstance(l2, nn.Conv2d)):
                assert l1.weight.size() == l2.weight.size()
                assert l1.bias.size() == l2.bias.size()
                l2.weight.data = l1.weight.data
                l2.bias.data = l1.bias.data
        for i1, i2 in zip([0, 3], [0, 3]):
            l1 = vgg16.classifier[i1]
            l2 = self.classifier[i2]
            l2.weight.data = l1.weight.data.view(l2.weight.data.size())
            l2.bias.data = l1.bias.data.view(l2.bias.data.size())
        n_class = self.classifier[6].weight.size()[0]
        if copy_fc8:
            l1 = vgg16.classifier[6]
            l2 = self.classifier[6]
            l2.weight.data = l1.weight.data[:n_class, :].view(l2.weight.size())
            l2.bias.data = l1.bias.data[:n_class]
        if init_upscore:
            # initialize upscore layer
            c1, c2, h, w = self.upscore.weight.data.size()
            assert c1 == c2 == n_class
            assert h == w
            weight = get_upsample_filter(h)
            self.upscore.weight.data = \
                weight.view(1, 1, h, w).repeat(c1, c2, 1, 1)
            c1, c2, h, w = self.upscore_4.weight.data.size()
            assert c1 == c2 == n_class
            assert h == w
            weight = get_upsample_filter(h)
            self.upscore_4.weight.data = \
                weight.view(1, 1, h, w).repeat(c1, c2, 1, 1)
            c1, c2, h, w = self.upscore_5.weight.data.size()
            assert c1 == c2 == n_class
            assert h == w
            weight = get_upsample_filter(h)
            self.upscore_5.weight.data = \
                weight.view(1, 1, h, w).repeat(c1, c2, 1, 1)
if __name__ == "__main__":
    x = torch.zeros(1, 3, 640, 640)
    model = FCN()
    y = model(x)
    print(y.shape)

image.png

  论文通过在PASCAL VOC 2012和MS COCO数据集上的实验验证了FCN的有效性。与传统的方法相比,FCN在语义分割任务上取得了更好的结果,不仅提高了分割的准确性,而且还能够处理不同大小的输入图像。此外,FCN还可以通过fine-tuning的方式应用于其他任务,例如目标检测和图像分割。

创新&不足

创新点:

  1. 全卷积神经网络架构:传统的卷积神经网络(CNN)只能输出一个固定大小的向量,而全卷积神经网络(FCN)可以输出与输入图像大小相同的特征图,从而实现像素级别的语义分割。
  2. 上采样和跳跃连接技术:FCN使用上采样技术将特征图放大到与输入图像相同的大小,同时使用跳跃连接技术将不同尺度的特征信息融合起来,从而提高分割的准确性。
  3. 可迁移性:FCN可以通过fine-tuning的方式应用于其他任务,例如目标检测和图像分割。

image.png

不足之处:

  1. 训练时间较长:FCN的训练时间较长,需要大量的计算资源和时间。
  2. 模型复杂度高:FCN的模型复杂度较高,需要更多的参数和计算资源。
  3. 对小目标的分割效果不佳:FCN在处理小目标的分割时效果不佳,这可能是因为FCN没有充分考虑小目标的特征信息。
  4. 对类别不平衡的数据集效果不佳:FCN在处理类别不平衡的数据集时效果不佳,这可能是因为FCN没有充分考虑类别之间的差异。

  FCN网络在PASCAL上产生状态最先进的性能。左栏显示了我们性能最高的网络FCN-8s的输出。第二张图显示了Hariharan等人先前最先进的系统产生的分割。注意恢复的精细结构(第一行),分离紧密交互对象的能力(第二行),以及对闭塞器的鲁棒性(第三行)。第四行显示了一个失败的案例:网络将船上的救生衣视为人。

image.png

影响和意义

  该论文提出了一种全卷积网络(简称FCN)的方法,将传统的卷积神经网络(Convolutional Neural Networks,简称CNN)应用到语义分割问题中。这种全卷积网络可以对整个图像进行像素级别的分类,实现了端到端的语义分割。相比于传统的基于CNN的分类方法,FCN可以输出与输入图像大小相同的分割结果,提高了分割的准确性和效率。

  其次,FCN的提出也启示了语义分割领域在模型设计和优化方面的改进。在FCN中,作者使用了反卷积层(Deconvolutional Layer)和池化层的反操作(Upsampling),将低分辨率的特征图还原到原始图像大小,从而得到像素级别的分割结果。这种方法不仅可以提高分割的准确性,还可以减少参数数量和计算量,提高模型的效率。

  此外,FCN的应用也促进了语义分割领域的发展和应用。通过FCN,可以实现对自然图像、医学图像、遥感图像等不同领域的图像进行语义分割,为图像分析、识别、理解等应用提供了重要的支持和参考。同时,FCN也为其他相关领域的研究和应用提供了借鉴和启示,如目标检测、图像生成、视频分割等。


相关文章
|
2月前
|
机器学习/深度学习 算法 网络架构
【CVPR2017】AOD-Net:端到端的除雾网络(原理&实操)
【CVPR2017】AOD-Net:端到端的除雾网络(原理&实操)
378 0
【CVPR2017】AOD-Net:端到端的除雾网络(原理&实操)
|
3月前
|
机器学习/深度学习 缓存 算法
【论文速递】CVPR2020 - CRNet:用于小样本分割的交叉参考网络
【论文速递】CVPR2020 - CRNet:用于小样本分割的交叉参考网络
|
3月前
【论文速递】ICLR2018 - 用于小样本语义分割的条件网络
【论文速递】ICLR2018 - 用于小样本语义分割的条件网络
57 0
|
3月前
|
计算机视觉
【论文速递】Arxiv2018 - 加州伯克利大学借助引导网络实现快速、准确的小样本分割
【论文速递】Arxiv2018 - 加州伯克利大学借助引导网络实现快速、准确的小样本分割
18 0
|
3月前
|
机器学习/深度学习 缓存 算法
【论文速递】IJCV2022 - CRCNet:基于交叉参考和区域-全局条件网络的小样本分割
【论文速递】IJCV2022 - CRCNet:基于交叉参考和区域-全局条件网络的小样本分割
|
4月前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch基础之网络模块torch.nn中函数和模板类的使用详解(附源码)
PyTorch基础之网络模块torch.nn中函数和模板类的使用详解(附源码)
57 0
|
3月前
|
机器学习/深度学习 算法 机器人
【论文速递】TMM2023 - FECANet:用特征增强的上下文感知网络增强小样本语义分割
【论文速递】TMM2023 - FECANet:用特征增强的上下文感知网络增强小样本语义分割
|
17天前
|
数据采集 网络协议 API
python中其他网络相关的模块和库简介
【4月更文挑战第4天】Python网络编程有多个流行模块和库,如requests提供简洁的HTTP客户端API,支持多种HTTP方法和自动处理复杂功能;Scrapy是高效的网络爬虫框架,适用于数据挖掘和自动化测试;aiohttp基于asyncio的异步HTTP库,用于构建高性能Web应用;Twisted是事件驱动的网络引擎,支持多种协议和异步编程;Flask和Django分别是轻量级和全栈Web框架,方便构建不同规模的Web应用。这些工具使网络编程更简单和高效。
|
2月前
|
机器学习/深度学习 编解码 数据可视化
UNet 和 UNet++:医学影像经典分割网络对比
UNet 和 UNet++:医学影像经典分割网络对比
28 0
|
3月前
|
自动驾驶 安全 网络安全
6G 移动通信网络端到端的技术需求指标
6G 移动通信网络端到端的技术需求指标
200 0