| """ |
| @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) |
|
|