ImageDataGeneratorで画像を水増しする方法

## はじめに
　DeepLearningを活用するにあたり，ImageDataGeneratorクラスを利用して画像の水増しを行い、フォルダごとのラベル付けから水増しまでを行う方法をメモしました。また、画像データをnpzファイルにバイナリデータとして保存する方法も示しました。

## ファイル・フォルダの位置関係
　今回は、ネットからリンゴ、バナナ、グレープの画像をそれぞれ3枚適当に持ってきて、それらをフォルダで分類しました。

```
$ tree
.
|-- img_increase.ipynb
`-- img_original
    |-- apple
    |   |-- apple1.jpg
    |   |-- apple2.jpg
    |   `-- apple3.jpg
    |-- banana
    |   |-- banana1.jpg
    |   |-- banana2.jpg
    |   `-- banana3.jpg
    `-- grape
        |-- grape1.jpg
        |-- grape2.jpg
        `-- grape3.jpg
```

## インポートするライブラリ

```python
import numpy as np
import glob
import random
import cv2
import keras
import matplotlib.pyplot as plt
from keras.preprocessing.image import ImageDataGenerator
%matplotlib inline
```

## 画像の読み込み
　以下に、上記の原画像を読み込み、フォルダごとにラベル付けするプログラムを示しました。このプログラムを実行すると、原画像データ及びそれに対するラベルデータがnpzファイルに保存されます。

```python
 # npzファイル：Numpy配列 ndarray を保存するバイナリファイル
outfile="fruits_org_data.npz"#保存ファイル名
max_photo=3 #各フォルダ内の枚数
photo_size=80 #画素数(80*80)
x=[]#画像データ
y=[]#ラベルデータ

#フォルダ名
categories = ["apple","banana","grape"]

#path以下の画像を読み込む
def glob_files(path,label):
    files=glob.glob(path+"/*.jpg") #フォルダ内の画像ファイル名をリストで返す
    random.shuffle(files) #フォルダごとに画像をシャッフル
    #各ファイルを処理
    num=0
    for f in files:
        if num >=max_photo:break #フォルダ内の写真を全て読み込んだら終了
        num+=1
        #画像ファイルを読む
        img=cv2.imread(f)
        img=cv2.resize(img, (photo_size,photo_size ))
        img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
        img=np.asarray(img)
        x.append(img)
        y.append(label)
        
    print(num)
    
def main():
    for cat, label in zip(categories, range(len(categories))):
        #各画像のフォルダーを読む(./は現在のフォルダ)
        glob_files("./img_original/" + cat, label)
    
    #x(学習データ),y(ラベル)の対応は維持したまま全画像データをシャッフル
    for l in [x, y]:
        np.random.seed(1)
        np.random.shuffle(l) 
    
    #ファイルへ保存
    np.savez(outfile,x=x,y=y)#xとyがnumpyのリストとして与えられる
    print("保存しました："+outfile,len(x))
    
main()
```

　プログラムの説明はコメント文で行っているので，細かいことは省略しますが，ここでは13行目のglob関数について説明します．

**glob関数の実行例**
　試しに、以下のプログラムを実行すると

```python
print(glob.glob("./img_original/apple/*.jpg"))
```

このような結果が出力されます。

```python
['./img_original/apple\\apple1.jpg', './img_original/apple\\apple2.jpg', './img_original/apple\\apple3.jpg']
```

このように、glob関数は、指定したファイルまでのpathをリストとして返すような関数となっています。

## ImageDataGeneratorによる画像水増し
　原画像の読み込みができたので、次は水増しを行っていきます。以下のコードを実行することで、90枚（＝3×3×10）に増えます。

```python
# ImageDataGeneratorクラスのオブジェクト生成
datagen = ImageDataGenerator(
        rotation_range=45, #±45°でランダムに回転
        vertical_flip=True, #垂直方向にランダムで反転
        horizontal_flip=True) #水平方向にランダムで反転

def images_gen(x_list,y_list):
    x_list_add=[]
    y_list_add=[]
    for x ,y in zip(x_list,y_list):
        x = x.reshape((1,) + x.shape)

        batch_list=[]
        i = 0
        
        # flowメソッド：numpyデータとラベルの配列を受け取り、拡張/正規化したデータのバッチを生成
        for batch in datagen.flow(x, batch_size=1):
            batch=batch.astype(np.uint8)#データ型を揃える
            batch=batch.reshape((photo_size, photo_size, 3))
            x_list_add.append(batch)
            y_list_add.append(y)
            i += 1
            if i > 9:#１枚から10枚作る(計90=3*3*10)
                break             
    x_np_add=np.array(x_list_add)
    y_np_add=np.array(y_list_add)

    #x(学習データ),y(ラベル)の対応は維持したままシャッフル
    for l in [x_np_add, y_np_add]:
        np.random.seed(1)
        np.random.shuffle(l) 

    return x_np_add,y_np_add


#画像データを読み込み
images=np.load("./fruits_org_data.npz")
x=images["x"] #画像データ
y=images["y"] #ラベルデータ

#読み込んだデータを三次元配列に変換
x=x.reshape(-1,photo_size,photo_size,3) #(サンプル数、行数、列数、RGB)の形に

#水増し
x_add,y_add=images_gen(x,y)
```

　ImageDataGeneratorクラスの詳しい内容は、こちらに載っています。⇒[KerasDocumentation](https://keras.io/ja/preprocessing/image/)
 上記のプログラムの3～5行目で、±45°の回転、垂直・水平方向の反転を行うことを指定しています。他にも、画像のシフト、ズーム、明暗を変える引数が存在します。
 
 **水増しされているか確認**

```python
plt.imshow(x_add[60])
print("Label: {}".format(y_add[60]))
```

　これを実行してみると、次のようにきちんと表示されたことから、水増しできていることが分かりますね。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397136/7497c7f2-c313-5aa4-179b-17e54698c419.png" width=40%>
 
## 最後に注意
・画像の水増しは、原画像をtrainとtestデータに分けてから行う！
・上記のプログラムではyのLabelデータは0~2のlistとなっているので、one-hot
エンコーディングを忘れずに！

## 終わりに
　このように、ImageDataGeneratorクラスを用いれば、簡単に水増しを行うことができます。また、ImageDataGeneratorクラスに含まれていないノイズなどは、クラスの継承により、ノイズをつける関数を加えることで解決できます。

## 参考文献
- [Keras の ImageDataGenerator を使って学習画像を増やす](https://www.pynote.info/entry/keras-image-data-generator)
- [Keras Documentation](https://keras.io/ja/preprocessing/image/)
- [ラーメンを本気で分類してみた](https://blog.aidemy.net/entry/2018/12/23/022554)