[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