Diffusion 课程文档

介绍

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始入门

Open In Colab

介绍

本笔记本将介绍如何使用 Stable Diffusion 通过现有 pipelines 创建和修改图像的基础知识。我们还将简要了解 pipeline 中的关键组件,更深入的探索将在深入研究笔记本中进行。具体来说,我们将涵盖:

  • 使用 StableDiffusionPipeline 从文本生成图像,并尝试可用的参数
  • 了解一些关键 pipeline 组件的实际应用
    • 使之成为“潜在扩散模型”的 VAE
    • 处理文本 prompt 的 tokenizer 和文本编码器
    • UNet 本身
    • scheduler,以及探索不同的 schedulers
  • 使用 pipeline 组件复现采样循环
  • 使用 Img2Img pipeline 编辑现有图像
  • 使用 inpainting 和 Depth2Img pipelines

❓如果您有任何问题,请在 Hugging Face Discord 服务器上的 #diffusion-models-class 频道上发布。如果您尚未注册,可以在这里注册:https://huggingface.co/join/discord

设置

%pip install -Uq diffusers ftfy accelerate
# Installing transformers from source for now since we need the latest version for Depth2Img
%pip install -Uq git+https://github.com/huggingface/transformers
import torch
import requests
from PIL import Image
from io import BytesIO
from matplotlib import pyplot as plt

# We'll be exploring a number of pipelines today!
from diffusers import (
    StableDiffusionPipeline,
    StableDiffusionImg2ImgPipeline,
    StableDiffusionInpaintPipeline,
    StableDiffusionDepth2ImgPipeline,
)


# We'll use a couple of demo images later in the notebook
def download_image(url):
    response = requests.get(url)
    return Image.open(BytesIO(response.content)).convert("RGB")


# Download images for inpainting example
img_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo.png"
mask_url = "https://raw.githubusercontent.com/CompVis/latent-diffusion/main/data/inpainting_examples/overture-creations-5sI6fQgYIuo_mask.png"

img_height = 512
img_width = 512

init_image = download_image(img_url).resize((img_height, img_width))
mask_image = download_image(mask_url).resize((img_height, img_width))
# Set device
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"

从文本生成图像

让我们加载一个 Stable Diffusion pipeline,看看它能做什么。Stable Diffusion 有多个不同的版本,在撰写本文时,最新版本是 2.1。如果您想探索旧版本,只需将模型 ID 替换为适当的模型即可(例如,您可以尝试 “CompVis/stable-diffusion-v1-4”,或从 dreambooth 概念库 中选择一个模型)。

# Load the pipeline
model_id = "stabilityai/stable-diffusion-2-1-base"
pipe = StableDiffusionPipeline.from_pretrained(model_id).to(device)

如果您的 GPU 内存不足,可以采取一些措施来减少 RAM 使用量:

  • 加载 FP16 版本(并非所有系统都支持)。使用此版本时,您可能还需要在试验 pipeline 的各个组件时将 tensors 转换为 torch.float16

    pipe = StableDiffusionPipeline.from_pretrained(model_id, revision="fp16", torch_dtype=torch.float16).to(device)

  • 启用 attention slicing。这会减少 GPU 内存使用量,但会略微降低速度

    pipe.enable_attention_slicing()

  • 减小您生成的图像的大小

加载 pipeline 后,我们可以使用以下代码根据 prompt 生成图像

>>> # Set up a generator for reproducibility
>>> generator = torch.Generator(device=device).manual_seed(42)

>>> # Run the pipeline, showing some of the available arguments
>>> pipe_output = pipe(
...     prompt="Palette knife painting of an autumn cityscape",  # What to generate
...     negative_prompt="Oversaturated, blurry, low quality",  # What NOT to generate
...     height=480,
...     width=640,  # Specify the image size
...     guidance_scale=8,  # How strongly to follow the prompt
...     num_inference_steps=35,  # How many steps to take
...     generator=generator,  # Fixed random seed
... )

>>> # View the resulting image
>>> pipe_output.images[0]

练习: 花一些时间玩玩上面的单元格,使用您自己的 prompts 并调整设置,看看它们如何影响输出。使用不同的随机种子或删除 generator 参数以每次获得不同的结果。

要调整的关键参数

  • Width 和 height 指定生成的图像的大小。为了使 VAE 正常工作,它们必须能被 8 整除(我们将在以后的章节中看到)。
  • 步数会影响生成质量。默认值 (50) 效果很好,但在某些情况下,您可以使用低至 20 步,这对于实验来说非常方便。
  • negative prompt 在无分类器引导过程中使用,并且可以成为添加额外控制的有用方法。您可以将其省略,但许多用户发现将一些不良描述列在 negative prompt 中很有用,如上所示。
  • guidance_scale 参数决定了无分类器引导 (CFG) 的强度。较高的 scale 会促使生成的图像更好地匹配 prompt,但如果 scale 过高,结果可能会变得过度饱和且令人不悦。

如果您正在寻找 prompt 灵感,Stable Diffusion Prompt Book 是一个不错的起点。

您可以在以下单元格中看到增加 guidance scale 的效果

>>> # @markdown comparing guidance scales:
>>> cfg_scales = [1.1, 8, 12]  # @param
>>> prompt = "A collie with a pink hat"  # @param
>>> fig, axs = plt.subplots(1, len(cfg_scales), figsize=(16, 5))
>>> for i, ax in enumerate(axs):
...     im = pipe(
...         prompt,
...         height=480,
...         width=480,
...         guidance_scale=cfg_scales[i],
...         num_inference_steps=35,
...         generator=torch.Generator(device=device).manual_seed(42),
...     ).images[0]
...     ax.imshow(im)
...     ax.set_title(f"CFG Scale {cfg_scales[i]}")

调整上面的值以尝试不同的 scales 和 prompts。当然,解释是主观的,但在我看来,8-12 范围内的任何值都比低于或高于此范围的值产生更好的结果。

Pipeline 组件

我们正在使用的 StableDiffusionPipeline 比我们在之前的单元中探索的 DDPMPipeline 稍微复杂一些。除了 UNet 和 scheduler 之外,pipeline 中还包含许多其他组件:

>>> print(list(pipe.components.keys()))  # List components
['vae', 'text_encoder', 'tokenizer', 'unet', 'scheduler', 'safety_checker', 'feature_extractor', 'image_encoder']

为了更好地理解 pipeline 的工作原理,让我们简要地单独了解每个组件的实际应用,然后将它们组合在一起,为我们自己复现 pipeline 的功能。

VAE

vae_diagram.png

VAE(变分自动编码器)是一种模型,可以将输入编码为压缩表示,然后将此“潜在”表示解码回接近原始输入的内容。当使用 Stable Diffusion 生成图像时,我们首先通过在 VAE 的“潜在空间”中应用 diffusion 过程来生成潜在表示,然后在最后对其进行解码以查看生成的图像。

以下是一些代码,它接收输入图像,将其编码为潜在表示,然后使用 VAE 再次解码

>>> # Create some fake data (a random image, range (-1, 1))
>>> images = torch.rand(1, 3, 512, 512).to(device) * 2 - 1
>>> print("Input images shape:", images.shape)

>>> # Encode to latent space
>>> with torch.no_grad():
...     latents = 0.18215 * pipe.vae.encode(images).latent_dist.mean
>>> print("Encoded latents shape:", latents.shape)

>>> # Decode again
>>> with torch.no_grad():
...     decoded_images = pipe.vae.decode(latents / 0.18215).sample
>>> print("Decoded images shape:", decoded_images.shape)
Input images shape: torch.Size([1, 3, 512, 512])
Encoded latents shape: torch.Size([1, 4, 64, 64])
Decoded images shape: torch.Size([1, 3, 512, 512])

正如您所看到的,512x512 图像被压缩为 64x64 潜在表示(具有四个通道)。空间维度上 8 倍的缩减是指定宽度和高度需要是 8 的倍数的原因。

使用这些信息丰富的 4x64x64 潜在表示比使用巨大的 512px 图像更有效,从而实现更快的 diffusion 模型,这些模型在训练和使用时占用更少的资源。VAE 解码过程并非完美,但它足够好,以至于小的质量权衡通常是值得的。

注意:上面的代码示例包含 0.18215 的缩放因子,这是匹配 SD 训练期间使用的处理所必需的。

Tokenizer 和文本编码器

text_encoder.png

文本编码器的目标是将输入字符串(prompt)转换为数值表示,该数值表示可以作为条件输入到 UNet 中。文本首先使用 pipeline 的 tokenizer 转换为一系列 tokens。文本编码器具有大约 50k tokens 的词汇表 - 任何不在此词汇表中的单词都会被拆分为更小的子词。然后,tokens 通过文本编码器模型本身 - 一个最初被训练为 CLIP 的文本编码器的 transformer 模型。希望这个预训练的 transformer 模型已经学习了丰富的文本表示,这些表示也将对 diffusion 任务有用。

让我们通过编码一个示例 prompt 来测试这个过程,首先手动进行 tokenization 并将其输入到文本编码器中,然后使用 pipelines 的 encode_prompt 方法来显示完整的过程,包括将长度填充/截断到最大长度 77 个 tokens

>>> # Tokenizing and encoding an example prompt manually

>>> # Tokenize
>>> input_ids = pipe.tokenizer(["A painting of a flooble"])["input_ids"]
>>> print("Input ID -> decoded token")
>>> for input_id in input_ids[0]:
...     print(f"{input_id} -> {pipe.tokenizer.decode(input_id)}")

>>> # Feed through CLIP text encoder
>>> input_ids = torch.tensor(input_ids).to(device)
>>> with torch.no_grad():
...     text_embeddings = pipe.text_encoder(input_ids)["last_hidden_state"]
>>> print("Text embeddings shape:", text_embeddings.shape)
Input ID -> decoded token
49406 -> <|startoftext|>
320 -> a
3086 -> painting
539 -> of
320 -> a
4062 -> floo
1059 -> ble
49407 -> <|endoftext|>
Text embeddings shape: torch.Size([1, 8, 1024])
# Get the final text embeddings using the pipeline's encode_prompt function
text_embeddings = pipe._encode_prompt("A painting of a flooble", device, 1, True, "")
text_embeddings.shape

这些文本嵌入(文本编码器模型中最后一个 transformer 块的所谓“隐藏状态”)将作为 forward 方法的附加参数输入到 UNet 中,我们将在下一节中看到。

UNet

unet.png

UNet 接收噪声输入并预测噪声,就像我们在之前的单元中看到的 UNets 一样。与之前的示例不同,输入不是图像,而是图像的潜在表示。除了时间步条件之外,此 UNet 还将 prompt 的文本嵌入作为附加输入。这是它在一些虚拟数据上进行预测

>>> # Dummy inputs
>>> timestep = pipe.scheduler.timesteps[0]
>>> latents = torch.randn(1, 4, 64, 64).to(device)
>>> text_embeddings = torch.randn(1, 77, 1024).to(device)

>>> # Model prediction
>>> with torch.no_grad():
...     unet_output = pipe.unet(latents, timestep, text_embeddings).sample
>>> print("UNet output shape:", unet_output.shape)  # Same shape as the input latents
UNet output shape: torch.Size([1, 4, 64, 64])

Scheduler

scheduler 存储噪声计划并管理基于模型预测更新噪声样本。默认的 scheduler 是 PNDMScheduler,但您可以使用其他 scheduler(例如 LMSDiscreteScheduler),只要它们使用相同的配置进行初始化即可。

我们可以绘制噪声计划,以查看噪声水平(基于 $\bar{\alpha}$)随时间的变化

>>> plt.plot(pipe.scheduler.alphas_cumprod, label=r"$\bar{\alpha}$")
>>> plt.xlabel("Timestep (high noise to low noise ->)")
>>> plt.title("Noise schedule")
>>> plt.legend()

如果您想尝试不同的 scheduler,您可以按如下方式换入新的 scheduler

>>> from diffusers import LMSDiscreteScheduler

>>> # Replace the scheduler
>>> pipe.scheduler = LMSDiscreteScheduler.from_config(pipe.scheduler.config)

>>> # Print the config
>>> print("Scheduler config:", pipe.scheduler)

>>> # Generate an image with this new scheduler
>>> pipe(
...     prompt="Palette knife painting of an winter cityscape",
...     height=480,
...     width=480,
...     generator=torch.Generator(device=device).manual_seed(42),
... ).images[0]
Scheduler config: LMSDiscreteScheduler &#123;
  "_class_name": "LMSDiscreteScheduler",
  "_diffusers_version": "0.30.3",
  "beta_end": 0.012,
  "beta_schedule": "scaled_linear",
  "beta_start": 0.00085,
  "clip_sample": false,
  "num_train_timesteps": 1000,
  "prediction_type": "epsilon",
  "set_alpha_to_one": false,
  "skip_prk_steps": true,
  "steps_offset": 1,
  "timestep_spacing": "linspace",
  "trained_betas": null,
  "use_karras_sigmas": false
}

您可以在此处阅读有关使用不同 schedulers 的更多信息。

DIY 采样循环

现在我们已经了解了所有这些组件的实际应用,我们可以将它们组合在一起以复现 pipeline 的功能

>>> guidance_scale = 8  # @param
>>> num_inference_steps = 30  # @param
>>> prompt = "Beautiful picture of a wave breaking"  # @param
>>> negative_prompt = "zoomed in, blurry, oversaturated, warped"  # @param

>>> # Encode the prompt
>>> text_embeddings = pipe._encode_prompt(prompt, device, 1, True, negative_prompt)

>>> # Create our random starting point
>>> latents = torch.randn((1, 4, 64, 64), device=device, generator=generator)
>>> latents *= pipe.scheduler.init_noise_sigma

>>> # Prepare the scheduler
>>> pipe.scheduler.set_timesteps(num_inference_steps, device=device)

>>> # Loop through the sampling timesteps
>>> for i, t in enumerate(pipe.scheduler.timesteps):

...     # Expand the latents if we are doing classifier free guidance
...     latent_model_input = torch.cat([latents] * 2)

...     # Apply any scaling required by the scheduler
...     latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)

...     # Predict the noise residual with the UNet
...     with torch.no_grad():
...         noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample

...     # Perform guidance
...     noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
...     noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)

...     # Compute the previous noisy sample x_t -> x_t-1
...     latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample

>>> # Decode the resulting latents into an image
>>> with torch.no_grad():
...     image = pipe.decode_latents(latents.detach())

>>> # View
>>> pipe.numpy_to_pil(image)[0]

在大多数情况下,使用现有的 pipelines 会更容易,但是拥有这种可破解的采样循环对于理解和修改每个组件的工作方式可能很有用。如果您想深入了解此代码以及所有不同的组件的探索和修改,请查看 “Stable Diffusion Deep Dive” notebook视频,以进行更全面的探索。

附加 Pipelines

那么除了从 prompt 生成图像之外,我们还能做什么呢?非常多!在本节中,我们将演示一些很酷的 pipelines,让您体验 Stable Diffusion 可以用于的其他一些任务。其中一些需要下载新模型,因此如果您时间紧迫,您可以浏览本节,只需查看现有的输出,而不是下载和运行所有模型。

Img2Img

在到目前为止的示例中,我们通过从随机潜在表示开始并应用完整的 diffusion 采样循环,完全从头开始生成图像。但是我们不必从头开始。Img2Img pipeline 首先将现有图像编码为一组潜在表示,然后向潜在表示添加一些噪声,并将其用作起点。添加的噪声量和应用的去噪步数决定了 img2img 过程的“强度”。添加少量噪声(低强度)只会导致非常小的变化,而添加最大量的噪声并运行完整的去噪过程将生成一张图像,除了整体结构上的一些相似之处外,几乎与输入图像无关。

为了更好地理解 img2img 过程,让我们手动实现 pipeline。这种方法将有助于阐明如何编码潜在表示、添加噪声以及应用 diffusion。在手动构建 pipeline 之后,我们将了解 diffusers 库如何提供随时可用的 StableDiffusionImg2ImgPipeline,以实现简单性和效率。

以下是如何手动实现 img2img pipeline 的方法

DIY Img2Img 循环

import numpy as np

# Encode init_image
init_image_tensor = torch.from_numpy(np.array(init_image).transpose(2, 0, 1)).float() / 255.0  # 0~255 => 0~1
init_image_tensor = 2.0 * init_image_tensor - 1.0  # 0~1 => -1~1
init_image_tensor = init_image_tensor.unsqueeze(0).to(device)  # add batch dim.

with torch.no_grad():
    init_image_latents = pipe.vae.encode(init_image_tensor).latent_dist.sample() * pipe.vae.config.scaling_factor
>>> guidance_scale = 7.5  # @param
>>> num_inference_steps = 30  # @param
>>> strength = 0.6
>>> prompt = "An oil painting of a man on a bench"  # @param

>>> # Encode the prompt
>>> text_embeddings = pipe._encode_prompt(prompt, device, 1, True, "")

>>> # Prepare the scheduler
>>> pipe.scheduler.set_timesteps(num_inference_steps, device=device)

>>> # Prepare latent variables
>>> # We don't use all timesteps in the noise scheduler.
>>> # Calculate a subset of timesteps based on `strength` to apply to the initial image.
>>> init_timestep = min(int(num_inference_steps * strength), num_inference_steps)
>>> t_start = max(num_inference_steps - init_timestep, 0)
>>> timesteps = pipe.scheduler.timesteps[t_start:]
>>> # The first timestep of the new timesteps will be the starting point for adding noise to the initial image.
>>> latent_timestep = timesteps[:1]

>>> # Add noise to init_image_latents at the noise level specified by latent_timestep.
>>> noise = torch.randn((1, 4, 64, 64), device=device, generator=torch.Generator(device=device).manual_seed(42))
>>> latents = pipe.scheduler.add_noise(init_image_latents, noise, latent_timestep)

>>> # Loop through the sampling timesteps
>>> for i, t in enumerate(timesteps):

...     # Expand the latents if we are doing classifier free guidance
...     latent_model_input = torch.cat([latents] * 2)

...     # Apply any scaling required by the scheduler
...     latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)

...     # Predict the noise residual with the UNet
...     with torch.no_grad():
...         noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample

...     # Perform guidance
...     noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
...     noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)

...     # Compute the previous noisy sample x_t -> x_t-1
...     latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample

>>> # Decode latents
>>> latents_norm = latents / pipe.vae.config.scaling_factor

>>> with torch.no_grad():
...     result_image = pipe.vae.decode(latents_norm).sample

>>> result_image = (result_image / 2 + 0.5).clamp(0, 1).squeeze()
>>> result_image = (result_image.permute(1, 2, 0) * 255).to(torch.uint8).cpu().numpy()
>>> result_image = Image.fromarray(result_image)

>>> # View the result
>>> fig, axs = plt.subplots(1, 2, figsize=(12, 5))
>>> axs[0].imshow(init_image)
>>> axs[0].set_title("Input Image")
>>> axs[1].imshow(result_image)
>>> axs[1].set_title("Result")

现在我们已经手动实现了 img2img 过程,让我们看看如何使用 diffusers 库提供的 StableDiffusionImg2ImgPipeline 更有效地实现相同的结果。

此 pipeline 不需要特殊的模型,因此只要模型 ID 与我们上面的文本到图像示例相同,就不需要下载新文件。

Img2Img Pipeline

# Loading an Img2Img pipeline
model_id = "stabilityai/stable-diffusion-2-1-base"
img2img_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(model_id).to(device)

在 “设置” 部分,我们加载了一个示例 init_image 用于此演示,但如果您愿意,可以将其替换为您自己的图像。以下是 pipeline 的实际应用

>>> # Apply Img2Img
>>> result_image = img2img_pipe(
...     prompt="An oil painting of a man on a bench",
...     image=init_image,  # The starting image
...     strength=0.6,  # 0 for no change, 1.0 for max strength
... ).images[0]

>>> # View the result
>>> fig, axs = plt.subplots(1, 2, figsize=(12, 5))
>>> axs[0].imshow(init_image)
>>> axs[0].set_title("Input Image")
>>> axs[1].imshow(result_image)
>>> axs[1].set_title("Result")

练习: 尝试使用此 pipeline。尝试您自己的图像,或尝试不同的强度和 prompts。您可以像文本到图像 pipeline 一样使用许多相同的参数,因此请随意尝试不同的尺寸、步数等。

In-Painting(图像修复)

如果我们想保持输入图像的某些部分不变,但在其他部分生成新的内容,该怎么办?这称为 “inpainting”(图像修复)。虽然可以使用与之前演示相同的模型(通过 StableDiffusionInpaintPipelineLegacy)来完成,但我们可以通过使用 Stable Diffusion 的自定义微调版本来获得更好的结果,该版本将蒙版图像和蒙版本身作为额外的条件输入。蒙版图像应与输入图像的形状相同,白色区域表示要替换的区域,黑色区域表示要保持不变的区域。

为了更深入地了解 inpainting 过程,让我们手动实现 StableDiffusionInpaintPipelineLegacy 背后的逻辑。这种方法将阐明 inpainting 在较低级别的工作原理,并深入了解 Stable Diffusion 如何处理输入。完成此操作后,我们将探索微调的 pipeline 以进行比较。以下是如何手动实现 inpainting pipeline 并将其应用于 “设置” 部分中加载的示例图像和蒙版的方法

inpainting from_scratch

DIY Inpainting 循环

>>> # Resize mask image
>>> mask_image_latent_size = mask_image.resize((64, 64))
>>> mask_image_latent_size = torch.tensor((np.array(mask_image_latent_size)[..., 0] > 5).astype(np.float32))
>>> plt.imshow(mask_image_latent_size.numpy(), cmap="gray")

>>> mask_image_latent_size = mask_image_latent_size.to(device)
>>> mask_image_latent_size.shape

再次编写去噪循环。

>>> guidance_scale = 8  # @param
>>> num_inference_steps = 30  # @param
>>> prompt = "A small robot, high resolution, sitting on a park bench"
>>> negative_prompt = "zoomed in, blurry, oversaturated, warped"
>>> generator = torch.Generator(device=device).manual_seed(42)

>>> # Encode the prompt
>>> text_embeddings = pipe._encode_prompt(prompt, device, 1, True, negative_prompt)

>>> # Create our random starting point
>>> latents = torch.randn((1, 4, 64, 64), device=device, generator=generator)
>>> latents *= pipe.scheduler.init_noise_sigma

>>> # Prepare the scheduler
>>> pipe.scheduler.set_timesteps(num_inference_steps, device=device)

>>> for i, t in enumerate(pipe.scheduler.timesteps):
...     # Expand the latents if we are doing classifier free guidance
...     latent_model_input = torch.cat([latents] * 2)

...     # Apply any scaling required by the scheduler
...     latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)

...     # Predict the noise residual with the UNet
...     with torch.no_grad():
...         noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample

...     # Perform guidance
...     noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
...     noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)

...     # Compute the previous noisy sample x_t -> x_t-1
...     latents = pipe.scheduler.step(noise_pred, t, latents, return_dict=False)[0]

...     # Perform inpainting to fill in the masked areas
...     if i < len(pipe.scheduler.timesteps) - 1:
...         # Add noise to the original image's latent at the previous timestep t-1
...         noise = torch.randn(init_image_latents.shape, generator=generator, device=device, dtype=torch.float32)
...         background = pipe.scheduler.add_noise(
...             init_image_latents, noise, torch.tensor([pipe.scheduler.timesteps[i + 1]])
...         )

...         latents = latents * mask_image_latent_size  # white in the areas
...         background = background * (1 - mask_image_latent_size)  # black in the areas

...         # Combine the generated and original image latents based on the mask
...         latents += background

>>> # Decode latents
>>> latents_norm = latents / pipe.vae.config.scaling_factor

>>> with torch.no_grad():
...     inpainted_image = pipe.vae.decode(latents_norm).sample

>>> inpainted_image = (inpainted_image / 2 + 0.5).clamp(0, 1).squeeze()
>>> inpainted_image = (inpainted_image.permute(1, 2, 0) * 255).to(torch.uint8).cpu().numpy()
>>> inpainted_image = Image.fromarray(inpainted_image)

>>> inpainted_image

Inpainting Pipeline(图像修复 Pipeline)

现在我们已经手动实现了 inpainting 逻辑,让我们看看如何使用专门为 inpainting 任务设计的微调 pipeline。以下是如何加载此类 pipeline 并将其应用于 “设置” 部分中加载的示例图像和蒙版

