画像の水増し方法をTensorFlowのコードから学ぶ

<!--
http://ai-programming.hatenablog.jp/entry/2016/03/05/150854
画像認識の前処理をTensorFlowのコードから学ぶ
前処理っていうか画像の水増しかな
reshapeも含まれる？
画像の水増しをTensorFlowのコードから学ぶ
この辺参考に画像水増しの意義を説明する。
・http://qiita.com/bohemian916/items/9630661cd5292240f8c7
-->

# はじめに
Deep Learningで画像を精度よく分類するにはとにかく枚数が必要です。しかし、大量の画像をすべて手作業で用意・タグ付けするのは困難です。そこで、タグ付けされた画像を加工することで画像の枚数を増やす(水増しする)ことが行われます。
<img width="850" alt="スクリーンショット 2016-09-07 13.40.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/ced27f05-9ff0-e2ad-e759-e1c88cae13a8.png">

今回は、水増しするためにどのようなことをするのかをTensorFlowのコードから学びたいと思います。

具体的にはCIFAR-10のコードから学んでいきます。
[cifar10/cifar10_input.py](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/models/image/cifar10/cifar10_input.py)

実際のコードでは以下のように複数の処理を組み合わせて画像の水増しを行っていました。

```py3
# Image processing for training the network. Note the many random
# distortions applied to the image.

# Randomly crop a [height, width] section of the image.
distorted_image = tf.random_crop(reshaped_image, [height, width, 3])

# Randomly flip the image horizontally.
distorted_image = tf.image.random_flip_left_right(distorted_image)

# Because these operations are not commutative, consider randomizing
# the order their operation.
distorted_image = tf.image.random_brightness(distorted_image, max_delta=63)
distorted_image = tf.image.random_contrast(distorted_image, lower=0.2, upper=1.8)

# Subtract off the mean and divide by the variance of the pixels.
float_image = tf.image.per_image_whitening(distorted_image)
```

一つずつ見ると、CIFAR-10のコード中では以下の5つの処理が行われていました:

* random_crop
* random_flip_left_right
* random_brightness
* random_contrast
* per_image_whitening

それぞれが何をしているのか**視覚的に**理解していきます。

# [tf.random_crop(value, size, seed=None, name=None)](https://www.tensorflow.org/versions/r0.10/api_docs/python/constant_op.html#random_crop)
random_crop関数は与えられたサイズ(size)で画像をランダムにトリミングするための関数です。
以下の画像は、実際に256x170の画像に対して、size=100x100でトリミングした結果です:

<img width="671" alt="スクリーンショット 2016-09-07 12.26.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/44ee00c5-b79f-210a-7708-abc48b7227e7.png">

seedに与える値によって、トリミングする位置が変化します。同じseed値なら何度実行しても同じ画像が生成されます。


# [tf.image.random_flip_left_right(image, seed=None)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_flip_left_right)

random_flip_left_right関数は画像をランダムで水平方向に反転するための関数です。
以下の画像は、実際にrandom_flip_left_right関数を適用してみた結果です:
<img width="559" alt="スクリーンショット 2016-09-07 13.21.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/9814b472-3924-425d-8898-a3b5de2ba4fe.png">

確率的に反転が行われるので、seedの値によっては反転されないこともあります。

random_flip_left_rightに似た関数として、[random_flip_up_down](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_flip_up_down)という関数もあります。random_flip_left_rightが水平方向に反転するのに対して、[random_flip_up_down](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_flip_up_down)は垂直方向に反転を行います。


# [tf.image.random_brightness(image, max_delta, seed=None)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_brightness)

random_brightness関数はランダムな要因によって画像の明るさを調整するための関数です。
以下の画像は、実際にrandom_brightness関数を適用してみた結果です:
<img width="850" alt="スクリーンショット 2016-09-07 13.40.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/ced27f05-9ff0-e2ad-e759-e1c88cae13a8.png">

max_deltaとseedの値によって、明るさの度合いが変わります。


# [tf.image.random_contrast(image, lower, upper, seed=None)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_contrast)

random_contrast関数はランダムな要因によって画像のコントラストを調整するための関数です。
以下の画像は、実際にrandom_contrast関数を適用してみた結果です:
<img width="848" alt="スクリーンショット 2016-09-07 13.46.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/3e4bdda0-5c9a-43f9-83f3-cdcafb973b66.png">

Contrast1はコントラストが低減され、Contrast2はコントラストが強調されていることがわかります。強弱の下限と上限は、lowerとupperで調整することができます。


# [tf.image.per_image_whitening(image)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#per_image_whitening)

per_image_whitening関数は平均が0になるように画像を白色化するための関数です。
以下の画像は、実際にper_image_whitening関数を適用してみた結果です:

<img width="555" alt="スクリーンショット 2016-09-07 13.50.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/fd325b2a-d295-bc7b-fab3-3ce9091d3360.png">


実際は各ピクセル値を(x - mean) / adjusted_stddevで計算しています。meanは画像内のすべてのピクセル値の平均で、adjusted_stddevはadjusted_stddev = max(stddev, 1.0/sqrt(image.NumElements()))と定義されます。ここで、stddevは画像内のすべてのピクセル値の標準偏差です。

<!--
均一な画像を扱うときに0による除算から保護するために、ゼロから離れてキャップされています。

一枚の画像しか考慮していません。
-->


# その他
TensorFlowのCIFAR-10のexampleでは使っていないものの、水増しに使えそうな関数は他にもあります。それらについて5つほど紹介します。
## [tf.image.transpose_image(image)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#transpose_image)
transpose_image関数は、画像を転置してくれる関数です。
以下の画像は、実際にtranspose_image関数を適用してみた結果です:
<img width="468" alt="スクリーンショット 2016-09-08 9.08.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/5be51dd3-30f1-840b-1b42-b843aa4c2292.png">

転置するだけなので、何回実行しても同じ結果です。転置した画像を更に転置すれば元の画像に戻ります。


## [tf.image.rot90(image, k=1)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#rot90)
rot90関数は、90度ごとに画像を反時計回りに回転させます。
以下の画像は、実際にrot90関数を適用してみた結果です:
<img width="919" alt="スクリーンショット 2016-09-08 9.12.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/1ce40476-cf69-fbbe-e7c1-80a72b7ac8e0.png">

kの値を変更することで、何度回転させるかを指定することができます。


## [tf.image.random_hue(image, max_delta, seed=None)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_hue)
random_hue関数は、ランダムな要因によりRGB画像の色相を調整するための関数です。
以下の画像は、実際にrandom_hue関数を適用してみた結果です:
<img width="819" alt="スクリーンショット 2016-09-08 9.17.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/d1b308d4-df6a-7335-7498-61986ac0f62f.png">

max_deltaは0から0.5の範囲である必要があります。


## [tf.image.random_saturation(image, lower, upper, seed=None)](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html#random_saturation)
random_saturation関数は、RGB画像の彩度を調整するための関数です。
以下の画像は、実際にrandom_saturation関数を適用してみた結果です:
<img width="819" alt="スクリーンショット 2016-09-08 9.21.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/77079/e17b4607-000c-4e15-d7f0-a940e9f39e43.png">

<!--
This is a convenience method that converts an RGB image to float representation, converts it to HSV, add an offset to the saturation channel, converts back to RGB and then back to the original data type. If several adjustments are chained it is advisable to minimize the number of redundant conversions.

image is an RGB image. The image saturation is adjusted by converting the image to HSV and multiplying the saturation (S) channel by saturation_factor and clipping. The image is then converted back to RGB.
-->

## 拡大・縮小
拡大・縮小については以下にまとめたのでそちらを参照してください:

* [TensorFlowのResizeがよくわからなかったので視覚的にまとめてみた](http://qiita.com/Hironsan/items/d2a6364221c588867a60)

<!--
# 本当に精度は上がるのか?
画像を水増しする場合としない場合で精度が変化するのかCIFAR-10のコードを動かして確認してみました。
30000ステップ動かしたところ以下のような結果になりました。

|   | precision |
|---|---|
| 水増しあり  | 0.847  |
| 水増しなし  | 0.851  |
-->

<!--
# おわりに
今回は、TensorFlowのコードから画像の水増し方法について学んでみました。
冒頭にも書いたように、CIFAR-10では複数の方法を組み合わせて一枚の画像を生成していました。
-->

# 参考

* [tensorflow/image](https://www.tensorflow.org/versions/r0.10/api_docs/python/image.html)