☰
Stable Diffusion原理详解:CLIP、扩散模型与UNet全解析
2026/10/26 20:27:52 网站建设 项目流程

1. 为什么"知其所以然"比"一键出图"更重要

我最早接触 Stable Diffusion 的时候,和大多数人一样,第一反应是找一个整合包装好,然后在提示词框里输入"仙女、珊瑚、光影特效",点击生成。图出多了以后我发现,同样的提示词换个模型表现完全不同,同一个模型换个采样步数结果也可能翻天覆地。如果只把 Stable Diffusion 当成抽卡工具,这些问题永远只能靠运气;但如果绕到背后,把 CLIP、扩散模型、UNet 这三块拼图拼起来,一切都会变得有迹可循。

这篇文章是我在看完四集精讲视频之后整理的完整学习笔记。四集内容其实不多,但信息密度很大,第一遍看很容易"眼睛会了、脑子没会"。我把四集拆成七个小节,按自己的理解重新组织了一遍,并把每部分的代码示例也跑通了,算是一份"看完就能照着复现"的笔记。

准备说在前面:这篇文章不是纯论文解读,也不是纯 GUI 操作教程。它更适合那些已经在用 Stable Diffusion 出图,但对内部机制还比较模糊的玩家;也适合刚入门的开发者,想知道 diffusers 这类开源库在调用时到底发生了什么。整条主线只有一条:一段文字是怎么一步步变成一张图的。沿着这条主线,CLIP、扩散模型、UNet 各自负责哪个环节,就都清楚了。

1.1 我为什么用四集笔记来整理这套原理

网上讲 Stable Diffusion 的文章非常多,但大多数要么只讲某一部分概念,要么直接教你怎么写提示词,缺少一条完整的链路。四集精讲的好处在于:从"为什么需要文本编码器"讲起,到"噪声如何被清除",再到"UNet 怎么处理信息",最后落到可运行的代码,逻辑是递进的。我整理笔记时没有照抄原稿,而是自己做了一张流程卡片,把每个环节的输入输出和关键参数都列出来了。这样即使隔了很久回头翻笔记,也只需要看那一张卡片就能回忆起来。

1.2 本文会讲清楚的两条主线

第一条主线是训练阶段的分工:CLIP 文本编码器把自然语言转换成向量,扩散模型定义"加噪-去噪"的学习目标,UNet 负责在每一步去噪中预测噪声,最后通过 VAE 解码器把潜在表示还原成图像。第二条主线是推理阶段的流水线:输入提示词 → 文本编码 → 随机噪声初始化 → UNet 逐步去噪 → VAE 解码 → 输出图片。这两条主线一旦清楚了,你在界面上看到的每一个参数(采样步数、CFG Scale、种子、分辨率)就都有了明确的对应关系。

2. 三大部分的整体串联:CLIP、扩散过程与 UNet 的各自职责

先来看一张"服务与被服务"的关系。很多人以为 Stable Diffusion 是一个端到端的大模型,其实它更像一个组装车间,四个模块分别处理自己的事。

2.1 从输入到输出的数据流动

如果只记一句话,可以这样概括:CLIP 负责把文字变成"条件",扩散过程负责定义"从纯噪声到画面的演化规则",UNet 负责在这个规则下实际执行去噪,VAE 解码器负责把压缩空间的图像表示还原成像素图。

具体的数据流向是这样的。用户输入提示词后,会先经过一个分词器,把句子切成一连串的 token,再送到 CLIP 文本编码器里,得到一组文本向量。这组向量不会直接变成图像,而是作为一个"条件"注入到 UNet 中,相当于告诉生成模型"你往这个方向画"。与此同时,模型从随机噪声开始,生成一张和最终图像同尺寸的初始噪声图。这个初始噪声在潜在空间里是一个带随机数的张量,它经过 UNet 的每一轮去噪,逐步暴露出清晰的轮廓、结构和细节。经过几十步迭代后,最终得到的干净潜在表示被塞进 VAE 解码器,就能还原成一张直观理解的 RGB 图片。

2.2 各模块的分工对比

我整理了一张简化的分工表,方便快速对照:

