tensorflow object detection的簡單使用(一)

在上一篇博文中,簡單介紹瞭如何在ubuntu16.04上安裝google提供的基於tensorflow的object detection模塊。本篇博文簡單介紹如何去使用這個模塊,主要以訓練PASCAL VOC2012數據集爲例進行講解。什麼是PASCAL VOC,如果有不瞭解的同學可以抽點時間看下這篇博文這個博主講的很詳細,簡單來說就是擁有20個類別的目標檢測數據集(當然除了目標檢測還有目標分割等數據),包括人、車、船、飛機等常見的20類物體。

首先簡單介紹下本次訓練的流程(默認已安裝好object detection模塊):

1.下載PASCAL VOC2012數據集,並將數據集轉爲tfrecord格式

2.選擇並下載預訓練模型

3.配置訓練文件configuration

4.訓練模型

5.利用tensorboard查看訓練過程中loss,accuracy等變化曲線

6.凍結模型參數

7.調用凍結pb文件進行預測

 

1.下載數據集與轉換格式

在該項目開始之前,我們先將文件結構建立起來(培養文件歸類的好習慣),根據官方的建議,文件結構如下,建立好文件結構後我們將pascal_lable_map.pbtxt標籤文件(裏面存放着id以及對應的目標名稱)放到data目錄下,該文件在/xxx/models-master/research/object_detection/data目錄下:

+data
  -label_map file
  -train TFRecord file
  -eval TFRecord file
+models
  + model
    -pipeline config file
    +train
    +eval

在終端通過以下指令複製(有圖形界面的同學直接複製粘貼就行):

cp /xxx/models-master/research/object_detection/data/pascal_label_map.pbtxt /xxx/xxx/data/

接下來直接用以下指令在ubuntu終端上下載,或者複製下面的鏈接到瀏覽器上也能下,大概有1.2G的樣子,下載完後放到data文件夾下。

wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar

下載完後解壓數據集:

tar -xvf VOCtrainval_11-May-2012.tar

接着通過終端進入/xxx/models-master/research目錄下建立一個sh腳本文件(例如translate_VOC.sh)複製以下代碼:

python object_detection/dataset_tools/create_pascal_tf_record.py \
    --label_map_path=object_detection/data/pascal_label_map.pbtxt \
    --data_dir=/xxx/data/VOCdevkit --year=VOC2012 --set=train \
    --output_path=/xxx/data/pascal_train.record
python object_detection/dataset_tools/create_pascal_tf_record.py \
    --label_map_path=object_detection/data/pascal_label_map.pbtxt \
    --data_dir=/xxx/data/VOCdevkit --year=VOC2012 --set=val \
    --output_path=/xxx/data/pascal_val.record

這是官網提供的將PASCAL VOC數據集轉爲tfrecord的代碼,簡單講一下,這個代碼調用了兩次create_pascal_tf_record.py文件分別去生成用於訓練的pascal_train.record文件以及用於驗證的pascal_val.record文件。在該代碼中,需要改幾處地方,(1)data_dir,這個是你解壓的pascal voc數據集的路徑(2)output_path,這個是你想要保存tfrecord文件的地方。其他的參數不要改動。保存腳本文件,在終端直接輸入sh translate_VOC.sh開始轉換格式,轉換完成後會在相應文件夾中生成tftrecord文件。

--------------------------------------------------------------------------------

注意:pascal voc2012中訓練集和驗證集均提供了大約5000+張樣本,如果你的電腦硬件配置不高,建議你減少驗證集的圖像數量,否則每次驗證時會浪費你很多的時間,嚴重的話會導致你陷入驗證的死循環,根本無法訓練(官方默認每隔600秒進行一次驗證)。

如何減少驗證集圖片數量呢?這裏提供兩個方法,第一個方法比較簡單,第二個方法麻煩點

