OKVIS(一)初始化流程及代码结构

OKVIS代码结构:算法

okvis_apps: your own app
okvis_ceres: backend main code, estimator, error term; 
okvis_common: interface need to be derived, parameterReader;
okvis_cv: frame and camera;
okvis_frontend: visual detection, matching, triangulation and initialization?;
okvis_kinematics: transformation, quaternion algebra;
okvis_matcher: keypoint matcher, which needs several threads;
okvis_multisensor_processing: synchronizer, thread safe queue for sensor data flow; also start multiple threads, and initialization!
okvis_time, okvis_timing: timing;
okvis_util: etc.

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()设定)的线程安全队列框架

1. IMU消费者线程:

在imuConsumerLoop()中主要处理imu的propagationfrontend

每次imuMeasurementsReceived_队列中出现IMU数据,就会propagate一次,若是刚完成BA优化(须要repropagationNeeded_),则将优化后的状态值做为propagation的初值,不然在上一状态基础上完成状态propagation。函数

主要对应ImuError::propagation()函数,该函数大概两百行,主要实现OKVIS论文中的 4.2 IMU Kinematics and bias model。oop

2. Frame消费者线程

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)

3. Matching线程

该线程须要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);

完成

  • 特征点匹配;
  • 3D点初始化;
  • 外点剔除
  • RANSAC
  • 关键帧选择

在rotationOnly的运动时,使用2D-2D跟踪,使用IMU给出轨迹;有平移运动能够三角化出3D点时,经过3D-2D匹配计算出pose;这里均使用了Opengv中算法

 

参考:

1. OKVIS代码框架

相关文章
相关标签/搜索