ComfyUI/comfy/ldm/hunyuan3d/model_/image_processor.py
2025-07-05 21:05:53 +03:00

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()