(1)在訓練過程中通過設置SAMPLE_1_OF_N_EVAL_EXAMPLES的值來改變驗證採樣頻率(默認爲1),比如設置爲5代表每五個驗證樣本我只抽取其中一個,簡單的說就是隻取樣本集的五分之一進行驗證,當爲1時對整個驗證集進行驗證。

(2)打開你下載的數據集目錄     /xxx/VOCdevkit/VOC2012/ImageSets/Main   ,在該目錄下找到aeroplane_val.txt這個文件,打開它,大概有5000+行,每一行代表着一個驗證樣本,比如我只保留前500行後面的全部刪掉,這樣驗證集就只剩500個了。

這麼做的原因是,在create_pascal_tf_record.py文件的main函數中(大概在代碼的165行左右)是通過aeroplane_train.txtaeroplane_val.txt這兩個文件去尋找並生成數據集的。這就是爲什麼我們直接去處理aeroplane_val.txt文件的原因。

---------------------------------------------------------------------------------

 

2.選擇並下載預訓練模型

在object detection模塊中,官方已經提供了很多基於coco數據集預訓練好的模型,鏈接

下載這些模型並使用遷移學習的方式能夠很快的訓練出一個新的模型。在這裏我們選擇一個模型參數較小的ssd_mobilenet_v2_coco作爲本次訓練實例。直接點擊模型名稱即可下載,下載完成後我們將文件放到1中介紹過的文件結構的model文件夾下,並進行解壓操作。

 

3.配置訓練文件configuration

關於配置文件,我們能夠在 models-master/research/object_detection/samples/configs/ 文件夾下找到很多配置文件模板。由於我們下載的是ssd_mobilenet_v2_coco模型,那麼我們就找ssd_mobilenet_v2_coco.config文件,將這個文件複製到model文件夾下,開始進行配置。

# SSD with Mobilenet v2 configuration for MSCOCO Dataset.
# Users should configure the fine_tune_checkpoint field in the train config as
# well as the label_map_path and input_path fields in the train_input_reader and
# eval_input_reader. Search for "PATH_TO_BE_CONFIGURED" to find the fields that
# should be configured.

model {
  ssd {
    num_classes: 20
    box_coder {
      faster_rcnn_box_coder {
        y_scale: 10.0
        x_scale: 10.0
        height_scale: 5.0
        width_scale: 5.0
      }
    }
    matcher {
      argmax_matcher {
        matched_threshold: 0.5
        unmatched_threshold: 0.5
        ignore_thresholds: false
        negatives_lower_than_unmatched: true
        force_match_for_each_row: true
      }
    }
    similarity_calculator {
      iou_similarity {
      }
    }
    anchor_generator {
      ssd_anchor_generator {
        num_layers: 6
        min_scale: 0.2
        max_scale: 0.95
        aspect_ratios: 1.0
        aspect_ratios: 2.0
        aspect_ratios: 0.5
        aspect_ratios: 3.0
        aspect_ratios: 0.3333
      }
    }
    image_resizer {
      fixed_shape_resizer {
        height: 300
        width: 300
      }
    }
    box_predictor {
      convolutional_box_predictor {
        min_depth: 0
        max_depth: 0
        num_layers_before_predictor: 0
        use_dropout: false
        dropout_keep_probability: 0.8
        kernel_size: 1
        box_code_size: 4
        apply_sigmoid_to_scores: false
        conv_hyperparams {
          activation: RELU_6,
          regularizer {
            l2_regularizer {
              weight: 0.00004
            }
          }
          initializer {
            truncated_normal_initializer {
              stddev: 0.03
              mean: 0.0
            }
          }
          batch_norm {
            train: true,
            scale: true,
            center: true,
            decay: 0.9997,
            epsilon: 0.001,
          }
        }
      }
    }
    feature_extractor {
      type: 'ssd_mobilenet_v2'
      min_depth: 16
      depth_multiplier: 1.0
      conv_hyperparams {
        activation: RELU_6,
        regularizer {
          l2_regularizer {
            weight: 0.00004
          }
        }
        initializer {
          truncated_normal_initializer {
            stddev: 0.03
            mean: 0.0
          }
        }
        batch_norm {
          train: true,
          scale: true,
          center: true,
          decay: 0.9997,
          epsilon: 0.001,
        }
      }
    }
    loss {
      classification_loss {
        weighted_sigmoid {
        }
      }
      localization_loss {
        weighted_smooth_l1 {
        }
      }
      hard_example_miner {
        num_hard_examples: 3000
        iou_threshold: 0.99
        loss_type: CLASSIFICATION
        max_negatives_per_positive: 3
        min_negatives_per_image: 3
      }
      classification_weight: 1.0
      localization_weight: 1.0
    }
    normalize_loss_by_num_matches: true
    post_processing {
      batch_non_max_suppression {
        score_threshold: 1e-8
        iou_threshold: 0.6
        max_detections_per_class: 100
        max_total_detections: 100
      }
      score_converter: SIGMOID
    }
  }
}

