KinectFusion
前言
本文是针对KinectFusion的一些学习总结,参考资料中的KinectFusion 解析一文已经有了详细解析,但是对与零基础的笔者来说,还是有些地方不够详尽。
参考资料
论文:KinectFusion: Real-time 3D Reconstruction and Interaction Using a Moving Depth Camera
PPT:KinectFusion和ElasticFusion三维重建方法
博客:KinectFusion 解析
课程:PCL-3D点云
流程
图片来源:论文:KinectFusion: Real-time 3D Reconstruction and Interaction Using a Moving Depth Camera
a) 2D 深度图像转换成 3D 点云并求得每一点的法向量
b) ICP 迭代求当前帧相机位姿
c) 根据相机的位姿将点云数据融合到场景的三维模型中
d) 光线投影算法求当前视角下能够看到的场景表面,得到点云,并且计算其法向量,用来对下一帧的输入图像进行配准,即重复b)
步骤详解
a) Depth Map Conversion
三维点云转换
相机参数:相机焦距(
f
x
/
f
y
f_x/f_y
fx/fy),光学中心(
c
x
/
c
y
c_x/c_y
cx/cy)、畸变参数(
d
0
−
d
4
d_0-d_4
d0−d4)和深度因子。
假定像素坐标为
(
u
,
v
)
(u,v)
(u,v),v高,u宽,深度值为
Z
Z
Z,三维点云信息:
p
(
u
,
v
)
=
[
x
y
z
]
=
Z
[
1
/
f
x
0
0
0
1
/
f
y
0
0
0
1
]
[
u
−
c
x
v
−
c
y
1
]
\quad p(u,v)=\begin{bmatrix} x \\ y \\ z \end{bmatrix}=Z\begin{bmatrix} 1/f_x &0&0 \\ 0 &1/f_y & 0 \\ 0 &0&1 \end{bmatrix} \begin{bmatrix} u-c_x\\ v-c_y \\ 1 \end{bmatrix} \quad
p(u,v)=⎣⎡xyz⎦⎤=Z⎣⎡1/fx0001/fy0001⎦⎤⎣⎡u−cxv−cy1⎦⎤
上述变换,得到的坐标系是以相机为原点的坐标。在进行上述转换之前必须对
u
u
u,
v
v
v进行undistort运算,即便如此最终得到的点云数据还是存在误差的。棋盘标定出的相机内参本身是带有误差的,它是一种近似逼近值,也就是说它不能真实的映射相机内部结构。
假设:深度图的缩放是5000:也就是说像素值为5000表示距离相机是1米,10000是两米;图像像素为640*480;深度图为16-bit的数据,转换代码如下:
fx = 525.0 # focal length x
fy = 525.0 # focal length y
cx = 319.5 # optical center x
cy = 239.5 # optical center y
factor = 5000 # for the 16-bit PNG files
# OR: factor = 1 # for the 32-bit float images in the ROS bag files
for v in range(depth_image.height):
for u in range(depth_image.width):
Z = depth_image[v,u] / factor;
X = (u - cx) * Z / fx;
Y = (v - cy) * Z / fy;
对于一张 x ∗ y x*y x∗y像素的图片是如何估计相机内参的呢?下面代码给出了另外一个1280720(宽高):
cx = 1280 / 2
cy = 720 / 2
fx = 1280 / (2 * np.tan(0.5 * np.pi * 90 / 180))
fy = 720 / (2 * np.tan(0.5 * np.pi * 59 / 180))
参考资料
每个点的法向量估计
表面法线是几何表面的重要属性,在许多领域(例如计算机图形应用程序)中大量使用,以应用正确的光源以产生阴影和其他视觉效果。
给定一个几何表面,通常很难将表面某个点的法线方向推断为垂直于该点表面的向量。但是,由于我们获取的点云数据集是真实表面上的一组点样本,因此有两种可能性:
- 使用表面网格化技术从获取的点云数据集中获取基础表面,然后从网格中计算表面法线;
- 使用近似值直接从点云数据集中推断表面法线。
KinectFusion中使用的是第二种:临近点重映射点
n
(
u
,
v
)
=
[
p
(
u
+
1
,
v
)
−
p
(
u
,
v
)
]
×
[
p
(
u
,
v
+
1
)
−
p
(
u
,
v
)
]
n(u,v)=[p(u+1,v)-p(u,v)]\times [p(u,v+1)-p(u,v)]
n(u,v)=[p(u+1,v)−p(u,v)]×[p(u,v+1)−p(u,v)]
且将法线重新归一化到长度为1。公式思想很简单,就是法线必须与切平面垂直,通过高度算法线,通过法线算高度都可以基于这个原理。但是,这种转换并不是非常准确。
假定一个6DOF相机的位姿(pose)在 i i i时刻相对于世界坐标系(global coordinates)的转换矩阵 T i = [ R i ∣ t i ] , T_i=[R_i | t_i], Ti=[Ri∣ti],其中, R i R_i Ri是 3 × 3 3\times 3 3×3的旋转矩阵, t i t_i ti是三维平移矩阵。所计算出来的坐标和法线,在世界坐标系中: p i g ( u , v ) = T i p i ( u , v ) , p^g_i(u,v)=T_ip_i(u,v), pig(u,v)=Tipi(u,v), n i g ( u , v ) = R i n i ( u , v ) . n_i^g(u,v)=R_in_i(u,v). nig(u,v)=Rini(u,v).
b) Camera Tracking
使用ICP,通过最小化匹配点到其切平面得距离,来求得每一帧之间得6DOF变换矩阵。ICP 算法主要用于点云配准,在两帧点云位姿差别较大时,通常先用 3D 特征匹配计算粗略位姿,然后再用 ICP 算法对位姿做 refine。在实时的三维重建中,由于相邻两帧之间位姿变化比较小,在变量求解时,直接用上一帧的位姿初始化。
1
^{1}
1
上一帧位姿变换矩阵
T
i
−
1
T_{i-1}
Ti−1,当前深度图
Z
i
Z_i
Zi
- 根据上一次迭代求得的 pose 变换点坐标。 初始值为 T i − 1 T_{i-1} Ti−1
- 投影匹配算法:匹配两幅点云中的点
- 位姿估计算法:极小化匹配点到切平面的距离
- 目标函数误差小于一定值时,或者到达设置的迭代次数停止迭代,否则进入第 1 步。
投影匹配算法
位姿估计
图片来源:论文:Linear Least-Squares Optimization for Point-to-Plane ICP Surface Registration
如上图,其实就是最小化
l
1
+
l
2
+
l
3
l_1+l_2+l_3
l1+l2+l3。目标函数:
a
r
g
m
i
n
∑
(
u
,
v
)
,
Z
(
u
,
v
)
>
0
∣
∣
(
T
i
p
i
(
u
,
v
)
−
p
i
−
1
g
(
u
′
,
v
′
)
)
⋅
n
i
−
1
g
(
u
′
,
v
′
)
∣
∣
2
argmin \sum_{(u,v), Z(u,v)>0}||(T_ip_i(u,v)-p^g_{i-1}(u',v'))\cdot n^g_{i-1}(u',v')||^2
argmin(u,v),Z(u,v)>0∑∣∣(Tipi(u,v)−pi−1g(u′,v′))⋅ni−1g(u′,v′)∣∣2
其中,
T
i
T_i
Ti是待求位姿。
利用某些近似,将
T
i
=
[
R
i
∣
t
i
]
T_i=[R_i | t_i]
Ti=[Ri∣ti]线性化。上式可转化为如下形式:
x
o
p
t
=
a
r
g
m
i
n
x
∣
C
x
−
b
∣
2
。
x_{opt}=argmin_x|Cx-b|^2。
xopt=argminx∣Cx−b∣2。
旋转矩阵:
R
(
α
,
β
,
γ
)
=
R
z
(
γ
)
⋅
R
y
(
β
)
⋅
R
x
(
α
)
=
[
r
11
r
12
r
13
r
21
r
22
r
23
r
31
r
32
r
33
]
R(\alpha, \beta,\gamma)=R_z(\gamma) \cdot R_y(\beta) \cdot R_x(\alpha)= \begin{bmatrix} r_{11} & r_{12} & r_{13} \\ r_{21} & r_{22} & r_{23} \\ r_{31} & r_{32} & r_{33} \end{bmatrix}
R(α,β,γ)=Rz(γ)⋅Ry(β)⋅Rx(α)=⎣⎡r11r21r31r12r22r32r13r23r33⎦⎤
r
11
=
c
o
s
γ
c
o
s
β
r
12
=
−
s
i
n
γ
c
o
s
α
+
c
o
s
γ
s
i
n
β
s
i
n
α
r
13
=
s
i
n
γ
s
i
n
α
+
c
o
s
γ
s
i
n
β
c
o
s
α
r
21
=
s
i
n
γ
c
o
s
β
r
22
=
c
o
s
γ
c
o
s
α
+
s
i
n
γ
s
i
n
β
s
i
n
α
r
23
=
−
c
o
s
γ
s
i
n
α
+
c
o
s
γ
s
i
n
β
c
o
s
α
r
31
=
−
s
i
n
β
r
32
=
c
o
s
β
s
i
n
α
r
33
=
c
o
s
β
c
o
s
α
\begin{aligned} r_{11}&=cos\gamma cos\beta\\ r_{12}&=-sin\gamma cos\alpha + cos\gamma sin\beta sin\alpha\\ r_{13}&=sin\gamma sin\alpha + cos\gamma sin\beta cos\alpha\\ r_{21}&=sin\gamma cos\beta\\ r_{22}&=cos\gamma cos\alpha + sin\gamma sin\beta sin\alpha\\ r_{23}&=-cos\gamma sin\alpha + cos\gamma sin\beta cos\alpha\\ r_{31}&=-sin\beta\\ r_{32}&=cos\beta sin\alpha\\ r_{33}&=cos\beta cos\alpha\\ \end{aligned}
r11r12r13r21r22r23r31r32r33=cosγcosβ=−sinγcosα+cosγsinβsinα=sinγsinα+cosγsinβcosα=sinγcosβ=cosγcosα+sinγsinβsinα=−cosγsinα+cosγsinβcosα=−sinβ=cosβsinα=cosβcosα
其中,$R_x(\alpha), R_y(\beta) 和 R_z(\gamma) $ 是绕
x
,
y
,
z
x,y,z
x,y,z轴分别旋转
α
,
β
和
γ
\alpha, \beta 和\gamma
α,β和γ。坐标轴旋转见绕任意轴旋转。
当
θ
≈
0
\theta \approx 0
θ≈0时,有
s
i
n
(
θ
)
≈
θ
,
c
o
s
(
θ
)
≈
1
sin(\theta)\approx\theta, cos(\theta)\approx 1
sin(θ)≈θ,cos(θ)≈1。
假设:相邻两帧之间位姿变化较小时,即
α
,
β
,
γ
≈
0
\alpha, \beta, \gamma\approx 0
α,β,γ≈0,我们有:
R
(
α
,
β
,
γ
)
≈
[
1
−
γ
β
γ
1
−
α
−
β
α
1
]
R(\alpha,\beta,\gamma)\approx\begin{bmatrix} 1 & -\gamma & \beta \\ \gamma & 1 & -\alpha \\ -\beta & \alpha & 1 \end{bmatrix}
R(α,β,γ)≈⎣⎡1γ−β−γ1αβ−α1⎦⎤
令
t
=
(
t
x
,
t
y
,
t
z
)
t=(t_x,t_y,t_z)
t=(tx,ty,tz),位姿矩阵:
T
i
=
[
1
−
γ
β
t
x
γ
1
−
α
t
y
−
β
α
1
t
z
0
0
0
1
]
T_i=\begin{bmatrix} 1 & -\gamma & \beta & t_x\\ \gamma & 1 & -\alpha & t_y\\ -\beta & \alpha & 1 & t_z\\ 0 & 0 & 0 & 1\end{bmatrix}
Ti=⎣⎢⎢⎡1γ−β0−γ1α0β−α10txtytz1⎦⎥⎥⎤
其怎么转换为 A x − b Ax-b Ax−b形式在论文:Linear Least-Squares Optimization for Point-to-Plane ICP Surface Registration中有详细介绍。理解代码的话,需要加上RGB-D 实时三维重建/SLAM 中的 ICP 算法解析中的介绍。
参考资料:
c) Volumetric Representation和点云融合
Volumetric Representation
将整个场景看作一个空间(Volume),空间由许多小体素(voxel)组成,如下图所示:
图片来源:PPT KinectFusion和ElasticFusion三维重建方法
TSDF 算法 truncated signed distance function
算法思想:对与当前体素,计算其到相机观测表面的距离(带符号);对不同的视图,用权重去平均的方法融合共有体素的距离值。Truncated截断指的是,离相机过远、过近或者在视域范围之外的这些体素,可以不用更新。
图片来源:论文 A Volumetric Method for Building Complex Models from Range Images
Step 1: 准备工作
- 如上图,对场景建立长方体包围盒,能够完全包围要重建的物体。
- 划分网格体素,对包围盒尽心划分n等分,体素的大小取决于包围盒和划分体素的数目决定。GPU并行时,可以一个线程处理一条(x,y)晶格柱,上图蓝色部分。
Step 2: 当前帧的TSDF值和权重计算,并进行融合
假定当前帧的位姿为 T i T_i Ti
- 对于构造的立体中的每个体素 g g g,转化为世界坐标系下的三维位置点 p g p^g pg(根据体素的大小,以及体素的数目);
- 从世界坐标系转换到相机坐标系 p ← T i − 1 p g p\leftarrow T_i^{-1}p^g p←Ti−1pg
- 由相机内参矩阵,计算 p p p点对应的像素 ( u , v ) (u,v) (u,v),也可以所是投影到相平面
- 如果像素 ( u , v ) (u,v) (u,v)在相机视场内,深度值为 D s ( g ) = D i ( u , v ) D_s(g) = D_i(u,v) Ds(g)=Di(u,v),点 p g p^g pg到相机坐标原点的距离为 D c ( g ) = ∣ ∣ t i − p g ∣ ∣ D_c(g)=||t_i-p^g|| Dc(g)=∣∣ti−pg∣∣,其中 t i t_i ti为位姿的平移向量。则其TSDF值: V s d f ( g ) = D c ( v ) − D s ( u , v ) V_{sdf}(g)=D_c(v)-D_s(u,v) Vsdf(g)=Dc(v)−Ds(u,v)
- 进行截断:
V t f d s ( g ) = { 1 , if V s d f ( g ) ≥ T t f d s _ m a x V s d f ( g ) / T t f d s _ m a x , if 0 < V s d f ( g ) < T t f d s _ m a x V s d f ( g ) / T t f d s _ m i n , if − T t f d s _ m i n < V s d f ( g ) < 0 − 1 , if V s d f ( g ) < − T t f d s _ m i n V_{tfds}(g) = \begin{cases} 1, & \text{if }V_{sdf}(g) \geq T_{tfds\_max} \\ V_{sdf}(g)/T_{tfds\_max}, &\text{if } 0 <V_{sdf}(g)<T_{tfds\_max} \\ V_{sdf}(g)/T_{tfds\_min}, &\text{if } -T_{tfds\_min} <V_{sdf}(g)< 0\\\\ -1, & \text{if }V_{sdf}(g) < -T_{tfds\_min} \end{cases} Vtfds(g)=⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧1,Vsdf(g)/Ttfds_max,Vsdf(g)/Ttfds_min,−1,if Vsdf(g)≥Ttfds_maxif 0<Vsdf(g)<Ttfds_maxif −Ttfds_min<Vsdf(g)<0if Vsdf(g)<−Ttfds_min
也就是说,小于某个值,为 − 1 -1 −1,且非正数部分归一到 [ − 1 , 0 ] [-1,0] [−1,0];大于某个值,为 1 1 1,且正数部分归一到 ( 0 , 1 ] (0,1] (0,1]。 - 计算权重: w i ( g ) = m i n ( m a x _ w e i g h t , w i − 1 ( g ) ) w_i(g)=min(max\_ weight, w_{i-1}(g)) wi(g)=min(max_weight,wi−1(g))
- 平均:
V
t
s
f
d
_
i
(
g
)
=
w
i
−
1
(
g
)
∗
V
t
s
f
d
_
i
−
1
(
g
)
+
w
i
(
g
)
∗
V
t
s
f
d
_
i
(
g
)
w
i
−
1
(
g
)
+
w
i
(
g
)
V_{tsfd\_i}(g)=\frac{w_{i-1}(g)*V_{tsfd\_i-1}(g)+w_i(g)*V_{tsfd\_i}(g)}{w_{i-1}(g) + w_i(g)}
Vtsfd_i(g)=wi−1(g)+wi(g)wi−1(g)∗Vtsfd_i−1(g)+wi(g)∗Vtsfd_i(g),如下图:
图片来源:论文 A Volumetric Method for Building Complex Models from Range Images
在其他TSFD算法中,权重计算公式
w
(
g
)
=
c
o
s
(
θ
)
/
D
c
(
g
)
w(g) = cos(θ)/D_c(g)
w(g)=cos(θ)/Dc(g),
θ
\theta
θ为点到相机的射线与表面法线的夹角。如下图,可以看到,入射角更小,则是比较好的情况,入射角越大,带来的结果也相对更不可靠,因为很明显它不应该是距离表面最近的距离。因此实际中的权重,应该给入射角小的相机位置采集到的帧率更大的权重。
图片来源:3D Reconstruction——TSDF volume reconstruction
TSDF 生成的网格的细节保持比较好,而且精确度也比较好,但是在边缘处以及前后景交界处,会出现较大的拖尾现象。因为在体素p向像素坐标系投影时会有一定的误差。TSDF学习_weixin_44299305的博客-程序员宅基地中有介绍hasing voxel。
d) Adaptive Raycasting
Raycasting是从TSDF场中恢复出表面的过程。从一个地方射出一条线过去,如果它经过了表面,那么这个光线经过的体素的TSDF值一定有一个从正变负,或者从负变正(如果是实时重建,那么只有从正到负)的过程。这个射出的方向,就是相机坐标系z轴的。而正负交接点0点,我们称为zero-crossing,就是表面所在的voxel。我们要做的就是找到这个地方,当然有时候可能不会有哪个的体素大小直接是0,这时候就需要进行三线性插值(trilineal interpolation),找到逼近0的哪个地方。
具体步骤:
- 对输出图片中的每一个像素:添加第三维度0/1,并转换为体素网格中的位置,下图1-3.
- 求对应的射线 r a y d i r ray^{dir} raydir,下图4
- 出射射线所撞击的第一个体素单元 g g g,下图6
- 求出对应网格顶点到起点的距离,作为另外一个终止条件,下图7-8
- 若 g g g在我们的包围盒中:下图9
- 更新 r a y l e n ray^{len} raylen,保存 g g g到 g p r e g^{pre} gpre,并沿着射线 r a y d i r ray^{dir} raydir求得下一个体素网格点 g g g,下图10-12
- 判断 g g g到 g p r e g^{pre} gpre是否是正负交接点0点(zero-crossing,从正变负,或者从负变正),如是,转8;否则转5。下图13
- 线性插值得到零点 p p p,将点 p p p转换到世界坐标,下图14-15
- 通过表面tsdf的梯度计算点 p p p的法线,下图16
- 渲染该点,得到颜色值;或进行光线跟踪。下图17-18
图片来源:论文:KinectFusion: Real-time 3D Reconstruction and Interaction Using a Moving Depth Camera
具体操作中,为了加速,可能会开始已一个比较大的步长,找到了zero-crossing,换成更小的步长,直到得到要求的精度,使得选取点的TSDF值尽量接近于0。得到这个点之后,再同样根据线性插值,写入color(实际中color可能是包含在TSDF场中的,以及权重)。
图片来源:3D Reconstruction——TSDF volume reconstruction
图片来源:PPT KinectFusion和ElasticFusion三维重建方法
参考资料: