Spaces:
Running on Zero
Running on Zero
File size: 4,283 Bytes
510e990 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | # Copyright 2026 Realsee. All rights reserved.
# Licensed under the Apache License, Version 2.0.
"""
Shared I/O and preprocessing utilities for panoramic image data.
These functions are used by both evaluation and training pipelines.
"""
import os
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
import cv2
import numpy as np
def read_image_cv2_360(path: str, rgb: bool = True, shape=(560, 280)) -> np.ndarray:
"""Read and resize a 360 panorama image.
Args:
path: Path to the image file.
rgb: If True, convert BGR to RGB (default: True).
shape: Target (width, height) tuple.
Returns:
Image as numpy array with shape (H, W, 3).
"""
img = cv2.imread(path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
if img.shape[1] != shape[0]:
img = cv2.resize(img, shape, interpolation=cv2.INTER_AREA)
return img
def read_depth_360(path: str, depth_scale=5000.0, shape=(560, 280)) -> np.ndarray:
"""Read and normalize a 360 depth map.
Args:
path: Path to the depth image file.
depth_scale: Scale factor to convert raw depth to meters.
shape: Target (width, height) tuple.
Returns:
Depth map as float32 numpy array with shape (H, W).
"""
d = cv2.imread(path, cv2.IMREAD_UNCHANGED)
if d.shape[1] != shape[0]:
d = cv2.resize(d, shape, interpolation=cv2.INTER_NEAREST)
d = d.astype(np.float32) / depth_scale
return d
def random_rotate_theta(W=560, max_shift_percent=0.5):
"""Generate a random rotation angle for panorama augmentation.
Args:
W: Panorama width in pixels.
max_shift_percent: Maximum horizontal shift as fraction of width.
Returns:
Rotation angle in radians.
"""
max_shift = int(W * max_shift_percent)
shift_pixels = np.random.randint(-max_shift, max_shift + 1)
theta = (shift_pixels * 2 * np.pi) / W
return theta
def rotate_y(theta):
"""Create a 3x3 rotation matrix around the Y-axis.
Args:
theta: Rotation angle in radians.
Returns:
3x3 rotation matrix as float64 numpy array.
"""
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
return np.array(
[[cos_theta, 0, -sin_theta], [0, 1, 0], [sin_theta, 0, cos_theta]],
dtype=np.float64,
)
def pano_depth_to_points(depth_map, pano_shape=(560, 280), crop=True, crop_ratio=0.15):
"""Convert a panorama depth map to 3D point cloud.
Args:
depth_map: 2D depth map (H, W) or flattened array.
pano_shape: Original panorama (width, height) tuple.
crop: Whether the depth map has been vertically cropped.
crop_ratio: Crop ratio applied to top and bottom.
Returns:
Point cloud as numpy array with shape (N, 3).
"""
w, h = pano_shape
if not crop:
px = np.tile(np.arange(w), int(h))
py = np.arange(0, int(h)).repeat(w)
else:
px = np.tile(np.arange(w), int(h * (1 - 2 * crop_ratio)))
py = np.arange(int(crop_ratio * h), int((1 - crop_ratio) * h)).repeat(w)
dist = depth_map.reshape(-1)
lat = (py / h - 0.5) * np.pi
long = (px / w - 0.5) * np.pi * 2.0
y = dist * np.sin(lat)
tmp = dist * np.cos(lat)
x = tmp * np.sin(long)
z = tmp * np.cos(long)
point_map = np.concatenate([i.reshape(-1, 1) for i in (x, y, z)], axis=-1)
return point_map # (h*w, 3)
def crop_panorama(pano, crop_ratio=0.15):
"""Crop the top and bottom of a panorama by a given ratio.
Args:
pano: Input panorama array with shape (H, W, ...).
crop_ratio: Fraction to crop from top and bottom.
Returns:
Cropped panorama.
"""
H, W = pano.shape[:2]
crop_H_top = int(crop_ratio * H)
crop_H_bottom = H - int(crop_ratio * H)
crop_pano = pano[crop_H_top:crop_H_bottom, ...]
return crop_pano
def rotate_panorama(panorama, theta):
"""Horizontally rotate a panorama by shifting pixels.
Args:
panorama: Input panorama array with shape (H, W, ...).
theta: Rotation angle in radians.
Returns:
Shifted panorama.
"""
H, W = panorama.shape[:2]
shift_pixels = int((theta * W) / (2 * np.pi))
shifted = np.roll(panorama, shift_pixels, axis=1)
return shifted
|