图像语义分割是计算机读懂图像的基础,所以叫图像语义分割,左侧是图像语义分割,右侧是实例分割,语义分割关注种类,实例分割关注个体,像我们左侧的语义分割,分割后机器就能大致了解,图里有5只羊,1个人,1条狗,这样我们再结合其他的操作,就可以让机器对这个图片进行描述

 

给不同类的物体赋给不同的数值

语义分割我们有两个网络可以使用,一个是FCN(fully convolutional network,全卷积网络),另一个是Unet,有关网络的详细介绍可以看一下这篇文章 深度学习:语义分割 FCN与Unet_work_coder的博客-CSDN博客_fcn unet

目录

1  上采样与下采样

1.1  插值法

1.2  反最大池化

1.3  反平均池化

1.4  反卷积(转置卷积)

2  FCN结构概览

3  数据集展示

4  训练模型

4.1  导入库

4.2  处理数据路径

4.3  创建数据集

4.4  定义加载图像函数

4.5  处理数据集

4.6  搭建网络

4.7  编译模型

4.8  训练模型

5  预测模型

5.1  预测数据集外的单张图像

5.2  预测数据集内的图像



1  上采样与下采样

在了解语义分割之前我们首先需要了解向上采样与向下采样两个概念,我在这篇文章中有介绍过 5.图像金字塔_potato123232的博客-CSDN博客

简单来讲就是,向上采样会让图像越来越大,向下采样会让图像越来越小,我们之前使用的最大池化,平均池化与卷积就是向下采样

我们一般有三种向上采样的方式,分别为插值法,反池化,反卷积。反池化与反卷积是池化与卷积的逆向操作


1.1  插值法

我们当前有一个2*1的图像,像素值为1和3

现在我们使用插值法,在1和3之间插入它们两个的平均值

那么它现在就变成了3*1大小的图像,用这种方法从视觉上看起来与原图像差不多,而且在计算机的运算中影响也不大

1.2  反最大池化

红线上面是最大池化的过程,红线下面是反最大池化的过程

1.3  反平均池化

红线上面是平均池化的过程,红线下面是反平均池化的过程

1.4  反卷积(转置卷积)

棕色线上面是正向卷积,下面是反向卷积,我们假定卷积核在两次运算过程中都是不变的,红框内是卷积核与偏置,我们先回顾一下正向卷积的计算过程

  • 左边的1 = 1-2-4+5+1
  • 右边的1 = 2-3-5+6+1

之后我们再用结果进行反卷积,这样我们就不能得到具体的值了,我们可以得到满足

  • a+e=b+d
  • b+c=e+f

的多组解

tensorflow内置的反卷积的一层,是这样写的,到时候我们会用到

2  FCN结构概览

大致的情况是这样的

这一部分我们很好理解,前两层变大是因为对图像有填充,后面就是正向卷积操作,不停的提取图片的特征

绿色是我们在卷积过程中得到的局部特征,我们要使用这些局部特征与最后的全局特征相结合,最终得到图像分割的结果

在提取局部特征并结合后方的结果,我们称之为跳接(Skip)。含有跳接的结构,我们称之为跳接结构

下面我们用代码实现语义分割


3  数据集展示

我们使用的数据集是 33.图像定位 中使用过的The Oxford-IIIT Pet Dataset,在图像定位中我们使用的是xml文件,在语义分割中,我们使用的是annotaions中的trimaps中的文件

trimaps中都是png文件,虽然是png文件,但是我们直接在windows中打不开,png文件的内容是对图像分割的结果

每一个图像对应着两种文件,下图右面的这个png能由代码打开,左侧的这个文件无法用代码或windows直接打开

我们使用代码打开这些png文件,我们读取 yorkshire_terrier_200.png 这张图像

  • 文件名中除了png前面那个点,在其他地方不能再有点,要不解码解不出来

我们把这张图打开看一下,我们发现它把这只狗的轮廓标注了出来

