|
""" |
|
@date: 2021/06/19 |
|
@description: |
|
""" |
|
import math |
|
import functools |
|
|
|
from scipy import stats |
|
from scipy.ndimage.filters import maximum_filter |
|
import numpy as np |
|
from typing import List |
|
from utils.conversion import uv2xyz, xyz2uv, depth2xyz, uv2pixel, depth2uv, pixel2uv, xyz2pixel, uv2lonlat |
|
from utils.visibility_polygon import calc_visible_polygon |
|
|
|
|
|
def connect_corners_uv(uv1: np.ndarray, uv2: np.ndarray, length=256) -> np.ndarray: |
|
""" |
|
:param uv1: [u, v] |
|
:param uv2: [u, v] |
|
:param length: Fix the total length in pixel coordinates |
|
:return: |
|
""" |
|
|
|
p_u1 = uv1[0] * length - 0.5 |
|
p_u2 = uv2[0] * length - 0.5 |
|
|
|
if abs(p_u1 - p_u2) < length / 2: |
|
start = np.ceil(min(p_u1, p_u2)) |
|
p = max(p_u1, p_u2) |
|
end = np.floor(p) |
|
if end == np.ceil(p): |
|
end = end - 1 |
|
else: |
|
start = np.ceil(max(p_u1, p_u2)) |
|
p = min(p_u1, p_u2) + length |
|
end = np.floor(p) |
|
if end == np.ceil(p): |
|
end = end - 1 |
|
p_us = (np.arange(start, end + 1) % length).astype(np.float64) |
|
if len(p_us) == 0: |
|
return None |
|
us = (p_us + 0.5) / length |
|
|
|
plan_y = boundary_type(np.array([uv1, uv2])) |
|
xyz1 = uv2xyz(np.array(uv1), plan_y) |
|
xyz2 = uv2xyz(np.array(uv2), plan_y) |
|
x1 = xyz1[0] |
|
z1 = xyz1[2] |
|
x2 = xyz2[0] |
|
z2 = xyz2[2] |
|
|
|
d_x = x2 - x1 |
|
d_z = z2 - z1 |
|
|
|
lon_s = (us - 0.5) * 2 * np.pi |
|
k = np.tan(lon_s) |
|
ps = (k * z1 - x1) / (d_x - k * d_z) |
|
cs = np.sqrt((z1 + ps * d_z) ** 2 + (x1 + ps * d_x) ** 2) |
|
|
|
lats = np.arctan2(plan_y, cs) |
|
vs = lats / np.pi + 0.5 |
|
uv = np.stack([us, vs], axis=-1) |
|
|
|
if start == end: |
|
return uv[0:1] |
|
return uv |
|
|
|
|
|
def connect_corners_xyz(uv1: np.ndarray, uv2: np.ndarray, step=0.01) -> np.ndarray: |
|
""" |
|
:param uv1: [u, v] |
|
:param uv2: [u, v] |
|
:param step: Fixed step size in xyz coordinates |
|
:return: |
|
""" |
|
plan_y = boundary_type(np.array([uv1, uv2])) |
|
xyz1 = uv2xyz(np.array(uv1), plan_y) |
|
xyz2 = uv2xyz(np.array(uv2), plan_y) |
|
|
|
vec = xyz2 - xyz1 |
|
norm = np.linalg.norm(vec, ord=2) |
|
direct = vec / norm |
|
xyz = np.array([xyz1 + direct * dis for dis in np.linspace(0, norm, int(norm / step))]) |
|
if len(xyz) == 0: |
|
xyz = np.array([xyz2]) |
|
uv = xyz2uv(xyz) |
|
return uv |
|
|
|
|
|
def connect_corners(uv1: np.ndarray, uv2: np.ndarray, step=0.01, length=None) -> np.ndarray: |
|
""" |
|
:param uv1: [u, v] |
|
:param uv2: [u, v] |
|
:param step: |
|
:param length: |
|
:return: [[u1, v1], [u2, v2]....] if length!=None,length of return result = length |
|
""" |
|
if length is not None: |
|
uv = connect_corners_uv(uv1, uv2, length) |
|
elif step is not None: |
|
uv = connect_corners_xyz(uv1, uv2, step) |
|
else: |
|
uv = np.array([uv1]) |
|
return uv |
|
|
|
|
|
def visibility_corners(corners): |
|
plan_y = boundary_type(corners) |
|
xyz = uv2xyz(corners, plan_y) |
|
xz = xyz[:, ::2] |
|
xz = calc_visible_polygon(center=np.array([0, 0]), polygon=xz, show=False) |
|
xyz = np.insert(xz, 1, plan_y, axis=1) |
|
output = xyz2uv(xyz).astype(np.float32) |
|
return output |
|
|
|
|
|
def corners2boundary(corners: np.ndarray, step=0.01, length=None, visible=True) -> np.ndarray: |
|
""" |
|
When there is occlusion, even if the length is fixed, the final output length may be greater than the given length, |
|
which is more defined as the fixed step size under UV |
|
:param length: |
|
:param step: |
|
:param corners: [[u1, v1], [u2, v2]....] |
|
:param visible: |
|
:return: [[u1, v1], [u2, v2]....] if length!=None,length of return result = length |
|
""" |
|
assert step is not None or length is not None, "the step and length parameters cannot be null at the same time" |
|
if len(corners) < 3: |
|
return corners |
|
|
|
if visible: |
|
corners = visibility_corners(corners) |
|
|
|
n_con = len(corners) |
|
boundary = None |
|
for j in range(n_con): |
|
uv = connect_corners(corners[j], corners[(j + 1) % n_con], step, length) |
|
if uv is None: |
|
continue |
|
if boundary is None: |
|
boundary = uv |
|
else: |
|
boundary = np.concatenate((boundary, uv)) |
|
boundary = np.roll(boundary, -boundary.argmin(axis=0)[0], axis=0) |
|
|
|
output_polygon = [] |
|
for i, p in enumerate(boundary): |
|
q = boundary[(i + 1) % len(boundary)] |
|
if int(p[0] * 10000) == int(q[0] * 10000): |
|
continue |
|
output_polygon.append(p) |
|
output_polygon = np.array(output_polygon, dtype=np.float32) |
|
return output_polygon |
|
|
|
|
|
def corners2boundaries(ratio: float, corners_xyz: np.ndarray = None, corners_uv: np.ndarray = None, step=0.01, |
|
length=None, visible=True): |
|
""" |
|
When both step and length are None, corners are also returned |
|
:param ratio: |
|
:param corners_xyz: |
|
:param corners_uv: |
|
:param step: |
|
:param length: |
|
:param visible: |
|
:return: floor_boundary, ceil_boundary |
|
""" |
|
if corners_xyz is None: |
|
plan_y = boundary_type(corners_uv) |
|
xyz = uv2xyz(corners_uv, plan_y) |
|
floor_xyz = xyz.copy() |
|
ceil_xyz = xyz.copy() |
|
if plan_y > 0: |
|
ceil_xyz[:, 1] *= -ratio |
|
else: |
|
floor_xyz[:, 1] /= -ratio |
|
else: |
|
floor_xyz = corners_xyz.copy() |
|
ceil_xyz = corners_xyz.copy() |
|
if corners_xyz[0][1] > 0: |
|
ceil_xyz[:, 1] *= -ratio |
|
else: |
|
floor_xyz[:, 1] /= -ratio |
|
|
|
floor_uv = xyz2uv(floor_xyz) |
|
ceil_uv = xyz2uv(ceil_xyz) |
|
if step is None and length is None: |
|
return floor_uv, ceil_uv |
|
|
|
floor_boundary = corners2boundary(floor_uv, step, length, visible) |
|
ceil_boundary = corners2boundary(ceil_uv, step, length, visible) |
|
return floor_boundary, ceil_boundary |
|
|
|
|
|
def depth2boundary(depth: np.array, step=0.01, length=None,): |
|
xyz = depth2xyz(depth) |
|
uv = xyz2uv(xyz) |
|
return corners2boundary(uv, step, length, visible=False) |
|
|
|
|
|
def depth2boundaries(ratio: float, depth: np.array, step=0.01, length=None,): |
|
""" |
|
|
|
:param ratio: |
|
:param depth: |
|
:param step: |
|
:param length: |
|
:return: floor_boundary, ceil_boundary |
|
""" |
|
xyz = depth2xyz(depth) |
|
return corners2boundaries(ratio, corners_xyz=xyz, step=step, length=length, visible=False) |
|
|
|
|
|
def boundary_type(corners: np.ndarray) -> int: |
|
""" |
|
Returns the boundary type that also represents the projection plane |
|
:param corners: |
|
:return: |
|
""" |
|
if is_ceil_boundary(corners): |
|
plan_y = -1 |
|
elif is_floor_boundary(corners): |
|
plan_y = 1 |
|
else: |
|
|
|
assert False, 'corners error!' |
|
return plan_y |
|
|
|
|
|
def is_normal_layout(boundaries: List[np.array]): |
|
if len(boundaries) != 2: |
|
print("boundaries length must be 2!") |
|
return False |
|
|
|
if boundary_type(boundaries[0]) != -1: |
|
print("ceil boundary error!") |
|
return False |
|
|
|
if boundary_type(boundaries[1]) != 1: |
|
print("floor boundary error!") |
|
return False |
|
return True |
|
|
|
|
|
def is_ceil_boundary(corners: np.ndarray) -> bool: |
|
m = corners[..., 1].max() |
|
return m < 0.5 |
|
|
|
|
|
def is_floor_boundary(corners: np.ndarray) -> bool: |
|
m = corners[..., 1].min() |
|
return m > 0.5 |
|
|
|
|
|
@functools.lru_cache() |
|
def get_gauss_map(sigma=1.5, width=5): |
|
x = np.arange(width*2 + 1) - width |
|
y = stats.norm(0, sigma).pdf(x) |
|
y = y / y.max() |
|
return y |
|
|
|
|
|
def get_heat_map(u_s, patch_num=256, sigma=2, window_width=15, show=False): |
|
""" |
|
:param window_width: |
|
:param sigma: |
|
:param u_s: [u1, u2, u3, ...] |
|
:param patch_num |
|
:param show |
|
:return: |
|
""" |
|
pixel_us = uv2pixel(u_s, w=patch_num, axis=0) |
|
gauss_map = get_gauss_map(sigma, window_width) |
|
heat_map_all = [] |
|
for u in pixel_us: |
|
heat_map = np.zeros(patch_num, dtype=np.float) |
|
left = u-window_width |
|
right = u+window_width+1 |
|
|
|
offset = 0 |
|
if left < 0: |
|
offset = left |
|
elif right > patch_num: |
|
offset = right - patch_num |
|
|
|
left = left - offset |
|
right = right - offset |
|
heat_map[left:right] = gauss_map |
|
if offset != 0: |
|
heat_map = np.roll(heat_map, offset) |
|
heat_map_all.append(heat_map) |
|
|
|
heat_map_all = np.array(heat_map_all).max(axis=0) |
|
if show: |
|
import matplotlib.pyplot as plt |
|
plt.imshow(heat_map_all[None].repeat(50, axis=0)) |
|
plt.show() |
|
return heat_map_all |
|
|
|
|
|
def find_peaks(signal, size=15*2+1, min_v=0.05, N=None): |
|
|
|
max_v = maximum_filter(signal, size=size, mode='wrap') |
|
pk_loc = np.where(max_v == signal)[0] |
|
pk_loc = pk_loc[signal[pk_loc] > min_v] |
|
if N is not None: |
|
order = np.argsort(-signal[pk_loc]) |
|
pk_loc = pk_loc[order[:N]] |
|
pk_loc = pk_loc[np.argsort(pk_loc)] |
|
return pk_loc, signal[pk_loc] |
|
|
|
|
|
def get_object_cor(depth, size, center_u, patch_num=256): |
|
width_u = size[0, center_u] |
|
height_v = size[1, center_u] |
|
boundary_v = size[2, center_u] |
|
|
|
center_boundary_v = depth2uv(depth[center_u:center_u + 1])[0, 1] |
|
center_bottom_v = center_boundary_v - boundary_v |
|
center_top_v = center_bottom_v - height_v |
|
|
|
base_v = center_boundary_v - 0.5 |
|
assert base_v > 0 |
|
|
|
center_u = pixel2uv(np.array([center_u]), w=patch_num, h=patch_num // 2, axis=0)[0] |
|
|
|
center_boundary_uv = np.array([center_u, center_boundary_v]) |
|
center_bottom_uv = np.array([center_u, center_bottom_v]) |
|
center_top_uv = np.array([center_u, center_top_v]) |
|
|
|
left_u = center_u - width_u / 2 |
|
right_u = center_u + width_u / 2 |
|
|
|
left_u = 1 + left_u if left_u < 0 else left_u |
|
right_u = right_u - 1 if right_u > 1 else right_u |
|
|
|
pixel_u = uv2pixel(np.array([left_u, right_u]), w=patch_num, h=patch_num // 2, axis=0) |
|
left_pixel_u = pixel_u[0] |
|
right_pixel_u = pixel_u[1] |
|
|
|
left_boundary_v = depth2uv(depth[left_pixel_u:left_pixel_u + 1])[0, 1] |
|
right_boundary_v = depth2uv(depth[right_pixel_u:right_pixel_u + 1])[0, 1] |
|
|
|
left_boundary_uv = np.array([left_u, left_boundary_v]) |
|
right_boundary_uv = np.array([right_u, right_boundary_v]) |
|
|
|
xyz = uv2xyz(np.array([left_boundary_uv, right_boundary_uv, center_boundary_uv])) |
|
left_boundary_xyz = xyz[0] |
|
right_boundary_xyz = xyz[1] |
|
|
|
|
|
center_boundary_xyz = xyz[2] |
|
center_bottom_xyz = uv2xyz(np.array([center_bottom_uv]))[0] |
|
center_top_xyz = uv2xyz(np.array([center_top_uv]))[0] |
|
center_boundary_norm = np.linalg.norm(center_boundary_xyz[::2]) |
|
center_bottom_norm = np.linalg.norm(center_bottom_xyz[::2]) |
|
center_top_norm = np.linalg.norm(center_top_xyz[::2]) |
|
center_bottom_xyz = center_bottom_xyz * center_boundary_norm / center_bottom_norm |
|
center_top_xyz = center_top_xyz * center_boundary_norm / center_top_norm |
|
|
|
left_bottom_xyz = left_boundary_xyz.copy() |
|
left_bottom_xyz[1] = center_bottom_xyz[1] |
|
right_bottom_xyz = right_boundary_xyz.copy() |
|
right_bottom_xyz[1] = center_bottom_xyz[1] |
|
|
|
left_top_xyz = left_boundary_xyz.copy() |
|
left_top_xyz[1] = center_top_xyz[1] |
|
right_top_xyz = right_boundary_xyz.copy() |
|
right_top_xyz[1] = center_top_xyz[1] |
|
|
|
uv = xyz2uv(np.array([left_bottom_xyz, right_bottom_xyz, left_top_xyz, right_top_xyz])) |
|
left_bottom_uv = uv[0] |
|
right_bottom_uv = uv[1] |
|
left_top_uv = uv[2] |
|
right_top_uv = uv[3] |
|
|
|
return [left_bottom_uv, right_bottom_uv, left_top_uv, right_top_uv], \ |
|
[left_bottom_xyz, right_bottom_xyz, left_top_xyz, right_top_xyz] |
|
|
|
|
|
def layout2depth(boundaries: List[np.array], return_mask=False, show=False, camera_height=1.6): |
|
""" |
|
|
|
:param camera_height: |
|
:param boundaries: [[[u_f1, v_f2], [u_f2, v_f2],...], [[u_c1, v_c2], [u_c2, v_c2]]] |
|
:param return_mask: |
|
:param show: |
|
:return: |
|
""" |
|
|
|
|
|
w = len(boundaries[0]) |
|
h = w//2 |
|
|
|
|
|
vf = uv2lonlat(boundaries[0]) |
|
vc = uv2lonlat(boundaries[1]) |
|
vc = vc[None, :, 1] |
|
vf = vf[None, :, 1] |
|
assert (vc > 0).sum() == 0 |
|
assert (vf < 0).sum() == 0 |
|
|
|
|
|
vs = ((np.arange(h) + 0.5) / h - 0.5) * np.pi |
|
vs = np.repeat(vs[:, None], w, axis=1) |
|
|
|
|
|
floor_h = camera_height |
|
floor_d = np.abs(floor_h / np.sin(vs)) |
|
|
|
|
|
cs = floor_h / np.tan(vf) |
|
|
|
|
|
ceil_h = np.abs(cs * np.tan(vc)) |
|
ceil_d = np.abs(ceil_h / np.sin(vs)) |
|
|
|
|
|
wall_d = np.abs(cs / np.cos(vs)) |
|
|
|
|
|
floor_mask = (vs > vf) |
|
ceil_mask = (vs < vc) |
|
wall_mask = (~floor_mask) & (~ceil_mask) |
|
depth = np.zeros([h, w], np.float32) |
|
depth[floor_mask] = floor_d[floor_mask] |
|
depth[ceil_mask] = ceil_d[ceil_mask] |
|
depth[wall_mask] = wall_d[wall_mask] |
|
|
|
assert (depth == 0).sum() == 0 |
|
if return_mask: |
|
return depth, floor_mask, ceil_mask, wall_mask |
|
if show: |
|
import matplotlib.pyplot as plt |
|
plt.imshow(depth) |
|
plt.show() |
|
return depth |
|
|
|
|
|
def calc_rotation(corners: np.ndarray): |
|
xz = uv2xyz(corners)[..., 0::2] |
|
max_norm = -1 |
|
max_v = None |
|
for i in range(len(xz)): |
|
p_c = xz[i] |
|
p_n = xz[(i + 1) % len(xz)] |
|
v_cn = p_n - p_c |
|
v_norm = np.linalg.norm(v_cn) |
|
if v_norm > max_norm: |
|
max_norm = v_norm |
|
max_v = v_cn |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rotation = np.arctan2(max_v[1], max_v[0]) |
|
return rotation |
|
|
|
|
|
if __name__ == '__main__': |
|
corners = np.array([[0.2, 0.7], |
|
[0.4, 0.7], |
|
[0.3, 0.6], |
|
[0.6, 0.6], |
|
[0.8, 0.7]]) |
|
get_heat_map(u=corners[..., 0], show=True, sigma=2, width=15) |
|
pass |
|
|
|
|