图像语义分割网络系列博文索引
FCN与SegNet | U-Net与V-Net | DeepLab系列 | DenseNet、PSPNet、与DenseASPP | Mask R-CNN |
---|
(后四个在计划中,敬请期待)
图像语义网络分割之FCN与SegNet
全卷积网络FCN
FCN为深度学习在图像语义分割领域里程碑的一篇,实现了对任意大小输入图像进行像素级别的端到端的语义分割。原文链接:Fully Convolutional Networks for Semantic Segmentation
我们的关键观点是构建“全卷积”网络,它接受任意大小的输入,并通过有效的推理和学习产生相应大小的输出。我们定义并详述全卷积网络的空间,解释它们在空间密集预测任务中的应用,并与之前的模型建立联系。我们将当前的分类网络(AlexNet [19], VGG网[31],和GoogLeNet[32])改造为完全卷积网络,并通过微调来将其学习到的表示迁移到分割任务。然后,我们定义了一种新的架构,该架构结合了来自深、粗层的语义信息和来自浅、细层的表征信息,从而产生精确和详细的分割。我们的全卷积网络实现了最先进的NYUDv2,SIFT流和PASCAL VOC分割(相对2012年的结果提升了20%,达到了62.2%的平均IU),而对一个典型图像的推断只需要不到五分之一秒。
FCN主要贡献
- 使用迁移学习的方法对已有的分类网络(AlexNet,VGG,GoogLeNet)进行结构改造和参数微调(fine-tuning)用于图像的语义分割。
- 使用全卷积的方法替代分类网络中的全连接层,因为作者认为全连接层破坏了图像像素中的空间关系。图2中展示了使用全连接层与卷积层的对比图,全连接层输出的是一维概率分布,卷积层输出为二维热力图(heat map),较好的保留了像素之间的空间关系,为之后像素级别的图像分割提供了便利。同时采用全卷积相对全连接也大大提升了计算效率。
图2 全连接与全卷积对比图 - 为了稀疏的输出(卷积得到的heat map)与密集的像素建立联系,作者采用了反卷积的方式进行上采样,采用反卷积进行上采样是需要学习的,也可以直接采用双线性插值的方式进行上采样。这个密集和稀疏建立联系是由分多个阶段的跃迁实现的,一定程度上可以理解为多尺度图像融合,目的是实现精细的预测。
FCN网络结构
如图3所示FCN-32s是由pool5得到的特征图直接采用上采样得到的结果,FCN-16s是pool5经过二倍上采样与pool4求和之后再进行上采样,FCN-8s与此过程类似。可视化的FCN网络机构可以参考这里(http://ethereon.github.io/netscope/#/preset/fcn-8s-pascal)
Pytorch框架下FCN实现
这里使用的代码是在Github中guanfuchen/semseg原代码的基础上做了一些顺序上的调整及更多的标注帮助理解。
# -*- coding: utf-8 -*-
import time
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision import models
# fcn32s模型
def fcn_32s(n_classes=21, pretrained=True):
model = fcn(module_type='32s', n_classes=n_classes, pretrained=pretrained)
return model
def fcn_16s(n_classes=21, pretrained=True):
model = fcn(module_type='16s', n_classes=n_classes, pretrained=pretrained)
return model
def fcn_8s(n_classes=21, pretrained=True):
model = fcn(module_type='8s', n_classes=n_classes, pretrained=pretrained)
return model
def cross_entropy2d(input, target, weight=None, size_average=True):
n, c, h, w = input.size()
nt, ht, wt = target.size()
# Handle inconsistent size between input and target
if h > ht and w > wt: # upsample labels
target = target.unsequeeze(1)
target = F.upsample(target, size=(h, w), mode="nearest")
target = target.sequeeze(1)
elif h < ht and w < wt: # upsample images
input = F.upsample(input, size=(ht, wt), mode="bilinear")
elif h != ht and w != wt:
raise Exception("Only support upsampling")
input = input.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c)
target = target.view(-1)
loss = F.cross_entropy(input, target, weight=weight, size_average=size_average, ignore_index=250)
return loss
class fcn(nn.Module):
def __init__(self, module_type='32s', n_classes=21, pretrained=True):
super(fcn, self).__init__()
self.n_classes = n_classes
self.module_type = module_type
# VGG16=2+2+3+3+3+3
# VGG16网络的第一个模块是两个out_channel=64的卷积块
self.conv1_block = nn.Sequential(
nn.Conv2d(3, 64, 3, padding=100), #输入3通道,输出64通道,卷积核大小为3,用100填充
nn.ReLU(inplace=True), #inplace=True,节省内存
nn.Conv2d(64, 64, 3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(2, stride=2, ceil_mode=True), #核的大小为2,步长为2,向上取整
)
# VGG16网络的第二个模块是两个out_channel=128的卷积块
self.conv2_block = nn.Sequential(
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),
)
# VGG16网络的第三个模块是三个out_channel=256的卷积块
self.conv3_block = nn.Sequential(
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),
)
# VGG16网络的第四个模块是三个out_channel=512的卷积块
self.conv4_block = nn.Sequential(
nn.Conv2d(256, 512, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512