基于keras的全卷积网络FCN—语义分割

语义分割的里程碑式模型FCN于2015年出现,一举将语义分割的准确率提高近20%个点,MIOU也有极大改善,FCN的表现远超传统模型。FCN叫做全卷积网络,顾名思义网络的各个层都是卷积层,即不再使用全连接层。这种方式使FCN很好地保存了特征的空间信息。

在传统的卷积网络中,一层一层的卷积核池化使特征维度不断降低,而语义分割最后是要得到和原图同尺寸的分割图。FCN的做法是使用上采样提高分辨率。而对于前面卷积层不断丢失的信息,FCN的做法是使用跳接结构,以此来保留特征。

关于FCN的具体介绍,请移步这里。本文主要是FCN的代码实现

博主同样实现了keras搭建unet网络,与本文使用的是相同的方法,具体请移步这里

基于猫狗数据的代码实现:

数据准备:
本文用的是猫狗数据集,images里面存放的是对应的图片(3通道),annotation存放的是语义分割的标签(1通道)。
在这里插入图片描述

在这里插入图片描述
数据的读取、转换和打乱操作:

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_anno


#加载函数
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,(256,256))
    input_anno=tf.image.resize(input_anno,(256,256))
    return normal_img(input_image,input_anno)
# 读取图像和目标图像
images=glob.glob(r"images\*.jpg")
anno=glob.glob(r"annotations\trimaps\*.png")

#现在对读取进来的数据进行制作batch
np.random.seed(1)
index=np.random.permutation(len(images)) # 随机打乱7390个数
images = np.array(images)[index]
anno = np.array(anno)[index]

#创建dataset
dataset=tf.data.Dataset.from_tensor_slices((images,anno))

# 测试数据量和训练数据量,20%测试。
test_count=int(len(images) * 0.2)
train_count=len(images) - test_count

# 取出训练数据和测试数据
data_train=dataset.skip(test_count)  # 跳过前test的数据
data_test=dataset.take(test_count)   # 取前test的数据
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会使维度由四维将为3维
BATCH_SIZE = 8
data_train=data_train.shuffle(100).batch(BATCH_SIZE)
data_test=data_test.batch(BATCH_SIZE)

经过了batch,我们的数据就是4维的了,可以送进模型了。

fcn模型:
本文使用了vgg16作为基础模型,也可以选择resnet等其他模型。fcn因为中间的图片维度非常小,经过一层层上采样再还原成原尺寸,会丢失许多信息,所以采用了跳级操作,具体看实现方法:

def fcn():
    conv_base = tf.keras.applications.VGG16(weights='imagenet',
                                            input_shape=(256, 256, 3),
                                            include_top=False)
    conv_base.summary()

    # 现在我们就可以拿到block5_conv3的输出,进行跳级连接了,连接之后再上采样
    sub_model = tf.keras.models.Model(inputs=conv_base.input,
                                      outputs=conv_base.get_layer('block5_conv3').output)

    sub_model.summary()

    # 现在创建多输出模型,三个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.summary()

    multiout_model.trainable = False

    inputs = tf.keras.layers.Input(shape=(256, 256, 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
    )

    model.summary()  # 因为有跳级结构,因此这个函数不能够进行很好的表示

    return model

看一下模型输出:
在代码中我将输入调整成了256。当然,你也可以根据自己的爱好随意调整它。

原始的vgg模型
在这里插入图片描述

整理后的FCN:
在这里插入图片描述

训练

将数据送进模型,编译,训练,保存模型。

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

# 可视化
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir="E:\jupyter\log2", histogram_freq=1)
# 训练
model.fit(data_train,
         epochs=1,
         batch_size = 8,
         validation_data=data_test,
          callbacks = [tensorboard_callback])

# 保存模型
model.save('FCN_model.h5')

#加载保存的模型
new_model=tf.keras.models.load_model('FCN_model.h5')
#查看模型的架构:
new_model.summary()

训练结果
在这里插入图片描述
tensorboard:
在这里插入图片描述
不好意思,只训练了一个epoch。

全部代码:

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import os
import glob
from tensorflow.compat.v1 import ConfigProto
from tensorflow.compat.v1 import InteractiveSession

# 调整显存使用情况,避免显存占满
config = ConfigProto()
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)


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_anno


# 加载函数
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,(256,256))
    input_anno=tf.image.resize(input_anno,(256,256))
    return normal_img(input_image,input_anno)


