模型结构
1.VGG16的全连接层(FC层)转为卷积层(步幅32,步幅=输入尺寸/输出特征尺寸)
2.最后的两个池化层去掉了下采样(目标步幅8)
3.后续卷积层的卷积核改为了空洞卷积(扩大感受野,缩小步幅)
4.在ImageNet上预训练的VGG16权重上做fine-tune(迁移学习)
DCNN存在的问题 DeepLab的解决思路
1.多次池化、下采样使输出信号分辨率变小:使用空洞卷积
2.池化对于输入变换具有内在空间不变性:使用CRF
对于输出信号分辨率变小这一问题,FCN采用的是上采样(反卷积)的方法进行恢复,而DeepLab采用空洞卷积的方法。
DCNN分数图可以可靠地预测图像中对象的存在和粗略位置,但不太适合用于指向其精确轮廓。 卷积网络在分类精度和定位精度之间有自然的权衡:具有多个最大池化层的卷积神经网络在分类任务中已被证明是最成功的应用,然而,他们增加的不变性和大的感受野使得在其最后的输出层推断位置上有很大的难度。
空洞卷积的使用,借鉴于有效计算非抽样离散小波变换的”孔洞算法 ”,在VGG16中使用不同采样率的空洞卷积,可以让模型再密集的计算时,明确控制网络的感受野。保证DCNN的预测图可靠的预测图像中物体的位置。
通过使用成对的完全连接的条件随机场(CRF)可以提高模型捕获细节的能力,通过结合多路分类器与由像素和边缘或超像素局部交互捕获的低级信息来计算得出的分数。
DeepLab是结合了DCNNs的识别能力和全连接的CRF的细粒度定位精度,寻求一个结合的方法,结果证明能够产生准确的语义分割结果。
条件随机场
对于每个像素位置ii具有隐变量xi(这里隐变量就是像素的真实类别标签,如果预测结果有21类,则(i∈1,2,…,21),还有对应的观测值yi(即像素点对应的颜色值)。以像素为节点,像素与像素间的关系作为边,构成了一个条件随机场(CRF)。通过观测变量yi来推测像素位置i对应的类别标签xi。条件随机场示意图如下:
完全连接的CRF模型采用能量函数
由一元势函数和二元势函数两部分组成。其中 x 是像素的标签分配。
一元势函数是定义在观测序列位置ii的状态特征函数,用于刻画观测序列对标记变量的影响,其中P(xi) 是由DCNN计算的像素 i 处的标签分配概率。
二元势函数定义在不同观测位置上的转移特征函数,用于刻画变量之间的相关关系以及观测序列对其影响。
简单来说,二元势函数是描述像素和像素之间的关系,如果比较相似,那可能是一类,否则就裂开,这可以细化边缘。一般的二元势函数只取像素点与周围像素之间的边,这里使用的是全连接,即像素点与其他所有像素之间的关系。
CRF是后处理,不参与训练,测试时对特征提取后得到的feature map进行双线性插值,恢复到原图尺寸,然后再进行CRF处理。
在v1的基础上,做了以下改进:
1.用多尺度获得更好的分割效果(使用ASPP)
2.基础层由VGG16转为ResNet
3.使用不同的学习策略(poly)
ASPP思想
v2用Resnet 101作为backbone,有一定提升,v1和v2都用了CRF。关于CRF的不再赘述了,其实后面的版本都没有用这个了。训练用的poly策略。
相对v1、v2的改进
1.提出了更通用的框架,适用于任何网络
2.复制了ResNet最后的block,并级联起来
3.在ASPP中使用BN层
4.没有使用CRF
论文中Fig2画了几种常见的捕获multi-scale context的方法。
(a)图像金字塔。输入图像进行尺度变换得到不同分辨率input,然后将所有尺度的图像放入CNN中得到不同尺度的分割结果,最后将不同分辨率的分割结果融合得到原始分辨率的分割结果,类似的方法为DeepMedic;
(b)编码-解码。FCN和UNet等结构;
©本文提出的串联结构。
(d)本文提出的Deeplab v3结构。最后两个结构右边其实还需要8×/16×的upsample,在deeplab v3+中有所体现。当然论文的Sec 4.1也有提到,下采样GT容易在反向传播中丢失细节,因此上采样feature map效果更好。
串联:
纵式
在deeplab v3中说到了需要8×/16×的upsample 最终的feature map,很明显这是一个很粗糙的做法。
v3+的创新点一是设计基于v3的decode module,二是用modify xception作为backbone。
论文中同样给出了一幅对比图,(a)是v3的纵式结构,(b)是常见的编码—解码结构,(c)是本文提出的基于deeplab v3的encode-decode结构。
主要参考:https://blog.csdn.net/lijiancheng0614/article/details/80490309
使用 TensorFlow DeepLab 进行语义分割
文件结构
这里以 PASCAL VOC 2012 为例,参考官方推荐的文件结构:
deeplab/datasets/pascal_voc_seg
├── exp
│ └── train_on_train_set
│ ├── eval
│ │ └── events.out.tfevents....
│ ├── export
│ │ └── frozen_inference_graph.pb
│ ├── train
│ │ ├── checkpoint
│ │ ├── events.out.tfevents....
│ │ ├── graph.pbtxt
│ │ ├── model.ckpt-0.data-00000-of-00001
│ │ ├── model.ckpt-0.index
│ │ ├── model.ckpt-0.meta
│ │ └── ...
│ └── vis
│ ├── graph.pbtxt
│ ├── raw_segmentation_results
│ └── segmentation_results
├── init_models
│ └── deeplabv3_pascal_train_aug
│ ├── frozen_inference_graph.pb
│ ├── model.ckpt.data-00000-of-00001
│ └── model.ckpt.index
├── tfrecord
│ ├── ....tfrecord
│ └── ...
└── VOCdevkit
└── VOC2012
├── Annotations
├── ImageSets
│ ├── Action
│ ├── Layout
│ ├── Main
│ └── Segmentation
├── JPEGImages
├── SegmentationClass
├── SegmentationClassRaw
└── SegmentationObject
安装 TensorFlow
参考 https://www.tensorflow.org/install/ ,安装 TensorFlow v1.5.0 或更新的版本。
如果操作系统、GPU 型号、Python 版本号等配置跟官方一致,可直接使用官网提供的安装包安装。
编译源码时注意 bazel 可能并不能总是获取
$LD_LIBRARY_PATH
,如有报错,可以尝试添加参数action_env
:
bazel build --config=opt --config=cuda tensorflow/tools/pip_package:build_pip_package --action_env="LD_LIBRARY_PATH=${LD_LIBRARY_PATH}"
配置 TensorFlow Models
git clone https://github.com/tensorflow/models.git
$PYTHONPATH
# From tensorflow/models/research/
export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
# From tensorflow/models/research/
python deeplab/model_test.py
若成功,显示OK
。
准备数据
这里以 PASCAL VOC 2012
为例。
参考 https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/pascal.md
运行以下代码即可:
# From deeplab/datasets/
sh download_and_convert_voc2012.sh
实际上,该脚本执行了以下操作:
# From deeplab/datasets/pascal_voc_seg/
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
tar -xf VOCtrainval_11-May-2012.tar
# From deeplab/datasets/
PASCAL_ROOT="pascal_voc_seg/VOCdevkit/VOC2012"
SEG_FOLDER="${PASCAL_ROOT}/SegmentationClass"
SEMANTIC_SEG_FOLDER="${PASCAL_ROOT}/SegmentationClassRaw"
python ./remove_gt_colormap.py \
--original_gt_folder="${SEG_FOLDER}" \
--output_dir="${SEMANTIC_SEG_FOLDER}"
# From deeplab/datasets/
OUTPUT_DIR="pascal_voc_seg/tfrecord"
mkdir -p "${OUTPUT_DIR}"
IMAGE_FOLDER="${PASCAL_ROOT}/JPEGImages"
LIST_FOLDER="${PASCAL_ROOT}/ImageSets/Segmentation"
python ./build_voc2012_data.py \
--image_folder="${IMAGE_FOLDER}" \
--semantic_segmentation_folder="${SEMANTIC_SEG_FOLDER}" \
--list_folder="${LIST_FOLDER}" \
--image_format="jpg" \
--output_dir="${OUTPUT_DIR}"
(可选)下载模型
官方提供了不少预训练模型( https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/model_zoo.md ),
这里以 deeplabv3_pascal_train_aug_2018_01_04
以例。
# From deeplab/datasets/pascal_voc_seg/
mkdir init_models
cd init_models
wget http://download.tensorflow.org/models/deeplabv3_pascal_train_aug_2018_01_04.tar.gz
tar zxf ssd_mobilenet_v1_coco_11_06_2017.tar.gz
如果使用现有模型进行预测则不需要训练。
训练
新建 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/train.sh
,内容如下:
mkdir -p logs/
now=$(date +"%Y%m%d_%H%M%S")
python ../../../../train.py \
--logtostderr \
--train_split="train" \
--model_variant="xception_65" \
--atrous_rates=6 \
--atrous_rates=12 \
--atrous_rates=18 \
--output_stride=16 \
--decoder_output_stride=4 \
--train_crop_size=513 \
--train_crop_size=513 \
--train_batch_size=4 \
--training_number_of_steps=10 \
--fine_tune_batch_norm=false \
--tf_initial_checkpoint="../../init_models/deeplabv3_pascal_train_aug/model.ckpt" \
--train_logdir="train/" \
--dataset_dir="../../tfrecord/" 2>&1 | tee logs/train_$now.txt &
进入 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/
,
运行 sh train.sh
即可训练。
验证
可一边训练一边验证,注意使用其它的GPU或合理分配显存。
新建 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/eval.sh
,内容如下:
python ../../../../eval.py \
--logtostderr \
--eval_split="val" \
--model_variant="xception_65" \
--atrous_rates=6 \
--atrous_rates=12 \
--atrous_rates=18 \
--output_stride=16 \
--decoder_output_stride=4 \
--eval_crop_size=513 \
--eval_crop_size=513 \
--checkpoint_dir="train/" \
--eval_logdir="eval/" \
--dataset_dir="../../tfrecord/" &
# --max_number_of_evaluations=1 &
进入 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/
,
运行 CUDA_VISIBLE_DEVICES="1" sh eval.sh
即可验证(这里指定了第二个 GPU)。
可视化 log
可一边训练一边可视化训练的 log,访问 http://localhost:6006/
即可看到 loss 等的变化。
# From deeplab/datasets/pascal_voc_seg/exp/train_on_train_set
tensorboard --logdir train/
可视化验证的 log,可看到 miou_1.0
的变化,这里指定了另一个端口。
# From deeplab/datasets/pascal_voc_seg/exp/train_on_train_set
tensorboard --logdir eval/ --port 6007
或同时可视化训练与验证的log:
# From deeplab/datasets/pascal_voc_seg/exp/train_on_train_set
tensorboard --logdir .
可视化分割结果
可一边训练一边可视化分割结果。
新建 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/vis.sh
,内容如下:
python ../../../../vis.py \
--logtostderr \
--vis_split="val" \
--model_variant="xception_65" \
--atrous_rates=6 \
--atrous_rates=12 \
--atrous_rates=18 \
--output_stride=16 \
--decoder_output_stride=4 \
--vis_crop_size=513 \
--vis_crop_size=513 \
--checkpoint_dir="train/" \
--vis_logdir="vis/" \
--dataset_dir="../../tfrecord/" &
# --max_number_of_evaluations=1 &
进入 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/
,
运行 sh vis.sh
即可生成分割结果,vis/segmentation_results/
里有彩色化的分割结果,vis/raw_segmentation_results/
里有原始的分割结果。
导出模型
训练完成后得到一些 checkpoint 文件在 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/train/
中,如:
其中 meta 文件保存了 graph 和 metadata,ckpt 文件保存了网络的 weights。
而进行预测时只需模型和权重,不需要 metadata,故可使用官方提供的脚本生成推导图。
新建 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/export_model.sh
,内容如下:
python ../../../../export_model.py \
--logtostderr \
--checkpoint_path="train/model.ckpt-$1" \
--export_path="export/frozen_inference_graph-$1.pb" \
--model_variant="xception_65" \
--atrous_rates=6 \
--atrous_rates=12 \
--atrous_rates=18 \
--output_stride=16 \
--decoder_output_stride=4 \
--num_classes=21 \
--crop_size=513 \
--crop_size=513 \
--inference_scales=1.0
进入 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/
,
运行 sh export_model.sh 1000
即可导出模型 export/frozen_inference_graph-1000.pb
。
测试图片
运行 deeplab_demo.ipynb
并修改其中的各种路径即可。
或自写 inference 脚本,如 deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/infer.py
import sys
sys.path.append('../../../../utils/')
from matplotlib import pyplot as plt
import numpy as np
from PIL import Image
import tensorflow as tf
import time
import get_dataset_colormap
import os
LABEL_NAMES = np.asarray([
'background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',
'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike',
'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tv'
])
FULL_LABEL_MAP = np.arange(len(LABEL_NAMES)).reshape(len(LABEL_NAMES), 1)
FULL_COLOR_MAP = get_dataset_colormap.label_to_color_image(FULL_LABEL_MAP)
class DeepLabModel(object):
"""Class to load deeplab model and run inference."""
INPUT_TENSOR_NAME = 'ImageTensor:0'
OUTPUT_TENSOR_NAME = 'SemanticPredictions:0'
INPUT_SIZE = 513
def __init__(self, model_path):
"""Creates and loads pretrained deeplab model."""
self.graph = tf.Graph()
with open(model_path,'rb') as fd:
graph_def = tf.GraphDef.FromString(fd.read())
with self.graph.as_default():
tf.import_graph_def(graph_def, name='')
self.sess = tf.Session(graph=self.graph)
def run(self, image):
"""Runs inference on a single image.
Args:
image: A PIL.Image object, raw input image.
Returns:
resized_image: RGB image resized from original input image.
seg_map: Segmentation map of `resized_image`.
"""
#start = time.time()
width, height = image.size
resize_ratio = 1.0 * self.INPUT_SIZE / max(width, height)
target_size = (int(resize_ratio * width), int(resize_ratio * height))
resized_image = image.convert('RGB').resize(target_size,
Image.ANTIALIAS)
batch_seg_map = self.sess.run(
self.OUTPUT_TENSOR_NAME,
feed_dict={
self.INPUT_TENSOR_NAME: [np.asarray(resized_image)]
})
seg_map = batch_seg_map[0]
#end = time.time()
#print(end-start)
return resized_image, seg_map
def vis_segmentation(image, seg_map):
plt.figure()
plt.subplot(221)
plt.imshow(image)
plt.axis('off')
plt.title('input image')
plt.subplot(222)
seg_image = get_dataset_colormap.label_to_color_image(
seg_map, get_dataset_colormap.get_pascal_name()).astype(np.uint8)
plt.imshow(seg_image)
plt.axis('off')
plt.title('segmentation map')
plt.subplot(223)
plt.imshow(image)
plt.imshow(seg_image, alpha=0.7)
plt.axis('off')
plt.title('segmentation overlay')
unique_labels = np.unique(seg_map)
ax = plt.subplot(224)
plt.imshow(
FULL_COLOR_MAP[unique_labels].astype(np.uint8),
interpolation='nearest')
ax.yaxis.tick_right()
plt.yticks(range(len(unique_labels)), LABEL_NAMES[unique_labels])
plt.xticks([], [])
ax.tick_params(width=0)
plt.show()
if __name__ == '__main__':
if len(sys.argv) < 3:
print('Usage: python {} image_path model_path'.format(sys.argv[0]))
exit()
image_path = sys.argv[1]
model_path = sys.argv[2]
model = DeepLabModel(model_path)
#orignal_im = Image.open(image_path)
#resized_im, seg_map = model.run(orignal_im)
#vis_segmentation(resized_im, seg_map)
for name in os.listdir(image_path):
start = time.time()
orignal_im = Image.open(name)
resized_im, seg_map = model.run(orignal_im)
end = time.time()
print(end-start)
运行以下命令即可:
# From deeplab/datasets/pascal_voc_seg/exp/train_on_train_set/
python infer.py \
../../../../g3doc/img/image1.jpg \
export/frozen_inference_graph.pb