天天看点

VINS-Mono理论学习——视觉惯性联合初始化与外参标定前言视觉惯性对齐外参标定

前言

本文主要介绍VINS状态估计器模块(estimator)初始化环节中视觉惯性对齐求解陀螺仪偏置、尺度、重力加速度、每帧速度以及相机到IMU的外参估计,其中前半部分对应论文第五章(V. ESTIMATOR INITIALIZATION B. Visual-Inertial Alignment),后半部分参考了沈老师组的之前的论文。

总的来说,视觉惯性对齐主要包括以下流程:

1、若旋转外参数 q b c q_{bc} qbc​未知,则先估计旋转外参

2、利用旋转约束估计陀螺仪bias

3、利用平移约束估计重力方向,速度,以及尺度初始值

4、对重力向量 g c 0 g^{c_0} gc0​进一步优化

5、求解世界坐标系w和初始相机坐标系 c 0 c_0 c0​之间的旋转矩阵,并讲轨迹对齐到世界坐标系

视觉惯性对齐

首先我们先推导论文式(14),所有帧的位姿 ( R c k c 0 , q c k c 0 ) (R_{c_k}^{c_0},q_{c_k}^{c_0}) (Rck​c0​​,qck​c0​​)表示相对于第一帧相机坐标系(·)c0。相机到IMU的外参为 ( R c b , q c b ) (R_c^b,q_c^b) (Rcb​,qcb​),得到姿态从相机坐标系转换到IMU坐标系的关系。

T c k c 0 = T b k c 0 T c b T^{c_0}_{c_k}=T^{c_0}_{b_k}T^{b}_{c} Tck​c0​​=Tbk​c0​​Tcb​

将T展开有成R与p有:

[ R c k c 0 s p c k c 0 0 1 ] = [ R b k c 0 s p b k c 0 0 1 ] [ R c b p c b 0 1 ] \left[\begin{array}{cccc} R^{c_0}_{c_k} &sp^{c_0}_{c_k}\\0&1 \end{array}\right]=\left[\begin{array}{cccc} R^{c_0}_{b_k} &sp^{c_0}_{b_k}\\0&1 \end{array}\right] \left[\begin{array}{cccc} R^{b}_{c} &p^{b}_{c}\\0&1 \end{array}\right] [Rck​c0​​0​spck​c0​​1​]=[Rbk​c0​​0​spbk​c0​​1​][Rcb​0​pcb​1​]

左侧矩阵的两项写开:

R c k c 0 = R b k c 0 R c b ⇒ R b k c 0 = R c k c 0 ( R c b ) − 1 R^{c_0}_{c_k}=R^{c_0}_{b_k}R^{b}_{c} \Rightarrow R^{c_0}_{b_k}=R^{c_0}_{c_k}(R^{b}_{c})^{-1} Rck​c0​​=Rbk​c0​​Rcb​⇒Rbk​c0​​=Rck​c0​​(Rcb​)−1

s p c k c 0 = R b k c 0 p c b + s p b k c 0 ⇒ s p b k c 0 = s p c k c 0 − R b k c 0 p c b sp^{c_0}_{c_k}=R^{c_0}_{b_k} p^{b}_{c} +sp^{c_0}_{b_k}\Rightarrow sp^{c_0}_{b_k}=sp^{c_0}_{c_k}-R^{c_0}_{b_k} p^{b}_{c} spck​c0​​=Rbk​c0​​pcb​+spbk​c0​​⇒spbk​c0​​=spck​c0​​−Rbk​c0​​pcb​

对于VINS初始化中视觉惯性对齐之外的部分已在博客VINS-Mono代码解读——视觉惯性联合初始化 中详细介绍了。这里就直接从VisualIMUAlignment()函数开始,结合相关数学推导进行解读。这里的代码均位于vins_estimator/src/initial/initial_aligment.cpp/.h。

VisualIMUAlignment()中调用了两个函数,分别用于对陀螺仪的偏置进行标定,以及估计尺度、重力以及速度。

bool VisualIMUAlignment(map<double, ImageFrame> &all_image_frame, Vector3d* Bgs, Vector3d &g, VectorXd &x)
{
    solveGyroscopeBias(all_image_frame, Bgs);
    if(LinearAlignment(all_image_frame, g, x))
        return true;
    else 
        return false;    
}
           

陀螺仪偏置标定 solveGyroscopeBias()

对于窗口中得连续两帧 b k b_k bk​和 b k + 1 b_{k+1} bk+1​,已经从视觉SFM中得到了旋转 q b k c 0 q^{c_0}_{b_k} qbk​c0​​和 q b k + 1 c 0 q^{c_0}_{b_{k+1}} qbk+1​c0​​,从IMU预积分中得到了相邻帧旋转 γ ^ b k + 1 b k \hat \gamma^{b_k}_{b_{k+1}} γ^​bk+1​bk​​。根据约束方程,联立所有相邻帧,最小化代价函数(论文式(15)):