train_config: {
  batch_size: 24
  optimizer {
    rms_prop_optimizer: {
      learning_rate: {
        exponential_decay_learning_rate {
          initial_learning_rate: 0.004
          decay_steps: 800
          decay_factor: 0.95
        }
      }
      momentum_optimizer_value: 0.9
      decay: 0.9
      epsilon: 1.0
    }
  }
  fine_tune_checkpoint: "/xxx/model/ssd_mobilenet_v2_coco_2018_03_29/model.ckpt"
  from_detection_checkpoint: true
  load_all_detection_checkpoint_vars: true
  # Note: The below line limits the training process to 200K steps, which we
  # empirically found to be sufficient enough to train the pets dataset. This
  # effectively bypasses the learning rate schedule (the learning rate will
  # never decay). Remove the below line to train indefinitely.
  num_steps: 20000
  data_augmentation_options {
    random_horizontal_flip {
    }
  }
  data_augmentation_options {
    ssd_random_crop {
    }
  }
}

train_input_reader: {
  tf_record_input_reader {
    input_path: "/xxx/data/pascal_train.record"
  }
  label_map_path: "/xxx/pascal_label_map.pbtxt"
}

eval_config: {
  num_examples: 500
  metrics_set: "coco_detection_metrics"
}

eval_input_reader: {
  tf_record_input_reader {
    input_path: "/xxx/data/pascal_val.record"
  }
  label_map_path: "/xxx/pascal_label_map.pbtxt"
  shuffle: false
  num_readers: 1
}

由於配置文件參數很多,我就講幾個只要簡單改動後就能訓練的參數:

1)num_classes:   這個參數就是你所訓練模型的檢測目標數(pascal voc是20類,所以改成20)

2)decay_steps:  這個參數的意義是每訓練decay_steps步數,就進行將學習率將低一次(new_learning_rate = old_learnning_rate * decay_factor)這個根據個人訓練情況改,因爲這裏並不是從頭訓練(我使用的遷移學習方法),而且我就準備迭代20000步,所以就加快降低學習率的速度(學習率過大會導致準確率一直震盪,很難收斂)。

3)fine_tune_checkpoint: 這個參數就是加載預訓練模型的路徑,也就是我們下載解壓的ssd_mobilenet_v2_coco路徑。

4)from_detection_checkpoint: 這個參數是選擇加載全部模型參數true,還是隻加載前置網絡模型參數(分類網絡)false。

5)num_steps: 20000   這個參數就是總迭代步數

6)input_path:   這個參數就是讀取tfrecord文件的路徑,在train_input_reader中填pascal_train.record路徑,在eval_input_reader中填pascal_val.record路徑。

7)label_map_path:  這個參數就是pascal_label_map.pbtxt字典文件的路徑。

8)num_examples:   這個參數就是你驗證集的圖像數目。

9)metrics_set:   這個參數指定在驗證時使用哪種評價標準,這裏使用的是"coco_detection_metrics"

-----------------------------------------------------------------------------------------------

