ORB-SLAM3笔记(编译、踩坑、论文、看代码)

目前基于orb_slam想做的方向

提升动态建图精度 ( √东西Map就是上不去 KITTI有几个groundtruth官网下架了找不到而且

红外相机退化环境下的点线融合 数据集https://sites.google.com/view/rpmsthereo/

导航思路1 融合二维激光雷达 视觉点投影或切面

导航思路2 稠密建图导航

nerf+slam


写得很烂,渣学校研一,啥也不懂,就看了十四讲和概率机器人,ros会调个包。希望能坚持下来

///???麻了,草稿不保存啊,发布了慢慢更新吧。

//去看每一个库函数、看懂所有代码不是很现实。半个月只看了三个部分。除了主体几个函数其他的应该看流程和大致代码。需要改的时候看具体哪个参数和具体的库函数。但是例如opencv提取特征点之类的函数看完之后应该好好学一下

//???为什么发布不了没显示哇写了好久的没了

//回环部分看的很难受不是很懂。尤其坐标变换啥的基本看不动。可能还需要过一下十四讲。后续可能会过下因子图优化

//看完了 后面的回环草草过了,看了一个多月了真看吐了啊。先看论文吧,如果打算做回环部分的再回头看

//这篇笔记虽然写的很烂,但对我来说收获肯定很大。对别人或者后面的学弟学妹只能当一下参考文档或踩坑笔记。其实应该做一下思维导图和参数名注释为了后面查询和改代码的,但是我懒而且别人做的也很好。

//一个月辣,5w字,润了润了,奖励自己打一晚上游戏。

//麻了,没做思维导图现在全忘辣,为我的错误自罚一杯。

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

目录

安装教程:

遇到和解决的问题

参考资料:

前期准备:

笔记:

main函数

System.cc

ORBextrsvtor.cc

rbrief描述子旋转不变性

brief描述子:

特征匹配

void ExtractorNode::DivideNode

ORBextractor::DistributeOctTree

ORBextractor::ComputeKeyPointsOld/

MapPoint

void MapPoint::Replace(MapPoint* pMP)替换地图点

void MapPoint::ComputeDistinctiveDescriptors()

void MapPoint::UpdateNormalAndDepth()

float MapPoint::GetMaxDistanceInvariance()

Frame.cc

Frame::Frame

Frame::Frame

void Frame::AssignFeaturesToGrid()

void Frame::ExtractORB

bool Frame::isInFrustum(MapPoint *pMP, float viewingCosLimit)

vector Frame::GetFeaturesInArea

void Frame::ComputeStereoMatches()

Frame::Frame

KeyFrame

void KeyFrame::AddConnection(KeyFrame *pKF, const int &weight)

void KeyFrame::UpdateBestCovisibles()

void KeyFrame::UpdateConnections(bool upParent)

void KeyFrame::SetBadFlag()

tracking

void Tracking::MonocularInitialization()

void Tracking::CreateInitialMapMonocular()

void Tracking::StereoInitialization()

void Tracking::Track()

1.恒速模型估计位姿

2.参考帧估计位姿

3.重定位估计位姿

bool Tracking::TrackLocalMap()

bool Tracking::NeedNewKeyFrame()

void Tracking::CreateNewKeyFrame()

LocalMapping

主函数void LocalMapping::Run()

void LocalMapping::ProcessNewKeyFrame()处理关键帧

void LocalMapping::MapPointCulling()剔除不好的地图点

void LocalMapping::CreateNewMapPoints()

void LocalMapping::SearchInNeighbors()融合当前关键帧和其共视帧的地图点

LoopClosing闭环

主函数run

bool LoopClosing::NewDetectCommonRegions()闭环检测

bool LoopClosing::DetectAndReffineSim3FromLastKF

void LoopClosing::CorrectLoop()回环矫正

void LoopClosing::MergeLocal2()惯性模式下的地图融合

前言:

我的环境:ubuntu20、opencv4.5.5、eigen3.4、boodt1.75.python3.8硬件环境:5900hx、3060laptop

nvidia显卡驱动不是很好装、每次装完后都黑屏,按照网上解决办法试了没成功,最后用的社区开源驱动。因为个人原因选择了ubuntu20,但实验室老哥用的ubuntu18遇到的问题比我少很多,虽然官方测试了18与20,但还是 推荐ubuntu18.

参考资料:

1:5小时让你假装大概看懂ORB-SLAM2源码_哔哩哔哩_bilibili

2:ORBSLAM源码及其算法详解 – 知乎

  1. 【泡泡机器人公开课】第三十六课:ORB-SLAM2源码详解-吴博_哔哩哔哩_bilibili

  2. ROS:使用usb_cam软件包调试usb摄像头_通哈膨胀哈哈哈的博客-CSDN博客_usb_cam

  3. ORB-SLAM2代码整理–LocalMapping线程_xiaoshuiyisheng的博客-CSDN博客_local mapping

6.这个老哥太强了:https://blog.csdn.net/ncepu_chen/category_9874746.html ORB-SLAM2代码详解01: ORB-SLAM2代码运行流程_ncepu_Chen的博客-CSDN博客_orbslam2代码

7.回环部分:orb_slam代码解析(3)LocalMapping线程 – h_立青人韦 – 博客园

前期准备:

以t265为例,基于ros运行时需要写一个setting,找一个Camera.type: “KannalaBrandt8″的yaml根据自己的相机改写就好,新版中有自带d435i和t265的yaml。在实际运行时候可能需要设置imu发布频率、话题名等。我的左摄像到imu一直提示none,用的同门的setting。

如果运行时候没有画面,需要查看入口函数,如ros_stereo_inertial.cc里订阅的话题名是否一样。并且t265、d435i、奥比中光等相机需要在launch文件中开启imu话题发布。

如果没有画面,显示waiting for images时,可以使用rqt_graph看看相机发布的图像、imu话题是否有被ros_setero_inertial订阅。

Kalibr相机校正工具安装与使用笔记

笔记:

main函数

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

【泡泡机器人公开课】第三十六课:ORB-SLAM2源码详解-吴博_哔哩哔哩_bilibili

ORB-SLAM3笔记(编译、踩坑、论文、看代码)
int main(int argc, char **argv)
{
  ros::init(argc, argv, "Stereo_Inertial");//
  ros::NodeHandle n("~");//初始化发布节点名和话题名
  ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info);//info级别的调试信息
  bool bEqual = false;//是否图像矫正
  if(argc < 4 || argc > 5)
  {
    cerr << endl << "Usage: rosrun ORB_SLAM3 Stereo_Inertial path_to_vocabulary path_to_settings do_rectify [do_equalize]" << endl;
    ros::shutdown();
    return 1;
  }//&#x68C0;&#x67E5;&#x8F93;&#x5165;&#x53C2;&#x6570;&#x7684;&#x4E2A;&#x6570;

  std::string sbRect(argv[3]);//&#xFF1F;
  if(argc==5)
  {
    std::string sbEqual(argv[4]);//&#xFF1F;
    if(sbEqual == "true")
      bEqual = true;
  }

  // &#x521D;&#x59CB;&#x5316;&#x7EBF;&#x7A0B;&#x51C6;&#x5907;&#x5904;&#x7406;&#x5E27;
  ORB_SLAM3::System SLAM(argv[1],argv[2],ORB_SLAM3::System::IMU_STEREO,true);

  ImuGrabber imugb;
  ImageGrabber igb(&SLAM,&imugb,sbRect == "true",bEqual);

    if(igb.do_rectify)
    {
       //yaml&#x7ED9;&#x5230;&#x6807;&#x5B9A;
        cv::FileStorage fsSettings(argv[2], cv::FileStorage::READ);
        if(!fsSettings.isOpened())
        {
            cerr << "ERROR: Wrong path to settings" << endl;
            return -1;
        }//&#x8BFB;&#x56FE;
//&#x5185;&#x53C2;&#x3001;&#x7578;&#x53D8;
        cv::Mat K_l, K_r, P_l, P_r, R_l, R_r, D_l, D_r;//
        fsSettings["LEFT.K"] >> K_l;
        fsSettings["RIGHT.K"] >> K_r;

        fsSettings["LEFT.P"] >> P_l;
        fsSettings["RIGHT.P"] >> P_r;

        fsSettings["LEFT.R"] >> R_l;
        fsSettings["RIGHT.R"] >> R_r;

        fsSettings["LEFT.D"] >> D_l;
        fsSettings["RIGHT.D"] >> D_r;

        int rows_l = fsSettings["LEFT.height"];
        int cols_l = fsSettings["LEFT.width"];
        int rows_r = fsSettings["RIGHT.height"];
        int cols_r = fsSettings["RIGHT.width"];

        if(K_l.empty() || K_r.empty() || P_l.empty() || P_r.empty() || R_l.empty() || R_r.empty() || D_l.empty() || D_r.empty() ||
                rows_l==0 || rows_r==0 || cols_l==0 || cols_r==0)
        {
            cerr << "ERROR: Calibration parameters to rectify stereo are missing!" << endl;
            return -1;
        }

        //&#x8BA1;&#x7B97;&#x65E0;&#x7578;&#x53D8;&#x548C;&#x4FEE;&#x6B63;&#x8F6C;&#x6362;&#x6620;&#x5C04;
cv::initUndistortRectifyMap(K_l,D_l,R_l,P_l.rowRange(0,3).colRange(0,3),cv::Size(cols_l,rows_l),CV_32F,igb.M1l,igb.M2l);
        cv::initUndistortRectifyMap(K_r,D_r,R_r,P_r.rowRange(0,3).colRange(0,3),cv::Size(cols_r,rows_r),CV_32F,igb.M1r,igb.M2r);
    }

  // Msubscriber&#x5DE6;&#x53F3;&#x76EE;image imu &#x8FD9;&#x91CC;&#x6CE8;&#x610F;&#x5982;&#x679C;&#x81EA;&#x5DF1;&#x642D;&#x76F8;&#x673A;&#x7684;&#x8981;&#x6539;setting&#x4E2D;&#x7684;&#x8BDD;&#x9898;&#x540D;
  ros::Subscriber sub_imu = n.subscribe("/imu", 1000, &ImuGrabber::GrabImu, &imugb);
  ros::Subscriber sub_img_left = n.subscribe("/camera/left/image_raw", 100, &ImageGrabber::GrabImageLeft,&igb);
  ros::Subscriber sub_img_right = n.subscribe("/camera/right/image_raw", 100, &ImageGrabber::GrabImageRight,&igb);
//&#x540C;&#x6B65;imu&#x548C;&#x56FE;&#x50CF;
  std::thread sync_thread(&ImageGrabber::SyncWithImu,&igb);

  ros::spin();

  return 0;
}