min ⁡ δ b w ∑ k ∈ B ∥ q b k + 1 c 0 − 1 ⊗ q b k c 0 ⊗ γ b k + 1 b k ∥ 2 \mathop {\min }\limits_{\delta b_w} \sum\nolimits_{k\in \mathcal B} \left\|{q^{c_0}_{b_{k+1}}}^{-1}\otimes q^{c_0}_{b_{k}}\otimes \gamma^{b_k}_{b_{k+1}} \right\|^2 δbw​min​∑k∈B​∥∥∥​qbk+1​c0​​−1⊗qbk​c0​​⊗γbk+1​bk​​∥∥∥​2

其中对陀螺仪偏置求IMU预积分项线性化,有:

γ b k + 1 b k ≈ γ ^ b k + 1 b k ⊗ [ 1 1 2 J b w γ δ b w ] \gamma^{b_k}_{b_{k+1}}\approx \hat \gamma^{b_k}_{b_{k+1}} \otimes \left[\begin{array}{cccc} 1 \\ \frac{1}{2}J^{\gamma}_{b_w}\delta b_w \end{array}\right] γbk+1​bk​​≈γ^​bk+1​bk​​⊗[121​Jbw​γ​δbw​​]

在具体实现的时候,因为上述约束方程为:

q b k + 1 c 0 − 1 ⊗ q b k c 0 ⊗ γ b k + 1 b k = [ 1 0 ] {q^{c_0}_{b_{k+1}}}^{-1}\otimes q^{c_0}_{b_{k}}\otimes \gamma^{b_k}_{b_{k+1}}= \left[\begin{array}{cccc} 1 \\ 0\end{array}\right] qbk+1​c0​​−1⊗qbk​c0​​⊗γbk+1​bk​​=[10​]

有:

γ b k + 1 b k = q b k c 0 − 1 ⊗ q b k + 1 c 0 ⊗ [ 1 0 ] \gamma^{b_k}_{b_{k+1}}= {q^{c_0}_{b_{k}}}^{-1}\otimes q^{c_0}_{b_{k+1}}\otimes \left[\begin{array}{cccc} 1 \\ 0\end{array}\right] γbk+1​bk​​=qbk​c0​​−1⊗qbk+1​c0​​⊗[10​]

代入 δ b w \delta b_w δbw​得:

γ ^ b k + 1 b k ⊗ [ 1 1 2 J b w γ δ b w ] = q b k c 0 − 1 ⊗ q b k + 1 c 0 ⊗ [ 1 0 ] \hat \gamma^{b_k}_{b_{k+1}} \otimes \left[\begin{array}{cccc} 1 \\ \frac{1}{2}J^{\gamma}_{b_w}\delta b_w \end{array}\right] ={q^{c_0}_{b_{k}}}^{-1}\otimes q^{c_0}_{b_{k+1}}\otimes \left[\begin{array}{cccc} 1 \\ 0\end{array}\right] γ^​bk+1​bk​​⊗[121​Jbw​γ​δbw​​]=qbk​c0​​−1⊗qbk+1​c0​​⊗[10​]

只考虑虚部,有:

J b w γ δ b w = 2 ( γ ^ b k + 1 b k − 1 ⊗ q b k c 0 − 1 ⊗ q b k + 1 c 0 ) v e c J^{\gamma}_{b_w}\delta b_w = 2({\hat \gamma^{b_k}_{b_{k+1}}}^{-1} \otimes{q^{c_0}_{b_{k}}}^{-1}\otimes q^{c_0}_{b_{k+1}})_{vec} Jbw​γ​δbw​=2(γ^​bk+1​bk​​−1⊗qbk​c0​​−1⊗qbk+1​c0​​)vec​

两侧乘以 J b w γ T {J^\gamma_{b_w}}^T Jbw​γ​T,用LDLT分解求得 δ b w \delta b_w δbw​。

在代码中,Quaterniond q_ij即 q b k + 1 b k = q b k c 0 − 1 ⊗ q b k + 1 c 0 q^{b_k}_{b_{k+1}}={q^{c_0}_{b_k}}^{-1} \otimes q^{c_0}_{b_{k+1}} qbk+1​bk​​=qbk​c0​​−1⊗qbk+1​c0​​

tmp_A即 J b w γ J^\gamma_{b_w} Jbw​γ​

tmp_B即 2 ( r ^ b k + 1 b k − 1 ⊗ q b k + 1 b k ) v e c = 2 ( r ^ b k + 1 b k − 1 ⊗ q b k c 0 − 1 ⊗ q b k + 1 c 0 ) v e c 2 ({\hat r^{b_k}_{b_{k+1}}}^{-1} \otimes q^{b_k}_{b_{k+1}})_{vec}=2 ({\hat r^{b_k}_{b_{k+1}}}^{-1} \otimes {q^{c_0}_{b_k}}^{-1} \otimes q^{c_0}_{b_{k+1}})_{vec} 2(r^bk+1​bk​​−1⊗qbk+1​bk​​)vec​=2(r^bk+1​bk​​−1⊗qbk​c0​​−1⊗qbk+1​c0​​)vec​

根据上面的代价函数构造Ax = B即

