Files
hackkhai-ComfyUI-Image-Matting/nodes.py
T
2024-03-08 16:28:08 +05:30

233 lines
8.0 KiB
Python

import os
import sys
import math
import copy
import torch
import numpy as np
import cv2
from tqdm import trange
import folder_paths
import comfy.model_management
from comfy.utils import ProgressBar
from transformers import VitMatteForImageMatting, VitMatteImageProcessor
import cv2
import numpy as np
import logging
from PIL import Image
logger = logging.getLogger('comfyui_image_matting')
def FB_blur_fusion_foreground_estimator_1(image, alpha, r=90):
alpha = alpha[:, :, None]
return FB_blur_fusion_foreground_estimator(image, F=image, B=image, alpha=alpha, r=r)[0]
def FB_blur_fusion_foreground_estimator_2(image, alpha, r=90):
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):
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
matting_model_dir_name = "matting_models"
matting_model_list = {
"vitmatte_small (103 MB)": {
"model_url": "hustvl/vitmatte-small-composition-1k"
},
"vitmatte_base (387 MB)": {
"model_url": "hustvl/vitmatte-base-composition-1k"
},
}
def list_matting_models():
return list(matting_model_list.keys())
def load_matting_model(model_name):
model_url = matting_model_list[model_name]["model_url"]
matting_model_checkpoint_path = get_local_filepath_(
matting_model_list[model_name]["model_url"], matting_model_dir_name,f"{model_url}")
matting_model = VitMatteForImageMatting.from_pretrained(matting_model_checkpoint_path)
preprocessor = VitMatteImageProcessor.from_pretrained(matting_model_checkpoint_path)
matting_model_device = comfy.model_management.get_torch_device()
matting_model = matting_model.to(matting_model_device)
matting_model.eval()
return (matting_model, preprocessor)
def get_local_filepath_(url, dirname, local_file_name=None):
destination = folder_paths.get_full_path(dirname, local_file_name)
if destination:
logger.warn(f'using extra model: {destination}')
return destination
folder = os.path.join(folder_paths.models_dir, dirname)
if not os.path.exists(folder):
os.makedirs(folder)
destination = os.path.join(folder, local_file_name)
if not os.path.exists(destination):
# download_url_to_file(url, destination)
logger.warn(f'downloading {url} to {destination}')
VitMatteForImageMatting.from_pretrained(url).save_pretrained(destination)
VitMatteImageProcessor.from_pretrained(url).save_pretrained(destination)
return destination
class MattingModelLoader:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_name": (list_matting_models(), ),
}
}
CATEGORY = "image_matting"
FUNCTION = "main"
RETURN_TYPES = ("MATTING_MODEL", "MATTING_PREPROCESSOR")
def main(self, model_name):
matting_model,matting_preprocessor = load_matting_model(model_name)
return (matting_model, matting_preprocessor)
def gen_trimap(alpha, kernel_size=25):
k_size = kernel_size
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,
(k_size, k_size))
dilated = cv2.dilate(alpha, kernel)
eroded = cv2.erode(alpha, kernel)
trimap = np.zeros(alpha.shape)
trimap.fill(128)
trimap[eroded > 254.5] = 255
trimap[dilated < 0.5] = 0
return trimap
class CreateTrimap:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"mask": ('MASK', {}),
"kernel_size": ("INT", {
"default": 9,
"min": 0,
"max": 50,
"step": 0.01
}),
}
}
CATEGORY = "image_matting"
FUNCTION = "main"
RETURN_TYPES = ("MASK",)
def main(self, mask, kernel_size):
res_masks = []
for mask_ in mask:
mask_ = mask_.cpu().numpy()*255
mask_ = mask_.astype(np.uint8)
mask_ = gen_trimap(mask_, kernel_size=kernel_size)/255.
res_masks.extend([torch.from_numpy(mask_).unsqueeze(0)])
return (torch.cat(res_masks, dim=0),)
class ApplyMatting:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"matting_model": ('MATTING_MODEL', {}),
"matting_preprocessor": ('MATTING_PREPROCESSOR', {}),
"image": ('IMAGE', {}),
"trimap": ("MASK", {}),
}
}
CATEGORY = "image_matting"
FUNCTION = "main"
RETURN_TYPES = ("IMAGE", "MASK")
def main(self, matting_model,matting_preprocessor, image, trimap):
res_images = []
res_masks = []
for item, tri in zip(image, trimap):
item = Image.fromarray(
np.clip(255. * item.cpu().numpy(), 0, 255).astype(np.uint8)).convert('RGBA')
print(tri.shape)
tri = Image.fromarray(
np.clip(255. * tri.cpu().numpy(), 0, 255).astype(np.uint8)).convert('L')
alpha = matting_predict(
matting_model,
matting_preprocessor,
item,
tri
)
alpha = (alpha*255).astype(np.uint8)
item_shape = item.size
alpha = cv2.resize(alpha, (item_shape[0], item_shape[1]), interpolation=cv2.INTER_NEAREST)
ffe_foreground = FB_blur_fusion_foreground_estimator(
np.array(item.convert("RGB"))/255.,np.array(item.convert("RGB"))/255.,np.array(item.convert("RGB"))/255., alpha[:,:,None]/255., r=90
)[0]*255
ffe_foreground = ffe_foreground.astype(np.uint8)
#H,W,C
alpha = alpha.astype(np.uint8)[..., None]
print(ffe_foreground.shape, alpha.shape)
#make it rgba with alpha
ffe_foreground = np.concatenate([ffe_foreground, alpha], axis=2)
#resize image from HWC to CHW
res_images.extend([torch.from_numpy(ffe_foreground).unsqueeze(0)/255.])
res_masks.extend([torch.from_numpy(alpha[:,:,0]).unsqueeze(0)/255.])
if len(res_images) == 0:
_, height, width, _ = image.size()
empty_mask = torch.zeros((1, height, width), dtype=torch.uint8, device="cpu")
return (empty_mask, empty_mask)
return (torch.cat(res_images, dim=0), torch.cat(res_masks, dim=0))
def matting_predict(
matting_model,
matting_preprocessor,
image,
tri
):
def load_matting_image(image_pil, tri, image_preprocessor):
inputs = image_preprocessor(images=image_pil, trimaps=tri, return_tensors="pt")
return inputs
inputs = load_matting_image(image.convert("RGB"), tri, matting_preprocessor)
for key in inputs:
inputs[key] = inputs[key].to(comfy.model_management.get_torch_device())
with torch.no_grad():
alphas = matting_model(**inputs).alphas[0][0].cpu().numpy()
return alphas
NODE_CLASS_MAPPINGS = {
"MattingModelLoader": MattingModelLoader,
"ApplyMatting": ApplyMatting,
"CreateTrimap": CreateTrimap,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"MattingModelLoader": "Load Matting Model",
"ApplyMatting": "Apply Matting",
"CreateTrimap": "Create Trimap",
}