首页 科技正文

使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现)

admin 科技 2020-07-25 66 0

一.导论

在图像语义支解领域,困扰了计算机科学家许多年的一个问题则是我们若何才气将我们感兴趣的工具和不感兴趣的工具划分支解开来呢?好比我们有一只小猫的图片,怎样才气够通过计算机自己对图像举行识别到达将小猫和图片当中的靠山相互支解开来的效果呢?如下图所示:

 

 

 而在2015年出来的FCN,全卷积神经网络完善地解决了这个问题,将曾经mean IU(识别平均准确度)只有百分之40的成就提升到了百分之62.2(在Pascal VOC数据集上跑的效果,FCN论文上写的),像素级别识别精确度则是90.2%。这已经是一个相当完善的效果了,险些逾越了人类对图像举行区分,支解的能力。如上图所示,小猫被支解为了靠山,小猫,边缘这三个部门,因此图像当中的每一个像素最后只有三个展望值,是否为小猫,靠山,或者边缘。全卷积网络要做的就是这种举行像素级别的分类义务。那么这个网络是若何设计和实现的呢?

二.网络的实现

这个网络的实现虽然听名字十分霸气,全卷积神经网络。不外事实上使用这个名字无非是把卷积网络的最后几层用于分类的全毗邻层换成了1*1的卷积网络,以是才叫这个名字。这个网络的首先对图片举行卷积——>卷积——>池化,再卷积——>卷积——>池化,直到我们的图像缩小得够小为止。这个时刻就可以举行上采样,恢复图像的巨细,那么什么是上采样呢?你估量还没有听说过,等下咱们一 一道来。这个网络的结构如下所示(附上论文上的原图):

 从中可以看到我们输入了一个小猫和小狗在一起时的图片,最后再前向流传,在这个前向流传网络的倒数第三层,卷积神经网络的长度就酿成了N*N*21,由于在VOC数据集上一共有21个softmax分类的效果,因此每个种别都需要有一个相关概率(置信度)的输出。而前面这个前向流传的卷积神经网络可以是VGG16,也可以是AlexNet,Google Inception Net,甚至是ResNet,论文作者在前三个Net上都做了响应的实验,然则由于ResNet那时还没出来,也就没有实验过在前面的网络当中使用它。当我们的网络酿成了一个N*N*21的输出时,我们将图像举行上采样,上接纳就相当于把我们适才获得的具有21个分类输出的效果还原成一个和原图像巨细相同,channel相同的图。这个图上的每个像素点都代表了21个事物种别的概率,这样就可以获得这个图上每一个像素点应该分为哪一个种别的概率了。那么什么是图像的上采样呢?

三.图像的上采样

图像的上采样正好和卷积的方式相反,我们可以通过正常的卷积让图像越来越小,而上采样则可以同样通过卷积将图像变得越来越大,最后缩放成和原图像同样巨细的图片,关于上采样的论文在这这篇论文当专门做了详解:https://arxiv.org/abs/1603.07285。上采样有3种常见的方式:双线性插值(bilinear),反卷积(Transposed Convolution),反池化(Unpooling)。在全卷积神经网络当中我们接纳了反卷积来实现了上采样。我们先来回首一下正向卷积,也称为下采样正向的卷积如下所示,首先我们拥有一个这样的3*3的卷积核:

 

然后对一个5*5的特征图行使滑动窗口法举行卷积操作,padding=0,stride=1,kernel size=3,以是最后获得一个3*3的特征图:
使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第1张

 那么上采样呢?则是这样的,我们假定输入只是一个2*2的特征图,输出则是一个4*4的特征图,我们首先将原始2*2的map举行周围填充pading=2的操作,笔者查阅了许多资料才知道,这里周围都填充了数字0,周围的padding并不是通过神经网络训练得出来的数字。然后用一个kernel size=3,stride=1的感受野扫描这个区域,这样就可以获得一个4*4的特征图了!:
使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第2张

 我们甚至可以把这个2*2的feature map,每一个像素点离隔一个空格,空格里的数字填充为0,周围的padding填充的数字也全都为零,然后再继续上采样,获得一个5*5的特征图,如下所示:

使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第3张

 这样咱们的反卷积就完成了。那么什么是1*1卷积呢?

四.1*1卷积

在我们的卷积神经网络前向流传的历程当中,最后是一个N*N*21的输出,这个21是可以我们举行人为通过1*1卷积界说出来的,这样我们才气够获得一个21个种别,每个种别泛起的概率,最后输出和原图图像巨细一致的谁人特征图,每个像素点上都有21个channel,示意这个像素点所具有的某个种别输出的概率值。吴恩达教授在解说卷积神经网络的时刻,用到了一张十分经典的图像来示意1*1卷积:
使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第4张

 

 原本的特征图长宽为28,channel为192,我们可以通过这种卷积,使用32个卷积核将28*28*192酿成一个28*28*32的特征图。在使用1*1卷积时,获得的输出长款保持稳定,channel数目和卷积核的数目相同。可以用抽象的3d立体图来示意这个历程:

