U-net用于人像分割的尝试

前言

最近在学习有关语义分割的知识,关于语义分割可能有的同学不清楚是什么含义,这里简单解释一下。
语义分割属于计算机视觉(Computer Vision,CV)中的一类任务,下面这张图展示了CV中的几大任务:
CV几大任务按照难以程度来看,由易到难依次为图像分类、目标检测、语义分割、实例分割,其中实例分割是语义分割的更进一步,具有挑战性,因为它需要正确检测图像中的目标,还要分出每个实例,而语义分割只要分割出像素类别即可,不需要知道图像中有多少个实例。
这么说并不意味着语义分割就很简单,实际上语义分割也具有挑战性。

常见语义分割模型

FCN系列:FCN-32、FCN-16、FCN-8。其中的32,16,8表示步长,可以理解为特征提取后特征图相对于原图的缩放比例,步长越大,提取的特征就越抽象,丢失的细节也就越多,在上采样时精度就越低。
U-net:U-net是2015年提出的编码器-解码器结构的语义分割模型,主要应用于医学图像,也可以用于一般图像,但是效果就比较一般,这个后面会详细介绍。
Deeplab系列:Deeplab v1、Deeplab v2、Deeplab v3、Deeplab v3+。这几种模型是谷歌开发并依次完善的,其中Deeplab v3+代表了当年语义分割的最高水平。

编码器/解码器(encoder/decoder)结构

下图为U-NET的结构图
在这里插入图片描述
U-net之所以被称为U-net,从这张图就可以一眼看出来,模型结构就类似于U形,包含了两个部分:特征提取部分与上采样部分,这种结构也被称为编码器-解码器结构。
与FCN系列不同的是,U-net采用了完全不同的特征融合方式:拼接。U-net采用将特征在channel维度拼接在一起,形成更厚的特征。而FCN融合时使用的对应点相加,并不形成更厚的特征。

U-net模型搭建

使用gkeras可以很容易地搭建U-net。如下:

from keras.models import *
from keras.layers import *
from keras.optimizers import *

IMG_SIZE = 512

def unet(pretrained_weights=None, input_size=(IMG_SIZE, IMG_SIZE, 3),num_class=2):
    inputs = Input(input_size)
    conv1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(inputs)
    conv1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv1)
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
    conv2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool1)
    conv2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv2)
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
    conv3 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool2)
    conv3 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv3)
    pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
    conv4 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool3)
    conv4 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv4)
    drop4 = Dropout(0.5)(conv4)
    pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)

    conv5 = Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool4)
    conv5 = Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv5)
    drop5 = Dropout(0.5)(conv5)

    up6 = Conv2D(512, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
        UpSampling2D(size=(2, 2))(drop5))

    merge6 = concatenate([drop4, up6], axis=3)
    conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge6)
    conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv6)

    up7 = Conv2D(256, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
        UpSampling2D(size=(2, 2))(conv6))
    merge7 = concatenate([conv3, up7], axis=3)
    conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge7)
    conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv7)

    up8 = Conv2D(128, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
        UpSampling2D(size=(2, 2))(conv7))
    merge8 = concatenate([conv2, up8], axis=3)
    conv8 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge8)
    conv8 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv8)

    up9 = Conv2D(64, 2, activation='relu', padding='same', kernel_initializer='he_normal')(
        UpSampling2D(size=(2, 2))(conv8))
    merge9 = concatenate([conv1, up9], axis=3)
    conv9 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge9)
    conv9 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9)
    conv9 = Conv2D(num_class, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9)
    if num_class == 2:
        conv10 = Conv2D(3, 1, activation='sigmoid')(conv9)
        loss_function = 'binary_crossentropy'
    else:
        conv10 = Conv2D(num_class, 1, activation='softmax')(conv9)
        loss_function = 'categorical_crossentropy'
    model = Model(input=inputs, output=conv10)

    model.compile(optimizer=Adam(lr=1e-4), loss=loss_function, metrics=["accuracy"])
    model.summary()

    if (pretrained_weights):
        model.load_weights(pretrained_weights)
    return model

U-net人像分割

我尝试使用U-net做人像分割,人像数据集地址,其中包含了训练与测试集。
完整的代码在我的github上。
以下是原图与掩膜图的演示(貌似比较肥猪流…):
在这里插入图片描述

训练过程

训练过程就不详细说了,其中有很多超参数,大家可以自己尝试一下。

测试结果

测试结果,有效果,但不太理想,放几张图感受一下
在这里插入图片描述在这里插入图片描述在这里插入图片描述

后记

U-net毕竟是针对医学图像开发的,医学图像的同一场景变化不大,所以只需要较少的训练数据就可以达到较好的效果,且U-net特征提取不宜太深,否则会丢失很多细节,不便于上采样,所以U-net用于自然图像分割,效果一般,这是我的一些个人理解。
下面打算将Deeplab v3+这个模型学习一下,然后用于对人像分割,据说效果很好…我这就来试试!

发布了6 篇原创文章 · 获赞 1 · 访问量 1841

猜你喜欢

转载自blog.csdn.net/m0_37798080/article/details/103088188