Face3D学习笔记(4)3DMM示例源码解析【中上】3DMM模型

写在前面

  • 为了保证整个示例项目更加直观,方便理解,在展示一些函数的源码时会使用numpy版本进行展示,而在示例程序中并未使用numpy版本的库,在Cython版本与numpy版本出现差异的原码前会有标注,希望读者留意。

  • 3DMM实例程序的jupyter版本后续会更新,完全免费,欢迎大家下载

预备

上一篇文章在MATLAB里面对BFM模型执行了Face3D提供的程序生成了新的模型数据BFM.mat、BFM_info.mat、BFM_UV.mat。这三个文件的格式分别如下:

  1. BFM.mat

在这里插入图片描述

  1. BFM_info.mat

在这里插入图片描述

  1. BFM_UV.mat 就是一个53215*2的矩阵

在这里插入图片描述

MATLAB里面的程序到底干了些啥,下面从原理进行一些分析:

首先要知道3DMM的公式为
公式
其中, S ‾ \overline{S} S代表平均人脸形状, S i S_i Si表示形状PCA主成分, α i \alpha_i αi是形状系数, E i E_i Ei是人脸表情PCA主成分, β i \beta_i βi是人脸表情系数。
BFM模型不直接提供原始人脸数据或参数化后的人脸,只提供了形状和纹理信息
PS:在BMF模型经过去中心化后的数据所对应式中的m、n均为199。
01_MorphableModel.mat中具体包含数据如下
数据
如表所示:

名称含义维度
shapeMU平均人脸形状(160470,1)
shapePC形状主成分(160470,199)
shapeEV形状主成分方差(199,1)
texMU平均人脸纹理(160470,1)
texPC纹理主成分(160470,199)
texEV纹理主成分方差(199,1)
tl三角面片(106466,3)
segbin区域分割信息(53490,4)

在经过MATLAB运算输出的BFM.mat从3DDFA那里获取了表情数据,这样来看face3d里面的3dmm例程:

bfm = MorphabelModel('Data/BFM/Out/BFM.mat')
print('init bfm model success')

其中MorphabelModel所对应的源码为

class  MorphabelModel(object):
    """docstring for  MorphabelModel
    model: nver: number of vertices. ntri: number of triangles. *: must have. ~: can generate ones array for place holder.
            'shapeMU': [3*nver, 1]. *
            'shapePC': [3*nver, n_shape_para]. *
            'shapeEV': [n_shape_para, 1]. ~
            'expMU': [3*nver, 1]. ~ 
            'expPC': [3*nver, n_exp_para]. ~
            'expEV': [n_exp_para, 1]. ~
            'texMU': [3*nver, 1]. ~
            'texPC': [3*nver, n_tex_para]. ~
            'texEV': [n_tex_para, 1]. ~
            'tri': [ntri, 3] (start from 1, should sub 1 in python and c++). *
            'tri_mouth': [114, 3] (start from 1, as a supplement to mouth triangles). ~
            'kpt_ind': [68,] (start from 1). ~
    """
    def __init__(self, model_path, model_type = 'BFM'):
        super( MorphabelModel, self).__init__()
        if model_type=='BFM':
            self.model = load.load_BFM(model_path)
        else:
            print('sorry, not support other 3DMM model now')
            exit()
            
        # fixed attributes
        self.nver = self.model['shapePC'].shape[0]/3
        self.ntri = self.model['tri'].shape[0]
        self.n_shape_para = self.model['shapePC'].shape[1]
        self.n_exp_para = self.model['expPC'].shape[1]
        self.n_tex_para = self.model['texPC'].shape[1]
        
        self.kpt_ind = self.model['kpt_ind']
        self.triangles = self.model
['tri']
        self.full_triangles = np.vstack((self.model['tri'], self.model['tri_mouth']))

就可以理解self.model = load.load_BFM(model_path)所读取的model里面所包含的元素都是什么了,下面以表格的形式列出来。

名称含义格式
shapeMU平均人脸形状(159645,1)
shapePC形状主成分(159645,199)
shapeEV形状主成分方差(199,1)
expMU平均人脸表情(159645,1)
expPC表情主成分(159645,29)
expEV表情主成分方差(29,1)
texMU平均人脸纹理(159645,1)
texPC纹理主成分(159645,199)
texEV纹理主成分方差(199,1)
tri三角格坐标(105840,3)
tri_mouth嘴部三角格坐标(114, 3)
kpt_ind特征点(68,)

