天天看點

VINS代碼主體結構和流程剖析——後端代碼剖析

目錄

  • 代碼剖析

代碼剖析

首先一定要對Keyframe這個類有充分的認識,其中比較難了解就是一些pose的資料結構:

/** @brief vio pose in backend */
  Eigen::Vector3d vio_T_w_i;
  Eigen::Matrix3d vio_R_w_i;
  /** @brief rectified pose in backend */
  Eigen::Vector3d T_w_i;
  Eigen::Matrix3d R_w_i;
  /** @brief vio pose in frontend */
  Eigen::Vector3d origin_vio_T;
  Eigen::Matrix3d origin_vio_R;
           
  • 第一個pose表示原始vio的位姿,也就是說直接保留前端發過來的pose,之後不做任何修改;
  • 第二個pose表示經過後端優化之後的位于map坐标系的pose;
  • 第三個pose表示被後端修改之後轉換到map坐标系的vio pose(沒有經過優化),這個要結合posegraph::addKeyFrame中的代碼了解,如下:
cur_kf->getVioPose(vio_P_cur, vio_R_cur);
    vio_P_cur = w_r_vio * vio_P_cur + w_t_vio;
    vio_R_cur = w_r_vio *  vio_R_cur;
    cur_kf->updateVioPose(vio_P_cur, vio_R_cur);
...
		if (cur_kf->findConnection(old_kf))
        {
            if (earliest_loop_index > loop_index || earliest_loop_index == -1)
                earliest_loop_index = loop_index;

            Vector3d w_P_old, w_P_cur, vio_P_cur;
            Matrix3d w_R_old, w_R_cur, vio_R_cur;
            old_kf->getVioPose(w_P_old, w_R_old);
            cur_kf->getVioPose(vio_P_cur, vio_R_cur);

            Vector3d relative_t;
            Quaterniond relative_q;
            relative_t = cur_kf->getLoopRelativeT();
            relative_q = (cur_kf->getLoopRelativeQ()).toRotationMatrix();
            w_P_cur = w_R_old * relative_t + w_P_old;
            w_R_cur = w_R_old * relative_q;
            double shift_yaw;
            Matrix3d shift_r;
            Vector3d shift_t; 
            if(use_imu)
            {
                shift_yaw = Utility::R2ypr(w_R_cur).x() - Utility::R2ypr(vio_R_cur).x();
                shift_r = Utility::ypr2R(Vector3d(shift_yaw, 0, 0));
            }
            else
                shift_r = w_R_cur * vio_R_cur.transpose();
            shift_t = w_P_cur - w_R_cur * vio_R_cur.transpose() * vio_P_cur; 
            // shift vio pose of whole sequence to the world frame
            if (old_kf->sequence != cur_kf->sequence && sequence_loop[cur_kf->sequence] == 0)
            {  
                w_r_vio = shift_r;
                w_t_vio = shift_t;
                vio_P_cur = w_r_vio * vio_P_cur + w_t_vio;
                vio_R_cur = w_r_vio *  vio_R_cur;
                cur_kf->updateVioPose(vio_P_cur, vio_R_cur);
                list<KeyFrame*>::iterator it = keyframelist.begin();
                for (; it != keyframelist.end(); it++)   
                {
                    if((*it)->sequence == cur_kf->sequence)
                    {
                        Vector3d vio_P_cur;
                        Matrix3d vio_R_cur;
                        (*it)->getVioPose(vio_P_cur, vio_R_cur);
                        vio_P_cur = w_r_vio * vio_P_cur + w_t_vio;
                        vio_R_cur = w_r_vio *  vio_R_cur;
                        (*it)->updateVioPose(vio_P_cur, vio_R_cur);
                    }
                }
                sequence_loop[cur_kf->sequence] = 1;
            }
            m_optimize_buf.lock();
            optimize_buf.push(cur_kf->index);
            m_optimize_buf.unlock();
        }
	}
           

其中shift_r和t表示當找到不同sequence之間的回環時,求得的兩個sequence之間的tf,之後會把所有新增的KeyFrame的vio位姿轉換到已有的sequence坐标下,儲存在上文提到的 vio_T_w_i,vio_R_w_i中,于是KeyFrame中的兩個vio pose就不一樣了,一個是前端發過來的pose,一個是将前端發過來的pose轉換到已有sequence坐标系(map坐标系,因為vins中暫時預設已有的sequence就是已經建立好的地圖,該sequence坐标系就是map坐标系)下的pose。,如果不提前加載地圖,那麼所有的KeyFrame應該位于同一sequence,w_r_vio就是個機關矩陣,不起任何作用,KeyFrame中的兩個vio pose就是相等的。

有個了這個了解之後,其他的算法才能了解清楚,還有個關鍵變量是r_drift,比較容易了解,看看optimize4DOF()。

...
            Vector3d cur_t, vio_t;
            Matrix3d cur_r, vio_r;
            cur_kf->getPose(cur_t, cur_r);
            cur_kf->getVioPose(vio_t, vio_r);
            m_drift.lock();
            yaw_drift = Utility::R2ypr(cur_r).x() - Utility::R2ypr(vio_r).x();
            r_drift = Utility::ypr2R(Vector3d(yaw_drift, 0, 0));
            t_drift = cur_t - r_drift * vio_t;
            ...
           

在優化完成之後,cur_kf的全局pose,也就是上文中的第二個pose被更新了,此時vio pose跟全局pose不相等了,r_drift就是兩者的內插補點,之後進來的KeyFrame都通過這個內插補點從vio pose中推導出全局pose,直到全局pose再被優化函數更新。

再來看看代碼整體邏輯和流程,主邏輯位于addKeyFrame()這個函數中:

VINS代碼主體結構和流程剖析——後端代碼剖析

如果不做回環這個後端基本就沒做啥了,detectLoop就是用DBOW在找回環,代碼邏輯很簡單就不詳解了,detectLoop如果通過DBOW找到了回環,會用回環找到的index索引到老的KeyFrame,并給到findConnection,由它去做進一步确認以及計算相對位姿,findConnection()也是一個很核心的函數,基本上論文中關于回環的部分都在這裡實作的,先看下主體邏輯:

VINS代碼主體結構和流程剖析——後端代碼剖析

核心就是用老的KeyFrame與目前KeyFrame進行比對得到結果,主要有以下幾個主要流程,論文中也都講過了:

  • matchByBriefDes(),通過feature描述子比對,對于論文中VII-B中2D-2D比對,這裡提一句,在建構KeyFrame時,會提取所有feature的描述子并儲存在window_brief_descriptors,此外,額外在全圖範圍内提取了大量feature,并提取了這些feature的描述子并儲存在brief_descriptors,這裡的matchByBriefDes()就是用目前KeyFrame的window_brief_descriptors與老的KeyFrame的brief_descriptors一一計算HammingDistance,大于一定門檻值則認為比對上了,比對上的feature數量大于一定門檻值(預設值25)才進入下一步。
  • PnPRANSAC(),對應論文中VII-B中3D-2D比對,名字很直覺了,通過上一步得到的比對到的feature對,進行PnPRANSAC操作,用以剔除掉錯誤的比對對,并計算出relative pose,同樣的,經過這一步後剩餘的比對feature對數量大于一定門檻值(預設值25)才進入下一步。
  • 最後判斷上一步得到的relative pose的yaw和t滿足一定門檻值(預設30度和20米以内),則認為得到回環。

之後就是把回環因子加入優化問題使用ceres進行優化的東西了,沒有太多可以講了。

繼續閱讀