[AnimateDiff] AIでテキストから動画を生成する

2023年7月23日日曜日

Artificial Intelligence

本記事では、AnimateDiffと呼ばれる機械学習手法を用いてテキストから動画を生成する方法をご紹介します。

eyecatch

AnimateDiff

概要

AnimateDiffは、Stable Diffusionなどをベースとした、テキストからアニメーションを生成するImage Animation技術です。

AnimateDiffでは、DreamBoothやLoRAなどのを用いて、パーソナライズされたText to Imageモデルを使用することでモデル固有のチューニングの労力を節約する実用的なフレームワークです。

このフレームワークは、新しく初期化されたMotion Modeling Moduleを挿入するだけで、Text to Imageモデルをアニメーション画像を生成するtext drive modelに変更することができます。
これにより、モデルの出力の領域と多様性を維持しながら時間的に滑らかなアニメーションクリップを生成することが可能となっています。

出典: AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning

詳細はこちらの論文をご参照ください。

本記事では上記手法を用いて、テキストからアニメーションクリップを生成していきます。

デモ(Colaboratory)

それでは、実際に動かしながらImage Animationタスクを実行します。
ソースコードは本記事にも記載していますが、下記のGitHubでも取得可能です。
GitHub - Colaboratory demo

また、下記から直接Google Colaboratoryで開くこともできます。
Open In Colab

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

環境セットアップ

それではセットアップしていきます。 Colaboratoryを開いたら下記を設定しGPUを使用するようにしてください。

「ランタイムのタイプを変更」→「ハードウェアアクセラレータ」をGPUに変更

初めにGithubからソースコードを取得します。

%cd /content

!git clone https://github.com/guoyww/AnimateDiff.git

%cd /content/AnimateDiff
# Commits on Jul 21, 2023
!git checkout 53c63ad8391d7095ab5364c13b4aa3a7d183dac5

次にライブラリをインストールします。

%cd /content/AnimateDiff

!pip install omegaconf einops omegaconf safetensors diffusers[torch]==0.11.1 transformers xformers==0.0.20 triton==2.0.0
!pip install --upgrade gdown

以上で環境セットアップは完了です。

学習済みモデルのセットアップ

続いて、Stable Diffusionや、Motion Module、T2Iモデルなどをダウンロードしていきます。

%cd /content/AnimateDiff

# download StableDiffusion
!rm -rf ./models/StableDiffusion/
!git clone -b fp16 https://huggingface.co/runwayml/stable-diffusion-v1-5 ./models/StableDiffusion/stable-diffusion-v1-5

