解释 SDXL 潜在空间

社区文章 发布于 2024 年 5 月 20 日

摘要
查看交互式演示

目录

简短背景故事
SDXL 潜在空间的 4 个通道
    8 位像素空间有 3 个通道
    图像的 SDXL 潜在表示有 4 个通道
    使用线性近似直接将 SDXL 潜在空间转换为 RGB
    SDXL 色彩范围偏向黄色的可能原因
需要纠正什么?
让我们看一个 SDXL 的输出示例
完整演示
增加色彩范围/移除色彩偏差
高引导尺度下的长提示成为可能

引言

这篇文章被其他网站爬取并改写后,我收到了一些奇怪的问题。如果你在 Hugging Face 以外的任何地方读到这篇文章,这里是原文:https://huggingface.co/blog/TimothyAlexisVass/explaining-the-sdxl-latent-space

简短背景故事

特别感谢:Ollin Boer BohanHaomingCristina SegalinBirchlabs 提供的帮助、讨论和知识!

我正在为我正在创建的扩散模型 UI 开发SDXL 推理过程的修正滤镜

经过多年图像修正经验,我希望具备从根本上改进 SDXL 实际输出的能力。我希望在用户体验中提供许多技术,我决定自己解决。我注意到 SDXL 输出几乎总是呈现有规律的噪点或过度平滑。由于 SD 模型的工作方式,色彩空间总是需要白平衡,并且色彩范围存在偏差和限制。

如果可以在实际输出之前改进信息和色彩范围,那么在图像生成并转换为 8 位 RGB 后进行后期处理的修正就意义不大了。

要创建滤镜和修正工具,最重要的是了解你正在处理的数据。

这促使我实验性地探索 SDXL 潜在空间,以期理解它们。扩散模型基于 SDXL 架构处理的张量看起来是这样的:

[batch_size, 4 channels, height (y), width (x)]

我的第一个问题很简单:“**这 4 个通道到底是什么?**”。我收到的多数回答都类似“这不是人类能理解的东西。”

但它绝对可以理解。甚至非常容易理解且很有用。

SDXL 潜在空间的 4 个通道

对于 SDXL 生成的 1024×1024 像素图像,潜在张量为 128×128 像素,其中潜在空间中的每个像素代表像素空间中的 64 (8×8) 个像素。如果我们将潜在空间生成并解码为标准 8 位 JPG 图像,那么...

8 位像素空间有 3 个通道

红色 (R)、绿色 (G) 和蓝色 (B),每个通道有 256 个可能值,范围在 0-255 之间。因此,为了存储 64 个像素的完整信息,我们每个潜在像素的每个通道需要存储 64×256 = 16,384 个值。

图像的 SDXL 潜在表示有 4 个通道

点击标题查看交互式演示!

0: 亮度
1: 青色/红色 => 等同于 rgb(0, 255, 255)/rgb(255, 0, 0)
2: 柠檬绿/中紫色 => 等同于 rgb(127, 255, 0)/rgb(127, 0, 255)
3: 模式/结构。

如果在解码时每个值可以在 -4 到 4 之间变化,那么在半精度 16 位浮点格式中,每个潜在像素的 4 个通道可以包含 16,384 个不同的值。

使用线性近似直接将 SDXL 潜在空间转换为 RGB

有了这个理解,我们可以创建一个近似函数,直接将潜在空间转换为 RGB

def latents_to_rgb(latents):
    weights = (
        (60, -60, 25, -70),
        (60,  -5, 15, -50),
        (60,  10, -5, -35)
    )

    weights_tensor = torch.t(torch.tensor(weights, dtype=latents.dtype).to(latents.device))
    biases_tensor = torch.tensor((150, 140, 130), dtype=latents.dtype).to(latents.device)
    rgb_tensor = torch.einsum("...lxy,lr -> ...rxy", latents, weights_tensor) + biases_tensor.unsqueeze(-1).unsqueeze(-1)
    image_array = rgb_tensor.clamp(0, 255)[0].byte().cpu().numpy()
    image_array = image_array.transpose(1, 2, 0)  # Change the order of dimensions

    return Image.fromarray(image_array)

