前言
如果你跟我一樣,用 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 | 提早結束可以讓細節更自然 |
常見問題與解法:
- 線稿太粗導致圖很僵硬 → 降低
controlnet_conditioning_scale到 0.5-0.6 - AI 完全忽略線稿 → 確認前處理的圖是否正確,線條是否清晰
- 細節不夠豐富 → 試著把 Ending Control Step 設到 0.7,讓後面幾步自由發揮
- 多個 ControlNet 衝突 → 降低各自的 weight,總和建議不超過 1.5
小結
ControlNet 徹底改變了 AI 生圖的可控性。對我來說,最大的價值是:我終於可以把腦中的構圖「畫」出來,不管我畫得多醜,AI 都能幫我轉成精緻的成品。
如果你想進一步探索,推薦看看:
- Multi-ControlNet:同時用 Canny + Depth,一個控制邊緣、一個控制空間
- ControlNet 1.1:更多模式,包括 Shuffle、Tile、Instruct Pix2Pix
- IP-Adapter:不只用線稿,還能用一張參考圖來控制風格
手繪草稿 + ControlNet 是我目前覺得「人機協作」最舒服的工作流之一。你不需要是藝術家,只需要有想法,AI 會幫你把想法實現。