import numpy as np import torch import sklearn from scipy import spatial def quat_degree_error(q1, q2): return 2 * torch.acos((q1 * q2).sum(1).abs()) * 180 / torch.pi def rotation_angular_error(R, Rgt): """ Computes rotation degree error of residual rotation angle [radians] Input: R - estimated rotation matrix [B, 3, 3] Rgt - groundtruth rotation matrix [B, 3, 3] Output: degree error """ residual = R.transpose(1, 2) @ Rgt trace = torch.diagonal(residual, dim1=-2, dim2=-1).sum(-1) cosine = (trace - 1) / 2 cosine = torch.clip(cosine, -0.99999, 0.99999) # handle numerical errors and NaNs R_err = torch.acos(cosine) # loss = F.l1_loss(R_err, torch.zeros_like(R_err)) return R_err def translation_angular_error(t, tgt): cosine = torch.cosine_similarity(t, tgt) cosine = torch.clip(cosine, -0.99999, 0.99999) # handle numerical errors and NaNs t_err = torch.acos(cosine) return t_err def error_auc(errors, thresholds, mode): """ Args: errors (list): [N,] thresholds (list) """ errors = [0] + sorted(list(errors)) recall = list(np.linspace(0, 1, len(errors))) aucs = [] # thresholds = [5, 10, 20] for thr in thresholds: last_index = np.searchsorted(errors, thr) y = recall[:last_index] + [recall[last_index-1]] x = errors[:last_index] + [thr] aucs.append(np.trapz(y, x) / thr) return {f'{mode}/auc@{t}': auc for t, auc in zip(thresholds, aucs)} def relative_pose_error(T_0to1, R, t, ignore_gt_t_thr=0.0): # angle error between 2 vectors t_gt = T_0to1[:3, 3] n = np.linalg.norm(t) * np.linalg.norm(t_gt) t_err = np.rad2deg(np.arccos(np.clip(np.dot(t, t_gt) / n, -1.0, 1.0))) t_err = np.minimum(t_err, 180 - t_err) # handle E ambiguity if np.linalg.norm(t_gt) < ignore_gt_t_thr: # pure rotation is challenging t_err = 0 # angle error between 2 rotation matrices R_gt = T_0to1[:3, :3] cos = (np.trace(np.dot(R.T, R_gt)) - 1) / 2 cos = np.clip(cos, -1., 1.) # handle numercial errors R_err = np.rad2deg(np.abs(np.arccos(cos))) return t_err, R_err def transform_pts_Rt(pts, R, t): """ Applies a rigid transformation to 3D points. :param pts: nx3 ndarray with 3D points. :param R: 3x3 rotation matrix. :param t: 3x1 translation vector. :return: nx3 ndarray with transformed 3D points. """ assert(pts.shape[1] == 3) pts_t = R.dot(pts.T) + t.reshape((3, 1)) return pts_t.T def reproj(K, R_est, t_est, R_gt, t_gt, pts): """ reprojection error. :param K intrinsic matrix :param R_est, t_est: Estimated pose (3x3 rot. matrix and 3x1 trans. vector). :param R_gt, t_gt: GT pose (3x3 rot. matrix and 3x1 trans. vector). :param model: Object model given by a dictionary where item 'pts' is nx3 ndarray with 3D model points. :return: Error of pose_est w.r.t. pose_gt. """ pts_est = transform_pts_Rt(pts, R_est, t_est) pts_gt = transform_pts_Rt(pts, R_gt, t_gt) pixels_est = K.dot(pts_est.T) pixels_est = pixels_est.T pixels_gt = K.dot(pts_gt.T) pixels_gt = pixels_gt.T n = pts.shape[0] est = np.zeros((n, 2), dtype=np.float32); est[:, 0] = np.divide(pixels_est[:, 0], pixels_est[:, 2]) est[:, 1] = np.divide(pixels_est[:, 1], pixels_est[:, 2]) gt = np.zeros((n, 2), dtype=np.float32); gt[:, 0] = np.divide(pixels_gt[:, 0], pixels_gt[:, 2]) gt[:, 1] = np.divide(pixels_gt[:, 1], pixels_gt[:, 2]) e = np.linalg.norm(est - gt, axis=1).mean() return e def add(R_est, t_est, R_gt, t_gt, pts): """ Average Distance of Model Points for objects with no indistinguishable views - by Hinterstoisser et al. (ACCV 2012). :param R_est, t_est: Estimated pose (3x3 rot. matrix and 3x1 trans. vector). :param R_gt, t_gt: GT pose (3x3 rot. matrix and 3x1 trans. vector). :param model: Object model given by a dictionary where item 'pts' is nx3 ndarray with 3D model points. :return: Error of pose_est w.r.t. pose_gt. """ pts_est = transform_pts_Rt(pts, R_est, t_est) pts_gt = transform_pts_Rt(pts, R_gt, t_gt) e = np.linalg.norm(pts_est - pts_gt, axis=1).mean() return e def adi(R_est, t_est, R_gt, t_gt, pts): """ Average Distance of Model Points for objects with indistinguishable views - by Hinterstoisser et al. (ACCV 2012). :param R_est, t_est: Estimated pose (3x3 rot. matrix and 3x1 trans. vector). :param R_gt, t_gt: GT pose (3x3 rot. matrix and 3x1 trans. vector). :param model: Object model given by a dictionary where item 'pts' is nx3 ndarray with 3D model points. :return: Error of pose_est w.r.t. pose_gt. """ pts_est = transform_pts_Rt(pts, R_est, t_est) pts_gt = transform_pts_Rt(pts, R_gt, t_gt) # Calculate distances to the nearest neighbors from pts_gt to pts_est nn_index = spatial.cKDTree(pts_est) nn_dists, _ = nn_index.query(pts_gt, k=1) e = nn_dists.mean() return e def compute_continuous_auc(metrics, thresholds): x_range = thresholds.max() - thresholds.min() results = np.array(metrics) accuracies = [(results <= t).sum() / len(results) for t in thresholds] auc = sklearn.metrics.auc(thresholds, accuracies) / x_range return auc