天天看點

基于keras的全卷積網絡FCN—語義分割基于貓狗資料的代碼實作:訓練全部代碼:

語義分割的裡程碑式模型FCN于2015年出現,一舉将語義分割的準确率提高近20%個點,MIOU也有極大改善,FCN的表現遠超傳統模型。FCN叫做全卷積網絡,顧名思義網絡的各個層都是卷積層,即不再使用全連接配接層。這種方式使FCN很好地儲存了特征的空間資訊。

在傳統的卷積網絡中,一層一層的卷積核池化使特征次元不斷降低,而語義分割最後是要得到和原圖同尺寸的分割圖。FCN的做法是使用上采樣提高分辨率。而對于前面卷積層不斷丢失的資訊,FCN的做法是使用跳接結構,以此來保留特征。

關于FCN的具體介紹,請移步這裡。本文主要是FCN的代碼實作

部落客同樣實作了keras搭建unet網絡,與本文使用的是相同的方法,具體請移步這裡

基于貓狗資料的代碼實作:

資料準備:

本文用的是貓狗資料集,images裡面存放的是對應的圖檔(3通道),annotation存放的是語義分割的标簽(1通道)。

基于keras的全卷積網絡FCN—語義分割基于貓狗資料的代碼實作:訓練全部代碼:
基于keras的全卷積網絡FCN—語義分割基于貓狗資料的代碼實作:訓練全部代碼:

資料的讀取、轉換和打亂操作:

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模型

基于keras的全卷積網絡FCN—語義分割基于貓狗資料的代碼實作:訓練全部代碼:

整理後的FCN:

基于keras的全卷積網絡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()
           

訓練結果

基于keras的全卷積網絡FCN—語義分割基于貓狗資料的代碼實作:訓練全部代碼:

tensorboard:

基于keras的全卷積網絡FCN—語義分割基于貓狗資料的代碼實作:訓練全部代碼:

不好意思,隻訓練了一個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()