使用扩散器为照片重新着色
扩散模型的另一个实际用例是尝试修复旧照片,这是实现这一目标的第一步。
这并不是什么新鲜事,已经有一些模型存在,但没有什么能比得上在本地使用一个模型的感受。我还发现,大多数时候它们效果不好,而且你无法控制最终图像,这种方法的优点在于你可以根据自己的喜好改变最终结果,例如你可以提示特定颜色,或者提供带有你想要颜色的源图像。
和我的所有指南一样,这只是一个起点,而不是最终产品,之后还有很大的改进空间,但这应该足以达到图像重新着色 SOTA 的水平。
对于只想玩和测试这个功能的人,我创建了一个概念验证空间,你可以在其中使用自己的照片测试这些技术。为了这个空间和这份指南,我创建了一个自定义管道,使其比普通管道更容易使用和更快,但这使得它几乎只适用于这个用例。
在本指南中,我将使用两张我重新缩放和裁剪为 1024px 方形图像的公共领域图像,以简化代码。
这项技术的关键点是
- 使用强调颜色并具有光照版本的模型,幸运的是我们有一个名为 ColorfulXL Lighting 的模型。
- 一个专门用于图像重新着色的 ControlNet,我获得最佳结果的是 kataragi/ControlNet-recolorXL,它似乎是针对动漫进行训练的,但对真实照片效果非常好。
- 一个有助于保留和检测微小细节的 ControlNet,我将使用 https://huggingface.co/xinsir/controlnet-union-sdxl-1.0 和线稿预处理器。
- IP 适配器
所有这些结合在一起,才能让我们得到一个非常好的结果,现在我将一步一步地解释为什么所有这些都是必需的。
如果我们只使用模型和重新着色 ControlNet,我们会得到这些结果
我们可以清楚地看到模型不知道如何重新着色图像,对于第一张图像,它没有检测到所有的人,对于第二张图像,它丢失了大量的细节,尤其是文本。所以我们需要开始解决这个问题。
我知道结果过度饱和,这没关系,我们需要它们这样,你稍后就会明白为什么。
使用 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)
我们得到了图像中更高的一致性,仍然有一些奇怪的错误,比如彩绘的手,但总体来说更好,记住这个解决方案不是一击即中,你必须生成几次才能得到预期的结果。
在第二步后禁用 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
使用第二个 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]
对于这个ControlNet,我还想给模型一些自由度,所以我们将把`controlnet_conditioning_scale`设置为`0.5`,并将`control_guidance_end`设置为`0.9`
完成这一步后,我们得到以下结果
现在我们几乎总是能得到一致的结果,并且颜色正确。如果你将这些结果与其他重新着色模型进行比较,我认为我们处于同一水平,我个人认为这些结果是我所见过的最好的,甚至是付费模型也无法比拟。
然而,一如既往,我对结果并不完全满意,我可以看到我们丢失了一些细节,特别是在第二张图像的文本和微小细节上,而且原始图像也有一些细小的颗粒。
因此,作为最后一步,也是我认为最重要的一步,以获得真实的观感,也是我说我们需要过度饱和结果的原因,是将原始图像与生成的图像混合。合并图像的比例非常重要,我们将丢失大量颜色信息,因此越饱和越好。
我发现能保留原始细节的最佳比例是将生成的图像不透明度设置为0.2进行合并,但当我们这样做时,我们会发现颜色所剩无几,这就是为什么拥有过度饱和图像很重要的原因,因为我们仍然有颜色,我们将使用Pillow进一步增强颜色。
以下是普通合并、饱和度2.0和3.0的示例
图片的最终结果(每个图片3个样本,不经过挑选)
如您所见,我们得到了相当一致的结果,但可能需要尝试几次才能获得我们想要的结果。这就是它好的原因,您可以根据需要多次在本地或 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")