|
""" |
|
@date: 2021/06/19 |
|
@description: |
|
Specification of 4 coordinate systems: |
|
Pixel coordinates (used in panoramic images), the range is related to the image size, |
|
generally converted to UV coordinates first, the first is horizontal coordinates, |
|
increasing to the right, the second is column coordinates, increasing down |
|
|
|
Uv coordinates (used in panoramic images), the range is [0~1], the upper left corner is the origin, |
|
u is the abscissa and increases to the right, V is the column coordinate and increases to the right |
|
|
|
Longitude and latitude coordinates (spherical), the range of longitude lon is [-pi~ PI], |
|
and the range of dimension is [-pi/2~ PI /2]. The center of the panorama is the origin, |
|
and the longitude increases to the right and the dimension increases to the down |
|
|
|
Xyz coordinate (used in 3-dimensional space, of course, |
|
it can also represent longitude and latitude coordinates on the sphere). |
|
If on the sphere, the coordinate mode length is 1, when y is projected to the height of the camera, |
|
the real position information of space points will be obtained |
|
|
|
Correspondence between longitude and latitude coordinates and xyz coordinates: |
|
| -pi/2 |
|
| |
|
lef _ _ _ _ _ |_ _ _ _ _ |
|
-pi / | \ |
|
pi | - - - - - -\ - z 0 mid |
|
right \_ _ _ _ _ /_|_ _ _ _ _ _/ |
|
/ | |
|
/ | |
|
x/ | y pi/2 |
|
""" |
|
|
|
import numpy as np |
|
import torch |
|
import functools |
|
|
|
|
|
@functools.lru_cache() |
|
def get_u(w, is_np, b=None): |
|
u = pixel2uv(np.array(range(w)) if is_np else torch.arange(0, w), w=w, axis=0) |
|
if b is not None: |
|
u = u[np.newaxis].repeat(b) if is_np else u.repeat(b, 1) |
|
return u |
|
|
|
|
|
@functools.lru_cache() |
|
def get_lon(w, is_np, b=None): |
|
lon = pixel2lonlat(np.array(range(w)) if is_np else torch.arange(0, w), w=w, axis=0) |
|
if b is not None: |
|
lon = lon[np.newaxis].repeat(b, axis=0) if is_np else lon.repeat(b, 1) |
|
return lon |
|
|
|
|
|
def pixel2uv(pixel, w=1024, h=512, axis=None): |
|
pixel = pixel.astype(np.float32) if isinstance(pixel, np.ndarray) else pixel.float() |
|
|
|
if axis is None: |
|
u = (pixel[..., 0:1] + 0.5) / w |
|
v = (pixel[..., 1:] + 0.5) / h |
|
elif axis == 0: |
|
u = (pixel + 0.5) / (w * 1.0) |
|
return u |
|
elif axis == 1: |
|
v = (pixel + 0.5) / (h * 1.0) |
|
return v |
|
else: |
|
assert False, "axis error" |
|
|
|
lst = [u, v] |
|
uv = np.concatenate(lst, axis=-1) if isinstance(pixel, np.ndarray) else torch.cat(lst, dim=-1) |
|
return uv |
|
|
|
|
|
def pixel2lonlat(pixel, w=1024, h=512, axis=None): |
|
uv = pixel2uv(pixel, w, h, axis) |
|
lonlat = uv2lonlat(uv, axis) |
|
return lonlat |
|
|
|
|
|
def pixel2xyz(pixel, w=1024, h=512): |
|
lonlat = pixel2lonlat(pixel, w, h) |
|
xyz = lonlat2xyz(lonlat) |
|
return xyz |
|
|
|
|
|
def uv2lonlat(uv, axis=None): |
|
if axis is None: |
|
lon = (uv[..., 0:1] - 0.5) * 2 * np.pi |
|
lat = (uv[..., 1:] - 0.5) * np.pi |
|
elif axis == 0: |
|
lon = (uv - 0.5) * 2 * np.pi |
|
return lon |
|
elif axis == 1: |
|
lat = (uv - 0.5) * np.pi |
|
return lat |
|
else: |
|
assert False, "axis error" |
|
|
|
lst = [lon, lat] |
|
lonlat = np.concatenate(lst, axis=-1) if isinstance(uv, np.ndarray) else torch.cat(lst, dim=-1) |
|
return lonlat |
|
|
|
|
|
def uv2xyz(uv, plan_y=None, spherical=False): |
|
lonlat = uv2lonlat(uv) |
|
xyz = lonlat2xyz(lonlat) |
|
if spherical: |
|
|
|
return xyz |
|
|
|
if plan_y is None: |
|
from utils.boundary import boundary_type |
|
plan_y = boundary_type(uv) |
|
|
|
xyz = xyz * (plan_y / xyz[..., 1])[..., None] |
|
|
|
return xyz |
|
|
|
|
|
def lonlat2xyz(lonlat, plan_y=None): |
|
lon = lonlat[..., 0:1] |
|
lat = lonlat[..., 1:] |
|
cos = np.cos if isinstance(lonlat, np.ndarray) else torch.cos |
|
sin = np.sin if isinstance(lonlat, np.ndarray) else torch.sin |
|
x = cos(lat) * sin(lon) |
|
y = sin(lat) |
|
z = cos(lat) * cos(lon) |
|
lst = [x, y, z] |
|
xyz = np.concatenate(lst, axis=-1) if isinstance(lonlat, np.ndarray) else torch.cat(lst, dim=-1) |
|
|
|
if plan_y is not None: |
|
xyz = xyz * (plan_y / xyz[..., 1])[..., None] |
|
|
|
return xyz |
|
|
|
|
|
|
|
|
|
|
|
def xyz2lonlat(xyz): |
|
atan2 = np.arctan2 if isinstance(xyz, np.ndarray) else torch.atan2 |
|
asin = np.arcsin if isinstance(xyz, np.ndarray) else torch.asin |
|
norm = np.linalg.norm(xyz, axis=-1) if isinstance(xyz, np.ndarray) else torch.norm(xyz, p=2, dim=-1) |
|
xyz_norm = xyz / norm[..., None] |
|
x = xyz_norm[..., 0:1] |
|
y = xyz_norm[..., 1:2] |
|
z = xyz_norm[..., 2:] |
|
lon = atan2(x, z) |
|
lat = asin(y) |
|
lst = [lon, lat] |
|
lonlat = np.concatenate(lst, axis=-1) if isinstance(xyz, np.ndarray) else torch.cat(lst, dim=-1) |
|
return lonlat |
|
|
|
|
|
def xyz2uv(xyz): |
|
lonlat = xyz2lonlat(xyz) |
|
uv = lonlat2uv(lonlat) |
|
return uv |
|
|
|
|
|
def xyz2pixel(xyz, w=1024, h=512): |
|
uv = xyz2uv(xyz) |
|
pixel = uv2pixel(uv, w, h) |
|
return pixel |
|
|
|
|
|
def lonlat2uv(lonlat, axis=None): |
|
if axis is None: |
|
u = lonlat[..., 0:1] / (2 * np.pi) + 0.5 |
|
v = lonlat[..., 1:] / np.pi + 0.5 |
|
elif axis == 0: |
|
u = lonlat / (2 * np.pi) + 0.5 |
|
return u |
|
elif axis == 1: |
|
v = lonlat / np.pi + 0.5 |
|
return v |
|
else: |
|
assert False, "axis error" |
|
|
|
lst = [u, v] |
|
uv = np.concatenate(lst, axis=-1) if isinstance(lonlat, np.ndarray) else torch.cat(lst, dim=-1) |
|
return uv |
|
|
|
|
|
def lonlat2pixel(lonlat, w=1024, h=512, axis=None, need_round=True): |
|
uv = lonlat2uv(lonlat, axis) |
|
pixel = uv2pixel(uv, w, h, axis, need_round) |
|
return pixel |
|
|
|
|
|
def uv2pixel(uv, w=1024, h=512, axis=None, need_round=True): |
|
""" |
|
:param uv: [[u1, v1], [u2, v2] ...] |
|
:param w: width of panorama image |
|
:param h: height of panorama image |
|
:param axis: sometimes the input data is only u(axis =0) or only v(axis=1) |
|
:param need_round: |
|
:return: |
|
""" |
|
if axis is None: |
|
pu = uv[..., 0:1] * w - 0.5 |
|
pv = uv[..., 1:] * h - 0.5 |
|
elif axis == 0: |
|
pu = uv * w - 0.5 |
|
if need_round: |
|
pu = pu.round().astype(np.int32) if isinstance(uv, np.ndarray) else pu.round().int() |
|
return pu |
|
elif axis == 1: |
|
pv = uv * h - 0.5 |
|
if need_round: |
|
pv = pv.round().astype(np.int32) if isinstance(uv, np.ndarray) else pv.round().int() |
|
return pv |
|
else: |
|
assert False, "axis error" |
|
|
|
lst = [pu, pv] |
|
if need_round: |
|
pixel = np.concatenate(lst, axis=-1).round().astype(np.int32) if isinstance(uv, np.ndarray) else torch.cat(lst, |
|
dim=-1).round().int() |
|
else: |
|
pixel = np.concatenate(lst, axis=-1) if isinstance(uv, np.ndarray) else torch.cat(lst, dim=-1) |
|
pixel[..., 0] = pixel[..., 0] % w |
|
pixel[..., 1] = pixel[..., 1] % h |
|
|
|
return pixel |
|
|
|
|
|
|
|
|
|
|
|
def xyz2depth(xyz, plan_y=1): |
|
""" |
|
:param xyz: |
|
:param plan_y: |
|
:return: |
|
""" |
|
xyz = xyz * (plan_y / xyz[..., 1])[..., None] |
|
xz = xyz[..., ::2] |
|
depth = np.linalg.norm(xz, axis=-1) if isinstance(xz, np.ndarray) else torch.norm(xz, dim=-1) |
|
return depth |
|
|
|
|
|
def uv2depth(uv, plan_y=None): |
|
if plan_y is None: |
|
from utils.boundary import boundary_type |
|
plan_y = boundary_type(uv) |
|
|
|
xyz = uv2xyz(uv, plan_y) |
|
depth = xyz2depth(xyz, plan_y) |
|
return depth |
|
|
|
|
|
def lonlat2depth(lonlat, plan_y=None): |
|
if plan_y is None: |
|
from utils.boundary import boundary_type |
|
plan_y = boundary_type(lonlat2uv(lonlat)) |
|
|
|
xyz = lonlat2xyz(lonlat, plan_y) |
|
depth = xyz2depth(xyz, plan_y) |
|
return depth |
|
|
|
|
|
def depth2xyz(depth, plan_y=1): |
|
""" |
|
:param depth: [patch_num] or [b, patch_num] |
|
:param plan_y: |
|
:return: |
|
""" |
|
is_np = isinstance(depth, np.ndarray) |
|
w = depth.shape[-1] |
|
|
|
lon = get_lon(w, is_np, b=depth.shape[0] if len(depth.shape) == 2 else None) |
|
if not is_np: |
|
lon = lon.to(depth.device) |
|
|
|
cos = np.cos if is_np else torch.cos |
|
sin = np.sin if is_np else torch.sin |
|
|
|
if len(depth.shape) == 2: |
|
b = depth.shape[0] |
|
xyz = np.zeros((b, w, 3)) if is_np else torch.zeros((b, w, 3)) |
|
else: |
|
xyz = np.zeros((w, 3)) if is_np else torch.zeros((w, 3)) |
|
|
|
if not is_np: |
|
xyz = xyz.to(depth.device) |
|
|
|
xyz[..., 0] = depth * sin(lon) |
|
xyz[..., 1] = plan_y |
|
xyz[..., 2] = depth * cos(lon) |
|
return xyz |
|
|
|
|
|
def depth2uv(depth, plan_y=1): |
|
xyz = depth2xyz(depth, plan_y) |
|
uv = xyz2uv(xyz) |
|
return uv |
|
|
|
|
|
def depth2pixel(depth, w=1024, h=512, need_round=True, plan_y=1): |
|
uv = depth2uv(depth, plan_y) |
|
pixel = uv2pixel(uv, w, h, need_round=need_round) |
|
return pixel |
|
|
|
|
|
if __name__ == '__main__': |
|
a = np.array([[0.5, 1, 0.5]]) |
|
a = xyz2pixel(a) |
|
print(a) |
|
|
|
|
|
if __name__ == '__main__1': |
|
np.set_printoptions(suppress=True) |
|
|
|
a = np.array([[0, 0], [1023, 511]]) |
|
a = pixel2xyz(a) |
|
a = xyz2pixel(a) |
|
print(a) |
|
|
|
|
|
a = torch.tensor([[0, 0], [1023, 511]]) |
|
a = pixel2xyz(a) |
|
a = xyz2pixel(a) |
|
print(a) |
|
|
|
|
|
u = np.array([0, 256, 512, 1023]) |
|
lon = pixel2lonlat(u, axis=0) |
|
u = lonlat2pixel(lon, axis=0) |
|
print(u) |
|
|
|
u = torch.tensor([0, 256, 512, 1023]) |
|
lon = pixel2lonlat(u, axis=0) |
|
u = lonlat2pixel(lon, axis=0) |
|
print(u) |
|
|
|
|
|
v = np.array([0, 256, 511]) |
|
lat = pixel2lonlat(v, axis=1) |
|
v = lonlat2pixel(lat, axis=1) |
|
print(v) |
|
|
|
v = torch.tensor([0, 256, 511]) |
|
lat = pixel2lonlat(v, axis=1) |
|
v = lonlat2pixel(lat, axis=1) |
|
print(v) |
|
|