全卷积网络(Fully Convolutional Networks,FCN) 是用深度神经网络来做语义分割的开山之作,它是首个端对端的针对像素级预测的全卷积网络,自从该网络提出后,就成为语义分割的基本框架,后续算法基本都是在该网络框架中改进而来。

FCN

FCN 以 VGG16 作为 backbone 提取不同层次的特征,之后再通过双线性插值方法恢复特征图的分辨率,在这过程中同时利用了跳跃连接,逐步融合下采样端产生的不同层次的特征信息,从而提高了分割效果。

FCN 将传统 CNN 中的全连接层都替换成了卷积层,例如在传统的 CNN 结构 VGG16 中前面 5 层对应着卷积层,第 6、7 为全连接层长度为 4096 的一维向量,第 8 层也为全连接层长度为 1000 的一维向量,代表着分类的类别为 1000。

而 FCN 通过将这 6、7、8 层都改为卷积层,对应着的卷积核大小分别为(7,7,4096)、(1,1,4096)、(1,1,1000)。由于网络中所有层都为卷积层,因此也称为全卷积神经网络。

通过多层的卷积和池化操作之后,我们得到的图像也会变得越来越小,随之分辨率也会变得也来越低,在 FCN 中通过使用上采样技术进行图像恢复,恢复到原始的图像大小以及分辨率,例如当网络经过 5 次卷积和池化操作以后,图像的分辨率依次缩小到了 2、4、8、16、32 倍的大小,因此对应着最后一层图像的输出,则需要经过一次 32 倍的上采样技术才能够得到与原始图像一样大小的图片。

在 FCN 中上采样技术是通过反卷积技术和跳层连接进行实现的,例如如果只通过对第 5 层的输出结果进行上采样得到原始图像大小这样的结果往往是不够的精确的,会导致一些细节特征丢失从而无法恢复,出于这样的考虑,作者又将第 4 层的输出和第 3 层的输出结果同样的进行反卷积操作,它们分别需要进行一次 16 倍的上采样和 8 倍的上采样,之后将它们得到的结果进行融合,从而达到了更加精确的分割效果。

如下图所示,图像进行 32 倍、16 倍、8 倍上采样的结果对比图可以发现,它们得到的分割结果也变得越来越精确了。

FCN-32s

原始论文中 FCN 算法实现的架构细节,模型前半部分和一般的 CNN 结构类似,交替使用卷积层和池化层,卷积层因先做了扩边处理(外围一圈填充 0,是 Padding-100)故处理完大小不变,池化层使图像长宽均缩短为原先的 1/2,这一部分总共经历 5 层池化层后图像长宽尺寸均减小到原先的 1/32,对此输出直接上采样就得到 FCN-32 的输出(第一个预测结果,精度略差),这里的上采样就是反卷积运算,主要通过间隔补零来改变大小。

由上图可以看到,图像进行32倍上采样对于一张输入图片(h,w,3) 通过 VGG16 backbone 的前 5 个卷积和池化层之后图像大小缩小为(h/32, w/32, 512)。

经过 FC6 卷积核大小为(7,7,4096),图像大小变为(h/32, w/32, 4096)。

经过 FC7 卷积核大小为(1,1,4096),图像大小变为(h/32, w/32, 4096)。

经过 FC8 卷积核大小为(1,1,num_cls),图像大小变成了(h/32, w/32, num_cls),最后通过反卷积操作对结果图进行32倍上采样,恢复到了原始图像大小。

代码实现如下,首先导入需要的工具包:

import torch
import torch.nn as nn
import numpy as np

建立 layer 和 block,建立 VGG 重复的 stage 结构,block 依次包含 conv-bn-relu:

# Block 包含:conv-bn-relu
class Block(nn.Module):def __init__(self, in_ch, out_ch, kernel_size=3, padding=1, stride=1):super(Block, self).__init__()self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=kernel_size, padding=padding, stride=stride)self.bn1 = nn.BatchNorm2d(out_ch)self.relu1 = nn.ReLU(inplace=True)def forward(self, x):out = self.relu1(self.bn1(self.conv1(x)))return out# 建立 layer 加入很多 Block
def make_layers(in_channels, layer_list):layers = []for out_channels in layer_list:layers += [Block(in_channels, out_channels)]in_channels = out_channelsreturn nn.Sequential(*layers)class Layer(nn.Module):def __init__(self, in_channels, layer_list):super(Layer, self).__init__()self.layer = make_layers(in_channels, layer_list)def forward(self, x):out = self.layer(x)return out

修改 VGG 的代码,改为 FCN-32s

  • 在 VGG 的代码里第一层的 padding=1 改成 padding = 100,是为了适应不同大小的输入图像,如果不设置padding,那么当输入图片大小 小于 192×192192 \times 192192×192 ,则在 7×77 \times 77×7 的卷积核处做卷积时会报错。

  • 将全连接层 self.fc6 = nn.Linear(512*7*7, 4096) 改为卷积层 self.fc6 = nn.Conv2d(512, 4096, 7) ,这样可以使用预训练模型的权重。后面的两个全连接层修改成 1×11 \times 11×1 的卷积

  • 上采样(upsampling)使用的是 Conv2DTranspose 方法,上采样32倍,self.upscore = nn.ConvTranspose2d(n_class, n_class, 64, 32)

  • 因有 padding=100 的操作,上采样 32 倍之后需要裁剪成与原图一样,左右裁剪 19 个像素,具体计算如下。

原图 SSS 经过self.conv1 之后特征图的大小变为 1 + (S + 200 -3) / 1 = S + 198;

后续的卷积层 kernel_size=3, padding=1, stride=1,尺寸不变,5 次降采样2倍,尺寸变为 (S+198)/32;

直到 self.fc6 = nn.Conv2d(512, 4096, 7),尺寸变为 1 + ( (S+198)/32 - 7 ) / 1 = (S+6)/32;

最后上采样 32 倍 self.upscore = nn.ConvTranspose2d(n_class, n_class, 64, 32),尺寸变为 ((S+6)/32 - 1 )* 32 + 64 = S + 38,中心裁剪 38/2 = 19。

class VGG_fcn32s(nn.Module):'''将 VGG model 改变成 FCN-32s '''def __init__(self, n_class=21):super(VGG_fcn32s, self).__init__()self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=100) # padding = 100,传统 VGG 为1self.bn1 = nn.BatchNorm2d(64)self.relu1 = nn.ReLU(inplace=True)self.layer1 = Layer(64, [64]) # 第一组 Stageself.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 降采样 /2self.layer2 = Layer(64, [128, 128]) # 第二组 Stageself.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)# 降采样 /4self.layer3 = Layer(128, [256, 256, 256, 256]) # 第三组 Stageself.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)# 降采样 /8self.layer4 = Layer(256, [512, 512, 512, 512]) # 第四组 Stageself.pool4 = nn.MaxPool2d(kernel_size=2, stride=2)# 降采样 /16self.layer5 = Layer(512, [512, 512, 512, 512]) # 第五组 Stageself.pool5 = nn.MaxPool2d(kernel_size=2, stride=2)# 降采样 /32# modify to be compatible with segmentation and classification#self.fc6 = nn.Linear(512*7*7, 4096) # 全连接层 VGGself.fc6 = nn.Conv2d(512, 4096, 7) # padding = 0self.relu6 = nn.ReLU(inplace=True)self.drop6 = nn.Dropout()#self.fc7 = nn.Linear(4096, 4096) # 全连接层 VGGself.fc7 = nn.Conv2d(4096, 4096, 1)self.relu7 = nn.ReLU(inplace=True)self.drop7 = nn.Dropout()#self.score = nn.Linear(4096, n_class) # 全连接层 VGGself.score = nn.Conv2d(4096, n_class, 1)self.upscore = nn.ConvTranspose2d(n_class, n_class, 64, 32) # 上采样 32 倍def forward(self, x):f0 = self.relu1(self.bn1(self.conv1(x)))f1 = self.pool1(self.layer1(f0))f2 = self.pool2(self.layer2(f1))f3 = self.pool3(self.layer3(f2))f4 = self.pool4(self.layer4(f3))f5 = self.pool5(self.layer5(f4))#f5 = f5.view(f5.size(0), -1) print('f5.shape:', f5.shape)f6 = self.drop6(self.relu6(self.fc6(f5)))print('f6.shape:', f6.shape)f7 = self.drop7(self.relu7(self.fc7(f6)))print('f7.shape:', f7.shape)score = self.score(f7)upscore = self.upscore(score)# crop 19 再相加融合 [batchsize, channel, H, W ] 要对 H、W 维度裁剪upscore = upscore[:, :, 19:19+x.size(2), 19:19+x.size(3)].contiguous() return upscore

