Supershipの名畑です。私の大好きなアニメイト池袋本店と言えばそのすぐそばには惜しまれながらも4月で営業を終えたハレスタ。ハレスタと言えば私の中では0.1gの誤算。0.1gの誤算と言えば「入場と同時に「受付で演奏を開始したら」バンギャはどんな反応をするか検証してみた」はここ最近で最も笑わせていただいた動画です。
はじめに
前々回の記事「Stable Diffusionでの画像生成をPythonとWeb APIで実装してみた記録」と前回の記事「Stable Diffusionにおける同一人物の別表情生成をPythonとWeb APIで試みた記録」に続いて、今回はStable Diffusionでアニメーション生成をしてみます。Stability Animationで紹介されているものです。Animation SDKを用います。
「画像生成AI「Stale Diffusion」開発元がテキストからアニメーションを自動生成できる開発者向けAIツール「Stable Animation SDK」をリリース - GIGAZINE」ですとか、ニュースサイトでも取り上げられていましたね。
料金
料金について詳しくはAnimation Pricingをご覧ください。静止画像の生成とアニメーションの生成を合わせた2重の金額がかかります。お気をつけください。
Stable Diffusion v1.5を使用して100フレームのアニメーションを生成した際の料金目安は下記となるそうです。Cadenceはどれだけ頻繁に画像を生成するかの数値です。1なら毎フレームなので金額も上がります。
Resolution | Cadence | Credits |
---|---|---|
512x512 | 1 | 37.5 ($0.0375) |
512x512 | 2 | 33.1 ($0.0331) |
512x512 | 3 | 29.9 ($0.0299) |
512x512 | 4 | 28.3 ($0.0283) |
768x768 | 1 | 130 ($0.13) |
768x768 | 2 | 97.2 ($0.0972) |
768x768 | 3 | 82.8 ($0.0828) |
768x768 | 4 | 74.7 ($0.0747) |
1024x1024 | 1 | 259 ($0.259) |
1024x1024 | 2 | 187 ($0.187) |
環境設定
言語はPythonです。
私の環境はインストール済みでしたのでバージョン確認のみしました。
$ python --version
Python 3.10.7
animation sdkをインストールします。
$ pip install "stability-sdk[anim]"
動画の生成のため、ffmpegとtqdmも入れておきます。
各フレームのpng画像の書き出しのみであれば不要です。
$ brew install ffmpeg
$ pip install tqdm
サイバーパンクな都市を生成してみる
では実際にアニメーションの生成を開始します。
import
まずは使用するモジュールをインポートします。timeはファイル名にtimestampを入れる目的のためだけに使っています。
from stability_sdk import api
from stability_sdk.animation import AnimationArgs, Animator
from stability_sdk.utils import create_video_from_frames
from stability_sdk.api import ClassifierException, OutOfCreditsException
from tqdm import tqdm
import os
import time
HOSTとAPI KEYの指定
STABILITY_API_KEYは過去の記事で取得済みのものを使います。
STABILITY_HOST = "grpc.stability.ai:443"
STABILITY_KEY = os.getenv("STABILITY_API_KEY")
context = api.Context(STABILITY_HOST, STABILITY_KEY)
パラメーター
args = AnimationArgs()
args.model = "stable-diffusion-xl-beta-v2-2-2" # モデル
args.max_frames = 72 # フレーム数
args.strength_curve = "0:(0)" # 前の画像をどれだけ反映するか
args.diffusion_cadence_curve = "0:(8)" # 画像生成間隔
args.cadence_interp = "film" # 補完モード
せっかくなのでモデルは最新版であるStable Diffusion XL(SDXL)を指定しています。
strength_curveについては、今回は前の画像を引き継がない設定としてみました。0フレーム目に0という値を指定しています。
diffusion_cadence_curveは何フレーム毎に画像生成を行うかになります。今回の場合は8フレーム毎を指定していますが、では生成される間の7フレームの画像がどう補完されるかというと、cadence_interpが適用されるようです。デフォルト(mix)のままだと透過切り替えのような出来栄えで見た目として好みではなかったのでfilmを指定してみました。
指定可能なパラメータについて詳しくは公式サイトのAnimation Parametersをご参照ください。Animation Handbookを読むとより理解が進みそうです。カメラ位置なども含め、かなり細かい設定が可能です。
プロンプト
Animation Handbookにあったサンプルを真似してみました。ネガティブプロンプト(除外要素)は指定していません。デフォルトで「blurry, low resolution」が指定されているそうです。
animation_prompts = {
0: "A cyberpunk futuristic colourful crowded luxurious pedestrian street avenue hi-tech at morning time, blue neon lights, ray tracing, hdr, realistic shaded, extremely detailed, sharp focus, soft lighting, sunny"
}
日本語に訳すと「朝の時間帯におけるサイバーパンクの未来的でカラフルで混雑した高級歩行者専用道路の大通り、ブルーのネオンライト、レイトレーシング、HDR、リアルなシェーディング、非常に詳細、鮮明な焦点、柔らかな照明、晴天」といったところでしょうか。
Animatorの生成
now_time = int(time.time()) # フォルダとファイルの名称に使うtimestamp
# Animatorの生成
# out_dirで指定したディレクトリに各フレーム毎のpng画像が書き出されます
animator = Animator(
api_context=context,
animation_prompts=animation_prompts,
args=args,
out_dir=f"video_{now_time}"
)
レンダリング
サンプルコードを参考に例外処理を加えています。
try:
for _ in tqdm(animator.render(), total=args.max_frames):
pass
except ClassifierException:
print("Animation terminated early due to NSFW classifier.")
except OutOfCreditsException as e:
print(f"Animation terminated early, out of credits.\n{e.details}")
except Exception as e:
print(f"Animation terminated early due to exception: {e}")
mp4ファイル生成
fps(1秒当たりのフレーム数)は24にしておきました。
create_video_from_frames(animator.out_dir, f"video_{now_time}.mp4", fps=12)
結果
アニメーションGIFにしたものがこちらです。変換過程で画質やフレーム数削減しているため、実際より滑らかさは減じています。
8フレーム毎に別画像に切り替わります。
人物で生成してみる
ありがちですが、年齢に応じた変化的なものをアニメーションにしてみようと思います。
先ほどのサイバーパンクを描いたコードのうちのパラメーターとプロンプトのみ変更します。
パラメーター
args = AnimationArgs()
args.model = "stable-diffusion-xl-beta-v2-2-2" # モデル
args.locked_seed = True # フレーム毎のシードを固定する
args.max_frames = 72 # フレーム数
args.seed = 99999 # シード値
args.strength_curve = "0:(0)" # 前の画像をどれだけ反映するか
args.diffusion_cadence_curve = "0:(4)" # 画像生成間隔
args.cadence_interp = "film" # 補完モード
seedとlocked_seedを追加してみました。こうすることで生成される画像に統一性が生まれることを期待しました。seedについては前回の記事「Stable Diffusionにおける同一人物の別表情生成をPythonとWeb APIで試みた記録」で説明していますので、よければご覧ください。
プロンプト
8フレーム毎に20歳、25歳、30歳、35歳……と年齢を加算していったプロンプトを記載しています。
animation_prompts = {
0: "20 years old Japanese woman",
8: "25 years old Japanese woman",
16: "30 years old Japanese woman",
24: "35 years old Japanese woman",
32: "40 years old Japanese woman",
40: "45 years old Japanese woman",
48: "50 years old Japanese woman",
56: "55 years old Japanese woman",
64: "60 years old Japanese woman",
}
結果
アニメーションGIFにしたものがこちらです。変換過程で画質やフレーム数削減しているため、実際より滑らかさは減じています。
最後に
アニメーション自体をプロンプトで指示するのではなく、プロンプトで指示した静止画をアニメーションさせる感じに近いですかね。私の理解がまだ追いついていないだけかもしれない。かなり面白いけれど私の頭だとまだ使い所が思い浮かんでおりません。
「After Effectsの方がやりやすいよね」とか言わないでください。
宣伝
SupershipのQiita Organizationを合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。
Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方はSupership株式会社 採用サイトよりご確認ください。