Files
2025-02-26 11:01:07 +08:00

235 lines
9.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from transformers import AutoModelForImageSegmentation
import torch
from torchvision import transforms
import numpy as np
from PIL import Image
import torch.nn.functional as F
import os
import cv2
import folder_paths
torch.set_float32_matmul_precision(["high", "highest"][0])
models_path = os.path.join(folder_paths.models_dir, "BiRefNet")
# 模型配置
MODEL_VERSIONS = {
'BiRefNet': ('BiRefNet', (1024, 1024)),
'BiRefNet_HR': ('BiRefNet_HR', (2048, 2048)),
'BiRefNet_lite': ('BiRefNet_lite', (1024, 1024)),
'BiRefNet_lite-2K': ('BiRefNet_lite-2K', (2560, 1440)),
'BiRefNet_512x512': ('BiRefNet_512x512', (512, 512)),
'BiRefNet-matting': ('BiRefNet-matting', (1024, 1024)),
'BiRefNet_HR-Matting':('BiRefNet_HR-Matting', (2048, 2048)),
'BiRefNet-portrait': ('BiRefNet-portrait', (1024, 1024)),
'BiRefNet-DIS5K': ('BiRefNet-DIS5K', (1024, 1024)),
'BiRefNet-HRSOD': ('BiRefNet-HRSOD', (1024, 1024)),
'BiRefNet-COD': ('BiRefNet-COD', (1024, 1024)),
'BiRefNet-DIS5K-TR_TEs': ('BiRefNet-DIS5K-TR_TEs', (1024, 1024)),
'BiRefNet-legacy': ('BiRefNet-legacy', (1024, 1024))
}
def refine_foreground(image, mask, r=90):
if mask.size != image.size:
mask = mask.resize(image.size)
image = np.array(image) / 255.0
mask = np.array(mask) / 255.0
estimated_foreground = FB_blur_fusion_foreground_estimator_2(image, mask, r=r)
new_foreground = Image.fromarray((estimated_foreground * 255.0).astype(np.uint8))
return new_foreground
def FB_blur_fusion_foreground_estimator_2(image, alpha, r=90):
# Thanks to the source: https://github.com/Photoroom/fast-foreground-estimation
alpha = alpha[:, :, None]
F, blur_B = FB_blur_fusion_foreground_estimator(image, image, image, alpha, r)
return FB_blur_fusion_foreground_estimator(image, F, blur_B, alpha, r=6)[0]
def FB_blur_fusion_foreground_estimator(image, F, B, alpha, r=90):
if isinstance(image, Image.Image):
image = np.array(image) / 255.0
blurred_alpha = cv2.blur(alpha, (r, r))[:, :, None]
blurred_FA = cv2.blur(F * alpha, (r, r))
blurred_F = blurred_FA / (blurred_alpha + 1e-5)
blurred_B1A = cv2.blur(B * (1 - alpha), (r, r))
blurred_B = blurred_B1A / ((1 - blurred_alpha) + 1e-5)
F = blurred_F + alpha * \
(image - alpha * blurred_F - (1 - alpha) * blurred_B)
F = np.clip(F, 0, 1)
return F, blurred_B
def get_device_by_name(device):
"""
根据名称获取设备
"""
if device == 'auto':
try:
device = "cpu"
if torch.cuda.is_available():
device = "cuda"
elif torch.backends.mps.is_available():
device = "mps"
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_model_path(model_name):
return os.path.join(models_path, model_name)
class ImagePreprocessor:
def __init__(self, resolution):
self.transform = transforms.Compose([
transforms.Resize(resolution),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
def __call__(self, image):
return self.transform(image)
# 模型加载节点
class BiRefNet_Loader:
def __init__(self):
self.loaded_model = None
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_version": (list(MODEL_VERSIONS.keys()), {"default": "BiRefNet_HR"}),
"device": (["auto", "cuda", "cpu", "mps", "xpu", "meta"], {"default": "auto"})
}
}
RETURN_TYPES = ("BIREFNET_MODEL",)
RETURN_NAMES = ("model",)
FUNCTION = "load_model"
CATEGORY = "BiRefNet🌟"
def load_model(self, model_version, device):
device = get_device_by_name(device)
model_name, resolution = MODEL_VERSIONS[model_version]
local_model_path = get_model_path(model_name)
# 首先尝试加载本地模型
try:
if os.path.exists(local_model_path):
print(f"\033[92mLoading local model from: {local_model_path}\033[0m")
model = AutoModelForImageSegmentation.from_pretrained(
local_model_path,
trust_remote_code=True
)
else:
print(f"\033[93mLocal model not found, downloading from HuggingFace: {model_name}\033[0m")
model = AutoModelForImageSegmentation.from_pretrained(
f"ZhengPeng7/{model_name}",
trust_remote_code=True,
cache_dir=local_model_path
)
except Exception as e:
print(f"\033[91mError loading local model: {str(e)}\033[0m")
print("\033[93mFallback to downloading from HuggingFace\033[0m")
try:
model = AutoModelForImageSegmentation.from_pretrained(
f"ZhengPeng7/{model_name}",
trust_remote_code=True,
cache_dir=local_model_path
)
except Exception as download_error:
raise RuntimeError(f"Failed to load model both locally and from HuggingFace: {str(download_error)}")
model.to(device)
model.eval()
if device == "cuda":
model.half()
return ({
"model": model,
"resolution": resolution,
"device": device,
"half_precision": (device == "cuda")
},)
# 推理节点
class BiRefNet_Remove_Background:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("BIREFNET_MODEL",),
"image": ("IMAGE",),
"background_color": (["transparency"] + ["white", "black", "green", "blue", "red"], {"default": "transparency"}),
"use_refine": ("BOOLEAN", {"default": True})
}
}
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "inference"
CATEGORY = "BiRefNet🌟"
def inference(self, image, model, background_color, use_refine):
model_data = model
model = model_data["model"]
device = model_data["device"]
use_half_precision = model_data["half_precision"]
resolution = model_data["resolution"] # 直接使用模型的最佳分辨率
preprocessor = ImagePreprocessor(resolution)
processed_images = []
processed_masks = []
for img in image:
# 转换为PIL图像
orig_image = Image.fromarray(np.clip(255. * img.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
w, h = orig_image.size
# 预处理
image_tensor = preprocessor(orig_image.convert('RGB')).unsqueeze(0)
if use_half_precision:
image_tensor = image_tensor.half()
image_tensor = image_tensor.to(device)
# 推理
with torch.no_grad():
preds = model(image_tensor)[-1].sigmoid().cpu()
pred = preds[0].squeeze()
pred_pil = transforms.ToPILImage()(pred)
mask = pred_pil.resize((w, h))
# 选择应用前景优化
if use_refine:
refined_image = refine_foreground(orig_image, pred_pil, r=90) # 使用固定的r值即可(实验验证过调整r值对结果影响很小)
# 设置背景和前景
if background_color == "transparency":
result_image = Image.new("RGBA", (w, h), (0, 0, 0, 0))
result_image.paste(refined_image if use_refine else orig_image, mask=mask)
else:
result_image = Image.new("RGB", (w, h), background_color)
result_image.paste(refined_image if use_refine else orig_image, mask=mask)
# 转换回tensor
processed_images.append(torch.from_numpy(np.array(result_image).astype(np.float32) / 255.0).unsqueeze(0))
processed_masks.append(torch.from_numpy(np.array(mask).astype(np.float32) / 255.0).unsqueeze(0))
return torch.cat(processed_images, dim=0), torch.cat(processed_masks, dim=0)
NODE_CLASS_MAPPINGS = {
"BiRefNet_Loader": BiRefNet_Loader,
"BiRefNet_Remove_Background": BiRefNet_Remove_Background
}
NODE_DISPLAY_NAME_MAPPINGS = {
"BiRefNet_Loader": "BiRefNet Loader🌟",
"BiRefNet_Remove_Background": "BiRefNet Remove Background🌟"
}