SLAM十四講---前端0.4----VO+局部地圖

地圖VO

之前計算位姿的方法是,兩兩幀間運用特徵匹配,PnP方法計算位姿並用g2o進行單元邊的3d-2d的優化。

雖說用g2o優化已經能夠避免一部分噪聲對RANSAC的PnP的影響。

但是,如果存在幀丟失的情況,可能距離比較遠的兩幀進行特徵匹配和PnP求解的話可能匹配上的點比較少。因此位姿計算可能不夠精確。並最終會出現追蹤丟失Lost狀態。
此外,全用兩幀間計算的話誤差累計漂移是個很嚴重的問題,

因此我們加入局部地圖的概念(local mapping),每一幀都爲VO維護的map_中提供新的地圖點(攜帶着自身的描述子),計算位姿時通過2d關鍵點與3d攜帶描述子的地圖點進行匹配,然後再進行3d-2d的PnP方法,能夠在一定程度上解決VO的LOST問題。

由於地圖點的座標直接採用世界座標,因此我們可以直接求出當前幀在世界座標系中的位姿Tcw,而不是Tcf (或者說而不是Tij)

這樣就講得通了,爲什麼說位姿圖中,誤差項爲Tij^(-1) * Ti * Tj,Ti和Tj是前端的結果,Tij是用特徵點法或者直接法或者IMU或者GPS得到的。終於明白了!!!!!!!!

下面着重考慮局部地圖 local mapping的VO,被稱爲——地圖VO

在這裏插入圖片描述

這裏的局部地圖和slam的建圖不一樣,暫時還沒有考慮全局地圖。這裏只是局部地圖,簡單的把一些比較近的、視野內的特徵點緩存到一個地方,是一個小地圖。而slam建圖過程中的全局地圖卻儲存了所有的特徵點。但是直接在全局上定位,計算量很大(全局地圖主要用於迴環檢測和地圖表達)。因此在這裏我們維護一個小的局部地圖,從而實現通過小地圖(局部地圖)進行定位當前位姿。

是否使用局部地圖,是出於對效率和精度這一矛盾體的仔細考量:
在這裏插入圖片描述
根據看代碼,有下面幾點發現:

(1)addKeyFrame,增加關鍵幀的時候,提供了一個功能:
因爲我們讓第一幀成爲了關鍵幀,所以加入第一幀的時候,把第一幀的特徵點加入到地圖中,在普通VO也確實是這麼做的,但是是根據setRef3DPoints這個單獨的函數做的。這裏是把功能集成到addKeyFrame中。

(2)Mat竟然也有尾插,這是很讓人興奮的:
desp_map.push_back( p->descriptor_ ); // 插上一行能觀察到的地圖點的描述子

(3)直接與地圖匹配,是地圖VO的特點,並且用了cv::FlannBasedMatcher matcher_flann_; 匹配器
matcher_flann_.match ( desp_map, descriptors_curr_, matches ); // flann matcher將嫩觀測到的地圖點與當前幀關鍵點進行匹配,注意,這裏和普通VO是不一樣的

(4)向地圖中增加關鍵幀的機制是:當前幀的關鍵點一旦和地圖點匹配上了,那麼就無視他。沒有匹配上的關鍵點需要映射到世界座標系中,增加局部地圖點的規模。具體看函數:void Map_VisualOdometry::addMapPoints()

(5)void Map_VisualOdometry::optimizeMap()這個函數實現了添加新點(嵌套着addMapPoints()),並且實現了控制局部地圖的規模,控制手段如下:
(i)根據匹配率篩選:
匹配率 = 匹配上次數/觀測到的次數
有些三維點被觀測到了很多次(isInFrame),但是匹配上的次數很少,不知道原因爲何,那既然匹配的少,就直接刪掉吧。
(ii)根據視角刪除點
angle = getViewAngle( curr_, iter->second );獲得視角,這個點如果在當前幀視野中太偏,即處在邊緣位置,那麼就刪掉。
(iii)當前幀與路標點的匹配少於100個,才加入地圖,否則地圖規模容易太大— 但我覺得問題不大,因爲有下面這一步:
(iv)直接控制地圖點規模,當地圖點增加到1000個以上的時候,迭代增加門檻map_point_erase_ratio_,這樣有更多匹配率比較低的點被刪除,一直刪到總地圖點小於1000個。------這一塊代碼是我自己補充的。

後話

由於我一直在0.1上增刪代碼,沒有直接用後面的0.2 0.3 0.4版本的代碼,所以直接繼承是一個好方法:

map_visual_odometry.h

繼承部分如下。子類叫做:map_visual_odometry

#ifndef MAP_VISUALODOMETRY_H
#define MAP_VISUALODOMETRY_H

#include "visual_odometry.h"

// 在這裏繼承一下父類(VisualOdometry),實現地圖VO

namespace myslam
{
  
  class Map_VisualOdometry:public VisualOdometry  // 繼承一下,避免大量修改原始VO代碼
  {
    
public:
    typedef shared_ptr<Map_VisualOdometry> Ptr;

附上優化地圖的代碼:(有我自己寫的一點) Map_VisualOdometry::optimizeMap()

void Map_VisualOdometry::optimizeMap()  // 加入新點,清理一波觀測次數太少的、太邊緣的點
{
    // remove the hardly seen and no visible points 
    for ( auto iter = map_->map_points_.begin(); iter != map_->map_points_.end(); )
    {
        if ( !curr_->isInFrame(iter->second->pos_) )   // 位姿估計完畢以後,如果當前幀看不到的地圖點,那麼清除掉
        {
            iter = map_->map_points_.erase(iter);
            continue;
        }
        float match_ratio = float(iter->second->matched_times_)/iter->second->visible_times_;  // 這個點的,匹配上次數/可觀測到的次數
        if ( match_ratio < map_point_erase_ratio_ )  // map_point_erase_ratio_ = 0.1,爲yaml讀入
        {
            iter = map_->map_points_.erase(iter);   // 在map_中去除這個點,因爲這個點匹配率太低了,可以控制局部地圖的規模
            continue;
        }
        
        double angle = getViewAngle( curr_, iter->second );
        if ( angle > M_PI/6. )
        {
            iter = map_->map_points_.erase(iter);  // 太邊緣的點也給擦掉
            continue;
        }
        if ( iter->second->good_ == false )
        {
            // TODO try triangulate this map point 
	    // 我覺得暫時不用——直接在上面3p-2p的g2o處同時優化位姿和對應的特徵點即可
	    // 也就是用二元邊,這個以後再加代碼吧
        }
        iter++;
    }
    
    if ( match_2dkp_index_.size()<100 )  // 匹配上少於100個再加入地圖,否則地圖規模容易太大
        addMapPoints();  // 在這裏加入新的地圖點
    if ( map_->map_points_.size() > 1000 )  // 控制地圖點規模,迭代增加門檻map_point_erase_ratio_
    {
      // TODO map is too large, remove some one
      map_point_erase_ratio_ += 0.05;
      while (map_->map_points_.size() > 1000)
      {
	Map_VisualOdometry::optimizeMap();  // 提高門檻,調用自己
      }
    }
    else 
        map_point_erase_ratio_ = 0.1;
    cout<<"map points: "<<map_->map_points_.size()<<endl;
}


至此前端完成。(普通VO + 地圖VO的實現)

暫時還沒有用到關鍵幀,僅僅是當前後兩幀距離較遠的時候插入關鍵幀。

關鍵幀主要是後端用的。

可以聯繫我獲取完整的帶註釋的和有我自己補充的、適應各種新版本庫(比如模板類sophus和新版本g2o)的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章