[ComfyUI] AnimateDiff + ControlNet Keyframe + Prompt Travel

Prompt Travel 用來製作動畫的效果不錯,但是麻煩的地方在於無法比較準確的控制。所以我參考了一些資料,整理了一份關於 ControlNet KeyFrames 的操作。


English Version


Prompt Travel

自從 AnimateDiff 開始熱門之後,Prompt Travel 這種操作也開始變得比較多人討論。我在這邊簡單的介紹一下什麼叫做 Prompt Travel?首先,在 AnimateDiff 當中,如果使用這個方式來製作動畫,那麼我們的提示詞(Prompt)就能使用 Prompt Travel 的撰寫方式。

他主要分為三個部分:

  1. 開頭提示詞( Head Prompt
  2. 指定幀數提示詞( Frames Prompt
  3. 結尾提示詞( Tail Prompt

所以,我們撰寫起來會類似這個樣子,

(best quality:1.2), ultra highres, 8k, vibrant, adult girl,
"0": "(close mouth:1.4), looking aside",
"12": "smile:1.3, looking at viewer",
"24": "smile:1.3, looking aside"
sitting, coffeeshop

其中,我們的開頭提示詞為,

(best quality:1.2), ultra highres, 8k, vibrant, adult girl,

然後中間的指定幀數提示詞,他的寫法是 "幀數":"提示詞",請注意,最後一個幀數的提示詞,後面沒有逗號(,

"0": "(close mouth:1.4), looking aside",
"12": "smile:1.3, looking at viewer",
"24": "smile:1.3, looking aside"

最後就是結尾提示詞,

sitting, coffeeshop

當我們在使用 AnimateDiff 的時候,他會將 開頭提示詞 加上每個幀數的提示詞,最後再加上 結尾提示詞 來當作你的指定幀數的完整提示詞。換句話說,每一個幀數會變成這樣,

Nth Frame Prompt = Head Prompt + Nth Frame's Prompt + Tail Prompt

這樣應該可以稍微理解 Prompt Travel 的寫法了。


ComfyUI & Prompt Travel

在 ComfyUI 當中,要使用 Prompt Travel 的話,建議可以安裝以下套件:

他有提供了 Batch Prompt Schedule 可以使用,很方便。

Batch Prompt Schedule

如果單純使用 Prompt Travel 來製作的話,基本上畫面都是由模型根據你的提示詞自由發揮,然後再依靠 AnimateDiff 模型的功能,把所產出來的圖片進行連接的動作。

Prompt Travel Simple Workflow

當然這樣的連接方式多少都會有點不自然或是跳動的情況。

Prompt Travel Failed

所以,為了避免這個情況,我們就需要利用 ControlNet 介入控制,嘗試去拿到我們想要的結果。


CLI 愛好者的福音

這一個專案可以用 Command Line 的方式產生不錯的結果,

AnimateDiff prompt travel

如果你是工程師,或是對於 Command Line 跟改 JSON 文件不排斥的話,可以試用看看。由於他後面幫你做掉很多事情,所以你不需要花心思去調整 ComfyUI,另外也能相對快速的拿到測試結果。不過,他對於 VRAM 需求較高,所以如果跑不起來的話,需要降低他設定檔中 ControlNets 的使用量。

這套工具提供的功能非常多元,他的步驟很多地方目前是無法做到 ComfyUI 裡面的,硬要做的話大概會出現上百個節點( Nodes )吧。


ControlNet & KeyFrames

在鳥巢群組中有人提到控制關鍵幀的作法,所以我就找了一下如何控制,剛好在 C 站也有類似的文章,

Animatediff Workflow: Openpose Keyframing in ComfyUI

所以稍微看了一下之後,整理出一些重點的地方。首先,我們放置 ControlNet 的地方還是一樣,只是,我們利用這個工具來做關鍵幀(Keyframe)的控制,

ComfyUI-Advanced-ControlNet

ControlNet Latent keyframe Interpolation

我們會用到以下兩種工具,

  • Timestamp Keyframe 用來控制關鍵幀的接口,並將轉換為 TIMESTEP_KEYFRAME 回傳給 ControletNet。
    • latent_keyframe 輸入潛空間關鍵幀,這是我們目前唯一需要用到的輸入。
  • Latent Keyframe Interpolation 利用潛空間( Latent )關鍵幀做插值,並將他回傳給 Timestamp Keyframe
    • prev_latent_keyframe 上一個潛空間關鍵幀。
    • batch_index_from 要從第幾個幀數開始。
    • batch_index_to_excl 要執行到第幾個幀數為止。
    • strength_from 開始執行的強度。
    • strength_to 結束的執行強度。
    • interpolation 插值從開始到結束要使用哪種函式,有四種可以使用 linear, ease-in, ease-out, ease-in-out

理解了基本的輸入輸出後,我們回頭來看看這個設定,你會發現我做了兩次 Latent Keyframe Interpolation,並讓他有前後關係。這個操作也是參考 C 站的文章,主要的目的是在關鍵幀處理的時候不會產生很生硬的變化,讓他有進入與離開兩個插值可以調整。

2-pass Interpolation

上面的 紅色 框框的部分就是「進入」這個關鍵幀區域做的插值運算,而 黃色 的框框則是將要「離開」這個關鍵幀區域時要做的插值運算。

所以我們會有三個數字,

  • Start ~ Middle 代表 紅色 插值運算從第幾幀開始到結束的幀數。
  • Middle ~ End 代表 黃色 插值運算從第幾幀開始到結束的幀數。

這樣一個 ControlNet 就可以針對這個關鍵幀做控制。

ControlNet with timestep_keyframe

上面這個 ControlNet 代表什麼意思呢?

  1. 0 ~ 5 幀帶入這個 OpenPose 到我的 Prompt Travel 裡面。
  2. 這個關鍵幀的強度以 ease-out 插值運算。
  3. 從強度 1.0 遞減至 0.2 然後結束。
  4. 後面的幀數交給 Prompt Travel 繼續自行發揮。

這樣我們就能夠控制 一部份 的關鍵幀,讓他符合 ControlNet 所指定的樣子,以上述的例子就是骨架圖,我們的 Prompt Travel 在這些幀數當中,會畫出我們指定的 OpenPose 的結果。

Keyframes with OpenPose

那麼,如果我們有很多關鍵幀想控制怎麼辦?

他就會變成這個樣子,

5 ControlNet's Timestep_Keyframe

上述的 ControlNets 每一組關鍵幀都使用了兩組 ControlNet,其中有一組是被我跳過( ByPass )的,所以他是紫色。

換句話說,上面這樣的所有的 ControlNets 總共有 10 個。而這 10 個 ControlNets 僅為了控制 5 個關鍵幀區段。

是不是覺得 VRAM 瑟瑟發抖?

上面全開的話會需要 15GB VRAM

這裡放上範例的產出影片給大家參考,

Prompt Travel with ControlNet


小結

這是一個簡單的介紹,實際上要怎麼操作其實就看你自己的創意。我的整個 Workflow 在這裡,有興趣的人可以自行研究。

Prompt_Travel_5Keyframes_10CN_5pass

https://github.com/hinablue/comfyUI-workflows/blob/main/Prompt_Travel_5Keyframes_10CN_5pass.png

Tips

如果你要使用我的 Workflow,請留意幾個地方,

  1. 影像放大倍數如果不是,儲存成 video/h264-mp4 時,會出現類似 width not divisible by 2 這種錯誤。
  2. RIFE VFI 裡面的 multiplier 數字越高,影片速度會越慢。

如果你發現影片不能儲存,然後是因為無法被 2 整除這種問題,你可以修改 custom_nodes/ComfyUI-AnimateDiff-Evolved/video_formats/h264-mp4.json 這個檔案,

{
    "main_pass":
    [
        "-n", "-c:v", "libx264",
        "-pix_fmt", "yuv420p",
        "-crf", "19",
        "-vf", "\"pad=ceil(iw/2)*2:ceil(ih/2)*2\""
    ],
     "extension": "mp4"
}

改成上面那樣,加入 "-vf", "\"pad=ceil(iw/2)*2:ceil(ih/2)*2\"" 這一行就好了,他會再執行 ffmpeg 時幫你把影片補白( padding )成可以被 2 整除的寬度或高度,讓你的影片可以正常儲存。

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei