Spaces:
Running
on
Zero
Running
on
Zero
| import cv2 | |
| import loguru | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| import trimesh | |
| from multipledispatch import dispatch | |
| from packaging import version | |
| from scipy.spatial.transform import Rotation | |
| def to_array(x, dtype=float): | |
| if isinstance(x, np.ndarray): | |
| return x.astype(dtype) | |
| elif isinstance(x, torch.Tensor): | |
| return x.detach().cpu().numpy().astype(dtype) | |
| elif isinstance(x, list): | |
| return [to_array(a) for a in x] | |
| # elif isinstance(x, SafeDict): | |
| # return SafeDict(to_array(dict(x))) | |
| elif isinstance(x, dict): | |
| return {k: to_array(v) for k, v in x.items()} | |
| else: | |
| return x | |
| def to_tensor(data, dtype=None, device=None): | |
| if isinstance(data, torch.Tensor): | |
| return data.clone().to(dtype=dtype, device=device) | |
| else: | |
| return torch.tensor(data, dtype=dtype, device=device) | |
| def cart_to_hom(pts): | |
| """ | |
| :param pts: (N, 3 or 2) | |
| :return pts_hom: (N, 4 or 3) | |
| """ | |
| if isinstance(pts, np.ndarray): | |
| pts_hom = np.hstack((pts, np.ones((pts.shape[0], 1), dtype=np.float32))) | |
| else: | |
| ones = torch.ones((pts.shape[0], 1), dtype=torch.float32, device=pts.device) | |
| pts_hom = torch.cat((pts, ones), dim=1) | |
| return pts_hom | |
| def hom_to_cart(pts): | |
| """ | |
| :param pts: (N, 4 or 3) | |
| :return pts_hom: (N, 3 or 2) | |
| """ | |
| return pts[:, :-1] / pts[:, -1:] | |
| def canonical_to_camera(pts, pose): | |
| """ | |
| :param pts: Nx3 | |
| :param pose: 4x4 | |
| :param calib:Calibration | |
| :return: | |
| """ | |
| pts = cart_to_hom(pts) | |
| # pts = np.hstack((pts, np.ones((pts.shape[0], 1)))) # 4XN | |
| pts = pts @ pose.T # 4xN | |
| pts = hom_to_cart(pts) | |
| return pts | |
| def canonical_to_camera(pts, pose): | |
| """ | |
| :param pts: Nx3 | |
| :param pose: 4x4 | |
| :param calib:Calibration | |
| :return: | |
| """ | |
| pts = cart_to_hom(pts) | |
| pts = pts @ pose.T | |
| pts = hom_to_cart(pts) | |
| return pts | |
| def canonical_to_camera(pts, pose): | |
| pts = cart_to_hom(pts) | |
| pts = pts @ pose.transpose(-1, -2) | |
| pts = hom_to_cart(pts) | |
| return pts | |
| transform_points = canonical_to_camera | |
| def camera_to_canonical(pts, pose): | |
| """ | |
| :param pts: Nx3 | |
| :param pose: 4x4 | |
| :return: | |
| """ | |
| if isinstance(pts, np.ndarray) and isinstance(pose, np.ndarray): | |
| pts = pts.T # 3xN | |
| pts = np.vstack((pts, np.ones((1, pts.shape[1])))) # 4XN | |
| p = np.linalg.inv(pose) @ pts # 4xN | |
| p[0:3] /= p[3:] | |
| p = p[0:3] | |
| p = p.T | |
| return p | |
| else: | |
| pts = cart_to_hom(pts) | |
| pts = pts @ torch.inverse(pose).t() | |
| pts = hom_to_cart(pts) | |
| return pts | |
| def xyzr_to_pose4x4(x, y, z, r): | |
| pose = np.eye(4) | |
| pose[0, 0] = np.cos(r) | |
| pose[0, 2] = np.sin(r) | |
| pose[2, 0] = -np.sin(r) | |
| pose[2, 2] = np.cos(r) | |
| pose[0, 3] = x | |
| pose[1, 3] = y | |
| pose[2, 3] = z | |
| return pose | |
| def xyzr_to_pose4x4_torch(x, y, z, r): | |
| if isinstance(x, torch.Tensor): | |
| pose = torch.eye(4, device=x.device, dtype=torch.float) | |
| pose[0, 0] = torch.cos(r) | |
| pose[0, 2] = torch.sin(r) | |
| pose[2, 0] = -torch.sin(r) | |
| pose[2, 2] = torch.cos(r) | |
| pose[0, 3] = x | |
| pose[1, 3] = y | |
| pose[2, 3] = z | |
| return pose | |
| else: | |
| return torch.from_numpy(xyzr_to_pose4x4_np(x, y, z, r)).float() | |
| def pose4x4_to_xyzr(pose): | |
| x = pose[0, 3] | |
| y = pose[1, 3] | |
| z = pose[2, 3] | |
| cos = pose[0, 0] | |
| sin = pose[0, 2] | |
| angle = np.arctan2(sin, cos) | |
| return x, y, z, angle | |
| def pose4x4_to_xyzr(pose): | |
| x = pose[0, 3] | |
| y = pose[1, 3] | |
| z = pose[2, 3] | |
| cos = pose[0, 0] | |
| sin = pose[0, 2] | |
| angle = torch.atan2(sin, cos) | |
| return x, y, z, angle | |
| def camera_coordinate_to_world_coordinate(pts_in_camera, cam_pose): | |
| """ | |
| transform points in camera coordinate to points in world coordinate | |
| :param pts_in_camera: n,3 | |
| :param cam_pose: 4,4 | |
| :return: | |
| """ | |
| if isinstance(pts_in_camera, np.ndarray): | |
| pts_hom = np.hstack((pts_in_camera, np.ones((pts_in_camera.shape[0], 1), dtype=np.float32))) | |
| pts_world = pts_hom @ cam_pose.T | |
| else: | |
| ones = torch.ones((pts_in_camera.shape[0], 1), dtype=torch.float32, device=pts_in_camera.device) | |
| pts_hom = torch.cat((pts_in_camera, ones), dim=1) | |
| cam_pose = torch.tensor(cam_pose).float().to(device=pts_in_camera.device) | |
| pts_world = pts_hom @ cam_pose.t() | |
| pts_world = pts_world[:, :3] / pts_world[:, 3:] | |
| return pts_world | |
| def world_coordinate_to_camera_coordinate(pts_in_world, cam_pose): | |
| """ | |
| transform points in camera coordinate to points in world coordinate | |
| :param pts_in_world: n,3 | |
| :param cam_pose: 4,4 | |
| :return: | |
| """ | |
| if isinstance(pts_in_world, np.ndarray): | |
| cam_pose_inv = np.linalg.inv(cam_pose) | |
| pts_hom = np.hstack((pts_in_world, np.ones((pts_in_world.shape[0], 1), dtype=np.float32))) | |
| pts_cam = pts_hom @ cam_pose_inv.T | |
| else: | |
| cam_pose = cam_pose.float().to(device=pts_in_world.device) | |
| cam_pose_inv = torch.inverse(cam_pose) | |
| ones = torch.ones((pts_in_world.shape[0], 1), dtype=torch.float32, device=pts_in_world.device) | |
| pts_hom = torch.cat((pts_in_world, ones), dim=1) | |
| pts_cam = pts_hom @ cam_pose_inv.t() | |
| pts_cam = pts_cam[:, :3] / pts_cam[:, 3:] | |
| return pts_cam | |
| def xyzr_to_pose4x4_np(x, y, z, r): | |
| pose = np.eye(4) | |
| pose[0, 0] = np.cos(r) | |
| pose[0, 2] = np.sin(r) | |
| pose[2, 0] = -np.sin(r) | |
| pose[2, 2] = np.cos(r) | |
| pose[0, 3] = x | |
| pose[1, 3] = y | |
| pose[2, 3] = z | |
| return pose | |
| def canonical_to_camera_np(pts, pose, calib=None): | |
| """ | |
| :param pts: Nx3 | |
| :param pose: 4x4 | |
| :param calib: KITTICalib | |
| :return: | |
| """ | |
| pts = pts.T # 3xN | |
| pts = np.vstack((pts, np.ones((1, pts.shape[1])))) # 4XN | |
| p = pose @ pts # 4xN | |
| if calib is None: | |
| p[0:3] /= p[3:] | |
| p = p[0:3] | |
| else: | |
| p = calib.P2 @ p | |
| p[0:2] /= p[2:] | |
| p = p[0:2] | |
| p = p.T | |
| return p | |
| def rotx_np(a): | |
| """ | |
| :param a: np.ndarray of (N, 1) or (N), or float, or int | |
| angle | |
| :return: np.ndarray of (N, 3, 3) | |
| rotation matrix | |
| """ | |
| if isinstance(a, (int, float)): | |
| a = np.array([a]) | |
| a = a.astype(float).reshape((-1, 1)) | |
| ones = np.ones_like(a) | |
| zeros = np.zeros_like(a) | |
| c = np.cos(a) | |
| s = np.sin(a) | |
| rot = np.stack([ones, zeros, zeros, | |
| zeros, c, -s, | |
| zeros, s, c]) | |
| return rot.reshape((-1, 3, 3)) | |
| def roty_np(a): | |
| """ | |
| :param a: np.ndarray of (N, 1) or (N), or float, or int | |
| angle | |
| :return: np.ndarray of (N, 3, 3) | |
| rotation matrix | |
| """ | |
| if isinstance(a, (int, float)): | |
| a = np.array([a]) | |
| a = a.astype(float).reshape((-1, 1)) | |
| ones = np.ones_like(a) | |
| zeros = np.zeros_like(a) | |
| c = np.cos(a) | |
| s = np.sin(a) | |
| # TODO validate | |
| rot = np.stack([c, zeros, s, | |
| zeros, ones, zeros, | |
| -s, zeros, c]) | |
| return rot.reshape((-1, 3, 3)) | |
| def roty_torch(a): | |
| """ | |
| :param a: np.ndarray of (N, 1) or (N), or float, or int | |
| angle | |
| :return: np.ndarray of (N, 3, 3) | |
| rotation matrix | |
| """ | |
| if isinstance(a, (int, float)): | |
| a = torch.tensor([a]) | |
| # if a.shape[-1] != 1: | |
| # a = a[..., None] | |
| a = a.float() | |
| ones = torch.ones_like(a) | |
| zeros = torch.zeros_like(a) | |
| c = torch.cos(a) | |
| s = torch.sin(a) | |
| # TODO validate | |
| rot = torch.stack([c, zeros, s, | |
| zeros, ones, zeros, | |
| -s, zeros, c], dim=-1) | |
| return rot.reshape(*rot.shape[:-1], 3, 3) | |
| def rotz_np(a): | |
| """ | |
| :param a: np.ndarray of (N, 1) or (N), or float, or int | |
| angle | |
| :return: np.ndarray of (N, 3, 3) | |
| rotation matrix | |
| """ | |
| if isinstance(a, (int, float)): | |
| a = np.array([a]) | |
| a = a.astype(float).reshape((-1, 1)) | |
| ones = np.ones_like(a) | |
| zeros = np.zeros_like(a) | |
| c = np.cos(a) | |
| s = np.sin(a) | |
| rot = np.stack([c, -s, zeros, | |
| s, c, zeros, | |
| zeros, zeros, ones]) | |
| return rot.reshape((-1, 3, 3)) | |
| def rotz_torch(a): | |
| if isinstance(a, (int, float)): | |
| a = torch.tensor([a]) | |
| if a.shape[-1] != 1: | |
| a = a[..., None] | |
| a = a.float() | |
| ones = torch.ones_like(a) | |
| zeros = torch.zeros_like(a) | |
| c = torch.cos(a) | |
| s = torch.sin(a) | |
| rot = torch.stack([c, -s, zeros, | |
| s, c, zeros, | |
| zeros, zeros, ones], dim=-1) | |
| return rot.reshape(*rot.shape[:-1], 3, 3) | |
| def rotx(t): | |
| """ | |
| Rotation along the x-axis. | |
| :param t: tensor of (N, 1) or (N), or float, or int | |
| angle | |
| :return: tensor of (N, 3, 3) | |
| rotation matrix | |
| """ | |
| if isinstance(t, (int, float)): | |
| t = torch.tensor([t]) | |
| if t.shape[-1] != 1: | |
| t = t[..., None] | |
| t = t.type(torch.float) | |
| ones = torch.ones_like(t) | |
| zeros = torch.zeros_like(t) | |
| c = torch.cos(t) | |
| s = torch.sin(t) | |
| rot = torch.stack([ones, zeros, zeros, | |
| zeros, c, -s, | |
| zeros, s, c], dim=-1) | |
| return rot.reshape(*rot.shape[:-1], 3, 3) | |
| def matrix_3x4_to_4x4(a): | |
| if len(a.shape) == 2: | |
| assert a.shape == (3, 4) | |
| else: | |
| assert len(a.shape) == 3 | |
| assert a.shape[1:] == (3, 4) | |
| if isinstance(a, np.ndarray): | |
| if a.ndim == 2: | |
| ones = np.array([[0, 0, 0, 1]]) | |
| return np.vstack((a, ones)) | |
| else: | |
| ones = np.array([[0, 0, 0, 1]])[None].repeat(a.shape[0], axis=0) | |
| return np.concatenate((a, ones), axis=1) | |
| else: | |
| ones = torch.tensor([[0, 0, 0, 1]]).float().to(device=a.device) | |
| if a.ndim == 3: | |
| ones = ones[None].repeat(a.shape[0], 1, 1) | |
| ret = torch.cat((a, ones), dim=1) | |
| else: | |
| ret = torch.cat((a, ones), dim=0) | |
| return ret | |
| def matrix_3x3_to_4x4(a): | |
| assert a.shape == (3, 3) | |
| if isinstance(a, np.ndarray): | |
| ret = np.eye(4) | |
| else: | |
| ret = torch.eye(4).float().to(a.device) | |
| ret[:3, :3] = a | |
| return ret | |
| def radian_to_negpi_to_pi(x): | |
| if isinstance(x, torch.Tensor): | |
| return x - 2 * torch.floor((x / np.pi + 1) / 2) * np.pi | |
| else: | |
| return x - 2 * np.floor((x / np.pi + 1) / 2) * np.pi | |
| def filter_bbox_3d(bbox_3d, point): | |
| """ | |
| @param bbox_3d: corners (8,3) | |
| @param point: (?,3) | |
| @return: | |
| """ | |
| v45 = bbox_3d[5] - bbox_3d[4] | |
| v40 = bbox_3d[0] - bbox_3d[4] | |
| v47 = bbox_3d[7] - bbox_3d[4] | |
| # point -= bbox_3d[4] | |
| point = point - bbox_3d[4] | |
| m0 = torch.matmul(point, v45) | |
| m1 = torch.matmul(point, v40) | |
| m2 = torch.matmul(point, v47) | |
| cs = [] | |
| for m, v in zip([m0, m1, m2], [v45, v40, v47]): | |
| c0 = 0 < m | |
| c1 = m < torch.matmul(v, v) | |
| c = c0 & c1 | |
| cs.append(c) | |
| cs = cs[0] & cs[1] & cs[2] | |
| passed_inds = torch.nonzero(cs).squeeze(1) | |
| num_passed = torch.sum(cs) | |
| return num_passed, passed_inds, cs | |
| def triangulate_static_points(projmat1, projmat2, projpoints1, projpoints2) -> np.ndarray: | |
| """ | |
| triangulate points python version | |
| :param projmat1: 3x4 | |
| :param projmat2: 3x4 | |
| :param projpoints1: 2xn | |
| :param projpoints2: 2xn | |
| :return: points4D: 4xn | |
| """ | |
| num_points = projpoints1.shape[1] | |
| assert projpoints2.shape[1] == num_points | |
| points4D = [] | |
| for i in range(num_points): | |
| x, y = projpoints1[:, i] | |
| xp, yp = projpoints2[:, i] | |
| A = np.stack([x * projmat1[2] - projmat1[0], | |
| y * projmat1[2] - projmat1[1], | |
| xp * projmat2[2] - projmat2[0], | |
| yp * projmat2[2] - projmat2[1]], axis=0) | |
| U, S, V = np.linalg.svd(A) | |
| points4D.append(V[3, 0:4]) | |
| points4D = np.stack(points4D).T | |
| return points4D | |
| def subsample_mask_by_grid(pc_rect): | |
| def filter_mask(pc_rect): | |
| """Return index of points that lies within the region defined below.""" | |
| valid_inds = (pc_rect[:, 2] < 80) * \ | |
| (pc_rect[:, 2] > 1) * \ | |
| (pc_rect[:, 0] < 40) * \ | |
| (pc_rect[:, 0] >= -40) * \ | |
| (pc_rect[:, 1] < 2.5) * \ | |
| (pc_rect[:, 1] >= -1) | |
| return valid_inds | |
| GRID_SIZE = 0.1 | |
| index_field_sample = np.full( | |
| (35, int(80 / 0.1), int(80 / 0.1)), -1, dtype=np.int32) | |
| N = pc_rect.shape[0] | |
| perm = np.random.permutation(pc_rect.shape[0]) | |
| pc_rect = pc_rect[perm] | |
| range_filter = filter_mask(pc_rect) | |
| pc_rect = pc_rect[range_filter] | |
| pc_rect_quantized = np.floor(pc_rect[:, :3] / GRID_SIZE).astype(np.int32) | |
| pc_rect_quantized[:, 0] = pc_rect_quantized[:, 0] \ | |
| + int(80 / GRID_SIZE / 2) | |
| pc_rect_quantized[:, 1] = pc_rect_quantized[:, 1] + int(1 / GRID_SIZE) | |
| index_field = index_field_sample.copy() | |
| index_field[pc_rect_quantized[:, 1], | |
| pc_rect_quantized[:, 2], pc_rect_quantized[:, 0]] = np.arange(pc_rect.shape[0]) | |
| mask = np.zeros(perm.shape, dtype=np.bool) | |
| mask[perm[range_filter][index_field[index_field >= 0]]] = 1 | |
| return mask | |
| def img_to_rect(fu, fv, cu, cv, u, v, depth_rect): | |
| """ | |
| :param u: (N) | |
| :param v: (N) | |
| :param depth_rect: (N) | |
| :return: pts_rect:(N, 3) | |
| """ | |
| # check_type(u) | |
| # check_type(v) | |
| if isinstance(depth_rect, np.ndarray): | |
| x = ((u - cu) * depth_rect) / fu | |
| y = ((v - cv) * depth_rect) / fv | |
| pts_rect = np.concatenate((x.reshape(-1, 1), y.reshape(-1, 1), depth_rect.reshape(-1, 1)), axis=1) | |
| else: | |
| x = ((u.float() - cu) * depth_rect) / fu | |
| y = ((v.float() - cv) * depth_rect) / fv | |
| pts_rect = torch.cat((x.reshape(-1, 1), y.reshape(-1, 1), depth_rect.reshape(-1, 1)), dim=1) | |
| # x = ((u - cu) * depth_rect) / fu | |
| # y = ((v - cv) * depth_rect) / fv | |
| # pts_rect = np.concatenate((x.reshape(-1, 1), y.reshape(-1, 1), depth_rect.reshape(-1, 1)), axis=1) | |
| return pts_rect | |
| def depth_to_rect(fu, fv, cu, cv, depth_map, ray_mode=False, select_coords=None): | |
| """ | |
| :param fu: | |
| :param fv: | |
| :param cu: | |
| :param cv: | |
| :param depth_map: | |
| :param ray_mode: whether values in depth_map are Z or norm | |
| :return: | |
| """ | |
| if len(depth_map.shape) == 2: | |
| if isinstance(depth_map, np.ndarray): | |
| x_range = np.arange(0, depth_map.shape[1]) | |
| y_range = np.arange(0, depth_map.shape[0]) | |
| x_idxs, y_idxs = np.meshgrid(x_range, y_range) | |
| else: | |
| x_range = torch.arange(0, depth_map.shape[1]).to(device=depth_map.device) | |
| y_range = torch.arange(0, depth_map.shape[0]).to(device=depth_map.device) | |
| y_idxs, x_idxs = torch.meshgrid(y_range, x_range, indexing='ij') | |
| x_idxs, y_idxs = x_idxs.reshape(-1), y_idxs.reshape(-1) | |
| depth = depth_map[y_idxs, x_idxs] | |
| else: | |
| x_idxs = select_coords[:, 1] | |
| y_idxs = select_coords[:, 0] | |
| depth = depth_map | |
| if ray_mode is True: | |
| if isinstance(depth, torch.Tensor): | |
| depth = depth / (((x_idxs.float() - cu.float()) / fu.float()) ** 2 + ( | |
| (y_idxs.float() - cv.float()) / fv.float()) ** 2 + 1) ** 0.5 | |
| else: | |
| depth = depth / (((x_idxs - cu) / fu) ** 2 + ( | |
| (y_idxs - cv) / fv) ** 2 + 1) ** 0.5 | |
| pts_rect = img_to_rect(fu, fv, cu, cv, x_idxs, y_idxs, depth) | |
| # if ray_mode is True: | |
| # todo check this | |
| # pts_rect[:, 2] = (pts_rect[:, 2] ** 2 - pts_rect[:, 0] ** 2 - pts_rect[:, 1] ** 2) ** 0.5 | |
| return pts_rect | |
| def depth_norm_to_depth_z(K, depth_map): | |
| fu, fv, cu, cv = K[0, 0], K[1, 1], K[0, 2], K[1, 2] | |
| if isinstance(depth_map, np.ndarray): | |
| x_range = np.arange(0, depth_map.shape[1]) | |
| y_range = np.arange(0, depth_map.shape[0]) | |
| x_idxs, y_idxs = np.meshgrid(x_range, y_range) | |
| else: | |
| x_range = torch.arange(0, depth_map.shape[1]).to(device=depth_map.device) | |
| y_range = torch.arange(0, depth_map.shape[0]).to(device=depth_map.device) | |
| y_idxs, x_idxs = torch.meshgrid(y_range, x_range, indexing='ij') | |
| x_idxs, y_idxs = x_idxs.reshape(-1), y_idxs.reshape(-1) | |
| depth = depth_map[y_idxs, x_idxs] | |
| if isinstance(depth, torch.Tensor): | |
| depth = depth / (((x_idxs.float() - cu.float()) / fu.float()) ** 2 + ( | |
| (y_idxs.float() - cv.float()) / fv.float()) ** 2 + 1) ** 0.5 | |
| else: | |
| depth = depth / (((x_idxs - cu) / fu) ** 2 + ((y_idxs - cv) / fv) ** 2 + 1) ** 0.5 | |
| return depth.reshape(depth_map.shape) | |
| def rect_to_img(fu, fv, cu, cv, pts_rect): | |
| K = np.array([[fu, 0, cu], | |
| [0, fv, cv], | |
| [0, 0, 1]]) | |
| pts_2d_hom = pts_rect @ K.T | |
| pts_img = hom_to_cart(pts_2d_hom) | |
| return pts_img | |
| def rect_to_img(fu, fv, cu, cv, pts_rect): | |
| device = pts_rect.device | |
| P2 = torch.tensor([[fu, 0, cu], | |
| [0, fv, cv], | |
| [0, 0, 1]], dtype=torch.float, device=device) | |
| pts_2d_hom = pts_rect @ P2.t() | |
| pts_img = hom_to_cart(pts_2d_hom) | |
| return pts_img | |
| def rect_to_img(K, pts_rect): | |
| pts_2d_hom = pts_rect @ K.T | |
| pts_img = hom_to_cart(pts_2d_hom) | |
| return pts_img | |
| def rect_to_img(K, pts_rect): | |
| pts_2d_hom = pts_rect @ K.t() | |
| pts_img = hom_to_cart(pts_2d_hom) | |
| return pts_img | |
| def backproject_flow3d_torch(flow2d, depth0, depth1, intrinsics, campose0, campose1): | |
| """ compute 3D flow from 2D flow + depth change """ | |
| # raise NotImplementedError() | |
| ht, wd = flow2d.shape[0:2] | |
| fx, fy, cx, cy = intrinsics[0, 0], intrinsics[1, 1], intrinsics[0, 2], intrinsics[1, 2] | |
| y0, x0 = torch.meshgrid( | |
| torch.arange(ht).to(depth0.device).float(), | |
| torch.arange(wd).to(depth0.device).float()) | |
| x1 = x0 + flow2d[..., 0] | |
| y1 = y0 + flow2d[..., 1] | |
| X0 = depth0 * ((x0 - cx) / fx) | |
| Y0 = depth0 * ((y0 - cy) / fy) | |
| Z0 = depth0 | |
| # X1 = depth1 * ((x1 - cx) / fx) | |
| # Y1 = depth1 * ((y1 - cy) / fy) | |
| # Z1 = depth1 | |
| grid = torch.stack([x1, y1], dim=-1)[None] | |
| grid[:, :, :, 0] = grid[:, :, :, 0] / (wd - 1) | |
| grid[:, :, :, 1] = grid[:, :, :, 1] / (ht - 1) | |
| grid = grid * 2 - 1 | |
| depth1_interp = torch.nn.functional.grid_sample( | |
| depth1[None, None], | |
| grid, | |
| mode='bilinear' | |
| )[0, 0] | |
| X1 = depth1_interp * ((x1 - cx) / fx) | |
| Y1 = depth1_interp * ((y1 - cy) / fy) | |
| Z1 = depth1_interp # todo: or interpolated depth? | |
| pts0_cam = torch.stack([X0, Y0, Z0], dim=-1) | |
| pts1_cam = torch.stack([X1, Y1, Z1], dim=-1) | |
| # pts0_world = camera_coordinate_to_world_coordinate(pts0_cam, campose0) | |
| # pts1_world = camera_coordinate_to_world_coordinate(pts1_cam, campose1) | |
| # flow3d = torch.stack([X1 - X0, Y1 - Y0, Z1 - Z0], dim=-1) | |
| flow3d = pts1_cam - pts0_cam | |
| return flow3d, pts0_cam, pts1_cam | |
| def backproject_flow3d_np(flow2d, depth0, depth1, intrinsics0, intrinsics1, campose0, campose1, occlusion, | |
| max_norm=0.5, interpolation_mode='nearest'): | |
| """ | |
| compute 3D flow from 2D flow + depth change | |
| :param flow2d: | |
| :param depth0: | |
| :param depth1: | |
| :param intrinsics0: | |
| :param intrinsics1: | |
| :param campose0: | |
| :param campose1: | |
| :param occlusion: 0,255 | |
| :param max_norm: | |
| :return: | |
| """ | |
| ht, wd = flow2d.shape[0:2] | |
| fx0, fy0, cx0, cy0 = intrinsics0[0, 0], intrinsics0[1, 1], intrinsics0[0, 2], intrinsics0[1, 2] | |
| fx1, fy1, cx1, cy1 = intrinsics1[0, 0], intrinsics1[1, 1], intrinsics1[0, 2], intrinsics1[1, 2] | |
| x0, y0 = np.meshgrid(np.arange(wd), np.arange(ht)) | |
| x1 = x0 + flow2d[..., 0] | |
| y1 = y0 + flow2d[..., 1] | |
| X0 = depth0 * ((x0 - cx0) / fx0) | |
| Y0 = depth0 * ((y0 - cy0) / fy0) | |
| Z0 = depth0 | |
| # bilinear sample | |
| grid = torch.from_numpy(np.stack([x1, y1], axis=-1)[None]).float() | |
| grid[:, :, :, 0] = grid[:, :, :, 0] / (wd - 1) | |
| grid[:, :, :, 1] = grid[:, :, :, 1] / (ht - 1) | |
| grid = grid * 2 - 1 | |
| depth1_interp = torch.nn.functional.grid_sample( | |
| torch.from_numpy(depth1)[None, None], | |
| grid, | |
| mode=interpolation_mode | |
| )[0, 0].numpy() | |
| X1 = depth1_interp * ((x1 - cx1) / fx1) | |
| Y1 = depth1_interp * ((y1 - cy1) / fy1) | |
| Z1 = depth1_interp # todo: or interpolated depth? | |
| pts0_cam = np.stack([X0, Y0, Z0], axis=-1).reshape(-1, 3) | |
| pts1_cam = np.stack([X1, Y1, Z1], axis=-1).reshape(-1, 3) | |
| posenorm = np.linalg.inv(campose0) | |
| campose0, campose1 = posenorm @ campose0, posenorm @ campose1 | |
| pts0_world = camera_coordinate_to_world_coordinate(pts0_cam, campose0) | |
| pts1_world = camera_coordinate_to_world_coordinate(pts1_cam, campose1) | |
| # flow3d = torch.stack([X1 - X0, Y1 - Y0, Z1 - Z0], dim=-1) | |
| flow3d = pts1_world - pts0_world | |
| # with Vis3D( | |
| # xyz_pattern=('x', '-y', '-z'), | |
| # out_folder="dbg" | |
| # ) as vis3d: | |
| # vis3d.set_scene_id(0) | |
| # vis3d.add_point_cloud(pts0_world) | |
| # vis3d.add_point_cloud(pts1_world) | |
| # print() | |
| flow3d = flow3d.reshape(ht, wd, 3) | |
| norm = np.linalg.norm(flow3d, axis=-1) | |
| ncc = occlusion != 255 | |
| flow3dncc = flow3d * ncc[:, :, None] | |
| normncc = norm * ncc | |
| # plt.imshow(normncc, 'jet') | |
| # plt.show() | |
| normmask = normncc < max_norm | |
| flow3dncc = flow3dncc * normmask[:, :, None] | |
| return flow3dncc, normmask | |
| def _sample_at_integer_locs(input_feats, index_tensor): | |
| assert input_feats.ndimension() == 5, 'input_feats should be of shape [B,F,D,H,W]' | |
| assert index_tensor.ndimension() == 4, 'index_tensor should be of shape [B,H,W,3]' | |
| # first sample pixel locations using nearest neighbour interpolation | |
| batch_size, num_chans, num_d, height, width = input_feats.shape | |
| grid_height, grid_width = index_tensor.shape[1], index_tensor.shape[2] | |
| xy_grid = index_tensor[..., 0:2] | |
| xy_grid[..., 0] = xy_grid[..., 0] - ((width - 1.0) / 2.0) | |
| xy_grid[..., 0] = xy_grid[..., 0] / ((width - 1.0) / 2.0) | |
| xy_grid[..., 1] = xy_grid[..., 1] - ((height - 1.0) / 2.0) | |
| xy_grid[..., 1] = xy_grid[..., 1] / ((height - 1.0) / 2.0) | |
| xy_grid = torch.clamp(xy_grid, min=-1.0, max=1.0) | |
| sampled_in_2d = F.grid_sample(input=input_feats.view(batch_size, num_chans * num_d, height, width), | |
| grid=xy_grid, mode='nearest', align_corners=False).view(batch_size, num_chans, num_d, | |
| grid_height, | |
| grid_width) | |
| z_grid = index_tensor[..., 2].view(batch_size, 1, 1, grid_height, grid_width) | |
| z_grid = z_grid.long().clamp(min=0, max=num_d - 1) | |
| z_grid = z_grid.expand(batch_size, num_chans, 1, grid_height, grid_width) | |
| sampled_in_3d = sampled_in_2d.gather(2, z_grid).squeeze(2) | |
| return sampled_in_3d | |
| def trilinear_interpolation(input_feats, sampling_grid): | |
| """ | |
| interploate value in 3D volume | |
| :param input_feats: [B,C,D,H,W] | |
| :param sampling_grid: [B,H,W,3] unscaled coordinates | |
| :return: | |
| """ | |
| assert input_feats.ndimension() == 5, 'input_feats should be of shape [B,F,D,H,W]' | |
| assert sampling_grid.ndimension() == 4, 'sampling_grid should be of shape [B,H,W,3]' | |
| batch_size, num_chans, num_d, height, width = input_feats.shape | |
| grid_height, grid_width = sampling_grid.shape[1], sampling_grid.shape[2] | |
| # make sure sampling grid lies between -1, 1 | |
| sampling_grid[..., 0] = 2 * sampling_grid[..., 0] / (num_d - 1) - 1 | |
| sampling_grid[..., 1] = 2 * sampling_grid[..., 1] / (height - 1) - 1 | |
| sampling_grid[..., 2] = 2 * sampling_grid[..., 2] / (width - 1) - 1 | |
| sampling_grid = torch.clamp(sampling_grid, min=-1.0, max=1.0) | |
| # map to 0,1 | |
| sampling_grid = (sampling_grid + 1) / 2.0 | |
| # Scale grid to floating point pixel locations | |
| scaling_factor = torch.FloatTensor([width - 1.0, height - 1.0, num_d - 1.0]).to(input_feats.device).view(1, 1, | |
| 1, 3) | |
| sampling_grid = scaling_factor * sampling_grid | |
| # Now sampling grid is between [0, w-1; 0,h-1; 0,d-1] | |
| x, y, z = torch.split(sampling_grid, split_size_or_sections=1, dim=3) | |
| x_0, y_0, z_0 = torch.split(sampling_grid.floor(), split_size_or_sections=1, dim=3) | |
| x_1, y_1, z_1 = x_0 + 1.0, y_0 + 1.0, z_0 + 1.0 | |
| u, v, w = x - x_0, y - y_0, z - z_0 | |
| u, v, w = map(lambda x: x.view(batch_size, 1, grid_height, grid_width).expand( | |
| batch_size, num_chans, grid_height, grid_width), [u, v, w]) | |
| c_000 = _sample_at_integer_locs(input_feats, torch.cat([x_0, y_0, z_0], dim=3)) | |
| c_001 = _sample_at_integer_locs(input_feats, torch.cat([x_0, y_0, z_1], dim=3)) | |
| c_010 = _sample_at_integer_locs(input_feats, torch.cat([x_0, y_1, z_0], dim=3)) | |
| c_011 = _sample_at_integer_locs(input_feats, torch.cat([x_0, y_1, z_1], dim=3)) | |
| c_100 = _sample_at_integer_locs(input_feats, torch.cat([x_1, y_0, z_0], dim=3)) | |
| c_101 = _sample_at_integer_locs(input_feats, torch.cat([x_1, y_0, z_1], dim=3)) | |
| c_110 = _sample_at_integer_locs(input_feats, torch.cat([x_1, y_1, z_0], dim=3)) | |
| c_111 = _sample_at_integer_locs(input_feats, torch.cat([x_1, y_1, z_1], dim=3)) | |
| c_xyz = (1.0 - u) * (1.0 - v) * (1.0 - w) * c_000 + \ | |
| (1.0 - u) * (1.0 - v) * w * c_001 + \ | |
| (1.0 - u) * v * (1.0 - w) * c_010 + \ | |
| (1.0 - u) * v * w * c_011 + \ | |
| u * (1.0 - v) * (1.0 - w) * c_100 + \ | |
| u * (1.0 - v) * w * c_101 + \ | |
| u * v * (1.0 - w) * c_110 + \ | |
| u * v * w * c_111 | |
| return c_xyz | |
| def create_center_radius(center=np.array([0, 0, 0]), dist=5., angle_z=30, nrad=180, start=0., endpoint=True, | |
| end=2 * np.pi): | |
| RTs = [] | |
| center = np.array(center).reshape(3, 1) | |
| thetas = np.linspace(start, end, nrad, endpoint=endpoint) | |
| angle_z = np.deg2rad(angle_z) | |
| radius = dist * np.cos(angle_z) | |
| height = dist * np.sin(angle_z) | |
| for theta in thetas: | |
| st = np.sin(theta) | |
| ct = np.cos(theta) | |
| center_ = np.array([radius * ct, radius * st, height]).reshape(3, 1) | |
| center_[0] += center[0, 0] | |
| center_[1] += center[1, 0] | |
| R = np.array([ | |
| [-st, ct, 0], | |
| [0, 0, -1], | |
| [-ct, -st, 0] | |
| ]) | |
| Rotx = cv2.Rodrigues(angle_z * np.array([1., 0., 0.]))[0] | |
| R = Rotx @ R | |
| T = - R @ center_ | |
| center_ = - R.T @ T | |
| RT = np.hstack([R, T]) | |
| RTs.append(RT) | |
| return np.stack(RTs) | |
| vol_bnds = np.zeros((3, 2)) | |
| # obj_poses = obj_poses @ np.linalg.inv(obj_poses[index]) | |
| for depth_im, op in safe_zip(depths, obj_poses): | |
| cam_pose = np.linalg.inv(op) | |
| # Compute camera view frustum and extend convex hull | |
| view_frust_pts = get_view_frustum(depth_im, cam_intr, cam_pose) | |
| vol_bnds[:, 0] = np.minimum(vol_bnds[:, 0], np.amin(view_frust_pts, axis=1)) | |
| vol_bnds[:, 1] = np.maximum(vol_bnds[:, 1], np.amax(view_frust_pts, axis=1)) | |
| vol_bnds[:, 0] = np.floor(vol_bnds[:, 0] / voxel_length) * voxel_length | |
| vol_bnds[:, 1] = np.ceil(vol_bnds[:, 1] / voxel_length) * voxel_length | |
| return vol_bnds | |
| def open3d_icp_api(pts0, pts1, thresh, init_Rt=np.eye(4), return_tsfm_only=True, use_cuda=False, voxel_size=-1): | |
| """ | |
| R*pts0+t=pts1 | |
| :param pts0: nx3 | |
| :param pts1: mx3 | |
| :param thresh: float | |
| :param init_Rt: 4x4 | |
| :return: | |
| """ | |
| import open3d as o3d | |
| if not use_cuda: | |
| pcd0 = o3d.geometry.PointCloud() | |
| pcd0.points = o3d.utility.Vector3dVector(pts0.copy()) | |
| pcd1 = o3d.geometry.PointCloud() | |
| pcd1.points = o3d.utility.Vector3dVector(pts1.copy()) | |
| if version.parse(o3d.__version__) < version.parse('0.10.0'): | |
| result = o3d.registration.registration_icp( | |
| pcd0, pcd1, thresh, init_Rt) | |
| else: | |
| result = o3d.pipelines.registration.registration_icp( | |
| pcd0, pcd1, thresh, init_Rt) | |
| del pcd0, pcd1 | |
| if return_tsfm_only: | |
| return result.transformation | |
| else: | |
| return result | |
| else: | |
| import open3d.cuda.pybind.t.pipelines.registration as treg | |
| pcd0 = o3d.t.geometry.PointCloud(o3d.cuda.pybind.core.Tensor(pts0.astype(np.float32))) | |
| # pcd0.point.positions = | |
| pcd1 = o3d.t.geometry.PointCloud(o3d.cuda.pybind.core.Tensor(pts1.astype(np.float32))) | |
| # pcd1.point.positions = o3d.core.Tensor(pts1.astype(np.float32)) | |
| result = treg.icp(pcd0, pcd1, thresh, | |
| init_Rt, | |
| # estimation, | |
| # voxel_size=voxel_size, | |
| ) | |
| del pcd0, pcd1 | |
| if return_tsfm_only: | |
| return result.transformation.numpy() | |
| else: | |
| return result | |
| def open3d_ransac_api(pts0, pts1, thresh): | |
| """ | |
| R*pts0+t=pts1 | |
| :param pts0: nx3 | |
| :param pts1: mx3 | |
| :param thresh: float | |
| :param init_Rt: 4x4 | |
| :return: | |
| """ | |
| import open3d as o3d | |
| pcd0 = o3d.geometry.PointCloud() | |
| pcd0.points = o3d.utility.Vector3dVector(pts0) | |
| pcd1 = o3d.geometry.PointCloud() | |
| pcd1.points = o3d.utility.Vector3dVector(pts1) | |
| corres = np.arange(pts0.shape[0])[:, None].repeat(2, axis=1) | |
| corres = o3d.utility.Vector2iVector(corres) | |
| result = o3d.pipelines.registration.registration_ransac_based_on_correspondence( | |
| pcd0, pcd1, corres, thresh) | |
| return result.transformation | |
| def open3d_colored_icp_api(src_pc, tgt_pc, src_color, tgt_color, init_tsfm=np.eye(4)): | |
| import open3d as o3d | |
| if isinstance(src_pc, trimesh.Trimesh): | |
| src_pc = src_pc.vertices | |
| if isinstance(src_pc, torch.Tensor): | |
| src_pc = src_pc.cpu().numpy() | |
| if isinstance(tgt_pc, trimesh.Trimesh): | |
| tgt_pc = tgt_pc.vertices | |
| if isinstance(tgt_pc, torch.Tensor): | |
| tgt_pc = tgt_pc.cpu().numpy() | |
| # if normalize_scale: | |
| # scaling = 1.0 / (src_pc.max(0) - src_pc.min(0)).max() | |
| # src_pc = src_pc * scaling | |
| # scaling = 1.0 / (tgt_pc.max(0) - tgt_pc.min(0)).max() | |
| # tgt_pc = tgt_pc * scaling | |
| # if normalize_position: | |
| # src_pc = src_pc - src_pc.min(0) | |
| # tgt_pc = tgt_pc - tgt_pc.min(0) | |
| source = o3d.geometry.PointCloud() | |
| source.points = o3d.utility.Vector3dVector(src_pc.copy()) | |
| source.colors = o3d.utility.Vector3dVector(src_color.copy()) | |
| target = o3d.geometry.PointCloud() | |
| target.points = o3d.utility.Vector3dVector(tgt_pc.copy()) | |
| target.colors = o3d.utility.Vector3dVector(tgt_color.copy()) | |
| # voxel_radius = [0.04, 0.02, 0.01] | |
| # voxel_radius = [0.02, 0.01] | |
| voxel_radius = [0.01] | |
| # voxel_radius = [0.08, 0.04, 0.02] | |
| # voxel_radius = [0.16, 0.08, 0.04] | |
| # max_iter = [50, 30, 14] | |
| max_iter = [14] | |
| current_transformation = init_tsfm | |
| print("3. Colored point cloud registration") | |
| results_icp = [] | |
| for scale in range(len(voxel_radius)): | |
| iter = max_iter[scale] | |
| radius = voxel_radius[scale] | |
| print([iter, radius, scale]) | |
| print("3-1. Downsample with a voxel size %.2f" % radius) | |
| source_down = source.voxel_down_sample(radius) | |
| target_down = target.voxel_down_sample(radius) | |
| print("3-2. Estimate normal.") | |
| source_down.estimate_normals( | |
| o3d.geometry.KDTreeSearchParamHybrid(radius=radius * 2, max_nn=30)) | |
| target_down.estimate_normals( | |
| o3d.geometry.KDTreeSearchParamHybrid(radius=radius * 2, max_nn=30)) | |
| print("3-3. Applying colored point cloud registration") | |
| result_icp = o3d.pipelines.registration.registration_colored_icp( | |
| source_down, target_down, radius, current_transformation, | |
| o3d.pipelines.registration.TransformationEstimationForColoredICP(), | |
| o3d.pipelines.registration.ICPConvergenceCriteria(relative_fitness=1e-6, | |
| relative_rmse=1e-6, | |
| max_iteration=iter)) | |
| current_transformation = result_icp.transformation | |
| results_icp.append(result_icp) | |
| # print(result_icp) | |
| return results_icp | |
| def cpa_pytorch3d_api(pts0, pts1, estimate_scale=False, use_gpu=True): | |
| """ | |
| R*pts0+t=pts1 | |
| :param pts0: nx3 | |
| :param pts1: nx3 | |
| :return: 4x4 | |
| """ | |
| from pytorch3d.ops import corresponding_points_alignment | |
| pts0 = torch.from_numpy(pts0).float() | |
| pts1 = torch.from_numpy(pts1).float() | |
| if use_gpu: | |
| pts0 = pts0.cuda() | |
| pts1 = pts1.cuda() | |
| cpa_res = corresponding_points_alignment(pts0[None], pts1[None], estimate_scale=estimate_scale) | |
| R = cpa_res.R[0].T | |
| t = cpa_res.T[0] | |
| Rt = torch.cat([R, t.reshape(3, 1)], dim=1) | |
| pose = matrix_3x4_to_4x4(Rt) | |
| return pose.cpu().numpy() | |
| def interp_pose(poses): | |
| """ | |
| :param poses: np.ndarray N,4,4 | |
| :return: | |
| """ | |
| N = len(poses) | |
| nN = N * 2 - 1 | |
| newposes = np.zeros([nN, 4, 4]) | |
| newposes[::2, :, :] = poses | |
| a = poses[:-1] | |
| b = poses[1:] | |
| aa = se3_log_map(torch.from_numpy(a.transpose(0, 2, 1))) | |
| bb = se3_log_map(torch.from_numpy(b.transpose(0, 2, 1))) | |
| cc = (aa + bb) / 2 | |
| c = se3_exp_map(cc).numpy().transpose(0, 2, 1) | |
| newposes[1::2, :, :] = c | |
| return newposes | |
| def compose_pair(pose_a, pose_b): | |
| # pose_new(x) = pose_b o pose_a(x) | |
| R_a, t_a = pose_a[..., :3], pose_a[..., 3:] | |
| R_b, t_b = pose_b[..., :3], pose_b[..., 3:] | |
| R_new = R_b @ R_a | |
| t_new = (R_b @ t_a + t_b)[..., 0] | |
| pose_new = torch.cat([R_new, t_new[..., None]], dim=-1) | |
| pose_new = matrix_3x4_to_4x4(pose_new) | |
| return pose_new | |
| def rotation_distance(R1, R2, eps=1e-7): | |
| # http://www.boris-belousov.net/2016/12/01/quat-dist/ | |
| if R1.ndim == 2 and R2.ndim == 2: | |
| R_diff = R1[:3, :3] @ R2[:3, :3].T | |
| trace = R_diff[0, 0] + R_diff[1, 1] + R_diff[2, 2] | |
| else: | |
| R_diff = R1[..., :3, :3] @ R2.transpose(-2, -1)[..., :3, :3] | |
| trace = R_diff[..., 0, 0] + R_diff[..., 1, 1] + R_diff[..., 2, 2] | |
| angle = ((trace - 1) / 2).clamp(-1 + eps, 1 - eps).acos_() # numerical stability near -1/+1 | |
| return angle | |
| def pose_distance(pred, gt, eps=1e-7, align=False): | |
| if pred.numel() == 0 or gt.numel() == 0: | |
| return torch.empty([0]), torch.empty([0]) | |
| if pred.ndim == 2 and gt.ndim == 2: | |
| pred = pred[None] | |
| gt = gt[None] | |
| if align: | |
| gt = gt @ gt[0].inverse()[None] | |
| pred = pred @ pred[0].inverse()[None] | |
| R_error = rotation_distance(pred, gt, eps) | |
| t_error = (pred[..., :3, 3] - gt[..., :3, 3]).norm(dim=-1) | |
| return R_error, t_error | |
| def project_to_img(pts_cam, K, shape): | |
| tmp = torch.zeros(shape) | |
| K = K.cpu() | |
| pts_img = rect_to_img(K[0, 0], K[1, 1], K[0, 2], K[1, 2], pts_cam.cpu()) | |
| tmp[pts_img[:, 1].long(), pts_img[:, 0].long()] = 1 | |
| return tmp | |
| def chamfer_distance_and_fscore(pts0, pts1, use_gpu=True, threshold=0.05): | |
| if use_gpu: | |
| from dmv.utils.chamfer3D import dist_chamfer_3D | |
| chamLoss = dist_chamfer_3D.chamfer_3DDist() | |
| points1 = to_tensor(pts0, device='cuda').float()[None] | |
| points2 = to_tensor(pts1, device='cuda').cuda().float()[None] | |
| # points1 = torch.rand(32, 1000, 3).cuda() | |
| # points2 = torch.rand(32, 2000, 3, requires_grad=True).cuda() | |
| dist1, dist2, idx1, idx2 = chamLoss(points1, points2) | |
| dist1 = dist1 ** 0.5 | |
| dist2 = dist2 ** 0.5 | |
| loss = dist1.mean() + dist2.mean() | |
| dist2_threshed = dist2 < threshold | |
| dist1_threshed = dist1 < threshold | |
| if dist2_threshed.sum() == 0 or dist1_threshed.sum() == 0: | |
| fscore = torch.zeros(1, device='cuda') | |
| else: | |
| precision = (dist2 < threshold).float().mean() | |
| recal = (dist1 < threshold).float().mean() | |
| fscore = 2 * precision * recal / (precision + recal) | |
| return loss.item(), fscore.item() | |
| else: | |
| raise NotImplementedError() | |
| pts0 = to_tensor(pts0) | |
| pts1 = to_tensor(pts1) | |
| def square_distance(src, dst): | |
| return torch.sum((src[:, None, :] - dst[None, :, :]) ** 2, dim=-1) | |
| dist_src = torch.min(square_distance(pts0, pts1), dim=-1) | |
| dist_ref = torch.min(square_distance(pts1, pts0), dim=-1) | |
| chamfer_dist = torch.mean(dist_src[0]) + torch.mean(dist_ref[0]) | |
| if color0 is not None or color1 is not None: | |
| raise NotImplementedError() | |
| return chamfer_dist.item() | |
| def open3d_plane_segment_api(pts, distance_threshold, ransac_n=3, num_iterations=1000): | |
| import open3d as o3d | |
| pts = to_array(pts) | |
| pcd0 = o3d.geometry.PointCloud() | |
| pcd0.points = o3d.utility.Vector3dVector(pts) | |
| plane_model, inliers = pcd0.segment_plane(distance_threshold, | |
| ransac_n=ransac_n, | |
| num_iterations=num_iterations) | |
| return plane_model, inliers | |
| def point_plane_distance_api(pts, plane_model): | |
| a, b, c, d = plane_model.tolist() | |
| if isinstance(pts, torch.Tensor): | |
| dists = (a * pts[:, 0] + b * pts[:, 1] + c * pts[:, 2] + d).abs() / ((a * a + b * b + c * c) ** 0.5) | |
| else: | |
| dists = np.abs(a * pts[:, 0] + b * pts[:, 1] + c * pts[:, 2] + d) / ((a * a + b * b + c * c) ** 0.5) | |
| return dists | |
| def se3_exp_map(log_transform: torch.Tensor, eps: float = 1e-4): | |
| return pytorch3d.transforms.se3.se3_exp_map(log_transform, eps) | |
| def se3_log_map(transform: torch.Tensor, eps: float = 1e-4, cos_bound: float = 1e-4, backend=None, | |
| test_acc=True): | |
| if backend is None: | |
| loguru.logger.warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") | |
| loguru.logger.warning("!!!!se3_log_map backend is None!!!!") | |
| loguru.logger.warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") | |
| backend = 'pytorch3d' | |
| if backend == 'pytorch3d': | |
| dof6 = pytorch3d.transforms.se3.se3_log_map(transform, eps, cos_bound) | |
| elif backend == 'opencv': | |
| from pytorch3d.transforms.se3 import _se3_V_matrix, _get_se3_V_input | |
| # from pytorch3d.common.compat import solve | |
| log_rotation = [] | |
| for tsfm in transform: | |
| cv2_rot = -cv2.Rodrigues(to_array(tsfm[:3, :3]))[0] | |
| log_rotation.append(torch.from_numpy(cv2_rot.reshape(-1)).to(transform.device).float()) | |
| log_rotation = torch.stack(log_rotation, dim=0) | |
| T = transform[:, 3, :3] | |
| V = _se3_V_matrix(*_get_se3_V_input(log_rotation), eps=eps) | |
| log_translation = torch.linalg.solve(V, T[:, :, None])[:, :, 0] | |
| dof6 = torch.cat((log_translation, log_rotation), dim=1) | |
| else: | |
| raise NotImplementedError() | |
| if test_acc: | |
| err = (se3_exp_map(dof6) - transform).abs().max() | |
| if err > 0.1: | |
| raise RuntimeError() | |
| return dof6 | |
| def ray_box_intersection(ray_o, ray_d, aabb_min=None, aabb_max=None): | |
| """Returns 1-D intersection point along each ray if a ray-box intersection is detected | |
| If box frames are scaled to vertices between [-1., -1., -1.] and [1., 1., 1.] aabbb is not necessary | |
| Args: | |
| ray_o: Origin of the ray in each box frame, [rays, boxes, 3] | |
| ray_d: Unit direction of each ray in each box frame, [rays, boxes, 3] | |
| (aabb_min): Vertex of a 3D bounding box, [-1., -1., -1.] if not specified [boxes,3] | |
| (aabb_max): Vertex of a 3D bounding box, [1., 1., 1.] if not specified [boxes,3] | |
| Returns: | |
| z_ray_in: | |
| z_ray_out: | |
| intersection_map: Maps intersection values in z to their ray-box intersection | |
| """ | |
| # Source: https://medium.com/@bromanz/another-view-on-the-classic-ray-aabb-intersection-algorithm-for-bvh-traversal-41125138b525 | |
| # https://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms | |
| if aabb_min is None: | |
| aabb_min = torch.ones_like(ray_o) * -1. # tf.constant([-1., -1., -1.]) | |
| if aabb_max is None: | |
| aabb_max = torch.ones_like(ray_o) # tf.constant([1., 1., 1.]) | |
| inv_d = torch.reciprocal(ray_d) | |
| t_min = (aabb_min - ray_o) * inv_d | |
| t_max = (aabb_max - ray_o) * inv_d | |
| t0 = torch.minimum(t_min, t_max) | |
| t1 = torch.maximum(t_min, t_max) | |
| t_near = torch.maximum(torch.maximum(t0[..., 0], t0[..., 1]), t0[..., 2]) | |
| t_far = torch.minimum(torch.minimum(t1[..., 0], t1[..., 1]), t1[..., 2]) | |
| # Check if rays are inside boxes | |
| intersection_map = torch.nonzero(t_far > t_near) | |
| # Check that boxes are in front of the ray origin | |
| positive_far = torch.nonzero(t_far[intersection_map[:, 0], intersection_map[:, 1]] > 0) | |
| # positive_far = torch.nonzero(tf.gather_nd(t_far, intersection_map) > 0) | |
| # intersection_map = tf.gather_nd(intersection_map, positive_far) | |
| intersection_map = intersection_map[positive_far[:, 0]] | |
| if intersection_map.shape[0] != 0: | |
| z_ray_in = t_near[intersection_map[:, 0], intersection_map[:, 1]] | |
| z_ray_out = t_far[intersection_map[:, 0], intersection_map[:, 1]] | |
| else: | |
| return None, None, None | |
| return z_ray_in, z_ray_out, intersection_map | |
| def point_cloud_to_volume(points, vsize, radius=1.0): | |
| """ input is Nx3 points. | |
| output is vsize*vsize*vsize | |
| assumes points are in range [-radius, radius] | |
| """ | |
| vol = np.zeros((vsize, vsize, vsize)) | |
| voxel = 2 * radius / float(vsize) | |
| locations = (points + radius) / voxel | |
| locations = locations.astype(int) | |
| vol[locations[:, 0], locations[:, 1], locations[:, 2]] = 1.0 | |
| return vol | |
| def pts_to_box(pts, vsize, radius, dbg=False): | |
| occ_map = point_cloud_to_volume(pts, vsize, radius).sum(-1) > 0 | |
| retval, labels, stats, cent = cv2.connectedComponentsWithStats(occ_map.astype(np.uint8)) | |
| maxcomp = np.argmax(stats[1:, 4]) + 1 | |
| targetpts = np.argwhere(labels == maxcomp) | |
| targetpts[..., [0, 1]] = targetpts[..., [1, 0]] | |
| rect = cv2.minAreaRect(targetpts) | |
| if dbg: | |
| box = cv2.boxPoints(rect) | |
| box = np.int0(box) | |
| tmp = (255 * occ_map).copy().astype(np.uint8) | |
| cv2.drawContours(tmp, [box], 0, 255, 1) | |
| plt.imshow(tmp) | |
| plt.show() | |
| print() | |
| voxel = 2 * radius / float(vsize) | |
| (x, y), (width, height), theta = rect | |
| x = x * voxel - radius | |
| y = y * voxel - radius | |
| width = width * voxel | |
| height = height * voxel | |
| return (x, y), (width, height), theta | |
| def euler_to_pose(euler): | |
| """ | |
| :param euler: ...,tx,ty,tz,roll pitch yaw | |
| :return: | |
| """ | |
| if len(euler.shape) == 1: | |
| R = Rotation.from_euler("xyz", euler[3:]).as_matrix() | |
| t = euler[:3] | |
| pose = np.eye(4) | |
| pose[:3, :3] = R | |
| pose[:3, 3] = t | |
| else: | |
| raise NotImplementedError() | |
| return pose | |
| def pose_to_euler(pose): | |
| """ | |
| :param pose: | |
| :return: tx,ty,tz,roll pitch yaw | |
| """ | |
| rot = Rotation.from_matrix(pose[:3, :3]).as_euler("xyz") | |
| trans = pose[:3, 3] | |
| euler = np.concatenate([trans, rot]) | |
| return euler | |
| def Rt_to_pose(R, t=np.zeros(3)): | |
| pose = np.eye(4) | |
| pose[:3, :3] = R | |
| pose[:3, 3] = t | |
| return pose | |
| def reproj_error(K, pose, pts2d, pts3d): | |
| pts_cam = transform_points(pts3d, pose) | |
| pts_img = rect_to_img(K[0, 0], K[1, 1], K[0, 2], K[1, 2], pts_cam) | |
| err = np.linalg.norm(pts_img - pts2d, axis=-1).mean() | |
| return err | |
| def pnp(points_3d, points_2d, K, method=cv2.SOLVEPNP_ITERATIVE, ransac=False, ransac_reproj_error=1.0, | |
| ransac_iterations_count=10000): | |
| try: | |
| dist_coeffs = pnp.dist_coeffs | |
| except: | |
| dist_coeffs = np.zeros(shape=[8, 1], dtype='float64') | |
| assert points_3d.shape[0] == points_2d.shape[0], 'points 3D and points 2D must have same number of vertices' | |
| if method == cv2.SOLVEPNP_EPNP: | |
| points_3d = np.expand_dims(points_3d, 0) | |
| points_2d = np.expand_dims(points_2d, 0) | |
| points_2d = np.ascontiguousarray(points_2d.astype(np.float64)) | |
| points_3d = np.ascontiguousarray(points_3d.astype(np.float64)) | |
| K = K.astype(np.float64) | |
| if not ransac: | |
| _, R_exp, t = cv2.solvePnP(points_3d, | |
| points_2d, | |
| K, | |
| dist_coeffs, | |
| flags=method) | |
| else: | |
| _, R_exp, t, inliers = cv2.solvePnPRansac( | |
| points_3d, | |
| points_2d, | |
| K, | |
| dist_coeffs, | |
| reprojectionError=ransac_reproj_error, | |
| iterationsCount=ransac_iterations_count, | |
| flags=cv2.SOLVEPNP_EPNP, | |
| ) | |
| # , None, None, False, cv2.SOLVEPNP_UPNP) | |
| # R_exp, t, _ = cv2.solvePnPRansac(points_3D, | |
| # points_2D, | |
| # cameraMatrix, | |
| # distCoeffs, | |
| # reprojectionError=12.0) | |
| R, _ = cv2.Rodrigues(R_exp) | |
| # trans_3d=np.matmul(points_3d,R.transpose())+t.transpose() | |
| # if np.max(trans_3d[:,2]<0): | |
| # R=-R | |
| # t=-t | |
| # pose=np.concatenate([R, t], axis=-1) | |
| pose = Rt_to_pose(R, t[:, 0]) | |
| return pose | |
| def query_pose_error(pose_pred, pose_gt, unit='m'): | |
| """ | |
| Input: | |
| ----------- | |
| pose_pred: np.array 3*4 or 4*4 | |
| pose_gt: np.array 3*4 or 4*4 | |
| """ | |
| # Dim check: | |
| if pose_pred.shape[0] == 4: | |
| pose_pred = pose_pred[:3] | |
| if pose_gt.shape[0] == 4: | |
| pose_gt = pose_gt[:3] | |
| # Convert results' unit to cm | |
| if unit == 'm': | |
| translation_distance = np.linalg.norm(pose_pred[:, 3] - pose_gt[:, 3]) * 100 | |
| elif unit == 'cm': | |
| translation_distance = np.linalg.norm(pose_pred[:, 3] - pose_gt[:, 3]) | |
| elif unit == 'mm': | |
| translation_distance = np.linalg.norm(pose_pred[:, 3] - pose_gt[:, 3]) / 10 | |
| else: | |
| raise NotImplementedError | |
| rotation_diff = np.dot(pose_pred[:, :3], pose_gt[:, :3].T) | |
| trace = np.trace(rotation_diff) | |
| trace = trace if trace <= 3 else 3 | |
| angular_distance = np.rad2deg(np.arccos((trace - 1.0) / 2.0)) | |
| return angular_distance, translation_distance | |
| def dummy_sdf_volume(): | |
| sphere_radius = 0.2 # Sphere radius (in meters) | |
| sdf_volume_size = 1.0 # SDF volume size (in meters) | |
| resolution = 128 # Resolution of the SDF volume | |
| # Calculate the grid spacing | |
| grid_spacing = sdf_volume_size / resolution | |
| # Create an empty SDF volume grid | |
| sdf_volume = np.zeros((resolution, resolution, resolution), dtype=float) | |
| # Loop through each point in the SDF volume grid | |
| for x in range(resolution): | |
| for y in range(resolution): | |
| for z in range(resolution): | |
| # Calculate the world coordinates of the current point | |
| world_x = x * grid_spacing - sdf_volume_size / 2.0 | |
| world_y = y * grid_spacing - sdf_volume_size / 2.0 | |
| world_z = z * grid_spacing - sdf_volume_size / 2.0 | |
| # Calculate the distance from the current point to the sphere's center | |
| distance_to_center = np.sqrt( | |
| (world_x ** 2) + (world_y ** 2) + (world_z ** 2) | |
| ) | |
| # Calculate the SDF value for the current point | |
| sdf_volume[x, y, z] = distance_to_center - sphere_radius | |
| return sdf_volume | |
| def calc_pose(phis, thetas, size, radius=1.2): | |
| import torch | |
| def normalize(vectors): | |
| return vectors / (torch.norm(vectors, dim=-1, keepdim=True) + 1e-10) | |
| device = torch.device('cpu') | |
| thetas = torch.FloatTensor(thetas).to(device) | |
| phis = torch.FloatTensor(phis).to(device) | |
| centers = torch.stack([ | |
| radius * torch.sin(thetas) * torch.sin(phis), | |
| -radius * torch.cos(thetas) * torch.sin(phis), | |
| radius * torch.cos(phis), | |
| ], dim=-1) # [B, 3] | |
| # lookat | |
| forward_vector = normalize(centers).squeeze(0) | |
| up_vector = torch.FloatTensor([0, 0, 1]).to(device).unsqueeze(0).repeat(size, 1) | |
| right_vector = normalize(torch.cross(up_vector, forward_vector, dim=-1)) | |
| if right_vector.pow(2).sum() < 0.01: | |
| right_vector = torch.FloatTensor([0, 1, 0]).to(device).unsqueeze(0).repeat(size, 1) | |
| up_vector = normalize(torch.cross(forward_vector, right_vector, dim=-1)) | |
| poses = torch.eye(4, dtype=torch.float, device=device).unsqueeze(0).repeat(size, 1, 1) | |
| poses[:, :3, :3] = torch.stack((right_vector, up_vector, forward_vector), dim=-1) | |
| poses[:, :3, 3] = centers | |
| return poses | |
| _dsp_model=None | |
| def simplify_mesh(input_mesh: trimesh): | |
| if input_mesh.vertices.shape[0]==0: | |
| return input_mesh | |
| global _dsp_model | |
| if _dsp_model is None: | |
| from dsp import DiffSP | |
| _dsp_model = DiffSP() | |
| verts, faces = _dsp_model.forward(torch.from_numpy(input_mesh.vertices).float().cuda(), torch.from_numpy(input_mesh.faces).int().cuda(), iter=1000000, threshold=0.0001) | |
| verts = verts.cpu().numpy() | |
| faces = faces.cpu().numpy() | |
| output_mesh = trimesh.Trimesh(vertices=verts, faces=faces) | |
| return output_mesh |