# ControlNet

[ControlNet](https://huggingface.co/papers/2302.05543)은 텍스트 프롬프트를 유지하면서 구조적 제어 이미지(엣지, 깊이, 또는 포즈)로 사전 학습된 diffusion 모델을 제어합니다. 기본 모델은 동결된 채로, 잔차를 통해 denoiser를 안내하는 병렬 네트워크가 추가되므로 fine-tuning 없이 레이아웃 제어가 가능합니다.

필요한 컨트롤(예: canny)에 해당하는 ControlNet을 로드한 후, [from_pretrained()](/docs/diffusers/main/ko/api/pipelines/overview#diffusers.DiffusionPipeline.from_pretrained)에 `controlnet=`로 전달합니다. `controlnet_conditioning_scale`를 사용하여 컨트롤이 생성을 얼마나 강하게 유도하는지 설정합니다.

> [!TIP]
> ControlNet은 [Flux](../api/pipelines/controlnet_flux), [Hunyuan-DiT](../api/pipelines/controlnet_hunyuandit), [Stable Diffusion 3](../api/pipelines/controlnet_sd3) 등 다양한 모델에서 사용할 수 있습니다. 이 가이드의 예제는 Flux와 Stable Diffusion XL를 사용합니다. 구형 Stable Diffusion 계열 체크포인트에 대한 T2I-Adapter는 [Legacy adapters](./legacy_adapters#t2i-adapter)를 참고하세요. 새로운 제어 가능 생성 작업에는 ControlNet 사용을 권장합니다.

아래 탭에는 텍스트-이미지 생성, 이미지-이미지 변환, 인페인팅을 위한 ControlNet이 표시됩니다.

[opencv-python](https://github.com/opencv/opencv-python)으로 canny 이미지를 생성합니다.

```py
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` 매개변수를 사용하여 컨트롤에 부여할 가중치를 결정합니다.

```py
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]
```

  
    
    원본 이미지
  
  
    
    canny 이미지
  
  
    
    생성된 이미지
  

Transformers의 깊이 추정 파이프라인을 사용하여 깊이 맵을 생성합니다.

```py
import torch
import numpy as np
from PIL import Image
from transformers import DPTImageProcessor, DPTForDepthEstimation
from diffusers import ControlNetModel, StableDiffusionXLControlNetImg2ImgPipeline, AutoencoderKL
from diffusers.utils import load_image

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

image = load_image(
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/non-enhanced-prompt.png"
).resize((1024, 1024))
depth_image = get_depth_map(image)
```

깊이 맵을 파이프라인에 전달합니다. `controlnet_conditioning_scale` 매개변수를 사용하여 컨트롤에 부여할 가중치를 결정합니다.

```py
controlnet = 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 = StableDiffusionXLControlNetImg2ImgPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    controlnet=controlnet,
    vae=vae,
    dtype=torch.float16,
).to(device)

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.
"""
controlnet_conditioning_scale = 0.5
pipeline(
    prompt,
    image=image,
    control_image=depth_image,
    controlnet_conditioning_scale=controlnet_conditioning_scale,
    strength=0.99,
    num_inference_steps=100,
).images[0]
```

  
    
    원본 이미지
  
  
    
    깊이 맵
  
   
    
    생성된 이미지
  

바꿀 픽셀을 표시하는 마스크 이미지를 생성한 후, 컨트롤 이미지와 함께 `mask_image`로 전달합니다.

```py
import cv2
import torch
import numpy as np
from PIL import Image
from diffusers.utils import load_image
from diffusers import StableDiffusionXLControlNetInpaintPipeline, ControlNetModel

init_image = load_image(
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/non-enhanced-prompt.png"
)
init_image = init_image.resize((1024, 1024))
mask_image = load_image(
    "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat_mask.png"
)
mask_image = mask_image.resize((1024, 1024))

def make_canny_condition(image):
    image = np.array(image)
    image = cv2.Canny(image, 100, 200)
    image = image[:, :, None]
    image = np.concatenate([image, image, image], axis=2)
    image = Image.fromarray(image)
    return image

control_image = make_canny_condition(init_image)
```

마스크와 제어 이미지를 파이프라인에 전달합니다. `controlnet_conditioning_scale` 매개변수를 사용하여 제어에 부여할 가중치를 결정합니다.

```py
controlnet = ControlNetModel.from_pretrained(
    "diffusers/controlnet-canny-sdxl-1.0", dtype=torch.float16
)
pipeline = StableDiffusionXLControlNetInpaintPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0", controlnet=controlnet, dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
pipeline(
    "a cute and fluffy bunny rabbit",
    num_inference_steps=100,
    strength=0.99,
    controlnet_conditioning_scale=0.5,
    image=init_image,
    mask_image=mask_image,
    control_image=control_image,
).images[0]
```

  
    
    원본 이미지
  
  
    
    마스크 이미지
  
   
    
    생성된 이미지
  

## Multi-ControlNet

여러 ControlNet(예: canny + depth)을 리스트로 전달하여 조합합니다. 겹치는 영역은 가급적 마스크 처리하고, 각 `controlnet_conditioning_scale`를 조정합니다. `canny_image`는 OpenCV Canny(텍스트-이미지 생성과 동일하게)로, `depth_image`는 DPT(이미지-이미지 생성과 동일하게)로 생성합니다. 파이프라인을 호출하기 전에 모든 제어 이미지를 파이프라인 크기로 리사이즈합니다.

```py
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 이미지
  
  
    
    깊이 맵
  
   
    
    생성된 이미지
  

## 추측 모드

텍스트 프롬프트가 없거나 원하지 않을 때 컨트롤만으로도 이미지를 생성하는 [Guess mode](https://github.com/lllyasviel/ControlNet/discussions/188)입니다. 이 모드는 ControlNet 잔차를 블록 깊이에 따라 스케일링하며, 초기 다운 블록에서는 약 `0.1`에서 중간 블록에서는 `1.0`까지 적용합니다. 이를 통해 초기 레이어가 과도하게 제약되지 않으면서도 더 깊은 블록이 구조를 유지할 수 있습니다.

```py
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 이미지
  
  
    
    생성된 이미지
  

