自从 Stable Diffusion 席卷全球以来,人们一直在寻找更多控制生成过程结果的方法。ControlNet 提供了一个简洁的界面,允许用户在很大程度上自定义生成过程。通过 ControlNet ,用户可以轻松地使用不同的空间上下文(如深度图、分割图、涂鸦、关键点等)来调整生成过程!
我们可以将卡通图转化为具有令人难以置信的一致性的逼真照片。
逼真的 Lofi 女孩 |
---|
![]() |
甚至可以将其用作室内设计师。
之前 | 之后 |
---|---|
![]() |
![]() |
您可以将您的草图涂鸦转化为艺术绘画。
之前 | 之后 |
---|---|
![]() |
![]() |
此外,还可以让一些著名的标志活起来。
之前 | 之后 |
---|---|
![]() |
![]() |
有了 ControlNet ,天空就是极限 🌠
在这篇博文中,我们首先介绍了 StableDiffusionControlNetPipeline
,然后展示了如何将其应用于各种控制条件。让我们开始控制吧!
ControlNet: 简介
ControlNet 是由 Lvmin Zhang 和 Maneesh Agrawala 在《Adding Conditional Control to Text-to-Image Diffusion Models》中提出的。它引入了一个框架,可以支持各种空间上下文,作为 Stable Diffusion 等 Diffusion 模型的额外条件。扩散器的实现是根据原始源代码进行了调整。
训练 ControlNet 包括以下步骤:
- 克隆 Diffusion 模型(如 Stable Diffusion 的潜在 UNet)的预训练参数(称为“可训练副本”),同时保留预训练参数的独立副本(“锁定副本”)。这样做是为了让锁定的参数副本保留从大型数据集中学到的丰富知识,而可训练副本用于学习特定任务的方面。
- 可训练副本和锁定副本的参数通过“零卷积”层连接(有关更多信息,请参见此处),作为 ControlNet 框架的一部分进行优化。这是一种训练技巧,用于在训练新条件时保留已经由冻结模型学习的语义。
从图像上看,训练 ControlNet 的过程如下所示:
图片取自此处。
类似于 ControlNet 训练集的样本如下所示(通过边缘图进行附加调节):
提示 | 原始图像 | 条件 |
---|---|---|
“鸟” | ![]() |
![]() |
同样地,如果我们要用语义分割图来作为ControlNet的条件,一个训练样本会是这样的:
提示 | 原始图像 | 条件 |
---|---|---|
“大房子” | ![]() |
![]() |
每一种新的条件都需要训练一个新的ControlNet权重副本。论文提出了8种不同的条件模型,Diffusers都支持这些模型!
对于推理,需要预训练的扩散模型权重以及经过训练的ControlNet权重。例如,使用稳定的扩散v1-5和一个ControlNet检查点相比,需要大约7亿个额外的参数,而只使用原始的稳定扩散模型则不需要这些参数,这使得ControlNet在推理过程中需要更多的内存。
由于预训练的扩散模型在训练过程中被锁定,所以只需要在使用不同条件时更换ControlNet的参数。这使得在一个应用程序中部署多个ControlNet权重变得相当简单,正如我们将在下面看到的那样。
StableDiffusionControlNetPipeline
在开始之前,我们要衷心感谢社区贡献者Takuma Mori为将ControlNet整合到Diffusers中 ❤️。
为了尝试ControlNet,Diffusers提供了与其他Diffusers管道类似的StableDiffusionControlNetPipeline
。在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
我们将使用著名的画作“戴珍珠耳环的少女”作为例子。所以,让我们下载这幅图片并看一下:
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
)
我们使用UniPCMultistepScheduler而不是使用稳定扩散的默认PNDMScheduler。选择一个改进的调度器可以大大减少推理时间 – 在我们的情况下,我们能够将推理步骤的数量从50减少到20,同时基本保持相同的图像生成质量。关于调度器的更多信息可以在这里找到。
from diffusers import UniPCMultistepScheduler
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
我们不是直接将管道加载到GPU上,而是启用智能CPU卸载,可以使用enable_model_cpu_offload
函数实现。
请记住,在推理扩散模型(例如稳定扩散)时,不仅需要一个模型组件,而是需要多个模型组件按顺序运行。在稳定扩散与ControlNet的情况下,我们首先使用CLIP文本编码器,然后是扩散模型unet和控制网络,然后是VAE解码器,最后运行安全检查器。大多数组件在扩散过程中仅运行一次,因此不需要一直占用GPU内存。通过启用智能模型卸载,我们确保每个组件仅在需要时加载到GPU中,从而显著节省内存消耗而不会显着减慢推理速度。
注意:在运行enable_model_cpu_offload
时,请不要手动将管道移动到GPU上,即.to("cuda")
– 一旦启用了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 = ", 最佳质量,非常详细"
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=["单色,低分辨率,解剖错误,最差质量,低质量"] * 4,
num_inference_steps=20,
generator=generator,
)
image_grid(output.images, 2, 2)
我们可以轻松地将ControlNet与微调相结合!例如,我们可以对DreamBooth模型进行微调,并将其用于将自己渲染到不同的场景中。
在本文中,我们将使用我们心爱的土豆先生作为示例,展示如何使用ControlNet与DreamBooth。
我们可以使用相同的ControlNet。然而,与其使用稳定扩散1.5,我们将加载土豆先生模型到我们的流程中 – 土豆先生是一个使用Dreambooth进行土豆先生概念微调的稳定扩散模型🥔
让我们再次运行上述命令,尽管保持相同的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 = "一张 sks 土豆先生的照片,最佳质量,非常详细"
output = pipe(
prompt,
canny_image,
negative_prompt="单色,低分辨率,解剖错误,最差质量,低质量",
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 = "超级英雄角色,最佳质量,极其详细"
output = pipe(
[prompt] * 4,
poses,
negative_prompt=["单色,低分辨率,解剖不良,最差质量,低质量"] * 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_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 = "一个站在幻想景观中的巨人,最佳质量"
negative_prompt = "单色,低分辨率,解剖不良,最差质量,低质量"
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等免费服务中,生成默认GPU(T4)大约需要5秒,而原始实现需要17秒才能创建相同的结果!将diffusers
工具箱中的所有部分组合起来,就是一个真正的超能力💪
结论
我们一直在玩弄StableDiffusionControlNetPipeline
,到目前为止,我们的经验一直很有趣!我们很期待看到社区在这个管道上构建的内容。如果您想了解Diffusers中支持的其他管道和技术,以实现受控生成,请查看我们的官方文档。
如果您迫不及待地想直接尝试ControlNet,我们也可以提供帮助!只需单击以下任一空间,即可玩转ControlNet: