import numpy as np import torch import poselib 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.0, 1.0) # handle numercial errors R_err = np.rad2deg(np.abs(np.arccos(cos))) return t_err, R_err def intrinsics_to_camera(K): px, py = K[0, 2], K[1, 2] fx, fy = K[0, 0], K[1, 1] return { "model": "PINHOLE", "width": int(2 * px), "height": int(2 * py), "params": [fx, fy, px, py], } def estimate_pose(kpts0, kpts1, K0, K1, thresh, conf=0.99999): M, info = poselib.estimate_relative_pose( kpts0, kpts1, intrinsics_to_camera(K0), intrinsics_to_camera(K1), {"max_epipolar_error": thresh, "success_prob": conf, "min_iterations": 20, "max_iterations": 1_000}, ) R, t, inl = M.R, M.t, info["inliers"] inl = np.array(inl) ret = (R, t, inl) return ret def tensor2bgr(t): return (t.cpu()[0].permute(1,2,0).numpy()*255).astype(np.uint8) def compute_pose_error(match_fn,data): result = {} with torch.no_grad(): mkpts0,mkpts1=match_fn(tensor2bgr(data["image0"]),tensor2bgr(data["image1"])) mkpts0=mkpts0 * data["scale0"].numpy() mkpts1=mkpts1 * data["scale1"].numpy() K0, K1 = data["K0"][0].numpy(), data["K1"][0].numpy() T_0to1 = data["T_0to1"][0].numpy() T_1to0 = data["T_1to0"][0].numpy() result={} conf = 0.99999 ret = estimate_pose(mkpts0,mkpts1,K0,K1,4.0,conf) if ret is not None: R, t, inliers = ret t_err, R_err = relative_pose_error(T_0to1, R, t, ignore_gt_t_thr=0.0) result['R_err'] = R_err result['t_err'] = t_err return result def error_auc(errors, thresholds=[5, 10, 20]): """ Args: errors (list): [N,] thresholds (list) """ errors = [0] + sorted(list(errors)) recall = list(np.linspace(0, 1, len(errors))) aucs = [] 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'auc@{t}': auc for t, auc in zip(thresholds, aucs)} def compute_maa(pairs, thresholds=[5, 10, 20]): # print("auc / mAcc on %d pairs" % (len(pairs))) errors = [] for p in pairs: et = p['t_err'] er = p['R_err'] errors.append(max(et, er)) d_err_auc = error_auc(errors) # for k,v in d_err_auc.items(): # print(k, ': ', '%.1f'%(v*100)) errors = np.array(errors) for t in thresholds: acc = (errors <= t).sum() / len(errors) # print("mAcc@%d: %.1f "%(t, acc*100)) return d_err_auc,errors def homo_trans(coord, H): kpt_num = coord.shape[0] homo_coord = np.concatenate((coord, np.ones((kpt_num, 1))), axis=-1) proj_coord = np.matmul(H, homo_coord.T).T proj_coord = proj_coord / proj_coord[:, 2][..., None] proj_coord = proj_coord[:, 0:2] return proj_coord