使用 Diffusers 中的 T2I-Adapter 实现可控图像生成:单控制与多控制组合实战指南
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
导读
本文以 Diffusers 官方文档 t2i_adapter.md 为核心,系统讲解 T2I-Adapter(Text-to-Image Adapter)这一轻量级可控生成方案:如何在 Stable Diffusion XL(SDXL)之上接入 Canny 边缘、深度图等控制信号,实现像 ControlNet 一样的可控生成;以及如何使用MultiAdapter将多个控制信号(如 Canny 边缘 + 深度图)组合使用,并通过adapter_conditioning_scale精细调节每个控制的权重。读完本文,你将掌握从控制图预处理、Adapter 加载、Pipeline 装配到多控制加权调参的完整实战能力,并了解其底层 ResNet 特征提取架构与注入 UNet 的原理。
T2I-Adapter 是什么:与 ControlNet 并列的轻量可控方案
T2I-Adapter(论文 T2I-Adapter: Learning Adapters to Dig out More Controllable Ability for Text-to-Image Diffusion Models 类似的、为扩散模型带来可控生成能力的适配器。它的核心思路是:学习一个控制信号(例如深度图 depth map)与预训练模型内部知识之间的"映射"(mapping)。这个 Adapter 以插件方式"插入"基础模型,在生成过程中依据控制信号额外提供引导,从而约束生成结果的空间结构、边缘轮廓等几何属性,而无需重新训练庞大的基础模型。
从实现层面看,两者在 UNet 内部的注入方式是一致的:无论是 T2I-Adapter 还是 ControlNet,最终都以down_block_additional_residuals的形式把额外残差特征传入 UNet 的每个下采样块。这一注入点可以在 unet_2d_condition.py 的forward中看到(该文件同时兼容两种机制,并对过时的传参方式给出了弃用警告)。区别在于:
- ControlNet:额外维护一个可训练的 UNet 副本作为条件编码器,参数规模大;
- T2I-Adapter:采用一个轻量的 ResNet 风格网络(见 adapter.py 中的
FullAdapter/FullAdapterXL/LightAdapter),参数量小、内存占用低,适合在移动端、低显存设备上运行。
从代码结构看,T2I-Adapter 在 Diffusers 仓库中的核心实现位于 adapter.py,相关 Pipeline 位于 pipelines/t2i_adapter 目录。
快速上手:Canny 边缘控制的端到端示例
第 1 步:加载 T2I-Adapter
针对某种特定控制信号(如 Canny 边缘),用T2IAdapter.from_pretrained加载对应的预训练 Adapter,并传入dtype=torch.float16以半精度加载,降低显存占用:
import torch from diffusers import T2IAdapter, StableDiffusionXLAdapterPipeline, AutoencoderKL t2i_adapter = T2IAdapter.from_pretrained( "TencentARC/t2i-adapter-canny-sdxl-1.0", dtype=torch.float16, )说明:
T2IAdapter继承自ModelMixin与ConfigMixin(见 adapter.py 第 220 行),因此支持from_pretrained/save_pretrained等标准接口。加载时会根据模型配置自动选择full_adapter、full_adapter_xl或light_adapter三种架构之一;若需显式指定(例如加载 sketch 类 SDXL 适配器),也可在from_pretrained中传入adapter_type="full_adapter_xl"。
第 2 步:用 OpenCV 生成 Canny 控制图
使用 opencv-python 从原始图片提取边缘,得到 Canny 图作为控制信号:
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)要点说明:
low_threshold/high_threshold(示例取值 100 / 200)是 Canny 算法的双阈值:低于low_threshold的梯度一律丢弃,高于high_threshold的梯度确定为边缘,介于两者之间且与强边缘相连的梯度也会保留。调低阈值会得到更多、更细碎的边缘;调高则只保留显著轮廓。- Canny 输出是单通道灰度图,需先扩成 3 通道(
image[:, :, None]后沿通道轴拼接 3 次),以匹配 Adapter 默认的in_channels=3输入。
第 3 步:装配 Pipeline 并生成图像
将 Adapter 与一个 FP16 优化的 VAE 一起传入StableDiffusionXLAdapterPipeline.from_pretrained:
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", dtype=torch.float16) pipeline = StableDiffusionXLAdapterPipeline.from_pretrained( "stabilityai/stable-diffusion-xl-base-1.0", adapter=t2i_adapter, vae=vae, dtype=torch.float16, ).to("cuda") # or "mps", "xpu", "cpu"随后把 Canny 图通过image参数传入,执行生成:
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, image=canny_image, num_inference_steps=100, guidance_scale=10, ).images[0]关键参数参考(来自 pipeline_stable_diffusion_xl_adapter.py 的__call__签名):
| 参数 | 默认值 | 作用 |
|---|---|---|
num_inference_steps | 50 | 去噪步数,越多质量越高但更慢;示例用 100 追求更精细的纹理 |
guidance_scale | 5.0 | 无分类器引导(CFG)强度,>1 生效;越大越贴合文本提示,但过大会牺牲画质 |
adapter_conditioning_scale | 1.0 | Adapter 输出的加权系数,控制信号强度(后文详解) |
adapter_conditioning_factor | 1.0 | 可传 0~1 的小数,在去噪早期逐步淡出控制信号的强度 |
negative_prompt | None | 负向提示词,配合 CFG 使用以规避不良内容 |
height/width | UNet 采样尺寸 × VAE 缩放系数 | 输出分辨率,SDXL 建议不低于 512 |
denoising_end | None | 提前终止去噪的比例(0~1),用于"多模型接力"精修场景 |
output_type | "pil" | 输出格式,可选pil或np.array |
generator | None | 传入torch.manual_seed的生成器可使结果可复现 |
设备支持:Pipeline 支持
.to("cuda")、.to("mps")、.to("xpu")、.to("cpu")等多种后端(示例注释中已列出)。SDXL 体量较大,CPU 推理会很慢,生产环境建议使用 GPU。
理解 T2I-Adapter 的底层工作原理
T2I-Adapter 本质是一个多尺度特征提取器:输入控制图后,输出一组不同尺度的特征图(feature maps)列表,供UNet2DConditionModel作为额外的条件输入使用。这在 adapter.py 的类文档中有明确描述。
三种网络架构(adapter_type)
T2IAdapter.__init__依据adapter_type分派到不同子网络:
| 架构 | 默认channels | 默认num_res_blocks | 默认downscale_factor | 特点 |
|---|---|---|---|---|
full_adapter | [320, 640, 1280, 1280] | 2 | 8 | 4 个下采样块,标准版 |
full_adapter_xl | [320, 640, 1280, 1280] | 2 | 16 | 仅 1 个下采样块,专为 SDXL 设计 |
light_adapter | [320, 640, 1280] | 4 | 8 | 轻量版,通道更窄(瓶颈mid_channels = out_channels // 4),适合低算力场景 |
前向流程与关键组件
以FullAdapterXL为例,前向过程(adapter.py 第 386-400 行)为:
- PixelUnshuffle 下采样:
nn.PixelUnshuffle(downscale_factor)把输入图像的空间像素重排到通道维度,输入通道数变为in_channels * downscale_factor**2(如 3×16²=768)。这正是源码属性downscale_factor注释所强调的:输入图像尺寸必须能被downscale_factor整除,否则会报异常。 - 输入卷积:
nn.Conv2d将高通道数压缩到首个channels[0]。 - 堆叠
AdapterBlock:每个AdapterBlock内部可选AvgPool2d下采样、可选1×1通道对齐卷积,以及一串AdapterResnetBlock(3×3 conv → ReLU → 1×1 conv的残差块,输出与输入相加)。 - 输出多尺度特征列表:每个 block 的输出被收集进
features列表并返回,列表长度等于下采样块个数。这些特征最终在 Pipeline 的__call__中按adapter_conditioning_scale加权后,作为down_block_additional_residuals注入 UNet 的各个下采样阶段。
total_downscale_factor计算规则(源码可见):full_adapter为downscale_factor × 2^(len(channels)-1),full_adapter_xl为downscale_factor × 2,light_adapter为downscale_factor × 2^len(channels)。
在 Pipeline 中的注入逻辑
在 pipeline_stable_diffusion_xl_adapter.py 的__call__中:
- 控制图先经
_preprocess_adapter_image统一缩放为(height, width)并归一化到[0, 1]; - 单 Adapter 场景:
adapter_state = self.adapter(adapter_input),然后逐尺度乘以adapter_conditioning_scale; num_images_per_prompt > 1时特征沿 batch 维重复,CFG 开启时特征沿 batch 维拼接两份(正向/负向各一份);- 最终特征以
down_block_additional_residuals传入 UNet 的forward(见 unet_2d_condition.py)。
进阶:用 MultiAdapter 组合多个控制信号
现实中常需要同时约束多个属性,例如"既保持 Canny 边缘轮廓、又符合深度结构"。MultiAdapter正是为此设计:它是一个包装模型,内含多个 T2I-Adapter,并把各 Adapter 的输出按权重加权求和后合并为一个多尺度特征列表。
加载多个 Adapter 与多个控制图
import torch from diffusers.utils import load_image from diffusers import StableDiffusionXLAdapterPipeline, AutoencoderKL, MultiAdapter, T2IAdapter canny_image = load_image( "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/canny-cat.png" ) depth_image = load_image( "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/sdxl_depth_image.png" ) controls = [canny_image, depth_image] 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 """] adapters = MultiAdapter( [ T2IAdapter.from_pretrained("TencentARC/t2i-adapter-canny-sdxl-1.0", dtype=torch.float16), T2IAdapter.from_pretrained("TencentARC/t2i-adapter-depth-midas-sdxl-1.0", dtype=torch.float16), ] )关键点:
- 控制图列表顺序与 Adapter 列表顺序一一对应(第 i 个控制图喂给第 i 个 Adapter);
MultiAdapter.__init__(adapter.py 第 28-74 行)要求至少 2 个 Adapter:传入空列表或仅 1 个 Adapter 都会抛出ValueError;- 由于各 Adapter 输出是逐尺度相加的,
MultiAdapter强制要求所有子 Adapter 的total_downscale_factor与downscale_factor完全一致,否则抛出ValueError(源码第 60-71 行的校验逻辑)。因此实践中通常组合同一系列(如都是 SDXL 系列)的 Adapter。
用 adapter_conditioning_scale 控制每个信号的权重
将MultiAdapter作为adapter传入 Pipeline,并通过adapter_conditioning_scale(列表)指定每个控制信号的权重:
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", dtype=torch.float16) pipeline = StableDiffusionXLAdapterPipeline.from_pretrained( "stabilityai/stable-diffusion-xl-base-1.0", dtype=torch.float16, vae=vae, adapter=adapters, ).to("cuda") # or "mps", "xpu", "cpu" pipeline( prompt, image=controls, height=1024, width=1024, adapter_conditioning_scale=[0.7, 0.7] ).images[0]工作原理(源码可证):
- 传入列表时,Pipeline 走
MultiAdapter分支:self.adapter(adapter_input, adapter_conditioning_scale); MultiAdapter.forward(adapter.py 第 76-108 行)将每个 Adapter 的输出特征乘以其权重再逐尺度累加:accume_state[i] += w * features[i];- 若不传
adapter_conditioning_scale,则默认使用等权平均1/num_adapter(源码第 93-94 行)。
调参建议(基于实现语义的实践推断):
- 权重之和无需为 1,它是逐尺度相加的系数;但建议各权重取相近量级,避免某个信号过强压制其他信号;
- 若某次生成中边缘轮廓过于生硬,可降低 Canny 对应权重(如
[0.5, 0.8]); - 多控制会占用更多显存并增加一次额外前向开销,实际部署时建议先用
num_inference_steps=20~30快速验证组合效果,再提高步数出正式结果。
多 Adapter 的保存与加载
MultiAdapter也支持save_pretrained/from_pretrained(源码第 110-217 行):
- 保存时第 1 个 Adapter 存入
./mydirectory/adapter,第 2 个存入./mydirectory/adapter_1,依此类推(目录名带_N后缀递增); - 加载时
from_pretrained会循环探测adapter、adapter_1、adapter_2……直到目录不存在为止,并将全部 Adapter 组装成MultiAdapter; - 仓库测试 test_stable_diffusion_xl_adapter.py 中的
StableDiffusionXLMultiAdapterPipelineTesterConfig以adapter_conditioning_scale=[0.5, 0.5]驱动同一 Pipeline 进行验证,可作为组合使用的参照。
训练自己的 T2I-Adapter
除了使用社区预训练权重,仓库还提供了从零训练 T2I-Adapter 的完整脚本:
- 训练入口:examples/t2i_adapter/train_t2i_adapter_sdxl.py
- 环境依赖:examples/t2i_adapter/requirements.txt
- 使用说明:examples/t2i_adapter/README_sdxl.md 与 examples/t2i_adapter/README.md
- 冒烟测试:examples/t2i_adapter/test_t2i_adapter.py
训练脚本通常需要准备"控制图 + 原图"配对数据集(如填充图像的线稿/深度/边缘与对应的真实图像),通过拟合 UNet 冻结情况下的重建损失来让 Adapter 学会从控制信号中恢复内容。训练完成后,同样可以用T2IAdapter.from_pretrained加载到推理 Pipeline 中。
实用技巧与注意事项
- 输入尺寸约束:控制图会被自动缩放到生成分辨率(
_preprocess_adapter_image使用 Lanczos 插值),但 T2I-Adapter 内部有 PixelUnshuffle 下采样,源码明确要求输入尺寸可被downscale_factor整除,建议控制图边长取 8 或 16 的整数倍。 - FP16 显存优化:Adapter、VAE、Pipeline 均以
dtype=torch.float16加载;SDXL 场景推荐配合madebyollin/sdxl-vae-fp16-fix这类 FP16 修复版 VAE,避免半精度下的 NaN/颜色偏移问题。 - 与 LoRA、IP-Adapter 协同:
StableDiffusionXLAdapterPipeline继承了StableDiffusionXLLoraLoaderMixin与IPAdapterMixin(见 pipeline_stable_diffusion_xl_adapter.py 的 import 与类定义),因此可以叠加加载 LoRA 权重,并支持传入ip_adapter_image做风格参考,实现"结构控制 + 风格迁移"的组合玩法。 - 控制强度调优:
adapter_conditioning_scale控制信号整体强度,adapter_conditioning_factor控制在去噪后期逐步弱化控制,二者配合可缓解"控制过强导致图像呆板"或"控制过弱导致轮廓漂移"的问题。 - 可复现性:传入
generator=torch.manual_seed(42)(官方文档示例做法)可使相同输入产出稳定结果,便于调试参数。
总结
T2I-Adapter 以极小的参数代价为 SDXL 等基础模型注入可控生成能力:单控制场景用T2IAdapter+StableDiffusionXLAdapterPipeline即可完成"Canny 边缘 → 生成图像"的闭环;多控制场景用MultiAdapter组合多个 Adapter,并通过adapter_conditioning_scale列表灵活调配各控制信号的权重。其底层实现(多尺度 ResNet 特征提取 +down_block_additional_residuals注入 UNet)与 ControlNet 机制互补,是资源受限场景下可控生成的务实之选。相关源码与测试可在 adapter.py、pipeline_stable_diffusion_xl_adapter.py 与 test_stable_diffusion_xl_adapter.py 中继续深入研读。
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考