首先定义了图像和imu抓取器,获取imu和图像的sensor_时都用的互斥锁


class ImageGrabber//
{
public:
    ImageGrabber(ORB_SLAM3::System* pSLAM, ImuGrabber *pImuGb, const bool bRect, const bool bClahe): mpSLAM(pSLAM), mpImuGb(pImuGb), do_rectify(bRect), mbClahe(bClahe){}

    void GrabImageLeft(const sensor_msgs::ImageConstPtr& msg);//https://zhuanlan.zhihu.com/p/310285167
    void GrabImageRight(const sensor_msgs::ImageConstPtr& msg);//&#x8BFB;&#x53D6;&#x5DE6;&#x53F3;&#x76EE;
    cv::Mat GetImage(const sensor_msgs::ImageConstPtr &img_msg);//ros&#x56FE;&#x7247;&#x8F6C;mat getimage
    void SyncWithImu();

    queue<sensor_msgs::imageconstptr> imgLeftBuf, imgRightBuf;
    std::mutex mBufMutexLeft,mBufMutexRight;//&#x5B9A;&#x4E49;&#x4E92;&#x65A5;&#x9501; &#x7528;&#x4E8E;&#x5DE6;&#x76EE;&#x53F3;&#x76EE;

    ORB_SLAM3::System* mpSLAM;
    ImuGrabber *mpImuGb;

    const bool do_rectify;
    cv::Mat M1l,M2l,M1r,M2r;

    const bool mbClahe;
    cv::Ptr<cv::clahe> mClahe = cv::createCLAHE(3.0, cv::Size(8, 8));
};

</cv::clahe></sensor_msgs::imageconstptr>

orb_slam3:: system在system.h里,主要用于输入imu:初始化slam system,初始化包括定位、回环、查看线程。 首先会根据输入选择rgbd、mono等容器,然后初始化启动定位、关闭定位、换地图、reset地图等。


void ImageGrabber::GrabImageLeft(const sensor_msgs::ImageConstPtr &img_msg)
{
  mBufMutexLeft.lock();
  if (!imgLeftBuf.empty())
    imgLeftBuf.pop();
  imgLeftBuf.push(img_msg);
  mBufMutexLeft.unlock();
}
//
&#x9501;&#xFF0C;&#x975E;&#x7A7A;pop&#xFF0C;&#x7A7A;push&#xFF0C;&#x53F3;&#x76EE;&#x3001;imu&#x540C;&#x7406;

ORB-SLAM3笔记(编译、踩坑、论文、看代码)
cv::Mat ImageGrabber::GetImage(const sensor_msgs::ImageConstPtr &img_msg)

  // ros&#x56FE;&#x50CF;&#x8F6C;&#x6210;cv::mat getimage
  cv_bridge::CvImageConstPtr cv_ptr;

void ImageGrabber::SyncWithImu()没咋看懂

