iMind Developers Blog

iMind開発者ブログ

Tensorflow/modelsのdata augmentationの動きを確認する

概要

Tensorflowのmodelsのresearchのところにいる物体検知(object detection)系のコードで、data augmentation周りの処理がいろいろ用意されていたのでそれぞれの動きを確認しておく。

バージョン情報

  • Python 3.7.3
  • tensorflow-gpu==1.13.1
  • tensorflow/models v1.13.0
  • matplotlib==3.1.0

Tensorflow/modelsのclone

下記URLからclone。

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

reseachに移動。

$ cd models/research

下記ファイルにdata augmentation用の関数が記述してある。

  • object_detection/core/preprocessor.py

import

下記のライブラリを利用しています。eager_executionもしておきます。

from matplotlib import pylab as plt
import numpy as np
import cv2
from object_detection.core import preprocessor as proc

import tensorflow as tf
tf.enable_eager_execution()

サンプル画像のロード

512x512の画像を用意してOpenCVで読み込みます。

img = cv2.imread('dog.jpg')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))

f:id:mwsoft:20190713181829p:plain

かわいい。

normalize_image

0〜255の画像の特徴を0〜1.0の間に変換する用途などで使える。

変換前の画像データの前3行。

img[0:3, 0]
    #=> array([[109, 144,  77],
    #=>        [172, 208, 138],
    #=>        [165, 202, 132]], dtype=uint8)

0〜255を0〜1.0に変換。

new_img = proc.normalize_image(img, 0, 255, 0, 1).numpy()
new_img[0:3, 0]
    #=> array([[0.427451  , 0.5647059 , 0.3019608 ],
    #=>        [0.6745098 , 0.81568635, 0.5411765 ],
    #=>        [0.64705884, 0.79215693, 0.5176471 ]], dtype=float32)

random_horizontal_flip

50%の確率で水平方向に反転する。サンプルでは引数をnumpyのarrayで渡しているので反転させた場合はTensorが返り、させなかった場合はndarrayが返る。

f, ax_list = plt.subplots(1, 7, figsize=(10, 2))
for i in range(7):
    new_img = proc.random_horizontal_flip(img)[0]
    if not isinstance(new_img, np.ndarray):
        new_img = new_img.numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img, cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713185532p:plain

random_vertical_flip

50%の確率で垂直方向に反転する。

f, ax_list = plt.subplots(1, 7, figsize=(10, 2))
for i in range(7):
    new_img = proc.random_vertical_flip(img)[0]
    if not isinstance(new_img, np.ndarray):
        new_img = new_img.numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img, cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713192640p:plain

random_rotation90

50%の確率で90度回展する。

f, ax_list = plt.subplots(1, 7, figsize=(10, 2))
for i in range(7):
    new_img = proc.random_rotation90(img)[0]
    if not isinstance(new_img, np.ndarray):
        new_img = new_img.numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img, cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713192705p:plain

random_pixel_value_scale

ランダムで画像のサイズを変更する。下記は0.5〜2.0の間でサイズ変更を実行した際のshapeを出力した例。

for i in range(5):
    new_img = proc.random_image_scale(
        img, min_scale_ratio=0.5, max_scale_ratio=2.0)[0]
    print(new_img.shape)
    #=> (270, 270, 3)
    #=> (917, 917, 3)
    #=> (703, 703, 3)
    #=> (464, 464, 3)
    #=> (905, 905, 3)

random_rgb_to_gray

ランダムでグレースケール画像に変換する。デフォルトのprobabilityは0.1。

f, ax_list = plt.subplots(1, 7, figsize=(10, 2))
for i in range(7):
    new_img = proc.random_rgb_to_gray(img, probability=0.5)
    if not isinstance(new_img, np.ndarray):
        new_img = new_img.numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img, cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713193237p:plain

random_adjust_brightness

ランダムで明度を変更する。

f, ax_list = plt.subplots(1, 7, figsize=(12, 3))
for i in range(7):
    new_img = proc.random_adjust_brightness(img).numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713193305p:plain

