Diffusers documentation

Outpainting

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.42.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

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.

image d'origine
fond supprimé

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
Update on GitHub