J b w γ T J b w γ δ b w = 2 J b w γ T ( r ^ b k + 1 b k − 1 ⊗ q b k c 0 − 1 ⊗ q b k + 1 c 0 ) v e c {J^\gamma_{b_w}}^T J^\gamma_{b_w} \delta b_w=2 {J^\gamma_{b_w}}^T({\hat r^{b_k}_{b_{k+1}}}^{-1} \otimes {q^{c_0}_{b_k}}^{-1} \otimes q^{c_0}_{b_{k+1}})_{vec} Jbw​γ​TJbw​γ​δbw​=2Jbw​γ​T(r^bk+1​bk​​−1⊗qbk​c0​​−1⊗qbk+1​c0​​)vec​

然后采用LDLT分解求得 δ b w \delta b_w δbw​。

void solveGyroscopeBias(map<double, ImageFrame> &all_image_frame, Vector3d* Bgs)
{
    Matrix3d A;
    Vector3d b;
    Vector3d delta_bg;
    A.setZero();
    b.setZero();
    map<double, ImageFrame>::iterator frame_i;
    map<double, ImageFrame>::iterator frame_j;
    for (frame_i = all_image_frame.begin(); next(frame_i) != all_image_frame.end(); frame_i++)
    {
        frame_j = next(frame_i);
        MatrixXd tmp_A(3, 3);
        tmp_A.setZero();
        VectorXd tmp_b(3);
        tmp_b.setZero();
        
        //R_ij = (R^c0_bk)^-1 * (R^c0_bk+1) 转换为四元数 q_ij = (q^c0_bk)^-1 * (q^c0_bk+1)
        Eigen::Quaterniond q_ij(frame_i->second.R.transpose() * frame_j->second.R);
        //tmp_A = J_j_bw
        tmp_A = frame_j->second.pre_integration->jacobian.template block<3, 3>(O_R, O_BG);
        //tmp_b = 2 * ((r^bk_bk+1)^-1 * (q^c0_bk)^-1 * (q^c0_bk+1))_vec
        //      = 2 * ((r^bk_bk+1)^-1 * q_ij)_vec
        tmp_b = 2 * (frame_j->second.pre_integration->delta_q.inverse() * q_ij).vec();
        //tmp_A * delta_bg = tmp_b
        A += tmp_A.transpose() * tmp_A;
        b += tmp_A.transpose() * tmp_b;
    }
    //LDLT方法
    delta_bg = A.ldlt().solve(b);
    ROS_WARN_STREAM("gyroscope bias initial calibration " << delta_bg.transpose());

    for (int i = 0; i <= WINDOW_SIZE; i++)
        Bgs[i] += delta_bg;

    for (frame_i = all_image_frame.begin(); next(frame_i) != all_image_frame.end( ); frame_i++)
    {
        frame_j = next(frame_i);
        frame_j->second.pre_integration->repropagate(Vector3d::Zero(), Bgs[0]);
    }
}
           

速度、重力和尺度初始化 LinearAlignment()

该函数需要优化的变量有速度、重力加速度和尺度(论文式(16)):

X I 3 ( n + 1 ) + 3 + 1 = [ v b 0 b 0 , v b 1 b 1 , . . . , v b n b n , g c 0 , s ] \mathcal X^{3(n+1)+3+1}_I=[v^{b_0}_{b_0},v^{b_1}_{b_1},...,v^{b_n}_{b_n},g^{c_0},s] XI3(n+1)+3+1​=[vb0​b0​​,vb1​b1​​,...,vbn​bn​​,gc0​,s]

其中, v b k b k v^{b_k}_{b_k} vbk​bk​​是第k帧图像在其IMU坐标系下的速度, g c 0 g^{c_0} gc0​是在第0帧相机坐标系下的重力向量。

在IMU预积分中我们已经推导过论文式(5):

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^{b_k}_w p^w_{b_{k+1}}=R^{b_k}_w (p^w_{b_k}+v^w_{b_k} \Delta t_k-\frac{1}{2} g^w\Delta t_k^2)+ \alpha^{b_k}_{b_{k+1}} Rwbk​​pbk+1​w​=Rwbk​​(pbk​w​+vbk​w​Δtk​−21​gwΔtk2​)+αbk+1​bk​​

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 R^{b_k}_w v^w_{b_{k+1}}=R^{b_k}_w (v^w_{b_k}- g^w\Delta t_k)+\beta^{b_k}_{b_{k+1}} Rwbk​​vbk+1​w​=Rwbk​​(vbk​w​−gwΔtk​)+βbk+1​bk​​

将 c 0 c_0 c0​替代 w w w可以写成:

α b k + 1 b k = R c 0 b k ( s p b k + 1 c 0 − s p b k c 0 + 1 2 g c 0 Δ t k 2 − R b k c 0 v b k b k Δ t k ) \alpha^{b_k}_{b_{k+1}}=R^{b_k}_{c_0}(sp^{c_0}_{b_{k+1}}-sp^{c_0}_{b_{k}}+\frac{1}{2} g^{c_0}\Delta t_k^2-R^{c_0}_{b_k}v^{b_k}_{b_k} \Delta t_k) αbk+1​bk​​=Rc0​bk​​(spbk+1​c0​​−spbk​c0​​+21​gc0​Δtk2​−Rbk​c0​​vbk​bk​​Δtk​)