random_adjust_contrast

ランダムでコントラストを変更する。デフォルト値はmin_deltaが0.8、max_deltaが1.25。効果が見やすいように下記は強めに設定。

f, ax_list = plt.subplots(1, 7, figsize=(12, 3))
for i in range(7):
    new_img = proc.random_adjust_contrast(
        img, min_delta=0.3, max_delta=1.5).numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713194123p:plain

random_adjust_hue

ランダムで色相を変更する。ランダムの範囲は -max_delta 〜 max_delta の間。

max_deltaのデフォルト値は0.02。効果が見やすいように下記は0.2まで上げて指定。

f, ax_list = plt.subplots(1, 7, figsize=(12, 3))
for i in range(7):
    new_img = proc.random_adjust_hue(
        img, max_delta=0.2).numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713200054p:plain

random_adjust_saturation

ランダムで彩度を変更する。デフォルト値はmin_delta=0.8, max_delta=1.25。

f, ax_list = plt.subplots(1, 7, figsize=(12, 3))
for i in range(7):
    new_img = proc.random_adjust_saturation(
        img, min_delta=0.3, max_delta=2.0).numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713201516p:plain

random_distort_color

ランダムでいろいろ効果をつける。

color_ordering=0の場合は、random_adjust_brightness, random_adjust_saturation, random_adjust_hue, random_adjust_contrastの順で実行する。

color_ordering=1の場合は、random_adjust_brightness, random_adjust_contrast, random_adjust_saturation, random_adjust_hueの順で実行する。

f, ax_list = plt.subplots(1, 7, figsize=(12, 3))
for i in range(7):
    new_img = proc.random_distort_color(
        img, color_ordering=0).numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190713202011p:plain

random_jitter_boxes

ランダムでboxesの指定を変更する。下記は0.1, 0.1, 0.2, 0.2が指定されたRectangleに対してrandom_jitter_boxesを実行している。4点の位置が少しズレて結果が返る。

proc.random_jitter_boxes(
    np.array([[0.1, 0.1, 0.2, 0.2]], np.float32), ratio=0.05)
    #=> array([[0.09792455, 0.10045671, 0.20128286, 0.20494111]]

random_crop_image

ランダムで画像をトリミングする。

まず顔を括るような矩形を用意する。

new_img = img.copy()
box = np.array([0.55, 0.1, 0.9, 0.5], np.float32)
left_top = tuple((box[0:2] * 512).astype(np.int))
right_bottom = tuple((box[2:4] * 512).astype(np.int))
cv2.rectangle(new_img, left_top, right_bottom, (255,0,0), 5)
plt.imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))

f:id:mwsoft:20190720045014p:plain

作成した矩形と画像を引数に渡して、random cropしてみる。

注意点として、用意した矩形波 (xmin, ymin), (xmax, ymax)だが、引数で渡す際は (ymin, xmin), (ymax, xmax)で扱う必要がある。

# 引数に渡すtensorを用意
new_img = tf.convert_to_tensor((img.copy() / 255).astype(np.float32))
tf_boxes = tf.convert_to_tensor(np.array([[box[1], box[0], box[3], box[2]]]))
labels = tf.convert_to_tensor(np.array([1]))
label_weights = tf.convert_to_tensor(np.array([1.0], np.float32))

