动画制作相关术语
Vertex(顶点):动画模型可以看成多个小三角形(四边形)组成,每个小三角形就可以看成一个顶点。顶点越多,动画模型越精细。
骨骼点:人体的一些关节点,类似于人体姿态估计的关键点。每个骨骼点都由一个三元组作为参数去控制(可以查看欧拉角,四元数相关概念)
蒙皮:将模型从一个姿态转变为另一个姿态,使用的转换矩阵叫做蒙皮矩阵。
骨骼蒙皮(Rig):建立骨骼点和顶点的关联关系。每个骨骼点会关联许多顶点,并且每一个顶点权重不一样。通过这种关联关系,就可以通过控制骨骼点的旋转向量来控制整个人运动。
纹理贴图:动画人体模型的表面纹理,即衣服裤子这些。
texture map:将3D多边形网格表面的纹理展开到2D平面,得到纹理图像
混合形状(BlendShape):控制动画角色运动有两种,一种是上面说的利用Rig,还有一种是利用BlendShape。比如:生成一种笑脸和正常脸,那么通过BlendShape就可以自动生成二者过渡的动画。这种方式相比于利用Rig,可以不定义骨骼点,比较方便。它指相对于base shape的变形(deformation),这种deformation是通常被表示为顶点的偏移量(vertex displacements),是由某种参数有关的function确定的
混合蒙皮技术(Blend Skinning) :一种模型网格(mesh)随内在的骨骼结构(skeletal structure)变形的方法。网格的每个顶点(vertex)对于不同的关节点有不同的影响权重(weighted influence),顶点在变形时,形变量与这个权重相关
LBS(Linear Blend Skinning ):线性混合蒙皮。使用最广泛,但是在关节处会产生不真实的变形
DQBS(dual-quaternion blend skinning ):双四元数混合蒙皮。
顶点权重(vertex weights):用于变形网格mesh
uv map:将3D多边形网格展开到2D平面得到 UV图像
拓扑(topology):重新拓扑是将高分辨率模型转换为可用于动画的较小模型的过程。两个mesh拓扑结构相同是指两个mesh上面任一个三角面片的三个顶点的ID是一样的(如某一个三角面片三个顶点是2,5,8;另一个mesh上也必有一个2,5,8组成的三角面片)
pose-dependent blend shape:姿势相关的混合变形
regressor from shape to joint locations: 形状到关节位置的回归函数
smpl介绍
SMPL(Skinned Multi-Person Linear Model)是一种裸体的(skinned),基于顶点(vertex-based)的人体三维模型,能够精确地表示人体的不同形状(shape)和姿态(pose)。
SMPL适用于动画领域,可以随姿态变化自然的变形,并伴随软组织的自然运动。SMPL与现有的许多图形渲染管线都是兼容的。
SMPL是一种可学习的模型,通过训练可以更好地拟合人体的形状和不同姿态下的形变。
它将身体形状分为identity-dependent shape和non-rigid pose-dependent shape。人体可以理解为是一个基础模型和在该模型基础上进行形变的总和,在形变基础上进行PCA,得到刻画形状的低维参数——形状参数(shape);同时,使用运动树表示人体的姿势,即运动树每个关节点和父节点的旋转关系,该关系可以表示为三维向量,最终每个关节点的局部旋转向量构成了SPML模型的姿势参数(pose)。
这种方法与传统的LBS的最大的不同在于其提出的人体姿态影像体表形貌的方法,这种方法可以模拟人的肌肉在肢体运动过程中的凸起和凹陷。因此可以避免人体在运动过程中的表面失真,可以精准的刻画人的肌肉拉伸以及收缩运动的形貌。
在SMPL文章中介绍了SMPL的总体模型,这个模型是通过训练得到,就是一些参数, 该模型中β和θ是其中的输入参数,其中β代表人体高矮胖瘦、头身比等比例的10个参数,是一个10-D的vector。θ是代表人体整体运动位姿和24个关节相对角度的75(24*3+3;每个关节点3个自由度,再加上3个根节点)个参数,是一个3K-D的vector(代表pose,其中K为骨架节点数,3是每个关节具有的3个自由度)。 β参数是Shape Blend Pose参数,可以通过10个增量模板控制人体形状变化: 具体而言:每个参数控制人体形态的变化可以通过动图来刻画。
SMPL骨架的节点个数为24,标注了人体影响姿态的几个主要关节,即:
smpl_names = [
'Left_Hip', 'Right_Hip', 'Waist', 'Left_Knee', 'Right_Knee',
'Upper_Waist', 'Left_Ankle', 'Right_Ankle', 'Chest',
'Left_Toe', 'Right_Toe', 'Base_Neck', 'Left_Shoulder',
'Right_Shoulder', 'Upper_Neck', 'Left_Arm', 'Right_Arm',
'Left_Elbow', 'Right_Elbow', 'Left_Wrist', 'Right_Wrist',
'Left_Finger', 'Right_Finger'
]
smpl.jpg
加上position的三个维度,则该模型最终总的输入就是10+3+3x24=85-D的数据。
根据输入的数据,对标准模型进行一步步的变化,大概流程就是:
- Add shape blend shapes(缩放)
- Infer shape-dependent joint locations.(根据shape调整joint)
- Add pose blend shapes(胖瘦变形)
- Get the global joint location(摆pose)
- Do skinning(给骨架包裹外皮)
最终生成的模型是具有6980个顶点的mesh。
smpl 10个shape参数分别对应的物理意义:(实际有50个参数,开源的只有10个)smpl官网的unity模型可以用slider 控制参数变化
0 代表整个人体的胖瘦和大小,初始为0的情况下,正数变瘦小,负数变大胖(±5)
1 侧面压缩拉伸,正数压缩
2 正数变胖大
3 负数肚子变大很多,人体缩小
4 代表 chest、hip、abdomen的大小,初始为0的情况下,正数变大,负数变小(±5)
5 负数表示大肚子+整体变瘦
6 正数表示肚子变得特别大的情况下,其他部位非常瘦小
7 正数表示身体被纵向挤压
8 正数表示横向表胖
9 正数表示肩膀变宽
2.相关参数:
顶点 vertical: N=6890
关节 joint: K=23
网格对男女具有相同的拓扑结构,空间分辨率可变,干净的四元数结构,分割成多部分,有初始混合权重和骨骼蒙皮。
代码介绍
SMPL的python版本在官方网站有两个,分别是SMPL_python_v.1.0.0,SMPL_python_v.1.1.0。区别是:SMPL_python_v.1.0.0不完备,只提供了10个shape PCA coefficients的模型。SMPL_python_v.1.1.0提供了3个性别300shape PCA coefficients的模型。
以SMPL_python_v1.1.0为例,其中包含了三个models和操作models的基础脚本。
三个模型是male,female,netrual的pkl格式模型,以netrual为例,我们看下其中的数据结构。
import pickle
with open(model_path, 'rb') as f:
smpl = pickle.load(f, encoding='latin1')
'J_regressor_prior': [24, 6890], scipy.sparse.csc.csc_matrix
# 面部
'f': [13776, 3], numpy.ndarray
# regressor array that is used to calculate the 3d joints from the position of the vertices
'J_regressor': [24, 6890], scipy.sparse.csc.csc_matrix
# indices of parents for each joints
'kintree_table': [2, 24], numpy.ndarray
'J': [24, 3], numpy.ndarray
'weights_prior': [6890, 24], numpy.ndarray
# linear blend skinning weights that represent how much the rotation matrix of each parr affects each vertex
'weights': [6890, 24], numpy.ndarray
# pose blend shape basis, pose PCA coefficients
'posedirs': [6890, 3, 207], numpy.ndarray
'bs_style': 'lbs'
# the vertices of the template model
'v_template': [6890, 3], numpy.ndarray
# tensor of PCA shape displacements
'shapedirs': [6890, 3, 300], chumpy.ch.Ch
'bs_type': 'lrotmin'
def forward_shape(self, betas):
v_shaped = self.v_template + blend_shapes(betas, self.shapedirs)
return SMPLOutput(vertices=v_shaped, betas=betas, v_shaped=v_shaped)
def forward(self, betas, body_pose, global_orient, transl,
return_verts=True, return_full_pose=False, pose2rot=True, **kwargs):
full_pose = torch.cat([global_orient, body_pose], dim=1)
vertices, joints = lbs(beta, full_pose, self.v_template, self.shapedirs,
self.posedirs, self.J_regressor, self.parents, self.lbs_weights,
pose2rot=pose2rot)
return SMPLOutput(vertices, global_orient=global_orient, body_pose=body_pose,
joints=joints, betas=betas, full_pose=full_pose)
核心是在lbs.py中。
# add shape contribution
v_shaped = v_template + blend_shapes(betas, shapedirs)
# Get the joints
J = vertices2joints(J_regressor, v_shaped)
# add pose blend shapes
ident = torch.eye(3, dtype=dtype, device=device)
pose_feature = pose[:, 1:].view(batch_size, -1, 3, 3) - ident
# [N, P] * [P, V*3] -> [N, V, 3]
pose_offsets = torch.matmul(pose_feature.view(batch_size, -1),
posedirs).view(batch_size, -1, 3)
v_posed = pose_offsets + v_shaped
# Get the global joint location
rot_mats = pose.view(batch_size, -1, 3, 3)
J_transformed, A = batch_rigid_transform(rot_mats, J, parents, dtype=dtype)
# Do skinning
W = lbs_weights.unsqueeze(dim=0).expand([batch_size, -1, -1])
T = torch.matmul(W, A.view(batch_size, num_joints, 16)) \
.view(batch_size, -1, 4, 4)
homogen_coord = torch.ones([batch_size, v_posed.shape[1], 1],
dtype=dtype, device=device)
v_posed_homo = torch.cat([v_posed, homogen_coord], dim=2)
v_homo = torch.matmul(T, torch.unsqueeze(v_posed_homo, dim=-1))
verts = v_homo[:, :, :3, 0]
代码顺序是shape blend shape + pose blend shape -> skinning。返回的是6890*3的模型顶点和n个3d关键点坐标。
对于结构体中的s c i p y . s p a r s e . c s c . c s c _ m a t r i x scipy.sparse.csc.csc_matrixscipy.sparse.csc.csc_matrix类型,在处理过程中可以通过以下代码转为n u m p y . n d a r r a y numpy.ndarraynumpy.ndarray类型。
def to_np(array, dtype=np.float32):
if 'scipy.sparse' in str(type(array)):
array = array.todense()
return np.array(array, dtype=dtype)
SMPL和SMPL-H的拓扑结构是一样的。
python3中没法识别chumpy.ch.Ch格式,为兼容python3,需要将该该格式的数据转为numpy.ndarray格式。
output_dict = {}
for key, data in body_data.iteritems():
if 'chumpy' in str(type(data)):
output_dict[key] = np.array(data)
else:
output_dict[key] = data
with open(out_path, 'wb') as f:
pickle.dump(output_dict, f)
有的工程会用到J_regressor_extra.npy补充额外关键点。
J_regressor_extra: [9, 6890], numpy.ndarray
extra_joints = vertice2joints(J_regressor_extra, smpl_output.vertices)
joints = torch.cat([smpl_output.joints, extra_joints], dim=1)
参考
https://blog.csdn.net/u011994454/article/details/120759217
https://www.cnblogs.com/sariel-sakura/p/14321818.html
网友评论