Azure Custom Vision に投入する学習用画像データを imgaug を使って水増ししてみた

Azure Custom Vision を使ったシステムを作っています。

[Azure Custom Vision](https://azure.microsoft.com/ja-jp/services/cognitive-services/custom-vision-service/) を使うには、1つのタグに5つ以上の画像ファイルが必要です。

が、データ提供元から画像データが一つしかもらえなかったり、そもそも5ファイル程度では期待した精度が得られなかったりします。

こういう時、機械学習の世界では、学習用のデータを水増し(augmentation)することがよくあるようです。

* [機械学習のデータセット画像枚数を増やす方法 - Qiita](https://qiita.com/bohemian916/items/9630661cd5292240f8c7)
* [水増しと転移学習 (Vol.7)](https://products.sint.co.jp/aisia/blog/vol1-7)

機械学習のフレームワーク(Cognitive toolkit を含む)には、データの水増し機能が入っているそうですが、今回は Custom Vision を使いたいだけなので、CNTK は使わず、画像の水増しを行うライブラリを使ってみました。

## 画像の水増しライブラリをいくつか

* [aleju/imgaug: Image augmentation for machine learning experiments.](https://github.com/aleju/imgaug)
* [mdbloice/Augmentor: Image augmentation library in Python for machine learning.](https://github.com/mdbloice/Augmentor)

Python ばっかりやなー。C# や JavaScript 製のも探してみたのですが Popular なものは見つからず。
Python はハンズオンを一度経験しただけの状態ですが、トライしてみます。

ツールは「imgaug」を使うことにしました。日本語の解説記事がありましたので。

* [imgaugを使ってPythonで画像増幅を行う ｜ DevelopersIO](https://dev.classmethod.jp/etc/imgaug-introduction/)

## ついでに Docker も使おう

たしか Python って 2.x と 3.x がどちらも生きてるんだよねえ、(macOSだけど)環境構築面倒そう。
ということで Docker を使うことにしました。Docker で動くようにしておけば、ゆくゆくはまるっと FaaS 化できるよね、という期待もあります。

Docker は version 18.09.0 が入っているのでこのままで。
まず Docker イメージを作るための ``Dockerfile`` を記述します。

**Dockerfile**

```
FROM python:3

RUN pip install imgaug
RUN pip install opencv-python
RUN pip install imageio
```
python:3 をベースに imgaug と処理に必要なライブラリ各種をインストールしておきます。

そして ``Dockerfile`` のあるディレクトリで

```bash
docker build . imgaug
```

を実行すると、 imgaug という名の Docker イメージが作成されます。(↓は ``docker images`` で確認した様子)

```bash
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
imgaug              latest              1fb215f98c9a        About an hour ago   1.41GB
python              3                   1e80caffd59e        3 weeks ago         923MB
```

## imgaug で画像を水増しするコードを書く

次に imgaug で水増しを行う処理を Python で書きます。

``work/`` ディレクトリを作って、その中に ``run_aug.py`` を作成することにしましょうか。
あ、今のところは docker 関係なく。ホストコンピュータの方で作業します。

**run_aug.py**

```python
import imgaug as ia
from imgaug import augmenters as iaa
from matplotlib import pyplot as plt
import imageio
import glob
import os.path

def remove_glob(pathname):
    for p in glob.glob(pathname, recursive=False):
        if os.path.isfile(p):
            os.remove(p)

def main(dir_in, dir_out):
    # 出力先ディレクトリをクリーン
    remove_glob(dir_out + '/*')

    for filepath in glob.glob(dir_in +'/*'):
        print('in: ' + filepath)
        img = imageio.imread(filepath)

        # ノイズ
        noise(filepath, dir_out, img, [0.3, 0.4, 0.5])

# 加工後画像をファイルに保存する
def writeFile(filepath, dir_out, prefix, i, aug_img):
    filename = os.path.basename(filepath) # /data_in/img.jpg -> img.jpg
    root, ext = os.path.splitext(filename) # img.jpg -> (img, jpg)
    outpath = dir_out + '/' + root + '_' + prefix + '_' + str(i) + ext
    imageio.imwrite(outpath, aug_img)
    print('out: ' + outpath)

# ノイズを入れる
def noise(filepath, dir_out, img, params):
    i = 0
    for d in params:
        i = i + 1
        # 画像に変換を適用する
        augDropout = iaa.Dropout(p=d)
        aug_img = augDropout.augment_image(img)
        writeFile(filepath, dir_out, 'noise', i, aug_img)

dir = os.path.dirname(__file__) # 実行ファイルの場所
main(dir + '/images_in', dir + '/images_out')
```

これが C#er が初めて書いた Python のコードだｗ

ええと、 ``run_aug.py`` のあるディレクトリに ``images_in/``、 ``images_out/`` というディレクトリを作り、入力画像を ``images_in/`` に入れます。

``image_in/`` 内のファイルを列挙して、入力ファイルにノイズを入れます。 ``noise`` 関数に渡しているパラメータ ``[0.3, 0.4, 0.5]`` はノイズの濃さを示しており、ノイズ薄い・中間・濃いの3つのファイルが ``images_out/`` に出力されるという仕組みです。

## Docker で ``run_aug.py`` を実行する

Docker 内で、Python で ``run_aug.py`` を実行します。
``work/`` ディレクトリで、以下のコマンド一発です。

```
cd work

docker run --rm -v $(pwd):/temp imgaug python /temp/work/run_aug.py
```

一応解説。

* docker run : docker コンテナを作って起動します
* --rm : 実行が終わったら直ちにコンテナを削除します
* -v : ``$(pwd)``=現在のディレクトリを docker 内の ``/temp`` ディレクトリにマップします
* imgaug : docker イメージ名です(先に作ったやつ)
* python /temp/work/run_aug.py : docker 内で実行するコマンドです。Python で ``run_aug.py`` を実行します。

完了すると、 ``images_out/`` ディレクトリに、

```
sample_noise_1.jpg
sample_noise_2.jpg
sample_noise_3.jpg
```

の水増し画像ファイル群が出力されます。

## 完全版

これの完全版を github に公開しています。
ノイズ以外に「一部欠落」「回転」「移動」「剪断(shear)」を行っています。

* [amay077/imgaug_sample_with_docker: imgaug を使うサンプルプログラム（実行環境 docker 付き）](https://github.com/amay077/imgaug_sample_with_docker)

**完全版の実行結果**

![](https://github.com/amay077/imgaug_sample_with_docker/raw/master/result.png)

## おわりに

こうして水増しされた画像を、元画像とともに Custom Vision に投入して Train し、まずまず期待した検出が行えるようになりました。

水増しの加工の種類は imgaug にもたくさん用意されており、今回試したものが最適とは思えませんが、とりあえず検証を繰り返すための 「たたき台」 としては使えると思っています。

ひとつ疑問なのは、

**「Custom Vision の Train の過程で、自動的にデータの水増し(augmentation)を行っているのではないか？」**

ということです。特に根拠はなく、「CNTKには、データの水増し機能が入っている」という情報から勝手に思っているだけなのですが。

できれば機械学習そのものに対する学習はできるだけ避けてとおりたい（手が回らん）ので、簡単な手間で使用できる Custom Vision を始めとする各社の PaaS は、大変助かります。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Q:どのくらい勉強しなければならないのか？<br>A:時間軸次第。２～３年先には機械学習もパターン化すると予想。その後にはアルゴリズムを選択する必用もなくなると思う。 <a href="https://twitter.com/hashtag/roomH?src=hash&amp;ref_src=twsrc%5Etfw">#roomH</a> <a href="https://twitter.com/hashtag/decode16?src=hash&amp;ref_src=twsrc%5Etfw">#decode16</a></p>&mdash; あめいぱわーにおまかせろ! (@amay077) <a href="https://twitter.com/amay077/status/735333613085020160?ref_src=twsrc%5Etfw">2016年5月25日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

これ、2年前の de:code でスピーカーの方が話されていたことですが、だいぶその通りな世界になりつつあるなあという感じがします。