使用 🧨 Diffusers 實現超快速 ControlNet
自 Stable Diffusion 風靡全球以來,人們一直在尋找方法來更好地控制生成過程的結果。ControlNet 提供了一個最小的介面,允許使用者在很大程度上自定義生成過程。透過 ControlNet,使用者可以輕鬆地透過不同的空間上下文(如深度圖、分割圖、塗鴉、關鍵點等)來控制生成!
我們可以將卡通畫變成具有驚人連貫性的真實照片。
逼真的 Lofi 女孩 |
---|
![]() |
甚至可以用它來做你的室內設計師。
之前 | 之後 |
---|---|
![]() |
![]() |
您可以將您的草圖塗鴉變成藝術畫作。
之前 | 之後 |
---|---|
![]() |
![]() |
此外,讓一些著名標誌栩栩如生。
之前 | 之後 |
---|---|
![]() |
![]() |
有了 ControlNet,一切皆有可能 🌠
在這篇博文中,我們首先介紹 StableDiffusionControlNetPipeline
,然後展示它如何應用於各種控制條件。讓我們開始控制吧!
ControlNet: 簡而言之
ControlNet 由 Lvmin Zhang 和 Maneesh Agrawala 在 Adding Conditional Control to Text-to-Image Diffusion Models 中引入。它引入了一個框架,允許支援各種空間上下文,這些上下文可以作為 Diffusion 模型(如 Stable Diffusion)的額外條件。Diffusers 的實現改編自原始 原始碼。
訓練 ControlNet 包含以下步驟:
- 克隆 Diffusion 模型的預訓練引數(例如 Stable Diffusion 的潛在 UNet),(稱為“可訓練副本”)同時單獨維護預訓練引數(“鎖定副本”)。這樣做是為了讓鎖定引數副本能夠保留從大量資料集中學習到的豐富知識,而可訓練副本則用於學習特定任務的方面。
- 引數的可訓練副本和鎖定副本透過“零卷積”層連線(更多資訊請參閱此處),這些層作為 ControlNet 框架的一部分進行最佳化。這是一種訓練技巧,用於在訓練新條件時保留凍結模型已學習的語義。
從影像上看,訓練 ControlNet 如下所示:
該圖取自此處。
ControlNet 訓練的訓練集樣本如下所示(透過邊緣圖進行額外條件控制)
提示 | 原始圖片 | 條件 |
---|---|---|
"鳥" | ![]() |
![]() |
同樣,如果我們要用語義分割圖來控制 ControlNet,一個訓練樣本會是這樣:
提示 | 原始圖片 | 條件 |
---|---|---|
"大房子" | ![]() |
![]() |
每種新的條件型別都需要訓練一個新的 ControlNet 權重副本。論文提出了 8 種不同的條件模型,這些模型在 Diffusers 中都受支援!
對於推理,需要預訓練的擴散模型權重和訓練好的 ControlNet 權重。例如,使用 Stable Diffusion v1-5 和 ControlNet 檢查點比僅使用原始 Stable Diffusion 模型需要多出大約 7 億個引數,這使得 ControlNet 在推理時對記憶體的消耗更大。
由於預訓練的擴散模型在訓練期間被鎖定,因此在使用不同條件時只需要切換 ControlNet 引數。這使得在單個應用程式中部署多個 ControlNet 權重變得相當簡單,如下所示。
StableDiffusionControlNetPipeline
在開始之前,我們要向社群貢獻者 Takuma Mori 致以崇高的敬意,感謝他領導了 ControlNet 在 Diffusers 中的整合 ❤️。
為了體驗 ControlNet,Diffusers 提供了 StableDiffusionControlNetPipeline
,類似於其他 Diffusers 管線。StableDiffusionControlNetPipeline
的核心是 controlnet
引數,它允許我們在保持預訓練擴散模型權重不變的情況下提供一個特定的已訓練 ControlNetModel
例項。
我們將在本部落格文章中探索 StableDiffusionControlNetPipeline
的不同用例。我們將介紹的第一個 ControlNet 模型是 Canny 模型——這是最受歡迎的模型之一,它生成了一些您可能在網際網路上看到的令人驚歎的影像。
歡迎您使用 此 Colab 筆記本 執行以下部分中顯示的程式碼片段。
在開始之前,我們先確保安裝了所有必要的庫
pip install diffusers==0.14.0 transformers xformers git+https://github.com/huggingface/accelerate.git
為了根據選擇的 ControlNet 處理不同的條件,我們還需要安裝一些額外的依賴項
- OpenCV
- controlnet-aux - 一個用於 ControlNet 的簡單預處理模型集合
pip install opencv-contrib-python
pip install controlnet_aux
我們將使用著名的畫作 《戴珍珠耳環的少女》 作為這個例子。所以,讓我們下載圖片並檢視一下
from diffusers.utils import load_image
image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png"
)
image
接下來,我們將把影像透過 canny 預處理器處理
import cv2
from PIL import Image
import numpy as np
image = np.array(image)
low_threshold = 100
high_threshold = 200
image = cv2.Canny(image, low_threshold, high_threshold)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
canny_image = Image.fromarray(image)
canny_image
如我們所見,它本質上是邊緣檢測
現在,我們載入 runwaylml/stable-diffusion-v1-5 以及用於 Canny 邊緣的 ControlNet 模型。模型以半精度 (torch.dtype
) 載入,以實現快速且記憶體高效的推理。
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
import torch
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16
)
我們不使用 Stable Diffusion 預設的 PNDMScheduler,而是使用目前最快的擴散模型排程器之一,稱為 UniPCMultistepScheduler。選擇改進的排程器可以大幅減少推理時間——在我們的例子中,我們可以將推理步數從 50 步減少到 20 步,同時或多或少地保持相同的影像生成質量。有關排程器的更多資訊可以在此處找到。
from diffusers import UniPCMultistepScheduler
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
我們沒有直接將管道載入到 GPU,而是啟用了智慧 CPU 解除安裝,這可以透過 enable_model_cpu_offload
函式實現。
請記住,在推理擴散模型(如 Stable Diffusion)時,不僅需要一個,而且需要多個模型元件按順序執行。對於帶有 ControlNet 的 Stable Diffusion,我們首先使用 CLIP 文字編碼器,然後是擴散模型 UNet 和 ControlNet,然後是 VAE 解碼器,最後執行安全檢查器。大多陣列件在擴散過程中只執行一次,因此不需要一直佔用 GPU 記憶體。透過啟用智慧模型解除安裝,我們確保每個元件只在需要時才載入到 GPU 中,這樣我們可以在不顯著減慢推理速度的情況下顯著節省記憶體消耗。
注意:執行 enable_model_cpu_offload
時,請勿手動使用 .to("cuda")
將管道移動到 GPU——一旦啟用 CPU 解除安裝,管道會自動處理 GPU 記憶體管理。
pipe.enable_model_cpu_offload()
最後,我們要充分利用令人驚歎的 FlashAttention/xformers 注意力層加速功能,所以讓我們啟用它!如果此命令對您不起作用,則可能沒有正確安裝 xformers
。在這種情況下,您可以跳過以下程式碼行。
pipe.enable_xformers_memory_efficient_attention()
現在我們準備執行 ControlNet 管道了!
我們仍然提供一個提示來指導影像生成過程,就像我們通常使用 Stable Diffusion 影像到影像管道那樣。然而,ControlNet 將對生成的影像提供更多的控制,因為我們將能夠使用我們剛剛建立的 canny 邊緣影像來控制生成影像中的精確構圖。
看看一些當代名人擺出這幅 17 世紀畫作的相同姿勢的圖片會很有趣。使用 ControlNet 做到這一點非常容易,我們所要做的就是將這些名人的名字包含在提示中!
首先,我們建立一個簡單的輔助函式來以網格形式顯示影像。
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
接下來,我們定義輸入提示並設定一個種子以確保可重現性。
prompt = ", best quality, extremely detailed"
prompt = [t + prompt for t in ["Sandra Oh", "Kim Kardashian", "rihanna", "taylor swift"]]
generator = [torch.Generator(device="cpu").manual_seed(2) for i in range(len(prompt))]
最後,我們可以執行管道並顯示影像!
output = pipe(
prompt,
canny_image,
negative_prompt=["monochrome, lowres, bad anatomy, worst quality, low quality"] * 4,
num_inference_steps=20,
generator=generator,
)
image_grid(output.images, 2, 2)
我們還可以輕鬆地將 ControlNet 與微調結合起來!例如,我們可以使用 DreamBooth 微調模型,並用它將我們自己渲染到不同的場景中。
在這篇文章中,我們將以我們心愛的土豆先生為例,展示如何將 ControlNet 與 DreamBooth 結合使用。
我們可以使用相同的 ControlNet。但是,我們不使用 Stable Diffusion 1.5,而是將 土豆先生模型 載入到我們的管道中——土豆先生是一個使用 Dreambooth 🥔 微調的 Stable Diffusion 模型,它使用土豆先生的概念。
讓我們再次執行上面的命令,保持相同的 ControlNet!
model_id = "sd-dreambooth-library/mr-potato-head"
pipe = StableDiffusionControlNetPipeline.from_pretrained(
model_id,
controlnet=controlnet,
torch_dtype=torch.float16,
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
pipe.enable_xformers_memory_efficient_attention()
現在,讓我們讓土豆先生為 約翰內斯·維米爾 擺姿勢吧!
generator = torch.manual_seed(2)
prompt = "a photo of sks mr potato head, best quality, extremely detailed"
output = pipe(
prompt,
canny_image,
negative_prompt="monochrome, lowres, bad anatomy, worst quality, low quality",
num_inference_steps=20,
generator=generator,
)
output.images[0]
可以看出,土豆先生不是最好的候選人,但他盡力了,在捕捉一些精髓方面做得相當不錯 🍟
ControlNet 的另一個獨家應用是,我們可以從一張影像中提取姿勢,並將其重複使用以生成具有完全相同姿勢的不同影像。因此,在下一個示例中,我們將使用 Open Pose ControlNet 教超級英雄們如何做瑜伽!
首先,我們需要獲取一些正在做瑜伽的人的影像
urls = "yoga1.jpeg", "yoga2.jpeg", "yoga3.jpeg", "yoga4.jpeg"
imgs = [
load_image("https://huggingface.co/datasets/YiYiXu/controlnet-testing/resolve/main/" + url)
for url in urls
]
image_grid(imgs, 2, 2)
現在,讓我們使用 controlnet_aux
中方便提供的 OpenPose 預處理器提取瑜伽姿勢。
from controlnet_aux import OpenposeDetector
model = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
poses = [model(img) for img in imgs]
image_grid(poses, 2, 2)
為了使用這些瑜伽姿勢生成新影像,我們建立一個 Open Pose ControlNet。我們將生成一些超級英雄影像,但採用上面所示的瑜伽姿勢。讓我們開始吧 🚀
controlnet = ControlNetModel.from_pretrained(
"fusing/stable-diffusion-v1-5-controlnet-openpose", torch_dtype=torch.float16
)
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionControlNetPipeline.from_pretrained(
model_id,
controlnet=controlnet,
torch_dtype=torch.float16,
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
現在是瑜伽時間!
generator = [torch.Generator(device="cpu").manual_seed(2) for i in range(4)]
prompt = "super-hero character, best quality, extremely detailed"
output = pipe(
[prompt] * 4,
poses,
negative_prompt=["monochrome, lowres, bad anatomy, worst quality, low quality"] * 4,
generator=generator,
num_inference_steps=20,
)
image_grid(output.images, 2, 2)
結合多種條件
單個影像生成可以結合多個 ControlNet 條件。將 ControlNet 列表傳遞給管道的建構函式,並將相應的條件列表傳遞給 __call__
。
在結合條件時,遮蓋條件使其不重疊是很有幫助的。在這個例子中,我們遮蓋了 canny 對映的中間部分,其中包含姿勢條件。
改變 controlnet_conditioning_scale
也可以幫助強調一個條件而不是另一個。
Canny 條件
原始圖片
準備條件
from diffusers.utils import load_image
from PIL import Image
import cv2
import numpy as np
from diffusers.utils import load_image
canny_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/landscape.png"
)
canny_image = np.array(canny_image)
low_threshold = 100
high_threshold = 200
canny_image = cv2.Canny(canny_image, low_threshold, high_threshold)
# zero out middle columns of image where pose will be overlayed
zero_start = canny_image.shape[1] // 4
zero_end = zero_start + canny_image.shape[1] // 2
canny_image[:, zero_start:zero_end] = 0
canny_image = canny_image[:, :, None]
canny_image = np.concatenate([canny_image, canny_image, canny_image], axis=2)
canny_image = Image.fromarray(canny_image)
Openpose 條件
原始圖片
準備條件
from controlnet_aux import OpenposeDetector
from diffusers.utils import load_image
openpose = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
openpose_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/person.png"
)
openpose_image = openpose(openpose_image)
執行帶有多個條件的 ControlNet
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
import torch
controlnet = [
ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-openpose", torch_dtype=torch.float16),
ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16),
]
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_xformers_memory_efficient_attention()
pipe.enable_model_cpu_offload()
prompt = "a giant standing in a fantasy landscape, best quality"
negative_prompt = "monochrome, lowres, bad anatomy, worst quality, low quality"
generator = torch.Generator(device="cpu").manual_seed(1)
images = [openpose_image, canny_image]
image = pipe(
prompt,
images,
num_inference_steps=20,
generator=generator,
negative_prompt=negative_prompt,
controlnet_conditioning_scale=[1.0, 0.8],
).images[0]
image.save("./multi_controlnet_output.png")
在這些示例中,我們探索了 StableDiffusionControlNetPipeline
的多個方面,以展示透過 Diffusers 使用 ControlNet 是多麼容易和直觀。但是,我們沒有涵蓋 ControlNet 支援的所有條件型別。要了解更多資訊,我們鼓勵您檢視相應的模型文件頁面:
- lllyasviel/sd-controlnet-depth
- lllyasviel/sd-controlnet-hed
- lllyasviel/sd-controlnet-normal
- lllyasviel/sd-controlnet-scribble
- lllyasviel/sd-controlnet-seg
- lllyasviel/sd-controlnet-openpose
- lllyasviel/sd-controlnet-mlsd
- lllyasviel/sd-controlnet-canny
我們歡迎您組合這些不同的元素,並與 @diffuserslib 分享您的成果。務必檢視Colab 筆記本,體驗上述一些示例!
我們還展示了一些技術,透過使用快速排程器、智慧模型解除安裝和 xformers
來加快生成過程並節省記憶體。結合這些技術,生成過程在 V100 GPU 上僅需約 3 秒,單張影像僅消耗約 4 GB VRAM ⚡️ 在 Google Colab 等免費服務上,生成時間約為 5 秒(預設 GPU 為 T4),而原始實現需要 17 秒才能建立相同結果!將 diffusers
工具箱中的所有元件結合起來真是一項超能力 💪
總結
我們一直在大量使用 StableDiffusionControlNetPipeline
,到目前為止我們的體驗非常有趣!我們很高興看到社群在這個管道之上構建什麼。如果您想檢視 Diffusers 中支援的其他允許受控生成的管道和技術,請檢視我們的 官方文件。
如果您迫不及待地想直接嘗試 ControlNet,我們也為您提供了!只需單擊以下空間之一即可玩轉 ControlNet