语义分割详解与实现

语义分割详解与实现

    • [0. 前言](#0. 前言)
    • [1. 图像分割](#1. 图像分割)
    • [2. 语义分割网络](#2. 语义分割网络)
    • [3. 使用 Keras 构建语义分割网络](#3. 使用 Keras 构建语义分割网络)
    • [4. 模型训练与测试](#4. 模型训练与测试)

0. 前言

目标检测的目的是对图像中的每个对象同时进行定位和识别,而在语义分割中,目的是根据每个像素的对象类别对它们进行分类。在目标检测中,我们使用边界框来展示结果;而在语义分割中,同一物体的所有像素均属于相同类别。从视觉上看,同一物体的所有像素将呈现相同颜色,例如属于汽水罐类别的所有像素都会显示为蓝色,非汽水罐物体的像素则呈现不同颜色。语义分割具有诸多实际应用场景,在医学影像领域,它可用于分离并测量正常细胞与异常细胞区域;在卫星影像分析中,可用于测算森林覆盖率或灾害期间的洪水蔓延范围。总体而言,语义分割能用于识别属于同类物体的像素,而无需区分单个物体实例。

1. 图像分割

分割算法将图像划分为若干像素集合或区域。划分的目的在于更准确地理解图像内容,这些像素集合可能对应特定应用场景中关注的图像主体。不同的划分方式构成了各类分割算法的本质区别,如下图所示。

语义分割 (Semantic Segmentation) 是为了便于图像分析而为图像中的每个像素分配标签的过程,属于某个对象的所有像素都被突出显示,比如用值 1 覆盖车辆对象像素(假设像素值在 0 - 1 之间),用值 0.5 覆盖人物对象像素,而其他像素则使用其他值显示。它的目标是对图像中的整体结构进行理解和解释,并且不需要区分不同实例之间的差异。

实例分割 (Instance Segmentation) 可以看作是目标检测和语义分割的结合,实例分割为属于同一对象类的不同对象实例分配不同的标签。相比目标检测标记对象的边界框,实例分割可以精确检测对象的边缘信息;相比语义分割,实例分割可以标注出图像上同一类对象的不同个体。例如,在一张道路图片中,实例分割能够识别并分割出不同的汽车,并给每个汽车分配不同的标识。

全景分割 (Panorama Segmentation) 可以看作是语义分割和实例分割的结合,需要同时对图像中所有物体和背景进行检测和分割。其中,对背景区域的分割属于语义分割,而对物体的分割属于实例分割。

2. 语义分割网络

语义分割网络是一种逐像素分类器,与简单分类器仅生成单个独热向量输出不同,语义分割网络需要同时运行多个并行分类器。每个分类器都会生成独立的独热向量预测结果,其数量等于输入图像的像素总数,即图像宽度与高度的乘积。每个独热向量的维度则等于待识别材质物体类别的数量。以关注四种类别为例:背景、水瓶、汽水罐和果汁罐。

每个像素都通过 4 维独热向量进行分类,并通过颜色阴影标示像素所属类别。由此可知,语义分割网络会预测 image_width × image_height4 维独热向量作为输出,每个像素对应一个 4 维独热向量:

在理解语义分割概念后,现在我们可以介绍神经网络逐像素分类器。本节构建的语义分割网络架构受全卷积网络 (Fully Convolutional Network, FCN) 启发,FCN 的核心思想在于利用多尺度特征图来生成最终预测。

下图展示了语义分割网络:输入为 RGB 图像,输出为维度相似的张量,但末维变为类别数。为便于可视化,我们通过为每个类别分配不同颜色将输出映射为 RGB 图像:

SSD 类似,采用骨干网络作为特征提取器,使用与 SSD 中相似的 ResNetv2 网络。ResNet 骨干网络经过两次最大池化得到第一组特征图,尺寸为输入图像的 1/4。随后通过连续的 Conv2D-BN-ReLU 层生成更多特征图,最终获得尺寸分别为输入图像 1/81/161/32 的特征图。

网络架构还融合了金字塔场景解析网络 (Pyramid Scene Parsing Network, PSPNet) 的改进:每个特征图都经过额外卷积层处理,且第一组特征图也会被充分利用。
FCNPSPNet 均对特征金字塔进行上采样,使其尺寸与第一组特征图保持一致。随后使用 Concatenate 层融合所有上采样特征。接着通过两次步长为 2 的转置卷积处理拼接后的特征,恢复原始图像尺寸。最后采用核大小为 1、卷积核数为 4 (即类别数)的转置卷积层和 Softmax 层,生成逐像素类别预测。

3. 使用 Keras 构建语义分割网络

使用 ResNet 模型,提取了四个级别的特征金字塔。conv_layer() 只是创建 Conv2D(strides = 2)-BN-ReLU 层的辅助函数。

python 复制代码
def features_pyramid(x,n_layers):
    outputs = [x]
    conv = keras.layers.AveragePooling2D(pool_size=2,name='pool1')(x)
    outputs.append(conv)
    prev_conv = conv
    n_filters = 512

    #additional feature map layers
    for i in range(n_layers - 1):
        postfix = '_layer' + str(i+2)
        conv = conv_layer(prev_conv,
                n_filters,
                kernel_size=3,
                strides=2,
                use_maxpool=False,
                postfix=postfix)
        outputs.append(conv)
        prev_conv = conv
    
    return outputs

以上代码仅完成了特征金字塔的一半构建。另一半则是在每组特征后进行的卷积操作,如以下所示,该部分同时包含了金字塔各层级的上采样过程,同时我们完成了整个分割模型的构建------从骨干网络到特征金字塔,从上采样特征金字塔的拼接,再到进一步的特征提取、上采样及预测。最终在输出层执行逐像素分类。

python 复制代码
def build_fcn(input_shape,
        backbone,
        n_classes=4):
    inputs = keras.layers.Input(shape=input_shape)
    features = backbone(inputs)

    main_feature = features[0]
    features = features[1:]
    out_features = [main_feature]
    feature_size = 8
    size = 2
    #other half of the features pyramid
    #including upsampling to restore the
    #feature maps to the dimensions
    #equal to 1/4 the image size
    for feature in features:
        postfix = 'fcn_' + str(feature_size)
        feature = conv_layer(feature,
                filters=256,
                use_maxpool=False,
                postfix=postfix)
        postfix = postfix + '_up2d'
        features = keras.layers.UpSampling2D(size=size,
                interpolation='bilinear',
                name=postfix)(feature)
        size = size * 2
        feature_size = feature_size * 2
        out_features.append(feature)
    
    #concatenate all upsampled features
    x = keras.layers.Concatenate()(out_features)
    #perform 2 additional feature extraction
    # and upsampling    
    x = tconv_layer(x,256,postfix='up_x2')
    x = tconv_layer(x,256,postfix='up_x4')
    #generate the pixel-wise classifier
    x = keras.layers.Conv2DTranspose(filters=n_classes,
            kernel_size=1,
            strides=1,
            padding='same',
            kernel_initializer='he_normal',
            name='pre_activation')(x)
    x = keras.layers.Softmax(name='segmentation')(x)

    model = keras.Model(inputs,x,name='fcn')

    return model

采用学习率为 1e-3Adam 优化器与分类交叉熵损失函数进行网络训练:

python 复制代码
    def build_model(self):
        # input shape is (480, 640, 3) by default
        self.input_shape = (self.args.height,
                            self.args.width,
                            self.args.channels)
        # build the backbone network (eg ResNet50)
        # the backbone is used for 1st set of features
        # of the features pyramid
        self.backbone = self.args.backbone(self.input_shape,
                                           n_layers=self.args.layers)
        # using the backbone, build fcn network
        # output layer is a pixel-wise classifier
        self.n_classes =  self.train_generator.n_classes
        self.fcn = build_fcn(self.input_shape,
                             self.backbone,
                             self.n_classes)
    def train(self):
        optimizer = Adam(lr=1e-3)
        loss = 'categorical_crossentropy'
        self.fcn.compile(optimizer=optimizer, loss=loss)
        log = "# of classes %d" % self.n_classes
                print_log(log, self.args.verbose)
        log = "Batch size: %d" % self.args.batch_size
        print_log(log, self.args.verbose)
        accuracy = AccuracyCallback(self)
        scheduler = LearningRateScheduler(lr_scheduler)
        callbacks = [accuracy, scheduler]
        # train the fcn network
        self.fcn.fit_generator(generator=self.train_generator,
                               use_multiprocessing=True,
                               callbacks=callbacks,
                               epochs=self.args.epochs,
                               workers=self.args.workers)

多线程数据生成器类 DataGenerator 中的 __data_generation(self,keys) 方法,生成一对图像张量及其对应的像素级标注标签或分割蒙版。

python 复制代码
def __data_generator(self,keys):
    #a batch of images
    x = []
    #and their corresponding segmentation masks
    y = []

    for i,key in enumerate(keys):
        #images are assumed to be stored
        #in self.args.data_path
        #key is the image filename
        image_path = os.path.join(self.args.data_path,key)
        image = skimage.img_as_float(imread(image_path))
        #append image to the list
        x.append(image)
        #and its corresponding label (sefmentation mask)
        labels = self.dictionary[key]
        y.append(labels)
    
    return np.array(x),np.array(y)

4. 模型训练与测试

执行以下命令训练模型:

shell 复制代码
python fcn.py --train

在每个 epoch,执行验证以确定性能最佳的参数。对于语义分割,可以使用两个度量。首先是 mIOU,另一个指标是平均像素准确率,分割网络具有的预测数量等于图像中的像素数量。对于每个测试输入图像,计算平均像素准确率。然后,计算所有测试图像的平均值。

可以通过执行以下命令来运行验证:

shell 复制代码
python3 fcn.py --evaluate --restore-weights = ResNet56v2-3layer-drinks-best-iou.h5
相关推荐
盼小辉丶4 小时前
Transformer实战(24)——通过数据增强提升Transformer模型性能
人工智能·深度学习·自然语言处理·transformer
拂过世俗的风4 小时前
Hopfield神经网络简介
人工智能·深度学习·神经网络
長安一片月4 小时前
深度学习的前世今生
人工智能·深度学习
骄傲的心别枯萎4 小时前
RV1126 NO.40:OPENCV图形计算面积、弧长API讲解
人工智能·opencv·计算机视觉·音视频·rv1126
Theodore_10228 小时前
深度学习(9)导数与计算图
人工智能·深度学习·机器学习·矩阵·线性回归
领航猿1号14 小时前
Pytorch 内存布局优化:Contiguous Memory
人工智能·pytorch·深度学习·机器学习
化作星辰15 小时前
使用房屋价格预测的场景,展示如何从多个影响因素计算权重和偏置的梯度
pytorch·深度学习
chao18984416 小时前
多光谱图像融合:IHS、PCA与小波变换的MATLAB实现
图像处理·计算机视觉·matlab
这张生成的图像能检测吗17 小时前
(论文速读)基于图像堆栈的低频超宽带SAR叶簇隐蔽目标变化检测
图像处理·人工智能·深度学习·机器学习·信号处理·雷达·变化检测