VINS-Mono关键知识点总结——预积分和后端优化IMU部分
VINS-Mono关键知识点总结——预积分和后端优化IMU部分
像VINS-Mono这样优秀的框架网上优秀的总结实在太多,我写这篇博客仅仅是为了自己将知识点梳理得更加清楚写的,也顺带分享出来和大家交流,先推荐几篇大佬的总结:
VINS技术路线与代码详解
VINS-Mono论文学习与代码解读——目录与参考
VINS-Mono详解.pdf
VINS-mono详细解读
VINS论文推导及代码解析 by 崔华坤
大家大都是相互借阅补充,以我的水平现在也只能是看了别人的总结自己的,不过也多亏了有这么多善意的分享才能这么快地对一个框架能有所了解,不废话了,进入正题
首先为什么要将预积分和后端优化放在一起写呢?这两个其实是很独立的知识点,因为我觉得这两者可以组成其中一条很重要的pipeline,通过这个把这条pipeline理清了就能更好地理解为什么要做预积分以及Vins-Mono的后端有什么不同,当然,预积分的结果不仅仅是给后端优化用的,下面从预积分开始总结。
1. 预积分的理论推导
第一个问题是为什么要预积分呢?
首先积分是肯定需要的,因为IMU的频率通常是远高于Camera的,而IMU获得的是每一时刻的加速度和角速度,我们通过积分获得两帧之间的由IMU测出的位移和旋转变换。那么预积分呢?在世界坐标系下的连续时刻积分是这样计算的:
p
b
k
+
1
w
=
p
b
k
w
+
v
b
k
w
Δ
t
k
+
∬
t
∈
[
k
,
k
+
1
]
[
R
t
w
(
a
^
t
−
b
a
t
)
−
g
w
]
d
t
2
p_{b_{k+1}}^{w}=p_{b_{k}}^{w}+v_{b_{k}}^{w} \Delta t_{k}+\iint_{t \in[k, k+1]}\left[R_{t}^{w}\left(\hat{a}_{t}-b_{a_{t}}\right)-g^{w}\right] d t^{2}
pbk+1w=pbkw+vbkwΔtk+∬t∈[k,k+1][Rtw(a^t−bat)−gw]dt2
v
b
k
+
1
w
=
v
b
k
w
+
∫
t
∈
[
k
,
k
+
1
]
[
R
t
w
(
a
^
t
−
b
a
t
)
−
g
w
]
d
t
(1)
v_{b_{k+1}}^{w}=v_{b_{k}}^{w}+\int_{t \in[k, k+1]}\left[R_{t}^{w}\left(\hat{a}_{t}-b_{a_{t}}\right)-g^{w}\right] dt \tag{1}
vbk+1w=vbkw+∫t∈[k,k+1][Rtw(a^t−bat)−gw]dt(1)
q
b
k
+
1
w
=
q
b
k
w
⊗
∫
t
∈
[
k
,
k
+
1
]
2
Ω
(
ω
^
t
−
b
ω
t
)
q
t
b
k
d
t
q_{b_{k+1}}^{w}=q_{b_{k}}^{w} \otimes \int_{t \in[k, k+1] 2} \Omega\left(\widehat{\omega}_{t}-b_{\omega_{t}}\right) q_{t}^{b_{k}} d t
qbk+1w=qbkw⊗∫t∈[k,k+1]2Ω(ω
t−bωt)qtbkdt其中
Ω
(
w
)
=
[
−
[
w
]
×
w
−
w
T
0
]
,
[
w
]
×
=
[
0
−
w
z
w
y
w
z
0
−
w
x
−
w
y
w
x
0
]
\Omega(w)=\left[\begin{array}{cc}{-[w]_{ \times}} & {w} \\ {-w^{T}} & {0}\end{array}\right],[w]_{ \times}=\left[\begin{array}{ccc}{0} & {-w_{z}} & {w_{y}} \\ {w_{z}} & {0} & {-w_{x}} \\ {-w_{y}} & {w_{x}} & {0}\end{array}\right]
Ω(w)=[−[w]×−wTw0],[w]×=⎣⎡0wz−wy−wz0wxwy−wx0⎦⎤因为我们获得的IMU数据是离散的,所以第一步我们先需要对上述(1)式进行离散化:
p
b
i
+
1
w
=
p
b
i
w
+
v
b
i
w
δ
t
+
1
2
a
^
‾
i
δ
t
2
p_{b_{i+1}}^{w}=p_{b_{i}}^{w}+v_{b_{i}}^{w} \delta t+\frac{1}{2} \overline{\hat{a}}_{i} \delta t^{2}
pbi+1w=pbiw+vbiwδt+21a^iδt2
v
b
i
+
1
w
=
v
b
i
w
+
a
^
‾
i
δ
t
(2)
v_{b_{i+1}}^{w}=v_{b_{i}}^{w}+\overline{\hat{a}}_{i} \delta t \tag{2}
vbi+1w=vbiw+a^iδt(2)
q
b
i
+
1
w
=
q
b
i
w
⊗
[
1
1
2
ω
^
i
δ
t
]
q_{b_{i+1}}^{w}=q_{b_{i}}^{w} \otimes\left[\begin{array}{cc}{1} \\ {\frac{1}{2} \widehat{\omega}_{i} \delta t}\end{array}\right]
qbi+1w=qbiw⊗[121ω
iδt]
其中,运用了中值积分有
a
^
‾
i
=
1
2
[
q
i
(
a
^
i
−
b
a
i
)
−
g
w
+
q
i
+
1
(
a
^
i
+
1
−
b
a
i
)
−
g
w
]
\overline{\hat{a}}_{i}=\frac{1}{2}\left[q_{i}\left(\hat{a}_{i}-b_{a_{i}}\right)-g^{w}+q_{i+1}\left(\hat{a}_{i+1}-b_{a_{i}}\right)-g^{w}\right]
a^i=21[qi(a^i−bai)−gw+qi+1(a^i+1−bai)−gw]
ω
^
‾
i
=
1
2
(
ω
^
i
+
ω
^
i
+
1
)
−
b
ω
i
{\overline{\hat{\omega}}}_{i}=\frac{1}{2}\left(\widehat{\omega}_{i}+\widehat{\omega}_{i+1}\right)-b_{\omega_{i}}
ω^i=21(ω
i+ω
i+1)−bωi
这样会遇到一个问题就是假如我们通过后端优化使得
p
b
k
w
p_{b_{k}}^{w}
pbkw和
v
b
k
w
v_{b_{k}}^{w}
vbkw等发生了变化,那么
p
b
k
+
1
w
p_{b_{k+1}}^{w}
pbk+1w我们就需要重新按照上述公式积分一次,如下图所示
这里需要先剧透下我们的后端优化里面的代价函数是包括两帧之间的残差量,以位置为例,就是状态量
p
b
k
w
p_{b_{k}}^{w}
pbkw和
p
b
k
+
1
w
p_{b_{k+1}}^{w}
pbk+1w以及IMU积分出来的结果之间的差,如果我们想获得这个位置差,我们如果按照(2)式计算,我们需要计算上图第一幅图中红色箭头的每一个位置的状态量,这是我们不愿意发生的,因此我们引入了预积分,它的作用就是避免重复积分,将IMU预积分的参考坐标系改为前一帧体坐标系,从而获得两帧之间的相对运动,也就是第二幅图中红色箭头之间的量。
下面就按照正常的推导走一遍,然后我最后会对应着代码解释推导的结果:
既然我们要避免第
b
k
b_k
bk帧的影响,我们就需要以
b
k
b_k
bk帧为参考系,如下:
R
w
b
k
p
b
k
+
1
w
=
R
w
b
k
(
p
b
k
w
+
v
b
k
w
Δ
t
k
−
1
2
g
w
Δ
t
k
2
)
+
α
b
k
+
1
b
k
R_{w}^{b_{k}} p_{b_{k+1}}^{w}=R_{w}^{b_{k}}\left(p_{b_{k}}^{w}+v_{b_{k}}^{w} \Delta t_{k}-\frac{1}{2} g^{w} \Delta t_{k}^{2}\right)+\alpha_{b_{k+1}}^{b_{k}}
Rwbkpbk+1w=Rwbk(pbkw+vbkwΔtk−21gwΔtk2)+αbk+1bk
R
w
b
k
v
b
k
+
1
w
=
R
w
b
k
(
v
b
k
w
−
g
w
Δ
t
k
)
+
β
b
k
+
1
b
k
(3)
R_{w}^{b_{k}} v_{b_{k+1}}^{w}=R_{w}^{b_{k}}\left(v_{b_{k}}^{w}-g^{w} \Delta t_{k}\right)+\beta_{b_{k+1}}^{b_{k}}\tag{3}
Rwbkvbk+1w=Rwbk(vbkw−gwΔtk)+βbk+1bk(3)
q
w
b
k
⊗
q
b
k
+
1
w
=
γ
b
k
+
1
b
k
q_{w}^{b_{k}} \otimes q_{b_{k+1}}^{w}=\gamma_{b_{k+1}}^{b_{k}}
qwbk⊗qbk+1w=γbk+1bk其中
α
b
k
+
1
b
k
=
∬
t
∈
[
k
,
k
+
1
]
[
R
t
b
k
(
a
^
t
−
b
a
t
)
]
d
t
2
\alpha_{b_{k+1}}^{b_{k}}=\iint_{t \in[k, k+1]}\left[R_{t}^{b_{k}}\left(\hat{a}_{t}-b_{a_{t}}\right)\right] d t^{2}
αbk+1bk=∬t∈[k,k+1][Rtbk(a^t−bat)]dt2
β
b
k
+
1
b
k
=
∫
t
∈
[
k
,
k
+
1
]
[
R
t
b
k
(
a
^
t
−
b
a
t
)
]
d
t
(4)
\beta_{b_{k+1}}^{b_{k}}=\int_{t \in[k, k+1]}\left[R_{t}^{b_{k}}\left(\hat{a}_{t}-b_{a_{t}}\right)\right] d t\tag{4}
βbk+1bk=∫t∈[k,k+1][Rtbk(a^t−bat)]dt(4)
γ
b
k
+
1
b
k
=
∫
t
∈
[
k
,
k
+
1
]
1
2
Ω
(
ω
^
t
−
b
ω
t
)
γ
t
b
k
d
t
\gamma_{b_{k+1}}^{b_{k}}=\int_{t \in[k, k+1]} \frac{1}{2} \Omega\left(\hat{\omega}_{t}-b_{\omega_{t}}\right) \gamma_{t}^{b_{k}} d t
γbk+1bk=∫t∈[k,k+1]21Ω(ω^t−bωt)γtbkdt上述(4)式就是我们需要的预积分公式了,但是它是连续形式下的,因此我们将其离散化:
α
^
i
+
1
b
k
=
α
^
i
b
k
+
β
^
i
b
k
δ
t
+
1
2
a
^
‾
i
′
δ
t
2
\hat{\alpha}_{i+1}^{b_{k}}=\hat{\alpha}_{i}^{b_{k}}+\hat{\beta}_{i}^{b_{k}} \delta t+\frac{1}{2} \overline{\hat{a}}_{i}^{\prime} \delta t^{2}
α^i+1bk=α^ibk+β^ibkδt+21a^i′δt2
β
^
i
+
1
b
k
=
β
^
i
b
k
+
a
^
‾
i
′
δ
t
(5)
\hat{\beta}_{i+1}^{b_{k}}=\hat{\beta}_{i}^{b_{k}}+\overline{\hat{a}}_{i}^{\prime} \delta t\tag{5}
β^i+1bk=β^ibk+a^i′δt(5)
γ
^
i
+
1
b
k
=
γ
^
i
b
k
⊗
γ
^
i
+
1
i
=
γ
^
i
b
k
⊗
[
1
1
2
ω
^
‾
i
′
δ
t
]
\hat{\gamma}_{i+1}^{b_{k}}=\hat{\gamma}_{i}^{b_{k}} \otimes \hat{\gamma}_{i+1}^{i}=\hat{\gamma}_{i}^{b_{k}} \otimes\left[\begin{array}{c}{1} \\ {\frac{1}{2} \overline{\hat \omega}_{i}^{\prime} \delta t}\end{array}\right]
γ^i+1bk=γ^ibk⊗γ^i+1i=γ^ibk⊗[121ω^i′δt]其中
a
^
‾
i
′
=
1
2
[
q
i
(
a
^
i
−
b
a
i
)
+
q
i
+
1
(
a
^
i
+
1
−
b
a
i
)
]
\overline{\hat{a}}_{i}^{\prime}=\frac{1}{2}\left[q_{i}\left(\hat{a}_{i}-b_{a_{i}}\right)+q_{i+1}\left(\hat{a}_{i+1}-b_{a_{i}}\right)\right]
a^i′=21[qi(a^i−bai)+qi+1(a^i+1−bai)]
ω
^
‾
i
′
=
1
2
(
ω
^
i
+
ω
^
i
+
1
)
−
b
ω
i
\overline{\hat{\omega}}_{i}^{\prime}=\frac{1}{2}\left(\widehat{\omega}_{i}+\widehat{\omega}_{i+1}\right)-b_{\omega_{i}}
ω^i′=21(ω
i+ω
i+1)−bωi这里值得注意的是,以位置变化为例,(5)中的
α
^
i
+
1
b
k
\hat{\alpha}_{i+1}^{b_{k}}
α^i+1bk和(6)中的
α
b
k
+
1
b
k
\alpha_{b_{k+1}}^{b_{k}}
αbk+1bk并不是一回事,当然,下标不一样可以忽略,主要不一样的地方是(6)中的
α
b
k
+
1
b
k
\alpha_{b_{k+1}}^{b_{k}}
αbk+1bk计算是加速度计和陀螺仪的Bias是实时在变化的,而(5)中的
α
^
i
+
1
b
k
\hat{\alpha}_{i+1}^{b_{k}}
α^i+1bk计算时默认是不变的,在等下的源码里可以看出来积分计算时用的Bias固定是是
b
k
b_k
bk帧时的,而不是实时变化的,因此会存在一个和Bias相关的误差如下:
α
b
k
+
1
b
k
≈
α
^
b
k
+
1
b
k
+
J
b
a
α
δ
b
a
+
J
b
ω
α
δ
b
ω
\alpha_{b_{k+1}}^{b_{k}} \approx \hat{\alpha}_{b_{k+1}}^{b_{k}}+J_{b_{a}}^{\alpha} \delta b_{a}+J_{b_{\omega}}^{\alpha} \delta b_{\omega}
αbk+1bk≈α^bk+1bk+Jbaαδba+Jbωαδbω
β
b
k
+
1
b
k
≈
β
^
b
k
+
1
b
k
+
J
b
a
β
δ
b
a
+
J
b
ω
β
δ
b
ω
(6)
\beta_{b_{k+1}}^{b_{k}} \approx \hat{\beta}_{b_{k+1}}^{b_{k}}+J_{b_{a}}^{\beta} \delta b_{a}+J_{b_{\omega}}^{\beta} \delta b_{\omega}\tag{6}
βbk+1bk≈β^bk+1bk+Jbaβδba+Jbωβδbω(6)
γ
b
k
+
1
b
k
≈
γ
^
b
k
+
1
b
k
⊗
[
1
1
2
J
b
ω
γ
δ
b
ω
]
\gamma_{b_{k+1}}^{b_{k}} \approx \hat{\gamma}_{b_{k+1}}^{b_{k}} \otimes\left[\begin{array}{cc}{1} \\ {\frac{1}{2} J_{b_{\omega}}^{\gamma} \delta b_{\omega}}\end{array}\right]
γbk+1bk≈γ^bk+1bk⊗[121Jbωγδbω]
那么上式中的
J
b
a
α
J_{b_{a}}^{\alpha}
Jbaα,
J
b
ω
α
J_{b_{\omega}}^{\alpha}
Jbωα,
J
b
a
β
J_{b_{a}}^{\beta}
Jbaβ,
J
b
ω
β
J_{b_{\omega}}^{\beta}
Jbωβ怎么计算呢,接下来进一步推导
---------------------我是分割线----------------------下面就是怎么推导误差了------------------------
在上面的推导中引入误差的不仅仅是将变换的Bias视为不变,还存在加速度计和陀螺仪的噪声,同时位置、速度和角度之间的误差也会相互影响,并且误差是随时间一直累计的,因此,我们直接给出误差的状态方程,也就是误差变化的情况,通过误差的变化以及上一时刻累计的误差我们就可以求得下一时刻的误差:
[
δ
α
˙
t
b
k
δ
β
˙
t
b
k
δ
θ
˙
t
b
k
δ
b
˙
a
t
δ
b
˙
w
i
]
=
[
0
I
0
0
0
0
0
−
R
^
t
b
k
[
a
t
−
b
^
a
i
]
×
−
R
^
t
b
k
0
0
0
−
[
w
t
−
b
^
w
i
]
×
0
−
I
0
0
0
0
0
0
0
0
0
0
]
[
δ
α
t
b
k
δ
β
t
b
k
δ
θ
t
b
k
δ
b
a
t
δ
b
w
i
]
+
[
0
0
0
0
−
R
^
t
b
k
0
0
0
0
−
I
0
0
0
0
I
0
0
0
0
I
]
[
n
a
n
w
n
b
a
n
b
w
]
=
F
t
δ
z
t
b
k
+
G
t
n
t
\begin{aligned}\left[\begin{array}{c}{\delta \dot{\alpha}_{t}^{b_{k}}} \\ {\delta \dot{\beta}_{t}^{b_{k}}} \\ {\delta \dot{\theta}_{t}^{b_{k}}} \\ {\delta \dot{b}_{a_{t}}} \\ {\delta \dot{b}_{w_{i}}}\end{array}\right]&=\left[\begin{array}{ccccc}{0} & {I} & {0} & {0} & {0} \\ {0} & {0} & {-\hat{R}_{t}^{b_{k}}\left[a_{t}-\hat{b}_{a_{i}}\right]_{\times}} & {-\hat{R}_{t}^{b_{k}}} & {0} \\ {0} & {0} & {-\left[w_{t}-\hat{b}_{w_{i}}\right]_{\times}} & {0} & {-I} \\ {0} & {0} & {0} & {0} & {0} \\ {0} & {0} & {0} & {0} & {0}\end{array}\right]\left[\begin{array}{c}{\delta \alpha_{t}^{b_{k}}} \\ {\delta \beta_{t}^{b_{k}}} \\ {\delta \theta_{t}^{b_{k}}} \\ {\delta b_{a_{t}}} \\ {\delta b_{w_{i}}}\end{array}\right]+\left[\begin{array}{cccc}{0} & {0} & {0} & {0} \\ {-\hat{R}_{t}^{b_{k}}} & {0} & {0} & {0} \\ {0} & {-I} & {0} & {0} \\ {0} & {0} & {I} & {0} \\ {0} & {0} & {0} & {I}\end{array}\right]\left[\begin{array}{c}{n_{a}} \\ {n_{w}} \\ {n_{b_{a}}} \\ {n_{b_{w}}}\end{array}\right]\\&=F_{t} \delta z_{t}^{b_{k}}+G_{t} n_{t}\end{aligned}
⎣⎢⎢⎢⎢⎡δα˙tbkδβ˙tbkδθ˙tbkδb˙atδb˙wi⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎡00000I00000−R^tbk[at−b^ai]×−[wt−b^wi]×000−R^tbk00000−I00⎦⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎡δαtbkδβtbkδθtbkδbatδbwi⎦⎥⎥⎥⎥⎤+⎣⎢⎢⎢⎢⎡0−R^tbk00000−I00000I00000I⎦⎥⎥⎥⎥⎤⎣⎢⎢⎡nanwnbanbw⎦⎥⎥⎤=Ftδztbk+Gtnt那么根据导数的定义有上一时刻推导下一时刻的误差如下:
δ
z
t
+
δ
t
b
k
=
δ
z
t
b
k
+
δ
z
˙
t
b
k
δ
t
=
(
I
+
F
t
δ
t
)
δ
z
t
b
k
+
(
G
t
δ
t
)
n
t
=
F
δ
z
t
b
k
+
V
n
t
(7)
\delta z_{t+\delta t}^{b_{k}}=\delta z_{t}^{b_{k}}+\delta \dot{z}_{t}^{b_{k}} \delta t=\left(\mathrm{I}+F_{t} \delta t\right) \delta z_{t}^{b_{k}}+\left(G_{t} \delta t\right) n_{t}=F \delta z_{t}^{b_{k}}+V n_{t}\tag{7}
δzt+δtbk=δztbk+δz˙tbkδt=(I+Ftδt)δztbk+(Gtδt)nt=Fδztbk+Vnt(7)类比EKF的运动转移方程(其实也是把一个原本非线性的方程线性化),我们类似给出误差的协方差变化:
P
t
+
δ
t
b
k
=
(
1
+
F
t
δ
t
)
P
t
b
k
(
1
+
F
t
δ
t
)
T
+
(
G
t
δ
t
)
Q
(
G
t
δ
t
)
T
(8)
P_{t+\delta t}^{b k}=\left(1+F_{t} \delta t\right) P_{t}^{b_{k}}\left(1+F_{t} \delta t\right)^{T}+\left(G_{t} \delta t\right) Q\left(G_{t} \delta t\right)^{T}\tag{8}
Pt+δtbk=(1+Ftδt)Ptbk(1+Ftδt)T+(Gtδt)Q(Gtδt)T(8)因为上面的方程是线性方程,那么我们的状态转移方程关于
δ
z
t
\delta z_{t}
δzt的雅克比矩阵也可以递推出来:
J
t
+
δ
t
=
(
I
+
F
t
δ
t
)
J
t
(9)
J_{t+\delta t}=\left(\mathrm{I}+F_{t} \delta t\right) J_{t}\tag{9}
Jt+δt=(I+Ftδt)Jt(9)上面的公式都是在连续时间推导的(t嘛)所以我们下面对应进行离散化一下:
(7)式离散形式为:
[
δ
α
k
+
1
δ
θ
k
+
1
δ
β
k
+
1
δ
b
a
k
+
1
δ
b
w
+
1
]
=
[
I
f
01
δ
t
f
03
f
04
0
f
11
0
0
−
δ
t
0
f
21
I
f
23
f
24
0
0
0
I
0
0
0
0
0
I
]
[
δ
α
k
δ
θ
k
δ
β
a
δ
b
a
k
δ
b
w
k
]
+
[
v
00
v
01
v
02
v
03
0
0
0
−
δ
t
2
0
−
δ
t
2
0
0
−
R
k
δ
t
2
v
21
−
R
k
+
1
δ
t
2
v
23
0
0
0
0
0
0
δ
t
0
0
0
0
0
0
δ
t
]
[
n
a
k
n
w
k
n
a
k
+
1
n
w
k
+
1
n
b
a
n
b
w
]
\left[\begin{array}{c}{\delta \alpha_{k+1}} \\ {\delta \theta_{k+1}} \\ {\delta \beta_{k+1}} \\ {\delta b_{a_{k+1}}} \\ {\delta b_{w+1}}\end{array}\right]=\left[\begin{array}{ccccc}{I} & {f_{01}} & {\delta t} & {f_{03}} & {f_{04}} \\ {0} & {f_{11}} & {0} & {0} & {-\delta t} \\ {0} & {f_{21}} & {I} & {f_{23}} & {f_{24}} \\ {0} & {0} & {0} & {I} & {0} \\ {0} & {0} & {0} & {0} & {I}\end{array}\right]\left[\begin{array}{c}{\delta \alpha_{k}} \\ {\delta \theta_{k}} \\ {\delta \beta_{a}} \\ {\delta b_{a_{k}}} \\ {\delta b_{w_{k}}}\end{array}\right]+\left[\begin{array}{ccccc}{v_{00}} & {v_{01}} & {v_{02}} & {v_{03}} & {0} & {0} \\ {0} & {\frac{-\delta t}{2}} & {0} & {\frac{-\delta t}{2}} & {0} & {0} \\ {-\frac{R_{k} \delta t}{2}} & {v_{21}} & {-\frac{R_{k+1} \delta t}{2}} & {v_{23}} & {0}& {0} \\ {0} & {0} & {0} & {0} & {\delta t} & {0} \\ {0} & {0} & {0} & {0} & {0} & {\delta t}\end{array}\right]\left[\begin{array}{c}{n_{a_{k}}} \\ {n_{w_{k}}} \\n_{a_{k+1}}\\ {n_{w_{k+1}}} \\ {n_{b_{a}}} \\ {n_{b_{w}}}\end{array}\right]
⎣⎢⎢⎢⎢⎡δαk+1δθk+1δβk+1δbak+1δbw+1⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡I0000f01f11f2100δt0I00f030f23I0f04−δtf240I⎦⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎡δαkδθkδβaδbakδbwk⎦⎥⎥⎥⎥⎤+⎣⎢⎢⎢⎢⎡v000−2Rkδt00v012−δtv2100v020−2Rk+1δt00v032−δtv2300000δt00000δt⎦⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎡naknwknak+1nwk+1nbanbw⎦⎥⎥⎥⎥⎥⎥⎤其中
f
01
=
δ
t
2
f
21
=
−
1
4
R
k
(
a
^
k
−
b
a
k
)
∧
δ
t
2
−
1
4
R
k
+
1
(
a
^
k
+
1
−
b
a
k
)
∧
[
I
−
(
ω
^
k
+
ω
^
k
+
1
2
−
b
ω
k
)
∧
δ
t
]
δ
t
2
f
03
=
−
1
4
(
R
k
+
R
k
+
1
)
δ
t
2
f
04
=
δ
t
2
f
24
=
1
4
R
k
+
1
(
a
^
k
+
1
−
b
a
k
)
∧
δ
t
3
f
11
=
I
−
(
ω
^
k
+
ω
^
k
+
1
2
−
b
ω
k
)
∧
δ
t
f
21
=
−
1
2
R
k
(
a
^
k
−
b
a
k
)
∧
δ
t
−
1
2
R
k
+
1
(
a
^
k
+
1
−
b
a
k
)
∧
[
I
−
(
ω
^
k
+
ω
^
k
+
1
2
−
b
ω
k
)
∧
δ
t
]
δ
t
f
23
=
−
1
2
(
R
k
+
R
k
+
1
)
δ
t
f
24
=
1
2
R
k
+
1
(
a
^
k
+
1
−
b
a
k
)
∧
δ
t
2
v
00
=
−
1
4
R
k
δ
t
2
v
01
=
v
03
=
δ
t
2
v
21
=
1
4
R
k
+
1
(
a
^
k
+
1
−
b
a
k
)
∧
δ
t
2
δ
t
2
v
02
=
−
1
4
R
k
+
1
δ
t
2
v
21
=
v
23
=
1
4
R
k
+
1
(
a
^
k
+
1
−
b
a
k
)
Λ
δ
t
2
\begin{aligned} &f_{01}=\frac{\delta t}{2} f_{21}=-\frac{1}{4} R_{k}\left(\hat{a}_{k}-b_{a_{k}}\right)^{\wedge} \delta t^{2}-\frac{1}{4} R_{k+1}\left(\hat{a}_{k+1}-b_{a_{k}}\right)^{\wedge}\left[I-\left(\frac{\widehat{\omega}_{k}+\widehat{\omega}_{k+1}}{2}-b_{\omega_{k}}\right)^{\wedge} \delta t\right] \delta t^{2} \\&f_{03}=-\frac{1}{4}\left(R_{k}+R_{k+1}\right) \delta t^{2} \\&f_{04}=\frac{\delta t}{2} f_{24}=\frac{1}{4} R_{k+1}\left(\hat{a}_{k+1}-b_{a_{k}}\right)^{\wedge} \delta t^{3} \\&f_{11}=I-\left(\frac{\widehat{\omega}_{k}+\widehat{\omega}_{k+1}}{2}-b_{\omega_{k}}\right)^{\wedge} \delta t \\&f_{21}=-\frac{1}{2} R_{k}\left(\hat{a}_{k}-b_{a_{k}}\right)^{\wedge} \delta t-\frac{1}{2} R_{k+1}\left(\hat{a}_{k+1}-b_{a_{k}}\right)^{\wedge}\left[I-\left(\frac{\widehat{\omega}_{k}+\widehat{\omega}_{k+1}}{2}-b_{\omega_{k}}\right)^{\wedge} \delta t\right] \delta t \\&f_{23}=-\frac{1}{2}\left(R_{k}+R_{k+1}\right) \delta t \\&f_{24}=\frac{1}{2} R_{k+1}\left(\hat{a}_{k+1}-b_{a_{k}}\right)^{\wedge} \delta t^{2} \\&v_{00}=-\frac{1}{4} R_{k} \delta t^{2} \\&v_{01}=v_{03}=\frac{\delta t}{2} v_{21}=\frac{1}{4} R_{k+1}\left(\hat{a}_{k+1}-b_{a_{k}}\right)^{\wedge} \delta t^{2} \frac{\delta t}{2} \\&v_{02}=-\frac{1}{4} R_{k+1} \delta t^{2} \\&v_{21}=v_{23}=\frac{1}{4} R_{k+1}\left(\hat{a}_{k+1}-b_{a_{k}}\right)^{\Lambda} \delta t^{2} \end{aligned}
f01=2δtf21=−41Rk(a^k−bak)∧δt2−41Rk+1(a^k+1−bak)∧[I−(2ω
k+ω
k+1−bωk)∧δt]δt2f03=−41(Rk+Rk+1)δt2f04=2δtf24=41Rk+1(a^k+1−bak)∧δt3f11=I−(2ω
k+ω
k+1−bωk)∧δtf21=−21Rk(a^k−bak)∧δt−21Rk+1(a^k+1−bak)∧[I−(2ω
k+ω
k+1−bωk)∧δt]δtf23=−21(Rk+Rk+1)δtf24=21Rk+1(a^k+1−bak)∧δt2v00=−41Rkδt2v01=v03=2δtv21=41Rk+1(a^k+1−bak)∧δt22δtv02=−41Rk+1δt2v21=v23=41Rk+1(a^k+1−bak)Λδt2
可以简写为
δ
z
k
+
1
15
×
1
=
F
15
×
15
δ
z
k
15
×
1
+
V
15
×
18
Q
18
×
1
(10)
\delta z_{k+1}^{15 \times 1}=F^{15 \times 15} \delta z_{k}^{15 \times 1}+V^{15 \times 18} Q^{18 \times 1}\tag{10}
δzk+115×1=F15×15δzk15×1+V15×18Q18×1(10)
(8)式的离散形式如下:
P
k
+
1
15
×
15
=
F
P
k
F
T
+
V
Q
V
T
(11)
P_{k+1}^{15 \times 15}=F P_{k} F^{T}+V Q V^{T}\tag{11}
Pk+115×15=FPkFT+VQVT(11)其中
Q
18
×
18
=
[
σ
a
2
0
0
0
0
0
0
σ
w
2
0
0
0
0
0
0
σ
a
2
0
0
0
0
0
0
σ
w
2
0
0
0
0
0
0
σ
b
a
2
0
0
0
0
0
0
σ
b
w
2
]
Q^{18 \times 18}=\left[\begin{array}{ccccc}{\sigma_{a}^{2}} & {0} & {0} & {0} & {0} & {0} \\ {0} & {\sigma_{w}^{2}} & {0} & {0} & {0} & {0} \\ {0} & {0} & {\sigma_{a}^{2}} & {0} & {0} & {0} \\ {0} & {0} & {0} & {\sigma_{w}^{2}} & {0} & {0} \\ {0} & {0} & {0} & {0} & {\sigma_{b_{a}}^{2}} & {0} \\ {0} & {0} & {0} & {0} & {0} & {\sigma_{b_{w}}^{2}}\end{array}\right]
Q18×18=⎣⎢⎢⎢⎢⎢⎢⎡σa2000000σw2000000σa2000000σw2000000σba2000000σbw2⎦⎥⎥⎥⎥⎥⎥⎤
(9)式的离散形式如下: J k + 1 15 × 15 = F J k (12) J_{k+1}^{15 \times 15}=F J_{k}\tag{12} Jk+115×15=FJk(12)上面我仅仅是给出了结果,从大流程上走了一遍(全部写一遍太费时间了),如果想知道这些公式是怎么给出的,可以参考VINS-Mono详解.pdf或者崔华坤老师的推导。
2. 预积分的代码分析
和预积分相关的代码主要在estimator_node.cpp、estimator.cpp和intergration_base.h中,三个文件中分别各有一段:
(1)在estimator_node.cpp中的pridict函数中
Eigen::Vector3d un_acc_0 = tmp_Q * (acc_0 - tmp_Ba) - estimator.g;
Eigen::Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - tmp_Bg;
tmp_Q = tmp_Q * Utility::deltaQ(un_gyr * dt);
Eigen::Vector3d un_acc_1 = tmp_Q * (linear_acceleration - tmp_Ba) - estimator.g;
Eigen::Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
tmp_P = tmp_P + dt * tmp_V + 0.5 * dt * dt * un_acc;
tmp_V = tmp_V + dt * un_acc;
这一段并不是预积分公式,只是和预积分相关的一部分,这一部分对应的是上面的公式(2),而且是在tmp_P、tmp_V、tmp_Q是在非线性优化之后获得的世界坐标系下准确的位置、速度和位姿。
(2)在estimator.cpp中的processIMU函数中
Vector3d un_acc_0 = Rs[j] * (acc_0 - Bas[j]) - g;
Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - Bgs[j];
Rs[j] *= Utility::deltaQ(un_gyr * dt).toRotationMatrix();
Vector3d un_acc_1 = Rs[j] * (linear_acceleration - Bas[j]) - g;
Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
Ps[j] += dt * Vs[j] + 0.5 * dt * dt * un_acc;
Vs[j] += dt * un_acc;
这里也是对应上面公式(2),但是这里的Ps[j]、 Vs[j]、Rs[j]是未经后端优化之前的位置、速度和位姿。
(3)在integration_base.h中的midPointIntegration函数中
Vector3d un_acc_0 = delta_q * (_acc_0 - linearized_ba);
Vector3d un_gyr = 0.5 * (_gyr_0 + _gyr_1) - linearized_bg;
result_delta_q = delta_q * Quaterniond(1, un_gyr(0) * _dt / 2, un_gyr(1) * _dt / 2, un_gyr(2) * _dt / 2);
Vector3d un_acc_1 = result_delta_q * (_acc_1 - linearized_ba);
Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
result_delta_p = delta_p + delta_v * _dt + 0.5 * un_acc * _dt * _dt;
result_delta_v = delta_v + un_acc * _dt;
这里对应上面的公式(5),这里的result_delta_p、result_delta_v和result_delta_q就是相对于
b
k
b_k
bk帧的预积分,对应预计分类IntergrationBase类中delta_p、delta_v、delta_q三个全局变量,每来一帧新图像或者滑动一次窗口时就会新创建一个预计分,创建时预积分中的delta_p、delta_v、delta_q中这三个量都设为零,然后在获得新的IMU值之后不断积分。
同样在midPointIntegration函数中,在上面预积分完成之后,马上会更新相关的雅克比矩阵和协方差矩阵:
MatrixXd F = MatrixXd::Zero(15, 15);//这里可以看出来F是一个15*15的矩阵
F.block<3, 3>(0, 0) = Matrix3d::Identity();
F.block<3, 3>(0, 3) = -0.25 * delta_q.toRotationMatrix() * R_a_0_x * _dt * _dt +
-0.25 * result_delta_q.toRotationMatrix() * R_a_1_x * (Matrix3d::Identity() - R_w_x * _dt) * _dt * _dt;
F.block<3, 3>(0, 6) = MatrixXd::Identity(3,3) * _dt;
F.block<3, 3>(0, 9) = -0.25 * (delta_q.toRotationMatrix() + result_delta_q.toRotationMatrix()) * _dt * _dt;
F.block<3, 3>(0, 12) = -0.25 * result_delta_q.toRotationMatrix() * R_a_1_x * _dt * _dt * -_dt;
F.block<3, 3>(3, 3) = Matrix3d::Identity() - R_w_x * _dt;
F.block<3, 3>(3, 12) = -1.0 * MatrixXd::Identity(3,3) * _dt;
F.block<3, 3>(6, 3) = -0.5 * delta_q.toRotationMatrix() * R_a_0_x * _dt +
-0.5 * result_delta_q.toRotationMatrix() * R_a_1_x * (Matrix3d::Identity() - R_w_x * _dt) * _dt;
F.block<3, 3>(6, 6) = Matrix3d::Identity();
F.block<3, 3>(6, 9) = -0.5 * (delta_q.toRotationMatrix() + result_delta_q.toRotationMatrix()) * _dt;
F.block<3, 3>(6, 12) = -0.5 * result_delta_q.toRotationMatrix() * R_a_1_x * _dt * -_dt;
F.block<3, 3>(9, 9) = Matrix3d::Identity();
F.block<3, 3>(12, 12) = Matrix3d::Identity();
MatrixXd V = MatrixXd::Zero(15,18);//V是一个15*18的矩阵
V.block<3, 3>(0, 0) = 0.25 * delta_q.toRotationMatrix() * _dt * _dt;
V.block<3, 3>(0, 3) = 0.25 * -result_delta_q.toRotationMatrix() * R_a_1_x * _dt * _dt * 0.5 * _dt;
V.block<3, 3>(0, 6) = 0.25 * result_delta_q.toRotationMatrix() * _dt * _dt;
V.block<3, 3>(0, 9) = V.block<3, 3>(0, 3);
V.block<3, 3>(3, 3) = 0.5 * MatrixXd::Identity(3,3) * _dt;
V.block<3, 3>(3, 9) = 0.5 * MatrixXd::Identity(3,3) * _dt;
V.block<3, 3>(6, 0) = 0.5 * delta_q.toRotationMatrix() * _dt;
V.block<3, 3>(6, 3) = 0.5 * -result_delta_q.toRotationMatrix() * R_a_1_x * _dt * 0.5 * _dt;
V.block<3, 3>(6, 6) = 0.5 * result_delta_q.toRotationMatrix() * _dt;
V.block<3, 3>(6, 9) = V.block<3, 3>(6, 3);
V.block<3, 3>(9, 12) = MatrixXd::Identity(3,3) * _dt;
V.block<3, 3>(12, 15) = MatrixXd::Identity(3,3) * _dt;
jacobian = F * jacobian;
covariance = F * covariance * F.transpose() + V * noise * V.transpose();
对应上述推导中的公式(11) 和 公式(12),但是并没有计算公式(10),这是应为,通过预积分误差的递推公式确实可以求出来从 b k b_k bk帧到 b k + 1 b_{k+1} bk+1帧的累积出来的误差大小,但是这个值并没有用,我们在后端需要的是误差对 b k b_k bk帧和 b k + 1 b_{k+1} bk+1帧的导数,而误差对 b k b_k bk帧的导数却可以通过公式(12)的雅克比矩阵直接计算出来,至于协方差矩阵是我们进行后端优化计算残差时用马氏距离需要用到的,这里提到了那么多和后端有关的东西,现在就顺着来总结下后端优化相关的知识
3. 后端优化IMU部分
(1)残差定义
这里仅仅总结下和预积分先关的后端优化IMU部分,完整的后端优化等把Marginization搞明白之后再总结,IMU定义的残差是两帧之间的位置、速度、旋转以及Bias变换量的差(就是通过公式(2)积分出来的世界坐标系下 b k b_k bk和 b k + 1 b_{k+1} bk+1之间的变换和预积分所求的变换之间的差),如下: r B 15 × 1 ( z ^ b k + 1 ′ b k , X ) = [ δ α b k + 1 b k δ θ b k + 1 b k δ β b k + 1 b k δ b a δ b g ] = [ R w b k ( p b k + 1 w − p b k w − v b k w Δ t k + 1 2 g w Δ t k 2 ) − α b k + 1 b k 2 [ γ b k + 1 b k − 1 ⊗ q b k w − 1 ⊗ q b k + 1 w ] x y z R w b k ( v b k + 1 w − v b k w + g w Δ t k ) − β b k + 1 b k b a b k + 1 − b a k b ω k + 1 − b ω b k ] (13) r_{B}^{15 \times 1}\left(\hat{z}_{b_{k+1}^{\prime}}^{b_{k}}, X\right)=\left[\begin{array}{c}{\delta \alpha_{b_{k+1}}^{b_{k}}} \\ {\delta \theta_{b_{k+1}}^{b_{k}}} \\ {\delta \beta_{b_{k+1}}^{b_{k}}} \\ {\delta b_{a}} \\ {\delta b_{g}}\end{array}\right] =\left[\begin{array}{c}{R_{w}^{b_{k}}\left(p_{b_{k+1}}^{w}-p_{b_{k}}^{w}-v_{b_{k}}^{w} \Delta t_{k}+\frac{1}{2} g^{w} \Delta t_{k}^{2}\right)-\alpha_{b_{k+1}}^{b_{k}}} \\ {2\left[\gamma_{b_{k+1}}^{b_{k}-1} \otimes q_{b_{k}}^{w-1} \otimes q_{b_{k+1}}^{w}\right]_{x y z}}\\\begin{array}{c}{R_{w}^{b_{k}}\left(v_{b_{k+1}}^{w}-v_{b_{k}}^{w}+g^{w} \Delta t_{k}\right)-\beta_{b_{k+1}}^{b_{k}}} \\ {b_{a_{b_{k+1}}}-b_{a_{k}}} \\ {b_{\omega_{k+1}}-b_{\omega_{b_{k}}}}\end{array} \end{array}\right]\tag{13} rB15×1(z^bk+1′bk,X)=⎣⎢⎢⎢⎢⎢⎡δαbk+1bkδθbk+1bkδβbk+1bkδbaδbg⎦⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎡Rwbk(pbk+1w−pbkw−vbkwΔtk+21gwΔtk2)−αbk+1bk2[γbk+1bk−1⊗qbkw−1⊗qbk+1w]xyzRwbk(vbk+1w−vbkw+gwΔtk)−βbk+1bkbabk+1−bakbωk+1−bωbk⎦⎥⎥⎥⎥⎥⎥⎥⎤(13)这里还有一点需要注意的是,公式中的预积分符号用的是 α b k + 1 b k \alpha_{b_{k+1}}^{b_{k}} αbk+1bk、 β b k + 1 b k \beta_{b_{k+1}}^{b_{k}} βbk+1bk和 γ b k + 1 b k \gamma_{b_{k+1}}^{b_{k}} γbk+1bk,而不是 α ^ b k + 1 b k \hat{\alpha}_{b_{k+1}}^{b_{k}} α^bk+1bk、 β ^ b k + 1 b k \hat{\beta}_{b_{k+1}}^{b_{k}} β^bk+1bk和 γ ^ b k + 1 b k \hat{\gamma}_{b_{k+1}}^{b_{k}} γ^bk+1bk,因此应该用公式(6)中的值,对应代码为integration_base.h里的evaluate函数:
//提取雅克比矩阵
Eigen::Matrix3d dp_dba = jacobian.block<3, 3>(O_P, O_BA);
Eigen::Matrix3d dp_dbg = jacobian.block<3, 3>(O_P, O_BG);
Eigen::Matrix3d dq_dbg = jacobian.block<3, 3>(O_R, O_BG);
Eigen::Matrix3d dv_dba = jacobian.block<3, 3>(O_V, O_BA);
Eigen::Matrix3d dv_dbg = jacobian.block<3, 3>(O_V, O_BG);
Eigen::Vector3d dba = Bai - linearized_ba;
Eigen::Vector3d dbg = Bgi - linearized_bg;
//计算修正后的位置、速度和旋转值
Eigen::Quaterniond corrected_delta_q = delta_q * Utility::deltaQ(dq_dbg * dbg);
Eigen::Vector3d corrected_delta_v = delta_v + dv_dba * dba + dv_dbg * dbg;
Eigen::Vector3d corrected_delta_p = delta_p + dp_dba * dba + dp_dbg * dbg;
//计算误差
residuals.block<3, 1>(O_P, 0) = Qi.inverse() * (0.5 * G * sum_dt * sum_dt + Pj - Pi - Vi * sum_dt) - corrected_delta_p;
residuals.block<3, 1>(O_R, 0) = 2 * (corrected_delta_q.inverse() * (Qi.inverse() * Qj)).vec();
residuals.block<3, 1>(O_V, 0) = Qi.inverse() * (G * sum_dt + Vj - Vi) - corrected_delta_v;
residuals.block<3, 1>(O_BA, 0) = Baj - Bai;
residuals.block<3, 1>(O_BG, 0) = Bgj - Bgi;
(2)优化变量
与
b
k
b_k
bk和
b
k
+
1
b_{k+1}
bk+1帧之间残差有关的变量如下:
[
p
b
k
w
,
q
b
k
w
]
,
[
v
b
k
w
,
b
a
k
,
b
ω
k
]
,
[
p
b
k
+
1
w
,
q
b
k
+
1
w
]
,
[
v
b
k
+
1
w
,
b
a
k
+
1
,
b
ω
k
+
1
]
\left[p_{b_{k}}^{w}, q_{b_{k}}^{w}\right],\left[v_{b_{k}}^{w}, b_{a_{k}}, b_{\omega_{k}}\right], \quad\left[p_{b_{k+1}}^{w}, q_{b_{k+1}}^{w}\right],\left[v_{b_{k+1}}^{w}, b_{a_{k+1}}, b_{\omega_{k+1}}\right]
[pbkw,qbkw],[vbkw,bak,bωk],[pbk+1w,qbk+1w],[vbk+1w,bak+1,bωk+1]
(3)雅克比矩阵
这个雅克比矩阵指的是高斯牛顿法求解时需要用到的残差相对于优化变量的雅克比矩阵,对应的代码在imu_factor.h文件的Evaluate函数中,按照上面优化变量的分类,列出公式和代码如下:Cannot read property 'type' of undefined对应代码:
Eigen::Matrix3d dq_dbg = pre_integration->jacobian.template block<3, 3>(O_R, O_BG);
if (jacobians[0])
{
Eigen::Map<Eigen::Matrix<double, 15, 7, Eigen::RowMajor>> jacobian_pose_i(jacobians[0]);
jacobian_pose_i.setZero();
jacobian_pose_i.block<3, 3>(O_P, O_P) = -Qi.inverse().toRotationMatrix();
jacobian_pose_i.block<3, 3>(O_P, O_R) = Utility::skewSymmetric(Qi.inverse() * (0.5 * G * sum_dt * sum_dt + Pj - Pi - Vi * sum_dt));
Eigen::Quaterniond corrected_delta_q = pre_integration->delta_q * Utility::deltaQ(dq_dbg * (Bgi - pre_integration->linearized_bg));
jacobian_pose_i.block<3, 3>(O_R, O_R) = -(Utility::Qleft(Qj.inverse() * Qi) * Utility::Qright(corrected_delta_q)).bottomRightCorner<3, 3>();
jacobian_pose_i.block<3, 3>(O_V, O_R) = Utility::skewSymmetric(Qi.inverse() * (G * sum_dt + Vj - Vi));
jacobian_pose_i = sqrt_info * jacobian_pose_i;
}
J
[
1
]
15
×
9
=
[
∂
r
B
∂
v
b
k
w
,
∂
r
B
∂
b
a
k
,
∂
r
B
∂
b
w
k
]
=
[
−
R
w
b
k
Δ
t
−
J
b
a
α
−
J
b
ω
α
0
0
−
L
[
q
b
k
+
1
w
−
1
⊗
q
b
k
w
⊗
γ
b
k
+
1
b
k
]
J
b
ω
γ
−
R
w
b
k
−
J
b
a
β
−
J
b
ω
β
0
−
I
0
0
0
−
I
]
J[1]^{15 \times 9}=\left[\frac{\partial r_{B}}{\partial v_{b_{k}}^{w}}, \frac{\partial r_{B}}{\partial b_{a_{k}}}, \frac{\partial r_{B}}{\partial b_{w_{k}}}\right]=\left[\begin{array}{cc}{-R_{w}^{b_{k}} \Delta t} & {-J_{b_{a}}^{\alpha}} & {-J_{b_{\omega}}^{\alpha}} \\ {0} & {0} &{-\mathcal{L}\left[q_{b_{k+1}}^{w-1}\otimes q_{b_{k}}^{w} \otimes \gamma_{b_{k+1}}^{b_{k}}\right]J_{b_{\omega}}^{\gamma}} \\ {-R_{w}^{b_{k}}} & {-J_{b_{a}}^{\beta}} &{-J_{b_{\omega}}^{\beta}} \\ {0} & {-I} &{0} \\ {0} & {0} &{-I}\end{array}\right]
J[1]15×9=[∂vbkw∂rB,∂bak∂rB,∂bwk∂rB]=⎣⎢⎢⎢⎢⎢⎡−RwbkΔt0−Rwbk00−Jbaα0−Jbaβ−I0−Jbωα−L[qbk+1w−1⊗qbkw⊗γbk+1bk]Jbωγ−Jbωβ0−I⎦⎥⎥⎥⎥⎥⎤
对应代码:
Eigen::Matrix3d dp_dba = pre_integration->jacobian.template block<3, 3>(O_P, O_BA);
Eigen::Matrix3d dp_dbg = pre_integration->jacobian.template block<3, 3>(O_P, O_BG);
Eigen::Matrix3d dv_dba = pre_integration->jacobian.template block<3, 3>(O_V, O_BA);
Eigen::Matrix3d dv_dbg = pre_integration->jacobian.template block<3, 3>(O_V, O_BG);
if (jacobians[1])
{
Eigen::Map<Eigen::Matrix<double, 15, 9, Eigen::RowMajor>> jacobian_speedbias_i(jacobians[1]);
jacobian_speedbias_i.setZero();
jacobian_speedbias_i.block<3, 3>(O_P, O_V - O_V) = -Qi.inverse().toRotationMatrix() * sum_dt;
jacobian_speedbias_i.block<3, 3>(O_P, O_BA - O_V) = -dp_dba;
jacobian_speedbias_i.block<3, 3>(O_P, O_BG - O_V) = -dp_dbg;
jacobian_speedbias_i.block<3, 3>(O_V, O_V - O_V) = -Qi.inverse().toRotationMatrix();
jacobian_speedbias_i.block<3, 3>(O_V, O_BA - O_V) = -dv_dba;
jacobian_speedbias_i.block<3, 3>(O_V, O_BG - O_V) = -dv_dbg;
jacobian_speedbias_i.block<3, 3>(O_BA, O_BA - O_V) = -Eigen::Matrix3d::Identity();
jacobian_speedbias_i.block<3, 3>(O_BG, O_BG - O_V) = -Eigen::Matrix3d::Identity();
jacobian_speedbias_i = sqrt_info * jacobian_speedbias_i;
}
Cannot read property 'type' of undefined对应代码:
if (jacobians[2])
{
Eigen::Map<Eigen::Matrix<double, 15, 7, Eigen::RowMajor>> jacobian_pose_j(jacobians[2]);
jacobian_pose_j.setZero();
jacobian_pose_j.block<3, 3>(O_P, O_P) = Qi.inverse().toRotationMatrix();
jacobian_pose_j = sqrt_info * jacobian_pose_j;
}
J [ 3 ] 15 × 9 = [ ∂ r B ∂ v b k + 1 w , ∂ r B ∂ b a k + 1 , ∂ r B ∂ b w k + 1 ] = [ 0 0 0 0 0 0 R w b k 0 0 0 I 0 0 0 I ] J[3]^{15 \times 9}=\left[\frac{\partial r_{B}}{\partial v_{b_{k+1}}^{w}}, \frac{\partial r_{B}}{\partial b_{a_{k+1}}}, \frac{\partial r_{B}}{\partial b_{w_{k+1}}}\right]=\left[\begin{array}{ccc}{0} & {0} & {0} \\ {0} & {0} & {0} \\ {R_{w}^{b_{k}}} & {0} & {0} \\ {0} & {I} & {0} \\ {0} & {0} & {I}\end{array}\right] J[3]15×9=[∂vbk+1w∂rB,∂bak+1∂rB,∂bwk+1∂rB]=⎣⎢⎢⎢⎢⎡00Rwbk00000I00000I⎦⎥⎥⎥⎥⎤对应代码:
if (jacobians[3])
{
Eigen::Map<Eigen::Matrix<double, 15, 9, Eigen::RowMajor>> jacobian_speedbias_j(jacobians[3]);
jacobian_speedbias_j.setZero();
jacobian_speedbias_j.block<3, 3>(O_V, O_V - O_V) = Qi.inverse().toRotationMatrix();
jacobian_speedbias_j.block<3, 3>(O_BA, O_BA - O_V) = Eigen::Matrix3d::Identity();
jacobian_speedbias_j.block<3, 3>(O_BG, O_BG - O_V) = Eigen::Matrix3d::Identity();
jacobian_speedbias_j = sqrt_info * jacobian_speedbias_j;
}
我这里不厌其烦地把公式对应的代码全部搬过来主要还是想说明这篇博客的主体,预积分的用处,在 J [ 0 ] J[0] J[0]和 J [ 1 ] J[1] J[1]中可以看到,我们是利用到了预积分求解的雅克比矩阵里面子矩阵,此外,每个雅克比矩阵最后一步都乘以了sqrt_info这个变量,这个变量怎么来的呢?是通过预积分时获得的误差的协方差获得的,如下:
Eigen::Matrix<double, 15, 15> sqrt_info = Eigen::LLT<Eigen::Matrix<double, 15, 15>>(pre_integration->covariance.inverse()).matrixL().transpose();
至于为什么要进行三角分解是因为ceres库使用的原因,到这里应该就能很清楚地看明白预积分的作用了,当然,预积分不仅仅只用在后端优化,在初始化进行视觉IMU标定时也用到了预积分,不过我觉得最核心部分就在这里了吧。
4. 总结
这里最后再总结下,这篇博客主要是从预积分的推导开始,最后在后端优化中说明的了它的用处,这里可以再回到我们的第一个问题,为什么要预积分?还是以这张图为例:
举一个例子,从上面的推导我们知道,我们优化的变量包括:
[
p
b
k
w
,
q
b
k
w
]
,
[
v
b
k
w
,
b
a
k
,
b
ω
k
]
,
[
p
b
k
+
1
w
,
q
b
k
+
1
w
]
,
[
v
b
k
+
1
w
,
b
a
k
+
1
,
b
ω
k
+
1
]
\left[p_{b_{k}}^{w}, q_{b_{k}}^{w}\right],\left[v_{b_{k}}^{w}, b_{a_{k}}, b_{\omega_{k}}\right], \quad\left[p_{b_{k+1}}^{w}, q_{b_{k+1}}^{w}\right],\left[v_{b_{k+1}}^{w}, b_{a_{k+1}}, b_{\omega_{k+1}}\right]
[pbkw,qbkw],[vbkw,bak,bωk],[pbk+1w,qbk+1w],[vbk+1w,bak+1,bωk+1]所谓IMU预积分就是建立帧间的约束,如果不进行预积分,我们需要做的工作是在完成一轮优化后这些优化变量发生了变化,前一帧的约束已经不适用于后一帧,如果要进行后一帧优化,需要将所有的位姿从头再积分一次,然后在计算两帧间的约束,这样是非常耗费资源的。
但是如果使用预积分,由于是基于上一帧相对积分,因此首先
[
p
b
k
w
,
q
b
k
w
,
v
b
k
w
]
\left[p_{b_{k}}^{w}, q_{b_{k}}^{w}, v_{b_{k}}^{w}\right]
[pbkw,qbkw,vbkw]都不再影响约束,其次
[
b
a
k
,
b
ω
k
]
\left[b_{a_{k}}, b_{\omega_{k}}\right]
[bak,bωk]对约束的影响可以通过雅克比进行传递,因此不需要再重新积分,节省了计算资源。
在计算预积分残差时代码如下:
//计算残差
Eigen::Matrix<double, 15, 1> evaluate(const Eigen::Vector3d &Pi, const Eigen::Quaterniond &Qi, const Eigen::Vector3d &Vi, const Eigen::Vector3d &Bai, const Eigen::Vector3d &Bgi,
const Eigen::Vector3d &Pj, const Eigen::Quaterniond &Qj, const Eigen::Vector3d &Vj, const Eigen::Vector3d &Baj, const Eigen::Vector3d &Bgj)
{
Eigen::Matrix<double, 15, 1> residuals;
Eigen::Matrix3d dp_dba = jacobian.block<3, 3>(O_P, O_BA);
Eigen::Matrix3d dp_dbg = jacobian.block<3, 3>(O_P, O_BG);
Eigen::Matrix3d dq_dbg = jacobian.block<3, 3>(O_R, O_BG);
Eigen::Matrix3d dv_dba = jacobian.block<3, 3>(O_V, O_BA);
Eigen::Matrix3d dv_dbg = jacobian.block<3, 3>(O_V, O_BG);
Eigen::Vector3d dba = Bai - linearized_ba;
Eigen::Vector3d dbg = Bgi - linearized_bg;
Eigen::Quaterniond corrected_delta_q = delta_q * Utility::deltaQ(dq_dbg * dbg);
Eigen::Vector3d corrected_delta_v = delta_v + dv_dba * dba + dv_dbg * dbg;
Eigen::Vector3d corrected_delta_p = delta_p + dp_dba * dba + dp_dbg * dbg;
residuals.block<3, 1>(O_P, 0) = Qi.inverse() * (0.5 * G * sum_dt * sum_dt + Pj - Pi - Vi * sum_dt) - corrected_delta_p;
residuals.block<3, 1>(O_R, 0) = 2 * (corrected_delta_q.inverse() * (Qi.inverse() * Qj)).vec();
residuals.block<3, 1>(O_V, 0) = Qi.inverse() * (G * sum_dt + Vj - Vi) - corrected_delta_v;
residuals.block<3, 1>(O_BA, 0) = Baj - Bai;
residuals.block<3, 1>(O_BG, 0) = Bgj - Bgi;
return residuals;
}
其中中间几行
Eigen::Vector3d dba = Bai - linearized_ba;
Eigen::Vector3d dbg = Bgi - linearized_bg;
Eigen::Quaterniond corrected_delta_q = delta_q * Utility::deltaQ(dq_dbg * dbg);
Eigen::Vector3d corrected_delta_v = delta_v + dv_dba * dba + dv_dbg * dbg;
Eigen::Vector3d corrected_delta_p = delta_p + dp_dba * dba + dp_dbg * dbg;
说明的就是这个问题,这就是为什么要预积分的原因。
这是我的个人理解,如果有不对的欢迎指正!
此外,对其他SLAM算法感兴趣的同学可以看考我的博客SLAM算法总结——经典SLAM算法框架总结