前言

如果你跟我一樣,用 Stable Diffusion 生圖時常常覺得「結果很美,但不是我要的構圖」,那 ControlNet 絕對是你的救星。ControlNet 讓你可以用一張手繪草稿、一張深度圖、甚至一個人體姿勢骨架,去「引導」AI 生成符合你預期構圖的圖片。對於有明確視覺想法但畫技有限的工程師來說,這簡直是作弊級的工具。

這篇文章會帶你從 ControlNet 的基本原理開始,走過 Canny、Depth、OpenPose 三種最常用的模式,最後實際用一張手繪線稿生成精緻的成品圖。我會分享我自己在專案中使用的參數調校心得,希望能幫你少走一些彎路。

ControlNet 是什麼?

核心原理

ControlNet 是 2023 年由 Lvmin Zhang 等人提出的神經網路架構。簡單來說,它在原本的 Stable Diffusion U-Net 旁邊「接了一條旁路」,這條旁路接收額外的條件輸入(比如邊緣圖、深度圖),然後把處理後的特徵注入回主網路,讓生成過程受到空間上的約束。

用白話講:Stable Diffusion 本來只聽文字 prompt 的話,現在 ControlNet 多給了一張「施工藍圖」,AI 會照著藍圖的結構去蓋房子,但細節和風格還是由 prompt 決定。

架構簡圖

輸入圖片 (手繪稿/照片)
    │
    ▼
[前處理器] ──→ 條件圖 (Canny邊緣/Depth深度/Pose骨架)
                  │
                  ▼
            [ControlNet 旁路網路]
                  │
                  ├──→ 注入 U-Net 各層
                  │
[文字 Prompt] → [CLIP] → [U-Net 主網路] → 生成圖片

關鍵在於 ControlNet 複製了 U-Net encoder 的權重作為初始化,然後透過「零卷積」(zero convolution) 層連接回主網路。訓練時只更新旁路的權重,不動原本的模型,所以不會破壞已經學好的生成能力。

三大常用模式

Canny 邊緣偵測

Canny 是最直覺的模式。它把輸入圖片轉成黑白邊緣線條,AI 會沿著這些邊緣生成內容。

適合場景:

  • 手繪線稿轉精緻插畫
  • 保留建築物或物件的輪廓
  • 需要精確邊緣控制的情況
# 使用 OpenCV 做 Canny 邊緣偵測(前處理)
import cv2
import numpy as np

img = cv2.imread("my_sketch.png") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# low_threshold 和 high_threshold 影響邊緣敏感度 # 手繪稿建議用較低的值,才能抓到淡淡的鉛筆線 low_threshold = 50 high_threshold = 150

canny = cv2.Canny(gray, low_threshold, high_threshold) cv2.imwrite("canny_output.png", canny)

參數調校建議:

  • Control Weight:0.7-1.0,太低會忽略線稿,太高會讓圖變得僵硬
  • Starting Control Step:0.0(從一開始就引導)
  • Ending Control Step:0.8-1.0(留一點空間讓 AI 自由發揮細節)

Depth 深度圖

Depth 模式用深度資訊來控制構圖。前景和背景的空間關係會被保留,但邊緣的約束比 Canny 鬆很多。

適合場景:

  • 保留場景的空間佈局
  • 從照片轉換風格但保持構圖
  • 室內設計或場景概念圖
# 使用 MiDaS 模型生成深度圖
import torch
import cv2
import numpy as np

# 載入 MiDaS 模型 model_type = "DPT_Large" midas = torch.hub.load("intel-isl/MiDaS", model_type) midas.eval()

# 載入對應的 transform midas_transforms = torch.hub.load("intel-isl/MiDaS", "transforms") transform = midas_transforms.dpt_transform

