slam十四讲第二版 pdf_视觉SLAM十四讲 第九章 实践:设计前端 局部地图 代码解析...

总体思路

增加了地图点模块,并且增加了对地图点的优化模块,维护地图规模。整个程序的流程如下:

  1. 输入:color,depth以及二者对应的时间戳
  2. 初始帧
    1. 当前帧和参考帧都设置为输入帧
    2. 提取特征点
    3. 计算描述子
    4. 增加关键帧
  3. 其他帧
    1. 将当前帧设置为输入帧
    2. 当前帧的位姿设置为参考帧的位姿
    3. 提取特征点
    4. 计算描述子
    5. 特征匹配
      1. 地图中的点都是三维坐标
        1. 首先检查点是否在视野范围内(先预设当前帧的位姿为参考帧的位姿)
        2. 匹配的时候,地图点的描述子进行匹配,
    6. PnP估计位姿
    7. 检查位姿是否OK,位姿的R,t不超过阈值量属OK。
      1. 如果ok
        1. 将当前帧的位姿更新为估计的位姿。
        2. 优化地图点
          1. 用更新的位姿计算地图点是否在当前帧可见,删除地图点
          2. 由匹配次数除以可见次数,得到一个匹配比例。如果匹配比例小于设定的匹配比例,则删除地图点。
          3. 获取当前帧和地图点的视角,向量为3D坐标减去相机中心坐标作为法向量n,
            乘以点的法向量,求反余弦,即为点与平面的视角。如果视角大于
            ,去掉该地图点。
          4. 如果地图点数量小于100,增加地图点。
          5. 如果地图点数量大于1000,将去掉的比例增加0.05。
          6. 否则擦除比例为0.1。
        3. 检查关键帧是否ok,如果Ok
          1. 增加关键帧
      2. 否则
          1. 对丢帧计数
          2. 如果计数超过阈值将状态更新为丢失。

visual_odometry.cpp

