简介面向结构光三维测量与光栅投影技术研究人员这份资源以四步相移法为核心提供3种频率下的相移算法实现解决了多频外差相位解算与三维重建的编程落地问题。压缩包仅含1个MATLAB脚本.m大小1KB代码精简适合作为算法验证或二次开发的起点。目前已有1003人学习参考价值较高。脚本通过生成并处理四幅相位差依次为0、π/2、π、3π/2的条纹图结合3种频率组合可完成相位主值计算与展开为后续高度映射和点云生成提供基础。对于从事光学测量、数字光栅投影或结构光成像的科研与工程人员该文件能帮助快速理解四步相移算法流程减少重复coding成本同时便于按需修改频率参数适配不同测量场景。1. 3种频率4步相移法结构光测量里最耐用的三维重建路径做着做着就会遇到“相位展开炸了”这种事。对精度要求高的测量场景比如反光零件表面、带深沟槽的工件单频四步相移解出来的包裹相位会在2π跳变处把三维结果撕成一层一层换个空间展开算法也只是把错误沿路径传得更远。3种频率4步相移法把这个问题压到了采集端对每个频率各投4张正弦光栅图用四步相移解包裹相位再用3个频率做层级展开得到连续相位最后结合标定参数映射出三维坐标。它比格雷码加相移少投一半图案又比单频多频混合方案更容易调通在结构光测量、光栅测量这类工程里属于长期能用的基线方案。这篇按能复现的最小闭环来写相位怎么解、频率怎么定、标定怎么做、东西出来怎么验。2. 四步相移的光强模型与包裹相位计算2.1 为什么四步相移是最常用的采集策略相移法的基础假设是正弦光栅经物体表面调制后相机看到的灰度仍近似正弦。投影图案相位每平移2π相机灰度序列就相当于对固定相位点做了多次采样。这些采样值在三角函数正交性下能解出相位采样点数决定了谐波误差的抑制能力。三步相移每帧间隔2π/3四步相移间隔π/2十二步相移间隔π/6。采样越多抗噪越好代价是采集时间线性增加投影仪和相机同步的时间窗也越紧。四步相移的特别之处在于它只需要4帧就能在相位域里抵消二次、三次和五次谐波误差这些谐波主要来自投影仪非线性Gamma响应和相机传感器响应不一致。所以在结构光测量相关的工程里“4步”大概率是默认值先按4帧跑通再根据噪声水平考虑是否加到8步或12步。单频四步相移本身解决不了大深度范围测量。包裹相位每隔2π折叠一次表面深度一旦超过一个相位周期重建结果就出现整条断层。于是3种频率4步相移法把精度和范围拆开低频负责无歧义范围高频负责细分辨率中间靠层级展开桥接。第一步要做的仍然是把单频的四步相移算对再谈多频展开。2.2 用NumPy实现四步相移从光强帧到包裹相位四步相移4帧的光强模型写作I1 A B cos(φ) I2 A B cos(φ π/2) I3 A B cos(φ π) I4 A B cos(φ 3π/2)A是背景和物体表面反射的直流分量B是光栅调制幅度φ就是需要恢复的相位。两两相减消去直流项后φ arctan2(I4 - I2, I1 - I3)。实现时把4帧堆成数组一次算完避免在Python里做像素级循环。import numpy as np def four_step_phase(frames): frames: (4, H, W) float32 按相移 0, pi/2, pi, 3pi/2 顺序排列 返回: wrapped: 包裹相位范围 (-pi, pi] modulation: 调制幅度等于 B 的估计值 I1, I2, I3, I4 frames.astype(np.float32) numerator I4 - I2 # 2 * B * sin(phi) denominator I1 - I3 # 2 * B * cos(phi) wrapped np.arctan2(numerator, denominator) modulation 0.5 * np.hypot(numerator, denominator) return wrapped, modulation这段代码的核心是用arctan2而不是arctan因为只有arctan2才能根据分子分母的符号把相位落到(-π, π]的四个象限里单独用arctan会丢失符号信息造成整行相位错位。modulation是比相位本身更值得先看的输出它是B的估计值反映该像素上光栅条纹的对比度。黑色表面、阴影、过曝区域的B会显著偏低。实际操作中我会统计整幅modulation的分位数把低于20%分位的像素直接置为无效区防止后面做频率展开时把坏点噪声当成有效相位。2.3 光栅条纹生成与投影参数设置条纹周期数和相移量必须在生成阶段就和后续算法对齐。生成正弦条纹时用周期数而不是像素周期来描述更稳因为结构光测量里分辨率直接和投影仪投出的条纹周期挂钩。def generate_fringe(width, height, period_px, phase_shift): 生成竖直正弦条纹宽度width高度height period_px 为一个周期占据的像素数 phase_shift 为相移量四步相移取 0, pi/2, pi, 3pi/2 x np.arange(width) phase (2.0 * np.pi * x / period_px phase_shift) % (2.0 * np.pi) pattern (0.5 0.5 * np.cos(phase)) * 255.0 return np.tile(pattern.reshape(1, -1), (height, 1)).astype(np.uint8) # 1280x800 投影分辨率周期16像素共80个周期 for k in range(4): fringe generate_fringe(1280, 800, 16, k * np.pi / 2.0) # cv2.imwrite(ffringe_p16_shift{k}.png, fringe)period_px一旦定下来一个频率的总条纹数就是width / period_px这个数必须是整数否则图案本身在边界处闭合不了解出来的相位在左右两端自带台阶。四步相移的phase_shift取0、π/2、π、3π/2和2.2节的公式一一对应。如果采集时发现条纹变淡优先怀疑投影仪或相机开启了自动增益先关闭自动曝光和自动增益再重新采。相位测量到底选几步取决于系统误差环境常用组合对比如下。相移方式每频率图案数误差抑制特点适用场景三步相移3对谐波误差较敏感计算量小快速预览、低精度点位四步相移4可抵消3阶、5阶谐波误差多数测量场景默认方案八步/十二步相移8或12随机噪声进一步压低耗时翻倍高精度计量、静态测量四步相移之所以能抵消3阶和5阶谐波是因为这两类谐波在π/2均匀采样下满足正交消去条件。换成三步相移就需要单独估计谐波系数才能达到同等效果工程上很少有人愿意为这个复杂度买单。3. 3种频率的相位展开为什么是3种而不是2种或5种3.1 单频与双频的局限单频四步相移解出的包裹相位在每一行上呈锯齿状展开就是把这些锯齿拼接成连续斜坡。空间邻域展开算法比如质量引导法依赖相邻像素相位差小于π这个假设。工件表面一旦有台阶、孔洞、深槽或者强反射边缘这个假设立刻失效误差会沿展开路径像流水一样蔓延到整个平面。所以测量类结构光系统很少用单频空间展开。双频方案通过两个频率的包裹相位差得到拍频相位。频率f1和f2的拍频等效频率是f2 - f1误差放大系数是f2 / (f2 - f1)。比如频率6和7拍频展开误差放大7倍两个频率的相位噪声稍大一点展开结果就布满毛刺。三频率层级展开则把放大过程拆成两级每级的频率比控制在1.1以内误差放大被压到十几倍而非几十上百倍。这就是“3种频率”在结构光测量里的真实意义用两个邻频组成一组可靠的对再把展开结果作为下一组的低频基准。3.2 层级展开算法与频率组合参数表层级展开的基本公式是由低频率的展开相位φunwrapped,low和高频率包裹相位φwrapped,high求高频条纹级次k round((ratio·φunwrapped,low − φwrapped,high) / 2π)然后φunwrapped,high φwrapped,high 2πk。def hierarchical_unwrap(wrapped_high, unwrapped_low, ratio): 用低频连续相位展开高频包裹相位 ratio 必须是 f_high / f_low wrapped_high 和 unwrapped_low 均为 (H, W) 数组 diff unwrapped_low * ratio - wrapped_high k np.round(diff / (2.0 * np.pi)) return wrapped_high 2.0 * np.pi * k def multi_frequency_unwrap(wrapped_list, freq_list): wrapped_list: 按频率从低到高排列的包裹相位列表 freq_list: 对应条纹总数列表例如 [6, 7, 8] 返回连续展开相位形状与输入一致 unwrapped wrapped_list[0].copy() for low, high, f_low, f_high in zip( wrapped_list[:-1], wrapped_list[1:], freq_list[:-1], freq_list[1:]): unwrapped hierarchical_unwrap(high, unwrapped, f_high / f_low) return unwrapped这里的ratio必须严格等于相邻频率条纹总数之比而且两端频率在整数上互质。常见错误是把某个频率设成另一频率的整数倍比如16和32那样拍频周期退化成低频自己展开会整片错乱。频率组合我一般按测量深度和噪声水平来选择。频率组合展开误差放大无歧义范围推荐场景6、7、87倍与8倍全幅1个完整条纹毛坯件、深槽结构10、11、1211倍与12倍全幅1个完整条纹常规机械零件30、31、3231倍与32倍全幅1个完整条纹低噪声光路、高精度表面频率差越小单级误差放大越小展开越稳代价是对相位噪声更敏感噪声稍微超界取整到错误的k出现2π级跳变。选择频率时需要综合评估相机的信噪比和投影仪的对比度。这里还要注意无歧义范围最高的频率组合不是越大越好当高频条纹太密、投影仪分辨率有限条纹对比度下降会反过来放大量化误差。3.3 展开失败时的排查顺序层级展开最典型的失败现象是重建三维点云上出现“撕裂”某一小块区域的深度整体错了一个周期。先用modulation掩膜检查是不是阴影或吸光材料被错误纳入计算再把没有掩膜、各频率的包裹相位逐列画出来对比看是否在某个边界处出现了超过半个周期的扰动。还有一条常被忽略投影图案本身如果经过了Gamma校正解出的包裹相位会带系统性的非正弦误差。比较直接的规避方法是在投影之前对条纹做反Gamma校正或者在标定阶段把这部分误差吸收进系统误差里。3种频率的图案总数是12张相比格雷码加相移的30多张在采集速度上有明显优势但如果现场光照变化剧烈多投几帧的冗余反而成为负担这时把展开容错优先级提到最高选频差更小的组合比如30、31、32会更容易展开成功。4. 从包裹相位到三维坐标结构光系统的标定与投影流程4.1 把投影仪当作逆相机来标定拿到展开相位之后三维重建还差最后一步建立“像素坐标相位”到三维点的映射。在这个体系里投影仪被当作一台“逆相机”它的成像过程是相位到像素坐标的转换。标定时需要对投影仪生成一套标定图案常见的做法是竖直方向投影正弦条纹水平方向投影格雷码或者另一组正交条纹这样相机画面里的每一个点都能解出对应的投影仪列坐标。配上已知的棋盘格或圆点标定板姿态就能得到投影仪的内参、畸变系数和相对于相机的位姿。相机和投影仪之间的几何关系等价于一个双目系统。相机图像坐标提供一条视线投影仪列坐标提供该点对应的投影仪视线两条直线在空间中的交点就是三维坐标。相位在这里的作用是亚像素级地给出投影仪坐标一个条纹周期被相位细分到远小于一个像素的精度因此结构光测量能达到的深度分辨率通常远高于单纯格雷码或二值条纹。4.2 相机-投影仪标定的最小代码骨架基于OpenCV做标定时用stereoCalibrate把相机和投影仪当作左右目处理需要先分别准备两边的角点。投影仪坐标对应的“图像点”是由相位换算出来的不是直接读出来的。import cv2 import numpy as np # obj_points: 三维标定板角点 # cam_corners: 相机图像角点 # proj_corners: 由相位换算得到的投影仪图像角点 # 两个内参初值由单目标定得到 ret, K_cam, dist_cam, K_proj, dist_proj, R, T cv2.stereoCalibrate( obj_points, cam_corners, proj_corners, K_cam_init, dist_cam_init, K_proj_init, dist_proj_init, image_size, flagscv2.CALIB_USE_INTRINSIC_GUESS )关键点是proj_corners必须和cam_corners一一对应两个坐标系下的角点来自同一次采集。如果投影仪画面上某些区域处于相机盲区对应的点对要整体剔除否则立体标定会把这些离群点当成正常约束导致旋转矩阵R出现可见的偏斜。标定完成后保存K_cam、dist_cam、K_proj、dist_proj、R、T六组数据重建时直接加载。常见做法是把这些数据封装成一个校准类在每次系统启动时重新加载而不是每次重建都重新标定。4.3 标定和重建中的关键参数与误差来源重建流程中需要关心的参数不只有内参和外参还有投影分辨率、条纹频率和标定板姿态数。标定板至少要覆盖视场中心、边缘和四个角姿态在深度方向分散开才能把外参的旋转和平移分量都约束住。常见做法是采集15到25组姿态每组都同时完成相机和投影仪角点的自动提取。如果重建结果在视场中心准、边缘飞多半是标定板没有覆盖到边缘区域如果深度方向整体有系统性弯曲优先怀疑畸变系数没有收敛。误差来源典型表现排查建议投影仪Gamma非线性包裹相位出现周期性波纹预标定Gamma并做逆变换相机自动增益条纹对比度浮动固定曝光时间和增益投影仪散焦高频条纹对比度低缩小频率组合或减少投影距离标定板表面平整度重建点云局部拱起换陶瓷或石英标定板投影仪的非线性响应在四步相移里虽然能消掉部分谐波但残差仍然存在。误差表现是相位图中叠了一层低频波纹展开后变成三维点云的面形弯曲。判断这类问题的方法是放一个高精度平面镜或平晶重建后拟合平面看残差图如果残差呈同心圆状或条纹状就基本上可以确认是投影光路或者Gamma问题。这一类系统性误差很难完全消除但可以在标定之后用平面残差做一次多项式面形补偿补偿系数存在标定文件里。5. 验证相位展开质量的3个检查项5.1 跳变点统计批量检测2π级错误展开相位在正确情况下沿条纹方向应该是连续变化的只有物体真实边缘才会让相位发生剧烈变化。要区分真实边缘和展开错误可以在每个频率层单独做差分统计差分绝对值超过π的像素数。def count_phase_jumps(unwrapped_phase, axis1): diff np.diff(unwrapped_phase, axisaxis) jumps np.abs(diff) np.pi # 返回每行跳变数方便进一步定位列位置 return np.sum(jumps, axisaxis)统计结果要和modulation掩膜交叉验证。真实边缘处的modulation往往因为遮挡而降得很低展开错误的像素则通常落在有效区域内。如果跳变点分布在平坦表面内部先降低modulation阈值重算一遍再决定是不是频率组合选得太激进。5.2 平面残差检验用平晶给系统“照镜子”重建一块标准平面拟合出空间平面方程统计每个点到拟合平面的距离RMS。RMS在0.05mm以下说明系统基本处于健康状态0.1mm以上就需要检查投影仪温度漂移和标定参数是否过期。平晶覆盖区域至少占视场中央一半面积这样拟合平面才不会被边缘畸变带偏。残差呈现同心圆状条纹时优先怀疑投影光路引入的低频面形误差。5.3 单点毛刺过滤中值滤波只打坏点不打边缘展开正确但仍有孤立的椒盐噪声时常见做法是把重建点云里的单点毛刺按邻域中值替换掉。滤波内核选3x3因为5x5会对真实锐边造成可感知的圆角。先用modulation掩膜把低置信像素排除只对有效深度做中值再对深度图做一次拉普拉斯检测确认边缘区域没有被抹平。把每次标定的跳变点数量导成CSV存下来连续几天做趋势对比跳变数超过正常运行范围1.5倍时优先检查投影光机温度和被测表面反光状态变化。本文还有配套的精品资源点击获取