422 lines
13 KiB
Python
422 lines
13 KiB
Python
import numpy as np
|
|
import equilib as py360
|
|
import torch
|
|
from .distort import plane_to_equirect
|
|
from .misc import *
|
|
import math
|
|
import cv2
|
|
|
|
def bitwise_and_masks(mask1, mask2):
|
|
mask1 = mask1.cpu()
|
|
mask2 = mask2.cpu()
|
|
cv2_mask1 = np.array(mask1)
|
|
cv2_mask2 = np.array(mask2)
|
|
|
|
if cv2_mask1.shape == cv2_mask2.shape:
|
|
cv2_mask = cv2.bitwise_and(cv2_mask1, cv2_mask2)
|
|
return torch.from_numpy(cv2_mask)
|
|
else:
|
|
# do nothing - incompatible mask shape: mostly empty mask
|
|
print("incompatible mask shape: mostly empty mask")
|
|
return mask1
|
|
def calculate_equ_heght(heght):
|
|
# 计算 sqrt(x * x * 3)
|
|
result = math.sqrt(heght * heght * 3)
|
|
|
|
# 将结果四舍五入到最接近的64的倍数
|
|
result = int((result / 64 + 0.5)) * 64
|
|
|
|
if result < 512:
|
|
result = 512
|
|
|
|
return result
|
|
|
|
def calculate_face_heght(heght):
|
|
# 计算 sqrt(x * x / 3)
|
|
result = math.sqrt(heght * heght / 3)
|
|
|
|
# 将结果四舍五入到最接近的64的倍数
|
|
result = int((result / 64 + 0.5)) * 64
|
|
|
|
if result < 512:
|
|
result = 512
|
|
|
|
return result
|
|
|
|
# Equirectangular全景图,Cubemap 立方体贴图 Perspective 透视图
|
|
class PanoImageCube2Equ:
|
|
def __init__(self):
|
|
pass
|
|
|
|
CATEGORY = "PanoCard/convert"
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"height": ("INT", {
|
|
"default": 832,
|
|
"min": 0,
|
|
"step": 8
|
|
}),
|
|
"mask_scale": ("FLOAT", {
|
|
"default": 0,
|
|
"max": 1.0,
|
|
"min": 0,
|
|
"step": 0.01
|
|
}),
|
|
"mask_split": ("BOOLEAN", {"default": True, "label_on": "true", "label_off": "false"}),
|
|
},
|
|
"optional": {
|
|
"face": ("IMAGE",),
|
|
"long": ("IMAGE",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE","MASK","MASK")
|
|
RETURN_NAMES = ("equ","equ_mask","equ_masks")
|
|
OUTPUT_IS_LIST = (False, False, True)
|
|
FUNCTION = "node"
|
|
|
|
def node(self, height, mask_scale, mask_split, face = None, long = None):
|
|
sub_height = 0
|
|
scale = mask_scale
|
|
result = np.array([])
|
|
order = "nearest"
|
|
if long is not None:
|
|
sub_height, sub_width = long.shape[1], long.shape[2]
|
|
|
|
if sub_height*6 != sub_width:
|
|
raise ValueError("传入图像宽高必须为6:1")
|
|
if height == 0:
|
|
height = calculate_equ_heght(sub_height)
|
|
|
|
long_face = long.permute(0, 3, 1, 2)
|
|
equ = py360.cube2equi(
|
|
cubemap = long_face,
|
|
height = height,
|
|
width = height * 2,
|
|
mode = order,
|
|
cube_format = 'horizon')
|
|
|
|
result = equ.permute(1, 2, 0).unsqueeze(0)
|
|
|
|
elif face is not None:
|
|
if not all(img.shape == face[0].shape for img in face):
|
|
raise ValueError("所有图像的尺寸必须相同")
|
|
|
|
sub_height = face[0].shape[0]
|
|
if height == 0:
|
|
height = calculate_equ_heght(sub_height)
|
|
|
|
face = tuple(face.permute(0, 3, 1, 2))
|
|
long_face = torch.cat(face, dim=2)
|
|
equ = py360.cube2equi(
|
|
cubemap = long_face,
|
|
height = height,
|
|
width = height * 2,
|
|
mode = order,
|
|
cube_format = 'horizon')
|
|
|
|
result = equ.permute(1, 2, 0).unsqueeze(0)
|
|
|
|
else:
|
|
if scale < 0.1:
|
|
raise ValueError("传入图像不能为空")
|
|
if height == 0:
|
|
height = 832
|
|
sub_height = height // 2
|
|
|
|
if scale > 0.01:
|
|
# 缩放并居中放置图像
|
|
def create_sub_image(height, scale, value):
|
|
H = int(height * scale)
|
|
# 计算目标位置
|
|
T = (height - H) // 2
|
|
# 创建目标大小的全零图像,形状为 [C, H, W]
|
|
result = torch.zeros((4, height, height), dtype=torch.uint8)
|
|
# 将缩放后的图像居中放置
|
|
result[:, T:T+H, T:T+H] = torch.full((4, H, H), value, dtype=torch.uint8)
|
|
return result
|
|
|
|
# 创建一个形状为 [C, H, W] 的组合图像
|
|
np_image = torch.zeros((4, sub_height, sub_height * 6), dtype=torch.uint8)
|
|
|
|
for i in range(6):
|
|
value = (i + 1) * 10
|
|
sub_image = create_sub_image(sub_height, scale, value)
|
|
# 将子图像拼接到 np_image 中
|
|
np_image[:, :, i * sub_height:(i + 1) * sub_height] = sub_image
|
|
equ = py360.cube2equi(
|
|
cubemap = np_image,
|
|
height = height,
|
|
width = height * 2,
|
|
mode = order,
|
|
cube_format='horizon')
|
|
|
|
equ_mask = equ.permute(1, 2, 0).unsqueeze(0)
|
|
|
|
# 转化输入为空时候,使用equ_mask返回
|
|
if np.size(result) == 0:
|
|
result = equ_mask / 255.0
|
|
result = result.to(torch.float32)
|
|
|
|
#提取透明通道转换为mask
|
|
mask = equ_mask[:, :, :, 3]
|
|
mask = (mask != 0).float()
|
|
|
|
#提取颜色通道转换为mask
|
|
masks = []
|
|
blue_channel = equ_mask[0][:, :, 2]
|
|
for i in range(6):
|
|
# 直接使用蓝色通道的值来创建遮罩
|
|
matching_pixels = (blue_channel == (i+1)*10).float()
|
|
|
|
# 转换为 PyTorch 张量
|
|
masks.append(matching_pixels)
|
|
|
|
masks = torch.stack(masks, dim=0)
|
|
|
|
if result.shape[-1] == 3:
|
|
result = add_alpha_channel(result)
|
|
|
|
if mask_split:
|
|
mask = result[:, :, :, 3]
|
|
mask_a = 1.0 - mask.squeeze(0)
|
|
mask_a = [mask_a] * 6
|
|
mask_a = torch.stack(mask_a, dim=0)
|
|
print(mask_a.shape, masks.shape)
|
|
masks = bitwise_and_masks(mask_a, masks)
|
|
|
|
masks = masks.unsqueeze(0)
|
|
|
|
return (result, mask, masks)
|
|
else:
|
|
# 当scale为0时,返回全透明
|
|
mask = result[:, :, :, 1]
|
|
masks = torch.zeros(1, 6, 64, 64)
|
|
return (result, mask, masks)
|
|
|
|
|
|
class PanoImageEqu2Cube:
|
|
def __init__(self):
|
|
pass
|
|
|
|
CATEGORY = "PanoCard/convert"
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"equ": ("IMAGE",),
|
|
"height": ("INT", {
|
|
"default": 832,
|
|
"min": 0,
|
|
"step": 8
|
|
}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE","MASK")
|
|
RETURN_NAMES = ("face","face_mask")
|
|
OUTPUT_IS_LIST = (True, True)
|
|
FUNCTION = "node"
|
|
|
|
def node(self, equ, height):
|
|
order = "nearest"
|
|
image = equ
|
|
sub_height, sub_width = image.shape[1], image.shape[2]
|
|
|
|
if sub_height*2 != sub_width:
|
|
raise ValueError("传入图像宽高必须为2:1")
|
|
|
|
if height == 0:
|
|
height = calculate_face_heght(sub_height)
|
|
|
|
Rot = [{'yaw': 0.0, 'pitch': 0.0, 'roll': 0.0}]
|
|
equ = image.permute(0, 3, 1, 2)
|
|
long_image = py360.equi2cube(
|
|
equi = equ,
|
|
rots = Rot,
|
|
w_face = height,
|
|
mode = order,
|
|
cube_format = 'horizon')
|
|
long_image = long_image.permute(0, 2, 3, 1)
|
|
|
|
# 分割图像
|
|
face_images = []
|
|
for i in range(6):
|
|
start_idx = i * height
|
|
end_idx = (i + 1) * height
|
|
face_image = long_image[0][:, start_idx:end_idx, :]
|
|
face_images.append(face_image)
|
|
|
|
# 将分割后的图像堆叠成一个新的张量
|
|
face_images = torch.stack(face_images, dim=0) # 形状变为 [6, H, H, C]
|
|
face = face_images.unsqueeze(0)
|
|
|
|
# 添加alpha通道
|
|
if face.shape[-1] == 3:
|
|
face = add_alpha_channel(face)
|
|
|
|
# 简单生成MASK
|
|
mask = face[:, :, :, :, -1]
|
|
|
|
return (face, mask)
|
|
|
|
def calculate_vfov(hfov, width, height):
|
|
# 将水平视场角从度转换为弧度
|
|
hfov_rad = hfov * np.pi / 180
|
|
|
|
# 计算水平方向的半视场角
|
|
half_hfov_rad = hfov_rad / 2
|
|
|
|
# 利用宽高比计算垂直方向的半视场角的正切值
|
|
tan_half_hfov = np.tan(half_hfov_rad)
|
|
tan_half_vfov = tan_half_hfov * (height / width)
|
|
|
|
# 计算垂直方向的半视场角
|
|
half_vfov_rad = np.arctan(tan_half_vfov)
|
|
|
|
# 计算垂直视场角并转换回度
|
|
vfov = 2 * half_vfov_rad * 180 / np.pi
|
|
|
|
return vfov
|
|
|
|
class PanoImageEqu2Equ:
|
|
def __init__(self):
|
|
pass
|
|
|
|
CATEGORY = "PanoCard/convert"
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"equ": ("IMAGE",),
|
|
"u_deg": ("FLOAT", {"default": 0.0,"min": -180,"max": 180,"step": 0.1}),
|
|
"v_deg": ("FLOAT", {"default": 0.0,"min": -90,"max": 90,"step": 0.1}),
|
|
"width": ("INT", {"default": 1664,"min": 0,"step": 8}),
|
|
"height": ("INT", {"default": 832,"min": 0,"step": 8}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", )
|
|
RETURN_NAMES = ("equ", )
|
|
FUNCTION = "node"
|
|
|
|
def node(self, equ, u_deg, v_deg, width, height):
|
|
image = equ.permute(0, 3, 1, 2)
|
|
|
|
if width == 0:
|
|
width = image.shape[3]
|
|
if height == 0:
|
|
height = image.shape[2]
|
|
|
|
# rotations
|
|
Rot = [{
|
|
'roll': 0.,
|
|
'pitch': np.pi * v_deg / 180, # rotate vertical
|
|
'yaw': np.pi * u_deg / 180, # rotate horizontal
|
|
}]
|
|
|
|
pic = py360.equi2equi(
|
|
src = image,
|
|
rots = Rot,
|
|
height = height,
|
|
width = width)
|
|
|
|
result = pic.permute(0, 2, 3, 1)
|
|
return (result,)
|
|
|
|
|
|
class PanoImagePic2Equ:
|
|
def __init__(self):
|
|
pass
|
|
|
|
CATEGORY = "PanoCard/convert"
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE", ),
|
|
"hfov": ("FLOAT", {"default": 60.0, "min": 0.0, "max": 180.0, "step": 0.1}),
|
|
"u_deg": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1}),
|
|
"v_deg": ("FLOAT", {"default": 0.0, "min": -90.0, "max": 90.0, "step": 0.1}),
|
|
"roll": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0, "step": 0.1}),
|
|
"height": ("INT", {"default": 1024,"min": 8,"step": 8})
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", )
|
|
RETURN_NAMES = ("equ", )
|
|
FUNCTION = "node"
|
|
|
|
def node(self, image, hfov, u_deg, v_deg, roll, height):
|
|
|
|
B = image.shape[0]
|
|
processed_images = []
|
|
|
|
x_yaw = -u_deg
|
|
y_pitch = -v_deg
|
|
|
|
for i in range(B):
|
|
# Process the image using the method
|
|
image_i = image[i]
|
|
vfov = calculate_vfov(hfov, image_i.shape[1], image_i.shape[0])
|
|
processed_image = plane_to_equirect(
|
|
image_i,
|
|
hfov,
|
|
vfov,
|
|
x_yaw,
|
|
y_pitch,
|
|
roll,
|
|
height + height
|
|
)
|
|
# Append the processed image to the list
|
|
processed_images.append(processed_image)
|
|
|
|
# Aggregate the processed images into a tensor of shape [B, H, W, C]
|
|
output_tensor = torch.stack(processed_images, dim=0)
|
|
|
|
return (output_tensor,)
|
|
|
|
class PanoImageEqu2Pic:
|
|
def __init__(self):
|
|
pass
|
|
|
|
CATEGORY = "PanoCard/convert"
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"equ": ("IMAGE",),
|
|
"u_deg": ("FLOAT", {"default": 0.0,"min": -180,"max": 180,"step": 0.1}),
|
|
"v_deg": ("FLOAT", {"default": 0.0,"min": -90,"max": 90,"step": 0.1}),
|
|
"hfov": ("FLOAT", {"default": 90.0,"min": 0,"max": 180,"step": 0.1}),
|
|
"width": ("INT", {"default": 512,"min": 8,"step": 8}),
|
|
"height": ("INT", {"default": 512,"min": 8,"step": 8}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", )
|
|
RETURN_NAMES = ("images", )
|
|
FUNCTION = "node"
|
|
|
|
def node(self, equ, u_deg, v_deg, hfov, width, height):
|
|
|
|
image = equ.permute(0, 3, 1, 2)
|
|
|
|
# rotations
|
|
Rot = [{
|
|
'roll': 0.,
|
|
'pitch': np.pi * v_deg / 180, # rotate vertical
|
|
'yaw': np.pi * u_deg / 180, # rotate horizontal
|
|
}]
|
|
|
|
pic = py360.equi2pers(
|
|
equi = image,
|
|
rots = Rot,
|
|
fov_x = hfov,
|
|
height = height,
|
|
width = width)
|
|
|
|
result = pic.permute(0, 2, 3, 1)
|
|
return (result,) |