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好像什么都没做

SLAM十四讲第二版项目代码总结的更多相关文章

  1. 视觉slam十四讲ch5 joinMap.cpp 代码注释(笔记版)

    #include <iostream> #include <fstream> using namespace std; #include <opencv2/core/co ...

  2. 视觉SLAM十四讲:从理论到实践 两版 PDF和源码

    视觉SLAM十四讲:从理论到实践 第一版电子版PDF 链接:https://pan.baidu.com/s/1SuuSpavo_fj7xqTYtgHBfw提取码:lr4t 源码github链接:htt ...

  3. 浅读《视觉SLAM十四讲:从理论到实践》--操作1--初识SLAM

    下载<视觉SLAM十四讲:从理论到实践>源码:https://github.com/gaoxiang12/slambook 第二讲:初识SLAM 2.4.2 Hello SLAM(书本P2 ...

  4. 《SLAM十四讲》个人学习知识点梳理

    0.引言 从六月末到八月初大概一个月时间一直在啃SLAM十四讲[1]这本书,这本书把SLAM中涉及的基本知识点都涵盖了,所以在这里做一个复习,对这本书自己学到的东西做一个梳理. 书本地址:http:/ ...

  5. 视觉slam十四讲第七章课后习题6

    版权声明:本文为博主原创文章,转载请注明出处: http://www.cnblogs.com/newneul/p/8545450.html 6.在PnP优化中,将第一个相机的观测也考虑进来,程序应如何 ...

  6. 视觉slam十四讲第七章课后习题7

    版权声明:本文为博主原创文章,转载请注明出处:http://www.cnblogs.com/newneul/p/8544369.html  7.题目要求:在ICP程序中,将空间点也作为优化变量考虑进来 ...

  7. 高博-《视觉SLAM十四讲》

    0 讲座 (1)SLAM定义 对比雷达传感器和视觉传感器的优缺点(主要介绍视觉SLAM) 单目:不知道尺度信息 双目:知道尺度信息,但测量范围根据预定的基线相关 RGBD:知道深度信息,但是深度信息对 ...

  8. SLAM十四讲中Sophus库安装

    Sophus截止目前有很多版本,其中大体分为两类,一种是用模板实现的方法,一种是用非模板类实现的,SLAM十四讲中使用的是非模板类库,clone Sophus: git clone http://gi ...

  9. 《视觉SLAM十四讲》第2讲

    目录 一 视觉SLAM中的传感器 二 经典视觉SLAM框架 三 SLAM问题的数学表述 注:原创不易,转载请务必注明原作者和出处,感谢支持! 本讲主要内容: (1) 视觉SLAM中的传感器 (2) 经 ...

随机推荐

  1. Paddle预训练模型应用工具PaddleHub

    Paddle预训练模型应用工具PaddleHub 本文主要介绍如何使用飞桨预训练模型管理工具PaddleHub,快速体验模型以及实现迁移学习.建议使用GPU环境运行相关程序,可以在启动环境时,如下图所 ...

  2. Cuda Stream流分析

    Cuda Stream流分析 Stream 一般来说,cuda c并行性表现在下面两个层面上: Kernel level Grid level Stream和event简介 Cuda stream是指 ...

  3. 【NX二次开发】Block UI 面收集器

    属性说明 属性   类型   描述   常规           BlockID    String    控件ID    Enable    Logical    是否可操作    Group    ...

  4. MySQL 到 ES 数据实时同步技术架构

    MySQL 到 ES 数据实时同步技术架构 我们已经讨论了数据去规范化的几种实现方式.MySQL 到 ES 数据同步本质上是数据去规范化多种实现方式中的一种,即通过"数据迁移同步" ...

  5. NAT介绍与配置

    一,NAT定义 二.NAT的分类 三,NAT配置实验 一,NAT定义 NAT(Network Address Translation),网络地址转换技术,随着Internet的发展,IPv4地址枯竭已 ...

  6. js笔记12

    1.元素的属性 div.attributes是所有标签属性构成的数组集合 div.classList是所有class名构成的数组集合 在classList的原型链上可以看到add()和remove() ...

  7. oscp-缓冲区溢出(持续更新)

    环境准备 Windows7虚拟机(我选了IE8,其实也没什么关系) 微软官方下载地址 These virtual machines expire after 90 days. We recommend ...

  8. 乘风破浪,Windows11官方原装4K壁纸,前卫的艺术数字设计

    Windows11预览版官方壁纸 默认主题Windows Windows.zip 月轮主题ThemeA ThemeA.zip 艺术石主题ThemeB ThemeB.zip 日升主题ThemeC The ...

  9. 基于 Electron 实现 uTools 的超级面板

    前言 为了进一步提高开发工作效率,最近我们基于 electron 开发了一款媲美 uTools 的开源工具箱 rubick.该工具箱不仅仅开源,最重要的是可以使用 uTools 生态内所有开源插件!这 ...

  10. 14.6、redis集群

    1.环境配置: 服务器名称 ip地址 实例6379 实例6380 实例6381 实例6381 实例6381 实例6381 controller-node1 172.16.1.90 主 从 主 从 主 ...