【StyleGAN入門】「ある男性の生涯」で遊んでみた♬

はっきり言ってExampleを利用するだけで、よく流れている動画（男性⇒女性とかの変換）はすぐ再現出来、やってみるとほんとに凄い。
今回は、その一番の入り口だけやってみての紹介記事です。
参考は以下のとおりです。
【参考】
①[StyleGANを使ってみた](https://qiita.com/nishiha/items/9bf2ea93d787477eaaf1)
②[StyleGanで福沢諭吉を混ぜてみる](https://qiita.com/nishiha/items/931ecb3541096a4c175b)
③[Puzer/stylegan-encoder](https://github.com/Puzer/stylegan-encoder)
④[NVlabs/stylegan](https://github.com/NVlabs/stylegan)
現在、[styleGAN2](https://github.com/NVlabs/stylegan2)も出ているようですが、まずはStyleGANの最初の一歩をやってみて、実感を持ちたいと思います。
その結果、ほぼ1日で以下の動画が作成できたので、気になったところを記事にしておきます。
![example17_18.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/f676eb74-1896-4bfe-3f98-e0556ea19754.gif)
※老人～幼児～老人に変化するものも作成しましたが、$512^2$でも10MB超えてしまいました
###やったこと
・環境
・pretrained_example.pyを動かす
・潜在空間を動かす
・Gifアニメーション
###・環境
先日Pytorchをインストールした関係でKeras-gpu環境が壊れているので、ゼロからインストールしました。
マシン；1060ベース
OS；Windows10
基本は、[以前の記事のとおり](https://qiita.com/MuAuan/items/7bdad74f423958350189)です。
ただし、Chainerなどはインストールしません。
環境構築終わったところで、Tensorflowにエラーがでました。
ということで、以下の参考のとおりTensorflow=1.13.1に入れ替えをしています。
入れ替えた後、うまく動かないので再起動を実施しています。
【参考】
⑤[Anacondaのconda installでビルドを指定する方法](https://qiita.com/ground0state/items/48f556479b770e31d599)
これで安定して動いてくれました。
エビデンスは以下のとおり（．．．の部分にワーニングが出ていますが今回は影響ないので削除しています）

```
(keras-gpu) C:\Users\user\stylegan-encoder-master>python
Python 3.6.9 |Anaconda, Inc.| (default, Jul 30 2019, 14:00:49) [MSC v.1915 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
．．．
>>> print(tf.__version__)
1.13.1
```
###・pretrained_example.pyを動かす
これを動かすには、参考①と同様、コード内ではWeightsがダウンロードできませんでした。
そこで、以下の参考⑦からダウンロードしてそれをTensorflow版を参考①と同様に配置しています。
参考⑥は面白そうですが、まだまだ理解するのは大変なので次回に後回しします。
【参考】
⑥[StyleGANを遊び尽くせ!! ~追加学習不要の画像編集~](https://qiita.com/pacifinapacific/items/1d6cca0ff4060e12d336)
⑦[pacifinapacific/StyleGAN_LatentEditor](https://github.com/pacifinapacific/StyleGAN_LatentEditor)

ということで、最初のコードは以下のとおりです。

```pretrained_example.py
# Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
#
# This work is licensed under the Creative Commons Attribution-NonCommercial
# 4.0 International License. To view a copy of this license, visit
# http://creativecommons.org/licenses/by-nc/4.0/ or send a letter to
# Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.

"""Minimal script for generating an image using pre-trained StyleGAN generator."""

import os
import pickle
import numpy as np
import PIL.Image
import dnnlib
import dnnlib.tflib as tflib
import config

def main():
    # Initialize TensorFlow.
    tflib.init_tf()
    # Load pre-trained network.
    #url = 'https://drive.google.com/uc?id=1MEGjdvVpUsu1jB4zrXZN7Y4kBBOzizDQ' # karras2019stylegan-ffhq-1024x1024.pkl
    #with dnnlib.util.open_url(url, cache_dir=config.cache_dir) as f:
    #    _G, _D, Gs = pickle.load(f)
    fpath = './weight_files/tensorflow/karras2019stylegan-ffhq-1024x1024.pkl'
    with open(fpath, mode='rb') as f:
        _G, _D, Gs = pickle.load(f)
        # _G = Instantaneous snapshot of the generator. Mainly useful for resuming a previous training run.
        # _D = Instantaneous snapshot of the discriminator. Mainly useful for resuming a previous training run.
        # Gs = Long-term average of the generator. Yields higher-quality results than the instantaneous snapshot.
    # Print network details.
    Gs.print_layers()
    # Pick latent vector.
    rnd = np.random.RandomState(5)
    latents = rnd.randn(1, Gs.input_shape[1])
    # Generate image.
    fmt = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True)
    images = Gs.run(latents, None, truncation_psi=0.7, randomize_noise=True, output_transform=fmt)
    # Save image.
    os.makedirs(config.result_dir, exist_ok=True)
    png_filename = os.path.join(config.result_dir, 'example.png')
    PIL.Image.fromarray(images[0], 'RGB').save(png_filename)

if __name__ == "__main__":
    main()
```
このコードの肝は、以下でGenerator Gsの入力初期値として同サイズの乱数を与えて、
`latents = rnd.randn(1, Gs.input_shape[1])`
それを使って以下で画像に変換している部分です。
`images = Gs.run(latents, None, truncation_psi=0.7, randomize_noise=True, output_transform=fmt)`
【参考】
⑧[Numpyによる乱数生成まとめ](https://qiita.com/yubais/items/bf9ce0a8fefdcc0b0c97)
このコードで以下が出力しました。
![example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/e5d0ba57-cf3a-cf7e-dabd-f8f44551b90c.png)
###・潜在空間を動かす
次は何をするかと言えば、上記のlatentsの乱数を変更して出力してみたいところです。
ということで、以下のコードでやってみました。（主要な部分のみ記載すると）

```PY
def main():
    # Initialize TensorFlow.
    tflib.init_tf()
    fpath = './weight_files/tensorflow/karras2019stylegan-ffhq-1024x1024.pkl'
    with open(fpath, mode='rb') as f:
        _G, _D, Gs = pickle.load(f)
    # Print network details.
    Gs.print_layers()

    # Pick latent vector.
    rnd = np.random.RandomState(5) #5
    latents1 = rnd.randn(1, Gs.input_shape[1])
    latents2 = rnd.randn(1, Gs.input_shape[1])
    for i in range(1,101,4):
        latents = i/100*latents1+(1-i/100)*latents2
        # Generate image.
        fmt = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True)
        images = Gs.run(latents, None, truncation_psi=0.7, randomize_noise=True, output_transform=fmt)
        # Save image.
        os.makedirs(config.result_dir, exist_ok=True)
        png_filename = os.path.join(config.result_dir, 'example{}.png'.format(i))
        PIL.Image.fromarray(images[0], 'RGB').save(png_filename)
```
すなわち、乱数ではあるが連続するlatents1 latents2を計算してその間をパラメータを変更して描画してみるということです。
この操作で見事に女性から男性１そして男性２に滑らかに変更していく様子が出力します。
![latents12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/5cf4f29f-68c3-7f98-5324-b1d61f10bfef.jpeg)
ここでは、さらにもう一歩潜在空間を彷徨う方法を考えました。
すなわち、以下のコードで当該部分を置き換えます。
これで、潜在空間の一部ですがどんな画像が隠れているか見えると思います。

```py
    # Pick latent vector.
    rnd = np.random.RandomState(6) #5
    for i in range(1,101,4):
        latents = rnd.randn(1, Gs.input_shape[1])
```
以下が得られました。
![latents.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/87ae0f13-e08b-fb88-ce74-2d52d7c6aea1.jpeg)
こうなると、やはりこの画像の中の任意の人を選んでその遷移を見たくなります。
ということで、一番変化の大きそうなexample73の男性とexample69の子供の絵をやってみました。たまたま隣り合っていますが…
コードは以下になります。

```py
    # Pick latent vector.
    rnd = np.random.RandomState(6) #5
    latents_=[]
    a=17
    b=18
    for i in range(1,101,4):
        latents = rnd.randn(1, Gs.input_shape[1])
        latents_.append(latents)
        
    for j in range(25):
        latents_mean=j/25*latents_[a]+(1-j/25)*latents_[b]
        # Generate image.
        fmt = dict(func=tflib.convert_images_to_uint8, nchw_to_nhwc=True)
        images = Gs.run(latents_mean, None, truncation_psi=0.7, randomize_noise=True, output_transform=fmt)
        # Save image.
        os.makedirs(config.result_dir, exist_ok=True)
        png_filename = os.path.join(config.result_dir, 'example{}.png'.format(j))
        PIL.Image.fromarray(images[0], 'RGB').save(png_filename)
```
出力結果は以下のように期待以上の結果がでました。
![boy2chinren.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/e270a0eb-81b7-2b23-324d-06038071e606.jpeg)
子供がほんとにおじさんに成長したような絵が出ているのがある意味不思議です。
###・Gifアニメーション
最後にみんながやっているようにGifアニメーションにしたいと思います。
それには以下のコードを最後に置けばgifが保存されます。
※ここでは最初から最後まで行ったところで逆回しして最初に戻るようにしています

```py
    s=50
    images = []
    for i in range(25):
        im = Image.open(config.result_dir+'/example'+str(i)+'.png') 
        im =im.resize(size=(512, 512), resample=Image.NEAREST)
        images.append(im)
    for i in range(24,0,-1):
        im = Image.open(config.result_dir+'/example'+str(i)+'.png') 
        im =im.resize(size=(512, 512), resample=Image.NEAREST)
        images.append(im)    
    
    images[0].save(config.result_dir+'/example{}_{}.gif'.format(a,b), save_all=True, append_images=images[1:s], duration=100*2, loop=0)    
```
以下の結果は、掲載サイズの関係でsize=(256, 256)に変更しています。
また、6の2値間補間を除いてs=25で出力しています。
※冒頭のGifアニメは$512^2$です。
　実際$1024^2$だとほんとに美しい絵が出力して驚くばかりです

| 乱数$seed$ | 2値間補間 |部分空間|
|:-:|:-:|:-:|
|6|![example17_18_256.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/81aecc9a-7ffd-1ac7-403d-c9f1cb0d4873.gif)|![example0_1_256.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/2475eddf-2854-efbd-75d4-297642d8a625.gif)|
|5|![example0_1_256.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/0e48aeb5-f1fd-0f48-007f-6fb357c8d21d.gif)|![example0_1_256.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/f33931aa-39fe-320e-ea63-14d5bf995579.gif)|
###まとめ
・環境構築が出来れば動かせると思う
・素晴らしい画像補間ができる

・このアプリだけでも応用はいろいろ考えられるので自前画像でやってみようと思う
・さらに発展的にいわゆる動画に挑戦したいと思う