34 光度立体
前面几章的三维成像,无论是激光三角(章节 30)还是相位偏折(章节 35),测的都是表面的宏观高度——零件凸起多少、凹陷多深。但工业检测常常关心的是另一回事:表面每一点究竟朝向哪里。一道压印字符的清晰度、一块磨砂面的纹理走向、一处浅缺陷的坡度,本质上都是表面法向(surface normal)的问题,而法向对绝对高度并不敏感。光度立体(photometric stereo)正是为此而生:相机固定不动,只切换几个已知方向的光源,逐张采集图像;同一个点在不同光照下明暗各异,这种明暗变化里恰好编码了它的朝向。把多张图联立求解,就能在每个像素上同时解出它的法向与反照率(albedo)。
这一手段还有一个别处难以替代的独门绝技:它能把”形状缺陷”与”外观缺陷”干净地分到两张图上。一道压痕改变的是法向,一块污渍改变的是反照率——单光照的灰度图把两者混在一起,而光度立体天然地把它们解耦。本章用一块合成浮雕板贯穿全部实验:平整基面上有一个凸起的十字 logo、一处 0.3 mm 的浅凹陷缺陷、还有一块只改反照率、不改高度的污渍——这正是检测中”该被分开的一对”。图 34.1 是四个光照方向(天顶角 45°、方位 0°/90°/180°/270°)下采集的输入。
34.1 朗伯模型与法向求解
光度立体建立在朗伯反射(Lambertian reflectance)模型上(呼应 章节 4 的漫反射):一个理想漫反射点的亮度只取决于它的法向 \(\mathbf n\) 与入射光方向 \(\mathbf l\) 的夹角余弦,与观察方向无关:
\[ I = \rho \,(\mathbf n \cdot \mathbf l), \qquad \mathbf n \cdot \mathbf l > 0, \]
其中 \(\rho\) 是该点的反照率(吸收多少、反射多少),\(\mathbf l\) 是单位光照向量。注意未知量有两部分:法向 \(\mathbf n\)(两个自由度,因为 \(\|\mathbf n\|=1\))和标量 \(\rho\),合起来三个未知数。一张图只给一个方程,远远不够;但若用 \(K\) 个已知方向的光源各拍一张,就得到 \(K\) 个方程。
求解的关键技巧是把 \(\rho\) 和 \(\mathbf n\) 合并成一个向量 \(\mathbf g = \rho\,\mathbf n\),于是方程对未知量变成线性的:第 \(k\) 个光源给出 \(I_k = \mathbf g \cdot \mathbf l_k\)。把 \(K\) 个方程叠成矩阵 \(\mathbf I = L\,\mathbf g\)(\(L\) 的每行是一个光向量),当 \(K=3\) 且三个光方向线性无关时 \(\mathbf g\) 唯一确定;当 \(K\ge 4\) 时系统超定,用最小二乘解正规方程(呼应 章节 2):
\[ \mathbf g = (L^\top L)^{-1} L^\top \mathbf I. \]
解出 \(\mathbf g\) 后,分解极其干净——模长就是反照率,方向就是法向:
\[ \rho = \|\mathbf g\|, \qquad \mathbf n = \mathbf g / \|\mathbf g\|. \]
为什么 4 光源好过 3 光源?3 光源恰好定解,任何噪声、任何一处轻微的非朗伯都会无衰减地进入结果;4 光源把系统变成超定,最小二乘在四个测量间做平均,噪声被部分抵消。本章实验用 4 光源,全图平均法向误差约 1.9°,已逼近 \(\sigma=3\) 的噪声底。多一个光源,就多一份冗余。
本章逐像素地解这个 \(4\times 3\) 的小系统。把法向按 \(r=(n_x{+}1)/2\)、\(g=(n_y{+}1)/2\)、\(b=n_z\) 编码成彩色,得到 图 34.2:平面区域因 \(\mathbf n\approx(0,0,1)\) 呈蓝色,logo 的四条侧壁朝向不同方向因而染上红绿色调;反照率图 图 34.3 则把朝向信息整个抽掉,只剩下”每点反射多强”——污渍盘在这里是一个清晰的暗斑,而凹陷缺陷几乎不可见。这两张图来自同一组数据,却分别只携带”形状”和”外观”,这正是下一节的主角。
34.2 分离形状与外观
这是光度立体的杀手级实验。在浮雕板上人为放置了两类截然不同的缺陷:一处 0.3 mm 的高斯凹陷(纯形状变化,反照率不变),一块污渍(纯反照率变化,高度不变)。我们在三个位置各取一个小圆盘统计:干净平底作为基线、凹陷处、污渍处。结果如下表,也对应 图 34.4 左右两个通道。
| 位置 | 法向偏离竖直 | 反照率 |
|---|---|---|
| 平底基线 | 0.99° | 0.901 |
| 凹陷(0.3 mm) | 8.26°(基线的 8.3 倍) | 0.900(−0.1%,不变) |
| 污渍 | 1.92°(近噪声底) | 0.500(基线的 55%) |
数字讲得清清楚楚:凹陷只在法向通道显形——8.26° 相对 0.99° 的基线是 8 倍以上的跃升,而它的反照率纹丝不动;污渍只在反照率通道显形——反照率掉到基线的 55%,而它的法向偏离仅 1.92°,几乎埋在噪声底里。形状缺陷与外观缺陷被光度立体送进了两条互不串扰的通道。
为什么 2D 灰度做不到这件事?单张灰度图里,一个点暗下去既可能是因为它的反照率低(污渍),也可能是因为它的法向偏离了光照(凹陷坡面)——两种原因在一个标量里无法分辨。光度立体用多个光照方向把这个标量”撑开”成一个向量方程,模长与方向就此分家。这正是单光照检测反复栽在”污渍误判为压痕”上的根因。
这件事直接关系到缺陷检测(章节 26)的判据设计:形状型缺陷(压痕、划伤、塌边)应当在法向或由法向导出的曲率上设阈值,外观型缺陷(污渍、氧化、印刷脏点)则应当在反照率上设阈值。两类缺陷需要不同的判据,而光度立体恰好提供了把它们分开评判的两张原始图。
34.3 高度积分
有了法向场,自然想把它积分回高度。法向 \(\mathbf n=(n_x,n_y,n_z)\) 对应的表面斜率为 \(p=h_x=-n_x/n_z\)、\(q=h_y=-n_y/n_z\),再解泊松方程 \(h_{xx}+h_{yy}=p_x+q_y\) 即可恢复高度(这与 章节 11 中 Frankot–Chellappa 的频域积分同源,本章用 SOR 迭代在空间域求解)。积分得到的高度图见 图 34.5,实验结果耐人寻味:
- 缓变特征恢复得好。0.3 mm 的高斯凹陷积分出 −0.317 mm,与真值 −0.300 mm 相差不到 6%——平滑的浅缺陷正是积分的强项。
- 陡壁严重低估。logo 凸起真值 2.00 mm,积分只给出 1.16 mm。原因诚实而深刻:logo 侧壁约 76° 的陡坡,在 45° 天顶角的光下会发生自遮挡,壁面要么背光要么被自身阴影盖住,于是它的法向被求解器”压平”,台阶高度随之衰减。这不是 bug,而是陡几何下光度立体的真实教学点——它擅长微观坡度,不擅长还原近乎垂直的台阶。
- 低频漂移。四角本应同高的平底,积分后高度跨度有 0.094 mm。这是泊松积分在诺伊曼(Neumann)边界下固有的低频漂移:积分只约束斜率(高度的导数),全局的缓慢倾斜在导数上几乎不留痕迹,因而无法被钉死。
一句话记住光度立体的高度能力边界:它擅长微观形貌、不擅长绝对高度。坡度信息是它的一手数据,积分只是事后重建;越是高频、缓变的形貌(划痕深浅、橘皮纹理)越准,越是大尺度的绝对台阶高、整体翘曲越不可靠。要绝对高度,请回到激光三角或相位偏折。
34.4 朗伯违背与稳健性
朗伯模型是整套求解的地基,而现实表面常常不朗伯:金属、釉面、油膜会产生镜面高光(specular highlight),凸起会投下阴影。高光是一个远超漫反射的亮斑,阴影则是一个本该有亮度却为零的暗区——两者都不满足 \(I=\rho(\mathbf n\cdot\mathbf l)\),一旦混进最小二乘,就会把那个像素的解整个带偏。
我们在浮雕板上再加一个镜面高光区,并补拍第 5 张含高光的图,对比三种解法在高光区的法向误差:
| 配置 | 高光区法向误差 |
|---|---|
| 4 光源(无高光) | 1.15° |
| 5 光源(含高光,朴素最小二乘) | 11.81° |
| 5 光源(含高光,稳健剔除) | 1.15° |
朴素最小二乘把那个高光测量当作可信数据,结果误差从 1° 爆炸到近 12°。稳健(robust)光度立体的做法极其简单却有效:先用全部测量解一次,找出残差最大的那一项(多半就是高光或阴影),剔除它,用剩下的测量重解。如此一来误差回落到 1.15°——与完全没有高光时持平。图 34.6 中部(朴素最小二乘)与右侧(稳健剔除)的误差图直观对比了两者在高光区的天壤之别。
稳健光度立体的配方可以浓缩成一句:多光源 + 离群剔除。这与 章节 14 中 Huber 稳健直线拟合是同源思想——都是承认”少数测量是离群点”,用剔除或降权把它们排除在估计之外。区别只在于那里的离群点来自缺陷边缘,这里的离群点来自高光与阴影。光源越多,可供剔除的冗余越大,稳健性越强。
34.5 SciVision 实现
如实说明:SciVision 的光度立体接口 SciSvPhotometricStereo::CalibratedPhotometricStereo 在本机静默无输出——它不崩溃、返回码 rc=0,但 Nx/Ny/Nz/albedo 输出全部是 0×0 的空图,并向 stderr 打印一句 “Directory does not exist.”(与 章节 35 中 PhaseMeasure 的失败签名如出一辙,疑似缺运行时资源目录)。因此本章用子进程探针如实记录这一缺陷,求解全部改用手写逐像素最小二乘——这恰好就是 小节 34.1 的数学本体。核心片段如下。
// 逐像素解 g = albedo·n:min_g Σ_k (I_k − g·l_k)²
// 正规方程 (Σ l_k l_kᵀ) g = Σ I_k l_k
double M[9] = {0}, b[3] = {0};
for (int k = 0; k < K; ++k) { // K 个光源
double l[3] = { Lx[k], Ly[k], Lz[k] }; // 已知光方向
for (int r = 0; r < 3; ++r) {
b[r] += I[k] * l[r]; // Σ I_k l_k
for (int c = 0; c < 3; ++c)
M[r*3 + c] += l[r] * l[c]; // Σ l_k l_kᵀ
}
}
solve3(M, b, g); // 3×3 解出 g
// 稳健:剔除残差最大的一项后用其余 ≥3 项重解(去高光/阴影)
if (robust && K >= 4) {
int worst = argmax_k |I[k] − g·l_k|;
重新累加 M,b(跳过 worst)后 solve3 -> g;
}
double albedo = norm(g); // 模长 = 反照率
n = g / albedo; // 方向 = 法向(取 n_z≥0 朝向相机)高度积分用泊松方程的逐点 SOR 迭代实现:由法向算斜率 \(p,q\),对其求散度得到右端项 \(f=p_x+q_y\),再以松弛因子 \(\omega=1.9\) 迭代求解 \(h_{xx}+h_{yy}=f\)(诺伊曼边界)。完整可运行工程见 code/photometric_stereo/。
工业案例:硬币/铭牌的字符压痕与污渍
某金属铭牌质检线要同时判两件事:压印字符是否清晰完整(形状型),以及表面是否被油污、氧化斑沾染(外观型)。早期用单光照灰度方案,压痕底部的阴影与一块深色油污在图像里都是”暗区”,二者难以区分——调高阈值漏掉浅压痕,调低又把油污误判成字符缺陷,误检率居高不下。改用 4 光源光度立体后,问题迎刃而解:法向图只反映字符的压印深度与边缘陡度,反照率图只反映油污与氧化的反射率变化。两张图各设各的判据,互不干扰,误检率大幅下降。教训很明确:当”缺陷”同时包含形状型与外观型时,光度立体把二者解耦到两个通道的能力无可替代,这是任何单光照方案都给不出的。
34.6 小结
- 光度立体测的是法向,不是高度。相机不动、切换已知方向的光源,朗伯模型 \(I=\rho(\mathbf n\cdot\mathbf l)\) 把明暗变化解成每点的朝向;3 光源定解、4+ 光源最小二乘,\(\mathbf g=\rho\mathbf n\) 的模长是反照率、方向是法向。
- 它能分开形状缺陷与外观缺陷。本章实验中凹陷在法向通道跳到基线的 8 倍而反照率不变,污渍把反照率压到 55% 而法向不动——这是单光照灰度做不到的解耦,直接决定缺陷判据应建在哪个通道上。
- 法向可积分回高度,但有边界。缓变浅缺陷恢复准(凹陷 −0.317 vs −0.300 mm),陡壁因自遮挡被低估(logo 1.16 vs 2.00 mm),低频漂移是诺伊曼边界的固有产物——擅长微观形貌、不擅长绝对高度。
- 稳健性 = 多光源 + 离群剔除。高光与阴影违背朗伯假设,朴素最小二乘误差爆炸(11.81°),剔除残差最大的测量后回落到 1.15°,与 Huber 稳健拟合同源。
- SDK 的光度立体接口本机不可用(静默空输出),手写逐像素最小二乘既是回退也是本章数学的本体。
光度立体的奠基性工作是 Woodham 提出的多光照确定表面朝向的方法 (Woodham 1980);针对高光与阴影破坏朗伯假设的问题,Wu 等人将其建模为低秩矩阵补全与恢复,给出了稳健光度立体的凸优化解法 (Wu 等 2011),与本章离群剔除的思路一脉相承。光度立体、形状重建与稳健估计在工业视觉中的系统论述另可参阅 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)。





