实践:设计SLAM系统

2023-05-16

实现一个双目视觉里程计在Kitti数据集中的运行效果。

很有必要多看几遍的例程。
这个视觉里程计由一个光流追踪的前端和一个局部BA的后端组成。双目只需单帧就可初始化,双目存在3D观测,实现效果比单目好。
程序:数据结构+算法
本例程的数据结构是:

  • 处理的最基本单元图像。在双目中是一对图像,为一帧。
  • 对帧提取特征点。特征点是有很多2D的点。
  • 图像之间寻找特征的关联。如果多次看到某个特征,就用三角化方法计算它的3D位置,即路标
    所以图像,特征,路标是这个系统的最基本的结构。其中路标,路标点,地图点都指代3D空间中的点,语义是一样的。
    在这里插入图片描述
    算法:负责提取特征点的算法,负责做三角化的算法,负责处理优化的算法。总的来说有两个模块:
  • 前端。负责计算相邻图像的特征匹配,往前端中插入图像帧,负责提取图像中的特征点,然后与上一帧进行光流追踪,通过光流结果计算该帧的定位。必要时,应该补充新的特征点并做三角化。前端快速处理保证实时性,前端处理的结果将作为后端优化的初始值
  • 后端。后端是一个较慢的线程,它拿到前端初始化好的关键帧和路标点后,对他们进行优化,然后返回优化结果。后端应该控制优化问题在一定的规模内,不能随时间增长。
    这样通过数据结构和算法确定了系统框架,前后端之间放了地图模块来处理他们之间的数据流动。前后段分别在各自的线程中处理数据,前端提取关键帧后,往梯度中添加新的数据;后端检测到地图更新时,运行一次优化,然后把旧的关键帧和地图点去掉,保持优化的规模。

后续的编程除了核心算法外,还需要一些小模块让系统更加方便:

  • 需要一个相机类来管理相机的内外参和投影函数;
  • 需要一个配置文件管理类,方便从配置文件中读取内容。配置文件中可以记录一些重要的配置参数;
  • 算法是在Kitti数据集上运行的,所以需要一个单独的类来按Kitti的存储格式来读取数据;
  • 需要一个可视化的模块来观察系统的运行状态,否则就是一连传数值;
    在这里插入图片描述
    实现:
    1> 实现基本数据结构
    先来实现帧,特征,路标点三个类。对于基本数据结构,通常设为struct,考虑到这些数据会被多个线程同时访问或修改,在关键部分我们需要加线程锁。
    Frame结果设计:include/myslam/frame.h
#pragma once

#ifndef MYSLAM_FRAME_H
#define MYSLAM_FRAME_H

#include "myslam/camera.h"
#include "myslam/common_include.h"

namespace myslam {
// forward declare
struct MapPoint;  //地图路标点类
struct Feature;   //特征点类

/**
 * 帧
 * 每一帧分配独立id,关键帧分配关键帧ID
 */
struct Frame {
   public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
    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

    // extracted features in left image
    std::vector<std::shared_ptr<Feature>> features_left_;
    // corresponding features in right image, set to nullptr if no corresponding
    std::vector<std::shared_ptr<Feature>> features_right_;

   public:  // data members
    Frame() {}
    //frame含有id, 位姿,图像,以及左右图像中的特征点
    Frame(long id, double time_stamp, const SE3 &pose, const Mat &left,
          const Mat &right);

    // set and get pose, thread safe
    //pose会被前后端同时设置或访问,所以定义Pose的Set和Get函数,在函数内加锁
    SE3 Pose() {
        std::unique_lock<std::mutex> lck(pose_mutex_);
        return pose_;
    }

    void SetPose(const SE3 &pose) {
        std::unique_lock<std::mutex> lck(pose_mutex_);
        pose_ = pose;
    }

    /// 设置关键帧并分配并键帧id
    void SetKeyFrame();

    /// 工厂构建模式,分配id 
    static std::shared_ptr<Frame> CreateFrame();   //Frame可以由静态函数构建,在静态函数中可以自动分配id
};

}  // namespace myslam

#endif  // MYSLAM_FRAME_H

Feature类:/include/myslam/feature.h

#pragma once

#ifndef MYSLAM_FEATURE_H
#define MYSLAM_FEATURE_H

#include <memory>
#include <opencv2/features2d.hpp>
#include "myslam/common_include.h"

namespace myslam {

struct Frame;
struct MapPoint;

/**
 * 2D 特征点
 * 在三角化之后会被关联一个地图点
 */
struct Feature {
   public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
    typedef std::shared_ptr<Feature> Ptr;

    std::weak_ptr<Frame> frame_;         // 持有该feature的frame
    cv::KeyPoint position_;              // 2D提取位置
    std::weak_ptr<MapPoint> map_point_;  // 关联地图点,为了避免shared_ptr产生的循环引用,使用了weak_ptr

    bool is_outlier_ = false;       // 是否为异常点
    bool is_on_left_image_ = true;  // 标识是否提在左图,false为右图

   public:
    Feature() {}
	//Frame和MapPoint的实际持有权归地图
    Feature(std::shared_ptr<Frame> frame, const cv::KeyPoint &kp)
        : frame_(frame), position_(kp) {}
};
}  // namespace myslam

