图像分割介绍

图像分割介绍

图像分割是什么

我们对图像分类比较熟悉,图像分类是指对一张图片进行分类,而图像分割是对每一个像素进行分类。如下所示:


右边是原图片,左边是进行图像分割的结果。 图像分割是一个很大的领域,大概可以分为以下 4 种。

  • 超像素分割(Superpixels Segmentation):少量超像素代替大量像素,常用于图像预处理。超像素是一系列像素的集合,这些像素具有类似的颜色、纹理等特征,距离也比较近。如下图,每个超像素就是原图的一小片。

  • 语义分割(Semantic Segmentation):对图像中的每个像素进行分类。从下图可以看到,不同的类别由不同的颜色,比如下图就把图像分为了草地(浅绿)、人(红色)、树木(深绿)、天空(蓝色)等标签,用不同的颜色来表示。但是在红色的人类别中,无法区分个体。一个像素虽然是属于人,但是无法区分是哪一个人的。

  • 实例分割(Instance Segmentation):对个体目标进行分割,像素级目标检测。实例分割方式有点类似于物体检测,不过物体检测一般输出的是 bounding box,实例分割输出的是一个mask。实例分割和上面的语义分割也不同,它不需要对每个像素进行标记,它只需要找到感兴趣物体的边缘轮廓就行,比如下图中的人就是感兴趣的物体。该图的分割方法采用了一种称为Mask R-CNN的方法。我们可以看到每个人都是不同的颜色的轮廓,因此我们可以区分出单个个体。

  • 全景分割(Panoptic Segmentation):这是语义分割和实例分割的结合。如下图所示,每个像素都被分为一类,如果一种类别里有多个实例,会用不同的颜色进行区分,我们可以知道哪个像素属于哪个类中的哪个实例。比如下图中黄色和红色都属于人这一个类别里,但是分别属于不同的实例(人),因此我们可以通过mask的颜色很容易分辨出不同的实例。

模型是如何将图像分割的

模型的输入是 3D 张量,表示 RGB 图像,比如(3, 224,224),其中 224*224 表示图像的宽高。最终模型的输出是 3D 张量(class_num, 224,224),其中第一个维度 class_num 表示类别数量。

下面是一个具体的例子,使用torch.hub调用预训练好的deeplab-V3模型进行图像分割。然后把分割结果进行可视化,就可以得到我们在博客一开始的分割示意图。

需要注意的是所有经过预训练的模型都期望输入图像以相同的方式进行标准化预处理,即形状为(N, 3, H, W)的3通道RGB图像的 mini batch,其中N是图像数, HW至少为224像素. 图像d的 RGB 值必须标准化到[0, 1]范围内,然后使用mean = [0.485, 0.456, 0.406]std = [0.229, 0.224, 0.225] [0, 1]进行标准化。

模型的返回是一个OrderedDict ,该OrderedDict具有两个与输入Tensor高度和宽度相同的Tensor,但具有21个类别。 output['out']的形状为(N, 21, H, W),包含分割的结果。而output['aux']包含每像素的辅助损耗值. 在 inference 模式下,output['aux']无效。

output[0]表示第一个图片,形状为(21, H, W),在每个像素处都有对应于每个类别的未归一化概率. 要获取每个类的最大预测概率的类别,可以执行output_predictions = output[0].argmax(0)

具体步骤在代码注释中已经写的很详细了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import torch
import torchvision.transforms as transforms
from matplotlib import pyplot as plt
from PIL import Image

path_img='imgs/demo_img4.jpg'

preprocess=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
])

input_image=Image.open(path_img).convert('RGB')
# 加载 pytorch/vision 库里的 deeplabv3_resnet101 模型, pretrained=True 表示加载在 Pascal VOC 数据集上预训练好的权重
model=torch.hub.load('pytorch/vision','deeplabv3_resnet101', pretrained=True)
# 设置模型为非训练状态
model.eval()

# 对图片进行预处理
input_tensor=preprocess(input_image)
# unsqueeze 的作用是增加维度,第一个维度为 Batch,形成一个 Batch再输入模型:[3, H, W] 变为 [1, 3, H, W]
input_bchw=input_tensor.unsqueeze(0)

with torch.no_grad():
# 模型输出是一个 dict, 我们需要获取 out 对应的 4d 张量:output_4d.shape: [1, 21, H, W]
output_4d=model(input_bchw)['out']
# 取出 Batch 的第一个数据, output.shape: [21, H, W]
output=output_4d[0]
# 取出每个像素概率最大的类别对应的index, output_predictions.shape: [H, W]
output_predictions=output.argmax(0)
# 由于 Pascal VOC 数据集有 21 个类别,因此这里需要生成 21 个类别的不同颜色。
# 这是RGB颜色映射矩阵,至于为什么是这些数就不清楚了,这里就是自动的生成21个颜色,并且颜色要有一定区分,
# 也可以手动设置21个RGB颜色
# palette: tensor([33554431, 32767, 2097151])
palette=torch.tensor([2**25-1,2**15-1,2**21-1])
# colors.size: [21, 3]
colors=torch.as_tensor([i for i in range(21)])[:,None]*palette
# 转化为 0-255 之间
colors=(colors%255).numpy().astype('uint8')
r=Image.fromarray(output_predictions.byte().cpu().numpy())
# 把类别映射到颜色
r.putpalette(colors)
plt.subplot(121).imshow(r)
plt.subplot(122).imshow(input_image)
# 需要先调用 plt.savefig(),再调用 plt.show()
# 在 plt.show() 后实际上已经创建了一个新的空白的图片(坐标轴),这时候你再 plt.savefig() 就会保存这个新生成的空白图片。
# 原因参考 https://blog.csdn.net/u010099080/article/details/52912439
plt.savefig("segmentation.png")
plt.show()

下面是一些运行的结果展示:




最后一个图像是由两个图片拼接而成,上半身是狗,下半身是猫,但是网络将这两个部分都认为是这一个类别。也就是说我们改变了对象的一部分,但是模型对其他没有变化的部分的分类也发生了变化,整个对象的分类都发生了改变。每一个像素的分类不仅会考虑每个像素自身的信息,还会综合考虑图像上整体区域的信息。

PyTorch-Hub 介绍

PyTorch-Hub 是PyTorch 模型库,有大量模型供开发者调用。下面是常用的 3 个方法介绍。

1
2
3
4
5
6
# 加载某个模型
model = torch.hub.load(github,model, pretrained=True)
# 列出某个项目的所有模型
model = torch.hub.list(github,force_reload=False)
# 查看这个模型有哪些参数
model = torch.hub.help(github,model, force_reload=False)

其中github表示项目名,model表示模型名。

更多详细信息请查看https://pytorch.org/hub

深度学习图像分割模型简介

FCN(Fully Convolutional Networks)

主要贡献是利用全卷积完成 pixelwise prediction(逐像素预测)。因为之前的网络最后几层是 Linear 层,这些全连接层有一个局限性,那就是输入的特征数量必须是一致的,这就意味着输入模型的图片大小也是固定的。但是在图像分割的,输 inference 阶段,输入的图片宽高可能不是固定的,最后的 Linear 层无法处理不一样的特征数量。因此 FCN 利用全卷积替换了所有的 Linear 层,使得模型可以处理不同宽高的输入图片。

U-Net 系列

最初是用于医学图像分割。医学图像分割有一个特点是数据量非常少,而 Unet 可以在非常小的数据集上获得非常好的结果。 下图是 Unet 的网络结构示意图,整个网络呈 U 的形状,因此被才成为 Unet。Unet 的左边称为 Encoder(编码器),右边称为 Decoder(解码器)。每一个 Encoder 输出的 feature map 除了给下一层的 Encoder,还会直接传到后面对应的 Decoder,奠定 Unet 系列图像分割模型的基本结构-编码器和解码器的特征融合。现在 Unet 系列已经有几十篇论文。Unet 的系列论文和代码可以查看 https://github.com/ShawnBIT/UNet-family


仔细查看上图中模型的输入的宽高和输出的宽高是不一样的,输入的宽高是 572572,输出的宽高是 388 388。这是因为输入的图像是由原始图像向四各方向镜像而来。而模型的输入是 1 通道的,因为医学图像是灰度图像;而输入是 2 通道的,因为这里是对每个像素做 2 分类。如下图所示:


