(超详细很完整)tensorflow下利用deeplabv3+对自己的数据进行训练

???????????????????????????????

文章目录

  • 大前提
  • DATA
    • Annotation
    • Convert to VOC-format Dataset
    • Convert to 灰度图
    • Convert to tfrecord
  • 修改训练文件
    • segmentation_dataset.py
    • train_utils.py
  • 训练,验证,可视化
    • 训练选择设置
    • 训练
    • 可视化
    • 验证
  • 一些可能的困难
    • 数据集不平衡
    • 输入数据尺寸统一
    • 分割类别过多
    • 官方FAQ


大前提

??????????????????

首先完成我的另一篇博文《(超详细很完整)tensorflow上实现deeplabv3+》中详细描述的tf下deeplabv3+的实现。

接下来才能进行对于自己数据集的训练。


DATA

Annotation

原始数据需要进行语义分割的标注。标注过程详见我的另一篇博文《使用labelme标注语义分割数据》。
标注完成获得原始图片对应的json文件。

Convert to VOC-format Dataset

利用对应的json文件,将数据转换成voc格式,方便后续进一步转换成deeplab训练所需的灰度图格式。

将labelme项目下载到本地:

git clone https://github.com/wkentaro/labelme.git

找到目录/labelme/examples/semantic_segmentation,里面有一个进行转换的完整示例,对照着示例,将自己的数据(原始图片和对应json标注)放入data_annotated文件夹,制作自己的labels.txt,拷贝labelme2voc.py文件不需改动,如下:
在这里插入图片描述
then:

# It generates:
#   - data_dataset_voc/JPEGImages
#   - data_dataset_voc/SegmentationClass
#   - data_dataset_voc/SegmentationClassVisualization
python labelme2voc.py data_annotated data_dataset_voc --labels labels.txt

会生成data_dataset_voc文件夹,里面包含:
(超详细很完整)tensorflow下利用deeplabv3+对自己的数据进行训练_第1张图片

Convert to 灰度图

deeplab使用单通道的标注图,即灰度图,并且类别的像素标记应该是0,1,2,3…n(共计n+1个类别,包含1个背景类和n个目标类),此外,标注图上忽略的像素值标记为255。

注意:不要把 ignore_label 和 background 混淆,ignore_label 没有做标注,不在预测范围内,即不参与计算loss。我们在mask中将 ignore_label 的灰度值标记为 255,而background 标记为 0。

我们上一步获得了voc格式数据,对于voc这种有colormap的标注图,可以利用remove_gt_colormap.py去掉colormap转成灰度图。

要使用remove_gt_colormap.py,首先要将tensorflow的models下载到本地:

git clone https://github.com/tensorflow/models.git

then:

# from models/research/deeplab/datasets
python remove_gt_colormap.py \
  --original_gt_folder="/path/SegmentationClassPNG" \
  --output_dir="/path/SegmentationClassRaw"

original_gt_folder是原始标签图文件夹,这里给定上一步生成的data_dataset_voc文件夹下的SegmentationClassPNG文件夹路径,output_dir是要输出的标签图文件夹的位置,设定为和SegmentationClassPNG文件夹同级目录下的SegmentationClassRaw文件夹。

生成的SegmentationClassRaw文件夹里面就是需要的灰度图:
(超详细很完整)tensorflow下利用deeplabv3+对自己的数据进行训练_第2张图片
乍一看黑不溜秋,其实对应标注像素值有变化,可以将图片像素值整体乘上100,效果如下:
(超详细很完整)tensorflow下利用deeplabv3+对自己的数据进行训练_第3张图片
see?像素值是不一样滴。

Convert to tfrecord

制作tfrecord之前,需要有指引文件将数据集分类成训练/测试/验证集。

制作指引文件,需要将之前生成的原始图片和灰度图分别放在两个文件夹下:/root/data/image//root/data/mask/,两个文件夹下的文件是一一对应的,文件名相同。并在文件夹/root/data/index/下创建3个txt文件:

  • train.txt:所有训练集的文件名
  • trainval.txt:所有验证集的文件名
  • val.txt:所有测试集的文件名

