语义分割简介

翻译来自:https://gist.github.com/khanhnamle1994/e2ff59ddca93c0205ac4e566d40b5e88
语义分割方面的资源:https://github.com/mrgloom/awesome-semantic-segmentation
1. 什么是语义分割

语义分割是当今计算机视觉领域的关键问题之一。从宏观上看,语义分割是一项高层次的任务,为实现场景的完整理解铺平了道路。场景理解作为一个核心的计算机视觉问题,其重要性在于越来越多的应用程序通过从图像中推断知识来提供营养。其中一些应用包括自动驾驶汽车、人机交互、虚拟现实等,近年来随着深度学习的普及,许多语义分割问题正在采用深层次的结构来解决,最常见的是卷积神经网络,在精度上大大超过了其他方法。以及效率。

什么是语义分割?
语义分割是从粗推理到精推理的自然步骤:
原点可以定位在分类,分类包括对整个输入进行预测。
下一步是本地化/检测,它不仅提供类,还提供关于这些类的空间位置的附加信息。
最后,语义分割通过对每个像素进行密集的预测、推断标签来实现细粒度的推理,从而使每个像素都被标记为其封闭对象矿石区域的类别。

2.语义分割的基础

也有必要回顾一些对计算机视觉领域做出重大贡献的标准深层网络,因为它们通常被用作语义分割系统的基础:
Alexnet:Toronto首创的Deep CNN,以84.6%的测试准确率赢得了2012年Imagenet竞赛。它由5个卷积层、最大池层、作为非线性的ReLUs、3个完全卷积层和dropout组成。
VGG-16:这款牛津型号以92.7%的准确率赢得了2013年的Imagenet竞争。它使用第一层中具有小接收场的卷积层堆栈,而不是具有大接收场的少数层。
GoogLeNet:这GoogLeNet赢得了2014年Imagenet的竞争,准确率为93.3%。它由22层和一个新引入的称为初始模块的构建块组成。该模块由网络层网络、池操作、大卷积层和小卷积层组成。
Resnet:这款微软的模型以96.4%的准确率赢得了2016年的Imagenet竞争。这是众所周知的,因为它的深度(152层)和残余块的引进。剩余的块通过引入标识跳过连接来解决培训真正深层架构的问题,以便层可以将其输入复制到下一层。

3.语义分割的方法

现有的语义分割方法是什么?
一个通用的语义分割体系结构可以被广泛认为是一个编码器网络,然后是一个解码器网络:

    编码器通常是一个预先训练的分类网络,如vgg/resnet,然后是一个解码器网络。
    解码器的任务是将编码器学习到的识别特征(低分辨率)语义投影到像素空间(高分辨率)上,得到密集的分类。

与分类不同的是,深度网络的最终结果是唯一重要的,语义分割不仅需要在像素级别上进行区分,而且还需要一种机制将编码器不同阶段学习到的区分特征投影到像素空间上。不同的方法使用不同的机制作为解码机制的一部分。让我们探讨三种主要方法:

1-基于区域的语义分割
基于区域的方法通常遵循“使用识别的分割”管道,首先从图像中提取自由形式的区域并对其进行描述,然后进行基于区域的分类。在测试时,基于区域的预测转换为像素预测,通常通过根据包含该预测的最高评分区域标记像素。

R-CNN(具有CNN特征的区域)是基于区域的方法的代表性工作之一。根据目标检测结果进行语义分割。具体来说,R-CNN首先利用选择性搜索来提取大量的目标提案,然后计算每个提案的CNN特征。最后,使用类特定的线性支持向量机对每个区域进行分类。与传统的以图像分类为主要目的的CNN结构相比,R-CNN能够处理更复杂的任务,如目标检测和图像分割,甚至成为这两个领域的重要基础。此外,R-CNN可以建立在任何CNN基准结构之上,如Alexnet、VGG、Googlenet和Resnet。

                                    

对于图像分割任务,R-CNN提取了每个区域的两种特征:全区域特征和前景特征,发现将它们作为区域特征连接在一起可以获得更好的性能。R-CNN由于使用了高度歧视性的CNN功能,取得了显著的性能改进。但是,它也面临着分割任务的一些缺点:

    此功能与分段任务不兼容。
    该特征包含的空间信息不足,无法精确生成边界。
    生成基于分段的建议需要时间,并且会极大地影响最终性能。

由于这些瓶颈,最近的研究提出了解决这些问题,包括SDS, Hypercolumns, Mask R-CNN。

2-全卷积网络语义分割
原始的完全卷积网络(FCN)学习从像素到像素的映射,而不提取区域建议。FCN网络管道是经典CNN的延伸。其主要思想是使经典的CNN以任意大小的图像作为输入。CNN仅接受和生产特定尺寸输入的标签的限制来自完全连接的固定层。与之相反,FCN只有卷积层和池层,它们能够对任意大小的输入进行预测。

                                   

在这个特定的FCN中,一个问题是通过几个交替的卷积层和池层传播,输出特征映射的分辨率被降采样。因此,FCN的直接预测通常分辨率较低,导致对象边界相对模糊。为了解决这个问题,已经提出了各种更先进的基于FCN的方法,包括SegNet, DeepLab-CRF, 和 Dilated Convolutions。

3-弱监督语义分割
语义分割中的大多数相关方法都依赖于大量带有像素级分割遮罩的图像。然而,手工注释这些面具是相当费时,令人沮丧和商业成本。因此,最近提出了一些弱监督的方法,这些方法致力于通过使用带注释的边界框来实现语义分割。

                                

例如,Boxsup使用边界框注释作为监督来训练网络,并迭代地改进用于语义分割的估计掩码。简单地说,它把弱监督限制看作输入标签噪声问题,并探讨了递归训练作为一种去噪策略。像素级标记解释了多实例学习框架中的分割任务,并添加了一个额外的层来约束模型,以将更多的权重分配给重要的像素进行图像级分类。
4.用全卷积网络进行语义分割

在本节中,我们将逐步介绍最流行的语义分割体系结构-完全卷积网(FCN)的实现。我们将使用Python3中的TensorFlow库以及其他依赖项(如numpy和scipy)来实现它。
在本练习中,我们将使用fcn在图像中标记道路的像素。我们将使用Kitti道路数据集进行道路/车道检测。这是Udacity的自动驾驶汽车纳米学位计划中的一个简单练习,您可以了解有关此Github回购中设置的更多信息。

以下是FCN体系结构的主要特点:

    FCN将知识从VGG16传输到执行语义分割。
    VGG16的全连接层使用1X1卷积转换为全卷积层。这个过程产生一个低分辨率的类存在热图。
    使用转置卷积(用双线性内插滤波器初始化)对这些低分辨率语义特征图进行上采样。
    在每个阶段,通过在VGG16中添加来自较粗但分辨率较高的底层特征图的特征,进一步细化了上采样过程。
    跳过连接在每个卷积块之后引入,以使后续块能够从以前的集合特性中提取更抽象、类显著的特性。

FCN有3个版本(FCN-32、FCN-16、FCN-8)。我们将实施FCN-8,具体步骤如下:

Encoder:使用预先培训过的VGG16作为编码器。解码器从VGG16的第7层开始。
FCN Layer-8:最后一个完全连接的VGG16层被1x1卷积替换。
FCN Layer-9:fcn layer-8升序2次,与VGG16的layer 4匹配,使用带参数的转置卷积:(kernel=(4,4),stead=(2,2),padding='same')。之后,在VGG16的第4层和fcn的第9层之间添加了一个跳过连接。
FCN Layer-10:fcn layer-9被放大2倍,以便与VGG16第3层的尺寸匹配,使用带参数的转置卷积:(kernel=(4,4),stead=(2,2),padding=(相同))。之后,在VGG16的第3层和fcn第10层之间添加了一个跳过连接。
FCN Layer-11:fcn layer-10被放大4倍以匹配输入图像大小的尺寸,因此我们得到实际图像,深度等于类数,使用带参数的转置卷积:(kernel=(16,16),step=(8,8),padding='same')。

           

步骤1
我们首先将预先培训过的VGG-16模型加载到TensorFlow中。以TensorFlow session和vgg文件夹的路径(可在此处下载)为例,我们返回vgg模型中的张量元组,包括图像输入、keep-prob(控制辍学率)、第3层、第4层和第7层。

    def load_vgg(sess, vgg_path):
      
      # load the model and weights
      model = tf.saved_model.loader.load(sess, ['vgg16'], vgg_path)
     
      # Get Tensors to be returned from graph
      graph = tf.get_default_graph()
      image_input = graph.get_tensor_by_name('image_input:0')
      keep_prob = graph.get_tensor_by_name('keep_prob:0')
      layer3 = graph.get_tensor_by_name('layer3_out:0')
      layer4 = graph.get_tensor_by_name('layer4_out:0')
      layer7 = graph.get_tensor_by_name('layer7_out:0')
     
    return image_input, keep_prob, layer3, layer4, layer7

步骤2
现在,我们主要使用vgg模型中的张量为fcn创建层。给定vgg层输出的张量和要分类的类数,我们返回该输出最后一层的张量。特别地,我们将1X1卷积应用于编码器层,然后将解码器层添加到具有跳过连接和升序采样的网络中。

    def layers(vgg_layer3_out, vgg_layer4_out, vgg_layer7_out, num_classes):
       
        # Use a shorter variable name for simplicity
        layer3, layer4, layer7 = vgg_layer3_out, vgg_layer4_out, vgg_layer7_out
     
        # Apply 1x1 convolution in place of fully connected layer
        fcn8 = tf.layers.conv2d(layer7, filters=num_classes, kernel_size=1, name="fcn8")
     
        # Upsample fcn8 with size depth=(4096?) to match size of layer 4 so that we can add skip connection with 4th layer
        fcn9 = tf.layers.conv2d_transpose(fcn8, filters=layer4.get_shape().as_list()[-1],
        kernel_size=4, strides=(2, 2), padding='SAME', name="fcn9")
     
        # Add a skip connection between current final layer fcn8 and 4th layer
        fcn9_skip_connected = tf.add(fcn9, layer4, name="fcn9_plus_vgg_layer4")
     
        # Upsample again
        fcn10 = tf.layers.conv2d_transpose(fcn9_skip_connected, filters=layer3.get_shape().as_list()[-1],
        kernel_size=4, strides=(2, 2), padding='SAME', name="fcn10_conv2d")
     
        # Add skip connection
        fcn10_skip_connected = tf.add(fcn10, layer3, name="fcn10_plus_vgg_layer3")
     
        # Upsample again
        fcn11 = tf.layers.conv2d_transpose(fcn10_skip_connected, filters=num_classes,
        kernel_size=16, strides=(8, 8), padding='SAME', name="fcn11")
     
    return fcn11

步骤3
下一步是优化我们的神经网络,也就是建立TensorFlow损失函数和优化器操作。这里我们使用交叉熵作为损失函数,使用Adam作为优化算法。

    def optimize(nn_last_layer, correct_label, learning_rate, num_classes):
      
      # Reshape 4D tensors to 2D, each row represents a pixel, each column a class
      logits = tf.reshape(nn_last_layer, (-1, num_classes), name="fcn_logits")
      correct_label_reshaped = tf.reshape(correct_label, (-1, num_classes))
     
      # Calculate distance from actual labels using cross entropy
      cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=correct_label_reshaped[:])
      # Take mean for total loss
      loss_op = tf.reduce_mean(cross_entropy, name="fcn_loss")
     
      # The model implements this operation to find the weights/parameters that would yield correct pixel labels
      train_op = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss_op, name="fcn_train_op")
     
    return logits, train_op, loss_op

步骤4
这里我们定义了train-nn函数,它接受重要的参数,包括epoch数、批大小、丢失函数、优化器操作和输入图像的占位符、标签图像、学习速率。对于培训过程,我们还将保持概率设置为0.5,学习率设置为0.001。为了跟踪进度,我们还打印出培训期间的损失。

    def train_nn(sess, epochs, batch_size, get_batches_fn, train_op,
                 cross_entropy_loss, input_image,
                 correct_label, keep_prob, learning_rate):
     
      keep_prob_value = 0.5
      learning_rate_value = 0.001
      for epoch in range(epochs):
          # Create function to get batches
          total_loss = 0
          for X_batch, gt_batch in get_batches_fn(batch_size):
     
              loss, _ = sess.run([cross_entropy_loss, train_op],
              feed_dict={input_image: X_batch, correct_label: gt_batch,
              keep_prob: keep_prob_value, learning_rate:learning_rate_value})
     
              total_loss += loss;
     
          print("EPOCH {} ...".format(epoch + 1))
          print("Loss = {:.3f}".format(total_loss))
    print()

步骤5
最后,是时候训练我们的网络了!在这个run函数中,我们首先使用load_vgg、layers和optimize函数构建网络。然后,我们使用train_nn函数对网络进行训练,并保存推理数据以备记录。

    def run():
      
      # Download pretrained vgg model
      helper.maybe_download_pretrained_vgg(data_dir)
     
      # A function to get batches
      get_batches_fn = helper.gen_batch_function(training_dir, image_shape)
      
      with tf.Session() as session:
            
        # Returns the three layers, keep probability and input layer from the vgg architecture
        image_input, keep_prob, layer3, layer4, layer7 = load_vgg(session, vgg_path)
     
        # The resulting network architecture from adding a decoder on top of the given vgg model
        model_output = layers(layer3, layer4, layer7, num_classes)
     
        # Returns the output logits, training operation and cost operation to be used
        # - logits: each row represents a pixel, each column a class
        # - train_op: function used to get the right parameters to the model to correctly label the pixels
        # - cross_entropy_loss: function outputting the cost which we are minimizing, lower cost should yield higher accuracy
        logits, train_op, cross_entropy_loss = optimize(model_output, correct_label, learning_rate, num_classes)
        
        # Initialize all variables
        session.run(tf.global_variables_initializer())
        session.run(tf.local_variables_initializer())
     
        print("Model build successful, starting training")
     
        # Train the neural network
        train_nn(session, EPOCHS, BATCH_SIZE, get_batches_fn,
                 train_op, cross_entropy_loss, image_input,
                 correct_label, keep_prob, learning_rate)
     
        # Run the model with the test images and save each painted output image (roads painted green)
        helper.save_inference_samples(runs_dir, data_dir, session, image_shape, logits, keep_prob, image_input)
        
    print("All done!")

关于我们的参数,我们选择epochs=40,batch_size=16,num_classes=2,image_shape=(160,576)。在两次试验通过后,dropout=0.5,dropout=0.75,我们发现第二次试验产生更好的结果,平均损失更好。

要查看完整代码,请查看此链接:https://gist.github.com/khanhnamle1994/e2ff59ddca93c0205ac4e566d40b5e88
 

你可能感兴趣的:(语义分割)