| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """ Frustum utilities, mostly using numpy. """ |
|
|
| import math |
| import torch |
| import numpy as np |
| from typing import Tuple |
|
|
|
|
| def frustum2planes(frustum: np.ndarray) -> dict: |
| """Convert frustum to planes. |
| Args: |
| frustum: Frustum. |
| Returns: |
| Planes. |
| """ |
| planes = {} |
| |
| |
| a = frustum[3] - frustum[0] |
| b = frustum[1] - frustum[0] |
| normal = np.cross(a, b) |
| d = -np.dot(normal, frustum[0]) |
| planes["near"] = np.array([normal[0], normal[1], normal[2], d]) |
|
|
| |
| a = frustum[5] - frustum[4] |
| b = frustum[7] - frustum[4] |
| normal = np.cross(a, b) |
| d = -np.dot(normal, frustum[4]) |
| planes["far"] = np.array([normal[0], normal[1], normal[2], d]) |
|
|
| |
| a = frustum[5] - frustum[1] |
| b = frustum[0] - frustum[1] |
| normal = np.cross(a, b) |
| d = -np.dot(normal, frustum[1]) |
| planes["left"] = np.array([normal[0], normal[1], normal[2], d]) |
|
|
| |
| a = frustum[3] - frustum[2] |
| b = frustum[6] - frustum[2] |
| normal = np.cross(a, b) |
| d = -np.dot(normal, frustum[2]) |
| planes["right"] = np.array([normal[0], normal[1], normal[2], d]) |
|
|
| |
| a = frustum[4] - frustum[0] |
| b = frustum[3] - frustum[0] |
| normal = np.cross(a, b) |
| d = -np.dot(normal, frustum[0]) |
| planes["top"] = np.array([normal[0], normal[1], normal[2], d]) |
|
|
| |
| a = frustum[2] - frustum[1] |
| b = frustum[5] - frustum[1] |
| normal = np.cross(a, b) |
| d = -np.dot(normal, frustum[1]) |
| planes["bottom"] = np.array([normal[0], normal[1], normal[2], d]) |
|
|
| return planes |
|
|
|
|
| def frustum_culling(points: np.ndarray, frustum: np.ndarray) -> np.ndarray: |
| """Cull points outside frustum. |
| Args: |
| points: Points. |
| frustum: Frustum. |
| Returns: |
| Points inside frustum. |
| """ |
| frustum_planes = frustum2planes(frustum) |
| points = np.concatenate([points, np.ones((len(points), 1))], 1) |
| flags = np.ones(len(points)) |
| for _, plane in frustum_planes.items(): |
| flag = np.dot(points, plane) >= 0 |
| flags = np.logical_and(flags, flag) |
|
|
| return points[flags][:, :3] |
|
|
|
|
| def frustum_transform(frustum: np.ndarray, transform: np.ndarray) -> np.ndarray: |
| """Transform frustum. |
| Args: |
| frustum: Frustum. |
| transform: Transform matrix. |
| Returns: |
| Transformed frustum. |
| """ |
| eight_points = np.concatenate([frustum, np.ones((8, 1))], 1).transpose() |
| frustum = np.dot(transform, eight_points).transpose() |
| return frustum[:, :3] |
|
|
|
|
| def generate_frustum( |
| image_size: Tuple, intrinsic_inv: np.ndarray, |
| depth_min: float, depth_max: float, |
| transform: np.ndarray = None |
| ) -> np.ndarray: |
| """Generate frustum. |
| Args: |
| image_size: Image size. |
| intrinsic_inv: Inverse intrinsic matrix. |
| depth_min: Minimum depth. |
| depth_max: Maximum depth. |
| transform: Transform matrix. |
| Returns: |
| Frustum. |
| """ |
| x = image_size[1] |
| y = image_size[0] |
|
|
| eight_points = np.array([[0.0, 0.0, depth_min, 1.0], |
| [0.0, y * depth_min, depth_min, 1.0], |
| [x * depth_min, y * depth_min, depth_min, 1.0], |
| [x * depth_min, 0.0, depth_min, 1.0], |
| [0.0, 0.0, depth_max, 1.0], |
| [0.0, y * depth_max, depth_max, 1.0], |
| [x * depth_max, y * depth_max, depth_max, 1.0], |
| [x * depth_max, 0.0, depth_max, 1.0]]).transpose() |
|
|
| frustum = np.dot(intrinsic_inv, eight_points) |
|
|
| if transform is not None: |
| frustum = np.dot(transform, frustum) |
|
|
| frustum = frustum.transpose() |
|
|
| return frustum[:, :3] |
|
|
|
|
| def generate_frustum_volume(frustum: np.ndarray, voxel_size: float) -> Tuple: |
| """Generate frustum volume. |
| Args: |
| frustum: Frustum. |
| voxel_size: Voxel size. |
| Returns: |
| Frustum volume. |
| Camera-to-frustum transform. |
| """ |
| max_x = np.max(frustum[:, 0]) / voxel_size |
| max_y = np.max(frustum[:, 1]) / voxel_size |
| max_z = np.max(frustum[:, 2]) / voxel_size |
| min_x = np.min(frustum[:, 0]) / voxel_size |
| min_y = np.min(frustum[:, 1]) / voxel_size |
| min_z = np.min(frustum[:, 2]) / voxel_size |
|
|
| dim_x = math.ceil(max_x - min_x) |
| dim_y = math.ceil(max_y - min_y) |
| dim_z = math.ceil(max_z - min_z) |
|
|
| camera2frustum = np.array([[1.0 / voxel_size, 0, 0, -min_x], |
| [0, 1.0 / voxel_size, 0, -min_y], |
| [0, 0, 1.0 / voxel_size, -min_z], |
| [0, 0, 0, 1.0]]) |
|
|
| return (dim_x, dim_y, dim_z), camera2frustum |
|
|
|
|
| def compute_camera2frustum_transform( |
| intrinsic: torch.Tensor, image_size: Tuple, |
| depth_min: float, depth_max: float, |
| voxel_size: float |
| ) -> torch.Tensor: |
| """Compute camera-to-frustum transform. |
| Args: |
| intrinsic: Intrinsic matrix. |
| image_size: Image size. |
| depth_min: Minimum depth. |
| depth_max: Maximum depth. |
| voxel_size: Voxel size. |
| Returns: |
| Camera-to-frustum transform. |
| """ |
| frustum = generate_frustum(image_size, torch.inverse(intrinsic).numpy(), depth_min, depth_max) |
| _, camera2frustum = generate_frustum_volume(frustum, voxel_size) |
| camera2frustum = torch.from_numpy(camera2frustum).float() |
|
|
| return camera2frustum |
|
|