使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第5张

 

 因此我们可以通过控制卷积核的数目,将数据举行降维或者升维。增添或者削减channel,然则feature map的长和宽是不会改变的。我们在全卷积神经网络(FCN)正向流传,下采样的最后一步(可以查看本博客的第一张图片)就是将一个N*N*4096的特征图酿成了一个N*N*21的特征图。

五.全卷积神经网络的跳级实现(skip)

我们若是直接接纳首先卷积,然后上采样获得与原图尺寸相同特征图的方式的话,举行语义支解的效果经由实验是不太好的。由于在举行卷积的时刻,在特征图还比较大的时刻,我们提取到的图像信息非常丰富,越到后面图像的信息丢失得就越显著。我们可以发现经由最前面的五次卷积和池化之后,原图的划分率划分缩小了2,4,8,16,32倍。对于最后一层的的图像,需要举行32倍的上采样才气够获得和原图一样的巨细,但仅依赖最后一层图像做上采样,获得的效果照样不太准确,一些细节依然很不准确。因此作者接纳了跳级毗邻的方式,即将在卷积的前几层提取到的特征图划分和后面的上采样层相连,然后再相加继续网上往上上采样,上采样多次之后就可以获得和原图巨细一致的特征图了,这样也可以在还原图像的时刻能够获得更多原图所拥有的信息。如下图所示:

使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第6张

 作者最先提出的跳级毗邻是把第五层的输出举行上采样,然后和池化层4的展望相结合起来,最后获得原图的计谋,这个计谋叫做FCN-16S,之后又实验了和所有池化层结合起来展望的方式叫做FCN-8S,发现这个方式准确率是最高的。如下图所示:

使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第7张

 Ground Truth示意原始图像的人为标注,前面的都是神经网络做出的展望。跳级毗邻,我们这类给出的原图的巨细是500*500*3,这个尺寸无所谓,由于全卷积神经网络可接受随便尺寸巨细的图片。我们首先从前面绿色刚刚从池化层做完maxpool的特征图上做一次卷积然后,然后再把下一个绿色的特征图做卷积,最后把16*16*21,已经做完1*1卷积的输出,把这个三个输出相加在一起,这样就实现了跳级(skip)输入的实现,再把这几个输入融合之后的效果举行上采样,获得一个568*568*21的图,将这个图通过一个softmax层酿成500*500*21的特征图,因此图像的长宽和原图一模一样了,每一个像素点都有21个概率值,示意这个像素点属于某个种别的概率,除了和原图的channel差别之外没啥差别的。

然后我们来看基于Tensorflow的代码实现。

六.Tensorflow代码实现全卷积神经网络

首先导包并读取图片数据:

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import os

import glob
images=glob.glob(r"F:\UNIVERSITY STUDY\AI\dataset\FCN\images\*.jpg")
#然后读取目的图像
anno=glob.glob(r"F:\UNIVERSITY STUDY\AI\dataset\FCN\annotations\trimaps\*.png")

glob库可以用于读取内陆的图片并用来制作每一个batch的数据,我把数据集放在了F:\UNIVERSITY STUDY\AI\dataset\FCN\,这个文件夹下。

幂image文件夹用于装载训练集的图片,annatation文件夹用于装载人们标注界限的数据集。

标注的图片显示如下:

使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第8张

 

 

原始图是一个小狗的图片,原始图在下面:
使用全卷积神经网络FCN,举行图像语义支解详解(附代码实现) 第9张

 然后制作dataset,batch数据,以及读取图片文件的函数,包罗png和jpg划分举行剖析为三维矩阵:

#现在对读取进来的数据举行制作batch
np.random.seed(2019)
index=np.random.permutation(len(images))

images=np.array(images)[index]
anno=np.array(anno)[index]
#确立dataset
dataset=tf.data.Dataset.from_tensor_slices((images,anno))
test_count=int(len(images)*0.2)
train_count=len(images)-test_count
data_train=dataset.skip(test_count)
data_test=dataset.take(test_count)

def read_jpg(path):
    img=tf.io.read_file(path)
    img=tf.image.decode_jpeg(img,channels=3)
    return img

def read_png(path):
    img=tf.io.read_file(path)
    img=tf.image.decode_png(img,channels=1)
    return img

#现在编写归一化的函数
def normal_img(input_images,input_anno):
    input_images=tf.cast(input_images,tf.float32)
    input_images=input_images/127.5-1
    input_anno-=1
    return input_images,input_ann

#加载函数
def load_images(input_images_path,input_anno_path):
    input_image=read_jpg(input_images_path)
    input_anno=read_png(input_anno_path)
    input_image=tf.image.resize(input_image,(224,224))
    input_anno=tf.image.resize(input_anno,(224,224))
    return normal_img(input_image,input_anno)

