SLAM十四講第二版項目代碼總結


github地址:https://github.com/gaoxiang12/slambook2/tree/master/ch13

雙目視覺里程計

頭文件

所有的類都在myslam命名空間中

1.common_include.h

定義常用的頭文件、EIgen矩陣格式

2.algorithm.h

common_include.h

三角化,已知位姿和歸一化平面的點,和書上有點區別,使用歸一化平面的點比較方便

/**
 * linear triangulation with SVD
 * @param poses     poses,  (已知)
 * @param points    points in normalized plane (已知)
 * @param pt_world  triangulated point in the world (輸出)
 * @return true if success
 */
inline bool triangulation(const std::vector<SE3> &poses,const std::vector<Vec3> points, Vec3 &pt_world) 

將opencv的點格式變為eigen2*1矩陣形式

inline Vec2 toVec2(const cv::Point2f p)

3.Config.h

common_include.h

class Config
/**
 * 配置類,使用SetParameterFile確定配置文件
 * 然后用Get得到對應值
 * 單例模式
 */
 static std::shared_ptr<Config> config_;
 cv::FileStorage file_;
 //設定參數文件名   參數文件讀給file_
 static bool SetParameterFile(const std::string &filename);
 static T Get(const std::string &key)   // 根據參數名key在file_中找到該參數的值

4.dataset.h

common_include.h frame.h camera.h

class Dataset
typedef std::shared_ptr<Dataset> Ptr;
Dataset(const std::string& dataset_path);
bool Init();  //初始化
Frame::Ptr NextFrame();  //創建並返回下一幀
Camera::Ptr GetCamera(int camera_id)   //根據id返回相機參數
std::string dataset_path_;  //數據集路徑
int current_image_index_ = 0;  //當前圖像id
std::vector<Camera::Ptr> cameras_;  //每一幀對應的相機參數(應該都是一樣的)

5.camera.h

common_include.h

class Camera
double fx_ = 0, fy_ = 0, cx_ = 0, cy_ = 0,baseline_ = 0;  // Camera intrinsics
SE3 pose_;             // 雙目到單目位姿變換 
SE3 pose_inv_;         // pose_的逆
Camera(double fx, double fy, double cx, double cy, double baseline,const SE3 &pose)
        : fx_(fx), fy_(fy), cx_(cx), cy_(cy), baseline_(baseline), pose_(pose) {
        pose_inv_ = pose_.inverse();
}
SE3 pose() //返回位姿
Mat33 K()  //返回3*3的內參矩陣
Vec3 world2camera(const Vec3 &p_w, const SE3 &T_c_w); //世界到相機
Vec3 camera2world(const Vec3 &p_c, const SE3 &T_c_w); //相機到世界
Vec2 camera2pixel(const Vec3 &p_c);  //相機到像素
Vec3 pixel2camera(const Vec2 &p_p, double depth = 1); //像素到相機(注意深度)
Vec3 pixel2world(const Vec2 &p_p, const SE3 &T_c_w, double depth = 1); //像素到世界
Vec2 world2pixel(const Vec3 &p_w, const SE3 &T_c_w); //世界到像素

6.feature.h

memory features2d.hpp common_include.h

struct Frame;
struct MapPoint;
struct Feature  (沒用類)  2D 特征點 在三角化之后會被關聯一個地圖點
typedef std::shared_ptr<Feature> Ptr;
std::weak_ptr<Frame> frame_;         // 持有該feature的frame
cv::KeyPoint position_;              // 2D提取位置
std::weak_ptr<MapPoint> map_point_;  // 關聯地圖點
bool is_outlier_ = false;       // 是否為異常點
bool is_on_left_image_ = true;  // 標識是否提在左圖,false為右圖
Feature(std::shared_ptr<Frame> frame, const cv::KeyPoint &kp): frame_(frame), position_(kp) {}  //構造

7.frame.h

camera.h common_include.h

