VINS-Mono代码精简版代码详解-无ROS依赖(二)

视觉惯性联合初始化

其对应的函数为

visualInitialAlign()

是在初始窗口中的图像帧完成SFM三维重建之后,即各图像帧在参考坐标系下的初始位姿都已经计算完成之后,执行的。该函数主要实现了陀螺仪的偏置校准(加速度偏置没有处理),计算速度V[0:n]、重力g、尺度s。
同时更新了Bgs后,IMU测量量需要repropagate;得到尺度s和重力g的方向后,需更新所有图像帧在世界座标系下的Ps、Rs、Vs。
1、 计算陀螺仪偏置,尺度,重力加速度和速度

bool result = VisualIMUAlignment(all_image_frame, Bgs, g, x);
    if(!result)
    {
        ROS_DEBUG("solve g failed!");
        return false;
    }

2、 获取所有图像帧的位姿Ps、Rs,并将其置为关键帧

for (int i = 0; i <= frame_count; i++)
    {
        Matrix3d Ri = all_image_frame[Headers[i].stamp.toSec()].R;
        Vector3d Pi = all_image_frame[Headers[i].stamp.toSec()].T;
        Ps[i] = Pi;
        Rs[i] = Ri;
        all_image_frame[Headers[i].stamp.toSec()].is_key_frame = true;
    }

3、 重新计算所有特征点的深度

  //将所有特征点的深度置为-1
    VectorXd dep = f_manager.getDepthVector();
    for (int i = 0; i < dep.size(); i++)
        dep[i] = -1;
    f_manager.clearDepth(dep);
    //重新计算特征点的深度
    Vector3d TIC_TMP[NUM_OF_CAM];
    for(int i = 0; i < NUM_OF_CAM; i++)
        TIC_TMP[i].setZero();
    ric[0] = RIC[0];
    f_manager.setRic(ric);
    f_manager.triangulate(Ps, &(TIC_TMP[0]), &(RIC[0]));

4、 陀螺仪的偏置bgs改变,重新计算预积分

for (int i = 0; i <= WINDOW_SIZE; i++)
    {
        pre_integrations[i]->repropagate(Vector3d::Zero(), Bgs[i]);
    }

5、 将Ps、Vs、depth尺度s缩放后转变为相对于第0帧图像座标系下
论文提到的以第一帧c0为基准座标系,通过相机座标系ck位姿得到IMU座标系bk位姿的公式为:
q b k c 0 = q c k c 0 ⊗ ( q c b ) − 1 s p ˉ b k c 0 = s p ˉ c k c 0 − R b k c 0 p c b \mathbf{q}_{b_k}^{c_0}=\mathbf{q}_{c_k}^{c_0} \otimes (\mathbf{q}_c^b)^{-1} \\ s\bar{\mathbf{p}}_{b_k}^{c_0}=s\bar{\mathbf{p}}_{c_k}^{c_0}-\mathbf{R}_{b_k}^{c_0}\mathbf{p}_c^b qbkc0=qckc0(qcb)1spˉbkc0=spˉckc0Rbkc0pcb
之前都是以第l帧为基准坐标系,转换到以第一帧b0为基准坐标系的话应该是:
s p b k b 0 = s p b k c l − s p b 0 c l = ( s p c k c l − R b k c l p c b ) − ( s p c 0 c l − R b 0 c l p c b ) s\mathbf{p}_{b_k}^{b_0}=s\mathbf{p}_{b_k}^{c_l}-s\mathbf{p}_{b_0}^{c_l}=(s{\mathbf{p}}_{c_k}^{c_l}-\mathbf{R}_{b_k}^{c_l}\mathbf{p}_c^b)-(s{\mathbf{p}}_{c_0}^{c_l}-\mathbf{R}_{b_0}^{c_l}\mathbf{p}_c^b) spbkb0=spbkclspb0cl=(spckclRbkclpcb)(spc0clRb0clpcb)

