この記事では、Stable Diffusionによるスタイル変換とThin Plate Spline Motion Modelによるモーショントレースを組み合わせて、人の顔の動画を簡単にスタイル変換する方法を紹介します。
概要
Stable DIffusionのimage-to-imageは、任意のプロンプトに合わせて元画像をハイクオリティに変換することができます。ここでは、これを利用して、好きな動画の1フレーム目だけをimage-to-imageで変換後、元動画のモーションをThin Plate Spline Motion Modelでトレースすることで、動画全体を様々なスタイルに変換してみます。
image-to-image、Thin Plate Spline Motion Modelの詳細は、それぞれ以下の記事をご覧ください。
デモ
それでは、Google Colaboratoryを使って実際に実行していきます。
なお、記事内で紹介したコードをすべて含むノートブックは、以下のリンクから直接参照することができます。
環境設定
はじめに、画面上部のメニューから、「ランタイム」、「ランタイムのタイプを変更」と進み、「ハードウェアアクセラレータ」を「GPU」に変更しておきます。これにより、推論時にGPUを利用することができます。
※ 2022年10月よりGoogle Colaboratoryの料金プランがこちらの内容に変更されました。ご利用の際はご注意ください。
次に、必要なライブラリのインストール、インポート、リポジトリのクローン、関数の定義を行います。
# ライブラリのインストール !pip install -qq diffusers==0.3.0 transformers scipy ftfy !pip install -qq imageio-ffmpeg # リポジトリのクローン !git clone https://github.com/yoyo-nb/Thin-Plate-Spline-Motion-Model.git # ライブラリのインポート import os import shutil import warnings import numpy as np from PIL import Image import imageio from skimage import img_as_ubyte import matplotlib.pyplot as plt import matplotlib.animation as animation from skimage.transform import resize from IPython.display import HTML import cv2 import torch from torch import autocast from diffusers import StableDiffusionImg2ImgPipeline warnings.filterwarnings('ignore') # 描画関数の定義 def image_grid(imgs, rows, cols): assert len(imgs) == rows * cols w, h = imgs[0].size grid = Image.new('RGB', size=(cols * w, rows * h)) grid_w, grid_h = grid.size for i, img in enumerate(imgs): grid.paste(img, box=(i % cols * w, i // cols * h)) return grid # 前処理関数の定義 def preprocess(image): w, h = image.size w, h = map(lambda x: x - x % 32, (w, h)) image = image.resize((w, h), resample=Image.LANCZOS) image = np.array(image).astype(np.float32) / 255.0 image = image[None].transpose(0, 3, 1, 2) image = torch.from_numpy(image) return 2. * image - 1.
続いて、Stable Diffusionの学習済みモデルを利用するために、Hugging Faceにログインしておきます。以下のセルを実行後、表示される入力欄にアクセストークンを入力します。アクセストークンの取得方法はこちらの記事をご確認ください。
from huggingface_hub import notebook_login notebook_login()
対象動画の準備
対象の動画をアップロードします。以下のコードでは正方形に近いものを想定しています。
uploaded = files.upload()
動画をフレームに分割します。
# 動画ファイルパスの取得 video_path = os.path.join('/content', list(uploaded.keys())[0]) # 保存ディレクトリの作成 frame_dir = '/content/frames/' if os.path.exists(frame_dir): shutil.rmtree(frame_dir) if not os.path.exists(frame_dir): os.makedirs(frame_dir) # 動画のフレーム分割 cmd = "ffmpeg -i %s -start_number 0 -vsync 0 %s/%%03d.png" % ( video_path, frame_dir, ) os.system(cmd)
今回の対象動画の1フレーム目はこちら。この記事よりお借りしています。
これで動画の準備は完了です。
Stable Diffusionによるimage-to-image
では、image-to-imageを実施します。ここではアニメ調に変換してみます。
# プロンプトの設定 prompt = 'Portrait of a female, defined facial features, highly detailed, animation cel, official Kyoto Animation and Studio Ghibli anime screenshot, by Makoto Shinkai and Range Murata' # 学習済みモデルのダウンロード pipe = StableDiffusionImg2ImgPipeline.from_pretrained( 'CompVis/stable-diffusion-v1-4', revision='fp16', torch_dtype=torch.float16, use_auth_token=True ).to('cuda') # 出力ディレクトリの作成 output_dir = '/content/output' !mkdir -p $output_dir if os.path.exists(output_dir): shutil.rmtree(output_dir) if not os.path.exists(output_dir): os.makedirs(output_dir) # 生成画像数の設定 num = 30 # 初期画像の読込 image_file = '/content/frames/000.png' original_image = Image.open(image_file).convert('RGB') resized_image = original_image.resize((512, 512)) init_image = preprocess(resized_image) plt.imshow(resized_image) plt.axis('off') plt.title('before processing') # 画像の生成 images = [] for i in range(num): with autocast('cuda'): image = pipe(prompt=prompt, init_image=init_image, strength=0.7)['images'][0] image.save(os.path.join(output_dir, f'{i:03d}.png')) images.append(image) # 結果の描画 image_grid(images, 6, 5)
上記のコードでは30枚を生成しています。枚数は求める完成度に応じて増減してください。
ここでは、以下の1枚を採用しました。
# 画像番号の指定 image_idx = '009' # 選択した画像の通番 # 選択画像の読込 image_file = os.path.join(output_dir, f'{image_idx}.png') image = Image.open(image_file).convert('RGB') plt.imshow(image) plt.axis('off') plt.title('selected image');
Thin Plate Spline Motion Modelによるモーショントレース
変換した1フレームに対して、元動画からモーションをコピーします。
# パッケージのインポート %cd Thin-Plate-Spline-Motion-Model from demo import load_checkpoints from demo import make_animation # パラメータ設定 device = torch.device('cuda:0') dataset_name = 'vox' source_image_path = image_file driving_video_path = video_path output_video_path = '/content/result.mp4' config_path = 'config/vox-256.yaml' checkpoint_path = 'checkpoints/vox.pth.tar' predict_mode = 'relative' find_best_frame = False pixel = 256 # トレース先の画像・トレース元の動画の読込 source_image = imageio.imread(source_image_path) reader = imageio.get_reader(driving_video_path) fps = reader.get_meta_data()['fps'] source_image = resize(source_image, (pixel, pixel))[..., :3] driving_video = [] try: for im in reader: driving_video.append(im) except RuntimeError: pass reader.close() driving_video = [resize(frame, (pixel, pixel))[..., :3] for frame in driving_video] def display(source, driving, generated=None): fig = plt.figure(figsize=(8 + 4 * (generated is not None), 6)) ims = [] for i in range(len(driving)): cols = [source] cols.append(driving[i]) if generated is not None: cols.append(generated[i]) im = plt.imshow(np.concatenate(cols, axis=1), animated=True) plt.axis('off') ims.append([im]) ani = animation.ArtistAnimation(fig, ims, interval=50, repeat_delay=1000) plt.close() return ani # 学習済みパラメータのダウンロード !mkdir -p checkpoints !wget -c https://cloud.tsinghua.edu.cn/f/da8d61d012014b12a9e4/?dl=1 -O checkpoints/vox.pth.tar # 学習済みパラメータのロード inpainting, kp_detector, dense_motion_network, avd_network = load_checkpoints(config_path = config_path, checkpoint_path = checkpoint_path, device = device) # トレースの実行 predictions = make_animation(source_image, driving_video, inpainting, kp_detector, dense_motion_network, avd_network, device = device, mode = predict_mode) # 結果の保存 result_dir = '/content/result' if os.path.exists(result_dir): shutil.rmtree(result_dir) if not os.path.exists(result_dir): os.makedirs(result_dir) for i, frame in enumerate(predictions): cv2.imwrite(os.path.join(result_dir, f'{i:03d}.png'), img_as_ubyte(frame)[:, :, ::-1]) imageio.mimsave(output_video_path, [img_as_ubyte(frame) for frame in predictions], fps=fps) # 描画 HTML(display(source_image, driving_video, predictions).to_html5_video())
結果は、フレームごとに/content/result
に保存されます。
別途実行した、ヒューマノイド風、少女風も含めた最終的な結果がこちらです。
まとめ
動画スタイル変換の数ある手法のうちの一つですが、1枚目のみをスタイル変換の基準とするため、Stable Diffusionの結果を厳選しやすい点、全体的に高速である点でオススメです。リンク先のcolabファイルでは、動画をアップロードするだけで上記のすべての処理を再現可能なので、ぜひ試してみてください。
参考文献
High-Resolution Image Synthesis with Latent Diffusion Models