语义分割网络FCN

news2024/12/24 8:26:42

语义分割是一种像素级的分类,输出是与输入图像大小相同的分割图,输出图像的每个像素对应输入图像每个像素的类别,每一个像素点的灰度值都是代表当前像素点属于该类的概率。

语义分割任务需要解决的是如何把定位和分类这两个问题一起解决,毕竟语义分割就是进行逐个像素点的分类,就是把where和what结合在了一起。这时候就需要物体的一些细节特征。

在传统的CNN网络中,在最后的卷积层之后会连接上若干个全连接层,将卷积层产生的特征图(feature map)映射成为一个固定长度的特征向量。一般的CNN结构适用于图像级别的分类和回归任务,因为它们最后都期望得到输入图像的分类的概率,如ALexNet网络最后输出一个1000维的向量表示输入图像属于每一类的概率.

而FCN是对图像进行像素级的分类(也就是每个像素点都进行分类),从而解决了语义级别的图像分割问题。与经典CNN在卷积层使用全连接层得到固定长度的特征向量进行分类不同,FCN可以接受任意尺寸的输入图像,采用反卷积层对最后一个卷基层的特征图(feature map)进行上采样,使它恢复到输入图像相同的尺寸,从而可以对每一个像素都产生一个预测,同时保留了原始输入图像中的空间信息,上采样的特征图进行像素的分类。

可以看出,FCN在CNN的基础上将全连接层转换为卷积层可以使分类网输出热图。添加层和空间损失产生了端到端密集学习的高效机器。

——————————————————————————————————————————

传统的图像分类会用到全连接层,但是使用全连接层的话会破坏原有的空间信息,而且使得特征不再具备局部信息,这对于图像分割这种像素级别的预测任务来说,影响非常大。

在FCN出现之前,比较常见的做法是采用滑动窗口,每个窗口从原图像上采集一小块区域,也可以叫做patchwise,作为网络的输入。虽然这样会减少全连接层所带来的对空间信息的破坏,但是它有几个比较明显的缺陷:

  • 它计算量非常大,因为一张图需要滑动次数非常多,这样一来,相邻patch之间重合的部分会重复计算;
  • 不同patch之间相互独立,没有利用到全局的空间信息;
  • patch的设置对模型性能的影响很大,patch过小,则感受野太小,非常影响准确率,patch过大,则重复计算的区域就很多;

FCN的思想

FCN将传统CNN中的全连接层去掉,取而代之的是转化为一个个的卷积层,这样可以保持空间的位置关系。如下图所示:

整体的网络结构分为两个部分:全卷积部分和反卷积部分,丢弃了全连接层,在保持了空间信息的同时,也可以接受任意尺寸的输入图像。其中全卷积部分借用了一些经典的CNN网络,如AlexNet,VGG等等,并把最后的全连接层换成卷积,用于提取特征,形成热点图;而反卷积部分则是将小尺寸的热点图上采样得到原尺寸的语义分割图像。

输入图像经过卷积和池化之后,得到的特征图的宽高相对原图缩小了好几倍,所产生图叫做heatmap----热图,热图就是我们最重要的高维特征图图,得到高维特征之后就是最重要的一步也是最后的一步对原图像进行upsampling,也就是上采样,将图像进行放大直到到原图像的大小。

其实FCN的根本在于丢掉全连接层,保留了原本的空间信息,然后利用跳跃连接来融合特征,将定位较准的浅层信息和分类较准的深层信息结合起来。

最后的输出是类别数大小的heatmap,经过上采样恢复为原图大小的图片,为了对每个像素进行分类预测,这样才能形成最后已经进行语义分割的图像。所以在最后通过逐个像素地求其在1000张图像该像素位置的最大概率作为该像素的分类。

算法细节

