外拓繪圖 I - Controlnet 版本
我所知道的至少有三種外拓繪圖方法,每種方法都有不同的變體和步驟。這是第一種使用 ControlNet 的方法,你可以在這裡閱讀其他方法:
使用 ControlNet 進行外拓繪圖需要使用蒙版,因此這種方法只適用於你可以在想要擴充套件的區域周圍繪製白色蒙版的情況。使用此方法無需事先準備區域,但它受到影像大小的限制,只能是你的 VRAM 允許的大小。
1.- 原始影像
在這種情況下,我將使用 Laidawang 在此評論中提供的狼影像。
2.- 外拓繪圖
起始提示詞是 `a wolf playing basketball`,我將使用 Juggernaut V9 模型。
destitech 訓練了一個用於影像修復的 SDXL ControlNet 模型,名為 controlnet-inpaint-dreamer-sdxl。這是一個早期 Alpha 版本,但我認為它在大多數情況下效果很好。
controlnet = ControlNetModel.from_pretrained(
"destitech/controlnet-inpaint-dreamer-sdxl", torch_dtype=torch.float16, variant="fp16"
)
這個 ControlNet 模型非常容易使用,你只需要將想要替換的部分塗成白色,所以在這種情況下,我將把影像的透明部分塗成白色。
為了將影像的 alpha 通道塗成白色,我使用這段程式碼
response = requests.get("https://huggingface.co/datasets/OzzyGT/testing-resources/resolve/main/outpainting/313891870-adb6dc80-2e9e-420c-bac3-f93e6de8d06b.png?download=true")
control_image = Image.open(BytesIO(response.content))
new_controlnet_image = Image.new("RGBA", control_image.size, "WHITE")
new_controlnet_image.alpha_composite(control_image)
這張圖片的大小是 720x720 畫素,SDXL 最好使用 1024x1024 的圖片,所以生成的圖片會被放大,你可以使用更小的圖片,但可能會得到質量更低的圖片。
條件尺度會影響原始影像的保留程度,由於這是外拓繪圖,使用較高的值是安全的;對於內繪圖和複雜影像,最好使用 0.5 左右的較低值。
pipeline = StableDiffusionXLControlNetPipeline.from_pretrained(
"RunDiffusion/Juggernaut-XL-v9",
torch_dtype=torch.float16,
variant="fp16",
controlnet=controlnet,
).to("cuda")
image = pipeline(
prompt=prompt,
negative_prompt=negative_prompt,
height=1024,
width=1024,
guidance_scale=6.5,
num_inference_steps=25,
generator=generator,
image=new_controlnet_image,
controlnet_conditioning_scale=0.9,
control_guidance_end=0.9,
).images[0]
使用相同的種子,我得到了這些結果
我總是傾向於讓模型有一些自由度,這樣它就可以調整微小的細節,使影像更連貫,所以在這種情況下,我將使用 `0.9`。
3.- 使用 IP Adapter 更好地外拓繪圖
目前我認為我們已經達到了其他解決方案的水平,但是假設我們希望狼看起來就像原始影像一樣,為此,我想給模型更多關於狼的上下文以及我希望它在哪裡出現,所以我將為此使用 IP Adapter。
有一個對我很有用的小技巧,那就是我使用我想要的基礎生成影像,並在其上繪製狼的蒙版,然後將其用作 IP Adapter 的注意力蒙版。
我生成蒙版的過程是這樣的
對我來說,用 GIMP 這樣的工具做這個花不了我一分鐘。
我正在使用 IP Plus Adapter,比例為 0.4,有了這些設定,我們現在得到了這些結果。
我猜測這種方法之所以有效,是因為我們繪製了狼的形狀併為其提供了狼的輸入影像,模型會嘗試保持形狀、位置和連貫性。簡而言之,我們為模型提供了更多的上下文,使其能夠生成最終影像,現在它看起來更像原始影像了。
例如,如果我們不使用帶有這個 IP Adapter 的蒙版,結果會是這樣的
4.- 更好的提示詞
現在影像看起來很不錯了,但我總是想要更多,所以我們用提示詞來改進它。
我將把提示詞改為這樣:
“一隻狼打籃球的高質量照片,高度細節,專業,戲劇性的環境光,電影般的,動態背景,焦點”
同時,我將給 ControlNet 更多的自由度,設定 `control_guidance_end=0.9`,這樣它就可以在不受限制的情況下完成細節。
5.- 影像到影像的轉換
最後,如果你曾經做過影像或影片合成,你會知道應用濾鏡到整個合成以統一最終外觀是常見的做法。對於穩定擴散也是如此,它有時能更好地隱藏接縫,如果存在的話。為此,我將對整個影像進行最後一次影像到影像的轉換。
Diffusers 允許僅用一行程式碼更改管道,同時保留載入的模型,這正是我們這裡需要的,我們只需要解除安裝 ControlNet 模型。
pipeline_img2img = AutoPipelineForImage2Image.from_pipe(pipeline, controlnet=None)
VAE 解碼是一個有損過程,所以每次我們編碼或解碼時,我們都會丟失細節並降低影像質量,為了防止這種情況,我們需要儘可能多地留在潛在空間中。
如果你將 `output_type="latent"` 傳遞給管道,Diffusers 就允許這樣做。然後我們將潛在向量輸入到影像到影像的管道中,但在此之前,我還想給它一個更電影化的外觀,所以我將再次更改提示詞。
提示詞 = "一隻狼打籃球的電影劇照,高度細節,高預算好萊塢電影,寬銀幕,史詩,華麗,膠片顆粒"
這應該只是一個快速通道,所以我將步數設定為 30,強度設定為 0.2。
這些是最終結果
在我看來,這些影像與使用其他 UI 完成的外拓繪圖一樣好甚至更好,希望它們能幫助人們更好地理解 Diffusers 的功能。
6.- 使用 IP Adapter 的外拓繪圖技巧
作為獎勵,這裡有一個巧妙的小技巧。之前我使用帶有蒙版的 IP Adapter 為生成提供更多的初始影像。然而,這也可以不使用蒙版。這樣做的效果是為模型提供了更多的上下文,使其能夠更好地猜測影像的其餘部分,例如僅使用提示詞“高質量”。
所以,如果我像之前那樣做,但除了 IP Adapter 蒙版之外,最終結果是這樣的:
別問我埃菲爾鐵塔為什麼在那裡 ^^
完整程式碼
import random
from io import BytesIO
import requests
import torch
from PIL import Image
from diffusers import (
AutoPipelineForImage2Image,
ControlNetModel,
DPMSolverMultistepScheduler,
StableDiffusionXLControlNetPipeline,
)
from diffusers.image_processor import IPAdapterMaskProcessor
from diffusers.utils import load_image, logging
from diffusers.utils.logging import set_verbosity
set_verbosity(logging.ERROR) # to not show cross_attention_kwargs..by AttnProcessor2_0 warnings
controlnet = ControlNetModel.from_pretrained(
"destitech/controlnet-inpaint-dreamer-sdxl", torch_dtype=torch.float16, variant="fp16"
)
pipeline = StableDiffusionXLControlNetPipeline.from_pretrained(
"RunDiffusion/Juggernaut-XL-v9",
torch_dtype=torch.float16,
variant="fp16",
controlnet=controlnet,
).to("cuda")
pipeline.scheduler = DPMSolverMultistepScheduler.from_config(pipeline.scheduler.config)
pipeline.scheduler.config.use_karras_sigmas = True
pipeline.load_ip_adapter(
"h94/IP-Adapter",
subfolder="sdxl_models",
weight_name="ip-adapter-plus_sdxl_vit-h.safetensors",
image_encoder_folder="models/image_encoder",
)
pipeline.set_ip_adapter_scale(0.4)
ip_wolf_image = load_image(
"https://huggingface.co/datasets/OzzyGT/testing-resources/resolve/main/outpainting/ip_wolf_source.png?download=true"
)
ip_mask = load_image(
"https://huggingface.co/datasets/OzzyGT/testing-resources/resolve/main/outpainting/wolf_position_mask.png?download=true"
)
processor = IPAdapterMaskProcessor()
ip_masks = processor.preprocess(ip_mask, height=1024, width=1024)
response = requests.get(
"https://huggingface.co/datasets/OzzyGT/testing-resources/resolve/main/outpainting/313891870-adb6dc80-2e9e-420c-bac3-f93e6de8d06b.png?download=true"
)
control_image = Image.open(BytesIO(response.content))
new_controlnet_image = Image.new("RGBA", control_image.size, "WHITE")
new_controlnet_image.alpha_composite(control_image)
prompt = "high quality photo of a wolf playing basketball, highly detailed, professional, dramatic ambient light, cinematic, dynamic background, focus"
negative_prompt = ""
seed = random.randint(0, 2**32 - 1)
generator = torch.Generator(device="cpu").manual_seed(seed)
latents = pipeline(
prompt=prompt,
negative_prompt=negative_prompt,
height=1024,
width=1024,
guidance_scale=6.5,
num_inference_steps=25,
generator=generator,
image=new_controlnet_image,
controlnet_conditioning_scale=0.9,
control_guidance_end=0.9,
ip_adapter_image=ip_wolf_image,
cross_attention_kwargs={"ip_adapter_masks": ip_masks},
output_type="latent",
).images[0]
pipeline_img2img = AutoPipelineForImage2Image.from_pipe(pipeline, controlnet=None)
prompt = "cinematic film still of a wolf playing basketball, highly detailed, high budget hollywood movie, cinemascope, epic, gorgeous, film grain"
image = pipeline_img2img(
prompt=prompt,
negative_prompt=negative_prompt,
guidance_scale=3.0,
num_inference_steps=30,
generator=generator,
image=latents,
strength=0.2,
ip_adapter_image=ip_wolf_image,
cross_attention_kwargs={"ip_adapter_masks": ip_masks},
).images[0]
image.save("result.png")