#endif  // MYSLAM_FEATURE_H

MapPoint类:路标点/include/myslam/mappoint.h

#pragma once
#ifndef MYSLAM_MAPPOINT_H
#define MYSLAM_MAPPOINT_H
#include "myslam/common_include.h"
namespace myslam {
struct Frame;
struct Feature;

/**
 * 路标点类
 * 特征点在三角化之后形成路标点
 */
//MapPoint 的主要信息是它的3D位置,即pos_变量
struct MapPoint {  
   public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
    typedef std::shared_ptr<MapPoint> Ptr;
    unsigned long id_ = 0;  // ID
    bool is_outlier_ = false;
    Vec3 pos_ = Vec3::Zero();  // Position in world
    std::mutex data_mutex_;
    int observed_times_ = 0;  // being observed by feature matching algo.
    std::list<std::weak_ptr<Feature>> observations_;
    MapPoint() {}

    MapPoint(long id, Vec3 position);

    Vec3 Pos() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return pos_;
    }

    void SetPos(const Vec3 &pos) {
        std::unique_lock<std::mutex> lck(data_mutex_);
        pos_ = pos;
    };
    //observation_变量记录了自己被哪些Feature观测
    void AddObservation(std::shared_ptr<Feature> feature) {
        std::unique_lock<std::mutex> lck(data_mutex_);
        observations_.push_back(feature);
        observed_times_++;
    }

    void RemoveObservation(std::shared_ptr<Feature> feat);

    std::list<std::weak_ptr<Feature>> GetObs() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return observations_;
    }

    // factory function
    static MapPoint::Ptr CreateNewMappoint();
};
}  // namespace myslam

#endif  // MYSLAM_MAPPOINT_H

最后还需要定义一个地图类:Frame和MapPoint类的对象归地图持有。

#pragma once
#ifndef MAP_H
#define MAP_H

#include "myslam/common_include.h"
#include "myslam/frame.h"
#include "myslam/mappoint.h"

namespace myslam {
/**
 * @brief 地图
 * 和地图的交互:前端调用InsertKeyframe和InsertMapPoint插入新帧和地图点,后端维护地图的结构,判定outlier/剔除等等
 */
//地图以散列的形式记录了所有的关键帧和对应的路标点,同时维护一个被激活的关键帧和地图点,激活的意思就是窗口
class Map {
   public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
    typedef std::shared_ptr<Map> Ptr;
    typedef std::unordered_map<unsigned long, MapPoint::Ptr> LandmarksType;
    typedef std::unordered_map<unsigned long, Frame::Ptr> KeyframesType;

    Map() {}

    /// 增加一个关键帧
    void InsertKeyFrame(Frame::Ptr frame);
    /// 增加一个地图顶点
    void InsertMapPoint(MapPoint::Ptr map_point);

    /// 获取所有地图点
    LandmarksType GetAllMapPoints() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return landmarks_;
    }
    /// 获取所有关键帧
    KeyframesType GetAllKeyFrames() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return keyframes_;
    }

    /// 获取激活地图点
    LandmarksType GetActiveMapPoints() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return active_landmarks_;
    }

    /// 获取激活关键帧
    KeyframesType GetActiveKeyFrames() {
        std::unique_lock<std::mutex> lck(data_mutex_);
        return active_keyframes_;
    }

    /// 清理map中观测数量为零的点
    void CleanMap();

   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;

    // settings
    int num_active_keyframes_ = 7;  // 激活的关键帧数量
};
}  // namespace myslam

#endif  // MAP_H

2> 前端
定义好基本的数据结构后,我们看来前端的功能,前端需要根据双目图像确定v该镇的位置。每帧都有左右目,进行三角化的时候如何选任意两张图像做三角化,考虑不一致,所以位我们先来确定前端的处理逻辑:

  • 前端本身有初始化,正常追踪,追踪丢失三种状态;
  • 在初始化状态中,根据左右目之间的光流匹配,寻找可以三角化的地图点,成功建立处是地图;
  • 追踪阶段中,前端计算上一帧的特征点到当前帧的光流,根据光流结果计算图像位姿。该计算只使用左目图像,不使用右目;
  • 如果追踪到的点较少,就判定当前帧为关键帧。对于关键帧,有以下几个点:
    - 提取新的特征点。
    - 找到这些点在右图中的对应点,用三角化建立新的路标点。
    - 将新的关键帧和路标点加入到地图,并触发一次后端优化。
  • 如果追踪丢失,就重置前端系统,重新初始化
//
// Created by gaoxiang on 19-5-2.
//

#include <opencv2/opencv.hpp>

#include "myslam/algorithm.h"
#include "myslam/backend.h"
#include "myslam/config.h"
#include "myslam/feature.h"
#include "myslam/frontend.h"
#include "myslam/g2o_types.h"
#include "myslam/map.h"
#include "myslam/viewer.h"

