|
|
import numpy as np |
|
|
import cv2 |
|
|
import torch |
|
|
import torch.nn.functional as F |
|
|
from PIL import Image |
|
|
import utils3d |
|
|
import os |
|
|
|
|
|
|
|
|
|
|
|
def spherical_uv_to_directions(uv: np.ndarray): |
|
|
theta, phi = (1 - uv[..., 0]) * (2 * np.pi), uv[..., 1] * np.pi |
|
|
directions = np.stack([ |
|
|
np.sin(phi) * np.cos(theta), |
|
|
np.sin(phi) * np.sin(theta), |
|
|
np.cos(phi) |
|
|
], axis=-1) |
|
|
return directions |
|
|
|
|
|
|
|
|
def spherical_uv_to_directions_torch(uv: torch.Tensor, device: str = 'cuda'): |
|
|
theta, phi = (1 - uv[..., 0]) * (2 * np.pi), uv[..., 1] * np.pi |
|
|
directions = torch.stack([ |
|
|
torch.sin(phi) * torch.cos(theta), |
|
|
torch.sin(phi) * torch.sin(theta), |
|
|
torch.cos(phi) |
|
|
], axis=-1).to(device) |
|
|
return directions |
|
|
|
|
|
|
|
|
def normal_normalize(normal: np.ndarray): |
|
|
normal_norm = np.linalg.norm(normal, axis=-1, keepdims=True) |
|
|
normal_norm[normal_norm < 1e-6] = 1e-6 |
|
|
return normal / normal_norm |
|
|
|
|
|
|
|
|
def normal_normalize_torch(normal: torch.Tensor): |
|
|
normal_norm = torch.norm(normal, dim=-1, keepdim=True) |
|
|
normal_norm = torch.where( |
|
|
normal_norm < 1e-6, |
|
|
torch.tensor(1e-6, device=normal_norm.device, dtype=normal_norm.dtype), |
|
|
normal_norm |
|
|
) |
|
|
return normal / normal_norm |
|
|
|
|
|
|
|
|
def normal_to_rgb(normal: np.ndarray | torch.Tensor, normal_mask: np.ndarray | torch.Tensor = None): |
|
|
""" normal ([-1,1]) → RGB ([0,255]) """ |
|
|
if torch.is_tensor(normal): |
|
|
normal = normal.detach().cpu().numpy() |
|
|
if normal_mask is not None: |
|
|
normal_mask = normal_mask.detach().cpu().numpy() |
|
|
|
|
|
normal_rgb = (((normal + 1) * 0.5) * 255).astype(np.uint8) |
|
|
|
|
|
if normal_mask is not None: |
|
|
normal_mask_c = np.stack([normal_mask]*3, axis=-1).astype(np.uint8) |
|
|
normal_rgb = normal_rgb * normal_mask_c |
|
|
|
|
|
return normal_rgb |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def depth2normal(depth: np.ndarray, mask: np.ndarray = None, to_rgb: bool = False): |
|
|
h, w = depth.shape[:2] |
|
|
|
|
|
points = depth[:, :, None] * spherical_uv_to_directions(utils3d.numpy.image_uv(width=w, height=h)) |
|
|
|
|
|
if mask is None: |
|
|
mask = np.ones_like(depth, dtype=bool) |
|
|
|
|
|
normal, normal_mask = utils3d.numpy.points_to_normals(points, mask) |
|
|
|
|
|
|
|
|
normal = normal * np.array([-1, -1, 1]) |
|
|
normal = normal_normalize(normal) |
|
|
|
|
|
|
|
|
normal = np.stack([normal[..., 0], normal[..., 2], normal[..., 1]], axis=-1) |
|
|
|
|
|
if to_rgb: |
|
|
return normal, normal_mask, Image.fromarray(normal_to_rgb(normal, normal_mask)) |
|
|
else: |
|
|
return normal, normal_mask |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def depth2normal_torch(depth: torch.Tensor, mask: torch.Tensor = None, to_rgb: bool = False): |
|
|
h, w = depth.shape[-2:] |
|
|
points = depth.unsqueeze(-1) * spherical_uv_to_directions_torch(utils3d.torch.image_uv(width=w, height=h), device=depth.device) |
|
|
|
|
|
if mask is None: |
|
|
mask = torch.ones_like(depth, dtype=torch.uint8) |
|
|
|
|
|
normal, normal_mask = utils3d.torch.points_to_normals(points, mask) |
|
|
|
|
|
|
|
|
normal = normal * torch.tensor([-1, -1, 1], device=normal.device, dtype=normal.dtype) |
|
|
normal = normal_normalize_torch(normal) |
|
|
|
|
|
|
|
|
normal = torch.stack([normal[..., 0], normal[..., 2], normal[..., 1]], dim=-1) |
|
|
|
|
|
if to_rgb: |
|
|
normal_mask_img = normal_mask.squeeze() |
|
|
normal_imgs = [Image.fromarray(normal_to_rgb(normal[i], normal_mask_img[i])) for i in range(normal.shape[0])] |
|
|
return normal, normal_mask, normal_imgs |
|
|
else: |
|
|
return normal, normal_mask |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
import argparse |
|
|
|
|
|
parser = argparse.ArgumentParser() |
|
|
parser.add_argument('--img-path', default='', type=str) |
|
|
|
|
|
args = parser.parse_args() |
|
|
|
|
|
|
|
|
|
|
|
save_out = os.path.dirname(args.img_path) + '/normal' |
|
|
os.makedirs(save_out, exist_ok=True) |
|
|
for depth_path in os.listdir(args.img_path): |
|
|
depth_path = os.path.join(args.img_path, depth_path) |
|
|
|
|
|
depth = np.load(depth_path).astype(np.float32) |
|
|
|
|
|
|
|
|
normal, mask, normal_img = depth2normal(depth, to_rgb=True) |
|
|
normal_img.save(os.path.join(save_out, depth_path.split('/')[-1].replace('.npy', '.png'))) |
|
|
|