Tensorflow下利用Deeplabv3+訓練自己的數據(超詳細完整版)

使用deeplabv3+進行語義分割

環境要求:python3、tensorflow-gpu 1.11.0或者以上,ubuntu/win都可以

0.DeepLabv3+代碼下載

0.1 將tensorflow的models下載到本地

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

 0.2 添加環境變量:

windows用戶:

右鍵點擊我的電腦==>屬性==>高級系統設置 ==>環境變量==>新建==>將slim文件路徑添加進來(路徑根據自己實際地填寫)

Linux用戶:

添加依賴庫到PYTHONPATH,在目錄/home/user/models/research下:

# From /home/user/models/research/
$ export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
$ source ~/.bashrc

1.數據準備:

1.1 數據集做標籤(語義分割、實例分割通用): 標註完成獲得原始圖片對應的json文件

    圖片(images:jpg)、標籤文件(annotations:json)

1.2 json轉灰度png: 利用labelme將json文件數據轉換成voc格式,方便後續進一步轉換成deeplab訓練所需的灰度圖格式

將labelme項目下載到本地:

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

找到目錄/labelme/examples/semantic_segmentation,裏面有一個進行轉換的完整示例;

對照着示例,將自己的數據(原始圖片和對應json標註)放入data_annotated文件夾;

製作自己的labels.txt,而labelme2voc.py文件不需改動。如下:

# 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文件夾,裏面包含:

1.3 colormap的PNG標註圖轉換到灰度圖PNG

deeplab使用單通道的標註圖,即灰度圖,並且類別的像素標記應該是0,1,2,3…n(共計n+1個類別,包含1個背景類和n個目標類),此外,標註圖上忽略的像素值標記爲255。

注意:不要把 ignore_label 和 background 混淆,ignore_label(作爲crop_size時像素大小不夠的情況下填充的白色像素點) 沒有做標註,不在預測範圍內,即不參與計算loss。我們在mask中將 ignore_label 的灰度值標記爲 255,而background 標記爲 0。

我們上一步獲得了voc格式數據,對於voc這種有colormap的標註圖,可以利用remove_gt_colormap.py去掉colormap轉成灰度圖。

使用models/research/deeplab/datasets/remove_gt_colormap.py

# 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文件夾裏面就是需要的灰度圖(mask):

1.4 生成train.txt、valid.txt,不需要分出測試集

  • train.txt: 訓練集
  • valid.txt: 驗證集

數據集目錄如下:

From /root/data

  • images
  • mask
  • txt_and_tfrecord:放置txt、tfrecord

              txt:放置train.txt、val.txt

              tfrecord:放置train.tfrecord、val.tfrecord等

這裏貼出我自己寫的生成代碼generate_train&valid_txt.py90%訓練集、10%驗證集

import os
import random
root_path = os.getcwd()
images_dir = root_path + '\\data\\images'
images_list = os.listdir(images_dir)
print(images_list)
print('----------------------------------------------')
random.shuffle(images_list) # 打亂圖片的分佈
print(images_list)
total_num = len(images_list)
print("總數量:",total_num)

train_num = int(total_num*0.9)
print("訓練集數量:",train_num)
print("驗證集數量:",total_num-train_num)

list_file_1 = open(root_path+'\\data\\txt_and_tfrecord\\txt\\train.txt', 'w')
for item1 in images_list[:train_num]:
    image_id = item1.split('.')[0]
    list_file_1.write('%s\n' % (image_id))
list_file_1.close()

list_file_2 = open(root_path + '\\data\\txt_and_tfrecord\\txt\\val.txt', 'w')
for item2 in images_list[train_num:]:
    image_id = item2.split('.')[0]
    list_file_2.write('%s\n' % (image_id))
list_file_2.close()

最終獲取的文件名如下:

1.5 生成train.tfrecord、val.tfrecord

利用build_voc2012_data.py轉換成tfrecord格式: image_format爲原始圖片的格式。

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

2. 修改訓練文件
2.1 修改data_generator.py(deeplab/datasets下)

找到data_generator.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, # 添加自己的數據集
}

2.2 train_utils.py

