6.3 KiB
6.3 KiB
Documentation
- Class name: ClipSegNode
- Category: Masquerade Nodes
- Output node: False
- Repo Ref: https://github.com/BadCafeCode/masquerade-nodes-comfyui
ClipSegNode 类旨在根据提供的文本提示自动从输入图像生成掩码。它利用预训练模型理解文本描述并将其应用于视觉内容,创建一个隔离图像中描述主题的分段掩码。
Input types
Required
- image
- 输入图像是节点操作的主要数据源。它是将生成掩码的视觉内容。图像的质量和分辨率直接影响生成的掩码的准确性。
- Comfy dtype: IMAGE
- Python dtype: torch.Tensor
- prompt
- 文本提示是指导节点在图像中识别用于掩码的主题的描述性输入。它对于模型在分割过程中理解要关注的内容至关重要。
- Comfy dtype: STRING
- Python dtype: str
- precision
- 精度参数决定了掩码生成的阈值。它是一个浮点值,决定了节点在决定哪些像素属于掩码时应有多严格。
- Comfy dtype: FLOAT
- Python dtype: float
- normalize
- normalize 参数指示是否应将结果掩码标准化到 0 到 1 之间的范围。在某些需要一致掩码强度的应用中,标准化可能会很有用。
- Comfy dtype: COMBO['no', 'yes']
- Python dtype: Literal['no', 'yes']
Optional
- negative_prompt
- 负面提示是一个可选输入,它通过指示应从分割中排除的内容来帮助细化掩码。它通过向模型提供额外的上下文来提高掩码的精度。
- Comfy dtype: STRING
- Python dtype: str
Output types
- thresholded_mask
- 阈值掩码是节点的主要输出,代表基于提供的文本提示对图像进行二进制分割。它是需要主题隔离以进行进一步处理的应用程序的关键组成部分。
- Comfy dtype: IMAGE
- Python dtype: torch.Tensor
- raw_mask
- 原始掩码输出在应用任何阈值之前提供了分割掩码的连续值表示。它对于需要更精细理解分割的应用程序可能有用。
- Comfy dtype: IMAGE
- Python dtype: torch.Tensor
Usage tips
- Infra type: GPU
Source code
class ClipSegNode:
"""
Automatically calculates a mask based on the text prompt
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {'required': {'image': ('IMAGE',), 'prompt': ('STRING', {'multiline': True}), 'negative_prompt': ('STRING', {'multiline': True}), 'precision': ('FLOAT', {'default': 0.5, 'min': 0.0, 'max': 1.0, 'step': 0.01}), 'normalize': (['no', 'yes'],)}}
RETURN_TYPES = ('IMAGE', 'IMAGE')
RETURN_NAMES = ('thresholded_mask', 'raw_mask')
FUNCTION = 'get_mask'
CATEGORY = 'Masquerade Nodes'
def get_mask(self, image, prompt, negative_prompt, precision, normalize):
model = self.load_model()
image = tensor2rgb(image)
(B, H, W, _) = image.shape
used_dim = max(W, H)
transform = transforms.Compose([transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), transforms.Resize((used_dim, used_dim), antialias=True)])
img = transform(image.permute(0, 3, 1, 2))
prompts = prompt.split(DELIMITER)
negative_prompts = negative_prompt.split(DELIMITER) if negative_prompt != '' else []
with torch.no_grad():
dup_prompts = [item for item in prompts for _ in range(B)]
preds = model(img.repeat(len(prompts), 1, 1, 1), dup_prompts)[0]
dup_neg_prompts = [item for item in negative_prompts for _ in range(B)]
negative_preds = model(img.repeat(len(negative_prompts), 1, 1, 1), dup_neg_prompts)[0] if len(negative_prompts) > 0 else None
preds = torch.nn.functional.interpolate(preds, size=(H, W), mode='nearest')
preds = torch.sigmoid(preds)
preds = preds.reshape(len(prompts), B, H, W)
mask = torch.max(preds, dim=0).values
if len(negative_prompts) > 0:
negative_preds = torch.nn.functional.interpolate(negative_preds, size=(H, W), mode='nearest')
negative_preds = torch.sigmoid(negative_preds)
negative_preds = negative_preds.reshape(len(negative_prompts), B, H, W)
mask_neg = torch.max(negative_preds, dim=0).values
mask = torch.min(mask, 1.0 - mask_neg)
if normalize == 'yes':
mask_min = torch.min(mask)
mask_max = torch.max(mask)
mask_range = mask_max - mask_min
mask = (mask - mask_min) / mask_range
thresholded = torch.where(mask >= precision, 1.0, 0.0)
return (thresholded.to(device=image.device), mask.to(device=image.device))
def load_model(self):
global cached_clipseg_model
if cached_clipseg_model == None:
ensure_package('clipseg', 'clipseg@git+https://github.com/timojl/clipseg.git@bbc86cfbb7e6a47fb6dae47ba01d3e1c2d6158b0')
from clipseg.clipseg import CLIPDensePredT
model = CLIPDensePredT(version='ViT-B/16', reduce_dim=64, complex_trans_conv=True)
model.eval()
d64_file = self.download_and_cache('rd64-uni-refined.pth', 'https://owncloud.gwdg.de/index.php/s/ioHbRzFx6th32hn/download?path=%2F&files=rd64-uni-refined.pth')
d16_file = self.download_and_cache('rd16-uni.pth', 'https://owncloud.gwdg.de/index.php/s/ioHbRzFx6th32hn/download?path=%2F&files=rd16-uni.pth')
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.load_state_dict(torch.load(d64_file, map_location=device), strict=False)
model = model.eval().to(device=device)
cached_clipseg_model = model
return cached_clipseg_model
def download_and_cache(self, cache_name, url):
cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'download_cache')
os.makedirs(cache_dir, exist_ok=True)
file_name = os.path.join(cache_dir, cache_name)
if not os.path.exists(file_name):
print(f'Downloading and caching file: {cache_name}')
with open(file_name, 'wb') as file:
import requests
r = requests.get(url, stream=True)
r.raise_for_status()
for block in r.iter_content(4096):
file.write(block)
print('Finished downloading.')
return file_name