1. 写在前面
在前面的博客中,介绍了如何通过 C/C++ 开发我们的结构光3D相机SDK。在这篇博客中,博主将介绍我们的结构光编解码算法:相移互补格雷码。完成本篇博客的学习内容后,你将收获相移互补格雷码的原理与代码实践经验。
本系列博客的完整项目代码皆位于博主的Github项目SLMaster👈中:
https://github.com/Practice3DVision/SLMaster
动动你的小指头给个Star⭐并follow博主吧!你的支持是博主不懈的动力!
2. 相移轮廓术(PSP)与相移互补格雷码编解码
在第0节中,博主介绍过结构光技术的最大优点是采用主动式编码为物体添加信息,有效避免了由于弱纹理等图像信息不足而导致的立体匹配失败情况。而相移轮廓术则是结构光技术中主要采用的方案之一,
N
N
N步相移中的第
n
n
n(
n
=
0
,
2
,
…
,
N
−
1
n = 0, 2,\dots,N-1
n=0,2,…,N−1)步相移条纹的强度编码可用通过下式进行表示:
I
n
(
x
,
y
)
=
A
(
x
,
y
)
+
B
(
x
,
y
)
c
o
s
[
ϕ
(
x
,
y
)
+
2
π
n
N
I_n(x, y) = A(x, y) + B(x, y)cos[\phi(x, y) + \frac{2\pi n}{N}
In(x,y)=A(x,y)+B(x,y)cos[ϕ(x,y)+N2πn
式中,
(
x
,
y
)
(x, y)
(x,y)为像素坐标系下点坐标,
ϕ
\phi
ϕ为包裹相位且有
ϕ
=
x
T
∗
2
π
\phi=\frac{x}{T}*2\pi
ϕ=Tx∗2π,
A
A
A为平均强度,
B
B
B为调制强度。为了使得条纹对比度和正弦性皆较好,一般令
A
=
B
=
127.5
A = B = 127.5
A=B=127.5。
由于 A A A与 B B B皆为常量,因此相移条纹单个周期上的包裹相位具有唯一性,而 N N N个周期上的包裹相位则具有模糊性,一般以包裹相位求解用于描述求解 ϕ \phi ϕ的过程,而相位解包裹则用于描述去除包裹相位模糊性的过程。经过相位解包裹所得到的全局唯一相位值称为绝对相位。通过全局唯一的绝对相位,可以使得立体匹配以极高的鲁棒性与效率进行工作。
博主将分别对包裹相位求解和相位解包裹进行原理说明。
1) 包裹相位求解
当编码的条纹经投影仪投影并被相机接收时,共有 A A A、 B B B、 ϕ \phi ϕ三个未知数。因此,根据多元方程组求解规则,至少需要三个方程且方程数目越多精度越高,也就是至少需要三步相移步数且相移步数越多包裹相位值求解精度越高。
最终的包裹相位求解计算公式如下:
ϕ
=
−
t
a
n
−
1
[
∑
n
=
0
N
−
1
I
n
s
i
n
(
2
n
π
N
)
∑
n
=
0
N
−
1
I
n
c
o
s
(
2
n
π
N
)
]
\phi = -tan^{-1} \left [ \frac{\sum^{N-1}_{n=0}I_nsin(\frac{2n\pi}{N})}{\sum^{N-1}_{n=0}I_ncos(\frac{2n\pi}{N})} \right ]
ϕ=−tan−1[∑n=0N−1Incos(N2nπ)∑n=0N−1Insin(N2nπ)]
实验
首先使用博主所编写的SLMaster软件生成三步相移条纹图案,如下图所示:
图1. 使用SLMaster软件编码三步相移条纹
然后保存条纹图案至本地文件夹中,编写如下代码便可得到最终的计算结果:
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/structured_light.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
vector<Mat> imgs;
for (int i = 0; i < 3; ++i) {
Mat img = imread("../imgs/IMG_" + to_string(i) + ".bmp", 0);
imgs.emplace_back(img);
}
auto params = structured_light::SinusCompleGrayCodePattern::Params();
params.shiftTime = 3;
params.nbrOfPeriods = 0;
auto wrapMapSolver = structured_light::SinusCompleGrayCodePattern::create(params);
Mat wrapMap;
wrapMapSolver->computePhaseMap(imgs, wrapMap);
normalize(wrapMap, wrapMap, 0, 255, NORM_MINMAX);
imwrite("wrapMap.bmp", wrapMap);
return 0;
}
该structured_light模块中的SinusCompleGrayCodePattern类为博主为opencv库所添加的新方法
然后便可查看应用程序目录下的包裹相位图案wrapMap.bmp。我们以三步相移图案为例,包裹相位计算的结果如下图所示:
图2. 三步相移包裹相位计算结果
2) 相位解包裹
为了获得全局唯一的绝对相位,博主在这里给大家推荐相移互补格雷码作为相位解包裹算法。该算法由吴周杰提出,取得了比较好的效果,编码与解码策略如下图所示[1]:
图2. 相移互补格雷码
图中 I i I_i Ii为相移图案,$ G C i GC_i GCi为格雷码图案。以往的阶次对齐形式的相移格雷码解码策略(如图中的 G C 1 GC_1 GC1~ G C 4 GC_4 GC4所示)会导致在阶次过渡处解码失败(如 k 1 k_1 k1与 G C 4 GC_4 GC4对齐所示),而相机或投影仪的离焦会进一步加剧这种现象。为了解决上述问题,该方法额外投影了一张更高阶次的格雷码图案(如图中的 G C 5 GC_5 GC5所示),从而有效的将条纹阶次 k 2 k_2 k2与格雷码阶次过渡处错开。
最终的绝对相位计算规则如下所示:
Φ = { ϕ ( x , y ) + 2 π k 2 ( x , y ) , ϕ ( x , y ) ≤ − π / 2 ϕ ( x , y ) + 2 π k 1 ( x , y ) , − π / 2 < ϕ ( x , y ) < π / 2 ϕ ( x , y ) + 2 π ( k 2 ( x , y ) − 1 ) , ϕ ( x , y ) ≥ π / 2 \Phi = \begin{cases}\phi (x,y)+2\pi k_2(x,y), \phi(x,y)\le-\pi / 2 \\\phi (x,y)+2\pi k_1(x,y), -\pi/2<\phi(x,y)<\pi / 2 \\\phi (x,y)+2\pi(k_2(x,y)-1), \phi(x,y)\ge\pi / 2 \end{cases} Φ=⎩ ⎨ ⎧ϕ(x,y)+2πk2(x,y),ϕ(x,y)≤−π/2ϕ(x,y)+2πk1(x,y),−π/2<ϕ(x,y)<π/2ϕ(x,y)+2π(k2(x,y)−1),ϕ(x,y)≥π/2
实验
首先使用博主所编写的SLMaster软件生成三步相移互补格雷码条纹图案,如下图所示:
图3. 使用SLMaster软件编码三步相移互补格雷码条纹
然后将图片保存至本地文件夹内,并编写如下代码:
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/structured_light.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
vector<Mat> imgs;
for (int i = 0; i < 9; ++i) {
Mat img = imread("../imgs/IMG_" + to_string(i) + ".bmp", 0);
imgs.emplace_back(img);
}
auto params = structured_light::SinusCompleGrayCodePattern::Params();
params.shiftTime = 3;
params.nbrOfPeriods = 32;
auto wrapMapSolver = structured_light::SinusCompleGrayCodePattern::create(params);
Mat wrapMap, confidenceMap, floorMap, unwrappedPhaseMap;
wrapMapSolver->computePhaseMap(imgs, wrapMap);
wrapMapSolver->computeConfidenceMap(imgs, confidenceMap);
wrapMapSolver->computeFloorMap(imgs, confidenceMap, wrapMap, floorMap);
wrapMapSolver->unwrapPhaseMap(wrapMap, floorMap, unwrappedPhaseMap);
normalize(unwrappedPhaseMap, unwrappedPhaseMap, 0, 255, NORM_MINMAX);
normalize(floorMap, floorMap, 0, 255, NORM_MINMAX);
imwrite("floorMap.bmp", floorMap);
imwrite("unwrappedMap.bmp", unwrappedPhaseMap);
return 0;
}
然后便可查看应用程序目录下的包裹相位图案unwrappedMap.bmp。我们以三步相移下周期数为32的相移互补格雷码条纹为例,绝对相位的计算结果如下图所示:
图2. 相移互补格雷码下的包裹相位展开结果
3. 总结
在这篇博客中,博主介绍了相移互补格雷码编解码算法 ,在下一小节中,博主将开始介绍相机与投影仪的标定。
参考文献
[1]: Wu, Zhoujie. High-speed three-dimensional shape measurement based on robust and high-efficient Gray-code coding strategies (in Chinese) [J]. 2020.
本系列文章将持续更新,如果有等不及想要获得代码的小伙伴,可访问博主Github中的SLMaster项目,动动你的小手指,follow and star⭐!你们的关注是博主持续的动力!
Github:https://github.com/Practice3DVision
QQ群:229441078
公众号:实战3D视觉