32  结构光三维成像

双目立体视觉(章节 31)的命门在于”找对应”:左右两幅图必须能在同名点上匹配,而匹配靠的是表面纹理。可工业现场偏偏有大量缺纹理的对象——抛光的金属面、注塑件的均匀漆面、白色陶瓷,在它们身上每个像素长得都一样,匹配算法无从下手,视差图上是一大片空洞。既然被测物自己不长纹理,那就主动投上去。结构光(structured light)的核心思想正是如此:用投影仪向物体表面打出一组已知的、可控的明暗图案,让它充当”人造纹理”。当图案落在起伏的表面上,会随高度发生形变,相机拍下这形变,反解出每一点的高度。在所有可编码的图案里,正弦条纹 + 相移是工业三维测量的主力——它把高度信息编码进条纹的相位,而相位可以做到亚像素、逐像素的稠密重建。图 32.1 是本章贯穿始终的一组四步相移条纹。

图 32.1: 四步相移正弦条纹(周期 \(P=32\) px),相邻两幅相移 \(\pi/2\)。场景为平坦底面上叠加一个抛物球冠(中部)与一个矩形台阶(右侧);仔细看条纹在球冠和台阶处发生了横向弯折与平移——这就是高度调制的直接证据。

为什么用正弦条纹而不是二进制格雷码?格雷码每个像素只编码”黑/白”,分辨率受条纹最细线宽限制;正弦相位是连续量,配合相移可以解算出像素内部的小数相位,精度高出一两个数量级。代价是相位有 \(2\pi\) 周期模糊,必须解包裹——这正是本章 小节 32.2 要解决的问题。

32.1 相移法原理

设投影仪打出的是空间正弦条纹,相机在像素 \((x,y)\) 处采到的灰度可写成

\[ I_k(x,y) = A(x,y) + B(x,y)\cos\!\big(\varphi(x,y) + k\,\tfrac{\pi}{2}\big), \qquad k=0,1,2,3. \]

这里 \(A\) 是背景光(直流分量,DC component),\(B\) 是条纹调制度(modulation)即正弦的幅值,\(\varphi\) 是我们真正想要的相位(phase)——它编码了该点的高度。我们连续投四幅条纹,每幅把初相整体平移 \(\pi/2\)(这就是”四步相移,four-step phase shifting”),于是同一像素拿到四个方程,未知数恰好是 \(A\)\(B\)\(\varphi\) 三个。把四式两两相减:

\[ I_3 - I_1 = 2B\sin\varphi, \qquad I_0 - I_2 = 2B\cos\varphi. \]

相除取反正切,\(A\)\(B\) 同时被消掉,只剩相位:

\[ \varphi(x,y) = \operatorname{atan2}\big(I_3 - I_1,\; I_0 - I_2\big). \]

这条公式是相移法的全部精华。值得反复体味的是它的鲁棒性来源\(\varphi\) 只取决于四个强度的差与比,与背景光 \(A\)、与条纹亮度 \(B\) 都无关。换句话说,相机增益漂移、环境光起伏、被测面反照率高低,都不会改变解出的相位——它们只改变 \(A\)\(B\),而这两者已被消去。这与 章节 17 中”梯度方向不随光照增益改变”是同源的工程智慧:把信息编码进对增益不变的量里,测量才稳。强度本身是脆弱的,相位才可靠。

\(\operatorname{atan2}\) 的值域是 \([-\pi,\pi)\),所以解出的相位是包裹相位(wrapped phase)——真实相位每超过一个 \(2\pi\),就被折回到这个区间,形成锯齿。图 32.2 展示了这一点:连续起伏的高度场,在包裹相位图上变成一条条等相位带,带与带之间是从 \(+\pi\) 陡降到 \(-\pi\) 的”断崖”。这些断崖不是真实的不连续,而是 \(\operatorname{atan2}\) 周期性的人为产物,下一节要把它们抹平。

图 32.2: 包裹相位 \(\varphi=\operatorname{atan2}(I_3-I_1,\,I_0-I_2)\),灰度映射 \([-\pi,\pi)\)。条纹周期 32 px 在画面上形成约十条等相位带,每条带末端从白(\(+\pi\))骤跳到黑(\(-\pi\))即包裹断崖;球冠与台阶处条纹带的弯曲,就是高度信息。

32.2 相位解包裹