/*
 * <one line to give the program's name and a brief idea of what it does.>
 * Copyright (C) 2016  <copyright holder> <email>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <algorithm>
#include <boost/timer.hpp>

#include "myslam/config.h"
#include "myslam/visual_odometry.h"
#include "myslam/g2o_types.h"

namespace myslam
{

VisualOdometry::VisualOdometry() :
    state_ ( INITIALIZING ), ref_ ( nullptr ), curr_ ( nullptr ), map_ ( new Map ), num_lost_ ( 0 ), num_inliers_ ( 0 ), matcher_flann_ ( new cv::flann::LshIndexParams ( 5,10,2 ) )
{
    num_of_features_    = Config::get<int> ( "number_of_features" );
    scale_factor_       = Config::get<double> ( "scale_factor" );
    level_pyramid_      = Config::get<int> ( "level_pyramid" );
    match_ratio_        = Config::get<float> ( "match_ratio" );
    max_num_lost_       = Config::get<float> ( "max_num_lost" );
    min_inliers_        = Config::get<int> ( "min_inliers" );
    key_frame_min_rot   = Config::get<double> ( "keyframe_rotation" );
    key_frame_min_trans = Config::get<double> ( "keyframe_translation" );
    map_point_erase_ratio_ = Config::get<double> ( "map_point_erase_ratio" );
    orb_ = cv::ORB::create ( num_of_features_, scale_factor_, level_pyramid_ );
}

VisualOdometry::~VisualOdometry()
{

}

bool VisualOdometry::addFrame ( Frame::Ptr frame )
{
    switch ( state_ )
    {
    case INITIALIZING:
    {
        state_ = OK;
        curr_ = ref_ = frame;
        // extract features from first frame and add them into map
        extractKeyPoints();
        computeDescriptors();
        addKeyFrame();      // the first frame is a key-frame
        break;
    }
    case OK:
    {
        curr_ = frame;
        curr_->T_c_w_ = ref_->T_c_w_;
        extractKeyPoints();
        computeDescriptors();
        featureMatching();
        poseEstimationPnP();
        if ( checkEstimatedPose() == true ) // a good estimation
        {
            curr_->T_c_w_ = T_c_w_estimated_;
            optimizeMap();
            num_lost_ = 0;
            if ( checkKeyFrame() == true ) // is a key-frame
            {
                addKeyFrame();
            }
        }
        else // bad estimation due to various reasons
        {
            num_lost_++;
            if ( num_lost_ > max_num_lost_ )
            {
                state_ = LOST;
            }
            return false;
        }
        break;
    }
    case LOST:
    {
        cout<<"vo has lost."<<endl;
        break;
    }
    }

    return true;
}

void VisualOdometry::extractKeyPoints()
{
    boost::timer timer;
    orb_->detect ( curr_->color_, keypoints_curr_ );
    cout<<"extract keypoints cost time: "<<timer.elapsed() <<endl;
}

void VisualOdometry::computeDescriptors()
{
    boost::timer timer;
    orb_->compute ( curr_->color_, keypoints_curr_, descriptors_curr_ );
    cout<<"descriptor computation cost time: "<<timer.elapsed() <<endl;
}

void VisualOdometry::featureMatching()
{
    boost::timer timer;
    vector<cv::DMatch> matches;
    // select the candidates in map 
    Mat desp_map;
    vector<MapPoint::Ptr> candidate;
    for ( auto& allpoints: map_->map_points_ )
    {
        MapPoint::Ptr& p = allpoints.second;
        // check if p in curr frame image 
        if ( curr_->isInFrame(p->pos_) )
        {
            // add to candidate 
            p->visible_times_++;
            candidate.push_back( p );
            desp_map.push_back( p->descriptor_ );
        }
    }
    
    matcher_flann_.match ( desp_map, descriptors_curr_, matches );
    // select the best matches
    float min_dis = std::min_element (
                        matches.begin(), matches.end(),
                        [] ( const cv::DMatch& m1, const cv::DMatch& m2 )
    {
        return m1.distance < m2.distance;
    } )->distance;

    match_3dpts_.clear();
    match_2dkp_index_.clear();
    for ( cv::DMatch& m : matches )
    {
        if ( m.distance < max<float> ( min_dis*match_ratio_, 30.0 ) )
        {
            match_3dpts_.push_back( candidate[m.queryIdx] );
            match_2dkp_index_.push_back( m.trainIdx );
        }
    }
    cout<<"good matches: "<<match_3dpts_.size() <<endl;
    cout<<"match cost time: "<<timer.elapsed() <<endl;
}

void VisualOdometry::poseEstimationPnP()
{
    // construct the 3d 2d observations
    vector<cv::Point3f> pts3d;
    vector<cv::Point2f> pts2d;

    for ( int index:match_2dkp_index_ )
    {
        pts2d.push_back ( keypoints_curr_[index].pt );
    }
    for ( MapPoint::Ptr pt:match_3dpts_ )
    {
        pts3d.push_back( pt->getPositionCV() );
    }

    Mat K = ( cv::Mat_<double> ( 3,3 ) <<
              ref_->camera_->fx_, 0, ref_->camera_->cx_,
              0, ref_->camera_->fy_, ref_->camera_->cy_,
              0,0,1
            );
    Mat rvec, tvec, inliers;
    cv::solvePnPRansac ( pts3d, pts2d, K, Mat(), rvec, tvec, false, 100, 4.0, 0.99, inliers );
    num_inliers_ = inliers.rows;
    cout<<"pnp inliers: "<<num_inliers_<<endl;
    T_c_w_estimated_ = SE3 (
                           SO3 ( rvec.at<double> ( 0,0 ), rvec.at<double> ( 1,0 ), rvec.at<double> ( 2,0 ) ),
                           Vector3d ( tvec.at<double> ( 0,0 ), tvec.at<double> ( 1,0 ), tvec.at<double> ( 2,0 ) )
                       );

    // using bundle adjustment to optimize the pose
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6,2>> Block;
    Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>();
    Block* solver_ptr = new Block ( linearSolver );
    g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg ( solver_ptr );
    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm ( solver );

    g2o::VertexSE3Expmap* pose = new g2o::VertexSE3Expmap();
    pose->setId ( 0 );
    pose->setEstimate ( g2o::SE3Quat (
        T_c_w_estimated_.rotation_matrix(), T_c_w_estimated_.translation()
    ));
    optimizer.addVertex ( pose );

    // edges
    for ( int i=0; i<inliers.rows; i++ )
    {
        int index = inliers.at<int> ( i,0 );
        // 3D -> 2D projection
        EdgeProjectXYZ2UVPoseOnly* edge = new EdgeProjectXYZ2UVPoseOnly();
        edge->setId ( i );
        edge->setVertex ( 0, pose );
        edge->camera_ = curr_->camera_.get();
        edge->point_ = Vector3d ( pts3d[index].x, pts3d[index].y, pts3d[index].z );
        edge->setMeasurement ( Vector2d ( pts2d[index].x, pts2d[index].y ) );
        edge->setInformation ( Eigen::Matrix2d::Identity() );
        optimizer.addEdge ( edge );
        // set the inlier map points 
        match_3dpts_[index]->matched_times_++;
    }

    optimizer.initializeOptimization();
    optimizer.optimize ( 10 );

    T_c_w_estimated_ = SE3 (
        pose->estimate().rotation(),
        pose->estimate().translation()
    );
    
    cout<<"T_c_w_estimated_: "<<endl<<T_c_w_estimated_.matrix()<<endl;
}

bool VisualOdometry::checkEstimatedPose()
{
    // check if the estimated pose is good
    if ( num_inliers_ < min_inliers_ )
    {
        cout<<"reject because inlier is too small: "<<num_inliers_<<endl;
        return false;
    }
    // if the motion is too large, it is probably wrong
    SE3 T_r_c = ref_->T_c_w_ * T_c_w_estimated_.inverse();
    Sophus::Vector6d d = T_r_c.log();
    if ( d.norm() > 5.0 )
    {
        cout<<"reject because motion is too large: "<<d.norm() <<endl;
        return false;
    }
    return true;
}

bool VisualOdometry::checkKeyFrame()
{
    SE3 T_r_c = ref_->T_c_w_ * T_c_w_estimated_.inverse();
    Sophus::Vector6d d = T_r_c.log();
    Vector3d trans = d.head<3>();
    Vector3d rot = d.tail<3>();
    if ( rot.norm() >key_frame_min_rot || trans.norm() >key_frame_min_trans )
        return true;
    return false;
}

void VisualOdometry::addKeyFrame()
{
    if ( map_->keyframes_.empty() )
    {
        // first key-frame, add all 3d points into map
        for ( size_t i=0; i<keypoints_curr_.size(); i++ )
        {
            double d = curr_->findDepth ( keypoints_curr_[i] );
            if ( d < 0 ) 
                continue;
            Vector3d p_world = ref_->camera_->pixel2world (
                Vector2d ( keypoints_curr_[i].pt.x, keypoints_curr_[i].pt.y ), curr_->T_c_w_, d
            );
            Vector3d n = p_world - ref_->getCamCenter();
            n.normalize();
            MapPoint::Ptr map_point = MapPoint::createMapPoint(
                p_world, n, descriptors_curr_.row(i).clone(), curr_.get()
            );
            map_->insertMapPoint( map_point );
        }
    }
    
    map_->insertKeyFrame ( curr_ );
    ref_ = curr_;
}

void VisualOdometry::addMapPoints()
{
    // add the new map points into map
    vector<bool> matched(keypoints_curr_.size(), false); 
    for ( int index:match_2dkp_index_ )
        matched[index] = true;
    for ( int i=0; i<keypoints_curr_.size(); i++ )
    {
        if ( matched[i] == true )   
            continue;
        double d = ref_->findDepth ( keypoints_curr_[i] );
        if ( d<0 )  
            continue;
        Vector3d p_world = ref_->camera_->pixel2world (
            Vector2d ( keypoints_curr_[i].pt.x, keypoints_curr_[i].pt.y ), 
            curr_->T_c_w_, d
        );
        Vector3d n = p_world - ref_->getCamCenter();
        n.normalize();
        MapPoint::Ptr map_point = MapPoint::createMapPoint(
            p_world, n, descriptors_curr_.row(i).clone(), curr_.get()
        );
        map_->insertMapPoint( map_point );
    }
}

void 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_ )
        {
            iter = map_->map_points_.erase(iter);
            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 
        }
        iter++;
    }
    
    if ( match_2dkp_index_.size()<100 )
        addMapPoints();
    if ( map_->map_points_.size() > 1000 )  
    {
        // TODO map is too large, remove some one 
        map_point_erase_ratio_ += 0.05;
    }
    else 
        map_point_erase_ratio_ = 0.1;
    cout<<"map points: "<<map_->map_points_.size()<<endl;
}

double VisualOdometry::getViewAngle ( Frame::Ptr frame, MapPoint::Ptr point )
{
    Vector3d n = point->pos_ - frame->getCamCenter();
    n.normalize();
    return acos( n.transpose()*point->norm_ );
}


}

《视觉SLAM十四讲》中对VO中加入地图的阐述:

改进地图:将VO匹配到的特征点放到地图中,并将当前帧与地图点进行匹配,计算位姿。这里的地图是把各帧特征点缓存到一个地方构成的特征点集合。

优势:能够维护一个不断更新的地图。只要地图是正确的,即使中间某帧出了差错,仍有希望求出之后那些帧的正确位置。

fdc91407bedf35135435b9cf4c0e3cfb.png

c25081822eca016620b06ef0cf841e82.png
两两VO与地图VO工作原理的差异

局部地图:描述了附近的特征点信息,只保留离相机当前位置较近的特征点,把远的或视野外的特征点丢掉。这些特征点用来和当前帧匹配来求相机位置,所以希望它能够做得比较快。

全局地图:记录了从SLAM运行以来的所有特征点。规模要大一些,主要用来表达整个环境。但直接在全局地图上定位,对计算机的负担太大。主要用于回环检测和地图表达。

视觉里程计中,直接用于定位的局部地图,随着相机运动,往地图里添加新的特征点。是否使用地图取决于对精度-效率这个矛盾的把握。可以出于效率的考虑,使用两两无结构式的VO,也可以为了更好的精度,构建局部地图乃至考虑地图的优化。

局部地图的考验:维护它的规模。为保证实时性,需要保证地图规模不至于太大(否则匹配会消耗大量的时间)。单个帧与地图的特征匹配存在一些加速手段。

地图优化:

删除不在视野内的点,在匹配数量减少时添加新点,等等。

使用三角化来更新特征点的世界坐标,或考虑更好地动态管理地图规模的策略。

特征匹配。匹配之前,从地图中拿出一些候选点(出现在视野内的点),然后将它们与当前帧的特征描述子进行匹配。

关键帧:后端优化的主要对象。每当相机运动经过一定间隔,就取一个新的关键帧并保存起来。这些关键帧的位姿将被优化,而位于两个关键帧之间的那些东西,除了对地图贡献一些地图点外,就被理所当然地忽略掉了。

将局部地图的点投影到图像平面并显示除了。如果位姿估计正确,它们看起来应该像是固定在空间中一样。如果感觉到某个特征点不自然地运动,那可能是相机位姿估计不够准确,或特征点位置不够准确。

地图的优化:用到的原理主要是最小二乘和三角化。

局限:

视觉里程计能够估算局部时间内的相机运动及特征点的位置,但这种局部方式有明显缺点

  1. 容易丢失。一旦丢失,要么“等待相机转回来”(保存参考帧并与新的帧比较),要么重置整个VO以跟踪新的图像数据。
  2. 轨迹漂移。主要原因是每次估计的误差会累积至下一次估计,导致长时间轨迹不准确。大一点的局部地图可以缓解这种现象,但它始终是存在的。

如果只关心短时间内的运动,或者VO的精度已经满足应用需求,有时候可能需要的仅仅就是一个视觉里程计,而不用完全的SLAM。

参考文献

高翔《视觉SLAM十四讲:从理论到实践》

参考代码

gaoxiang12/slambook2

gaoxiang12/slambook

### 回答1: 《视觉SLAM十四第二版PDF是一本关于视觉定位与地图构建技术的经典教材,由国内知名专家学者合作撰写而成。本书系统地介绍了基于视觉传感器的SLAM技术的各个方面,包括相机模型、图像特征提取、初始姿态估计、运动估计、地图构建、回环检测等。同时,本书还特别关注了SLAM方法的实际应用,解了多个真实场景下的SLAM应用案例,并介绍了如何使用OpenCV、G2O等开源工具实现SLAM算法。此外,本书还对SLAM技术的最新进展进行了介绍,如基于深度学习的特征提取与描述、多目标视觉SLAM、无人机视觉SLAM等。本书所涉及的内容深入浅出,既适合初学者入门,也适合高级研究人员深入了解SLAM技术的最新进展。该书是目前国内最权威、最全面的视觉SLAM入门教材之一,值得广大从事机器人、计算机视觉、机器人自主导航、无人机等相关领域的科研人员、工程师和学生学习使用。 ### 回答2: 《视觉SLAM十四第二版PDF》是一本关于算法技术的经典教材,主要介绍了SLAM技术的应用和原理。本教材全面详细地介绍了视觉SLAM的相关知识,从基础的数学知识开始,述了传感器、滤波、3D点云和视觉测量等原理,帮助读者逐步深入了解SLAM技术的实现过程。 此外,《视觉SLAM十四第二版PDF》还详细介绍了几种常用的视觉SLAM算法,包括基于特征匹配的视觉SLAM算法VFH、基于直接法的RGB-D SLAM算法和基于半稠密法的DSO算法。对于学习SLAM的人来说,这些算法的深入掌握能够让他们更好地理解SLAM的各种应用场景和内部原理。 此外,本教材还包括各种真实案例以及实验代码,帮助读者更好地了解SLAM技术的应用和实现方法。总体而言,对于想要深入了解SLAM技术和拓展相关应用的读者来说,《视觉SLAM十四第二版PDF》是一本经典的参考教材,具有极高的价值和实用性。 ### 回答3: 《视觉slam十四 第二版 pdf》是一本涉及到计算机视觉和机器人技术的重要书籍。这本书主要介绍了基于视觉技术的跟踪和定位算法SLAMSLAM是一种用来对无人机、自动驾驶汽车和机器人等进行实时建图、路径规划和导航的技术。 本书包括十四个主题,内容涉及相机模型、卡尔曼滤波器、非线性优化、回环检测等各个方面。这本书适合于计算机视觉和机器人领域的专业人员和学生,以及对这些领域感兴趣的人。 此外,本书不仅仅是介绍了SLAM算法,还对视觉slam的一些经典算法进行了详细的解,同时也包括最新的研究成果,以及许多高质量的实验结果和实际应用案例。 通过阅读这本书,读者将能够全面了解视觉slam技术的基础和应用,对理解和掌握slam算法具有很大的帮助和作用。除此之外,本书也是一个很好的参考工具,尤其适合从事相关技术研究和开发的人员。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值