struct MapPoint;
struct Feature;
struct Frame  每一幀分配獨立id,關鍵幀分配關鍵幀ID
typedef std::shared_ptr<Frame> Ptr;
unsigned long id_ = 0;           // id of this frame
unsigned long keyframe_id_ = 0;  // id of key frame
bool is_keyframe_ = false;       // 是否為關鍵幀
double time_stamp_;              // 時間戳,暫不使用
SE3 pose_;                       // Tcw 形式Pose
std::mutex pose_mutex_;          // Pose數據鎖
cv::Mat left_img_, right_img_;   // stereo images
std::vector<std::shared_ptr<Feature>> features_left_; //左圖像中提取的特征點
std::vector<std::shared_ptr<Feature>> features_right_; //左圖像在右圖像中
Frame(long id, double time_stamp, const SE3 &pose, const Mat &left,const Mat &right);
SE3 Pose(); //返回位姿,加鎖
void SetPose(const SE3 &pose)  //設定位姿 加鎖
void SetKeyFrame();  // 設置關鍵幀並分配並鍵幀id
static std::shared_ptr<Frame> CreateFrame(); // 工廠構建模式,分配id 

8.mappoint.h

common_include.h

typedef std::shared_ptr<MapPoint> Ptr;
unsigned long id_ = 0;  // ID
bool is_outlier_ = false;  //是否為外點
Vec3 pos_ = Vec3::Zero();  // 世界坐標
std::mutex data_mutex_;    //數據鎖
int observed_times_ = 0;  // 特征匹配時被觀測到的次數
std::list<std::weak_ptr<Feature>> observations_;  //觀測到該地圖點的特征
MapPoint(long id, Vec3 position);
Vec3 Pos() //返回世界坐標系中位置  (加鎖)
void SetPos(const Vec3 &pos)   //設定地圖點世界坐標 (加鎖)
void AddObservation(std::shared_ptr<Feature> feature) //添加觀測到該地圖點的特征 (加鎖)
void RemoveObservation(std::shared_ptr<Feature> feat); 
std::list<std::weak_ptr<Feature>> GetObs()  //獲取觀測到該地圖點的所有特征
static MapPoint::Ptr CreateNewMappoint(); //工廠模式 創建地圖點

9.map.h

common_include.h frame.h mappoint.h

class Map
typedef std::shared_ptr<Map> Ptr;
typedef std::unordered_map<unsigned long, MapPoint::Ptr> LandmarksType;  //地圖點
typedef std::unordered_map<unsigned long, Frame::Ptr> KeyframesType; //關鍵幀
void InsertKeyFrame(Frame::Ptr frame); //  增加一個關鍵幀
void InsertMapPoint(MapPoint::Ptr map_point); // 增加一個地圖頂點
LandmarksType GetAllMapPoints() // 獲取所有地圖點 (加鎖)
KeyframesType GetAllKeyFrames() // 獲取所有關鍵幀 (加鎖)
LandmarksType GetActiveMapPoints() // 獲取激活地圖點
KeyframesType GetActiveKeyFrames() // 獲取激活關鍵幀
void CleanMap(); // 清理map中觀測數量為零的點
private:
void RemoveOldKeyframe();  // 將舊的關鍵幀置為不活躍狀態
std::mutex data_mutex_;     //數據鎖
LandmarksType landmarks_;         // all landmarks
LandmarksType active_landmarks_;  // active landmarks
KeyframesType keyframes_;         // all key-frames
KeyframesType active_keyframes_;  // all key-frames
Frame::Ptr current_frame_ = nullptr;  //當前幀
int num_active_keyframes_ = 7;  // 激活的關鍵幀數量

10.frontend.h

map.h frame.h common_include.h