關於如何控制驗證頻率的問題:

在之前的版本中可以在eval_config中設置eval_interval_secs的值來改變驗證之間的間隔,但現在已經失效了。官方也有說要解決這個問題,但到現在還未解決。下面給出一個控制驗證間隔的方法:

打開  models-master/research/object_detection/model_lib.py文件在create_train_and_eval_specs函數中,大概750行左右的位置有如下代碼:

tf.estimator.EvalSpec(
            name=eval_spec_name,
            input_fn=eval_input_fn,
            steps=None,
            exporters=exporter)

在tf.estimator.EvalSpec函數中添加如下一行(throttle_secs=3600,代表間隔1個小時驗證一次,在Tensorflow文檔,tf.estimator.EvalSpec函數中,默認參數start_delay_secs=120,throttle_secs=600代表默認從訓練開始的第120秒開始第一次驗證,之後每隔600秒驗證一次):

tf.estimator.EvalSpec(
            name=eval_spec_name,
            input_fn=eval_input_fn,
            steps=None,
            exporters=exporter,
            throttle_secs=3600)

-----------------------------------------------------------------------------------------------

修改完配置文件後,我們就可以開始訓練模型了。

 

4.訓練模型

我們回到  models-master/research文件夾下,建立一個訓練模型的腳本文件(train_VOC.sh):

PIPELINE_CONFIG_PATH=/xxx/model/ssd_mobilenet_v2_coco.config
MODEL_DIR=/xxx/model/train
NUM_TRAIN_STEPS=20000
SAMPLE_1_OF_N_EVAL_EXAMPLES=1
python object_detection/model_main.py \
    --pipeline_config_path=${PIPELINE_CONFIG_PATH} \
    --model_dir=${MODEL_DIR} \
    --num_train_steps=${NUM_TRAIN_STEPS} \
    --sample_1_of_n_eval_examples=$SAMPLE_1_OF_N_EVAL_EXAMPLES \
    --alsologtostderr

其中PIPELINE_CONFIG_PATH是我們的config配置文件路徑;MODEL_DIR是訓練模型的保存位置;NUM_TRAIN_STEPS是總共訓練的步數;SAMPLE_1_OF_N_EVAL_EXAMPLES是驗證過程中對樣本的採樣頻率(在之前說過,假設爲n則表示只對驗證樣本中的n分一的樣本進行採樣驗證),若爲1表示將驗證所有樣本,一般設爲1。建立好腳本文件後通過sh train_VOC.sh開始執行。

--------------------------------------------------------------------------------

如果需要在後臺運行可通過以下指令,通過該指令將終端輸出信息定向輸出到train.log文件中:

nohup sh train_VOC.sh > train.log 2>&1 &

通過下面指令查看log中的信息:

tail -f train.log

若需要提前終止程序,可通過查詢對應程序的pid進行終止。

ps -ef | grep python  # 查找後臺運行的所有pyhton程序的pid
kill -9 9208  # 終端pid爲9208的程序

-----------------------------------------------------------------------------------

本人訓練大概8000 step後就基本收斂了,爲了節約時間,停止了訓練。在coco的檢測標準中IOU=0.50:0.95對應的Average Precision爲0.367,IOU=0.50(IOU=0.5可以理解爲pscal voc的評價標準)對應的Average Precision爲0.604。

 

5.使用tensorboard查看訓練過程

在訓練過程中,所有的訓練文件都保存在我們的train文件夾下,打開train文件夾你會發現裏面有個eval_0文件夾,這個文件裏保存是在整個訓練過程中在驗證集上的準確率變化。我們通過終端進入train文件夾,接着輸入以下指令調用tensorboard:

tensorboard --logdir=eval_0/

輸入指令後終端會打印出一個本地網頁鏈接,複製該鏈接,打開你的瀏覽器輸入該鏈接就能看到整個過程的訓練結果。在SCALARS欄中有DetectionBoxes_Precision、DetectionBoxes_Pecall、Loss、leaning_rate等曲線,在IMAGES欄中有使用模型在驗證集上預測的圖像,左側是預測的結果右側是標準檢測結果。GRAPHS是整個訓練的流程圖,太亂了看不懂。

 

