語義分割的裡程碑式模型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()