最后打印模型的输出,查看效果。

FCN-16 和 FCN-8 的计算思想类似,只是把最后一个预测卷积层的输入修改了一下大小,以增加输入信息。

FCN-16s

输入图片(h, w, 3) 通过 VGG16 backbone 的前 5 个卷积和池化层之后图像大小缩小为(h/32, w/32, 512),然后经过 FC6、FC7 卷积核大小分别为(7,7,4096)、(1,1,4096),图像大小变为(h/32, w/32, 4096);

其次经过 FC8 卷积核大小为(1,1,num_cls),图像大小变成了(h/32, w/32,num_cls),之后经过反卷积 2 倍上采样将图像大小变为(h/16, w/16, num_cls);

另一部分则经过 pool4 图像大小变成了(h/16, w/16, 512),然后通过卷积核大小为(1,1,num_cls),图像大小变成了(h/16, w/16, num_cls),最后将得到的两部分进行融合,得到的结果经过一次 16 倍的上采样技术恢复到原始图片的大小。

FCN-8s

FCN-16 和 FCN-8 代码实现类似,这里代码实现稍微复杂的 FCN-8s。

输入图片经过 FC8 卷积核大小为(1,1,num_cls),图像大小变成了(h/32, w/32, num_cls),之后经过反卷积 2 倍上采样将图像大小变为(h/16, w/16, num_cls),代码标记为 up2_feat

另一部分则经过 pool4 图像大小变成了(h/16,w/16,512),然后通过卷积核大小为(1,1,num_cls),图像大小变成了(h/16, w/16, num_cls),代码实现细节在h = self.trans_f4(f4)

将以上得到的两部分进行融合,代码实现细节在h = h + up2_feat

得到的结果经过一次 2 倍的上采样技术得到图像大小为(h/8, w/8, num_cls),代码实现细节在up4_feat = self.up4times(h)

h 之后与经过 pool3 图像大小变为(h/8, w/8, 256),通过卷积和大小为(1,1,num_cls),图像大小变成了(h/8, w/8, num_cls)两部分进行融合,代码细节在h = self.trans_f3(f3)h = h + up4_feat

最终通过一次8倍的上采样技术,恢复到了原始图像的大小,代码细节在h = self.up32times(h)

整体代码如下:

class VGG_19bn_8s(nn.Module):def __init__(self, n_class=21):super(VGG_19bn_8s, self).__init__()self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=100)self.bn1 = nn.BatchNorm2d(64)self.relu1 = nn.ReLU(inplace=True)self.layer1 = Layer(64, [64])self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)self.layer2 = Layer(64, [128, 128])self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)self.layer3 = Layer(128, [256, 256, 256, 256])self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2)self.layer4 = Layer(256, [512, 512, 512, 512])self.pool4 = nn.MaxPool2d(kernel_size=2, stride=2)self.layer5 = Layer(512, [512, 512, 512, 512])self.pool5 = nn.MaxPool2d(kernel_size=2, stride=2)self.fc6 = nn.Conv2d(512, 4096, 7) # padding=0self.relu6 = nn.ReLU(inplace=True)self.drop6 = nn.Dropout2d()self.fc7 = nn.Conv2d(4096, 4096, 1)self.relu7 = nn.ReLU(inplace=True)self.drop7 = nn.Dropout2d()self.score_fr = nn.Conv2d(4096, n_class, 1)self.trans_f4 = nn.Conv2d(512, n_class, 1) # 通道数归一化成 n_calssself.trans_f3 = nn.Conv2d(256, n_class, 1) # 通道数归一化成 n_calssself.up2times = nn.ConvTranspose2d(n_class, n_class, 4, stride=2, bias=False) # 上采样2倍self.up4times = nn.ConvTranspose2d(n_class, n_class, 4, stride=2, bias=False) # 上采样2倍self.up32times = nn.ConvTranspose2d(n_class, n_class, 16, stride=8, bias=False) # 上采样8倍for m in self.modules():if isinstance(m, nn.ConvTranspose2d):m.weight.data = bilinear_kernel(n_class, n_class, m.kernel_size[0])def forward(self, x):f0 = self.relu1(self.bn1(self.conv1(x)))f1 = self.pool1(self.layer1(f0))f2 = self.pool2(self.layer2(f1))f3 = self.pool3(self.layer3(f2))f4 = self.pool4(self.layer4(f3))f5 = self.pool5(self.layer5(f4))f6 = self.drop6(self.relu6(self.fc6(f5)))f7 = self.score_fr(self.drop7(self.relu7(self.fc7(f6))))up2_feat = self.up2times(f7) # 上采样2倍h = self.trans_f4(f4) # pool4 通道数归一化成 n_calss,便于相加print(h.shape)print(up2_feat.shape)h = h[:, :, 5:5 + up2_feat.size(2), 5:5 + up2_feat.size(3)] h = h + up2_featup4_feat = self.up4times(h) # 上采样2倍h = self.trans_f3(f3) # pool3 通道数归一化成 n_calss,便于相加print(h.shape)print(up4_feat.shape)h = h[:, :, 9:9 + up4_feat.size(2), 9:9 + up4_feat.size(3)]h = h + up4_feath = self.up32times(h) # 上采样8倍print(h.shape)final_scores = h[:, :, 31:31 + x.size(2), 31:31 + x.size(3)].contiguous()return final_scores

