273 lines
9.0 KiB
Python
273 lines
9.0 KiB
Python
import torch
|
|
import torch.nn.functional as F
|
|
import cv2
|
|
import numpy as np
|
|
|
|
|
|
class MaskSplit:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"mask": ("MASK",),
|
|
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE","MASK")
|
|
RETURN_NAMES = ("segmented_images","segmented_masks")
|
|
FUNCTION = "segment_mask"
|
|
|
|
CATEGORY = "CyberEveLoop🐰"
|
|
|
|
def find_top_left_point(self, mask_np):
|
|
"""找到mask中最左上角的点"""
|
|
# 找到所有非零点
|
|
y_coords, x_coords = np.nonzero(mask_np)
|
|
if len(x_coords) == 0:
|
|
return float('inf'), float('inf')
|
|
|
|
# 找到最小x值
|
|
min_x = np.min(x_coords)
|
|
# 在最小x值的点中找到最小y值
|
|
min_y = np.min(y_coords[x_coords == min_x])
|
|
|
|
return min_x, min_y
|
|
|
|
def segment_mask(self, mask, image):
|
|
"""使用OpenCV快速分割蒙版并处理图像"""
|
|
# 保存原始设备信息
|
|
device = mask.device if isinstance(mask, torch.Tensor) else torch.device('cpu')
|
|
|
|
# 确保mask是正确的形状并转换为numpy数组
|
|
if isinstance(mask, torch.Tensor):
|
|
if len(mask.shape) == 2:
|
|
mask = mask.unsqueeze(0)
|
|
mask_np = (mask[0] * 255).cpu().numpy().astype(np.uint8)
|
|
else:
|
|
mask_np = (mask * 255).astype(np.uint8)
|
|
|
|
# 使用OpenCV找到轮廓
|
|
contours, hierarchy = cv2.findContours(
|
|
mask_np,
|
|
cv2.RETR_TREE,
|
|
cv2.CHAIN_APPROX_SIMPLE
|
|
)
|
|
|
|
mask_info = [] # 用于排序的信息列表
|
|
|
|
if hierarchy is not None and len(contours) > 0:
|
|
hierarchy = hierarchy[0]
|
|
contour_masks = {}
|
|
|
|
# 创建每个轮廓的mask
|
|
for i, contour in enumerate(contours):
|
|
mask = np.zeros_like(mask_np)
|
|
cv2.drawContours(mask, [contour], -1, 255, -1)
|
|
contour_masks[i] = mask
|
|
|
|
# 处理每个轮廓
|
|
processed_indices = set()
|
|
|
|
for i, (contour, h) in enumerate(zip(contours, hierarchy)):
|
|
if i in processed_indices:
|
|
continue
|
|
|
|
current_mask = contour_masks[i].copy()
|
|
child_idx = h[2]
|
|
|
|
if child_idx != -1:
|
|
while child_idx != -1:
|
|
current_mask = cv2.subtract(current_mask, contour_masks[child_idx])
|
|
processed_indices.add(child_idx)
|
|
child_idx = hierarchy[child_idx][0]
|
|
|
|
# 找到最左上角的点
|
|
min_x, min_y = self.find_top_left_point(current_mask)
|
|
|
|
# 转换为tensor
|
|
mask_tensor = torch.from_numpy(current_mask).float() / 255.0
|
|
mask_tensor = mask_tensor.unsqueeze(0)
|
|
mask_tensor = mask_tensor.to(device)
|
|
|
|
# 保存mask和排序信息
|
|
mask_info.append((mask_tensor, min_x, min_y))
|
|
processed_indices.add(i)
|
|
|
|
# 如果没有找到任何轮廓,使用原始mask
|
|
if not mask_info:
|
|
if isinstance(mask, torch.Tensor):
|
|
mask_info.append((mask, 0, 0))
|
|
else:
|
|
mask_tensor = torch.from_numpy(mask).float()
|
|
if len(mask_tensor.shape) == 2:
|
|
mask_tensor = mask_tensor.unsqueeze(0)
|
|
mask_tensor = mask_tensor.to(device)
|
|
mask_info.append((mask_tensor, 0, 0))
|
|
|
|
# 根据最左上角点排序
|
|
mask_info.sort(key=lambda x: (x[1], x[2]))
|
|
|
|
# 确保image是正确的形状
|
|
if len(image.shape) == 3:
|
|
image = image.unsqueeze(0)
|
|
|
|
# 处理masks和images
|
|
result_masks = None
|
|
result_images = None
|
|
|
|
for mask_tensor, _, _ in mask_info:
|
|
# 处理masks
|
|
if result_masks is None:
|
|
result_masks = mask_tensor
|
|
else:
|
|
result_masks = torch.cat([result_masks, mask_tensor], dim=0)
|
|
|
|
# 处理images
|
|
if result_images is None:
|
|
result_images = image.clone()
|
|
else:
|
|
result_images = torch.cat([result_images, image.clone()], dim=0)
|
|
|
|
return (result_images, result_masks)
|
|
|
|
|
|
|
|
class MaskMerge:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"original_image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"processed_images": ("IMAGE", {"forceInput": True}),
|
|
"masks": ("MASK", {"forceInput": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("merged_image",)
|
|
FUNCTION = "merge_masked_images"
|
|
CATEGORY = "CyberEveLoop🐰"
|
|
|
|
def standardize_input(self, image, processed_images=None, masks=None):
|
|
"""
|
|
标准化输入格式
|
|
- image: [H,W,C] -> [1,H,W,C]
|
|
- processed_images: [...] -> [B,H,W,C]
|
|
- masks: [...] -> [B,H,W]
|
|
"""
|
|
# 处理原始图像
|
|
if len(image.shape) == 3:
|
|
image = image.unsqueeze(0)
|
|
assert len(image.shape) == 4, f"Original image must be 4D [B,H,W,C], got shape {image.shape}"
|
|
|
|
# 处理processed_images
|
|
if processed_images is not None:
|
|
if isinstance(processed_images, list):
|
|
processed_images = torch.cat(processed_images, dim=0)
|
|
if len(processed_images.shape) == 3:
|
|
processed_images = processed_images.unsqueeze(0)
|
|
assert len(processed_images.shape) == 4, \
|
|
f"Processed images must be 4D [B,H,W,C], got shape {processed_images.shape}"
|
|
|
|
# 处理masks
|
|
if masks is not None:
|
|
if isinstance(masks, list):
|
|
masks = torch.cat(masks, dim=0)
|
|
if len(masks.shape) == 2:
|
|
masks = masks.unsqueeze(0)
|
|
assert len(masks.shape) == 3, f"Masks must be 3D [B,H,W], got shape {masks.shape}"
|
|
|
|
return image, processed_images, masks
|
|
|
|
def resize_tensor(self, x, size, mode='bilinear'):
|
|
"""调整tensor尺寸的辅助函数"""
|
|
# 确保输入是4D tensor [B,C,H,W]
|
|
orig_dim = x.dim()
|
|
if orig_dim == 3:
|
|
x = x.unsqueeze(0)
|
|
|
|
# 如果是图像 [B,H,W,C],需要转换为 [B,C,H,W]
|
|
if x.shape[-1] in [1, 3, 4]:
|
|
x = x.permute(0, 3, 1, 2)
|
|
|
|
# 执行调整
|
|
x = F.interpolate(x, size=size, mode=mode, align_corners=False if mode in ['bilinear', 'bicubic'] else None)
|
|
|
|
# 转换回原始格式
|
|
if x.shape[1] in [1, 3, 4]:
|
|
x = x.permute(0, 2, 3, 1)
|
|
|
|
# 如果原始输入是3D,去掉batch维度
|
|
if orig_dim == 3:
|
|
x = x.squeeze(0)
|
|
|
|
return x
|
|
|
|
def merge_masked_images(self, original_image, processed_images=None, masks=None):
|
|
"""合并处理后的图像"""
|
|
# 确保输入有效
|
|
if processed_images is None or masks is None:
|
|
return (original_image,)
|
|
|
|
# 标准化输入
|
|
original_image, processed_images, masks = self.standardize_input(
|
|
original_image, processed_images, masks
|
|
)
|
|
|
|
# 创建结果图像的副本
|
|
result = original_image.clone()
|
|
|
|
# 获取目标尺寸
|
|
target_height = original_image.shape[1]
|
|
target_width = original_image.shape[2]
|
|
|
|
# 调整处理图像的尺寸(如果需要)
|
|
if processed_images.shape[1:3] != (target_height, target_width):
|
|
processed_images = self.resize_tensor(
|
|
processed_images,
|
|
(target_height, target_width),
|
|
mode='bilinear'
|
|
)
|
|
|
|
# 调整蒙版尺寸(如果需要)
|
|
if masks.shape[1:3] != (target_height, target_width):
|
|
masks = self.resize_tensor(
|
|
masks,
|
|
(target_height, target_width),
|
|
mode='bilinear'
|
|
)
|
|
|
|
# 扩展蒙版维度以匹配图像通道
|
|
masks = masks.unsqueeze(-1).expand(-1, -1, -1, 3)
|
|
|
|
# 批量处理所有图片
|
|
for i in range(processed_images.shape[0]):
|
|
current_image = processed_images[i:i+1]
|
|
current_mask = masks[i:i+1]
|
|
result = current_mask * current_image + (1 - current_mask) * result
|
|
|
|
assert len(result.shape) == 4, "Output must be 4D [B,H,W,C]"
|
|
return (result,)
|
|
|
|
|
|
Mask_CLASS_MAPPINGS = {
|
|
"CyberEve_MaskSegmentation": MaskSplit,
|
|
"CyberEve_MaskMerge": MaskMerge,
|
|
}
|
|
|
|
Mask_DISPLAY_NAME_MAPPINGS = {
|
|
"CyberEve_MaskSegmentation": "Mask Segmentation🐰",
|
|
"CyberEve_MaskMerge": "Mask Merge🐰",
|
|
}
|
|
|