这是源码的整体结构,先简单说一下各个文件里面是什么。
一、models文件
包含3个模型的.pkl文件,.pkl文件是python提供的可以以字符串的形式记录对象、变量的文件格式。这个模型里面包括了:
1.'J_regressor_prior':关节回归矩阵的先验,保存形式为CSC(用array保存的稀疏矩阵)
2.'f':面信息(三个顶点为一组表示面片的组成)
(不是全部,有省略)
3.'J_regressor':关节回归矩阵,保存形式CSC
4. 'kintree_table':关节树表
5.'J':关节位置
6.'weights_prior':蒙皮权重先验
7.'weights':蒙皮权重
8.'vert_sym_idxs' :顶点索引
9.'posedirs':姿势矫正
10.:姿势矫正蒙皮方式
11.'v_template':T pose 顶点信息
12.'':形状矫正
13.形状矫正蒙皮方式:
以上内容都有省略,但其实只需要知道有什么就行了,具体数字不重要(人眼也看不出意义),感兴趣可以用自己用下面代码写个脚本查看
注释掉的是用来显示全部内容,并写到.txt里面,用的时候记得改文件路径
二、smpl_webuser
可以看到里面还有个文件夹hello_world,里面两个脚本hello_smpl.py和render_smpl.py,这是作者提供的示例代码,可用其生成自己设定shape、pose参数的模型,区别是hello_smpl.py是直接生成.obj文件,render_smpl.py是直接渲染在屏幕上;个人觉得用meshlab或者Blender查看.obj比较舒服,所以只介绍hello_smpl.py。
hello_smpl.py
可以看到很简单,先用写好的load_model方法,读出.pkl文件的内容,然后将里面的m.r(定点信息)和m.f(面信息)写进.obj,meshlab既可以根据这两个信息渲染出最终的模型。
所以重点放在其他几个脚本上,lbs.py, posemapper.py, serialization.py, verts.py,一共四个脚本
我根据正须(整体调用顺序)推一遍各个脚本和里面函数的作用。
1.serialization.py
脚本名可翻译为串行化,前面也提到这个名词,串行化,就是python用来将对象、变量转换成字符进行存储,所以里面是关于模型存储、读取的方法。
(1)存储模型
(attribute n.属性)
可以看到,就是检查model里面的内容,再写进新的文件里。
(2)命名规范检查
对模型的参数可能存在的不同的命名进行检查,并规范化,算是提高复用性
(3)模型初始化(更添加顶点信息 &更新 形状修正后的关节点信息)
在 if want_shapemodel:这一行之前,都是在加载,判断,添加修改 传入的数据,都是直接调用库里的函数。
之后,通过将数据里的'shapedirs'(形状纠正)和'beta'(形状参数)点乘,加上'v_template'(T pose定点信息),得到形状修正后的定点信息'v_shaped'。
再用'J_regressor'和'v_shaped'计算出形状矫正后的新的关节位置'J'。
'v_posed'的计算中调用了其他脚本的方法'posemap',等下再看
最终这个方法的作用是根据数据里的'posedirs'&'shapedirs'计算出'v_posed' & 'J',并更新\添加到原来的数据文件中。
(4)模型加载
首先调用方法(3)读取.pkl文件中的'v_posed' & 'J'以及其他信息,然后将.pkl中的参数按照名称建立成字典。verts_core是一个重载函数,主要作用是在蒙皮过程中模型空间和世界空间进行转换,返回值是顶点息和Jtr,具体之后分析。setattr将未进行坐标转换的定点信息作为属性k,添加到result。所以最后返回的加载模型得到的result包含了顶点的模型空间和世界空间内的坐标(疑问:其他信息呢?hello_smpl.py里明明还对。
2.posemapper.py
接下来先看在初始化模型中调用的posemap函数,已知这个是用来计算pose参数的
有三个方法,但主要都是为第二个方法lortmin()服务,lortmin是一个很复杂的计算(没看懂),用到了罗德里斯公式,输出的是一个一维向量,这个向量与pose参数相乘后和posedirs矩阵点乘,这个脚本是用来将pose参数(旋转轴角,而且非线性)转换为线性可插值的旋转矩阵。
从纬度的计算看,
的结果是Nx3,从公式里可之'posedirs'也就是P的纬度是3Nx9K,''pose'向量在模型初始化的方法里初始化的3K,可以推导出'lortmin'的纬度是3x9x1,也符合旋转矩阵的表达形式(将3维的周角转换成3x3的旋转矩阵)。
3.verts.py
前面load_model中用到的重载函数verts_core(**args)也在该脚本调用。先看这个。
这个方法我这里只截图了命名,内容上也是根据已知的posedirs和shapedirs算出v_posed和J,其中的J的回归矩阵对是否为系数矩阵进行了判断,可能是为了防止有的模型定义变量名的不同。其他跟模型初始化的内容一样。作者给的注释是
其他地方也没有调用这个方法,应该是个模型定义的备选项
这里是被调用的重载函数的定义,假定了蒙皮形式为线性蒙皮和参数状态。
整个脚本就是先假定一下smpl的T pose 和 J 和 蒙皮形式。
4.lbs.py
(1)将模型从模型空间转变到世界空间
通过对kintree_table的按列读取,确定每个关节的parent
从kintree_table按列看,即可得知关节之间的父子关系
lambda 是方法的简写,冒号前为变量,后面为函数体,.vstack()是按列方向排列,result[0]是通过罗德里斯公式,得到0关节在世界坐标系的位置。因为0关节没有parent,其世界坐标和模型坐标是一样的。所以没有运算,只是通过行列排列即可得到。
for循环对每个关节,通过点承消去parent对自身坐标的改变,得到关节在世界坐标中的位置。
排列后得到所有关节世界位置组成的矩阵result_global。
再将世界坐标逐个点乘parent的坐标得到关节在模型坐标中的位置。返回最终结果
(2)关节在蒙皮中对顶点的影响