最后总结一下 FCN-32s、FCN-8s 的中心裁剪细节。

FCN-32s 裁剪细节

输出特征图大小计算公式 O = (I + 2P – K) / S + 1

上采样的计算公式: O = (I - 1)* S + K - 2P

self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=100)

尺寸
conv1 (S + 200 -3) / 1 + 1 = S + 198
Pool1 (S + 198) / 2
Pool2 (S + 198) / 4
Pool3 (S + 198) / 8
Pool4 (S + 198) / 16
Pool5 (S + 198) / 32
Fc6 (S + 198) / 32 -7 + 1 = (S + 198 - 6*32) / 32 = (S + 6) / 32
Fc7 (S + 6) / 32 - 1 + 1 = (S + 6) / 32

FCN-32S : 上采样Fc7 32倍 为 [(S + 6) / 32 - 1 ] * 32 + 64 - 2*0 = (S + 6) -32 + 64 = S + 38

FCN-32S 要跟原图一样,应该前后左右裁剪 38/2 = 19

FCN-8s 裁剪细节

Fc7:
上采样 2 倍 为 [(S + 6) / 32 - 1 ] * 2 + 4 - 2*0 = (S + 6) / 16 + 2 = ( S + 38) / 16

上采样 4 倍为[(S + 6) / 32 -1] * 4 + 8 = (S+6)/8 + 4 = (S + 38) / 8

h-up2_feat 是上采样 2 倍的 fc7 与 pool4 相加,即 ( S + 38) / 16 + (S + 198) / 16

只看分母 pool4 相比 h-up2_feat 多160,计算过程是 198-38 = 160 —> 再除分子 160/16 = 10 —> 中心对称裁剪 10 / 2 =5 —> Pool4 前后裁剪5

h-up4_feat:上采样 2 倍 h-up2_feat 与 pool3 相加

上采样2倍的 h-up2_feat : [( S + 38) / 16 - 1] * 2 + 4 = (S+38) / 8 + 2 = (S + 54) / 8

Pool3:(S + 198) / 8

198-54 = 144 --> 144 / 8 = 18 —> 18/2 = 9—> Pool3 前后裁剪 9

最后上采样 8 倍:[(S + 54) / 8 -1] * 8 + 16 = S + 54 + 8 = S + 62 —> 62/2 = 31,与原图S相比(前后裁剪31)。
~

参考文献:

[1]http://home.ustc.edu.cn/~liujunyan/blog/fcn/

[2]Nucleus image segmentation method based on GAN and FCN model

[3]https://blog.csdn.net/m0_56192771/article/details/124113078

[4]https://www.wandouip.com/t5i87567/