模块输入输出核心作用典型瓶颈
CLIP 文本编码器提示词 token 序列文本嵌入向量序列把文字语义转成模型能用的条件最大支持 77 个 token
扩散调度器初始噪声 / 上一步结果逐步去噪后的潜在向量定义噪声强度随时间的变化规则采样步数影响速度与质量
UNet 去噪网络噪声潜在向量 + 时间步 + 文本条件预测出的噪声学习"图片长什么样",从而去噪显存占用最大
VAE 解码器干净潜在向量像素图像把压缩空间的表示还原成真实图片解码耗时短,基本忽略

这张表非常重要。尤其是"UNet 负责预测噪声"这件事,很多新手第一次听到会困惑:为什么要预测噪声而不是直接预测图像?这个问题我放在第 5 节详细讲。这里先记住结论:扩散模型的训练目标就是从随机噪声里反推加噪过程的逆步骤,UNet 输出的其实是一个和输入同样尺寸的噪声图,用它来"修正"当前潜在向量,从而一步步逼近真实图像。

2.3 四集笔记里最容易被忽略的一句话

四集视频里有一句话我印象特别深:Stable Diffusion 本质上是一个条件生成模型。"条件"二字是一切设计的前提。没有 CLIP 生成的条件,UNet 面对同一个噪声输入就只能随机发挥;有了条件,生成的每一步都被校准到"和提示词语义相符"的方向上。那些换提示词就像换了一个世界一样的体验,就是因为文本条件在昂首塑造生成结果。

3. CLIP 双编码器架构细拆:文本如何变成图像的"方向盘"

CLIP 的全称是 Contrastive Language-Image Pre-training,也就是"对比语言-图像预训练"。它最早是作为一种多模态模型被提出来的,主要任务是让模型理解"一段文本和一张图像是否匹配"。在 Stable Diffusion 里,我们只用到它的文本编码器部分,但这部分决定了模型能"听懂"多少提示词。

3.1 CLIP 到底训练了什么

CLIP 的训练方式和传统分类任务完全不同。它不用人工标注的类别标签,而是直接使用海量的图文对:一张图片配一句描述文字。训练时,模型把一张图编码成一个向量,也把一段描述文字编码成一个向量,然后计算这两个向量的相似度。目标是:如果这张图和这段文字本来就是一对,相似度要高;如果它们不是一对,相似度要低。

这个目标是通过对比学习实现的。简单说,模型会同时看一个批次里的多张图和多个句子,找出哪些组合是正样本对,哪些是负样本对,然后不断调整编码器参数。训练结束后,两个编码器——图像编码器和文本编码器——被推进同一个语义向量空间。在这个空间里,"一只橘猫坐在沙发上"这句话的向量,和一张真实橘猫坐在沙发上的图片向量,会非常靠近。

Stable Diffusion 在生成图片时,正是希望文本向量能起到"方向盘"的作用。它先让文本编码器把用户提示词映射成一个向量序列,然后在 UNet 每一步去噪时,都用这个向量序列来引导,告诉模型"当前潜在表示应该更像猫,而不是像狗"。

3.2 为什么这里的文本编码器直接决定了"懂不懂话"

很多人测试不同模型时,会发现同一个提示词在 SD 1.5、SD 2.1、SDXL 上的表现差异很大。除了 UNet 本身的改进之外,文本编码器也是一个重要变量。SD 1.x 系列使用的是 CLIP ViT-L/14 的文本分支,它的"理解能力"上限决定了提示词表达的天花板。

文本编码器的工作流程大致是:先通过 BPE 分词器把提示词拆成 token 序列,每个 token 对应一个嵌入向量,然后经过一层层的 Transformer 编码器,最终输出一组 hidden states。这组 hidden states 就被当作文本条件,送入 UNet 的交叉注意力层。

我自己的实测感受是:SD 1.5 对比较复杂的长句容易"丢细节",比如你写"蓝色眼睛的女孩穿着红色连衣裙在雨中等候",五个关键词可能只能突出两三个;而 SDXL 使用了两套文本编码器组合,理解长句的能力明显更强。这和文本编码器的上下文长度、参数规模直接相关。所以不要小看 CLIP 这部分,它决定了提示词里有多少信息能真正进入去噪过程。

3.3 77 token 限制与 CLIP limit 的坑