typedef std::shared_ptr<Frontend> Ptr;
bool AddFrame(Frame::Ptr frame);  // 外部接口,添加一個幀並計算其定位結果
void SetMap(Map::Ptr map) { map_ = map; }  //設定地圖
void SetBackend(std::shared_ptr<Backend> backend) { backend_ = backend; }  //設定后端
void SetViewer(std::shared_ptr<Viewer> viewer) { viewer_ = viewer; }  //可視化
FrontendStatus GetStatus() const { return status_; }  //返回當前狀態  初始化 丟失 正常追蹤
void SetCameras(Camera::Ptr left, Camera::Ptr right)   //設定左右相機
bool Track();  //追蹤  成功返回true
bool Reset();   //追蹤丟失之后重置 成功返回seccess
int TrackLastFrame();  // 追蹤上一幀 返回追蹤到的點
int EstimateCurrentPose();  //估計當前幀的位姿,返回內點個數
bool InsertKeyframe();  //將當前幀設為關鍵幀 並插入后端
bool StereoInit();  //雙目初始化
int DetectFeatures();  //檢測當前幀左圖像中的特征,返回數量 keypoint保存在當前幀
int FindFeaturesInRight(); //找到對應在右圖像中的特征 返回找到對應的特征數量
bool BuildInitMap();  //用單張圖像構建初始化特征
int TriangulateNewPoints();  //三角化當前幀的2d點  返回三角化的點
void SetObservationsForKeyFrame();  //將關鍵幀中的地圖點設為新的地圖點
FrontendStatus status_ = FrontendStatus::INITING;  //前段狀態 初始化 丟失 正常追蹤
Frame::Ptr current_frame_ = nullptr;  // 當前幀
Frame::Ptr last_frame_ = nullptr;     // 上一幀
Camera::Ptr camera_left_ = nullptr;   // 左側相機
Camera::Ptr camera_right_ = nullptr;  // 右側相機
Map::Ptr map_ = nullptr;  //地圖
std::shared_ptr<Backend> backend_ = nullptr;  //后端
std::shared_ptr<Viewer> viewer_ = nullptr;  //初始化
SE3 relative_motion_;  // 當前幀與上一幀的相對運動,用於估計當前幀pose初值
int tracking_inliers_ = 0;   //內點數,用於測試是否加入新的關鍵幀
int num_features_ = 200;
int num_features_init_ = 100;
int num_features_tracking_ = 50;
int num_features_tracking_bad_ = 20;
int num_features_needed_for_keyframe_ = 80;
cv::Ptr<cv::GFTTDetector> gftt_;  //opencv特征點檢測

11.backend.h

common_include.h frame.h map.h

typedef std::shared_ptr<Backend> Ptr;
Backend();  // 構造函數中啟動優化線程並掛起
void SetCameras(Camera::Ptr left, Camera::Ptr right) // 設置左右目的相機,用於獲得內外參
void SetMap(std::shared_ptr<Map> map) // 設置地圖
void UpdateMap(); // 觸發地圖更新,啟動優化 (喚醒線程)
void Stop();  // 關閉后端線程
private:
void BackendLoop(); // 后端線程
// 對給定關鍵幀和路標點進行優化
void Optimize(Map::KeyframesType& keyframes, Map::LandmarksType& landmarks);
std::shared_ptr<Map> map_;   //地圖
std::thread backend_thread_;
std::mutex data_mutex_;   //數據鎖
std::condition_variable map_update_;
std::atomic<bool> backend_running_;     
Camera::Ptr cam_left_ = nullptr, cam_right_ = nullptr;  //相機

12.visual_odometry.h

backend.h commom_include.h frontend.h viewer.h

class VisualOdometry
typedef std::shared_ptr<VisualOdometry> Ptr;
VisualOdometry(std::string &config_path);  //參數文件用於構造
bool Init();  //初始化
void Run();   //跑數據集VO
bool Step();    //啥啥啥
FrontendStatus GetFrontendStatus()  //獲取前端狀態
private:
bool inited_ = false; //是否初始化
std::string config_file_path_;  //參數文件路徑
Frontend::Ptr frontend_ = nullptr;  
Backend::Ptr backend_ = nullptr;
Map::Ptr map_ = nullptr;
Viewer::Ptr viewer_ = nullptr; 
Dataset::Ptr dataset_ = nullptr;  //數據集

13.viewer.h

thread pangolin.h common_include.h frame.h map.h

typedef std::shared_ptr<Viewer> Ptr;
Viewer();
void SetMap(Map::Ptr map)
void Close();
void AddCurrentFrame(Frame::Ptr current_frame); // 增加一個當前幀
void UpdateMap();   // 更新地圖
private:
void ThreadLoop();
void DrawFrame(Frame::Ptr frame, const float* color);  //繪制幀
void DrawMapPoints();  //繪制地圖點
void FollowCurrentFrame(pangolin::OpenGlRenderState& vis_camera);  //跟蹤當前幀
cv::Mat PlotFrameImage();  //繪制當前幀的特征點
Frame::Ptr current_frame_ = nullptr;
Map::Ptr map_ = nullptr;
std::thread viewer_thread_;
bool viewer_running_ = true;
std::unordered_map<unsigned long, Frame::Ptr> active_keyframes_;   
std::unordered_map<unsigned long, MapPoint::Ptr> active_landmarks_;
bool map_updated_ = false;  
std::mutex viewer_data_mutex_;

代碼流程

初始化

