353 lines
14 KiB
Python
353 lines
14 KiB
Python
|
'''
|
||
|
@ Date: 2020-11-19 10:49:26
|
||
|
@ Author: Qing Shuai
|
||
|
@ LastEditors: Qing Shuai
|
||
|
@ LastEditTime: 2021-01-14 20:19:34
|
||
|
@ FilePath: /EasyMocap/code/pyfitting/optimize_simple.py
|
||
|
'''
|
||
|
import numpy as np
|
||
|
import torch
|
||
|
from .lbfgs import LBFGS
|
||
|
from .optimize import FittingMonitor, grad_require, FittingLog
|
||
|
from .lossfactory import SMPLAngleLoss, SmoothLoss, RegularizationLoss, ReprojectionLoss
|
||
|
|
||
|
def create_closure(optimizer, body_model, body_params, body_params_init, cam_params,
|
||
|
keypoints2d, bboxes, weight_loss_, debug=False, verbose=False):
|
||
|
K, Rc, Tc = cam_params['K'], cam_params['Rc'], cam_params['Tc']
|
||
|
bbox_sizes = np.maximum(bboxes[:, 2] - bboxes[:, 0], bboxes[:, 3] - bboxes[:, 1])/100
|
||
|
inv_bbox_sizes = torch.Tensor(1./bbox_sizes[:, None, None]).to(body_model.device)**2
|
||
|
nFrames = keypoints2d.shape[0]
|
||
|
nPoses = body_params['poses'].shape[0]
|
||
|
if nFrames == nPoses:
|
||
|
weight_loss = {key:val/nFrames for key, val in weight_loss_.items()} # normalize by frames
|
||
|
else:
|
||
|
# the multiple views case
|
||
|
weight_loss = weight_loss_.copy()
|
||
|
weight_loss['repro'] /= nFrames
|
||
|
angle_prior = SMPLAngleLoss(keypoints2d)
|
||
|
|
||
|
if len(K.shape) == 2:
|
||
|
K, Rc, Tc = K[None, :, :], Rc[None, :, :], Tc[None, :, :]
|
||
|
def closure(debug=False):
|
||
|
optimizer.zero_grad()
|
||
|
keypoints3d = body_model(return_verts=False, return_tensor=True, **body_params)
|
||
|
loss_dict = {}
|
||
|
loss_dict['repro'] = ReprojectionLoss(keypoints3d, keypoints2d, K, Rc, Tc, inv_bbox_sizes)
|
||
|
# smooth
|
||
|
loss_dict.update(SmoothLoss(body_params, ['poses', 'Th'], weight_loss))
|
||
|
# regularize
|
||
|
loss_dict.update(RegularizationLoss(body_params, body_params_init, weight_loss))
|
||
|
loss_dict['reg_poses_zero'] = angle_prior.loss(body_params['poses'])
|
||
|
# fittingLog.step(loss_dict, weight_loss)
|
||
|
if verbose:
|
||
|
print(' '.join([key + ' %f'%(loss_dict[key].item()*weight_loss[key])
|
||
|
for key in loss_dict.keys() if weight_loss[key]>0]))
|
||
|
loss = sum([loss_dict[key]*weight_loss[key]
|
||
|
for key in loss_dict.keys()])
|
||
|
if not debug:
|
||
|
loss.backward()
|
||
|
return loss
|
||
|
else:
|
||
|
return loss_dict
|
||
|
return closure
|
||
|
|
||
|
def findNeighborIdx(nf, nF, nspan):
|
||
|
idx = [i for i in range(nf-nspan, nf+nspan+1) if i >=0 and i<nF and i!= nf]
|
||
|
return idx
|
||
|
|
||
|
def viewSelection(body_params, span=5):
|
||
|
""" Apply view selection for body parameters
|
||
|
|
||
|
Args:
|
||
|
body_params (DictParams)
|
||
|
"""
|
||
|
assert span % 2 == 1, 'span = {} must be an odd number'.format(span)
|
||
|
nspan = (span - 1)//2
|
||
|
# for linear data
|
||
|
nF = body_params['poses'].shape[0]
|
||
|
min_thres = {'Th': 0.2, 'poses': 0.2}
|
||
|
for key in ['poses', 'Th']:
|
||
|
weight = np.ones((nF))
|
||
|
for nf in range(nF):
|
||
|
# first find neighbor
|
||
|
neighbor_idx = findNeighborIdx(nf, nF, nspan)
|
||
|
dist = np.linalg.norm(body_params[key][neighbor_idx, :] - body_params[key][nf:nf+1, :], axis=1)
|
||
|
conf = dist.min()/np.linalg.norm(body_params[key][nf])
|
||
|
weight[nf] = min_thres[key] - conf
|
||
|
if conf > min_thres[key]:
|
||
|
weight[nf] = 0
|
||
|
for nf in range(nF):
|
||
|
neighbor_idx = findNeighborIdx(nf, nF, nspan) + [nf]
|
||
|
weight_cur = weight[neighbor_idx]
|
||
|
weight_sum = weight_cur.sum()
|
||
|
if weight_sum == 0:
|
||
|
pass
|
||
|
else:
|
||
|
val = body_params[key][neighbor_idx, :]*weight_cur[:, None]
|
||
|
val = val.sum(axis=0)/weight_sum
|
||
|
# simply remove outliers
|
||
|
body_params[key][nf] = val
|
||
|
# for rotation data
|
||
|
pass
|
||
|
return body_params
|
||
|
|
||
|
|
||
|
def optimizeVideo(body_model, params_init, cam_params,
|
||
|
keypoints, bbox,
|
||
|
weight, cfg=None):
|
||
|
""" simple function for optimizing video/image
|
||
|
|
||
|
Args:
|
||
|
body_model (SMPL model)
|
||
|
params_init (DictParam): poses(F, 72), shapes(1/F, 10), Rh(F, 3), Th(F, 3)
|
||
|
cam_params (DictParam): K, R, T
|
||
|
keypoints (F, J, 3): 2D keypoints
|
||
|
bbox (F, 7): bounding box
|
||
|
weight (Dict): string:float
|
||
|
cfg (Config): Config Node controling running mode
|
||
|
"""
|
||
|
device = body_model.device
|
||
|
cam_params = {key:torch.Tensor(val).to(device) for key, val in cam_params.items()}
|
||
|
keypoints = torch.Tensor(keypoints).to(device)
|
||
|
body_params = {key:torch.Tensor(val).to(device) for key, val in params_init.items()}
|
||
|
body_params_init = {key:val.clone() for key, val in body_params.items()}
|
||
|
if cfg is None:
|
||
|
opt_params = [body_params['Rh'], body_params['Th'], body_params['poses']]
|
||
|
else:
|
||
|
opt_params = []
|
||
|
if cfg.OPT_R:
|
||
|
opt_params.append(body_params['Rh'])
|
||
|
if cfg.OPT_T:
|
||
|
opt_params.append(body_params['Th'])
|
||
|
if cfg.OPT_POSE:
|
||
|
opt_params.append(body_params['poses'])
|
||
|
|
||
|
grad_require(opt_params, True)
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
|
||
|
closure = create_closure(optimizer, body_model, body_params,
|
||
|
body_params_init, cam_params,
|
||
|
keypoints, bbox, weight, verbose=cfg.VERBOSE)
|
||
|
|
||
|
fitting = FittingMonitor(ftol=1e-4)
|
||
|
final_loss = fitting.run_fitting(optimizer, closure, opt_params)
|
||
|
fitting.close()
|
||
|
grad_require(opt_params, False)
|
||
|
loss_dict = closure(debug=True)
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
body_params = {key:val.detach().cpu().numpy() for key, val in body_params.items()}
|
||
|
return body_params
|
||
|
|
||
|
def optimizeMultiImage(body_model, params_init, cam_params,
|
||
|
keypoints, bbox,
|
||
|
weight, cfg):
|
||
|
""" simple function for optimizing multiple images
|
||
|
|
||
|
Args:
|
||
|
body_model (SMPL model)
|
||
|
params_init (DictParam): poses(1, 72), shapes(1, 10), Rh(1, 3), Th(1, 3)
|
||
|
cam_params (DictParam): K(nV, 3, 3), R(nV, 3, 3), T(nV, 3, 1)
|
||
|
keypoints (nV, J, 3): 2D keypoints
|
||
|
bbox (nV, 7): bounding box
|
||
|
weight (Dict): string:float
|
||
|
cfg (Config): Config Node controling running mode
|
||
|
"""
|
||
|
device = body_model.device
|
||
|
cam_params = {key:torch.Tensor(val).to(device) for key, val in cam_params.items()}
|
||
|
keypoints = torch.Tensor(keypoints).to(device)
|
||
|
body_params = {key:torch.Tensor(val).to(device) for key, val in params_init.items()}
|
||
|
body_params_init = {key:val.clone() for key, val in body_params.items()}
|
||
|
if cfg is None:
|
||
|
opt_params = [body_params['Rh'], body_params['Th'], body_params['poses']]
|
||
|
else:
|
||
|
opt_params = []
|
||
|
if cfg.OPT_R:
|
||
|
opt_params.append(body_params['Rh'])
|
||
|
if cfg.OPT_T:
|
||
|
opt_params.append(body_params['Th'])
|
||
|
if cfg.OPT_POSE:
|
||
|
opt_params.append(body_params['poses'])
|
||
|
if cfg.OPT_SHAPE:
|
||
|
opt_params.append(body_params['shapes'])
|
||
|
grad_require(opt_params, True)
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
|
||
|
closure = create_closure(optimizer, body_model, body_params,
|
||
|
body_params_init, cam_params,
|
||
|
keypoints, bbox, weight, verbose=cfg.VERBOSE)
|
||
|
|
||
|
fitting = FittingMonitor(ftol=1e-4)
|
||
|
final_loss = fitting.run_fitting(optimizer, closure, opt_params)
|
||
|
fitting.close()
|
||
|
grad_require(opt_params, False)
|
||
|
loss_dict = closure(debug=True)
|
||
|
for key in loss_dict.keys():
|
||
|
loss_dict[key] = loss_dict[key].item()
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
body_params = {key:val.detach().cpu().numpy() for key, val in body_params.items()}
|
||
|
return body_params, loss_dict
|
||
|
|
||
|
def optimizeShape(body_model, body_params, keypoints3d,
|
||
|
weight_loss, kintree, cfg=None):
|
||
|
""" simple function for optimizing model shape given 3d keypoints
|
||
|
|
||
|
Args:
|
||
|
body_model (SMPL model)
|
||
|
params_init (DictParam): poses(1, 72), shapes(1, 10), Rh(1, 3), Th(1, 3)
|
||
|
keypoints (nFrames, nJoints, 3): 3D keypoints
|
||
|
weight (Dict): string:float
|
||
|
kintree ([[src, dst]]): list of list:int
|
||
|
cfg (Config): Config Node controling running mode
|
||
|
"""
|
||
|
device = body_model.device
|
||
|
# 计算不同的骨长
|
||
|
kintree = np.array(kintree, dtype=np.int)
|
||
|
# limb_length: nFrames, nLimbs, 1
|
||
|
limb_length = np.linalg.norm(keypoints3d[:, kintree[:, 1], :3] - keypoints3d[:, kintree[:, 0], :3], axis=2, keepdims=True)
|
||
|
# conf: nFrames, nLimbs, 1
|
||
|
limb_conf = np.minimum(keypoints3d[:, kintree[:, 1], 3:], keypoints3d[:, kintree[:, 0], 3:])
|
||
|
limb_length = torch.Tensor(limb_length).to(device)
|
||
|
limb_conf = torch.Tensor(limb_conf).to(device)
|
||
|
body_params = {key:torch.Tensor(val).to(device) for key, val in body_params.items()}
|
||
|
opt_params = [body_params['shapes']]
|
||
|
grad_require(opt_params, True)
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe', max_iter=10)
|
||
|
nFrames = keypoints3d.shape[0]
|
||
|
verbose = False
|
||
|
def closure(debug=False):
|
||
|
optimizer.zero_grad()
|
||
|
keypoints3d = body_model(return_verts=False, return_tensor=True, only_shape=True, **body_params)
|
||
|
src = keypoints3d[:, kintree[:, 0], :3] #.detach()
|
||
|
dst = keypoints3d[:, kintree[:, 1], :3]
|
||
|
direct_est = (dst - src).detach()
|
||
|
direct_norm = torch.norm(direct_est, dim=2, keepdim=True)
|
||
|
direct_normalized = direct_est/direct_norm
|
||
|
err = dst - src - direct_normalized * limb_length
|
||
|
loss_dict = {
|
||
|
's3d': torch.sum(err**2*limb_conf)/nFrames,
|
||
|
'reg_shape': torch.sum(body_params['shapes']**2)}
|
||
|
# fittingLog.step(loss_dict, weight_loss)
|
||
|
if verbose:
|
||
|
print(' '.join([key + ' %f'%(loss_dict[key].item()*weight_loss[key])
|
||
|
for key in loss_dict.keys() if weight_loss[key]>0]))
|
||
|
loss = sum([loss_dict[key]*weight_loss[key]
|
||
|
for key in loss_dict.keys()])
|
||
|
if not debug:
|
||
|
loss.backward()
|
||
|
return loss
|
||
|
else:
|
||
|
return loss_dict
|
||
|
|
||
|
fitting = FittingMonitor(ftol=1e-4)
|
||
|
final_loss = fitting.run_fitting(optimizer, closure, opt_params)
|
||
|
fitting.close()
|
||
|
grad_require(opt_params, False)
|
||
|
loss_dict = closure(debug=True)
|
||
|
for key in loss_dict.keys():
|
||
|
loss_dict[key] = loss_dict[key].item()
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
body_params = {key:val.detach().cpu().numpy() for key, val in body_params.items()}
|
||
|
return body_params
|
||
|
|
||
|
def optimizePose(body_model, body_params, keypoints3d,
|
||
|
weight_loss, kintree, cfg=None):
|
||
|
""" simple function for optimizing model pose given 3d keypoints
|
||
|
|
||
|
Args:
|
||
|
body_model (SMPL model)
|
||
|
params_init (DictParam): poses(1, 72), shapes(1, 10), Rh(1, 3), Th(1, 3)
|
||
|
keypoints (nFrames, nJoints, 3): 3D keypoints
|
||
|
weight (Dict): string:float
|
||
|
kintree ([[src, dst]]): list of list:int
|
||
|
cfg (Config): Config Node controling running mode
|
||
|
"""
|
||
|
device = body_model.device
|
||
|
# 计算不同的骨长
|
||
|
kintree = np.array(kintree, dtype=np.int)
|
||
|
nFrames = keypoints3d.shape[0]
|
||
|
# limb_length: nFrames, nLimbs, 1
|
||
|
limb = keypoints3d[:, kintree[:, 1], :3] - keypoints3d[:, kintree[:, 0], :3]
|
||
|
limb_length = np.linalg.norm(limb, axis=2, keepdims=True)
|
||
|
# conf: nFrames, nLimbs, 1
|
||
|
limb_conf = np.minimum(keypoints3d[:, kintree[:, 1], 3:], keypoints3d[:, kintree[:, 0], 3:])
|
||
|
limb_dir = limb/limb_length
|
||
|
|
||
|
keypoints3d = torch.Tensor(keypoints3d).to(device)
|
||
|
limb_dir = torch.Tensor(limb_dir).to(device).unsqueeze(2)
|
||
|
limb_conf = torch.Tensor(limb_conf).to(device)
|
||
|
angle_prior = SMPLAngleLoss(keypoints3d)
|
||
|
|
||
|
body_params = {key:torch.Tensor(val).to(device) for key, val in body_params.items()}
|
||
|
if cfg is None:
|
||
|
opt_params = [body_params['Rh'], body_params['Th'], body_params['poses']]
|
||
|
verbose = False
|
||
|
else:
|
||
|
opt_params = []
|
||
|
if cfg.OPT_R:
|
||
|
opt_params.append(body_params['Rh'])
|
||
|
if cfg.OPT_T:
|
||
|
opt_params.append(body_params['Th'])
|
||
|
if cfg.OPT_POSE:
|
||
|
opt_params.append(body_params['poses'])
|
||
|
if cfg.OPT_SHAPE:
|
||
|
opt_params.append(body_params['shapes'])
|
||
|
verbose = cfg.VERBOSE
|
||
|
grad_require(opt_params, True)
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
zero_pose = torch.zeros((nFrames, 3), device=device)
|
||
|
def closure(debug=False):
|
||
|
optimizer.zero_grad()
|
||
|
new_params = body_params.copy()
|
||
|
new_params['poses'] = torch.cat([zero_pose, body_params['poses'][:, 3:]], dim=1)
|
||
|
kpts_est = body_model(return_verts=False, return_tensor=True, **new_params)
|
||
|
diff_square = (kpts_est - keypoints3d[..., :3])**2
|
||
|
if False:
|
||
|
pass
|
||
|
else:
|
||
|
conf = keypoints3d[..., 3:]
|
||
|
loss_3d = torch.sum(conf * diff_square)
|
||
|
if False:
|
||
|
src = keypoints3d[:, kintree[:, 0], :3].detach()
|
||
|
dst = keypoints3d[:, kintree[:, 1], :3]
|
||
|
direct_est = dst - src
|
||
|
direct_norm = torch.norm(direct_est, dim=2, keepdim=True)
|
||
|
direct_normalized = direct_est/direct_norm
|
||
|
|
||
|
loss_dict = {
|
||
|
'k3d': loss_3d,
|
||
|
'reg_poses_zero': angle_prior.loss(body_params['poses'])
|
||
|
}
|
||
|
# smooth
|
||
|
loss_dict.update(SmoothLoss(body_params, ['poses', 'Th'], weight_loss))
|
||
|
for key in loss_dict.keys():
|
||
|
loss_dict[key] = loss_dict[key]/nFrames
|
||
|
# fittingLog.step(loss_dict, weight_loss)
|
||
|
if verbose:
|
||
|
print(' '.join([key + ' %f'%(loss_dict[key].item()*weight_loss[key])
|
||
|
for key in loss_dict.keys() if weight_loss[key]>0]))
|
||
|
loss = sum([loss_dict[key]*weight_loss[key]
|
||
|
for key in loss_dict.keys()])
|
||
|
if not debug:
|
||
|
loss.backward()
|
||
|
return loss
|
||
|
else:
|
||
|
return loss_dict
|
||
|
|
||
|
fitting = FittingMonitor(ftol=1e-4)
|
||
|
final_loss = fitting.run_fitting(optimizer, closure, opt_params)
|
||
|
fitting.close()
|
||
|
grad_require(opt_params, False)
|
||
|
loss_dict = closure(debug=True)
|
||
|
for key in loss_dict.keys():
|
||
|
loss_dict[key] = loss_dict[key].item()
|
||
|
optimizer = LBFGS(
|
||
|
opt_params, line_search_fn='strong_wolfe')
|
||
|
body_params = {key:val.detach().cpu().numpy() for key, val in body_params.items()}
|
||
|
return body_params
|