PRNet论文解读

原文链接:https://www.pianshen.com/article/8608380708/

 

人脸3D重建ECCV2018PRNet论文解读

github地址:https://github.com/leoluopy/paper_discussing/blob/master/body/PRNet/PRNet.md

Overview

  • 端到端的人脸3D重建模型,同时直接预测人脸的【实际上就是UV空间预测并直接解码,UV空间后面叙述】

以前的模型或者需要首先回归3DMM参数来计算3D空间,或者需要回归特征3D点随后进行非线性优化获取3DMM参数,随后使用3DMM参数来得到稠密的人脸模型,这些方法都是基于3DMM模型的。 当然也有2D的方法,先回归得到2D的不少坐标点,并预测深度图,根据深度度和2D点重建稠密3D人脸

  • 这个模型做到了在1080的显卡上仅仅需要 9ms的速率,相对于之前的各种方法有了很明显的提升,同时在评判指标CED,NME上也有显著的提升

NME : normalised mean error : 归一化的,参考长度的坐标偏差衡量指标,用来评判人脸关键点回归质量的重要指数

CED : NME和数据集比例曲线,衡量在NME达到一定错误率时,已经覆盖的数据集比例,模型的鲁棒性指标

效果概览

  • 人脸关键点和3D重建结构效果

  • PRNet 根据人脸关键3D位置及其相应纹理,进行的换脸术。

  • PRNet重建的稠密3D人脸

UV 空间

  • 左图是二维人脸图片以及对应的稠密3D点云。
  • 右图是UV空间表示。

第一排第一张是原始图片,第一排第二张是将人脸RGB通道映射到UV空间的对应位置【UV空间纹理图】,第一排第三张是UV空间位置图,每个通道表示对应纹理的xyz。

第二排是 UV空间的位置图三个通道的展开。 GT 就是UV位置空间,在代码中维度是(256,256,3)

网络结构


   
   
  1. def resBlock(x, num_outputs, kernel_size = 4, stride=1, activation_fn=tf.nn.relu, normalizer_fn=tcl.batch_norm, scope=None):
  2. assert num_outputs% 2== 0 #num_outputs must be divided by channel_factor(2 here)
  3. with tf.variable_scope(scope, 'resBlock'):
  4. shortcut = x
  5. if stride != 1 or x.get_shape()[ 3] != num_outputs:
  6. shortcut = tcl.conv2d(shortcut, num_outputs, kernel_size= 1, stride=stride,
  7. activation_fn= None, normalizer_fn= None, scope= 'shortcut')
  8. x = tcl.conv2d(x, num_outputs/ 2, kernel_size= 1, stride= 1, padding= 'SAME')
  9. x = tcl.conv2d(x, num_outputs/ 2, kernel_size=kernel_size, stride=stride, padding= 'SAME')
  10. x = tcl.conv2d(x, num_outputs, kernel_size= 1, stride= 1, activation_fn= None, padding= 'SAME', normalizer_fn= None)
  11. x += shortcut
  12. x = normalizer_fn(x)
  13. x = activation_fn(x)
  14. return x

残差结构实现,如上**是relu,归一化是BN,shortcut对应三次卷积,随后通道合并,最后归一化和**。


   
   
  1. size = 16
  2. # x: s x s x 3
  3. se = tcl.conv2d( x, num_outputs=size, kernel_size= 4, stride= 1) # 256 x 256 x 16
  4. se = resBlock(se, num_outputs=size * 2, kernel_size= 4, stride= 2) # 128 x 128 x 32
  5. se = resBlock(se, num_outputs=size * 2, kernel_size= 4, stride= 1) # 128 x 128 x 32
  6. se = resBlock(se, num_outputs=size * 4, kernel_size= 4, stride= 2) # 64 x 64 x 64
  7. se = resBlock(se, num_outputs=size * 4, kernel_size= 4, stride= 1) # 64 x 64 x 64
  8. se = resBlock(se, num_outputs=size * 8, kernel_size= 4, stride= 2) # 32 x 32 x 128
  9. se = resBlock(se, num_outputs=size * 8, kernel_size= 4, stride= 1) # 32 x 32 x 128
  10. se = resBlock(se, num_outputs=size * 16, kernel_size= 4, stride= 2) # 16 x 16 x 256
  11. se = resBlock(se, num_outputs=size * 16, kernel_size= 4, stride= 1) # 16 x 16 x 256
  12. se = resBlock(se, num_outputs=size * 32, kernel_size= 4, stride= 2) # 8 x 8 x 512
  13. se = resBlock(se, num_outputs=size * 32, kernel_size= 4, stride= 1) # 8 x 8 x 512
  14. pd = tcl.conv2d_transpose(se, size * 32, 4, stride= 1) # 8 x 8 x 512
  15. pd = tcl.conv2d_transpose(pd, size * 16, 4, stride= 2) # 16 x 16 x 256
  16. pd = tcl.conv2d_transpose(pd, size * 16, 4, stride= 1) # 16 x 16 x 256
  17. pd = tcl.conv2d_transpose(pd, size * 16, 4, stride= 1) # 16 x 16 x 256
  18. pd = tcl.conv2d_transpose(pd, size * 8, 4, stride= 2) # 32 x 32 x 128
  19. pd = tcl.conv2d_transpose(pd, size * 8, 4, stride= 1) # 32 x 32 x 128
  20. pd = tcl.conv2d_transpose(pd, size * 8, 4, stride= 1) # 32 x 32 x 128
  21. pd = tcl.conv2d_transpose(pd, size * 4, 4, stride= 2) # 64 x 64 x 64
  22. pd = tcl.conv2d_transpose(pd, size * 4, 4, stride= 1) # 64 x 64 x 64
  23. pd = tcl.conv2d_transpose(pd, size * 4, 4, stride= 1) # 64 x 64 x 64
  24. pd = tcl.conv2d_transpose(pd, size * 2, 4, stride= 2) # 128 x 128 x 32
  25. pd = tcl.conv2d_transpose(pd, size * 2, 4, stride= 1) # 128 x 128 x 32
  26. pd = tcl.conv2d_transpose(pd, size, 4, stride= 2) # 256 x 256 x 16
  27. pd = tcl.conv2d_transpose(pd, size, 4, stride= 1) # 256 x 256 x 16
  28. pd = tcl.conv2d_transpose(pd, 3, 4, stride= 1) # 256 x 256 x 3
  29. pd = tcl.conv2d_transpose(pd, 3, 4, stride= 1) # 256 x 256 x 3
  30. pos = tcl.conv2d_transpose(pd, 3, 4, stride= 1, activation_fn = tf.nn.sigmoid)

网络结构如上图所示,有两部分组成,网络残差和反卷积。最后**使用sigmoid得到输出UV空间。

Loss设计

 

  • P(x,y) 是UV位置空间的预测结果,表征了UV图上对应像素的xyz位置
  • W(x,y) 是UV位置空间的权重,对UV空间进行权重控制,关键点:眼鼻嘴:脸部其他:其他 = 16:4:3:0

训练细节

  • 训练数据来源: 使用了300W-LP数据集,
    • 拥有各个角度的人脸数据,使用时scale到256x256
    • 3DMM系数的标注
    • 使用3DMM 生成3D点云,并转换3D点云至UV空间

注: 虽然生成GT使用了3DMM的标注系数,但是模型本身不包含3DMM模型的任何线性约束.

  • 数据增广包含了所有的困难场景:
    • 角度变换 -45 ~ 45 度
    • 平移系数 0.9 ~ 1.2 (原图大小为基数)
    • 颜色通道变换 0.6 ~ 1.4
    • 添加噪音纹理遮挡,模拟真实情况遮挡.
  • adam优化器,初始学习率0.0001,每5个epoch,衰减1半,batch size:16

测试结果

 

  • 上面两图记录了68个关键点和所有点的CED曲线走向情况,总的来说都有不少提升.

  • 角度变化情况下的,3D人脸关键点的NME效果对比

  • 上图使用了瞳间距作为归一化分母,这样更加形象的表达了算法效果.

  • 推理时间[1080显卡]

  • 与各种方法比如VRN在不同角度下,和3d稠密重建效果的对比.

  • 部分推理结果超过GT,训练过程忽略掉的GT噪声

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值