run_kitti_stereo.cpp 初始化一個VisualOdmetry類(記為VO),傳入參數文件,並調用VO類中的初始化函數Init()(記為voInit),voInit函數建立一個Dataset類,並調用Dataset類中的初始化函數Init()(記為dataInit),dataInit讀取相機參數和標定的參數。隨后voInit創建前端、后端、map、viewer類(生成對應的智能指針,調用一些set函數)。初始化完成后,run_kitti_stereo.cpp調用VO類中的啟動函數run()(記為vorun)。vorun循環調用VO類中的step()函數(除非step()函數范圍0,則系統停止),該step函數通過Dataset類中的NextFrame()函數,讀取下一幀,調整了圖片的大小,返回新的幀,隨后調用前端類(frontend)中的AddFrame函數添加該關鍵幀。

前端

Frontend() 初始化設定特征點個數的參數

AddFrame函數中根據當前狀態status_分為初始化StereoInit()、好的追蹤(當前幀設為下一幀,return繼續追蹤)、壞的追蹤都進行Track(),丟失Reset()。最后將當前幀設為上一幀。

StereoInit()

檢測左邊圖像特征DetectFeatures(),在右邊圖像找到左邊圖像對應的特征FindFeaturesInRight(),如果對應的特征點找的太少,返回false,這會使狀態仍是初始化,下一幀依然進行初始化。特征點足夠則建立初始地圖,BuildInitMap(),並將狀態改為好的追蹤。並向viewer類中增減當前幀,且更新viewer類中的地圖。BuildInitMap函數中進行三角化,初始化地圖點:左右對應的特征點已知,而初始化的左相機位置為原點,且右相機可以通過標定的參數求出,因此能進行三角化。初始化地圖點后,則插入地圖中map->InsertMapPoint(),並且將第一幀作為關鍵幀,由於更新了關鍵幀,后端則需要更新地圖了backend_->UpdateMap()。

Track()

追蹤時假設的是恆速模型,作為位姿估計的初始值,然后使用光流法進行追蹤TrackLastFrame,求出前后兩幀的對應特征點並返回追蹤到的特征點數,隨后估計當前幀的位姿EstimateCurrentPose,采用了直接法求解當前位姿,僅優化位姿,返回追蹤到的內點,如果追蹤到的內點太少則設為丟失,Reset(),這里重新初始化沒看到??隨后判斷是否插入關鍵幀InsertKeyframe()。最后計算相對上一幀的位姿用於下一幀估計的初始化,並向viewer中加入當前幀。

Frontend::InsertKeyframe()

追蹤的內點數大於閾值時將當前幀設為關鍵幀,並且調用map_->InsertKeyFrame,這里判斷關鍵幀是不是太多,太多需要刪除舊的關鍵幀,對於關鍵幀,首先為地圖點增加觀測到該點的特征點SetObservationsForKeyFrame(),然后提取新的特征點,找到這些點在右圖的對應點,使用三角化建立新的路標點,將新的特征點和路標點加入地圖觸發一次后端優化(更新地圖)backend->UpdateMap,viewer也更新地圖。

后端

構造函數初始化一個線程,回調函數BackendLoop

BackendLoop: 僅優化激活的Frames和Landmarks:map->GetActiveKeyFrames(),map_->GetActiveMapPoints(); g2o優化Optimize()。

總結

三個線程:前端、后端、可視化,沒有回環檢測。雙目算法的流程比較簡單,只追蹤左邊圖像的位姿(groundtruth給的也是左邊相機的位姿)。初始化時,檢測左邊圖像的特征,在右圖像中找到與左圖像對應的特征,如果找到的點太少,則下一幀繼續進行初始化。由於左圖像初始位姿為原點,而右圖像可以通過標定參數求得,下面進行三角化,建立初始地圖,第一幀作為關鍵幀(每次更新關鍵幀后端都進行一次優化)。假設恆速模型(最近兩幀位姿差恆定),作為估計初始值,主要用於光流法追蹤,得到前后兩個左邊圖像幀匹配的特征點。隨后用直接法切結位姿,如果追蹤的點太少則reset。隨后判斷是否加入關鍵幀,當追蹤的內點太少,則當前幀設為關鍵幀。如果關鍵幀太多需要進行剔除,這里直接去掉舊的關鍵幀和地圖點,使關鍵幀和地圖點維持一定的數量。添加關鍵幀繼續提取新的特征點,並找到右圖的對應點,三角化新的地圖點,並更新地圖,觸發后端優化。

實現細節

待補

注意:

kitti中給定的groundtruth是左邊相機的位姿。