img = cv2.imread("scene_photo.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) input_batch = transform(img_rgb).unsqueeze(0)

with torch.no_grad(): depth = midas(input_batch) depth = torch.nn.functional.interpolate( depth.unsqueeze(1), size=img_rgb.shape[:2], mode="bicubic", align_corners=False, ).squeeze()

# 正規化到 0-255 depth_np = depth.cpu().numpy() depth_norm = cv2.normalize(depth_np, None, 0, 255, cv2.NORM_MINMAX) cv2.imwrite("depth_output.png", depth_norm.astype(np.uint8))

OpenPose 人體姿勢

OpenPose 偵測人體的關節點,生成骨架圖。AI 會按照骨架的姿勢生成人物,但衣著、風格完全由 prompt 決定。

適合場景:

  • 控制人物姿勢
  • 角色設計中保持一致的動作
  • 多人構圖的位置安排
# 在 Stable Diffusion WebUI 的 API 中使用 OpenPose ControlNet
import requests
import base64

def encode_image(path): with open(path, "rb") as f: return base64.b64encode(f.read()).decode()

payload = { "prompt": "1girl, warrior armor, dynamic pose, fantasy art, detailed", "negative_prompt": "low quality, blurry, deformed", "steps": 30, "sampler_name": "DPM++ 2M Karras", "width": 512, "height": 768, "cfg_scale": 7, "alwayson_scripts": { "controlnet": { "args": [ { "enabled": True, "module": "openpose_full", "model": "control_v11p_sd15_openpose", "weight": 0.85, "input_image": encode_image("pose_reference.png"), "processor_res": 512, "guidance_start": 0.0, "guidance_end": 0.9, "control_mode": 0, # Balanced } ] } } }

response = requests.post( "http://localhost:7860/sdapi/v1/txt2img", json=payload ) result = response.json()

實戰:手繪線稿轉精緻圖

這是我最常用的工作流。步驟如下:

Step 1:畫草稿

不需要畫得多好,用 iPad 或紙筆畫個大致的構圖就好。重點是:

  • 線條要清楚(鉛筆太淡會偵測不到)
  • 主要結構要畫出來
  • 不需要畫細節,AI 會幫你補

Step 2:前處理

import cv2
import numpy as np

# 讀取手繪稿(通常拍照或掃描) sketch = cv2.imread("hand_drawn_sketch.jpg", cv2.IMREAD_GRAYSCALE)

# 提高對比度,讓線條更清晰 clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) enhanced = clahe.apply(sketch)

# 二值化,去掉紙張紋理 _, binary = cv2.threshold(enhanced, 180, 255, cv2.THRESH_BINARY)

# 反轉(ControlNet 預期白線黑底) inverted = cv2.bitwise_not(binary)

# 調整到目標解析度 resized = cv2.resize(inverted, (512, 768)) cv2.imwrite("processed_sketch.png", resized)

Step 3:ControlNet 生成

# 完整的 ControlNet 生成流程(使用 diffusers)
from diffusers import (
    StableDiffusionControlNetPipeline,
    ControlNetModel,
    UniPCMultistepScheduler,
)
from PIL import Image
import torch

# 載入 ControlNet 模型 controlnet = ControlNetModel.from_pretrained( "lllyasviel/control_v11p_sd15_canny", torch_dtype=torch.float16, )

# 載入主模型 + ControlNet pipe = StableDiffusionControlNetPipeline.from_pretrained( "runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16, ) pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config) pipe.to("cuda")

# 載入處理好的線稿 control_image = Image.open("processed_sketch.png")

# 生成 result = pipe( prompt="detailed fantasy landscape, castle on hill, river, sunset, " "digital art, highly detailed, artstation", negative_prompt="low quality, blurry, watermark, text", image=control_image, num_inference_steps=30, guidance_scale=7.5, controlnet_conditioning_scale=0.8, # ControlNet 權重 generator=torch.Generator("cuda").manual_seed(42), ).images[0]

result.save("final_output.png")

參數調校心得

經過大量實驗,以下是我整理的調校建議:

| 參數 | 建議範圍 | 說明 |
|——|———-|——|
| controlnet_conditioning_scale | 0.5-1.0 | 低值 = 更自由,高值 = 更忠於線稿 |
| guidance_scale (CFG) | 6-9 | 配合 ControlNet 不要太高,否則過度飽和 |
| num_inference_steps | 25-40 | 30 步通常就夠了 |
| Ending Control Step | 0.7-1.0 | 提早結束可以讓細節更自然 |

常見問題與解法:

  1. 線稿太粗導致圖很僵硬 → 降低 controlnet_conditioning_scale 到 0.5-0.6
  2. AI 完全忽略線稿 → 確認前處理的圖是否正確,線條是否清晰
  3. 細節不夠豐富 → 試著把 Ending Control Step 設到 0.7,讓後面幾步自由發揮
  4. 多個 ControlNet 衝突 → 降低各自的 weight,總和建議不超過 1.5

小結

ControlNet 徹底改變了 AI 生圖的可控性。對我來說,最大的價值是:我終於可以把腦中的構圖「畫」出來,不管我畫得多醜,AI 都能幫我轉成精緻的成品。

如果你想進一步探索,推薦看看:

  • Multi-ControlNet:同時用 Canny + Depth,一個控制邊緣、一個控制空間
  • ControlNet 1.1:更多模式,包括 Shuffle、Tile、Instruct Pix2Pix
  • IP-Adapter:不只用線稿,還能用一張參考圖來控制風格

手繪草稿 + ControlNet 是我目前覺得「人機協作」最舒服的工作流之一。你不需要是藝術家,只需要有想法,AI 會幫你把想法實現。