天天看點

語義分割--DeepLab系列V3+(待完善)Deeplab v3+

寫在前面

本文主要讨論 deeplab系列中的deeplabv3+, 後續将貼上模型在pytorch架構下的結果。

關于deeplabv2的相關知識參考我的另一篇部落格:

https://blog.csdn.net/l_z_z_z/article/details/119600501

Deeplab v3+

deeplabv3+ 主要在模型的架構上作文章,引入了可任意控制編碼器提取特征的分辨率,通過空洞卷積平衡精度和耗時。

其在Encoder部分引入了大量的空洞卷積,所謂空洞,就是特征點提取的時候會跨像素,在不損失資訊的情況下,加大了感受野,讓每一個卷積輸出都包含較大範圍的資訊。(空洞卷積示意圖見下方圖2)

語義分割--DeepLab系列V3+(待完善)Deeplab v3+
語義分割--DeepLab系列V3+(待完善)Deeplab v3+

實作思路

一、預測部分

1、主幹網絡介紹

在論文中,Deeplabv3+采用的是Xception系列作為主幹特征提取網絡。對應圖檔中的DCNN,通過這個DCNN,輸入進來的圖檔,會變成2個有效特征層,這兩個有效特征層,一個進行并行的空洞卷積,一個傳入DECODER。

但是,本部落格采用的是mobilenet v2網絡(由于算力的限制)。

MobileNet模型是google針對手機等嵌入式裝置提出的一種輕量級的深層神經網絡。而MobileNetV2是其更新版,其中一個重要特點是其使用了 Inverted resblock,整個mobilenet v2都有 Inverted resblock組成。

關于Inverted resblock(如圖)介紹如下:

語義分割--DeepLab系列V3+(待完善)Deeplab v3+

其分成兩個部分:左邊是主幹部分,首先使用1x1卷積進行升維,然後利用3x3深度可分離卷積進行特征提取,然後再利用1x1卷積降維; 右邊是殘差部分,輸入和輸出直接相接。

再經過MobileNetV2的特征提取後,可以獲得兩個有效特征層。一個有效特征層是輸入圖檔的 高和寬 壓縮兩次的結果; 一個有效特征層是 輸入圖檔 高和寬壓縮四次的結果。

(下面貼出在Keras架構下的代碼)

from keras import layers
from keras.activations import relu
from keras.layers import (Activation, Add, BatchNormalization, Concatenate,
                          Conv2D, DepthwiseConv2D, Dropout,
                          GlobalAveragePooling2D, Input, Lambda, ZeroPadding2D)
from keras.models import Model


def _make_divisible(v, divisor, min_value=None):
    if min_value is None:
        min_value = divisor
    new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
    if new_v < 0.9 * v:
        new_v += divisor
    return new_v

def relu6(x):
    return relu(x, max_value=6)

def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id, skip_connection, rate=1):
    in_channels = inputs.shape[-1].value  # inputs._keras_shape[-1]
    pointwise_conv_filters = int(filters * alpha)
    pointwise_filters = _make_divisible(pointwise_conv_filters, 8)
    x = inputs
    prefix = 'expanded_conv_{}_'.format(block_id)
    if block_id:
        # Expand

        x = Conv2D(expansion * in_channels, kernel_size=1, padding='same',
                   use_bias=False, activation=None,
                   name=prefix + 'expand')(x)
        x = BatchNormalization(epsilon=1e-3, momentum=0.999,
                               name=prefix + 'expand_BN')(x)
        x = Activation(relu6, name=prefix + 'expand_relu')(x)
    else:
        prefix = 'expanded_conv_'
    # Depthwise
    x = DepthwiseConv2D(kernel_size=3, strides=stride, activation=None,
                        use_bias=False, padding='same', dilation_rate=(rate, rate),
                        name=prefix + 'depthwise')(x)
    x = BatchNormalization(epsilon=1e-3, momentum=0.999,
                           name=prefix + 'depthwise_BN')(x)

    x = Activation(relu6, name=prefix + 'depthwise_relu')(x)

    # Project
    x = Conv2D(pointwise_filters,
               kernel_size=1, padding='same', use_bias=False, activation=None,
               name=prefix + 'project')(x)
    x = BatchNormalization(epsilon=1e-3, momentum=0.999,
                           name=prefix + 'project_BN')(x)

    if skip_connection:
        return Add(name=prefix + 'add')([inputs, x])

    # if in_channels == pointwise_filters and stride == 1:
    #    return Add(name='res_connect_' + str(block_id))([inputs, x])

    return x

