mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-08-20 08:16:38 +08:00
192 lines
5.4 KiB
Python
192 lines
5.4 KiB
Python
import torch
|
|
import torch.nn as nn
|
|
import numpy as np
|
|
from PIL import Image
|
|
import torch.nn.functional as F
|
|
|
|
def to_tensor(image_pt):
|
|
|
|
image_pt = image_pt / 255 * 2 - 1
|
|
if image_pt.dim() == 4:
|
|
image_pt = image_pt.permute(0, 3, 1, 2)
|
|
else:
|
|
image_pt = image_pt.permute(2, 1, 0)
|
|
|
|
return image_pt
|
|
|
|
def resize_bilinear(img: torch.Tensor, size: int) -> torch.Tensor:
|
|
# pytorch implementation of cv2.INTER_LINEAR
|
|
|
|
batched = (img.ndim == 4)
|
|
if img.ndim == 3:
|
|
img = img.unsqueeze(0)
|
|
|
|
B, _, H, W = img.shape
|
|
H_out = W_out = size
|
|
|
|
xs = torch.linspace(0, H_out - 1, H_out, device = img.device)
|
|
ys = torch.linspace(0, W_out - 1, W_out, device = img.device)
|
|
|
|
xs = (xs + 0.5) * (H / H_out) - 0.5
|
|
ys = (ys + 0.5) * (W / W_out) - 0.5
|
|
|
|
# normalize
|
|
xs = 2 * xs / (H - 1) - 1
|
|
ys = 2 * ys / (W - 1) - 1
|
|
|
|
# meshgrid in “ij” order: first rows (xs), then cols (ys)
|
|
grid_i, grid_j = torch.meshgrid(xs, ys, indexing='ij')
|
|
|
|
# stack into (x,y) where x=columns, y=rows
|
|
grid = torch.stack((grid_j, grid_i), dim=-1)
|
|
grid = grid.unsqueeze(0).expand(B, -1, -1, -1)
|
|
|
|
out = F.grid_sample(img, grid, mode = 'bilinear',
|
|
padding_mode = 'zeros', align_corners = True)
|
|
|
|
return out if batched else out.squeeze(0)
|
|
|
|
def resize_bicubic(img: torch.Tensor, size: int) -> torch.Tensor:
|
|
# pytorch implementation of INTER_CUBIC
|
|
|
|
was_batched = img.ndim == 4
|
|
if img.ndim == 3:
|
|
img = img.unsqueeze(0)
|
|
|
|
out = F.interpolate(
|
|
img.permute(0, 3, 2, 1),
|
|
size = (size, size),
|
|
mode = "bicubic",
|
|
align_corners = True
|
|
)
|
|
|
|
return out if was_batched else out.squeeze(0)
|
|
|
|
def resize_area(img: torch.Tensor, size: tuple) -> torch.Tensor:
|
|
# pytorch implementation of INTER_AREA
|
|
|
|
was_batched = img.ndim == 4
|
|
if img.ndim == 3:
|
|
img = img.unsqueeze(0)
|
|
|
|
image = F.interpolate(img.permute(0,3,1,2).float(), (size[1], size[0]), mode = "area")
|
|
|
|
if was_batched:
|
|
image = image.permute(0, 2, 3, 1) # return to channel last
|
|
else:
|
|
image = image.squeeze(0).permute(1, 2, 0)
|
|
|
|
return image if was_batched else image.squeeze(0)
|
|
|
|
class ImageProcessorV2(nn.Module):
|
|
def __init__(self, size: int = 512, border_ratio: float = None):
|
|
self.size = size
|
|
self.border_ratio = border_ratio
|
|
|
|
def load_image(self, pic, border_ratio: float = 0.15) -> torch.Tensor:
|
|
|
|
if isinstance(pic, str):
|
|
img = Image.open(pic)
|
|
img = np.array(img)
|
|
|
|
elif isinstance(pic, Image.Image):
|
|
img = np.array(pic)
|
|
|
|
if img.ndim == 2: # grayscale
|
|
img = img[:, :, None]
|
|
|
|
img = torch.from_numpy(img)
|
|
img, mask = self.recenter(img, border_ratio = border_ratio)
|
|
|
|
img = resize_bicubic(img, size = self.size)
|
|
mask = resize_bilinear(mask.float(), size = self.size)
|
|
mask = mask[..., torch.newaxis]
|
|
|
|
img = to_tensor(img)
|
|
mask = to_tensor(mask)
|
|
|
|
return img, mask
|
|
|
|
@staticmethod
|
|
def recenter(image, border_ratio: float = 0.2):
|
|
|
|
if image.shape[-1] == 4:
|
|
mask = image[..., 3]
|
|
else:
|
|
mask = torch.ones_like(image[..., 0:1]) * 255
|
|
image = torch.concatenate([image, mask], axis=-1)
|
|
mask = mask[..., 0]
|
|
|
|
H, W, C = image.shape
|
|
|
|
size = max(H, W)
|
|
result = torch.zeros((size, size, C), dtype = torch.uint8)
|
|
|
|
# as_tuple to match numpy behaviour
|
|
x_coords, y_coords = torch.nonzero(mask, as_tuple=True)
|
|
|
|
y_min, y_max = y_coords.min(), y_coords.max()
|
|
x_min, x_max = x_coords.min(), x_coords.max()
|
|
|
|
h = x_max - x_min
|
|
w = y_max - y_min
|
|
|
|
if h == 0 or w == 0:
|
|
raise ValueError('input image is empty')
|
|
|
|
desired_size = int(size * (1 - border_ratio))
|
|
scale = desired_size / max(h, w)
|
|
|
|
h2 = int(h * scale)
|
|
w2 = int(w * scale)
|
|
|
|
x2_min = (size - h2) // 2
|
|
x2_max = x2_min + h2
|
|
|
|
y2_min = (size - w2) // 2
|
|
y2_max = y2_min + w2
|
|
|
|
# note: opencv takes columns first (opposite to pytorch and numpy that take the row first)
|
|
result[x2_min:x2_max, y2_min:y2_max] = resize_area(image[x_min:x_max, y_min:y_max], (w2, h2))
|
|
|
|
bg = torch.ones((result.shape[0], result.shape[1], 3), dtype = torch.uint8) * 255
|
|
|
|
mask = result[..., 3:].to(torch.float32) / 255
|
|
result = result[..., :3] * mask + bg * (1 - mask)
|
|
|
|
mask = mask * 255
|
|
result = result.clip(0, 255).to(torch.uint8)
|
|
mask = mask.clip(0, 255).to(torch.uint8)
|
|
|
|
return result, mask
|
|
|
|
def __call__(self, image, border_ratio = 0.15, **kwargs):
|
|
|
|
if self.border_ratio is not None:
|
|
border_ratio = self.border_ratio
|
|
|
|
image, mask = self.load_image(image, border_ratio = border_ratio)
|
|
|
|
outputs = {
|
|
'image': image,
|
|
'mask': mask
|
|
}
|
|
|
|
return outputs
|
|
|
|
def test_image_processor():
|
|
import matplotlib.pyplot as plt
|
|
|
|
image_processor = ImageProcessorV2(size = 224)
|
|
import time
|
|
start = time.time()
|
|
outputs = image_processor(image = r"C:\Users\yrafa\Work\Hunyuan 3D\cat.jpg")
|
|
print(time.time() - start)
|
|
image = outputs["image"]
|
|
print(image.shape)
|
|
plt.imshow(image)
|
|
plt.axis("off")
|
|
plt.show()
|
|
|
|
if __name__ == "__main__":
|
|
test_image_processor() |