stvo-pl融合点线段特征的立体视觉里程计代码解读

摘要

基于点和线段的SLAM系统在点特征不充足的场景下具有一定的优势。本文对文章Robust Stereo Visual Odometry through a Probabilistic Combination of Points and Line Segments对应的源码进行记录。

主函数入口在imagesStVO.cpp中
首先进入初始化部分

if( frame_counter == 0 ) // initialize
            StVO->initialize(img_l,img_r,0);

初始化主要完成系统主要参数的设置
包括,fast角点的阈值,线段的最小长度阈值,初始化一个双目帧,然后提取视觉帧上的特征,包括点特征和线段特征,然后初始化位姿变换,位姿估计的协防差,DT,以及用于关键帧的一些参数。初始化的代码如下:

void StereoFrameHandler::initialize(const Mat img_l_, const Mat img_r_ , const int idx_)
{
    // variables for adaptative thresholds
    orb_fast_th = Config::orbFastTh();
    llength_th  = Config::minLineLength() * std::min( cam->getWidth(), cam->getHeight() ) ;
    // define StereoFrame
    prev_frame = new StereoFrame( img_l_, img_r_, idx_, cam );
    prev_frame->extractStereoFeatures( llength_th, orb_fast_th );
    prev_frame->Tfw     = Matrix4d::Identity();
    prev_frame->Tfw_cov = Matrix6d::Identity();
    prev_frame->DT      = Matrix4d::Identity();
    curr_frame = prev_frame;
    // SLAM variables for KF decision
    T_prevKF         = Matrix4d::Identity();
    cov_prevKF_currF = Matrix6d::Zero();
    prev_f_iskf      = true;
    N_prevKF_currF   = 0;
}

下面介绍特征提取模块

prev_frame->extractStereoFeatures( llength_th, orb_fast_th );

void StereoFrame::extractStereoFeatures( double llength_th, int fast_th )
{

    if( Config::plInParallel() )
    {
        auto detect_p = async(launch::async, &StereoFrame::detectStereoPoints,        this, fast_th );
        auto detect_l = async(launch::async, &StereoFrame::detectStereoLineSegments,  this, llength_th );
        detect_p.wait();
        detect_l.wait();
    }
    else
    {
        detectStereoPoints(fast_th);
        detectStereoLineSegments(llength_th);
    }

}

这里设置了并行计算,因为线段的提取和描述子计算都会提升计算力需求,所以为了提高在线效率,这里进行并行提取线段特征和点特征。
首先介绍立体图像上点特征的提取

void StereoFrame::detectStereoPoints( int fast_th )
{

    if( !Config::hasPoints() )
        return;

    // detect and estimate each descriptor for both the left and right image
    if( Config::lrInParallel() )
    {
        auto detect_l = async(launch::async, &StereoFrame::detectPointFeatures, this, img_l, ref(points_l), ref(pdesc_l), fast_th );
        auto detect_r = async(launch::async, &StereoFrame::detectPointFeatures, this, img_r, ref(points_r), ref(pdesc_r), fast_th );
        detect_l.wait();
        detect_r.wait();
    }
    else
    {
        detectPointFeatures( img_l, points_l, pdesc_l, fast_th );
        detectPointFeatures( img_r, points_r, pdesc_r, fast_th );
    }

    // perform the stereo matching
    matchStereoPoints(points_l, points_r, pdesc_l, pdesc_r, (frame_idx==0) );
}

这里也是设置了并行,对左右目图像进行分别特征点 提取,然后再进行立体视觉下的特征点匹配。提取特征点的代码如下

void StereoFrame::detectPointFeatures( Mat img, vector<KeyPoint> &points, Mat &pdesc, int fast_th )
{
    // Detect point features
    if( Config::hasPoints() )
    {
        int fast_th_ = Config::orbFastTh();
        if( fast_th != 0 )
            fast_th_ = fast_th;
        Ptr<ORB> orb = ORB::create( Config::orbNFeatures(), Config::orbScaleFactor(), Config::orbNLevels(),
                                    Config::orbEdgeTh(), 0, Config::orbWtaK(), Config::orbScore(),
                                    Config::orbPatchSize(), fast_th_ );
        orb->detectAndCompute( img, Mat(), points, pdesc, false);
    }

}

这里主要是提取orb特征点。然后是对特征点进行立体匹配。思路如下,首先对整个图像进行栅格化,计算每个orb对应的栅格的索引。存储在coords中。然后在每个栅格中存储位于其内的特征点。栅格化是为了对于每个特征点,能够快速的在一定范围中找到其潜在的匹配点。这里根据描述子对特征点进行匹配,然后根据第一小距离和第二小距离之比进行筛选。得到的匹配存储在matches_12中。然后就是进行反向的匹配,如果通过正反向的匹配,都是一样的结果,那就保留这个匹配。代码如下

int matchGrid(const std::vector<point_2d> &points1, const cv::Mat &desc1, const GridStructure &grid, const cv::Mat &desc2, const GridWindow &w, std::vector<int> &matches_12) {

    if (points1.size() != desc1.rows)
        throw std::runtime_error("[matchGrid] Each point needs a corresponding descriptor!");

    int matches = 0;
    matches_12.resize(desc1.rows, -1);

    int best_d, best_d2, best_idx;
    std::vector<int> matches_21, distances;

    if (Config::bestLRMatches()) {
        matches_21.resize(desc2.rows, -1);
        distances.resize(desc2.rows, std::numeric_limits<int>::max());
    }

    for (int i1 = 0; i1 < points1.size(); ++i1) {

        best_d = std::numeric_limits<int>::max();
        best_d2 = std::numeric_limits<int>::max();
        best_idx = -1;

        const std::pair<int, int> &coords = points1[i1];
        cv::Mat desc = desc1.row(i1);

        std::unordered_set<int> candidates;
        grid.get(coords.first, coords.second, w, candidates);

        if (candidates.empty()) continue;
        for (const int &i2 : candidates) {
            if (i2 < 0 || i2 >= desc2.rows) continue;

            const int d = distance(desc, desc2.row(i2));

            if (Config::bestLRMatches()) {
                if (d < distances[i2]) {
                    distances[i2] = d;
                    matches_21[i2] = i1;
                } else continue;
            }

            if (d < best_d) {
                best_d2 = best_d;
                best_d = d;
                best_idx = i2;
            } else if (d < best_d2)
                best_d2 = d;
        }

        if (best_d < best_d2 * Config::minRatio12P()) {
            matches_12[i1] = best_idx;
            matches++;
        }
    }

    if (Config::bestLRMatches()) {
        for (int i1 = 0; i1 < matches_12.size(); ++i1) {
            int &i2 = matches_12[i1];
            if (i2 >= 0 && matches_21[i2] != i1) {
                i2 = -1;
                matches--;
            }
        }
    }

    return matches;
}

得到匹配之后,对每对匹配进行立体的极线约束检验。在y方向的偏差应该足够小,在x方向,即视差应该足够大,这样的匹配再进行保存。然后通过重投影,得到三维特征点的坐标。关于立体视觉的深度恢复

 // bucle around pmatches
    Mat pdesc_l_aux;
    int pt_idx = 0;
    for (int i1 = 0; i1 < matches_12.size(); ++i1) {
        const int i2 = matches_12[i1];
        if (i2 < 0) continue;

        // check stereo epipolar constraint
        if (std::abs(points_l[i1].pt.y - points_r[i2].pt.y) <= Config::maxDistEpip()) {
            // check minimal disparity
            double disp_ = points_l[i1].pt.x - points_r[i2].pt.x;
            if (disp_ >= Config::minDisp()){
                pdesc_l_aux.push_back(pdesc_l_.row(i1));
                Vector2d pl_(points_l[i1].pt.x, points_l[i1].pt.y);
                Vector3d P_ = cam->backProjection(pl_(0), pl_(1), disp_);
                if (initial)
                    stereo_pt.push_back(new PointFeature(pl_, disp_, P_, pt_idx++, points_l[i1].octave));
                else
                    stereo_pt.push_back(new PointFeature(pl_, disp_, P_, -1, points_l[i1].octave));
            }
        }
    }

然后存储到特征点的序列中,包括特征点的图像坐标,视差,空间点坐标,以及octave。
接下来介绍对线段特征的检测


void StereoFrame::detectStereoLineSegments(double llength_th)
{

    if( !Config::hasLines() )
        return;

    // detect and estimate each descriptor for both the left and right image
    if( Config::lrInParallel() )
    {
        auto detect_l = async(launch::async, &StereoFrame::detectLineFeatures, this, img_l, ref(lines_l), ref(ldesc_l), llength_th );
        auto detect_r = async(launch::async, &StereoFrame::detectLineFeatures, this, img_r, ref(lines_r), ref(ldesc_r), llength_th );
        detect_l.wait();
        detect_r.wait();
    }
    else
    {
        detectLineFeatures( img_l, lines_l, ldesc_l, llength_th );
        detectLineFeatures( img_r, lines_r, ldesc_r, llength_th );
    }

    // perform the stereo matching
    matchStereoLines(lines_l,  lines_r,  ldesc_l, ldesc_r, (frame_idx==0));

}

在detectLineFeatures( img_l, lines_l, ldesc_l, llength_th )存储了对直线的端点,方向,直线的位置是以直线段的中点定义的。直线的response是以直线段的长度除以图像长宽中的最大值。
然后对左右目的图像进行立体匹配。
这里存储的是左目起点,左目终点,起点终点的视差,以及对应的空间坐标。

stereo_ls.push_back( new LineFeature(Vector2d(sp_l(0),sp_l(1)),disp_s,sP_,
                                                     Vector2d(ep_l(0),ep_l(1)),disp_e,eP_,
                                                     le_l,angle_l,ls_idx,lines_l[i1].octave) );
                ls_idx++;

这里在预处理的时候要对左右匹配图像的端点的视差进行检查,是否满足相应的阈值。
现在已经知道了这两个特征对应的存储信息了。
后面就是对特征在连续帧上进行跟踪,然后进行位姿的优化。

StVO->insertStereoPair( img_l, img_r, frame_counter );
StVO->optimizePose();
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值