zoukankan      html  css  js  c++  java
  • pl-svo代码解读

    pl-svo是在svo的基础上结合点和线特征的半直接法视觉里程计

    程序启动通过app文件夹下的run_pipeline.cpp主程序启动,其它的函数文件统一放在src文件夹下,我们先从run_pipeline.cpp进行分析,了解整个算法流程。

    首先定义了 svo_options的数据结构,里面包含的是程序的运行参数

    struct svo_options {
        int seq_offset;
        int seq_step;
        int seq_length;
        bool has_points;
        bool has_lines ;
        bool is_tum;
        string dataset_dir;
        string images_dir;
        string traj_out;
        string map_out;
    };

    接下来在plsvo命名空间下声明了一个 ConvergedSeed 结构类型和BenchmarkNode类

    struct ConvergedSeed {
      int x_, y_;
      Vector3d pos_;
      cv::Vec3b col_;
      ConvergedSeed(int x, int y, Vector3d pos, cv::Vec3b col) :
        x_(x), y_(y), pos_(pos), col_(col)
      {}
    };

    我们下面将详细介绍BenchmarkNode类,他包含了主程序的主要功能函数

    4个私有成员

      vk::AbstractCamera* cam_;
      FrameHandlerMono* vo_;
      DepthFilter* depth_filter_;
      std::list<ConvergedSeed> results_;

    公开的成员函数

    public:
      BenchmarkNode(vk::AbstractCamera *cam_);
      BenchmarkNode(vk::AbstractCamera *cam_, const plsvo::FrameHandlerMono::Options& handler_opts);
      ~BenchmarkNode();
      void depthFilterCbPt(plsvo::Point* point);
      void depthFilterCbLs(plsvo::LineSeg* ls);
      int runFromFolder(svo_options opts);
      int runFromFolder(vk::PinholeCamera* cam_, svo_options opts);
      int runFromFolder(vk::ATANCamera* cam_,    svo_options opts);
    };
    负责启动视觉里程计的同名构造函数

    BenchmarkNode::BenchmarkNode(vk::AbstractCamera* cam_)
    {
      vo_ = new plsvo::FrameHandlerMono(cam_);
      vo_->start();
    }
    
    BenchmarkNode::BenchmarkNode(
        vk::AbstractCamera* cam_,
        const plsvo::FrameHandlerMono::Options& handler_opts)
    {
      vo_ = new plsvo::FrameHandlerMono(cam_, handler_opts);
      vo_->start();
    }

    点和线特征的深度滤波器函数

    void BenchmarkNode::depthFilterCbPt(plsvo::Point* point)
    {
      cv::Vec3b color = point->obs_.front()->frame->img_pyr_[0].at<cv::Vec3b>(point->obs_.front()->px[0], point->obs_.front()->px[1]);
      results_.push_back(ConvergedSeed(
          point->obs_.front()->px[0], point->obs_.front()->px[1], point->pos_, color));
      delete point->obs_.front();
    }
    
    void BenchmarkNode::depthFilterCbLs(plsvo::LineSeg* ls)
    {
      cv::Vec3b color = ls->obs_.front()->frame->img_pyr_[0].at<cv::Vec3b>(ls->obs_.front()->spx[0], ls->obs_.front()->spx[1]);
      results_.push_back(ConvergedSeed(
          ls->obs_.front()->spx[0], ls->obs_.front()->spx[1], ls->spos_, color)); // test only with spoint
      delete ls->obs_.front();
    }

    int BenchmarkNode::runFromFolder是算法的主流程,读取数据文件并运行。

    int BenchmarkNode::runFromFolder(vk::ATANCamera* cam_, svo_options opts)

    获取img目录中的已排序文件列表

    YAML::Node dset_config = YAML::LoadFile(dataset_dir+"/dataset_params.yaml");

    获取img目录中的所有文件

        size_t max_len = 0;
        std::list<std::string> imgs;
        boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
        for (boost::filesystem::directory_iterator file(img_dir_path); file != end_itr; ++file)
        {
            boost::filesystem::path filename_path = file->path().filename();
            if (boost::filesystem::is_regular_file(file->status()) &&
                    (filename_path.extension() == ".png"  ||
                     filename_path.extension() == ".jpg"  ||
                     filename_path.extension() == ".jpeg" ||
                     filename_path.extension() == ".tiff") )
            {
                std::string filename(filename_path.string());
                imgs.push_back(filename);
                max_len = max(max_len, filename.length());
            }
        }

    按文件名排序; 如果需要,添加前导零以使文件名长度相等

        for (std::list<std::string>::iterator img = imgs.begin(); img != imgs.end(); ++img)
        {
            sorted_imgs[std::string(max_len - img->length(), '0') + (*img)] = *img;
            n_imgs++;
        }

    根据初始偏移,步长和数据长度把图像数据存储到

    std::map<std::string, std::string> sorted_imgs;

    场景初始化

    sceneRepresentation scene("../app/scene_config.ini");

    运行SVO进行姿态估计

    进入循环读入图像进行处理

    cv::Mat img(cv::imread(img_path.string(), CV_8UC1));

    图像去畸变

    cam_->undistortImage(img,img_rec);

    开始对图像进行处理

    vo_->addImage(img_rec, frame_counter / (double)fps_);
    void FrameHandlerMono::addImage(const cv::Mat& img, const double timestamp)

    Frame类初始化为new_frame_,创建图像金字塔(也作为img_pyr_存储在Frame中)

    new_frame_.reset(new Frame(cam_, img.clone(), timestamp));

    处理图像帧

      UpdateResult res = RESULT_FAILURE;
      if(stage_ == STAGE_DEFAULT_FRAME)
        res = processFrame();
      else if(stage_ == STAGE_SECOND_FRAME)
        res = processSecondFrame();
      else if(stage_ == STAGE_FIRST_FRAME)
        res = processFirstFrame();
      else if(stage_ == STAGE_RELOCALIZING)
        res = relocalizeFrame(SE3(Matrix3d::Identity(), Vector3d::Zero()),
                              map_.getClosestKeyframe(last_frame_));

    先处理第一帧,设置第一帧以进行身份转换,第一帧视为关键帧,进行特征提取并添加关键帧

    FrameHandlerMono::UpdateResult FrameHandlerMono::processFirstFrame()
    {
      // set first frame to identity transformation
      new_frame_->T_f_w_ = SE3(Matrix3d::Identity(), Vector3d::Zero());
      // for now the initialization is done with points and endpoints only (consider use lines)
      if(klt_homography_init_.addFirstFrame(new_frame_) == initialization::FAILURE)
        return RESULT_NO_KEYFRAME;
      new_frame_->setKeyframe();
      map_.addKeyframe(new_frame_);
      stage_ = STAGE_SECOND_FRAME;
      SVO_INFO_STREAM("Init: Selected first frame.");
      return RESULT_IS_KEYFRAME;
    }
    InitResult KltHomographyInit::addFirstFrame(FramePtr frame_ref)
    {
      reset();
      detectFeatures(frame_ref, px_ref_, f_ref_);
      if(px_ref_.size() < 100)
      //if(px_ref_.size() < 80)
      {
        SVO_WARN_STREAM_THROTTLE(2.0, "First image has less than 80 features. Retry in more textured environment.");
        return FAILURE;
      }
      frame_ref_ = frame_ref;
      // initialize points in current frame (query or second frame) with points in ref frame
      px_cur_.insert(px_cur_.begin(), px_ref_.begin(), px_ref_.end());
      return SUCCESS;
    }

    循环过来,对第二帧进行跟踪

    FrameHandlerBase::UpdateResult FrameHandlerMono::processSecondFrame()
    {
      initialization::InitResult res = klt_homography_init_.addSecondFrame(new_frame_);
      if(res == initialization::FAILURE)
        return RESULT_FAILURE;
      else if(res == initialization::NO_KEYFRAME)
        return RESULT_NO_KEYFRAME;
    
      // two-frame bundle adjustment
    #ifdef USE_BUNDLE_ADJUSTMENT
      ba::twoViewBA(new_frame_.get(), map_.lastKeyframe().get(), Config::lobaThresh(), &map_);
    #endif
    
      new_frame_->setKeyframe();
      double depth_mean, depth_min;
      frame_utils::getSceneDepth(*new_frame_, depth_mean, depth_min);
      depth_filter_->addKeyframe(new_frame_, depth_mean, 0.5*depth_min);
    
      // add frame to map
      map_.addKeyframe(new_frame_);
      stage_ = STAGE_DEFAULT_FRAME;
      klt_homography_init_.reset();
      SVO_INFO_STREAM("Init: Selected second frame, triangulated initial map.");
      return RESULT_IS_KEYFRAME;
    }

    对第二帧进行klt光流跟踪

    InitResult KltHomographyInit::addSecondFrame(FramePtr frame_cur)
    {
      trackKlt(frame_ref_, frame_cur, px_ref_, px_cur_, f_ref_, f_cur_, disparities_);
      SVO_INFO_STREAM("Init: KLT tracked "<< disparities_.size() <<" features");
    
      // check the number of points tracked is high enough
      if(disparities_.size() < Config::initMinTracked())
        return FAILURE;
    
      // check the median disparity is high enough to compute homography robustly
      double disparity = vk::getMedian(disparities_);
      SVO_INFO_STREAM("Init: KLT "<<disparity<<"px median disparity.");
      if(disparity < Config::initMinDisparity())
        return NO_KEYFRAME;
    
      computeHomography(
          f_ref_, f_cur_,
          frame_ref_->cam_->errorMultiplier2(), Config::poseOptimThresh(),
          inliers_, xyz_in_cur_, T_cur_from_ref_);
      SVO_INFO_STREAM("Init: Homography RANSAC "<<inliers_.size()<<" inliers.");
    
      if(inliers_.size() < Config::initMinInliers())
      {
        SVO_WARN_STREAM("Init WARNING: "<<Config::initMinInliers()<<" inliers minimum required.");
        return FAILURE;
      }
    
      // Rescale the map such that the mean scene depth is equal to the specified scale
      vector<double> depth_vec;
      for(size_t i=0; i<xyz_in_cur_.size(); ++i)
        depth_vec.push_back((xyz_in_cur_[i]).z());
      double scene_depth_median = vk::getMedian(depth_vec);
      double scale = Config::mapScale()/scene_depth_median;
      frame_cur->T_f_w_ = T_cur_from_ref_ * frame_ref_->T_f_w_;
      frame_cur->T_f_w_.translation() =
          -frame_cur->T_f_w_.rotation_matrix()*(frame_ref_->pos() + scale*(frame_cur->pos() - frame_ref_->pos()));
    
      // For each inlier create 3D point and add feature in both frames
      SE3 T_world_cur = frame_cur->T_f_w_.inverse();
      for(vector<int>::iterator it=inliers_.begin(); it!=inliers_.end(); ++it)
      {
        Vector2d px_cur(px_cur_[*it].x, px_cur_[*it].y);
        Vector2d px_ref(px_ref_[*it].x, px_ref_[*it].y);
        // add 3D point (in (w)olrd coordinates) and features if
        // BOTH ref and cur points lie within the image (with a margin)
        // AND the 3D point lies in front of the camera
        if(frame_ref_->cam_->isInFrame(px_cur.cast<int>(), 10) && frame_ref_->cam_->isInFrame(px_ref.cast<int>(), 10) && xyz_in_cur_[*it].z() > 0)
        {
          Vector3d pos = T_world_cur * (xyz_in_cur_[*it]*scale);
          Point* new_point = new Point(pos);
    
          PointFeat* ftr_cur(new PointFeat(frame_cur.get(), new_point, px_cur, f_cur_[*it], 0));
          frame_cur->addFeature(ftr_cur);
          new_point->addFrameRef(ftr_cur);
    
          PointFeat* ftr_ref(new PointFeat(frame_ref_.get(), new_point, px_ref, f_ref_[*it], 0));
          frame_ref_->addFeature(ftr_ref);
          new_point->addFrameRef(ftr_ref);
        }
      }
      return SUCCESS;
    }

    从第三帧开始进入正常处理

    FrameHandlerBase::UpdateResult FrameHandlerMono::processFrame()
    {
      // Set initial pose TODO use prior
      new_frame_->T_f_w_ = last_frame_->T_f_w_;

      // sparse image align
      SVO_START_TIMER("sparse_img_align");
      bool display = false;
      bool verbose = false;
      SparseImgAlign img_align(Config::kltMaxLevel(), Config::kltMinLevel(),
                               30, SparseImgAlign::GaussNewton, display, verbose);
      size_t img_align_n_tracked = img_align.run(last_frame_, new_frame_);
      SVO_STOP_TIMER("sparse_img_align");
      SVO_LOG(img_align_n_tracked);
      SVO_DEBUG_STREAM("Img Align: Tracked = " << img_align_n_tracked);

      // show reference features
      cv::cvtColor(last_frame_->img(), FrameHandlerMono::debug_img, cv::COLOR_GRAY2BGR);
      {
        // draw point features
        {
          auto fts = last_frame_->pt_fts_;
          Patch patch( 4, debug_img );
          for(auto it=fts.begin(); it!=fts.end(); ++it)
          {
            patch.setPosition((*it)->px);
            patch.setRoi();
            cv::rectangle(debug_img,patch.rect,cv::Scalar(0,255,0));
          }
        }
        // draw segment features
        {
          auto fts = last_frame_->seg_fts_;
          std::for_each(fts.begin(), fts.end(), [&](plsvo::LineFeat* i){
              if( i->feat3D != NULL )
                cv::line(debug_img,cv::Point2f(i->spx[0],i->spx[1]),cv::Point2f(i->epx[0],i->epx[1]),cv::Scalar(0,255,0));
          });
        }
        //cv::imshow("cv: Ref image", debug_img);
        //cv::waitKey(30);
      }

    稀疏图像对齐

    sparse_img_align.cpp/h 提供稀疏图像对齐函数

  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/feifanrensheng/p/10502073.html
Copyright © 2011-2022 走看看