可以看到输入和输出的宽高可以是不匹配的,这样就可以综合考虑 572572 区域上的信息,来完成 388 388 区域上 的图像分割。

DeepLab 系列

DeepLab 系列到目前位置有 V1、V2、V3、V3+ 四个模型。

DeepLab V1

主要贡献是有两个:

  • 孔洞卷积:借助孔洞卷积,增大感受野,解决了下采样如 max pooling 导致的细节信息丢失问题。

  • CRF:对于图像分类等 High Level Vision Task,对同一张图片进行空间变换(如平移、旋转),其图片分类结果是不变的。但是但对于图像分割等 Low-Level Vision Task,对于一张图片进行空间变换后,其结果是改变的。这篇论文提出采用 CRF 进行 mask 后处理,得到精细的 mask 输出,解决空间不变形问题。


但是是基于VGG提取特征,从现在的角度看有些落后。

论文地址:Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs

DeepLab V2

主要贡献有 3 点。

  • 使用 Atrous Convolution 代替原来上采样的方法,比之前得到更高像素的 score map,并且增加了感受野的大小。
  • 使用 ASPP(Atrous spatial pyramid pooling) 空间金字塔池化代替原来对图像做预处理 resize 的方法,解决多尺度的池化问题,使得输入图片可以具有任意尺度,而不影响神经网络中全连接层的输入大小。这个贡献最大。


- 使用全连接的 CRF,利用低层的细节信息对分类的局部特征进行优化。

论文地址:DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs

DeepLab V3

主要贡献有两点:

  • 孔洞卷积的串行(原始的卷积会使得特征图越来越小,而孔洞卷积使得图片大小不变,获得更加精确的图像信息)


- ASPP 的并行:其中一层同时使用不同的卷积 rate,得到不同大小的特侦图,最后对这些特征图进行 concat 和 1*1 的卷积。


论文地址:Rethinking Atrous Convolution for Semantic Image Segmentation

DeepLab V3+

最主要的贡献是在 DeepLab V3 的基础上加上 Encoder-Decoder 的思想,也就是借鉴了 Unet 中的思想。如下图所示,在 Encoder 中首先使用孔洞卷积得到 Low-Level Features,然后使用不同 rate 的并行 ASPP 提取不同大小的 feature map,最后拼接得到最终的 feature map,把这个 feature map 输入到 Decoder 中。

在 Decoder 中对 feature map 进行 4 倍的上采样,和原来的 Low-Level Features 进行拼接,接着卷积,最后再上采样 4 倍得到最终的输出。

1589458606727

论文地址:Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation

图像分割方法总结

最后总结一下在图像分割问题中解决多尺度问题的方法总结,主要有 4 点。

  • Image Pyramid:对图像进行多尺度的输入,得到不同的 feature map,然后再进行融合。
  • Encoder-Decoder:Unet 提出的方法。
  • Atrous Convolution:空洞卷积,可以保持图像的分辨率,可以获得全局信息。
  • Spatial Pyramid Pooling:空间金字塔池化,利用不同 rate 的池化层提出不同大小的 feature map,最后拼接得到最终的 feature map。


这里给出两个资源,一个是最新的图像分割综述,另一个是图像分割资源汇总。

训练 Unet 完成人像分割

这里使用 4 个 encoder,每一个 encoder 由一个 block 和 maxpool 组成,而每个 block 包括两个Conv->BN->Relu

然后经过 bottle neck,bottle neck 的构成和 encoder 一样。

再经过 4 个 decoder ,每一个 decoder 都是由 一个转置卷积得到 4 倍的上采样,然后经过一个 block,再和 Encoder 中对应的 feature map 拼接起来,输入到下一个 Decoder。

具体可以参考上面 Unet 的结构示意图。

训练的数据来自于http://xiaoyongshen.me/webpage_portrait/index.html。原始的数据较大,这里只是采用了其中的一部分数据。

代码见 https://github.com/xiechuanyu/PyTorch_Practice

数据见 待补充



参考

评论