Spaces:
Runtime error
Runtime error
import numpy as np | |
import torch | |
import common.data_utils as data_utils | |
from common.np_utils import permute_np | |
""" | |
Useful geometric operations, e.g. Perspective projection and a differentiable Rodrigues formula | |
Parts of the code are taken from https://github.com/MandyMo/pytorch_HMR | |
""" | |
def to_xy(x_homo): | |
assert isinstance(x_homo, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert x_homo.shape[1] == 3 | |
assert len(x_homo.shape) == 2 | |
batch_size = x_homo.shape[0] | |
x = torch.ones(batch_size, 2, device=x_homo.device) | |
x = x_homo[:, :2] / x_homo[:, 2:3] | |
return x | |
def to_xyz(x_homo): | |
assert isinstance(x_homo, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert x_homo.shape[1] == 4 | |
assert len(x_homo.shape) == 2 | |
batch_size = x_homo.shape[0] | |
x = torch.ones(batch_size, 3, device=x_homo.device) | |
x = x_homo[:, :3] / x_homo[:, 3:4] | |
return x | |
def to_homo(x): | |
assert isinstance(x, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert x.shape[1] == 3 | |
assert len(x.shape) == 2 | |
batch_size = x.shape[0] | |
x_homo = torch.ones(batch_size, 4, device=x.device) | |
x_homo[:, :3] = x.clone() | |
return x_homo | |
def to_homo_batch(x): | |
assert isinstance(x, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert x.shape[2] == 3 | |
assert len(x.shape) == 3 | |
batch_size = x.shape[0] | |
num_pts = x.shape[1] | |
x_homo = torch.ones(batch_size, num_pts, 4, device=x.device) | |
x_homo[:, :, :3] = x.clone() | |
return x_homo | |
def to_xyz_batch(x_homo): | |
""" | |
Input: (B, N, 4) | |
Ouput: (B, N, 3) | |
""" | |
assert isinstance(x_homo, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert x_homo.shape[2] == 4 | |
assert len(x_homo.shape) == 3 | |
batch_size = x_homo.shape[0] | |
num_pts = x_homo.shape[1] | |
x = torch.ones(batch_size, num_pts, 3, device=x_homo.device) | |
x = x_homo[:, :, :3] / x_homo[:, :, 3:4] | |
return x | |
def to_xy_batch(x_homo): | |
assert isinstance(x_homo, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert x_homo.shape[2] == 3 | |
assert len(x_homo.shape) == 3 | |
batch_size = x_homo.shape[0] | |
num_pts = x_homo.shape[1] | |
x = torch.ones(batch_size, num_pts, 2, device=x_homo.device) | |
x = x_homo[:, :, :2] / x_homo[:, :, 2:3] | |
return x | |
# VR Distortion Correction Using Vertex Displacement | |
# https://stackoverflow.com/questions/44489686/camera-lens-distortion-in-opengl | |
def distort_pts3d_all(_pts_cam, dist_coeffs): | |
# egocentric cameras commonly has heavy distortion | |
# this function transform points in the undistorted camera coord | |
# to distorted camera coord such that the 2d projection can match the pixels. | |
pts_cam = _pts_cam.clone().double() | |
z = pts_cam[:, :, 2] | |
z_inv = 1 / z | |
x1 = pts_cam[:, :, 0] * z_inv | |
y1 = pts_cam[:, :, 1] * z_inv | |
# precalculations | |
x1_2 = x1 * x1 | |
y1_2 = y1 * y1 | |
x1_y1 = x1 * y1 | |
r2 = x1_2 + y1_2 | |
r4 = r2 * r2 | |
r6 = r4 * r2 | |
r_dist = (1 + dist_coeffs[0] * r2 + dist_coeffs[1] * r4 + dist_coeffs[4] * r6) / ( | |
1 + dist_coeffs[5] * r2 + dist_coeffs[6] * r4 + dist_coeffs[7] * r6 | |
) | |
# full (rational + tangential) distortion | |
x2 = x1 * r_dist + 2 * dist_coeffs[2] * x1_y1 + dist_coeffs[3] * (r2 + 2 * x1_2) | |
y2 = y1 * r_dist + 2 * dist_coeffs[3] * x1_y1 + dist_coeffs[2] * (r2 + 2 * y1_2) | |
# denormalize for projection (which is a linear operation) | |
cam_pts_dist = torch.stack([x2 * z, y2 * z, z], dim=2).float() | |
return cam_pts_dist | |
def rigid_tf_torch_batch(points, R, T): | |
""" | |
Performs rigid transformation to incoming points but batched | |
Q = (points*R.T) + T | |
points: (batch, num, 3) | |
R: (batch, 3, 3) | |
T: (batch, 3, 1) | |
out: (batch, num, 3) | |
""" | |
points_out = torch.bmm(R, points.permute(0, 2, 1)) + T | |
points_out = points_out.permute(0, 2, 1) | |
return points_out | |
def solve_rigid_tf_np(A: np.ndarray, B: np.ndarray): | |
""" | |
“Least-Squares Fitting of Two 3-D Point Sets”, Arun, K. S. , May 1987 | |
Input: expects Nx3 matrix of points | |
Returns R,t | |
R = 3x3 rotation matrix | |
t = 3x1 column vector | |
This function should be a fix for compute_rigid_tf when the det == -1 | |
""" | |
assert A.shape == B.shape | |
A = A.T | |
B = B.T | |
num_rows, num_cols = A.shape | |
if num_rows != 3: | |
raise Exception(f"matrix A is not 3xN, it is {num_rows}x{num_cols}") | |
num_rows, num_cols = B.shape | |
if num_rows != 3: | |
raise Exception(f"matrix B is not 3xN, it is {num_rows}x{num_cols}") | |
# find mean column wise | |
centroid_A = np.mean(A, axis=1) | |
centroid_B = np.mean(B, axis=1) | |
# ensure centroids are 3x1 | |
centroid_A = centroid_A.reshape(-1, 1) | |
centroid_B = centroid_B.reshape(-1, 1) | |
# subtract mean | |
Am = A - centroid_A | |
Bm = B - centroid_B | |
H = Am @ np.transpose(Bm) | |
# find rotation | |
U, S, Vt = np.linalg.svd(H) | |
R = Vt.T @ U.T | |
# special reflection case | |
if np.linalg.det(R) < 0: | |
Vt[2, :] *= -1 | |
R = Vt.T @ U.T | |
t = -R @ centroid_A + centroid_B | |
return R, t | |
def batch_solve_rigid_tf(A, B): | |
""" | |
“Least-Squares Fitting of Two 3-D Point Sets”, Arun, K. S. , May 1987 | |
Input: expects BxNx3 matrix of points | |
Returns R,t | |
R = Bx3x3 rotation matrix | |
t = Bx3x1 column vector | |
""" | |
assert A.shape == B.shape | |
dev = A.device | |
A = A.cpu().numpy() | |
B = B.cpu().numpy() | |
A = permute_np(A, (0, 2, 1)) | |
B = permute_np(B, (0, 2, 1)) | |
batch, num_rows, num_cols = A.shape | |
if num_rows != 3: | |
raise Exception(f"matrix A is not 3xN, it is {num_rows}x{num_cols}") | |
_, num_rows, num_cols = B.shape | |
if num_rows != 3: | |
raise Exception(f"matrix B is not 3xN, it is {num_rows}x{num_cols}") | |
# find mean column wise | |
centroid_A = np.mean(A, axis=2) | |
centroid_B = np.mean(B, axis=2) | |
# ensure centroids are 3x1 | |
centroid_A = centroid_A.reshape(batch, -1, 1) | |
centroid_B = centroid_B.reshape(batch, -1, 1) | |
# subtract mean | |
Am = A - centroid_A | |
Bm = B - centroid_B | |
H = np.matmul(Am, permute_np(Bm, (0, 2, 1))) | |
# find rotation | |
U, S, Vt = np.linalg.svd(H) | |
R = np.matmul(permute_np(Vt, (0, 2, 1)), permute_np(U, (0, 2, 1))) | |
# special reflection case | |
neg_idx = np.linalg.det(R) < 0 | |
if neg_idx.sum() > 0: | |
raise Exception( | |
f"some rotation matrices are not orthogonal; make sure implementation is correct for such case: {neg_idx}" | |
) | |
Vt[neg_idx, 2, :] *= -1 | |
R[neg_idx, :, :] = np.matmul( | |
permute_np(Vt[neg_idx], (0, 2, 1)), permute_np(U[neg_idx], (0, 2, 1)) | |
) | |
t = np.matmul(-R, centroid_A) + centroid_B | |
R = torch.FloatTensor(R).to(dev) | |
t = torch.FloatTensor(t).to(dev) | |
return R, t | |
def rigid_tf_np(points, R, T): | |
""" | |
Performs rigid transformation to incoming points | |
Q = (points*R.T) + T | |
points: (num, 3) | |
R: (3, 3) | |
T: (1, 3) | |
out: (num, 3) | |
""" | |
assert isinstance(points, np.ndarray) | |
assert isinstance(R, np.ndarray) | |
assert isinstance(T, np.ndarray) | |
assert len(points.shape) == 2 | |
assert points.shape[1] == 3 | |
assert R.shape == (3, 3) | |
assert T.shape == (1, 3) | |
points_new = np.matmul(R, points.T).T + T | |
return points_new | |
def transform_points(world2cam_mat, pts): | |
""" | |
Map points from one coord to another based on the 4x4 matrix. | |
e.g., map points from world to camera coord. | |
pts: (N, 3), in METERS!! | |
world2cam_mat: (4, 4) | |
Output: points in cam coord (N, 3) | |
We follow this convention: | |
| R T | |pt| | |
| 0 1 | * | 1| | |
i.e. we rotate first then translate as T is the camera translation not position. | |
""" | |
assert isinstance(pts, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert isinstance(world2cam_mat, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert world2cam_mat.shape == (4, 4) | |
assert len(pts.shape) == 2 | |
assert pts.shape[1] == 3 | |
pts_homo = to_homo(pts) | |
# mocap to cam | |
pts_cam_homo = torch.matmul(world2cam_mat, pts_homo.T).T | |
pts_cam = to_xyz(pts_cam_homo) | |
assert pts_cam.shape[1] == 3 | |
return pts_cam | |
def transform_points_batch(world2cam_mat, pts): | |
""" | |
Map points from one coord to another based on the 4x4 matrix. | |
e.g., map points from world to camera coord. | |
pts: (B, N, 3), in METERS!! | |
world2cam_mat: (B, 4, 4) | |
Output: points in cam coord (B, N, 3) | |
We follow this convention: | |
| R T | |pt| | |
| 0 1 | * | 1| | |
i.e. we rotate first then translate as T is the camera translation not position. | |
""" | |
assert isinstance(pts, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert isinstance(world2cam_mat, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert world2cam_mat.shape[1:] == (4, 4) | |
assert len(pts.shape) == 3 | |
assert pts.shape[2] == 3 | |
batch_size = pts.shape[0] | |
pts_homo = to_homo_batch(pts) | |
# mocap to cam | |
pts_cam_homo = torch.bmm(world2cam_mat, pts_homo.permute(0, 2, 1)).permute(0, 2, 1) | |
pts_cam = to_xyz_batch(pts_cam_homo) | |
assert pts_cam.shape[2] == 3 | |
return pts_cam | |
def project2d_batch(K, pts_cam): | |
""" | |
K: (B, 3, 3) | |
pts_cam: (B, N, 3) | |
""" | |
assert isinstance(K, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert isinstance(pts_cam, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert K.shape[1:] == (3, 3) | |
assert pts_cam.shape[2] == 3 | |
assert len(pts_cam.shape) == 3 | |
pts2d_homo = torch.bmm(K, pts_cam.permute(0, 2, 1)).permute(0, 2, 1) | |
pts2d = to_xy_batch(pts2d_homo) | |
return pts2d | |
def project2d_norm_batch(K, pts_cam, patch_width): | |
""" | |
K: (B, 3, 3) | |
pts_cam: (B, N, 3) | |
""" | |
assert isinstance(K, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert isinstance(pts_cam, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert K.shape[1:] == (3, 3) | |
assert pts_cam.shape[2] == 3 | |
assert len(pts_cam.shape) == 3 | |
v2d = project2d_batch(K, pts_cam) | |
v2d_norm = data_utils.normalize_kp2d(v2d, patch_width) | |
return v2d_norm | |
def project2d(K, pts_cam): | |
assert isinstance(K, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert isinstance(pts_cam, (torch.FloatTensor, torch.cuda.FloatTensor)) | |
assert K.shape == (3, 3) | |
assert pts_cam.shape[1] == 3 | |
assert len(pts_cam.shape) == 2 | |
pts2d_homo = torch.matmul(K, pts_cam.T).T | |
pts2d = to_xy(pts2d_homo) | |
return pts2d | |