def fcn():
    conv_base = tf.keras.applications.VGG16(weights='imagenet',
                                            input_shape=(256, 256, 3),
                                            include_top=False)
    conv_base.summary()

    # 现在我们就可以拿到block5_conv3的输出,进行跳级连接了,连接之后再上采样
    sub_model = tf.keras.models.Model(inputs=conv_base.input,
                                      outputs=conv_base.get_layer('block5_conv3').output)

    sub_model.summary()

    # 现在创建多输出模型,三个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.summary()

    multiout_model.trainable = False

    inputs = tf.keras.layers.Input(shape=(256, 256, 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
    )

    model.summary()  # 因为有跳级结构,因此这个函数不能够进行很好的表示

    return model


# 读取图像和目标图像
images=glob.glob(r"images\*.jpg")
anno=glob.glob(r"annotations\trimaps\*.png")

#现在对读取进来的数据进行制作batch
np.random.seed(1)
index=np.random.permutation(len(images)) # 随机打乱7390个数
images = np.array(images)[index]
anno = np.array(anno)[index]

#创建dataset
dataset=tf.data.Dataset.from_tensor_slices((images,anno))

# 测试数据量和训练数据量,20%测试。
test_count=int(len(images) * 0.2)
train_count=len(images) - test_count

# 取出训练数据和测试数据
data_train=dataset.skip(test_count)  # 跳过前test的数据
data_test=dataset.take(test_count)   # 取前test的数据
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会使维度由四维将为3维
BATCH_SIZE = 8
data_train=data_train.shuffle(100).batch(BATCH_SIZE)
data_test=data_test.batch(BATCH_SIZE)

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

# 可视化
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir="E:\jupyter\log2", histogram_freq=1)
# 训练
model.fit(data_train,
         epochs=1,
         batch_size = 8,
         validation_data=data_test,
          callbacks = [tensorboard_callback])

# 保存模型
model.save('FCN_model.h5')

#加载保存的模型
new_model=tf.keras.models.load_model('FCN_model.h5')
#查看模型的架构:
new_model.summary()
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
当然可以!以下是一个基于Keras的DeepLabV3+语义分割三分类模型的示例代码: ```python from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Conv2D, BatchNormalization, Activation, MaxPooling2D, Conv2DTranspose, concatenate def conv_block(inputs, filters, kernel_size=3, strides=1): x = Conv2D(filters, kernel_size, strides=strides, padding='same')(inputs) x = BatchNormalization()(x) x = Activation('relu')(x) return x def encoder(inputs): # Encoder block 1 x = conv_block(inputs, 64) x = conv_block(x, 64) skip_1 = x x = MaxPooling2D(pool_size=(2, 2))(x) # Encoder block 2 x = conv_block(x, 128) x = conv_block(x, 128) skip_2 = x x = MaxPooling2D(pool_size=(2, 2))(x) # Encoder block 3 x = conv_block(x, 256) x = conv_block(x, 256) skip_3 = x x = MaxPooling2D(pool_size=(2, 2))(x) return x, skip_1, skip_2, skip_3 def decoder(inputs, skip_1, skip_2, skip_3): # Decoder block 1 x = Conv2DTranspose(128, (3, 3), strides=(2, 2), padding='same')(inputs) x = concatenate([x, skip_3]) x = conv_block(x, 256) x = conv_block(x, 256) # Decoder block 2 x = Conv2DTranspose(64, (3, 3), strides=(2, 2), padding='same')(x) x = concatenate([x, skip_2]) x = conv_block(x, 128) x = conv_block(x, 128) # Decoder block 3 x = Conv2DTranspose(32, (3, 3), strides=(2, 2), padding='same')(x) x = concatenate([x, skip_1]) x = conv_block(x, 64) x = conv_block(x, 64) return x def DeepLabV3Plus(input_shape, num_classes): inputs = Input(shape=input_shape) # Encoder encoder_output, skip_1, skip_2, skip_3 = encoder(inputs) # ASPP (Atrous Spatial Pyramid Pooling) x = conv_block(encoder_output, 256, kernel_size=1) x = conv_block(x, 256, kernel_size=3, strides=1, dilation_rate=6) x = conv_block(x, 256, kernel_size=3, strides=1, dilation_rate=12) x = conv_block(x, 256, kernel_size=3, strides=1, dilation_rate=18) x = Conv2D(256, 1)(x) x = BatchNormalization()(x) # Decoder x = decoder(x, skip_1, skip_2, skip_3) # Output outputs = Conv2D(num_classes, 1, activation='softmax')(x) model = Model(inputs=inputs, outputs=outputs) return model # 创建模型 input_shape = (256, 256, 3) # 输入图像的尺寸 num_classes = 3 # 分类的类别数量 model = DeepLabV3Plus(input_shape, num_classes) # 编译模型 model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 打印模型结构 model.summary() ``` 这段代码实现了一个简单的DeepLabV3+语义分割模型,具有三个分类类别。你可以根据自己的需求修改模型的输入尺寸、分类数量以及其他超参数。记得根据你的数据集调整模型的输入尺寸和输出类别数量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值