丟失Reset好像什么都沒做
github地址:https://github.com/gaoxiang12/slambook2/tree/master/ch13

雙目視覺里程計

頭文件

所有的類都在myslam命名空間中

1.common_include.h

定義常用的頭文件、EIgen矩陣格式

2.algorithm.h

common_include.h

三角化,已知位姿和歸一化平面的點,和書上有點區別,使用歸一化平面的點比較方便

/**
 * linear triangulation with SVD
 * @param poses     poses,  (已知)
 * @param points    points in normalized plane (已知)
 * @param pt_world  triangulated point in the world (輸出)
 * @return true if success
 */
inline bool triangulation(const std::vector<SE3> &poses,const std::vector<Vec3> points, Vec3 &pt_world) 

將opencv的點格式變為eigen2*1矩陣形式

inline Vec2 toVec2(const cv::Point2f p)

3.Config.h

common_include.h

class Config
/**
 * 配置類,使用SetParameterFile確定配置文件
 * 然后用Get得到對應值
 * 單例模式
 */
 static std::shared_ptr<Config> config_;
 cv::FileStorage file_;
 //設定參數文件名   參數文件讀給file_
 static bool SetParameterFile(const std::string &filename);
 static T Get(const std::string &key)   // 根據參數名key在file_中找到該參數的值

4.dataset.h

common_include.h frame.h camera.h

class Dataset
typedef std::shared_ptr<Dataset> Ptr;
Dataset(const std::string& dataset_path);
bool Init();  //初始化
Frame::Ptr NextFrame();  //創建並返回下一幀
Camera::Ptr GetCamera(int camera_id)   //根據id返回相機參數
std::string dataset_path_;  //數據集路徑
int current_image_index_ = 0;  //當前圖像id
std::vector<Camera::Ptr> cameras_;  //每一幀對應的相機參數(應該都是一樣的)

5.camera.h

common_include.h

class Camera
double fx_ = 0, fy_ = 0, cx_ = 0, cy_ = 0,baseline_ = 0;  // Camera intrinsics
SE3 pose_;             // 雙目到單目位姿變換 
SE3 pose_inv_;         // pose_的逆
Camera(double fx, double fy, double cx, double cy, double baseline,const SE3 &pose)
        : fx_(fx), fy_(fy), cx_(cx), cy_(cy), baseline_(baseline), pose_(pose) {
        pose_inv_ = pose_.inverse();
}
SE3 pose() //返回位姿
Mat33 K()  //返回3*3的內參矩陣
Vec3 world2camera(const Vec3 &p_w, const SE3 &T_c_w); //世界到相機
Vec3 camera2world(const Vec3 &p_c, const SE3 &T_c_w); //相機到世界
Vec2 camera2pixel(const Vec3 &p_c);  //相機到像素
Vec3 pixel2camera(const Vec2 &p_p, double depth = 1); //像素到相機(注意深度)
Vec3 pixel2world(const Vec2 &p_p, const SE3 &T_c_w, double depth = 1); //像素到世界
Vec2 world2pixel(const Vec3 &p_w, const SE3 &T_c_w); //世界到像素

6.feature.h

memory features2d.hpp common_include.h

struct Frame;
struct MapPoint;
struct Feature  (沒用類)  2D 特征點 在三角化之后會被關聯一個地圖點
typedef std::shared_ptr<Feature> Ptr;
std::weak_ptr<Frame> frame_;         // 持有該feature的frame
cv::KeyPoint position_;              // 2D提取位置
std::weak_ptr<MapPoint> map_point_;  // 關聯地圖點
bool is_outlier_ = false;       // 是否為異常點
bool is_on_left_image_ = true;  // 標識是否提在左圖,false為右圖
Feature(std::shared_ptr<Frame> frame, const cv::KeyPoint &kp): frame_(frame), position_(kp) {}  //構造

7.frame.h

camera.h common_include.h