源码解读

1.读取模型&&生成面部网格

读取处理过的BFM模型

bfm = MorphabelModel('Data/BFM/Out/BFM.mat')
print('init bfm model success')

用随机的形状系数和表情系数生成面部网格

sp = bfm.get_shape_para('random')
ep = bfm.get_exp_para('random')
vertices = bfm.generate_vertices(sp, ep)

tp = bfm.get_tex_para('random')
colors = bfm.generate_colors(tp)
colors = np.minimum(np.maximum(colors, 0), 1)

sp对应形状系数 α \alpha α,ep对应表情系数 β \beta β,tp对应的是纹理系数。这些系数均随机产生。
这部分对应的源码如下:

def get_shape_para(self, type = 'random'):
        if type == 'zero':
            sp = np.zeros((self.n_shape_para, 1))
        elif type == 'random':
            sp = np.random.rand(self.n_shape_para, 1)*1e04
        return sp

    def get_exp_para(self, type = 'random'):
        if type == 'zero':
            ep = np.zeros((self.n_exp_para, 1))
        elif type == 'random':
            ep = -1.5 + 3*np.random.random([self.n_exp_para, 1])
            ep[6:, 0] = 0

        return ep 

    def generate_vertices(self, shape_para, exp_para):
        '''
        Args:
            shape_para: (n_shape_para, 1)
            exp_para: (n_exp_para, 1) 
        Returns:
            vertices: (nver, 3)
        '''
        vertices = self.model['shapeMU'] + \
                   self.model['shapePC'].dot(shape_para) + \
                   self.model['expPC'].dot(exp_para)
        vertices = np.reshape(vertices, [int(3), int(len(vertices)/3)], 'F').T

        return vertices

    # -------------------------------------- texture: here represented with rgb value(colors) in vertices.
    def get_tex_para(self, type = 'random'):
        if type == 'zero':
            tp = np.zeros((self.n_tex_para, 1))
        elif type == 'random':
            tp = np.random.rand(self.n_tex_para, 1)
        return tp

    def generate_colors(self, tex_para):
        '''
        Args:
            tex_para: (n_tex_para, 1)
        Returns:
            colors: (nver, 3)
        '''
        colors = self.model['texMU'] + self.model['texPC'].dot(tex_para)
        colors = np.reshape(colors, [int(3), int(len(colors)/3)], 'F').T/255.  
        
        return colors

形状和表情部分进行了下式的矩阵运算:
公式
纹理部分也进行了类似运算,这里不赘述。
到这里产生了新的人脸模型。

2. 网格位置变换

变换网格顶点位置到合适处。

s = 8e-04
angles = [10, 30, 20]
t = [0, 0, 0]
transformed_vertices = bfm.transform(vertices, s, angles, t)
projected_vertices = transformed_vertices.copy() # using stantard camera & orth projection
 def transform(self, vertices, s, angles, t3d):
        R = mesh.transform.angle2matrix(angles)
        return mesh.transform.similarity_transform(vertices, s, R, t3d)

对应的源码:

def similarity_transform(vertices, s, R, t3d):
    ''' similarity transform. dof = 7.
    3D: s*R.dot(X) + t
    Homo: M = [[sR, t],[0^T, 1]].  M.dot(X)
    Args:(float32)
        vertices: [nver, 3]. 
        s: [1,]. scale factor.
        R: [3,3]. rotation matrix.
        t3d: [3,]. 3d translation vector.
    Returns:
        transformed vertices: [nver, 3]
    '''
    t3d = np.squeeze(np.array(t3d, dtype = np.float32))
    transformed_vertices = s * vertices.dot(R.T) + t3d[np.newaxis, :]

    return transformed_vertices

这部分和之前pipeline源码分析中的顶点位置变换部分相同,输入为网格顶点vertices、缩放比例s、旋转矩阵R和平移向量t3d执行空间坐标变换s*R.dot(X) + t后输出变换后的顶点位置

3.转化为二维图像

这部分同样与pipeline例程相同,放出来代码:

h = w = 256; c = 3
image_vertices = mesh.transform.to_image(projected_vertices, h, w)
image = mesh.render.render_colors(image_vertices, bfm.triangles, colors, h, w)
plt.imshow(image)
plt.show()

得到新生成的三维人脸模型图片如下图:
结果
后续将继续更新代码后半部分的解读,敬请关注

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值