Files
2024-08-14 03:19:26 +08:00

203 lines
6.6 KiB
Python
Executable File

from .semantic_sam import build_semantic_sam, SemanticSAMPredictor
import numpy as np
from PIL import Image
import torch
from torchvision import transforms
from torchvision.transforms import ToPILImage
import scipy
import sys, os
import logging
import cv2
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sam_model_list = {"L": {"model_url": "custom_nodes/ComfyUI_SemanticSAM/ckpt/swinl_only_sam_many2many.pth"},
"T": {"model_url": "custom_nodes/ComfyUI_SemanticSAM/ckpt/swint_only_sam_many2many.pth"}}
def list_sam_model():
return list(sam_model_list.keys())
def sort_masks(masks, ious, thresh):
ious = ious[0, 0]
ids = torch.argsort(ious, descending=True)
sort_masks = []
for i, (mask, iou) in enumerate(zip(masks[ids], ious[ids])):
iou = round(float(iou), 2)
mask = mask.cpu().detach().numpy()
if iou < thresh:
continue
mask[mask<=0]=0
mask[mask>0]=255
mask = mask.astype(np.uint8)
mask = mask/255
sort_masks.append(mask)
return sort_masks
class PointPrompt():
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"x_coord": ("INT",{}),
"y_coord": ("INT",{})
},
"optional": {}
}
CATEGORY = "SemanticSAM"
RETURN_TYPES = ("POINTS",)
FUNCTION = "main"
def main(self, x_coord, y_coord):
return ([[x_coord,y_coord]],)
class SemanticSAMLoader():
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_name": (list_sam_model(),)
},
"optional": {}
}
CATEGORY = "SemanticSAM"
RETURN_TYPES = ("SemanticSAM_Model",)
FUNCTION = "main"
def main(self, model_name):
ckpt_path = sam_model_list[model_name]["model_url"]
mask_generator = SemanticSAMPredictor(build_semantic_sam(model_type=model_name, ckpt=ckpt_path))
return (mask_generator,)
class SemanticSAMSegment:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("SemanticSAM_Model", {}),
"image": ("IMAGE", {}),
"points": ("POINTS", {}),
"expand": ("INT", {"default":0, "min":-10, "max":10, "step":1}),
"threshold": ("FLOAT", {"default":0.5, "min":0, "max":1, "step": 0.01}),
"num_masks": ("INT", {"default":6, "min":1, "max":6, "step":1})
},
"optional": {}
}
CATEGORY = "SemanticSAM"
RETURN_TYPES = ("IMAGE","MASK")
FUNCTION = "main"
def main(self, model, image, points: list, expand: int, threshold: float=0.3, num_masks: int=0):
# image [BxHxWxC]
original_image, input_image = self.prepare_image(image)
#print(image.shape, original_image.shape, input_image.shape)
h = image.shape[1]
w = image.shape[2]
# 坐标归一化
points = np.array(points).astype(np.float32)
#print(points, [:,0],points[:,1])
points[:, 0] = points[:, 0]/w
points[:, 1] = points[:, 1]/h
#print(points)
# 分割
masks, ious = model.predict(original_image, input_image, point=points)
masks = masks[0:num_masks]
# sort_mask, np.array, pixel range: [0,1], 二值图
# sort_masks, list
sorted_masks = sort_masks(masks, ious, threshold)
if expand!=0:
sorted_masks = expand_mask(sorted_masks, expand=expand)
rgba_imgs, masks_tensor = masks2rgba(image[0], sorted_masks)
#rint(sorted_masks)
#masks_tensor = torch.from_numpy(sorted_masks)
return (torch.cat(rgba_imgs, dim=0), masks_tensor)
def prepare_image(self, image: torch.tensor):
"""_summary_
Args:
image (torch.tensor): [BxHxWxC]
Returns:
image_ori (np.array): [HxWxC]
images (torch.tensor): [CxHxW]
"""
#image = Image.fromarray(image[0].numpy().astype('uint8'))
t = []
image = image[0].permute(2,0,1) #[B,H,W,C] -> [C,H,W]
#print(image.shape)
t.append(ToPILImage())
t.append(transforms.Resize(640, interpolation=Image.BICUBIC))
transform1 = transforms.Compose(t)
image_ori = transform1(image)
image_ori = np.asarray(image_ori) # [HxWxC]
#print(image_ori.shape)
images = torch.from_numpy(image_ori.copy()).permute(2,0,1).cuda() #permute(2,0,1) CxHxW
#print(images.size())
return image_ori, images
def expand_mask(mask, expand=0, tapered_corners=True):
"""對masks進行膨脹或者腐蝕
Args:
mask (list[np.array]): mask list
expand (int, optional): (扩展大小. Defaults to 0.
tapered_corners (bool, optional): (是否使用锥形角). Defaults to True.
Returns:
(list[np.array]): processed mask list
"""
mask = np.array(mask.astype(np.uint8))
c = 0 if tapered_corners else 1
kernel = np.array([[c, 1, c],
[1, 1, 1],
[c, 1, c]])
mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
out = []
for m in mask:
output = m
for _ in range(abs(expand)):
if expand < 0:
output = scipy.ndimage.grey_erosion(output, footprint=kernel)
else:
output = scipy.ndimage.grey_dilation(output, footprint=kernel)
output = np.array(output)
out.append(output)
return out
def masks2rgba(image, masks):
rgba_imgs = []
masks_tensor = []
for i, mask in enumerate(masks):
mask = cv2.resize(mask, (image.shape[1], image.shape[0]))
rgba_tensor = mask2rgba(image, mask)
rgba_imgs.append(rgba_tensor)
#torch.unsqueeze(mask, dim=0)
masks_tensor.append(torch.from_numpy(mask).unsqueeze(dim=0))
masks_tensor = torch.cat(masks_tensor, dim=0)
return rgba_imgs, masks_tensor
def mask2rgba(image, mask):
#print("mask shape:",mask.shape)
rgba_image = np.zeros((mask.shape[0], mask.shape[1], 4))
# 将 RGB 图像复制到 RGBA 图像的前三个通道
rgba_image[:, :, :3] = image.numpy().astype(np.float32)
# 将 mask 应用到 alpha 通道,其中 mask 为 255(白色)的部分设为完全不透明(255),其他部分设为完全透明(0)
rgba_image[:, :, 3] = np.where(mask == 1, 1, 0)
#cv2.imwrite(f'mask_{i}.png', rgba_image*255)
rgba_tensor = torch.from_numpy(rgba_image)
rgba_tensor = torch.unsqueeze(rgba_tensor, dim=0)
return rgba_tensor