要把包裹相位 \(\varphi\in[-\pi,\pi)\) 还原成连续的绝对相位(absolute phase)\(\Phi = \varphi + 2\pi n\),必须逐像素定出整数条纹级数(fringe order)\(n\)。最朴素的做法是空间解包裹(spatial unwrapping):沿着图像扫描,相邻像素相位差若突然跳过 \(\pi\),就判定跨越了一条包裹断崖,给后续像素累加一个 \(2\pi\)。它对光滑、连通的表面有效,但脆弱得令人不安——一旦遇到噪声造成的伪跳变、或物体上真实的高度断层(如本章的台阶),积分就会从某点起整体错一个或多个 \(2\pi\),而且错误会沿扫描方向一路传播下去,无法自愈。

工业上更稳健的是双频/多频层级法(dual-/multi-frequency unwrapping)。它的思路是用两套不同周期的条纹:一套低频条纹周期足够大,整个视场内相位变化不足 \(2\pi\),因而天然无歧义,可以直接定出”我大概在第几条纹”;另一套高频条纹周期小、相位斜率陡,提供最终的测量精度。图 32.3 是本章用的低频组(\(P=128\) px,是高频 \(P=32\) 的四倍)。具体地,先对低频包裹相位做一次空间展开得到低频绝对相位 \(\Phi_{\text{lo}}\)(低频场光滑,台阶引起的相位跳变仅约 \(0.6\) rad,远小于 \(\pi\),空间展开不会出错),再用频率比 \(P_{\text{lo}}/P_{\text{hi}}=4\) 把它放大去预测高频绝对相位,逐像素取整定级数:

\[ \Phi_{\text{hi}} = \varphi_{\text{hi}} + 2\pi\,\operatorname{round}\!\Big(\frac{\Phi_{\text{lo}}\cdot(P_{\text{lo}}/P_{\text{hi}}) - \varphi_{\text{hi}}}{2\pi}\Big). \]

关键在于:级数 \(n\)逐像素独立算出来的,不靠邻域积分,因此一个像素的错误绝不会传染给邻居——台阶的真实断层被如实保留,而不是被”抹平”。这就是双频法压倒空间法的根本原因。图 32.4 是展开后的绝对相位,平滑单调,球冠的圆顶与台阶的方块清晰可辨。

图 32.3: 低频引导条纹(\(P=128\) px)。整个视场内相位变化不足一个 \(2\pi\),因而无周期模糊,可直接为高频条纹定级。
图 32.4: 双频层级解包裹后的绝对相位。包裹断崖被全部抹平,相位随高度连续单调上升,球冠(中部圆顶)与台阶(右侧方块)一目了然。

解包裹还有另一条技术路线:时间相位解包裹(temporal unwrapping)。它不在空间邻域里找断崖,而是对同一像素沿时间投多套频率递增的条纹,用前一频率的绝对相位为后一频率定级。双频法是它最精简的两级特例。时间法的优点是逐点独立、抗断层;缺点是投影帧数翻倍,不适合高速运动场景——速度与稳健的经典权衡。

32.3 高度重建与精度

绝对相位到高度的最后一跃,是一个三角标定关系。在本章的成像模型里,投影坐标 \(u = x + c\cdot h\),其中 \(h\) 是高度、\(c\) 是由投影仪—相机几何决定的三角测量常数(triangulation constant)(本例 \(c=4\) px/mm,即 1 mm 高度令条纹横移 4 px)。而绝对相位与投影坐标的关系是 \(\Phi = 2\pi u / P_{\text{hi}}\),联立反解:

\[ h(x,y) = \frac{1}{c}\Big(\frac{\Phi_{\text{hi}}\,P_{\text{hi}}}{2\pi} - x\Big). \]

实测精度令人满意。在 \(A=120\)\(B=80\)、噪声 \(\sigma=3\) 的条件下,球冠顶点重建为 5.994 mm,真值 5.994 mm台阶高 2.999 mm,真值 3.000 mm——双双命中。图 32.5 是最终高度图。平坦底面上的 RMS 噪声底为 0.0338 mm,这就是该配置下的高度复现精度。

图 32.5: 重建高度图,灰度映射 \([-0.5,\,6.5]\) mm。平坦底面均匀,球冠呈光滑圆顶,台阶为右侧亮方块——三维形貌被稠密、逐像素地还原出来。

这个噪声底从何而来,又由什么决定?答案是结构光的命脉——调制度 \(B\)。对四步相移做误差传播,相位的标准差满足

\[ \sigma_\varphi \approx \frac{\sigma}{B}, \]

即相位噪声反比于条纹幅值。把 \(B\) 从 80 压到 15(约 \(5.3\) 倍),理论上相位与高度噪声应放大同样倍数。实验给出:\(B=15\) 时底面 RMS 升到 0.1823 mm,正是 \(B=80\) 时 0.0338 mm 的 5.4 倍——与理论预测的 5.3 倍几乎严丝合缝。这条 \(\sigma_\varphi\propto\sigma/B\) 的规律,把一个抽象的图像质量指标(条纹清不清晰)直接翻译成了高度精度。结构光的精度,本质上由调制度决定。凡是压低 \(B\) 的因素——暗表面、低反差投影、离焦、运动模糊——都会成比例地恶化三维精度。

\(\sigma_\varphi\approx\sigma/B\) 也解释了为何工业结构光系统要尽量投高对比条纹:投影仪亮度、镜头光圈、相机曝光,最终都服务于一个目标——在不饱和的前提下把 \(B\) 顶到最大。但”不饱和”这个前提至关重要,下一节会看到,一旦越界,灾难以另一种形式降临。

32.4 调制度、饱和与表面

既然 \(B\) 决定精度,一个自然的工程动作就是用调制度掩膜(modulation mask)\(B\) 过低的像素剔除——四步相移可顺带算出 \(B=\tfrac12\sqrt{(I_3-I_1)^2+(I_0-I_2)^2}\),对它阈值化即可。在本章的”实拍”实验里,底面左下角嵌了一块反照率仅 0.18 的暗区,模拟黑漆或低反射表面。结果漂亮:以 \(B<20\) 为阈值,暗区像素 99.7% 被标记,而干净底面 零虚警——掩膜精准圈出了不可信区域。图 32.6 左为调制度图、右为二值掩膜。

图 32.6: 左:调制度图,亮度正比于 \(B\),暗区与饱和高光盘清晰可见;右:\(B<20\) 的二值掩膜,暗区被整片标记(白),而饱和的球冠高光盘几乎未被标记——掩膜对暗区灵敏,却对饱和盲视。

但调制度掩膜有一个致命盲点:它挡不住饱和。实验在球冠顶部叠了一块镜面高光(增益 1.7 倍后被 255 截断),模拟金属反光。直觉上饱和会破坏正弦、降低调制度,掩膜应该抓得住——可实测饱和区被标记的比例是 0%。原因在于:四步相移的四幅图被同等地截断到 255,差分 \(I_3-I_1\)\(I_0-I_2\) 在截断后仍保留相当的幅度,算出的 \(B\) 并未跌破阈值。于是掩膜以为这里”调制度良好、数据可信”,实则相位已被截断扭曲。后果是球冠顶点从 5.994 mm 偏到了 5.928 mm,饱和盘内 RMS 升到 0.1087 mm(相对干净区放大约 3.2 倍),而暗区 RMS 为 0.1911 mm。掩膜失效,唯有误差图能暴露真相——图 32.7 把重建误差画出来,误差恰好聚成两团:暗区和那块饱和高光盘,与掩膜漏掉的位置精确吻合。

图 32.7: 高度误差图(\(|h-h_{\text{GT}}|\),灰度 \([0,0.6]\) mm)。误差集中成两块:左下暗区与中部饱和高光盘。后者正是调制度掩膜没能标记的区域——掩膜挡不住饱和,误差图才能暴露它。

这把不同表面对结构光的”适应性”摆上了台面。暗/低反照率表面压低 \(B\)、放大噪声,可由掩膜剔除;饱和高光在掩膜眼皮底下悄悄注入系统性偏差,必须靠曝光控制(避免越界)从源头预防(呼应 章节 4 的光照设计);半透明与次表面散射会让条纹”晕开”、相位失真。还有一类表面结构光根本无能为力——镜面:光线被镜面反射走,相机接收不到落在表面的条纹,此时要换用偏折术(deflectometry,章节 35),它测的不是条纹位置而是表面法线对图案的偏折。没有一种三维方法通吃所有表面,结构光的边界,正是它的方法假设的边界(章节 30)。

32.5 SciVision 实现

本章原计划复用 SDK 的相移测量模块 SCIMV::SciSvPhaseMeasure,但实测其核心入口不可用,如实记录于此。DecodePatterns(四步相移解码)在我们尝试的全部 16 种调用变体下——8U 与 32F 输入、是否先 InitPMD、UNDEF 与全图矩形 ROI、是否补一组 Y 方向条纹——均返回 rc=0 却无任何输出:相位图、灰度图、调制度图全是 \(0\times0\) 空对象,DLL 还向 stderr 打一句 “Directory does not exist.”(疑似缺运行时资源目录)。依赖其输出的 CalUnPhase(空间解包裹)随之同样不可用。因此整条流水线全部手写。核心只有两段,却是本章算法的全部:

// 1. 四步相移包裹相位 + 调制度(A、B 在差分中被消去,只剩相位)
for (size_t i = 0; i < (size_t)W * H; ++i) {
    double s = (double)im[3][i] - im[1][i];   // =  2B*sin(phi)
    double c = (double)im[0][i] - im[2][i];   // =  2B*cos(phi)
    phi[i] = std::atan2(s, c);                // 包裹相位 [-pi, pi)
    mod[i] = 0.5 * std::sqrt(s * s + c * c);   // 调制度 ~ B
}

// 2. 双频层级解包裹:低频绝对相位 *4 预测高频级数,逐像素取整(不靠邻域积分)
const double ratio = P_LO / P_HI;             // 128/32 = 4
for (size_t i = 0; i < abs.size(); ++i)
    abs[i] = wHi[i] + TWO_PI * std::round((uLo[i] * ratio - wHi[i]) / TWO_PI);

第一段直接落实 小节 32.1 的反正切公式:im[k] 是第 \(k\) 幅相移图,sc 分别正比于 \(\sin\varphi\)\(\cos\varphi\)atan2 给出包裹相位,顺带由两者的模长得到调制度。第二段是 小节 32.2 的层级法:uLo 是空间展开过的低频绝对相位,乘频率比 4 后预测高频绝对相位,std::round 把每个像素独立归到最近的 \(2\pi\) 整数倍。完整可运行工程见 code/structured_light/,包括条纹合成、低调制重跑与实拍陷阱实验。

工业案例:锡膏检测的反光与黑油墨

PCB 焊膏印刷检测(SPI,Solder Paste Inspection)是结构光三维的经典战场——要逐点测出每块锡膏的体积,直接关系焊接良率。难点恰是本章的两难同框:刚印上的锡膏呈金属光泽、极易镜面反光而饱和;旁边的黑色阻焊油墨反照率极低、几乎吞光。若按锡膏调曝光,黑油墨区相位淹没在噪声里;若按黑油墨调曝光,锡膏高光饱和、注入系统性高度偏差——而饱和又骗过调制度掩膜,偏差悄无声息。

实战对策是HDR 多曝光融合条纹(呼应 章节 12):对每个相移相位投多档曝光,逐像素挑选未饱和且调制度最高的那一档合成,让锡膏与黑油墨各取所需。教训朴素而深刻:结构光的整场精度,由全场最差的那块调制度决定——优化必须面向最弱表面,而非平均表面。

32.6 小结

  • 缺纹理就主动投上去。双目靠表面纹理找对应,光面物体会让它失效;结构光投出可控的”人造纹理”,把高度编码进条纹相位,实现稠密三维重建。
  • 相位比强度鲁棒。四步相移 \(\varphi=\operatorname{atan2}(I_3-I_1,\,I_0-I_2)\) 把背景光 \(A\) 与条纹幅值 \(B\) 一并消去,相位对增益、环境光、反照率不敏感——这是测量稳定的根基。
  • 包裹必解,双频最稳。\(\operatorname{atan2}\)\(2\pi\) 模糊须靠解包裹消除;空间法逐点积分、断层即崩,双频/多频层级法逐像素定级、抗断层,是工业标准。
  • 调制度是命脉。高度噪声 \(\sigma_\varphi\propto\sigma/B\),实测 \(B\) 降 5.3 倍、误差升 5.4 倍;一切压低 \(B\) 的因素都按比例恶化精度。
  • 掩膜挡不住饱和。调制度掩膜能精准剔除暗区(99.7%、零虚警),却对饱和盲视(0% 标记)——饱和不降调制度却扭曲相位,唯误差图能暴露;防饱和要靠曝光控制与 HDR,而非事后掩膜。

关于结构光编码图案的系统分类,可参阅 Salvi 等人的综述 (Salvi 等 2010);条纹投影轮廓术的发展脉络与关键问题见 Gorthi 与 Rastogi 的评述 (Gorthi 和 Rastogi 2010),Geng 的教程 (Geng 2011) 则对结构光三维成像各类方法做了系统而易读的梳理。相移轮廓术与多频解包裹在工业实现中的细节另可参阅 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)