void ImageGrabber::SyncWithImu()
{
  const double maxTimeDiff = 0.01;&#x4E24;&#x76EE;&#x6700;&#x5927;&#x65F6;&#x95F4;&#x5DEE;
  while(1)
  {
    cv::Mat imLeft, imRight;
    double tImLeft = 0, tImRight = 0;
    if (!imgLeftBuf.empty()&&!imgRightBuf.empty()&&!mpImuGb->imuBuf.empty())//&#x5DE6;&#x53F3;&#x76EE;&#x56FE;&#x50CF;imu&#x975E;&#x7A7A;
    {
      tImLeft = imgLeftBuf.front()->header.stamp.toSec();
      tImRight = imgRightBuf.front()->header.stamp.toSec();

      this->mBufMutexRight.lock();
      while((tImLeft-tImRight)>maxTimeDiff && imgRightBuf.size()>1)
      {
        imgRightBuf.pop();
        tImRight = imgRightBuf.front()->header.stamp.toSec();
      }

      this->mBufMutexRight.unlock();

      this->mBufMutexLeft.lock();
      while((tImRight-tImLeft)>maxTimeDiff && imgLeftBuf.size()>1)
      {
        imgLeftBuf.pop();
        tImLeft = imgLeftBuf.front()->header.stamp.toSec();
      }
      this->mBufMutexLeft.unlock();

      if((tImLeft-tImRight)>maxTimeDiff || (tImRight-tImLeft)>maxTimeDiff)
      {
        // std::cout << "big time difference" << std::endl;
        continue;
      }
      if(tImLeft>mpImuGb->imuBuf.back()->header.stamp.toSec())
          continue;

      this->mBufMutexLeft.lock();

//front&#x662F; element of the %queue.    timleft&#x662F;&#x5DE6;&#x76EE;&#x7B2C;&#x4E00;&#x5E27;&#x7ED9;&#x5230;stamp&#x5934;&#xFF0C;&#x53F3;&#x76EE;&#x540C;&#x7406;&#xFF0C;&#x5DE6;&#x53F3;&#x76EE;&#x5DEE;&#x6700;&#x5927;&#x65F6;&#x95F4;&#x5DEE;0.01&#x65F6;&#x5019;&#xFF0C;&#x5DE6;&#x76EE;&#x9000;&#x5230;&#x4E0B;&#x4E00;&#x5E27;&#xFF0C;&#x76F4;&#x5230;&#x540C;&#x6B65;&#x3002;
      imLeft = GetImage(imgLeftBuf.front());
      imgLeftBuf.pop();
      this->mBufMutexLeft.unlock();

      this->mBufMutexRight.lock();
      imRight = GetImage(imgRightBuf.front());
      imgRightBuf.pop();
      this->mBufMutexRight.unlock();
//&#x4E92;&#x65A5;&#x9501;&#x8BFB;&#x56FE;&#x7247;
      vector<orb_slam3::imu::point> vImuMeas;
      mpImuGb->mBufMutex.lock();
      if(!mpImuGb->imuBuf.empty())//imu&#x975E;&#x7A7A;
      {
        // Load imu measurements from buffer
        vImuMeas.clear();
        while(!mpImuGb->imuBuf.empty() && mpImuGb->imuBuf.front()->header.stamp.toSec()<=timleft) 非空且时间戳同步 { double t="mpImuGb-">imuBuf.front()->header.stamp.toSec();
          cv::Point3f acc(mpImuGb->imuBuf.front()->linear_acceleration.x, mpImuGb->imuBuf.front()->linear_acceleration.y, mpImuGb->imuBuf.front()->linear_acceleration.z);
          cv::Point3f gyr(mpImuGb->imuBuf.front()->angular_velocity.x, mpImuGb->imuBuf.front()->angular_velocity.y, mpImuGb->imuBuf.front()->angular_velocity.z);
          vImuMeas.push_back(ORB_SLAM3::IMU::Point(acc,gyr,t));//?

          mpImuGb->imuBuf.pop();
        }
      }
      mpImuGb->mBufMutex.unlock();
      if(mbClahe)
      {
        mClahe->apply(imLeft,imLeft);
        mClahe->apply(imRight,imRight);
      }
//&#x81EA;&#x9002;&#x5E94;&#x76F4;&#x65B9;&#x56FE;&#x5747;&#x8861;&#x7684;&#x4E1C;&#x897F;
      if(do_rectify)
      {
        cv::remap(imLeft,imLeft,M1l,M2l,cv::INTER_LINEAR);
        cv::remap(imRight,imRight,M1r,M2r,cv::INTER_LINEAR);
      }//&#x6CA1;&#x627E;&#x5230;remap&#x5728;&#x54EA; &#x8D44;&#x6599;&#x8BF4;&#x662F;&#x56FE;&#x50CF;&#x77EB;&#x6B63;

      mpSLAM->TrackStereo(imLeft,imRight,tImLeft,vImuMeas);
//&#x8DDF;&#x8E2A; &#x5F00;&#x51B2;
      std::chrono::milliseconds tSleep(1);
      std::this_thread::sleep_for(tSleep);
    }
  }
}</=timleft)></orb_slam3::imu::point>

System.cc

1读取当前传感器类型

2.读取settings

3.加载Vocabulary

4创建关键帧库

5创建多地图

6创建线程 跟踪、局部建图、回环、显示路径。

计算机视觉life的注释:

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

这里的参数、变量大都在system.h里定义,函数基本在对应功能的头文件,头文件的参数不在system里就在对应的cc中,有时候跳转定义不跳转。

esensor传感器类型,枚举类。

mpViewer:The viewer draws the map and the current camera pose. It uses Pangolin.

Viewer:这里把mpviewer强制转换成空指针,Main thread function. Draw points, keyframes, the current camera pose and the last processed frame. Drawing is refreshed according to the camera fps. We use Pangolin.

接下来 选择传感器类型读setting文件加载ORB_Vocabulary,这里加载Vocabulary调用了DBow2库(红色加粗标注了大体步骤)

    mpVocabulary = new ORBVocabulary();
    //&#x65B0;&#x5EFA;Vocabulary
    bool bVocLoad = mpVocabulary->loadFromTextFile(strVocFile);//&#x8BFB;&#x5B57;&#x5178;
//ORB vocabulary used for place recognition and feature matching.

创建关键帧的DATABASE

pKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);

atlas地图

