【画像生成】AutoencoderでDenoising, Coloring, そして拡大カラー画像生成♬

今回は、「AutoencoderでDenoising, Coloring, そして拡大画像生成」を取り上げます。
大切なことは**この３つ（元々のAutoencoder含め４つ）のことは、ほぼ同じプログラムでデータをそのように用意すればできるというところがミソ**です。
いつもなんとなく別々に説明されるので、難しいことかと思いがちですが、基本はAutoencoderなので簡単です。
【参考】
・[畳み込みオートエンコーダによる画像の再現、ノイズ除去、セグメンテーション](https://qiita.com/cvusk/items/019c254db883957b3050)

###簡単に今回の記事を要約すると以下のようになります。
すなわち、autoencoder-decoderの学習部分のfit関数を示します。

```py
vae.fit(x_train_gray,x_train_size,
                epochs=epochs,
                batch_size=batch_size,
                callbacks=callbacks,
                validation_data=(x_test_gray, x_test_size))
```
これは、

```
入力；x_train_gray（32，32，1）のgray画像
出力；x_train_size（64，64，3）のcolor画像
```
として学習し、

```
入力；x_test_gray（32，32，1）のテストgray画像
出力；x_test_size（64，64，3）のテストcolor画像
```
で検証することになります。
すなわち、絵にすると以下のような感じです。
![autoencoder.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/0a1c629d-9671-9c33-60a7-ed74e3263dae.jpeg)
**問題は、入力と出力を何にするかということです**
そして、ドメイン間を変換する関数がencoder-decoderということになり、ドメインの性質に合わせて適切なモデルを配置すればよいことになります。
ということで、いろいろなドメイン間の変換ができることを意味しています。
###今回やったこと
①入力：ノイズあり画像、出力；ノイズ無し画像
②入力；Gray画像、出力；カラー画像
③入力；32x32サイズGray画像、出力；64x64サイズカラー画像
###基本のコードは以下に置きました
以下はMNISTのノイズ除去のコードです
・[VAE/keras_conv2d_noise_reduction.py](https://github.com/MuAuan/VAE/blob/master/keras_conv2d_noise_reduction.py)
###①入力：ノイズあり画像、出力；ノイズ無し画像
####Denoisingのコード解説
一応、簡単に上記のコードの骨の部分を解説します。
以下のとおり、encoder-decoder-modelは[前回と同様](https://qiita.com/MuAuan/items/4391ad91e22585cd0d59)です。

```py
# encoder model
inputs = Input(shape=input_shape, name='encoder_input')
x = Conv2D(32, (3, 3), activation='relu', strides=2, padding='same')(inputs)
x = Conv2D(64, (3, 3), activation='relu', strides=2, padding='same')(x)
shape = K.int_shape(x)
print("shape[1], shape[2], shape[3]",shape[1], shape[2], shape[3])
x = Flatten()(x)
z_mean = Dense(latent_dim, name='z_mean')(x)

encoder = Model(inputs, z_mean, name='encoder')
encoder.summary()

# decoder model
latent_inputs = Input(shape=(latent_dim,), name='z_sampling')
x = Dense(shape[1] * shape[2] * shape[3], activation='relu')(latent_inputs)
x = Reshape((shape[1], shape[2], shape[3]))(x)
x = Conv2DTranspose(64, (3, 3), activation='relu', strides=2, padding='same')(x)
x = Conv2DTranspose(32, (3, 3), activation='relu', strides=2, padding='same')(x)
outputs = Conv2DTranspose(1, (3, 3), activation='sigmoid', padding='same')(x)
decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()

outputs = decoder(encoder(inputs))
vae = Model(inputs, outputs, name='vae_mlp')
vae.compile(loss='binary_crossentropy',optimizer='adam') 
```
**変更は、以下のように入力データとしてノイズを重畳**します。
【参考】
・[numpy.random.normal@SciPy.org](https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.normal.html)
loc;Mean (“centre”) of the distribution.
scale;Standard deviation (spread or “width”) of the distribution.
size;Output shape
np.clip;0,1を下回るまたは上回る場合に0，1に制限します

```py
noise_train = np.random.normal(loc=0.5, scale=0.5, size=x_train.shape)
noise_test = np.random.normal(loc=0.5, scale=0.5, size=x_test.shape)
# 学習に使うデータを限定する
x_train_noisy = np.clip(x_train + noise_train, 0, 1)
x_test_noisy = np.clip(x_test+ noise_test, 0, 1)
```
一番大切なのは、学習のとき、入力；x_train_noisy, 出力；x_trainで学習し、検証も入力；x_test_noisy, 出力；x_testで実施しています。
実は、**今回の３つのテーマではこの入力と出力を変更し、それらに合わせてネットワークなどをちょっと変更するだけで、いろいろな変換ができることがミソ**です。

```py
class Check_layer(keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs={}):
        if epoch%2==0:
            ...
            decoded_imgs = vae.predict(x_test_noisy[:n])
            plot_irregular(x_test,x_test_noisy,decoded_imgs,epoch=epoch)
ch_layer = Check_layer()
callbacks = [ch_layer]

# 学習
vae.fit(x_train_noisy,x_train,
                epochs=epochs,
                batch_size=batch_size,
                callbacks=callbacks,
                validation_data=(x_test_noisy, x_test))
```
###Denoisingの結果
以下のとおり、潜在空間10次元で100epochだと完全にノイズ除去してくれました。
![10autodetect_noise_reduction.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/449661e4-15a4-b6d8-30e5-850ec5d573e0.jpeg)
もう少し、見ると以下のとおり、綺麗に出力できました。
![cifar10_noisy_vae_0_90.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/67d20b2c-b1ac-5c25-a7e1-03b2d9c81e6b.png)
###②入力；Gray画像、出力；カラー画像
####Coloring
同じように、カラー画像であるCifr10でgray-colorの変換を実施してみます。
コード全体は、以下のとおりです。
・[VAE/keras_conv2d_coloring.py](https://github.com/MuAuan/VAE/blob/master/keras_conv2d_coloring.py)
そして、コードの大きな変更部分は、以下のとおりです。
１．描画サイズは慎重に変更しましょう
２．入力は（３２，３２，１）のGray画像で、出力は（３２，３２，３）のカラー画像
３．描画の中で、以下のとおり、カテゴリの色指定を以下のとおり１－Dに変換しています

```py
y_test1 = np.ravel(y_test)
sc=plt.scatter(z_mean[:, 0], z_mean[:, 1], c=y_test1,s=50,cmap=plt.cm.jet)
plt.colorbar(sc)
```
４．学習と検証に使うGray画像変換は以下の関数を使っています

```py
def rgb2gray(rgb):
    return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])
x_train_gray =  rgb2gray(x_train).reshape(len(x_train),32,32,1)
x_test_gray =  rgb2gray(x_test).reshape(len(x_test),32,32,1)
```
５．モデルは入出力に注意して、以下のようにしました

```py
# encoder model
inputs = Input(shape=input_shape, name='encoder_input')
x = Conv2D(32, (3, 3), activation='relu', strides=2, padding='same')(inputs)
x = Conv2D(64, (3, 3), activation='relu', strides=2, padding='same')(x)
shape = K.int_shape(x)
print("shape[1], shape[2], shape[3]",shape[1], shape[2], shape[3])
x = Flatten()(x)
z_mean = Dense(latent_dim, name='z_mean')(x)
encoder = Model(inputs, z_mean, name='encoder')
encoder.summary()

# decoder model
latent_inputs = Input(shape=(latent_dim,), name='z_sampling')
x = Dense(shape[1] * shape[2] * shape[3], activation='relu')(latent_inputs)
x = Reshape((shape[1], shape[2], shape[3]))(x)
x = Conv2DTranspose(64, (3, 3), activation='relu', strides=2, padding='same')(x)
x = Conv2DTranspose(32, (3, 3), activation='relu', strides=2, padding='same')(x)
outputs = Conv2DTranspose(3, (3, 3), activation='sigmoid', padding='same')(x)
decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()
outputs = decoder(encoder(inputs))
vae = Model(inputs, outputs, name='vae_mlp')
vae.compile(loss='binary_crossentropy',optimizer='adam')
```
６．学習は以下のようにしています
入力；x_train_gray,出力；x_train、そして検証は、入力；x_test_gray, 出力；x_testで学習しています。
相変わらず、`vae.compile(loss='binary_crossentropy',optimizer='adam')`
として、loss='binary_crossentropy'or 'mse'で実施しました。

```py
class Check_layer(keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs={}):
        if epoch%20==0:
            ...
            n=10
            decoded_imgs = vae.predict(x_test_gray[:n])
            plot_irregular(x_test,x_test_gray,decoded_imgs,epoch=epoch)
ch_layer = Check_layer()
callbacks = [ch_layer]
# autoencoderの実行
vae.fit(x_train_gray,x_train,
                epochs=epochs,
                batch_size=batch_size,
                callbacks=callbacks,
                validation_data=(x_test_gray, x_test))
```
####Coloringの結果
上段が元の絵で、中断が入力のGray画像、そして下段が色付けされた画像。
まあ、元の絵とは配色が異なりますが、一応塗れています。
100epoch
![cifar10_color_100.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/6a9414a3-3e03-2f87-7d28-147cbbf37665.jpeg)
###③入力；32x32サイズGray画像、出力；64x64サイズカラー画像
####Grayから拡大カラー画像生成
コード全体は以下のとおりです。
・[VAE/keras_conv2d_x4coloring.py](https://github.com/MuAuan/VAE/blob/master/keras_conv2d_x4coloring.py)
複雑な変換のようみ見えますが、上記と大きな違いは、元のCifar10画像を拡大して学習データを作成するところです。
入力のGray画像は上記のものを使います。
そして、カラー画像の拡大は以下のコードで実施します。
※**本来は高精細画像を縮小して学習すればもっと高精細な変換器が作成できる**はずですが、ここではさぼりました
そのためのコードは以下のとおりです。cv2.resizeを使います。
※サイズは今回は1060で計算したのでメモリーの関係で（64,64）としました
　ここには示しませんが1080、8MB だと（256,256）まで拡大できました。
　ただし、batch_size=8(128@1080マシン)など小さな値にしています

```py
img_rows, img_cols=64,64  #256,256 #128,128
X_train =[]
X_test = []
for i in range(len(x_train)):
    dst = cv2.resize(x_train[i], (img_rows, img_cols), interpolation=cv2.INTER_CUBIC)
    X_train.append(dst)
for i in range(len(x_test)):
    dst = cv2.resize(x_test[i], (img_rows, img_cols), interpolation=cv2.INTER_CUBIC)
    X_test.append(dst)
x_train_size = np.array(X_train)
x_test_size = np.array(X_test)
```

モデルは以下のとおりとしています。

```py
# encoder model
inputs = Input(shape=input_shape, name='encoder_input')
x = Conv2D(32, (3, 3), activation='relu', strides=2, padding='same')(inputs)
x = Conv2D(64, (3, 3), activation='relu', strides=2, padding='same')(x)
shape = K.int_shape(x)
print("shape[1], shape[2], shape[3]",shape[1], shape[2], shape[3])
x = Flatten()(x)
z_mean = Dense(latent_dim, name='z_mean')(x)
encoder = Model(inputs, z_mean, name='encoder')
encoder.summary()

# decoder model
latent_inputs = Input(shape=(latent_dim,), name='z_sampling')
x = Dense(shape[1] * shape[2] * shape[3], activation='relu')(latent_inputs)
x = Reshape((shape[1], shape[2], shape[3]))(x)
x = Conv2DTranspose(64, (3, 3), activation='relu', strides=2, padding='same')(x)
x = Conv2DTranspose(32, (3, 3), activation='relu', strides=2, padding='same')(x)
x = Conv2DTranspose(16, (3, 3), activation='relu', strides=2, padding='same')(x)
outputs = Conv2DTranspose(3, (3, 3), activation='sigmoid', padding='same')(x)
decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()
outputs = decoder(encoder(inputs))
vae = Model(inputs, outputs, name='vae_mlp')
vae.compile(loss='mse',optimizer='adam')
```
####拡大カラー画像生成
描画時点で拡大の効果は消えちゃいますが、それぞれ拡大して上記の画像と比較すると、ギザギザが消えて少し綺麗になったように思います。以下、20epochと100epoch時点の出力です。
20epoch
![cifar10_color_20.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/1535507b-0b72-0647-5bc8-8e1509a50f34.jpeg)
100epoch
![cifar10_color_100.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/5955580d-4cbd-df54-52db-46fe83ea7a15.jpeg)
###まとめ
・Autoencoder-decoderでDenoising、Coloring、そして画像拡大をやってみた
・ほぼ同じ仕組みで実施できることを示した
・精度はまだまだだが、潜在空間の次元を増やして一定精度がえられた

・さらなる精度改善を実施する
・アニメ画像やコマ補間から動画生成も同様な方法で実施できるのでやってみようと思う
###おまけ
綺麗な画像生成するためには、潜在空間の次元をなるべく大きくするとより綺麗な画像が生成できます。ということで、今回は以下のように4096次元としています。

```
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
encoder_input (InputLayer)   (None, 32, 32, 1)         0
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 16, 16, 32)        320
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 8, 8, 64)          18496
_________________________________________________________________
flatten_1 (Flatten)          (None, 4096)              0
_________________________________________________________________
z_mean (Dense)               (None, 4096)              16781312
=================================================================
Total params: 16,800,128
Trainable params: 16,800,128
Non-trainable params: 0
_________________________________________________________________
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
z_sampling (InputLayer)      (None, 4096)              0
_________________________________________________________________
dense_1 (Dense)              (None, 4096)              16781312
_________________________________________________________________
reshape_1 (Reshape)          (None, 8, 8, 64)          0
_________________________________________________________________
conv2d_transpose_1 (Conv2DTr (None, 16, 16, 64)        36928
_________________________________________________________________
conv2d_transpose_2 (Conv2DTr (None, 32, 32, 32)        18464
_________________________________________________________________
conv2d_transpose_3 (Conv2DTr (None, 64, 64, 16)        4624
_________________________________________________________________
conv2d_transpose_4 (Conv2DTr (None, 64, 64, 3)         435
=================================================================
Total params: 16,841,763
Trainable params: 16,841,763
Non-trainable params: 0
_________________________________________________________________
```