429 lines
16 KiB
Python
429 lines
16 KiB
Python
from transformers import AutoModelForImageSegmentation, AutoConfig
|
||
from transformers.dynamic_module_utils import get_class_from_dynamic_module
|
||
from transformers.models.auto.auto_factory import add_generation_mixin_to_remote_model
|
||
import torch
|
||
from torchvision import transforms
|
||
import numpy as np
|
||
from PIL import Image
|
||
import torch.nn.functional as F
|
||
import os
|
||
from scipy.ndimage import binary_erosion
|
||
from pymatting.alpha.estimate_alpha_cf import estimate_alpha_cf
|
||
from pymatting.foreground.estimate_foreground_ml import estimate_foreground_ml
|
||
from pymatting.util.util import stack_images
|
||
from .rmbg.session_factory import new_session
|
||
from folder_paths import models_dir, add_model_folder_path, get_folder_paths
|
||
|
||
torch.set_float32_matmul_precision(["high", "highest"][0])
|
||
|
||
transform_image = transforms.Compose(
|
||
[
|
||
transforms.Resize((1024, 1024)),
|
||
transforms.ToTensor(),
|
||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||
]
|
||
)
|
||
|
||
current_path = os.getcwd()
|
||
|
||
## ComfyUI portable standalone build for Windows
|
||
|
||
add_model_folder_path('birefnet', os.path.join(models_dir, "birefnet"))
|
||
model_path = "BiRefNet"
|
||
|
||
max_gpu_size = 3
|
||
|
||
def clear_memory():
|
||
import gc
|
||
# Cleanup
|
||
gc.collect()
|
||
if torch.cuda.is_available():
|
||
torch.cuda.empty_cache()
|
||
torch.cuda.ipc_collect()
|
||
|
||
def tensor2pil(image):
|
||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||
|
||
def pil2tensor(image):
|
||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||
|
||
def resize_image(image):
|
||
image = image.convert('RGB')
|
||
model_input_size = (1024, 1024)
|
||
image = image.resize(model_input_size, Image.BILINEAR)
|
||
return image
|
||
|
||
|
||
def alpha_matting_cutout(
|
||
img: Image,
|
||
mask: Image,
|
||
foreground_threshold: int,
|
||
background_threshold: int,
|
||
erode_structure_size: int,
|
||
) -> Image:
|
||
"""
|
||
Perform alpha matting on an image using a given mask and threshold values.
|
||
|
||
This function takes a PIL image `img` and a PIL image `mask` as input, along with
|
||
the `foreground_threshold` and `background_threshold` values used to determine
|
||
foreground and background pixels. The `erode_structure_size` parameter specifies
|
||
the size of the erosion structure to be applied to the mask.
|
||
|
||
The function returns a PIL image representing the cutout of the foreground object
|
||
from the original image.
|
||
"""
|
||
if img.mode == "RGBA" or img.mode == "CMYK":
|
||
img = img.convert("RGB")
|
||
|
||
img_array = np.asarray(img)
|
||
mask_array = np.asarray(mask)
|
||
|
||
is_foreground = mask_array > foreground_threshold
|
||
is_background = mask_array < background_threshold
|
||
|
||
structure = None
|
||
if erode_structure_size > 0:
|
||
structure = np.ones(
|
||
(erode_structure_size, erode_structure_size), dtype=np.uint8
|
||
)
|
||
|
||
is_foreground = binary_erosion(is_foreground, structure=structure)
|
||
is_background = binary_erosion(is_background, structure=structure, border_value=1)
|
||
|
||
trimap = np.full(mask_array.shape, dtype=np.uint8, fill_value=128)
|
||
trimap[is_foreground] = 255
|
||
trimap[is_background] = 0
|
||
|
||
img_normalized = img_array / 255.0
|
||
trimap_normalized = trimap / 255.0
|
||
|
||
alpha = estimate_alpha_cf(img_normalized, trimap_normalized)
|
||
foreground = estimate_foreground_ml(img_normalized, alpha)
|
||
cutout = stack_images(foreground, alpha)
|
||
|
||
cutout = np.clip(cutout * 255, 0, 255).astype(np.uint8)
|
||
cutout = Image.fromarray(cutout)
|
||
|
||
return cutout
|
||
|
||
|
||
def naive_cutout(img: Image, mask: Image) -> Image:
|
||
"""
|
||
Perform a simple cutout operation on an image using a mask.
|
||
|
||
This function takes a PIL image `img` and a PIL image `mask` as input.
|
||
It uses the mask to create a new image where the pixels from `img` are
|
||
cut out based on the mask.
|
||
|
||
The function returns a PIL image representing the cutout of the original
|
||
image using the mask.
|
||
"""
|
||
empty = Image.new("RGBA", (img.size), 0)
|
||
cutout = Image.composite(img, empty, mask)
|
||
return cutout
|
||
|
||
def putalpha_cutout(img: Image, mask: Image) -> Image:
|
||
"""
|
||
Apply the specified mask to the image as an alpha cutout.
|
||
|
||
Args:
|
||
img (PILImage): The image to be modified.
|
||
mask (PILImage): The mask to be applied.
|
||
|
||
Returns:
|
||
PILImage: The modified image with the alpha cutout applied.
|
||
"""
|
||
img.putalpha(mask)
|
||
return img
|
||
|
||
colors = ["transparency", "green", "white", "red", "yellow", "blue", "black", "pink", "purple", "brown", "violet", "wheat", "whitesmoke", "yellowgreen", "turquoise", "tomato", "thistle", "teal", "tan", "steelblue", "springgreen", "snow", "slategrey", "slateblue", "skyblue", "orange"]
|
||
|
||
def get_device_by_name(device):
|
||
"""
|
||
"device": (["auto", "cuda", "cpu", "mps", "xpu", "meta"],{"default": "auto"}),
|
||
"""
|
||
if device == 'auto':
|
||
try:
|
||
device = "cpu"
|
||
if torch.cuda.is_available():
|
||
device = "cuda"
|
||
elif torch.backends.mps.is_available():
|
||
device = "cpu"
|
||
elif torch.xpu.is_available():
|
||
device = "xpu"
|
||
except:
|
||
raise AttributeError("What's your device(到底用什么设备跑的)?")
|
||
print("\033[93mUse Device(使用设备):", device, "\033[0m")
|
||
return device
|
||
|
||
def get_device_provider(device):
|
||
providers = []
|
||
cuda_provider_options = {
|
||
"gpu_mem_limit": 9 * 1024 * 1024 * 1024, # 9GB.
|
||
"arena_extend_strategy": "kSameAsRequested",
|
||
"cudnn_conv_use_max_workspace": "0"
|
||
}
|
||
if device == 'cuda' and torch.cuda.is_available():
|
||
providers.append(("CUDAExecutionProvider", cuda_provider_options))
|
||
|
||
if len(providers) == 0:
|
||
providers.append(("CPUExecutionProvider", {}))
|
||
|
||
return providers
|
||
|
||
|
||
_birefnet_model = None
|
||
_birefnet_model_name = ""
|
||
_birefnet_onnx_model = None
|
||
_birefnet_model_name_onnx = ""
|
||
|
||
class BiRefNet_onnx:
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"image": ("IMAGE",),
|
||
"device": (["cpu", "cuda"],{"default": "cpu"}),
|
||
"model_name": ([
|
||
"birefnet-general",
|
||
"birefnet-general-lite",
|
||
"birefnet-portrait",
|
||
"birefnet-cod",
|
||
"birefnet-dis",
|
||
"birefnet-hrsod",
|
||
"birefnet-massive"
|
||
], {"default": "birefnet-general"}),
|
||
"cutout_func": (["putalpha", "naive", "alpha_matting"],{"default": "putalpha"}),
|
||
# "cached": ("BOOLEAN", {"default": True}),
|
||
"alpha_matting_foreground_threshold": ("INT", {"default": 240}),
|
||
"alpha_matting_background_threshold": ("INT", {"default": 10}),
|
||
"alpha_matting_erode_size": ("INT", {"default": 10}),
|
||
},
|
||
}
|
||
RETURN_TYPES = ("IMAGE", "MASK",)
|
||
RETURN_NAMES = ("image", "mask",)
|
||
FUNCTION = "background_remove"
|
||
CATEGORY = "🔥BiRefNet"
|
||
|
||
def background_remove(self,
|
||
image,
|
||
device,
|
||
model_name,
|
||
cutout_func,
|
||
# cached,
|
||
alpha_matting_foreground_threshold,
|
||
alpha_matting_background_threshold,
|
||
alpha_matting_erode_size,
|
||
):
|
||
providers = get_device_provider(device)
|
||
processed_images = []
|
||
processed_masks = []
|
||
global _birefnet_onnx_model, _birefnet_model_name_onnx
|
||
|
||
## TODO: cache model
|
||
if _birefnet_onnx_model is not None and _birefnet_model_name_onnx == model_name:
|
||
session = _birefnet_onnx_model
|
||
print("Loading from cache...")
|
||
else:
|
||
session = new_session(model_name, providers=providers)
|
||
if _birefnet_onnx_model is not None:
|
||
_birefnet_onnx_model = None
|
||
_birefnet_model_name_onnx = ''
|
||
clear_memory()
|
||
# if cached:
|
||
# _birefnet_onnx_model = session
|
||
# _birefnet_model_name_onnx = model_name
|
||
|
||
for image in image:
|
||
orig_image = tensor2pil(image)
|
||
masks = session.predict(orig_image)
|
||
pil_im = masks[0]
|
||
if cutout_func == 'putalpha':
|
||
new_im = putalpha_cutout(orig_image, pil_im)
|
||
elif cutout_func == 'naive':
|
||
new_im = naive_cutout(orig_image, pil_im)
|
||
elif cutout_func == 'alpha_matting':
|
||
new_im = alpha_matting_cutout(
|
||
orig_image,
|
||
pil_im,
|
||
foreground_threshold=alpha_matting_foreground_threshold,
|
||
background_threshold=alpha_matting_background_threshold,
|
||
erode_structure_size=alpha_matting_erode_size
|
||
)
|
||
|
||
new_im_tensor = pil2tensor(new_im)
|
||
pil_im_tensor = pil2tensor(pil_im)
|
||
processed_images.append(new_im_tensor)
|
||
processed_masks.append(pil_im_tensor)
|
||
|
||
clear_memory()
|
||
|
||
new_ims = torch.cat(processed_images, dim=0)
|
||
new_masks = torch.cat(processed_masks, dim=0)
|
||
|
||
return new_ims, new_masks
|
||
|
||
class BiRefNet_Lite:
|
||
def __init__(self):
|
||
pass
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
|
||
return {
|
||
"required": {
|
||
"image": ("IMAGE",),
|
||
"load_local_model": ("BOOLEAN", {"default": True}),
|
||
"device": (["auto", "cuda", "cpu", "mps", "xpu", "meta"],{"default": "auto"}),
|
||
"cutout_func": (["putalpha", "naive", "alpha_matting"],{"default": "putalpha"}),
|
||
"mask_precision_threshold": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0}),
|
||
"cached": ("BOOLEAN", {"default": True}),
|
||
"cpu_size": ("FLOAT",{"default": 0}),
|
||
"alpha_matting_foreground_threshold": ("INT", {"default": 240}),
|
||
"alpha_matting_background_threshold": ("INT", {"default": 10}),
|
||
"alpha_matting_erode_size": ("INT", {"default": 10}),
|
||
},
|
||
"optional": {
|
||
"local_model_path": ("STRING", {"default": model_path}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE", "MASK",)
|
||
RETURN_NAMES = ("image", "mask",)
|
||
FUNCTION = "background_remove"
|
||
CATEGORY = "🔥BiRefNet"
|
||
|
||
def background_remove(self,
|
||
image,
|
||
load_local_model,
|
||
device,
|
||
cutout_func,
|
||
mask_precision_threshold,
|
||
cached,
|
||
cpu_size,
|
||
alpha_matting_foreground_threshold,
|
||
alpha_matting_background_threshold,
|
||
alpha_matting_erode_size,
|
||
*args, **kwargs
|
||
):
|
||
processed_images = []
|
||
processed_masks = []
|
||
|
||
device = get_device_by_name(device)
|
||
birefnet = None
|
||
global _birefnet_model
|
||
global _birefnet_model_name
|
||
|
||
local_model_path = kwargs.get("local_model_path", model_path)
|
||
if cached and _birefnet_model is not None and _birefnet_model_name == local_model_path:
|
||
birefnet = _birefnet_model
|
||
else:
|
||
if _birefnet_model is not None:
|
||
_birefnet_model = None
|
||
_birefnet_model_name = None
|
||
clear_memory()
|
||
|
||
if load_local_model:
|
||
local_model_path = os.path.join(get_folder_paths('birefnet')[-1], local_model_path)
|
||
# 判断是否开启双卡支持
|
||
# 获取不同显卡的内存可用量
|
||
# 显卡不足时,自动往CPU调度
|
||
spare_params = {}
|
||
config, kwargs = AutoConfig.from_pretrained(
|
||
local_model_path,
|
||
return_unused_kwargs=True,
|
||
trust_remote_code=True,
|
||
code_revision=None,
|
||
_commit_hash=None,
|
||
**kwargs,
|
||
)
|
||
|
||
class_ref = config.auto_map["AutoModelForImageSegmentation"]
|
||
model_class = get_class_from_dynamic_module(
|
||
class_ref, local_model_path, code_revision=None, **kwargs
|
||
)
|
||
if cpu_size > 0 and device == 'cuda':
|
||
spare_params['device_map'] = 'auto'
|
||
spare_params['max_memory'] = {0: f"{max_gpu_size}GiB", "cpu": f"{cpu_size}GiB"}
|
||
setattr(model_class, '_no_split_modules', ["Decoder", "SwinTransformer"])
|
||
else:
|
||
try:
|
||
if hasattr(model_class, "_no_split_modules"):
|
||
delattr(model_class, "_no_split_modules")
|
||
except Exception as e:
|
||
print('No need to delete:', e)
|
||
|
||
AutoModelForImageSegmentation.register(config.__class__, model_class, exist_ok=True)
|
||
model_class = add_generation_mixin_to_remote_model(model_class)
|
||
birefnet = model_class.from_pretrained(
|
||
local_model_path, config=config, **spare_params
|
||
)
|
||
# birefnet = AutoModelForImageSegmentation.from_pretrained(local_model_path,trust_remote_code=True, **spare_params)
|
||
if cached:
|
||
_birefnet_model = birefnet
|
||
_birefnet_model_name = local_model_path
|
||
else:
|
||
birefnet = AutoModelForImageSegmentation.from_pretrained(
|
||
"ZhengPeng7/BiRefNet", trust_remote_code=True
|
||
)
|
||
# 远程加载不启动分块
|
||
cpu_size = 0
|
||
if cpu_size == 0:
|
||
birefnet.to(device)
|
||
|
||
for image in image:
|
||
orig_image = tensor2pil(image)
|
||
w,h = orig_image.size
|
||
image = resize_image(orig_image)
|
||
im_tensor = transform_image(image).unsqueeze(0)
|
||
im_tensor=im_tensor.to(device)
|
||
with torch.no_grad():
|
||
result = birefnet(im_tensor)[-1].sigmoid().cpu()
|
||
result = torch.squeeze(F.interpolate(result, size=(h,w)))
|
||
ma = torch.max(result)
|
||
mi = torch.min(result)
|
||
result = (result-mi)/(ma-mi)
|
||
im_array = (result*255).cpu().data.numpy().astype(np.uint8)
|
||
pil_im = Image.fromarray(np.squeeze(im_array))
|
||
if cutout_func == 'putalpha':
|
||
new_im = putalpha_cutout(orig_image, pil_im)
|
||
elif cutout_func == 'naive':
|
||
new_im = naive_cutout(orig_image, pil_im)
|
||
elif cutout_func == 'alpha_matting':
|
||
new_im = alpha_matting_cutout(
|
||
orig_image,
|
||
pil_im,
|
||
foreground_threshold=alpha_matting_foreground_threshold,
|
||
background_threshold=alpha_matting_background_threshold,
|
||
erode_structure_size=alpha_matting_erode_size
|
||
)
|
||
new_im_tensor = pil2tensor(new_im)
|
||
pil_im_tensor = pil2tensor(pil_im)
|
||
if mask_precision_threshold > 0:
|
||
pil_im_tensor[pil_im_tensor <= mask_precision_threshold] = 0.0
|
||
pil_im_tensor[pil_im_tensor > mask_precision_threshold] = 1.0
|
||
|
||
processed_images.append(new_im_tensor)
|
||
processed_masks.append(pil_im_tensor)
|
||
|
||
clear_memory()
|
||
|
||
new_ims = torch.cat(processed_images, dim=0)
|
||
new_masks = torch.cat(processed_masks, dim=0)
|
||
|
||
return new_ims, new_masks
|
||
|
||
|
||
NODE_CLASS_MAPPINGS = {
|
||
"BiRefNet_Super": BiRefNet_Lite,
|
||
"BiRefNet_Lite": BiRefNet_Lite,
|
||
"BiRefNet_onnx": BiRefNet_onnx
|
||
}
|
||
|
||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"BiRefNet_Lite": "🔥BiRefNet_Lite",
|
||
"BiRefNet_Super": "🔥BiRefNet_Super",
|
||
"BiRefNet_onnx": "🔥BiRefNet_onnx"
|
||
}
|