{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{

atlas定义在atlas.h中,initkfid初始化时创建第一幅地图,mnLastInitKFidMap是在上一个地图最大关键帧的id +1(?mHasViewer没找到)

Atlas::Atlas(int initKFid): mnLastInitKFidMap(initKFid), mHasViewer(false)
{
    mpCurrentMap = static_cast<map*>(NULL);
    CreateNewMap();</map*>

析构函数~atlas中创建一个迭代器,使用mspMaps存档当前地图信息,如果当前活跃地图有效,则存储当前地图为不活跃地图,如果当前地图非空或当前地图最新帧id大于当前地图第一帧id时,mnLastInitKFidMap = mpCurrentMap->GetMaxKFid()+1存储地图

  for(std::set<map*>::iterator it = mspMaps.begin(), end = mspMaps.end(); it != end;)
 Map* pMi = *it;

        if(pMi)</map*>

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

创建atlas地图,初始化initKFid,接着判断是否需要imu信息来设置mbIsInertial以此开启追踪和预积分

 mpAtlas = new Atlas(0);

    if (mSensor==IMU_STEREO || mSensor==IMU_MONOCULAR)

        mpAtlas->SetInertialSensor();
   mpFrameDrawer = new FrameDrawer(mpAtlas);
    mpMapDrawer = new MapDrawer(mpAtlas, strSettingsFile);

FrameDrawer:

{{{{{{{{{{{{{{{{{

&#x8BA1;&#x7B97;&#x673A;&#x89C6;&#x89C9;life&#x7684;&#x6CE8;&#x91CA;&#xFF1A;
FrameDrawer::FrameDrawer(Atlas* pAtlas):both(false),mpAtlas(pAtlas)
{
    mState=Tracking::SYSTEM_NOT_READY;
    // &#x521D;&#x59CB;&#x5316;&#x56FE;&#x50CF;&#x663E;&#x793A;&#x753B;&#x5E03;
    // &#x5305;&#x62EC;&#xFF1A;&#x56FE;&#x50CF;&#x3001;&#x7279;&#x5F81;&#x70B9;&#x8FDE;&#x7EBF;&#x5F62;&#x6210;&#x7684;&#x8F68;&#x8FF9;&#xFF08;&#x521D;&#x59CB;&#x5316;&#x65F6;&#xFF09;&#x3001;&#x6846;&#xFF08;&#x8DDF;&#x8E2A;&#x65F6;&#x7684;MapPoint&#xFF09;&#x3001;&#x5708;&#xFF08;&#x8DDF;&#x8E2A;&#x65F6;&#x7684;&#x7279;&#x5F81;&#x70B9;&#xFF09;
    mIm = cv::Mat(480,640,CV_8UC3, cv::Scalar(0,0,0));
    mImRight = cv::Mat(480,640,CV_8UC3, cv::Scalar(0,0,0));

}}}}}}}}}}}}}}}}}]

创建跟踪线程,

Tracking:

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

首先选择相机配置文件,得到相机输入,特征提取和匹配。

属实有点多,先搞system回头看吧。

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

开启local mapping

localmapping:Local Mapper. It manages the local map and performs local bundle adjustment.

mptLocalMapping:The Tracking thread “lives” in the main execution thread that creates the System object.

判断三角测距是否可行,

 mpLocalMapper->mThFarPoints = fsSettings["thFarPoints"];
    if(mpLocalMapper->mThFarPoints!=0)
    {
        cout << "Discard points further than " << mpLocalMapper->mThFarPoints << " m from current camera" << endl;
        mpLocalMapper->mbFarPoints = true;
    }
    else
        mpLocalMapper->mbFarPoints = false;

创建闭环,创建可视化线程

 mpLoopCloser = new LoopClosing(mpAtlas, mpKeyFrameDatabase, mpVocabulary, mSensor!=MONOCULAR); // mSensor!=MONOCULAR);
    mptLoopClosing = new thread(&ORB_SLAM3::LoopClosing::Run, mpLoopCloser);
    //Initialize the Viewer thread and launch
    if(bUseViewer)
    {
        mpViewer = new Viewer(this, mpFrameDrawer,mpMapDrawer,mpTracker,strSettingsFile);
        mptViewer = new thread(&Viewer::Run, mpViewer);
        mpTracker->SetViewer(mpViewer);
        mpLoopCloser->mpViewer = mpViewer;
        mpViewer->both = mpFrameDrawer->both;
    }

创建线程之间的指针

mpLocalMapper mpLoopCloser 地图和回环之间的指针

mpTracker mpLoopCloser 回环和跟踪

mpTracker mpLocalMapper 追踪和地图

刷志愿者时长去了 摸3天鱼刷志愿者时长去了 摸3天鱼刷志愿者时长去了 摸3天鱼

System::Track 以stereo

选择定位模式或者定位建图

 {
        unique_lock<mutex> lock(mMutexMode);
        if(mbActivateLocalizationMode)
        {
            mpLocalMapper->RequestStop();// &#x8BF7;&#x6C42;&#x505C;&#x6B62;&#x7EBF;&#x7A0B;
            while(!mpLocalMapper->isStopped())
            {
                usleep(1000);
            }

            mpTracker->InformOnlyTracking(true);//&#x53EA;&#x5EFA;&#x56FE;
            mbActivateLocalizationMode = false;&#x8BBE;&#x7F6E;&#x4E3A;false&#x907F;&#x514D;&#x4E0A;&#x8FF0;&#x64CD;&#x4F5C;&#x5FAA;&#x73AF;
        }
        if(mbDeactivateLocalizationMode)
        {
            mpTracker->InformOnlyTracking(false);
            mpLocalMapper->Release();
            mbDeactivateLocalizationMode = false;
        }
    }</mutex>

将imu存储到imudata中

if (mSensor == System::IMU_STEREO)
        for(size_t i_imu = 0; i_imu < vImuMeas.size(); i_imu++)
            mpTracker->GrabImuData(vImuMeas[i_imu]);

得到图像深度时间戳文件名,记录mstate,当前的关键帧的mappoint,当前帧的关键点。

cv::Mat Tcw = mpTracker->GrabImageStereo(imLeft,imRight,timestamp,filename);

    unique_lock<mutex> lock2(mMutexState);
    mTrackingState = mpTracker->mState;
    mTrackedMapPoints = mpTracker->mCurrentFrame.mvpMapPoints;
    mTrackedKeyPointsUn = mpTracker->mCurrentFrame.mvKeysUn;
</mutex>

SaveTrajectoryTUM:

void System::SaveTrajectoryTUM(const string &filename)
{
    cout << endl << "Saving camera trajectory to " << filename << " ..." << endl;
    if(mSensor==MONOCULAR)
    {
        cerr << "ERROR: SaveTrajectoryTUM cannot be used for monocular." << endl;
        return;
    }

    vector<keyframe*> vpKFs = mpAtlas->GetAllKeyFrames();//&#x6240;&#x6709;&#x5173;&#x952E;&#x5E27;
    sort(vpKFs.begin(),vpKFs.end(),KeyFrame::lId);//&#x5E27;&#x6392;&#x5E8F;

    // Transform all keyframes so that the first keyframe is at the origin.

    // After a loop closure the first keyframe might not be at the origin.

    cv::Mat Two = vpKFs[0]->GetPoseInverse();&#x3001;&#x3001;&#x7B2C;&#x4E00;&#x5E27;&#x7684;&#x4F4D;&#x59FF;

    ofstream f;
    f.open(filename.c_str());
    f << fixed;+

    // Frame pose is stored relative to its reference keyframe (which is optimized by BA and pose graph).

    // We need to get first the keyframe pose and then concatenate the relative transformation.

    // Frames not localized (tracking failure) are not saved.

    // For each frame we have a reference keyframe (lRit), the timestamp (lT) and a flag
    // which is true when tracking failed (lbL).

    list<orb_slam3::keyframe*>::iterator lRit = mpTracker->mlpReferences.begin();
    list<double>::iterator lT = mpTracker->mlFrameTimes.begin();
    list<bool>::iterator lbL = mpTracker->mlbLost.begin();
    for(list<cv::mat>::iterator lit=mpTracker->mlRelativeFramePoses.begin(),
        lend=mpTracker->mlRelativeFramePoses.end();lit!=lend;lit++, lRit++, lT++, lbL++)
    {
        if(*lbL)
            continue;

        KeyFrame* pKF = *lRit;

        cv::Mat Trw = cv::Mat::eye(4,4,CV_32F);

        // If the reference keyframe was culled, traverse the spanning tree to get a suitable keyframe.

        while(pKF->isBad())
        {
            Trw = Trw*pKF->mTcp;
            pKF = pKF->GetParent();
        }

        Trw = Trw*pKF->GetPose()*Two;

        cv::Mat Tcw = (*lit)*Trw;
        cv::Mat Rwc = Tcw.rowRange(0,3).colRange(0,3).t();
        cv::Mat twc = -Rwc*Tcw.rowRange(0,3).col(3);

        vector<float> q = Converter::toQuaternion(Rwc);

        f << setprecision(6) << *lT << " " <<  setprecision(9) << twc.at<float>(0) << " " << twc.at<float>(1) << " " << twc.at<float>(2) << " " << q[0] << " " << q[1] << " " << q[2] << " " << q[3] << endl;
    }
    f.close();
    // cout << endl << "trajectory saved!" << endl;
}
</float></float></float></float></cv::mat></bool></double></orb_slam3::keyframe*></keyframe*>

清明假期 开卷

上面大概走了下程序流程,现在打算走一遍特征点法→定位→建图

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

ORBextrsvtor.cc

原理:

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

以某个像素为中心p,取周围16个像素点,近似一个圆。设置一个阈值T,假设这个点附近有N个点的亮度大于或者小于p的亮度+T或者p的亮度-T则认为这个点是特征点。然后算这个圆的灰度质心。链接这个圆的几何中心与质心得到一个向量,这个向量就是fast关键点的方向。

rbrief描述子旋转不变性

brief描述子:

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

rbrief描述子因为fast关键点有角度,所以计算描述子时以fast关键点的方向进行旋转,故rbeief具有旋转不变性 。

特征匹配

SearchForInitializtion

orb特征提取使用四叉树特征提取比较均匀。特征匹配时在金字塔最底层的图像上进行,以特征点为中心半径一百的方形区域内遍历所有特征点,并计算他们描述子之间的汉明距离(二进制之间的距离)。最优距离小于50,计算最优距离和次优距离之间的比值。之后统计原特征点和匹配到的特征点之间的角度(每30为一个范围列直方图),判断特征点最多的三个方向。此时第二多的特征数量<0.1最多的方向,则证明第一方向为主方向。若第三多的方向<0.1最多的方向,则证明第一个第二多的方向为主方向。

SearchByBoW 2种

每个节点中包含很多帧 ,对每个节点中对应的特征点一一匹配

SearchByProjection 4种

利用EPnP计算位姿进行motion-onl-BA优化,如果内点不足则投影匹配

computeSim3 将回环关键帧的共视关系投影到回环关键帧中搜索匹配点,匹配点足够多,则说明回环成功

TrackWithModel

三帧中,某点从第一帧到第二帧之间的变换为T,则假设第二帧到第三帧的变换为T’=T,在第三帧点的区域附近进项匹配,找到汉明距离最小的点,若小于设置的阈值,则匹配成功;若没找到。则提高阈值重试

SearchForTriangulation

跟踪线程中没有跟踪到的特征点在参考关键帧中进行三角测距

Fuse

回环之后将位姿和地图点矫正

SearchBySim3

高斯图像金字塔:图像处理中的高斯金字塔和拉普拉斯金字塔_熊彬程的博客的博客-CSDN博客_高斯金字塔的作用

  • 高斯金字塔(Gaussianpyramid): 用来向下采样,主要的图像金字塔
  • 拉普拉斯金字塔(Laplacianpyramid): 用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。
static float IC_Angle(const Mat& image, Point2f pt,  const vector<int> & u_max)
</int>

首先计算特征点的方向,使用了几何中心和灰度质心的连线方向

const uchar* center = &image.at<uchar> (cvRound(pt.y), cvRound(pt.x));
</uchar>

灰度值指针

定义 m_01为的加权,m_10为的加权,首先得到特征点所在图像块的灰度值指针center,除对称轴外每次可以计算(x,y)(x,-y),计算上方灰度值。轴上坐标加权m_10 += u * (val_plus + val_minus);在这一行上的和按照坐标加权。其中使用了fastAtan

 return fastAtan2((float)m_01, (float)m_10);

计算描述子的角度

static void computeOrbDescriptor(const KeyPoint& kpt,
                                 const Mat& img, const Point* pattern,
                                 uchar* desc)
const float factorPI = (float)(CV_PI/180.f);//&#x4E00;&#x5EA6;&#x5BF9;&#x5E94;&#x7684;&#x5F27;&#x5EA6;&#x5927;&#x5C0F;
const float factorPI = (float)(CV_PI/180.f);//&#x5F97;&#x5230;KPT&#x7279;&#x5F81;&#x70B9;&#x7684;&#x5BF9;&#x5E94;&#x89D2;&#x5EA6;
    for (int i = 0; i < 32; ++i, pattern += 16)
    {

        int t0,
            t1,
            val;

        t0 = GET_VALUE(0); t1 = GET_VALUE(1);
        val = t0 < t1;                          //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit0
        t0 = GET_VALUE(2); t1 = GET_VALUE(3);
        val |= (t0 < t1) << 1;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit1
        t0 = GET_VALUE(4); t1 = GET_VALUE(5);
        val |= (t0 < t1) << 2;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit2
        t0 = GET_VALUE(6); t1 = GET_VALUE(7);
        val |= (t0 < t1) << 3;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit3
        t0 = GET_VALUE(8); t1 = GET_VALUE(9);
        val |= (t0 < t1) << 4;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit4
        t0 = GET_VALUE(10); t1 = GET_VALUE(11);
        val |= (t0 < t1) << 5;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit5
        t0 = GET_VALUE(12); t1 = GET_VALUE(13);
        val |= (t0 < t1) << 6;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit6
        t0 = GET_VALUE(14); t1 = GET_VALUE(15);
        val |= (t0 < t1) << 7;                  //&#x63CF;&#x8FF0;&#x5B50;&#x672C;&#x5B57;&#x8282;&#x7684;bit7

        desc[i] = (uchar)val;

每个描述子是一个二进制字符,有两个灰度值比较得来,一共比32次,每次比较除8bit信息 ,所以是256位比特。使用预先定义好的随机点集

 for(int i=1; i<nlevels; i++) { mvscalefactor[i]="mvScaleFactor[i-1]*scaleFactor;" mvlevelsigma2[i]="mvScaleFactor[i]*mvScaleFactor[i];" }< code></nlevels;>

resize CV13 图像分辨率操作(图像金字塔与resize()函数)_什么都只会一点的博客-CSDN博客

计算每层缩放系数的平方,其中nLevels:ORBextractor.nLevels: 8写死在yaml中,又用mnScaleLevels = mpORBextractorLeft->GetLevels();在Frame.cc中得到,在ORBextractor中返回。 &#x53EF;&#x4EE5;&#x53D1;&#x73B0;&#x91D1;&#x5B57;&#x5854;&#x4E2D;&#x7B2C;0&#x5C42;&#x7684;&#x5C3A;&#x5EA6;&#x56E0;&#x5B50;&#x662F;1,&#x7136;&#x540E;&#x6BCF;&#x5411;&#x4E0A;&#x9AD8;&#x4E00;&#x5C42;&#xFF0C;&#x56FE;&#x50CF;&#x7684;&#x5C3A;&#x5EA6;&#x56E0;&#x5B50;&#x662F;&#x5728;&#x4E0A;&#x4E00;&#x5C42;&#x56FE;&#x50CF;&#x7684;&#x5C3A;&#x5EA6;&#x56E0;&#x5B50; 1 1*1.2 1*1.2*1.2 1*1.2*1.2*1.2 ...

loat nDesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 (float)pow((double)factor, (double)nlevels)); //&#x6BCF;&#x4E2A;&#x5355;&#x4F4D;&#x7F29;&#x653E;&#x7CFB;&#x6570;&#x6240;&#x5E0C;&#x671B;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x4E2A;&#x6570;
for( int level = 0; level < nlevels-1; level++ )
    {
        //&#x5206;&#x914D; cvRound : &#x8FD4;&#x56DE;&#x4E2A;&#x53C2;&#x6570;&#x6700;&#x63A5;&#x8FD1;&#x7684;&#x6574;&#x6570;&#x503C;
        mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);
        //&#x7D2F;&#x8BA1;
        sumFeatures += mnFeaturesPerLevel[level];
        //&#x4E58;&#x7CFB;&#x6570;
        nDesiredFeaturesPerScale *= factor;
    }&#x8BA1;&#x7B97;&#x9664;&#x4E86;&#x6700;&#x9876;&#x5C42;&#x5916;&#x6BCF;&#x5C42;&#x7CFB;&#x6570;&#xFF0C;&#x6BCF;&#x5C42;&#x6839;&#x636E;&#x91C7;&#x6837;&#x7F29;&#x653E;&#x7CFB;&#x6570;&#x7684;&#x5012;&#x6570;&#x5F97;&#x5230;&#x671F;&#x671B;&#x4E2A;&#x6570;&#x3002;&#x5E0C;&#x671B;&#x4E2A;&#x6570;&#x51CF;&#x53BB;&#x6BCF;&#x5C42;&#x4E2A;&#x6570;&#x548C;&#x5269;&#x4E0B;&#x7684;&#x5206;&#x914D;&#x5230;&#x6700;&#x9AD8;&#x5C42;

计算四分之一愿的边界,利用对称性得到边界

void ExtractorNode::DivideNode

将特征区域分为左上左下右上右下四块。存储边界点并将特征点放入对应的块中

 const int halfX = ceil(static_cast<float>(UR.x-UL.x)/2);
    const int halfY = ceil(static_cast<float>(BR.y-UL.y)/2);
//&#x7279;&#x5F81;&#x533A;&#x57DF;&#x53D6;&#x5F97;&#x4E2D;&#x95F4;&#x5750;&#x6807;&#x5C06;&#x533A;&#x57DF;&#x5206;&#x6210;&#x56DB;&#x5757;&#xFF0C;
 n1.UL = UL;
    n1.UR = cv::Point2i(UL.x+halfX,UL.y);
    n1.BL = cv::Point2i(UL.x,UL.y+halfY);
    n1.BR = cv::Point2i(UL.x+halfX,UL.y+halfY);//&#x5C06;&#x56DB;&#x5757;&#x5750;&#x6807;&#x5B58;&#x50A8;

 for(size_t i=0;i<vkeys.size();i++) 遍历特征点 const cv::keypoint &kp="vKeys[i];" if(kp.pt.x<n1.ur.x) { if(kp.pt.y<n1.br.y) n1.vkeys.push_back(kp); else n3.vkeys.push_back(kp); } n2.vkeys.push_back(kp); n4.vkeys.push_back(kp);将当前提取其中的特征点放入各自容器中< code></vkeys.size();i++)></float></float>

ORBextractor::DistributeOctTree

使用四叉树对一个金字塔图层中的特征点进行特征分发。

1.计算宽高、长宽比。resize提取器指针。
2.生成提取器节点

     ni.UL = cv::Point2i(hX*static_cast<float>(i),0);    //UpLeft
        ni.UR = cv::Point2i(hX*static_cast<float>(i+1),0);  //UpRight
        ni.BL = cv::Point2i(ni.UL.x,maxY-minY);             //BottomLeft
        ni.BR = cv::Point2i(ni.UR.x,maxY-minY);             //BottomRight
</float></float>
    // S &#x5C06;&#x7279;&#x5F81;&#x70B9;&#x5206;&#x914D;&#x5230;&#x5B50;&#x63D0;&#x53D6;&#x5668;&#x8282;&#x70B9;&#x4E2D;
    for(size_t i=0;i<vtodistributekeys.size();i++) 遍历此提取器节点列表,标记那些不可再分裂的节点,删除那些没有分配到特征点的节点 list<extractornode>::iterator lit = lNodes.begin();
    while(lit!=lNodes.end())</vtodistributekeys.size();i++)>

