フレンズ画像を生成させてみたい-画像収集編+DCGAN

現在けものフレンズのキャラは300ぐらいあるそうです。この数ならGANsでそれっぽいキャラクターを生成できるのではないかという素人考えから、この課題に挑戦しています。
なかなか思うような結果が未だ出せていないので、まずはやってみた過程を記録しておこうと思います。もし「ここはこうした方が良い」といった意見があればぜひコメントをいただけると幸いです。

# データの収集

まずはデータを集める必要がありますが、そのデータは第三者の著作物であるという点に常に留意してください。

## wikiのスクレイピング

サービスは終了していますが、ゲームの攻略Wikiは今も残っています。有志によってスクリーンショットがまとめられており、背景をうまいこと処理さえできれば利用できそうです。

* https://kemono-friends.gamerch.com/
  * [キャラ一覧](https://kemono-friends.gamerch.com/%E3%82%AD%E3%83%A3%E3%83%A9%E4%B8%80%E8%A6%A7)

現状は、ここで躓いています。基本的に多くの画像で背景は共通なのでやりようはあると思っているのですが…

背景は赤・青・緑の3種類×コモン・レア・スーパーレアの3種類、全部で9種類あります。スクリーンショットであるため、解像度は統一されていません。おそらくはオリジナルの解像度より低いであろうと予想されますが、それほど高精細な画像の生成は考えていないので、GANsのデータとしては使えるだろうと思っています。

### スクレイピングスクリプト

python, BeautifulSoupで実装したダウンロードスクリプトを書きました。

```py3
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup
import urllib.request
import urllib.parse
import time
import os

wait_sec = 5

start_url = 'https://kemono-friends.gamerch.com/%E3%82%AD%E3%83%A3%E3%83%A9%E4%B8%80%E8%A6%A7'
base_url = 'https://kemono-friends.gamerch.com'
output_dir = 'save'

html = urllib.request.urlopen(start_url)
soup = BeautifulSoup(html, 'lxml')
elems = soup.select('a[href^="/"]')
vals = []
for e in elems:
    p = e.parent
    if p.name == 'td':
        vals.append(e)
#import pdb; pdb.set_trace()
time.sleep(wait_sec)

for v in vals:
    c = v['href']
    page_url = base_url + urllib.parse.quote(c)
    html = urllib.request.urlopen(page_url)
    soup = BeautifulSoup(html, 'lxml')
    imgs = soup.select('a[href$=".jpg"]')
    for i in imgs:
        jpg_url = i['href']
        fname = os.path.basename(jpg_url)
        fname = os.path.join(output_dir, fname)
        if os.path.exists(fname):
            #import pdb; pdb.set_trace()
            print("skip %s" % fname)
            continue
        with open(fname, "wb") as f:
            raw_img = urllib.request.urlopen(jpg_url).read()
            f.write(raw_img)
        #
        print("%s saved" % fname)
        time.sleep(wait_sec)
```

このスクリプトは一つのディレクトリに、同種の画像の重複も含め全部取得する作りになっています。背景の処理を考えると更に分類する必要があります。数が少ないので私は手で行いましたが、スクリプト化ぐらいはしようと考えています。

#### スクリプトの修正、公開

きちん属性情報、レアリティ情報を見て分類するように修正したコードを用意しました。githubにあります。

* https://github.com/knok/kemono-friends-images

2017/11/16追記: さらに色々と手を入れたりしたので個別の記事にしました。

* [フレンズ画像を生成させてみたい-画像収集編(2) wiki, twitter - Qiita](https://qiita.com/knok/items/a50a6f1fddb715a8eea2)

## コラボサイトからの収集

これとは別に、JRAがコラボサイトを立ち上げています。

* http://umabi.jp/kemono-friends/

この中には占いコンテンツがあり、外見から「これはアルファチャンネルを持ったデータを持っているのでは」と類推し調べてみたところ当たりでした。URLも連番となっていたので、以下のようなワンライナーで簡単に収集が可能です。

```shell-session
$ for i in $(seq 1 82); do \
 wget https://umabi.jp/kemono-friends/shindan/asset/img/result/character/$i.png; \
 sleep 5; done
```

これで収集できる画像数は82枚になります。解像度はすべて640x800のpngファイルです。非常にきれいなデータなので、背景除去を考える必要がありません。

### 2017/12/13追記

2017/12/28にてコラボサイトのクローズが決定しています。データが欲しい人は今のうちに取得しましょう。とはいえ、Internet Archiveに収録はされているようです。

* http://umabi.jp/kemono-friends/
* https://web.archive.org/web/20170719220003/ttps://umabi.jp/kemono-friends/shindan/asset/img/result/character/1.png (画像の一例)

## けものフレンズあらーむ(スマホアプリ)

スマホアプリがリリースされています。

* [けものフレンズあらーむ - Android アプリ](https://play.google.com/store/apps/details?id=com.tyrellsys.apps.kemonoalarm&hl=ja)
* [けものフレンズあらーむを App Store で](https://itunes.apple.com/jp/app/%E3%81%91%E3%82%82%E3%81%AE%E3%83%95%E3%83%AC%E3%83%B3%E3%82%BA%E3%81%82%E3%82%89%E3%83%BC%E3%82%80/id1268624173?mt=8)

このアプリには占い機能があり、そちらで使うキャラクター画像が含まれています(apk調べ)。ただし、解像度は353x500、キャラクター数61体と前述のJRAコラボサイトより得られる情報は少なくなります。

# chainer-gan-libを使う

現状一番きれいな状態で数多く集められたJRAコラボサイト画像を使うことを考えてみます。PFNetが公式にリリースしているchainer-gan-libには、多種多様なGANsが実装されているので、こちらで処理をしてみます。

## 下処理

元画像は解像度が高すぎるので適度に小さくします。NVIDIAが高解像度なGANの画像生成に成功していますが、そういった手法を使うのはまずある程度きちんとした結果を出せてからにするべきでしょう。

* [【やじうまPC Watch】NVIDIAのAI、“実在しない”セレブ写真の生成技術を獲得 - PC Watch](https://pc.watch.impress.co.jp/docs/news/yajiuma/1089073.html)

画像を32x32カラー、なおかつアルファチャンネルを除去するスクリプトを作り、処理しました。ImageMagickのconvertを使っています。

```bash
#!/bin/sh

INDIR=$1
OUTDIR=$2
SIZE=32x32

mkdir -p $OUTDIR

FILES=$(cd $INDIR; ls *)

for fname in $FILES
do
    convert $INDIR/$fname -resize $SIZE \
        \( +clone -alpha opaque -fill white -colorize 100% \) +swap \
        -geometry +0+0 -compose Over -composite -alpha off \
        -gravity center -extent $SIZE \
         $OUTDIR/$fname
done
```

このスクリプトは以下の記事を参考にしました。

* [コマンド一発で透過 png を綺麗に非透過 png にする](https://qiita.com/iwiwi/items/fdec3466c4dea5818b3a)


## ソースコードの準備

chainer-gan-libはCIFAR10を用いるコードしか用意されていないので、任意のデータを扱えるよう手を加えたコードをgithubに用意しました。

* https://github.com/knok/chainer-gan-lib

### データセットのnpz化

先のスクリプトで処理した画像を、npz形式にまとめられるようにしてあります。

```shell-session
$ python dataset/cifar10like.py --data-dir /path/to/32x32img image.npz
```

## DCGANでの訓練

絶対うまくいかないとは思いつつ、まずはベーシックな手法であるDCGANを試してみます。

```shell-session
$ python c10like-train.py -g 0 \ # c10like-train.py: 任意の32x32カラー画像を訓練するスクリプト
 --image-npz image.npz -o result-c10like-dcgan \ # result-c10like-dcgan に出力
 --algorithm dcgan --adam_alpha 0.0001 --adam_beta1 0.5 \
 --adam_beta2 0.9 --snapshot-iter 2000
```

### ログを見る

訓練途中の結果を見てみましょう。

```shell-session
$ cat result-c10like-dcgan/log
[
    {
        "loss_dis": 0.039919644594192505,
        "loss_gen": 9.003257751464844,
        "epoch": 78,
        "iteration": 100,
        "elapsed_time": 56.77760434150696
    },
    {
        "loss_dis": 0.0010271351784467697,
        "loss_gen": 9.35523509979248,
        "epoch": 156,
        "iteration": 200,
        "elapsed_time": 87.36827898025513
    }
]
```

Discriminatorの損失があっという間に0に近づいてしまっています。Generatorの損失も増加しています。これらは均衡するのが理想の状態なので、このまま訓練を継続してもmode collapseを起こした結果しか得られないでしょう。

## DCGANの考察

chainer-gan-libのDCGAN Generatorはデフォルトで潜在空間が128次元あります。データ数82に対してこれはあきらかに無駄に広いので、原因の一つはここにあると思われます。
その他、chainer-gan-libには他のさまざまな手法のGAN(WGAN, WGAN-gp, Cramer GAN, SNDCGAN)が実装されているので、それらを使うことでもより良い結果が得られるのではないかと予想されます。

一応、潜在空間を2次元にまで狭くしたときの試行結果についてもいくつか示しておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31714/aefa4cbc-8ae1-c1d6-24ab-90a5d54b02fd.png) ![image.png](https://qiita-image-store.s3.amazonaws.com/0/31714/a217919e-86c6-f734-1c50-5fdffc2a6d2e.png) ![image.png](https://qiita-image-store.s3.amazonaws.com/0/31714/e04fc621-a7f7-e06d-16f0-e5a588dd54a8.png)

最初の2つは一見それなりにいけてるっぽく見えるのですが、学習を継続すると完全にDiscriminator優位になってダメでした。最後の画像は完全にmode collapseを起こしています。

### Progressive Growing GAN

NVIDIAのProgressive Growing GANをChainerで実装されている方がいます。こちらを使えば高精細な画像を生成できそうです。

* [櫟井唯ちゃんの画像を無限に生成する話りぴーと - ｼﾞｮｲｼﾞｮｲｼﾞｮｲ](http://joisino.hatenablog.com/entry/2017/11/07/200000)
* [joisino/chainer-PGGAN: Progressive Growing of GANs implemented with chainer](https://github.com/joisino/chainer-PGGAN)

記事中に解説がありますが、ベースはWGAN-gpのようです。

# 今後

やはり82枚という画像数は心もとないので、Wikiにある画像の背景を除去して訓練データに使えるようにすることを考えてゆきたいと思います。ただ、今の所完全にはうまくいっていないので、「こういう手法を採ってみたけどダメだった」という内容になってしまいそうです。

生成に関しては、DCGAN以外の手法を試した結果を提示できればと思って言います。DCGANでも訓練の序盤はそれなりにいい感じの出力が出ているので、gradient penaltyが入っていればそれなりに収束しそうだという希望は持っています。