Diffusers 集成 ControlNet:StableDiffusionControlNetPipeline 使用教程
ControlNet in 🧨 Diffusers
Hugging Face 博客介绍 Diffusers 中的 StableDiffusionControlNetPipeline,并演示用 Canny、OpenPose 等条件控制 Stable Diffusion 生成图像。
官方教程给出 ControlNet 在 Diffusers 中的完整用法,并配可运行代码与显存推理耗时对比,便于复现其各类条件控制。
自从 Stable Diffusion 席卷全球以来,人们一直在寻找方法来更好地控制生成过程的结果。ControlNet 提供了一个极简接口,使用户能够在很大程度上自定义生成过程。借助 ControlNet,用户可以轻松地利用不同的空间上下文(如深度图、分割图、涂鸦、关键点等)来对生成进行条件化!
我们可以将卡通画转化为具有惊人连贯性的逼真照片。
| 逼真的 Lofi Girl |
|---|
![]() |
甚至可以用它作为你的室内设计师。
| 之前 | 之后 |
|---|---|
![]() |
![]() |
你可以将你的素描涂鸦变成一幅艺术画。
| 之前 | 之后 |
|---|---|
![]() |
![]() |
此外,还可以让一些著名的标志栩栩如生。
| 之前 | 之后 |
|---|---|
![]() |
![]() |
有了 ControlNet,一切皆有可能 🌠
在这篇博客文章中,我们首先介绍 StableDiffusionControlNetPipeline,然后展示它如何应用于各种控制条件。让我们开始控制吧!
ControlNet:概览
ControlNet 由 Lvmin Zhang 和 Maneesh Agrawala 在 Adding Conditional Control to Text-to-Image Diffusion Models 中提出。 它引入了一个框架,支持将各种空间上下文作为额外的条件用于 Stable Diffusion 等扩散模型。 diffusers 实现改编自原始源代码。
训练 ControlNet 包括以下步骤:
- 克隆扩散模型(如 Stable Diffusion 的潜在 UNet)的预训练参数(称为“可训练副本”),同时单独维护预训练参数(“锁定副本”)。这样做是为了让锁定的参数副本能够保留从大型数据集中学到的丰富知识,而可训练副本则用于学习特定任务的方面。
- 参数的可训练副本和锁定副本通过“零卷积”层连接(更多信息见此处),这些层作为 ControlNet 框架的一部分进行优化。这是一种训练技巧,用于在训练新条件时保留冻结模型已经学到的语义。
用图表示,训练 ControlNet 的过程如下:

该图取自此处。
用于类似 ControlNet 训练的训练集样本如下所示(额外条件通过边缘图提供):
| 提示词 | 原始图像 | 条件 |
|---|---|---|
| "bird" | ![]() |
![]() |
同样,如果我们用语义分割图对 ControlNet 进行条件化,训练样本将如下所示:
| 提示词 | 原始图像 | 条件 |
|---|---|---|
| "big house" | ![]() |
![]() |
每种新的条件类型都需要训练一份新的 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 Notebook 运行以下各节中展示的代码片段。
在我们开始之前,让我们确保已安装所有必要的库:
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
我们将在此示例中使用著名画作 "Girl With A Pearl"。所以,让我们下载图像并查看一下:
from diffusers.utils import load_image
image = load_image(
"https://hf.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,而是使用当前最快的 diffusion 模型调度器之一,称为 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 和 control net,接着是 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 微调一个模型,并用它把我们渲染到不同的场景中。
在这篇文章中,我们将以我们心爱的 Mr Potato Head 为例,展示如何将 ControlNet 与 DreamBooth 结合使用。
我们可以使用相同的 ControlNet。不过,我们不再使用 Stable Diffusion 1.5,而是将 Mr Potato Head 模型加载到我们的流程中——Mr Potato Head 是一个使用 Dreambooth 🥔 基于 Mr Potato Head 概念微调过的 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()
现在让 Mr Potato 为 Johannes Vermeer 摆姿势!
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]
值得注意的是,Mr Potato Head 并不是最佳候选,但他尽了最大努力,并且在捕捉某些精髓方面做得相当不错 🍟
ControlNet 的另一个独有应用是,我们可以从一张图像中提取姿势,并复用它来生成一张具有完全相同姿势的不同图像。所以在下一个示例中,我们将使用 Open Pose ControlNet 教超级英雄如何做瑜伽!
首先,我们需要获取一些人们做瑜伽的图像:
urls = "yoga1.jpeg", "yoga2.jpeg", "yoga3.jpeg", "yoga4.jpeg"
imgs = [
load_image("https://hf-mirror.com/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://hf-mirror.com/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://hf-mirror.com/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 Notebook,动手试试上面的一些示例!
我们还展示了一些技术,通过使用快速调度器、智能模型卸载和 xformers 来加快生成过程并减少内存占用。将这些技术结合起来,在 V100 GPU 上生成过程仅需约 3 秒,单张图像仅消耗约 4 GB 的显存 ⚡️ 在 Google Colab 等免费服务上,使用默认 GPU(T4)生成大约需要 5 秒,而原始实现生成相同结果需要 17 秒!将 diffusers 工具箱中的所有组件结合起来,真是一种超能力 💪
结论
我们一直在大量使用 StableDiffusionControlNetPipeline,到目前为止体验非常有趣!我们很期待看到社区在这个 pipeline 之上构建出什么。如果你想了解 Diffusers 中支持的其他可实现可控生成的 pipeline 和技术,请查看我们的官方文档。
如果你迫不及待想直接试用 ControlNet,我们也为你准备好了!只需点击以下某个 space 即可体验 ControlNet:
来源:Hugging Face Blog · hf-mirror.com










