AnimeGANによるリアル画像をアニメ調に変換する方法

# はじめに
今回は、入力した画像を任意のアニメ画像風に変換するAnimeGANについて紹介します。

# AnimeGAN
**AnimeGAN**とは、Jie Chenらが提案した現実世界の写真をアニメ調に変換する技術のことです。Jieらは敵対的生成ネットワーク（GAN）を用いた深層学習モデルを使用することで、以下の問題点を解決するように取り組んでいます。
- 生成された画像から、アニメーションっぽいテクスチャが見つからない。
- 生成された画像から、もとの画像の風味が失われる。
- パラメータ量が多いため、学習および推論で大量のメモリ容量を必要とする。

この問題を解決するために、Jieらは独自の損失関数を定義して学習することで、現実世界の画像を高品質にかつ迅速に変換することを実現しています。
もし、具体的な提案手法が知りたい方は以下のリンクから論文を参照してください。

[論文](https://github.com/TachibanaYoshino/AnimeGAN/blob/master/doc/Chen2020_Chapter_AnimeGAN.pdf)


GANについて知りたい方は、こちらの記事を参考にしてください。

[GAN(敵対的生成ネットワーク)について説明します!](https://www.12-technology.com/2021/11/blendganai.html)

GANでは教師なし学習を行うため、教師画像のラベリングを行う必要がありません。つまり、面倒なアノテーションなどの手間が不要で自分の好きな画像で学習が可能となっています。
この記事では、**AnimeGANで提供されている宮崎駿風の画像生成の手順と、自分で用意した画像から学習および画像生成を行う手順を紹介します。**

# 環境構築
動作環境を以下に示す。試していませんが推論だけならCPUだけでも試せると思います。

| 項目 | 名称 |
| - | - |
|OS|Ubuntu20.04|
|CPU|32G|
|GPU|RTX2070SUPER|

続いて、インストールしたライブラリを以下に示す。すべてanacondaの仮想環境で動作することを確認しました。

- python 3.6
- tensorflow-gpu 1.15.0 (cuda 10.0.130, cudnn 7.6.0)
- opencv
- tqdm
- numpy
- glob
- argparse
- onnxruntime (If onnx file needs to be run.)

以下に仮想環境の構築方法の例を示す。GPUを使用するには、nvidia-smiなどが正しく導入されているかを確認してください。

```bash
$ conda create -n anime python=3.6
$ conda activate anime
$ conda install tensorflow-gpu==1.15.0 opencv tqdm
```

AnimeGANの改良版であるAnimeGANv2のリポジトリから資材をダウンロードする。

```
$ git clone https://github.com/TachibanaYoshino/AnimeGANv2
$ cd AnimeGANv2
```
自前の画像で学習したい場合は、README.mdを参考にして、vgg19.npyをダウンロードしてvgg19_wight直下に配置する。

# 実行方法
## 宮崎駿風の画像生成
すでに学習済みの重みパラメータが配置してあるため、すぐに実行できます。もし自分の用意した画像で試したいならば、--test_dirの指定ディレクトリを任意の画像を配置したディレクトリに変更してください。


ちなみに、学習には「風立ちぬ」の画像を使っているみたいです。データセットも公開しているのですが、それは著作権的にアウトです。この記事では学習済みのパラメータのみを使っているため、安心して作業を進めてください。学習済みパラメータは法律的にOKになったはず。。。


```bash
$ pwd
/home/user/AnimeGANv2
$ python test.py --checkpoint_dir checkpoint/generator_Hayao_weight --test_dir dataset/test/HR_photo --save_dir Hayao/HR_photo
```

## 北斗の拳風の画像生成
今回のディレクトリ構成を示す。

```
AnimeGANv2/
├ dataset/
│ └test/
│  └HR_photo
├ frames/
├ video/
│ └ input/
│   └ [任意のmp4ファイル](例：hokuto.mp4)
├ results/ 
├ video2frames.py
├ train.py
├ get_generator_ckpt.py
└ test.py
```

準備するのは２つです。

1. 任意のmp4ファイル
2. video2frames.py

まずは、学習したいスタイルのアニメ動画を用意します。今回は北斗の拳の1話を使用しました。
学習用の画像が1500枚前後生成できるとよいため、動画の長さは24分（24分*60秒=1440枚）のものを使用しています。

動画をvideo/input直下に配置したら、video2frames.pyを作成してください。ソースコードと実行コマンドは以下に記載しています。

```bash
$ pwd
/home/user/AnimeGANv2
$ python video2frames.py --input_path video/input/hokuto.mp4 --output_dir frames --seconds 1.0
$ mkdir dataset/Hokuto && cp frames/hokuto dataset/Hokuto/style
$ python edge_smooth.py --dataset Hayao --img_size 256
```

```python:video2frames.py
import cv2
from os import makedirs
from os.path import splitext, basename, join
import argparse

def parse_args():
    parser = argparse.ArgumentParser(description="Cut out video frame by frame")

    parser.add_argument('--input_path', type=str, default='sample_video.mp4',
                        help='video name')
    parser.add_argument('--output_dir', type=str, default='frames',
                        help='output directoty name')
    parser.add_argument('--seconds', type=float, default=1.0,
                        help='capture rate (sec)')
    return parser.parse_args()

def save_frames(video_path: str, output_dir: str, seconds: float):
    name = "image"
    ext = "jpg"
    w_size = 256
    h_size = 256
    
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        print("not found video")
        return
    video_name = splitext(basename(video_path))[0]
    output_frame_dir = join(output_dir, video_name)

    makedirs(output_frame_dir + "style", exist_ok=True)
    base_path = join(output_frame_dir + "style", name)
    idx = 0
    while cap.isOpened():
        idx += 1
        ret, frame = cap.read()
        if ret:
            if cap.get(cv2.CAP_PROP_POS_FRAMES) == 1:  # 0秒のフレームを保存
                frame = cv2.resize(frame, dsize=(w_size, h_size))
                cv2.imwrite("{}_{}.{}".format(base_path, "0000", ext),
                            frame)
            elif (idx / seconds) < cap.get(cv2.CAP_PROP_FPS):
                continue
            else:  # フレームを保存
                number = int(cap.get(cv2.CAP_PROP_POS_FRAMES)/idx)
                filled_number = str(number).zfill(4)
                frame = cv2.resize(frame, dsize=(w_size, h_size))
                cv2.imwrite("{}_{}.{}".format(base_path, filled_number, ext),
                            frame)
                idx = 0
        else:
            break

if __name__ == '__main__':
    args = parse_args()
    save_frames(args.input_path, args.output_dir, args.seconds)
```

学習用の画像が生成できたので、モデルの学習から画像生成までを実行します。もし別のGPUを使用していてメモリが足りなくなる場合は、batch_sizeの値を小さくしてみると動くかもしれません。反対にもっと高性能なメモリを積んでいる場合は、batch_sizeを大きくして動かすと学習効率が上がります。

```bash
# 学習
$ python train.py --dataset Hokuto --epoch 101 --init_epoch 10 --batch_size 4
# 重みの生成
$ python get_generator_ckpt.py --checkpoint_dir ../checkpoint AnimeGANv2_Hokuto_lsgan_300_300_1_2_10_1 --style_name Hokuto
# 画像生成
$ python test.py --checkpoint_dir checkpoint/generator_Hokuto_weight --test_dir dataset/test/HR_photo --save_dir Hokuto/HR_photo
```

# 出力結果
もとの画像、宮崎駿風の画像、北斗の拳風の画像の順で並べています。以下は宮崎駿の「風立ちぬ」と北斗の拳のサンプル画像です。この雰囲気に画像が変換できていればいいのですが、どうなったか確認してみましょう。

## サンプル
![001_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531981/e7619455-7e66-c615-dc74-2c400c5d2bce.jpeg)


## 人物
![007_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531981/0bfcd92d-8b5c-e3de-9e71-5bd74b81495e.jpeg)

色味とタッチは北斗の拳っぽい。現実の人物をアニメ調に調整するのは難しそう。少し前の風潮だが、アニメでもリアルに寄せている「アバター」くらいのものなら変換することは可能な気がする。

## 森林
![019_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531981/f40f662c-8077-4334-10f3-cf5a2191a372.jpeg)

真ん中の画像について、緑鮮やかな感じが宮崎駿のサンプル画像と似ている。対して右の画像は、ベタ塗りをしたような感じが北斗の拳の書き方に近いと思う。

## 水辺
![034_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531981/cc59ec2d-23d7-7768-0cb4-6fcac2a72b79.jpeg)

「千と千尋の神隠し」っぽい画像が出力された。これは実際に映画のワンシーンになっても違和感ない。

## 雪景色
![024_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531981/0c2ee505-64d1-d9b3-ef1d-b29e94e466e5.jpeg)

宮崎駿作品で雪の描写って記憶にないんですが、こんな感じになるんですかね。北斗の拳の1話って砂漠のシーンが多いからか、全体的に茶色っぽくなるように学習されています。

# 考察
確かにそれっぽくなっている画像はあるが、基本的にはう〜ん。。。という結果に見える。
色味は確かに似ているのだが、どことなくぼかした画像が生成されており、線などの輪郭が似ているとは言えない画像が多く生成されていた。

また、人物については思ったような性能は出ていなかった。輪郭などの線がくっきりしていないため、表現しきれていないことが原因かと思う。特に現実の人間をアニメのキャラクターにするには、「デフォルメ」が重要だと考えているため、それがうまくできていないことがうまく画像変換できない原因の一つであると思います。

# おわりに
今回は、**AnimeGANで提供されている宮崎駿風の画像生成と、自分で用意した北斗の拳の画像から学習および画像生成**を行いました。

風景などの写真はいい感じに変換できているものもあったが、期待しているようなデフォルメができるものではなかった。しかし、**自分で用意した画像から手軽に学習できるため、データの質がよければもっと性能がよい画像が生成できる**と思いました。

たまに似顔絵屋さんなどを見かけることがあるが、依頼主の顔の特徴を掴みながらも、作品のスタイルに合うようにデフォルメされている。
あれって、その人ならではの特徴を強調しつつも、表現したい画風に変換しながら崩していくというような作業をしているのだと思っている。どうでしょうか？

現在の技術では、画像の特徴を掴むという点は得意だと思うが、その特徴をアニメスタイルなど別の特徴と組み合わせることはまだ難しいと思う。
**もし、美しいという感覚、つまり、審美眼をAIが学習できれば、異なる特徴がうまく噛み合った画像が生成されると思います。**損失関数として審美眼をどうやって定式化するかが難しいですが、うまく学習に取り入れられるとさらなる深層学習モデルの製品化が進むと期待しています。

# 参考文献
[AnimeGANv2](https://tachibanayoshino.github.io/AnimeGANv2/)