namespace myslam {

Frontend::Frontend() {
    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");
}

bool Frontend::AddFrame(myslam::Frame::Ptr frame) {
    current_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;
}

bool Frontend::Track() {
    if (last_frame_) {
        current_frame_->SetPose(relative_motion_ * last_frame_->Pose());
    }

    int num_track_last = TrackLastFrame();
    tracking_inliers_ = EstimateCurrentPose();

    if (tracking_inliers_ > num_features_tracking_) {
        // tracking good
        status_ = FrontendStatus::TRACKING_GOOD;
    } else if (tracking_inliers_ > num_features_tracking_bad_) {
        // tracking bad
        status_ = FrontendStatus::TRACKING_BAD;
    } else {
        // lost
        status_ = FrontendStatus::LOST;
    }

    InsertKeyframe();
    relative_motion_ = current_frame_->Pose() * last_frame_->Pose().inverse();

    if (viewer_) viewer_->AddCurrentFrame(current_frame_);
    return true;
}

bool Frontend::InsertKeyframe() {
    if (tracking_inliers_ >= num_features_needed_for_keyframe_) {
        // still have enough features, don't insert keyframe
        return false;
    }
    // current frame is a new keyframe
    current_frame_->SetKeyFrame();
    map_->InsertKeyFrame(current_frame_);

    LOG(INFO) << "Set frame " << current_frame_->id_ << " as keyframe "
              << current_frame_->keyframe_id_;

    SetObservationsForKeyFrame();
    DetectFeatures();  // detect new features

    // track in right image
    FindFeaturesInRight();
    // triangulate map points
    TriangulateNewPoints();
    // update backend because we have a new keyframe
    backend_->UpdateMap();

    if (viewer_) viewer_->UpdateMap();

    return true;
}

void Frontend::SetObservationsForKeyFrame() {
    for (auto &feat : current_frame_->features_left_) {
        auto mp = feat->map_point_.lock();
        if (mp) mp->AddObservation(feat);
    }
}

int Frontend::TriangulateNewPoints() {
    std::vector<SE3> poses{camera_left_->pose(), camera_right_->pose()};
    SE3 current_pose_Twc = current_frame_->Pose().inverse();
    int cnt_triangulated_pts = 0;
    for (size_t i = 0; i < current_frame_->features_left_.size(); ++i) {
        if (current_frame_->features_left_[i]->map_point_.expired() &&
            current_frame_->features_right_[i] != nullptr) {
            // 左图的特征点未关联地图点且存在右图匹配点,尝试三角化
            std::vector<Vec3> points{
                camera_left_->pixel2camera(
                    Vec2(current_frame_->features_left_[i]->position_.pt.x,
                         current_frame_->features_left_[i]->position_.pt.y)),
                camera_right_->pixel2camera(
                    Vec2(current_frame_->features_right_[i]->position_.pt.x,
                         current_frame_->features_right_[i]->position_.pt.y))};
            Vec3 pworld = Vec3::Zero();

            if (triangulation(poses, points, pworld) && pworld[2] > 0) {
                auto new_map_point = MapPoint::CreateNewMappoint();
                pworld = current_pose_Twc * pworld;
                new_map_point->SetPos(pworld);
                new_map_point->AddObservation(
                    current_frame_->features_left_[i]);
                new_map_point->AddObservation(
                    current_frame_->features_right_[i]);

                current_frame_->features_left_[i]->map_point_ = new_map_point;
                current_frame_->features_right_[i]->map_point_ = new_map_point;
                map_->InsertMapPoint(new_map_point);
                cnt_triangulated_pts++;
            }
        }
    }
    LOG(INFO) << "new landmarks: " << cnt_triangulated_pts;
    return cnt_triangulated_pts;
}

int Frontend::EstimateCurrentPose() {
    // setup g2o
    typedef g2o::BlockSolver_6_3 BlockSolverType;
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType>
        LinearSolverType;
    auto solver = new g2o::OptimizationAlgorithmLevenberg(
        g2o::make_unique<BlockSolverType>(
            g2o::make_unique<LinearSolverType>()));
    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver);

    // vertex
    VertexPose *vertex_pose = new VertexPose();  // camera vertex_pose
    vertex_pose->setId(0);
    vertex_pose->setEstimate(current_frame_->Pose());
    optimizer.addVertex(vertex_pose);

    // K
    Mat33 K = camera_left_->K();

    // edges
    int index = 1;
    std::vector<EdgeProjectionPoseOnly *> edges;
    std::vector<Feature::Ptr> features;
    for (size_t i = 0; i < current_frame_->features_left_.size(); ++i) {
        auto mp = current_frame_->features_left_[i]->map_point_.lock();
        if (mp) {
            features.push_back(current_frame_->features_left_[i]);
            EdgeProjectionPoseOnly *edge =
                new EdgeProjectionPoseOnly(mp->pos_, K);
            edge->setId(index);
            edge->setVertex(0, vertex_pose);
            edge->setMeasurement(
                toVec2(current_frame_->features_left_[i]->position_.pt));
            edge->setInformation(Eigen::Matrix2d::Identity());
            edge->setRobustKernel(new g2o::RobustKernelHuber);
            edges.push_back(edge);
            optimizer.addEdge(edge);
            index++;
        }
    }