struct MapPoint;
struct Feature;
struct Frame  每一幀分配獨立id,關鍵幀分配關鍵幀ID
typedef std::shared_ptr<Frame> Ptr;
unsigned long id_ = 0;           // id of this frame
unsigned long keyframe_id_ = 0;  // id of key frame
bool is_keyframe_ = false;       // 是否為關鍵幀
double time_stamp_;              // 時間戳,暫不使用
SE3 pose_;                       // Tcw 形式Pose
std::mutex pose_mutex_;          // Pose數據鎖
cv::Mat left_img_, right_img_;   // stereo images
std::vector<std::shared_ptr<Feature>> features_left_; //左圖像中提取的特征點
std::vector<std::shared_ptr<Feature>> features_right_; //左圖像在右圖像中
Frame(long id, double time_stamp, const SE3 &pose, const Mat &left,const Mat &right);
SE3 Pose(); //返回位姿,加鎖
void SetPose(const SE3 &pose)  //設定位姿 加鎖
void SetKeyFrame();  // 設置關鍵幀並分配並鍵幀id
static std::shared_ptr<Frame> CreateFrame(); // 工廠構建模式,分配id 

8.mappoint.h

common_include.h

typedef std::shared_ptr<MapPoint> Ptr;
unsigned long id_ = 0;  // ID
bool is_outlier_ = false;  //是否為外點
Vec3 pos_ = Vec3::Zero();  // 世界坐標
std::mutex data_mutex_;    //數據鎖
int observed_times_ = 0;  // 特征匹配時被觀測到的次數
std::list<std::weak_ptr<Feature>> observations_;  //觀測到該地圖點的特征
MapPoint(long id, Vec3 position);
Vec3 Pos() //返回世界坐標系中位置  (加鎖)
void SetPos(const Vec3 &pos)   //設定地圖點世界坐標 (加鎖)
void AddObservation(std::shared_ptr<Feature> feature) //添加觀測到該地圖點的特征 (加鎖)
void RemoveObservation(std::shared_ptr<Feature> feat); 
std::list<std::weak_ptr<Feature>> GetObs()  //獲取觀測到該地圖點的所有特征
static MapPoint::Ptr CreateNewMappoint(); //工廠模式 創建地圖點

9.map.h

common_include.h frame.h mappoint.h

class Map
typedef std::shared_ptr<Map> Ptr;
typedef std::unordered_map<unsigned long, MapPoint::Ptr> LandmarksType;  //地圖點
typedef std::unordered_map<unsigned long, Frame::Ptr> KeyframesType; //關鍵幀
void InsertKeyFrame(Frame::Ptr frame); //  增加一個關鍵幀
void InsertMapPoint(MapPoint::Ptr map_point); // 增加一個地圖頂點
LandmarksType GetAllMapPoints() // 獲取所有地圖點 (加鎖)
KeyframesType GetAllKeyFrames() // 獲取所有關鍵幀 (加鎖)
LandmarksType GetActiveMapPoints() // 獲取激活地圖點
KeyframesType GetActiveKeyFrames() // 獲取激活關鍵幀
void CleanMap(); // 清理map中觀測數量為零的點
private:
void RemoveOldKeyframe();  // 將舊的關鍵幀置為不活躍狀態
std::mutex data_mutex_;     //數據鎖
LandmarksType landmarks_;         // all landmarks
LandmarksType active_landmarks_;  // active landmarks
KeyframesType keyframes_;         // all key-frames
KeyframesType active_keyframes_;  // all key-frames
Frame::Ptr current_frame_ = nullptr;  //當前幀
int num_active_keyframes_ = 7;  // 激活的關鍵幀數量

10.frontend.h

map.h frame.h common_include.h

