如何评价ORB-SLAM3?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何评价ORB-SLAM3?相关的知识,希望对你有一定的参考价值。
我觉得 ORB-SLAM3 系统是基于之前的 ORB-SLAM2、ORB-SLAM-VI 进行扩展。作者组的工作一脉相承,围绕着 ORB feature-based SLAM 做了非常多有重大意义的工作。本文其中在一些重要改进模块,如 IMU 初始化、multi-map system 等,是作者组里前几年的工作。我认为这是一篇更加偏向于系统性质的文章,把这么多工作串了起来,并且作者非常慷慨的把它开源了出来,非常赞!
参考技术A优势: 在静态环境下定位准确,稳定, 单目和双目版本都可以达到实时(高于10frames/s)。代码可读性强,易扩展, 网上也有实现和imu融合的版本。
劣势:建的地图点云稀疏。 运行速度方面,因为提特征点的时间有瓶颈最快的运行速度应该不超过30frames/s, 我在本机 (i7-6600U) 测的速度基本都在20frames/s左右,因此对于高帧率的相机需要降帧率才能用。对动态物体很敏感,再有动态物体时非常容易tracking lost。
总的来说ORB-SLAM还是在智能驾驶领域用得最广泛的SLAM算法,因为它在work的时候可以做得很好,急需解决的问题是对特征点提取的加速,以及处理的环境中的动态物体。
参考技术BORB-SLAM3是一个基于单目、双目、RGB-D相机的实时视觉SLAM系统,是ORB-SLAM系列的最新版本。以下是对ORB-SLAM3的评价:
精度高:ORB-SLAM3采用了多种技术来提高定位和建图的精度,例如在ORB特征匹配、全局优化和回环检测等方面进行了优化,使得系统的精度得到了提高。
实时性好:ORB-SLAM3采用了多线程和GPU加速等技术,使得系统可以在实时性要求较高的场景下运行,例如在移动机器人或自动驾驶等领域。
稳定性强:ORB-SLAM3在不同场景下的稳定性得到了保证,例如在光照变化、运动模糊和动态物体等复杂场景下,系统的表现依然稳定。
易于使用:ORB-SLAM3提供了友好的用户界面和API,使得用户可以方便地使用系统进行SLAM任务。
开放源代码:ORB-SLAM3是一个开源系统,用户可以方便地修改和扩展系统的功能,例如增加新的相机模型或传感器等。
综上所述,ORB-SLAM3是一个功能强大、精度高、实时性好、稳定性强、易于使用和开放源代码的视觉SLAM系统,具有很高的实际应用价值。
ORB-SLAM2 地图加载
一、前面说了ORB-SLAM地图的保存部分,继续说地图如何加载,因为加载部分相比保存要稍微复杂一些,所以要多说一点。
二、ORB-SLAM2地图加载构成
首先同样是在头文件中声明加载函数,包含地图点和关键帧类的加载。
void Load( const string &filename, SystemSetting* mySystemSetting ); MapPoint* LoadMapPoint( ifstream &f ); KeyFrame* LoadKeyFrame( ifstream &f, SystemSetting* mySystemSetting );
下面先是加载主函数Load的构成,关于SystemSetting类后面再说:
void Map::Load ( const string &filename, SystemSetting* mySystemSetting ) { cerr << "Map reading from:"<<filename<<endl; ifstream f; f.open( filename.c_str() ); //按照保存的顺序,先读取MapPoints的数目; unsigned long int nMapPoints, max_id = 0; f.read((char*)&nMapPoints, sizeof(nMapPoints)); //依次读取每一个MapPoints,并将其加入到地图中 cerr<<"The number of MapPoints:"<<nMapPoints<<endl; for ( unsigned int i = 0; i < nMapPoints; i ++ ) { MapPoint* mp = LoadMapPoint(f); if ( mp->mnId >= max_id ) max_id = mp->mnId; AddMapPoint(mp); } //获取所有的MapPoints; std::vector<MapPoint*> vmp = GetAllMapPoints(); //读取关键帧的数目; unsigned long int nKeyFrames; f.read((char*)&nKeyFrames, sizeof(nKeyFrames)); cerr<<"The number of KeyFrames:"<<nKeyFrames<<endl; //依次读取每一关键帧,并加入到地图; vector<KeyFrame*>kf_by_order; for( unsigned int i = 0; i < nKeyFrames; i ++ ) { KeyFrame* kf = LoadKeyFrame(f, mySystemSetting); AddKeyFrame(kf); kf_by_order.push_back(kf); } cerr<<"KeyFrame Load OVER!"<<endl; //读取生长树; map<unsigned long int, KeyFrame*> kf_by_id; for ( auto kf: mspKeyFrames ) kf_by_id[kf->mnId] = kf; cerr<<"Start Load The Parent!"<<endl; for( auto kf: kf_by_order ) { //读取当前关键帧的父节点ID; unsigned long int parent_id; f.read((char*)&parent_id, sizeof(parent_id)); //给当前关键帧添加父节点关键帧; if ( parent_id != ULONG_MAX ) kf->ChangeParent(kf_by_id[parent_id]); //读取当前关键帧的关联关系; //先读取当前关键帧的关联关键帧的数目; unsigned long int nb_con; f.read((char*)&nb_con, sizeof(nb_con)); //然后读取每一个关联关键帧的ID和weight,并把该关联关键帧加入关系图中; for ( unsigned long int i = 0; i < nb_con; i ++ ) { unsigned long int id; int weight; f.read((char*)&id, sizeof(id)); f.read((char*)&weight, sizeof(weight)); kf->AddConnection(kf_by_id[id],weight); } } cerr<<"Parent Load OVER!"<<endl; for ( auto mp: vmp ) { if(mp) { mp->ComputeDistinctiveDescriptors(); mp->UpdateNormalAndDepth(); } } f.close(); cerr<<"Load IS OVER!"<<endl; return; }
其过程就是根据保存的顺序依次加载地图点的数目、地图点、关键帧的数目、关键帧、生长树和关联关系。
下面是LoadMapPoints函数的构成:
MapPoint* Map::LoadMapPoint( ifstream &f ) { //主要包括MapPoints的位姿和ID; cv::Mat Position(3,1,CV_32F); long unsigned int id; f.read((char*)&id, sizeof(id)); f.read((char*)&Position.at<float>(0), sizeof(float)); f.read((char*)&Position.at<float>(1), sizeof(float)); f.read((char*)&Position.at<float>(2), sizeof(float)); //初始化一个MapPoint,并设置其ID和Position; MapPoint* mp = new MapPoint(Position, this ); mp->mnId = id; mp->SetWorldPos( Position ); return mp; }
从这里开始涉及到了MapPoint类的初始化问题,由于这里只有Position以及当前的Map,所以需要从新定义MapPoint的构造函数,分别加入到MapPoint的头文件和源文件中:
MapPoint( const cv::Mat& Pos, Map* pMap ); MapPoint::MapPoint(const cv::Mat &Pos, Map* pMap): mnFirstKFid(0), mnFirstFrame(0), nObs(0), mnTrackReferenceForFrame(0), mnLastFrameSeen(0), mnBALocalForKF(0), mnFuseCandidateForKF(0), mnLoopPointForKF(0), mnCorrectedByKF(0), mnCorrectedReference(0), mnBAGlobalForKF(0), mpRefKF(static_cast<KeyFrame*>(NULL)), mnVisible(1), mnFound(1), mbBad(false), mpReplaced(static_cast<MapPoint*>(NULL)), mfMinDistance(0), mfMaxDistance(0), mpMap(pMap) { Pos.copyTo(mWorldPos); mNormalVector = cv::Mat::zeros(3,1,CV_32F); // MapPoints can be created from Tracking and Local Mapping. This mutex avoid conflicts with id. unique_lock<mutex> lock(mpMap->mMutexPointCreation); mnId=nNextId++; }
紧接着是LoadKeyFrame函数的构成,这里由于KeyFrame类需要的初始化信息比较多,因此定义了一个InitKeyFrame类,它通过SystemSetting进行初始化,二SystemSetting的主要作用就是读取设置文件(相机内参,ORB特征参数等),后面将给出SystemSetting和InitKeyFrame类的代码:
KeyFrame* Map::LoadKeyFrame( ifstream &f, SystemSetting* mySystemSetting ) { //声明一个初始化关键帧的类initkf; InitKeyFrame initkf(*mySystemSetting); //按照保存次序,依次读取关键帧的ID和时间戳; f.read((char*)&initkf.nId, sizeof(initkf.nId)); f.read((char*)&initkf.TimeStamp, sizeof(double)); //读取关键帧位姿矩阵; cv::Mat T = cv::Mat::zeros(4,4,CV_32F); std::vector<float> Quat(4); //Quat.reserve(4); for ( int i = 0; i < 4; i ++ ) f.read((char*)&Quat[i],sizeof(float)); cv::Mat R = Converter::toCvMat( Quat ); for ( int i = 0; i < 3; i ++ ) f.read((char*)&T.at<float>(i,3),sizeof(float)); for ( int i = 0; i < 3; i ++ ) for ( int j = 0; j < 3; j ++ ) T.at<float>(i,j) = R.at<float>(i,j); T.at<float>(3,3) = 1; // for ( int i = 0; i < 4; i ++ ) // { // for ( int j = 0; j < 4; j ++ ) // { // f.read((char*)&T.at<float>(i,j), sizeof(float)); // cerr<<"T.at<float>("<<i<<","<<j<<"):"<<T.at<float>(i,j)<<endl; // } // } //读取当前关键帧特征点的数目; f.read((char*)&initkf.N, sizeof(initkf.N)); initkf.vKps.reserve(initkf.N); initkf.Descriptors.create(initkf.N, 32, CV_8UC1); vector<float>KeypointDepth; std::vector<MapPoint*> vpMapPoints; vpMapPoints = vector<MapPoint*>(initkf.N,static_cast<MapPoint*>(NULL)); //依次读取当前关键帧的特征点和描述符; std::vector<MapPoint*> vmp = GetAllMapPoints(); for(int i = 0; i < initkf.N; i ++ ) { cv::KeyPoint kp; f.read((char*)&kp.pt.x, sizeof(kp.pt.x)); f.read((char*)&kp.pt.y, sizeof(kp.pt.y)); f.read((char*)&kp.size, sizeof(kp.size)); f.read((char*)&kp.angle,sizeof(kp.angle)); f.read((char*)&kp.response, sizeof(kp.response)); f.read((char*)&kp.octave, sizeof(kp.octave)); initkf.vKps.push_back(kp); //根据需要读取特征点的深度值; //float fDepthValue = 0.0; //f.read((char*)&fDepthValue, sizeof(float)); //KeypointDepth.push_back(fDepthValue); //读取当前特征点的描述符; for ( int j = 0; j < 32; j ++ ) f.read((char*)&initkf.Descriptors.at<unsigned char>(i,j),sizeof(char)); //读取当前特征点和MapPoints的对应关系; unsigned long int mpidx; f.read((char*)&mpidx, sizeof(mpidx)); //从vmp这个所有的MapPoints中查找当前关键帧的MapPoint,并插入 if( mpidx == ULONG_MAX ) vpMapPoints[i] = NULL; else vpMapPoints[i] = vmp[mpidx]; } initkf.vRight = vector<float>(initkf.N,-1); initkf.vDepth = vector<float>(initkf.N,-1); //initkf.vDepth = KeypointDepth; initkf.UndistortKeyPoints(); initkf.AssignFeaturesToGrid(); //使用initkf初始化一个关键帧,并设置相关参数 KeyFrame* kf = new KeyFrame( initkf, this, NULL, vpMapPoints ); kf->mnId = initkf.nId; kf->SetPose(T); kf->ComputeBoW(); for ( int i = 0; i < initkf.N; i ++ ) { if ( vpMapPoints[i] ) { vpMapPoints[i]->AddObservation(kf,i); if( !vpMapPoints[i]->GetReferenceKeyFrame()) vpMapPoints[i]->SetReferenceKeyFrame(kf); } } return kf; }
从文件中读取的内容同保存的一致,同时由于是通过InitKeyFrame初始化的关键帧类KeyFrame,因此这里同样需要添加构造函数以及初始化方式:
KeyFrame(InitKeyFrame &initkf, Map* pMap, KeyFrameDatabase* pKFDB,vector< MapPoint*>& vpMapPoints); KeyFrame::KeyFrame(InitKeyFrame &initkf, Map *pMap, KeyFrameDatabase *pKFDB, vector<MapPoint*> &vpMapPoints): mnFrameId(0), mTimeStamp(initkf.TimeStamp), mnGridCols(FRAME_GRID_COLS), mnGridRows(FRAME_GRID_ROWS), mfGridElementWidthInv(initkf.fGridElementWidthInv), mfGridElementHeightInv(initkf.fGridElementHeightInv), mnTrackReferenceForFrame(0), mnFuseTargetForKF(0), mnBALocalForKF(0), mnBAFixedForKF(0), mnLoopQuery(0), mnLoopWords(0), mnRelocQuery(0), mnRelocWords(0), mnBAGlobalForKF(0), fx(initkf.fx), fy(initkf.fy), cx(initkf.cx), cy(initkf.cy), invfx(initkf.invfx), invfy(initkf.invfy), mbf(initkf.bf), mb(initkf.b), mThDepth(initkf.ThDepth), N(initkf.N), mvKeys(initkf.vKps), mvKeysUn(initkf.vKpsUn), mvuRight(initkf.vRight), mvDepth(initkf.vDepth), mDescriptors(initkf.Descriptors.clone()), mBowVec(initkf.BowVec), mFeatVec(initkf.FeatVec), mnScaleLevels(initkf.nScaleLevels), mfScaleFactor(initkf.fScaleFactor), mfLogScaleFactor(initkf.fLogScaleFactor), mvScaleFactors(initkf.vScaleFactors), mvLevelSigma2(initkf.vLevelSigma2),mvInvLevelSigma2(initkf.vInvLevelSigma2), mnMinX(initkf.nMinX), mnMinY(initkf.nMinY), mnMaxX(initkf.nMaxX), mnMaxY(initkf.nMaxY), mK(initkf.K), mvpMapPoints(vpMapPoints), mpKeyFrameDB(pKFDB), mpORBvocabulary(initkf.pVocabulary), mbFirstConnection(true), mpParent(NULL), mbNotErase(false), mbToBeErased(false), mbBad(false), mHalfBaseline(initkf.b/2), mpMap(pMap) { mnId = nNextId ++; }
加载了一个关键帧之后还需要计算其BoW向量等操作,同时指定关键帧对地图点的观测。
三、总结
加载的过程大概就是这样,时间太晚了,下次在给出InitKeyFrame和SystemSetting两个类的代码,以及其它修改的地方。总结来看,加载时关键帧类和地图点类的初始化是比较烦人的,各种繁琐的参数需要设置,所以很可能存在什么bug也说不定。另外,博客里面的代码部分是参考的网上的代码,部分是自己写的,希望有人看到了有什么错误及时指正。在数据库rgbd_dataset_freiburg1_xyz上的视频测试时是没问题的。
以上是关于如何评价ORB-SLAM3?的主要内容,如果未能解决你的问题,请参考以下文章