β b k + 1 b k = R c 0 b k ( R b k + 1 c 0 v b k + 1 b k + 1 + g c 0 Δ t k − R b k c 0 v b k b k ) \beta^{b_k}_{b_{k+1}}=R^{b_k}_{c_0}(R^{c_0}_{b_{k+1}}v^{b_{k+1}}_{b_{k+1}}+g^{c_0}\Delta t_k-R^{c_0}_{b_k}v^{b_k}_{b_k}) βbk+1​bk​​=Rc0​bk​​(Rbk+1​c0​​vbk+1​bk+1​​+gc0​Δtk​−Rbk​c0​​vbk​bk​​)

将论文式(14)带入 α b k + 1 b k \alpha^{b_k}_{b_{k+1}} αbk+1​bk​​有:

δ α b k + 1 b k = α b k + 1 b k − R c 0 b k ( s p b k + 1 c 0 − s p b k c 0 + 1 2 g c 0 Δ t k 2 − R b k c 0 v b k b k Δ t k ) \delta \alpha^{b_k}_{b_{k+1}}=\alpha^{b_k}_{b_{k+1}}-R^{b_k}_{c_0}(sp^{c_0}_{b_{k+1}}-sp^{c_0}_{b_{k}}+\frac{1}{2} g^{c_0}\Delta t_k^2-R^{c_0}_{b_k}v^{b_k}_{b_k} \Delta t_k) δαbk+1​bk​​=αbk+1​bk​​−Rc0​bk​​(spbk+1​c0​​−spbk​c0​​+21​gc0​Δtk2​−Rbk​c0​​vbk​bk​​Δtk​)

= α b k + 1 b k − R c 0 b k ( s p c k + 1 c 0 − R b k + 1 c 0 p c b − ( s p c k c 0 − R b k c 0 p c b ) + 1 2 g c 0 Δ t k 2 − R b k c 0 v b k b k Δ t k ) =\alpha^{b_k}_{b_{k+1}}-R^{b_k}_{c_0}(sp^{c_0}_{c_{k+1}}-R^{c_0}_{b_{k+1}} p^{b}_{c}-(sp^{c_0}_{c_k}-R^{c_0}_{b_k} p^{b}_{c})+\frac{1}{2} g^{c_0}\Delta t_k^2-R^{c_0}_{b_k}v^{b_k}_{b_k} \Delta t_k) =αbk+1​bk​​−Rc0​bk​​(spck+1​c0​​−Rbk+1​c0​​pcb​−(spck​c0​​−Rbk​c0​​pcb​)+21​gc0​Δtk2​−Rbk​c0​​vbk​bk​​Δtk​)

= α b k + 1 b k − R c 0 b k s ( p c k + 1 c 0 − p c k c 0 ) + R c 0 b k R b k + 1 c 0 p c b − p c b + v b k b k Δ t k − 1 2 R c 0 b k g c 0 Δ t k 2 = 0 3 × 1 =\alpha^{b_k}_{b_{k+1}}-R^{b_k}_{c_0}s(p^{c_0}_{c_{k+1}}-p^{c_0}_{c_k})+R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}p^b_c-p^b_c+v^{b_k}_{b_k}\Delta t_k-\frac{1}{2}R^{b_k}_{c_0} g^{c_0}\Delta t_k^2=0_{3\times1} =αbk+1​bk​​−Rc0​bk​​s(pck+1​c0​​−pck​c0​​)+Rc0​bk​​Rbk+1​c0​​pcb​−pcb​+vbk​bk​​Δtk​−21​Rc0​bk​​gc0​Δtk2​=03×1​

这里 v b k b k , s , g c 0 v^{b_k}_{b_k},s,g^{c_0} vbk​bk​​,s,gc0​是待优化变量,将其转换成 H x = b Hx=b Hx=b的形式:

R c 0 b k ( p c k + 1 c 0 − p c k c 0 ) s − v b k b k Δ t k + 1 2 R c 0 b k Δ t k 2 g c 0 = α b k + 1 b k + R c 0 b k R b k + 1 c 0 p c b − p c b R^{b_k}_{c_0}(p^{c_0}_{c_{k+1}}-p^{c_0}_{c_k})s-v^{b_k}_{b_k}\Delta t_k+\frac{1}{2}R^{b_k}_{c_0}\Delta t_k^2 g^{c_0}= \alpha^{b_k}_{b_{k+1}}+R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}p^b_c-p^b_c Rc0​bk​​(pck+1​c0​​−pck​c0​​)s−vbk​bk​​Δtk​+21​Rc0​bk​​Δtk2​gc0​=αbk+1​bk​​+Rc0​bk​​Rbk+1​c0​​pcb​−pcb​

写成矩阵形式:

[ − I Δ t k 0 1 2 R c 0 b k Δ t k 2 R c 0 b k ( p c k + 1 c 0 − p c k c 0 ) ] [ v b k b k v b k + 1 b k + 1 g c 0 s ] = α b k + 1 b k + R c 0 b k R b k + 1 c 0 p c b − p c b \left[\begin{array}{cccc} -I\Delta t_k &0&\frac{1}{2}R^{b_k}_{c_0}\Delta t_k^2 & R^{b_k}_{c_0}(p^{c_0}_{c_{k+1}}-p^{c_0}_{c_k})\end{array}\right] \left[\begin{array}{cccc} v^{b_k}_{b_k}\\v^{b_{k+1}}_{b_{k+1}}\\g^{c_0}\\s \end{array}\right] = \alpha^{b_k}_{b_{k+1}}+R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}p^b_c-p^b_c [−IΔtk​​0​21​Rc0​bk​​Δtk2​​Rc0​bk​​(pck+1​c0​​−pck​c0​​)​]⎣⎢⎢⎡​vbk​bk​​vbk+1​bk+1​​gc0​s​⎦⎥⎥⎤​=αbk+1​bk​​+Rc0​bk​​Rbk+1​c0​​pcb​−pcb​

