Diffusers 文档

ControlNet

Hugging Face's logo
加入 Hugging Face 社区

并获得增强型文档体验

开始

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 图像

接下来,加载一个以 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 图像

加载一个以 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 并传递 imagecontrolnet_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。为了获得更好的结果,通常需要

  1. 掩盖条件,使其不重叠(例如,掩盖 Canny 图像中姿态条件所在区域的区域)
  2. 尝试使用 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)
原始图像
canny 图像

对于人体姿态估计,请安装 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)
< > 在 GitHub 上更新