数据集目录如下:

from /root/data/

  • image
  • mask
  • index
    • train.txt
    • trainval.txt
    • val.txt
  • tfrecord

这里贴一段python脚本,能够获取文件名写入txt:

# 文件名写入txt
import random
import glob

img_path = glob.glob('/root/data/image/*.jpg') 
for each in img_path:
    with open('/root/data/image/all.txt','a')as f:
        f.write(each[15:-4]+'\n')# 切片换成自己路径对应的文件名位置

然后对文件名进行随机分配成三个txt:

# 随机分配训练集测试集验证集
import random

with open('/root/data/image/all.txt','r')as f:
    lines = f.readlines()
    g = [i for i in range(1, 2172)]# 设置文件总数
    random.shuffle(g)
    # 设置需要的文件数
    train = g[:1500]
    trainval = g[1500:1900]
    val = g[1900:]

    for index, line in enumerate(lines,1):
        if index in train:
            with open('/root/data/index/train.txt','a')as trainf:
                trainf.write(line)
        elif index in trainval:
            with open('/root/data/index/trainval.txt','a')as trainvalf:
                trainvalf.write(line)
        elif index in val:
            with open('/root/data/index/val.txt','a')as valf:
                valf.write(line)

最终获取的文件名如下:

# train.txt
58085ecdN8bb23aac
57bab542N6c4ce0a1
57b6bb67Nf43e4339
5805d40aNa7d92e9b
57c5012eN0abc3ee9
57c14fa2N61ed6861
580c56b5N89d4a70f
...

# trainval.txt
58047265Nd6c07b78
57bd5265N893399af
580b1b06N48ea8b80
5805f8d7N3a7a6bf0
57bc0968N417b3217
...

# val.txt
580c8b51N03677a99
57bc05e6Nca67dbea
58087c18N474843d1
...

然后利用build_voc2012_data.py转换成tfrecord格式,cmd输入指令:

# from /root/models/research/deeplab/datasets/
python ./build_voc2012_data.py \
  --image_folder="/root/data/image" \
  --semantic_segmentation_folder="/root/data/mask" \
  --list_folder="/root/data/index" \
  --image_format="jpg" \
  --output_dir="/root/data/tfrecord"

image_format为原始图片的格式。

转换成功:
(超详细很完整)tensorflow下利用deeplabv3+对自己的数据进行训练_第4张图片


修改训练文件

segmentation_dataset.py

找到segmentation_dataset.py文件,在大概110行的位置,添加自己数据集的描述,假设数据集有a,b,background三个类别,加上ignore_label,一共4类,所以num_classes=4:

_MYDATA_INFORMATION = DatasetDescriptor(
    splits_to_sizes={
     
        'train': 1500,  # 训练集数量
        'val': 300,  # 测试集数量
    },
    num_classes=4,
    ignore_label=255,
)

之后注册数据集,在大概112行的位置添加自己的数据集:

_DATASETS_INFORMATION = {
     
    'cityscapes': _CITYSCAPES_INFORMATION,
    'pascal_voc_seg': _PASCAL_VOC_SEG_INFORMATION,
    'ade20k': _ADE20K_INFORMATION,
    'mydata':_MYDATA_INFORMATION, # 添加自己的数据集
}

train_utils.py

train_utils.py中,先将大概109行的关于exclude_list的设置修改,作用是在使用预训练权重时候,不加载该logit层:

# Variables that will not be restored.
exclude_list = ['global_step','logits']
if not initialize_last_layer:
exclude_list.extend(last_layers)

对于数据集本身,如果数据不平衡,即各类别a,b,background在数据集中占比不相同,比如background占比远大于a,b类别,则需要对权重进行分配,假设权重比为1:10:11,则在train_utils.py的大概70行修改权重:

ignore_weight = 0
label0_weight = 1 # 对应background,mask中灰度值0
label1_weight = 10 # 对应a,mask中灰度值1
label2_weight = 11 # 对应b,mask中灰度值2

not_ignore_mask = tf.to_float(tf.equal(scaled_labels, 0)) * label0_weight + \
tf.to_float(tf.equal(scaled_labels, 1)) * label1_weight + \
tf.to_float(tf.equal(scaled_labels, 2)) * label2_weight + \
tf.to_float(tf.equal(scaled_labels, ignore_label)) * ignore_weight 

tf.losses.softmax_cross_entropy(
    one_hot_labels,
    tf.reshape(logits, shape=[-1, num_classes]),
    weights=not_ignore_mask,
    scope=loss_scope)

如果数据不平衡,这里涉及到对各类别像素的统计,贴一个脚本:

# 统计类别像素比例
import cv2
import numpy as np
import glob

pngpath = glob.glob('e:\\data\\seg420190709\\clo\\mask\\*.png')
zmat = np.zeros([30], dtype = np.float32)

for path in pngpath:
    mask = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
    for pixelvalue in range(30):
        a1 = mask == pixelvalue
        a1_count = len(mask[a1])
        zmat[pixelvalue]+=a1_count/10000

list  = []
for a in zmat:
    b = zmat[0]/a
    list.append(b)
    print(list)

训练,验证,可视化

终于可以开始训练鸟?

训练选择设置

如果想在DeepLab的基础上fine-tune其他数据集, 可在train.py中修改输入参数。有一些选项:

  • 使用预训练的所有权重,设置initialize_last_layer=True
  • 只使用网络的backbone,设置initialize_last_layer=Falselast_layers_contain_logits_only=False
  • 使用所有的预训练权重,除了logits,因为如果是自己的数据集,对应的classes不同(这个我们前面已经设置不加载logits),可设置initialize_last_layer=Falselast_layers_contain_logits_only=True

最终设置:

  • initialize_last_layer=False
  • last_layers_contain_logits_only=True

训练

训练指令:

# from /root/models/research/
python deeplab/train.py \
    --logtostderr \
    --num_clones=2 \
    --training_number_of_steps=100000 \
    --train_split="train" \
    --model_variant="xception_71" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --train_crop_size=321 \
    --train_crop_size=321 \
    --train_batch_size=12 \
    --dataset="mydata" \
    --fine_tune_batch_norm=True \
    --tf_initial_checkpoint='/root/models/research/deeplab/backbone/xception_71/model.ckpt' \
    --train_logdir='/root/models/research/deeplab/exp/mydata_train/train/' \
    --dataset_dir='/root/data/tfrecord/'

其中:
num_clones:用2个gpu进行训练所以设置成2,默认为1。

train_crop_size:裁剪完成的图片大小。对于参数的说明:

  • 不得小于 [321, 321]
  • (crop_size - 1)/4 = 整数
  • 将crop_size设置为[256, 256],结果不会好,因为其有ASPP(atrous spatial pyramid pooling)模块,如果图片过小,到feature map时没有扩张卷积的范围大了,所以要求一个最小值

train_batch_size:batch尺寸,如要训练BN层,batch_size值最好大于12,如果显存不够,可调整crop_size大小,但不得小于[321, 321]。

fine_tune_batch_norm:当batch_size大于12时,设置为True。

tf_initial_checkpoint:修改成自己的预训练权重路径,我这边使用的是xception_71_imagenet在网站 https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/model_zoo.md 可根据自己的需求获取对应的预训练权重。

train_logdir:训练产生的文件存放路径。

训练时部分输出:

...
INFO:tensorflow:global step 98250: loss = 1.9128 (0.731 sec/step)
INFO:tensorflow:global step 98260: loss = 3.2374 (0.740 sec/step)
INFO:tensorflow:global step 98270: loss = 1.3137 (0.736 sec/step)
INFO:tensorflow:global step 98280: loss = 3.3541 (0.732 sec/step)
INFO:tensorflow:global step 98290: loss = 1.1512 (0.740 sec/step)
INFO:tensorflow:global step 98300: loss = 1.8416 (0.735 sec/step)
INFO:tensorflow:global step 98310: loss = 1.5447 (0.753 sec/step)
...

可视化

指令:

python deeplab/vis.py \
    --logtostderr \
    --vis_split="val" \
    --model_variant="xception_71" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --vis_crop_size=512 \
    --vis_crop_size=512 \
    --dataset="mydata" \
    --colormap_type="pascal" \
    --checkpoint_dir='/root/models/research/deeplab/exp/mydata_train/train/' \
    --vis_logdir='/root/models/research/deeplab/exp/mydata_train/vis/' \
    --dataset_dir='/root/data/tfrecord/'

其中:
vis_split:设置为测试集val。

vis_crop_size:设置成数据集里面val数据的大小,比如我的是512*512。

dataset:设置为我们在segmentation_dataset.py文件设置的数据集名称。

dataset_dir:设置为创建的tfrecord路径。

colormap_type:可视化标注的颜色。

可视化部分输出:

INFO:tensorflow:Restoring parameters from /root/models/research/deeplab/exp/mydata_train/train/model.ckpt-100000
INFO:tensorflow:Visualizing batch 1 / 271
INFO:tensorflow:Visualizing batch 2 / 271
INFO:tensorflow:Visualizing batch 3 / 271
INFO:tensorflow:Visualizing batch 4 / 271
...

可视化结果:
(超详细很完整)tensorflow下利用deeplabv3+对自己的数据进行训练_第5张图片

验证

指令:

python deeplab/eval.py \
    --logtostderr \
    --eval_split="val" \
    --model_variant="xception_71" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --eval_crop_size=512 \
    --eval_crop_size=512 \
    --dataset="mydata" \
    --checkpoint_dir='/root/models/research/deeplab/exp/mydata_train/train/' \
    --eval_logdir='/root/models/research/deeplab/exp/mydata_train/eval/' \
    --dataset_dir='/root/data/tfrecord/'

其中:
eval_split:设置为测试集val。

eval_crop_size:同样设置为val图片大小512*512。

部分输出:

INFO:tensorflow:Starting evaluation at 2049-06-27-00:54:14
INFO:tensorflow:Evaluation [27/271]
INFO:tensorflow:Evaluation [54/271]
INFO:tensorflow:Evaluation [81/271]
INFO:tensorflow:Evaluation [108/271]
INFO:tensorflow:Evaluation [135/271]
INFO:tensorflow:Evaluation [162/271]
INFO:tensorflow:Evaluation [189/271]
INFO:tensorflow:Evaluation [216/271]
INFO:tensorflow:Evaluation [243/271]
INFO:tensorflow:Evaluation [270/271]
INFO:tensorflow:Evaluation [271/271]
INFO:tensorflow:Finished evaluation at 2019-06-27-00:54:36
miou_1.0[0.998610853]

一些可能的困难

????????????????????

数据集不平衡

之前已经说过这个问题鸟,如果各类别的像素区域差别大,需要设置权重进行平衡。

如果可视化输出图片全黑,则可能是background权重太大。

输入数据尺寸统一

我的原始数据大小不一,由于在训练时设置crop_size不能小于321321,所以我将原始图片和mask进行尺寸统一为512512。

贴一段统一尺寸的脚本8:

# mask的size统一
import cv2
import glob
maskpath = glob.glob('./mask/*.png')
for path in maskpath:
        name = path[31:]
        crop_size = (512, 512)
        img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
        img_new = cv2.resize(img, crop_size, interpolation = cv2.INTER_LINEAR)
        cv2.imwrite('./maskc/'+name, img_new, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])

注意,对于灰度图的mask,我使用的插值方式是cv2.INTER_LINEAR,因为只有这个方式能够保证在缩放的时候不引入其他的像素值(比如这个类别的像素值是3,在缩放的时候边缘不会出现2,1)。

分割类别过多

我预设的分类为24类,vis出来的结果小连通域非常多,导致画面碎片化,且物体中包含多种分割类别,缺损严重,分割空洞多,边缘吻合差,尝试将类别减少到5类之后,eval结果提升,vis结果观感好了不少。

官方FAQ

deeplab官方FAQ:https://github.com/tensorflow/models/blob/master/research/deeplab/g3doc/faq.md
可能会找到想要的问题答案。

[References]:

https://blog.csdn.net/u011974639/article/details/80948990
https://blog.csdn.net/weixin_41713230/article/details/81937763

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