    // estimate the Pose the determine the outliers
    const double chi2_th = 5.991;
    int cnt_outlier = 0;
    for (int iteration = 0; iteration < 4; ++iteration) {
        vertex_pose->setEstimate(current_frame_->Pose());
        optimizer.initializeOptimization();
        optimizer.optimize(10);
        cnt_outlier = 0;

        // count the outliers
        for (size_t i = 0; i < edges.size(); ++i) {
            auto e = edges[i];
            if (features[i]->is_outlier_) {
                e->computeError();
            }
            if (e->chi2() > chi2_th) {
                features[i]->is_outlier_ = true;
                e->setLevel(1);
                cnt_outlier++;
            } else {
                features[i]->is_outlier_ = false;
                e->setLevel(0);
            };

            if (iteration == 2) {
                e->setRobustKernel(nullptr);
            }
        }
    }

    LOG(INFO) << "Outlier/Inlier in pose estimating: " << cnt_outlier << "/"
              << features.size() - cnt_outlier;
    // Set pose and outlier
    current_frame_->SetPose(vertex_pose->estimate());

    LOG(INFO) << "Current Pose = \n" << current_frame_->Pose().matrix();

    for (auto &feat : features) {
        if (feat->is_outlier_) {
            feat->map_point_.reset();
            feat->is_outlier_ = false;  // maybe we can still use it in future
        }
    }
    return features.size() - cnt_outlier;
}

int Frontend::TrackLastFrame() {
    // use LK flow to estimate points in the right image
    std::vector<cv::Point2f> kps_last, kps_current;
    for (auto &kp : last_frame_->features_left_) {
        if (kp->map_point_.lock()) {
            // use project point
            auto mp = kp->map_point_.lock();
            auto px =
                camera_left_->world2pixel(mp->pos_, current_frame_->Pose());
            kps_last.push_back(kp->position_.pt);
            kps_current.push_back(cv::Point2f(px[0], px[1]));
        } else {
            kps_last.push_back(kp->position_.pt);
            kps_current.push_back(kp->position_.pt);
        }
    }

    std::vector<uchar> status;
    Mat error;
    cv::calcOpticalFlowPyrLK(
        last_frame_->left_img_, current_frame_->left_img_, kps_last,
        kps_current, status, error, cv::Size(11, 11), 3,
        cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30,
                         0.01),
        cv::OPTFLOW_USE_INITIAL_FLOW);

    int num_good_pts = 0;

    for (size_t i = 0; i < status.size(); ++i) {
        if (status[i]) {
            cv::KeyPoint kp(kps_current[i], 7);
            Feature::Ptr feature(new Feature(current_frame_, kp));
            feature->map_point_ = last_frame_->features_left_[i]->map_point_;
            current_frame_->features_left_.push_back(feature);
            num_good_pts++;
        }
    }

    LOG(INFO) << "Find " << num_good_pts << " in the last image.";
    return num_good_pts;
}

bool Frontend::StereoInit() {
    int num_features_left = DetectFeatures();
    int num_coor_features = FindFeaturesInRight();
    if (num_coor_features < num_features_init_) {
        return false;
    }

    bool build_map_success = BuildInitMap();
    if (build_map_success) {
        status_ = FrontendStatus::TRACKING_GOOD;
        if (viewer_) {
            viewer_->AddCurrentFrame(current_frame_);
            viewer_->UpdateMap();
        }
        return true;
    }
    return false;
}

int Frontend::DetectFeatures() {
    cv::Mat mask(current_frame_->left_img_.size(), CV_8UC1, 255);
    for (auto &feat : current_frame_->features_left_) {
        cv::rectangle(mask, feat->position_.pt - cv::Point2f(10, 10),
                      feat->position_.pt + cv::Point2f(10, 10), 0, CV_FILLED);
    }

    std::vector<cv::KeyPoint> keypoints;
    gftt_->detect(current_frame_->left_img_, keypoints, mask);
    int cnt_detected = 0;
    for (auto &kp : keypoints) {
        current_frame_->features_left_.push_back(
            Feature::Ptr(new Feature(current_frame_, kp)));
        cnt_detected++;
    }

    LOG(INFO) << "Detect " << cnt_detected << " new features";
    return cnt_detected;
}

int Frontend::FindFeaturesInRight() {
    // use LK flow to estimate points in the right image
    std::vector<cv::Point2f> kps_left, kps_right;
    for (auto &kp : current_frame_->features_left_) {
        kps_left.push_back(kp->position_.pt);
        auto mp = kp->map_point_.lock();
        if (mp) {
            // use projected points as initial guess
            auto px =
                camera_right_->world2pixel(mp->pos_, current_frame_->Pose());
            kps_right.push_back(cv::Point2f(px[0], px[1]));
        } else {
            // use same pixel in left iamge
            kps_right.push_back(kp->position_.pt);
        }
    }

    std::vector<uchar> status;
    Mat error;
    cv::calcOpticalFlowPyrLK(
        current_frame_->left_img_, current_frame_->right_img_, kps_left,
        kps_right, status, error, cv::Size(11, 11), 3,
        cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30,
                         0.01),
        cv::OPTFLOW_USE_INITIAL_FLOW);

    int num_good_pts = 0;
    for (size_t i = 0; i < status.size(); ++i) {
        if (status[i]) {
            cv::KeyPoint kp(kps_right[i], 7);
            Feature::Ptr feat(new Feature(current_frame_, kp));
            feat->is_on_left_image_ = false;
            current_frame_->features_right_.push_back(feat);
            num_good_pts++;
        } else {
            current_frame_->features_right_.push_back(nullptr);
        }
    }
    LOG(INFO) << "Find " << num_good_pts << " in the right image.";
    return num_good_pts;
}

