
先交代一下背景我这几年一直在做三维重建相关的项目从最早的散斑结构光一路做到条纹投影结构光。中间踩过的坑、绕过的弯真不少。如果你也刚接触这个方向或者说想在“3D结构光相机”这条路上找个靠谱的切入点那条纹投影结构光Fringe Projection Profilometry简称FPP值得你重点研究。它跟散斑结构光那种“投随机斑点、靠图像块匹配”的思路完全不同它用的是规则正弦条纹通过相位来编码空间位置精度能做到亚毫米甚至更高而且硬件成本相对可控——一台普通工业相机加一个DLP投影仪就能搭起来。这篇文章是这个系列的第一篇我先把整体思路、核心原理和系统选型讲透后面几篇再逐个展开标定流程、解相位算法和误差补偿。还有一点要提前说这套方法对静态或者准静态物体效果最好。拿来做人体扫描、文物数字化、工业零件检测都没问题但如果想做高速运动的实时重建需要额外考虑投影策略和同步方案。先把基础吃透再谈进阶。1. 为什么选条纹投影结构光先看清各路方案的位置1.1 从主流3D结构光方案说起现在市面上能看到的3D结构光相机原理大致可以分成几类。第一类是散斑结构光像早期消费级深度相机用的就是这种思路核心是投影仪投出一幅随机散斑图案然后通过局部图像块匹配来找对应点再用三角测量算出深度。它的优点是技术成熟、对硬件要求低缺点是分辨率有限边缘和弱纹理区域容易出现匹配错误精度到了一定程度就上不去了。第二类是编码结构光投射的是某种有规律的编码图案比如格雷码、De Bruijn序列、彩色编码条纹等。这类方法通过码字来唯一标识投影仪像素也不需要复杂的相位计算但是编码图案通常需要投影多帧逐像素码字的切换会导致测量速度变慢而且对表面纹理和颜色比较敏感。第三个方向就是我重点要讲的条纹投影结构光它投的是相位连续变化的正弦条纹每个点的相位都携带了位置信息再加上相移技术可以把精度做到很高。1.2 条纹投影的核心优势为什么最终我选择深耕条纹投影这一支最核心的原因在于“相位”这个量。跟散斑匹配找对应点不一样条纹投影是通过相位来连续编码整个测量区域的它不像散斑那样依赖局部纹理的随机性。这样做的好处有三个第一精度高。相位解算可以达到亚像素级别配合高分辨率相机深度方向的分辨率能到几十微米量级。第二点云密度大。因为每个相机像素都能输出一个三维点不再受匹配窗口的限制。第三对弱纹理物体友好。很多工业零件表面就是金属、塑料、纯色用双目或者散斑方案很容易翻车但条纹投影只要物体有反射光就行。当然它也有限制它对环境光、物体表面反射率变化比较敏感反光表面容易过曝透明或半透明物体基本没法直接测。这些都是后面要处理的工程问题不是理论上的死路。1.3 这套方案的适用场景如果你要做的是高精度静态三维扫描——比如雕刻扫描、文博数字化、精密零件测量、模具检测那条纹投影几乎是最合适的选择。如果是想做人脸识别、刷脸支付这类消费级场景散斑结构光或者ToF可能更现实因为成本、体积和实时性摆在那里。我自己比较推荐的学习路径是先用条纹投影打好相位解算和标定的底子再回头去看散斑和ToF会发现很多概念是相通的——都是立体视觉的变体本质都在解决“对应点从哪来”的问题。区别只在于条纹用相位找对应点散斑用纹理相似度找对应点ToF直接用飞行时间。2. 条纹投影结构光的工作原理一帧条纹背后是相位和高度在对话2.1 单幅条纹的数学表达先看到最基础的一步投影仪投出一幅正弦条纹图它沿某个方向的光强分布可以写成I(x, y) a(x, y) b(x, y) · cos(φ(x, y))这里的a(x, y)是背景光强也叫直流量DCb(x, y)是条纹调制幅度对比度φ(x, y)就是我们最关心的相位。物体表面的高度变化会改变条纹的形貌导致同一个相机像素点的相位发生偏移。换句话说只要我们能从拍摄到的图像里解出φ(x, y)就能反过来推算物体表面高度。一个很容易混淆的点是这里的φ本身是“包裹相位”它被arctan函数限制在(-π, π]之间直接拿它算高度是不行的。我们需要先解出相位主值再做相位展开Phase Unwrapping拿到连续的绝对相位才能建立相位与三维坐标的联系。这也是整个条纹投影技术最核心、也最容易被新手卡住的地方。2.2 为什么用多帧相移而不是单帧解相单从一幅正弦条纹图其实没法直接解出相位。原因很简单方程里有三个未知数背景、幅度、相位但你只有一幅光强图方程数量不够。所以工程上普遍采用“相移法”也就是投影多幅相位差固定的条纹图通过联立方程组把相位解出来。以最常用的三步相移为例三幅条纹的相位依次偏移0、2π/3、4π/3光强可以写成I1 a b · cos(φ - 2π/3) I2 a b · cos(φ) I3 a b · cos(φ 2π/3)将三式联立代入三角函数恒等式就可以解出φ arctan(√3 · (I1 - I3) / (2I2 - I1 - I3))这就是三步相移法的标准公式。之所以推荐三步起步是因为它用的帧数最少适合后续扩展到高速测量如果追求更好的抗噪性可以上四步、五步甚至更高阶的相移算法。四步相移的相位计算公式是φ arctan((I4 - I2) / (I1 - I3))相位误差会随着帧数增加而下降但代价是测量时间变长。实际项目中要根据物体是否静止、运动速度、环境光是否变化来取舍。2.3 从包裹相位到绝对相位多频外差法解出来的φ是被包裹的分支相位每遇到2π就跳变一次形成锯齿状图案。要做三维重建必须把这个锯齿展开成连续相位。展开方法有很多比如空间相位展开沿着像素路径积分、时间相位展开投影多组不同频率的条纹。我最推荐的是多频外差法也叫层次相移法。它投影两组或者三组频率不同的条纹通过两个频率间的相位差构造“拍频”得到一个等效频率很低、测量范围很大的相位图再用这个低频相位去引导高频相位展开。打个比方高频条纹负责精细测量低频条纹负责“数圈数”两相结合就能得到既连续又精确的绝对相位。假设两个频率是f1和f2它们的包裹相位分别是φ1和φ2那么差频相位φ12 φ1 - φ2。这个差频对应的等效频率是feq f1 · f2 / |f1 - f2|。比如投影仪水平方向1024像素用f1100、f290两组条纹等效频率就是900接近投影仪分辨率的量级。只要保证等效频率的波长能覆盖整个测量区域就可以无歧义地完成绝对相位展开。3. 系统搭建与硬件选型投影仪和相机怎么搭才不踩坑3.1 相机、投影仪、镜头的匹配逻辑搭建一套条纹投影系统硬件的核心是三个东西相机、投影仪、以及相机镜头。很多人一开始不在乎这些配置随便拿手头的设备就开跑结果后期各种问题。我的建议是项目初期就按这样的思路去配相机优先选黑白工业相机分辨率根据你需要的点云密度来定至少200万像素起步。为什么强调黑白因为条纹投影本质是检测光强变化彩色相机的拜耳滤波器会引入颜色插值误差直接影响相位的精度。投影仪方面选DLP还是LCD取决于你刷新率和灰度精度的需求。DLP的灰度线性度比LCD好不少测量效果也更稳定预算够就上DLP。镜头选型要注意一点相机镜头和投影仪的投射范围要匹配尽量做到公共视场最大化。一个常见错误是投影仪视场远大于相机视场导致大量投影条纹浪费在无效区域同时也造成亮度分布不均。先大致框定测量范围再反推镜头焦距和投影距离这才是正确的顺序。3.2 同步触发与曝光控制条纹投影系统最容易被忽视的是同步问题。相机曝光和投影仪切换不同步拍出来的条纹就会“花”。尤其是DLP投影仪存在色轮或微镜切换时间乱序或半帧曝光都会引入严重的相位误差。我在实际工程里用过两种方式。工作频率要求不高的时候投影仪逐帧投影相机用软件触发抓拍简单可靠。但是如果做动态测量就必须上硬件同步用单片机或者相机的外触发信号去触发投影仪切换图案确保每次曝光都对应完整的一帧条纹。硬件同步看起来麻烦其实一次调通之后能省掉大量后期修复时间。曝光时间也是一个需要调的点。曝光太短条纹对比度低相位噪声大曝光太长高亮区域饱和相位直接解错。我的做法是先固定投影仪亮度再调整相机曝光使图像中最亮区域刚好达到最大灰度值的80%左右留出裕量。3.3 标定系统相机-投影仪联合标定条纹投影的三维重建效果很大程度上取决于标定质量。这里我多说一句很多人把标定当成走流程随便拍几块棋盘格就开始重建后面点云变形了都不知道是哪一步出的问题。其实条纹投影系统要标定的不只是相机而是“相机投影仪”这个整体。常用的思路是把投影仪当成一个逆向相机向标定板投影一系列格雷码或相移条纹让投影仪的每个像素都能找到它在标定板上的对应三维点然后用张正友标定法的思路标定出投影仪的内外参。这样做的目的是获得每个像素从相机坐标到投影仪坐标的映射关系也就是建立起相位与三维坐标的直接对应。具体的标定流程我会在系列下一篇里详细写。这里只给一个执行建议标定板的姿态要覆盖整个测量空间近中远、左中右、俯仰偏转都要有。我见过太多人只拍了平视角度导致外参病态重建出来的平面是弯曲的。4. 从零跑通一次条纹投影三维重建完整实操拆解4.1 准备阶段投影条纹图和采集环境先把环境布置好。测量区域要保持光照相对稳定尽量避免太阳光直射这类强烈波动的环境光。虽然相移法本身有一定的抗环境光能力但过强的外部背景光会压缩条纹的动态范围等效于降低了信噪比。条纹图怎么生成用代码画正弦条纹再投出去是最常见的做法。以Python生成一组三步相移条纹为例import numpy as np import cv2 width, height 1280, 800 # 投影仪分辨率 freq 60 # 条纹频率单位像素/周期 fringes [] for k in range(3): phase_shift 2 * np.pi * k / 3 stripe 127.5 127.5 * np.cos(2 * np.pi * freq * np.arange(width) / width phase_shift) img np.tile(stripe, (height, 1)).astype(np.uint8) fringes.append(img) for idx, img in enumerate(fringes): cv2.imwrite(ffringe_{idx}.png, img)注意生成条纹时需要把灰度范围映射到0到255之间也就是上面代码里常数127.5的处理。直接把cos输出当灰度用会丢失对比度、甚至截断成二值条纹。这个细节虽然不起眼但很多人第一次跑出来的条纹图是糊的就是这个原因。4.2 三步相移解相一个像素一个像素地算拍完三幅条纹图逐个像素带入前面提到的公式就能得到包裹相位图。实际用Python实现时可以用向量化的方式写# 假设 im1, im2, im3 是三幅采集到的条纹图灰度范围 0~255转成 float im1 im1.astype(np.float64) im2 im2.astype(np.float64) im3 im3.astype(np.float64) phi np.arctan2(np.sqrt(3) * (im1 - im3), 2 * im2 - im1 - im3)这里用np.arctan2而不是np.arctan是因为arctan2可以根据分子分母的正负自动判断象限得到的包裹相位范围是(-π, π]避免公式里人为处理的麻烦。拿到包裹相位之后可以先做一个质量显示把phi归一化到0到255保存成灰度图看看。如果是平滑的锯齿条纹说明相移和解算基本正常如果图上有很多颗粒状噪声要优先检查投影仪对焦和相机曝光是否合适。4.3 多频外差展开相位把锯齿缝起来这一步是很多教程一笔带过、但实际做起来最容易出问题的部分。我用双频外差来举例假设投了f1100和f290两组条纹各自做三步相移分别得到包裹相位φ1和φ2。先看拍频相位phi_beat phi1 - phi2 phi_beat np.mod(phi_beat, 2 * np.pi)注意这里的取模操作。直接相减得到的相位范围可能超过2π需要再做一次wrap把它拉回到(-π, π]区间。这个wrap操作如果漏了后续展开值会整体出错。得到拍频相位之后理论上可以用它来对高频相位做展开但实际操作中为了增强鲁棒性我习惯再投影一组更低频率的条纹比如f1100、f290、f380做两次外差。第一次用f1和f2得等效频率900的相位第二次用f2和f3得等效频率720的相位然后再把这两个等效相位做一次外差得到更低的等效频率。层级越多展开越稳但需要投影的帧数也越多。做静态扫描时多花几帧换取稳定性非常划算。4.4 从绝对相位到三维坐标展开后的绝对相位Φ是连续变化的但要从它得到三维坐标还需要查“相位-高度映射表”。这个映射关系的严格推导需要结合标定参数通俗地说系统里相位与空间坐标之间存在一一对应关系可以通过标定建立多项式模型或者查表模型。简化理解在平行光轴且投影仪、相机相对位置固定的理想系统里物体表面高度h和相位变化量ΔΦ近似成正比h k · ΔΦ / (2π · f0)其中k是和系统几何参数有关的常数f0是条纹频率。但实际系统往往不满足理想的平行光轴假设所以工程上更常用的是用一个二元二次多项式去拟合z a0 a1 · Φ a2 · Φ² ...标定的时候用已知高度的平面做最小二乘拟合把系数a0、a1、a2拟合出来重建时直接代入。需要提醒的是如果你把标定和重建分开做那每一次重建时投影仪、相机、物体的相对位置都必须跟标定时一致。任何一台设备的移动都意味着要重新标定。很多新人重建出来的点云错位不是算法写得有问题而是设备被碰了一下没重新标定。5. 常见问题与排查实录我在实际项目中反复踩过的坑5.1 反光与高光区域相位飞掉这是条纹投影里最经典的难题。物体表面如果是金属、陶瓷或者湿表面某些角度会出现强镜面反射相机看到的光强直接饱和解出的相位就完全是噪声。我的排查顺序是这样先看拍摄图像里有没有局部过曝。有的话第一步降低曝光时间或者投影亮度让高光区不过曝代价是暗部对比度下降。第二步是用多曝光融合分别拍低曝光和高曝光的条纹图然后按像素选取有效区域做融合。第三步在上偏振方案包括投影仪端和相机端都加偏振片可以有效压制镜面反射分量但会损失一部分光强。如果是在做工业零件扫描还有一个土办法在表面喷一层显影剂。这个方法虽然看起来粗暴但确实是快速验证整套流程的最有效手段。喷粉之后扫描反光问题基本消失先保证算法链路是通的再回来优化成像方案。5.2 投影仪伽马非线性带来的周期误差投影仪本质是显示设备它的灰度-光强响应并不线性而是近似一个幂函数关系通常称为伽马曲线。如果忽略这个非线性投影的理想正弦条纹到达物体表面时实际光强分布已经畸变了最后解出来的相位会带有明显的周期波纹误差。处理方案有两条路。一条是用gamma预校正先标定出投影仪的灰度-光强曲线再反向生成补偿后的条纹图。另一条是后天补偿在解相位之后通过查找表或多项式模型把周期性误差修正掉。我的经验是预校正的效果更稳定而且实现起来也不难只需要用投影仪投一系列灰阶图用相机记录对应的光强值拟合出伽马值之后所有条纹都按这个伽马值重新生成。5.3 环境光变化和整套系统的稳定性环境光低频漂移、投影仪预热后亮度变化、相机噪声这些不起眼的因素叠加起来也会让相位质量明显下降。我碰到过一种很隐蔽的情况系统刚开机的时候重建效果很好运行半小时后点云出现周期性的波纹最后排查发现是投影灯泡热量导致投影亮度漂移。应对的办法很简单每次测量前进行白平衡和暗电平校准拍摄一组全暗和全亮参考图同时保证投影仪有足够的预热时间不建议一开机直接做精密测量。另外所有拍摄到的条纹图最好先做差分处理减去暗电流偏置后再解相这样能把固定模式噪声的影响压下去。5.4 相位展开跳变怎么快速定位多频外差展开出来的相位如果仍然有跳变表现是重建点云里出现局部的高度台阶或“撕裂”。排查时先查看展开相位图沿着条纹方向看相位是否连续有跳变的地方通常是物体表面剧烈遮断、阴影、或者低反射区域。常见原因是低频参考相位在被高频引导时噪声太大导致整数级圈数判断错误。解决思路是增加高频和低频之间的频率差让等效波长变小或者像前面说的增加外差层级。如果项目允许投影格雷码辅助做绝对相位展开也是一个非常稳的选择——用格雷码确定整数级条纹序号再用高频相位做细分这种混合策略在工业扫描里非常流行。还有一个细节投影仪离焦问题。投影镜头对焦不准会让高频条纹对比度下降但低频条纹几乎不受影响。如果观察到的现象是低频正常、高频噪声大先别急着改算法把投影仪对焦重新调一下往往能直接解决问题。6. 这期先到这里后续可以再展开的方向这第一篇我主要把条纹投影结构光是什么、为什么选它、以及最基础的原理和实操链路梳理了一遍。真正到做产品或者深入研究时后面还有很多内容值得单独展开系统标定的完整推导、多频外差与时间相位展开的代码实现、基于查表法的快速三维重建、以及如何处理高反光和运动物体。我自己在实践中最深的体会是条纹投影这套方案对工程细节的要求非常高很多问题都不是理论难而是“变量太多、环节之间互相牵连”。所以如果你正要上手我建议按照这个顺序来先保证条纹生成正确再保证相移解算正确然后一步步去处理投影仪非线性、曝光、标定最后再谈精度优化。每一步都要用可视化结果去验证不要想着一次跑通全流程之后统一排查——那样出了问题你根本不知道锅该甩给谁。