这里是 latents_to_rgb 结果和常规解码输出,为了比较而调整了大小

SDXL 色彩范围偏向黄色的可能原因

自然界中蓝色或白色的事物相对较少。这些颜色在宜人的天气条件下在天空中最为突出。因此,模型通过图像了解现实,以亮度(通道 0)、青色/红色(通道 1)和柠檬绿/中紫色(通道 2)进行思考,其中红色和绿色是主色,蓝色是副色。这就是为什么 SDXL 生成的图像通常偏向黄色(红色 + 绿色)。

在推理过程中,张量中的值将从 min < -30max > 30 开始,解码时的 min/max 边界约为 -44。在较高的 guidance_scale 下,值在 minmax 之间会有更高的差异。

理解边界的一个关键是查看解码过程中发生的事情

decoded = vae.decode(latents / vae.scaling_factor).sample # (SDXL vae.scaling_factor = 0.13025)
decoded = decoded.div(2).add(0.5).clamp(0, 1) # The dynamics outside of 0 to 1 at this point will be lost

如果此时的值超出 0 到 1 的范围,某些信息将在裁剪中丢失。因此,如果我们在去噪过程中进行修正以提供 VAE 期望的值,我们可能会获得更好的结果。

需要纠正什么?

如何锐化模糊图像、白平衡、改善细节、增加对比度或增加色彩范围?最好的方法是从一张锐利、白平衡正确、对比度好、细节清晰且范围广的图像开始。

模糊一张锐利图像、改变色彩平衡、降低对比度、产生无意义的细节并限制色彩范围,远比改进它容易。

SDXL 有一个非常显著的颜色偏差倾向,并将值放在实际边界之外(左图)。通过将值居中并使其位于边界内,这很容易解决(右图)

超出边界的原始输出
为说明目的而夸大的校正
def center_tensor(input_tensor, per_channel_shift=1, full_tensor_shift=1, channels=[0, 1, 2, 3]):
    for channel in channels:
        input_tensor[0, channel] -= input_tensor[0, channel].mean() * per_channel_shift
    return input_tensor - input_tensor.mean() * full_tensor_shift

让我们看一个 SDXL 的输出示例

seed: 77777777
guidance_scale: 20 # A high guidance scale can be fixed too
steps with base: 23
steps with refiner: 10

prompt: Cinematic.Beautiful smile action woman in detailed white mecha gundam armor with red details,green details,blue details,colorful,star wars universe,lush garden,flowers,volumetric lighting,perfect eyes,perfect teeth,blue sky,bright,intricate details,extreme detail of environment,infinite focus,well lit,interesting clothes,radial gradient fade,directional particle lighting,wow

negative_prompt: helmet, bokeh, painting, artwork, blocky, blur, ugly, old, boring, photoshopped, tired, wrinkles, scar, gray hair, big forehead, crosseyed, dumb, stupid, cockeyed, disfigured, crooked, blurry, unrealistic, grayscale, bad anatomy, unnatural irises, no pupils, blurry eyes, dark eyes, extra limbs, deformed, disfigured eyes, out of frame, no irises, assymetrical face, broken fingers, extra fingers, disfigured hands

请注意,我特意选择了高引导尺度。

我们如何修复这张图像?它半是绘画,半是照片。色彩范围偏向黄色。右边是使用完全相同设置的修复生成图像。

即使在 guidance_scale 设置为 7.5 的合理值下,我们仍然可以得出结论,修复后的输出更好,没有无意义的细节,并且白平衡正确。

我们可以在潜在空间中做很多事情来普遍改善生成,也可以做一些非常简单的事情来针对生成中的特定错误

异常值移除

这将通过修剪偏离分布均值最远的值来控制无意义细节的数量。它还有助于在更高的 guidance_scale 下生成。