6.凍結模型參數

模型訓練完成後所有的參數是以model.ckpt.data、model.ckpt.meta和model.ckpt.index三個文件共同保存,在我們使用的ssd_mobilenet_v2模型中,模型ckpt文件大小約78M,如果將模型參數凍結後只有20M。當然,不論是ckpt文件還是凍結後的pb文件我們都能夠進行調用,但ckpt文件更適合在訓練模型等實驗過程中使用,而pb文件是根據我們需要的輸出節點反向查找將ckpt中不需要的節點全部刪除後獲得的文件(還將模型參數的保存形式進行了改變)由於該文件佔用內存更小,更適合部署在你所需要使用的設備上。(tensorflow還有很多方法能夠將模型的大小進一步縮減佔用更少的內存)

我們直接進入 models-master/research/ 文件夾下,建立凍結模型權重腳本(export_Pb.sh):

INPUT_TYPE=image_tensor
PIPELINE_CONFIG_PATH=/xxx/model/ssd_mobilenet_v2_coco.config
TRAINED_CKPT_PREFIX=/xxx/model/train/model.ckpt-8000
EXPORT_DIR=/xxx/model/eval/
python object_detection/export_inference_graph.py \
    --input_type=${INPUT_TYPE} \
    --pipeline_config_path=${PIPELINE_CONFIG_PATH} \
    --trained_checkpoint_prefix=${TRAINED_CKPT_PREFIX} \
    --output_directory=${EXPORT_DIR}

PIPELINE_CONFIG_PATH就是模型的config配置文件路徑;TRAINED_CKPT_PREFIX是訓練模型的ckpt權重最後面的數字對應的訓練步數(一般選擇最後保存的一個ckpt就行);EXPORT_DIR就是凍結模型pb的輸出位置。執行腳本後在/xxx/model/eval/目錄下就會生成我們所需要的pb文件(frozen_inference_graph.pb)。

 

7.調用pb文件進行預測

我們新建一個python項目,將 models-master/research/目錄下的object_detection 整個文件夾複製到我們的python項目中,接着將之前生成的pb文件所在文件夾eval複製到我們的項目中,接着將我們的標籤文件pascal_lable_map.pbtxt文件複製到項目中,接着檢查下object_detection文件夾下的test_images文件夾中是否有兩張圖片(一張是兩個狗狗,一張是object detection官網上用的那個海邊圖片),後面需要使用,目錄結構如下。

+pythonProject
   +object_detection
      +test_images
   +eval
   -pascal_label_map.pbtxt
   -main.py

創建一個python文件main.py,將下面代碼複製到py文件中,該代碼是根據官網的object_detection_tutorial.ipynb教程簡單修改得到的。代碼中也有註釋,便於理解。

 

import numpy as np
import os
import glob
import tensorflow as tf

from distutils.version import StrictVersion
import matplotlib
from matplotlib import pyplot as plt
from PIL import Image


from object_detection.utils import ops as utils_ops

from object_detection.utils import label_map_util

from object_detection.utils import visualization_utils as vis_util

# 防止backend='Agg'導致不顯示圖像的情況
matplotlib.use('TkAgg')

if StrictVersion(tf.__version__) < StrictVersion('1.12.0'):
    raise ImportError('Please upgrade your TensorFlow installation to v1.12.*.')

MODEL_NAME = 'eval'

# Path to frozen detection graph. This is the actual model that is used for the object detection.
PATH_TO_FROZEN_GRAPH = MODEL_NAME + '/frozen_inference_graph.pb'

# List of the strings that is used to add correct label for each box.
PATH_TO_LABELS = 'pascal_label_map.pbtxt'

