Spaces:
Build error
Build error
""" | |
@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: | |
""" | |
# why -0.5? Check out the uv2Pixel function | |
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 # why +0.5? Check out the uv2Pixel function | |
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: | |
# An intersection occurs and an exception is considered | |
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 | |
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): | |
# code from HorizonNet: https://github.com/sunset1995/HorizonNet/blob/master/inference.py | |
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] | |
# need align | |
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: | |
""" | |
# code from HorizonNet: https://github.com/sunset1995/HorizonNet/blob/master/eval_general.py | |
w = len(boundaries[0]) | |
h = w//2 | |
# Convert corners to per-column boundary first | |
# Up -pi/2, Down pi/2 | |
vf = uv2lonlat(boundaries[0]) | |
vc = uv2lonlat(boundaries[1]) | |
vc = vc[None, :, 1] # [1, w] | |
vf = vf[None, :, 1] # [1, w] | |
assert (vc > 0).sum() == 0 | |
assert (vf < 0).sum() == 0 | |
# Per-pixel v coordinate (vertical angle) | |
vs = ((np.arange(h) + 0.5) / h - 0.5) * np.pi | |
vs = np.repeat(vs[:, None], w, axis=1) # [h, w] | |
# Floor-plane to depth | |
floor_h = camera_height | |
floor_d = np.abs(floor_h / np.sin(vs)) | |
# wall to camera distance on horizontal plane at cross camera center | |
cs = floor_h / np.tan(vf) | |
# Ceiling-plane to depth | |
ceil_h = np.abs(cs * np.tan(vc)) # [1, w] | |
ceil_d = np.abs(ceil_h / np.sin(vs)) # [h, w] | |
# Wall to depth | |
wall_d = np.abs(cs / np.cos(vs)) # [h, w] | |
# Recover layout depth | |
floor_mask = (vs > vf) | |
ceil_mask = (vs < vc) | |
wall_mask = (~floor_mask) & (~ceil_mask) | |
depth = np.zeros([h, w], np.float32) # [h, w] | |
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 | |
# v<-----------|o | |
# | | | | |
# | ----|----z | | |
# | | | | |
# | x \|/ | |
# |------------u | |
# It is required that the vector be aligned on the x-axis, z equals y, and x is still x. | |
# In floorplan, x is displayed as the x-coordinate and z as the y-coordinate | |
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 | |