为提取器的区域分配一个提取器指针,并分配每个区域特征点期望个数。如果个数为1 ,则标志标志位表述不可再分,个数为0则删除。

    vector<pair<int,extractornode*> > vSizeAndPointerToNode;//&#x8BB0;&#x5F55;&#x4E86;&#x53EF;&#x4EE5;&#x7EE7;&#x7EED;&#x5206;&#x88C2;&#x7684;&#x8282;&#x70B9;</pair<int,extractornode*>

将一个初始化节点分为4个。利用四叉树划分区域。然后继续遍历提取器,如果提取器只有一个特征点,则list++,else则分成四个子区域。如果四个子区域中特征点数>0,则把这个子区域的提取器添加到列表前面。添加这个子区域中特征点的数目和节点指针vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));之后再循环分配器、提取器。分裂方式是后加的先分裂,先加的后分裂。

   if((int)lNodes.size()>=N|| (int)lNodes.size()==prevSize)
&#x5982;&#x679C;&#x5F53;&#x524D;&#x7279;&#x5F81;&#x70B9;&#x4E2A;&#x6570;&#x8FBE;&#x5230;&#x4E86;&#x9608;&#x503C;&#x6216;&#x6BCF;&#x4E2A;&#x533A;&#x57DF;&#x5DF2;&#x7ECF;&#x4E0D;&#x80FD;&#x518D;&#x5206;&#x5219;&#x505C;&#x6B62;
  else if(((int)lNodes.size()+nToExpand*3)>N)&#x5982;&#x679C;&#x5F53;&#x524D;&#x7279;&#x5F81;&#x70B9;&#x548C;&#x5C06;&#x8981;&#x5C55;&#x5F00;&#x7684;&#x70B9;&#x5DF2;&#x7ECF;&#x8D85;&#x8FC7;&#x6240;&#x8981;&#x4E2A;&#x6570;&#x3002;&#xFF0C;&#x5219;&#x60F3;&#x529E;&#x6CD5;&#x4F7F;&#x5176;&#x5728;&#x8FBE;&#x5230;&#x6216;&#x8005;&#x521A;&#x8D85;&#x8FC7;&#x6240;&#x9700;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x6570;&#x91CF;&#x65F6;&#x9000;&#x51FA;&#x3002;&#x4E00;&#x76F4;&#x5FAA;&#x73AF;&#x5230;&#x6807;&#x5FD7;&#x4F4D;&#x88AB;&#x6807;&#x5FD7;

此时我们使用pair将可以分裂的数量保存后用sort进行排序 ,从后往前遍历这个使用pair的vector对每个点进行分裂。pair的p2位待分裂节点、给到DivideNode(n1、n2、 3、 4),将需要分裂的点放到前面,并且如果大于1,则放入lnode的前面

 vector<cv::keypoint> vResultKeys;//&#x4FDD;&#x5B58;&#x5C06;&#x5174;&#x8DA3;&#x70B9;
for(list<extractornode>::iterator lit=lNodes.begin(); lit!=lNodes.end(); lit++)//&#x904D;&#x5386;</extractornode></cv::keypoint>
   for(size_t k=1;k<vnodekeys.size();k++) 遍历容器中的特征点,得到第一个点的最大响应值作为后续的最大响应值 if(vnodekeys[k].response>maxResponse)
&#x82E5;&#x6700;&#x5927;&#x54CD;&#x5E94;&#x503C;&#x5927;&#x4E8E;&#x7B2C;&#x4E00;&#x4E2A;&#x6700;&#x5927;&#x54CD;&#x5E94;&#x503C;&#x5219;&#x66F4;&#x65B0;&#x6700;&#x5927;&#x54CD;&#x5E94;&#x503C;
 vResultKeys.push_back(*pKP);
&#x5C06;&#x8FD9;&#x4E2A;&#x533A;&#x57DF;&#x7684;&#x6700;&#x5927;&#x54CD;&#x5E94;&#x503C;&#x52A0;&#x5165;&#x6700;&#x7EC8;&#x7ED3;&#x679C;&#x5BB9;&#x5668;
</vnodekeys.size();k++)>

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

首先计算图像坐标边界,存储需要进行分配的点和特增点个数。开始遍历,FAST提取FAST角点

FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX),
 if(vKeysCell.empty())
                {
                    //&#x90A3;&#x4E48;&#x5C31;&#x4F7F;&#x7528;&#x66F4;&#x4F4E;&#x7684;&#x9608;&#x503C;&#x6765;&#x8FDB;&#x884C;&#x91CD;&#x65B0;&#x68C0;&#x6D4B;
                    FAST(mvImagePyramid[level].rowRange(iniY,maxY).colRange(iniX,maxX), //&#x5F85;&#x68C0;&#x6D4B;&#x7684;&#x56FE;&#x50CF;
 for(vector<cv::keypoint>::iterator vit=vKeysCell.begin(); vit!=vKeysCell.end();vit++)
                    {
                        //NOTICE &#x5230;&#x76EE;&#x524D;&#x4E3A;&#x6B62;&#xFF0C;&#x8FD9;&#x4E9B;&#x89D2;&#x70B9;&#x7684;&#x5750;&#x6807;&#x90FD;&#x662F;&#x57FA;&#x4E8E;&#x56FE;&#x50CF;cell&#x7684;&#xFF0C;&#x73B0;&#x5728;&#x6211;&#x4EEC;&#x8981;&#x5148;&#x5C06;&#x5176;&#x6062;&#x590D;&#x5230;&#x5F53;&#x524D;&#x7684;&#x3010;&#x5750;&#x6807;&#x8FB9;&#x754C;&#x3011;&#x4E0B;&#x7684;&#x5750;&#x6807;
                        //&#x8FD9;&#x6837;&#x505A;&#x662F;&#x56E0;&#x4E3A;&#x5728;&#x4E0B;&#x9762;&#x4F7F;&#x7528;&#x516B;&#x53C9;&#x6811;&#x6CD5;&#x6574;&#x7406;&#x7279;&#x5F81;&#x70B9;&#x7684;&#x65F6;&#x5019;&#x5C06;&#x4F1A;&#x4F7F;&#x7528;&#x5F97;&#x5230;&#x8FD9;&#x4E2A;&#x5750;&#x6807;
                        //&#x5728;&#x540E;&#x9762;&#x5C06;&#x4F1A;&#x88AB;&#x7EE7;&#x7EED;&#x8F6C;&#x6362;&#x6210;&#x4E3A;&#x5728;&#x5F53;&#x524D;&#x56FE;&#x5C42;&#x7684;&#x6269;&#x5145;&#x56FE;&#x50CF;&#x5750;&#x6807;&#x7CFB;&#x4E0B;&#x7684;&#x5750;&#x6807;
                        (*vit).pt.x+=j*wCell;
                        (*vit).pt.y+=i*hCell;</cv::keypoint>

ORB-SLAM3笔记(编译、踩坑、论文、看代码)

cv::point Opencv KeyPoint类_DXT00的博客-CSDN博客

ORBextractor::ComputeKeyPointsOld/

使用老办法来得到特征点

计算cell个数,保存cell特征点数、位置、是否只有一个特征点等。之后遍历网格。如果cell在第一行则计算初始边界,如果在最后一个则计算增量坐标(有几个像素用几个,如果太小则舍弃),

Mat cellImage = mvImagePyramid[level].rowRange(iniY,iniY+hY).colRange(iniX,iniX+hX);
cellimage&#x7ED9;&#x4ED6;&#x6263;&#x51FA;&#x6765;&#xFF0C;&#x7B2C;&#x51E0;&#x5C42;.rosrange.lowrange
&#x5982;&#x679C;&#x63D0;&#x53D6;&#x51FA;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x5C0F;&#x4E8E;3&#x4E2A;&#xFF0C;&#x6E05;&#x7A7A;&#x540E;&#x964D;&#x4F4E;&#x9608;&#x503C;&#x91CD;&#x65B0;&#x63D0;&#x53D6;&#x3002;&#x5982;&#x679C;&#x6EE1;&#x8DB3;&#xFF0C;&#x5219;&#x4FDD;&#x5B58;&#x7279;&#x5F81;&#x70B9;&#x6570;&#x76EE;

根据评分使特征点分布均匀。根据响应值保留符合要求的特征点,并且进行坐标的转换

 KeyPointsFilter::retainBest(keysCell,          //&#x8F93;&#x5165;&#x8F93;&#x51FA;&#xFF0C;&#x7528;&#x4E8E;&#x63D0;&#x4F9B;&#x5F85;&#x4FDD;&#x7559;&#x7684;&#x7279;&#x5F81;&#x70B9;vector&#xFF0C;&#x64CD;&#x4F5C;&#x5B8C;&#x6210;&#x540E;&#x5C06;&#x4FDD;&#x7559;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x5B58;&#x653E;&#x5728;&#x91CC;&#x9762;
                                                                //&#x5176;&#x5B9E;&#x5C31;&#x662F;&#x628A;&#x90A3;&#x4E9B;&#x54CD;&#x5E94;&#x503C;&#x4F4E;&#x7684;&#x70B9;&#x4ECE;vector&#x4E2D;&#x7ED9;&#x5220;&#x9664;&#x4E86;
                                            nToRetain[i][j]);   //&#x6307;&#x5B9A;&#x8981;&#x4FDD;&#x7559;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x6570;&#x76EE;

  for(size_t k=0, kend=keysCell.size(); k<kend; k++) 坐标变换< code></kend;>

ORBextractor::operator()计算特征点

1.检查是否非空

2,构建图像金字塔ComputePyramid(image);

3.可以使用四叉树或者传统方法进行特征点计算,使特征点均匀化。

4.深拷贝描述子到新矩阵descriptors

4.1统计金字塔每层特征点个数并累加求和。

4.2存储图像金字塔中描述子的矩阵

5.使用高斯模糊避免噪声影响描述子计算

6.计算高斯模糊之后的描述子

7.对高斯模糊后更层上的描述子恢复到原本的图像金字塔坐标上

   void ORBextractor::ComputePyramid(cv::Mat image)

MapPoint

‘这部分函数命名很舒服,基本看下名称就知道干啥的,就是变量名有点烦

MapPoint::SetWorldPos 设置地图点坐标

cv::Mat MapPoint::GetWorldPos()返回世界坐标

cv::Mat MapPoint::GetNormal()获取平均观测方向

void MapPoint::AddObservation添加观测点

std::map

void MapPoint::SetBadFlag()先标记后删除,遍历mObservations,标记后直接擦除内存mpMap->EraseMapPoint(this);

void MapPoint::Replace(MapPoint* pMP)替换地图点

同一个点跳过 ,pMP用来替换的那个地图点 ,观测到的点的指针给到obs并清除原有观测

 map<keyframe*,tuple<int,int>> obs;
    {
        unique_lock<mutex> lock1(mMutexFeatures);
        unique_lock<mutex> lock2(mMutexPos);
        obs=mObservations;
    for(map<keyframe*,tuple<int,int>>::iterator mit=obs.begin(), mend=obs.end(); mit!=mend; mit++)
 &#x5C06;&#x539F;&#x6709;&#x80FD;&#x89C2;&#x6D4B;&#x5230;&#x539F;&#x6765;&#x5730;&#x56FE;&#x70B9;&#x7684;&#x5173;&#x952E;&#x5E27;&#x590D;&#x5236;&#x5230;&#x8981;&#x66FF;&#x6362;&#x7684;&#x5730;&#x56FE;&#x70B9;&#x4E0A;

&#x4E0D;&#x5728;&#x5219;&#x66FF;&#x6362;  if(!pMP->IsInKeyFrame(pKF))
        {
            // &#x5982;&#x679C;&#x4E0D;&#x5728;&#xFF0C;&#x66FF;&#x6362;&#x7279;&#x5F81;&#x70B9;&#x4E0E;mp&#x7684;&#x5339;&#x914D;&#x5173;&#x7CFB;
            if(leftIndex != -1){
                pKF->ReplaceMapPointMatch(leftIndex, pMP);
                pMP->AddObservation(pKF,leftIndex);
            }
            if(rightIndex != -1){
                pKF->ReplaceMapPointMatch(rightIndex, pMP);
                pMP->AddObservation(pKF,rightIndex);
            }
&#x5982;&#x679C;&#x5728; &#x5220;&#x9664;&#x65E7;&#x7684; &#x65B0;&#x7684;&#x4E0D;&#x52A8;
&#x66F4;&#x65B0;&#x539F;&#x6709; &#x53EF;&#x89C2;&#x6D4B;&#x6B64;&#x70B9;&#x7684;&#x5E27;&#x6570; &#x53EF;&#x89C6;&#x70B9;&#xFF08;&#x975E;&#x7279;&#x5F81;&#x70B9;&#x548C;&#x5730;&#x56FE;&#x70B9; &#x80FD;&#x591F;&#x770B;&#x5230;&#x7684;&#x70B9;&#xFF09; &#x63CF;&#x8FF0;&#x5B50;</keyframe*,tuple<int,int></mutex></mutex></keyframe*,tuple<int,int>

void MapPoint::ComputeDistinctiveDescriptors()

  • 由于一个MapPoint会被许多相机观测到,因此在插入关键帧后,需要判断是否更新当前点的最适合的描述子。先获得当前点的所有描述子,然后计算描述子之间的两两距离,最好的描述子与其他描述子应该具有最小的距离中值

1.跳过坏点、

2.遍历点获取描述子放入vDESCRIPTORS

.3.遍历点取得距离给distij

 int distij = ORBmatcher::DescriptorDistance(vDescriptors[i],vDescriptors[j]);
  1. 选择最有代表性的描述子,它与其他描述子应该具有最小的距离中值,距离中值为INT_MAX
vector<int> vDists(Distances[i],Distances[i]+N);
        sort(vDists.begin(),vDists.end());
        // &#x83B7;&#x5F97;&#x4E2D;&#x503C;
        int median = vDists[0.5*(N-1)];

        // &#x5BFB;&#x627E;&#x6700;&#x5C0F;&#x7684;&#x4E2D;&#x503C;
        if(median<bestmedian) { bestmedian="median;" bestidx="i;" }< code></bestmedian)></int>

返回描述子,确定是否在关键帧中并得到这个点在关键帧中对应的id

void MapPoint::UpdateNormalAndDepth()

更新平均观测方向以及观测距离范围。由于一个MapPoint会被许多相机观测到,因此在插入关键帧后,需要更新相应变量。创建新的关键帧的时候会调用。

1.获得该地图点的相关信息

 observations=mObservations; // &#x83B7;&#x5F97;&#x89C2;&#x6D4B;&#x5230;&#x8BE5;&#x5730;&#x56FE;&#x70B9;&#x7684;&#x6240;&#x6709;&#x5173;&#x952E;&#x5E27;
        pRefKF=mpRefKF;             // &#x89C2;&#x6D4B;&#x5230;&#x8BE5;&#x70B9;&#x7684;&#x53C2;&#x8003;&#x5173;&#x952E;&#x5E27;&#xFF08;&#x7B2C;&#x4E00;&#x6B21;&#x521B;&#x5EFA;&#x65F6;&#x7684;&#x5173;&#x952E;&#x5E27;&#xFF09;
        Pos = mWorldPos.clone();    // &#x5730;&#x56FE;&#x70B9;&#x5728;&#x4E16;&#x754C;&#x5750;&#x6807;&#x7CFB;&#x4E2D;&#x7684;&#x4F4D;&#x7F6E;

2.能观测到此点的所有观测方向归一化。得到该点的朝向

主要是用该点的相机坐标减去其世界坐标,normal为法线。首先得到法线。然后计算改点到参考关键帧相机的距离得到这个点的观测距离上下限和地图平均观测方向

float MapPoint::GetMaxDistanceInvariance()

这里没太看懂

ORB-SLAM3笔记(编译、踩坑、论文、看代码)
// &#x5728;&#x8FDB;&#x884C;&#x6295;&#x5F71;&#x5339;&#x914D;&#x7684;&#x65F6;&#x5019;&#x4F1A;&#x7ED9;&#x5B9A;&#x7279;&#x5F81;&#x70B9;&#x7684;&#x641C;&#x7D22;&#x8303;&#x56F4;,&#x8003;&#x8651;&#x5230;&#x5904;&#x4E8E;&#x4E0D;&#x540C;&#x5C3A;&#x5EA6;(&#x4E5F;&#x5C31;&#x662F;&#x8DDD;&#x79BB;&#x76F8;&#x673A;&#x8FDC;&#x8FD1;,&#x4F4D;&#x4E8E;&#x56FE;&#x50CF;&#x91D1;&#x5B57;&#x5854;&#x4E2D;&#x4E0D;&#x540C;&#x56FE;&#x5C42;)&#x7684;&#x7279;&#x5F81;&#x70B9;&#x53D7;&#x5230;&#x76F8;&#x673A;&#x65CB;&#x8F6C;&#x7684;&#x5F71;&#x54CD;&#x4E0D;&#x540C;,
// &#x56E0;&#x6B64;&#x4F1A;&#x5E0C;&#x671B;&#x8DDD;&#x79BB;&#x76F8;&#x673A;&#x8FD1;&#x7684;&#x70B9;&#x7684;&#x641C;&#x7D22;&#x8303;&#x56F4;&#x66F4;&#x5927;&#x4E00;&#x70B9;,&#x8DDD;&#x79BB;&#x76F8;&#x673A;&#x66F4;&#x8FDC;&#x7684;&#x70B9;&#x7684;&#x641C;&#x7D22;&#x8303;&#x56F4;&#x66F4;&#x5C0F;&#x4E00;&#x70B9;,&#x6240;&#x4EE5;&#x8981;&#x5728;&#x8FD9;&#x91CC;,&#x6839;&#x636E;&#x70B9;&#x5230;&#x5173;&#x952E;&#x5E27;/&#x5E27;&#x7684;&#x8DDD;&#x79BB;&#x6765;&#x4F30;&#x8BA1;&#x5B83;&#x5728;&#x5F53;&#x524D;&#x7684;&#x5173;&#x952E;&#x5E27;/&#x5E27;&#x4E2D;,
// &#x4F1A;&#x5927;&#x6982;&#x5904;&#x4E8E;&#x54EA;&#x4E2A;&#x5C3A;&#x5EA6;
int MapPoint::PredictScale(const float &#xA4;tDist, KeyFrame* pKF)
{
    float ratio;
    {
        unique_lock<mutex> lock(mMutexPos);
        // mfMaxDistance = ref_dist*levelScaleFactor &#x4E3A;&#x53C2;&#x8003;&#x5E27;&#x8003;&#x8651;&#x4E0A;&#x5C3A;&#x5EA6;&#x540E;&#x7684;&#x8DDD;&#x79BB;
        // ratio = mfMaxDistance/currentDist = ref_dist/cur_dist
        ratio = mfMaxDistance/currentDist;
    }

    // &#x540C;&#x65F6;&#x53D6;log&#x7EBF;&#x6027;&#x5316;
    int nScale = ceil(log(ratio)/pKF->mfLogScaleFactor);
    if(nScale<0) nscale="0;" else if(nscale>=pKF->mnScaleLevels)
        nScale = pKF->mnScaleLevels-1;

    return nScale;
}

/**
 * @brief &#x6839;&#x636E;&#x5730;&#x56FE;&#x70B9;&#x5230;&#x5149;&#x5FC3;&#x7684;&#x8DDD;&#x79BB;&#x6765;&#x9884;&#x6D4B;&#x4E00;&#x4E2A;&#x7C7B;&#x4F3C;&#x7279;&#x5F81;&#x91D1;&#x5B57;&#x5854;&#x7684;&#x5C3A;&#x5EA6;
 *
 * @param[in] currentDist       &#x5730;&#x56FE;&#x70B9;&#x5230;&#x5149;&#x5FC3;&#x7684;&#x8DDD;&#x79BB;
 * @param[in] pF                &#x5F53;&#x524D;&#x5E27;
 * @return int                  &#x5C3A;&#x5EA6;
 */
int MapPoint::PredictScale(const float &#xA4;tDist, Frame* pF)
{
    float ratio;
    {
        unique_lock<mutex> lock(mMutexPos);
        ratio = mfMaxDistance/currentDist;
    }

    int nScale = ceil(log(ratio)/pF->mfLogScaleFactor);
    if(nScale<0) nscale="0;" else if(nscale>=pF->mnScaleLevels)
        nScale = pF->mnScaleLevels-1;

    return nScale;
}

Map* MapPoint::GetMap()
{
    unique_lock<mutex> lock(mMutexMap);
    return mpMap;
}

void MapPoint::UpdateMap(Map* pMap)
{
    unique_lock<mutex> lock(mMutexMap);
    mpMap = pMap;
}

} //namespace ORB_SLAM
</mutex></mutex></0)></mutex></0)></mutex>

Frame.cc

麻了呀写完了都,结果发布之后就没了

Frame::Frame

构造frame

for(int i=0;i

Original: https://blog.csdn.net/qq_39533374/article/details/123709139
Author: 菜饭骨头汤无锡总代理(销售王子臧)
Title: ORB-SLAM3笔记(编译、踩坑、论文、看代码)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/637633/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球