for (int i = frame_count; i >= 0; i--)
       Ps[i] = s * Ps[i] - Rs[i] * TIC[0] - (s * Ps[0] - Rs[0] * TIC[0]);    
   int kv = -1;
   map<double, ImageFrame>::iterator frame_i;
   for (frame_i = all_image_frame.begin(); frame_i != all_image_frame.end(); frame_i++)
   {
       if(frame_i->second.is_key_frame)
       {
           kv++;
           Vs[kv] = frame_i->second.R * x.segment<3>(kv * 3);
       }
   }
   for (auto &it_per_id : f_manager.feature)
   {
       it_per_id.used_num = it_per_id.feature_per_frame.size();
       if (!(it_per_id.used_num >= 2 && it_per_id.start_frame < WINDOW_SIZE - 2))
           continue;
       it_per_id.estimated_depth *= s;
   }

6、 通过将重力旋转到z轴上,得到世界座标系与摄像机座标系c0之间的旋转矩阵rot_diff

Matrix3d R0 = Utility::g2R(g);
   double yaw = Utility::R2ypr(R0 * Rs[0]).x();
   R0 = Utility::ypr2R(Eigen::Vector3d{-yaw, 0, 0}) * R0;
   g = R0 * g;
   Matrix3d rot_diff = R0;

7、 所有变量从参考座标系c0旋转到世界座标系w

  for (int i = 0; i <= frame_count; i++)
    {
        Ps[i] = rot_diff * Ps[i];
        Rs[i] = rot_diff * Rs[i];
        Vs[i] = rot_diff * Vs[i];
    }

陀螺仪偏置,尺度,重力加速度和速度初始化

所有帧的位姿 ( R c k c 0 , q c k c 0 ) (\mathbf{R}_{c_k}^{c_0},\mathbf{q}_{c_k}^{c_0}) (Rckc0,qckc0)表示相对于第一帧相机位姿态的坐标系。相机到IMU的外参为 ( R c b , q c b ) (\mathbf{R}_c^b,\mathbf{q}_c^b) (Rcb,qcb),则有
T c k c 0 = T b k c 0 T c b R c k c 0 = R b k c 0 R c b = R b k c 0 ( R c b ) − 1 s p c k c 0 = R b k c 0 p c b + s p b k c 0 &ThickSpace; ⟹ &ThickSpace; s p b k c 0 = s p c k c 0 − R b k c 0 p c b \mathbf{T}_{c_k}^{c_0} = \mathbf{T}_{b_k}^{c_0}\mathbf{T}_c^b \\ \mathbf{R}_{c_k}^{c_0}=\mathbf{R}_{b_k}^{c_0}\mathbf{R}_{c}^{b} = \mathbf{R}_{b_k}^{c_0}(\mathbf{R}_{c}^{b})^{-1} \\ s\mathbf{p}_{c_k}^{c_0}=\mathbf{R}_{b_k}^{c_0}\mathbf{p}_c^b+s\mathbf{p}_{b_k}^{c_0} \implies s\mathbf{p}_{b_k}^{c_0}=s\mathbf{p}_{c_k}^{c_0}-\mathbf{R}_{b_k}^{c_0}\mathbf{p}_c^b Tckc0=Tbkc0TcbRckc0=Rbkc0Rcb=Rbkc0(Rcb)1spckc0=Rbkc0pcb+spbkc0spbkc0=spckc0Rbkc0pcb
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 \mathbf{q}_{b_k}^{c_0} qbkc0 q b k + 1 c 0 \mathbf{q}_{b_{k+1}}^{c_0} qbk+1c0,从预积分中得到了相邻两帧旋转 γ ^ b k + 1 b k \hat \mathbf{\gamma}_{b_{k+1}}^{b_k} γ^bk+1bk,根据约束方程,建立所有相邻帧最小代价函数
min ⁡ ∑ k ∈ B ∥ q b k + 1 c 0 − 1 ⊗ q b k c 0 ⊗ γ b k + 1 b k ∥ \min \sum_{k \in \mathcal{B}} \lVert { \mathbf{q}_{b_{k+1}}^{c_0}}^{-1} \otimes \mathbf{q}_{b_k}^{c_0} \otimes \mathbf{\gamma}_{b_{k+1}}^{b_k} \rVert minkBqbk+1c01qbkc0γbk+1bk
其中,对陀螺仪偏置求IMU预积分项线性化,有
γ b k + 1 b k ≈ γ ^ b k + 1 b k ⊗ [ 1 1 2 J b w γ δ b w ] \mathbf{\gamma}_{b_{k+1}}^{b_k}\approx \hat \mathbf{\gamma}_{b_{k+1}}^{b_k} \otimes \begin {bmatrix} 1 \\ \frac{1}{2} \mathbf{J}_{b_w}^{\gamma}\delta{b_w} \end{bmatrix} γbk+1bkγ^bk+1bk[121Jbwγδbw]
在具体实现的时候,上述约束方程为
q b k + 1 c 0 − 1 ⊗ q b k c 0 ⊗ γ b k + 1 b k = [ 1 0 ] { \mathbf{q}_{b_{k+1}}^{c_0}}^{-1} \otimes \mathbf{q}_{b_k}^{c_0} \otimes \mathbf{\gamma}_{b_{k+1}}^{b_k} =\begin{bmatrix}1 \\ 0 \end{bmatrix} qbk+1c01qbkc0γbk+1bk=[10]
有:
γ b k + 1 b k = q b k c 0 − 1 ⊗ q b k + 1 c 0 ⊗ [ 1 0 ] \mathbf{\gamma}_{b_{k+1}}^{b_k}= {\mathbf{q}_{b_k}^{c_0}}^{-1} \otimes \mathbf{q}_{b_{k+1}}^{c_0} \otimes \begin{bmatrix}1 \\ 0 \end{bmatrix} γbk+1bk=qbkc01qbk+1c0[10]
代入上一阶展开式,有
γ ^ 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 \mathbf{\gamma}_{b_{k+1}}^{b_k} \otimes \begin{bmatrix} 1\\ \frac{1}{2} \mathbf{J}_{b_w}^{\gamma}\delta{b_w} \end{bmatrix}={\mathbf{q}_{b_k}^{c_0}}^{-1} \otimes \mathbf{q}_{b_{k+1}}^{c_0} \otimes \begin{bmatrix}1 \\ 0 \end{bmatrix} γ^bk+1bk[121Jbwγδbw]=qbkc01qbk+1c0[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 ) \mathbf{J}_{b_w}^{\gamma}\delta{b_w}=2({\hat \mathbf{\gamma}_{b_{k+1}}^{b_k}}^{-1} \otimes {\mathbf{q}_{b_k}^{c_0}}^{-1} \otimes \mathbf{q}_{b_{k+1}}^{c_0} ) Jbwγδbw=2(γ^bk+1bk1qbkc01qbk+1c0)
两侧乘以 J b w γ T {\mathbf{J}_{b_w}^{\gamma}}^{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 \mathbf{q}_{b_{k+1}}^{b_k}={\mathbf{q}_{b_k}^{c_0}}^{-1} \otimes\mathbf{q}_{b_{k+1}}^{c_0} qbk+1bk=qbkc01qbk+1c0,tmp_A即 J b w γ \mathbf{J}_{b_w}^{\gamma} Jbwγ,tmp_B即 2 ( γ ^ b k + 1 b k − 1 ⊗ q b k c 0 − 1 ⊗ q b k + 1 c 0 ) 2({\hat \mathbf{\gamma}_{b_{k+1}}^{b_k}}^{-1} \otimes {\mathbf{q}_{b_k}^{c_0}}^{-1} \otimes \mathbf{q}_{b_{k+1}}^{c_0} ) 2(γ^bk+1bk1qbkc01qbk+1c0),根据上面的代价函数构造 A x = B \mathbf{Ax=B} Ax=B,即
J b w γ T J b w γ δ b w = 2 J b w γ T ( γ ^ b k + 1 b k − 1 ⊗ q b k c 0 − 1 ⊗ q b k + 1 c 0 ) {\mathbf{J}_{b_w}^{\gamma}}^{T}\mathbf{J}_{b_w}^{\gamma}\delta{b_w}=2{\mathbf{J}_{b_w}^{\gamma}}^{T}({\hat \mathbf{\gamma}_{b_{k+1}}^{b_k}}^{-1} \otimes {\mathbf{q}_{b_k}^{c_0}}^{-1} \otimes \mathbf{q}_{b_{k+1}}^{c_0} ) JbwγTJbwγδbw=2JbwγT(γ^bk+1bk1qbkc01qbk+1c0)
然后用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]);
    }
}

