4. 结构光三维重建:相移法、格雷码与点云生成
结构光三维重建,说白了就是给被测物体“打光编码”,然后通过相机拍回来的变形条纹,反算出物体的三维形貌。我最早接触这个技术是在2016年做手机玻璃盖板检测的时候,当时被客户要求检测0.02mm的划痕深度,传统方法根本搞不定,最后就是靠这套方案解决的。
这一章我们重点讲四个核心模块:相移法(三步、四步)、格雷码编码、多频外差解包裹,以及最后的点云生成与滤波。嗯,内容不少,但都是实战中绕不开的硬骨头。
4.1 相移法:三步与四步
相移法的核心思想很简单:投影一组正弦条纹,每次移动一定的相位,然后通过多幅图像的灰度值变化,反算出每个像素点的相位值。你想想看,这本质上就是一个三角测量问题。
4.1.1 三步相移法
三步相移法需要投影三幅条纹图,每幅之间相位差120°。公式如下:
I1(x,y) = I'(x,y) + I''(x,y) * cos(φ(x,y) - 2π/3)
I2(x,y) = I'(x,y) + I''(x,y) * cos(φ(x,y))
I3(x,y) = I'(x,y) + I''(x,y) * cos(φ(x,y) + 2π/3)
其中I'是背景光强,I''是调制强度,φ就是我们要的相位。解出来的相位公式是:
φ(x,y) = atan2(√3 * (I1 - I3), 2*I2 - I1 - I3)
4.1.2 四步相移法
四步相移法投影四幅图,相位差90°。公式更对称:
I1 = I' + I'' * cos(φ)
I2 = I' + I'' * cos(φ + π/2)
I3 = I' + I'' * cos(φ + π)
I4 = I' + I'' * cos(φ + 3π/2)
解相位公式:
φ = atan2(I4 - I2, I1 - I3)
| 方法 | 投影幅数 | 抗噪性 | 计算速度 | 适用场景 |
|---|---|---|---|---|
| 三步相移 | 3 | 一般 | 快 | 高速在线检测 |
| 四步相移 | 4 | 较好 | 稍慢 | 高精度离线测量 |
4.2 格雷码编码
相移法算出来的相位是包裹在[-π, π]之间的,说白了就是有歧义——你不知道这个点是第几个周期的相位。格雷码就是用来解决这个问题的。
格雷码的特点是相邻两个码字之间只有一位不同。这有什么好处?你想想看,如果投影仪或相机有噪声,传统二进制码可能因为一位误判导致整个解码出错,但格雷码最多只错一个条纹周期。
// 格雷码解码伪代码
for each pixel (x,y):
for each bit k:
if 投影的格雷码条纹在该像素为亮:
gray_code[k] = 1
else:
gray_code[k] = 0
// 格雷码转二进制
binary[0] = gray_code[0]
for k = 1 to N-1:
binary[k] = binary[k-1] XOR gray_code[k]
// 二进制转十进制,得到条纹序号
stripe_index = binary_to_decimal(binary)
避坑指南:我曾经在项目里直接用8位格雷码,结果发现物体表面反光太强,高光区域解码全错。后来我改用“格雷码+相移”混合策略——格雷码只用来确定周期序号,相位值由相移法提供。这样即使格雷码偶尔错一位,相位连续性也能帮你纠回来。
4.3 多频外差解包裹
多频外差是另一种解包裹思路,不需要投影格雷码,而是投影多个不同频率的条纹。核心原理是:两个不同频率的相位差,可以等效为一个更低频率的相位,从而扩大无歧义范围。
假设有两个频率f1和f2,对应的包裹相位分别是φ1和φ2。外差后的等效频率为:
f_eq = |f1 - f2|
等效相位为:
φ_eq = φ1 - φ2 (如果φ_eq < 0, 则加2π)
然后利用这个低频相位去辅助高频相位的解包裹。实际操作中,我一般用三个频率:高频(精细)、中频(过渡)、低频(全局)。
4.4 点云生成与滤波
相位解包裹完成后,我们就得到了每个像素点的绝对相位值。接下来就是根据三角测量原理,把相位映射到三维坐标。
点云生成的公式(简化版):
Z = (L * f) / (d + f * tan(θ))
X = (u - cx) * Z / fx
Y = (v - cy) * Z / fy
其中L是基线距离,f是焦距,d是视差,θ是投影角度。这些参数需要通过标定获得。
点云生成后,滤波是必不可少的。我常用的滤波方法:
- 统计滤波:去除离群点。每个点计算到邻域点的平均距离,超过阈值就剔除。
- 体素滤波:下采样。把空间划分成小立方体,每个立方体内只保留一个重心点。
- 双边滤波:保边去噪。在深度突变区域保留边缘,在平坦区域平滑。
// PCL 统计滤波示例
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50); // 邻域点数
sor.setStddevMulThresh(1.0); // 标准差倍数阈值
sor.filter(*cloud_filtered);
4.5 知识体系总览
下面这张图是我自己整理的,把这一章的核心逻辑串起来了。你跟着这个流程走,基本不会跑偏。
这张图把整个流程串起来了。你从投影开始,走相移法或者格雷码或者多频外差,最终都汇聚到相位解包裹,然后生成点云,滤波后输出。实际项目中,我经常把格雷码和相移法结合使用——格雷码定周期,相移法定精度,这样既快又准。