手撕 视觉slam14讲 ch13 代码(4)VisualOdometry类和工程主函数

1.run_kitti_stereo.cpp

接下来我们开始进入工程主函数run_kitti_stereo.cpp,流程非常简单:

  1. 首先实例化了一个VisualOdometry类的类指针vo,传入config的地址
  2. 然后vo类指针调用 VisualOdometry 类中的 Init() 函数 ,进行VO的初始化。
  3. 在初始化成功后运行VisualOdometry类的Run()函数,开始正常运行VO。
#include <gflags/gflags.h>
#include"MYSLAM/visual_odometry.h"

//DEFINE_string参数1是一个变量;参数2是用户指定,给到参数1的;参数3,类似一个说明为这个变量的提示信息
//在程序中使用DEFINE_XXX函数定义的变量时,需要在每个变量前加上FLAGS_前缀。

DEFINE_string (config_file, "./config/default.yaml" ,"config file path");

int main (int argc , char **argv){
    google::ParseCommandLineFlags(&argc, &argv, true);

    MYSLAM::VisualOdometry::Ptr vo (new MYSLAM::VisualOdometry(FLAGS_config_file));

    // 初始化
    vo->Init();
    // 开始运行
    vo->Run();

    return 0;
}

所以在写主函数之前,我们需要先实现VisualOdometry类

2. VisualOdometry类:

VO 类是一个对外接口,有以下功能:

  •  1.读取配置文件
  •  2.通过Dataset::Init()函数设置相机的内参数,以及双目相机的外参数
  •  3.实例化并初始化frontend_,backend_,map_,viewer_四个对象。并且创建3个线程之间的联系

 visualodometry.h:

//  VO 对外接口
// 1.读取配置文件 
// 2.通过Dataset::Init()函数设置相机的内参数,以及双目相机的外参数 
// 3.实例化并初始化frontend_,backend_,map_,viewer_四个对象。并且创建3个线程之间的联系

#pragma once
#ifndef MYSLAM_VISUAL_ODOMETRY_H
#define MYSLAM_VISUAL_ODOMETRY_H

#include"MYSLAM/frontend.h"
#include"MYSLAM/backend.h"
#include"MYSLAM/viewer.h"
#include"MYSLAM/common_include.h"
#include"MYSLAM/dataset.h"

namespace MYSLAM{

//VO类
class VisualOdometry{

public:

    EIGEN_MAKE_ALIGNED_OPERATOR_NEW; //内存对齐
    typedef std::shared_ptr<VisualOdometry>Ptr;  //智能指针

    // 构造函数:   参数是配置文件路径
    VisualOdometry(std::string &config_path); 

    // 初始化,return true if success
    bool Init();

    // 开始运行 start vo in the dataset
    void Run();

    // 从dataset开始逐帧读取、运行
    bool Step();

    // 获取前端状态
    FrontendStatus  GetFrontendStatus() const {return frontend_->GetStatus();}

private:
    bool inited_=false;// 是否初始化
    std::string config_file_path_;// 参数文件路径   

    //实例化并初始化frontend_,backend_,map_,viewer_四个对象
    Frontend::Ptr frontend_=nullptr;
    Backend::Ptr backend_=nullptr;
    Map::Ptr map_=nullptr;
    Viewer::Ptr viewer_=nullptr;

    //实例化并初始化dataset对象
    Dataset::Ptr dataset_=nullptr;
};
}//namespace MYSLAM
#endif  // MYSLAM_VISUAL_ODOMETRY_H

因为需要实例化并初始化frontend_,backend_,map_,viewer_四个对象,因此我们需要再额外写出前端、后端、显示这三个类的构造函数

前端构造函数:Frontend::Frontend()

// 构造函数 :根据配置文件(Config类) 的参数来创建GFTT的特征点提取器。
    // num_features_是每帧最多提取的特征点数量,此外还保存一个参数num_features_init_,在后面的地图初始化中会用到
Frontend::Frontend() {

// 以下是 cv::GFTTDetector 的构造函数:
//     static Ptr<GFTTDetector> create( int maxCorners=1000, double qualityLevel=0.01, double minDistance=1,
//                                              int blockSize=3, bool useHarrisDetector=false, double k=0.04 );

//    其中,maxCorners 决定提取关键点最大个数;    qualityLevel 表示关键点强度阈值,只保留大于该阈值的关键点(阈值 = 最强关键点强度 * qualityLevel);
//     minDistance 决定关键点间的最小距离;blockSize 决定自相关函数累加区域,也决定了 Sobel 梯度时窗口尺寸;
//     useHarrisDetector 决定使用 Harris 判定依据还是 Shi-Tomasi 判定依据;k 仅在使用 Harris 判定依据时有效;
    
    gftt_=
        cv::GFTTDetector::create(Config::Get<int>("num_features"),0.01, 20);
        num_features_init_=Config::Get<int>("num_features_init");
        num_features_= Config::Get<int>("num_features");
}

前端的初始化主要就是根据配置文件(Config类) 的参数来创建GFTT的特征点提取器。num_features_是每帧最多提取的特征点数量,此外还保存一个参数num_features_init_,这个参数在后面的地图初始化中会用到。

后端构造函数:Backend::Backend()

// 构造函数 启动优化线程并挂起
Backend::Backend(){
    backend_running_.store(true);
    backend_thread_ = std::thread(std::bind(&Backend::BackendLoop, this));//上锁,唤醒一个wait的线程
}

在后端初始化中,主要是新开一个线程backend_thread_,然后把这个线程中运行的函数设置为Backend::BackendLoopI()函数。线程运行状态 backend_running_ 设置为true

显示构造函数:Viewer::Viewer()

