Outpainting
Outpainting은 이미지의 원래 경계를 넘어 확장하여, 원본 이미지를 보존하면서 시각적 요소를 추가, 교체 또는 수정할 수 있게 합니다. inpainting과 마찬가지로, 원본 이미지(검은색 픽셀의 마스크로 표현됨)를 유지하면서 흰색 영역(이 경우 원본 이미지 외부 영역)을 새로운 시각적 요소로 채우는 것이 목표입니다. ControlNet 또는 Differential Diffusion를 사용하여 outpainting을 수행하는 방법 등이 있습니다.
이 가이드에서는 inpainting 모델, ControlNet, ZoeDepth 추정기를 사용하여 outpainting을 수행하는 방법을 보여줍니다.
시작하기 전에 ZoeDepth 추정기를 사용할 수 있도록 controlnet_aux 라이브러리가 설치되어 있는지 확인하세요.
!pip install -q controlnet_aux
이미지 준비
outpainting할 이미지를 선택하고 BRIA-RMBG-1.4와 같은 Space를 사용해 배경을 제거하는 것으로 시작합니다.
예를 들어, 이 신발 사진에서 배경을 제거합니다.
Stable Diffusion XL (SDXL) 모델은 1024x1024 이미지에 최적화되어 있지만, 하드웨어에 충분한 메모리가 있다면 이미지를 원하는 크기로 리사이즈할 수 있습니다. 이미지의 투명 배경은 흰색 배경으로 교체해야 합니다. 아래와 같은 함수를 만들어 이미지를 스케일링하고 흰색 배경에 붙여넣으세요.
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)원치 않는 추가 세부 사항이 포함되는 것을 방지하려면, ZoeDepth 추정기를 사용하여 생성 과정에서 추가적인 지침을 제공하고 신발이 원본 이미지와 일관되도록 유지하세요.
zoe = ZoeDetector.from_pretrained("lllyasviel/Annotators")
image_zoe = zoe(white_bg_image, detect_resolution=512, image_resolution=1024)
image_zoe
Outpaint
이미지가 준비되면, 인페인팅을 위해 학습된 SDXL ControlNet인 controlnet-inpaint-dreamer-sdxl을 사용하여 신발 주변의 흰색 영역에 콘텐츠를 생성할 수 있습니다.
인페인팅용 ControlNet, ZoeDepth 모델, VAE를 로드하고 StableDiffusionXLControlNetPipeline에 전달합니다. 그런 다음 편의를 위해 초기 이미지를 아웃페인팅하는 선택적 generate_image 함수를 만들 수 있습니다.
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)…
x = (1024 - resized_img.width) // 2
y = (1024 - resized_img.height) // 2
temp_image.paste(resized_img, (x, y), resized_img)
temp_image
메모리가 부족하다면 지금이 메모리를 해제할 좋은 타이밍입니다!
pipeline=None torch.cuda.empty_cache()
초기 outpainted 이미지를 확보했으므로, StableDiffusionXLInpaintPipeline을 RealVisXL 모델과 함께 로드하여 더 높은 품질의 최종 outpainted 이미지를 생성하세요.
pipeline = StableDiffusionXLInpaintPipeline.from_pretrained(
"OzzyGT/RealVisXL_V4.0_inpainting",
dtype=torch.float16,
variant="fp16",
vae=vae,
).to("cuda") # or "mps", "xpu", "cpu"최종 아웃페인팅된 이미지에 사용할 마스크를 준비합니다. 원본 이미지와 아웃페인팅된 배경 사이의 더 자연스러운 전환을 위해 마스크를 흐리게 처리하여 더 잘 어우러지도록 합니다.
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
더 나은 프롬프트를 작성하여 generate_outpaint 함수에 전달하고 최종 아웃페인팅 이미지를 생성합니다. 다시 한 번, 원본 이미지를 최종 아웃페인팅 배경 위에 겹쳐 배치합니다.
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