Help us understand the problem. What is going on with this article?

ニートの僕が幼女の顔生成に挑戦〜PGGANを使った顔生成モデルの学習からWeb公開までの知見

1.はじめに

「PGGAN」1と「heroku」を使って幼女の顔生成サービスを開発したノウハウを紹介します。

ゆる〜くプログラミングをすることが目標なので、この記事では詳細な技術的なことには全く触れず、「自分で用意した画像を使ってPGGANを学習させたい」と考えている人向けに記載しています。
皆さんのサービス開発時の検討材料になれば幸いです。

プログラミング初心者なので、間違いなどあるかもしれません。
ご指摘いただけると嬉しいです。

PGGANの特徴

  • 学習が安定している
  • 大きい画像をきれいに生成できる
  • 多様性のある画像が生成できる

ということのようです。
NVIDIAが公開している結果を見ると、その凄さがわかるかと思います。
Generated celeb sample
PGGANにより生成された顔画像
動画

詳しい解説は、とてもわかりやすく解説されている方がいらっしゃるので、こちらと、もとの論文を是非参考にしてください。
すごく参考にさせていただきました。
ありがとうございます。

開発したサービス

face-morpher.gif

幼女ジェネレーター
ちゃっかり紹介。誰でも幼女を作成・共有できるサービスです。
技術的には、幼女データの保持・共有などの処理をDjangoで実現しています。

2.実装

ゆるーく実装していきましょう
解説を読んで、なんとなく理解したつもりになったら早速開始です。

モデル

オリジナルはTheanoで書かれているのですが、ありがたいことにTensorFlowに書き直して、なおかつ便利機能まで付けてくださっている方がいるので使わせていただきました。
※論文執筆者のTero Karrasさんでした

https://github.com/tkarras/progressive_growing_of_gans

最終的にCPUで動かしたいのでGeneratorのConvolutional Layerのdata_formatをNCHWからNHWCに変更します。
最終的な推論にはGeneratorのみ使用するので、DiscriminatorはそのままでもOKです。

networks.py
return tf.nn.conv2d_transpose(x, w, os, strides=[1,1,2,2], padding='SAME', data_format='NCHW')

#こんなふうに変更、何箇所かあります。

x = tf.transpose(x, perm=[0, 2, 3, 1])
x = tf.nn.conv2d_transpose(x, w, os, strides=[1,2,2,1], padding='SAME', data_format='NHWC')
x = tf.transpose(x, perm=[0, 3, 1, 2])
return x

モデル自体も小さくします
これで、Herokuのメモリに乗る500MB程度までモデルサイズを小さくすることができます。

config.py
grid = EasyDict(size='128', layout='random')
G = EasyDict(func='networks.G_paper', fmap_max=256, fmap_base=4096)
D = EasyDict(func='networks.D_paper', fmap_max=256, fmap_base=4096) 

データセット

いかがわしいサイトから幼女の画像をスクレイピングしてきます。
児童ポルノには十分に気をつけましょう!

  • Python
  • BeautifulSoup4
  • Celenium

この3つを使ってスクレイプングを行いました。
もとのデータセットは60万枚ほどなのですが、とりあえず5000枚を目安に集めました。

画像の前処理

論文によると、次の前処理を行うと、きれいな生成画像ができるそうです。
Screenshot from 2018-12-14 19-52-16.png
データセット作成プロセス

a). 元画像
b). JPEGノイズの除去とアップサンプリング
c). 画像をmirror paddingして切り抜いたときに背景色にならないようにする
d). 顔以外似ガウスフィルターをかけて顔にピントがあってるようにする
e). 顔のlandmark locations(鼻とか目の位置)から切り抜く範囲を決定する

b) ノイズ除去とアップサンプリング

今回は、128x128の画像を出力する予定なので行いませんでした。

c) mirror padding

Pillowを使って反転,ペースト。

d) 背景をぼかす

この工程を行っていないサンプルもあるので、必ずしも必須の工程ではないようです。
より高解像度の画像を生成する必要がある場合は次の技術で実現はできるようです。
aaa
Automatically Remove Backgrounds From Images

e) facial landmarkの正規化

論文にでている次の式を使って顔を正規化します。

x' = e_1 - e_0\\
y' = \frac{1}{2}(e_1 + e_0) - \frac{1}{2}(m_0 + m_1)\\
c = \frac{1}{2}(e_1 + e_0) - 0.1\cdot y'\\
s = max(4.0\cdot|x'|, 3.6\cdot|y'|)\\
x = Normalize(x' - Rotate90(y'))\\
y = Rotate90(x)\\

※式が間違っていたので修正しました(2019-06-14)

