ControlNet:为文生图扩散模型注入精准空间条件控制

论文信息


开篇:文生图的「控制难题」

玩过Stable Diffusion的朋友大概率都有过这种崩溃:明明prompt写了「站在厨房的厨师,左手颠锅右手拿铲,身体正对镜头」,结果生成的图里人物姿势千奇百怪,手指还经常多出来几根;想画一张精准构图的场景图,改了几十版prompt,出来的画面还是和脑子里的构想差了十万八千里。

这就是文生图扩散模型的天生短板:纯文本prompt根本无法实现细粒度的空间构图控制。文字擅长描述语义和风格,却很难精准表达复杂的布局、人物姿势、物体轮廓、空间深度这些空间信息,想要一张符合预期的图,往往要经历无数次的prompt修改和试错。

而2023年横空出世的ControlNet,直接把这个行业难题给彻底解决了。它能让你用一张边缘图、姿势骨架图、深度图、甚至随手画的涂鸦,精准控制Stable Diffusion的生成过程,想让人物摆什么姿势就摆什么姿势,想让画面是什么构图就是什么构图,也因此成了AI绘图圈里当之无愧的「控制神器」。


研究背景:给大模型加控制的两大核心挑战

在ControlNet出现之前,业内已经做了很多尝试来解决扩散模型的控制问题,但都卡在了两个核心痛点上:

痛点1:空间控制的粒度不足

早期的控制方法,要么是靠修改prompt、调整CLIP特征、修改交叉注意力层,要么是用空间掩码做局部控制,只能解决简单的图像变体、局部重绘(inpainting)问题。面对深度图生成、姿势控制、边缘引导这类复杂的空间控制需求,这些方法要么效果拉胯,要么完全不work。想要真正实现端到端的精准空间控制,必须靠数据驱动的学习方案。

痛点2:微调大模型的「灾难性遗忘」

想要给预训练的大模型加新的条件控制,最直接的办法就是全模型微调,但这条路根本走不通:

  • 预训练的Stable Diffusion是用LAION-5B里数十亿张图片训出来的,而特定控制条件的数据集(比如人体姿势、边缘图),最大的也只有10万张左右,规模差了5万倍;
  • 直接用小数据集微调大模型,必然会出现过拟合灾难性遗忘------模型学会了控制条件,却把原本强大的文生图能力给丢了,生成的画面质量断崖式下跌。

业内也有一些轻量化微调方案来缓解这个问题,比如LoRA、Adapter、HyperNetwork、Side-Tuning等,这些方法通过冻结原模型、只训练少量新增参数来避免遗忘,但它们的网络结构都太浅,没法处理复杂的野外条件图和高层语义信息,控制效果始终达不到工业级要求。

ControlNet的出现,完美解决了这两个核心痛点:它既保留了预训练大模型的全部生成能力,又能学习到复杂的空间条件控制,甚至单张消费级显卡就能完成训练。


核心方法:ControlNet的架构设计

ControlNet的核心设计思路极其巧妙:锁住预训练大模型的全部参数,做一个可训练的编码器副本,用「零卷积」层把两者连接起来,既复用了大模型的强大特征提取能力,又不会破坏原模型的权重,还能学习到新的条件控制能力

3.1 ControlNet基本单元

我们先从最基础的神经块讲起,任何深度网络都是由一个个基础神经块堆叠而成的,比如ResNet块、Transformer块、卷积-归一化-激活块等。

基础神经块公式

一个预训练好的神经块,可以用这个公式表示:
y = F ( x ; Θ ) y=\mathcal {F}(x;\Theta ) y=F(x;Θ)

我们把公式里的每个字母拆解得明明白白,连大白话解释都给你安排上:

符号 专业名称 通俗解释
y y y 神经块输出特征图 这个网络块处理完输入后,输出的图片特征信息
F ( ⋅ ) \mathcal{F}(\cdot) F(⋅) 预训练神经块运算函数 Stable Diffusion里已经训好的网络模块,比如ResNet块、ViT注意力块,是大模型能力的核心
x x x 输入特征图 上一层网络传递过来的图片特征,是这个模块的处理对象
Θ \Theta Θ 预训练神经块的参数 这个模块里已经训好的权重和偏置,是我们要完全锁住、绝对不动的部分,保住大模型的「老本」
ControlNet的核心改造

给这个基础神经块加上ControlNet,只需要三步:

  1. 锁住原模型 :把原神经块的参数 Θ \Theta Θ完全冻结,训练过程中绝对不更新,彻底避免灾难性遗忘;
  2. 做可训练副本 :把原神经块完整克隆一份,得到参数为 Θ c \Theta_c Θc的可训练副本,初始值和原块 Θ \Theta Θ完全一致,训练时只有这个副本会更新;
  3. 零卷积连接:用两个「零卷积层」把副本和原块连接起来,实现控制特征的融合。

这里必须重点讲零卷积 :它就是一个1×1的卷积层,权重和偏置全部初始化为0。通俗说,训练刚开始的时候,这个卷积层就是个「透明人」,对输入信号完全没有任何修改,不会给原模型引入一丁点儿有害噪声。

ControlNet核心计算公式

完整的ControlNet块运算公式如下:
y c = F ( x ; Θ ) + Z ( F ( x + Z ( c ; Θ z 1 ) ; Θ c ) ; Θ z 2 ) y_{c}=\mathcal{F}(x ; \Theta)+\mathcal{Z}\left(\mathcal{F}\left(x+\mathcal{Z}\left(c ; \Theta_{z 1}\right) ; \Theta_{c}\right) ; \Theta_{z 2}\right) yc=F(x;Θ)+Z(F(x+Z(c;Θz1);Θc);Θz2)

我们依旧把每个字母拆解得清清楚楚,绝不放过任何一个细节:

符号 专业名称 通俗解释
y c y_c yc ControlNet块最终输出 融合了原模型生成能力和条件控制信息的最终特征,会送入后续网络
F ( x ; Θ ) \mathcal{F}(x ; \Theta) F(x;Θ) 锁定的原神经块输出 完全保留了Stable Diffusion原本的生成能力,是整个模型的基础
Z ( ⋅ ) \mathcal{Z}(\cdot) Z(⋅) 零卷积运算 权重和偏置都初始化为0的1×1卷积层,是ControlNet的核心设计
c c c 外部控制条件 我们输入的控制蓝图,比如Canny边缘图、人体姿势骨架、深度图、分割图、涂鸦等
Θ z 1 \Theta_{z1} Θz1 第一个零卷积的参数 初始全0,作用是把外部控制条件 c c c编码成和输入特征 x x x同维度的特征,让两者能相加融合
Θ c \Theta_c Θc 可训练副本的参数 和原块初始值完全一致,训练时仅更新这个副本,学习控制条件的特征
Θ z 2 \Theta_{z2} Θz2 第二个零卷积的参数 初始全0,作用是把副本输出的控制特征,调整成和原块输出同维度,最终和原块输出相加融合
训练初始态的关键特性

在训练的第一步,因为两个零卷积的权重和偏置全都是0,所以公式里后面加的那一长串控制项,结果全都是0,此时:
y c = y y_{c}=y yc=y

这就是ControlNet最天才的设计:训练初期,模型的输出和原预训练模型完全一致,不会给原模型引入任何有害噪声,彻底避免了训练初期破坏预训练骨干网络的问题。通俗说就是,ControlNet一开始完全是「跟着原模型走」,不会瞎搞,训练过程中才慢慢学会怎么把控制条件加进去,绝不会把原模型的本事给弄丢了。

基础结构可视化

图片1 ControlNet基本结构示意图(出处:论文原文Figure 2)

图片分析

  • 左侧(a)是原生的预训练神经块,输入特征 x x x经过网络块处理后输出 y y y;
  • 右侧(b)是加入ControlNet后的结构,灰色块是完全锁定的原神经块,蓝色块是可训练的副本,白色块是零卷积层;
  • 外部控制条件 c c c经过第一个零卷积后,和输入特征 x x x融合,送入可训练副本;副本的输出经过第二个零卷积后,和原块的输出相加,得到最终结果。

3.2 ControlNet对接Stable Diffusion的完整实现

论文里以Stable Diffusion为例,详细讲了ControlNet的工程落地,我们先快速过一下Stable Diffusion的核心结构:

Stable Diffusion的生成核心,是一个经典的U-Net网络,包含编码器、中间块、带跳跃连接的解码器三部分,总共25个块:12个编码器块、1个中间块、12个解码器块。其中12个编码器块分布在4个分辨率上:64×64、32×32、16×16、8×8,每个分辨率对应3个块。

ControlNet的整体架构对接

ControlNet的对接逻辑极其简洁,完全不修改Stable Diffusion的原生结构:

  1. 把Stable Diffusion的12个编码器块+1个中间块,完整克隆出一套可训练的副本,原块完全锁定不动;
  2. 副本的输出,经过零卷积层调整后,分别加到Stable Diffusion U-Net对应的12个跳跃连接+1个中间块里,直接控制解码器的去噪过程;
  3. 整个架构对Stable Diffusion没有任何侵入性修改,完美兼容所有社区微调的SD模型。
整体架构可视化

图片2 Stable Diffusion与ControlNet的整体架构图(出处:论文原文Figure 3)

图片分析

  • 左侧(a)是Stable Diffusion原生的U-Net架构,灰色块是编码器、中间块和解码器;
  • 右侧(b)是加入ControlNet后的完整架构,灰色块依旧是锁定的原生SD网络,蓝色块是ControlNet新增的可训练编码器副本,白色块是零卷积层;
  • 输入的控制条件经过编码后,送入ControlNet的可训练副本,最终输出的控制特征,逐分辨率加到SD的解码器跳跃连接里,实现对生成过程的精准控制。
计算效率与条件编码
  • 计算效率:在单张NVIDIA A100 PCIE 40GB显卡上测试,加入ControlNet后,仅比原生Stable Diffusion多占用23%的显存,单步训练时间仅增加34%,轻量化程度拉满。
  • 条件编码网络 :Stable Diffusion是在64×64的隐空间中完成扩散过程的,而我们输入的控制图通常是512×512的像素级图像,因此需要一个小网络把条件图编码到隐空间。论文里用了一个4层的卷积网络 E ( ⋅ ) \mathcal{E}(\cdot) E(⋅),公式如下:
    c f = E ( c i ) c_{f}=\mathcal {E}(c_{i}) cf=E(ci)
    其中:
    • c i c_i ci:输入的像素空间条件图(512×512),比如边缘图、姿势图;
    • E ( ⋅ ) \mathcal{E}(\cdot) E(⋅):条件编码网络,4个卷积层,卷积核4×4、步长2×2,激活函数ReLU,通道数依次为16、32、64、128,和整个模型联合训练;
    • c f c_f cf:编码后的隐空间条件特征(64×64),和SD的隐空间输入尺寸完全匹配,送入ControlNet的可训练副本。

3.3 训练细节与关键发现

扩散模型的训练目标

ControlNet完全复用了扩散模型的经典训练目标,也就是预测加在隐向量里的噪声,损失函数公式如下:

L = E z 0 , t , c t , c f , ϵ ∼ N ( 0 , 1 ) ∥ ϵ − ϵ θ ( z t , t , c t , c f ) ∥ 2 2 \mathcal{L} = \mathbb{E}_{z_0, t, c_t, c_f, \epsilon \sim \mathcal{N}(0,1)} \left \\left\\\| \\epsilon - \\epsilon_\\theta(z_t, t, c_t, c_f) \\right\\\|_2\^2 \\right L=Ez0,t,ct,cf,ϵ∼N(0,1)∥ϵ−ϵθ(zt,t,ct,cf)∥22

每个符号的详细解释:

符号 专业名称 通俗解释
L \mathcal{L} L 整体损失函数 训练的核心目标就是让这个值越小越好,用的是扩散模型经典的均方误差损失
E \mathbb{E}\\cdot E 数学期望 对括号里所有随机变量的结果取平均值
z 0 z_0 z0 干净隐向量 真实图片经过VAE编码后,得到的无噪声隐空间特征,是扩散模型的训练目标
t t t 扩散时间步 代表给图片加噪声的步数,t越大,加的噪声越多,图片越模糊
c t c_t ct 文本特征 输入的prompt经过CLIP文本编码器编码后的特征,控制生成的语义和风格
c f c_f cf 条件编码特征 公式(4)里的ControlNet控制特征,控制生成的空间布局和结构
ϵ ∼ N ( 0 , 1 ) \epsilon \sim \mathcal{N}(0,1) ϵ∼N(0,1) 随机噪声 从标准正态分布中采样的噪声,是扩散过程中往图片里添加的内容
z t z_t zt 加噪隐向量 给干净的 z 0 z_0 z0加了t步噪声后的隐向量,是网络的输入
ϵ θ ( ⋅ ) \epsilon_\theta(\cdot) ϵθ(⋅) 待训练的U-Net 原生SD U-Net+ControlNet的整体网络,目标是预测出加在 z t z_t zt里的真实噪声 ϵ \epsilon ϵ
∣ ⋅ ∣ 2 2 |\cdot|_2^2 ∣⋅∣22 L2范数平方 也就是均方误差,用来衡量网络预测的噪声和真实噪声之间的差距
训练的两个关键技巧
  1. 随机置空文本prompt:训练过程中,随机把50%的文本prompt替换成空字符串。这个技巧能让ControlNet学会直接从条件图里识别语义信息,哪怕没有任何文本prompt,也能根据输入的边缘图、姿势图生成合理的图片,大幅提升了模型的鲁棒性。
  2. 突然收敛现象:因为零卷积的存在,训练全程模型都能生成高质量的图片,绝不会出现训崩的情况。而且模型不是慢慢学会控制条件,而是在某个训练步数(通常不到10000步)突然就完美学会了跟随输入条件,论文把这个现象命名为「sudden convergence phenomenon(突然收敛现象)」。
突然收敛现象可视化

图片3 突然收敛现象可视化(出处:论文原文Figure 4)

图片分析

  • 训练步数100、1000、2000时,模型还完全没有跟随输入的边缘条件,生成的画面和输入的房子轮廓毫无关系,但画面质量依旧很高,没有出现训崩的情况;
  • 到了6133步,模型突然就完美贴合了输入的边缘图,生成的房子轮廓和输入完全一致;
  • 后续8000、12000步,生成的细节越来越丰富,控制效果也越来越稳定。

3.4 推理阶段的核心优化技巧

CFG分辨率加权(CFG Resolution Weighting)

玩过SD的朋友都知道,CFG(Classifier-Free Guidance)是控制生成图贴合prompt程度的核心参数,原生CFG的公式如下:
ϵ p r d = ϵ u c + β c f g ( ϵ c − ϵ u c ) \epsilon_{prd}=\epsilon_{uc}+\beta_{cfg}(\epsilon_{c}-\epsilon_{uc}) ϵprd=ϵuc+βcfg(ϵc−ϵuc)

每个符号的解释:

符号 通俗解释
ϵ p r d \epsilon_{prd} ϵprd 模型最终输出的预测噪声,用来做去噪生成
ϵ u c \epsilon_{uc} ϵuc 无条件输出,也就是prompt为空时模型预测的噪声
ϵ c \epsilon_c ϵc 条件输出,也就是有文本prompt和ControlNet条件时模型预测的噪声
β c f g \beta_{cfg} βcfg CFG权重,用户可调节,值越大,生成图越贴合prompt和条件,但也越容易失真

ControlNet在推理时,会遇到一个CFG的矛盾问题:

  • 如果把ControlNet的条件同时加到 ϵ u c \epsilon_{uc} ϵuc和 ϵ c \epsilon_c ϵc里,会完全抵消CFG的作用,生成的画面缺乏多样性,细节模糊;
  • 如果只把ControlNet的条件加到 ϵ c \epsilon_c ϵc里,又会让CFG的强度过高,生成的画面生硬、有伪影。

论文提出了CFG分辨率加权 的解决方案:给ControlNet和SD之间的每个连接,都乘一个权重 w i = 64 / h i w_i=64/h_i wi=64/hi,其中 h i h_i hi是对应块的特征图尺寸。比如8×8的低分辨率块, w i = 64 / 8 = 8 w_i=64/8=8 wi=64/8=8;16×16的块, w i = 4 w_i=4 wi=4;64×64的高分辨率块, w i = 1 w_i=1 wi=1。

这个方案的核心逻辑是:低分辨率块负责控制整体的结构和布局,给更高的权重;高分辨率块负责控制细节,给更低的权重。这样既保证了整体结构的控制效果,又保留了画面的细节和多样性,完美平衡了CFG的强度。

CFG分辨率加权效果对比

图片4 CFG与CFG分辨率加权的效果对比(出处:论文原文Figure 5)

图片分析

  • (a) 是输入的Canny边缘图,目标是生成对应的房子;
  • (b) 是无CFG的结果,整体结构对了,但细节极其模糊,缺乏质感;
  • © 是无CFG-RW的结果,结构贴合了,但画面生硬,有明显的伪影和过曝;
  • (d) 是加入完整CFG-RW的结果,既完美贴合了输入的边缘轮廓,画面细节又极其丰富,光影和质感都拉满了。
多ControlNet组合

如果想同时用多个条件控制生成,比如同时控制人物的姿势和画面的深度,直接把对应ControlNet的输出加在一起就行,不需要额外的加权、插值或者其他复杂操作,简单到离谱。

图片5 多条件组合控制效果(出处:论文原文Figure 6)

图片分析:同时输入了人体姿势和深度图两个条件,不管prompt是「boy」还是「astronaut」,生成的人物都完美贴合了输入的姿势和深度,实现了多维度的精准控制,没有出现任何冲突。

全类型条件支持

ControlNet支持的控制条件极其丰富,包括Canny边缘、Hough直线、用户涂鸦、人体关键点、语义分割图、表面法线图、深度图、卡通线稿等等,哪怕没有任何文本prompt,也能完美识别条件里的语义信息,生成高质量的图片。

图片6 无prompt条件下的多类型控制效果(出处:论文原文Figure 7)

图片分析:第一行是输入的控制条件,包括草图、法线图、深度图、Canny边缘、直线检测、软边缘、分割图、人体姿势,下面所有行都是无prompt生成的结果。可以看到,ControlNet完美识别了每个条件里的语义和结构,生成的画面和输入条件完全贴合,哪怕没有任何文本描述,也能生成合理的高质量图片。


实验验证:效果碾压同期所有方法

论文做了极其全面的实验,从消融实验、用户研究、定量指标对比,到和工业级模型的盲测,全方位验证了ControlNet的优越性。

4.1 消融实验:验证每个组件的核心作用

论文做了两组对照消融实验,来验证零卷积和完整可训练副本的作用:

  1. 对照组1:把零卷积替换成高斯初始化的标准卷积层;
  2. 对照组2:把完整的可训练副本,替换成单个卷积层,命名为ControlNet-lite。

同时测试了4种真实用户会遇到的prompt场景:

  • 无prompt:prompt为空,完全靠ControlNet控制;
  • 不充分prompt:只写了「高质量、细节丰富的专业图片」,没提画面里的物体;
  • 冲突prompt:prompt写的是「美味的蛋糕」,和输入的房子草图完全冲突;
  • 完美prompt:完整描述了画面内容「一座房子,高质量、4K、超细节」。
消融实验可视化结果

图片7 不同架构的消融实验效果对比(出处:论文原文Figure 8)

图片分析

  • (a) 完整的ControlNet:4种场景全部完美适配,哪怕prompt是冲突的「美味的蛋糕」,也能跟着输入的房子草图生成房子,同时融入蛋糕的风格,控制能力拉满;
  • (b) 去掉零卷积的版本:效果直接崩了,生成的画面和输入条件完全不贴合,说明零卷积确实保护了预训练骨干网络不被破坏,去掉之后模型直接学废了;
  • © ControlNet-lite轻量化版本:能力严重不足,无prompt和不充分prompt的场景直接失效,说明完整的可训练副本,是保证ControlNet复杂控制能力的核心。

4.2 定量评估:全指标碾压基线方法

用户偏好打分

论文邀请了12位用户,对20组 unseen 的手绘草图生成结果,从「画面质量」和「条件贴合度」两个维度打分,1分最差,5分最好,平均打分结果如下:

表格1 不同方法的用户平均打分(出处:论文原文Table 1)

Method Result Quality ↑ Condition Fidelity ↑
PITI 89 1.10 ± 0.05 1.02 ± 0.01
Sketch-Guided 88 (β=1.6) 3.21 ± 0.62 2.31 ± 0.57
Sketch-Guided 88 (β=3.2) 2.52 ± 0.44 3.28 ± 0.72
ControlNet-lite 3.93 ± 0.59 4.09 ± 0.46
ControlNet 4.22 ± 0.43 4.28 ± 0.45

表格分析:完整的ControlNet在「画面质量」和「条件贴合度」两个维度,都拿到了全场最高分,远超同期的所有基线方法。哪怕是轻量化的ControlNet-lite,也比之前的Sketch-Guided方法表现更好,充分证明了ControlNet架构的优越性。

分割条件重建精度

论文用ADE20K分割数据集,测试了不同方法对分割条件的还原能力,用IoU(交并比)衡量,IoU越高,代表生成的图和输入分割图的贴合度越高,结果如下:

表格2 语义分割标签重建IoU结果(出处:论文原文Table 2)

ADE20K (GT) VQGAN 19 LDM 72 PITI 89 ControlNet-lite ControlNet
0.58 ± 0.10 0.21 ± 0.15 0.31 ± 0.09 0.26 ± 0.16 0.32 ± 0.12 0.35 ± 0.14

表格分析:ControlNet的IoU达到了0.35,远超VQGAN、LDM、PITI这些基线方法,说明ControlNet对分割条件的还原能力,是所有方法里最强的。

生成质量与文本贴合度

论文用FID、CLIP分数、CLIP美学分数,全面评估了生成图的质量,其中:

  • FID:弗雷歇 inception距离,越低代表生成图和真实图的分布越接近,质量越高;
  • CLIP-score:越高代表生成图和文本prompt的贴合度越好;
  • CLIP-aes.:越高代表生成图的美学质量越好。

表格3 分割条件生成的全指标对比(出处:论文原文Table 3)

Method FID ↓ CLIP-score ↑ CLIP-aes. ↑
Stable Diffusion 6.09 0.26 6.32
VQGAN 19* 26.28 0.17 5.14
LDM 72* 25.35 0.18 5.15
PITI 89 19.74 0.20 5.77
ControlNet-lite 17.92 0.26 6.30
ControlNet 15.27 0.26 6.31

表格分析

  • ControlNet的FID只有15.27,是所有方法里最低的,说明生成的画面质量远超其他基线方法;
  • ControlNet的CLIP分数和原生Stable Diffusion完全持平,美学分数也几乎和原生SD一致,说明ControlNet在给SD加上超强控制能力的同时,完全没有损失SD原本的生成质量和文本贴合能力,完美解决了「加控制就丢质量」的行业难题。
与工业级模型的盲测对比

Stable Diffusion V2的Depth-to-Image模型,是用NVIDIA A100大集群、数千小时GPU算力、1200万+训练图片训出来的工业级模型。而ControlNet只用了20万训练样本、单张RTX 3090Ti消费级显卡、5天的训练时间,就达到了几乎一样的效果。

论文做了用户盲测:先用100张图让用户学会区分两个模型的生成结果,再用200张图让用户判断每张图是哪个模型生成的。最终用户的平均判断准确率只有0.52±0.17,和瞎猜的50%几乎没有区别。通俗说就是,单卡3090训出来的ControlNet,效果和大厂用集群训出来的工业级模型,人眼根本分不出来。

4.3 与同期方法的可视化对比

论文把ControlNet和同期的PITI、Sketch-Guided、Taming Transformers等方法做了可视化对比,结果如下:

图片8 与同期方法的效果对比(出处:论文原文Figure 9)

图片分析:可以清晰看到,其他方法生成的画面,要么和输入的草图、分割图、边缘图完全不贴合,要么细节模糊、结构崩坏;而ControlNet生成的画面,既完美贴合了输入的控制条件,细节又极其丰富清晰,效果直接碾压了同期的所有方法。

4.4 拓展性与兼容性验证

数据集大小的鲁棒性

论文测试了不同训练数据集大小对ControlNet的影响,结果如下:

图片9 不同训练数据集大小的效果对比(出处:论文原文Figure 10)

图片分析:哪怕只有1000张训练图片,ControlNet也不会训崩,能生成可识别的狮子;5万张图片训练的模型,效果已经非常出色;300万张图片训练的模型,效果直接拉满。这说明ControlNet对小数据集极其友好,不会出现过拟合,可扩展性极强。

社区模型迁移能力

因为ControlNet完全没有修改Stable Diffusion的原生网络结构,所以训好的ControlNet可以直接迁移到各种社区微调的SD模型上,比如Comic Diffusion、Protogen 3.4,不需要重新训练。

图片10 社区模型迁移效果(出处:论文原文Figure 12)

图片分析:同一个预训练ControlNet,在原生SD1.5、Comic Diffusion、Protogen 3.4上都能完美生效,生成的房子既完全贴合了控制条件,又保留了对应模型的独特风格,兼容性直接拉满。


核心代码实现

下面我们用PyTorch实现ControlNet的核心模块,包括零卷积层、ControlNet基本块、条件编码器,完全对齐论文里的架构设计。

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

# -------------------------- 核心:零卷积层 --------------------------
class ZeroConv2d(nn.Module):
    """
    零卷积层:1×1卷积,权重和偏置全部初始化为0
    对应论文里的Z(·)零卷积运算
    """
    def __init__(self, in_channels, out_channels):
        super().__init__()
        # 1×1卷积层
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0)
        # 权重和偏置全部初始化为0
        nn.init.zeros_(self.conv.weight)
        nn.init.zeros_(self.conv.bias)

    def forward(self, x):
        return self.conv(x)

# -------------------------- ControlNet基本块 --------------------------
class ControlNetBlock(nn.Module):
    """
    ControlNet基本单元,对应论文3.1节的结构
    包含锁定的原神经块、可训练副本、两个零卷积层
    """
    def __init__(self, original_block, in_channels, condition_channels):
        super().__init__()
        # 锁定的原预训练神经块,完全冻结参数
        self.original_block = original_block
        for param in self.original_block.parameters():
            param.requires_grad = False
        
        # 可训练的副本,和原块结构完全一致,初始参数相同
        self.trainable_copy = type(original_block)(**original_block.get_config())
        self.trainable_copy.load_state_dict(original_block.state_dict())
        
        # 两个零卷积层
        self.zero_conv1 = ZeroConv2d(condition_channels, in_channels)
        self.zero_conv2 = ZeroConv2d(in_channels, in_channels)

    def forward(self, x, condition):
        # 锁定的原块前向传播,输出完全和原生模型一致
        original_out = self.original_block(x)
        
        # 控制条件经过第一个零卷积,和输入特征融合
        condition_feat = self.zero_conv1(condition)
        copy_input = x + condition_feat
        
        # 可训练副本前向传播
        copy_out = self.trainable_copy(copy_input)
        
        # 副本输出经过第二个零卷积,和原块输出融合
        control_out = self.zero_conv2(copy_out)
        final_out = original_out + control_out
        
        return final_out

# -------------------------- 条件编码网络 --------------------------
class ConditionEncoder(nn.Module):
    """
    条件编码网络,对应论文3.2节的E(·)
    把512×512的像素级条件图,编码成64×64的隐空间特征
    """
    def __init__(self, in_channels=3, out_channels=320):
        super().__init__()
        # 4层卷积,卷积核4×4,步长2×2,ReLU激活
        self.conv_layers = nn.Sequential(
            nn.Conv2d(in_channels, 16, kernel_size=4, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(16, 32, kernel_size=4, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(32, 64, kernel_size=4, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1),
            nn.ReLU(),
            # 最终对齐SD隐空间的通道数
            nn.Conv2d(128, out_channels, kernel_size=1)
        )

    def forward(self, condition_img):
        # 输入:[B, 3, 512, 512] 条件图
        # 输出:[B, 320, 64, 64] 隐空间条件特征
        return self.conv_layers(condition_img)

# -------------------------- 完整ControlNet核心逻辑 --------------------------
class ControlNet(nn.Module):
    """
    完整的ControlNet实现,对接Stable Diffusion的U-Net编码器
    """
    def __init__(self, sd_unet, condition_in_channels=3):
        super().__init__()
        self.sd_unet = sd_unet
        # 冻结整个SD U-Net的参数
        for param in self.sd_unet.parameters():
            param.requires_grad = False
        
        # 条件编码器
        self.condition_encoder = ConditionEncoder(
            in_channels=condition_in_channels,
            out_channels=sd_unet.config.in_channels
        )
        
        # 克隆SD U-Net的编码器和中间块,构建可训练副本
        self.down_blocks_copy = nn.ModuleList()
        self.mid_block_copy = type(sd_unet.mid_block)(**sd_unet.mid_block.get_config())
        self.mid_block_copy.load_state_dict(sd_unet.mid_block.state_dict())
        
        # 克隆每个下采样块
        for down_block in sd_unet.down_blocks:
            copy_block = type(down_block)(**down_block.get_config())
            copy_block.load_state_dict(down_block.state_dict())
            self.down_blocks_copy.append(copy_block)
        
        # 零卷积层,用于输出控制特征到SD U-Net的跳跃连接
        self.zero_convs_down = nn.ModuleList([
            ZeroConv2d(block.out_channels, block.out_channels) 
            for block in self.down_blocks_copy
        ])
        self.zero_conv_mid = ZeroConv2d(
            self.mid_block_copy.out_channels, 
            self.mid_block_copy.out_channels
        )

    def forward(self, latent, timestep, text_emb, condition_img):
        # 1. 编码条件图
        condition_feat = self.condition_encoder(condition_img)
        
        # 2. SD U-Net的时间步和文本编码
        t_emb = self.sd_unet.time_proj(timestep)
        t_emb = self.sd_unet.time_embedding(t_emb)
        
        # 3. ControlNet可训练副本的前向传播
        x = latent + condition_feat
        down_block_res_samples = []
        
        # 下采样块前向
        for down_block in self.down_blocks_copy:
            x = down_block(x, t_emb, text_emb)
            down_block_res_samples.append(x)
        
        # 中间块前向
        mid_out = self.mid_block_copy(x, t_emb, text_emb)
        
        # 4. 零卷积处理控制特征,输出到SD U-Net
        control_down_res = [
            zero_conv(res) 
            for zero_conv, res in zip(self.zero_convs_down, down_block_res_samples)
        ]
        control_mid = self.zero_conv_mid(mid_out)
        
        return control_down_res, control_mid

结论

ControlNet的出现,彻底改写了文生图扩散模型的控制范式,它的核心贡献可以总结为三点:

  1. 提出了一种全新的神经网络架构,能为预训练的文生图扩散模型,高效地添加空间条件控制能力,同时完全保留原模型的生成质量,彻底解决了微调过程中的灾难性遗忘问题;
  2. 开源了覆盖边缘、深度、姿势、分割、涂鸦等十几种场景的预训练ControlNet,单张消费级显卡就能完成训练和推理,把工业级的控制能力带到了普通用户手中;
  3. 通过全面的消融实验、用户研究和定量对比,充分验证了方法的有效性,效果碾压了同期所有的基线方法,也为后续扩散模型的控制研究奠定了核心基础。

直到今天,ControlNet依旧是AI绘图领域里最核心、使用最广泛的控制工具,从专业的影视概念设计、工业绘图,到普通用户的日常创作,都能看到它的身影。而它最天才的零卷积设计,也为大模型的轻量化微调、能力拓展,提供了一个全新的、极具启发性的思路。

相关推荐
chaojihaomai2 小时前
AI电商商品图生成中的材质保真问题——扩散模型重建优先级分析与锁定式方案
人工智能·材质
大模型探索者4 小时前
2026 企业智能体平台选型指南:从通用到行业深耕,怎么选?
大数据·人工智能
深圳行云创新4 小时前
北极星知识图谱平台|太空越来越“拥挤”,知识图谱如何预测空间碎片碰撞风险?
人工智能·知识图谱
Am-Chestnuts4 小时前
国产AI多轮对话归档与多格式导出实践
人工智能·word
麻瓜生活睁不开眼5 小时前
Android16修改全局桌面视图边框四直角显示为弧边圆角
android·java·深度学习
workflower5 小时前
装备企业的 AI 路线图
人工智能·深度学习·机器学习·设计模式·机器人
AKAMAI5 小时前
Linode接口及默认防火墙现已全面开放使用
人工智能·云计算
前端开发江鸟6 小时前
RAG 回答错了,问题到底出在召回、重排,还是生成?
人工智能
美狐美颜SDK开放平台6 小时前
直播app开发如何实现主播级美颜效果?美颜sdk开发方案详解
人工智能·音视频·美颜sdk·视频美颜sdk·美颜api
东方小月6 小时前
从零开发一个 Coding Agent(四):使用状态机校验大模型事件流
前端·人工智能·后端