速度/重力和尺度初始化 LinearAligment()

该函数需要优化的变量有速度/重力加速度和尺度:
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 ] \mathbf{X}_I^{3(n+1)+3+1}=[\mathbf{v}_{b_0}^{b_0},\mathbf{v}_{b_1}^{b_1},\dots,\mathbf{v}_{b_n}^{b_n},\mathbf{g}^{c_0},s] XI3(n+1)+3+1=[vb0b0,vb1b1,,vbnbn,gc0,s]
其中, v b k b k \mathbf{v}_{b_k}^{b_k} vbkbk是第 k k k帧图像在其IMU坐标系下的速度, g c 0 \mathbf{g}^{c_0} gc0是在第0帧相机坐标系下的重力向量。
在IMU预积分中,有
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 v b k + 1 w = R w b k ( v b k w − g w Δ t k + β b k + 1 b k \mathbf{R}_w^{b_k}\mathbf{p}_{b_{k+1}}^{w}=\mathbf{R}_w^{b_k}(\mathbf{p}_{b_k}^w+\mathbf{v}_{b_k}^w \Delta t_k-\frac{1}{2}\mathbf{g}^w\Delta t_k^2)+\alpha_{b_{k+1}}^{b_k} \\ \mathbf{R}_w^{b_k}\mathbf{v}_{b_{k+1}}^w=\mathbf{R}_w^{b_k}(\mathbf{v}_{b_k}^w-\mathbf{g}^w \Delta t_k+\beta_{b_{k+1}}^{b_k} Rwbkpbk+1w=Rwbk(pbkw+vbkwΔtk21gwΔtk2)+αbk+1bkRwbkvbk+1w=Rwbk(vbkwgwΔtk+βbk+1bk
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 ) β 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 ) \alpha_{b_{k+1}}^{b_k}=\mathbf{R}_{c_0}^{b_k}(s\mathbf{p}_{b_{k+1}}^{c_0}-s\mathbf{p}_{b_k}^{c_0}+\frac{1}{2}\mathbf{g}^{c_0}\Delta t_k^2-\mathbf{R}_{b_k}^{c_0}\mathbf{v}_{b_k}^{b_k}\Delta t_k) \\ \beta_{b_{k+1}}^{b_k}=\mathbf{R}_{c_0}^{b_k}(\mathbf{R}_{b_{k+1}}^{c_0}\mathbf{v}_{b_{k+1}}^{b_{k+1}}+\mathbf{g}^{c_0}\Delta t_k-\mathbf{R}_{b_k}^{c_0}\mathbf{v}_{b_k}^{b_k}) αbk+1bk=Rc0bk(spbk+1c0spbkc0+21gc0Δtk2Rbkc0vbkbkΔtk)βbk+1bk=Rc0bk(Rbk+1c0vbk+1bk+1+gc0ΔtkRbkc0vbkbk)
进一步,有
α 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+1}}^{b_k}=\mathbf{R}_{c_0}^{b_k}(s\mathbf{p}_{c_{k+1}}^{c_0}-\mathbf{R}_{b_{k+1}}^{c_0}\mathbf{p}_c^b-(s\mathbf{p}_{c_{k}}^{c_0}-\mathbf{R}_{b_{k}}^{c_0}\mathbf{p}_c^b)+\frac{1}{2}\mathbf{g}^{c_0}\Delta t_k^2-\mathbf{R}_{b_k}^{c_0}\mathbf{v}_{b_k}^{b_k}\Delta t_k) αbk+1bk=Rc0bk(spck+1c0Rbk+1c0pcb(spckc0Rbkc0pcb)+21gc0Δtk2Rbkc0vbkbkΔtk)
根据待优化变量,整理上述方程,转换成 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 \mathbf{R}_{c_0}^{b_k}(\mathbf{p}_{c_{k+1}}^{c_0}-\mathbf{p}_{c_k}^{c_0})s-\mathbf{v}_{b_k}^{b_k}\Delta t_k+\frac{1}{2}\mathbf{R}_{c_0}^{b_k}\Delta t_k^2\mathbf{g}^{c_0}=\delta \alpha_{b_{k+1}}^{b_k}+\mathbf{R}_{c_0}^{b_k}\mathbf{R}_{b_{k+1}}^{c_0}\mathbf{p})_c^b-\mathbf{p}_c^b Rc0bk(pck+1c0pckc0)svbkbkΔtk+21Rc0bkΔtk2gc0=δαbk+1bk+Rc0bkRbk+1c0p)cbpcb
转换成矩阵形式:
[ − 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 − p c b + R c 0 b k R b k + 1 c 0 p c b \begin{bmatrix} -\mathbf{I} \Delta t_k &amp;0 &amp;\frac{1}{2}\mathbf{R}_{c_0}^{b_k}\Delta t_k^2 &amp; \mathbf{R}_{c_0}^{b_k}(\mathbf{p}_{c_{k+1}}^{c_0}-\mathbf{p}_{c_k}^{c_0})\end{bmatrix} \begin{bmatrix} \mathbf{v}_{b_k}^{b_k} \\ \mathbf{v}_{b_{k+1}}^{b_{k+1}} \\ \mathbf{g}^{c_0} \\ s\end{bmatrix}=\hat \mathbf{\alpha}_{b_{k+1}}^{b_k}-\mathbf{p}_c^b+\mathbf{R}_{c_0}^{b_k}\mathbf{R}_{b_{k+1}}^{c_0}\mathbf{p}_c^b [IΔtk021Rc0bkΔtk2Rc0bk(pck+1c0pckc0)]vbkbkvbk+1bk+1gc0s=α^bk+1bkpcb+Rc0bkRbk+1c0pcb
同理,有
[ − 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 \begin{bmatrix} -\mathbf{I} &amp;\mathbf{R}_{c_0}^{b_k}\mathbf{R}_{b_{k+1}}^{c_0} &amp;\mathbf{R}_{c_0}^{b_k}\Delta t_k&amp;0\end{bmatrix} \begin{bmatrix} \mathbf{v}_{b_k}^{b_k} \\ \mathbf{v}_{b_{k+1}}^{b_{k+1}} \\ \mathbf{g}^{c_0} \\ s\end{bmatrix}=\hat \mathbf{\beta}_{b_{k+1}}^{b_k} [IRc0bkRbk+1c0Rc0bkΔtk0]vbkbkvbk+1bk+1gc0s=β^bk+1bk
最后得到
H 6 × 10 X I 10 × 1 = b 6 × 1 H T H X I 10 × 1 = H T b 6 × 1 \mathbf{H}^{6 \times 10}\mathbf{X}_I^{10 \times 1}=\mathbf{b}^{6 \times 1} \\ \mathbf{H}^T\mathbf{H}\mathbf{X}_I^{10 \times 1}=\mathbf{H}^T\mathbf{b}^{6 \times 1} H6×10XI10×1=b6×1HTHXI10×1=HTb6×1
使用LDLT分解求解上式。代码中,MatrixXd tmp_A(6, 10)就是 H \mathbf{H} H,VectorXd tmp_b(6,1)就是 b \mathbf{b} b

bool LinearAlignment(map<double, ImageFrame> &all_image_frame, Vector3d &g, VectorXd &x)

最后求得尺度 s s s和重力加速度 g \mathbf{g} 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 ^ ˉ c 0 + w 1 b 1 + w 2 b 2 = ∥ g ∥ g ^ ˉ c 0 + b T w \hat \mathbf{g}=\lVert \mathbf{g} \rVert \bar{\hat \mathbf{g}}^{c_0} +w_1b_1+w_2b_2= \lVert \mathbf{g} \rVert \bar{\hat \mathbf{g}}^{c_0} +\mathbf{b^Tw} g^=gg^ˉc0+w1b1+w2b2=gg^ˉc0+bTw
将其带入优化求解公式,待优化变量和观测方程变为
[ v b k b k v b k + 1 b k + 1 g c 0 s ] → [ v b k b k v b k + 1 b k + 1 w c 0 s ] z ^ b k + 1 b k = [ α ^ b k + 1 b k − p c b + R c 0 b k R b k + 1 c 0 p c b − 1 2 R c 0 b k Δ t k 2 ∥ g ∥ ⋅ g ^ ˉ c 0 β b k + 1 b k − R c 0 b k Δ t k ∥ g ∥ ⋅ g ^ ˉ c 0 ] \begin {bmatrix} \mathbf{v}_{b_k} ^{b_k} \\ \mathbf{v}_{b_{k+1}}^{b_{k+1}} \\ \mathbf{g}^{c_0} \\ s \end {bmatrix} \to \begin {bmatrix} \mathbf{v}_{b_k} ^{b_k} \\ \mathbf{v}_{b_{k+1}}^{b_{k+1}} \\ \mathbf{w}^{c_0} \\ s \end {bmatrix} \\ \hat \mathbf{z}_{b_{k+1}}^{b_k}=\begin{bmatrix} \hat \mathbf{\alpha}_{b_{k+1}}^{b_k}-\mathbf{p}_c^b+\mathbf{R}_{c_0}^{b_k}\mathbf{R}_{b_{k+1}}^{c_0}\mathbf{p}_c^b-\frac{1}{2}\mathbf{R}_{c_0}^{b_k}\Delta t_k^2 \lVert \mathbf{g} \rVert \cdot \bar{\hat \mathbf{g}}^{c_0} \\ \mathbf{\beta}_{b_{k+1}}^{b_{k}}-\mathbf{R}_{c_0}^{b_k}\Delta t_k \lVert \mathbf{g} \rVert \cdot \bar{\hat \mathbf{g}}^{c_0} \end{bmatrix} vbkbkvbk+1bk+1gc0svbkbkvbk+1bk+1wc0sz^bk+1bk=[α^bk+1bkpcb+Rc0bkRbk+1c0pcb21Rc0bkΔtk2gg^ˉc0βbk+1bkRc0bkΔtkgg^ˉc0]
对应的函数为

void RefineGravity(map<double, ImageFrame> &all_image_frame, Vector3d &g, VectorXd &x)

函数中的MatrixXd lxly(3, 2)= TangentBasis(g0); 即对应 b \mathbf{b} b,VectorXd dg = x.segment<2>(n_state - 3); 即对应 w \mathbf{w} w,用于寻找参数 b \mathbf{b} b的Algorithm 1对应的代码如下

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;
            }
        }
    }

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 根据对极几何求解这两帧的本质矩阵