FCN上采样使用的是反卷积,也叫转置卷积,文章采用了好几种上采样的结果。为了得到更好的分割效果,论文提出几种方式FCN-32s、FCN-16s、FCN-8s,如下图所示:

  • 网络对原图像进行第一次卷积与下采样后原图像缩小为1/2;
  • 之后对图像进行第二次卷积与下采样后图像缩小为1/4;
  • 重复上面过程,接着继续对图像进行第三次相同的操作,图像缩小为原图像的1/8,此时保留这个阶段的特征图;
  • 同样,在第四次后得到为原图像的1/16的特征图并保留;
  • 最后对图像进行第五次操作,缩小为原图像的1/32,然后把原来CNN操作中的全连接变成两次卷积操作,也就是conv6、conv7,但此时图像的大小还是为原图的1/32,最后生成的可以叫做heatmap了,也就是我们最终想要的结果。

最后我们可以得到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-8s的细节特征最为丰富,分割效果良好。同时论文中也尝试了将pool2、pool1的特征图进行融合,但是效果提升不明显,所以最终效果最好的就是FCN-8s。

FCN仍有一些缺点,比如:

  • 得到的效果还不够好,对细节不够敏感;
  • 没有考虑像素与像素之间的关系,缺乏空间一致性等。

其实FCN最大的贡献其实是提供了用于分割的一种全新思路,相比于传统做法它更加高效,因为避免了由于使用像素块而带来的重复存储和计算卷积的问题。

之后的分割算法基本上都是基于全卷积的方式来进行改进、优化的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1284165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

佛罗里达大学利用神经网络,解密 GPCR-G 蛋白偶联选择性

内容一览:G 蛋白偶联受体 (GPCRs) 是一种将细胞膜外的刺激,传递到细胞膜内的跨膜蛋白,广泛参与到人体生理活动当中。近日,佛罗里达大学的研究者测定了 GPCRs 和 G 蛋白的结合选择性,并开发了预测二者选择性的算法&…

kubernetes监控GPA安装部署

本文在于指导如何对k8s的监控GPA(Grafana,prometheus以及alertmanager)进行安装部署。 1. 介绍 Prometheus 在真正部署Prometheus之前,应了解一下Prometheus的各个组件之间的关系及作用: 1)MertricServer:是k8s集群…

朋友圈7大黄金发圈时间

众所周知,朋友圈运营是私域运营必不可少的重要环节。 因为做好朋友圈运营,能够打造形成高质量、高价值的私域流量,加快实现用户成交。 那么如何形成一个吸粉又吸金的人设,做出高质量的朋友圈发圈内容呢? 那么如何确保能…

SSM整合(注解版)

SSM 整合是指将学习的 Spring,SpringMVC,MyBatis 进行整合,来进行项目的开发。 1 项目基本的配置类 1.1 Spring 配置类 这个配置类主要是管理 Service 中的 bean,controller 层的 bean 对象是 SpringMVC 管理的 package cn.ed…

二极管:二极管的基本原理

一、认识导体、绝缘体、半导体 什么是导体? 导体 conductor ,是指电阻率很小,且容易传导电流的物质。导体中存在大量可自由移动的带电粒子,也称为载流子。在外电场的作用下,载流子作定向运动,形成电流。 …

安装配置JDK1.8

JDK1.8的下载及配置 1.进入甲骨文官网甲骨文官网往下翻找到java8并且点击windows. 2.下载Java8必须登录账号 3下载完后点击进入安装,直接下一步就可以,记住这个路径。 4.右击我的电脑进入环境配置,新增变量。 CLASSPATH .;%JAVAHOME%\lib;…

3.C程序编译步骤

目录 1 预处理 2 编译 3 汇编 4 链接 5 文件大小情况 依次执行下面4个步骤 预处理 将所有头文件展开,比如stdio.h等,展开就相当于把stdio.h中的所有代码粘贴到你的代码里。将所有的宏文件展开,像stdio.h是官方定义的头文件&#x…

C# - Opencv应用(3) 之矩阵Mat使用[图像截取粘贴、ROI操作、位运算、数学计算]

C# - Opencv应用(3) 之矩阵Mat使用[图像截取粘贴、ROI操作、位运算、数学计算] 图像读取,大小、截取、位运算图像ROI操作:粘贴赋值、滤波图像数学计算部分结果如下: 1.图像读取,大小、截取、位运算 //图…

计算机辅助药物设计AIDD-小分子-蛋白质|分子生成|蛋白质配体相互作用预测

