PRN解析:稠密三维人脸重建与对齐的UV位置图回归方法 做三维人脸这行的人一听到人面对齐大概率先想到68个关键点、99个关键点这类稀疏检测。但真到了重建、换脸、AR贴妆这类应用里稀疏关键点根本不够用——眼睛鼻尖轮廓能对上额头、脸颊、下颌缘这些大片平滑区域全靠猜。所以当Position Map Regression Network以下简称PRN这篇工作出来的时候很多人的第一反应是终于有人把稠密对齐和三维重建放进同一个网络里一次性解决了。PRN的完整思路是Joint 3D Face Reconstruction and Dense Alignment输入一张2D人脸图输出一个叫UV位置图的东西然后从位置图解码出完整的3D人脸Mesh同时天然携带逐像素的稠密对应关系。这篇文章我不打算复述论文公式而是从我实际读代码、跑通、再拿它落地项目的角度把它从头到尾拆一遍包括它的思路为什么成立、训练数据怎么造、Loss为什么要这么设计、部署时有哪些坑。1. 稠密三维人脸对齐的老问题以及PRN换了个什么解题姿势1.1 稀疏关键点对齐的天花板传统上做人脸对齐大家熟悉的是级联回归、热图回归那一套输出的是稀疏的语义关键点。好处是标注相对容易人脸数据集里人工标68点、106点是成熟流程。但稠密对齐要的是每个像素、或者每个三维顶点级别的对应关系这就不是标注成本的问题而是表示方式的问题。想象一下你要把一个三维脸的Mesh一一对应到二维图像的每个脸部像素上。稀疏关键点给出的对应只能覆盖面部一小块区域额头、脸颊这些位置没有关键点软件只能用插值甚至最近邻瞎补。换到3D重建场景稀疏关键点只能勉强约束五官位置面部曲率、轮廓起伏全部缺失出来的模型像一张贴了五官的平面完全没法用于需要真实几何的任务。1.2 3DMM拟合的老路子优化难、初始化敏感在那之前想从单张图得到三维人脸主流方案是3DMM三维形变模型拟合。思路是先学一个形状空间和表情空间把3D人脸表示成基底系数然后通过优化相机参数、形状系数、表情系数让渲染出来的人脸在关键点位置上逼近输入图。这条路子的问题也很经典。第一优化本身是非凸的对初始值极其敏感同一个模型换个姿态、换个光照可能收敛到完全不同的形状第二用关键点做能量项本质上还是稀疏约束稠密细节依旧缺失第三每张图要单独迭代优化虽然最终精度可以很高但速度根本谈不上实时一帧几百毫秒到几秒都有工程上很难受。其实说到底这些人脸重建方案的共同弱点是它们都在参数空间或者稀疏点空间里做优化中间都没有一个让卷积网络直接输出稠密三维信息的媒介。PRN换了一个很直接的思路——我不优化系数我直接让网络学会输出一个稠密的东西。1.3 回归位置图这个姿势妙在哪PRN核心创新在于定义一个叫Position Map的二维表示。简单说把人脸Mesh的每个顶点映射到UV坐标系的二维平面上每个UV像素存储该顶点在三维空间中的XYZ坐标。这样一来原本需要解非线性优化问题才能得到的稠密三维信息被转换成了一个标准的图像到图像的回归问题。姿势一换整个流程就走进了卷积网络最舒服的领域。CNN天然擅长从2D图像回归到2D结构而且2D卷积的局部性和参数共享对空间相关性的建模能力远超全连接层。更重要的是因为Position Map的每个像素位置是固定的UV坐标所以它天然携带了顶点之间的拓扑连接关系输出一个Position Map就等于同时输出了一个稠密Mesh和一组稠密对应关系。我第一次看到这个设计的时候心里想的是原来可以这样偷懒。但后来仔细想这其实是非常聪明的表示设计它把一个三维重建问题巧妙地变成了一个数据充分、监督明确、网络容易学的二维回归任务。2. UV位置图详解三维人脸是怎么被摊平成一张图的2.1 UV空间说白了就是给模型贴图用的坐标系很多做图像的人听到UV坐标会有点懵但做过三维建模的人都知道UV展开就是把三维物体的表面摊平成一张二维贴图。地球仪展开成世界地图就是这个道理每个三维点都能找到它在二维地图上的唯一位置反过来地图上每个像素也能对应回三维表面上的一个点。PRN里的人脸模型有一个固定的UV模板模型的几千上万个顶点每个点都有一个固定的UV坐标。这个UV坐标不随人脸变化不管这个人胖了瘦了、笑了哭了额头中心在UV图上的位置永远不变。所以UV图本质上是一张带编码规则的脸的地图它记录的不是颜色而是位置。2.2 Position Map里存的是XYZ不是RGB这是理解PRN最关键的一点。普通贴图里UV图存的是颜色信息红绿蓝三个通道是反射率。而PRN的Position Map每个像素的三个通道直接存储该位置对应三维顶点的X、Y、Z坐标。训练的时候我们用已知的3D模型渲染出标准的Position Map当作监督信号推理的时候网络输出一张Position Map你把它读出来就得到了一堆三维顶点坐标。这里有个容易踩坑的细节坐标值范围很大一个顶点的X坐标可能在正负100毫米这个量级不能直接塞进网络输出层。PRN的处理是把坐标值归一化到0到1之间也就是用255来除一下坐标范围让输出通道落在适合网络学习的数值区间。推理结束后再乘回去还原成毫米量级的真实坐标。import numpy as np def decode_position_map(pos_map): # pos_map: HxWx3网络输出数值范围约 0~1 vertices pos_map * 255.0 return vertices # 还原为原始三维坐标范围2.3 相比直接回归3D点云Position Map最大的优势是拓扑不乱也有人问过为什么不直接让网络回归一个N乘3的顶点列表理论上可以但实际上有两个致命问题。第一顶点数量太多如果网络最后一层直接输出几万个坐标全连接层的参数量大到根本训不动第二更关键的是如果回归的是无序点云你怎么保证网络输出的第100个点永远对应鼻尖、第200个点永远对应颧骨一旦顶点顺序错乱整个重建也就废了。Position Map用固定UV坐标把每个三维点钉死在二维网格上从表示层面杜绝了这个混乱问题。网络学到了在哪个UV位置上该出现哪个三维点换句话说它学到的不仅是三维形状还学到了一个隐式的顶点索引和拓扑结构。这个特性在后续做稠密对齐、贴图映射、网格变形时都极其有价值是我认为PRN最值得借鉴的地方。3. 网络结构、训练数据与Loss设计这是一套系统工程3.1 ResNet编码器加解码器对称而且简单PRN的网络结构放在现在看并不复杂但非常务实。输入是一张256乘256的RGB人脸图像主干特征提取用的是ResNet-152去掉了最后的全连接层相当于一个强大的编码器通过一系列残差块和上采样操作把特征图逐步恢复到256乘256分辨率输出3通道的Position Map。整体上这就是一个标准的编码器-解码器结构中间没有花哨的注意力机制、没有图卷积就是朴素的卷积和残差连接。但效果就是稳。我跑下来觉得它稳的关键在于任务本身被Position Map定义得足够干净网络有多少能力都能全部用来学图像到坐标的映射不需要多余的建模能力去和耦合的参数纠缠。3.2 训练数据靠3DMM造真实图像用了拟合结果做微调稍微懂点深度学习的人都会问稠密的三维坐标监督信号从哪里来总不可能人工标注人脸的三维CT吧PRN的做法是用3DMM随机采样大量形状参数、表情参数再随机设置姿态和光照用渲染管线自动生成二维图像 对应Position Map的数据对。这个造数据的过程我特别喜欢因为它干净且成本低。传统人工标注只能标稀疏关键点3DMM合成数据却能轻松产生像素级稠密监督。当然合成数据也有问题——渲染出来的人脸太CG感缺少真实纹理和光照复杂度所以PRN还做了一步用当时已有人脸对齐方法拟合一批真实照片把拟合出的Mesh当作弱监督真值用真实照片微调网络。这样既保证了数据多样性又让模型见识了真实世界的各种干扰。3.3 加权L1 Loss、可见性Mask看似细节实则是成败关键Loss设计是PRN里容易被忽略但影响很大的部分。表面看就是一个L1回归Loss实际上里面加了三层心思。第一不同区域的权重不一样。眼睛、嘴巴、鼻子这些五官区域形状复杂而且对下游应用价值高权重给得大脸颊、额头这些相对平坦区域权重给得小。这样网络会把有限的学习能力优先用在最关键的部位。第二UV图并不完全对应可见区域比如侧脸时候鼻子会挡住一部分脸被遮挡区域在输入图像里根本没有有效信息。PRN的做法是给这些不可见区域增加权重抑制不让网络去瞎猜被遮挡区域减少无关干扰。第三Loss用L1而不是L2因为L1对大误差的惩罚相对温和对离群点没那么敏感训练早期不容易被少数难样本带偏。这些设计单拎出来都不难理解但能把每一项都想清楚并且落实在训练里才是这篇文章工程功底扎实的体现。后来我调自己的类似任务时深有体会很多模型训崩不是网络结构的问题而是Loss没有把该学什么、不该学什么表达清楚。表格对比一下设计选择设计点选择出发点输出表示256x256x3 Position Map稠密且拓扑稳定主干网络ResNet-152深度足够提取全局特征训练数据3DMM合成 真实图拟合兼顾多样性和真实性Loss类型区域加权L1关键五官优先抑制离群点遮挡处理可见性Mask避免对无信息区域做无意义回归4. 推理阶段从一张位置图解码出完整三维人脸4.1 一张图从输入到输出的完整流程PRN的推理流程非常短这也是它能做到实时的重要原因。前置是人脸检测加对齐裁剪把检测到的人脸区域裁剪成256乘256的正方形喂给网络。网络前向一次得到一张256乘256乘3的Position Map。接下来就是纯后处理把Position Map按之前归一化的逆操作还原成三维坐标然后按固定拓扑连接成Mesh。整个网络推理耗时官方在CPU上能做到每帧约10毫秒的量级GPU更不用说了。相比传统3DMM拟合一次要跑好几百次迭代优化这个速度完全是代际差异。我实际用PyTorch复现的时候在普通显卡上单张图前向加后处理也就几毫秒这在工程上意味着你可以把它放在实时视频流里做人脸跟踪放在移动端做滤镜甚至虚拟试戴。4.2 稠密对齐是怎么和三维重建同时完成的Joint这个关键词是PRN题目的核心。一次前向为什么能同时完成三维重建和稠密对齐逻辑很简单Position Map的每个像素位置是固定的UV坐标它既是三维顶点的索引也是和二维图像像素的对应关系。网络输出了一张Position Map你取某个UV位置的数值就拿到了对应三维顶点的坐标同时因为这个UV位置和输入图像的某个像素位置是对应的你就知道了这个三维点对应图像上哪个像素。这就是稠密对齐的本质三维点与二维像素的逐点对应。相比稀疏关键点需要在Mesh上反复找最近点PRN在表示层面就把对应关系直接输出了连后处理查表都省了。我在做换脸渲染的时候深有体会先前稀疏对齐做完还得在网格上插值对应关系PRN这种方案拿来就能用而且因为是逐顶点对应换脸贴图的细节保持得特别完整嘴角眼角这种微妙区域都不容易穿帮。4.3 我自己梳理的最小可跑推理链路如果你想快速验证PRN的效果核心链路其实很短。我整理了一份伪代码方便理解整个流程用现成的人脸检测器获取边界框按边界框裁剪区域缩放到256乘256做归一化网络前向输出256乘256乘3的Position Map反归一化得到三维坐标组织成顶点数组按预定义的三角形索引生成Mesh渲染出来真正会影响效果好坏的是第2步。裁剪时如果人脸没对齐居中或者边界框留白过多网络输出的Position Map坐标质量会明显下降。我的经验是先做一次粗的人脸关键点检测用两眼连线把人脸旋转到水平然后再裁切出来的重建稳定性会好很多。这种预处理细节论文里不会细说但属于实操中人人都要趟一遍的环节。5. 工程落地实录复现PRN最容易踩的坑和我的解决办法5.1 跑官方源码之前先确认环境依赖的兼容性PRN官方实现基于Caffe框架这个框架本身已经在很多新环境上很难编译了尤其是换到新版本Python或者新版本依赖库之后各种链接错误和版本冲突能把人磨到没脾气。我当时的做法是放弃直接跑老环境直接在PyTorch里按结构重写了一遍效果完全可以复现训练和推理反而更方便。如果你也需要复现我的建议是除非你有特殊理由非要跑老代码否则直接用当前主流的深度学习框架实现。PRN的核心结构并不复杂编码器换成ResNet-152解码器按原论文的残差块和上采样层垒上去Loss按加权L1实现训练数据用3DMM渲染生成基本就能获得接近论文报告的效果。省下来的时间足够你把后处理、评估、可视化全都过一遍。5.2 预处理决定成败裁剪对齐比网络结构更影响结果复现过程中我做过一个对照实验同一张图分别做两种预处理一种是直接按检测框裁剪缩放另一种是先检测关键点做刚性对齐再裁剪缩放。结果后者的稠密重建效果明显更好特别是侧脸和低头角度重建出来的脸不会出现五官扭曲、左右脸不对称这类问题。原因是网络训练时输入的图像基本都是正脸居中的标准裁剪如果推理时输入的图像在几何上偏离了这个分布网络就会倾向于输出一个标准脸而不是这张人脸的形状。所以复现阶段别急着调网络先把预处理流程拉齐。另外输入像素的归一化方式也要和训练保持一致有的实现是减均值除方差有的是映射到0到1混用会让输出坐标整体偏移。5.3 光照、肤色、遮挡的实测观察我在测试集上跑了很多自然场景照片总结下来对于正常光照下的正脸PRN重建的轮廓和五官位置相当准稠密对齐结果可以直接用于贴图和换脸对偏暗光、强逆光场景整体形状会往均值脸方向收缩五官立体感变弱戴眼镜、口罩这类遮挡遮挡区域的Position Map预测会明显退化但没遮住的区域受影响不大。肤色方面我的观察是PRN并没有明显偏科毕竟训练数据里3DMM覆盖了不同肤色的纹理。真正影响大的是姿态极端大角度比如侧面超过90度时自遮挡面积过高可见性Mask能帮到的有限重建会明显失真。这是PRN这类单眼重建方案的原理性局限不要指望靠调参能解决。我当时还专门做了一组针对真实场景的增强策略训练阶段增加随机亮度扰动、随机模糊、随机遮挡块比如在眼睛或嘴巴区域盖一个黑块配合原有可见性Mask能有效提升遮挡场景下的重建质量。小改动收益明显算是亲测有效的技巧。5.4 移动端和实时部署的量化问题PRN模型大小在百兆级别直接部署到手机端偏重。我自己试过做8位量化直接用现成工具量化后Position Map的输出精度损失明显重建的人脸出现可见的接缝和抖动。原因是Position Map的数值本身就是连续坐标量化误差会直接放大成三维坐标误差这跟分类网络对量化不敏感很不一样。我的建议是如果非要在移动端做先做模型剪枝或者知识蒸馏把模型瘦身再考虑量化最后加一个轻量级的关键点稳定后处理。另外实时视频流里可以考虑用光流或上一帧的网格做时序平滑这样视觉上的抖动会小很多这个属于通用后处理方案PRN本身没有涉及。6. PRN的局限和它给后来工作留下的位置图遗产6.1 不能回避的短板无纹理区、极端表情、大姿态PRN在2018年算惊艳拿到今天看短板也一目了然。最大问题是几何精度有限UV位置图分辨率只有256乘256相当于你的人脸Mesh顶点数受这个分辨率限制细节上没办法表达皱纹、毛孔这种高频几何信息。你要做精细的影视级重建PRN这类方法只能打底细节还得靠别的方案补。第二个短板是极端表情跟踪困难。训练数据里3DMM的表情空间虽然是连续的但夸张表情比如大喊、做鬼脸在训练分布里占比少网络往往把这类输入拉回均值表情。我实测张嘴大笑时重建出的嘴部形状会明显收敛不像原图那么夸张。第三个短板是忽略了光照、反照率和纹理的分离。PRN重建出的Mesh是裸几何没有材质属性也没办法做重光照。所以它适合AR、换脸、人脸识别等不需要照片级真实渲染的场景但如果你想把一张人脸放进一个新光照环境里PRN做不到还得借助光照估计和纹理生成模型。6.2 回归位置图这个思路其实影响了很多后续方向虽然PRN本身有局限但它选择的位置图回归这个中间表示影响深远。后续的人脸重演、口型同步、表情迁移等方向很多都借鉴了这个思想先用某种稠密2D表示编码空间信息再让网络直接回归这个表示绕开复杂度高的参数优化。后来的人脸重建工作也延续了PRN的轨迹有的做轻量化把主干结构换成MobileNet系列让模型可以实时跑在移动端有的引入GAN分支生成纹理补上PRN没有的纹理能力有的把几何细节分离成基础形状加细节残差弥补PRN高频信息不足的问题。但底层那个用固定UV映射存储XYZ的表示还是在很多系统里不断出现。我后来做数字人驱动、面部动作捕捉优化的时候也经常回头翻PRN的代码参考它的数据构造和Loss设计很多套路依然可以直接套用。最后的实际操作体会如果这篇文章只能记住一句话我想说PRN最大的价值不是那个具体网络而是把在表示层面解决对应关系混乱问题这个思路演示了一遍而且演示得很彻底。做三维视觉的人都知道方向性的创新比刷点重要得多。我在后续项目里尝试过不少类似问题——比如人体姿态重建、手部重建都试着找一个类似Position Map的稠密二维表示效果确实比直接回归三维坐标稳定得多。另外还有一点个人分享复现PRN时不要把太多精力放在调网络结构上真正花时间的应该是预处理一致性和训练数据分布。我一开始复现效果偏模糊一直以为是解码器不够深后来查了代码发现是数据归一化跟原版实现不一致改完之后效果立刻上一个台阶。这类表示型方法数据和预处理的影响往往被低估建议每一个做类似工作的人先把数据管线检查三遍再动结构。