data_train=data_train.map(load_images,num_parallel_calls=tf.data.experimental.AUTOTUNE)
data_test=data_test.map(load_images,num_parallel_calls=tf.data.experimental.AUTOTUNE)

#现在最先batch的制作
BATCH_SIZE=3#凭据显存举行调整
data_train=data_train.repeat().shuffle(100).batch(BATCH_SIZE)

data_test=data_test.batch(BATCH_SIZE)

然后我们使用VGG16举行卷积操作,同时使用imagenet的预训练模子举行迁徙学习,搭建神经网络和跳级毗邻:

conv_base=tf.keras.applications.VGG16(weights='imagenet',
                                     input_shape=(224,224,3),
                                     include_top=False)
#现在确立子model用于继续conv_base的权重,用于获取模子的中心输出
#使用这个方式居然能够继续,而没有显式的指定到底继续哪一个模子,确实神奇
#确实是可以使用这个的,这个方式就是在模子确立完之后再举行的挪用
#这样就会继续自动继续之前的网络结构
#而若是界说
sub_model=tf.keras.models.Model(inputs=conv_base.input,
                               outputs=conv_base.get_layer('block5_conv3').output)

#现在确立多输出模子,三个output
layer_names=[
    'block5_conv3',
    'block4_conv3',
    'block3_conv3',
    'block5_pool'
]

layers_output=[conv_base.get_layer(layer_name).output for layer_name in layer_names]

#确立一个多输出模子,这样一张图片经由这个网络之后,就会有多个输出值了
#不外输出值虽然有了,怎么能够举行跳级毗邻呢?
multiout_model=tf.keras.models.Model(inputs=conv_base.input,
                               outputs=layers_output)

multiout_model.trainable=False

inputs=tf.keras.layers.Input(shape=(224,224,3))
#这个多输出模子会输出多个值,因此前面用多个参数来接受即可。
out_block5_conv3,out_block4_conv3,out_block3_conv3,out=multiout_model(inputs)
#现在将最后一层输出的效果举行上采样,然后划分和中心层多输出的效果举行相加,实现跳级毗邻
#这里示意有512个卷积核,filter的巨细是3*3
x1=tf.keras.layers.Conv2DTranspose(512,3,
                                   strides=2,
                                   padding='same',
                                   activation='relu')(out)
#上采样之后再加上一层卷积来提取特征
x1=tf.keras.layers.Conv2D(512,3,padding='same',
                                   activation='relu')(x1)
#与多输出效果的倒数第二层举行相加,shape稳定
x2=tf.add(x1,out_block5_conv3)
#x2举行上采样
x2=tf.keras.layers.Conv2DTranspose(512,3,
                                   strides=2,
                                   padding='same',
                                   activation='relu')(x2)
#直接拿到x3,不使用
x3=tf.add(x2,out_block4_conv3)
#x3举行上采样
x3=tf.keras.layers.Conv2DTranspose(256,3,
                                   strides=2,
                                   padding='same',
                                   activation='relu')(x3)
#增添卷积提取特征
x3=tf.keras.layers.Conv2D(256,3,padding='same',activation='relu')(x3)
x4=tf.add(x3,out_block3_conv3)
#x4还需要再次举行上采样,获得和原图一样巨细的图片,再举行分类
x5=tf.keras.layers.Conv2DTranspose(128,3,
                                   strides=2,
                                   padding='same',
                                   activation='relu')(x4)
#继续举行卷积提取特征
x5=tf.keras.layers.Conv2D(128,3,padding='same',activation='relu')(x5)
#最后一步,图像还原
preditcion=tf.keras.layers.Conv2DTranspose(3,3,
                                   strides=2,
                                   padding='same',
                                   activation='softmax')(x5)

model=tf.keras.models.Model(
inputs=inputs,
outputs=preditcion
)

编译和fit模子:

model.compile(
optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['acc']#这个参数应该是用来打印正确率用的,现在终于明白啦啊
)

model.fit(data_train,
         epochs=1,
         steps_per_epoch=train_count//BATCH_SIZE,
         validation_data=data_test,
         validation_steps=train_count//BATCH_SIZE)

输出:

Train for 1970 steps, validate for 1970 steps
1969/1970 [============================>.] - ETA: 1s - loss: 0.3272 - acc: 0.8699WARNING:tensorflow:Your input ran out of data; interrupting training. Make sure that your dataset or generator can generate at least `steps_per_epoch * epochs` batches (in this case, 1970 batches). You may need to use the repeat() function when building your dataset.
1970/1970 [==============================] - 3233s 2s/step - loss: 0.3271 - acc: 0.8699 - val_loss: 0.0661 - val_acc: 0.8905

效果只用了一个epoch,像素精确度就已经到达了百分之89了,是不是很神奇呢?嘿嘿

,

AllbetGmaing下载

欢迎进入AllbetGmaing下载(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

版权声明

本文仅代表作者观点,
不代表本站Allbet欧博官网的立场。
本文系作者授权发表,未经许可,不得转载。

评论