bool Frontend::BuildInitMap() {
    std::vector<SE3> poses{camera_left_->pose(), camera_right_->pose()};
    size_t cnt_init_landmarks = 0;
    for (size_t i = 0; i < current_frame_->features_left_.size(); ++i) {
        if (current_frame_->features_right_[i] == nullptr) continue;
        // create map point from triangulation
        std::vector<Vec3> points{
            camera_left_->pixel2camera(
                Vec2(current_frame_->features_left_[i]->position_.pt.x,
                     current_frame_->features_left_[i]->position_.pt.y)),
            camera_right_->pixel2camera(
                Vec2(current_frame_->features_right_[i]->position_.pt.x,
                     current_frame_->features_right_[i]->position_.pt.y))};
        Vec3 pworld = Vec3::Zero();

        if (triangulation(poses, points, pworld) && pworld[2] > 0) {
            auto new_map_point = MapPoint::CreateNewMappoint();
            new_map_point->SetPos(pworld);
            new_map_point->AddObservation(current_frame_->features_left_[i]);
            new_map_point->AddObservation(current_frame_->features_right_[i]);
            current_frame_->features_left_[i]->map_point_ = new_map_point;
            current_frame_->features_right_[i]->map_point_ = new_map_point;
            cnt_init_landmarks++;
            map_->InsertMapPoint(new_map_point);
        }
    }
    current_frame_->SetKeyFrame();
    map_->InsertKeyFrame(current_frame_);
    backend_->UpdateMap();

    LOG(INFO) << "Initial map created with " << cnt_init_landmarks
              << " map points";

    return true;
}

bool Frontend::Reset() {
    LOG(INFO) << "Reset is not implemented. ";
    return true;
}

}  // namespace myslam

后端优化:

//
// Created by gaoxiang on 19-5-2.
//

#include "myslam/backend.h"
#include "myslam/algorithm.h"
#include "myslam/feature.h"
#include "myslam/g2o_types.h"
#include "myslam/map.h"
#include "myslam/mappoint.h"

namespace myslam {

Backend::Backend() {
    backend_running_.store(true);
    backend_thread_ = std::thread(std::bind(&Backend::BackendLoop, this));
}

void Backend::UpdateMap() {
    std::unique_lock<std::mutex> lock(data_mutex_);
    map_update_.notify_one();
}

void Backend::Stop() {
    backend_running_.store(false);
    map_update_.notify_one();
    backend_thread_.join();
}

void Backend::BackendLoop() {
    while (backend_running_.load()) {
        std::unique_lock<std::mutex> lock(data_mutex_);
        map_update_.wait(lock);

        /// 后端仅优化激活的Frames和Landmarks
        Map::KeyframesType active_kfs = map_->GetActiveKeyFrames();
        Map::LandmarksType active_landmarks = map_->GetActiveMapPoints();
        Optimize(active_kfs, active_landmarks);
    }
}

void Backend::Optimize(Map::KeyframesType &keyframes,
                       Map::LandmarksType &landmarks) {
    // setup g2o
    typedef g2o::BlockSolver_6_3 BlockSolverType;
    typedef g2o::LinearSolverCSparse<BlockSolverType::PoseMatrixType>
        LinearSolverType;
    auto solver = new g2o::OptimizationAlgorithmLevenberg(
        g2o::make_unique<BlockSolverType>(
            g2o::make_unique<LinearSolverType>()));
    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver);

    // pose 顶点,使用Keyframe id
    std::map<unsigned long, VertexPose *> vertices;
    unsigned long max_kf_id = 0;
    for (auto &keyframe : keyframes) {
        auto kf = keyframe.second;
        VertexPose *vertex_pose = new VertexPose();  // camera vertex_pose
        vertex_pose->setId(kf->keyframe_id_);
        vertex_pose->setEstimate(kf->Pose());
        optimizer.addVertex(vertex_pose);
        if (kf->keyframe_id_ > max_kf_id) {
            max_kf_id = kf->keyframe_id_;
        }

        vertices.insert({kf->keyframe_id_, vertex_pose});
    }

    // 路标顶点,使用路标id索引
    std::map<unsigned long, VertexXYZ *> vertices_landmarks;

    // K 和左右外参
    Mat33 K = cam_left_->K();
    SE3 left_ext = cam_left_->pose();
    SE3 right_ext = cam_right_->pose();

    // edges
    int index = 1;
    double chi2_th = 5.991;  // robust kernel 阈值
    std::map<EdgeProjection *, Feature::Ptr> edges_and_features;

