[1] 语义分割--全卷积网络FCN详解【解释详细】
[4] keras.applications【VGG16】
[5] keras—VGG16
一、总体说明

FCN = CNN(VGG16) + UpSampling
-
FCN-32s: conv7 直接上采样 32 倍到原来的尺寸
-
FCN-16s:
-
conv7 上采样 2 倍 (/16)
-
pool4 (/16)
-
两者 add 之后,上采样 16 倍到原来的尺寸
-
-
FCN-8s:
-
conv7 上采样 2 倍 (/16)
-
pool4 (/16)
-
两者 add 之后,上采样 2 倍 (/8)
-
pool3 (/8)
- 上面两者 add 之后,上采样 8 倍到原来的尺寸
-
二、结构图示
图示如下所示:
第1阶段
以经典的分类网络为初始化。最后两级是全连接(红色),参数弃去不用。
第2阶段
从特征小图(16*16*4096)预测分割小图(16*16*21),之后直接升采样为大图。
反卷积(橙色)的步长为32,这个网络称为FCN-32s。
这一阶段使用单GPU训练约需3天。
升采样分为两次完成(橙色×2)。
在第二次升采样前,把第4个pooling层(绿色)的预测结果(蓝色)融合进来。使用跳级结构提升精确性。
第二次反卷积步长为16,这个网络称为FCN-16s。
这一阶段使用单GPU训练约需1天。
升采样分为三次完成(橙色×3)。
进一步融合了第3个pooling层的预测结果。
第三次反卷积步长为8,记为FCN-8s。
三、代码实现
FCN-32s
from keras.applications import vgg16
from keras.models import Model, Sequential
from keras.layers import Conv2D, Conv2DTranspose, Input, Cropping2D, add, Dropout, Reshape, Activation
from keras.utils.vis_utils import plot_model
def FCN32(nClasses, input_height, input_width):
assert input_height % 32 == 0
assert input_width % 32 == 0
img_input = Input(shape=(input_height, input_width, 3))
# VGG16 structrue
# 2conv + 1pool -> block1_pool
# 2conv + 1pool -> block2_pool
# 3conv + 1pool -> block3_pool
# 3conv + 1pool -> block4_pool
# 3conv + 1pool -> block5_pool
model = vgg16.VGG16(include_top=False, weights='imagenet', input_tensor=img_input)
assert isinstance(model, Model)
o = Conv2D(4096, 7, padding="same", activation="relu", name="fc6")(model.output)
o = Dropout(rate=0.5)(o)
o = Conv2D(4096, 1, padding="same", activation="relu", name="fc7")(o)
o = Dropout(rate=0.5)(o)
o = Conv2D(nClasses, 1, padding="same", activation="relu", kernel_initializer="he_normal", name="score_fr")(o)
o = Conv2DTranspose(nClasses, 32, strides=(32, 32), padding="valid", activation=None, name="score2")(o)
#o = Reshape((-1, nClasses))(o)
o = Activation("softmax")(o)
fcn8 = Model(inputs=img_input, outputs=o)
# mymodel.summary()
return fcn8
if __name__ == '__main__':
m = FCN32(2, 512, 512)
m.summary()
plot_model(m, show_shapes=True, to_file='model_fcn32.png')
print(len(m.layers))
FCN-16s
from keras.applications import vgg16
from keras.models import Model, Sequential
from keras.layers import Conv2D, Conv2DTranspose, Input, Cropping2D, add, Dropout, Reshape, Activation
def FCN16_helper(nClasses, input_height, input_width):
assert input_height % 32 == 0
assert input_width % 32 == 0
img_input = Input(shape=(input_height, input_width, 3))
model = vgg16.VGG16(include_top=False,weights='imagenet', input_tensor=img_input)
assert isinstance(model, Model)
o = Conv2D(4096,7,padding="same",activation="relu",name="fc6")(model.output)
o = Dropout(rate=0.5)(o)
o = Conv2D(4096,1,padding="same",activation="relu",name="fc7")(o)
o = Dropout(rate=0.5)(o)
# /32
o = Conv2D(nClasses, 1, padding="same", activation="relu", kernel_initializer="he_normal",name="score_fr")(o)
# 用于与 block4_pool合并
# /32 * 2 = /16
o = Conv2DTranspose(nClasses, 2, strides=(2, 2), padding="valid", activation=None, name="score2")(o)
fcn16 = Model(inputs=img_input, outputs=o)
return fcn16
def FCN16(nClasses, input_height, input_width):
fcn16 = FCN16_helper(nClasses, input_height, input_width)
# Conv to be applied on Pool4
# 这些 layer 的名字也可以通过后面的 summary 显示查看
# /16
skip_con1 = Conv2D(nClasses, 1, padding="same", activation=None, kernel_initializer="he_normal",
name="score_pool4")(fcn16.get_layer("block4_pool").output)
Summed = add(inputs=[skip_con1, fcn16.output])
# /16 * 16 = /1
Up = Conv2DTranspose(nClasses, 16, strides=(16, 16), padding="valid", activation=None,name="upsample")(Summed)
#Up = Reshape((-1, nClasses))(Up)
Up = Activation("softmax")(Up)
mymodel = Model(inputs=fcn16.input, outputs=Up)
return mymodel
if __name__ == '__main__':
m16 = FCN16(2, 512, 512)
#plot_model(m, show_shapes=True, to_file='model_fcn8.png')
m16.summary()
print(len(m.layers))
FCN-8s
from keras.applications import vgg16
from keras.models import Model, Sequential
from keras.layers import Conv2D, Conv2DTranspose, Input, Cropping2D, add, Dropout, Reshape, Activation
def FCN8_helper(nClasses, input_height, input_width):
assert input_height % 32 == 0
assert input_width % 32 == 0
img_input = Input(shape=(input_height, input_width, 3))
model = vgg16.VGG16(include_top=False,weights='imagenet', input_tensor=img_input)
assert isinstance(model, Model)
o = Conv2D(4096,7,padding="same",activation="relu",name="fc6")(model.output)
o = Dropout(rate=0.5)(o)
o = Conv2D(4096,1,padding="same",activation="relu",name="fc7")(o)
o = Dropout(rate=0.5)(o)
# /32
o = Conv2D(nClasses, 1, padding="same", activation="relu", kernel_initializer="he_normal",name="score_fr")(o)
# 用于与 block4_pool合并
# /32 * 2 = /16
o = Conv2DTranspose(nClasses, 2, strides=(2, 2), padding="valid", activation=None, name="score2")(o)
fcn8 = Model(inputs=img_input, outputs=o)
return fcn8
def FCN8(nClasses, input_height, input_width):
fcn8 = FCN8_helper(nClasses, input_height, input_width)
# Conv to be applied on Pool4
# 这些 layer 的名字也可以通过后面的 summary 显示查看
# /16
skip_con1 = Conv2D(nClasses, 1, padding="same", activation=None, kernel_initializer="he_normal",
name="score_pool4")(fcn8.get_layer("block4_pool").output)
Summed = add(inputs=[skip_con1, fcn8.output])
# /16 * 2 = /8
x = Conv2DTranspose(nClasses, 2, strides=(2, 2), padding="valid", activation=None,name="score4")(Summed)
###
# /8
skip_con2 = Conv2D(nClasses, 1, padding="same", activation=None, kernel_initializer="he_normal",
name="score_pool3")(fcn8.get_layer("block3_pool").output)
Summed2 = add(inputs=[skip_con2, x])
#####
# /8 * 8 = /1
Up = Conv2DTranspose(nClasses, 8, strides=(8, 8),padding="valid", activation=None, name="upsample")(Summed2)
#Up = Reshape((-1, nClasses))(Up)
Up = Activation("softmax")(Up)
mymodel = Model(inputs=fcn8.input, outputs=Up)
return mymodel
if __name__ == '__main__':
m = FCN8(2, 512, 512)
#plot_model(m, show_shapes=True, to_file='model_fcn8.png')
m.summary()
print(len(m.layers))
四、模型网络图
FCN-32s

FCN-16s

FCN-8s
