diff --git a/BiRefNet_config.py b/BiRefNet_config.py new file mode 100644 index 0000000..37c8ac5 --- /dev/null +++ b/BiRefNet_config.py @@ -0,0 +1,11 @@ +from transformers import PretrainedConfig + +class BiRefNetConfig(PretrainedConfig): + model_type = "SegformerForSemanticSegmentation" + def __init__( + self, + bb_pretrained=False, + **kwargs + ): + self.bb_pretrained = bb_pretrained + super().__init__(**kwargs) diff --git a/README.md b/README.md new file mode 100644 index 0000000..757e7f1 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# ComfyUI-BiRefNet-Hugo + +## 介绍 | Introduction + +本仓库将BiRefNet最新模型封装为ComfyUI节点来使用,相较于旧模型,最新模型的抠图精度更高更好。
+This repository wraps the latest BiRefNet model as ComfyUI nodes. Compared to the previous model, the latest model offers higher and better matting accuracy. + +## 安装 | Installation + +1. 进入节点目录, `ComfyUI/custom_nodes/` +2. `git clone https://github.com/MoonHugo/ComfyUI-BiRefNet-Hugo.git` +3. `cd ComfyUI-BiRefNet-Hugo` +4. `pip install -r requirements.txt` +___ + +1. Go to comfyUI custom_nodes folder, `ComfyUI/custom_nodes/` +2. `git clone https://github.com/MoonHugo/ComfyUI-BiRefNet-Hugo.git` +3. `cd ComfyUI-BiRefNet-Hugo` +4. `pip install -r requirements.txt` + +## 使用 | Usage + +示例工作流放置在`ComfyUI-BiRefNet-Hugo/workflow`中
+The demo workflow placed in `ComfyUI-BiRefNet-Hugo/workflow` +___ +工作流workflow.json的使用
+The use of workflow.json + +![plot](./assets/d0a22b2a-ceb3-4205-9b4e-f6a68e4337c7.png) + +工作流video_workflow.json的使用
+The use of video_workflow.json +___ +![plot](./assets/2de5b085-1125-46f9-8ef3-06706743f182.png) + +## 效果演示 | Sample Result + +![](./assets/demo1.gif) + +![](./assets/demo2.gif) + +![](./assets/demo3.gif) + +## 社交账号 | Social Account Homepage +- Bilibili:[我的B站主页](https://space.bilibili.com/1303099255) + +## 感谢 | Acknowledgments + +感谢BiRefNet仓库的所有作者 [ZhengPeng7/BiRefNet](https://github.com/zhengpeng7/birefnet) + +Thanks to BiRefNet repo owner [ZhengPeng7/BiRefNet](https://github.com/zhengpeng7/birefnet) + +部分代码参考了 [ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO](https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO) 感谢! + +Some of the code references [ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO](https://github.com/ZHO-ZHO-ZHO/ComfyUI-BiRefNet-ZHO) Thanks! + +## 关注历史 | star history + +[![Star History Chart](https://api.star-history.com/svg?repos=MoonHugo/ComfyUI-BiRefNet-Hugo&type=Date)](https://star-history.com/#MoonHugo/ComfyUI-BiRefNet-Hugo&Date) \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d721463 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/assets/2de5b085-1125-46f9-8ef3-06706743f182.png b/assets/2de5b085-1125-46f9-8ef3-06706743f182.png new file mode 100644 index 0000000..6d07113 Binary files /dev/null and b/assets/2de5b085-1125-46f9-8ef3-06706743f182.png differ diff --git a/assets/d0a22b2a-ceb3-4205-9b4e-f6a68e4337c7.png b/assets/d0a22b2a-ceb3-4205-9b4e-f6a68e4337c7.png new file mode 100644 index 0000000..53a1562 Binary files /dev/null and b/assets/d0a22b2a-ceb3-4205-9b4e-f6a68e4337c7.png differ diff --git a/assets/demo1.gif b/assets/demo1.gif new file mode 100644 index 0000000..9e7ccaa Binary files /dev/null and b/assets/demo1.gif differ diff --git a/assets/demo2.gif b/assets/demo2.gif new file mode 100644 index 0000000..e7549bb Binary files /dev/null and b/assets/demo2.gif differ diff --git a/assets/demo3.gif b/assets/demo3.gif new file mode 100644 index 0000000..b2cee1f Binary files /dev/null and b/assets/demo3.gif differ diff --git a/config.py b/config.py new file mode 100644 index 0000000..3ddd09f --- /dev/null +++ b/config.py @@ -0,0 +1,148 @@ +### config.py + +import os +import math +from folder_paths import models_dir +os.environ['HOME'] = os.path.expanduser("~") +class Config(): + def __init__(self) -> None: + # PATH settings + self.sys_home_dir = os.environ['HOME'] # Make up your file system as: SYS_HOME_DIR/codes/dis/BiRefNet, SYS_HOME_DIR/datasets/dis/xx, SYS_HOME_DIR/weights/xx + # TASK settings + self.task = ['DIS5K', 'COD', 'HRSOD', 'DIS5K+HRSOD+HRS10K', 'P3M-10k'][0] + self.training_set = { + 'DIS5K': ['DIS-TR', 'DIS-TR+DIS-TE1+DIS-TE2+DIS-TE3+DIS-TE4'][0], + 'COD': 'TR-COD10K+TR-CAMO', + 'HRSOD': ['TR-DUTS', 'TR-HRSOD', 'TR-UHRSD', 'TR-DUTS+TR-HRSOD', 'TR-DUTS+TR-UHRSD', 'TR-HRSOD+TR-UHRSD', 'TR-DUTS+TR-HRSOD+TR-UHRSD'][5], + 'DIS5K+HRSOD+HRS10K': 'DIS-TE1+DIS-TE2+DIS-TE3+DIS-TE4+DIS-TR+TE-HRS10K+TE-HRSOD+TE-UHRSD+TR-HRS10K+TR-HRSOD+TR-UHRSD', # leave DIS-VD for evaluation. + 'P3M-10k': 'TR-P3M-10k', + }[self.task] + self.prompt4loc = ['dense', 'sparse'][0] + + # Faster-Training settings + self.load_all = True + self.compile = True # 1. Trigger CPU memory leak in some extend, which is an inherent problem of PyTorch. + # Machines with > 70GB CPU memory can run the whole training on DIS5K with default setting. + # 2. Higher PyTorch version may fix it: https://github.com/pytorch/pytorch/issues/119607. + # 3. But compile in Pytorch > 2.0.1 seems to bring no acceleration for training. + self.precisionHigh = True + + # MODEL settings + self.ms_supervision = True + self.out_ref = self.ms_supervision and True + self.dec_ipt = True + self.dec_ipt_split = True + self.cxt_num = [0, 3][1] # multi-scale skip connections from encoder + self.mul_scl_ipt = ['', 'add', 'cat'][2] + self.dec_att = ['', 'ASPP', 'ASPPDeformable'][2] + self.squeeze_block = ['', 'BasicDecBlk_x1', 'ResBlk_x4', 'ASPP_x3', 'ASPPDeformable_x3'][1] + self.dec_blk = ['BasicDecBlk', 'ResBlk', 'HierarAttDecBlk'][0] + + # TRAINING settings + self.batch_size = 4 + self.IoU_finetune_last_epochs = [ + 0, + { + 'DIS5K': -50, + 'COD': -20, + 'HRSOD': -20, + 'DIS5K+HRSOD+HRS10K': -20, + 'P3M-10k': -20, + }[self.task] + ][1] # choose 0 to skip + self.lr = (1e-4 if 'DIS5K' in self.task else 1e-5) * math.sqrt(self.batch_size / 4) # DIS needs high lr to converge faster. Adapt the lr linearly + self.size = 1024 + self.num_workers = max(4, self.batch_size) # will be decrease to min(it, batch_size) at the initialization of the data_loader + + # Backbone settings + self.bb = [ + 'vgg16', 'vgg16bn', 'resnet50', # 0, 1, 2 + 'swin_v1_t', 'swin_v1_s', # 3, 4 + 'swin_v1_b', 'swin_v1_l', # 5-bs9, 6-bs4 + 'pvt_v2_b0', 'pvt_v2_b1', # 7, 8 + 'pvt_v2_b2', 'pvt_v2_b5', # 9-bs10, 10-bs5 + ][6] + self.lateral_channels_in_collection = { + 'vgg16': [512, 256, 128, 64], 'vgg16bn': [512, 256, 128, 64], 'resnet50': [1024, 512, 256, 64], + 'pvt_v2_b2': [512, 320, 128, 64], 'pvt_v2_b5': [512, 320, 128, 64], + 'swin_v1_b': [1024, 512, 256, 128], 'swin_v1_l': [1536, 768, 384, 192], + 'swin_v1_t': [768, 384, 192, 96], 'swin_v1_s': [768, 384, 192, 96], + 'pvt_v2_b0': [256, 160, 64, 32], 'pvt_v2_b1': [512, 320, 128, 64], + }[self.bb] + if self.mul_scl_ipt == 'cat': + self.lateral_channels_in_collection = [channel * 2 for channel in self.lateral_channels_in_collection] + self.cxt = self.lateral_channels_in_collection[1:][::-1][-self.cxt_num:] if self.cxt_num else [] + + # MODEL settings - inactive + self.lat_blk = ['BasicLatBlk'][0] + self.dec_channels_inter = ['fixed', 'adap'][0] + self.refine = ['', 'itself', 'RefUNet', 'Refiner', 'RefinerPVTInChannels4'][0] + self.progressive_ref = self.refine and True + self.ender = self.progressive_ref and False + self.scale = self.progressive_ref and 2 + self.auxiliary_classification = False # Only for DIS5K, where class labels are saved in `dataset.py`. + self.refine_iteration = 1 + self.freeze_bb = False + self.model = [ + 'BiRefNet', + ][0] + if self.dec_blk == 'HierarAttDecBlk': + self.batch_size = 2 ** [0, 1, 2, 3, 4][2] + + # TRAINING settings - inactive + self.preproc_methods = ['flip', 'enhance', 'rotate', 'pepper', 'crop'][:4] + self.optimizer = ['Adam', 'AdamW'][1] + self.lr_decay_epochs = [1e5] # Set to negative N to decay the lr in the last N-th epoch. + self.lr_decay_rate = 0.5 + # Loss + self.lambdas_pix_last = { + # not 0 means opening this loss + # original rate -- 1 : 30 : 1.5 : 0.2, bce x 30 + 'bce': 30 * 1, # high performance + 'iou': 0.5 * 1, # 0 / 255 + 'iou_patch': 0.5 * 0, # 0 / 255, win_size = (64, 64) + 'mse': 150 * 0, # can smooth the saliency map + 'triplet': 3 * 0, + 'reg': 100 * 0, + 'ssim': 10 * 1, # help contours, + 'cnt': 5 * 0, # help contours + 'structure': 5 * 0, # structure loss from codes of MVANet. A little improvement on DIS-TE[1,2,3], a bit more decrease on DIS-TE4. + } + self.lambdas_cls = { + 'ce': 5.0 + } + # Adv + self.lambda_adv_g = 10. * 0 # turn to 0 to avoid adv training + self.lambda_adv_d = 3. * (self.lambda_adv_g > 0) + + # PATH settings - inactive + self.data_root_dir = os.path.join(self.sys_home_dir, 'datasets/dis') + self.weights_root_dir = os.path.join(self.sys_home_dir, 'weights') + self.weights = { + 'pvt_v2_b2': os.path.join(self.weights_root_dir, 'pvt_v2_b2.pth'), + 'pvt_v2_b5': os.path.join(self.weights_root_dir, ['pvt_v2_b5.pth', 'pvt_v2_b5_22k.pth'][0]), + 'swin_v1_b': os.path.join(self.weights_root_dir, ['swin_base_patch4_window12_384_22kto1k.pth', 'swin_base_patch4_window12_384_22k.pth'][0]), + 'swin_v1_l': os.path.join(self.weights_root_dir, ['swin_large_patch4_window12_384_22kto1k.pth', 'swin_large_patch4_window12_384_22k.pth'][0]), + 'swin_v1_t': os.path.join(self.weights_root_dir, ['swin_tiny_patch4_window7_224_22kto1k_finetune.pth'][0]), + 'swin_v1_s': os.path.join(self.weights_root_dir, ['swin_small_patch4_window7_224_22kto1k_finetune.pth'][0]), + 'pvt_v2_b0': os.path.join(self.weights_root_dir, ['pvt_v2_b0.pth'][0]), + 'pvt_v2_b1': os.path.join(self.weights_root_dir, ['pvt_v2_b1.pth'][0]), + } + + # Callbacks - inactive + self.verbose_eval = True + self.only_S_MAE = False + self.use_fp16 = False # Bugs. It may cause nan in training. + self.SDPA_enabled = False # Bugs. Slower and errors occur in multi-GPUs + + # others + self.device = [0, 'cpu'][0] # .to(0) == .to('cuda:0') + + self.batch_size_valid = 1 + self.rand_seed = 7 + # run_sh_file = [f for f in os.listdir('.') if 'train.sh' == f] + [os.path.join('..', f) for f in os.listdir('..') if 'train.sh' == f] + # with open(run_sh_file[0], 'r') as f: + # lines = f.readlines() + # self.save_last = int([l.strip() for l in lines if '"{}")'.format(self.task) in l and 'val_last=' in l][0].split('val_last=')[-1].split()[0]) + # self.save_step = int([l.strip() for l in lines if '"{}")'.format(self.task) in l and 'step=' in l][0].split('step=')[-1].split()[0]) + # self.val_step = [0, self.save_step][0] \ No newline at end of file diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..f99438b --- /dev/null +++ b/nodes.py @@ -0,0 +1,95 @@ +from transformers import AutoModelForImageSegmentation +import torch +from torchvision import transforms +import numpy as np +from PIL import Image +import torch.nn.functional as F +from .config import Config + +Config() + +torch.set_float32_matmul_precision(["high", "highest"][0]) + +birefnet = AutoModelForImageSegmentation.from_pretrained( + "ZhengPeng7/BiRefNet", trust_remote_code=True +) + +birefnet.to("cuda") + +transform_image = transforms.Compose( + [ + transforms.Resize((1024, 1024)), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] +) + +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 + + +class BiRefNet_Hugo: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + } + } + + RETURN_TYPES = ("IMAGE", "MASK",) + RETURN_NAMES = ("image", "mask",) + FUNCTION = "background_remove" + CATEGORY = "🔥BiRefNet" + + def background_remove(self, image): + processed_images = [] + processed_masks = [] + 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) + if torch.cuda.is_available(): + im_tensor=im_tensor.cuda() + 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)) + new_im = Image.new("RGBA", pil_im.size, (0,0,0,0)) + new_im.paste(orig_image, mask=pil_im) + new_im_tensor = pil2tensor(new_im) + pil_im_tensor = pil2tensor(pil_im) + processed_images.append(new_im_tensor) + processed_masks.append(pil_im_tensor) + + 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_Hugo": BiRefNet_Hugo +} + +# A dictionary that contains the friendly/humanly readable titles for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "BiRefNet_Hugo": "🔥BiRefNet" +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..22380da --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy +timm \ No newline at end of file diff --git a/workflow/image.webp b/workflow/image.webp new file mode 100644 index 0000000..73f818b Binary files /dev/null and b/workflow/image.webp differ diff --git a/workflow/video_workflow.json b/workflow/video_workflow.json new file mode 100644 index 0000000..6ef5786 --- /dev/null +++ b/workflow/video_workflow.json @@ -0,0 +1,378 @@ +{ + "last_node_id": 15, + "last_link_id": 16, + "nodes": [ + { + "id": 11, + "type": "VHS_LoadVideo", + "pos": [ + 840, + -90 + ], + "size": [ + 210, + 480 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "meta_batch", + "type": "VHS_BatchManager", + "link": null, + "label": "批次管理" + }, + { + "name": "vae", + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 12 + ], + "shape": 3, + "label": "图像", + "slot_index": 0 + }, + { + "name": "frame_count", + "type": "INT", + "links": null, + "shape": 3, + "label": "帧计数" + }, + { + "name": "audio", + "type": "AUDIO", + "links": null, + "shape": 3, + "label": "音频" + }, + { + "name": "video_info", + "type": "VHS_VIDEOINFO", + "links": null, + "shape": 3, + "label": "视频信息" + } + ], + "properties": { + "Node name for S&R": "VHS_LoadVideo" + }, + "widgets_values": { + "video": "6月28日(1).mp4", + "force_rate": 0, + "force_size": "Disabled", + "custom_width": 512, + "custom_height": 512, + "frame_load_cap": 0, + "skip_first_frames": 0, + "select_every_nth": 1, + "choose video to upload": "image", + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "frame_load_cap": 0, + "skip_first_frames": 0, + "force_rate": 0, + "filename": "6月28日(1).mp4", + "type": "input", + "format": "video/mp4", + "select_every_nth": 1 + }, + "muted": false + } + } + }, + { + "id": 1, + "type": "BiRefNet_Hugo", + "pos": [ + 1110, + -60 + ], + "size": { + "0": 210, + "1": 60 + }, + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 12, + "slot_index": 0, + "label": "image" + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 14 + ], + "slot_index": 0, + "shape": 3, + "label": "image" + }, + { + "name": "mask", + "type": "MASK", + "links": [ + 15 + ], + "slot_index": 1, + "shape": 3, + "label": "mask" + } + ], + "properties": { + "Node name for S&R": "BiRefNet_Hugo" + } + }, + { + "id": 14, + "type": "MaskToImage", + "pos": [ + 1410, + 30 + ], + "size": { + "0": 210, + "1": 30 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 15, + "label": "遮罩" + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 16 + ], + "shape": 3, + "label": "图像", + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 15, + "type": "VHS_VideoCombine", + "pos": [ + 1410, + 120 + ], + "size": [ + 360, + 660 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 16, + "label": "图像" + }, + { + "name": "audio", + "type": "AUDIO", + "link": null, + "label": "音频" + }, + { + "name": "meta_batch", + "type": "VHS_BatchManager", + "link": null, + "label": "批次管理" + }, + { + "name": "vae", + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null, + "shape": 3, + "label": "文件名" + } + ], + "properties": { + "Node name for S&R": "VHS_VideoCombine" + }, + "widgets_values": { + "frame_rate": 30, + "loop_count": 0, + "filename_prefix": "AnimateDiff", + "format": "video/h265-mp4", + "pix_fmt": "yuv420p10le", + "crf": 22, + "save_metadata": true, + "pingpong": false, + "save_output": true, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "AnimateDiff_00005.mp4", + "subfolder": "", + "type": "output", + "format": "video/h265-mp4", + "frame_rate": 30 + }, + "muted": false + } + } + }, + { + "id": 13, + "type": "VHS_VideoCombine", + "pos": [ + 1830, + -180 + ], + "size": [ + 360, + 660 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 14, + "label": "图像" + }, + { + "name": "audio", + "type": "AUDIO", + "link": null, + "label": "音频" + }, + { + "name": "meta_batch", + "type": "VHS_BatchManager", + "link": null, + "label": "批次管理" + }, + { + "name": "vae", + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null, + "shape": 3, + "label": "文件名" + } + ], + "properties": { + "Node name for S&R": "VHS_VideoCombine" + }, + "widgets_values": { + "frame_rate": 30, + "loop_count": 0, + "filename_prefix": "AnimateDiff", + "format": "video/h265-mp4", + "pix_fmt": "yuv420p10le", + "crf": 22, + "save_metadata": true, + "pingpong": false, + "save_output": true, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "AnimateDiff_00004.mp4", + "subfolder": "", + "type": "output", + "format": "video/h265-mp4", + "frame_rate": 30 + }, + "muted": false + } + } + } + ], + "links": [ + [ + 12, + 11, + 0, + 1, + 0, + "IMAGE" + ], + [ + 14, + 1, + 0, + 13, + 0, + "IMAGE" + ], + [ + 15, + 1, + 1, + 14, + 0, + "MASK" + ], + [ + 16, + 14, + 0, + 15, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.8390545288824037, + "offset": [ + -471.7394319558889, + 296.200180526101 + ] + } + }, + "version": 0.4 +} \ No newline at end of file diff --git a/workflow/workflow.json b/workflow/workflow.json new file mode 100644 index 0000000..ed4f754 --- /dev/null +++ b/workflow/workflow.json @@ -0,0 +1,231 @@ +{ + "last_node_id": 5, + "last_link_id": 4, + "nodes": [ + { + "id": 1, + "type": "BiRefNet_Hugo", + "pos": [ + 921, + 256 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 1, + "label": "image", + "slot_index": 0 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 2 + ], + "shape": 3, + "label": "image", + "slot_index": 0 + }, + { + "name": "mask", + "type": "MASK", + "links": [ + 3 + ], + "shape": 3, + "label": "mask", + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "BiRefNet_Hugo" + } + }, + { + "id": 4, + "type": "MaskToImage", + "pos": [ + 922, + 352 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 3, + "label": "遮罩" + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 4 + ], + "shape": 3, + "label": "图像", + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 5, + "type": "PreviewImage", + "pos": [ + 1299, + 410 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 4, + "label": "图像" + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 3, + "type": "PreviewImage", + "pos": [ + 1295, + 74 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 2, + "label": "图像" + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 561, + 254 + ], + "size": [ + 315, + 314 + ], + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1 + ], + "shape": 3, + "label": "图像" + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3, + "label": "遮罩" + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "image.webp", + "image" + ] + } + ], + "links": [ + [ + 1, + 2, + 0, + 1, + 0, + "IMAGE" + ], + [ + 2, + 1, + 0, + 3, + 0, + "IMAGE" + ], + [ + 3, + 1, + 1, + 4, + 0, + "MASK" + ], + [ + 4, + 4, + 0, + 5, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 1.2100000000000002, + "offset": [ + -299.9616678351899, + 94.8424540865177 + ] + } + }, + "version": 0.4 +} \ No newline at end of file