ControlNet
ControlNet 是一种模型,它通过对模型进行额外的输入图像条件化来控制图像扩散模型。可以使用许多类型的条件输入(canny 边缘、用户草图、人体姿态、深度等)来控制扩散模型。这非常有用,因为它使您能够更好地控制图像生成,从而更容易生成特定图像,而无需过多地尝试不同的文本提示或去噪值。
查看 ControlNet 论文 v1 的第 3.5 节,了解在各种条件输入上实施 ControlNet 的列表。您可以在 lllyasviel 的 Hub 个人资料中找到官方的 Stable Diffusion ControlNet 条件模型,以及更多在 Hub 上 社区训练的 模型。
对于 Stable Diffusion XL (SDXL) ControlNet 模型,您可以在 🤗 Diffusers Hub 组织中找到它们,或者您可以在 Hub 上浏览 社区训练的 模型。
ControlNet 模型有两组权重(或块),由一个零卷积层连接
- 一个 锁定副本 保留了大型预训练扩散模型所学到的所有内容
- 一个 可训练副本 在额外的条件输入上进行训练
由于锁定副本保留了预训练模型,因此在新的条件输入上训练和实现 ControlNet 与微调任何其他模型一样快,因为您无需从头开始训练模型。
本指南将向您展示如何在文本到图像、图像到图像、修复等场景中使用 ControlNet!您可以选择许多类型的 ControlNet 条件输入,但在本指南中,我们只关注其中几种。欢迎尝试其他条件输入!
在开始之前,请确保您已安装以下库
# uncomment to install the necessary libraries in Colab
#!pip install -q diffusers transformers accelerate opencv-python
文本到图像
对于文本到图像,您通常会将文本提示传递给模型。但是,使用 ControlNet,您可以指定额外的条件输入。让我们用 canny 图像(黑色背景上的图像白色轮廓)对模型进行条件化。这样,ControlNet 可以使用 canny 图像作为控制,引导模型生成具有相同轮廓的图像。
加载图像并使用 opencv-python 库提取 canny 图像
from diffusers.utils import load_image, make_image_grid
from PIL import Image
import cv2
import numpy as np
original_image = load_image(
"https://hf.co/datasets/huggingface/documentation-images/resolve/main/diffusers/input_image_vermeer.png"
)
image = np.array(original_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 边缘检测为条件的 ControlNet 模型,并将其传递给 StableDiffusionControlNetPipeline。使用更快的 UniPCMultistepScheduler 并启用模型卸载以加快推理速度并减少内存使用。
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
import torch
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16, use_safetensors=True)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16, use_safetensors=True
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
现在将您的提示和 canny 图像传递给管道
output = pipe(
"the mona lisa", image=canny_image
).images[0]
make_image_grid([original_image, canny_image, output], rows=1, cols=3)
图像到图像
对于图像到图像,您通常会将初始图像和提示传递给管道以生成新图像。使用 ControlNet,您可以传递额外的条件输入来引导模型。让我们用深度图(包含空间信息的图像)对模型进行条件化。这样,ControlNet 可以使用深度图作为控制,引导模型生成保留空间信息的图像。
您将使用 StableDiffusionControlNetImg2ImgPipeline 来完成此任务,它与 StableDiffusionControlNetPipeline 不同,因为它允许您将初始图像作为图像生成过程的起点传递。
加载图像并使用 depth-estimation
Pipeline 从 🤗 Transformers 提取图像的深度图
import torch
import numpy as np
from transformers import pipeline
from diffusers.utils import load_image, make_image_grid
image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/controlnet-img2img.jpg"
)
def get_depth_map(image, depth_estimator):
image = depth_estimator(image)["depth"]
image = np.array(image)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
detected_map = torch.from_numpy(image).float() / 255.0
depth_map = detected_map.permute(2, 0, 1)
return depth_map
depth_estimator = pipeline("depth-estimation")
depth_map = get_depth_map(image, depth_estimator).unsqueeze(0).half().to("cuda")
接下来,加载一个以深度图为条件的 ControlNet 模型,并将其传递给 StableDiffusionControlNetImg2ImgPipeline。使用更快的 UniPCMultistepScheduler 并启用模型卸载以加快推理速度并减少内存使用。
from diffusers import StableDiffusionControlNetImg2ImgPipeline, ControlNetModel, UniPCMultistepScheduler
import torch
controlnet = ControlNetModel.from_pretrained("lllyasviel/control_v11f1p_sd15_depth", torch_dtype=torch.float16, use_safetensors=True)
pipe = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16, use_safetensors=True
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
现在将您的提示、初始图像和深度图传递给管道
output = pipe(
"lego batman and robin", image=image, control_image=depth_map,
).images[0]
make_image_grid([image, output], rows=1, cols=2)
修复
对于修复,您需要初始图像、蒙版图像和描述要替换蒙版内容的提示。ControlNet 模型允许您添加另一个控制图像来对模型进行条件化。让我们用修复蒙版对模型进行条件化。这样,ControlNet 可以使用修复蒙版作为控制,引导模型在蒙版区域内生成图像。
加载初始图像和蒙版图像
from diffusers.utils import load_image, make_image_grid
init_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/controlnet-inpaint.jpg"
)
init_image = init_image.resize((512, 512))
mask_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/controlnet-inpaint-mask.jpg"
)
mask_image = mask_image.resize((512, 512))
make_image_grid([init_image, mask_image], rows=1, cols=2)
创建一个函数来从初始图像和蒙版图像准备控制图像。这将创建一个张量,如果 mask_image
中的相应像素超过某个阈值,则将 init_image
中的像素标记为蒙版。
import numpy as np
import torch
def make_inpaint_condition(image, image_mask):
image = np.array(image.convert("RGB")).astype(np.float32) / 255.0
image_mask = np.array(image_mask.convert("L")).astype(np.float32) / 255.0
assert image.shape[0:1] == image_mask.shape[0:1]
image[image_mask > 0.5] = -1.0 # set as masked pixel
image = np.expand_dims(image, 0).transpose(0, 3, 1, 2)
image = torch.from_numpy(image)
return image
control_image = make_inpaint_condition(init_image, mask_image)
加载一个以修复为条件的 ControlNet 模型,并将其传递给 StableDiffusionControlNetInpaintPipeline。使用更快的 UniPCMultistepScheduler 并启用模型卸载以加快推理速度并减少内存使用。
from diffusers import StableDiffusionControlNetInpaintPipeline, ControlNetModel, UniPCMultistepScheduler
controlnet = ControlNetModel.from_pretrained("lllyasviel/control_v11p_sd15_inpaint", torch_dtype=torch.float16, use_safetensors=True)
pipe = StableDiffusionControlNetInpaintPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16, use_safetensors=True
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
现在将您的提示、初始图像、蒙版图像和控制图像传递给管道
output = pipe(
"corgi face with large ears, detailed, pixar, animated, disney",
num_inference_steps=20,
eta=1.0,
image=init_image,
mask_image=mask_image,
control_image=control_image,
).images[0]
make_image_grid([init_image, mask_image, output], rows=1, cols=3)
猜测模式
猜测模式 不需要向 ControlNet 提供任何提示!这将迫使 ControlNet 编码器尽力“猜测”输入控制图(深度图、姿态估计、Canny 边缘等)的内容。
猜测模式通过一个固定比例调整 ControlNet 输出残差的比例,该比例取决于块深度。最浅的 DownBlock
对应 0.1,随着块变得更深,比例呈指数增长,使得 MidBlock
输出的比例变为 1.0。
猜测模式不会影响提示条件,如果你想的话,你仍然可以提供提示。
在管道中设置 guess_mode=True
,建议将 guidance_scale
值设置为 3.0 到 5.0 之间。
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
from diffusers.utils import load_image, make_image_grid
import numpy as np
import torch
from PIL import Image
import cv2
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", use_safetensors=True)
pipe = StableDiffusionControlNetPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", controlnet=controlnet, use_safetensors=True).to("cuda")
original_image = load_image("https://huggingface.co/takuma104/controlnet_dev/resolve/main/bird_512x512.png")
image = np.array(original_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)
image = pipe("", image=canny_image, guess_mode=True, guidance_scale=3.0).images[0]
make_image_grid([original_image, canny_image, image], rows=1, cols=3)
ControlNet 与 Stable Diffusion XL
目前,与 Stable Diffusion XL (SDXL) 兼容的 ControlNet 模型并不多,但我们已经为 SDXL 训练了两个全尺寸 ControlNet 模型,它们以 Canny 边缘检测和深度图作为条件。我们还在尝试创建这些 SDXL 兼容 ControlNet 模型的较小版本,以便更容易在资源有限的硬件上运行。您可以在 🤗 Diffusers Hub 组织 上找到这些检查点!
让我们使用一个以 Canny 图像作为条件的 SDXL ControlNet 来生成图像。首先加载图像并准备 Canny 图像。
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel, AutoencoderKL
from diffusers.utils import load_image, make_image_grid
from PIL import Image
import cv2
import numpy as np
import torch
original_image = load_image(
"https://huggingface.co/datasets/hf-internal-testing/diffusers-images/resolve/main/sd_controlnet/hf-logo.png"
)
image = np.array(original_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)
make_image_grid([original_image, canny_image], rows=1, cols=2)
加载一个以 Canny 边缘检测为条件的 SDXL ControlNet 模型,并将其传递给 StableDiffusionXLControlNetPipeline。您也可以启用模型卸载以减少内存使用。
controlnet = ControlNetModel.from_pretrained(
"diffusers/controlnet-canny-sdxl-1.0",
torch_dtype=torch.float16,
use_safetensors=True
)
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16, use_safetensors=True)
pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
controlnet=controlnet,
vae=vae,
torch_dtype=torch.float16,
use_safetensors=True
)
pipe.enable_model_cpu_offload()
现在将您的提示(以及您要使用的话,可选的负面提示)和 Canny 图像传递给管道。
controlnet_conditioning_scale
参数决定为条件输入分配多少权重。建议将值设置为 0.5 以实现良好的泛化,但您可以随意尝试不同的数值!
prompt = "aerial view, a futuristic research complex in a bright foggy jungle, hard lighting"
negative_prompt = 'low quality, bad quality, sketches'
image = pipe(
prompt,
negative_prompt=negative_prompt,
image=canny_image,
controlnet_conditioning_scale=0.5,
).images[0]
make_image_grid([original_image, canny_image, image], rows=1, cols=3)
您也可以通过将参数设置为 True
,在猜测模式下使用 StableDiffusionXLControlNetPipeline。
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel, AutoencoderKL
from diffusers.utils import load_image, make_image_grid
import numpy as np
import torch
import cv2
from PIL import Image
prompt = "aerial view, a futuristic research complex in a bright foggy jungle, hard lighting"
negative_prompt = "low quality, bad quality, sketches"
original_image = load_image(
"https://hf.co/datasets/hf-internal-testing/diffusers-images/resolve/main/sd_controlnet/hf-logo.png"
)
controlnet = ControlNetModel.from_pretrained(
"diffusers/controlnet-canny-sdxl-1.0", torch_dtype=torch.float16, use_safetensors=True
)
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16, use_safetensors=True)
pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0", controlnet=controlnet, vae=vae, torch_dtype=torch.float16, use_safetensors=True
)
pipe.enable_model_cpu_offload()
image = np.array(original_image)
image = cv2.Canny(image, 100, 200)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
canny_image = Image.fromarray(image)
image = pipe(
prompt, negative_prompt=negative_prompt, controlnet_conditioning_scale=0.5, image=canny_image, guess_mode=True,
).images[0]
make_image_grid([original_image, canny_image, image], rows=1, cols=3)
您可以将优化器模型与 StableDiffusionXLControlNetPipeline
一起使用以提高图像质量,就像您使用常规的 StableDiffusionXLPipeline
一样。请参阅 优化图像质量 部分以了解如何使用优化器模型。确保使用 StableDiffusionXLControlNetPipeline
并传递 image
和 controlnet_conditioning_scale
。
base = StableDiffusionXLControlNetPipeline(...)
image = base(
prompt=prompt,
controlnet_conditioning_scale=0.5,
image=canny_image,
num_inference_steps=40,
denoising_end=0.8,
output_type="latent",
).images
# rest exactly as with StableDiffusionXLPipeline
多 ControlNet
将 SDXL 模型替换为类似 runwayml/stable-diffusion-v1-5 的模型,以使用 Stable Diffusion 模型的多重条件输入。
您可以将来自不同图像输入的多个 ControlNet 条件组合在一起以创建多 ControlNet。为了获得更好的结果,通常需要
- 掩盖条件,使其不重叠(例如,掩盖 Canny 图像中姿态条件所在区域的区域)
- 尝试使用
controlnet_conditioning_scale
参数,以确定为每个条件输入分配多少权重。
在本例中,您将结合 Canny 图像和人体姿态估计图像来生成新图像。
准备 Canny 图像条件。
from diffusers.utils import load_image, make_image_grid
from PIL import Image
import numpy as np
import cv2
original_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/landscape.png"
)
image = np.array(original_image)
low_threshold = 100
high_threshold = 200
image = cv2.Canny(image, low_threshold, high_threshold)
# zero out middle columns of image where pose will be overlaid
zero_start = image.shape[1] // 4
zero_end = zero_start + image.shape[1] // 2
image[:, zero_start:zero_end] = 0
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
canny_image = Image.fromarray(image)
make_image_grid([original_image, canny_image], rows=1, cols=2)
对于人体姿态估计,请安装 controlnet_aux。
# uncomment to install the necessary library in Colab
#!pip install -q controlnet-aux
准备人体姿态估计条件。
from controlnet_aux import OpenposeDetector
openpose = OpenposeDetector.from_pretrained("lllyasviel/ControlNet")
original_image = load_image(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/person.png"
)
openpose_image = openpose(original_image)
make_image_grid([original_image, openpose_image], rows=1, cols=2)
加载与每个条件相对应的 ControlNet 模型列表,并将它们传递给 StableDiffusionXLControlNetPipeline。使用更快的 UniPCMultistepScheduler 并启用模型卸载以减少内存使用。
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel, AutoencoderKL, UniPCMultistepScheduler
import torch
controlnets = [
ControlNetModel.from_pretrained(
"thibaud/controlnet-openpose-sdxl-1.0", torch_dtype=torch.float16
),
ControlNetModel.from_pretrained(
"diffusers/controlnet-canny-sdxl-1.0", torch_dtype=torch.float16, use_safetensors=True
),
]
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16, use_safetensors=True)
pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0", controlnet=controlnets, vae=vae, torch_dtype=torch.float16, use_safetensors=True
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
现在,您可以将您的提示(如果您正在使用的话,可选的负面提示)、Canny 图像和姿态图像传递给管道。
prompt = "a giant standing in a fantasy landscape, best quality"
negative_prompt = "monochrome, lowres, bad anatomy, worst quality, low quality"
generator = torch.manual_seed(1)
images = [openpose_image.resize((1024, 1024)), canny_image.resize((1024, 1024))]
images = pipe(
prompt,
image=images,
num_inference_steps=25,
generator=generator,
negative_prompt=negative_prompt,
num_images_per_prompt=3,
controlnet_conditioning_scale=[1.0, 0.8],
).images
make_image_grid([original_image, canny_image, openpose_image,
images[0].resize((512, 512)), images[1].resize((512, 512)), images[2].resize((512, 512))], rows=2, cols=3)