对于 β b k + 1 b k \beta^{b_k}_{b_{k+1}} βbk+1​bk​​有:

δ β b k + 1 b k = β b k + 1 b k − R c 0 b k ( R b k + 1 c 0 v b k + 1 b k + 1 + g c 0 Δ t k − R b k c 0 v b k b k ) = 0 3 × 1 \delta \beta^{b_k}_{b_{k+1}}=\beta^{b_k}_{b_{k+1}}-R^{b_k}_{c_0}(R^{c_0}_{b_{k+1}}v^{b_{k+1}}_{b_{k+1}}+g^{c_0}\Delta t_k-R^{c_0}_{b_k}v^{b_k}_{b_k})=0_{3\times1} δβbk+1​bk​​=βbk+1​bk​​−Rc0​bk​​(Rbk+1​c0​​vbk+1​bk+1​​+gc0​Δtk​−Rbk​c0​​vbk​bk​​)=03×1​

R c 0 b k R b k + 1 c 0 v b k + 1 b k + 1 + R c 0 b k Δ t k g c 0 − R c 0 b k R b k c 0 v b k b k = β b k + 1 b k R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}v^{b_{k+1}}_{b_{k+1}}+R^{b_k}_{c_0}\Delta t_kg^{c_0}-R^{b_k}_{c_0}R^{c_0}_{b_k}v^{b_k}_{b_k}=\beta^{b_k}_{b_{k+1}} Rc0​bk​​Rbk+1​c0​​vbk+1​bk+1​​+Rc0​bk​​Δtk​gc0​−Rc0​bk​​Rbk​c0​​vbk​bk​​=βbk+1​bk​​

写成矩阵形式:

[ − I R c 0 b k R b k + 1 c 0 R c 0 b k Δ t k 0 ] [ v b k b k v b k + 1 b k + 1 g c 0 s ] = β b k + 1 b k \left[\begin{array}{cccc} -I&R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}&R^{b_k}_{c_0}\Delta t_k& 0\end{array}\right] \left[\begin{array}{cccc} v^{b_k}_{b_k}\\v^{b_{k+1}}_{b_{k+1}}\\g^{c_0}\\s \end{array}\right] = \beta^{b_k}_{b_{k+1}} [−I​Rc0​bk​​Rbk+1​c0​​​Rc0​bk​​Δtk​​0​]⎣⎢⎢⎡​vbk​bk​​vbk+1​bk+1​​gc0​s​⎦⎥⎥⎤​=βbk+1​bk​​

由此得到论文式(18)(19):

[ − I Δ t k 0 1 2 R c 0 b k Δ t k 2 R c 0 b k ( p c k + 1 c 0 − p c k c 0 ) − I R c 0 b k R b k + 1 c 0 R c 0 b k Δ t k 0 ] [ v b k b k v b k + 1 b k + 1 g c 0 s ] = [ α b k + 1 b k + R c 0 b k R b k + 1 c 0 p c b − p c b β b k + 1 b k ] \left[\begin{array}{cccc} -I\Delta t_k &0&\frac{1}{2}R^{b_k}_{c_0}\Delta t_k^2 & R^{b_k}_{c_0}(p^{c_0}_{c_{k+1}}-p^{c_0}_{c_k})\\-I&R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}&R^{b_k}_{c_0}\Delta t_k& 0\end{array}\right] \left[\begin{array}{cccc} v^{b_k}_{b_k}\\v^{b_{k+1}}_{b_{k+1}}\\g^{c_0}\\s \end{array}\right] =\left[\begin{array}{cccc} \alpha^{b_k}_{b_{k+1}}+R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}p^b_c-p^b_c\\ \beta^{b_k}_{b_{k+1}} \end{array}\right] [−IΔtk​−I​0Rc0​bk​​Rbk+1​c0​​​21​Rc0​bk​​Δtk2​Rc0​bk​​Δtk​​Rc0​bk​​(pck+1​c0​​−pck​c0​​)0​]⎣⎢⎢⎡​vbk​bk​​vbk+1​bk+1​​gc0​s​⎦⎥⎥⎤​=[αbk+1​bk​​+Rc0​bk​​Rbk+1​c0​​pcb​−pcb​βbk+1​bk​​​]

即 H 6 × 10 X I 10 × 1 = b 6 × 1 H^{6\times10}\mathcal X^{10\times1}_I=b^{6\times1} H6×10XI10×1​=b6×1,使用LDLT分解求解:

H T H X I 10 × 1 = H T b H^TH\mathcal X^{10\times1}_I=H^Tb HTHXI10×1​=HTb

对应代码实现在以下函数中,因为内容和上述理论一致就不放代码了。里面的MatrixXd tmp_A(6, 10)就是H,VectorXd tmp_b(6,1)就是b

最后求得尺度s和重力加速度g

double s = x(n_state - 1) / 100.0;
    g = x.segment<3>(n_state - 4);
  	//如果重力加速度与参考值差太大或者尺度为负则说明计算错误
    if(fabs(g.norm() - G.norm()) > 1.0 || s < 0)
    {
        return false;
    }
           

重力细化 RefineGravity()

考虑到上一步求得的g存在误差,一般认为重力矢量的模是已知的,因此重力向量只剩两个自由度,在切线空间上用两个变量重新参数化重力,将其表示为

g ^ = ∣ ∣ g ∣ ∣ g ^ ˉ + w 1 b 1 + w 2 b 2 = ∣ ∣ g ∣ ∣ g ^ ˉ + b ⃗ 3 × 2 w 2 × 1 \hat g=||g||\bar{\hat g}+w_1b_1+w_2b_2=||g||\bar{\hat g}+\vec b^{3\times2}w^{2\times1} g^​=∣∣g∣∣g^​ˉ​+w1​b1​+w2​b2​=∣∣g∣∣g^​ˉ​+b

3×2w2×1

其中 w 2 × 1 = [ w 1 , w 2 ] T , b ⃗ 3 × 2 = [ b 1 , b 2 ] w^{2\times1}=[w_1,w_2]^T,\vec b^{3\times2}=[b_1,b_2] w2×1=[w1​,w2​]T,b

3×2=[b1​,b2​]

g ^ ˉ \bar{\hat g} g^​ˉ​为上一步求得的重力方向,, b 1 、 b 2 b_1、b_2 b1​、b2​在 g ^ \hat g g^​的正切平面上且正交。 b 1 b_1 b1​与 b 2 b_2 b2​的设置为:

VINS-Mono理论学习——视觉惯性联合初始化与外参标定前言视觉惯性对齐外参标定

将其带入论文式(18)(19)中,有:

[ − I Δ t k 0 1 2 R c 0 b k Δ t k 2 b ⃗ R c 0 b k ( p c k + 1 c 0 − p c k c 0 ) − I R c 0 b k R b k + 1 c 0 R c 0 b k Δ t k b ⃗ 0 ] [ v b k b k v b k + 1 b k + 1 w s ] = [ δ α b k + 1 b k + R c 0 b k R b k + 1 c 0 p c b − p c b − 1 2 R c 0 b k Δ t k 2 ∣ ∣ g ∣ ∣ g ^ ˉ β b k + 1 b k − R c 0 b k Δ t k ∣ ∣ g ∣ ∣ g ^ ˉ ] \left[\begin{array}{cccc} -I\Delta t_k &0&\frac{1}{2}R^{b_k}_{c_0}\Delta t_k^2 \vec b & R^{b_k}_{c_0}(p^{c_0}_{c_{k+1}}-p^{c_0}_{c_k})\\-I&R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}&R^{b_k}_{c_0}\Delta t_k \vec b& 0\end{array}\right] \left[\begin{array}{cccc} v^{b_k}_{b_k}\\v^{b_{k+1}}_{b_{k+1}}\\w\\s \end{array}\right] =\left[\begin{array}{cccc} \delta \alpha^{b_k}_{b_{k+1}}+R^{b_k}_{c_0}R^{c_0}_{b_{k+1}}p^b_c-p^b_c-\frac{1}{2}R^{b_k}_{c_0}\Delta t_k^2||g||\bar{\hat g}\\ \beta^{b_k}_{b_{k+1}}-R^{b_k}_{c_0}\Delta t_k||g||\bar{\hat g} \end{array}\right] [−IΔtk​−I​0Rc0​bk​​Rbk+1​c0​​​21​Rc0​bk​​Δtk2​b

Rc0​bk​​Δtk​b

​Rc0​bk​​(pck+1​c0​​−pck​c0​​)0​]⎣⎢⎢⎡​vbk​bk​​vbk+1​bk+1​​ws​⎦⎥⎥⎤​=[δαbk+1​bk​​+Rc0​bk​​Rbk+1​c0​​pcb​−pcb​−21​Rc0​bk​​Δtk2​∣∣g∣∣g^​ˉ​βbk+1​bk​​−Rc0​bk​​Δtk​∣∣g∣∣g^​ˉ​​]

即 H 6 × 9 X I 9 × 1 = b 6 × 1 H^{6\times9}\mathcal X^{9\times 1}_I=b^{6\times1} H6×9XI9×1​=b6×1。使用LDLT分解求解:

H T H X I 9 × 1 = H T b H^TH\mathcal X^{9\times1}_I=H^Tb HTHXI9×1​=HTb

在代码实现中,以下函数用于重力细化,其流程基本对应上文推导,方程迭代求解4次:

函数中的MatrixXd lxly(3, 2)= TangentBasis(g0); 即对应 b ⃗ 3 × 2 = [ b 1 , b 2 ] \vec b^{3\times2}=[b_1,b_2] b

3×2=[b1​,b2​],

