Diffusers documentation

ControlNet

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

ControlNet

ControlNet направляет предобученную диффузионную модель с помощью структурного управляющего изображения (контуров, глубины или позы), сохраняя при этом текстовый промпт. Базовая модель замораживается, а добавленная параллельная сеть, чьи остаточные сигналы направляют процесс денойзинга, обеспечивает контроль над компоновкой без необходимости дообучения.

Загрузите ControlNet для нужного вам типа управления (например, canny), затем передайте его в from_pretrained() как controlnet=. Используйте controlnet_conditioning_scale, чтобы задать, насколько сильно управление направляет генерацию.

ControlNet доступны для многих моделей, таких как Flux, Hunyuan-DiT, Stable Diffusion 3 и других. В примерах этого руководства используются Flux и Stable Diffusion XL. Для T2I-Adapter на более старых чекпоинтах семейства Stable Diffusion см. Legacy adapters. Для новых задач управляемой генерации предпочитайте ControlNet.

Вкладки ниже демонстрируют ControlNet для генерации изображений по тексту, преобразования изображения в изображение и инпейнтинга.

text-to-image
image-to-image
inpainting

Сгенерируйте изображение с эффектом canny с помощью opencv-python.

import cv2
import numpy as np
from PIL import Image
from diffusers.utils import load_image

original_image = load_image(
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/non-enhanced-prompt.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_conditioning_scale, чтобы определить, какой вес следует назначить управлению.

import torch
from diffusers.utils import load_image
from diffusers import FluxControlNetPipeline, FluxControlNetModel

controlnet = FluxControlNetModel.from_pretrained(
    "InstantX/FLUX.1-dev-Controlnet-Canny", dtype=torch.bfloat16
)
pipeline = FluxControlNetPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev", controlnet=controlnet, dtype=torch.bfloat16
).to("cuda")  # or "mps", "xpu", "cpu"

prompt = """
A photorealistic overhead image of a cat reclining sideways in a flamingo pool floatie holding a margarita. 
The cat is floating leisurely in the pool and completely relaxed and happy.
"""

pipeline(
    prompt, 
    control_image=canny_image,
    controlnet_conditioning_scale=0.5,
    num_inference_steps=50, 
    guidance_scale=3.5,
).images[0]
Generated image (prompt only)
original image
Control image (Canny edges)
canny image
Generated image (ControlNet + prompt)
сгенерированное изображение

Multi-ControlNet

Compose several ControlNets (for example canny + depth) by passing them as a list. Mask overlapping regions when you can, and tune each controlnet_conditioning_scale. Build canny_image with OpenCV Canny (as in text-to-image) and depth_image with DPT (as in image-to-image). Resize every control image to the pipeline size before you call the pipeline.

import cv2
import torch
import numpy as np
from PIL import Image
from transformers import DPTImageProcessor, DPTForDepthEstimation
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel, AutoencoderKL
from diffusers.utils import load_image

original_image = load_image(
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/non-enhanced-prompt.png"
)
image = np.array(original_image)
canny_image = Image.fromarray(
    np.concatenate([cv2.Canny(image, 100, 200)[:, :, None]] * 3, axis=2)
)

device = "cuda"  # or "mps", "xpu", "cpu"
depth_estimator = DPTForDepthEstimation.from_pretrained("Intel/dpt-hybrid-midas").to(device)
feature_extractor = DPTImageProcessor.from_pretrained("Intel/dpt-hybrid-midas")

def get_depth_map(image):
    image = feature_extractor(images=image, return_tensors="pt").pixel_values.to(device)
    with torch.no_grad(), torch.autocast(device):
        depth_map = depth_estimator(image).predicted_depth

    depth_map = torch.nn.functional.interpolate(
        depth_map.unsqueeze(1),
        size=(1024, 1024),
        mode="bicubic",
        align_corners=False,
    )
    depth_min = torch.amin(depth_map, dim=[1, 2, 3], keepdim=True)
    depth_max = torch.amax(depth_map, dim=[1, 2, 3], keepdim=True)
    depth_map = (depth_map - depth_min) / (depth_max - depth_min)
    image = torch.cat([depth_map] * 3, dim=1)
    image = image.permute(0, 2, 3, 1).cpu().numpy()[0]
    image = Image.fromarray((image * 255.0).clip(0, 255).astype(np.uint8))
    return image

depth_image = get_depth_map(original_image.resize((1024, 1024)))

controlnets = [
    ControlNetModel.from_pretrained(
        "diffusers/controlnet-canny-sdxl-1.0", dtype=torch.float16
    ),
    ControlNetModel.from_pretrained(
        "diffusers/controlnet-depth-sdxl-1.0-small", dtype=torch.float16
    ),
]

vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", dtype=torch.float16)
pipeline = StableDiffusionXLControlNetPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0", controlnet=controlnets, vae=vae, dtype=torch.float16
).to(device)

prompt = """
a relaxed rabbit sitting on a striped towel next to a pool with a tropical drink nearby, 
bright sunny day, vacation scene, 35mm photograph, film, professional, 4k, highly detailed
"""
negative_prompt = "lowres, bad anatomy, worst quality, low quality, deformed, ugly"

images = [canny_image.resize((1024, 1024)), depth_image.resize((1024, 1024))]

pipeline(
    prompt,
    negative_prompt=negative_prompt,
    image=images,
    num_inference_steps=100,
    controlnet_conditioning_scale=[0.5, 0.5],
).images[0]
Canny image
canny image
Depth map
карта глубины
Generated image (ControlNet + prompt)
сгенерированное изображение

Режим угадывания

Guess mode генерирует изображение на основе только контрольного сигнала, если у вас нет (или нет необходимости в) текстовом промпте. Он масштабирует остатки ControlNet по глубине блоков: от примерно 0.1 в ранних down-блоках до 1.0 в среднем блоке. Это предотвращает чрезмерное ограничение ранних слоев, в то время как более глубокие блоки продолжают передавать структуру.

import torch
from diffusers.utils import load_image
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel

controlnet = ControlNetModel.from_pretrained(
  "diffusers/controlnet-canny-sdxl-1.0", dtype=torch.float16
)
pipeline = StableDiffusionXLControlNetPipeline.from_pretrained(
  "stabilityai/stable-diffusion-xl-base-1.0",
  controlnet=controlnet,
  dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"

canny_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/canny-cat.png")
pipeline(
  "",
  image=canny_image,
  guess_mode=True
).images[0]
Canny image
canny image
Generated image (Guess mode)
сгенерированное изображение
Update on GitHub