train_utils.py中,先將大概109行的關於exclude_list的設置修改,作用是在使用預訓練權重時候,不加載該logit層:

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('./datac/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)

3. 模型訓練

3.1 訓練設置

如果想在DeepLab的基礎上fine-tune其他數據集, 可在train.py中修改輸入參數。有一些選項:

使用預訓練的所有權重,設置initialize_last_layer=True
只使用網絡的backbone,設置initialize_last_layer=False和last_layers_contain_logits_only=False
使用所有的預訓練權重,除了logits,因爲如果是自己的數據集,對應的classes不同(這個我們前面已經設置不加載logits),可設置initialize_last_layer=False和last_layers_contain_logits_only=True

最終設置:

  • initialize_last_layer=False
  • last_layers_contain_logits_only=True

3.2  修改訓練代碼只保存唯一模型

訓練代碼334行,修改三處:紅色部分

saver = tf.train.Saver(max_to_keep=1)
hooks = [stop_hook, tf.train.CheckpointSaverHook(checkpoint_dir=train_logdir, save_steps=200, saver=saver)]

profile_dir = None
if profile_dir is not None:
  tf.gfile.MakeDirs(profile_dir)

with tf.contrib.tfprof.ProfileContext(
    enabled=profile_dir is not None, profile_dir=profile_dir):
  with tf.train.MonitoredTrainingSession(
      master='',
      is_chief=True,
      config=session_config,
      scaffold=scaffold,
      checkpoint_dir=train_logdir,
      # summary_dir=train_logdir,
      log_step_count_steps=log_steps,
      save_summaries_steps=600,   # 600
      save_checkpoint_secs=None,
      hooks=hooks) as sess:            # 增加hooks回調參數保存唯一模型
    while not sess.should_stop():
      sess.run([train_tensor])

3.3 訓練指令

# from /root/models/research/
python deeplab/train.py \
    --logtostderr \
    --num_clones=1 \
    --training_number_of_steps=50000 \
    --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=6 \
    --dataset="mydata" \
    --fine_tune_batch_norm=False \
    --tf_initial_checkpoint='./deeplab/backbone/xception_65/model.ckpt' \
    --train_logdir='./deeplab/models/mydata' \
    --dataset_dir='./deeplab/data/txt_and_tfrecord/tfrecord'

其中:
num_clones:用1個gpu進行訓練所以設置成1,默認爲1。

train_crop_size:裁剪圖片大小,先高後寬(height+1, width+1)

對於參數的說明:

不得小於 [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)
...

4.模型驗證:計算驗證集的miou

4.1 修改eval.py代碼

eval.py159行增加二行代碼:

print_miou = tf.Print(miou, [miou], 'miou is:')  #創建tf.Print()的op
tf.summary.scalar('print_miou', print_miou)      #把這個op加到summary裏,後面在tf.contrib.training.evaluate_repeatedly中會自動調用

4.2 拿到miou變量值

eval.py最後增加五行代碼:

event_file = os.path.join(eval_logdir, os.listdir(eval_logdir)[0])
  for e in tf.train.summary_iterator(event_file):
    for v in e.summary.value:
      if v.tag == 'miou_1.0':               # 選取要讀取數據的節點    v.tag == 'miou_1.0' or v.tag == 'print_miou_'
        miou_value = round(v.simple_value, 4)   # 保留小數點後4位

4.3 驗證指令:

python deeplab/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 \
    --dataset="mydata" \
    --checkpoint_dir='./deeplab/models/mydata/' \
    --eval_logdir='./deeplab/models/mydata/eval/' \
    --dataset_dir='./deeplab/data/txt_and_tfrecord/tfrecord'

其中:
eval_split:設置爲測試集val。

eval_crop_size:同樣設置爲val圖片大小513*513,先高後寬(height+1, width+1)

部分輸出:

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]

如果之後顯示INFO:tensorflow:Waiting for new checkpoint 

 設置:max_number_of_evaluations = 1  就可以解決運行一次之後還沒關閉程序的問題

5.模型可視化

可視化指令:

python deeplab/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 \
    --dataset="mydata" \
    --colormap_type="pascal" \
    --checkpoint_dir='./deeplab/mdoels/mydata/' \
    --vis_logdir='./deeplab/models/mydata/vis/' \
    --dataset_dir='./deeplab/data/txt_and_tfrecord/tfrecord/'

其中:
vis_split:設置爲測試集val。

vis_crop_size:設置成val數據集裏面圖片的大小,比如我的是512*512,那就設置先高後寬(height+1, width+1):513*513

dataset:設置爲我們在data_generator.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
...

可視化結果:

6. 導出模型,生成frozen_inference_graph.pb以便接下來inference

python deeplab/export_model.py \
  --logtostderr \
  --checkpoint_path="./deeplab/mdoels/mydata/model.ckpt-48214" \
  --export_path="./deeplab/models/mydata/frozen_inference_graph.pb" \
  --model_variant="xception_65" \
  --atrous_rates=6 \
  --atrous_rates=12 \
  --atrous_rates=18 \
  --output_stride=16 \
  --decoder_output_stride=4 \
  --num_classes=4 \
  --crop_size=513 \
  --crop_size=513 \
  --inference_scales=1.0

嘗試輸入不同分辨率的圖像,分割效果差距很大。
export_model 的時候crop_size 的參數選擇:根據自己的測試,此時的crop_size :[513,513]

如果比513大,預測出來的效果會很稀疏,不好。如果比513小,直接報錯。

一些可能的困難

數據集不平衡

之前已經說過這個問題,如果各類別的像素區域差別大,需要設置權重進行平衡。

輸入數據尺寸統一(可選)

我的原始數據大小不一,由於在訓練時設置crop_size不能小於321321,所以我將原始圖片和mask進行尺寸統一爲512,512。

貼一段統一尺寸的腳本:

# 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)。

官方FAQ

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

[References]:

https://blog.csdn.net/malvas/article/details/88896283

https://blog.csdn.net/malvas/article/details/90776327

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

https://blog.csdn.net/weixin_41713230/article/details/8193776

https://www.jianshu.com/p/dcca31142b99

https://www.aiuai.cn/aifarm276.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章