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 direciona um modelo de difusão pré-treinado com uma imagem de controle estrutural (bordas, profundidade ou pose) enquanto você mantém um prompt de texto. Ele congela o modelo base e adiciona uma rede paralela cujos resíduos guiam o desruidador, permitindo controle de layout sem ajuste fino.

Carregue um ControlNet para o controle de que você precisa (por exemplo, canny) e, em seguida, passe-o como controlnet= para from_pretrained(). Use controlnet_conditioning_scale para definir com que intensidade o controle direciona a geração.

ControlNets estão disponíveis para muitos modelos, como Flux, Hunyuan-DiT, Stable Diffusion 3 e outros. Os exemplos neste guia usam Flux e Stable Diffusion XL. Para T2I-Adapter em checkpoints mais antigos da família Stable Diffusion, consulte Adaptadores legados. Prefira ControlNet para novos trabalhos de geração controlável.

As abas abaixo mostram o ControlNet para texto-para-imagem, imagem-para-imagem e inpainting.

texto-para-imagem
imagem-para-imagem
inpainting

Gere uma imagem canny com 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)

Passe a imagem canny para o pipeline. Use o parâmetro controlnet_conditioning_scale para determinar quanto peso atribuir ao controle.

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)
imagem original
Control image (Canny edges)
imagem canny
Generated image (ControlNet + prompt)
imagem gerada

Multi-ControlNet

Compose vários ControlNets (por exemplo, canny + depth) passando-os como uma lista. Mescle as regiões sobrepostas quando possível e ajuste cada controlnet_conditioning_scale. Construa canny_image com OpenCV Canny (como em text-to-image) e depth_image com DPT (como em image-to-image). Redimensione todas as imagens de controle para o tamanho do pipeline antes de chamar o 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
imagem canny
Depth map
mapa de profundidade
Generated image (ControlNet + prompt)
imagem gerada

Modo de suposição

Modo de estimativa gera uma imagem a partir de um controle isolado quando você não tem (ou não deseja) um prompt de texto. Ele escala os resíduos do ControlNet pela profundidade do bloco, de aproximadamente 0.1 nos blocos de downblock iniciais até 1.0 no bloco intermediário. Isso impede que as camadas iniciais super-restrinjam a geração, enquanto os blocos mais profundos ainda carregam a estrutura.

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
imagem canny
Generated image (Guess mode)
imagem gerada
Update on GitHub