
简介这份PDF收录了发表于《传感器与微系统》2021年的学术论文提出一种基于卷积神经网络CNN与三维形变模型3DMM的单幅图像三维人脸重建方法。资源面向计算机视觉、深度学习领域的研究人员和工程开发者尤其适合需要了解人脸重建前沿方法、网络结构改进或迁移学习应用的读者。包体仅含1个PDF文件大小约1.04MB内容精炼但包含完整的算法设计、实验对比与结论。目前已有197人浏览学习。论文详细介绍了在VGG-16基础上改进的VGG-BN模型通过在卷积层后加入批归一化层提升训练稳定性与泛化能力并采用预训练模型进行迁移学习在300W-LP数据集上训练、AFLW2000-3D数据集上测试实验证明该方法能有效提高三维人脸重建的准确性和表情形状效果。对想快速把握单目三维人脸重建技术脉络的研究者而言是一份不错的入门与参考资料。1. 单图三维人脸重建的路线之争回归参数还是回归顶点直接回归3DMM参数做单图三维人脸重建听起来顺理成章真正落地复现过这套流程的人大概率遇到过同一个怪现象网络在验证集上的参数损失已经压得很低重建出来的面片却明显走形嘴部开合对不上照片眼窝区域塌陷成一块平板。王育坚等人这篇发表在《传感器与微系统》上的工作把问题定位到一个经常被忽略的设计决策上——损失函数到底应该作用在参数空间还是作用在三维顶点空间。方案以VGG-16为骨架在卷积层后加入批归一化层构成VGG-BN配合迁移学习和顶点距离代价VDC在AFLW2000-3D上把平均NME压到2.08%比直接回归参数的评价指标低了近三个百分点。对于做三维重建、数字人建模或人脸分析系统的工程师来说这篇论文的价值不在网络结构多新颖而在于把参数回归类任务的一个典型坑讲透了。2. 3DMM参数化为什么62维参数能表征一张三维人脸2.1 从顶点集合到PCA基向量3DMM三维形变模型是Blanz和Vetter提出的人脸线性表示模型。一张人脸网格通常有几万个顶点每个顶点一个三维坐标如果让CNN直接回归这个原始顶点集输出层就是数万维的回归头在数据量有限的情况下几乎训不动。3DMM的核心做法是收集m个人脸三维扫描样本稠密对齐后把每个样本的形状向量展开为S (x1, y1, z1, …, xn, yn, zn)^T ∈ R^{3n}然后计算所有样本的平均形状和协方差矩阵做PCA分解取前若干个主成分作为基向量。这样一张新的人脸就可以用线性组合来表示S_new S_bar Σ α_j * s_j Σ β_j * e_j其中S_bar是平均脸s_j是形状基向量e_j是表情基向量α_j和β_j是对应的组合系数。PCA保证基向量彼此正交系数大致服从高斯分布。这就是参数化的本质把几万个顶点坐标压缩成几十个系数CNN只需要回归这些系数。2.2 基向量选择BFM的40个形状基和FaceWarehouse的10个表情基论文中形状部分采用BFMBasel Face Model的前40个基向量表情部分采用FaceWarehouse的前10个基向量。这两个数字不是随便拍的。BFM提供了完整的形状统计模型前40个主成分已经覆盖了绝大部分脸型变化能量再往后基本都是噪声基FaceWarehouse是Cao等人构建的表情数据库前10个基足以覆盖常见的嘴、眼、眉部变形。参数规模直接影响回归任务的难度50个形变系数加上投影参数正好把输出维度控制在全连接层可以稳定拟合的量级。参数类别数据来源基向量数量物理含义形状参数 αBFM40脸型、五官轮廓、下颌线表情参数 βFaceWarehouse10嘴部开合、眼部形态等局部变形投影参数弱透视投影模型12缩放、旋转、平移把这三组参数拼接起来就是论文里的62维参数向量P [f, R, t, α, β]^T。这里有个工程上的简化值得注意论文明确提到当3DMM包含纹理部分时拟合效果并不好所以重建时只回归形状和表情纹理直接从原始照片中提取贴图。这个取舍很实际——纹理回归会显著放大输出维度而最终可视化效果并不一定更好。2.3 弱透视投影把三维顶点映射回二维平面参数化只是第一步训练时还需要把三维形状投影回二维图像平面才能计算误差。论文使用的弱透视投影公式为y f * P_r * R * S_new t其中f是比例因子P_r是正交投影矩阵R是由9个参数组成的旋转矩阵t是平移向量。这个投影过程是可微的梯度可以从二维误差一路回传到参数空间这正是后面顶点距离代价VDC能够成立的前提。如果用numpy实现参数到顶点的转换核心代码非常短import numpy as np # shape_basis: (3N, 40) BFM前40个形状基 # expr_basis: (3N, 10) FaceWarehouse前10个表情基 def params_to_vertices(alpha, beta, shape_basis, expr_basis, mean_shape): # 将40维形状参数和10维表情参数映射回N个顶点的三维坐标 return mean_shape shape_basis alpha expr_basis beta这段代码的逻辑是mean_shape是平均脸的顶点坐标shape_basis alpha把形状系数映射成形状偏移量expr_basis beta把表情系数映射成表情偏移量三者相加就得到最终的三维人脸顶点集合。BN层保证每一次卷积输出的数据分布稳定ReLU的梯度不会因为输入偏移而大面积进入死区。3. VGG-BN网络设计批归一化加在哪个位置才算改对了3.1 为什么选VGG-16做骨架三维人脸重建领域里CNN回归3DMM参数的方案并不少见。Tran等人用ResNet-101回归形状和纹理参数Zhu等人用级联CNN做不同姿态下的参数回归Dou等人则在VGG-Face基础上增加子网络做端到端重建。论文选择VGG-16作为骨架理由很实际VGG结构规整所有卷积层都是3×3、步长为1、填充为1特征图尺寸只在池化层减半网络行为容易预测卷积层提取的通用特征边缘、曲线、轮廓对后续回归头是现成的且ImageNet预训练权重容易获取迁移学习成本很低。在只有几万张人脸训练数据的情况下从头训练一个深层网络几乎必然过拟合加载预训练权重是性价比最高的起点。3.2 BN层放在卷积之后、ReLU之前VGG-BN对VGG-16的改进点很集中在每个卷积层之后、激活函数ReLU之前加入批归一化层。BN的计算过程是μ (1/m) Σ x_iσ² (1/m) Σ (x_i - μ)²x_i (x_i - μ) / sqrt(σ² ε)y_i γ * x_i β其中ε是避免分母为零的极小正数论文取1e-5γ是尺度因子β是平移因子两者通过梯度下降学习。位置细节很关键BN必须在ReLU之前。ReLU会把所有负值压成0如果先激活再归一化BN统计量会被激活后的分布干扰归一化的意义就打了折扣先BN再ReLU数据以近似零均值进入激活函数负半轴的神经元不会成片失活梯度在深层网络中传递更稳定。PyTorch实现中VGG-BN的卷积段结构如下import torch.nn as nn class VGGBN(nn.Module): def __init__(self, num_params62): super(VGGBN, self).__init__() # 每个卷积层后接BatchNorm2d再接ReLU self.features nn.Sequential( # Block1: 3 - 64 nn.Conv2d(3, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.Conv2d(64, 64, 3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 后续Block按64-128-256-512-512递增结构相同 ) self.classifier nn.Sequential( nn.Linear(512 * 7 * 7, 4096), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(4096, 4096), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(4096, num_params), # 原始VGG-16这里是1000类 ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)代码里每个卷积层后面紧跟BatchNorm2d再跟ReLU这个顺序和原论文一致。卷积核全部是3×3、步长1、填充1特征图分辨率不缩水只有MaxPool2d(2)把尺寸减半。第三个全连接层从1000类分类改成62维回归输出这是结构上第二处关键改动。3.3 池化策略与Dropout参数的考量卷积层参数表的配置值得单独列出来复现时照这个表核对即可。Block卷积层输入通道输出通道卷积核步长填充Block1Conv1_1 / Conv1_23 / 6464 / 643×311Block2Conv2_1 / Conv2_264 / 128128 / 1283×311Block3Conv3_1 / Conv3_2 / Conv3_3128 / 256256 / 2563×311Block4Conv4_1 / Conv4_2 / Conv4_3256 / 512512 / 5123×311Block5Conv5_1 / Conv5_2 / Conv5_35125123×311池化层全部是2×2的最大值池化步长2。最大值池化在小区域内保留最强响应对边缘和轮廓特征更友好。Dropout的失活概率设为0.5放在前两个全连接层之后。4096维全连接层的参数量非常大在数万张人脸数据上极易过拟合0.5的失活概率配合BN层是这套结构里控制过拟合的两个主要手段。4. 训练策略对比PDC损失与VDC损失到底差在哪4.1 数据准备与归一化实验采用300W-LP作为训练集AFLW2000-3D作为独立测试集。300W-LP由Zhu等人生成总共61,225个样本再通过水平翻转扩增到122,450个每个样本包含二维人脸图像和对应的3DMM系数。原始图像尺寸是450×450论文根据标注的人脸区域将人脸裁剪并缩放到150×150。送入网络前还做了一步关键的均值方差归一化把图像数据和目标参数都映射到均值为0、方差为1的分布X_norm (X - X_mean) / X_std注意标签参数P也需要归一化并且要保存训练集的P_mean和P_std。推理时网络输出的是归一化后的参数必须反归一化才能代入3DMM公式P P_out * P_std P_mean这个细节容易被忽略但直接影响重建结果量级。我在类似任务里踩过这个坑忘掉反归一化时重建出的脸整体缩了一圈一脸思考表情。4.2 损失函数参数距离代价与顶点距离代价论文对比了两种损失。参数距离代价PDC直接计算预测参数和标签参数的欧氏距离L(P) 1/2 * ||P_pred - P_gt||²顶点距离代价VDC则先把预测参数和标签参数分别代入式(6)的投影模型算出三维顶点坐标再计算顶点坐标之间的距离L(P) 1/2 * Σ ||V(P_pred) - V(P_gt)||²为什么VDC明显优于PDC因为3DMM的基向量对顶点坐标的影响是不均衡的。前几个形状主成分对应全局脸型后面的基只影响局部细节参数空间里等权重的误差映射到顶点空间可能被放大或者被稀释。直接回归参数时网络无法感知某个参数偏差在三维形状上造成的实际影响收敛方向就会偏离。VDC把梯度来源从参数空间切换到顶点空间让网络反向传播时接收到的信号直接对应重建歪了多少这在工程上是本质区别。模型是否预训练是否加BN损失函数平均NME/%VGG-PDC否否PDC6.84VGG-prePDC是否PDC3.67VGG-preVDC是否VDC2.20VGG-BN是是VDC2.08这组对照实验的结论非常干净预训练带来了3.17个百分点的提升VDC损失换来了1.47个百分点的提升BN层的贡献是0.12个百分点。前期优化空间最大损失函数的改进次之结构微调只是锦上添花。4.3 迁移学习与超参数配置训练配置采用PyTorch实现加载ImageNet预训练的VGG-16权重初始化卷积层全连接层随机初始化。优化器选择Adam初始学习率1e-4动量0.9L2权重衰减1e-5训练batch_size为64验证batch_size为8epoch总数100。论文对学习率调度的描述略有歧义复现时建议直接按前2个epoch用1e-5热身、之后恢复1e-4、在第60和90个epoch各衰减0.1来尝试收敛曲线更稳定。import torch # optimizer和lr schedule optimizer torch.optim.Adam(model.parameters(), lr1e-4, weight_decay1e-5) scheduler torch.optim.lr_scheduler.MultiStepLR( optimizer, milestones[60, 90], gamma0.1) def vdc_loss(pred_p, gt_p, shape_basis, expr_basis, mean_shape): # 把参数映射回三维顶点再计算距离 pred_v params_to_vertices(pred_p, shape_basis, expr_basis, mean_shape) gt_v params_to_vertices(gt_p, shape_basis, expr_basis, mean_shape) return torch.mean((pred_v - gt_v) ** 2) for epoch in range(100): for x, p_gt in train_loader: pred_p model(x) loss vdc_loss(pred_p, p_gt, shape_basis, expr_basis, mean_shape) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()训练循环里最关键的是vdc_loss函数它把模型输出的参数和标签参数同时做一次顶点映射再在顶点坐标上算均方误差。注意params_to_vertices中的shape_basis、expr_basis、mean_shape是固定的不参与梯度更新它们只是把参数空间映射到几何空间的一座桥梁。5. NME评估与复现落地ICP对齐、CED曲线和那丢掉的504张图5.1 ICP配准与归一化均值误差计算定量评估的流程是先用迭代最近点ICP算法在重建顶点集和真实顶点集之间找到对应关系然后计算归一化均值误差NME。NME的公式为NME sqrt(Σ (v_pred - v_gt)² / n) / sqrt(width * height)分母是人脸区域边界框的几何平均这样不同尺寸的人脸图像误差可比。ICP的核心步骤是在重建顶点集取一个点到真实顶点集找最近邻求解旋转矩阵和平移向量应用变换后计算两个点集的距离小于阈值则停止。实际操作时ICP容易陷进局部最优我一般会把初始旋转矩阵设为单位矩阵平移向量设为两个点集质心的差值能少调几次迭代。5.2 CED曲线和测试集清洗累积误差分布CED曲线是评估报告的核心图横轴是NME阈值纵轴是低于该阈值的样本比例。曲线在左端涨得越陡说明高精度重建的样本占比越大。论文给出的对比结果中VGG-BN的CED曲线全程在VGG-preVDC和文献[14]方法左边说明改进是全面的不是靠一部分样本拉低平均值。复现时最容易忽略的是测试集清洗。AFLW2000-3D有2000张图但论文实际只用了1496张进行评估原因是其中一部分大角度偏转的人脸图连dlib人脸检测器都检测不到只能排除。不同论文排除的样本数可能不同直接拿论文报告的数字对比是不严谨的必须对齐评测样本后再比NME。5.3 复现时的三个具体技巧第一训练日志里同时打印PDC和VDC两个loss。VDC是主优化目标但PDC能反映参数本身的收敛情况。如果VDC下降而PDC震荡说明网络在顶点空间走了一个局部最优可以考虑降低学习率重新跑几个epoch。第二反归一化用训练集保存的P_mean和P_std不要在推理阶段重新统计测试集分布否则不同测试集之间结果不可比。第三BN层在训练和推理模式下行为不同PyTorch里评估前记得调用model.eval()否则BN会使用当前batch的统计量而不是训练时累积的移动平均NME会莫名其妙劣化零点几个百分点。人脸检测器对早期实验的影响比想象中大。手头没有dlib环境的场景下可以先用OpenCV的DNN人脸检测替代但要注意检测框的松紧直接影响裁剪后的人脸对齐质量进而影响重建结果的NME。检测框比标注框松5个像素NME可能浮动0.1到0.2评测报告里应当固定检测器版本这个细节决定了两个模型之间的差异到底是结构改进带来的还是评测噪声带来的。本文还有配套的精品资源点击获取