def mobilenetV2(inputs, alpha=1, downsample_factor=8):
    if downsample_factor == 8:
        block4_dilation = 2
        block5_dilation = 4
        block4_stride = 1
        atrous_rates = (12, 24, 36)
    elif downsample_factor == 16:
        block4_dilation = 1
        block5_dilation = 2
        block4_stride = 2
        atrous_rates = (6, 12, 18)
    else:
        raise ValueError('Unsupported factor - `{}`, Use 8 or 16.'.format(downsample_factor))
    
    first_block_filters = _make_divisible(32 * alpha, 8)
    # 512,512,3 -> 256,256,32
    x = Conv2D(first_block_filters,
                kernel_size=3,
                strides=(2, 2), padding='same',
                use_bias=False, name='Conv')(inputs)
    x = BatchNormalization(
        epsilon=1e-3, momentum=0.999, name='Conv_BN')(x)
    x = Activation(relu6, name='Conv_Relu6')(x)

    
    x = _inverted_res_block(x, filters=16, alpha=alpha, stride=1,
                            expansion=1, block_id=0, skip_connection=False)

    #---------------------------------------------------------------#
    # 256,256,16 -> 128,128,24
    x = _inverted_res_block(x, filters=24, alpha=alpha, stride=2,
                            expansion=6, block_id=1, skip_connection=False)
    x = _inverted_res_block(x, filters=24, alpha=alpha, stride=1,
                            expansion=6, block_id=2, skip_connection=True)
    skip1 = x
    #---------------------------------------------------------------#
    # 128,128,24 -> 64,64.32
    x = _inverted_res_block(x, filters=32, alpha=alpha, stride=2,
                            expansion=6, block_id=3, skip_connection=False)
    x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1,
                            expansion=6, block_id=4, skip_connection=True)
    x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1,
                            expansion=6, block_id=5, skip_connection=True)
    #---------------------------------------------------------------#
    # 64,64,32 -> 32,32.64
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=block4_stride,
                            expansion=6, block_id=6, skip_connection=False)
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, rate=block4_dilation,
                            expansion=6, block_id=7, skip_connection=True)
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, rate=block4_dilation,
                            expansion=6, block_id=8, skip_connection=True)
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1, rate=block4_dilation,
                            expansion=6, block_id=9, skip_connection=True)

    # 32,32.64 -> 32,32.96
    x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, rate=block4_dilation,
                            expansion=6, block_id=10, skip_connection=False)
    x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, rate=block4_dilation,
                            expansion=6, block_id=11, skip_connection=True)
    x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1, rate=block4_dilation,
                            expansion=6, block_id=12, skip_connection=True)

    #---------------------------------------------------------------#
    # 32,32.96 -> 32,32,160 -> 32,32,320
    x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, rate=block4_dilation,  # 1!
                            expansion=6, block_id=13, skip_connection=False)
    x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, rate=block5_dilation,
                            expansion=6, block_id=14, skip_connection=True)
    x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1, rate=block5_dilation,
                            expansion=6, block_id=15, skip_connection=True)

    x = _inverted_res_block(x, filters=320, alpha=alpha, stride=1, rate=block5_dilation,
                            expansion=6, block_id=16, skip_connection=False)
    return x,atrous_rates,skip1
           

2、加強特征提取結構

在Deeplabv3+,加強特征提取網絡可以分成兩個部分: 在Encoder中,會對壓縮四次的初步有效特征層 利用 并行的 Atrous Convolution, 分别用不同的rate的 Atrous Convolution進行特征提取,再提取合并,在進行1x1卷積 壓縮特征。在Decoder中,會對壓縮兩次的初步特征有效層利用 1x1卷積 調整通道數, 再和空洞卷積後 的 有效特征層上采樣(Upsample by 4)的結果進行堆疊,在完成堆疊後,進行兩次深度可分離卷積塊。

此時,就獲得最終的有效特征層,是整張圖檔的特征濃縮。

3、利用特征獲得預測結果

在經過1、2步之後,已經得到輸入進來的圖檔的特征,此時需要利用特征獲得最終的預測結果。

這個具體的過程分成2步:

  1. 利用一個1x1卷積進行通道調整,調整成Num_Classes.
  2. 利用resize進行上采樣使得最終的輸出層,寬、高與輸入圖檔一緻。

二、訓練部分

運作過程

本部落格使用的是voc2012資料集,shell指令如下所示:

python train.py --dataset pascal --backbone mobilenet --lr 0.007 --workers 1 --epochs 5 --batch-size 8 --gpu-ids 0 --checkname deeplab-mobilenet
           

運作結果:  

語義分割--DeepLab系列V3+(待完善)Deeplab v3+
語義分割--DeepLab系列V3+(待完善)Deeplab v3+
語義分割--DeepLab系列V3+(待完善)Deeplab v3+
語義分割--DeepLab系列V3+(待完善)Deeplab v3+

繼續閱讀