ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零学习SMPL:三维人体重建与参数化建模核心原理

从零学习SMPL:三维人体重建与参数化建模核心原理 很多人第一次接触“人体三维重建”这个词都是被一张图、一个视频就能生成3D人体模型的效果吸引来的。但真动手做项目后会发现几乎所有主流方案到最后都会指向同一个东西——SMPL。SMPLSkinned Multi-Person Linear model把一个人体压缩成两组数字体态参数β和姿态参数θ再通过一套可微的变形流程输出一个有6890个顶点的三维网格。这篇笔记是从零学习SMPL的完整记录包含原理、加载方法、实操代码和踩坑经验适合正在入门三维人体重建、动作捕捉数据处理或CG工具开发的人直接参考。1. 先搞懂SMPL的定位为什么三维视觉和动画行业都在用它1.1 在参数化人体模型出现之前事情有多麻烦传统CG流程里一个人体模型就是一个静态OBJ或者FBX做动画要手动绑定骨骼、刷权重做不同体型就要重新建模。深度学习火了以后问题更明显神经网络需要大量带标注的3D数据但真实采集的扫描模型顶点数不同、网格拓扑不同根本没法直接当训练标签。这时候出现一个强烈需求能不能像“数字捏人”一样用少数几个数字就生成一个结构统一、顶点一一对应的人体网格SMPL就是答案。SMPL最大的特点有三个。第一是低维一个人形只靠β和θ两个参数就能表达。第二是结构固定任何参数组合输出的网格都是6890个顶点拓扑完全一致不同人之间可以直接比较和插值。第三是可微整个变形过程对参数是连续可导的这意味着深度学习网络可以预测SMPL参数梯度也能从三维网格一路回传到损失函数。后面这一点是SMPL能在学术界“封神”的最根本原因。1.2 两个参数的分工β管“长得像谁”θ管“摆什么姿势”SMPL的核心参数其实非常简单。β是体态参数默认10维每一维大致对应一个形态方向比如第一维可能对应胖瘦、第二维对应高矮。β的值越大或越小网格就在这个方向上被“拉伸”或“压缩”。θ是姿态参数默认72维表示人体24个关节中23个可旋转关节的旋转量外加一个全局朝向每个旋转用3D的axis-angle表示所以总共是1 23× 3 72维。理解这两个参数最好的办法是把它想成一个橡皮人β决定橡皮人的基础体型是把人拉高还是催胖θ决定他摆什么姿势抬手、抬腿、弯腰全靠它。β不影响姿态θ不会改变身高臂长这两件事是解耦的。对做深度学习的人来说解耦意味着你可以分别设计损失函数姿态估计网络只关心θ体型估计网络只关心β。1.3 一次完整的“人”是怎么生成的SMPL生成网格的过程可以分成四个环节先从一个标准模板网格出发加上体型混合变形得到对应体型的零姿态网格再根据当前姿态加一个姿态混合变形来修正关节附近的变形接着从网格顶点回归出每个关节的位置最后用线性混合蒙皮把这些顶点变换到目标姿态。整个过程用公式概括就是M(β, θ) W(T_p(β, θ), J(β), θ, W)其中T_p(β, θ) T̄ B_s(β) B_p(θ)。这里的T̄是平均模板网格B_s是体型混合变形B_p是姿态混合变形J是关节位置W是蒙皮权重。听起来有点绕但核心就一句话SMPL先用β把体型改了再用θ把姿态摆好。后面第二大部分我会把每一步拆开讲清楚。2. 原理拆解网格变形每一步都在干什么2.1 模板网格、24个关节和蒙皮权重SMPL的模板网格就是T-pose类似标准立正、双臂略微举起的姿势下的一个平均人体模型包含6890个顶点和13776个三角面。这个顶点数在所有SMPL模型里是固定的无论男女无论什么体型顶点数和三角形拓扑都不变变化的是顶点坐标。这种拓扑统一特性让跨个体比较、做训练标签、训练回归网络都变得非常方便。人体骨架部分定义了24个关节从骨盆开始往上延伸到脊柱、脖子、头部向下延伸到左右髋、膝、踝再分支到肩膀、手部等。蒙皮权重则记录每个顶点受哪些关节影响以及影响多少。比如膝盖附近的顶点很大程度上同时受大腿骨和小腿骨两个关节影响而当两个关节相对旋转时顶点位置的插值就产生了蒙皮效果。这里有一个初学者容易忽略的点SMPL的关节位置不是外部给的骨架解算结果而是从网格顶点通过一个线性回归矩阵计算得到的。也就是说不同体型的人即使姿态参数完全一样关节的具体坐标也不一样。这是为了黏合“体型”和“姿态”两个空间避免出现模型“皮肉对不上骨头”的违和感。2.2 形状混合β如何改变体型体型混合变形是SMPL最直观的部分。公式是B_s(β) Σ β_n * S_n其中S_n是形状基向量。这组形状基向量是通过大量三维人体扫描数据做PCA主成分分析学到的每一个基向量代表一个常见形态变化方向。β就是你在这些主成分方向上的坐标。10维β已经可以解释现实人群里绝大多数体型差异如果对精度要求更高也可以用300维的β但维度越大训练时越容易过拟合。实际操作中这个公式本质上是线性加权。把β里的第5维调大一点模型可能整体变高把第1维调小模型可能变瘦。正因为是线性关系网络预测β时非常“舒服”优化算法不会遇到奇怪的局部极小点定量测量比如身高、臂长也可以直接通过回归β来估计。你甚至可以拿两个β做线性插值生成中间体型这在动画行业做角色变体时非常实用。2.3 姿态混合为什么要修正LBS伪影线性混合蒙皮LBS有个著名缺陷叫“糖果纸伪影”或者“双弯曲效果”。当你弯起手肘或膝盖时关节附近会发生不自然的扭曲、塌陷或“橡皮筋勒紧”现象。这个问题在传统动画绑定里同样存在但SMPL选择了更聪明的处理方式专门加一项姿态混合变形B_p(θ)来修正。B_p(θ)的思路是为每个关节学习一组修正向量。当某个关节的旋转角度和初始T-pose不一样时就根据旋转差异把修正量叠加到顶点上。公式框架是这样的B_p(θ) Σ (R_i(θ) - R_i(θ*)) * P_i其中R_i(θ)是关节i在当前姿态下的旋转矩阵R_i(θ*)是零姿态下的旋转矩阵P_i是对应的修正模板。为什么要用旋转矩阵的“差”而不用绝对旋转角度因为修正量必须跟随旋转幅度连续变化而且用相对差异可以避免在零姿态时误加修正。有了这一项弯曲手肘时肌肉鼓胀、膝盖弯曲时周围软组织变形都能被网络学习到。这也是SMPL相比早期纯LBS模型的巨大优势——它不是纯蒙皮而是用数据驱动方式把“人体的真实变形”记录到了模型里。2.4 关节回归与最后一步LBS蒙皮当体型和姿态混合都完成后下一步是计算关节位置。公式是J(β) J * (T̄ B_s(β))这里的J是一个线性回归矩阵作用在零姿态网格顶点上输出24个关节的坐标。因为体型已经变了所以每个人同一个姿态下的关节坐标不同。这一步完成后就拥有了一副完整骨架和一张零姿态下的皮肤网格。最后通过LBS把每个顶点从零姿态变换到带姿态的坐标系。对每个顶点先分别按它所属关节的旋转矩阵进行变换然后以蒙皮权重做加权平均最终得到具有姿态的人体网格。注意SMPL的蒙皮权重是学习得到而不是手工刷出来的它的稀疏性保持得很好所以渲染和求导都很快。3. 实操从拿到模型文件到生成第一个“参数化人体”3.1 第一步去官网申请模型文件SMPL模型本身是数据文件不能直接靠pip install装好需要去官网申请下载。在浏览器搜“SMPL model download”进入官方项目页面注册账号后按页面要求填写单位和用途一般的学术研究用途都会被批准。审批通过后可以从下载链接获取模型压缩包里面有中性模型、男性模型和女性模型文件是pkl格式。我强烈建议把模型文件放在一个固定目录比如项目的models/目录下不要随手丢到桌面。因为后面所有代码都要反复引用这个路径路径越规范越省心。下载好之后可以先解压看一眼目录结构确认存在类似basicModel_neutral_lbs_10_207_0_v1.1.0.pkl的文件。这个文件名里的数字不用纠结关键是它是“neutral”也就是中性模型。3.2 环境准备安装smplx库并加载SMPL虽然现在有SMPL-X这种升级版模型但它的官方工具库smplx是兼容SMPL的所以用起来最方便。再配上PyTorch做张量计算trimesh或open3d做可视化。安装命令很简单pip install smplx torch trimesh open3d加载SMPL的代码更简单import torch from smplx import SMPL model_path ./models # 指向包含 pkl 的目录 model SMPL(model_path, genderneutral, extpkl, batch_size1) # 默认全零参数相当于最普通的T-pose betas torch.zeros(1, 10) body_pose torch.zeros(1, 69) # 23个关节 × 3 global_orient torch.zeros(1, 3) # 全局朝向 output model(betasbetas, body_posebody_pose, global_orientglobal_orient) vertices output.vertices # shape: (1, 6890, 3) joints output.joints # shape: (1, 24, 3) print(vertices:, vertices.shape) print(joints:, joints.shape)这里的body_pose传69维是23个可旋转关节的axis-angleglobal_orient是全局旋转。合起来就是72维。如果你看到某些仓库把body_pose写成63维那多半是封装方式不同通过打印shape确认就行了。这一段跑通后SMPL的基本流程就已经走完了。3.3 用β捏“胖瘦高矮”接下来可以拿β做点好玩的事。调整不同维度的β观察网格变化import numpy as np import trimesh # 生成三种体型标准、偏高、偏胖 beta_list [ np.zeros(10), # 标准体型 np.array([0, 2.0, 0, 0, 0, 0, 0, 0, 0, 0]), # 某个方向拉高 np.array([3.0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), # 某个方向变胖 ] for i, beta in enumerate(beta_list): torch_beta torch.from_numpy(beta).float().unsqueeze(0) out model(betastorch_beta, body_posetorch.zeros(1, 69), global_orienttorch.zeros(1, 3)) v out.vertices[0].detach().numpy() face model.faces mesh trimesh.Trimesh(v, face, processFalse) mesh.export(f/tmp/smpl_beta_{i}.obj)导出OBJ后用Meshlab、Blender或者本地可视化工具打开就能看到不同β产生的体型变化。注意β的具体方向在不同模型版本里可能略有差异不要在“第几维对应胖”这个问题上死记硬背直接肉眼观察最可靠。3.4 用θ让模型动起来让人体动起来本质上是修改某个关节的旋转。axis-angle比较抽象最直接的做法是使用一个工具函数把轴和角度拼成旋转向量。比如让左肩关节绕X轴转0.5弧度大概就是让手臂抬高一点import math body_pose torch.zeros(1, 69) # 关节索引来自SMPL输出具体打印joints和自己的骨骼标注确认 left_shoulder_idx 12 body_pose[0, left_shoulder_idx * 3:left_shoulder_idx * 3 3] torch.tensor([0.5, 0.0, 0.0])这里有一个非常重要的思维转换SMPL里的旋转单位是弧度不是角度。想转90度就填math.pi / 2填90会被当成90弧度结果模型会变成一团不可名状的乱麻。生成新的网格后可以继续用trimesh导出同时打印一下对应关节点的3D坐标很多动捕数据预处理的起点就是从这里开始的。4. 常见问题与避坑经验4.1 维度、版本和导出格式的老坑最常见的问题就是维度错误。SMPL在smplx库中通常要求betas是(B, 10)或(B, 300)body_pose是(B, 69)global_orient是(B, 3)。如果你从某个旧项目里拷贝代码很可能会遇到63维的body_pose或72维的pose混用的情况。解决方法是先看模型类定义再打印出参数量维度不要盲目沿用网上老代码。其次是pkl版本问题。旧版pkl里的数据可能是chumpy对象不是numpy数组用深度框架直接加载会报类似UnpicklingError或者类型转换错误。建议统一使用较新的smplx库加载因为它已经帮你处理好了这些历史包袱。另外模型文件下载后要留意扩展名是pkl还是npz版本选v1.1.0之类的新版能少踩很多坑。4.2 axis-angle不是欧拉角更不是四元数axis-angle用3个数表示旋转方向是旋转轴模长是旋转角度。它和欧拉角、四元数都不是一回事。很多人第一次自己构造姿势时习惯性把欧拉角填进去结果模型立刻“抽筋”。正确做法是用cv2.Rodrigues或Scipy的Rotation库把旋转矩阵或四元数转成axis-angle再传给SMPL。比如from scipy.spatial.transform import Rotation as R import numpy as np r R.from_euler(XYZ, [0, 30, 0], degreesTrue) axis_angle r.as_rotvec() # 弧度向量长度为角度这个细节在深度学习训练里尤其重要。网络输出的旋转表示五花八门有的是欧拉角有的是其归一后的四元数如果最终和SMPL对接前不统一转成axis-angleloss计算得再漂亮也没用。4.3 坐标轴、法线和缩放问题SMPL默认的是Y轴向上的右手坐标系模型单位的数量级接近米。如果你要导入Unity、Unreal或者Blender这类Z轴向上的软件直接导出的网格很可能横躺在场景里。解决方法是做一次轴变换通常会把顶点从Y-up转成Z-up。别硬记哪个轴换到哪个直接在代码里打印原始顶点坐标、用肉眼观察模型朝向再用旋转矩阵试几次很快就能确认。法线问题也很常见。有的OBJ直接在渲染器里显示成“半透明”或“里面翻到外面”说明三角形面的法线方向反了。SMPL模型本身的顶点顺序是正确的但当使用trimesh导出时如果processTrue有时会重新计算顶点顺序导致法线异常。可以在导出时改成processFalse或者导出后统一用mesh.fix_normals()处理。4.4 和深度学习网络对接时容易被忽视的细节现在主流的三维人体估计算法比如HMR、VIBE、ROMP输出一般就是SMPL参数但有几个细节常常让人卡住。第一个是“全局旋转”和“局部旋转”的区分。网络预测的72维向量里前3维是全局朝向后69维是23个骨骼的局部旋转。很多人直接把72维塞进body_pose结果模型“原地起飞”。第二个是根关节位移。SMPL的网格坐标通常是以人体根关节为原点如果你要做的是“把人体放到真实相机坐标里”就得额外处理全局平移和朝向不能指望零姿态的肢体网格自动出现在正确位置。第三个是单位。网络回归的β和θ是抽象数值但网格顶点的尺度是固定的如果项目里需要真实身高要用β和数据集统计的回归矩阵去计算不能简单把网格缩放到一个任意尺度。这里我把最常见的故障整理成了一张速查表症状可能原因解决思路模型加载报维度错误body_pose或betas维度不对打印模型配置按69/10/3传入模型摆出扭曲姿势axis-angle填成欧拉角或角度制用scipy/cv2转换统一为弧度制导出的网格横躺在地面坐标系不匹配做Y-up到Z-up轴变换法线朝内/半透明顶点顺序或tirmesh处理问题processFalse或fix_normals手臂位置“炸开”root alignment没做对预测输出做关节对齐和位移补偿5. 从SMPL到SMPL-X后续扩展与学习路径5.1 SMPL没有手和脸SMPL-X补上了SMPL给行业解决了很大的问题但它有一个明显的短板不专门处理手部细节和面部表情。SMPL把整个手部简化成了几个关节你想做精确的手指动作很难人脸也没有表情空间。所以就有了SMPLH、SMPL-X这些扩展版。SMPL-X在体型和姿态基础上额外加入面部关键点、表情参数、左右手部的姿态参数参数总量比SMPL大很多但“参数化人体”的基本思想完全一致。如果你之后要做手语识别、表情驱动或者高精度数字人就别在SMPL上死磕直接跳到SMPL-X更省事。5.2 SMPL在主流项目里扮演的角色做三维人体姿态估计、动作捕捉、虚拟试衣、游戏骨骼绑定每个方向都会遇到SMPL。比如HMR用一张图回归SMPL参数VIBE把这个过程扩展到视频ROMP实现了多人的实时估计。这些工作把SMPL当成了一个稳定的输出格式网络只负责预测参数渲染和驱动交给SMPL完成。理解这一点以后很多论文里的“predicted mesh”其实就不再神秘了它就是一组SMPL参数加上一次前向推理而已。5.3 给后来者的一条建议我自己学SMPL时犯过一个错误一头扎进论文公式结果看了三天还是一头雾水。后来换了节奏先跑通代码、打印网格、调β和θ再回到论文对着公式逐行看理解速度反而快得多。所以我真心建议后来的同学先“玩”起来再读“书”把模型当成一个复杂但友好的游戏工具你已经会用了再去理解它为什么这么设计效率会高一个量级。SMPL之后再扩展学习SMPL-X、AMASS数据集、SMPLify优化算法都会顺畅很多。
返回列表