120 个顶点的点云,用 2 个形变基反推 2 个系数。 5 个关键点时重建误差 0.0139,10 个点掉到 0.0054,120 个点只剩 0.0022。 前十几个点最值钱,再往上加收益递减。
上一节算完了标注的账。从这一节起是五个动手实例,都用 VGG 的标注工具。 沙箱进不去浏览器,所以每个实例分两半:界面上的清单,和能用代码跑通的那一半。
一、人脸怎么变成一堆点
三维人脸重建的通行做法不是「拍出一张三维图」,是拟合一个参数化模型。 模型里存着一张「平均脸」和若干「形变方向」。给每个方向一个系数,加起来就得到一张具体的脸。 Demo 用了 2 个方向:一个负责左右宽度,一个负责上下高度。 真实模型(3DMM 那一类)有几十到两百个方向。 要反推的就是这几个系数。已知的是照片上标出来的关键点坐标——这就是标注在这里的作用。 所以「标关键点」不是给人看的,是给这套反解提供方程的。
二、为什么点越多越准
两个未知数,理论上两个方程就够。一个关键点给两个坐标,所以标一个点就够解了。 但人工标点有误差。Demo 给每个点加了 σ=0.02 的偏差,模拟手抖。 点少的时候,这点偏差被放大成很大的系数偏差;点多了,最小二乘会把误差平均掉。 实测:5 个点误差 0.0139,10 个点 0.0054,40 个点 0.0037,120 个点 0.0022。 掉得最快的是从 5 到 10 这一段。 这个结论对标注有直接意义:标 68 个点比标 20 个点贵三倍多,但精度只提升一倍。 所以低精度场景(比如做卡通头像)标 20 个点就够。
三、界面上的清单(我没有跑)
书里用的是 VGG Image Annotator,一个纯浏览器的标注页。 沙箱里没有浏览器,这一步我给不了截图,也不假装跑过。 操作顺序大致是这样: 1. 打开 VIA 页面,把要标的人脸图片拖进去。 2. 左侧新建一个「点」类型的属性,命名成 68pt 之类。 3. 逐张点出关键点,按固定顺序点(下巴从左到右、眉毛、鼻子、眼睛、嘴)。 4. 全部点完,导出成 JSON 或 CSV。 5. 把导出的坐标喂给重建脚本,解出系数。 第 3 步的顺序是关键:顺序乱了,第 5 步的方程就对不上平均脸的点序,解出来是一团乱麻。 这件事在真实项目里翻车过很多次。
四、沙箱里能跑的那一半
Demo 不做标注界面,直接造一个「已经标好的」点集——用椭球面采样 120 个顶点当人脸点云,投影到二维, 再按比例抽点当关键点。 然后做最小二乘反解,算重建误差。这一步是真跑的,数字是算出来的。 所以这个实例能给的是:反解这一步的完整流程和误差量级。给不了的是:真实的 VIA 界面操作。 要真做,得在你自己的电脑上开浏览器。这部分我给不了,就不编。
五、动手 Demo:关键点重建三维形状:点越少,形变越猜不准
在椭球面上采样 120 个顶点当人脸点云,加两个形变基,给定真实系数造出目标形状; 正交投影到二维后按 5、10、20、40、120 个点抽样,每点加 σ=0.02 的标点误差; 用最小二乘反解两个系数,每种点数跑 30 次取均值,算平均重建误差。
A = np.vstack([np.column_stack([b1[idx, 0], b2[idx, 0]]),
np.column_stack([b1[idx, 1], b2[idx, 1]])])
vb = np.concatenate([pn[:, 0] - base[idx, 0],
pn[:, 1] - base[idx, 1]])
sol, *_ = np.linalg.lstsq(A, vb, rcond=None)
rec = base + sol[0] * b1 + sol[1] * b2 # 重建形状
实跑输出:
--------------------------------------------------------------------
关键点重建三维形状:点越少,形变越猜不准
--------------------------------------------------------------------
顶点数 120(椭球面采样,当人脸点云)
真实形变参数 a = 0.80,c = -0.50(这是要反推的)
关键点误差 σ = 0.02(模拟人工标点的偏差)
投影 正交投影到 xy 平面,不含旋转
关键点数 方程数 解出的 a 解出的 c 误差均值 误差最大
------------------------------------------------------------
5 10 0.826 -0.508 0.0139 0.0265
10 20 0.799 -0.498 0.0054 0.0112
20 40 0.804 -0.502 0.0054 0.0139
40 80 0.802 -0.501 0.0037 0.0094
120 240 0.797 -0.500 0.0022 0.0049
两个未知数,至少要两个方程。一个关键点给两个坐标,所以哪怕只标 5 个点,也是超定的——但标点带误差,点少的时候误差被放大,重建偏差就大。
每种点数我都跑了 30 次不同的标点误差再取均值,单跑一次会跳,第一版我只跑一次,20 个点那行反而比 10 个点差,那是误差没平均掉。
趋势很清楚:5 个点误差最大,10 个点掉一半多,之后越加越平,到 120 个点只剩千分之二。也就是说前十几个关键点最值钱,再往上加收益递减。
这就是三维人脸重建的骨架:给人一个「平均脸加若干形变方向」的模型,用照片上标出来的关键点反推形变系数。真实模型有上百个方向,这里只有 2 个,所以误差只说明趋势,不代表真实精度。
还有一点:这里用的是正交投影、没算旋转。真实重建要联立解旋转、缩放、平移,那就成了非凸问题,只能迭代求解。
解出来的 a、c 五档都稳定在 0.80 和 -0.50 附近,说明这个线性反解本身是准的。 差的是重建之后的整体形状——因为两个基撑不起真实形变。 误差列是单调下降的:0.0139 → 0.0054 → 0.0054 → 0.0037 → 0.0022。 10 个点和 20 个点并列,说明这个区间已经进了平台。 「误差最大」那一列比均值大一倍左右,说明单次跑跳动不小。 第一版我只跑一次,20 个点那行反而比 10 个点差,就是没平均掉。 这里的误差是相对单位(点云尺度是 1 左右),不是毫米。换成真实人脸,除以脸宽再乘毫米数才能对上。
30 秒实验
- 把标点误差从 0.02 改成 0.05,看五档误差整体抬多少。
- 把形变基从 2 个加到 4 个,看点数的收益曲线怎么变。
- 想一想:为什么标注关键点必须按固定顺序?
常见误区
- 「三维重建就是拍三维」——主流做法是用参数化模型加关键点反解。
- 「关键点越多越好」——过了一个点就是收益递减,标得越贵。
- 「点序无所谓」——顺序错了,方程和平均脸对不上,解出来全是乱的。
自测三问(点开对答案)
1. 三维人脸重建的基本思路是什么? —— 用「平均脸加若干形变方向」的参数化模型,靠关键点坐标反解形变系数。 2. 为什么两个未知数却要标几十个点? —— 标点有误差,点多才能用最小二乘把误差平均掉。 3. 关键点顺序为什么必须固定? —— 反解时每个点要和平均脸上对应的点配对,顺序乱了对不上。人脸重建讲完,下一个实例换到人体:把骨架坐标拿出来做姿态检索。
- ☐ (本节完,下一节 7.2.2 实例2:检索人体姿态)