文章目录 计算机辅助药物设计AIDD【小分子专题】AIDD概述及药物综合数据库学习机器学习辅助药物设计图神经网络辅助药物设计自然语言处理辅助药物设计药物设计与分子生成 计算机辅助药物设计【蛋白质专题】蛋白质数据结构激酶-Kinase相似性学习基于序列的蛋白质属性预测基于结构…

解决xshell连接诶树莓派中文乱码的问题

系统版本 解决办法 在根目录下找到 /etc/profile 修改profile文件,添加以下两行.以便重启之后也能生效: export LANGzh_CN.utf8 export LC_ALLzh_CN.utf8注意: /etc/profile的修改需要root权限才能修改! 在xshell的编码格式改为UTF-8

一次性客户的笔记总结

创建一次性客户,系统会给出一个客户编码; 每次记账的时候,在录入过账码及客户编码后,点击回车,都需要录入这个客户的详细信息(比如 客户名称等) 一次性客户的信息存储在BSEC表中,这种…

飞致云1panel + 雷池WAF

可能有许多人都有这个需求:为自己的个人站点套上WAF,增加安全性,本文将介绍如何将1panel面板深度结合长亭雷池防火墙,实现为个人站点套上WAF并且自动续签ssl证书。 前提条件: 服务器IP已绑定域名 完整的1panel环境 …

springboot简单集成上传和下载(带页面)

来学习一下文件上传和下载 一、页面开发 整体思路 登录页 主页 二、库表设计 SET FOREIGN_KEY_CHECKS0;-- ---------------------------- -- Table structure for t_files -- ---------------------------- DROP TABLE IF EXISTS t_files; CREATE TABLE t_files (id int(11) N…

【五分钟】熟练使用numpy.cumsum()函数(干货!!!)

引言 numpy.cumsum()函数用于计算输入数组的累积和。当输入是多维数组时,numpy.cumsum()函数可以沿着指定轴计算累积和。 计算一维数组的累计和 代码如下: # 计算一维数组的累计和 tmp_array np.ones((4,), dtypenp.uint8) # [1, 1, 1, 1] print(&…

java--接口概述

1.认识接口 ①java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构:接口。 ②注意:接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类称为实现类。 ③一个类可以实现多个接口(接…

1、Spring基础概念总结

Spring概述: Spring体系结构 IOC的概念和作用 耦合指的是对象之间的依赖关系,耦合越小越好 以jdbc为例 通过反射来注册驱动,那么会造成驱动名称写死在程序当中,这种结果显然是不太合理的通过配置文件的形式可以解决这种耦合问…

微服务--一篇入门kubernets

Kubernetes 1. Kubernetes介绍1.1 应用部署方式演变1.2 kubernetes简介1.3 kubernetes组件1.4 kubernetes概念 2. kubernetes集群环境搭建2.1 前置知识点2.2 kubeadm 部署方式介绍2.3 安装要求2.4 最终目标2.5 准备环境2.6 系统初始化2.6.1 设置系统主机名以及 Host 文件的相互…

成为AI产品经理——模型稳定性评估(PSI)

一、PSI作用 稳定性是指模型性能的稳定程度。 上线前需要进行模型的稳定性评估,是否达到上线标准。 上线后需要进行模型的稳定性的观测,判断模型是否需要迭代。 稳定度指标(population stability index ,PSI)。通过PSI指标,我们可以获得不…

Python类型注解必备利器:typing模块解读指南

更多资料获取 📚 个人网站:ipengtao.com 在Python 3.5版本后引入的typing模块为Python的静态类型注解提供了支持。这个模块在增强代码可读性和维护性方面提供了帮助。本文将深入探讨typing模块,介绍其基本概念、常用类型注解以及使用示例&am…

ssm农业信息管理系统源码和论文

摘 要 网络的广泛应用给生活带来了十分的便利。所以把农业信息管理与现在网络相结合,利用java技术建设农业信息管理系统,实现农业信息管理的信息化。则对于进一步提高农业信息管理发展,丰富农业信息管理经验能起到不少的促进作用。 农业信息…