学習に利用する画像のデータセットを作成する

# はじめに
- 今回の、学習画像、テスト画像、水増し画像の合計サイズは、`1.4GB` 程度です。
- これらの画像を、学習プログラム実施時に、読み込むとそれなりの時間が必要になります。
- また、別の環境で学習プログラムを実施する時も、転送時間が発生します。
- その他、画像サイズをリサイズしたり、カラーからグレーに変換する処理にも時間が必要になります。
- あらかじめ、リサイズ、グレー変換したデータセットを作成する事で、`50MB` 程度にすることが出来ました。
- ソース一式は [ここ](https://github.com/maedamikio/public/tree/master/abe_or_ishihara/learning) です。


# ライブラリ
- 前回と同様、`Numpy` `Pillow` を使っています。


# 設定
- 以下の設定を追加しています。
- `DATASETS_PATH` に、今回作成するデータセットが保存されます。
- `IMG_ROWS` `IMG_COLS` は、画像サイズのリサイズです。今回は、`28 x 28` のサイズへリサイズします。
- 画像サイズは、後工程の学習モデルでも参照されます。

```config.py
DATASETS_PATH = os.path.join(DATA_PATH, 'datasets')

IMG_ROWS, IMG_COLS = 28, 28
```

# ファイル一覧の作成
- 学習画像、テスト画像、水増し画像のファイル一覧を作成します。
- query には、`CLASSES` が順次与えられます。
- `augment` 引数は、水増し画像の利用可否のフラグです。
- 前回、水増し画像は、`query` 毎に `6000` 作成しました。足りない場合は、エラーにしています。

```
def make_filesets(augment):
    """ファイルセットの作成."""

    filesets = {'train': dict(), 'test': dict(), 'augment': dict()}

    for query in CLASSES:

        train_path = os.path.join(TRAIN_PATH, query)
        test_path = os.path.join(TEST_PATH, query)
        augment_path = os.path.join(AUGMENT_PATH, query)

        if not os.path.isdir(train_path):
            print('no train path: {}'.format(train_path))
            return None
        if not os.path.isdir(test_path):
            print('no test path: {}'.format(test_path))
            return None
        if not os.path.isdir(augment_path):
            print('no augment path: {}'.format(augment_path))
            return None

        train_files = glob.glob(os.path.join(train_path, '*.jpeg'))
        train_files.sort()
        filesets['train'][query] = train_files

        test_files = glob.glob(os.path.join(test_path, '*.jpeg'))
        test_files.sort()
        filesets['test'][query] = test_files

        augment_files = glob.glob(os.path.join(augment_path, '*.jpeg'))
        random.shuffle(augment_files)
        filesets['augment'][query] = augment_files

        if augment and len(augment_files) < AUGMENT_NUM:
            print('less augment num: {}, path: {}'.format(len(augment_files), augment_path))
            return None

    return filesets
```


# 画像の読み込みの関数
- ファイルのフルパスを元に画像を処理します。
- 設定ファイルに従い、リサイズされます。
- もともと、OpenCV Haar Cascades では、リサイズを行わずに保存していました。後工程でリサイズする方が、様々なサイズで試すのに便利ですね。
- `LANCZOS` は、時間はかかるが、品質良くリサイズしてくれます。デフォルトは、`NEAREST` ですね。品質より、速さ優先です。
    - 参考: https://pillow.readthedocs.io/en/4.0.x/handbook/concepts.html#filters
- その後、グレースケールに変換し、さらに`uint8` に変換します。

```
def read_image(filename):
    """画像の読み込み、リサイズ、グレー変換."""

    image = Image.open(filename)
    image = image.resize((IMG_ROWS, IMG_COLS), Image.LANCZOS)
    image = image.convert('L')
    image = np.array(image, dtype=np.uint8)

    return image
```


# データセットの作成
- 学習画像、学習ラベル、テスト画像、テストラベルの配列を準備します。

```
def make_datasets(augment, filesets):
    """データセットの作成."""

    train_images = []
    train_labels = []
    test_images = []
    test_labels = []
```

- query には、`CLASSES` が順次与えられます。
- num には、ラベル が順次与えられます。
- 例えば、`CLASSES` の 最初は、`安倍乙` ラベルは `0` と言う感じです。
- `augment` で水増し画像を利用するか判断します。利用する場合は、`AUGMENT_NUM` に記載の数のみ`train_files` に設定します。
- 各画像の読み込みには、`tqdm` も合わせて利用しています。処理経過が表示され、分かりやすいですね。
- `read_image` に画像のファイルパスを与えて、リサイズ、グレースケール化した画像を読み込みます。
- 同時にラベルも付与します。

```
    for num, query in enumerate(CLASSES):
        print('create dataset: {}'.format(query))

        if augment:
            train_files = filesets['augment'][query][:AUGMENT_NUM]
        else:
            train_files = filesets['train'][query]
        test_files = filesets['test'][query]

        for train_file in tqdm.tqdm(train_files, desc='create train', leave=False):
            train_images.append(read_image(train_file))
            train_labels.append(num)
        for test_file in tqdm.tqdm(test_files, desc='create test', leave=False):
            test_images.append(read_image(test_file))
            test_labels.append(num)
```

- 学習画像、学習ラベル、テスト画像、テストラベルをデータセットとしてまとめます。
- `DATASET_PATH` `CLASSES` `IMG_ROWS` `IMG_COLS` 水増し画像の利用有無を元にデータセットのファイル名を決めます。

```
    datasets = ((np.array(train_images), (np.array(train_labels))), (np.array(test_images), (np.array(test_labels))))

    datasets_path = os.path.join(DATASETS_PATH, ','.join(CLASSES))
    os.makedirs(datasets_path, exist_ok=True)
    train_num = AUGMENT_NUM if augment else 0
    datasets_file = os.path.join(datasets_path, '{}x{}-{}.pickle'.format(IMG_ROWS, IMG_COLS, train_num))
    with open(datasets_file, 'wb') as fout:
        pickle.dump(datasets, fout)
    print('save datasets: {}'.format(datasets_file))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/bd07cf4b-843f-4422-60eb-2aff723cd7df.png)

- 水増し画像の利用有無は、下記のオプションで切り替えています。

```
$ python save_datasets.py

$ python save_datasets.py --augment
```

- pickle 化したデータセットは、下記の様になりました。
- オリジナルの場合は、`train` `test` の合計 約 `148MB` から `3.2MB` の pickleファイル
- 水増し画像の場合は、`augment` `test` の合計 約 `1433MB` から `46MB` の pickleファイル

```
$ du -d1 -h .
115M	./train
 33M	./test
 51M	./datasets
1.4G	./augment


$ ls
3.2M 12 15 23:22 28x28-0.pickle
46M 12 15 22:24 28x28-6000.pickle
```


# おわりに
- 学習プログラムから利用しやすい様に、画像データをリサイズ、グレースケールしたデータセットを作成しました。
- 複数のサイズ、水増し画像の数の変化などで、色々データセットを作り、ファイル名で切り替えながら利用が出来ます。
- 次回は、データセットを学習プログラムから読み込む部分を作成する予定です。