# Shrinking towards the mean (will also remove outliers)
def soft_clamp_tensor(input_tensor, threshold=3.5, boundary=4):
    if max(abs(input_tensor.max()), abs(input_tensor.min())) < 4:
        return input_tensor
    channel_dim = 1

    max_vals = input_tensor.max(channel_dim, keepdim=True)[0]
    max_replace = ((input_tensor - threshold) / (max_vals - threshold)) * (boundary - threshold) + threshold
    over_mask = (input_tensor > threshold)

    min_vals = input_tensor.min(channel_dim, keepdim=True)[0]
    min_replace = ((input_tensor + threshold) / (min_vals + threshold)) * (-boundary + threshold) - threshold
    under_mask = (input_tensor < -threshold)

    return torch.where(over_mask, max_replace, torch.where(under_mask, min_replace, input_tensor))

色彩平衡和增加范围

我主要有两种方法来实现这一点。第一种是通过归一化值来收缩到平均值(这也会移除异常值),第二种是当值偏向某种颜色时进行修复。这也有助于在较高的 guidance_scale 下生成。

# Center tensor (balance colors)
def center_tensor(input_tensor, channel_shift=1, full_shift=1, channels=[0, 1, 2, 3]):
    for channel in channels:
        input_tensor[0, channel] -= input_tensor[0, channel].mean() * channel_shift
    return input_tensor - input_tensor.mean() * full_shift

张量最大化

这基本上是通过将张量乘以一个非常小的量(如 1e-5)进行几步操作,并确保最终张量在使用所有可能的范围(接近 -4/4)后才转换为 RGB。请记住,在像素空间中,在不损害动态的情况下降低对比度、饱和度和锐度比提高它们更容易。

# Maximize/normalize tensor
def maximize_tensor(input_tensor, boundary=4, channels=[0, 1, 2]):
    min_val = input_tensor.min()
    max_val = input_tensor.max()

    normalization_factor = boundary / max(abs(min_val), abs(max_val))
    input_tensor[0, channels] *= normalization_factor

    return input_tensor

回调实现示例

def callback(pipe, step_index, timestep, cbk):
      if timestep > 950:
          threshold = max(cbk["latents"].max(), abs(cbk["latents"].min())) * 0.998
          cbk["latents"] = soft_clamp_tensor(cbk["latents"], threshold*0.998, threshold)
      if timestep > 700:
          cbk["latents"] = center_tensor(cbk["latents"], 0.8, 0.8)
      if timestep > 1 and timestep < 100:
          cbk["latents"] = center_tensor(cbk["latents"], 0.6, 1.0)
          cbk["latents"] = maximize_tensor(cbk["latents"])
      return cbk

  image = base(
      prompt,
      guidance_scale = guidance_scale,
      callback_on_step_end=callback,
      callback_on_step_end_inputs=["latents"]
  ).images[0]

这三种方法的简单实现用于最后几张图片,其中有花园里的女人

完整演示

点击标题或此链接查看交互式演示!

本演示通过使用 Z-score 检测异常值、动态地向均值移动以及对每种技术施加强度,使用了更高级的技术实现。

原始 SDXL(太黄)与轻微修改(白平衡)

中等修改和强力修改(均应用了所有 3 种技术)

增加色彩范围/移除色彩偏差

在下方,SDXL 在常规输出中将色彩范围限制为红色和绿色。因为提示中没有提及蓝色的存在。这是一个相当好的生成,但色彩范围已被限制。

如果你给某人提供黑、红、绿、黄的调色板,然后让他们画一片湛蓝的天空,自然的反应是要求你提供蓝色和白色。

要在生成中包含蓝色,我们只需在色彩空间受限时重新对齐,SDXL 就会在生成中适当地包含完整的色彩光谱。

高引导尺度下的长提示成为可能

这是一个典型的场景,增加色彩范围使整个提示变得可能。
此示例应用了前面所示的简单、强力修改,以更清晰地说明差异。

提示:身穿红色连衣裙的女人在奢华花园中的照片,周围环绕着蓝色、黄色、紫色和多种颜色的花朵,高级,屡获殊荣的摄影,Portra 400,全画幅。蓝天,即使是最小的粒子也有复杂的细节,环境的极致细节,清晰的肖像,光线充足有趣的服装,美丽的阴影,明亮,照片质量,超现实,杰作

这里是关于同一概念的更多比较

请记住,这些都只使用了相同的静态修改

UDI SDXL 修正滤镜

返回顶部

image/jpeg

社区

注册登录 以发表评论