typedef std::shared_ptr<Frontend> Ptr;
bool AddFrame(Frame::Ptr frame);  // 外部接口,添加一個幀並計算其定位結果
void SetMap(Map::Ptr map) { map_ = map; }  //設定地圖
void SetBackend(std::shared_ptr<Backend> backend) { backend_ = backend; }  //設定后端
void SetViewer(std::shared_ptr<Viewer> viewer) { viewer_ = viewer; }  //可視化
FrontendStatus GetStatus() const { return status_; }  //返回當前狀態  初始化 丟失 正常追蹤
void SetCameras(Camera::Ptr left, Camera::Ptr right)   //設定左右相機
bool Track();  //追蹤  成功返回true
bool Reset();   //追蹤丟失之后重置 成功返回seccess
int TrackLastFrame();  // 追蹤上一幀 返回追蹤到的點
int EstimateCurrentPose();  //估計當前幀的位姿,返回內點個數
bool InsertKeyframe();  //將當前幀設為關鍵幀 並插入后端
bool StereoInit();  //雙目初始化
int DetectFeatures();  //檢測當前幀左圖像中的特征,返回數量 keypoint保存在當前幀
int FindFeaturesInRight(); //找到對應在右圖像中的特征 返回找到對應的特征數量
bool BuildInitMap();  //用單張圖像構建初始化特征
int TriangulateNewPoints();  //三角化當前幀的2d點  返回三角化的點
void SetObservationsForKeyFrame();  //將關鍵幀中的地圖點設為新的地圖點
FrontendStatus status_ = FrontendStatus::INITING;  //前段狀態 初始化 丟失 正常追蹤
Frame::Ptr current_frame_ = nullptr;  // 當前幀
Frame::Ptr last_frame_ = nullptr;     // 上一幀
Camera::Ptr camera_left_ = nullptr;   // 左側相機
Camera::Ptr camera_right_ = nullptr;  // 右側相機
Map::Ptr map_ = nullptr;  //地圖
std::shared_ptr<Backend> backend_ = nullptr;  //后端
std::shared_ptr<Viewer> viewer_ = nullptr;  //初始化
SE3 relative_motion_;  // 當前幀與上一幀的相對運動,用於估計當前幀pose初值
int tracking_inliers_ = 0;   //內點數,用於測試是否加入新的關鍵幀
int num_features_ = 200;
int num_features_init_ = 100;
int num_features_tracking_ = 50;
int num_features_tracking_bad_ = 20;
int num_features_needed_for_keyframe_ = 80;
cv::Ptr<cv::GFTTDetector> gftt_;  //opencv特征點檢測

11.backend.h

common_include.h frame.h map.h

typedef std::shared_ptr<Backend> Ptr;
Backend();  // 構造函數中啟動優化線程並掛起
void SetCameras(Camera::Ptr left, Camera::Ptr right) // 設置左右目的相機,用於獲得內外參
void SetMap(std::shared_ptr<Map> map) // 設置地圖
void UpdateMap(); // 觸發地圖更新,啟動優化 (喚醒線程)
void Stop();  // 關閉后端線程
private:
void BackendLoop(); // 后端線程
// 對給定關鍵幀和路標點進行優化
void Optimize(Map::KeyframesType& keyframes, Map::LandmarksType& landmarks);
std::shared_ptr<Map> map_;   //地圖
std::thread backend_thread_;
std::mutex data_mutex_;   //數據鎖
std::condition_variable map_update_;
std::atomic<bool> backend_running_;     
Camera::Ptr cam_left_ = nullptr, cam_right_ = nullptr;  //相機

12.visual_odometry.h

backend.h commom_include.h frontend.h viewer.h

class VisualOdometry
typedef std::shared_ptr<VisualOdometry> Ptr;
VisualOdometry(std::string &config_path);  //參數文件用於構造
bool Init();  //初始化
void Run();   //跑數據集VO
bool Step();    //啥啥啥
FrontendStatus GetFrontendStatus()  //獲取前端狀態
private:
bool inited_ = false; //是否初始化
std::string config_file_path_;  //參數文件路徑
Frontend::Ptr frontend_ = nullptr;  
Backend::Ptr backend_ = nullptr;
Map::Ptr map_ = nullptr;
Viewer::Ptr viewer_ = nullptr; 
Dataset::Ptr dataset_ = nullptr;  //數據集

13.viewer.h

thread pangolin.h common_include.h frame.h map.h

typedef std::shared_ptr<Viewer> Ptr;
Viewer();
void SetMap(Map::Ptr map)
void Close();
void AddCurrentFrame(Frame::Ptr current_frame); // 增加一個當前幀
void UpdateMap();   // 更新地圖
private:
void ThreadLoop();
void DrawFrame(Frame::Ptr frame, const float* color);  //繪制幀
void DrawMapPoints();  //繪制地圖點
void FollowCurrentFrame(pangolin::OpenGlRenderState& vis_camera);  //跟蹤當前幀
cv::Mat PlotFrameImage();  //繪制當前幀的特征點
Frame::Ptr current_frame_ = nullptr;
Map::Ptr map_ = nullptr;
std::thread viewer_thread_;
bool viewer_running_ = true;
std::unordered_map<unsigned long, Frame::Ptr> active_keyframes_;   
std::unordered_map<unsigned long, MapPoint::Ptr> active_landmarks_;
bool map_updated_ = false;  
std::mutex viewer_data_mutex_;

代碼流程

初始化