通过两张图我们可以发现,实际上分割图像由三类构成,一类是背景,一类是轮廓,另一类是物体的非轮廓,我们验证一下我们的猜想是否是正确的

发现这这张图就是由三个值构成

我们从这三个值就可以发现,我们实际上是对图像中的每一个像素点做分类

我们的图像一共由7390张,我们的标注文件一共由7390个,这次我们的数据是一一对应的

下面我们使用代码进行实现图像分割


4  训练模型


4.1  导入库

4.2  处理数据路径

首先我们把图像与标注文件的路径读取进来

由于使用相同的方法读取进来的,所以一定是一一对应的,之后我们使用相同的索引对其进行乱序,使用的方法是np.random.seed(),在这篇文章中介绍过 17.200种鸟类图片分类_potato123232的博客-CSDN博客

4.3  创建数据集

之后我们分出训练数据与测试数据

4.4  定义加载图像函数

我们由两个格式的图像,所以我们首先定义两个读取函数

之后我们定义归一化函数

此处input_images的归一化为先除127.5,然后减1,这样我们就把图像值的范围控制在[-1,1]之间,与直接除255效果相同,直接除255的范围在[0,1]之间

annotations在上面我们看到由[1,2,3]三个值组成,我们将其减1,这样我们的三个值就为[0,1,2],其实不减也可以,我们习惯将0作为第一个值

之后定义加载图像函数,先读取,再归一化,我们此处加入图运算,让其运行速度更快

4.5  处理数据集

首先我们将之前定义的路径数据集加载成图像数据集

之后我们设置随机批次与乱序

我们现在使用数据集显示一下,看看对不对

发现没有什么问题


4.6  搭建网络

我们要搭建的网络为FCN,tensorflow中是没有内置FCN网络的,所以我们需要自己攒一个,我们使用VGG16构成FCN,在搭建之前,我们先画一个图,看一下该怎么做

tensorflow中的VGG预训练模型既不是VGG16-C也不是VGG16-D,不过也差不多,我们读取过来看一下

我们用上面定义的conv_base作为我们下图画红框的位置,这个也就是我们的主干

之后我们要使用block5_conv3,block4_conv3,block3_conv3(这三个层并不固定)与输出结果进行跳接,下面这个图是进行了两次跳接,我们在模型中进行三次跳接(次数并不固定)

之后就是细节问题,像这里进行的上采样操作(明显这个图变大了,但是变薄了,变薄与卷积核个数相关,我们也可以不使其变薄)

还有这里进行的下采样操作,这里下采样操作的目的是在丢失过一部分特征信息后,再次对特征信息进行提取,以达到更好的效果,下图是让图像变小了,我们也可以不让其变小

那么总的来说我们整个的网络应该是这样的,我们首先使用VGG16创建Conv_base,然后将Conv_base改为多输出模型(以供我们后面跳接),我们称多输出模型为Multi_out_model

之后我们再通过上采样与下采样,使图像大小一致后,我们对其余三个输出进行跳接

网络我们了解了之后,我们使用代码来构建网络

首先我们创建conv_base,是上图中绿色的部分

之后我们利用conv_base生成多输出模型

我们使用'block5_conv3','block4_conv3','block3_conv3','block5_pool',这四层的输出,前三层是我们之前的结果,最后一个是我们VGG本来应输出的结果

这里涉及到两个方法,一个是get_layer()这个是获取这一层的对象,参数为该层的名称(字符串类型,可在model.summary()中查看),然后后面的output是层对象中的方法,会返回输出对象,在函数式构建网络中可以作为outputs使用

此时我们就有了多输出模型multi_out_model,由于参数过大,我们这里选择不对其参数进行修改,也可以改,改了更准,但更慢

之后我们正式构建模型,我们首先定义输入

之后获取多分类模型的四个输出

我们在这里要看一下四个输出的shape