VectorXd dg = x.segment<2>(n_state - 3); 即对应 w 2 × 1 = [ w 1 , w 2 ] T w^{2\times1}=[w_1,w_2]^T w2×1=[w1​,w2​]T

以下函数对应论文中Algorithm 1,用于在半径为g的半球找到切面的一对正交基。

MatrixXd TangentBasis(Vector3d &g0)
{
    Vector3d b, c;
    Vector3d a = g0.normalized();
    Vector3d tmp(0, 0, 1);
    if(a == tmp)
        tmp << 1, 0, 0;
    b = (tmp - a * (a.transpose() * tmp)).normalized();
    c = a.cross(b);
    MatrixXd bc(3, 2);
    bc.block<3, 1>(0, 0) = b;
    bc.block<3, 1>(0, 1) = c;
    return bc;
}
           

外参标定

VINS外参标定指的是对相机坐标系到IMU坐标系的变换矩阵进行在线标定与优化。

在参数配置文件yaml中,参数estimate_extrinsic反映了外参的情况:

1、等于0表示这外参已经是准确的了,之后不需要优化;

2、等于1表示外参只是一个估计值,后续还需要将其作为初始值放入非线性优化中;

3、等于2表示不知道外参,需要进行标定,从代码estimator.cpp中的processImage()中的以下代码进入,主要是标定外参的旋转矩阵。

if(ESTIMATE_EXTRINSIC == 2)//如果没有外参则进行标定
    {
        ROS_INFO("calibrating extrinsic param, rotation movement is needed");
        if (frame_count != 0)
        {
            //得到当前帧与前一帧之间归一化特征点
            vector<pair<Vector3d, Vector3d>> corres = f_manager.getCorresponding(frame_count - 1, frame_count);
            Matrix3d calib_ric;
            //标定从camera到IMU之间的旋转矩阵
            if (initial_ex_rotation.CalibrationExRotation(corres, pre_integrations[frame_count]->delta_q, calib_ric))
            {
                ROS_WARN("initial extrinsic rotation calib success");
                ROS_WARN_STREAM("initial extrinsic rotation: " << endl << calib_ric);
                ric[0] = calib_ric;
                RIC[0] = calib_ric;
                ESTIMATE_EXTRINSIC = 1;
            }
        }
    }
           

InitialEXRotation类

该类位于vins_estimator/src/initial/initial_ex_rotation.cpp/.h中,用于标定外参旋转矩阵。首先简要介绍一下InitialEXRotation类的成员:

class InitialEXRotation
{
public:
	InitialEXRotation();//构造函数
	//标定外参旋转矩阵
    bool CalibrationExRotation(vector<pair<Vector3d, Vector3d>> corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result);
private:
	//求解帧间cam坐标系的旋转矩阵
	Matrix3d solveRelativeR(const vector<pair<Vector3d, Vector3d>> &corres);
	//三角化验证Rt
    double testTriangulation(const vector<cv::Point2f> &l,
                             const vector<cv::Point2f> &r,
                             cv::Mat_<double> R, cv::Mat_<double> t);
    //本质矩阵SVD分解计算4组Rt值
    void decomposeE(cv::Mat E,
                    cv::Mat_<double> &R1, cv::Mat_<double> &R2,
                    cv::Mat_<double> &t1, cv::Mat_<double> &t2);
                    
    int frame_count;//帧计数器
    vector< Matrix3d > Rc;//帧间cam的R,由对极几何得到
    vector< Matrix3d > Rimu;//帧间IMU的R,由IMU预积分得到
    vector< Matrix3d > Rc_g;//每帧IMU相对于起始帧IMU的R
    Matrix3d ric;//cam到IMU的外参
};
           

CalibrationExRotation() 标定外参旋转矩阵

该函数目的是标定外参的旋转矩阵。由于函数内部并不复杂,将所有内部调用的函数也放在一起介绍。

1、solveRelativeR(corres)根据对极几何计算相邻帧间相机坐标系的旋转矩阵,这里的corres传入的是当前帧和其之前一帧的对应特征点对的归一化坐标。

1.1、将corres中对应点的二维坐标分别存入ll与rr中。

vector<cv::Point2f> ll, rr;
        for (int i = 0; i < int(corres.size()); i++)
        {
            ll.push_back(cv::Point2f(corres[i].first(0), corres[i].first(1)));
            rr.push_back(cv::Point2f(corres[i].second(0), corres[i].second(1)));
        }
           

1.2、根据对极几何求解这两帧的本质矩阵

1.3、对本质矩阵进行svd分解得到4组Rt解

cv::Mat_<double> R1, R2, t1, t2;
        decomposeE(E, R1, R2, t1, t2);
           

1.4、采用三角化对每组Rt解进行验证,选择是正深度的Rt解

double ratio1 = max(testTriangulation(ll, rr, R1, t1), testTriangulation(ll, rr, R1, t2));
        double ratio2 = max(testTriangulation(ll, rr, R2, t1), testTriangulation(ll, rr, R2, t2));
        cv::Mat_<double> ans_R_cv = ratio1 > ratio2 ? R1 : R2;
           

1.5、这里的R是上一帧到当前帧的变换矩阵,对其求转置为当前帧相对于上一帧的姿态。