Viewer::Viewer(){
    // 上锁,唤醒一个wait的线程
    viewer_thread_= std::thread( std::bind(& Viewer::ThreadLoop,this));
}

Viewer类的初始化里面 开启一个线程做显示,并绑定 Viewer::ThreadLoop() 函数。

另外,Map类的构造函数是空的,没有在构造函数里面设置任何东西。

然后我们来看 visualodometry.h中的函数:

 初始化函数 bool VisualOdometry::Init():

// 初始化,return true if success
bool VisualOdometry::Init(){
    //  1. 调用Config::SetParameterFile()函数读取配置文件,如果找不到对应文件就返回false。
    if ( Config::SetParameterFile(config_file_path_)==false)
    {
        return false;
    }
    
    // 2. 通过Dataset::Init()函数设置相机的内参数,以及双目相机的外参数。
    dataset_=Dataset::Ptr(new Dataset(Config::Get<std::string>("dataset_dir")));
    CHECK_EQ(dataset_->Init(), true);
    
    // 3. 实例化并初始化frontend_,backend_,map_,viewer_四个对象。并且创建3个线程之间的联系,
    frontend_=Frontend::Ptr(new Frontend);
    backend_=Backend::Ptr(new Backend);
    map_=Map::Ptr(new Map);
    viewer_=Viewer::Ptr(new Viewer);

    frontend_->SetBackend(backend_);
    frontend_->SetMap(map_);
    frontend_->SetViewer(viewer_);
    frontend_->SetCameras(dataset_->GetCamera(0),dataset_->GetCamera(1));

    backend_->SetMap(map_);
    backend_->SetCameras(dataset_->GetCamera(0), dataset_->GetCamera(1));

    viewer_->SetMap(map_);
    return true;
}

运行函数:VisualOdometry::Run():

// 开始运行 :一直循环step函数
void VisualOdometry::Run(){
    while (1)
    {
        if (Step()==false)
        {
           break;
        }
    }
    // 关闭后端线程
    backend_->Stop();

    // 关闭显示线程
    viewer_->Close();

    LOG(INFO) << "VO exit";
}

在Run()函数里面一直循环 Step() 函数,直到 Step() 函数返回false,就break出去关闭后端、显示线程

Step() 函数:bool VisualOdometry::Step():

// Step() 函数: 其实也一直在循环两个函数 Dataset::NextFrame() 和 Frontend::AddFrame() 。 
bool VisualOdometry::Step(){

    // 从数据集里面读取一下帧图像,然后调用 Frame::CreateFrame() 函数为每一帧赋一个id号
    Frame::Ptr new_frame = dataset_->NextFrame();
    if (new_frame == nullptr) return false;

    // 调用AddFrame()函数:去根据前端状态变量FrontendStatus,运行不同的三个函数,StereoInit(),Track()和Reset()
    auto t1 = std::chrono::steady_clock::now();        //计时 时间戳t1
    bool success = frontend_->AddFrame(new_frame);
    auto t2 = std::chrono::steady_clock::now();     //计时 时间戳t2
    auto time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);   //计算用时 (t2-t1)
    LOG(INFO) << "VO cost time: " << time_used.count() << " seconds.";
    return success;
}

之后就会进入前端主函数 Frontend::AddFrame(),开始SLAM的过程...

Frontend::AddFrame()函数:

//  外部接口,添加一个帧并计算其定位结果
bool Frontend::AddFrame(Frame::Ptr frame ){//用frame接收传入的new_frame
    current_frame_ = frame;//令当前帧等于上一行的frame
    // 判断跟踪状态
     switch(status_){
        // 如果状态为初始化,进入双目初始化
        case FrontendStatus::INITING: 
            StereoInit();
            break;

        // 跟踪状态好 或者  跟踪状态差,进入跟踪
        case FrontendStatus::TRACKING_GOOD:
        case FrontendStatus::TRACKING_BAD:
            Track();
            break;

        // 如果跟踪丢失, 重定位
        case FrontendStatus::LOST:
            Reset();
            break;
     }
    //对当前帧操作完毕,更新帧:令当前帧变为上一帧
    last_frame_ =current_frame_;
    return true;
}

根据前端状态变量FrontendStatus,运行不同的三个函数,StereoInit(),Track()和Reset()

视觉SLAM14》第13主要介绍了多视图几何(Multi-view Geometry)在视觉SLAM中的重要性和应用。本章内容包括三维重建、相机姿态估计、稠密地图构建、三维点云的优化等方面。 首先,介绍了三维重建的基本概念和方法。通过多视图之间的特征匹配和三角化,可以获取相机位置和场景的三维结构。其中使用了基础矩阵、本质矩阵和投影矩阵等几何工具进行相机位置估计。 其次,解了相机姿态估计的原理和方法。通过将特征点在不同视角中的投影进行匹配,可以计算得到相机之间的位姿变化。常用的方法包括通过两帧图像的本质矩阵或单应性矩阵来进行计算。 然后,述了稠密地图构建的过程。通过对特征点云进一步处理,可以得到更加丰富的场景信息。常用的方法有基于三维重建的稠密地图构建和基于场景几何关系的稠密地图构建等。 最后,介绍了三维点云的优化方法。从视觉SLAM系统的角度出发,通过优化相机的位姿和特征点的三维位置,提高系统的准确性和鲁棒性。常用的方法有基于图优化的方法和基于束优化的方法等。 综上所述,《视觉SLAM14》第13详细介绍了多视图几何在视觉SLAM中的关键技术和应用。可以通过多视图的特征匹配和三角化,实现三维重建和相机姿态估计。同时,通过稠密地图构建和三维点云的优化,提高系统的精度和鲁棒性。这些技术对于实现高效的视觉SLAM系统具有重要意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值