我们要从(7,7)的图像变为(14,14),之后从(14,14)变为(28,28),再之后变为(56,56),最后还会进行两次翻倍,知道与原图像同样大小

之后对原本VGG的输出进行反卷积与卷积操作,反卷积操作是让图像变大,卷积操作的目的是提取特征

这里涉及到一个新的层 Conv2DTranspose ,参数和卷积层相似,512是卷积核个数,3是卷积核大小,3表示(3,3),strides是步长,padding是填充,activation是激活函数

我们之前介绍卷积原理是介绍过,步长为1时,图像不会变小,卷积核为2时,图像的宽与高都会扩大为原来的2倍,扩大2倍的目的是让它和第一次跳接的宽与高数值相同,上面512个卷积核是和我们第一次跳接的第三维度的数是相同的

下面我们进行一次跳接,跳接就是使用tf.add将它们加到一起

跳接之后我们对跳接的结果进行反卷积与卷积,之后以同样的方法进行第二次跳接与第三次跳接

第三次跳接之后进行的反卷积与卷积的卷积核个数就可以自定了,但是strides一定还是2,因为我们最终输出的结果需要和原图一样大

之后我们进行反卷积,这一层也是我们的输出层,由于我们之前看了,我们的标注有三个部分,所以我们此处的反卷积核个数一定为3,激活函数一定为softmax,strides一定为2,其余可以自定

我们看一下输出的shape

这个结果是符合我们的预期的,与原图像同样的大小,其中包含着三种值

最后我们创建模型

我们看一下这个模型的概览,由于有多输出和跳接的存在,概览感觉上会非常混乱

4.7  编译模型

本质上还是分类问题,所以我们使用 sparse_categorical_crossentropy 作为损失函数

4.8  训练模型

加入tensorboard与checkpoint之后,我们开始训练,训练后保存模型

训练之后我们确认一下模型是否存在

之后看一下tensorboard,红色是训练情况,蓝色的预测情况

通过两条曲线可以发现,模型存在过拟合情况,但最终的正确率基本在0.9左右,说明这个模型还算可以

5  预测模型


5.1  预测数据集外的单张图像

我们先预测数据集外单张的图像,我们的预测结果形状是(224,224,3),我们这样理解这个结果,我们当前一共有224*224个像素点,这些像素点,在三个维度中都有一个值,也就是一个像素点对应三个值,在这三个值中,哪一个值最大,就表明该像素是哪一类

我们找一张预测图像

我们首先导入库

之后我们读取模型

然后我们定义加载图像函数

  • 由于我们训练的时候有batch,所以我们在这里增加一维度

之后我们使用加载函数读取图像,然后使用model.predict预测一下这张图像,之后显示出来看一下

我们再看一下shape

可以发现我们的值都在最内(后)的维度,所以我们要对最后的一个维度进行操作,我们提取出其三个值中的最大值,执行完毕后我们看一下shape

之后我们再在最后加一个维度,这个维度是通道数,这样我们才能将其转换为plt可以显示的img

然后我们再将其转换为plt可现实的img,这里使用的索引0,是解决掉batch这个维度的,也就是我们现在的图像为(224,224,1),此时img图像中没有内置shape这个功能,所以我们看不了shape

我们直接看显示的结果

我们与原图像对比一下

发现还是很相似的,我们把这个过程封装成一个函数,然后预测另一张图片

发现效果也还可以

5.2  预测数据集内的图像

我们另开一个py文件,首先我们导入库

导入数据路径

定义加载图像函数

加载并处理数据集

导入模型

我们对第一个batch中的前3张图像进行预测,image是原图像,mask是标注图像,pred_mask是预测图像

  • plt.figure()是画布的大小,想figsize=(10,5)的意思是,宽为1000px,高为500px
  • plt.tight_layout是间距的意思

我们发现预测图像与标注图像虽然有差异,但大体上差不多

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