$e_0, e_1$ はそれぞれ左右の目の中心
$m_0, m_1$ はそれぞれ左右の口の端
cは切り抜く中心
sは一辺の大きさ
x, yは、縦横の方向ベクトルです。

facial landmarkの検出にはdlibをより簡単にしたライブラリのface_recognitionを使いました。
あまりに横向きの顔はいらないので、face detectorはhogで
あとはPillowでゴニョゴニョと書きます。

128x128にダウンサイジングして、幼女データセットの完成です。

animation.gif
正規化済み顔画像

水増し(Data Augmentation)

今回の場合のようなCNNを使った画像処理では、データの水増しが効果を発揮します。
今回は

  • 左右反転
  • 変形

の2つで水増しを行いました。

KerasやTensorFlowにはこのような水増し機能が用意されているようなのですが、これを書いているときに知ったのでPillowで実装しました。

左右反転

Pillowで反転

変形

以前作った顔の変形を行うサイトがあるので、その技術を使って顔画像を変形させました。
WONS-ビューティー効果復元サイト

目、鼻、口、顎の大きさを少しずつ変えています。
今回は、変形して画像を10倍に水増ししました。
結果はこんな感じ
animation.gif
変形済み顔画像

TFRecord形式にデータを変換

新しく関数を定義してあげましょう

dataset_tool.py
def create_youjo(tfrecord_dir, img_dir, cx=128, cy=128):
    print('Loading youjo img from "%s"' % img_dir)
    glob_pattern = os.path.join(img_dir, '*.png')
    print(glob_pattern)
    image_filenames = sorted(glob.glob(glob_pattern))
    print(len(image_filenames))

    with TFRecordExporter(tfrecord_dir, len(image_filenames)) as tfr:
        order = tfr.choose_shuffled_order()
        for idx in range(len(image_filenames)):
            img = PIL.Image.open(image_filenames[order[idx]])
            img = np.asarray(img.convert('RGB'))
            if img.shape != (128, 128, 3):
                print('[error] img shape not match.', image_filenames)
                continue
            img = img.transpose(2, 0, 1) # HWC => CHW
            tfr.add_image(img)

TFRecordについて詳しくはこちらで
https://deepage.net/tensorflow/2017/10/07/tfrecord.html

3.モデルのトレーニング

ローカル環境に十分なリソースがある場合はローカルでトレーニングすれば良いのですが、家にはお高いGPUがないのでGoogle Colabを使わせてもらいました。
geforce x80が無料で使えるなんて、ホントgoogleさんに感謝です。
4日間ぐらいかかるので、インスタンスが終了したらまた起動してを繰り返します。
train.image_snapshot_ticksのオプションを1に設定すると、1tick毎にモデルのスナップショットを保存してくれるので、インスタンスが終了してもまた続きから学習を再開することができます。

config.py
train.image_snapshot_ticks = 1

前回のスナップショットからレジュームする場合はこんな感じで指定してあげます。

config.py
train.resume_run_id = 4
train.resume_snapshot = 4867
train.resume_kimg = 4867.6

4.結果

とりあえず最終的な結果から
animation.gif

実は最終的に5千枚の画像では低クオリティーの画像しか作成できなかったため、画像データを追加で1.5万枚ほど集めなおしています。

顔の正規化がうまく行えていない場合

animation.gif
正規化がうまく行われていない場合の学習過程

顔が歪んだり、目が無くなったりします。
比較的学習の初期段階でわかるので、中止して、正規化をやり直します。
飴を舐めている画像や、肩で顎が隠れている画像なども生成画像のクオリティーを落としてしまうようなので、除外します。

データの水増しの効果

animation.gif
水増しなしデータでの学習過程

animation.gif
水増し済みデータでの学習過程

データの水増しを行ったほうが高解像度になった際、より破綻のない画像が生成できるようです(たぶん)。
本当は、論文のようにSliced Wasserstein distanceなどを使って評価を行うべきなのですが、めんどくさいので目視で

ですが、どちらも高解像度になるにつれ画像が乱れてしまいます。
おそらくデータが少なすぎて、オーバーフィッティングを起こしてしまっているためだと思います。
5000枚の画像からトレーニングする場合は出力サイズが64x64あたりが限界なのかもしれません。

5.herokuに実装

疲れてしまったので、今回はここまでで。
需要があれば、次回書きたいと思います。

続き書きました
https://qiita.com/pnyompen/items/5b6a587b2392cabfda86

6.終わりに

記事自体のクオリティがかなり荒く申し訳ないですが何方かの参考になれば幸いです。
まずは何かアプトプットするということが大事と、敬愛する先輩から教わったので、ご容赦を

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away