网上搜"CLIP limit"会看到大量相关讨论,几乎所有 SD 老玩家都遇到过:提示词写得太长,后半段被截断,生成结果完全没体现后面的内容。

这是怎么回事?因为 SD 1.x 的 CLIP 文本编码器在训练时只支持 77 个 token 的上下文窗口。一旦超过 77 个 token,超出部分要么被直接截断,要么只参与计算一部分。很多人在 WebUI 里堆了几百个词,前面一段是主要角色,后面全是"极其精细的细节、让人惊叹的光影",结果发现模型实际只用了前 77 个 token,后面的全部被忽略。

我用一个简单例子说明:提示词"a cat, a dog, a bird, a fish"只有 8 个 token,但如果你在后面继续加几百个修饰词,超过 77 个 token 之后的内容确实会被丢掉。所以在实操里,重要的主体描述一定要放在提示词最前面,质量 tag 可以往后放,反正如果超过窗口也未必能起作用。

提示:如果你用 ComfyUI 里常见的 CLIP Text Encode 节点,可以在 KSampler 的配置里看到clip_skip这一类参数。SD 1.x 常用clip_skip=2跳过最后一层文本编码输出,能改善部分画面偏灰的问题。这也是属于 CLIP 的"微观调校"。

3.4 一个值得尝试的小实验

如果你手头有 SD WebUI 或者 ComfyUI,可以做一个实验:把clip_skip分别设为 1 和 2,固定种子、提示词和采样参数,对比两次输出。你会发现画面的对比度、细节锐度和风格倾向会有细微变化。这个实验不需要花很多时间,但对理解"文本编码器的哪一层被使用了"非常有帮助。CLIP 最后一层往往更偏向全局语义,而倒数第二层可能保留更多细粒度信息,跳过最后一层有时反而让画面更符合提示词。

4. 扩散模型的数学直觉:前向加噪与反向去噪

扩散模型听起来玄,核心思想其实一句话就能讲明白:给图像不断加噪声,直到变成纯噪声;然后训练一个神经网络,学会把噪声一步步去掉,还原出图像。Stable Diffusion 用的扩散模型不是最原始的版本,而是"潜在扩散模型",但底层原理一脉相承。

4.1 前向扩散过程:一张图的信息被逐步掩盖

前向过程就是你坐在那里,往一张清晰的图片上叠加高斯噪声。每叠一步,图片就模糊一点。加噪声的强度由一个"时间步 t"控制,t 越大,噪声越强;当 t 达到最大值时,图已经面目全非,和真正的随机高斯噪声没什么区别。

数学上,这个加噪过程通常写成下面的形式:

x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * epsilon

其中 x_0 是原始图像,epsilon 是从标准高斯分布中采样的噪声,alpha_bar_t 是一个随着 t 增大而递减的系数。当 t 接近 0 时,sqrt(alpha_bar_t) 接近 1,所以 x_t 几乎是原始图像;当 t 很大时,sqrt(alpha_bar_t) 接近 0,所以 x_t 几乎全是噪声。

这个公式的意义在于:它不需要真正一步一步去加噪声,而是可以直接从 x_0 一步算出任意 t 时刻的加噪结果。训练扩散模型时,我们随机抽一个 t,随机生成一个噪声 epsilon,然后用这个公式得到 x_t,再让神经网络去预测当初的噪声 epsilon。整个过程既简单又高效。

4.2 反向去噪过程:让模型学会"擦掉噪声"

反向去噪是扩散模型的推理阶段。我们想要的效果是:从一张纯噪声开始,一步步经过 UNet 的预测和修正,最终得到接近真实分布的图像。

在 DDPM 原始论文中,反向过程的单步更新公式大致是:

x_{t-1} = (1 / sqrt(alpha_t)) * (x_t - (1 - alpha_t) / sqrt(1 - alpha_bar_t) * eps_theta) + sigma_t * z

这个公式看着吓人,拆开理解就不难:eps_theta是 UNet 对当前噪声图中"所含噪声"的预测;x_t减去噪声项之后,得到一个更干净一点的潜在表示;最后再加上一个随机扰动sigma_t * z来保证生成多样性。

