使用SMPL的过程中遇到了一个大坑, 在此记录一下.
踩坑过程:
将SMPL在maya中的旋转参数用如下代码导出后保存起来
数据样例如下所示
{
"shape": [
-1.1676373481750488,
0.0,
0.7026347517967224,
-0.6037481427192688,
0.6046652793884277,
0.0,
1.1819937229156494,
0.8521347641944885,
1.3137569427490234,
-0.39927607774734497
],
"trans": [
-16.9407701057938,
81.43054041149115,
-17.379529651080375
],
"pose": [
[
-1.2300408964079839,
60.87467575073242,
-2.2755821915688337
],
[
-16.84311440799761,
5.185341658415617,
13.773090221263741
],
[
7.020085591851186,
-3.0269137334086786,
-4.544121901545129
],
[
1.7932181888156469,
2.717320961726481,
1.062192563657407
],
[
14.070326173776268,
7.3665357519079135,
-0.3312955681588212
],
[
0.03709220499904067,
-6.294331614924962,
0.9927839188415509
],
[
1.202923810040509,
-4.489031857885156,
0.08547615121912072
],
[
2.2876832658881385,
3.837096249615703,
-4.001307956488775
],
[
-15.149088259096498,
-2.1525425346043283,
1.3961921797858343
],
[
-1.4177366539284033,
0.15677062807815287,
0.5882486590632686
],
[
0.0,
0.0,
0.0
],
[
0.0,
0.0,
0.0
],
[
5.069978502061632,
16.906971401638458,
-3.639994724901513
],
[
-4.318501507794415,
-8.755624559190537,
0.024882670905854963
],
[
-1.9028504607664005,
19.229754695185907,
0.14497061570485434
],
[
-6.7910243846752,
5.8034296388979305,
-5.582838058471679
],
[
-0.7849124625877097,
-19.80789608425564,
-28.250218144169544
],
[
9.131919013129341,
33.655449196144374,
39.719461511682574
],
[
51.95742006655092,
-100.60467755353108,
-24.901278460467307
],
[
45.06835089789497,
86.17715341073506,
24.845804285120078
],
[
-11.493217144522381,
-19.804811830873845,
19.8789047229983
],
[
-15.749351870115982,
16.70951701976635,
-16.144037356644485
],
[
4.235540319372105,
2.7374518359148934,
-17.558513217502167
],
[
5.200717007672346,
-0.8718210679513436,
-32.190611097547745
],
[
-3.716000804194698,
-4.215261318065502,
-7.825357649061411
],
[
-5.341095394558376,
0.7620738170765062,
-22.55488360369647
],
[
-6.3985393665455,
0.08861873988752009,
-29.97952213993779
],
[
-0.1428171771543997,
-3.9809873369004993,
-14.3453711050528
],
[
-16.092659279152194,
-13.445568791142215,
-24.49389704951533
],
[
-8.060806415699144,
-4.92116574887876,
-21.133692706072765
],
[
-15.60036129421658,
-3.1178453233506933,
-2.833860362017596
],
[
-4.523669348822699,
-5.2461638274016185,
-26.75318400065104
],
[
-11.349463286223234,
-6.180775253861038,
-26.174853289568865
],
[
-6.317445966932508,
-6.835497396963612,
-15.398747479474098
],
[
36.555003413447636,
11.932007118507666,
1.343177071324101
],
[
-21.183915314850985,
1.6296628669456201,
1.8769728695904764
],
[
22.2539958247432,
4.9019622802734375,
-10.745315551757809
],
[
4.235540319372105,
-2.7374518359148934,
17.558513217502167
],
[
5.200717007672346,
0.8718210679513436,
32.190611097547745
],
[
-3.716000804194698,
4.215261318065502,
7.825357649061411
],
[
-5.341095394558376,
-0.7620738170765062,
22.55488360369647
],
[
-6.3985393665455,
-0.08861873988752009,
29.97952213993779
],
[
-0.1428171771543997,
3.9809873369004993,
14.3453711050528
],
[
-16.092659279152194,
13.445568791142215,
24.49389704951533
],
[
-8.060806415699144,
4.92116574887876,
21.133692706072765
],
[
-15.60036129421658,
3.1178453233506933,
2.833860362017596
],
[
-4.523669348822699,
5.2461638274016185,
26.75318400065104
],
[
-11.349463286223234,
6.180775253861038,
26.174853289568865
],
[
-6.317445966932508,
6.835497396963612,
15.398747479474098
],
[
36.555003413447636,
-11.932007118507666,
-1.343177071324101
],
[
-21.183915314850985,
-1.6296628669456201,
-1.8769728695904764
],
[
24.479395407217517,
-5.392158508300782,
11.819847106933596
]
]
}
这里有一点需要注意, 导出的为角度, 需要先转化为弧度. 转化后在Python中加载, 传入SMPL模型中
class BodyModel(torch.nn.Module):
def __init__(self, support_dir):
super().__init__()
subject_gender = "male"
bm_fname = os.path.join(
support_dir, "smplh/{}/model.npz".format(subject_gender)
)
dmpl_fname = os.path.join(
support_dir, "dmpls/{}/model.npz".format(subject_gender)
)
num_betas = 16 # number of body parameters
num_dmpls = 8 # number of DMPL parameters
self.body_model = BM(
bm_fname=bm_fname,
num_betas=num_betas,
num_dmpls=num_dmpls,
dmpl_fname=dmpl_fname,
)
def forward(self, body_params): # body_params:{pose_body:(N, 63), root_orient:(N, 3)}
# with torch.no_grad():
body_pose = self.body_model(
**{
k: v
for k, v in body_params.items()
if k in ["pose_body", "trans", "root_orient", "betas"]
}
)
return body_pose
此时得到的SMPL模型姿势大体看着没啥问题, 但是root的旋转对不上! 七扭八歪
起初以为是坐标系的问题, 但是经过仔细check发现maya也是右手坐标系, 从道理上来讲应该是不需要进行任何坐标系变换, 这就很奇怪.
这里最坑的一点是其他的关节看着都很好, 只有root节点偏了, 这就很不合常理, 要歪应该是一块歪的, 在这个地方卡了很久.
debug了整整1天, 发现问题出在旋转的表示上, maya导出的结果旋转表示为欧拉角表示, 表示绕着xyz轴分别旋转了多少角度, 然而SMPL的输入参数为轴角表示, 类似于一个方向向量!!! 两者并不是同一个东西! 这个问题是从SMPL的内部对输入的处理函数中发现的
def batch_rodrigues(
rot_vecs: Tensor,
epsilon: float = 1e-8,
) -> Tensor:
"""Calculates the rotation matrices for a batch of rotation vectors
Parameters
----------
rot_vecs: torch.tensor Nx3
array of N axis-angle vectors
Returns
-------
R: torch.tensor Nx3x3
The rotation matrices for the given axis-angle parameters
"""
batch_size = rot_vecs.shape[0]
device, dtype = rot_vecs.device, rot_vecs.dtype
angle = torch.norm(rot_vecs + 1e-8, dim=1, keepdim=True)
rot_dir = rot_vecs / angle
cos = torch.unsqueeze(torch.cos(angle), dim=1)
sin = torch.unsqueeze(torch.sin(angle), dim=1)
# Bx1 arrays
rx, ry, rz = torch.split(rot_dir, 1, dim=1)
K = torch.zeros((batch_size, 3, 3), dtype=dtype, device=device)
zeros = torch.zeros((batch_size, 1), dtype=dtype, device=device)
K = torch.cat([zeros, -rz, ry, rz, zeros, -rx, -ry, rx, zeros], dim=1).view((batch_size, 3, 3))
ident = torch.eye(3, dtype=dtype, device=device).unsqueeze(dim=0)
rot_mat = ident + sin * K + (1 - cos) * torch.bmm(K, K)
return rot_mat
于是乎需要将eular角先转换为轴角表示, 才能作为SMPL的输入参数, 可以先将欧拉角转成旋转矩阵, 然后根据旋转矩阵变成轴角
def euler_to_matrix_batch(euler):
# euler: (batch_size, 3)
batch_size = euler.shape[0]
rx, ry, rz = euler[:, 0], euler[:, 1], euler[:, 2]
# Create rotation matrices for each axis
R_x = torch.zeros((batch_size, 3, 3), dtype=euler.dtype, device=euler.device)
R_y = torch.zeros((batch_size, 3, 3), dtype=euler.dtype, device=euler.device)
R_z = torch.zeros((batch_size, 3, 3), dtype=euler.dtype, device=euler.device)
# Fill in the rotation matrices
R_x[:, 0, 0] = 1
R_x[:, 1, 1] = torch.cos(rx)
R_x[:, 1, 2] = -torch.sin(rx)
R_x[:, 2, 1] = torch.sin(rx)
R_x[:, 2, 2] = torch.cos(rx)
R_y[:, 0, 0] = torch.cos(ry)
R_y[:, 0, 2] = torch.sin(ry)
R_y[:, 1, 1] = 1
R_y[:, 2, 0] = -torch.sin(ry)
R_y[:, 2, 2] = torch.cos(ry)
R_z[:, 0, 0] = torch.cos(rz)
R_z[:, 0, 1] = -torch.sin(rz)
R_z[:, 1, 0] = torch.sin(rz)
R_z[:, 1, 1] = torch.cos(rz)
R_z[:, 2, 2] = 1
# Compute the final rotation matrix for each batch
R = torch.bmm(R_z, torch.bmm(R_y, R_x))
return R
def rotation_matrix_to_axis_angle_batch(R):
# R: (batch_size, 3, 3)
batch_size = R.shape[0]
# Calculate the angle
trace = R[:, 0, 0] + R[:, 1, 1] + R[:, 2, 2]
theta = torch.acos((trace - 1) / 2)
# Handle the case when theta is very small (to avoid division by zero)
sin_theta = torch.sin(theta)
sin_theta[sin_theta == 0] = 1e-6 # Avoid division by zero
# Calculate the rotation axis
rx = (R[:, 2, 1] - R[:, 1, 2]) / (2 * sin_theta)
ry = (R[:, 0, 2] - R[:, 2, 0]) / (2 * sin_theta)
rz = (R[:, 1, 0] - R[:, 0, 1]) / (2 * sin_theta)
axis = torch.stack((rx, ry, rz), dim=1)
# Return the axis-angle representation
return axis * theta.unsqueeze(1)
def euler_to_axis_angle_batch(euler):
# Convert Euler angles to rotation matrices
R = euler_to_matrix_batch(euler)
# Convert rotation matrices to axis-angle representation
axis_angle = rotation_matrix_to_axis_angle_batch(R)
return axis_angle
转换完成后得到了正确结果!
这里有个地方比较奇怪! 不光把root转成了轴角表示, 其他的也转成了轴角表示, 但是貌似对于其他关节来讲好像没差多少?也挺神奇的.