# 5枚作ってみる
f, ax_list = plt.subplots(1, 5, figsize=(12, 4))
for i in range(5):
    # random_crop_image(の呼び出し
    crop_img, crop_boxes, crop_labels, crop_label_weights = \
        proc.random_crop_image(new_img, tf_boxes, labels, label_weights)

    # 画像とboxの生成
    crop_img = (crop_img.numpy() * 255).astype(np.uint8)
    crop_boxes = crop_boxes.numpy()
    left_top = tuple(np.array([crop_boxes[0][1] * crop_img.shape[1], 
        crop_boxes[0][0] * crop_img.shape[0]]).astype(np.int))
    right_bottom = tuple(np.array([crop_boxes[0][3] * crop_img.shape[1],
        crop_boxes[0][2] * crop_img.shape[0]]).astype(np.int))

    # 描画
    cv2.rectangle(crop_img, left_top, right_bottom, (255,0,0), 5)
    ax_list[i].imshow(cv2.cvtColor(crop_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190720045357p:plain

random_black_patches

ランダムで黒点のノイズを入れる。

f, ax_list = plt.subplots(1, 7, figsize=(12, 3))
for i in range(7):
    new_img = proc.random_black_patches(
        img, max_black_patches=5).numpy()
    if not isinstance(new_img, np.ndarray):
        new_img = new_img.numpy()
    ax_list[i].imshow(cv2.cvtColor(new_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190720050903p:plain

random_self_concat_image

ランダムで自身の画像を縦か横にconcatする。

デフォルトだとprobability=0.1だが、下記ではvertical, horizontalともに0.3を指定している。

# random_self_concat_image
new_img = tf.convert_to_tensor((img.copy() / 255).astype(np.float32))
tf_boxes = tf.convert_to_tensor(np.array([[box[1], box[0], box[3], box[2]]]))
labels = tf.convert_to_tensor(np.array([1]))
label_weights = tf.convert_to_tensor(np.array([1.0], np.float32))

f, ax_list = plt.subplots(1, 5, figsize=(12, 4))
for i in range(5):
    crop_img, crop_boxes, crop_labels, crop_label_weights = \
        proc.random_self_concat_image(
            new_img, tf_boxes, labels, label_weights,
            concat_vertical_probability=0.3,
            concat_horizontal_probability=0.3)

    crop_img = (crop_img.numpy() * 255).astype(np.uint8)
    crop_boxes = crop_boxes.numpy()
    for crop_box in crop_boxes:
        left_top = tuple(np.array([crop_box[1] * crop_img.shape[1],
            crop_box[0] * crop_img.shape[0]]).astype(np.int))
        right_bottom = tuple(np.array([crop_box[3] * crop_img.shape[1],
            crop_box[2] * crop_img.shape[0]]).astype(np.int))
        cv2.rectangle(crop_img, left_top, right_bottom, (255,0,0), 5)
    ax_list[i].imshow(cv2.cvtColor(crop_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190720051433p:plain

random_absolute_pad_image

ランダムでpaddingを入れる。

new_img = tf.convert_to_tensor((img.copy() / 255).astype(np.float32))
tf_boxes = tf.convert_to_tensor(np.array([[box[1], box[0], box[3], box[2]]]))

# 0〜200ピクセルの間でランダムになる引数を用意
max_height_padding = tf.convert_to_tensor(200)
max_width_padding = tf.convert_to_tensor(200)

f, ax_list = plt.subplots(1, 5, figsize=(12, 4))
for i in range(5):
    # random_absolute_pad_imageの呼び出し
    crop_img, crop_boxes = proc.random_absolute_pad_image(
        new_img, tf_boxes, max_width_padding, max_width_padding)

    crop_img = (crop_img.numpy() * 255).astype(np.uint8)
    crop_boxes = crop_boxes.numpy()
    left_top = tuple(np.array([crop_boxes[0][1] * crop_img.shape[1], crop_boxes[0][0] * crop_img.shape[0]]).astype(np.int))
    right_bottom = tuple(np.array([crop_boxes[0][3] * crop_img.shape[1], crop_boxes[0][2] * crop_img.shape[0]]).astype(np.int))

    cv2.rectangle(crop_img, left_top, right_bottom, (255,0,0), 5)
    ax_list[i].imshow(cv2.cvtColor(crop_img.astype(np.uint8), cv2.COLOR_BGR2RGB))
    ax_list[i].axis('off')

f:id:mwsoft:20190720052046p:plain

改定履歴

Author: Masato Watanabe, Date: 2019-07-20, 記事投稿