ORB-SLAM3 单目惯导ros-system-track
文章目录
- ROS
- ros_mono_interial.cc
- Function
- 主函数
- imu抓取
- image 抓取
- image与imu同步
- ORB_SLAM3::System
- Function
- 构造
- TrackMonocular 单目跟踪
- TrackStereo
- TrackRGBD
- Shutdown 关闭
- SaveTrajectoryTUM 按TUM保存轨迹
- 其他数据的保存
- Tracking
- 核心函数
- 构造
- GrabImageMonocular
- Track 跟踪
- 相关函数-地图集
- CreateMapInAtlas
- ResetActiveMap
- 相关函数-参数读取
- ParseCamParamFile 读取相机文件
- ParseORBParamFile 读取orb参数
- ParseIMUParamFile 读取imu参数
- 相关函数-Imu相关
- PreintegrateIMU IMU预积分
- PredictStateIMU 利用IMU计算位姿
- ComputeGyroBias 角度零偏
- ComputeVelocitiesAccBias 加速度零偏
- 相关函数-初始化
- MonocularInitialization 单目初始化
- CreateInitialMapMonocular 创建初始化单目地图
- 相关函数-跟踪
- TrackWithMotionModel 恒速模型跟踪
- UpdateLastFrame 更新上一Frame
- TrackReferenceKeyFrame 跟踪参考关键帧
- TrackLocalMap 跟踪局部地图
- 相关函数-关键帧
- Relocalization 重定位
- NeedNewKeyFrame 关键帧选取策略
- CreateNewKeyFrame 创建新的关键帧
ROS
- 程序分为ROS接口和非ROS接口的。
- 主程序 main 在ROS/ORB_SLAM3/src/中:
ros_mono_interial.cc
Function
- 主函数:读取参数,并创建了slam系统,订阅两topic+创建同步线程。
- 回调函数:只将数据放入队列,上锁
- 同步线程:1s一次,先数据同步,后进行跟踪
主函数
- 读取参数 argc
- 创建SLAM系统,它初始化所有系统线程并准备好处理帧。
- 订阅IMU+CAMERA topic,回调中将数据放入队列
- 数据同步线程 ( Image 与 imu)
int main(int argc, char **argv)
{ros::init(argc, argv, "Mono_Inertial");ros::NodeHandle n("~");ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info);bool bEqual = false;if(argc < 3 || argc > 4){cerr << endl << "Usage: rosrun ORB_SLAM3 Mono_Inertial path_to_vocabulary path_to_settings [do_equalize]" << endl;ros::shutdown();return 1;}if(argc==4){std::string sbEqual(argv[3]);if(sbEqual == "true")bEqual = true;}// Create SLAM system. It initializes all system threads and gets ready to process frames.ORB_SLAM3::System SLAM(argv[1],argv[2],ORB_SLAM3::System::IMU_MONOCULAR,true);ImuGrabber imugb;ImageGrabber igb(&SLAM,&imugb,bEqual); // TODO// Maximum delay, 5 secondsros::Subscriber sub_imu = n.subscribe("/imu", 1000, &ImuGrabber::GrabImu, &imugb); ros::Subscriber sub_img0 = n.subscribe("/camera/image_raw", 100, &ImageGrabber::GrabImage,&igb);std::thread sync_thread(&ImageGrabber::SyncWithImu,&igb);ros::spin();return 0;
}
imu抓取
class ImuGrabber
{public:ImuGrabber(){};void GrabImu(const sensor_msgs::ImuConstPtr &imu_msg);queue<sensor_msgs::ImuConstPtr> imuBuf;std::mutex mBufMutex;
};
// 直接放入imubuf中
void ImuGrabber::GrabImu(const sensor_msgs::ImuConstPtr &imu_msg)
{mBufMutex.lock();imuBuf.push(imu_msg);mBufMutex.unlock();return;
}
image 抓取
class ImageGrabber
{public:ImageGrabber(ORB_SLAM3::System* pSLAM, ImuGrabber *pImuGb, const bool bClahe): mpSLAM(pSLAM), mpImuGb(pImuGb), mbClahe(bClahe){}void GrabImage(const sensor_msgs::ImageConstPtr& msg);cv::Mat GetImage(const sensor_msgs::ImageConstPtr &img_msg);void SyncWithImu();queue<sensor_msgs::ImageConstPtr> img0Buf;std::mutex mBufMutex;ORB_SLAM3::System* mpSLAM;ImuGrabber *mpImuGb;const bool mbClahe;cv::Ptr<cv::CLAHE> mClahe = cv::createCLAHE(3.0, cv::Size(8, 8));
};// 回调就是将其数据放入队列
void ImageGrabber::GrabImage(const sensor_msgs::ImageConstPtr &img_msg)
{mBufMutex.lock();if (!img0Buf.empty())img0Buf.pop();img0Buf.push(img_msg);mBufMutex.unlock();
}
image与imu同步
步骤:
- 确保imu时间是否包含住image时间,否则continue
- 取出 image + vector
- 基于参数确定图像是否需要直方图均衡。
void ImageGrabber::SyncWithImu()
{while(1){cv::Mat im;double tIm = 0;if (!img0Buf.empty()&&!mpImuGb->imuBuf.empty()){tIm = img0Buf.front()->header.stamp.toSec();// 保证:imu 最新时间比 image 新if(tIm>mpImuGb->imuBuf.back()->header.stamp.toSec())continue;{ // 取出图片this->mBufMutex.lock();im = GetImage(img0Buf.front());img0Buf.pop();this->mBufMutex.unlock();}// 取出 小于image时间的imu数据,{time,线加速度,角加速度}vector<ORB_SLAM3::IMU::Point> vImuMeas;mpImuGb->mBufMutex.lock();if(!mpImuGb->imuBuf.empty()){// Load imu measurements from buffervImuMeas.clear();while(!mpImuGb->imuBuf.empty() && mpImuGb->imuBuf.front()->header.stamp.toSec()<=tIm){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(im,im);mpSLAM->TrackMonocular(im,tIm,vImuMeas);}std::chrono::milliseconds tSleep(1);std::this_thread::sleep_for(tSleep);}
}
// cv::CLAHE 是另外一种直方图均衡算法,CLAHE 和 AHE 的区别在于前者对区域对比度实行了限制,并且利用插值来加快计算。它能有效的增强或改善图像(局部)对比度,从而获取更多图像相关边缘信息有利于分割。还能够有效改善 AHE 中放大噪声的问题。另外,CLAHE 的有一个用途是被用来对图像去雾。
ros-image->Mat
cv::Mat ImageGrabber::GetImage(const sensor_msgs::ImageConstPtr &img_msg)
{// Copy the ros image message to cv::Mat.cv_bridge::CvImageConstPtr cv_ptr;try{cv_ptr = cv_bridge::toCvShare(img_msg, sensor_msgs::image_encodings::MONO8);}catch (cv_bridge::Exception& e){ROS_ERROR("cv_bridge exception: %s", e.what());}if(cv_ptr->image.type()==0){return cv_ptr->image.clone();}else{std::cout << "Error type" << std::endl;return cv_ptr->image.clone();}
}
ORB_SLAM3::System
Function
- main主函数定义了System类, 他创建了跟踪、局部地图线程、闭环线程。
- 跟踪:image+imu同步线程中。
- System.cc这个文件主要是SLAM系统的初始化、跟踪线程的入口以及轨迹的保存。
构造
- 构建 字典 并加载已有字典
- 建立 关键帧库 和 地图集
- 创建 显示 Frame和地图的类,由viewer调用
- 创建跟踪线程
- 创建局部地图线程
- 创建闭环线程
System::System(
const string &strVocFile //词典文件路径
, const string &strSettingsFile //配置文件路径
, const eSensor sensor //传感器类型
,const bool bUseViewer // 是否使用可视化界面
, const int initFr // 初始化Frame
, const string &strSequence
, const string &strLoadingFile):mSensor(sensor), mpViewer(static_cast<Viewer*>(NULL)), mbReset(false), mbResetActiveMap(false),mbActivateLocalizationMode(false), mbDeactivateLocalizationMode(false)
{// 打印 什么传感器if(mSensor==MONOCULAR) //单目cout << "Monocular" << endl;else if(mSensor==STEREO) //双目cout << "Stereo" << endl;else if(mSensor==RGBD)cout << "RGB-D" << endl;else if(mSensor==IMU_MONOCULAR) //IMU+单目cout << "Monocular-Inertial" << endl;else if(mSensor==IMU_STEREO) // IMU+双目cout << "Stereo-Inertial" << endl;//Check settings file //将配置文件名转换成为字符串 且只读cv::FileStorage fsSettings(strSettingsFile.c_str(), cv::FileStorage::READ);//如果打开失败,就输出调试信息,然后退出if(!fsSettings.isOpened()){ cerr << "Failed to open settings file at: " << strSettingsFile << endl;exit(-1);}// 加载Atlas的标志,目前没有用bool loadedAtlas = false;//Load ORB Vocabularycout << endl << "Loading ORB Vocabulary. This could take a while..." << endl;// 建立一个新的ORB字典,并加载已有的orb字典词袋mpVocabulary = new ORBVocabulary();bool bVocLoad = mpVocabulary->loadFromTextFile(strVocFile);// 如果加载失败,就输出调试信息,并退出if(!bVocLoad){cerr << "Wrong path to vocabulary. " << endl;cerr << "Falied to open at: " << strVocFile << endl;exit(-1);}cout << "Vocabulary loaded!" << endl << endl;//Create KeyFrame Database 创建关键帧数据库mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);//Create the Atlas 创建地图集,参数0表示初始化关键帧id为0mpAtlas = new Atlas(0);// 若使用了imu,则mpAtlas设定imu传感器if (mSensor==IMU_STEREO || mSensor==IMU_MONOCULAR)mpAtlas->SetInertialSensor();// 这里的帧绘制器和地图绘制器将会被可视化的Viewer所使用mpFrameDrawer = new FrameDrawer(mpAtlas);mpMapDrawer = new MapDrawer(mpAtlas, strSettingsFile);//Initialize the Tracking thread 初始化跟踪线程//(它将存在于执行的主线程中,即调用此构造函数的线程中)cout << "Seq. Name: " << strSequence << endl;mpTracker = new Tracking(this, mpVocabulary, mpFrameDrawer, mpMapDrawer,mpAtlas, mpKeyFrameDatabase, strSettingsFile, mSensor, strSequence);//创建并开启local mapping线程,回调函数为LocalMapping::Run()mpLocalMapper = new LocalMapping(this, mpAtlas, mSensor==MONOCULAR || mSensor==IMU_MONOCULAR, mSensor==IMU_MONOCULAR || mSensor==IMU_STEREO, strSequence);mptLocalMapping = new thread(&ORB_SLAM3::LocalMapping::Run,mpLocalMapper);//设置区分远点和近点的阈值mpLocalMapper->mThFarPoints = fsSettings["thFarPoints"];if(mpLocalMapper->mThFarPoints!=0){cout << "Discard points further than " << mpLocalMapper->mThFarPoints << " m from current camera" << endl;mpLocalMapper->mbFarPoints = true;}elsempLocalMapper->mbFarPoints = false;// 创建并开启闭环线程,回调函数为LoopClosing::Run()mpLoopCloser = new LoopClosing(mpAtlas, mpKeyFrameDatabase, mpVocabulary, mSensor!=MONOCULAR); // mSensor!=MONOCULAR);mptLoopClosing = new thread(&ORB_SLAM3::LoopClosing::Run, mpLoopCloser);//Initialize the Viewer thread and launchif(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;}//Set pointers between threads 设置进程间的指针mpTracker->SetLocalMapper(mpLocalMapper);mpTracker->SetLoopClosing(mpLoopCloser);mpLocalMapper->SetTracker(mpTracker);mpLocalMapper->SetLoopCloser(mpLoopCloser);mpLoopCloser->SetTracker(mpTracker);mpLoopCloser->SetLocalMapper(mpLocalMapper);// Fix verbosity 设置打印信息的等级,只有小于这个等级的信息才会被打印Verbose::SetTh(Verbose::VERBOSITY_QUIET);}
TrackMonocular 单目跟踪
cv::Mat System::TrackMonocular(const cv::Mat &im, const double ×tamp, const vector<IMU::Point>& vImuMeas, string filename)
{ // 单目 和 IMU+单目if(mSensor!=MONOCULAR && mSensor!=IMU_MONOCULAR){cerr << "ERROR: you called TrackMonocular but input sensor was not set to Monocular nor Monocular-Inertial." << endl;exit(-1);}// Check mode change 检查是否有运行模式的改变{unique_lock<mutex> lock(mMutexMode);if(mbActivateLocalizationMode) //激活定位模式?,局部建图线程会关闭{ mpLocalMapper->RequestStop();//请求停止局部建图线程// Wait until Local Mapping has effectively stoppedwhile(!mpLocalMapper->isStopped()){usleep(1000);}// 运行到这里的时候,局部建图部分就真正地停止了// 通知Tracking线程,现在只进行跟踪,不建图mpTracker->InformOnlyTracking(true);// 设置为false,避免重复进行以上操作mbActivateLocalizationMode = false;}if(mbDeactivateLocalizationMode) //取消定位模式?,重新打开局部建图线程{ // 仅跟踪设置为false,localmap =Release,同时避免重复mpTracker->InformOnlyTracking(false);mpLocalMapper->Release(); //局部建图器要开始工作呢mbDeactivateLocalizationMode = false; //防止重复执行}}// Check reset 检查是否有复位的操作{unique_lock<mutex> lock(mMutexReset);if(mbReset) // 是否有复位请求?{ //有,追踪器复位+清除标志+不重置活跃子图mpTracker->Reset();mbReset = false;mbResetActiveMap = false;}else if(mbResetActiveMap) // 重置活跃地图?{cout << "SYSTEM-> Reseting active map in monocular case" << endl;mpTracker->ResetActiveMap();mbResetActiveMap = false;}}// 若imu+单目,则 tracking 中抓imu数据if (mSensor == System::IMU_MONOCULAR)for(size_t i_imu = 0; i_imu < vImuMeas.size(); i_imu++)mpTracker->GrabImuData(vImuMeas[i_imu]);// 开始跟踪,获取相机位姿的估计结果 ****运动估计的函数cv::Mat Tcw = mpTracker->GrabImageMonocular(im,timestamp,filename);unique_lock<mutex> lock2(mMutexState);// 记录track追踪的状态mTrackingState = mpTracker->mState; ///获取当前帧追踪到的地图点向量指针mTrackedMapPoints = mpTracker->mCurrentFrame.mvpMapPoints;// 获取当前帧追踪到的关键帧特征点向量的指针mTrackedKeyPointsUn = mpTracker->mCurrentFrame.mvKeysUn;// //返回获得的相机运动估计return Tcw;
}
TrackStereo
- 双目 或 双目+IMU
TrackRGBD
- RGBD
Shutdown 关闭
void System::Shutdown()
{// 对局部建图线程和回环检测线程发送终止请求mpLocalMapper->RequestFinish();mpLoopCloser->RequestFinish();//如果使用了可视化窗口查看器if(mpViewer){ //向查看器发送终止请求mpViewer->RequestFinish();//等待,直到真正地停止while(!mpViewer->isFinished())usleep(5000);}// Wait until all thread have effectively stopped// 等所有线程工作完成后关闭while(!mpLocalMapper->isFinished() || !mpLoopCloser->isFinished() || mpLoopCloser->isRunningGBA()){if(!mpLocalMapper->isFinished())cout << "mpLocalMapper is not finished" << endl;if(!mpLoopCloser->isFinished())cout << "mpLoopCloser is not finished" << endl;if(mpLoopCloser->isRunningGBA()){cout << "mpLoopCloser is running GBA" << endl;cout << "break anyway..." << endl;break;}usleep(5000);}if(mpViewer) pangolin::BindToContext("ORB-SLAM2: Map Viewer");#ifdef REGISTER_TIMESmpTracker->PrintTimeStats();
#endif
}
SaveTrajectoryTUM 按TUM保存轨迹
- 以TUM格式(tx,ty,tz,qx,qy,qz,qw)保存所有成功定位的帧的位姿
- 关键帧是相对于其参考关键帧(由 BA 和姿势图优化)存储的。 我们需要首先获得关键帧姿势,然后连接相对变换。 未定位的帧(跟踪失败)不会保存。
void System::SaveTrajectoryTUM(const string &filename)
{cout << endl << "Saving camera trajectory to " << filename << " ..." << endl;// 只有在传感器为双目或者RGBD时才可以工作if(mSensor==MONOCULAR){cerr << "ERROR: SaveTrajectoryTUM cannot be used for monocular." << endl;return;}// 获取地图集中所有的关键帧,并给予Id排序vector<KeyFrame*> vpKFs = mpAtlas->GetAllKeyFrames();sort(vpKFs.begin(),vpKFs.end(),KeyFrame::lId);// 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();ofstream f;f.open(filename.c_str());// 用一般的方式输出浮点型,例如C++程序在控制台显示大一点的数,显示的时候使用了科学计数法,使用该命令即可像一般的方式显示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).// 对于每一帧,我们都有一个参考关键帧 (lRit)、时间戳 (lT) 和跟踪失败时为真的标志 (lbL)。list<ORB_SLAM3::KeyFrame*>::iterator lRit = mpTracker->mlpReferences.begin(); // 关键帧listlist<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;
}
其他数据的保存
- 以TUM格式保存所有关键帧的位姿
void SaveKeyFrameTrajectoryTUM(const string &filename); - 以EuRoC格式保存所有帧的位姿
void SaveTrajectoryEuRoC(const string &filename); - 以EuRoC格式保存所有关键帧的位姿
void SaveKeyFrameTrajectoryEuRoC(const string &filename); - 以KITTI格式保存所有帧的位姿
void SaveTrajectoryKITTI(const string &filename);
Tracking
核心函数
构造
namespace ORB_SLAM3
{Tracking::Tracking(
System *pSys // systerm 中 this指针
, ORBVocabulary* pVoc // BOW词袋,包括了已加载的词袋
, FrameDrawer *pFrameDrawer //帧绘制器
, MapDrawer *pMapDrawer // 地图点绘制器
, Atlas *pAtlas // 地图集句柄
, KeyFrameDatabase* pKFDB // 关键帧产生的词袋数据库
, const string &strSettingPath // 配置文件路径
, const int sensor // 传感器类型
, const string &_nameSeq):mState(NO_IMAGES_YET) // 当前状态还没准备好, mSensor(sensor), mTrackedFr(0), mbStep(false),mbOnlyTracking(false) // 处于slam模式, mbMapUpdated(false), mbVO(false), //当处于纯跟踪模式的时候,这个变量表示了当前跟踪状态的好坏mpORBVocabulary(pVoc), mpKeyFrameDB(pKFDB),mpInitializer(static_cast<Initializer*>(NULL)), //暂时给地图初始化器设置为空指针mpSystem(pSys), mpViewer(NULL),mpFrameDrawer(pFrameDrawer), mpMapDrawer(pMapDrawer), mpAtlas(pAtlas), mnLastRelocFrameId(0), time_recently_lost(5.0), time_recently_lost_visual(2.0),mnInitialFrameId(0), mbCreatedMap(false), mnFirstFrameId(0), mpCamera2(nullptr)
{// Load camera parameters from settings filecv::FileStorage fSettings(strSettingPath, cv::FileStorage::READ);///< step 1: 从配置文件中加载相机参数// Load ORB parameters 加载相机参数文件// 相机参数,两类:// 1. PinHole: fx,fy,cx,cy,k1,k2,p1,p2 --k3// 2. KannalaBrandt8:fx,fy,cx,cy,k1,k2,k3,k4 其他就不支持了// STEREO or IMU_STEREO: br// fps , fps==0 ? 30 : fps// RGB ? RGB : BGR// STEREO || RGBD || IMU_STEREO ? ThDepth (深度阈值(近/远点))// RGBD ? DepthMapFactor// 若上述都存在且真实,则 return true 否则 falsebool b_parse_cam = ParseCamParamFile(fSettings);if(!b_parse_cam) // 读取失败则打印error{std::cout << "*Error with the camera parameters in the config file*" << std::endl;}///< step 2: 加载ORB特征点有关的参数,并构造ORB提取对象// nFeatures: 1000 每一帧提取的特征点数 1000// scaleFactor: 1.2 图像建立金字塔时的变化尺度 1.2// nLevels: 8 尺度金字塔的层数 8// iniThFAST: 20 提取fast特征点的默认阈值 20// minThFAST: 7 如果默认阈值提取不出足够fast特征点,则使用最小阈值 7/// 构造orb提取对象:构造左目提取器,若双目或imu_双目时?再构造右目提取器,若单目或imu_单目时?构造mpIniORBextractor/// 特征数:mpIni=5*(mpLeft=mpRight)bool b_parse_orb = ParseORBParamFile(fSettings);if(!b_parse_orb){std::cout << "*Error with the ORB parameters in the config file*" << std::endl;}initID = 0; lastID = 0;// Load IMU parameters bool b_parse_imu = true; // 加载 imu 参数if(sensor==System::IMU_MONOCULAR || sensor==System::IMU_STEREO) // 只使用imu时加载{ // 外参 Tcw 4*4 ///< **IMU noise (Use those from VINS-mono)**// IMU.NoiseGyro: 0.00016 # rad/s^0.5 // IMU.NoiseAcc: 0.0028 # m/s^1.5// IMU.GyroWalk: 0.000022 # rad/s^1.5// IMU.AccWalk: 0.00086 # m/s^2.5// IMU.Frequency: 200b_parse_imu = ParseIMUParamFile(fSettings);if(!b_parse_imu){std::cout << "*Error with the IMU parameters in the config file*" << std::endl;}mnFramesToResetIMU = mMaxFrames;}mbInitWith3KFs = false;mnNumDataset = 0;if(!b_parse_cam || !b_parse_orb || !b_parse_imu){std::cerr << "**ERROR in the config file, the format is not correct**" << std::endl;try{throw -1;}catch(exception &e){}}#ifdef REGISTER_TIMESvdRectStereo_ms.clear();vdORBExtract_ms.clear();vdStereoMatch_ms.clear();vdIMUInteg_ms.clear();vdPosePred_ms.clear();vdLMTrack_ms.clear();vdNewKF_ms.clear();vdTrackTotal_ms.clear();vdUpdatedLM_ms.clear();vdSearchLP_ms.clear();vdPoseOpt_ms.clear();
#endifvnKeyFramesLM.clear();vnMapPointsLM.clear();
}
读取参数文件时,是否存在以及是否real案例:
// Camera calibration parameterscv::FileNode node = fSettings["Camera.fx"];if(!node.empty() && node.isReal()){fx = node.real();}else{std::cerr << "*Camera.fx parameter doesn't exist or is not a real number*" << std::endl;b_miss_params = true;}
GrabImageMonocular
- 输入:左目RGB或RGBA图像
1、 将图像转为mImGray并初始化mCurrentFrame
2、进行tracking过程 - 输出:世界坐标系到该帧相机坐标系的变换矩阵
cv::Mat Tracking::GrabImageMonocular(const cv::Mat &im, const double ×tamp, string filename)
{mImGray = im;// step 1 :将RGB或RGBA图像转为灰度图像if(mImGray.channels()==3){if(mbRGB)cvtColor(mImGray,mImGray,cv::COLOR_RGB2GRAY);elsecvtColor(mImGray,mImGray,cv::COLOR_BGR2GRAY);}else if(mImGray.channels()==4){if(mbRGB)cvtColor(mImGray,mImGray,cv::COLOR_RGBA2GRAY);elsecvtColor(mImGray,mImGray,cv::COLOR_BGRA2GRAY);}// step 2 :构造Frame if (mSensor == System::MONOCULAR) // 若是单目{ // 《状态没有初始化or没有图片or lastid与初始id间隔太小》时?// 初始特征提取;否则orb特征提取,提取特征值的数量不一样if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET ||(lastID - initID) < mMaxFrames)mCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mpCamera,mDistCoef,mbf,mThDepth);else mCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mpCamera,mDistCoef,mbf,mThDepth);}else if(mSensor == System::IMU_MONOCULAR) // 单目Imu{// 《状态没有初始化or没有图片》时,初始特征提取;否则orb特征提取if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET){mCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mpCamera,mDistCoef,mbf,mThDepth,&mLastFrame,*mpImuCalib);}elsemCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mpCamera,mDistCoef,mbf,mThDepth,&mLastFrame,*mpImuCalib);}if (mState==NO_IMAGES_YET)t0=timestamp;mCurrentFrame.mNameFile = filename;mCurrentFrame.mnDataset = mnNumDataset;#ifdef REGISTER_TIMESvdORBExtract_ms.push_back(mCurrentFrame.mTimeORB_Ext);
#endiflastID = mCurrentFrame.mnId;// step 3 :跟踪Track();// 返回当前帧的位姿return mCurrentFrame.mTcw.clone();
}
Track 跟踪
- 若 localmap中imu数据有问题,则系统:重置活跃地图ResetActiveMap
- 地图集中 取出 当前地图
- mState!=NO_IMAGES_YET:
- 当前帧时间回跳时,清空imu数据,并创建地图CreateMapInAtlas,return
- 当前帧时间与上一帧相差了1s以上时 ,若地图集未使用imu则return,否则:
- imu未初始化成功,则 重置活跃子图ResetActiveMap
- 若imu初始化成功时:且GetIniertialBA2成功时在地图集中创建地图CreateMapInAtlas,否则重置活跃子图ResetActiveMap
- 若使用imu且上一帧是关键帧时,当前帧设置新的bias
- 若 mState == NO_IMAGES_YET,则:mState = NOT_INITIALIZED
- 若使用imu且非创建地图时,imu进行预积分 (两套)
- 上一状态和创建地图 标记赋值
- mLastProcessedState=mState;
- mbCreatedMap = false;
- 若 mState == NOT_INITIALIZED
- 基于传感器参数进行相应初始化,如MonocularInitialization
- 绘制地图更新 mpFrameDrawer->Update(this)
- 若未正常初始化时, mLastFrame = currentFrame ,return
- 若地图集 == 1时,mnFirstFrameId = mCurrentFrame.mnId;
- 否则 mState 已经初始化:
- 若不是仅跟踪模式:
- 若 mState == ok时:
- 检查上一帧中被替换的地图点,将地图点替换成新的地图点CheckReplacedInLastFrame
- 状态跟踪
- 若运动模式是空且imu未初始化 或刚刚完成初始化时,跟踪参考关键帧TrackReferenceKeyFrame
- 否则,跟踪运动模式TrackWithMotionModel,若运动模式跟踪失败,则跟踪 参考帧
- 若跟踪失败时,
- 若刚刚进行了重定位且imu模式,则直接将mState置为lost
- 否则,如果地图中关键帧数量大于10,则 RECENTLY_LOST
- 否则 mState = LOST;
- 若状态不是ok时:
- 如果是最近丢失时 :mState == RECENTLY_LOST
- bOK = true;
- 如果使用imu时,
- 若:imu初始化成功,则用imu跟踪 PredictStateIMU
- 否则初始化失败,bOK = false;
3. 若与前一次跟踪相隔5s,记为lost- mState = LOST;,OK=false;
- 纯视觉模式下进行重定位,重定位失败记为lost
- mState = LOST;,OK=false;
2. 若状态为 lost: mState == LOST - 若地图关键帧数量小于10个时,则重置活跃地图
- 否则新建地图,删除mLastKeyFrame,从头开始
- mState = LOST;,OK=false;
- 如果是最近丢失时 :mState == RECENTLY_LOST
- 若 mState == ok时:
- 若是仅跟踪模式:
- 若定位状态为lost,定位丢失则重定位 Relocalization
- 否则 若跟踪正常时:
- 恒速模式时使用速度,bOK = TrackWithMotionModel
- 否则使用参考帧, bOK = TrackReferenceKeyFrame
- 状态不正常时
- 恒速模式非空时用恒速模型,bOKMM = TrackWithMotionModel
- 重定位 Relocalization
- 恒速模型 且 重定位失败时,使用了恒速预测的数据
- mCurrentFrame 设置为 恒速模型的数据
- 若仍失败,则当前帧地图点 IncreaseFound
- 否则 若重定位成功时:设置状态正常 mbVO = false;
- 跟踪成功:bOKMM || 重定位成功
3. 若当前帧是关键帧,则更新关键帧
4. 若不是仅跟踪模式: // slam模式
1. 若跟踪成功时,则跟综局部地图(寻找更多的匹配,优化当前帧的pose) TrackLocalMap
2. 若跟踪失败,则打印
5. 若定位模式时:
1. 跟踪成功 且 状态ok(!mbVO):跟踪localmap(寻找更多的匹配,优化当前帧的pose)TrackLocalMap
2. mbVO true 表示地图中与 MapPoints 的匹配项很少。 我们无法检索本地地图,因此我们不执行 TrackLocalMap()。 一旦系统重新定位相机,我们将再次使用本地地图。
6. 跟踪成功时:bOK //包括了跟踪局部地图的结果
1. mstate =ok。
7. 跟踪失败时:bOKfalse
1. 若前面位姿跟踪成功时:mState == OK
1. 使用imu,且 当前地图未初始化完imu 或 imu做ba2失败时,系统重置活跃子图ResetActiveMap, mState = RECENTLY_LOST;
2. 不使用imu时: mState = RECENTLY_LOST;
3. 更新丢失时间 mTimeStampLost
8. 如果最近重新定位,且当前帧id>上次重置imu,且使用imu,且地图已经初始化imu
1. 当前帧构建frame,并进行预积分 Preintegrated
9. 若pCurrentMap已初始化imu成功时:
1. 若跟踪成功,且未重置imu,则 更新mLastBias
显示设置当前帧位姿
10. 如果跟踪成功bOK或者 mStateRECENTLY_LOST, - 更新恒速模型,计算上一帧到当前帧的位姿变换
- 若使用imu,则更新当前相机pose SetCurrentCameraPose
- 清除恒速跟踪中的临时地图点
- 清除观测不到的地图点
- 判断是否需要插入关键帧,插入关键帧(对双目或rgb-d会产生新的地图点)
11. 若 状态失败 mState==LOST)时: - 若:关键帧小于5个,或使用imu且imu未初始化时:系统重置活跃子图
- 否则 创建新地图
12. 若当前帧不是关键帧,则赋值临时帧。赋值mLastFrame
- 若不是仅跟踪模式:
- 若状态ok 或 状态是 最近丢失时:
- 存储帧姿态信息以在之后检索完整的相机轨迹。
void Tracking::Track()
{if (bStepByStep){while(!mbStep)usleep(500);mbStep = false;}// 若 localmap中imu数据有问题,则系统重置活跃地图if(mpLocalMapper->mbBadImu){cout << "TRACK: Reset map because local mapper set the bad imu flag " << endl;mpSystem->ResetActiveMap();return;}// 地图集中 取出 当前地图Map* pCurrentMap = mpAtlas->GetCurrentMap();// 如果图像复位过、或者第一次运行,则为NO_IMAGE_YET状态if(mState!=NO_IMAGES_YET) // 有图片时{// 当前帧时间回跳时,清空imu数据,并创建地图if(mLastFrame.mTimeStamp>mCurrentFrame.mTimeStamp){cerr << "ERROR: Frame with a timestamp older than previous frame detected!" << endl;unique_lock<mutex> lock(mMutexImuQueue);mlQueueImuData.clear();CreateMapInAtlas();return;}else // 当前帧与上一帧相差了1s以上时,若未使用imu,直接return,若使用imu,则创建地图 or 重置地图if(mCurrentFrame.mTimeStamp>mLastFrame.mTimeStamp+1.0){cout << "id last: " << mLastFrame.mnId << " id curr: " << mCurrentFrame.mnId << endl;if(mpAtlas->isInertial()) // 使用了IMU{if(mpAtlas->isImuInitialized()) // imu已经初始化{cout << "Timestamp jump detected. State set to LOST. Reseting IMU integration..." << endl;// 1. 若imu初始化成功时:若得到imu ba 错误时重置活跃子图,否则创建地图在地图集中if(!pCurrentMap->GetIniertialBA2()){mpSystem->ResetActiveMap();}else{CreateMapInAtlas();}}else{cout << "Timestamp jump detected, before IMU initialization. Reseting..." << endl;// 重置活跃子图mpSystem->ResetActiveMap();}}return;}}// 若使用imu且上一帧是关键帧时,当前帧设置新的biasif ((mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO) && mpLastKeyFrame)mCurrentFrame.SetNewBias(mpLastKeyFrame->GetImuBias());if(mState==NO_IMAGES_YET){mState = NOT_INITIALIZED;}mLastProcessedState=mState;if ((mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO) && !mbCreatedMap){#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartPreIMU = std::chrono::steady_clock::now();
#endifPreintegrateIMU();
#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_EndPreIMU = std::chrono::steady_clock::now();double timePreImu = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndPreIMU - time_StartPreIMU).count();vdIMUInteg_ms.push_back(timePreImu);
#endif}mbCreatedMap = false;// Get Map Mutex -> Map cannot be changedunique_lock<mutex> lock(pCurrentMap->mMutexMapUpdate);mbMapUpdated = false;int nCurMapChangeIndex = pCurrentMap->GetMapChangeIndex();int nMapChangeIndex = pCurrentMap->GetLastMapChange();if(nCurMapChangeIndex>nMapChangeIndex){// cout << "Map update detected" << endl;pCurrentMap->SetLastMapChange(nCurMapChangeIndex);mbMapUpdated = true;}if(mState==NOT_INITIALIZED){// 如果没有初始化,则先进行初始化if(mSensor==System::STEREO || mSensor==System::RGBD || mSensor==System::IMU_STEREO)StereoInitialization();else{MonocularInitialization();}// 地图更新此mpFrameDrawer->Update(this);// 如果正确初始化,mState=OKif(mState!=OK) // If rightly initialized, mState=OK{ // 若状态不是ok,则当前帧赋值last,returnmLastFrame = Frame(mCurrentFrame);return;}if(mpAtlas->GetAllMaps().size() == 1){mnFirstFrameId = mCurrentFrame.mnId;}}else{// System is initialized. Track Frame.bool bOK;#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartPosePred = std::chrono::steady_clock::now();
#endif// Initial camera pose estimation using motion model or relocalization (if tracking is lost)// 使用运动模型或重新定位的初始相机姿态估计(如果跟踪丢失)if(!mbOnlyTracking) // 不仅仅是跟踪模式{ // State OK// Local Mapping is activated. This is the normal behaviour, unless// you explicitly activate the "only tracking" mode.// 局部地图工作,这是正常行为,除非您明确激活“仅跟踪”模式。if(mState==OK){// Local Mapping might have changed some MapPoints tracked in last frame// 检查上一帧中被替换的地图点,将地图点替换成新的地图点CheckReplacedInLastFrame();// 若运动模式是空且imu未初始化 或刚刚完成初始化时 跟踪参考关键帧if((mVelocity.empty() && !pCurrentMap->isImuInitialized()) || mCurrentFrame.mnId<mnLastRelocFrameId+2){//Verbose::PrintMess("TRACK: Track with respect to the reference KF ", Verbose::VERBOSITY_DEBUG);// 跟踪参考关键帧bOK = TrackReferenceKeyFrame();}else{//Verbose::PrintMess("TRACK: Track with motion model", Verbose::VERBOSITY_DEBUG);// 跟踪运动模式bOK = TrackWithMotionModel();if(!bOK)bOK = TrackReferenceKeyFrame();}if (!bOK) //如果跟踪失败了{// 如果刚刚进行了重定位且imu模式,则直接将mState置为lostif ( mCurrentFrame.mnId<=(mnLastRelocFrameId+mnFramesToResetIMU) &&(mSensor==System::IMU_MONOCULAR || mSensor==System::IMU_STEREO)){mState = LOST;} // 否则,如果地图中关键帧数量大于10,则 RECENTLY_LOSTelse if(pCurrentMap->KeyFramesInMap()>10){cout << "KF in map: " << pCurrentMap->KeyFramesInMap() << endl;mState = RECENTLY_LOST;mTimeStampLost = mCurrentFrame.mTimeStamp;//mCurrentFrame.SetPose(mLastFrame.mTcw);}else{mState = LOST;}}}else // 状态不ok时{// 如果是最近丢失时if (mState == RECENTLY_LOST){Verbose::PrintMess("Lost for a short time", Verbose::VERBOSITY_NORMAL);bOK = true;// 如果是imu模式,则用imu跟踪,最多跟踪5s,否则记为lostif((mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO)){if(pCurrentMap->isImuInitialized())PredictStateIMU();elsebOK = false;if (mCurrentFrame.mTimeStamp-mTimeStampLost>time_recently_lost){mState = LOST;Verbose::PrintMess("Track Lost...", Verbose::VERBOSITY_NORMAL);bOK=false;}}else { // 纯视觉模式下进行重定位,重定位失败记为lost// TODO fix relocalizationbOK = Relocalization();if(!bOK && mCurrentFrame.mTimeStamp-mTimeStampLost>time_recently_lost_visual){mState = LOST;Verbose::PrintMess("Track Lost...", Verbose::VERBOSITY_NORMAL);bOK=false;}}}else if (mState == LOST) // 状态处于lost时{Verbose::PrintMess("A new map is started...", Verbose::VERBOSITY_NORMAL);// 若地图关键帧数量小于10个时,则重置活跃地图if (pCurrentMap->KeyFramesInMap()<10){mpSystem->ResetActiveMap();cout << "Reseting current map..." << endl;}else // 否则新建地图,删除mLastKeyFrame,从头开始CreateMapInAtlas();if(mpLastKeyFrame)mpLastKeyFrame = static_cast<KeyFrame*>(NULL);Verbose::PrintMess("done", Verbose::VERBOSITY_NORMAL);return;}}}else // 若处于定位模式时(局部地图不工作){// Localization Mode: Local Mapping is deactivated (TODO Not available in inertial mode)if(mState==LOST) //若定位状态为lost,定位丢失则重定位{if(mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO)Verbose::PrintMess("IMU. State LOST", Verbose::VERBOSITY_NORMAL);bOK = Relocalization();}else{if(!mbVO) // 跟踪正常时{// In last frame we tracked enough MapPoints in the map// 恒速模式时使用速度,否则使用参考帧if(!mVelocity.empty()){ bOK = TrackWithMotionModel();}else{bOK = TrackReferenceKeyFrame();}}else // 状态不正常时{ // In last frame we tracked mainly "visual odometry" points.// We compute two camera poses, one from motion model and one doing relocalization.// If relocalization is sucessfull we choose that solution, otherwise we retain// the "visual odometry" solution.//在最后一帧中,我们主要跟踪“视觉里程计”点。 我们计算两个相机姿势,一个来自运动模型,另一个进行重新定位。 如果重新定位成功,我们选择该解决方案,否则我们保留“视觉里程计”解决方案。bool bOKMM = false;bool bOKReloc = false;vector<MapPoint*> vpMPsMM;vector<bool> vbOutMM;cv::Mat TcwMM;// 恒速模式非空用恒速模型if(!mVelocity.empty()){bOKMM = TrackWithMotionModel();vpMPsMM = mCurrentFrame.mvpMapPoints;vbOutMM = mCurrentFrame.mvbOutlier;TcwMM = mCurrentFrame.mTcw.clone();}// 重定位bOKReloc = Relocalization();if(bOKMM && !bOKReloc){mCurrentFrame.SetPose(TcwMM);mCurrentFrame.mvpMapPoints = vpMPsMM;mCurrentFrame.mvbOutlier = vbOutMM;if(mbVO){for(int i =0; i<mCurrentFrame.N; i++){if(mCurrentFrame.mvpMapPoints[i] && !mCurrentFrame.mvbOutlier[i]){mCurrentFrame.mvpMapPoints[i]->IncreaseFound();}}}}else if(bOKReloc){mbVO = false;}bOK = bOKReloc || bOKMM;}}}// 若当前帧是关键帧,则更新关键帧if(!mCurrentFrame.mpReferenceKF)mCurrentFrame.mpReferenceKF = mpReferenceKF;#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_EndPosePred = std::chrono::steady_clock::now();double timePosePred = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndPosePred - time_StartPosePred).count();vdPosePred_ms.push_back(timePosePred);
#endif#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartLMTrack = std::chrono::steady_clock::now();
#endif// If we have an initial estimation of the camera pose and matching. Track the local map.if(!mbOnlyTracking) // 若不是仅跟踪模式:{ // 若跟踪成功时,则跟综局部地图,寻找更多的匹配,优化当前帧的poseif(bOK) {bOK = TrackLocalMap();}if(!bOK)cout << "Fail to track local map!" << endl;}else{// mbVO true means that there are few matches to MapPoints in the map. We cannot retrieve// a local map and therefore we do not perform TrackLocalMap(). Once the system relocalizes// the camera we will use the local map again.// mbVO true 表示地图中与 MapPoints 的匹配项很少。 我们无法检索本地地图,因此我们不执行 TrackLocalMap()。 一旦系统重新定位相机,我们将再次使用本地地图。if(bOK && !mbVO) //跟踪成功 且 状态ok(!mbVO):跟踪localmap(寻找更多的匹配,优化当前帧的pose)bOK = TrackLocalMap();}// 局部地图跟踪成功,mstate =ok。局部地图失败但前面位姿跟踪成功,则imu模式置为recent_lost,纯视觉置为lostif(bOK)mState = OK;else if (mState == OK){if (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO){Verbose::PrintMess("Track lost for less than one second...", Verbose::VERBOSITY_NORMAL);if(!pCurrentMap->isImuInitialized() || !pCurrentMap->GetIniertialBA2()){cout << "IMU is not or recently initialized. Reseting active map..." << endl;mpSystem->ResetActiveMap();}mState = RECENTLY_LOST;}elsemState = RECENTLY_LOST; // visual to lostif(mCurrentFrame.mnId>mnLastRelocFrameId+mMaxFrames){mTimeStampLost = mCurrentFrame.mTimeStamp;}}// Save frame if recent relocalization, since they are used for IMU reset (as we are making copy, it shluld be once mCurrFrame is completely modified)
// 如果最近重新定位,且帧id>上次重置imu,且使用imu,且地图已经初始化imu
if((mCurrentFrame.mnId<(mnLastRelocFrameId+mnFramesToResetIMU)) && (mCurrentFrame.mnId > mnFramesToResetIMU) && ((mSensor == System::IMU_MONOCULAR) || (mSensor == System::IMU_STEREO)) && pCurrentMap->isImuInitialized()){// TODO check this situationVerbose::PrintMess("Saving pointer to frame. imu needs reset...", Verbose::VERBOSITY_NORMAL);Frame* pF = new Frame(mCurrentFrame);pF->mpPrevFrame = new Frame(mLastFrame);// Load preintegrationpF->mpImuPreintegratedFrame = new IMU::Preintegrated(mCurrentFrame.mpImuPreintegratedFrame);}if(pCurrentMap->isImuInitialized()){if(bOK){if(mCurrentFrame.mnId==(mnLastRelocFrameId+mnFramesToResetIMU)){cout << "RESETING FRAME!!!" << endl;}else if(mCurrentFrame.mnId>(mnLastRelocFrameId+30))mLastBias = mCurrentFrame.mImuBias;}}#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_EndLMTrack = std::chrono::steady_clock::now();double timeLMTrack = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndLMTrack - time_StartLMTrack).count();vdLMTrack_ms.push_back(timeLMTrack);
#endif// Update drawermpFrameDrawer->Update(this);if(!mCurrentFrame.mTcw.empty())mpMapDrawer->SetCurrentCameraPose(mCurrentFrame.mTcw);// 如果跟踪成功或者recent_lost,更新恒速模型,计算上一帧到当前帧的位姿变换if(bOK || mState==RECENTLY_LOST){// Update motion modelif(!mLastFrame.mTcw.empty() && !mCurrentFrame.mTcw.empty()){cv::Mat LastTwc = cv::Mat::eye(4,4,CV_32F);mLastFrame.GetRotationInverse().copyTo(LastTwc.rowRange(0,3).colRange(0,3));mLastFrame.GetCameraCenter().copyTo(LastTwc.rowRange(0,3).col(3));mVelocity = mCurrentFrame.mTcw*LastTwc;}elsemVelocity = cv::Mat();if(mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO)mpMapDrawer->SetCurrentCameraPose(mCurrentFrame.mTcw);// Clean VO matches 清除恒速跟踪中的临时地图点for(int i=0; i<mCurrentFrame.N; i++){MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];if(pMP)if(pMP->Observations()<1){mCurrentFrame.mvbOutlier[i] = false;mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);}}// Delete temporal MapPoints 清除观测不到的地图点for(list<MapPoint*>::iterator lit = mlpTemporalPoints.begin(), lend = mlpTemporalPoints.end(); lit!=lend; lit++){MapPoint* pMP = *lit;delete pMP;}mlpTemporalPoints.clear();#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartNewKF = std::chrono::steady_clock::now();
#endif//判断是否需要插入关键帧bool bNeedKF = NeedNewKeyFrame();// Check if we need to insert a new keyframe 插入关键帧(对双目或rgb-d会产生新的地图点)if(bNeedKF && (bOK|| (mState==RECENTLY_LOST && (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO))))CreateNewKeyFrame();#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_EndNewKF = std::chrono::steady_clock::now();double timeNewKF = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndNewKF - time_StartNewKF).count();vdNewKF_ms.push_back(timeNewKF);
#endif// We allow points with high innovation (considererd outliers by the Huber Function)// pass to the new keyframe, so that bundle adjustment will finally decide// if they are outliers or not. We don't want next frame to estimate its position// with those points so we discard them in the frame. Only has effect if lastframe is trackedfor(int i=0; i<mCurrentFrame.N;i++){if(mCurrentFrame.mvpMapPoints[i] && mCurrentFrame.mvbOutlier[i])mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);}}// Reset if the camera get lost soon after initialization//若:关键帧小于5个,或使用imu且imu未初始化时:系统重置活跃子图//否则 创建新地图if(mState==LOST) {if(pCurrentMap->KeyFramesInMap()<=5){mpSystem->ResetActiveMap();return;}if ((mSensor == System::IMU_MONOCULAR) || (mSensor == System::IMU_STEREO))if (!pCurrentMap->isImuInitialized()){Verbose::PrintMess("Track lost before IMU initialisation, reseting...", Verbose::VERBOSITY_QUIET);mpSystem->ResetActiveMap();return;}CreateMapInAtlas();}if(!mCurrentFrame.mpReferenceKF)mCurrentFrame.mpReferenceKF = mpReferenceKF;mLastFrame = Frame(mCurrentFrame);}// 保存当前关键帧相对参考帧的位姿,参考kf、当前帧时间戳,当前帧状态if(mState==OK || mState==RECENTLY_LOST){// Store frame pose information to retrieve the complete camera trajectory afterwards.if(!mCurrentFrame.mTcw.empty()){cv::Mat Tcr = mCurrentFrame.mTcw*mCurrentFrame.mpReferenceKF->GetPoseInverse();mlRelativeFramePoses.push_back(Tcr);mlpReferences.push_back(mCurrentFrame.mpReferenceKF);mlFrameTimes.push_back(mCurrentFrame.mTimeStamp);mlbLost.push_back(mState==LOST);}else{// This can happen if tracking is lostmlRelativeFramePoses.push_back(mlRelativeFramePoses.back());mlpReferences.push_back(mlpReferences.back());mlFrameTimes.push_back(mlFrameTimes.back());mlbLost.push_back(mState==LOST);}}
}
相关函数-地图集
CreateMapInAtlas
- 在地图集中新建地图,并更新所有状态。
void Tracking::CreateMapInAtlas()
{// 当前帧id赋值给 lastInitFrameIdmnLastInitFrameId = mCurrentFrame.mnId;// 地图集中创建新地图mpAtlas->CreateNewMap();// 如果使用imu时,if (mSensor==System::IMU_STEREO || mSensor == System::IMU_MONOCULAR)mpAtlas->SetInertialSensor();// 告诉地图集,使用imumbSetInit=false;// 重置初始帧frameid和状态mnInitialFrameId = mCurrentFrame.mnId+1;mState = NO_IMAGES_YET;// Restart the variable with information about the last KFmVelocity = cv::Mat();// 最后重定位KF_id是当前id,因为它是新地图的新起点mnLastRelocFrameId = mnLastInitFrameId; // The last relocation KF_id is the current id, because it is the new starting point for new mapVerbose::PrintMess("First frame id in map: " + to_string(mnLastInitFrameId+1), Verbose::VERBOSITY_NORMAL);mbVO = false; // Init value for know if there are enough MapPoints in the last KFif(mSensor == System::MONOCULAR || mSensor == System::IMU_MONOCULAR){if(mpInitializer)delete mpInitializer;mpInitializer = static_cast<Initializer*>(NULL);}if((mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO ) && mpImuPreintegratedFromLastKF){delete mpImuPreintegratedFromLastKF;mpImuPreintegratedFromLastKF = new IMU::Preintegrated(IMU::Bias(),*mpImuCalib);}if(mpLastKeyFrame)mpLastKeyFrame = static_cast<KeyFrame*>(NULL);if(mpReferenceKF)mpReferenceKF = static_cast<KeyFrame*>(NULL);mLastFrame = Frame();mCurrentFrame = Frame();mvIniMatches.clear();mbCreatedMap = true;}
ResetActiveMap
- 把当前地图给仍了
void Tracking::ResetActiveMap(bool bLocMap)
{Verbose::PrintMess("Active map Reseting", Verbose::VERBOSITY_NORMAL);// 若使用显示,则 显示类停止等待直到停止。if(mpViewer){mpViewer->RequestStop();while(!mpViewer->isStopped())usleep(3000);}// 取到当前地图Map* pMap = mpAtlas->GetCurrentMap();if (!bLocMap) // 重置localmap标记{ Verbose::PrintMess("Reseting Local Mapper...", Verbose::VERBOSITY_NORMAL);// 请求重置活跃地图mpLocalMapper->RequestResetActiveMap(pMap);Verbose::PrintMess("done", Verbose::VERBOSITY_NORMAL);}// Reset Loop ClosingVerbose::PrintMess("Reseting Loop Closing...", Verbose::VERBOSITY_NORMAL);// 重置闭环mpLoopClosing->RequestResetActiveMap(pMap);Verbose::PrintMess("done", Verbose::VERBOSITY_NORMAL);// Clear BoW DatabaseVerbose::PrintMess("Reseting Database", Verbose::VERBOSITY_NORMAL);// 清除 词袋数据mpKeyFrameDB->clearMap(pMap); // Only clear the active map referencesVerbose::PrintMess("done", Verbose::VERBOSITY_NORMAL);// Clear Map (this erase MapPoints and KeyFrames)// 清除地图点和关键帧mpAtlas->clearMap();mnLastInitFrameId = Frame::nNextId;mnLastRelocFrameId = mnLastInitFrameId;mState = NO_IMAGES_YET;if(mpInitializer){delete mpInitializer;mpInitializer = static_cast<Initializer*>(NULL);}list<bool> lbLost;unsigned int index = mnFirstFrameId;cout << "mnFirstFrameId = " << mnFirstFrameId << endl;for(Map* pMap : mpAtlas->GetAllMaps()){if(pMap->GetAllKeyFrames().size() > 0){if(index > pMap->GetLowerKFID())index = pMap->GetLowerKFID();}}int num_lost = 0;cout << "mnInitialFrameId = " << mnInitialFrameId << endl;for(list<bool>::iterator ilbL = mlbLost.begin(); ilbL != mlbLost.end(); ilbL++){if(index < mnInitialFrameId)lbLost.push_back(*ilbL);else{lbLost.push_back(true);num_lost += 1;}index++;}cout << num_lost << " Frames set to lost" << endl;mlbLost = lbLost;mnInitialFrameId = mCurrentFrame.mnId;mnLastRelocFrameId = mCurrentFrame.mnId;mCurrentFrame = Frame();mLastFrame = Frame();mpReferenceKF = static_cast<KeyFrame*>(NULL);mpLastKeyFrame = static_cast<KeyFrame*>(NULL);mvIniMatches.clear();if(mpViewer)mpViewer->Release();Verbose::PrintMess(" End reseting! ", Verbose::VERBOSITY_NORMAL);
}
相关函数-参数读取
ParseCamParamFile 读取相机文件
采用cv::FileStorage读取,且增加了有效判断
// Camera calibration parameterscv::FileNode node = fSettings["Camera.fx"];if(!node.empty() && node.isReal()){fx = node.real();}else{std::cerr << "*Camera.fx parameter doesn't exist \or is not a real number*" << std::endl;b_miss_params = true;}
主要参数有:
- Camera.type :PinHole + KannalaBrandt8
- PinHole:fx, fy, cx, cy, k1, k2, p1, p2, k3
- KannalaBrandt8: fx, fy, cx, cy, k1, k2, k3, k4
- STEREO or IMU_STEREO ?: bf
- fps (30)
- RGB?: 0: BGR, 1: RGB.
- STEREO or RGBD or IMU_STEREO ?: ThDepth
- mThDepth = mbf*mThDepth/fx 深度阈值
- RGBD ? DepthMapFactor
- Camera.type :PinHole + KannalaBrandt8
ParseORBParamFile 读取orb参数
ORBextractor.nFeatures: 1500 # 每张图像的特征数
ORBextractor.scaleFactor:1.2 # 比例金字塔中各级别之间的比例因子
ORBextractor.nLevels: 8 # 比例金字塔中的级别数
# ORB 提取器:快速阈值,如果您的图像对比度低,您可以降低这些值
# 图像被分成一个网格。 在每个单元格处提取 FAST,施加最小响应。
# 首先我们强加 iniThFAST。 如果没有检测到角点,我们会施加一个较低的值 minThFAST
ORBextractor.iniThFAST: 20
ORBextractor.minThFAST: 7
ParseIMUParamFile 读取imu参数
# 从 body-frame (imu) 到相机的转换
Tbc: !!opencv-matrixrows: 4cols: 4dt: fdata: [-0.9995250378696743, 0.0075019185074052044, -0.02989013031643309, 0.045574835649698026, 0.029615343885863205, -0.03439736061393144, -0.998969345370175, -0.071161801837997044,-0.008522328211654736, -0.9993800792498829, 0.03415885127385616, -0.044681254117144367,0.0, 0.0, 0.0, 1.0]
# IMU noise (Use those from VINS-mono)
IMU.NoiseGyro: 0.00016 # rad/s^0.5
IMU.NoiseAcc: 0.0028 # m/s^1.5
IMU.GyroWalk: 0.000022 # rad/s^1.5
IMU.AccWalk: 0.00086 # m/s^2.5
IMU.Frequency: 200
相关函数-Imu相关
PreintegrateIMU IMU预积分
步骤:
- 当前帧没有前一个frame时,return
- 有Frame了,先clear 容器
- imuDeque为空时,return
- while 循环,取出队列中 last_frame-> frame时间段内的数据
- 构建 IMU::Preintegrated对象
- 总体预积分+帧间预积分
void Tracking::PreintegrateIMU()
{//cout << "start preintegration" << endl;/// 当前帧没有前一个frame时,returnif(!mCurrentFrame.mpPrevFrame){Verbose::PrintMess("non prev frame ", Verbose::VERBOSITY_NORMAL);mCurrentFrame.setIntegrated();return;}/// 上次计算数据清空(联想trick:多线程操作可以在启动时清空,防止未计算完clear造成coredump)mvImuFromLastFrame.clear();mvImuFromLastFrame.reserve(mlQueueImuData.size());/// imuDeque为空时,returnif(mlQueueImuData.size() == 0){Verbose::PrintMess("Not IMU data in mlQueueImuData!!", Verbose::VERBOSITY_NORMAL);mCurrentFrame.setIntegrated();return;}// while 循环,取出队列中 last_frame-> frame时间段内的数据 while(true){bool bSleep = false;{unique_lock<mutex> lock(mMutexImuQueue);if(!mlQueueImuData.empty()){ // 取mlQueueImuData中front,IMU::Point* m = &mlQueueImuData.front();cout.precision(17);// 若 time 小于前 last_frame时, pop_frontif(m->t<mCurrentFrame.mpPrevFrame->mTimeStamp-0.001l){mlQueueImuData.pop_front();} // 否则 若time < current_time时,放入收集队列并pop_frontelse if(m->t<mCurrentFrame.mTimeStamp-0.001l){mvImuFromLastFrame.push_back(*m);mlQueueImuData.pop_front();}else // 否则:2,3不满足,即此间隔数据已经取完,break{mvImuFromLastFrame.push_back(*m);break;}}else{break;bSleep = true;}}if(bSleep) //500us轮询一次usleep(500);}const int n = mvImuFromLastFrame.size()-1;// 构建 IMU::Preintegrated对象IMU::Preintegrated* pImuPreintegratedFromLastFrame = new IMU::Preintegrated(mLastFrame.mImuBias,mCurrentFrame.mImuCalib);for(int i=0; i<n; i++){float tstep;cv::Point3f acc, angVel;// 得角速度+角度if((i==0) && (i<(n-1))) //第一个但不是最后一个时{ float tab = mvImuFromLastFrame[i+1].t-mvImuFromLastFrame[i].t;float tini = mvImuFromLastFrame[i].t-mCurrentFrame.mpPrevFrame->mTimeStamp;acc = (mvImuFromLastFrame[i].a+mvImuFromLastFrame[i+1].a-(mvImuFromLastFrame[i+1].a-mvImuFromLastFrame[i].a)*(tini/tab))*0.5f;angVel = (mvImuFromLastFrame[i].w+mvImuFromLastFrame[i+1].w-(mvImuFromLastFrame[i+1].w-mvImuFromLastFrame[i].w)*(tini/tab))*0.5f;tstep = mvImuFromLastFrame[i+1].t-mCurrentFrame.mpPrevFrame->mTimeStamp;}else if(i<(n-1)) // 不是最后一个时{acc = (mvImuFromLastFrame[i].a+mvImuFromLastFrame[i+1].a)*0.5f;angVel = (mvImuFromLastFrame[i].w+mvImuFromLastFrame[i+1].w)*0.5f;tstep = mvImuFromLastFrame[i+1].t-mvImuFromLastFrame[i].t;}else if((i>0) && (i==(n-1))) // 不是第一个但是最后一个{float tab = mvImuFromLastFrame[i+1].t-mvImuFromLastFrame[i].t;float tend = mvImuFromLastFrame[i+1].t-mCurrentFrame.mTimeStamp;acc = (mvImuFromLastFrame[i].a+mvImuFromLastFrame[i+1].a-(mvImuFromLastFrame[i+1].a-mvImuFromLastFrame[i].a)*(tend/tab))*0.5f;angVel = (mvImuFromLastFrame[i].w+mvImuFromLastFrame[i+1].w-(mvImuFromLastFrame[i+1].w-mvImuFromLastFrame[i].w)*(tend/tab))*0.5f;tstep = mCurrentFrame.mTimeStamp-mvImuFromLastFrame[i].t;}else if((i==0) && (i==(n-1))) // 只有一个时{acc = mvImuFromLastFrame[i].a;angVel = mvImuFromLastFrame[i].w;tstep = mCurrentFrame.mTimeStamp-mCurrentFrame.mpPrevFrame->mTimeStamp;}if (!mpImuPreintegratedFromLastKF)cout << "mpImuPreintegratedFromLastKF does not exist" << endl;// 两个预积分 总体预积分+帧间预积分mpImuPreintegratedFromLastKF->IntegrateNewMeasurement(acc,angVel,tstep);pImuPreintegratedFromLastFrame->IntegrateNewMeasurement(acc,angVel,tstep);}mCurrentFrame.mpImuPreintegratedFrame = pImuPreintegratedFromLastFrame;mCurrentFrame.mpImuPreintegrated = mpImuPreintegratedFromLastKF;mCurrentFrame.mpLastKeyFrame = mpLastKeyFrame;// 设置当前帧为已预积分状态mCurrentFrame.setIntegrated();Verbose::PrintMess("Preintegration is finished!! ", Verbose::VERBOSITY_DEBUG);
}
PredictStateIMU 利用IMU计算位姿
- 利用IMU计算位姿, 地图更新且有关键帧时,从关键帧开始积,否则按照上一帧积
- 有两种情况会用到此函数:
视觉跟丢时用imu预测位姿
imu模式下,恒速模型跟踪时提供位姿初始值
bool Tracking::PredictStateIMU()
{// 没有前一帧时直接return falseif(!mCurrentFrame.mpPrevFrame){Verbose::PrintMess("No last frame", Verbose::VERBOSITY_NORMAL);return false;}// 地图已更新 且 有上一关键帧if(mbMapUpdated && mpLastKeyFrame){// 得到上一关键帧的imu位姿+速度const cv::Mat twb1 = mpLastKeyFrame->GetImuPosition();const cv::Mat Rwb1 = mpLastKeyFrame->GetImuRotation();const cv::Mat Vwb1 = mpLastKeyFrame->GetVelocity();// 从上一关键帧到当前的时间差const cv::Mat Gz = (cv::Mat_<float>(3,1) << 0,0,-IMU::GRAVITY_VALUE);const float t12 = mpImuPreintegratedFromLastKF->dT;// 得到此时 位置+角度+速度cv::Mat Rwb2 = IMU::NormalizeRotation(Rwb1*mpImuPreintegratedFromLastKF->GetDeltaRotation(mpLastKeyFrame->GetImuBias()));cv::Mat twb2 = twb1 + Vwb1*t12 + 0.5f*t12*t12*Gz+ Rwb1*mpImuPreintegratedFromLastKF->GetDeltaPosition(mpLastKeyFrame->GetImuBias());cv::Mat Vwb2 = Vwb1 + t12*Gz + Rwb1*mpImuPreintegratedFromLastKF->GetDeltaVelocity(mpLastKeyFrame->GetImuBias());// 赋值当前帧数据mCurrentFrame.SetImuPoseVelocity(Rwb2,twb2,Vwb2);mCurrentFrame.mPredRwb = Rwb2.clone();mCurrentFrame.mPredtwb = twb2.clone();mCurrentFrame.mPredVwb = Vwb2.clone();mCurrentFrame.mImuBias = mpLastKeyFrame->GetImuBias();mCurrentFrame.mPredBias = mCurrentFrame.mImuBias;return true;}else if(!mbMapUpdated) // 如果地图没更新时{ // 取上一帧 位置+角度+速度const cv::Mat twb1 = mLastFrame.GetImuPosition();const cv::Mat Rwb1 = mLastFrame.GetImuRotation();const cv::Mat Vwb1 = mLastFrame.mVw;const cv::Mat Gz = (cv::Mat_<float>(3,1) << 0,0,-IMU::GRAVITY_VALUE);const float t12 = mCurrentFrame.mpImuPreintegratedFrame->dT;// 得到此时 位置+角度+速度cv::Mat Rwb2 = IMU::NormalizeRotation(Rwb1*mCurrentFrame.mpImuPreintegratedFrame->GetDeltaRotation(mLastFrame.mImuBias));cv::Mat twb2 = twb1 + Vwb1*t12 + 0.5f*t12*t12*Gz+ Rwb1*mCurrentFrame.mpImuPreintegratedFrame->GetDeltaPosition(mLastFrame.mImuBias);cv::Mat Vwb2 = Vwb1 + t12*Gz + Rwb1*mCurrentFrame.mpImuPreintegratedFrame->GetDeltaVelocity(mLastFrame.mImuBias);// 赋值当前帧mCurrentFrame.SetImuPoseVelocity(Rwb2,twb2,Vwb2);mCurrentFrame.mPredRwb = Rwb2.clone();mCurrentFrame.mPredtwb = twb2.clone();mCurrentFrame.mPredVwb = Vwb2.clone();mCurrentFrame.mImuBias =mLastFrame.mImuBias;mCurrentFrame.mPredBias = mCurrentFrame.mImuBias;return true;}elsecout << "not IMU prediction!!" << endl;return false;
}
ComputeGyroBias 角度零偏
- 多帧数据,帧间旋转与IMU旋转之差作为误差项
- 多帧数据中,bias一样。采用高斯牛顿,得到 bias:xyz
void Tracking::ComputeGyroBias(const vector<Frame*> &vpFs, float &bwx, float &bwy, float &bwz)
{const int N = vpFs.size();vector<float> vbx;vbx.reserve(N);vector<float> vby;vby.reserve(N);vector<float> vbz;vbz.reserve(N);cv::Mat H = cv::Mat::zeros(3,3,CV_32F);cv::Mat grad = cv::Mat::zeros(3,1,CV_32F);for(int i=1;i<N;i++){Frame* pF2 = vpFs[i];Frame* pF1 = vpFs[i-1];// 计算观测旋转量cv::Mat VisionR = pF1->GetImuRotation().t()*pF2->GetImuRotation();// 计算imu帧间旋转量cv::Mat JRg = pF2->mpImuPreintegratedFrame->JRg;// 得到预积分与观测误差量cv::Mat E = pF2->mpImuPreintegratedFrame->GetUpdatedDeltaRotation().t()*VisionR;cv::Mat e = IMU::LogSO3(E);assert(fabs(pF2->mTimeStamp-pF1->mTimeStamp-pF2->mpImuPreintegratedFrame->dT)<0.01);cv::Mat J = -IMU::InverseRightJacobianSO3(e)*E.t()*JRg;// 计算 J^T *J * Δx = -J^T * f(x) grad += J.t()*e;H += J.t()*J;}cv::Mat bg = -H.inv(cv::DECOMP_SVD)*grad;bwx = bg.at<float>(0);bwy = bg.at<float>(1);bwz = bg.at<float>(2);// bias 数据赋值for(int i=1;i<N;i++){Frame* pF = vpFs[i];pF->mImuBias.bwx = bwx;pF->mImuBias.bwy = bwy;pF->mImuBias.bwz = bwz;pF->mpImuPreintegratedFrame->SetNewBias(pF->mImuBias);pF->mpImuPreintegratedFrame->Reintegrate();}
}
Δ\DeltaΔ
ComputeVelocitiesAccBias 加速度零偏
- 多帧数据,帧间位置、速度与IMU旋转之差作为误差项
- 多帧数据中,bias一样。采用高斯牛顿,得到 bias:xyz
void Tracking::ComputeVelocitiesAccBias(const vector<Frame*> &vpFs, float &bax, float &bay, float &baz)
{const int N = vpFs.size();const int nVar = 3*N +3; // 3 velocities/frame + acc biasconst int nEqs = 6*(N-1);// J e gcv::Mat J(nEqs,nVar,CV_32F,cv::Scalar(0));cv::Mat e(nEqs,1,CV_32F,cv::Scalar(0));cv::Mat g = (cv::Mat_<float>(3,1)<<0,0,-IMU::GRAVITY_VALUE);for(int i=0;i<N-1;i++){Frame* pF2 = vpFs[i+1];Frame* pF1 = vpFs[i];// 取到pF1位置cv::Mat twb1 = pF1->GetImuPosition();cv::Mat twb2 = pF2->GetImuPosition();cv::Mat Rwb1 = pF1->GetImuRotation();// 取到帧间imu积分的数据cv::Mat dP12 = pF2->mpImuPreintegratedFrame->GetUpdatedDeltaPosition();cv::Mat dV12 = pF2->mpImuPreintegratedFrame->GetUpdatedDeltaVelocity();cv::Mat JP12 = pF2->mpImuPreintegratedFrame->JPa;cv::Mat JV12 = pF2->mpImuPreintegratedFrame->JVa;float t12 = pF2->mpImuPreintegratedFrame->dT;// Position p2=p1+v1*t+0.5*g*t^2+R1*dP12J.rowRange(6*i,6*i+3).colRange(3*i,3*i+3) += cv::Mat::eye(3,3,CV_32F)*t12;J.rowRange(6*i,6*i+3).colRange(3*N,3*N+3) += Rwb1*JP12;e.rowRange(6*i,6*i+3) = twb2-twb1-0.5f*g*t12*t12-Rwb1*dP12;// Velocity v2=v1+g*t+R1*dV12J.rowRange(6*i+3,6*i+6).colRange(3*i,3*i+3) += -cv::Mat::eye(3,3,CV_32F);J.rowRange(6*i+3,6*i+6).colRange(3*(i+1),3*(i+1)+3) += cv::Mat::eye(3,3,CV_32F);J.rowRange(6*i+3,6*i+6).colRange(3*N,3*N+3) -= Rwb1*JV12;e.rowRange(6*i+3,6*i+6) = g*t12+Rwb1*dV12;}// 得到加速度偏差cv::Mat H = J.t()*J;cv::Mat B = J.t()*e;cv::Mat x(nVar,1,CV_32F);cv::solve(H,B,x);bax = x.at<float>(3*N);bay = x.at<float>(3*N+1);baz = x.at<float>(3*N+2);for(int i=0;i<N;i++){Frame* pF = vpFs[i];x.rowRange(3*i,3*i+3).copyTo(pF->mVw);if(i>0){pF->mImuBias.bax = bax;pF->mImuBias.bay = bay;pF->mImuBias.baz = baz;pF->mpImuPreintegratedFrame->SetNewBias(pF->mImuBias);}}
}
相关函数-初始化
MonocularInitialization 单目初始化
- 并行地计算基础矩阵和单应性矩阵,选取其中一个模型,恢复出最开始两帧之间的相对姿态以及点云,得到初始两帧的匹配、相对运动、初始MapPoints
- 每个track 都会判断
void Tracking::MonocularInitialization()
{// 如果单目初始器还没有被创建,则创建单目初始器 // NOTICE 也只有单目会使用到这个if(!mpInitializer) {// Set Reference Frame 若当前帧的 特征点个数大于100时if(mCurrentFrame.mvKeys.size()>100){// 当前帧作为初始帧+上一帧,mInitialFrame = Frame(mCurrentFrame);mLastFrame = Frame(mCurrentFrame);// mvbPrevMatched最大的情况就是所有特征点都被跟踪上// 因为当前帧会变成"上一帧"",所以存储到了这个变量中mvbPrevMatched.resize(mCurrentFrame.mvKeysUn.size());for(size_t i=0; i<mCurrentFrame.mvKeysUn.size(); i++)mvbPrevMatched[i]=mCurrentFrame.mvKeysUn[i].pt;// 初始化 mpInitializer 无效if(mpInitializer)delete mpInitializer;// 由当前帧构造初始器 sigma:1.0 iterations:200mpInitializer = new Initializer(mCurrentFrame,1.0,200);// -1 表示没有任何匹配。这里面存储的是匹配的点的idfill(mvIniMatches.begin(),mvIniMatches.end(),-1);// 如果使用imu,则初始化 imu一些数据if (mSensor == System::IMU_MONOCULAR){if(mpImuPreintegratedFromLastKF){delete mpImuPreintegratedFromLastKF;}mpImuPreintegratedFromLastKF = new IMU::Preintegrated(IMU::Bias(),*mpImuCalib);mCurrentFrame.mpImuPreintegrated = mpImuPreintegratedFromLastKF;}return;}}else //mpInitializer 已初始化{ // 因此只有两帧的特征点个数都大于100 且 使用imu且lastFrame与初始帧时间间隔小于1s, 才能继续初始化if (((int)mCurrentFrame.mvKeys.size()<=100)||((mSensor == System::IMU_MONOCULAR)&&(mLastFrame.mTimeStamp-mInitialFrame.mTimeStamp>1.0))){ // 重置 mpInitializer 为未初始化,并returndelete mpInitializer;mpInitializer = static_cast<Initializer*>(NULL);fill(mvIniMatches.begin(),mvIniMatches.end(),-1);return;}// step 3:在mInitialFrame与mCurrentFrame中找匹配的特征点对// Find correspondences 构造orb匹配子ORBmatcher matcher(0.9 //最佳的和次佳评分的比值阈值,true); //检查特征点的方向// step 4:针对单目初始化的时候,进行特征点的匹配int nmatches = matcher.SearchForInitialization(mInitialFrame,mCurrentFrame //初始化时的参考帧和当前帧,mvbPrevMatched, //在初始化参考帧中提取得到的特征点mvIniMatches,//保存匹配关系100); //搜索窗口大小// 如果初始化的两帧之间的匹配点太少,重新初始化if(nmatches<100){delete mpInitializer;mpInitializer = static_cast<Initializer*>(NULL);fill(mvIniMatches.begin(),mvIniMatches.end(),-1);return;}cv::Mat Rcw; // Current Camera Rotationcv::Mat tcw; // Current Camera Translationvector<bool> vbTriangulated; // Triangulated Correspondences (mvIniMatches)// ReconstructWithTwoViews()是个虚函数,会根据所使用的相机模型调用不同的实现// Param: 初始帧(去过畸变)+第一帧(去过畸变)+第0帧到第1帧的匹配mvIniMatches(不成功为-1)+第一帧位姿Rcw+tcw+第0帧特征点三维坐标+存储三角测量匹配结果if(mpCamera->ReconstructWithTwoViews(mInitialFrame.mvKeysUn,mCurrentFrame.mvKeysUn,mvIniMatches,Rcw,tcw,mvIniP3D,vbTriangulated)){ // 若匹配不成功,则 标记个数--for(size_t i=0, iend=mvIniMatches.size(); i<iend;i++){if(mvIniMatches[i]>=0 && !vbTriangulated[i]){mvIniMatches[i]=-1;nmatches--;}}// Set Frame PosesmInitialFrame.SetPose(cv::Mat::eye(4,4,CV_32F));cv::Mat Tcw = cv::Mat::eye(4,4,CV_32F);Rcw.copyTo(Tcw.rowRange(0,3).colRange(0,3));tcw.copyTo(Tcw.rowRange(0,3).col(3));mCurrentFrame.SetPose(Tcw);// 创建初始化单目地图CreateInitialMapMonocular();}}
}
CreateInitialMapMonocular 创建初始化单目地图
- 使用在单目初始化过程中三角化得到的点,包装成为地图点,并且生成初始地图
void Tracking::CreateInitialMapMonocular()
{// Create KeyFrames 认为单目初始化时候的参考帧和当前帧都是关键帧KeyFrame* pKFini = new KeyFrame(mInitialFrame,mpAtlas->GetCurrentMap(),mpKeyFrameDB);KeyFrame* pKFcur = new KeyFrame(mCurrentFrame,mpAtlas->GetCurrentMap(),mpKeyFrameDB);// 如果使用imu,则 重置IMU预积分if(mSensor == System::IMU_MONOCULAR)pKFini->mpImuPreintegrated = (IMU::Preintegrated*)(NULL);// 计算 初始帧与当前帧 的词袋pKFini->ComputeBoW();pKFcur->ComputeBoW();// Insert KFs in the map 地图集中插入关键帧mpAtlas->AddKeyFrame(pKFini);mpAtlas->AddKeyFrame(pKFcur);for(size_t i=0; i<mvIniMatches.size();i++){// 非匹配点略过if(mvIniMatches[i]<0)continue;// Create MapPoint. 创建地图点cv::Mat worldPos(mvIniP3D[i]);MapPoint* pMP = new MapPoint(worldPos,pKFcur,mpAtlas->GetCurrentMap());// 关键帧中添加地图点pKFini->AddMapPoint(pMP,i);pKFcur->AddMapPoint(pMP,mvIniMatches[i]);// 地图点中添加观测点pMP->AddObservation(pKFini,i);pMP->AddObservation(pKFcur,mvIniMatches[i]);pMP->ComputeDistinctiveDescriptors();pMP->UpdateNormalAndDepth();//Fill Current Frame structuremCurrentFrame.mvpMapPoints[mvIniMatches[i]] = pMP;mCurrentFrame.mvbOutlier[mvIniMatches[i]] = false;//Add to MapmpAtlas->AddMapPoint(pMP);}// Update ConnectionspKFini->UpdateConnections();pKFcur->UpdateConnections();std::set<MapPoint*> sMPs;sMPs = pKFini->GetMapPoints();// Bundle AdjustmentVerbose::PrintMess("New Map created with " + to_string(mpAtlas->MapPointsInMap()) + " points", Verbose::VERBOSITY_QUIET);Optimizer::GlobalBundleAdjustemnt(mpAtlas->GetCurrentMap(),20);pKFcur->PrintPointDistribution();float medianDepth = pKFini->ComputeSceneMedianDepth(2);float invMedianDepth;if(mSensor == System::IMU_MONOCULAR)invMedianDepth = 4.0f/medianDepth; // 4.0felseinvMedianDepth = 1.0f/medianDepth;if(medianDepth<0 || pKFcur->TrackedMapPoints(1)<50) // TODO Check, originally 100 tracks{Verbose::PrintMess("Wrong initialization, reseting...", Verbose::VERBOSITY_NORMAL);mpSystem->ResetActiveMap();return;}// Scale initial baselinecv::Mat Tc2w = pKFcur->GetPose();Tc2w.col(3).rowRange(0,3) = Tc2w.col(3).rowRange(0,3)*invMedianDepth;pKFcur->SetPose(Tc2w);// Scale pointsvector<MapPoint*> vpAllMapPoints = pKFini->GetMapPointMatches();for(size_t iMP=0; iMP<vpAllMapPoints.size(); iMP++){if(vpAllMapPoints[iMP]){MapPoint* pMP = vpAllMapPoints[iMP];pMP->SetWorldPos(pMP->GetWorldPos()*invMedianDepth);pMP->UpdateNormalAndDepth();}}if (mSensor == System::IMU_MONOCULAR){pKFcur->mPrevKF = pKFini;pKFini->mNextKF = pKFcur;pKFcur->mpImuPreintegrated = mpImuPreintegratedFromLastKF;mpImuPreintegratedFromLastKF = new IMU::Preintegrated(pKFcur->mpImuPreintegrated->GetUpdatedBias(),pKFcur->mImuCalib);}mpLocalMapper->InsertKeyFrame(pKFini);mpLocalMapper->InsertKeyFrame(pKFcur);mpLocalMapper->mFirstTs=pKFcur->mTimeStamp;mCurrentFrame.SetPose(pKFcur->GetPose());mnLastKeyFrameId=mCurrentFrame.mnId;mpLastKeyFrame = pKFcur;mnLastRelocFrameId = mInitialFrame.mnId;mvpLocalKeyFrames.push_back(pKFcur);mvpLocalKeyFrames.push_back(pKFini);mvpLocalMapPoints=mpAtlas->GetAllMapPoints();mpReferenceKF = pKFcur;mCurrentFrame.mpReferenceKF = pKFcur;// Compute here initial velocityvector<KeyFrame*> vKFs = mpAtlas->GetAllKeyFrames();cv::Mat deltaT = vKFs.back()->GetPose()*vKFs.front()->GetPoseInverse();mVelocity = cv::Mat();Eigen::Vector3d phi = LogSO3(Converter::toMatrix3d(deltaT.rowRange(0,3).colRange(0,3)));double aux = (mCurrentFrame.mTimeStamp-mLastFrame.mTimeStamp)/(mCurrentFrame.mTimeStamp-mInitialFrame.mTimeStamp);phi *= aux;mLastFrame = Frame(mCurrentFrame);mpAtlas->SetReferenceMapPoints(mvpLocalMapPoints);mpMapDrawer->SetCurrentCameraPose(pKFcur->GetPose());mpAtlas->GetCurrentMap()->mvpKeyFrameOrigins.push_back(pKFini);mState=OK;initID = pKFcur->mnId;
}
相关函数-跟踪
- 恒速模型+
TrackWithMotionModel 恒速模型跟踪
IMU 好使时,IMU预估,否则 与上一帧的地图点匹配跟踪
根据匀速度模型对上一帧的MapPoints进行跟踪
1. 非单目情况,需要对上一帧产生一些新的MapPoints(临时)
2. 将上一帧的MapPoints投影到当前帧的图像平面上,在投影的位置进行区域匹配
3. 根据匹配对估计当前帧的姿态
4. 根据姿态剔除误匹配
- see V-B Initial Pose Estimation From Previous Frame
bool Tracking::TrackWithMotionModel()
{ // 构造ORB匹配器ORBmatcher matcher(0.9,true);// 步骤1:赋值上一帧,并满足条件时根据深度值为上一关键帧生成新的MapPoints,方便跟踪// Update last frame pose according to its reference keyframe// Create "visual odometry" points if in Localization Mode// 根据其参考关键帧更新最后一帧姿势。如果处于定位模式,则创建“视觉里程计”点UpdateLastFrame();// IMu已初始化,且 最近未重定位if (mpAtlas->isImuInitialized() && (mCurrentFrame.mnId>mnLastRelocFrameId+mnFramesToResetIMU)){// Predict ste with IMU if it is initialized and it doesnt need reset// 如果已初始化且不需要重置,则使用 IMU 预测站点PredictStateIMU();return true;}else{ // 根据Const Velocity Model(认为这两帧之间的相对运动和之前两帧间相对运动相同)估计当前帧的位姿mCurrentFrame.SetPose(mVelocity*mLastFrame.mTcw);}fill(mCurrentFrame.mvpMapPoints.begin(),mCurrentFrame.mvpMapPoints.end(),static_cast<MapPoint*>(NULL));// Project points seen in previous frame // 在前一帧中看到的投影点的阈值,双目7,其他15int th;if(mSensor==System::STEREO)th=7;elseth=15;// 步骤2:根据匀速度模型进行对上一帧的MapPoints进行跟踪// 根据上一帧特征点对应的3D点投影的位置缩小特征点匹配范围int nmatches = matcher.SearchByProjection(mCurrentFrame,mLastFrame,th,mSensor==System::MONOCULAR || mSensor==System::IMU_MONOCULAR);// If few matches, uses a wider window search// 如果跟踪的点少,则扩大搜索半径再来一次if(nmatches<20){Verbose::PrintMess("Not enough matches, wider window search!!", Verbose::VERBOSITY_NORMAL);fill(mCurrentFrame.mvpMapPoints.begin(),mCurrentFrame.mvpMapPoints.end(),static_cast<MapPoint*>(NULL));nmatches = matcher.SearchByProjection(mCurrentFrame,mLastFrame,2*th,mSensor==System::MONOCULAR || mSensor==System::IMU_MONOCULAR);Verbose::PrintMess("Matches with wider search: " + to_string(nmatches), Verbose::VERBOSITY_NORMAL);}/// 扩大范围后,匹配对仍小于20,则若使用IMU,则返回true,否则falseif(nmatches<20){Verbose::PrintMess("Not enough matches!!", Verbose::VERBOSITY_NORMAL);if (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO)return true;elsereturn false;}// 步骤3:优化位姿// Optimize frame pose with all matchesOptimizer::PoseOptimization(&mCurrentFrame);// Discard outliers// 步骤4:优化位姿后剔除outlier的mvpMapPointsint nmatchesMap = 0;for(int i =0; i<mCurrentFrame.N; i++){if(mCurrentFrame.mvpMapPoints[i]){if(mCurrentFrame.mvbOutlier[i]){MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);mCurrentFrame.mvbOutlier[i]=false;if(i < mCurrentFrame.Nleft){pMP->mbTrackInView = false;}else{pMP->mbTrackInViewR = false;}pMP->mnLastFrameSeen = mCurrentFrame.mnId;nmatches--;}else if(mCurrentFrame.mvpMapPoints[i]->Observations()>0)nmatchesMap++;}}// 定位模式时,需要有至少20个匹配上if(mbOnlyTracking){mbVO = nmatchesMap<10;return nmatches>20;}// 非定位模式,使用IMU,或者匹配数超过10个if (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO)return true;elsereturn nmatchesMap>=10;
}
UpdateLastFrame 更新上一Frame
- 跟新最近一帧 的位姿
- 满足条件后,为上一帧临时生成新的MapPoints
- 这些地图点不加入Map中,在tracking的最后会删除
- 跟踪过程中需要将将上一帧的MapPoints投影到当前帧可以缩小匹配范围,加快当前帧与上一帧进行特征点匹配
void Tracking::UpdateLastFrame()
{/// 步骤1:更新最近一帧的位姿// Update pose according to reference keyframeKeyFrame* pRef = mLastFrame.mpReferenceKF;cv::Mat Tlr = mlRelativeFramePoses.back();mLastFrame.SetPose(Tlr*pRef->GetPose());// 如果上一帧为关键帧,或者单目\单目IMU的情况,或 slam模式下if(mnLastKeyFrameId==mLastFrame.mnId || mSensor==System::MONOCULAR || mSensor==System::IMU_MONOCULAR || !mbOnlyTracking)return;// Create "visual odometry" MapPoints// We sort points according to their measured depth by the stereo/RGB-D sensor// 步骤2.1:得到上一帧有深度值的特征点vector<pair<float,int> > vDepthIdx;vDepthIdx.reserve(mLastFrame.N);for(int i=0; i<mLastFrame.N;i++){float z = mLastFrame.mvDepth[i];if(z>0){vDepthIdx.push_back(make_pair(z,i));}}if(vDepthIdx.empty())return;// 步骤2.2:按照深度从小到大排序sort(vDepthIdx.begin(),vDepthIdx.end());// We insert all close points (depth<mThDepth)// If less than 100 close points, we insert the 100 closest ones.// 步骤2.3:将距离比较近的点包装成MapPointsint nPoints = 0;for(size_t j=0; j<vDepthIdx.size();j++){int i = vDepthIdx[j].second;bool bCreateNew = false;MapPoint* pMP = mLastFrame.mvpMapPoints[i];if(!pMP)bCreateNew = true;else if(pMP->Observations()<1){bCreateNew = true;}if(bCreateNew){// 这些生成MapPoints后并没有通过:// a.AddMapPoint、// b.AddObservation、// c.ComputeDistinctiveDescriptors、// d.UpdateNormalAndDepth添加属性,// 这些MapPoint仅仅为了提高双目和RGBD的跟踪成功率cv::Mat x3D = mLastFrame.UnprojectStereo(i);MapPoint* pNewMP = new MapPoint(x3D,mpAtlas->GetCurrentMap(),&mLastFrame,i);mLastFrame.mvpMapPoints[i]=pNewMP;// 标记为临时添加的MapPoint,之后在CreateNewKeyFrame之前会全部删除mlpTemporalPoints.push_back(pNewMP);nPoints++;}else{nPoints++;}if(vDepthIdx[j].first>mThDepth && nPoints>100){break;}}
}
TrackReferenceKeyFrame 跟踪参考关键帧
- 计算当前帧的词包,将当前帧的特征点分到特定层的nodes上
- 对属于同一node的描述子进行匹配
- 根据匹配对估计当前帧的姿态
- 根据姿态剔除误匹配
bool Tracking::TrackReferenceKeyFrame()
{// Compute Bag of Words vector// 步骤1:将当前帧的描述子转化为BoW向量mCurrentFrame.ComputeBoW();// We perform first an ORB matching with the reference keyframe// If enough matches are found we setup a PnP solver// 我们首先与参考关键帧进行 ORB 匹配,如果找到足够的匹配,我们设置一个 PnP 求解器ORBmatcher matcher(0.7,true);vector<MapPoint*> vpMapPointMatches;// 步骤2:通过特征点的BoW加快当前帧与参考帧之间的特征点匹配// 特征点的匹配关系由MapPoints进行维护int nmatches = matcher.SearchByBoW(mpReferenceKF,mCurrentFrame,vpMapPointMatches);if(nmatches<15) // 匹配点小于15 则return false{cout << "TRACK_REF_KF: Less than 15 matches!!\n";return false;}// 步骤3:将上一帧的位姿态作为当前帧位姿的初始值mCurrentFrame.mvpMapPoints = vpMapPointMatches;mCurrentFrame.SetPose(mLastFrame.mTcw);//mCurrentFrame.PrintPointDistribution();// cout << " TrackReferenceKeyFrame mLastFrame.mTcw: " << mLastFrame.mTcw << endl;// 步骤4:通过优化3D-2D的重投影误差来获得位姿Optimizer::PoseOptimization(&mCurrentFrame);// Discard outliers// 步骤5:剔除优化后的outlier匹配点(MapPoints)int nmatchesMap = 0;for(int i =0; i<mCurrentFrame.N; i++){if(mCurrentFrame.mvpMapPoints[i]){if(mCurrentFrame.mvbOutlier[i]){MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);mCurrentFrame.mvbOutlier[i]=false;if(i < mCurrentFrame.Nleft){pMP->mbTrackInView = false;}else{pMP->mbTrackInViewR = false;}pMP->mbTrackInView = false;pMP->mnLastFrameSeen = mCurrentFrame.mnId;nmatches--;}else if(mCurrentFrame.mvpMapPoints[i]->Observations()>0)nmatchesMap++;}}// TODO check these conditionsif (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO)return true;elsereturn nmatchesMap>=10;
}
TrackLocalMap 跟踪局部地图
- 估计了相机姿态和帧中跟踪的一些地图点。
检索本地地图并尝试在本地地图中找到与点的匹配项。 - 步骤:
- 更新局部关键帧mvpLocalKeyFrames和局部地图点mvpLocalMapPoints
- 在局部地图中查找与当前帧匹配的MapPoints
- 更新局部所有MapPoints后对位姿再次优化
- 这儿区分了是否跟上帧还是整个局部地图优化
- 评判是否跟踪成功
bool Tracking::TrackLocalMap()
{// We have an estimation of the camera pose and some map points tracked in the frame.// We retrieve the local map and try to find matches to points in the local map.mTrackedFr++;#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartLMUpdate = std::chrono::steady_clock::now();
#endif/// 步骤1:更新局部关键帧mvpLocalKeyFrames和局部地图点mvpLocalMapPointsUpdateLocalMap();
#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartSearchLP = std::chrono::steady_clock::now();double timeUpdatedLM_ms = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_StartSearchLP - time_StartLMUpdate).count();vdUpdatedLM_ms.push_back(timeUpdatedLM_ms);
#endif// 步骤2:在局部地图中查找与当前帧匹配的MapPointsSearchLocalPoints();
#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_StartPoseOpt = std::chrono::steady_clock::now();double timeSearchLP_ms = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_StartPoseOpt - time_StartSearchLP).count();vdSearchLP_ms.push_back(timeSearchLP_ms);
#endif// TOO check outliers before PO 在PO之前检查异常值// 只记录了 当前帧 地图点的个数 + 外点的个数 int aux1 = 0, aux2=0;for(int i=0; i<mCurrentFrame.N; i++)if( mCurrentFrame.mvpMapPoints[i]){aux1++;if(mCurrentFrame.mvbOutlier[i])aux2++;}int inliers;// 步骤3:更新局部所有MapPoints后对位姿再次优化if (!mpAtlas->isImuInitialized()) // IMU未初始化时,进行优化Optimizer::PoseOptimization(&mCurrentFrame);else{ // 最近重定位了,也进行优化if(mCurrentFrame.mnId<=mnLastRelocFrameId+mnFramesToResetIMU){Verbose::PrintMess("TLM: PoseOptimization ", Verbose::VERBOSITY_DEBUG);Optimizer::PoseOptimization(&mCurrentFrame);}else{ // 使用IMU,且最近未重定位,则用lastframe进行优化if(!mbMapUpdated){Verbose::PrintMess("TLM: PoseInertialOptimizationLastFrame ", Verbose::VERBOSITY_DEBUG);inliers = Optimizer::PoseInertialOptimizationLastFrame(&mCurrentFrame); // , !mpLastKeyFrame->GetMap()->GetIniertialBA1());}else{Verbose::PrintMess("TLM: PoseInertialOptimizationLastKeyFrame ", Verbose::VERBOSITY_DEBUG);inliers = Optimizer::PoseInertialOptimizationLastKeyFrame(&mCurrentFrame); // , !mpLastKeyFrame->GetMap()->GetIniertialBA1());}}}
#ifdef REGISTER_TIMESstd::chrono::steady_clock::time_point time_EndPoseOpt = std::chrono::steady_clock::now();double timePoseOpt_ms = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(time_EndPoseOpt - time_StartPoseOpt).count();vdPoseOpt_ms.push_back(timePoseOpt_ms);
#endifvnKeyFramesLM.push_back(mvpLocalKeyFrames.size());vnMapPointsLM.push_back(mvpLocalMapPoints.size());aux1 = 0, aux2 = 0;for(int i=0; i<mCurrentFrame.N; i++)if( mCurrentFrame.mvpMapPoints[i]){aux1++;if(mCurrentFrame.mvbOutlier[i])aux2++;}mnMatchesInliers = 0;// Update MapPoints Statistics 更新 MapPoints 统计信息for(int i=0; i<mCurrentFrame.N; i++){if(mCurrentFrame.mvpMapPoints[i]){if(!mCurrentFrame.mvbOutlier[i]){mCurrentFrame.mvpMapPoints[i]->IncreaseFound();if(!mbOnlyTracking){if(mCurrentFrame.mvpMapPoints[i]->Observations()>0)mnMatchesInliers++;}elsemnMatchesInliers++;}else if(mSensor==System::STEREO)mCurrentFrame.mvpMapPoints[i] = static_cast<MapPoint*>(NULL);}}// Decide if the tracking was succesful// More restrictive if there was a relocalization recently// 记录当前帧跟踪到的MapPoints,用于统计跟踪效果mpLocalMapper->mnMatchesInliers=mnMatchesInliers;// 步骤4:决定是否跟踪成功if(mCurrentFrame.mnId<mnLastRelocFrameId+mMaxFrames && mnMatchesInliers<50)return false;// 若最近几帧丢失,若有10个跟踪上,则认为跟踪成功if((mnMatchesInliers>10)&&(mState==RECENTLY_LOST))return true;// 使用IMU时,跟踪上15个就算跟踪成功,否则跟踪30个才算成功if (mSensor == System::IMU_MONOCULAR){if(mnMatchesInliers<15){return false;}elsereturn true;}else if (mSensor == System::IMU_STEREO){if(mnMatchesInliers<15){return false;}elsereturn true;}else{if(mnMatchesInliers<30)return false;elsereturn true;}
}
相关函数-关键帧
Relocalization 重定位
步骤:
- 根据当前帧特征点的Bow映射,先从当前地图中找出候选关键帧 vpCandidateKFs
- 与候选值进行ORB 特征匹配(0.75),舍弃不相似的候选关键帧(<15个)
- 剩下的初始候选值 进行 PnP 求解.
- 对候选值5点迭代法进行评判,评判失败的丢弃
- 通过PoseOptimization对姿态进行优化求解,得到nGood 匹配对
- nGood <50,放宽查找,若两者大于50时,再次进行PoseOptimization
- 再次优化后匹配点在30-50时,再次放宽查找优化
- 若匹配点 > 50时,返回成功
bool Tracking::Relocalization()
{Verbose::PrintMess("Starting relocalization", Verbose::VERBOSITY_NORMAL);// Compute Bag of Words Vector/// 步骤1:计算当前帧特征点的Bow映射mCurrentFrame.ComputeBoW();// Relocalization is performed when tracking is lost// Track Lost: Query KeyFrame Database for keyframe candidates for relocalisation/// 步骤2:找到与当前帧相似的候选关键帧vector<KeyFrame*> vpCandidateKFs = mpKeyFrameDB->DetectRelocalizationCandidates(&mCurrentFrame, mpAtlas->GetCurrentMap());// 若无候选值,则 返回falseif(vpCandidateKFs.empty()) {Verbose::PrintMess("There are not candidates", Verbose::VERBOSITY_NORMAL);return false;}const int nKFs = vpCandidateKFs.size();// We perform first an ORB matching with each candidate// If enough matches are found we setup a PnP solverORBmatcher matcher(0.75,true);// 预设值用 PnP 求解vector<MLPnPsolver*> vpMLPnPsolvers;vpMLPnPsolvers.resize(nKFs);vector<vector<MapPoint*> > vvpMapPointMatches;vvpMapPointMatches.resize(nKFs);vector<bool> vbDiscarded;vbDiscarded.resize(nKFs);int nCandidates=0;/// 步骤3: 初始候选值筛选for(int i=0; i<nKFs; i++){KeyFrame* pKF = vpCandidateKFs[i];if(pKF->isBad())vbDiscarded[i] = true;else{ // 当前帧与候选值通过BoW进行匹配int nmatches = matcher.SearchByBoW(pKF,mCurrentFrame,vvpMapPointMatches[i]);// 若 匹配个数小于15个,则 舍弃该候选值if(nmatches<15){vbDiscarded[i] = true;continue;}else{ // 当前帧与候选值地图点进行PnP求解得到值MLPnPsolver* pSolver = new MLPnPsolver(mCurrentFrame,vvpMapPointMatches[i]);pSolver->SetRansacParameters(0.99,10,300,6,0.5,5.991); //This solver needs at least 6 pointsvpMLPnPsolvers[i] = pSolver;nCandidates++;}}}// Alternatively perform some iterations of P4P RANSAC// Until we found a camera pose supported by enough inliers// 或者执行 P4P RANSAC 的一些迭代,直到我们找到一个由足够多的内点支持的相机姿势bool bMatch = false; ORBmatcher matcher2(0.9,true); /// 增加ORB匹配难度// 还有候选值,且未匹配成功while(nCandidates>0 && !bMatch){for(int i=0; i<nKFs; i++){if(vbDiscarded[i])continue;// Perform 5 Ransac Iterations 5点迭代法vector<bool> vbInliers;int nInliers;bool bNoMore;MLPnPsolver* pSolver = vpMLPnPsolvers[i];cv::Mat Tcw = pSolver->iterate(5,bNoMore,vbInliers,nInliers);// If Ransac reachs max. iterations discard keyframeif(bNoMore) // 如果 Ransac 达到最大值。 迭代丢弃关键帧{vbDiscarded[i]=true;nCandidates--;}// If a Camera Pose is computed, optimizeif(!Tcw.empty()){Tcw.copyTo(mCurrentFrame.mTcw);set<MapPoint*> sFound;const int np = vbInliers.size();for(int j=0; j<np; j++){if(vbInliers[j]){mCurrentFrame.mvpMapPoints[j]=vvpMapPointMatches[i][j];sFound.insert(vvpMapPointMatches[i][j]);}elsemCurrentFrame.mvpMapPoints[j]=NULL;}// 步骤5:通过PoseOptimization对姿态进行优化求解int nGood = Optimizer::PoseOptimization(&mCurrentFrame);if(nGood<10)continue;for(int io =0; io<mCurrentFrame.N; io++)if(mCurrentFrame.mvbOutlier[io])mCurrentFrame.mvpMapPoints[io]=static_cast<MapPoint*>(NULL);// If few inliers, search by projection in a coarse window and optimize again// 如果内点很少,则在粗窗口中通过投影搜索并再次优化if(nGood<50){int nadditional =matcher2.SearchByProjection(mCurrentFrame,vpCandidateKFs[i],sFound,10,100);if(nadditional+nGood>=50){nGood = Optimizer::PoseOptimization(&mCurrentFrame);// If many inliers but still not enough, search by projection again in a narrower window// the camera has been already optimized with many pointsif(nGood>30 && nGood<50){sFound.clear();for(int ip =0; ip<mCurrentFrame.N; ip++)if(mCurrentFrame.mvpMapPoints[ip])sFound.insert(mCurrentFrame.mvpMapPoints[ip]);nadditional =matcher2.SearchByProjection(mCurrentFrame,vpCandidateKFs[i],sFound,3,64);// Final optimizationif(nGood+nadditional>=50){nGood = Optimizer::PoseOptimization(&mCurrentFrame);for(int io =0; io<mCurrentFrame.N; io++)if(mCurrentFrame.mvbOutlier[io])mCurrentFrame.mvpMapPoints[io]=NULL;}}}}// If the pose is supported by enough inliers stop ransacs and continueif(nGood>=50){bMatch = true;break;}}}}if(!bMatch){return false;}else{mnLastRelocFrameId = mCurrentFrame.mnId;cout << "Relocalized!!" << endl;return true;}}
NeedNewKeyFrame 关键帧选取策略
明确不需要插入新关键帧条件:
- 在定位模式下,不需要新增关键帧
- 如果局部地图被闭环检测使用,则不插入关键帧
- 如果IMU 正在初始化,则不需要关键帧
- 如果关键帧比较多 且 距离上一次重定位未超过1s,则不需插入关键帧
得到参考关键帧跟踪到的MapPoints数量
nRefMatches
和查询局部地图管理器是否繁忙bLocalMappingIdle
对于双目或RGBD摄像头,统计总的可以添加的MapPoints数量和跟踪到地图中的MapPoints数量
决策是否插入关键帧,条件:
- 被跟踪地图点 小于 100 且 被创建大于 70 则
需要插入
- 条件 1a:插入最后一个关键帧与当前帧间隔大于 “MaxFrames”
- 条件 1b:插入最后一个关键帧与当前帧间隔大于 “MinFrames”,且局部地图是空闲状态
- 条件 1c:跟踪很脆弱,重复读0.25 或者
需要被插入
- 条件2:跟踪要大于15 且 重复度小于阈值或
需要被插入
,重复度阈值比c1c要高 - 条件3:时间阈值判断,使用imu时,与上一关键帧时间大于500ms
- 条件4:单目IMU 且 重复:度在[15,75]或最近失位
- 被跟踪地图点 小于 100 且 被创建大于 70 则
如果(((c1a||c1b||c1c) && c2)||c3 ||c4),且插入关键帧队列阻塞不能太多,则需插入关键帧,
- tracking插入关键帧不是直接插入,而且先插入到mlNewKeyFrames中,然后localmapper再逐个pop出来插入到mspKeyFrames
///< 步骤1:确定不需要插入新关键帧
// 在定位模式下,不需要新增关键帧
if(mbOnlyTracking)return false;// If Local Mapping is freezed by a Loop Closure do not insert keyframes
// 如果局部地图被闭环检测使用,则不插入关键帧
if(mpLocalMapper->isStopped() || mpLocalMapper->stopRequested())
{return false;
}// Return false if IMU is initialazing
// 如果IMU 正在初始化,则不需要关键帧
if (mpLocalMapper->IsInitializing())return false;const int nKFs = mpAtlas->KeyFramesInMap();
// Do not insert keyframes if not enough frames have passed from last relocalisation
// 如果关键帧比较多 且 距离上一次重定位未超过1s,则不需插入关键帧
if(mCurrentFrame.mnId<mnLastRelocFrameId+mMaxFrames && nKFs>mMaxFrames)
{return false;
}///< 步骤2:得到参考关键帧跟踪到的MapPoints数量 和查询局部地图管理器是否繁忙
int nMinObs = 3;
if(nKFs<=2)nMinObs=2;// 参考关键帧中跟踪的 MapPoints,返回Matcher个数
int nRefMatches = mpReferenceKF->TrackedMapPoints(nMinObs);// Local Mapping accept keyframes? 局部地图接受关键帧 ??
bool bLocalMappingIdle = mpLocalMapper->AcceptKeyFrames();// Check how many "close" points are being tracked and how many could be potentially created.
int nNonTrackedClose = 0;
int nTrackedClose= 0;///< 步骤3:对于双目或RGBD摄像头,统计总的可以添加的MapPoints数量和跟踪到地图中的MapPoints数量
// 对于双目或RGBD摄像头,统计总的可以添加的MapPoints数量和跟踪到地图中的MapPoints数量
if(mSensor!=System::MONOCULAR && mSensor!=System::IMU_MONOCULAR)
{int N = (mCurrentFrame.Nleft == -1) ? mCurrentFrame.N : mCurrentFrame.Nleft;for(int i =0; i<N; i++){if(mCurrentFrame.mvDepth[i]>0 && mCurrentFrame.mvDepth[i]<mThDepth){if(mCurrentFrame.mvpMapPoints[i] && !mCurrentFrame.mvbOutlier[i])nTrackedClose++;elsenNonTrackedClose++;}}
}///< 步骤4:决策是否需要插入关键帧// 被跟踪地图点 小于 100 且 被创建大于 70 则需要插入
bool bNeedToInsertClose;
bNeedToInsertClose = (nTrackedClose<100) && (nNonTrackedClose>70);// Thresholds
float thRefRatio = 0.75f;
if(nKFs<2)thRefRatio = 0.4f;if(mSensor==System::MONOCULAR)thRefRatio = 0.9f;if(mpCamera2) thRefRatio = 0.75f;if(mSensor==System::IMU_MONOCULAR)
{if(mnMatchesInliers>350) // Points tracked from the local mapthRefRatio = 0.75f;elsethRefRatio = 0.90f;
}// Condition 1a: More than "MaxFrames" have passed from last keyframe insertion
// 条件 1a:插入最后一个关键帧与当前帧间隔大于 “MaxFrames”
const bool c1a = mCurrentFrame.mnId>=mnLastKeyFrameId+mMaxFrames;
// Condition 1b: More than "MinFrames" have passed and Local Mapping is idle
// 条件 1b:插入最后一个关键帧与当前帧间隔大于 “MaxFrames”,且局部地图是空闲状态
const bool c1b = ((mCurrentFrame.mnId>=mnLastKeyFrameId+mMinFrames) && bLocalMappingIdle);
//Condition 1c: tracking is weak
// 条件 1c:跟踪很脆弱,重复读0.25 或者 需要被插入
const bool c1c = mSensor!=System::MONOCULAR && mSensor!=System::IMU_MONOCULAR && mSensor!=System::IMU_STEREO && (mnMatchesInliers<nRefMatches*0.25 || bNeedToInsertClose) ;
// Condition 2: Few tracked points compared to reference keyframe. Lots of visual odometry compared to map matches.// 条件 2:与参考关键帧相比,跟踪点很少。 大量视觉里程计与地图匹配。
// 条件2:跟踪要大于15 且 重复度小于阈值或`需要被插入`,重复度阈值比c1c要高
const bool c2 = (((mnMatchesInliers<nRefMatches*thRefRatio || bNeedToInsertClose)) && mnMatchesInliers>15);// 条件3:时间阈值判断,使用imu时,与上一关键帧时间大于500ms
bool c3 = false;
if(mpLastKeyFrame)
{ // 有IMU 时 500msif (mSensor==System::IMU_MONOCULAR){if ((mCurrentFrame.mTimeStamp-mpLastKeyFrame->mTimeStamp)>=0.5)c3 = true;}else if (mSensor==System::IMU_STEREO){if ((mCurrentFrame.mTimeStamp-mpLastKeyFrame->mTimeStamp)>=0.5)c3 = true;}
}bool c4 = false;
/// 条件4:单目IMU 且 重复:度在[15,75]或最近失位
if ((((mnMatchesInliers<75) && (mnMatchesInliers>15)) || mState==RECENTLY_LOST) && ((mSensor == System::IMU_MONOCULAR))) // MODIFICATION_2, originally ((((mnMatchesInliers<75) && (mnMatchesInliers>15)) || mState==RECENTLY_LOST) && ((mSensor == System::IMU_MONOCULAR)))c4=true;
elsec4=false;if(((c1a||c1b||c1c) && c2)||c3 ||c4)
{// If the mapping accepts keyframes, insert keyframe.// Otherwise send a signal to interrupt BAif(bLocalMappingIdle){return true;}else{mpLocalMapper->InterruptBA();if(mSensor!=System::MONOCULAR && mSensor!=System::IMU_MONOCULAR){ // 队列里不能阻塞太多关键帧// tracking插入关键帧不是直接插入,而且先插入到mlNewKeyFrames中,然后localmapper再逐个pop出来插入到mspKeyFramesif(mpLocalMapper->KeyframesInQueue()<3)return true;elsereturn false;}elsereturn false;}
}
elsereturn false;
}
CreateNewKeyFrame 创建新的关键帧
- 局部地图在初始化 或 设置局部地图stop失败,则 return
- 当前帧构造关键帧:
- 将当前帧构造成关键帧
- 将当前关键帧设置为当前帧的参考关键帧
- 若上一关键帧存在,则赋值:当前帧的上一关键帧+上一帧的下一关键帧
- 从上一关键帧开始 新建 预积分对象,零偏+外参
- 若传感器不是单目 或者 imu单目时,为当前帧生成新的MapPoints
- 根据Tcw计算mRcw、mtcw和mRwc、mOw
- 遍历特征点,得到当前帧深度小于阈值的特征点
- 将特征点按照深度从小到大排序
- 将距离比较近的点包装成MapPoints
- 更新状态:
- local_map 插入 关键帧
- local_map
SetNotStop(false)
- 赋值上一关键帧数据
void Tracking::CreateNewKeyFrame()
{ // 步骤0:局部地图在初始化 或 设置局部地图stop失败,则 returnif(mpLocalMapper->IsInitializing())return;if(!mpLocalMapper->SetNotStop(true))return;// 步骤1:将当前帧构造成关键帧KeyFrame* pKF = new KeyFrame(mCurrentFrame,mpAtlas->GetCurrentMap(),mpKeyFrameDB);if(mpAtlas->isImuInitialized()) // 确定使用IMU与否pKF->bImu = true;pKF->SetNewBias(mCurrentFrame.mImuBias); //设置bias// 步骤2:将当前关键帧设置为当前帧的参考关键帧mpReferenceKF = pKF;mCurrentFrame.mpReferenceKF = pKF;// 若上一关键帧存在,则赋值:当前帧的上一关键帧+上一帧的下一关键帧if(mpLastKeyFrame){pKF->mPrevKF = mpLastKeyFrame;mpLastKeyFrame->mNextKF = pKF;}elseVerbose::PrintMess("No last KF in KF creation!!", Verbose::VERBOSITY_NORMAL);// Reset preintegration from last KF (Create new object)// 从上一关键帧开始 新建 预积分对象,零偏+外参if (mSensor == System::IMU_MONOCULAR || mSensor == System::IMU_STEREO){mpImuPreintegratedFromLastKF = new IMU::Preintegrated(pKF->GetImuBias(),pKF->mImuCalib);}// 步骤3:若传感器不是单目 或者 imu单目时,为当前帧生成新的MapPointsif(mSensor!=System::MONOCULAR && mSensor != System::IMU_MONOCULAR) // TODO check if incluide imu_stereo{ // 根据Tcw计算mRcw、mtcw和mRwc、mOwmCurrentFrame.UpdatePoseMatrices();// cout << "create new MPs" << endl;// We sort points by the measured depth by the stereo/RGBD sensor.// We create all those MapPoints whose depth < mThDepth.// If there are less than 100 close points we create the 100 closest.int maxPoint = 100;if(mSensor == System::IMU_STEREO)maxPoint = 100;// 步骤3.1:得到当前帧深度小于阈值的特征点vector<pair<float,int> > vDepthIdx;int N = (mCurrentFrame.Nleft != -1) ? mCurrentFrame.Nleft : mCurrentFrame.N;vDepthIdx.reserve(mCurrentFrame.N);for(int i=0; i<N; i++){float z = mCurrentFrame.mvDepth[i];if(z>0){vDepthIdx.push_back(make_pair(z,i));}}if(!vDepthIdx.empty()){ // 步骤3.2:按照深度从小到大排序sort(vDepthIdx.begin(),vDepthIdx.end());// 步骤3.3:将距离比较近的点包装成MapPointsint nPoints = 0;for(size_t j=0; j<vDepthIdx.size();j++){int i = vDepthIdx[j].second;bool bCreateNew = false;MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];if(!pMP)bCreateNew = true;else if(pMP->Observations()<1){bCreateNew = true;mCurrentFrame.mvpMapPoints[i] = static_cast<MapPoint*>(NULL);}if(bCreateNew){cv::Mat x3D;if(mCurrentFrame.Nleft == -1){x3D = mCurrentFrame.UnprojectStereo(i);}else{x3D = mCurrentFrame.UnprojectStereoFishEye(i);}MapPoint* pNewMP = new MapPoint(x3D,pKF,mpAtlas->GetCurrentMap());pNewMP->AddObservation(pKF,i);//Check if it is a stereo observation in order to not//duplicate mappointsif(mCurrentFrame.Nleft != -1 && mCurrentFrame.mvLeftToRightMatch[i] >= 0){mCurrentFrame.mvpMapPoints[mCurrentFrame.Nleft + mCurrentFrame.mvLeftToRightMatch[i]]=pNewMP;pNewMP->AddObservation(pKF,mCurrentFrame.Nleft + mCurrentFrame.mvLeftToRightMatch[i]);pKF->AddMapPoint(pNewMP,mCurrentFrame.Nleft + mCurrentFrame.mvLeftToRightMatch[i]);}// 这些添加属性的操作是每次创建MapPoint后都要做的pKF->AddMapPoint(pNewMP,i);pNewMP->ComputeDistinctiveDescriptors();pNewMP->UpdateNormalAndDepth();mpAtlas->AddMapPoint(pNewMP);mCurrentFrame.mvpMapPoints[i]=pNewMP;nPoints++;}else{nPoints++;}// 这里决定了双目和rgbd摄像头时地图点云的稠密程度// 但是仅仅为了让地图稠密直接改这些不太好,// 因为这些MapPoints会参与之后整个slam过程if(vDepthIdx[j].first>mThDepth && nPoints>maxPoint){break;}}Verbose::PrintMess("new mps for stereo KF: " + to_string(nPoints), Verbose::VERBOSITY_NORMAL);}}mpLocalMapper->InsertKeyFrame(pKF);mpLocalMapper->SetNotStop(false);mnLastKeyFrameId = mCurrentFrame.mnId;mpLastKeyFrame = pKF;//cout << "end creating new KF" << endl;
}
ORB-SLAM3 单目惯导ros-system-track相关推荐
- PL-VINS:实时基于点线的单目惯导SLAM系统
摘要: 利用线特征来提高基于点特征的视觉惯性SLAM(VINS)的定位精度越来越重要,因为它们提供了结构化场景中规则性的额外约束,然而,实时性能一直没有得到关注.本文介绍了PL-VINS,一种基于实时 ...
- 十六.激光和惯导LIO-SLAM框架学习之配置自用传感器实时室外跑LIO-SAM框架
专栏系列文章如下: 一:Tixiao Shan最新力作LVI-SAM(Lio-SAM+Vins-Mono),基于视觉-激光-惯导里程计的SLAM框架,环境搭建和跑通过程_goldqiu的博客-CSDN ...
- PSL‑SLAM:PSL‑SLAM: a monocular SLAM system using points and structure lines in Manh 曼哈顿世界中的点线单目SLAM
文章下载地址:https://download.csdn.net/download/weixin_50508111/85811572 一.文章简介 1. 文章类型/时间/作者/所属单位: 期刊论文/2 ...
- Ubuntu下使用单目相机运行ORB SLAM2
环境:Ubuntu16.04+ROS Kinetic+USB单目摄像头 虽然ORB SLAM2的官方说明中表示没有ROS也可以编译运行,但要实时的跑ORB SLAM2还是需要ROS平台的,所以之前没有 ...
- 翻译 | ORB-SLAM: a Versatile and Accurate Monocular SLAM System(ORB-SLAM:一种通用的(全能的)精确的单目SLAM系统)
博主github:https://github.com/MichaelBeechan 博主CSDN:https://blog.csdn.net/u011344545 ORB-SLAM:一种通用的(全能 ...
- ROS获取串口信息及后续处理(以惯导IMU XW-GI5651为例)
一.问题简介 自动驾驶小车的底层的数据相当一部分是通过串口发送的,以惯导为例,惯导的定位信息大概如下所示: $GPFPD,0,1666.330,0.000,0.015,0.129,0.00000000 ...
- Ubuntu20.04 ROS读取basler相机图像步骤。MATLAB2021b标定basler单目相机
运行环境:Ubuntu20.04(64-Bit) ROS版本:Noetic 网卡型号:Realtek RTL 8156外置网卡 相机型号:acA 1920-25gc (GigE接口) 驱动版本:pyl ...
- ros打开笔记本摄像头,单目标定
笔记本摄像头多是usb 镜头 下载包或者sudo apt-get install ros-indigo-usb-cam git clone https://github.com/bosch-ros-p ...
- ROS USB摄像头配置(单目和双目)
ROS USB摄像头配置(单目和双目) 原文 目前使用ROS和PX4固件控制无人机飞行,现在需要在ROS中加入USB摄像头,并且是两个.目前根据其它博客,以及ROS WIKI,对该流程进行记录 参考: ...
最新文章
- Centos 6启动流程详解
- Latex算法伪代码使用总结
- Web前端开发笔记——第二章 HTML语言 第七节 表格标签
- Spring Boot Web Slice测试–示例
- 如何把一个float存到一个长度为4的char数组中?
- Android开发笔记(八十三)多语言支持
- 对象创建的过程 java_Java 对象创建过程
- 微软开始受到越来越多尊重 谁是幕后功臣?
- 国内开源镜像站点汇总
- 硬盘整数分区计算方法(精确硬盘分区算法)
- 华为服务器SNMP协议怎么修改,华为迈普交换机、瑞斯康达SNMP协议配置方法
- Asterisk 对VoiceXML 及CSTA的支持
- 分享39个大数据可视化工具(数据分析必备)
- 壳的机制以及脱壳技术
- android logo制作教程视频,Android的APP怎样制作LOGO的尺寸
- 奔腾的芯——英特尔公司
- ubuntu安装nvidia 750ti显卡驱动
- 51款BI产品、80种可视化工具、80张图(总有一款适合你)
- 打印后台程序没有运行,怎么办?
- seaborn seaborn色板的使用设置(二)