作者:小灰灰 来源:投稿
编辑:学姐
本次的例子是将pipeline生成的图片作用于3DMM,重新拟合成新的图片。
3DMM的表达式:
̅ ∈ 3是平均人脸形状, 脸扫描训练得到的身份基,是人脸的身份参数。是表情基,是人脸的表情参数。这个公式只要我们确定199维的形状参数和29维的表情参数就可以得到一张三维模型。
bfm = MorphabelModel('Data/BFM/Out/BFM.mat')
这里面是使用牙买加人脸,200个人脸,男生与女生个100个训练出来的。这个mode里面有:
'shapeMU': [3*nver, 1]. * 自然状态下的一个人脸
'shapePC': [3*nver, n_shape_para]. * s1,s2 有199 特征向量
'shapeEV': [n_shape_para, 1]. ~ 形状的特征值
'expMU': [3*nver, 1]. ~ 有平均的表情形状,有一个人脸
'expPC': [3*nver, n_exp_para]. ~ 有29个表情特征向量
'expEV': [n_exp_para, 1]. ~ 有29个特征值
'texMU': [3*nver, 1]. ~ 有平均形状的纹理
'texPC': [3*nver, n_tex_para]. ~ 有纹理199特征向量
'texEV': [n_tex_para, 1]. ~ 有 199纹理特征值
'tri': [ntri, 3] (start from 1, should sub 1 in python and c++). * 105840行三角形
'tri_mouth': [114, 3] (start from 1, as a supplement to mouth triangles). ~ 嘴114
'kpt_ind': [68,] (start from 1). ~ 在53215中其中68个是关键点
可以看到我们将表情的与形状的值加起来得到新的。
model['shapeMU'] = (model['shapeMU'] + model['expMU']).astype(np.float32)
model['tri'] = model['tri'].T.copy(order = 'C').astype(np.int32) - 1
#减一是从1开始,从零开始
model['tri_mouth'] = model['tri_mouth'].T.copy(order = 'C').astype(np.int32) - 1
# 114,3 包含嘴的三角形
# kpt ind
model['kpt_ind'] = (np.squeeze(model['kpt_ind']) - 1).astype(np.int32)
#原始的索引都是从1开始的,68个索引。
根据3DMM我们知道就可以生成一张图片,我们将需要的参数进行随机数乘以一个非常大的数。
sp = bfm.get_shape_para('random')#形状
ep = bfm.get_exp_para('random')#表情
(-1.5,1.5)之间,有可能增强表情,优肯削弱表情。
在根据sp, ep的参数,根据3DMM公式去生成人脸。
公式对应的的代码
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
colors = self.model['texMU'] + self.model['texPC'].dot(tex_para*self.model['texEV'])
colors = np.reshape(colors, [int(3), int(len(colors)/3)], 'F').T/255.
随机生成的三维人脸如下:
我们将随机生成的三维人脸经过
s = 8e-04
angles = [10, 30, 20]
t = [0, 0, 0]
pipepline中讲过,通过s,a,t可以直接生成下面的图片左图所示。对应的三维模型右图所示。这是通过代码生成的图片。
本节介绍怎样通过3DMM来拟合生成跟输入的上述图片近似一样。
代码中取出68个点进行比较。
x = projected_vertices[bfm.kpt_ind, :2]
接下来需要68个点x,68个点对应的索引。
fitted_sp, fitted_ep, fitted_s, fitted_angles, fitted_t = bfm.fit(x, X_ind, max_iter = 3)
x: (n, 2) 是二维图片的坐标,X_ind:代表的是68个点对应的索引的三维点。max_iter:是迭代的次数。
进行优化
fitted_sp, fitted_ep, s, R, t = fit.fit_points(x, X_ind, self.model, n_sp = self.n_shape_para, n_ep = self.n_exp_para,max_iter = max_iter)
接下来看代码:
sp = np.zeros((n_sp, 1), dtype = np.float32)
ep = np.zeros((n_ep, 1), dtype = np.float32)
shapeMU = model['shapeMU'][valid_ind, :]
shapePC = model['shapePC'][valid_ind, :n_sp]
expPC = model['expPC'][valid_ind, :n_ep]
上面的shapeMU是根据平均人脸,shapePC选出平均的199个包含68个关键点的人脸,expPC选出29个表情包含68个关键点的人脸。
上面的就是以前53215个点,现在变成68个点进行采集,204是68*3,3表示的是x,y,z
接下来进行迭代:
for i in range(max_iter):
我们根据3DMM公式:
对应的代码为:
X = shapeMU + shapePC.dot(sp) + expPC.dot(ep) #X.shape=(204, 1)
将(204,1)变成(3,68)
X = np.reshape(X, [int(len(X)/3), 3]).T
这个函数是我们要找一个变换将三维空间的点变换到一个二维空间的点。
X表示三维的点,x表示二维的点。
X = X.T; x = x.T
assert(x.shape[1] == X.shape[1])
n = x.shape[1]
assert(n >= 4)
这里面我们要解方程组,八个未知数,所以n的大于等于4才能解出方程组。
求x的均值,求模长的平均
x = x - np.tile(mean[:, np.newaxis], [1, n])
average_norm = np.mean(np.sqrt(np.sum(x**2, 0)))
scale = np.sqrt(2) / average_norm #0.03789943607443278
为什么是np.sqrt(2),因为边长为1的等腰直角三角形的斜边就是np.sqrt(2)
三维的点同理可得:
我们需要求八个数,来得到二维坐标
A = np.zeros((n*2, 8), dtype = np.float32);
X_homo = np.vstack((X, np.ones((1, n)))).T
A[:n, :4] = X_homo
A[n:, 4:] = X_homo
b = np.reshape(x, [-1, 1])
X1=P1X1+P2X2+P3X3+P4
Y1=P1Y1+P2Y2+P3Y3+P4
X1=P1XZ1+P2YZ2+P3Z3+P4Z
可使用下面的代码可得
我们估计P矩阵,第一行是P8的前四个数,第二行是P8的后四个是,第三行是0 0 0 1
通过齐次坐标可以是x=PX实现二维点与三维点的转换。
这里面的UV是下面的这个图:
代码对应的意思就是将x,y,z旋转之后到x,y的坐标轴上。
主要恢复S,R,T
主要用R矩阵恢复夹角。
x: pitch y: yaw z: roll
会把旋转的三个角度算出来,把算出来的三个角度与一开始生成图片的角度的图片10,30,20,匹配看是否相等,那么根据步骤(2 再固定αi,βi 优化s,R,t ) 我们已经把s,r,t估计出来了,那就会估计 αi,βi 形状参数和表情参数。
下面就是估计
3 固定s,R,t,先优化βi 。代码里面是estimate_expression.
4 固定s,R,t,在优化αi。代码里面是estimate_shape.
接下来就是通过参数进行顶点的生成,通过s,r,t得到旋转的顶点,然后渲染回2D图片,具体可详看pipepline。
# verify fitted parameters
fitted_vertices = bfm.generate_vertices(fitted_sp, fitted_ep)
transformed_vertices = bfm.transform(fitted_vertices, fitted_s, fitted_angles, fitted_t)
image_vertices = mesh.transform.to_image(transformed_vertices, h, w)
fitted_image = mesh.render.render_colors(image_vertices, bfm.triangles, colors, h, w)
三维人脸重建经典论文解读点击卡片学起来