Matrix3d ans_R_eigen;
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                ans_R_eigen(j, i) = ans_R_cv(i, j);
        return ans_R_eigen;
}

           

2、获取相邻帧之间相机坐标系和IMU坐标系的旋转矩阵,存入vector中

frame_count++;
    Rc.push_back(solveRelativeR(corres));//帧间cam的R,由对极几何得到
    Rimu.push_back(delta_q_imu.toRotationMatrix());//帧间IMU的R,由IMU预积分得到
    Rc_g.push_back(ric.inverse() * delta_q_imu * ric);//每帧IMU相对于起始帧IMU的R
           

3、求解相机到IMU的外参旋转矩阵,根据等式:

R c k + 1 b k = R b k + 1 b k ⋅ R c b = R c b ⋅ R c k + 1 c k R_{c_{k+1}}^{b_k}=R_{b_{k+1}}^{b_k} \cdot R_c^b = R_c^b \cdot R_{c_{k+1}}^{c_k} Rck+1​bk​​=Rbk+1​bk​​⋅Rcb​=Rcb​⋅Rck+1​ck​​

其中 R b k + 1 b k R_{b_{k+1}}^{b_k} Rbk+1​bk​​为IMU坐标系之间的旋转矩阵, R c k + 1 c k R_{c_{k+1}}^{c_k} Rck+1​ck​​为相机坐标系之间的旋转矩阵, R c b R_c^b Rcb​为从相机到IMU的旋转矩阵。

用四元素重写:

q b k + 1 b k ⊗ q c b = q c b ⊗ q c k + 1 c k q_{b_{k+1}}^{b_k} \otimes q_c^b = q_c^b \otimes q_{c_{k+1}}^{c_k} qbk+1​bk​​⊗qcb​=qcb​⊗qck+1​ck​​

[ Q 1 ( q b k + 1 b k ) − Q 2 ( q c k + 1 c k ) ] ⋅ q c b = Q c b ⋅ q c b = 0 [\mathcal{Q_1}(q_{b_{k+1}}^{b_k})-\mathcal{Q_2}(q_{c_{k+1}}^{c_k})]\cdot q_c^b=Q^b_c\cdot q_c^b=0 [Q1​(qbk+1​bk​​)−Q2​(qck+1​ck​​)]⋅qcb​=Qcb​⋅qcb​=0

将多个帧之间的等式关系一起构建超定方程 A x = 0 Ax=0 Ax=0。对A进行svd分解,其中最小奇异值对应的右奇异向量便为结果x,即旋转四元数 q c b q_c^b qcb​。

在代码中,L为相机旋转四元数的左乘矩阵,R为IMU旋转四元数的右乘矩阵,因此其实构建的是:

q b c ⊗ q b k + 1 b k = q c k + 1 c k ⊗ q b c q_b^c \otimes q_{b_{k+1}}^{b_k} =q_{c_{k+1}}^{c_k} \otimes q_b^c qbc​⊗qbk+1​bk​​=qck+1​ck​​⊗qbc​

所求的x是 q b c q_b^c qbc​,在最后需要转换成旋转矩阵并求逆。

Eigen::MatrixXd A(frame_count * 4, 4);
    A.setZero();
    int sum_ok = 0;
    for (int i = 1; i <= frame_count; i++)
    {
        Quaterniond r1(Rc[i]);
        Quaterniond r2(Rc_g[i]);
        double angular_distance = 180 / M_PI * r1.angularDistance(r2);
        ROS_DEBUG(
            "%d %f", i, angular_distance);
        //huber核函数做加权
        double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0;
        ++sum_ok;
        Matrix4d L, R;
        //L R 分别为左乘和右乘矩阵
        double w = Quaterniond(Rc[i]).w();
        Vector3d q = Quaterniond(Rc[i]).vec();
        L.block<3, 3>(0, 0) = w * Matrix3d::Identity() + Utility::skewSymmetric(q);
        L.block<3, 1>(0, 3) = q;
        L.block<1, 3>(3, 0) = -q.transpose();
        L(3, 3) = w;

        Quaterniond R_ij(Rimu[i]);
        w = R_ij.w();
        q = R_ij.vec();
        R.block<3, 3>(0, 0) = w * Matrix3d::Identity() - Utility::skewSymmetric(q);
        R.block<3, 1>(0, 3) = q;
        R.block<1, 3>(3, 0) = -q.transpose();
        R(3, 3) = w;

        A.block<4, 4>((i - 1) * 4, 0) = huber * (L - R);
    }

    //svd分解中最小奇异值对应的右奇异向量作为旋转四元数
    JacobiSVD<MatrixXd> svd(A, ComputeFullU | ComputeFullV);
    Matrix<double, 4, 1> x = svd.matrixV().col(3);
    Quaterniond estimated_R(x);
    ric = estimated_R.toRotationMatrix().inverse();
           

4、至少迭代计算了WINDOW_SIZE次,且R的奇异值大于0.25才认为标定成功

Vector3d ric_cov;
    ric_cov = svd.singularValues().tail<3>();
    if (frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25)
    {
        calib_ric_result = ric;
        return true;
    }
    else
        return false;
           

在初步标定完外参的旋转矩阵后,对旋转矩阵与平移向量的优化将在非线性优化中一起介绍。