位置图回归:打破3DMM束缚的人脸三维重建与稠密对齐新思路

📅 发布时间:2026/10/14 20:12:25
位置图回归:打破3DMM束缚的人脸三维重建与稠密对齐新思路
人脸三维重建这个方向很长一段时间里大家的默认路径都是先套 3DMM 这类参数化模型。基向量组合的思路当然清晰人脸形状可以被看作一个线性子空间里的点拟合参数就是在这个子空间里找一个最接近输入图像的点。但真到工程上就会撞上那堵墙——子空间外的细节表达不了大表情和侧脸很容易崩稀疏关键点还总得依赖一个额外的检测器去支撑初始化。所以我第一次看到 Position Map Regression Network 这个思路的时候第一反应是终于有人不再纠结于“参数”了。这篇工作做了一件很纯粹的事输入一张人脸图直接回归一张 UV 空间里的位置图位置图上的每个像素存的是三维坐标。拿到这张图三维网格能重建出来稠密对齐关系也顺带解决了。不需要迭代优化不需要 3DMM 系数不需要单独的关键点对齐。今天这篇博文我会把它的原理、网络设计、损失函数、训练细节和落地时容易踩的坑一次讲透。不管你刚接触三维重建还是已经在做数字人和人脸特效的工程开发应该都能从这里拿到一些可以直接用的东西。1. 项目概述与整体思路拆解1.1 三维人脸重建为什么一直卡在“参数”上先复盘一下传统方案死在哪里。以 3DMM 为例它的核心假设是人脸形状可以用一组基向量的线性组合来近似。拟合的时候你要么用稀疏特征点去约束参数要么直接用像素误差做光度优化。主力优化算法还都绕不开迭代求解构建残差、计算雅可比、更新参数然后在重建和匹配之间反复切。这个框架的问题是全局参数耦合。形状基向量之间是相关的一个表情参数动一点整个脸型都会跟着变化而线性子空间的表达能力有限典型人脸上没问题换到数据分布覆盖少的长相出来的脸就“太平均”。更麻烦的是整个过程高度依赖初始姿态和关键点检测。关键点偏一点优化大概率落到局部极小。你去看一些早期方法生成的侧脸重建结果会发现脸型被硬掰回正面像贴了一张面具。后来有人直接用 CNN 回归 3DMM 参数把迭代优化换成了单次前向推理。速度确实上来了但你回归的还是同一组全局参数。网络的表达天花板被参数空间锁死等于换了一辆更快的车路还是原来的路。这就是为什么“位置图回归”这个方向会让人眼前一亮——它把问题从连续空间里的参数优化直接改造成了二维图像上的逐像素回归。1.2 位置图把三维坐标“画”成一张图核心洞察其实不复杂。人脸的表面可以展开到一个二维的 UV 平面上就像把地球仪的表面展开成一幅世界地图。你给每个三维顶点分配一对固定的 UV 坐标并按分辨率在 UV 图里铺像素那么“人脸三维形状”就变成了一张图。更进一步你可以把这个像素的值设成它对应顶点的三维空间坐标也就是用 RGB 三个通道分别存 x、y、z。这张图就是位置图Position Map。为什么这个看似简单的表示能解决参数化模型的痛点因为网络不再通过一组全局参数去影响所有顶点而是直接在输出端逐像素地决定每个顶点的三维位置。这意味着它有能力表达线性空间之外的几何细节——酒窝、法令纹、夸张表情的局部隆起只要训练数据里出现过网络理论上都能逼近。更重要的是位置图是稠密的每个有效像素都是人脸三维表面上的一个点它天然内建了顶点索引关系可以同时处理自遮挡区域。深度图在姿态估计里也是常见的输出表示但深度图本质是 2.5D它保存的是从相机视角看到的深度值背后的曲面信息是缺失的被遮挡的区域没有值。位置图不一样它存的是绝对三维坐标不依赖观察视角人脸的侧面和背面只要拓扑展开时有定义网络就可以去预测。这也为后面做稠密对齐留好了伏笔。1.3 重建与稠密对齐为什么能合成一件事传统流程里重建和对齐是两件需要交替迭代的事。你先要重建出三维模型再把这个模型投影到图像上跟输入做对齐然后根据对齐误差再反过来修正三维模型。每一步都有误差传播每一步都需要设计目标函数。PRN 把这两件事合并了。训练阶段网络学习的是一个从二维图像到固定拓扑三维表面的映射。因为位置图的 UV 坐标是预先定义好的网络在预测三维坐标的同时其实就已经在推理“这个顶点对应图像的哪个位置”。到了推理阶段你只需要从位置图里读出三维点和它们在 UV 拓扑中的索引再用 PnP 求解相机位姿稠密对应关系就全部确定了。整个过程没有迭代、没有交替优化。所以它叫“联合重建与稠密对齐”而不是两阶段拼接。2. 核心原理与网络结构细读2.1 UV 展开与位置图的数学定义想真正看懂 PRN先得搞明白 UV 展开是怎么做的。这里我以通用的人脸模型为例说明。假设你有一个标准三维人脸网格每个顶点坐标是 v (x, y, z)。把它按圆柱坐标展开到二维平面上公式一般长这样u 0.5 0.5 * atan2(z, x) / π v 0.5 - (y - y_min) / (y_max - y_min)做完这个映射再按分辨率量化到像素坐标三维顶点就落到了 UV 图里固定的位置上。生成位置图的时候把顶点的三维坐标归一化到 0~1 或者 0~255 范围分别写入对应 UV 像素的 RGB 通道。一张位置图本质上就是你预先定义好的拓扑网格在三维空间里的坐标快照。这个展开过程有两点要特别留意。第一UV 边界处的映射会有拉伸畸变边缘像素对应的三维表面面积往往很小有些区域甚至可能是无效像素处理这些位置需要专门的掩码。第二归一化方式决定了网络学习的目标范围。位置图坐标是绝对空间坐标不能用图像分类任务里常用的全局均值方差归一化而是要根据人脸包围盒做线性缩放保证输出的坐标回到真实度量比例。2.2 编码器-解码器网络长什么样网络本身不复杂大体上是标准的编码器-解码器结构。输入是一张裁剪好的人脸图经过编码器逐步下采样提取高层语义特征再经过解码器逐步上采样恢复到 UV 位置图分辨率最后一层卷积输出 3 个通道对应 x、y、z 坐标。我用过的大致结构可以描述成这样def forward(self, img): feat self.encoder(img) # 4~5 个下采样 stageResBlock 堆叠 feat self.decoder(feat) # 对称上采样双线性插值 卷积 position_map self.head(feat) # 1x1 卷积输出 3 通道 return position_map编码器的下采样次数和解码器的上采样次数要匹配否则最后输出的分辨率对不上位置图。原思路里一般用类似 ResNet 的 stem 加 4 个 stage解码器用双线性上采样加卷积而不是单纯的反卷积。反卷积容易产生棋盘格伪影尤其在 UV 图边缘这种像素本来就稀疏的区域棋盘格会让重建网格出现不自然的条带。激活函数上中间层用 ReLU 或 LeakyReLU 都可以最后一层因为要输出连续坐标值不需要加 Sigmoid直接用线性输出。很多人在这里会犯迷糊以为位置图是图像输出层要加归一化。实际上坐标值可能是负的加了 Sigmoid 反而会限制三维坐标的表达范围。2.3 损失函数与权重图最终效果的一半秘密位置图回归的损失函数形式上很简单就是带权重的逐像素 L2 损失loss (weight * (pred - gt).pow(2)).sum() / weight.sum()简单公式里藏着的门道全在 weight 这个权重图上。如果不加权重网络通常会在这些地方翻车第一个是边缘和 UV 边界。由于圆柱展开的畸变人脸边缘的 UV 像素对应着非常少的表面面积但它们的回归误差在数值上跟中心区域是同等对待的这等于让网络把大量精力花在几乎不可信的边缘像素上。解决方法是给中心区域更高的权重边缘递减这样可以减少畸变带来的不均衡。第二个是被遮挡区域。训练数据里如果有人脸侧转、低头、被手遮挡某些顶点实际上是不可见的。如果位置图的 GT 还是填了值网络在可见性不确定的区域就会学到矛盾信息。更合理的是用遮挡掩码把这些区域的权重设成 0只让可见区域提供监督。第三个是眼睛、嘴巴这些高形变区域。这些地方近邻帧之间的拓扑位置变化很剧烈如果权重跟额头一样大损失会被这些高变区域主导最后输出的脸容易变成“五官模糊”的平均脸。实践中可以适当降低高变区域的权重让网络先保证整体脸型的稳定再去抠局部细节。权重图不需要网络自己学习它完全由网格拓扑和训练数据的可见性预先计算出来训练前放到 GPU 显存里当常量用就行。3. 从位置图到三维网格与稠密对齐3.1 后处理管线位置图到 Mesh网络输出的位置图不是可以直接渲染的网格它只是一堆有组织的坐标点。要把它转成可用的人脸三维模型还需要几步后处理遍历位置图所有像素读取归一化坐标并反变换到真实三维坐标。根据 UV 模板的有效掩码把无效像素剔除。利用预设的拓扑索引表把三维点连接成三角网格。保存为 OBJ 或 glTF 等标准网格格式或直接送进渲染管线。这个过程看起来简单但有一个最容易被搞错的细节位置图的分辨率和模板网格的拓扑索引必须严格一致。UV 分辨率是 256 的话网格顶点数差不多就是 65536 这个量级。你如果训练时的 UV 分辨率是 256推理时换了 128 的位置图顶点索引就对不上了。所以后处理里那个索引表最好在预处理阶段就固化好跟渲染尺寸一起作为常量保存。另外一个实操点是读坐标时要注意存储顺序。常见实现里会把顶点坐标按 xyz 放进 RGB 通道但因为归一化范围不同颜色差异会很大直接用图像查看器打开位置图看到的往往是一堆奇怪的色块。别用肉眼看颜色去判断模型对不对直接用三维查看器看 mesh。3.2 稠密对齐与相机位姿求解有了三维网格稠密对齐怎么解这里用到的是 2D-3D 对应关系。因为位置图是在固定 UV 拓扑上定义的每个有效像素都知道自己在三维网格里的顶点索引又因为输入图像经过一个人脸检测器裁剪后位置图的 UV 坐标和图像像素坐标之间存在一个固定的对应关系所以你手上其实已经有了成对的“三维点图像二维点”对应集合。把这些对应点喂给 PnP 求解器就能得到相机旋转和平移。以透视相机为例投影关系可以写成s * [u, v, 1]^T K * [R | t] * [x, y, z, 1]^T其中 K 是相机内参R 是旋转矩阵t 是平移向量。求解 R 和 t 之后每个三维顶点都能投影回图像平面稠密对齐关系就齐了。这里再强调一次之所以能做到“稠密”是因为拓扑固定顶点和 UV 像素一一对应而不是传统的稀疏关键点对齐。稀疏对齐只能得到几十个点的对应PRN 得到的是上万甚至更多的对应关系。实际工程中如果只是做人脸特效贴图使用正交相机模型近似透视投影通常也够了而且 PnP 求解更稳定。但如果你做的是需要精确叠加虚拟眼镜的增强现实应用还是按透视模型来减少边缘区域的投影偏差。3.3 为什么这个设计在工程上很省事很多人会忽略一个隐性优势固定拓扑意味着不需要每次推理都重新生成网格连接关系。三角面索引表只需要生成一次之后所有帧的 mesh 都复用同一张面表。3DMM 方法里拟合出的网格拓扑也是固定的但为了匹配图像往往要做额外的变形优化。PRN 没有这个过程网络输出的顶点坐标直接就是最终形状后处理几乎是一个纯查表的操作。这种设计还天然支持 GPU 加速。读取位置图坐标、按索引连接顶点、计算法线这些操作都可以写成 GPU kernel而不是在 CPU 上用嵌套循环遍历每个面。我之前把一个简单的参考实现重写成 GPU 版本单帧后处理时间从十几毫秒降到了两三毫秒对一个追求实时性的应用来说这个差距很关键。4. 训练一个可用模型数据、参数与实操经验4.1 训练数据从哪来监督怎么生成PRN 这种监督学习方法最大的训练难点不在网络而在监督数据。每张人脸图都要有一张对应的三维位置图作为 GT。手工标注三维网格是不可能的所以常规做法是先用一个已有的高精度三维人脸重建方法去拟合出每张图的形状、表情、姿态再把拟合出的三维模型按同一个 UV 展开渲染成位置图作为训练用的监督信号。这里有个“自举”的味道用别的方法生成标签来训练新的网络。所以监督质量直接决定了 PRN 的天花板。拟合得不准的数据会让网络学会输出一个“平均脸版本”的扭曲错误。训练前最好抽一批数据做可视化检查看位置图渲染出的 mesh 是不是跟原图姿态一致。还有一个细节是数据源的人脸框。训练数据里的图像如果是用不同的人脸检测器裁剪的裁剪框中心和尺度会不一致直接训练会让网络以为输入分布是乱的。统一的人脸框处理包括统一裁剪到固定尺寸、用同一套关键点做相似变换会影响最终的姿态估计稳定性。4.2 训练配置我用过的参数组合我在复现时用过一组比较稳定的配置给你参考配置项参数值输入图像尺寸256x256位置图分辨率256x256编码器主干类似 ResNet 的前 4 个 stage优化器Adam初始学习率1e-4学习率策略每 10 个 epoch 降为原来的 0.5Batch Size16总训练轮数30~50数据增强随机旋转 30 度、亮度抖动、随机遮挡这个配置不是唯一答案但比较稳。如果显存有限可以先把输入降到 224输出位置图降到 192效果损失不会太大。学习率这里要留意位置图回归的 L2 损失通常在前几个 epoch 下降很快后面会进入一个平台期。如果你发现 loss 一直在 1e-3 量级下不去多半不是学习率问题而是权重图里无效像素处理得不对。4.3 复现时最容易被忽略的四个细节第一个是坐标归一化。位置图里存储的三维坐标如果训练时用的归一化范围和推理时不一样mesh 会整体变形。我建议把坐标系固定在一个以人脸中心为原点的包围盒内训练和推理用同一套缩放因子。第二个是权重图的预计算。别在训练循环里每次现算权重把权重图和遮挡掩码提前在数据阶段算好、存成常量训练时直接从显存读。否则这会变成一个不明显但很拖速度的瓶颈。第三个是解码器的上采样方式。强烈建议用双线性插值加卷积而不是直接反卷积。反卷积在 UV 图这种稀疏目标上容易产生棋盘伪影特别是眼睛周围网格会出现规则的条纹噪声排查起来很费时间。第四个是有效掩码的传播。位置图上有些像素永远没有对应的三维顶点这些像素在损失计算里必须被排除。但注意不要只排除一次后处理阶段如果丢失了掩码会导致边缘顶点坐标读出无意义的噪声。把这个掩码同时用于训练和后处理是保证稳定性的基础。5. 常见问题与排查技巧实录5.1 重建结果出现飞点和空洞怎么办飞点是位置图后处理里最常见的现象。某个本应是无效像素的位置网络给出了一个巨大的坐标值网格渲染出来就会在五官周围长出刺状的孤点。排查思路分三步先看是不是损失函数没加权重导致网络在边缘无效像素上乱猜再看后处理有没有用掩码剔除无效像素最后对输出的顶点坐标做一个统计离群点剔除把超过正常人脸包围盒范围的顶点直接丢弃。空洞通常出现在 UV 展开的边界或拓扑覆盖盲区比如耳朵和脖子连接处。如果你不需要耳朵建模时就把这些区域直接从模板网格里去掉不要硬让网络去预测一个它没见过的表面。5.2 视频里姿态和形状抖得厉害单帧独立预测带来的时序抖动是这类方法最常见的落地痛点。网络每一帧都看到一个新的输入输出的形状和姿态会存在帧间噪声。解决方案分两层。第一层是在推理后做时间平滑比如对输出的旋转矩阵做滑动平均对顶点坐标做指数移动平均。注意对旋转矩阵做平均时要用矩阵形式不能直接平均欧拉角否则会出现奇异性问题。第二层是在训练阶段做视频增强。把同一人的相邻多帧一起喂给网络在损失函数里加一个时序一致性正则项让网络对相邻帧输出尽量接近的形状。这个改动比单纯的后处理平滑更治本但需要视频数据对数据准备的要求更高。5.3 遮挡和大表情导致结果退化口罩、墨镜、夸张表情会造成明显的退化。口罩会把下半脸整个遮挡网络在遮挡区域缺少有效监督往往倾向于把嘴部回归成一个平滑的凸面。墨镜会让眼睛区域出现奇怪的凹陷。这里最管用的办法是训练数据里加随机遮挡增强。用随机形状的 mask 覆盖输入图像然后在损失计算时把这些 mask 区域的权重设成 0。这样一来网络学会了在不完整的输入上利用对称性和上下文先验补齐被遮挡的区域而不是死记硬背输入的完整像素。5.4 实时推理的性能优化思路想要达到实时需要从三个方向同时优化。网络层面把主干换轻量级网络比如用 MobileNet 的卷积结构替换 ResNet stage输入尺寸降到 128 或 192位置图分辨率降到 128。模型精度会有一点损失但视频应用里通常可以接受。后处理层面把顶点索引和三角化表从 CPU 搬到 GPU坐标读取、离群点剔除、法线计算全部并行化。这一步通常能省掉大多数耗时。渲染层面如果目标是贴特效可以不用显式导出 mesh直接在片段着色器里采样位置图做深度测试和法线计算。位置图在网络输出后的显示流程里本来就是一个 GPU 纹理不需要转成 CPU 数据来回拷贝的延迟完全可以省掉。最后再分享一个我自己的体会。PRN 这个方案真正的价值可能不在网络结构本身而在“位置图”这个中间表示的设计。它把三维重建问题转成了一个跟卷积网络天然契合的二维映射问题。比起调参调结构我后来花在调整 UV 展开、权重图和掩码上的时间反而更多而且每次改动都直接反映在最终 mesh 质量上。如果你正准备做轻量级的人脸重建和稠密对齐不妨沿着这个框架自己搭一版改起来会比想象中顺手得多。