ImageDataGenaratorを用いた画像の水増し

# はじめに
機械学習で画像データを扱うにあたって、データ数が少なくて精度が低いという問題は避けては通れません。
そんな時、画像データを水増しするオーグメンテーションが有効的な手段となります。
今回は自分の備忘録も兼ねて、KerasのImageDataGeneratorというライブラリを用いて、
画像の水増しが簡単に行えるようにしておこうと思います。

# 必要なモジュール

```py
import PIL
import numpy as np
import os
import glob
import matplotlib.pyplot as plt
from keras.preprocessing.image import load_img, img_to_array, ImageDataGenerator, array_to_img, save_img
%matplotlib inline
```

# 画像の水増し

[ImageDataGenerator](https://keras.io/ja/preprocessing/image/)の引数は以下のような初期値を持っております。

```py
keras.preprocessing.image.ImageDataGenerator(
    featurewise_center=False, 
    samplewise_center=False, 
    featurewise_std_normalization=False, 
    samplewise_std_normalization=False, 
    zca_whitening=False, 
    zca_epsilon=1e-06, 
    rotation_range=0.0, 
    width_shift_range=0.0, 
    height_shift_range=0.0, 
    brightness_range=None, 
    shear_range=0.0, 
    zoom_range=0.0, 
    channel_shift_range=0.0, 
    fill_mode='nearest', 
    cval=0.0, 
    horizontal_flip=False,
    vertical_flip=False, 
    rescale=None, 
    preprocessing_function=None, 
    data_format=None, 
    validation_split=0.0
)
```

[ImageDataGenerator](https://keras.io/ja/preprocessing/image/)の引数についての説明は以下のようです。

>引数
>featurewise_center: 真理値．データセット全体で，入力の平均を0にします．
samplewise_center: 真理値．各サンプルの平均を0にします．
featurewise_std_normalization: 真理値．入力をデータセットの標準偏差で正規化します．
samplewise_std_normalization: 真理値．各入力をその標準偏差で正規化します．
zca_epsilon: ZCA白色化のイプシロン．デフォルトは1e-6．
zca_whitening: 真理値．ZCA白色化を適用します．
rotation_range: 整数．画像をランダムに回転する回転範囲．
width_shift_range: 浮動小数点数（横幅に対する割合）．ランダムに水平シフトする範囲．
height_shift_range: 浮動小数点数（縦幅に対する割合）．ランダムに垂直シフトする範囲．
shear_range: 浮動小数点数．シアー強度（反時計回りのシアー角度）．
zoom_range: 浮動小数点数または[lower，upper]．ランダムにズームする範囲．浮動小数点数が与えられた場合，[lower, upper] = [1-zoom_range, 1+zoom_range]です．
channel_shift_range: 浮動小数点数．ランダムにチャンネルをシフトする範囲．
fill_mode: {"constant", "nearest", "reflect", "wrap"}のいずれか．デフォルトは 'nearest'です．指定されたモードに応じて，入力画像の境界周りを埋めます．
"constant": kkkkkkkk|abcd|kkkkkkkk (cval=k)
"nearest": aaaaaaaa|abcd|dddddddd
"reflect": abcddcba|abcd|dcbaabcd
"wrap": abcdabcd|abcd|abcdabcd
cval: 浮動小数点数または整数．fill_mode = "constant"のときに境界周辺で利用される値．
horizontal_flip: 真理値．水平方向に入力をランダムに反転します．
vertical_flip: 真理値．垂直方向に入力をランダムに反転します．
rescale: 画素値のリスケーリング係数．デフォルトはNone．Noneか0ならば，適用しない．それ以外であれば，(他の変換を行う前に) 与えられた値をデータに積算する．
preprocessing_function: 各入力に適用される関数です．この関数は他の変更が行われる前に実行されます．この関数は3次元のNumpyテンソルを引数にとり，同じshapeのテンソルを出力するように定義する必要があります．
data_format: {"channels_first", "channels_last"}のどちらか．"channels_last"の場合，入力のshapeは(samples, height, width, channels)となり，"channels_first"の場合は(samples, channels, height, width)となります．デフォルトはKerasの設定ファイル~/.keras/keras.jsonのimage_data_formatの値です．一度も値を変更していなければ，"channels_last"になります．
validation_split: 浮動小数点数．検証のために予約しておく画像の割合（厳密には0から1の間）です．


たくさんあってわかりにくいと思いますが、とりあえずこれらの引数をいじれば大抵のデータの水増しを行うことができます。

では、目的別にどのように引数を与えれば良いのかについて書いていきます。
**後で一つの画像ファイルに対して一気にデータの水増しをするために、全て関数にしています。**

## 回転
`rotation_range=90`とすると-90°~90°回転させた画像が出力されます。

```py
def create_rotate_images(rotation_range=90, image_array=None, num=10, img_name='hoge'):
    datagen = ImageDataGenerator(rotation_range=rotation_range)
    g = datagen.flow(image_array, batch_size=1)

    # num枚画像を取得
    for i in range(num):
        batches = g.next()
        # 画像として表示するため、４次元から3次元データにし、配列から画像にする。
        gen_img = array_to_img(batches[0])
        save_img(f'保存したいディレクトリのパス/rotate_{img_name}_{i}.jpg', gen_img)
```

## シフト（水平移動、上下移動）
`width_shift_range=0.1`で水平に0~10%移動
`height_shift_range=0.1`で垂直に0~10%移動

```py
def create_shift_images(width_shift_range=0.1, height_shift_range=0.1, image_array=None, num=10, img_name='hoge'):
    datagen = ImageDataGenerator(width_shift_range=width_shift_range,
height_shift_range=height_shift_range)
    g = datagen.flow(image_array, batch_size=1)

    for i in range(num):
        batches = g.next()
        # 画像として表示するため、４次元から3次元データにし、配列から画像にする。
        gen_img = array_to_img(batches[0])
        save_img(f'保存したいディレクトリのパス/with_shift_{img_name}_{i}.jpg', gen_img)
```

## 上下反転、左右反転
`vertical_flip=True`でランダムに上下反転が、
`horizontal_flip=True`でランダムに左右反転が反映されます。

```py
def create_flip_images(vertical_flip=True, horizontal_flip=True, image_array=None, num=10, img_name='hoge'):
    datagen = ImageDataGenerator(vertical_flip=vertical_flip, horizontal_flip=horizontal_flip)
    g = datagen.flow(image_array, batch_size=1)

    for i in range(num):
        batches = g.next()
        # 画像として表示するため、４次元から3次元データにし、配列から画像にする。
        gen_img = array_to_img(batches[0])
        save_img(f'保存したいディレクトリのパス/flip_{img_name}_{i}.jpg', gen_img)
```

## 拡大
`zoom_range=[0.3, 0.7]`で30%~70%の拡大をランダムにされる。

```py
def create_zoom_images(zoom_range=[0.3, 0.7], image_array=None, num=10, img_name='hoge'):
    datagen = ImageDataGenerator(zoom_range=zoom_range)
    g = datagen.flow(image_array, batch_size=1)

    for i in range(num):
        batches = g.next()
        # 画像として表示するため、４次元から3次元データにし、配列から画像にする。
        gen_img = array_to_img(batches[0])
        save_img(f'保存したいディレクトリのパス/zoom_{img_name}_{i}.jpg', gen_img)
```

## せん断写像
`shear_range=20`のようにパラメータを設定するとせん断写像というものになるみたいです。
斜めに傾けて伸ばしたような画像になったりします。

```py
def create_shear_images(shear_range=40, image_array=None, num=10, img_name='hoge'):
    datagen = ImageDataGenerator(shear_range=shear_range)
    g = datagen.flow(image_array, batch_size=1)

    for i in range(num):
        batches = g.next()
        # 画像として表示するため、４次元から3次元データにし、配列から画像にする。
        gen_img = array_to_img(batches[0])
        save_img(f'保存したいディレクトリのパス/shear_{img_name}_{i}.jpg', gen_img)
```

## カラーチャンネルの変更

`channel_shift_range=100`とすると、`-100~100`の範囲で、ランダムに画素データに値を足します。

```py
def create_channel_shift_images(channel_shift_range=100, image_array=None, num=10, img_name='hoge'):
    datagen = ImageDataGenerator(channel_shift_range=channel_shift_range)
    g = datagen.flow(image_array, batch_size=1)

    for i in range(num):
        batches = g.next()
        # 画像として表示するため、４次元から3次元データにし、配列から画像にする。
        gen_img = array_to_img(batches[0])
        save_img(f'保存したいディレクトリのパス/channel_shift_{img_name}_{i}.jpg', gen_img)
```

# 関数を使って画像の水増し

上で作成した関数を用いて、３つの画像に対して一気に水増しを行う例を下に書いておく。
以下のコードを実行することで３枚の画像が、
`6×5×3=90枚  (水増しの種類×それぞれの水増し枚数×画像の種類=合計枚数)`
というように、90枚に水増しをすることができます。（これは水増ししすぎな気がしますが。。）

```py
# 以下の３つの画像に対して水増しをする
image_files = ['./ImageData/001.jpg', './ImageData/002.jpg', './ImageData/003.jpg']

for image_file in image_files:
    # 画像をロード（PIL形式画像）
    img = load_img(image_file)

    #貼り付け
    plt.imshow(img)

    #表示
    plt.show()

    # numpyの配列に変換
    x = img_to_array(img)

    # 4次元配列に変換、以下同じ意味
    # x = np.expand_dims(x, axis=0)
    x = x.reshape((1,) + x.shape)

    #（1,縦サイズ, 横サイズ, チャンネル数)
    #  print(x.shape)

    # 回転
    create_rotate_images(rotation_range=90, image_array=x, num=5, img_name=image_file.split('/')[-1].replace('.jpg', ''))
    # 水平移動、上下移動
    create_shift_images(width_shift_range=0.1, height_shift_range=0.1, image_array=x, num=5, img_name=image_file.split('/')[-1].replace('.jpg', ''))
    # 反転
    create_flip_images(vertical_flip=True, horizontal_flip=True, image_array=x, num=5, img_name=image_file[.split('/')[-1].replace('.jpg', ''))
    # 拡大、縮小
    create_zoom_images(zoom_range=[0.3, 0.7], image_array=x, num=5, img_name=image_file.split('/')[-1].replace('.jpg', ''))
    # せん断写像
    create_shear_images(shear_range=40, image_array=x, num=5, img_name=image_file.split('/')[-1].replace('.jpg', ''))
    # カラーチャンネル変更
    create_channel_shift_images(channel_shift_range=100, image_array=x, num=5, img_name=image_file.split('/')[-1].replace('.jpg', ''))
```

# さいごに
今回はKerasのImageDataGeneratorというライブラリを用いて、
画像の水増しをする方法について書かせていただきました。
紹介した引数以外にもまだまだ色々と存在しているので、そちらをいじってみても楽しいかと思われます。
また、今回紹介した引数を組み合わせて新しく関数を作ってみるのも良いと思います。
最後まで読んでいただき、ありがとうございました。