距离上次更新已经过去两年,实在是比较懒。最近工作中需要批量进行图像的outpaint操作,笔者对文生图、图生图、图生视频的了解学习是不够充分的。只想基于diffusers库快速复现出outpaint的功能。huggingface上基于diffusers库有很多模型做inpaint的实现,例如SD1.5 、SD2、SD3、SDXL、flux等等。本质上outpaint是一种特殊的inpaint,但是找遍huggingface也没有直接实现的,开源的很多都是基于comfyUI或者powerpaint等方式,感觉还是比较复杂,不是傻瓜式的 几十行代码快速复现。用inpaint模型自己尝试的时候发现有些模型有坑,怎么都做不出outpaint的操作,给自己造成了困扰,特此记录一下。
目录
本篇宗旨,无需特别了解文生图、图生图的原理,直接实现扩图。话不多说,开始正文。
一、选inpaint的模型
outpaint的本质上也是inpaint,只不过从图像内部的局部重绘变成了,外扩部分的重绘,都参考了整幅图像的语义信息。
笔者用了两个inpaint模型:diffusers/stable-diffusion-xl-1.0-inpainting-0.1,black-forest-labs/FLUX.1-Fill-dev。其中sdxl模型有坑,他只能上下或者左右进行扩图。不能够上下左右同时,并且expand image时候要用127像素,笔者之前用0 或255填充,怎么都做不出outpaint的操作,而flux.1-fill就无所谓了,官方说此模型也可以做outpaint,实测下来确实如此。下面代码是两个模型,具体用哪个注释掉另一个就行。关于huggingface上的模型怎么下载,就不多言了。
from diffusers import AutoPipelineForInpainting
from diffusers import FluxFillPipeline
from diffusers.utils import load_image
import torch
import os
import numpy as np
import cv2
import random
from PIL import Image
"""
sdxl
"""
pipe = AutoPipelineForInpainting.from_pretrained("/data/caidou/diffusers/stable-diffusion-xl-1.0-inpainting-0.1", torch_dtype=torch.float16, variant="fp16").to("cuda")
generator = torch.Generator(device="cuda").manual_seed(0)
"""
flux.1fill-dev
"""
pipe = FluxFillPipeline.from_pretrained("/data/caidou/black-forest-labs/FLUX.1-Fill-dev", torch_dtype=torch.bfloat16).to("cuda")
二、扩充图像和重绘mask
expand image 参考PowerPaint的做法。很多方法的expand image应该都是经过了一个模型,经过语义理解,重新的到一张待重绘的带噪声图,然后在用inpaint模型进行重绘。笔者直接使用127像素值填充待原图的待扩充区域。
def get_expand_img_mask(input_image,vertical_expansion_ratio,horizontal_expansion_ratio):
if vertical_expansion_ratio is not None and horizontal_expansion_ratio is not None:
# o_W, o_H = input_image["image"].convert("RGB").size
o_W, o_H = input_image.convert("RGB").size
c_W = int(horizontal_expansion_ratio * o_W)
c_H = int(vertical_expansion_ratio * o_H)
expand_img = np.ones((c_H, c_W, 3), dtype=np.uint8) * 127
original_img = np.array(input_image)
expand_img[
int((c_H - o_H) / 2.0) : int((c_H - o_H) / 2.0) + o_H,
int((c_W - o_W) / 2.0) : int((c_W - o_W) / 2.0) + o_W,
:,
] = original_img
blurry_gap = 0
expand_mask = np.ones((c_H, c_W, 3), dtype=np.uint8) * 255
if vertical_expansion_ratio == 1 and horizontal_expansion_ratio != 1:
expand_mask[
int((c_H - o_H) / 2.0) : int((c_H - o_H) / 2.0) + o_H,
int((c_W - o_W) / 2.0) + blurry_gap : int((c_W - o_W) / 2.0) + o_W - blurry_gap,
:,
] = 0
elif vertical_expansion_ratio != 1 and horizontal_expansion_ratio != 1:
expand_mask[
int((c_H - o_H) / 2.0) + blurry_gap : int((c_H - o_H) / 2.0) + o_H - blurry_gap,
int((c_W - o_W) / 2.0) + blurry_gap : int((c_W - o_W) / 2.0) + o_W - blurry_gap,
:,
] = 0
elif vertical_expansion_ratio != 1 and horizontal_expansion_ratio == 1:
expand_mask[
int((c_H - o_H) / 2.0) + blurry_gap : int((c_H - o_H) / 2.0) + o_H - blurry_gap,
int((c_W - o_W) / 2.0) : int((c_W - o_W) / 2.0) + o_W,
:,
] = 0
expand_img = Image.fromarray(expand_img)
expand_mask = Image.fromarray(expand_mask)
return expand_img,expand_mask
三、outpaint的推理
扩图的比例合适就行。其它参数也可以调一调。prompt空就可以,啥也不用写就能outpaint。
1. sdxl-inpaint
if __name__=="__main__":
image = load_image(image_path)
expandimg,expandmask = get_expand_img_mask(image,vertical_expansion_ratio=1,horizontal_expansion_ratio=1.2)
W = int(np.shape(expandimg)[0] - np.shape(expandimg)[0] % 8)
H = int(np.shape(expandimg)[1] - np.shape(expandimg)[1] % 8)# sdxl的size必须要被8整除
expandimg = expandimg.resize((H, W))
expandmask = expandmask.resize((H, W))
w,h = expandimg.size
print(expandimg.size,expandmask.size)
generator = torch.Generator(device="cuda").manual_seed(0)
image = pipe(
prompt="",
height=h,
width=w,
image=expandimg,
mask_image=expandmask,
guidance_scale=8.0,
num_inference_steps=20, # steps between 15 and 30 work well for us
strength=0.99, # make sure to use `strength` below 1.0
generator=generator,
).images[0]
image.save("./sdxl-outpaint.jpg")
原图部分也是变模糊了,扩图效果这张不好,其实好多张还是可以的。
2. flux.1fill-dev
if __name__=="__main__":
image = load_image(image_path)
expandimg,expandmask = get_expand_img_mask(image,vertical_expansion_ratio=1.2,horizontal_expansion_ratio=1.0)
w,h = expandimg.size
print(expandimg.size,expandmask.size)
image = pipe(
prompt="",
image=expandimg,
mask_image=expandmask,
height=h,
width=w,
guidance_scale=30,
num_inference_steps=50,
max_sequence_length=512,
generator=torch.Generator("cpu").manual_seed(0)
).images[0]
image.save("./flux.1fill-dev_outpaint.jpg")
结果看起来原图保持的其实一般,扩图之后的原图部分会比较模糊,但是flux的扩图的效果还是比较好的。
发现很多博主涉及到outpainting都开收费,很是无语...特此简单记录下,如果对大家有帮助 欢迎点赞