【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月前
|
消息中间件 监控 网络协议
Python中的Socket魔法:如何利用socket模块构建强大的网络通信
本文介绍了Python的`socket`模块,讲解了其基本概念、语法和使用方法。通过简单的TCP服务器和客户端示例,展示了如何创建、绑定、监听、接受连接及发送/接收数据。进一步探讨了多用户聊天室的实现,并介绍了非阻塞IO和多路复用技术以提高并发处理能力。最后,讨论了`socket`模块在现代网络编程中的应用及其与其他通信方式的关系。
233 3
|
2月前
|
运维 安全 网络安全
|
3月前
|
数据采集 Web App开发 开发工具
|
3月前
|
数据安全/隐私保护
|
7月前
|
域名解析 安全 网络协议
WebKit的网络模块支持的最新网络协议和安全标准
WebKit的网络模块支持的最新网络协议和安全标准
|
2月前
|
边缘计算 安全 5G
|
2月前
|
JSON API 开发者
深入解析Python网络编程与Web开发:urllib、requests和http模块的功能、用法及在构建现代网络应用中的关键作用
深入解析Python网络编程与Web开发:urllib、requests和http模块的功能、用法及在构建现代网络应用中的关键作用
22 0
|
4月前
|
JSON API 数据格式
Python网络编程:HTTP请求(requests模块)
在现代编程中,HTTP请求几乎无处不在。无论是数据抓取、API调用还是与远程服务器进行交互,HTTP请求都是不可或缺的一部分。在Python中,requests模块被广泛认为是发送HTTP请求的最简便和强大的工具之一。本文将详细介绍requests模块的功能,并通过一个综合示例展示其应用。
101 11
|
6月前
|
数据采集 JSON 数据格式
三:《智慧的网络爬虫》— 网络请求模块(下)
本篇文章讲解了网络请求模块中Requests模块的get请求和post请求,并用十几张图示详细介绍了爬虫工具库与开发者工具的操作与使用;同时本篇文章也列举了多个代码示例如:对搜狗网页的爬取;爬取360翻译(中英文互译程序)并以此介绍了重放请求(通过重放请求来确定反爬参数)以及Cookie与Session实战案例 -- 爬取12306查票
74 9
三:《智慧的网络爬虫》—  网络请求模块(下)
|
5月前
|
计算机视觉 网络架构
【YOLOv8改进 - 卷积Conv】DWRSeg:扩张式残差分割网络,提高特征提取效率和多尺度信息获取能力,助力小目标检测
YOLO目标检测专栏探讨了YOLO的创新改进,如多尺度特征提取的DWRSeg网络。该网络通过区域残差化和语义残差化提升效率,使用DWR和SIR模块优化高层和低层特征。DWRSeg在Cityscapes和CamVid数据集上表现优秀,速度与准确性兼备。论文和代码已公开。核心代码展示了一个包含DWR模块的卷积层。更多配置详情见相关链接。
下一篇
DataWorks