run_kitti_stereo.cpp 初始化一個VisualOdmetry類(記為VO),傳入參數文件,並調用VO類中的初始化函數Init()(記為voInit),voInit函數建立一個Dataset類,並調用Dataset類中的初始化函數Init()(記為dataInit),dataInit讀取相機參數和標定的參數。隨后voInit創建前端、后端、map、viewer類(生成對應的智能指針,調用一些set函數)。初始化完成后,run_kitti_stereo.cpp調用VO類中的啟動函數run()(記為vorun)。vorun循環調用VO類中的step()函數(除非step()函數范圍0,則系統停止),該step函數通過Dataset類中的NextFrame()函數,讀取下一幀,調整了圖片的大小,返回新的幀,隨后調用前端類(frontend)中的AddFrame函數添加該關鍵幀。

前端

Frontend() 初始化設定特征點個數的參數

AddFrame函數中根據當前狀態status_分為初始化StereoInit()、好的追蹤(當前幀設為下一幀,return繼續追蹤)、壞的追蹤都進行Track(),丟失Reset()。最后將當前幀設為上一幀。

StereoInit()

檢測左邊圖像特征DetectFeatures(),在右邊圖像找到左邊圖像對應的特征FindFeaturesInRight(),如果對應的特征點找的太少,返回false,這會使狀態仍是初始化,下一幀依然進行初始化。特征點足夠則建立初始地圖,BuildInitMap(),並將狀態改為好的追蹤。並向viewer類中增減當前幀,且更新viewer類中的地圖。BuildInitMap函數中進行三角化,初始化地圖點:左右對應的特征點已知,而初始化的左相機位置為原點,且右相機可以通過標定的參數求出,因此能進行三角化。初始化地圖點后,則插入地圖中map->InsertMapPoint(),並且將第一幀作為關鍵幀,由於更新了關鍵幀,后端則需要更新地圖了backend_->UpdateMap()。

Track()

追蹤時假設的是恆速模型,作為位姿估計的初始值,然后使用光流法進行追蹤TrackLastFrame,求出前后兩幀的對應特征點並返回追蹤到的特征點數,隨后估計當前幀的位姿EstimateCurrentPose,采用了直接法求解當前位姿,僅優化位姿,返回追蹤到的內點,如果追蹤到的內點太少則設為丟失,Reset(),這里重新初始化沒看到??隨后判斷是否插入關鍵幀InsertKeyframe()。最后計算相對上一幀的位姿用於下一幀估計的初始化,並向viewer中加入當前幀。

Frontend::InsertKeyframe()

追蹤的內點數大於閾值時將當前幀設為關鍵幀,並且調用map_->InsertKeyFrame,這里判斷關鍵幀是不是太多,太多需要刪除舊的關鍵幀,對於關鍵幀,首先為地圖點增加觀測到該點的特征點SetObservationsForKeyFrame(),然后提取新的特征點,找到這些點在右圖的對應點,使用三角化建立新的路標點,將新的特征點和路標點加入地圖觸發一次后端優化(更新地圖)backend->UpdateMap,viewer也更新地圖。

后端

構造函數初始化一個線程,回調函數BackendLoop

BackendLoop: 僅優化激活的Frames和Landmarks:map->GetActiveKeyFrames(),map_->GetActiveMapPoints(); g2o優化Optimize()。

總結

三個線程:前端、后端、可視化,沒有回環檢測。雙目算法的流程比較簡單,只追蹤左邊圖像的位姿(groundtruth給的也是左邊相機的位姿)。初始化時,檢測左邊圖像的特征,在右圖像中找到與左圖像對應的特征,如果找到的點太少,則下一幀繼續進行初始化。由於左圖像初始位姿為原點,而右圖像可以通過標定參數求得,下面進行三角化,建立初始地圖,第一幀作為關鍵幀(每次更新關鍵幀后端都進行一次優化)。假設恆速模型(最近兩幀位姿差恆定),作為估計初始值,主要用於光流法追蹤,得到前后兩個左邊圖像幀匹配的特征點。隨后用直接法切結位姿,如果追蹤的點太少則reset。隨后判斷是否加入關鍵幀,當追蹤的內點太少,則當前幀設為關鍵幀。如果關鍵幀太多需要進行剔除,這里直接去掉舊的關鍵幀和地圖點,使關鍵幀和地圖點維持一定的數量。添加關鍵幀繼續提取新的特征點,並找到右圖的對應點,三角化新的地圖點,並更新地圖,觸發后端優化。

實現細節

待補

注意:

kitti中給定的groundtruth是左邊相機的位姿。

丟失Reset好像什么都沒做


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM