使用扩散器为照片重新着色

社区文章 发布于 2024 年 10 月 9 日

扩散模型的另一个实际用例是尝试修复旧照片,这是实现这一目标的第一步。

这并不是什么新鲜事,已经有一些模型存在,但没有什么能比得上在本地使用一个模型的感受。我还发现,大多数时候它们效果不好,而且你无法控制最终图像,这种方法的优点在于你可以根据自己的喜好改变最终结果,例如你可以提示特定颜色,或者提供带有你想要颜色的源图像。

和我的所有指南一样,这只是一个起点,而不是最终产品,之后还有很大的改进空间,但这应该足以达到图像重新着色 SOTA 的水平。

对于只想玩和测试这个功能的人,我创建了一个概念验证空间,你可以在其中使用自己的照片测试这些技术。为了这个空间和这份指南,我创建了一个自定义管道,使其比普通管道更容易使用和更快,但这使得它几乎只适用于这个用例。

在本指南中,我将使用两张我重新缩放和裁剪为 1024px 方形图像的公共领域图像,以简化代码。

多萝西娅·兰格的《移民母亲》 阿瑟·罗斯坦的《罗克维尔主街,印第安纳》
image/jpeg image/png

这项技术的关键点是

所有这些结合在一起,才能让我们得到一个非常好的结果,现在我将一步一步地解释为什么所有这些都是必需的。

如果我们只使用模型和重新着色 ControlNet,我们会得到这些结果

image/png image/png image/png
image/png image/png image/png

我们可以清楚地看到模型不知道如何重新着色图像,对于第一张图像,它没有检测到所有的人,对于第二张图像,它丢失了大量的细节,尤其是文本。所以我们需要开始解决这个问题。

我知道结果过度饱和,这没关系,我们需要它们这样,你稍后就会明白为什么。

使用 IP 适配器

我将使用 IP 适配器,唯一的原因是向模型提供图像信息,这几乎与提供提示的效果相同,有时甚至更好。由于我们有灰度图像,我们需要禁用提供颜色信息的块。

pipe.load_ip_adapter(
    "h94/IP-Adapter",
    subfolder="sdxl_models",
    weight_name="ip-adapter_sdxl_vit-h.safetensors",
    image_encoder_folder="models/image_encoder",
)

scale = {
    "up": {"block_0": [1.0, 0.0, 1.0]},
}
pipe.set_ip_adapter_scale(scale)
image/png image/png image/png
image/png image/png image/png

我们得到了图像中更高的一致性,仍然有一些奇怪的错误,比如彩绘的手,但总体来说更好,记住这个解决方案不是一击即中,你必须生成几次才能得到预期的结果。

在第二步后禁用 CFG

我在上一份指南中做过这个,但没有写出来,所以我将在本指南中解释这部分。

为了加速生成,我们主要有两种不降低质量的选择,第一种是编译模型,第二种是尽可能早地禁用 CFG。禁用 CFG 实际上可以使推理速度加倍,但由于我们是在第二步之后禁用它,所以不是双倍,但它能达到与编译模型相同的速度,而无需慢速的首次推理及其带来的限制。

在我看来,在第二步之后禁用 CFG 没有损失,我没有看到质量下降,但它会使生成结果与全程使用 CFG 的情况不同。

你可以在这篇论文中阅读更多关于禁用 CFG 的影响,以及同一页面上图书馆机器人推荐的论文。

我在自定义管道中执行此操作,但也可以通过回调函数完成。

if i == 2:
    prompt_embeds = prompt_embeds[-1:]
    add_text_embeds = add_text_embeds[-1:]
    add_time_ids = add_time_ids[-1:]

    added_cond_kwargs = {
        "text_embeds": add_text_embeds,
        "time_ids": add_time_ids,
    }

    controlnet_prompt_embeds = prompt_embeds
    controlnet_added_cond_kwargs = added_cond_kwargs

    image = [single_image[-1:] for single_image in image]
    self._guidance_scale = 0.0
image/png image/png image/png
image/png image/png image/png

使用第二个 ControlNet

添加一个 ControlNet 有助于在物体之间划清界限,保留细节,它还有助于模型更好地理解图像。我还没有用普通的 ControlNet 测试过这个,但它与 ControlNet Union 配合得非常好。我们将使用线稿预处理器,因为它比其他预处理器添加更精细的线条,为此我将使用我们正在构建的一个新库,用于图像和视频的预处理器和更多实用工具,如图像放大器、自动遮罩和帧插值。

线稿预处理器对此效果很好,但我们需要降低分辨率,使线条更粗,这主要是因为 ControlNet 是用低分辨率图像训练的。

from image_gen_aux import LineArtPreprocessor

lineart_preprocessor = LineArtPreprocessor.from_pretrained("OzzyGT/lineart").to("cuda")
lineart_image = lineart_preprocessor(image, resolution_scale=0.7)[0]
分辨率比例=1.0 分辨率比例=0.7
image/png image/png

对于这个ControlNet,我还想给模型一些自由度,所以我们将把`controlnet_conditioning_scale`设置为`0.5`,并将`control_guidance_end`设置为`0.9`

完成这一步后,我们得到以下结果

image/png image/png image/png
image/png image/png image/png

现在我们几乎总是能得到一致的结果,并且颜色正确。如果你将这些结果与其他重新着色模型进行比较,我认为我们处于同一水平,我个人认为这些结果是我所见过的最好的,甚至是付费模型也无法比拟。

