摘要
从单一图像中估计三维人脸形状必须对光照、头部姿势、表情、面部毛发、化妆和遮挡的变化具有鲁棒性。
RingNet利用一个人的多张图像,自动检测出2D面部特征
提出的方法
- 目标是从单一的人脸图像估计3D头和脸形状
- 输入的是:2D landmarks 和 identity labels
- During inference it uses only image pixels; 2D landmarks and identity labels are not used.
- 关键思想:
- 一个人的脸型是不变的,即使一个人脸的图像可能在观看角度、光照条件、分辨率、遮挡、表情或其他因素不同。
- 每个人都有独特的脸型(不包括同卵双胞胎)。
- 引入形状一致性损失来利用这个思想:
- RingNet是一种基于多个编码器-解码器的架构。环中的每个编码器是特征提取网络和回归网络的组合。
- 在形状变量上施加形状约束,可以迫使网络分解面部形状、表情、头部姿态和摄像机参数
- 我们使用FLAME作为解码器,从中重构三维人脸
- RingNet输入一张图片来预测相应的3D mesh
- 损失有一部分是:从预测的3D mesh中计算出3D landmarks,然后将其映射到2D landmarks,然后计算其与真实的2D landmarks之间的值,并将其作为损失
FLAME 模型
- FLAME使用线性变换来描述身份和表情依赖的形状变化,并使用标准线性混合蒙皮(LBS)来模拟颈部、下颌和眼球的旋转
RingNet
-
2D 带landmark和 identity的语料库很充足
-
RingNet引入了一种环形结构,因为输入任意数量的图像,该结构在图像的形状一致性上可以联合优化
-
形状一致性loss:
- 我们工作的一个关键目标是,制作一个健壮的端到端可训练的网络,可以从同一对象的图像产生相同的形状,不同的对象产生不同的形状。
- 我们通过要求:匹配的对在形状空间中的距离 比 未匹配的对的距离小一点来加强这一点。
- 距离是在面部形状参数空间中计算的,该空间对应于中立姿态下的顶点欧几里德空间。
- shape constancy 简单的很呀 ∣∣βj?βk∣∣22+η<=∣∣βj?βR∣∣22|| \beta_j - \beta_k ||^2_2 + \eta <= || \beta_j - \beta_R ||^2_2∣∣βj??βk?∣∣22?+η<=∣∣βj??βR?∣∣22?
- 因此最大化这个距离即可
-
2D feature loss
- 计算真实2D landmark和训练投影之后landmark之间的L1 loss。我们并不直接检索2D landmark,而是已知拓扑结构的三维网格,从中检索地标。
- 给定FLAME模板网格,我们为每个OpenPose关键点寻找网格表面中相应的3D点。
- 口、鼻、眼、眉关键点对应一个固定的三维点(称为静态三维地标),轮廓特征的位置随着头部姿态的变化而变化(称为动态三维地标)。
- 我们将轮廓地标建模为随着头部的全局旋转而动态移动。为了自动计算这个动态轮廓,我们将FLAME模板向左和向右旋转-20度到40度,渲染带有纹理的网格,运行OpenPose来预测2D的路标,并将这些2D点投射到3D表面。由此产生的轨迹在左脸和右脸之间对称地传递。
- 在训练期间,RingNet输出3D网格,计算这些网格的静态和动态3D地标,并使用编码器输出的摄像机参数将这些地标投影到图像平面。
-
RingNet被划分为R个环单元,其中每个ei由一个编码器和一个解码器网络组成。编码器在ei之间共享权值,解码器权值在训练过程中保持固定。该编码器是特征提取网络和回归网络的结合。给定一个图像, 特征提取网络输出一个高维向量,然后被回归网络编码成一个语义上有意义的向量。这个向量可以表示为包含相机、姿态、形状和表情参数的一个集合向量。而姿态、形状和表情参数都是FLAME参数。
-
该回归网络在迭代误差反馈环路中对集合向量进行迭代回归,而不是直接从回归网络的高维向量中回归集合向量。
-
所以回归网络以ffeat,i,fenc,if_{feat,i}, f_{enc,i}ffeat,i?,fenc,i?作为输入,输出δfenc,i\delta f_{enc,i}δfenc,i?
-
环形元素的数量R是我们网络的一个超参数,它决定了在shape上以优化的一致性并行处理图像的数量
-
为了不失一般性,将相同身份的人脸图像输入前R-1个环中,不同身份的人脸图像输入最后一个环中
-
总的损失函数
Ltot=λSCLSC+λprojLproj+λβ∣∣β∣∣22+λ?∣∣?∣∣22L_{tot} = \lambda_{SC}L_{SC} + \lambda_{proj}L_{proj} + \lambda_{\beta}||\beta||^2_2 + \lambda_{\phi}||\phi||^2_2Ltot?=λSC?LSC?+λproj?Lproj?+λβ?∣∣β∣∣22?+λ??∣∣?∣∣22?
实现细节
- 马氏距离(Mahalanobis Distance)是一种距离的度量,可以看作是欧氏距离的一种修正,修正了欧式距离中各个维度尺度不一致且相关的问题。
- 特征提取网络使用预先训练好的ResNet-50,其输出一个2048维度的向量。然后将其输入到一个回归网络中。
- 回归网络由两个512维的全连通层(ReLu激活层和dropout层)组成,最后一个是159维的线性全连接层(linear full -connected layer)。
- 这159个参数如下:前3个元素代表比例和二维图像平移。接下来6个元素是全局旋转和颚旋转,每一个以轴角表示。FLAME的颈部和眼球旋转没有回归,因为面部标志没有对颈部施加任何限制。接下来的100个元素是形状参数,然后是50个FLAME表达式参数。
- 以1e-4的恒定学习率训练RingNet 10 epochs,并使用Adam进行优化。
- R=6,λSC=1,λproj=60,λβ?=1e?4,λψ?=1e?4,η=0.5R = 6, λ_{SC} = 1, λ_{proj} = 60, λ_β? =1e?4,λ_ψ? =1e?4,η=0.5R=6,λSC?=1,λproj?=60,λβ??=1e?4,λψ??=1e?4,η=0.5
- 使用VGG2人脸数据库作为训练数据集。在数据集上运行OpenPose,并计算出脸部对应的68个landmarks。我们考虑了大约3000张极端姿态图像和相应的地标。由于这些极端的图像我们没有任何标签,我们random crops复制每张图像并为了配对将其进行缩放。
制作的NoW数据集
结论
- 缺陷:Our 2D feature detector does not include the ears, though these are highly distinctive fea- tures. Adding 2D ear detections would further improve the 3D head pose and shape. While our model stops with the neck, we plan to extend our model to the full body
- Expressive body capture: 3d hands, face, and body from a single image.
- Gener- ating 3D faces using convolutional mesh autoencoders
- 全身的模型现在有HMR
在PyTorch中实现FLAME3D头部模型代码解读:
main.py
argsparse是python的命令行解析的标准模块,内置于python,不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数并让程序运行。
- ??flame_model_path的默认路径为: default = ‘./model/generic_model.pkl’ (但是自己的文件中没有这个模型)
- 为了在命令行中避免上述位置参数的bug(容易忘了顺序),可以使用可选参数,这个有点像关键词传参,但是需要在关键词前面加–
import argparseparser = argparse.ArgumentParser(description='姓名')
parser.add_argument('--family', type=str,help='姓')
parser.add_argument('--name', type=str,help='名')
args = parser.parse_args()#打印姓名
print(args.family+args.name)
在命令行中输入(就是麻烦了一点,但是不容易混淆)
python demo.py --family=张 --name=三
运行结果:张三
FLAME.py
- FLAME layer:在深度学习框架中作为解码器层直接插入,用于训练和测试
- 给定FLAME参数,该类生成一个可微分的FLAME函数,该函数输出网格和三维面部地标
- view()函数作用是将一个多行的Tensor,拼接成一行。
b = torch.index_select(a, 0, torch.tensor([0, 2]))
// 第一个参数是索引的对象,第二个参数0表示按行索引,1表示按列进行索引,第三个参数是一个tensor,就是索引的序号,比如这里 tensor[0, 2]表示第0行和第2行
- 罗德里格斯(Rodrigues)旋转方程 batch_rodrigues函数
def batch_rodrigues(rot_vecs: Tensor,epsilon: float = 1e-8,
) -> Tensor:#计算一批旋转向量的旋转矩阵Parameters----------rot_vecs: torch.tensor Nx3array of N axis-angle vectorsReturns-------R: torch.tensor Nx3x3The rotation matrices for the given axis-angle parameters'''batch_size = rot_vecs.shape[0]device, dtype = rot_vecs.device, rot_vecs.dtypeangle = torch.norm(rot_vecs + 1e-8, dim=1, keepdim=True)rot_dir = rot_vecs / anglecos = torch.unsqueeze(torch.cos(angle), dim=1)sin = torch.unsqueeze(torch.sin(angle), dim=1)# Bx1 arraysrx, ry, rz = torch.split(rot_dir, 1, dim=1)K = torch.zeros((batch_size, 3, 3), dtype=dtype, device=device)zeros = torch.zeros((batch_size, 1), dtype=dtype, device=device)K = torch.cat([zeros, -rz, ry, rz, zeros, -rx, -ry, rx, zeros], dim=1) \.view((batch_size, 3, 3))ident = torch.eye(3, dtype=dtype, device=device).unsqueeze(dim=0)rot_mat = ident + sin * K + (1 - cos) * torch.bmm(K, K)return rot_mat
- unsqueeze()函数就是对张量的维度进行扩充的操作。
- torch.bmm() 函数计算两个tensor的矩阵乘法
- torch.clamp()函数的功能将输入input张量每个元素的值压缩到区间 [min,max],并返回结果到一个新张量。
- torch.lt() 函数。两个可广播的张量之间进行逐元素比较操作, 判断是否满足小于的条件.
- torch.cat是将两个张量(tensor)拼接在一起