inpainting from_scratch

# Load the inpainting pipeline (requires a suitable inpainting model)
# pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/stable-diffusion-inpainting")

# "runwayml/stable-diffusion-inpainting" is no longer available.
# Therefore, we are using the "stabilityai/stable-diffusion-2-inpainting" model instead.
pipe = StableDiffusionInpaintPipeline.from_pretrained("stabilityai/stable-diffusion-2-inpainting")
pipe = pipe.to(device)
>>> # Inpaint with a prompt for what we want the result to look like
>>> prompt = "A small robot, high resolution, sitting on a park bench"
>>> image = pipe(prompt=prompt, image=init_image, mask_image=mask_image).images[0]

>>> # View the result
>>> fig, axs = plt.subplots(1, 3, figsize=(16, 5))
>>> axs[0].imshow(init_image)
>>> axs[0].set_title("Input Image")
>>> axs[1].imshow(mask_image)
>>> axs[1].set_title("Mask")
>>> axs[2].imshow(image)
>>> axs[2].set_title("Result")

当与另一个模型结合以自动生成蒙版时,这尤其强大。例如,此演示空间 使用名为 CLIPSeg 的模型,根据文本描述蒙版要替换的对象。

旁注:管理你的模型缓存

探索不同的管道和模型变体可能会占用你的磁盘空间。你可以通过以下方式查看当前已下载的模型:

>>> !ls ~/.cache/huggingface/hub/ # List the contents of the cache directory
models--CompVis--stable-diffusion-v1-4
models--ddpm-bedroom-256
models--google--ddpm-bedroom-256
models--google--ddpm-celebahq-256
models--runwayml--stable-diffusion-inpainting
models--stabilityai--stable-diffusion-2-1-base

请查看关于缓存的文档,了解如何有效地查看和管理你的缓存。

Depth2Image

depth to image examples 输入图像、深度图像和生成的示例(图像来源:StabilityAI)

Img2Img 很棒,但有时我们想创建一个新图像,它具有原始图像的构图,但颜色或纹理完全不同。找到一个 Img2Img 强度来保留我们想要的布局,同时又不保留输入颜色,可能会很困难。

是时候使用另一个微调模型了!这个模型在生成时将深度信息作为额外的条件。该管道使用深度估计模型来创建深度图,然后在生成图像时将其馈送到微调的 UNet,以(希望)在填充全新内容的同时保留初始图像的深度和结构。

# Load the Depth2Img pipeline (requires a suitable model)
pipe = StableDiffusionDepth2ImgPipeline.from_pretrained("stabilityai/stable-diffusion-2-depth")
pipe = pipe.to(device)
>>> # Inpaint with a prompt for what we want the result to look like
>>> prompt = "An oil painting of a man on a bench"
>>> image = pipe(prompt=prompt, image=init_image).images[0]

>>> # View the result
>>> fig, axs = plt.subplots(1, 2, figsize=(16, 5))
>>> axs[0].imshow(init_image)
>>> axs[0].set_title("Input Image")
>>> axs[1].imshow(image)
>>> axs[1].set_title("Result")

请注意输出与 img2img 示例的比较 - 这里颜色变化更多,但整体结构仍然忠实于原始图像。在这种情况下,这并不理想,因为为了匹配狗的形状,这个男人被赋予了一些非常奇怪的解剖结构,但在某些情况下,这非常有用。有关此方法“杀手级应用”的示例,请查看这条推文,其中展示了深度模型被用于纹理化 3D 场景!

下一步?

希望这能让你体验到 Stable Diffusion 可以做的许多事情!一旦你厌倦了在本笔记本中玩示例,请查看 DreamBooth 黑客马拉松 笔记本,了解如何微调你自己的 Stable Diffusion 版本,该版本可以与我们在这里看到的文本到图像或 img2img 管道一起使用。

如果你有兴趣更深入地了解不同组件的工作原理,请查看 Stable Diffusion 深度探索 笔记本,其中更详细地介绍了我们可以做的一些额外技巧。

请务必与我们和社区分享你的作品!

< > 在 GitHub 上更新