换句话说,每一步去噪都不是直接把噪声完全清零,而是"走一小步"。从 t = T 开始,每一轮 UNet 都做三件事:看一眼当前带噪的潜在向量,结合当前时间步 t,再结合文本条件,然后输出一个预测噪声;用这个预测噪声计算下一步的潜在向量。这样重复 20 到 50 次,就完成了从噪声到图像的转变。

4.3 潜在空间扩散:Stable Diffusion 名字里的"潜在"二字

传统扩散模型直接操作像素空间,也就是在 512×512×3 的 RGB 图像上做加噪去噪。这个做法的计算量非常大,因为每个像素都是一个维度,一张图就是几十万个维度,在一张消费级显卡上跑一次生成要很慢。

Stable Diffusion 采用的潜在扩散模型不一样。它先训练一个 VAE 自编码器,把图像压缩到一个更小的潜在空间里,比如 512×512 的图可能被压缩成 64×64 或 128×128 的潜在表示。扩散过程就发生在这个低维潜在空间里,而不是直接在像素空间里。这相当于把"在几十万维空间里做扩散"降为"在几千维空间里做扩散",计算量下降非常明显。

这也是为什么 Stable Diffusion 能在消费级 GPU 上跑,而很多早期的 Diffusion 模型却很难实用。代价是:潜在空间的压缩会让一些非常细微的结构信息丢失,比如手部细节。这一部分与后面要讲的手部修复问题直接相关。

提示:你可以把 VAE 潜在空间理解为"图像的浓缩版糖浆"。浓缩之后,大方向(构图、主体、材质)都被保留,但有些细小颗粒(手指、发丝)可能会被压缩得面目全非。UNet 在潜在空间里预测噪声,自然也就很难把每个手指的关节都维护得严丝合缝。

5. UNet 架构:去噪主角,以及文本条件如何注入

如果说 CLIP 是方向盘,扩散过程是道路规划,那 UNet 就是实际开车的司机。它是整个 Stable Diffusion 里参数最多的模块,也是每一步迭代中最消耗显存的部分。

5.1 UNet 的骨架:下采样、上采样与跳跃连接

UNet 最早是为图像分割提出来的,形状像字母 U,因此得名。它由两部分组成:左侧的编码器负责逐步下采样,把特征图缩小,通道数扩大,从而提取越来越抽象的语义特征;右侧的解码器负责逐步上采样,把特征图恢复到原始尺寸,并结合左侧对应层的特征进行重建。

关键在于跳跃连接。下采样时,每一层的特征图不仅向下传递,还会"抄近路"直接跳到右侧对应的上采样层,和上采样结果拼接在一起。这样,右层既能利用低层的高分辨率细节,也能利用高层的抽象语义。对去噪任务来说,这种做法非常合适:既要保持构图的大结构,也要保留边缘和纹理等局部细节。

在 Stable Diffusion 中,UNet 的输入通常是带噪声的潜在向量、时间步 t 的嵌入,以及文本条件。它输出的是预测噪声,本质上也是一个和输入潜在向量同样尺寸的张量。

5.2 时间步条件与文本条件:CrossAttention 是关键

UNet 不是普通的分割网络,它额外增加了两种条件输入。

第一种是时间步 t。模型通过一个正弦位置编码把 t 映射成向量,再通过几个全连接层变成"时间嵌入",注入到 UNet 的每个残差块中。这样,模型在 t 比较大的阶段主要处理高噪声,在 t 比较小的阶段主要恢复细节,不同阶段可以用不同的策略。

第二种是文本条件。文本嵌入序列并不是简单地拼接到潜在向量后面,而是通过交叉注意力机制进入 UNet。交叉注意力的公式和标准注意力一样,但有一个关键区别:查询 Query 来自图像特征,键 Key 和值 Value 来自文本嵌入。这样,每个图像位置都会去"查找"文本中最相关的部分,相当于告诉模型"这片区域要表现的是词 A 的含义,那个区域要受词 B 的影响"。

我画过一条很形象的类比:把整个生成过程想象成开会。每次开会,图像特征负责提问,文本条件负责回答问题,最后的决议就是融合后的新特征。提问内容不同、回答内容不同,决议自然不同。这也是为什么"一只坐着的猫"和"一只奔跑的猫"在同样的噪声初始化下会引导出完全不同的画面。