detection_graph = tf.Graph()
with detection_graph.as_default():
    od_graph_def = tf.GraphDef()
    with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid:
        serialized_graph = fid.read()
        od_graph_def.ParseFromString(serialized_graph)
        tf.import_graph_def(od_graph_def, name='')

category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True)

def load_image_into_numpy_array(image):
    im_width, im_height = image.size
    return np.array(image.getdata()).reshape(
        (im_height, im_width, 3)).astype(np.uint8)

# For the sake of simplicity we will use only 2 images:
# image1.jpg
# image2.jpg
# If you want to test the code with your images, just add path to the images to the TEST_IMAGE_PATHS.
PATH_TO_TEST_IMAGES_DIR = 'object_detection/test_images/*.jpg'
TEST_IMAGE_PATHS = glob.glob(PATH_TO_TEST_IMAGES_DIR)

# Size, in inches, of the output images.
IMAGE_SIZE = (12, 8)

def run_inference_for_single_image(image, graph):
    with graph.as_default():
        with tf.Session() as sess:
            # Get handles to input and output tensors
            ops = tf.get_default_graph().get_operations()
            all_tensor_names = {output.name for op in ops for output in op.outputs}
            tensor_dict = {}
            for key in [
                'num_detections', 'detection_boxes', 'detection_scores',
                'detection_classes', 'detection_masks'
            ]:
                tensor_name = key + ':0'
                if tensor_name in all_tensor_names:
                    tensor_dict[key] = tf.get_default_graph().get_tensor_by_name(
                        tensor_name)
            if 'detection_masks' in tensor_dict:
                # The following processing is only for single image
                detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0])
                detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0])
                # Reframe is required to translate mask from box coordinates to image coordinates and fit the image size.
                real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32)
                detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1])
                detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1])
                detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks(
                    detection_masks, detection_boxes, image.shape[1], image.shape[2])
                detection_masks_reframed = tf.cast(
                    tf.greater(detection_masks_reframed, 0.5), tf.uint8)
                # Follow the convention by adding back the batch dimension
                tensor_dict['detection_masks'] = tf.expand_dims(
                    detection_masks_reframed, 0)
            image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0')

            # Run inference
            output_dict = sess.run(tensor_dict,
                                   feed_dict={image_tensor: image})

            # all outputs are float32 numpy arrays, so convert types as appropriate
            output_dict['num_detections'] = int(output_dict['num_detections'][0])
            output_dict['detection_classes'] = output_dict[
                'detection_classes'][0].astype(np.int64)
            output_dict['detection_boxes'] = output_dict['detection_boxes'][0]
            output_dict['detection_scores'] = output_dict['detection_scores'][0]
            if 'detection_masks' in output_dict:
                output_dict['detection_masks'] = output_dict['detection_masks'][0]
    return output_dict

for image_path in TEST_IMAGE_PATHS:
    image = Image.open(image_path)
    # the array based representation of the image will be used later in order to prepare the
    # result image with boxes and labels on it.
    image_np = load_image_into_numpy_array(image)
    # Expand dimensions since the model expects images to have shape: [1, None, None, 3]
    image_np_expanded = np.expand_dims(image_np, axis=0)
    # Actual detection.
    output_dict = run_inference_for_single_image(image_np_expanded, detection_graph)
    # Visualization of the results of a detection.
    vis_util.visualize_boxes_and_labels_on_image_array(
        image_np,
        output_dict['detection_boxes'],
        output_dict['detection_classes'],
        output_dict['detection_scores'],
        category_index,
        instance_masks=output_dict.get('detection_masks'),
        use_normalized_coordinates=True,
        line_thickness=8)
    plt.figure(figsize=IMAGE_SIZE)
    plt.imshow(image_np)
    plt.show()

大家可以自己多找幾張圖片放到test_images文件夾下試試效果,是不是很有成就感,撒花✿✿ヽ(°▽°)ノ✿    

在下一篇博客中將會介紹如何使用自己的數據集去訓練模型。

 

 

 

 

 

 

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