[機械学習]TargetCLIPで顔を合成してみる

2021年10月30日土曜日

Artificial Intelligence

本記事は、画像処理系の機械学習手法であるTargetCLIPを使用して、2者の顔を自然に合成する方法を解説します。

アイキャッチ

TargetCLIPとは

TargetCLIPは、ある画像の概念的な「スタイル」を、別の画像の「コンテンツ」組み合わせ新しい画像を生成します。
この画像のブレンディングは、StyleGAN generatorimage-language matchingであるCLIPを組み合わせて実現されています。
そして、発表された論文によるとこれらが各空間で別々に得られるものよりもはるかに自然なブレンドにつながるとしています。

TargetCLIPの出力例その1
TargetCLIPの出力例その2
出典: https://github.com/hila-chefer/targetclip

TargetCLIPの特徴は、従来技術より高レベルな「スタイル」の概念を他方の画像に転送します。
先ほどのジョーカーであれば、従来技術が単に配色やテクスチャを転送していたのに対し、ジョーカーの髪、象徴的な赤い笑顔などをグループ化し転送します。

TargetCLIPの導入手順

セットアップ

それでは早速、開発環境にTargetCLIPをセットアップしていきます。
動作確認は下記の環境で行っています。

OS: Ubuntu 18.04.3 LTS
GPU: GeForce GTX 1080

なお、このデモはPythonで実装しています。
Pythonの実装に不安がある方、Pythonを使った機械学習について詳しく勉強したい方は、以下の書籍やオンライン講座などがおすすめです。

それでは、TargetCLIPをインストールしていきます。他の機械学習環境に影響を与えないためにMinicondaの仮想環境上に構築していきます。Minicondaのインストール手順は公式ドキュメントをご参照ください。

# python version 3.7のconda環境を作成
$ conda create -n targetclip python=3.7

# 作成した環境をアクティベート
$ conda activate targetclip
$ cd targetclip

# gitからコードをclone
$ git clone https://github.com/hila-chefer/TargetCLIP.git
$ cd TargetCLIP

# 関連するライブラリをインストール
$ pip3 install git+https://github.com/openai/CLIP.git
# https://pytorch.org/get-started/previous-versions/ でバージョン確認
$ conda install pytorch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 cudatoolkit=10.1 -c pytorch
$ conda install -c conda-forge opencv
$ conda install -c conda-forge matplotlib

# Proxy配下の場合は下記
$ pip3 install "モジュール名" --proxy=http://"username":"password"@proxy:port

# 学習済みモデルをダウンロード(stylegan2-ffhq-config-f.pt)
$ https://drive.google.com/file/d/1EM87UquaoQmk17Q8d5kYIAHqu0dkYqdT/edit
# 実行環境の任意のディレクトリに配置

以上で、セットアップは完了です。
なおGoogle Colaboratoryも提供されているため
とりあえず動かすだけであれば以下の公式のコードもおすすめです
Open In Colab

顔の合成(ブレンディング)

ここでは、Google Colaboratoryではなく、ローカル環境で動作可能なコードを記述します。
サンプルは下記のコードです。
下記のコードはTargetCLIP/local_image_transfer.pyとして配置されることを前提としています。

import os
# Proxy配下の場合は下記を設定
# os.environ["http_proxy"] = "http://"username":"password"@proxy:port"
# os.environ["https_proxy"] = "http://"username":"password"@proxy:port"

import numpy as np
import torch
import torchvision

from models.stylegan2.model import Generator
import math

import argparse

#@title Aux functions
def get_latent(args, g_ema):
    mean_latent = g_ema.mean_latent(4096)
    latent_code_init_not_trunc = torch.randn(1, 512).cuda()
    with torch.no_grad():
        # _, latent_code_init = g_ema([latent_code_init_not_trunc], return_latents=True,
        #                             truncation=args.truncation, truncation_latent=mean_latent)
        _, latent_code_init,_ = g_ema([latent_code_init_not_trunc], return_latents=True,
                                    truncation=args.truncation, truncation_latent=mean_latent)

    direction = latent_code_init.detach().clone()
    direction.requires_grad = True
    return direction


def load_model(args):
    g_ema = Generator(args.stylegan_size, 512, 8)
    g_ema.load_state_dict(torch.load(args.ckpt)["g_ema"], strict=False)
    g_ema.eval()
    g_ema = g_ema.cuda()
    return g_ema


def get_lr(t, initial_lr, rampdown=0.75, rampup=0.005):
    lr_ramp = min(1, (1 - t) / rampdown)
    lr_ramp = 0.5 - 0.5 * math.cos(lr_ramp * math.pi)
    lr_ramp = lr_ramp * min(1, t / rampup)

    return initial_lr * lr_ramp