5.3 为什么预测噪声而不是直接预测图像

初学者最容易在这个问题上卡住。既然最终目标是图像,为什么不直接让 UNet 从噪声一步到位预测出清晰图像?

关键在于"学习难度"。直接预测图像相当于让网络一步跨越几十步的复杂映射,很难优化,而且生成的图像容易模糊。预测噪声的本质是学习"这一步该去除多少噪声",这是一步一步的增量学习,每一步的预测目标都很明确。训练时,我们已知 x_t 是通过公式 x_t = sqrt(alpha_bar_t) * x_0 + sqrt(1 - alpha_bar_t) * epsilon 得到的,最终的噪声 epsilon 是已知的,网络只需要学会从 x_t 中反推 epsilon,任务简单得多。

在采样时,每次得到预测噪声 epsilon_theta 后,再通过调度器的更新公式推算出 x_{t-1}。所以 UNet 不是直接输出图像,而是输出一张"噪声地图",指挥调度器如何修改当前潜在向量。整个过程像修复旧照片:每次只擦掉一层污渍,擦三四次就能看清大轮廓,再擦十几次就能看到完整细节。

6. 一块 GPU 也能跑的文生图实战:diffusers 库从零出图

理论部分铺完了,接下来进入代码实战。我用的环境是 Python + diffusers 库,这也是目前最主流的开源推理方案。只要显卡显存不低于 6GB,跑 SD 1.5 一般没有太大压力;如果只有 CPU,也能跑,只是速度慢很多,一张 512×512 的图可能需要几分钟到十几分钟。

6.1 环境准备与依赖安装

建议创建一个干净的环境,然后安装以下依赖:

pip install diffusers transformers accelerate torch torchvision pillow

如果你有 NVIDIA 显卡,确保安装了对应版本的 CUDA 和 PyTorch;没有显卡的话,PyTorch 会走 CPU 路线,也可以跑但体验会差不少。我建议先用一个现成的模型权重文件夹,或用社区里常见的模型仓库,把模型下载到本地,这样后面代码里的from_pretrained路径就能直接指向本地路径。

提示:第一次加载模型需要下载权重,时间会比较长。建议直接在代码里设置本地路径。文件名不要包含中文和空格,避免出现奇怪的路径问题。

6.2 跑通 StableDiffusionPipeline:最简三行出图

diffusers 把整个流程封装在StableDiffusionPipeline里,用起来非常简洁。下面是一个最基础的例子:

from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler import torch pipe = StableDiffusionPipeline.from_pretrained( "你的本地模型路径", torch_dtype=torch.float16, safety_checker=None, ) pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config) pipe = pipe.to("cuda") prompt = "a cute corgi floating in a cup of coffee, masterpiece, best quality" image = pipe( prompt=prompt, negative_prompt="lowres, bad anatomy, worst quality", num_inference_steps=25, guidance_scale=7.5, seed=42, ).images[0] image.save("output.png")

这段代码涵盖了文生图的完整流程:文本编码、随机初始化噪声、UNet 迭代去噪、VAE 解码。seed=42是 diffusers 里常见的固定随机种子方式,可以保证同一提示词在同一模型下生成结果可复现。如果你发现输出的图片完全随机,可以检查一下是否手动设置了随机种子。

DPMSolverMultistepScheduler是比原始 DDPM 调度器快很多的采样器,25 步的效果通常能追平之前的 50 步,是当前最常用的一套配置。

6.3 自己拆开采样循环,看清 UNet 每次在干嘛

如果你只是想出图,用pipe()就够了;但如果你想真正理解"UNet 到底在哪一步被调用、CFG 是怎么工作的",建议把采样循环拆开手动跑一遍。diffusers 提供了StableDiffusionPipeline内部组件,可以像下面这样逐个调用:

import torch from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler pipe = StableDiffusionPipeline.from_pretrained( "你的本地模型路径", torch_dtype=torch.float16, safety_checker=None, ) pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config) pipe = pipe.to("cuda") prompt = "a cute corgi floating in a cup of coffee" negative_prompt = "lowres, bad anatomy" prompt_embeds = pipe.encode_prompt(prompt, negative_prompt, do_classifier_free_guidance=True) # prompt_embeds 返回 (text_embeds, negative_text_embeds) 或合并后的序列,视版本而定 latents = torch.randn( (1, pipe.unet.config.in_channels, 64, 64), device="cuda", dtype=torch.float16, ) latents = latents * pipe.scheduler.init_noise_sigma pipe.scheduler.set_timesteps(num_inference_steps=25) for t in pipe.scheduler.timesteps: latent_model_input = torch.cat([latents] * 2) # 无条件与有条件各一份 latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t) with torch.no_grad(): noise_pred = pipe.unet( latent_model_input, t, encoder_hidden_states=prompt_embeds, ).sample uncond_pred, cond_pred = noise_pred.chunk(2) # CFG 公式:最终预测 = uncond + guidance_scale * (cond - uncond) noise_pred = uncond_pred + 7.5 * (cond_pred - uncond_pred) latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample with torch.no_grad(): image = pipe.vae.decode(latents / pipe.vae.config.scaling_factor, return_dict=False)[0] image = (image / 2 + 0.5).clamp(0, 1) image = image.permute(0, 2, 3, 1).float().cpu().numpy()

这段代码里最关键的部分是do_classifier_free_guidance=True时,为了做 CFG,需要把输入复制成两份:一份用于无条件预测,一份用于带条件预测。最终噪声预测是两者的加权融合:

noise_pred = uncond_pred + guidance_scale * (cond_pred - uncond_pred)

guidance_scale越大,模型越"听话",画面越贴合提示词,但多样性会降低,超过一定数值还可能过饱和、出现伪影。这也是手调参数时最需要关注的旋钮。

手动拆开采样循环还有一个好处:你可以清楚地看到 UNet 每步的输入是 latents、时间步 t、文本嵌入;输出是 noise_pred。你会真切体会到第 5 节说的"UNet 在预测噪声",而不是在生成图像。

6.4 输出质量相关的几个调参位置

我在实测过程中总结了一套比较稳的基准参数,适合 SD 1.5 模型:

参数推荐范围说明
采样步数20 - 30步数过少容易粗糙,步数过多收益递减
guidance_scale5 - 8数值越大越贴近提示词,但过高会糊
分辨率512×512 或 768×768超出模型训练分辨率容易重复构图或结构崩坏
种子固定或不固定固定种子便于复现,不固定更利于抽卡
CFG 搭配建议同时设置负面提示词负面提示词能显著减少画质崩坏

如果对自己的显卡显存没有把握,可以把torch_dtype设置为torch.float16,并使用pipe.enable_attention_slicing()降低显存占用。有些集成包已经自动做了这些优化,但自己跑的时候别忘了。

7. 提示词与手部修复:实操中最常踩的几个坑

最后一部分聊聊实操里的高频问题。我相信几乎每个 SD 玩家都被"手指崩坏"折磨过。为什么 AI 绘画手部修复难题这么常见?网上甚至有人统计,SD 1.5 生成的人物出图,手部完全正确的比例很低。这不是模型的"道德问题",背后有清晰的技术原因。

7.1 手部崩坏的根源:并不是模型"看不懂手"

手部崩坏的根本原因有三个。第一,训练数据里手部区域占图像的比例很小,画面里大量的信号来自脸、身体和背景,模型会重点学这些高频信息;第二,手部的结构复杂、自由度极高,手指之间的重叠、弯曲、遮挡难以用简单的特征表达;第三,潜在空间扩散压缩了图像信息,精细的手部结构在压缩和还原过程中容易丢失,扩散模型难以准确恢复。

很多新手试图通过"在手部区域拼命堆提示词"来解决,比如加"perfect hands, eight fingers, beautiful fingers",实际上作用有限。原因在于 CLIP 文本编码器只能把提示词映射成语义向量,它不能精准地告诉 UNet 每个像素里的手指关节该怎么旋转。所以行业内更常用的其实是修复流程,而不是"提示词魔法"。

7.2 修复手部的三招组合拳

我实测下来比较有效的方案,按照从简单到复杂排序,可以归纳为三招:

第一招:负面提示词与负面嵌入。在负面提示词里写入bad anatomy, bad hands, extra fingers, missing fingers, distorted fingers等标签,能在一定程度上降低手部出错概率。如果模型支持负面嵌入(negative embedding),还可以加载专门训练过的负面嵌入文件,比单纯的负面提示词更稳定。

第二招:局部重绘。先在 WebUI 或 ComfyUI 里用修复模式或蒙版工具,把崩坏的手部区域涂掉,然后只对该区域进行 inpaint 重绘。关键技巧是:重绘时选择的区域不要太小,最好把整个手腕和部分前臂一起框进去,给模型足够的上下文来决定手指的位置和朝向;另外把重绘幅度(denoising strength)控制在 0.4 到 0.6 之间,既保留周围风格,又能重置手部结构。

第三招:分步优化。先生成一张构图满意的图,再用局部重绘或 imagedetail 类扩展,单独把人物手臂部分裁剪出来,放大大去噪,修复细节后回贴原图。这一招更适合有明确构图需求、又不希望整体重抽的情况。

我自己实践后的感受是:指望删掉"手"这个角色来避坑是不行的,因为构图需要手;合理安排流程才是关键。先靠负面提示词降低概率,再靠局部修复清理剩余问题,基本能应对 80% 的手部事故。

7.3 提示词工程:从"魔法单词"到结构化指令

用过一段 SD 后你会发现,提示词不是"写得多就有用",而是要按结构化方式组织。我习惯把正面提示词拆成四段:主体描述、环境背景、风格修饰、质量标签。

主体描述:a young woman with long silver hair, wearing a black leather jacket 环境背景:standing in a rainy cyberpunk street, neon lights reflecting on wet asphalt 风格修饰:cinematic lighting, detailed face, intricate details 质量标签:masterpiece, best quality, ultra detailed, 8k

负面提示词我通常会写得稍微宽一些,但不要写满 77 个 token,因为负面提示词也会占用文本编码窗口:

lowres, bad anatomy, bad hands, missing fingers, extra digits, blurry, jpeg artifacts, watermark, oversaturated, deformed

很多人忽略了一个点:提示词里的逗号不是分隔符,而是 token 序列的自然分隔。模型不会严格按照逗号来组织语义,相邻 token 的组合可能会产生意外联想。所以不要写太长的短语堆叠,尽量用简短的词块,让 CLIP 能更准确地捕捉每个概念。

7.4 种子、CFG 与采样步数:三个影响稳定性与随机性的旋钮

最后聊聊参数背后的逻辑。种子决定了初始噪声的样子。固定种子后,同一个提示词、同一个模型、同一个采样配置,会得到几乎相同的构图。如果你的提示词没变,但生成结果一直在漂,先检查是不是没固定种子。

CFG Scale 控制的是无条件预测和条件预测的差值权重。数值调大,模型会更"迎合"提示词,但可能丢失自然多样性;数值过小,生成结果容易偏抽象,和提示词脱节。我看到很多新人在 12 以上乱拉,结果画质反而下降。我通常建议从 7 起步,微调范围控制在 5-9。

采样步数也不是越多越好。对 DPMSolver 这种高阶调度器来说,25 步和 30 步的差距已经很小,但 50 步会让单张图耗时明显上升。如果你追求效率,推荐 20-25 步;如果画面出现明显楼梯效应或条纹,再适当增加到 30 步左右。

7.5 一点个人小结

跑图这件事,说到底是一个"控制变量"的过程。固定种子、固定模型、固定负面提示词,然后单独调整一个参数,观察变化,记录结果。这样做三五次,你就能摸清某个模型在哪些提示词、哪些参数下表现最好。很多看似困难的手部修复、风格统一问题,本质上都是可以通过流程拆解来缓解的,而不是完全依赖运气。

我在整理完这套笔记后的最大体会是:Stable Diffusion 的原理并不复杂,但各个组件之间的配合非常有讲究。CLIP 负责理解语义,扩散过程负责定义演化路径,UNet 负责执行去噪,VAE 负责最终还原。这四个模块串起来的链条,就是整条文生图的完整链路。只要你愿意顺着这条链路走一遍源码、跑一次代码实验,很多界面上的玄学参数都会变成有逻辑可推导的选择。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询