    for (auto &landmark : landmarks) {
        if (landmark.second->is_outlier_) continue;
        unsigned long landmark_id = landmark.second->id_;
        auto observations = landmark.second->GetObs();
        for (auto &obs : observations) {
            if (obs.lock() == nullptr) continue;
            auto feat = obs.lock();
            if (feat->is_outlier_ || feat->frame_.lock() == nullptr) continue;

            auto frame = feat->frame_.lock();
            EdgeProjection *edge = nullptr;
            if (feat->is_on_left_image_) {
                edge = new EdgeProjection(K, left_ext);
            } else {
                edge = new EdgeProjection(K, right_ext);
            }

            // 如果landmark还没有被加入优化,则新加一个顶点
            if (vertices_landmarks.find(landmark_id) ==
                vertices_landmarks.end()) {
                VertexXYZ *v = new VertexXYZ;
                v->setEstimate(landmark.second->Pos());
                v->setId(landmark_id + max_kf_id + 1);
                v->setMarginalized(true);
                vertices_landmarks.insert({landmark_id, v});
                optimizer.addVertex(v);
            }

            edge->setId(index);
            edge->setVertex(0, vertices.at(frame->keyframe_id_));    // pose
            edge->setVertex(1, vertices_landmarks.at(landmark_id));  // landmark
            edge->setMeasurement(toVec2(feat->position_.pt));
            edge->setInformation(Mat22::Identity());
            auto rk = new g2o::RobustKernelHuber();
            rk->setDelta(chi2_th);
            edge->setRobustKernel(rk);
            edges_and_features.insert({edge, feat});

            optimizer.addEdge(edge);

            index++;
        }
    }

    // do optimization and eliminate the outliers
    optimizer.initializeOptimization();
    optimizer.optimize(10);

    int cnt_outlier = 0, cnt_inlier = 0;
    int iteration = 0;
    while (iteration < 5) {
        cnt_outlier = 0;
        cnt_inlier = 0;
        // determine if we want to adjust the outlier threshold
        for (auto &ef : edges_and_features) {
            if (ef.first->chi2() > chi2_th) {
                cnt_outlier++;
            } else {
                cnt_inlier++;
            }
        }
        double inlier_ratio = cnt_inlier / double(cnt_inlier + cnt_outlier);
        if (inlier_ratio > 0.5) {
            break;
        } else {
            chi2_th *= 2;
            iteration++;
        }
    }

    for (auto &ef : edges_and_features) {
        if (ef.first->chi2() > chi2_th) {
            ef.second->is_outlier_ = true;
            // remove the observation
            ef.second->map_point_.lock()->RemoveObservation(ef.second);
        } else {
            ef.second->is_outlier_ = false;
        }
    }

    LOG(INFO) << "Outlier/Inlier in optimization: " << cnt_outlier << "/"
              << cnt_inlier;

    // Set pose and lanrmark position
    for (auto &v : vertices) {
        keyframes.at(v.first)->SetPose(v.second->estimate());
    }
    for (auto &v : vertices_landmarks) {
        landmarks.at(v.first)->SetPos(v.second->estimate());
    }
}

}  // namespace myslam

