| import os |
| os.environ['OPENCV_IO_ENABLE_OPENEXR'] = '1' |
| from pathlib import Path |
| from typing import * |
| import itertools |
| import json |
| import warnings |
|
|
| import cv2 |
| import numpy as np |
| from numpy import ndarray |
| from tqdm import tqdm, trange |
| from scipy.sparse import csr_array, hstack, vstack |
| from scipy.ndimage import convolve |
| from scipy.sparse.linalg import lsmr |
|
|
| import utils3d |
|
|
|
|
| def get_panorama_cameras(): |
| vertices, _ = utils3d.numpy.icosahedron() |
| intrinsics = utils3d.numpy.intrinsics_from_fov(fov_x=np.deg2rad(90), fov_y=np.deg2rad(90)) |
| extrinsics = utils3d.numpy.extrinsics_look_at([0, 0, 0], vertices, [0, 0, 1]).astype(np.float32) |
| return extrinsics, [intrinsics] * len(vertices) |
|
|
|
|
| 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 directions_to_spherical_uv(directions: np.ndarray): |
| directions = directions / np.linalg.norm(directions, axis=-1, keepdims=True) |
| u = 1 - np.arctan2(directions[..., 1], directions[..., 0]) / (2 * np.pi) % 1.0 |
| v = np.arccos(directions[..., 2]) / np.pi |
| return np.stack([u, v], axis=-1) |
|
|
|
|
| def split_panorama_image(image: np.ndarray, extrinsics: np.ndarray, intrinsics: np.ndarray, resolution: int): |
| height, width = image.shape[:2] |
| uv = utils3d.numpy.image_uv(width=resolution, height=resolution) |
| splitted_images = [] |
| for i in range(len(extrinsics)): |
| spherical_uv = directions_to_spherical_uv(utils3d.numpy.unproject_cv(uv, extrinsics=extrinsics[i], intrinsics=intrinsics[i])) |
| pixels = utils3d.numpy.uv_to_pixel(spherical_uv, width=width, height=height).astype(np.float32) |
|
|
| splitted_image = cv2.remap(image, pixels[..., 0], pixels[..., 1], interpolation=cv2.INTER_LINEAR) |
| splitted_images.append(splitted_image) |
| return splitted_images |
|
|
|
|
| def poisson_equation(width: int, height: int, wrap_x: bool = False, wrap_y: bool = False) -> Tuple[csr_array, ndarray]: |
| grid_index = np.arange(height * width).reshape(height, width) |
| grid_index = np.pad(grid_index, ((0, 0), (1, 1)), mode='wrap' if wrap_x else 'edge') |
| grid_index = np.pad(grid_index, ((1, 1), (0, 0)), mode='wrap' if wrap_y else 'edge') |
| |
| data = np.array([[-4, 1, 1, 1, 1]], dtype=np.float32).repeat(height * width, axis=0).reshape(-1) |
| indices = np.stack([ |
| grid_index[1:-1, 1:-1], |
| grid_index[:-2, 1:-1], |
| grid_index[2:, 1:-1], |
| grid_index[1:-1, :-2], |
| grid_index[1:-1, 2:] |
| ], axis=-1).reshape(-1) |
| indptr = np.arange(0, height * width * 5 + 1, 5) |
| A = csr_array((data, indices, indptr), shape=(height * width, height * width)) |
| |
| return A |
|
|
|
|
| def grad_equation(width: int, height: int, wrap_x: bool = False, wrap_y: bool = False) -> Tuple[csr_array, np.ndarray]: |
| grid_index = np.arange(width * height).reshape(height, width) |
| if wrap_x: |
| grid_index = np.pad(grid_index, ((0, 0), (0, 1)), mode='wrap') |
| if wrap_y: |
| grid_index = np.pad(grid_index, ((0, 1), (0, 0)), mode='wrap') |
|
|
| data = np.concatenate([ |
| np.concatenate([ |
| np.ones((grid_index.shape[0], grid_index.shape[1] - 1), dtype=np.float32).reshape(-1, 1), |
| -np.ones((grid_index.shape[0], grid_index.shape[1] - 1), dtype=np.float32).reshape(-1, 1), |
| ], axis=1).reshape(-1), |
| np.concatenate([ |
| np.ones((grid_index.shape[0] - 1, grid_index.shape[1]), dtype=np.float32).reshape(-1, 1), |
| -np.ones((grid_index.shape[0] - 1, grid_index.shape[1]), dtype=np.float32).reshape(-1, 1), |
| ], axis=1).reshape(-1), |
| ]) |
| indices = np.concatenate([ |
| np.concatenate([ |
| grid_index[:, :-1].reshape(-1, 1), |
| grid_index[:, 1:].reshape(-1, 1), |
| ], axis=1).reshape(-1), |
| np.concatenate([ |
| grid_index[:-1, :].reshape(-1, 1), |
| grid_index[1:, :].reshape(-1, 1), |
| ], axis=1).reshape(-1), |
| ]) |
| indptr = np.arange(0, grid_index.shape[0] * (grid_index.shape[1] - 1) * 2 + (grid_index.shape[0] - 1) * grid_index.shape[1] * 2 + 1, 2) |
| A = csr_array((data, indices, indptr), shape=(grid_index.shape[0] * (grid_index.shape[1] - 1) + (grid_index.shape[0] - 1) * grid_index.shape[1], height * width)) |
|
|
| return A |
|
|
|
|
| def merge_panorama_depth(width: int, height: int, distance_maps: List[np.ndarray], pred_masks: List[np.ndarray], extrinsics: List[np.ndarray], intrinsics: List[np.ndarray]): |
| if max(width, height) > 256: |
| panorama_depth_init, _ = merge_panorama_depth(width // 2, height // 2, distance_maps, pred_masks, extrinsics, intrinsics) |
| panorama_depth_init = cv2.resize(panorama_depth_init, (width, height), cv2.INTER_LINEAR) |
| else: |
| panorama_depth_init = None |
|
|
| uv = utils3d.numpy.image_uv(width=width, height=height) |
| spherical_directions = spherical_uv_to_directions(uv) |
|
|
| |
| panorama_log_distance_grad_maps, panorama_grad_masks = [], [] |
| panorama_log_distance_laplacian_maps, panorama_laplacian_masks = [], [] |
| panorama_pred_masks = [] |
| for i in range(len(distance_maps)): |
| projected_uv, projected_depth = utils3d.numpy.project_cv(spherical_directions, extrinsics=extrinsics[i], intrinsics=intrinsics[i]) |
| projection_valid_mask = (projected_depth > 0) & (projected_uv > 0).all(axis=-1) & (projected_uv < 1).all(axis=-1) |
| |
| projected_pixels = utils3d.numpy.uv_to_pixel(np.clip(projected_uv, 0, 1), width=distance_maps[i].shape[1], height=distance_maps[i].shape[0]).astype(np.float32) |
| |
| log_splitted_distance = np.log(distance_maps[i]) |
| panorama_log_distance_map = np.where(projection_valid_mask, cv2.remap(log_splitted_distance, projected_pixels[..., 0], projected_pixels[..., 1], cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE), 0) |
| panorama_pred_mask = projection_valid_mask & (cv2.remap(pred_masks[i].astype(np.uint8), projected_pixels[..., 0], projected_pixels[..., 1], cv2.INTER_NEAREST, borderMode=cv2.BORDER_REPLICATE) > 0) |
|
|
| |
| padded = np.pad(panorama_log_distance_map, ((0, 0), (0, 1)), mode='wrap') |
| grad_x, grad_y = padded[:, :-1] - padded[:, 1:], padded[:-1, :] - padded[1:, :] |
|
|
| padded = np.pad(panorama_pred_mask, ((0, 0), (0, 1)), mode='wrap') |
| mask_x, mask_y = padded[:, :-1] & padded[:, 1:], padded[:-1, :] & padded[1:, :] |
| |
| panorama_log_distance_grad_maps.append((grad_x, grad_y)) |
| panorama_grad_masks.append((mask_x, mask_y)) |
|
|
| |
| padded = np.pad(panorama_log_distance_map, ((1, 1), (0, 0)), mode='edge') |
| padded = np.pad(padded, ((0, 0), (1, 1)), mode='wrap') |
| laplacian = convolve(padded, np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]], dtype=np.float32))[1:-1, 1:-1] |
|
|
| padded = np.pad(panorama_pred_mask, ((1, 1), (0, 0)), mode='edge') |
| padded = np.pad(padded, ((0, 0), (1, 1)), mode='wrap') |
| mask = convolve(padded.astype(np.uint8), np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.uint8))[1:-1, 1:-1] == 5 |
|
|
| panorama_log_distance_laplacian_maps.append(laplacian) |
| panorama_laplacian_masks.append(mask) |
| |
| panorama_pred_masks.append(panorama_pred_mask) |
| |
| panorama_log_distance_grad_x = np.stack([grad_map[0] for grad_map in panorama_log_distance_grad_maps], axis=0) |
| panorama_log_distance_grad_y = np.stack([grad_map[1] for grad_map in panorama_log_distance_grad_maps], axis=0) |
| panorama_grad_mask_x = np.stack([mask_map[0] for mask_map in panorama_grad_masks], axis=0) |
| panorama_grad_mask_y = np.stack([mask_map[1] for mask_map in panorama_grad_masks], axis=0) |
|
|
| panorama_log_distance_grad_x = np.sum(panorama_log_distance_grad_x * panorama_grad_mask_x, axis=0) / np.sum(panorama_grad_mask_x, axis=0).clip(1e-3) |
| panorama_log_distance_grad_y = np.sum(panorama_log_distance_grad_y * panorama_grad_mask_y, axis=0) / np.sum(panorama_grad_mask_y, axis=0).clip(1e-3) |
|
|
| panorama_laplacian_maps = np.stack(panorama_log_distance_laplacian_maps, axis=0) |
| panorama_laplacian_masks = np.stack(panorama_laplacian_masks, axis=0) |
| panorama_laplacian_map = np.sum(panorama_laplacian_maps * panorama_laplacian_masks, axis=0) / np.sum(panorama_laplacian_masks, axis=0).clip(1e-3) |
|
|
| grad_x_mask = np.any(panorama_grad_mask_x, axis=0).reshape(-1) |
| grad_y_mask = np.any(panorama_grad_mask_y, axis=0).reshape(-1) |
| grad_mask = np.concatenate([grad_x_mask, grad_y_mask]) |
| laplacian_mask = np.any(panorama_laplacian_masks, axis=0).reshape(-1) |
|
|
| |
| A = vstack([ |
| grad_equation(width, height, wrap_x=True, wrap_y=False)[grad_mask], |
| poisson_equation(width, height, wrap_x=True, wrap_y=False)[laplacian_mask], |
| ]) |
| b = np.concatenate([ |
| panorama_log_distance_grad_x.reshape(-1)[grad_x_mask], |
| panorama_log_distance_grad_y.reshape(-1)[grad_y_mask], |
| panorama_laplacian_map.reshape(-1)[laplacian_mask] |
| ]) |
| x, *_ = lsmr( |
| A, b, |
| atol=1e-5, btol=1e-5, |
| x0=np.log(panorama_depth_init).reshape(-1) if panorama_depth_init is not None else None, |
| show=False, |
| ) |
| |
| panorama_depth = np.exp(x).reshape(height, width).astype(np.float32) |
| panorama_mask = np.any(panorama_pred_masks, axis=0) |
|
|
| return panorama_depth, panorama_mask |
| |
|
|