Diffusers documentation
Outpainting
Outpainting
L’outpainting étend une image au-delà de ses limites d’origine, vous permettant d’ajouter, de remplacer ou de modifier des éléments visuels tout en préservant l’image initiale. Tout comme pour l’inpainting, l’objectif est de combler la zone blanche (ici, la zone située à l’extérieur de l’image d’origine) avec de nouveaux éléments visuels, tout en conservant l’image d’origine (représentée par un masque de pixels noirs). Il existe plusieurs méthodes pour réaliser un outpainting, par exemple à l’aide d’un ControlNet ou de la Differential Diffusion.
Ce guide vous montre comment effectuer un outpainting avec un modèle d'inpainting, ControlNet et un estimateur ZoeDepth.
Avant de commencer, assurez-vous d'avoir installé la bibliothèque controlnet_aux afin de pouvoir utiliser l'estimateur ZoeDepth.
!pip install -q controlnet_aux
Préparation des images
Commencez par choisir une image à outpainter et supprimez son arrière-plan à l'aide d'un Space comme BRIA-RMBG-1.4.
Par exemple, supprimez l'arrière-plan de cette image d'une paire de chaussures.
Les modèles Stable Diffusion XL (SDXL) fonctionnent de manière optimale avec des images de 1024x1024, mais vous pouvez redimensionner l’image à n’importe quelle taille, à condition que votre matériel dispose de mémoire suffisante pour le supporter. Le fond transparent de l’image doit également être remplacé par un fond blanc. Créez une fonction (comme celle ci-dessous) qui met à l’échelle et colle l’image sur un fond blanc.
import random
import requests
import torch
from controlnet_aux import ZoeDetector
from PIL import Image, ImageOps
from diffusers import (
AutoencoderKL,
ControlNetModel,
StableDiffusionXLControlNetPipeline,
StableDiffusionXLInpaintPipeline,
)
def scale_and_paste(original_image):
aspect_ratio = original_image.width / original_image.height
if original_image.width > original_image.height:
new_width = 1024
new_height = round(new_width / aspect_ratio)
else:
new_height = 1024
new_width = round(new_height * aspect_ratio)
resized_original = original_image.resize((new_width, new_height), Image.LANCZOS)
white_background = Image.new("RGBA", (1024, 1024), "white")
x = (1024 - new_width) // 2
y = (1024 - new_height) // 2
white_background.paste(resized_original, (x, y), resized_original)
return resized_original, white_background
original_image = Image.open(
requests.get(
"https://huggingface.co/datasets/stevhliu/testing-images/resolve/main/no-background-jordan.png",
stream=True,
).raw
).convert("RGBA")
resized_img, white_bg_image = scale_and_paste(original_image)Pour éviter d’ajouter des détails superflus, utilisez l’estimateur ZoeDepth pour fournir des indications supplémentaires lors de la génération et vous assurer que les chaussures restent cohérentes avec l’image d’origine.
zoe = ZoeDetector.from_pretrained("lllyasviel/Annotators")
image_zoe = zoe(white_bg_image, detect_resolution=512, image_resolution=1024)
image_zoe
Outpaint
Une fois votre image prête, vous pouvez générer du contenu dans la zone blanche autour des chaussures avec controlnet-inpaint-dreamer-sdxl, un ControlNet SDXL entraîné pour l'inpainting.
Chargez le ControlNet d'inpainting, le modèle ZoeDepth et le VAE, puis transmettez-les à StableDiffusionXLControlNetPipeline. Vous pouvez ensuite créer une fonction optionnelle generate_image (pour plus de commodité) afin d'outpaint une image initiale.
controlnets = [
ControlNetModel.from_pretrained(
"destitech/controlnet-inpaint-dreamer-sdxl", dtype=torch.float16, variant="fp16"
),
ControlNetModel.from_pretrained(
"diffusers/controlnet-zoe-depth-sdxl-1.0", dtype=torch.float16
),
]
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", dtype=torch.float16).to("cuda") # or "mps", "xpu", "cpu"
pipeline = StableDiffusionXLControlNetPipeline.from_pretrained(
"SG161222/RealVisXL_V4.0", dtype=torch.float16, variant="fp16", controlnet=controlnets, vae=vae
).to("cuda")
def generate_image(prompt, negative_prompt, inpaint_image, zoe_image, seed: int = None):
if seed is None:
seed = random.randint(0, 2**32 - 1)
generator = torch.Generator(device="cpu").manual_seed(seed)
image = pipeline(
prompt,
negative_prompt=negative_prompt,
image=[inpaint_image, zoe_image],
guidance_scale=6.5,
num_inference_steps=25,
generator=generator,
controlnet_conditioning_scale=[0.5, 0.8],
control_guidance_end=[0.9, 0.6],
).images[0]
return image
prompt = "nike air jordans on a basketball court"
negative_prompt = ""
temp_image = generate_image(prompt, negative_prompt, white_bg_image, image_zoe, 908097)Collez l'image d'origine sur l'image initialement outpaintée. Vous améliorerez l'arrière-plan outpainté à une étape ultérieure.
x = (1024 - resized_img.width) // 2
y = (1024 - resized_img.height) // 2
temp_image.paste(resized_img, (x, y), resized_img)
temp_image
C'est le bon moment pour libérer de la mémoire si vous en manquez !
pipeline=None torch.cuda.empty_cache()
Maintenant que vous disposez d'une image outpaintée initiale, chargez le StableDiffusionXLInpaintPipeline avec le modèle RealVisXL pour générer l'image outpaintée finale avec une meilleure qualité.
pipeline = StableDiffusionXLInpaintPipeline.from_pretrained(
"OzzyGT/RealVisXL_V4.0_inpainting",
dtype=torch.float16,
variant="fp16",
vae=vae,
).to("cuda") # or "mps", "xpu", "cpu"Préparez un masque pour l'image finale après outpainting. Pour créer une transition plus naturelle entre l'image d'origine et l'arrière-plan généré, appliquez un flou au masque afin de faciliter son intégration.
mask = Image.new("L", temp_image.size)
mask.paste(resized_img.split()[3], (x, y))
mask = ImageOps.invert(mask)
final_mask = mask.point(lambda p: p > 128 and 255)
mask_blurred = pipeline.mask_processor.blur(final_mask, blur_factor=20)
mask_blurred
Créez un meilleur prompt et transmettez-le à la fonction generate_outpaint pour générer l'image finale outpaintée. De nouveau, superposez l'image originale sur le fond outpainté final.
def generate_outpaint(prompt, negative_prompt, image, mask, seed: int = None):
if seed is None:
seed = random.randint(0, 2**32 - 1)
generator = torch.Generator(device="cpu").manual_seed(seed)
image = pipeline(
prompt,
negative_prompt=negative_prompt,
image=image,
mask_image=mask,
guidance_scale=10.0,
strength=0.8,
num_inference_steps=30,
generator=generator,
).images[0]
return image
prompt = "high quality photo of nike air jordans on a basketball court, highly detailed"
negative_prompt = ""
final_image = generate_outpaint(prompt, negative_prompt, temp_image, mask_blurred, 7688778)
x = (1024 - resized_img.width) // 2
y = (1024 - resized_img.height) // 2
final_image.paste(resized_img, (x, y), resized_img)
final_image