实现效果:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实践:设计SLAM系统 的相关文章

  • 每日浅读SLAM论文——简析Cartographer

    文章目录 二维激光SLAM 简单框架 前端 scan matching Submaps构建 后端 分支定界优化csm CorrelativeScanMatch 代码实现框架 Cartographer 论文名 Real Time Loop C
  • Ubuntu20.04编译安装opencv3.2和opencv_contrib-3.2

    图像特征提取中需要用到SIFT等算法 因此不得不安装从源码编译安装opencv contrib 网上有很多教程 但是在不同的环境下多少会出现一些错误 针对Ubuntu20 04 gcc 7环境下对opencv opencv contrib编
  • vscode_c++_slambook 编译配置

    工作目录 配置文件 launch json version 0 2 0 configurations name slamBook程序调试 type cppdbg request launch program fileDirname buil
  • No rule to make target

    No rule to make target 引言 解决方法 引言 报错 No rule to make target Thirdparty g2o lib libg2o so needed by lib libygz SLAM so 停止
  • 《视觉SLAM十四讲》第一版源码slambook编译调试

    slambook master ch2 编译正常 log如下 slambook master ch2 mkdir build cd build cmake make j8 The C compiler identification is G
  • Event-based Stereo Visual Odometry(双目事件相机里程计)论文学习

    本文详细介绍一篇双目事件相机里程计的论文 Event based Stereo Visual Odometry 港科大沈邵劼团队Yi Zhou和TU Berlin的Guillermo Gallego共同完成 并公布了代码 我准备在接下来一段
  • 激光SLAM直接线性方法里程计运动模型及标定

    原创作者 W Tortoise 原创作者文章 https blog csdn net learning tortosie article details 107763626 1 里程计运动模型 1 1 两轮差分底盘的运动模型 1 2 三轮全
  • 【SLAM】卡尔曼滤波(Kalman Filter)

    卡尔曼滤波 Kalman filter 一种利用线性系统状态方程 通过系统输入输出观测数据 对系统状态进行最优估计的算法 由于观测数据中包括系统中的噪声和干扰的影响 所以最优估计也可看作是滤波过程 卡尔曼滤波器的原理解释如下 首先 我们先要
  • 速腾聚创雷达最新驱动安装(包含ring和timestamp)运行lio-sam

    记录一下搞slam的过程 ring和timestamp 最近想跑lio sam 需要用到ring和timestamp两个参数 lio sam作者用的velodyne雷达是带这两个参数的 但是rs雷达的老版驱动录制的点云包没有这两个参数 在g
  • 从零开始一起学习SLAM(9)不推公式,如何真正理解对极约束?

    文章目录 对极几何基本概念 如何得到极线方程 作业 此文发于公众号 计算机视觉life 原文链接 从零开始一起学习SLAM 不推公式 如何真正理解对极约束 自从小白向师兄学习了李群李代数和相机成像模型的基本原理后 感觉书上的内容没那么难了
  • 深度相机Kinect2.0三维点云拼接实验(一)

    文章目录 摘要 Kinect2 0简介 工作原理 RGB相机成像原理 深度相机成像原理 总结 参考文献 摘要 Kinect2 0是微软推出的一款RGB D相机 它即支持普通相机的拍摄 也支持脉冲测量深度信息 本系列文章基于该传感器给出基本的
  • 关于GPS、惯导、视觉里程计的几个定义

    1 首先写几个定义 惯性导航系统 Inertial Navigation System INS 全球定位卫星系统 Global Navigation Satellite System GNSS GNSS 包括全球定位系统 Global Po
  • IMU预积分的一些理解

    IMU预积分 算是比较简单的一个算法 无奈网上找到的资料都讲的晦涩难懂 看明白了也觉得不过如此 讲一下我的理解 整体流程 1 推导IMU离散运动方程 2 根据离散运动方程 进行预积分 并将预积分的误差项拆分出来 因为我们在定义误差的时候 有
  • PnP 问题

    欢迎访问我的博客首页 PnP 问题 1 DLT 2 P3P 3 G2O 求解 PnP 3 1 单目 3 2 双目 4 自定义顶点与边优化内参 4 1 二元边 4 2 三元边 4 3 总结 5 参考 PnP Perspective n Poi
  • lego-LOAM跑自己的数据包无法显示全局点云地图解决(速腾聚创RS-LiDAR-16 雷达 )---SLAM不学无术小问题

    LeGo LOAM跑自己的数据包无法显示全局地图问题 注意 本文笔者使用环境 Ubuntu18 04 ROS melodic 版本 背景 3D SLAM新手 在看到了各种狂拽炫酷的3D点云图的之后决定亲自上手一试 首先当然的是最为经典的LO
  • docker dbus-x11

    本来想用terminator启动nvidia docker 显示出图形界面的 结果发现启动的时候出问题了 terminator 1 dbind WARNING 07 31 53 725 Couldn t connect to accessi
  • 【Pytorch论文相关代码】使用SOLD2预训练好的模型检测与匹配线段(自己的数据集)

    文章目录 前言 使用流程 检测与匹配结果 前言 论文链接 SOLD2 Self supervised Occlusion aware Line Description and Detection 论文源码 https github com
  • ORB-SLAM2:基于可识别特征的自主导航与地图构建

    ORB SLAM2 基于可识别特征的自主导航与地图构建 ORB SLAM Tracking and Mapping Recognizable Features 转自 http blog csdn net cicibabe article d
  • 视觉SLAM漫谈

    视觉SLAM漫谈 1 前言 开始做SLAM 机器人同时定位与建图 研究已经近一年了 从一年级开始对这个方向产生兴趣 到现在为止 也算是对这个领域有了大致的了解 然而越了解 越觉得这个方向难度很大 总体来讲有以下几个原因 入门资料很少 虽然国
  • ORB_SLAM2运行官方数据集/自己数据集

    官方数据集运行结果 WeChat 20230210194425 可以正常运行 自己数据集运行结果 自己的数据集 主要是用手机摄像头采集的实验室进行了一下简单的运行 可以成功运行 但是由于查看的相关程序的是死循环不能像运行官方数据集那样完整保

