MSCKF 源码解析 一

2023-05-16

论文:https://arxiv.org/abs/1712.00036

源码路径:  https://github.com/daniilidis-group/msckf_mono

 

源码框架

上图展示了整个msckf源码框架,每当收到新图像时,首先判断是否进行imu初始化,如果没有则进行imu的初始化,否则记录当前图像帧和上一图像帧之间的imu信息,然后利用记录的imu信息进行EKF滤波的imu预测过程,之后更新图像跟踪器里面的图像和imu信息并跟踪和产生新特征;在跟踪完新图像之后,紧接着进行误差状态向量和协方差矩阵的增广,向误差状态向量里面添加新的相机状态和协方差.然后添加、更新和确定移除的特征点.紧接着进行特征的移除和EKF测量更新过程.最后删除掉老旧的不带有跟踪特征点的空相机状态.
基于上述过程,接下来将源码分为以下模块解析:

  • IMU初始化

  • EKF之IMU预测过程

  • 跟踪器跟踪产生特征

  • 误差状态向量和协方差矩阵的增广

  • 移除特征点确定

  • EKF之测量更新过程    

  • 删除空状态

 

一、IMU初始化

系统静止状态的初始化

void RosInterface::initialize_imu()
{
  Eigen::Vector3f accel_accum;
  Eigen::Vector3f gyro_accum;
  int num_readings = 0;

  accel_accum.setZero();
  gyro_accum.setZero();

  // 累加初始一段时间内imu的测量值
  for (const auto &entry : imu_queue_)
  {
    auto imu_time = std::get<0>(entry);
    auto imu_reading = std::get<1>(entry);

    accel_accum += imu_reading.a;
    gyro_accum += imu_reading.omega;
    num_readings++;
  }

  // 计算初始静止时间段内加速度和角速度的平均值
  Eigen::Vector3f accel_mean = accel_accum / num_readings;
  Eigen::Vector3f gyro_mean = gyro_accum / num_readings;

  // 将静止时刻陀螺仪角速度均值作为角速度偏置
  init_imu_state_.b_g = gyro_mean;
  init_imu_state_.g << 0.0, 0.0, -9.81; //默认重力加速度
  // 由静止时刻imu加速度均值向量与默认重力加速度向量之间的轴角转换作为初始时刻imu状态的姿态信息
  init_imu_state_.q_IG = Quaternion<float>::FromTwoVectors(
      -init_imu_state_.g, accel_mean);

  init_imu_state_.b_a = init_imu_state_.q_IG * init_imu_state_.g + accel_mean; //0

  init_imu_state_.p_I_G.setZero(); //imu初始状态位置信息为0
  init_imu_state_.v_I_G.setZero(); //imu初始状态速度信息为0
  const auto q = init_imu_state_.q_IG;
}

理解:系统的初始化,位置、速度都为零, 姿态是平均加速度和重力加速度的转换得到的; 角速度的零偏是静止角速度的平均值,而加速度的零偏是0

 

二、EKF之IMU预测过程

2.1 理论部分 (https://blog.csdn.net/hltt3838/article/details/113647649

理解:因为IMU的更新频率是大于图像的更新屏率,当没有图像更新时,根据IMU的建模方程,推导系统的状态;

 

2.2 代码部分

  void propagate(imuReading<_S> &measurement_)
  {
    //按论文中的公式(10)计算F矩阵和G矩阵
    calcF(imu_state_, measurement_);
    calcG(imu_state_);

    // 预测k+1时刻imu状态(使用5阶龙格库塔法积分)
    imuState<_S> imu_state_prop = propogateImuStateRK(imu_state_, measurement_);

    // 预测协方差
    // F * dt
    F_ *= measurement_.dT;

    // Matrix exponential
    Phi_ = F_.exp();


    // 看这篇论文,感觉是考虑到了EKF的不一致性,IMU在这作出了补偿,零空间
    // Apply observability constraints - enforce nullspace of Phi
    // Ref: Observability-constrained Vision-aided Inertial Navigation, Hesch J.
    // et al. Feb, 2012
    Matrix3<_S> R_kk_1(imu_state_.q_IG_null);
    Phi_.template block<3, 3>(0, 0) =
        imu_state_prop.q_IG.toRotationMatrix() * R_kk_1.transpose();

    Vector3<_S> u = R_kk_1 * imu_state_.g;
    RowVector3<_S> s = (u.transpose() * u).inverse() * u.transpose();

    Matrix3<_S> A1 = Phi_.template block<3, 3>(6, 0);
    Vector3<_S> tmp = imu_state_.v_I_G_null - imu_state_prop.v_I_G;
    Vector3<_S> w1 = vectorToSkewSymmetric(tmp) * imu_state_.g;
    Phi_.template block<3, 3>(6, 0) = A1 - (A1 * u - w1) * s;

    Matrix3<_S> A2 = Phi_.template block<3, 3>(12, 0);
    tmp = measurement_.dT * imu_state_.v_I_G_null +
          imu_state_.p_I_G_null - imu_state_prop.p_I_G;
    Vector3<_S> w2 = vectorToSkewSymmetric(tmp) * imu_state_.g;
    Phi_.template block<3, 3>(12, 0) = A2 - (A2 * u - w2) * s;

    Matrix<_S, 15, 15> imu_covar_prop = Phi_ * (imu_covar_ + G_ * noise_params_.Q_imu * G_.transpose() * measurement_.dT) * Phi_.transpose();

    // Apply updates directly
    imu_state_ = imu_state_prop;
    imu_state_.q_IG_null = imu_state_.q_IG;
    imu_state_.v_I_G_null = imu_state_.v_I_G;
    imu_state_.p_I_G_null = imu_state_.p_I_G;

    imu_covar_ = (imu_covar_prop + imu_covar_prop.transpose()) / 2.0;
    imu_cam_covar_ = Phi_ * imu_cam_covar_;
  }

  void calcF(), void calcG

  inline void calcF(const imuState<_S> &imu_state_k,
                    const imuReading<_S> &measurement_k)
  {
    /* Multiplies the error state in the linearized continuous-time
           error state model */
    F_.setZero();

    // 计算角速度和加速度名义状态
    Vector3<_S> omegaHat, aHat;
    omegaHat = measurement_k.omega - imu_state_k.b_g;
    aHat = measurement_k.a - imu_state_k.b_a; //论文中有考虑地球自转影响,这里没考虑
    Matrix3<_S> C_IG = imu_state_k.q_IG.toRotationMatrix();

    F_.template block<3, 3>(0, 0) = -vectorToSkewSymmetric(omegaHat);
    F_.template block<3, 3>(0, 3) = -Matrix3<_S>::Identity();
    F_.template block<3, 3>(6, 0) = -C_IG.transpose() * vectorToSkewSymmetric(aHat);
    F_.template block<3, 3>(6, 9) = -C_IG.transpose();
    F_.template block<3, 3>(12, 6) = Matrix3<_S>::Identity();
  }

  inline void calcG(const imuState<_S> &imu_state_k)
  {
    /* Multiplies the noise std::vector in the linearized continuous-time
           error state model */
    G_.setZero();

    Matrix3<_S> C_IG = imu_state_k.q_IG.toRotationMatrix();

    G_.template block<3, 3>(0, 0) = -Matrix3<_S>::Identity();
    G_.template block<3, 3>(3, 3) = Matrix3<_S>::Identity();
    G_.template block<3, 3>(6, 6) = -C_IG.transpose();
    G_.template block<3, 3>(9, 9) = Matrix3<_S>::Identity();
  }

 

三、跟踪器跟踪产生特征

3.1 理论部分

https://blog.csdn.net/hltt3838/article/details/112525638

 

3.2 代码部分

特征跟踪(光流法)

void CornerTracker::track_features(cv::Mat img_1, cv::Mat img_2, Point2fVector& points1, Point2fVector& points2, IdVector& id1, IdVector& id2)
{
  std::vector<uchar> status;

  std::vector<float> err;
 //使用具有金字塔的迭代Lucas-Kanade方法计算稀疏特征集的光流 
  cv::calcOpticalFlowPyrLK(img_1, img_2,
                           points1, points2,
                           status, err,
                           window_size_, max_level_,
                           termination_criteria_,
                           cv::OPTFLOW_USE_INITIAL_FLOW, min_eigen_threshold_);

  int h = img_1.rows;
  int w = img_1.cols;

  // 移除跟踪失败的或者在图像边界之外的特征点
  // remove failed or out of image points
  int indexCorrection = 0;
  for(int i=0; i<status.size(); i++)
{
    cv::Point2f pt = points2.at(i- indexCorrection);
    cv::Point2f dist_vector = points2.at(i-indexCorrection)-points1.at(i-indexCorrection);
    double dist = std::sqrt(dist_vector.x*dist_vector.x+dist_vector.y*dist_vector.y);
    if(dist>25.0 || (status.at(i) == 0)||(pt.x<0)||(pt.y<0)||(pt.x>w)||(pt.y>h))	
   {
      if((pt.x<0)||(pt.y<0)||(pt.x>w)||(pt.y>h))
        status.at(i) = 0;

      points1.erase (points1.begin() + (i - indexCorrection));
      points2.erase (points2.begin() + (i - indexCorrection));

      id1.erase (id1.begin() + (i - indexCorrection));
      id2.erase (id2.begin() + (i - indexCorrection));

      indexCorrection++;
    }
  }
}

产生特征(fast角点)

track_handler_->new_features()

源码中使用了https://github.com/uzh-rpg/fast库进行角点检测,就不深入细节了.可以直接参照opencv的FastFeatureDetector()角点检测器原理理解该部分,原理都是一样的,也可以用opencv的接口替换掉该fast角点检测库.

注意: 编译MSCKF的时候,一般会出现错误,就是因为没有安装这个库

 

四、误差状态向量和协方差矩阵的增广

4.1 理论部分

状态向量增广

当获得一张新的相机图像时,需要将相机位姿加入到当前状态向量中,相机 位姿可以通过 IMU 估计出来:

协方差矩阵增广

当来一帧新图像后,新的协方差矩阵可写成:

4.2 代码部分

   // 与论文中III-C一致
  // Generates a new camera state and adds it to the full state and covariance.
  void augmentState(const int &state_id, const _S &time)
  {
    map_.clear();

    // 依据当前IMU位姿计算出当前相机位姿(论文中公式14)
    // Compute camera_ pose from current IMU pose
    Quaternion<_S> q_CG = camera_.q_CI * imu_state_.q_IG;

    q_CG.normalize();
    camState<_S> cam_state;
    cam_state.last_correlated_id = -1;
    cam_state.q_CG = q_CG;

    cam_state.p_C_G =
        imu_state_.p_I_G + imu_state_.q_IG.inverse() * camera_.p_C_I;

    cam_state.time = time;
    cam_state.state_id = state_id;

    // 协方差维度增广
    // Build MSCKF covariance matrix
    if (cam_states_.size())
    {
      P_.resize(15 + cam_covar_.cols(), 15 + cam_covar_.cols());
      P_.template block<15, 15>(0, 0) = imu_covar_;
      P_.block(0, 15, 15, cam_covar_.cols()) = imu_cam_covar_;
      P_.block(15, 0, cam_covar_.cols(), 15) = imu_cam_covar_.transpose();
      P_.block(15, 15, cam_covar_.rows(), cam_covar_.cols()) = cam_covar_;
    }
    else
    {
      P_ = imu_covar_;
    }

    if (P_.determinant() < -0.000001)
    {
      //ROS_ERROR("Covariance determinant is negative! %f", P.determinant());
    }

    // 计算新相机状态相对于状态向量的jacobi矩阵(论文中公式16)
    MatrixX<_S> J = MatrixX<_S>::Zero(6, 15 + 6 * cam_states_.size());
    J.template block<3, 3>(0, 0) = camera_.q_CI.toRotationMatrix();
    J.template block<3, 3>(3, 0) =
        vectorToSkewSymmetric(imu_state_.q_IG.inverse() * camera_.p_C_I);
    J.template block<3, 3>(3, 12) = Matrix3<_S>::Identity();

    // Camera<_S> State Jacobian
    // MatrixX<_S> J = calcJ(imu_state_, cam_states_);

    MatrixX<_S> tempMat = MatrixX<_S>::Identity(15 + 6 * cam_states_.size() + 6,
                                                15 + 6 * cam_states_.size());
    tempMat.block(15 + 6 * cam_states_.size(), 0, 6,
                  15 + 6 * cam_states_.size()) = J;

    // 增广协方差矩阵
    // Augment the MSCKF covariance matrix
    MatrixX<_S> P_aug = tempMat * P_ * tempMat.transpose();

    MatrixX<_S> P_aug_sym = (P_aug + P_aug.transpose()) / 2.0;

    P_aug = P_aug_sym;

    // 增广状态向量
    // Break everything into appropriate structs
    cam_states_.push_back(cam_state);
    imu_covar_ = P_aug.template block<15, 15>(0, 0);

    // 更新相机协方差(P_CC)和相机imu协方差(P_IC)
    cam_covar_.resize(P_aug.rows() - 15, P_aug.cols() - 15);
    cam_covar_ = P_aug.block(15, 15, P_aug.rows() - 15, P_aug.cols() - 15);

    imu_cam_covar_.resize(15, P_aug.cols() - 15);
    imu_cam_covar_ = P_aug.block(0, 15, 15, P_aug.cols() - 15);

    VectorX<_S> cov_diag = imu_covar_.diagonal();
  }

 

五、移除特征点确定

  void update(const std::vector<Vector2<_S>, Eigen::aligned_allocator<Vector2<_S>>> &measurements,
              const std::vector<size_t> &feature_ids)
  {

    feature_tracks_to_residualize_.clear();
    tracks_to_remove_.clear();

    int id_iter = 0;
    // feature_ids指代当前特征点索引集
    // tracked_feature_ids_指代已跟踪的特征的索引集
    // Loop through all features being tracked
    for (auto feature_id : tracked_feature_ids_)
    {
      // Check if old feature is seen in current measurements
      auto input_feature_ids_iter =
          find(feature_ids.begin(), feature_ids.end(), feature_id);
      bool is_valid = (input_feature_ids_iter != feature_ids.end());

      // If so, get the relevant track
      auto track = feature_tracks_.begin() + id_iter;

      // 如果跟踪的特征点出现在当前图像中,则将当前观测信息记录到对应特征的分组中
      // If we're still tracking this point, add the observation
      if (is_valid)
      {
        // 根据特征点对观测数据进行分组
        size_t feature_ids_dist =
            distance(feature_ids.begin(), input_feature_ids_iter);
        track->observations.push_back(measurements[feature_ids_dist]);

        // 更新相机状态量中的特征点集
        auto cam_state_iter = cam_states_.end() - 1;
        cam_state_iter->tracked_feature_ids.push_back(feature_id);

        // 记录特征点分组中相机状态索引
        track->cam_state_indices.push_back(cam_state_iter->state_id);
      }

      // 如果特征点未出现在当前图像帧中,或者特征点被跟踪的次数超过给定阈值,则移除特征点
      // If corner is not valid or track is too long, remove track to be residualized
      if (!is_valid || (track->observations.size() >=
                        msckf_params_.max_track_length))
      {
        // 移除观测到"移除特征点"的相机状态中的"移除特征点",并记录对应的相机状态
        featureTrackToResidualize<_S> track_to_residualize;
        removeTrackedFeature(feature_id, track_to_residualize.cam_states,
                             track_to_residualize.cam_state_indices);

        // 如果"移除特征点"被跟踪的次数超过设定的阈值,则将track的相关信息记录到 track_to_residualize 中
        // feature_tracks_to_residualize_中记录了被丢弃的特征点信息
        // If track is long enough, add to the residualized list
        if (track_to_residualize.cam_states.size() >=
            msckf_params_.min_track_length)
        {
          track_to_residualize.feature_id = track->feature_id;
          track_to_residualize.observations = track->observations;
          track_to_residualize.initialized = track->initialized;
          if (track->initialized)
            track_to_residualize.p_f_G = track->p_f_G;

          feature_tracks_to_residualize_.push_back(track_to_residualize);
        }

        // 记录要移除的特征点索引
        tracks_to_remove_.push_back(feature_id);
      }

      id_iter++;
    }

    // TODO: Double check this stuff and maybe use use non-pointers for accessing
    // elements so that it only requires one pass
    // 移除 tracks_to_remove_ 中的特征点
    for (auto feature_id : tracks_to_remove_)
    {
      auto track_iter = feature_tracks_.begin();
      while (track_iter != feature_tracks_.end())
      {
        if (track_iter->feature_id == feature_id)
        {
          size_t last_id = track_iter->cam_state_indices.back();
          for (size_t index : track_iter->cam_state_indices)
          {
            for (auto &camstate : cam_states_)
            {
              if (!camstate.tracked_feature_ids.size() &&
                  camstate.state_id == index)
              {
                camstate.last_correlated_id = last_id;
              }
            }
          }
          track_iter = feature_tracks_.erase(track_iter);
          break;
        }
        else
          track_iter++;
      }

      auto corresponding_id = std::find(tracked_feature_ids_.begin(),
                                        tracked_feature_ids_.end(), feature_id);

      if (corresponding_id != tracked_feature_ids_.end())
      {
        tracked_feature_ids_.erase(corresponding_id);
      }
    }
  }

 

六、EKF之测量更新过程

  void marginalize()
  {
    // 如果被丢弃的特征点数量不为0
    if (!feature_tracks_to_residualize_.empty())
    {
      int num_passed, num_rejected, num_ransac, max_length, min_length;
      _S max_norm, min_norm;
      num_passed = 0;
      num_rejected = 0;
      num_ransac = 0;
      max_length = -1;
      min_length = std::numeric_limits<int>::max();
      max_norm = -1;
      min_norm = std::numeric_limits<_S>::infinity();

      std::vector<bool> valid_tracks;
      std::vector<Vector3<_S>, Eigen::aligned_allocator<Vector3<_S>>> p_f_G_vec;
      int total_nObs = 0;

      for (auto track = feature_tracks_to_residualize_.begin();
           track != feature_tracks_to_residualize_.end(); track++)
      {
        // 如果被丢弃的有效特征点数量超过3个时,需要检查丢弃特征点内的相机状态是否移动了足够距离
        if (num_feature_tracks_residualized_ > 3 &&
            !checkMotion(track->observations.front(), track->cam_states))
        {
          num_rejected += 1;
          valid_tracks.push_back(false);
          continue;
        }

        Vector3<_S> p_f_G;
        _S Jcost, RCOND;

        // 估计特征点位置
        // Estimate feature 3D location with intersection, LM
        bool isvalid =
            initializePosition(track->cam_states, track->observations, p_f_G);

        if (isvalid)
        {
          track->initialized = true;
          track->p_f_G = p_f_G;
          map_.push_back(p_f_G); //map_记录特征点位置
        }

        p_f_G_vec.push_back(p_f_G); //记录新还原出来的特征点位置
        int nObs = track->observations.size();

        // 转换特征点位置到c1坐标系
        Vector3<_S> p_f_C1 = (track->cam_states[0].q_CG.toRotationMatrix()) *
                             (p_f_G - track->cam_states[0].p_C_G);

        Eigen::Array<_S, 3, 1> p_f_G_array = p_f_G.array();

        if (!isvalid)
        {
          num_rejected += 1;
          valid_tracks.push_back(false);
        }
        else
        {
          num_passed += 1;
          valid_tracks.push_back(true);
          total_nObs += nObs;
          if (nObs > max_length)
          {
            max_length = nObs;
          }
          if (nObs < min_length)
          {
            min_length = nObs;
          }

          num_feature_tracks_residualized_ += 1;
        }
      }

      if (!num_passed)
      {
        return;
      }
      // 初始化投影到零空间后的观测方程中的变量
      MatrixX<_S> H_o = MatrixX<_S>::Zero(2 * total_nObs - 3 * num_passed,
                                          15 + 6 * cam_states_.size());
      MatrixX<_S> R_o = MatrixX<_S>::Zero(2 * total_nObs - 3 * num_passed,
                                          2 * total_nObs - 3 * num_passed);
      VectorX<_S> r_o(2 * total_nObs - 3 * num_passed);

      Vector2<_S> rep;
      rep << noise_params_.u_var_prime, noise_params_.v_var_prime;

      int stack_counter = 0;
      for (int iter = 0; iter < feature_tracks_to_residualize_.size(); iter++)
      {
        if (!valid_tracks[iter])
          continue;

        featureTrackToResidualize<_S> track = feature_tracks_to_residualize_[iter];

        Vector3<_S> p_f_G = p_f_G_vec[iter];
        VectorX<_S> r_j = calcResidual(p_f_G, track.cam_states, track.observations); //计算视觉测量残差,与论文中公式18,19,20一致

        int nObs = track.observations.size();
        MatrixX<_S> R_j = (rep.replicate(nObs, 1)).asDiagonal();

        // Calculate H_o_j and residual
        MatrixX<_S> H_o_j, A_j;
        calcMeasJacobian(p_f_G, track.cam_state_indices, H_o_j, A_j); //计算论文公式(23)中的A_T和H_0

        // Stacked residuals and friends
        VectorX<_S> r_o_j = A_j.transpose() * r_j;       //将论文公式(22)中的r_j投影到左零空间
        MatrixX<_S> R_o_j = A_j.transpose() * R_j * A_j; //将测量噪声投影到左零空间

        if (gatingTest(H_o_j, r_o_j, track.cam_states.size() - 1))
        {
          r_o.segment(stack_counter, r_o_j.size()) = r_o_j;
          H_o.block(stack_counter, 0, H_o_j.rows(), H_o_j.cols()) = H_o_j;
          R_o.block(stack_counter, stack_counter, R_o_j.rows(), R_o_j.cols()) =
              R_o_j;

          stack_counter += H_o_j.rows();
        }
      }

      H_o.conservativeResize(stack_counter, H_o.cols());
      r_o.conservativeResize(stack_counter);
      R_o.conservativeResize(stack_counter, stack_counter);

      // 至此计算出论文中的观测方程中的H_0, r_0, 以及n_0对应的噪声方差矩阵
      // 然后根据ekf的观测更新过程更新状态和协方差
      measurementUpdate(H_o, r_o, R_o);
    }
  }

 

七、删除空状态

  // 删除不包含任何激活特征点的相机状态,并更新对应的协方差矩阵
  void pruneEmptyStates()
  {
    int max_states = msckf_params_.max_cam_states;
    if (cam_states_.size() < max_states)
      return;
    std::vector<size_t> deleteIdx;
    deleteIdx.clear();

    size_t num_states = cam_states_.size();

    // 找到所有的不带有特征点信息的相机状态,并删除它们
    // Find all cam_states_ with no tracked landmarks and prune them
    auto camState_it = cam_states_.begin();
    size_t num_deleted = 0;
    int camstate_pos = 0;
    int num_cam_states = cam_states_.size();

    int last_to_remove = num_cam_states - max_states - 1;

    if (cam_states_.front().tracked_feature_ids.size())
    {
      return;
    }

    for (int i = 1; i < num_cam_states - max_states; i++)
    {
      if (cam_states_[i].tracked_feature_ids.size())
      {
        last_to_remove = i - 1;
        break;
      }
    }

    //删除无效相机状态
    for (int i = 0; i <= last_to_remove; ++i)
    {
      deleteIdx.push_back(camstate_pos + num_deleted);
      pruned_states_.push_back(*camState_it);
      camState_it = cam_states_.erase(camState_it);
      num_deleted++;
    }

    if (deleteIdx.size() != 0)
    {
      int n_remove = 0;
      int n_keep = 0;
      std::vector<bool> to_keep(num_states, false);
      for (size_t IDx = 0; IDx < num_states; IDx++)
      {
        if (find(deleteIdx.begin(), deleteIdx.end(), IDx) != deleteIdx.end())
          n_remove++;
        else
        {
          to_keep[IDx] = true;
          n_keep++;
        }
      }

      //更新对应的协方差(直接移除状态位对应的协方差位)
      int remove_counter = 0;
      int keep_counter = 0;
      VectorXi keepCovarIdx(6 * n_keep);
      VectorXi removeCovarIdx(6 * n_remove);
      for (size_t IDx = 0; IDx < num_states; IDx++)
      {
        if (!to_keep[IDx])
        {
          removeCovarIdx.segment<6>(6 * remove_counter) =
              VectorXi::LinSpaced(6, 6 * IDx, 6 * (IDx + 1) - 1);
          remove_counter++;
        }
        else
        {
          keepCovarIdx.segment<6>(6 * keep_counter) =
              VectorXi::LinSpaced(6, 6 * IDx, 6 * (IDx + 1) - 1);
          keep_counter++;
        }
      }

      MatrixX<_S> prunedCamCovar;
      square_slice(cam_covar_, keepCovarIdx, prunedCamCovar);
      cam_covar_.resize(prunedCamCovar.rows(), prunedCamCovar.cols());
      cam_covar_ = prunedCamCovar;

      Matrix<_S, 15, Dynamic> prunedImuCamCovar;
      column_slice(imu_cam_covar_, keepCovarIdx, prunedImuCamCovar);
      imu_cam_covar_.resize(prunedImuCamCovar.rows(), prunedImuCamCovar.cols());
      imu_cam_covar_ = prunedImuCamCovar;
    }

    // TODO: Additional outputs = deletedCamCovar (used to compute sigma),
    // deletedCamStates
  }

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MSCKF 源码解析 一 的相关文章

  • 运行msckf_vio

    1 编译 cd span class token operator span span class token operator span msckf catkin make span class token operator span p
  • 机器人地面站-[QGroundControl源码解析]-[9]-[Camera]

    目录 前言 一 QGCCameraManager 二 QGCCameraIO 三 QGCCameraControl 前言 本篇介绍Camera文件夹下的内容 xff0c 该文件夹下又三个类文件 xff0c 分别是QGCCameraManag
  • FREERTOS源码解析——heap4.c

    目录 内存管理heap4无法解决的内存碎片 xff1a HEAP4简析分配内存在哪 xff0c 大小多少如何管理 重要源码解析 内存管理 freertos目前提供了以下5种内存管理 xff0c 特点如下 heap1 xff1a 最简单的内存
  • GVINS源码解析

    GVINS是基于VINS MONO写的 xff0c 视觉 IMU部分与VINS MONO类似 xff0c 可参考我的前一篇文章VINS MONO学习 这篇文章主要解析与GNSS有关的部分 持续更新中 文章目录 estimator node
  • ubuntu16.04运行MSCKF Mono

    仅作为笔记 环境 xff1a ROS Kinetic Boost OpenCV Eigen fast 依赖 span class token function sudo span span class token function apt
  • Volley源码解析

    概述 本文基于Volley 1 1 1版本的源码 Volley是Google官方出的一套小而巧的异步请求库 xff0c 该框架封装的扩展性很强 xff0c 支持HttpClient HttpUrlConnection xff0c 甚至支持O
  • Android-Handler源码解析-Message

    成员变量 标识Message public int what 存储简单数据 xff0c 如果存储复杂的数据使用setData 方法 public int arg1 public int arg2 发送给接收者的任意对象 public Obj
  • MSCKF

    主要数据结构 xff1a StateServer当前里程计状态 imu状态 若干相机位姿 状态协方差矩阵 噪声协方差 struct StateServer IMUState imu state CamStateServer cam stat
  • freeRtos源码解析(二)–任务调度

    freeRtos源码解析 二 任务调度 一 启动任务调度器 启动任务调度器之后 xff0c CPU正式进入任务模式调度各任务 xff08 CPU在中断模式和任务模式之间不断轮转 xff09 freeRtos任务调度依赖于内核的三个中断 xf
  • ROS kinetic 运行s_msckf和 vins_fusion

    s msckf xff1a 采用多状态约束的双目vio系统 注意 imuCallback xff1a 接收IMU数据 xff0c 将IMU数据存到imu msg buffer中 xff0c 这里只会利用开头200帧IMU数据进行静止初始化
  • MSCKF 源码解析 一

    论文 xff1a https arxiv org abs 1712 00036 源码路径 https github com daniilidis group msckf mono 源码框架 上图展示了整个msckf源码框架 xff0c 每当
  • msckf_mono构建运行方法

    背景 博主是在读Davide Scaramuzza投稿到ICRA 2018的VIO综述文章 A Benchmark Comparison of Monocular Visual Odometry Algorithms for Flying
  • Pytorch学习(3) —— nn.Parameter nn.ParameterList nn.ParameterDict 源码解析

    为了更好理解Pytorch基本类的实现方法 xff0c 我这里给出了关于参数方面的3个类的源码详解 此部分可以更好的了解实现逻辑结构 xff0c 有助于后续代码理解 xff0c 学pytorch的话这个不是必须掌握的 xff0c 看不懂也没
  • ASCII表

    http office microsoft com zh cn assistance HA011331362052 aspx ASCII 打印字符 数字 32 126 分配给了能在键盘上找到的字符 当您查看或打印文档时就会出现 数字 127
  • LevelDB源码解析(2) SkipList(跳跃表)

    你也可以通过我的独立博客 www huliujia com 获取本篇文章 背景 SkipList是LevelDB的MemTable使用的底层存储结构 LevelDB实现了一个支持泛型的跳跃表 本文不会具体介绍跳跃表的数据结构 如果读者不了解
  • 对象池(连接池):commons-pool2源码解析:GenericObjectPool的borrowObject方法

    为什么会有对象池 在实际的应用工程当中 存在一些被频繁使用的 创建或者销毁比较耗时 持有的资源也比较昂贵的一些对象 比如 数据库连接对象 线程对象 所以如果能够通过一种方式 把这类对象统一管理 让这类对象可以被循环利用的话 就可以减少很多系
  • xxl-job(2.4.1)使用spring-mvc替换netty的功能

    xxl job 2 4 1 使用spring mvc替换netty的功能 1 xxl job core引入spring mvc的依赖
  • Hashmap源码详解

    在开发中的对于数据结构如何选 我们要知道各个数据结构的优缺点 数组 采用一段连续的存储单元来存储数据 对于指定下标的查找 时间复杂度为O 1 但在数组中间以及头部插入数据时 需要复制移动后面的元素O n 优点 查找快 缺点插入慢 链表 一种
  • 对象池(连接池):commons-pool2源码解析:GenericObjectPool的继承结构、构造方法

    概述 GenericObjectPool是apache commons pool 源码分析基于commons pool2 框架中的一个非常重要的类 解析GenericObjectPool就有必要先了解一下apache commons poo
  • ARoute源码分析之初始化过程

    已在多个项目中使用ARoute实现组件化间的路由通信 但是一直没有很完整的阅读过ARoute框架的源码 刚好这段时间想拜读一些知名框架的源码 那就从熟悉的ARoute开始吧 若有错误的地方欢迎大家指正 本篇博客就从ARoute的初始化流程开

随机推荐

  • 1PPS:秒脉冲 相关概念理解

    时钟模块上的GPS接收机负责接收GPS天线传输的射频信号 xff0c 然后进行变频解调等信号处理 xff0c 向基站提供1pps信号 xff0c 进行同步 GPS使用原子钟 xff08 原子钟 xff0c 是一种计时装置 xff0c 精度可
  • opencv GStreamer-CRITICAL

    使用openvino中的opencv跑之前的代码 碰到个问题 span class token punctuation span myProg span class token operator span span class token
  • 激光雷达 LOAM 论文 解析

    注意 xff1a 本人实验室买的是Velodyne VLP 16激光雷和 LOAM 论文中作者用的不一样 xff0c 在介绍论文之前先介绍一下激光雷达的工作原路 xff0c 这样更容易理解激光雷达的工作过程 xff0c 其实物图如下图1所示
  • VINS 细节系列 - 坐标转换关系

    前言 在学习VINS Mono过程中 xff0c 对初始化代码中的坐标转换关系做出了一些推导 xff0c 特意写了博客记录一下 xff0c 主要记录大体的变量转换关系 相机和IMU的外参 若需要VINS标定旋转外参 xff0c 则进入以下代
  • VINS 细节系列 - 光束法平差法(BA)Ceres 求解

    一 理论部分 学习过VINS的小伙伴应该知道 xff0c 在SFM xff08 structure from motion xff09 的计算中 光束法平差法 BA xff08 Bundle Adjustment xff09 的重要性 xf
  • Ceres 详解(一) Problem类

    引言 Ceres 是由Google开发的开源C 43 43 通用非线性优化库 xff08 项目主页 xff09 xff0c 与g2o并列为目前视觉SLAM中应用最广泛的优化算法库 xff08 VINS Mono中的大部分优化工作均基于Cer
  • VINS - Fusion GPS/VIO 融合 一、数据读取

    目录 一 相关概念 二 程序解读 2 1 参数读取 解析 xff1a 2 2 获取图像时间信息 解析 xff1a 2 3 获取图像时间信息 解析 xff1a 2 4 定义VIO结果输出路径和读取图像信息 解析 xff1a 2 5 读取GPS
  • VINS - Fusion GPS/VIO 融合 二、数据融合

    https zhuanlan zhihu com p 75492883 一 简介 源代码 xff1a VINS Fusion 数据集 xff1a KITTI 数据 程序入口 xff1a globalOptNode cpp 二 程序解读 2
  • VINS - Fusion GPS/INS/视觉 融合 0、 Kitti数据测试

    放两张图片 至于为什么 xff1f 后面会解释 xff01 程序下载 xff1a https github com HKUST Aerial Robotics VINS Fusion 数据集制作 xff1a https zhuanlan z
  • GPS/INS/视觉 融合 、 自己采集数据测试

    cd VIO GPS MapVIG cmake build cmake make run MapVIG 别忘了更新一下 一 运行程序 打开第一个终端 roscore 打开第二个终端 进入工作区间内 xff0c 分别输入 xff1a cd G
  • 九、 惯性导航解算

    一 概述 惯性导航解算就是从上一时刻的导航信息推导本时刻的导航信息 xff0c 包括姿态 速度 位置 具体来讲 xff0c 就是构建当前时刻导航信息与上一时刻导航信息 运动输入 角速度 加速度 之间的关系 xff0c 确切地说 xff0c
  • 十二、构建一个基本的组合导航系统

    一 概述 在之前的文章里 xff0c 我们从一个基本的IMU模块开始讲起 xff08 二 xff09 xff0c 利用Allan方差分析方法 xff08 三 四 xff09 xff0c 得出了其误差参数 xff0c 又通过标定 xff08
  • 电脑键盘工作原理

    xfeff xfeff 随着IBM PC机的发展 xff0c 键盘也分为XT AT PS 2键盘以至于后来的USB键盘 PC系列机使用的键盘有83键 84键 101键 102键和104键等多种 XT和AT机的标准键盘分别为83键和84键 x
  • 十三、扩展一个组合导航系统

    感谢大神分享 xff1a https zhuanlan zhihu com p 156958777 一 概述 上一篇文章我们讲述了怎样搭建一个基本的组合导航系统 xff0c 它仅仅包括IMU做预测 GPS做观测 xff0c 而实际中 xff
  • 三、FSAS + Novatel接收机和相机时间同步、数据保存、解码等 细节说明

    一 器件说明 1 GNSS 接收机是 P ropak6 如下图所示 xff1a 具体得介绍 xff1a 1 https blog csdn net hltt3838 article details 110622732 2 二 NovAtel
  • Word 中出现公式不能编辑问题(兼容模式)

    当word中的公式不能编辑 xff0c 多数原因是由于word是处于兼容模式下 xff0c 这个时候就需要取消world的兼容模式 xff0c 具体步骤如下 xff1a 一 打开word文档 二 点击 word中的文件 出现如下结果 xff
  • 理论三、 MSCKF 数学基础

    感谢 xff1a 紫薯萝卜 https zhuanlan zhihu com p 76793847 1 数学定义 后续将进行MSCKF数学基础介绍 xff0c 先总结一下数据符号定义 坐标系定义 物理量定义 估计量定义 四元数定义 xff1
  • 算法改进5:开源算法Open VINS试用

    1 Open VINS简介 Open VINS是Huang Guoquan老师团队在2019年8月份开源的一套基于MSCKF的VINS算法 xff0c 黄老师曾是Tango项目的核心成员 xff0c 在MSCKF这块非常的权威 Github
  • MSCKF 公式推导

    看完这篇博客建议再看 xff1a http www xinliang zhong vip msckf notes 目录 一 简介 二 符号说明 三 状态向量 3 1 真实状态向量 true state 3 2 误差状态向量 error st
  • MSCKF 源码解析 一

    论文 xff1a https arxiv org abs 1712 00036 源码路径 https github com daniilidis group msckf mono 源码框架 上图展示了整个msckf源码框架 xff0c 每当