# download base T2I
!rm -rf ./models/Motion_Module/*
# !bash download_bashscripts/0-MotionModule.sh
!wget -c https://huggingface.co/camenduru/AnimateDiff/resolve/main/mm_sd_v14.ckpt \
      -O ./models/Motion_Module/mm_sd_v14.ckpt

!wget -c https://huggingface.co/camenduru/AnimateDiff/resolve/main/mm_sd_v15.ckpt \
      -O ./models/Motion_Module/mm_sd_v15.ckpt

続いて、パーソナライズされたT2Iモデルをダウンロードします。
それぞれのモデルの出力についてはこちらをご参照ください。

T2I_TYPE = "1-ToonYou" #@param ["1-ToonYou", "2-Lyriel", "3-RcnzCartoon", "4-MajicMix", "5-RealisticVision", "6-Tusun", "7-FilmVelvia", "8-GhibliBackground"]

!bash download_bashscripts/{T2I_TYPE}.sh

Image Animation

それでは、テキストからアニメーションを生成していきます。

初めに一部ソースコードを修正します。

%cd /content/AnimateDiff

# fix codes
!sed -i ./animatediff/utils/convert_from_ckpt.py \
     -e "s/load_state_dict(text_model_dict)/load_state_dict(text_model_dict, strict=False)/g"

!sed -i ./animatediff/utils/util.py \
     -e "s/fps=fps/duration=1000\/fps/g"

次にライブラリをインポートします。

%cd /content/AnimateDiff

import argparse
import datetime
import inspect
import os
from omegaconf import OmegaConf

import torch

import diffusers
from diffusers import AutoencoderKL, DDIMScheduler

from tqdm.auto import tqdm
from transformers import CLIPTextModel, CLIPTokenizer

from animatediff.models.unet import UNet3DConditionModel
from animatediff.pipelines.pipeline_animation import AnimationPipeline
from animatediff.utils.util import save_videos_grid
from animatediff.utils.convert_from_ckpt import convert_ldm_unet_checkpoint, convert_ldm_clip_checkpoint, convert_ldm_vae_checkpoint
from animatediff.utils.convert_lora_safetensor_to_diffusers import convert_lora
from diffusers.utils.import_utils import is_xformers_available

from einops import rearrange, repeat

import csv, pdb, glob
from safetensors import safe_open
import math
from pathlib import Path

続いて、テキストプロンプトなどを設定します。

%cd /content/AnimateDiff

# set args
args = argparse.ArgumentParser()
args.pretrained_model_path = 'models/StableDiffusion/stable-diffusion-v1-5'
args.inference_config = 'configs/inference/inference.yaml'
args.config = f'configs/prompts/{T2I_TYPE}.yaml'
args.L = 16
args.W = 512
args.H = 512
args.prompt = 'cherry blossoms, woman, 4k, definition, colorful'
args.n_prompt = ''
args.random_seed = 12

*_, func_args = inspect.getargvalues(inspect.currentframe())
func_args = dict(func_args)

time_str = datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
savedir = f"samples/{Path(args.config).stem}-{time_str}"
os.makedirs(savedir)
inference_config = OmegaConf.load(args.inference_config)

config  = OmegaConf.load(args.config)
samples = []

テキストプロンプトからアニメーションを生成します。

sample_idx = 0
for model_idx, (config_key, model_config) in enumerate(list(config.items())[:1]):
  print(model_idx, (config_key, model_config))

  motion_modules = model_config.motion_module
  motion_modules = [motion_modules] if isinstance(motion_modules, str) else list(motion_modules)
  for motion_module in motion_modules[:1]:
    print(motion_module)

    ### >>> create validation pipeline >>> ###
    tokenizer    = CLIPTokenizer.from_pretrained(args.pretrained_model_path, subfolder="tokenizer")
    text_encoder = CLIPTextModel.from_pretrained(args.pretrained_model_path, subfolder="text_encoder")
    vae          = AutoencoderKL.from_pretrained(args.pretrained_model_path, subfolder="vae")
    unet         = UNet3DConditionModel.from_pretrained_2d(args.pretrained_model_path, subfolder="unet", unet_additional_kwargs=OmegaConf.to_container(inference_config.unet_additional_kwargs))

    if is_xformers_available(): unet.enable_xformers_memory_efficient_attention()
    else: assert False

    pipeline = AnimationPipeline(
        vae=vae, text_encoder=text_encoder, tokenizer=tokenizer, unet=unet,
        scheduler=DDIMScheduler(**OmegaConf.to_container(inference_config.noise_scheduler_kwargs)),
    ).to("cuda")

    # 1. unet ckpt
    # 1.1 motion module
    motion_module_state_dict = torch.load(motion_module, map_location="cpu")
    if "global_step" in motion_module_state_dict: func_args.update({"global_step": motion_module_state_dict["global_step"]})
    missing, unexpected = pipeline.unet.load_state_dict(motion_module_state_dict, strict=False)
    assert len(unexpected) == 0

    # 1.2 T2I
    if model_config.path != "":
      if model_config.path.endswith(".ckpt"):
        state_dict = torch.load(model_config.path)
        pipeline.unet.load_state_dict(state_dict)

      elif model_config.path.endswith(".safetensors"):
        state_dict = {}
        if T2I_TYPE == "5-RealisticVision":
          model_config.path = './models/DreamBooth_LoRA/realisticVisionV40_v20Novae.safetensors'
        with safe_open(model_config.path, framework="pt", device="cpu") as f:
          for key in f.keys():
            state_dict[key] = f.get_tensor(key)

        is_lora = all("lora" in k for k in state_dict.keys())
        if not is_lora:
          base_state_dict = state_dict
        else:
          base_state_dict = {}
          with safe_open(model_config.base, framework="pt", device="cpu") as f:
            for key in f.keys():
              base_state_dict[key] = f.get_tensor(key)

        # vae
        converted_vae_checkpoint = convert_ldm_vae_checkpoint(base_state_dict, pipeline.vae.config)
        pipeline.vae.load_state_dict(converted_vae_checkpoint)
        # unet
        converted_unet_checkpoint = convert_ldm_unet_checkpoint(base_state_dict, pipeline.unet.config)
        pipeline.unet.load_state_dict(converted_unet_checkpoint, strict=False)
        # text_model
        pipeline.text_encoder = convert_ldm_clip_checkpoint(base_state_dict)

        # import pdb
        # pdb.set_trace()
        if is_lora:
          pipeline = convert_lora(pipeline, state_dict, alpha=model_config.lora_alpha)

    pipeline.to("cuda")
    ### <<< create validation pipeline <<< ###

    config[config_key].random_seed = []

    prompt, n_prompt, random_seed = args.prompt, args.n_prompt, args.random_seed

    # manually set random seed for reproduction
    if random_seed != -1: torch.manual_seed(random_seed)
    else: torch.seed()
    config[config_key].random_seed.append(torch.initial_seed())

    print(f"current seed: {torch.initial_seed()}")
    print(f"sampling {prompt} ...")

    sample = pipeline(
        prompt,
        negative_prompt     = n_prompt,
        num_inference_steps = model_config.steps,
        guidance_scale      = model_config.guidance_scale,
        width               = args.W,
        height              = args.H,
        video_length        = args.L).videos
    samples.append(sample)

    prompt = "-".join((prompt.replace("/", "").split(" ")[:10]))
    save_videos_grid(sample, f"{savedir}/sample/{sample_idx}-{prompt}.gif")
    print(f"save to {savedir}/sample/{prompt}.gif")

出力結果は以下の通りです。
テキストから16フレームのアニメーションが生成されています。

from IPython.display import Image

Image(open(f"{savedir}/sample/{sample_idx}-{prompt}.gif", 'rb').read())
image animation result

まとめ

本記事では、AnimateDiffを用いてテキストからアニメーションを生成する方法をご紹介しました。

Stable Diffusionから端を発し、益々の発展を見せています。

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


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

参考文献

1.  論文 - AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning

2. GitHub - guoyww/animatediff

AIで副業ならココから!

まずは無料会員登録

プロフィール

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

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


Twitter

カテゴリ

このブログを検索

ブログ アーカイブ

TeDokology