cv::Mat E = cv::findFundamentalMat(ll, rr);

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 b c + 1 c k q b k + 1 b k ⋅ q c b = q c b ⋅ q b c + 1 c k \mathbf{R}_{c_{k+1}}^{b_k}=\mathbf{R}_{b_{k+1}}^{b_k}\cdot \mathbf{R}_c^b= \mathbf{R}_c^b \cdot \mathbf{R}_{b_{c+1}}^{c_k} \\ \mathbf{q}_{b_{k+1}}^{b_k}\cdot \mathbf{q}_c^b= \mathbf{q}_c^b \cdot \mathbf{q}_{b_{c+1}}^{c_k} Rck+1bk=Rbk+1bkRcb=RcbRbc+1ckqbk+1bkqcb=qcbqbc+1ck
则可以得到
( [ q b k + 1 b k ] L − [ q c k + 1 c k ] R ) q c b = Q k + 1 k ⋅ q c b = 0 ([\mathbf{q}_{b_{k+1}}^{b_k}]_L-[\mathbf{q}_{c_{k+1}}^{c_k}]_R)\mathbf{q}_c^b=\mathbf{Q}_{k+1}^k\cdot \mathbf{q}_c^b=\mathbf{0} ([qbk+1bk]L[qck+1ck]R)qcb=Qk+1kqcb=0
将多帧之间的等式关联在一起构建超定方程 A x = 0 Ax=0 Ax=0,对 A A A进行svd分解,其中最小的奇异值对应的右奇异向量便为 x x x的估计。对应的代码为

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;

在初步标定完外参的旋转矩阵后,后续会对旋转矩阵与平移向量进行继续优化。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值