151 lines
5.8 KiB
Python
151 lines
5.8 KiB
Python
import numpy as np
|
||
from itertools import combinations
|
||
from easymocap.mytools.camera_utils import Undistort
|
||
from easymocap.mytools.triangulator import iterative_triangulate
|
||
|
||
def batch_triangulate(keypoints_, Pall, min_view=2):
|
||
""" triangulate the keypoints of whole body
|
||
|
||
Args:
|
||
keypoints_ (nViews, nJoints, 3): 2D detections
|
||
Pall (nViews, 3, 4): projection matrix of each view
|
||
min_view (int, optional): min view for visible points. Defaults to 2.
|
||
|
||
Returns:
|
||
keypoints3d: (nJoints, 4)
|
||
"""
|
||
# keypoints: (nViews, nJoints, 3)
|
||
# Pall: (nViews, 3, 4)
|
||
# A: (nJoints, nViewsx2, 4), x: (nJoints, 4, 1); b: (nJoints, nViewsx2, 1)
|
||
v = (keypoints_[:, :, -1]>0).sum(axis=0)
|
||
valid_joint = np.where(v >= min_view)[0]
|
||
keypoints = keypoints_[:, valid_joint]
|
||
conf3d = keypoints[:, :, -1].sum(axis=0)/v[valid_joint]
|
||
# P2: P矩阵的最后一行:(1, nViews, 1, 4)
|
||
P0 = Pall[None, :, 0, :]
|
||
P1 = Pall[None, :, 1, :]
|
||
P2 = Pall[None, :, 2, :]
|
||
# uP2: x坐标乘上P2: (nJoints, nViews, 1, 4)
|
||
uP2 = keypoints[:, :, 0].T[:, :, None] * P2
|
||
vP2 = keypoints[:, :, 1].T[:, :, None] * P2
|
||
conf = keypoints[:, :, 2].T[:, :, None]
|
||
Au = conf * (uP2 - P0)
|
||
Av = conf * (vP2 - P1)
|
||
A = np.hstack([Au, Av])
|
||
u, s, v = np.linalg.svd(A)
|
||
X = v[:, -1, :]
|
||
X = X / X[:, 3:]
|
||
# out: (nJoints, 4)
|
||
result = np.zeros((keypoints_.shape[1], 4))
|
||
result[valid_joint, :3] = X[:, :3]
|
||
result[valid_joint, 3] = conf3d #* (conf[..., 0].sum(axis=-1)>min_view)
|
||
return result
|
||
|
||
def project_wo_dist(keypoints, RT, einsum='vab,kb->vka'):
|
||
homo = np.concatenate([keypoints[..., :3], np.ones_like(keypoints[..., :1])], axis=-1)
|
||
kpts2d = np.einsum(einsum, RT, homo)
|
||
depth = kpts2d[..., 2]
|
||
kpts2d[..., :2] /= kpts2d[..., 2:]
|
||
return kpts2d, depth
|
||
|
||
class SimpleTriangulate:
|
||
def __init__(self, mode):
|
||
self.mode = mode
|
||
|
||
@staticmethod
|
||
def undistort(points, cameras):
|
||
nViews = len(points)
|
||
pelvis_undis = []
|
||
for nv in range(nViews):
|
||
camera = {key:cameras[key][nv] for key in ['R', 'T', 'K', 'dist']}
|
||
if points[nv].shape[0] > 0:
|
||
pelvis = Undistort.points(points[nv], camera['K'], camera['dist'])
|
||
else:
|
||
pelvis = points[nv].copy()
|
||
pelvis_undis.append(pelvis)
|
||
return pelvis_undis
|
||
|
||
def __call__(self, keypoints, cameras):
|
||
'''
|
||
keypoints: [nViews, nJoints, 3]
|
||
|
||
output:
|
||
keypoints3d: (nJoints, 4)
|
||
'''
|
||
keypoints = self.undistort(keypoints, cameras)
|
||
keypoints = np.stack(keypoints)
|
||
if self.mode == 'naive':
|
||
keypoints3d = batch_triangulate(keypoints, cameras['P'])
|
||
else:
|
||
keypoints3d, k2d = iterative_triangulate(keypoints, cameras['P'], dist_max=25)
|
||
return {'keypoints3d': keypoints3d}
|
||
|
||
class RobustTriangulate(SimpleTriangulate):
|
||
def __init__(self, mode, cfg):
|
||
super().__init__(mode)
|
||
self.cache_view = {}
|
||
self.cfg = cfg
|
||
|
||
def try_to_triangulate_and_project(self, index, keypoints, cameras):
|
||
# 选择最好的3个视角
|
||
P = cameras['P'][index]
|
||
kpts = keypoints[index][:, None]
|
||
k3d = batch_triangulate(kpts, P)
|
||
k2d, depth = project_wo_dist(k3d, P)
|
||
dist_repro = np.linalg.norm(k2d[..., :2] - kpts[..., :2], axis=-1).mean(axis=-1)
|
||
return k3d, dist_repro
|
||
|
||
def robust_triangulate(self, keypoints, cameras):
|
||
# 选择最好的3个视角
|
||
# TODO: 移除不合理的视角
|
||
nViews = keypoints.shape[0]
|
||
if nViews not in self.cache_view:
|
||
views = list(range(nViews))
|
||
combs = list(combinations(views, self.cfg.triangulate.init_views))
|
||
combs = np.array(combs)
|
||
self.cache_view[nViews] = combs
|
||
combs = self.cache_view[nViews]
|
||
keypoints_comb = keypoints[combs]
|
||
conf_sum = keypoints_comb[..., 2].mean(axis=1) * (keypoints_comb[..., 2]>0.05).all(axis=1)
|
||
comb_sort_id = (-conf_sum).argsort()
|
||
flag_find_init = False
|
||
for comb_id in comb_sort_id:
|
||
if conf_sum[comb_id] < 0.1:
|
||
break
|
||
comb = combs[comb_id]
|
||
k3d, dist_repro = self.try_to_triangulate_and_project(comb, keypoints, cameras)
|
||
if (dist_repro < self.cfg.triangulate.repro_init).all():
|
||
flag_find_init = True
|
||
init = comb.tolist()
|
||
break
|
||
if not flag_find_init:
|
||
print('Cannot find good initialize pair')
|
||
import ipdb; ipdb.set_trace()
|
||
view_idxs = (-keypoints[:, -1]).argsort()
|
||
for view_idx in view_idxs:
|
||
if view_idx in init:
|
||
continue
|
||
if keypoints[view_idx, 2] < 0.1:
|
||
continue
|
||
k3d, dist_repro = self.try_to_triangulate_and_project(init+[view_idx], keypoints, cameras)
|
||
if (dist_repro < self.cfg.triangulate.repro_2d).all():
|
||
# print('Add view {}'.format(view_idx))
|
||
init.append(view_idx)
|
||
return k3d, init
|
||
|
||
def __call__(self, keypoints, cameras):
|
||
"""
|
||
keypoints: (nViews, nJoints, 3)
|
||
cameras: (nViews, 3, 4)
|
||
"""
|
||
nViews, nJoints, _ = keypoints.shape
|
||
keypoints_undis = np.stack(self.undistort(keypoints, cameras))
|
||
# for each points, find good initial pairs
|
||
points_all = np.zeros((nJoints, 4))
|
||
keypoints_copy = keypoints.copy()
|
||
for nj in range(nJoints):
|
||
point, select_views = self.robust_triangulate(keypoints_undis[:, nj], cameras)
|
||
points_all[nj:nj+1] = point
|
||
keypoints_copy[select_views, nj, 2] += 10
|
||
keypoints_copy[:, nj, 2] = np.clip(keypoints_copy[:, nj, 2]-10, 0, 1)
|
||
return {'keypoints3d': points_all, 'keypoints_select': keypoints_copy} |