model_args = {
    "ckpt": "./learned_models/stylegan2-ffhq-config-f.pt",
    "stylegan_size": 1024,
    "lr": 0.1,
    "truncation": 0.7,
    "save_intermediate_image_every": 1,
    "results_dir": "results",
    "dir_name": "results",
    "num_batches": 1,
    "real_images": True,
    "data_path": "train_faces.pt",
}
dirs = {
  'Elsa': 'dirs/elsa.npy',
  'Pocahontas': 'dirs/pocahontas.npy',
  'Keanu Reeves': 'dirs/keanu.npy',
  'Trump': 'dirs/trump.npy',
  'Joker': 'dirs/joker.npy',
  'Ariel': 'dirs/ariel.npy',
  'Doc Brown': 'dirs/doc.npy',
  'Beyonce': 'dirs/beyonce.npy',
  'Morgan Freeman': 'dirs/morgan.npy',
}
targets = {
  'Elsa': 'dirs/targets/elsa.jpg',   
  'Pocahontas': 'dirs/targets/pocahontas.jpg',
  'Keanu Reeves': 'dirs/targets/keanu.jpg',
  'Trump': 'dirs/targets/trump.jpg',
  'Joker': 'dirs/targets/joker.jpg',
  'Ariel': 'dirs/targets/ariel.jpeg',
  'Doc Brown': 'dirs/targets/doc_brown.jpg',
  'Beyonce': 'dirs/targets/beyonce.jpg',
  'Morgan Freeman': 'dirs/targets/morgan_freeman.jpg',
}
sources_ids = {
  'Taylor Swift': 67,   
  'Elon Musk': 4,
  'Hillary Clinton': 9,
  'Alfie Allen': 34,
  'Obama': 61
}

def main() -> None:
    parser = argparse.ArgumentParser(description='説明')
    parser.add_argument(
        '--model_path',
        default='./learned_models/stylegan2-ffhq-config-f.pt',
        help='modelのパス'
    )
    parser.add_argument(
        '--target', 
        default='Joker',
        help='合成元 Trump,Keanu Reeves, Elsa, Pocahontas, Joker, Ariel, Doc Brown, Beyonce, Morgan Freemanから選択')
    parser.add_argument(
        '--src',
        default='Taylor Swift',
        help='合成先 Taylor Swift, Elon Musk, Hillary Clinton, Alfie Allen, Obama'
    )
    parser.add_argument(
        '--alpha',
        type=float, default=1.0,
        help='min:0, max:2, step:0.1'
    )
    parser.add_argument(
        '--out_dir',
        default='./results',
        help='出力先'
    )

    args = parser.parse_args()
    model_args['ckpt'] = args.model_path

    a=argparse.Namespace(**model_args)
    g_ema = load_model(a)

    target = args.target
    source_name = args.src

    sources = torch.from_numpy(np.load('dirs/w_plus.npy'))
    source = sources[sources_ids[source_name]].unsqueeze(0)
    dir = torch.from_numpy(np.load(dirs[target]))

    alpha = args.alpha
    
    dir = dir.cuda()
    source = source.cuda()
    source_img, _ = g_ema([source], input_is_latent=True, randomize_noise=False)
    source_amp, _ = g_ema([source + dir * alpha], input_is_latent=True,
                            randomize_noise=False)

    out_dir = args.out_dir
    org_path = os.path.join(out_dir, 'results_orig.png')
    mani_path = os.path.join(out_dir, 'results_manipulated.png')

    torchvision.utils.save_image(source_img, org_path, normalize=True, range=(-1, 1))
    torchvision.utils.save_image(source_amp, mani_path, normalize=True, range=(-1, 1))

if __name__ == "__main__":
    main()

上記のプログラムは下記のコマンドで実行できます。

python3 local_image_transfer.py \
--model_path ./learned_models/stylegan2-ffhq-config-f.pt \
--target Joker \
--src Obama \
--alpha 1.1 \
--out_dir ./results

実行結果は以下の通りです。

TargetCLIP出力結果

口角の上がり方や、目元の変化などが非常に自然に変換されていることが見て取れます。
TargetCLIPはこのように自然な変換が可能となるため法律や倫理に反さない利用が求められます。

まとめ

本記事では、TargetCLIPで2者の顔を合成する方法をご紹介しました。
パッと見では、本当に本人がその恰好をしたのか、合成によって生成された画像なのか判断が難しい画像も存在します。
このため、当たり前のことですが、誰かの名誉を毀損するような悪用は厳禁となります

また本記事では、機械学習を動かすことにフォーカスしてご紹介しました。
もう少し学術的に体系立てて学びたいという方には以下の書籍などがお勧めです。ぜひご一読下さい。


また動かせるだけから理解して応用できるエンジニアの足掛かりに下記のUdemyなどもお勧めです。

参考文献

1. IMAGE-BASED CLIP-GUIDED ESSENCE TRANSFER

2. hila-chefer/TargetCLIP

3. Casual GAN Paper - 66: TargetCLIP

AIで副業ならココから!

まずは無料会員登録

プロフィール

メーカーで研究開発を行う現役エンジニア
組み込み機器開発や機会学習モデル開発に従事しています

本ブログでは最新AI技術を中心にソースコード付きでご紹介します


Twitter

カテゴリ

このブログを検索

ブログ アーカイブ

TeDokology