OKVIS代码结构:算法
VIO的初始化是一个比较重要的问题,和纯视觉SLAM初始化只须要三角化出3D地图点的深度不一样,还须要完成相机IMU外参、陀螺仪零偏、尺度以及重力的估计。可是,OKVIS的初始化流程彷佛很是简单,可是须要对传感器各项参数有较好的先验值,例如须要在配置文件中给出一个比较靠谱的IMU零偏prior:后端
1 sigma_bg: 0.01 # gyro bias prior [rad/s] 2 sigma_ba: 0.1 # accelerometer bias prior [m/s^2]
根据提供的okvis_app_synchronous.cpp,系统入口在类ThreadedKFVio(该类继承自VioInterface接口)的构造函数中,在okvis_multisensor_processing目录下找到该类对应的文件,构造函数中调用init(),接着调用startThreads(),开启各线程:安全
1 void ThreadedKFVio::startThreads() { 2 3 // consumer threads 4 for (size_t i = 0; i < numCameras_; ++i) { 5 frameConsumerThreads_.emplace_back(&ThreadedKFVio::frameConsumerLoop, this, i); 6 } 7 for (size_t i = 0; i < numCameraPairs_; ++i) { 8 keypointConsumerThreads_.emplace_back(&ThreadedKFVio::matchingLoop, this); 9 } 10 imuConsumerThread_ = std::thread(&ThreadedKFVio::imuConsumerLoop, this); 11 positionConsumerThread_ = std::thread(&ThreadedKFVio::positionConsumerLoop, 12 this); 13 gpsConsumerThread_ = std::thread(&ThreadedKFVio::gpsConsumerLoop, this); 14 magnetometerConsumerThread_ = std::thread( 15 &ThreadedKFVio::magnetometerConsumerLoop, this); 16 differentialConsumerThread_ = std::thread( 17 &ThreadedKFVio::differentialConsumerLoop, this); 18 19 // algorithm threads 20 visualizationThread_ = std::thread(&ThreadedKFVio::visualizationLoop, this); 21 optimizationThread_ = std::thread(&ThreadedKFVio::optimizationLoop, this); 22 publisherThread_ = std::thread(&ThreadedKFVio::publisherLoop, this); 23 }
其中,positionConsumerLoop,gpsConsumerLoop,magnetmeterConsumerLoop,differentialConsumerLoop均未实现(暂不提供GPS,磁力计以及差分气压计支持),也就是开了6个线程,分别执行6个函数:app
1 void ThreadedKFVio::frameConsumerLoop(size_t cameraIndex) 2 void ThreadedKFVio::matchingLoop() 3 void ThreadedKFVio::imuConsumerLoop() 4 // backend algorithms 5 void ThreadedKFVio::visualizationLoop() 6 void ThreadedKFVio::optimizationLoop() 7 void ThreadedKFVio::publisherLoop()
而后,在okvis_app_synchronous.cpp中,将IMU和camera的数据使用addImage()和addImuMeasurement()传入,注意OKVIS中数据流采用了阻塞式(能够经过ThreadKFVio.setBlocking()设定)的线程安全队列。框架
在imuConsumerLoop()中主要处理imu的propagationfrontend
每次imuMeasurementsReceived_队列中出现IMU数据,就会propagate一次,若是刚完成BA优化(须要repropagationNeeded_),则将优化后的状态值做为propagation的初值,不然在上一状态基础上完成状态propagation。函数
主要对应ImuError::propagation()函数,该函数大概两百行,主要实现OKVIS论文中的 4.2 IMU Kinematics and bias model。oop
2.1 判断该帧是否关键帧(第一帧是关键帧)优化
2.2 利用IMU预测pose,为特征点匹配提供方向参考this
在frameConsumerLoop()中Image和IMU的同步策略是这样的:
若没有IMU数据,则不处理;IMU第一帧数据以前的那一帧Image也抛弃,下一帧Image(第一帧Frame)才进行特征检测处理。同时第一帧以前的IMU数据会用来计算pose(该函数返回值永远是true,所以initPose是否准确彻底依赖IMU给出的读数):
bool success = okvis::Estimator::initPoseFromImu(imuData, T_WS);
第一帧以后的IMU数据进行propagation(注意multiframe在单目情形下就是frame),注意到这里propagation的covariance和jacobian均为0,仅仅用于预测,对特征点检测提供先验的T_WC:
okvis::ceres::ImuError::propagation(imuData, parameters_.imu, T_WS, speedAndBiases, lastTimestamp, multiFrame->timestamp());
2.3 Harris角点检测+BRISK描述子计算
接下来对frame特征检测(Harris)和描述子(BRISK)计算(这里的T_WC由前一步的propagation提供,主要为了获取重力方向,提升描述子匹配鲁棒性):
frontend_.detectAndDescribe(frame->sensorId, multiFrame, T_WC, nullptr);
将检测到的keyPoint都push到队列中,提供给matchingLoop()线程使用:
keypointMeasurements_.PushBlockingIfFull(multiFrame, 1)
该线程须要Frame线程提供的keyPointMeasrements_(阻塞队列)。
在matching以前,经过frame和imuData的信息,将当前状态添加到后端估计中去;这里的imuData包含上一帧和当前帧时间戳±20ms范围内的IMU,所以,在frame附近的IMU数据,是会重复使用一次的。OKVIS的算法能够解决该问题(TODO)。
estimator_.addStates(frame, imuData, asKeyframe)
至此,能够获取经过上一帧和IMU数据计算出的系统状态(T_WS和speedAndBias)。
该线程最主要的函数是:
frontend_.dataAssociationAndInitialization(estimator_, T_WS, parameters_, map_, frame, &asKeyframe);
完成
在rotationOnly的运动时,使用2D-2D跟踪,使用IMU给出轨迹;有平移运动能够三角化出3D点时,经过3D-2D匹配计算出pose;这里均使用了Opengv中算法
参考:
1. OKVIS代码框架