图像语义分割模型 FCN相关推荐

  1. Recorder︱图像语义分割(FCN、CRF、MRF)、论文延伸(Pixel Objectness、)

    图像语义分割的意思就是机器自动分割并识别出图像中的内容,我的理解是抠图- 之前在Faster R-CNN中借用了RPN(region proposal network)选择候选框,但是仅仅是候选框,那 ...

  2. 从零开始的图像语义分割:FCN快速复现教程(Pytorch+CityScapes数据集)

    从零开始的图像语义分割:FCN复现教程(Pytorch+CityScapes数据集) 前言 一.图像分割开山之作FCN 二.代码及数据集获取 1.源项目代码 2.CityScapes数据集 三.代码复 ...

  3. 图像语义分割:FCN全卷积网络概述

    图像语义分割:FCN全卷积网络概述 Why does FCN work? FCN网络的基本概念 FCN的计算原理 Feature map上采样 What is FCN? FCN网络的结构 FCN每层的 ...

  4. 深度学习-Tensorflow2.2-图像处理{10}-UNET图像语义分割模型-24

    UNET图像语义分割模型简介 代码 import tensorflow as tf import matplotlib.pyplot as plt %matplotlib inline import ...

  5. 带你1小时掌握Google图像语义分割模型,更有《深度学习》实体书免费送

    计算机视觉作为人工智能的主流技术领域之一,历经图像分类-->目标定位-->目标检测,最终发展到图像语义分割技术. 如下图所示,从最初的识别图片信息进行单一分类,到单图片中多目标识别分析,而 ...

  6. 用PaddlePaddle实现图像语义分割模型ICNet

    什么是图像语义分割? 图像语意分割顾名思义是将图像像素按照表达的语义含义的不同进行分组/分割,图像语义是指对图像内容的理解,例如,能够描绘出什么物体在哪里做了什么事情等,分割是指对图片中的每个像素点进 ...

  7. 图像语义分割之FCN和CRF

    https://blog.csdn.net/u012759136/article/details/52434826?locationNum=7&fps=1 前言 (呕血制作啊!)前几天刚好做了 ...

  8. matlab 图像语义分割,笔记︱图像语义分割(FCN、CRF、MRF)、论文延伸(Pixel Objectness、)...

    图像语义分割的意思就是机器自动分割并识别出图像中的内容,我的理解是抠图- 之前在Faster R-CNN中借用了RPN(region proposal network)选择候选框,但是仅仅是候选框,那 ...

  9. 图像语义分割网络FCN(32s、16s、8s)原理及MindSpore实现

    一.FCN网络结构 全卷积网络(Fully Convolutional Networks),是较早用于图像语义分割的神经网络.根据名称可知,FCN主要网络结构全部由卷积层组成,在图像领域,卷积是一种非 ...

最新文章

  1. PHP SSL certificate: unable to get local issuer certificate的解决办法
  2. html怎么把图片作为背景_抖音背景图片怎么弄,抖音背景图片引导关注
  3. 2019年云计算行业深度报告
  4. Html.DropDownListFor练习(2)
  5. 计算机与广播电视论文,计算机技术在广播电视节目的应用论文
  6. 敏捷开发系列学习总结(14)——Spotify敏捷模式详解三部曲第二篇:研发过程
  7. spring+mybatis 多数据源切换
  8. 基于C# 和Access数据库的电影院管理系统
  9. 不一样的设计!20个国外优秀的电子商务网站
  10. Layui select 的动态添加
  11. 基于因子分析和聚类分析 的SPSS河南省各地区综合发展分析+操作步骤+全文详细
  12. ug筋板不能正确覆盖开放轮廓_安徽省六安市第一中学2017届高三上学期第二次月考地理【解析】...
  13. 浙江农林大学计算机分数线,浙江农林大学各专业录取分数线
  14. 怎样快速提高计算机能力,如何提高算术能力?不借助计算机、笔、纸等工具,怎么能快速心算出多位数计算结果?如:489x85 如:128965-98542有什么口决及速算的方法的详细步骤?...
  15. basler相机的类
  16. 计算机专业网名英语翻译,英语网名大全带翻译【三篇】
  17. php 图片印章_php版圆形印章生成器
  18. 中国运动传感器陀螺仪行业市场供需与战略研究报告
  19. Vivado 2020.1 and 2020.2 错误 arm-none-eabi-ar: *.o: Invalid argument
  20. STM32 之三 标准外设版USB驱动库详解(架构+文件+函数+使用说明+示例程序)

热门文章

  1. 女生适合做 Linux 工程师吗?
  2. bazel使用学习杂记
  3. 查看电脑软件安装路径
  4. opencv-python 图片旋转90度
  5. rancher重启失败修复
  6. 手机移动端如何跳转至QQ 或者QQ的加好友页面
  7. iptable防火墙
  8. 分布式的开发与运行流程
  9. 电子器件系列八:肖特基二极管
  10. 分享一个moba游戏王者荣耀类简单技能指示器