然而,一如既往,我对结果并不完全满意,我可以看到我们丢失了一些细节,特别是在第二张图像的文本和微小细节上,而且原始图像也有一些细小的颗粒。

因此,作为最后一步,也是我认为最重要的一步,以获得真实的观感,也是我说我们需要过度饱和结果的原因,是将原始图像与生成的图像混合。合并图像的比例非常重要,我们将丢失大量颜色信息,因此越饱和越好。

我发现能保留原始细节的最佳比例是将生成的图像不透明度设置为0.2进行合并,但当我们这样做时,我们会发现颜色所剩无几,这就是为什么拥有过度饱和图像很重要的原因,因为我们仍然有颜色,我们将使用Pillow进一步增强颜色。

以下是普通合并、饱和度2.0和3.0的示例

1.0 2.0 3.0
image/png image/png image/png

图片的最终结果(每个图片3个样本,不经过挑选)

image/png image/png image/png
image/png image/png image/png

如您所见,我们得到了相当一致的结果,但可能需要尝试几次才能获得我们想要的结果。这就是它好的原因,您可以根据需要多次在本地或 HuggingFace 空间中运行它,而且我把它做得很快。

后续改进思路

  • 对不同类型的图像使用不同的模型。
  • 调整 IP 适配器和 ControlNet 参数,唯一可能不应更改的是 ReColor 参数。
  • 添加提示特定内容的功能,我在这里没有这样做,但是您可以将当前的提示设置为模板,让用户在其上指定一些内容,例如棕发、绿色毛衣、蓝色汽车等,这将允许用户更改或获取特定部分的特定颜色。

希望这能为未来酷炫的应用提供更多想法。

这是完整的代码

import torch
from diffusers import (
    AutoencoderKL,
    ControlNetModel,
    StableDiffusionXLControlNetPipeline,
    TCDScheduler,
)
from diffusers.utils import load_image
from image_gen_aux import LineArtPreprocessor
from PIL import Image, ImageEnhance

from controlnet_union import ControlNetModel_Union

controlnet = [
    ControlNetModel.from_pretrained(
        "OzzyGT/ControlNet-recolorXL", torch_dtype=torch.float16, variant="fp16"
    ),
    ControlNetModel_Union.from_pretrained(
        "OzzyGT/controlnet-union-promax-sdxl-1.0",
        torch_dtype=torch.float16,
        variant="fp16",
    ),
]

vae = AutoencoderKL.from_pretrained(
    "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
).to("cuda")

pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
    "recoilme/ColorfulXL-Lightning",
    custom_pipeline="OzzyGT/pipeline_sdxl_recolor",
    torch_dtype=torch.float16,
    variant="fp16",
    controlnet=controlnet,
    vae=vae,
).to("cuda")
pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)

pipe.load_ip_adapter(
    "h94/IP-Adapter",
    subfolder="sdxl_models",
    weight_name="ip-adapter_sdxl_vit-h.safetensors",
    image_encoder_folder="models/image_encoder",
)

scale = {
    "up": {"block_0": [1.0, 0.0, 1.0]},
}
pipe.set_ip_adapter_scale(scale)

pipe.enable_model_cpu_offload()

source_image = load_image(
    "https://huggingface.co/datasets/OzzyGT/testing-resources/resolve/main/recolor/migrant_mother.jpg?download=true"
)

lineart_preprocessor = LineArtPreprocessor.from_pretrained("OzzyGT/lineart").to("cuda")
lineart_image = lineart_preprocessor(source_image, resolution_scale=0.7)[0]

prompt = "high quality color photo, sharp, detailed, 4k, colorized, remastered"
negative_prompt = "blurry, low resolution, bad quality, pixelated, black and white, b&w, grayscale, monochrome, sepia"

(
    prompt_embeds,
    negative_prompt_embeds,
    pooled_prompt_embeds,
    negative_pooled_prompt_embeds,
) = pipe.encode_prompt(prompt, negative_prompt, "cuda", True)


image = pipe(
    prompt_embeds=prompt_embeds,
    negative_prompt_embeds=negative_prompt_embeds,
    pooled_prompt_embeds=pooled_prompt_embeds,
    negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
    image=[source_image, lineart_image],
    ip_adapter_image=source_image,
    num_inference_steps=8,
    guidance_scale=2.0,
    controlnet_conditioning_scale=[1.0, 0.5],
    control_guidance_end=[1.0, 0.9],
).images

if source_image.mode != "RGBA":
    source_image = source_image.convert("RGBA")

if image.mode != "RGBA":
    image = image.convert("RGBA")

enhancer = ImageEnhance.Color(image)
image = enhancer.enhance(4.0)

alpha = image.split()[3]
alpha = alpha.point(lambda p: p * 0.20)
image.putalpha(alpha)

merged_image = Image.alpha_composite(source_image, image)

merged_image.save("recolored.png")

社区

你是从哪里安装的controlnet_union?在pypi上找不到

·
文章作者

这是一个有点旧的指南,controlnet union现在已经集成到diffusers中,如果你有最新版本的diffusers,你不需要做任何特殊的事情。

import torch

from diffusers import ControlNetUnionModel


controlnet_model = ControlNetUnionModel.from_pretrained(
    "OzzyGT/controlnet-union-promax-sdxl-1.0",
    torch_dtype=torch.float16,
    variant="fp16",
)
文章作者
此评论已被隐藏

注册登录 发表评论