随机推荐

  • 01_HC-SR04超声波传感器(GPIO中断+定时器方式)

    1 简介 xff1a HC SR04 超声波测距模块可提供 2cm 400cm 的非接触式距离感测功能 xff0c 测 距精度可达高 3mm xff1b 模块包括超声波发射器 接收器与控制电路 2 工作原理 xff1a 1 采用 IO 口
  • Faster R-CNN论文解读

    文章目录 AbstractIntroduction缘由RPN训练方案 Faster R CNN整体流程Conv layersRPNclsreganchorTranslation Invariant AnchorsMuti Scale Anc
  • c语言输入字符时控制符%c前加空格的原因解释

    文章目录 一 前景知识1 缓冲区2 标准输入流 二 scanf语句的执行1 scanf对于整形 d的输入2 scanf对于字符 c的输入 在编一个代码时偶然间发现一个知识盲点 用scanf语句输入字符时需要在控制符 c前加空格 在解释相关这
  • 解决c++中头文件重复包含的问题

    前言 c 43 43 项目中经常会使用到自己定义的一些函数和接口 xff0c 我们通常在头文件中包含进来 xff0c 但这样存在头文件被多次包含的危险 xff0c 导致编译报错 xff0c 以下介绍了几种常用的解决方法 一 采用宏定义的方法
  • 华为交换机5700 SSH配置

    一 在本地设备服务端生成密匙 Huawei rsa local span class token operator span span class token keyword key span span class token operat
  • 函数模板、类模板

    泛型编程 泛型编程 xff1a 编写与类型无关的通用代码 xff0c 是代码复用的一种手段 模板是泛型编程的基础 函数模板 函数模板代表了一个函数家族 xff0c 该函数模板与类型无关 xff0c 在使用时被参数化 xff0c 根据实参类型
  • STM32 第4讲 STM32原理图

    本文为学习正点原子得笔记 xff0c 主要讲解STM32原理图绘制 xff0c 主要由最小系统 43 IO口分布两步完成 引脚分布 STM引脚分类 xff1a 电源引脚晶振引脚复位引脚下载引脚 xff1a JTAG SWD 串口 JTAG
  • STM32 第12讲 GPIO:结构/8种工作模式/寄存器/驱动模型/配置步骤/实验

    文章目录 GPIO简介GPIO特点电气特性GPIO引脚分布 GPIO8种工作模式GPIO的基本结构8种工作模式 GPIO寄存器GPIO端口模式寄存器 xff08 GPIOx MODER xff09 GPIO端口输出类型寄存器 xff08 G
  • PID/LQR/MPC自行总结使用

    PID LQR MPC自行总结使用 自学控制相关知识 xff0c 已经一年多了 xff0c 现在回头看看还是有很多模糊不明确的地方 xff0c 准备借此机会进行总结一下 xff0c 第一次写博客 xff0c 如果错误和不合理之处 xff0c
  • 对 torch.nn.Linear 的理解

    torch nn Linear 是 pytorch 的线性变换层 xff0c 定义如下 xff1a Linear in features int out features int bias bool 61 True device Any N
  • rosdep init 错误解决终极方法(药到病除)

    rosdep init 错误解决方法 一 安装ROS执行以下指令时报错二 原因三 解决办法1 查询IP地址2 将IP地址添加进文件3 重新执行指令 成功解决 xff01 xff01 xff01 一 安装ROS执行以下指令时报错 sudo r
  • Intel Realsense T265 在ubuntu下的环境配置

    Intel Realsense T265 在ubuntu下的环境配置 一 T265介绍二 realsense SDK 安装配置1 注册服务器的公钥2 将服务器添加到存储库列表3 安装所需的库 xff0c 开发者和调试包5 插上T265打开
  • SLAM图优化一

    前言 SLAM问题的处理方法主要分为滤波和图优化两类 滤波的方法中常见的是扩展卡尔曼滤波 粒子滤波 信息滤波等 xff0c 熟悉滤波思想的同学应该容易知道这类SLAM问题是递增的 实时的处理数据并矫正机器人位姿 比如基于粒子滤波的SLAM的
  • 编程实现MapReduce操作

    文章目录 一 MapReduce的WordCount应用二 Partitioner 操作三 xff0e 排序实现四 xff0e 二次排序实现五 hadoop实现六 出现的问题与解决方案 提示 xff1a 以下是本篇文章正文内容 xff0c
  • React项目中TS报错解决方案

    Umi amp amp React amp amp Vue3 amp amp TS报错解决方案总结 个人向 Redux开发工具报错window下没有某属性 解决方案 项目根目录创建global d ts文件 span class token
  • Nvidia NX 运行vins-fusion + DenseSurfelMapping

    Nvidia NX 运行vins fusion 43 DenseSurfelMapping 实现姿态估计和稠密建图 xff0c 记录自用 参考博客 xff1a 使用Realsense D435i运行VINS Fusion并建图 从零开始使用
  • UR5机械臂与realsense相机在Gazebo仿真环境下的手眼标定(眼在手上)

    简介 这是一个Gazebo仿真环境下利用UR5机械臂和realsense相机进行手眼标定的教程 xff08 眼在手上 xff09 准备相关文件 span class token constant UR5 span git clone htt
  • 六,WiFi天猫精灵零配详解

    1 xff0c IOT设备配网方法 smartconfig手机热点配网设备热点配网路由器热点配网扫描二维码配网 2 xff0c 什么是零配 概念 xff1a 让已连网的设备告诉未配网的设备路由器的SSID和密码 xff08 天猫精灵语音寻找
  • 蓝牙Mesh

    1 蓝牙mesh介绍 蓝牙Mesh网络模型 xff1a 蓝牙Mesh提高灵活度 xff1a 代理节点 xff08 Proxy xff09 低功耗节点 xff08 Low Power xff09 转发节点 xff08 Relay xff09
  • 实践:设计SLAM系统

    实现一个双目视觉里程计在Kitti数据集中的运行效果 很有必要多看几遍的例程 这个视觉里程计由一个光流追踪的前端和一个局部BA的后端组成 双目只需单帧就可初始化 xff0c 双目存在3D观测 xff0c 实现效果比单目好 程序 xff1a