以下所有内容均为高翔大神所注的《自动驾驶与机器人中的SLAM技术》中的内容
2D SLAM
- 作者实现了一个2D 的ICP
3D SLAM
ICP
- 实现了一个并发的ICP配准
- 实现了点到面的ICP
- 实现了点到线的ICP
- 点到线的ICP的结果与点到点的ICP相当,略差于点到面的、在三中算法中,点到面的ICP在精度和效率上都具有一定优势,明显快于PCL的内置版本,单其单次迭代中计算量明显大于原始ICP
NDT
本书各配准算法与PCL的对比
增量式NDT
需要解决两个问题:
- 每个体素内的高斯参数如何改变
- 如何维护不断增加的体素
高斯分布的增量更新
体素的增量维护
融合导航
1. EKF和优化的关系
2. 组合导航eskf中的预测部分,主要是F矩阵的构建
template <typename S>
bool ESKF<S>::Predict(const IMU& imu) {assert(imu.timestamp_ >= current_time_);double dt = imu.timestamp_ - current_time_;if (dt > (5 * options_.imu_dt_) || dt < 0) {// 时间间隔不对,可能是第一个IMU数据,没有历史信息LOG(INFO) << "skip this imu because dt_ = " << dt;current_time_ = imu.timestamp_;return false;}// nominal state 递推VecT new_p = p_ + v_ * dt + 0.5 * (R_ * (imu.acce_ - ba_)) * dt * dt + 0.5 * g_ * dt * dt;VecT new_v = v_ + R_ * (imu.acce_ - ba_) * dt + g_ * dt;SO3 new_R = R_ * SO3::exp((imu.gyro_ - bg_) * dt);R_ = new_R;v_ = new_v;p_ = new_p;// 其余状态维度不变// error state 递推// 计算运动过程雅可比矩阵 F,见(3.47)// F实际上是稀疏矩阵,也可以不用矩阵形式进行相乘而是写成散装形式,这里为了教学方便,使用矩阵形式Mat18T F = Mat18T::Identity(); // 主对角线F.template block<3, 3>(0, 3) = Mat3T::Identity() * dt; // p 对 vF.template block<3, 3>(3, 6) = -R_.matrix() * SO3::hat(imu.acce_ - ba_) * dt; // v对thetaF.template block<3, 3>(3, 12) = -R_.matrix() * dt; // v 对 baF.template block<3, 3>(3, 15) = Mat3T::Identity() * dt; // v 对 gF.template block<3, 3>(6, 6) = SO3::exp(-(imu.gyro_ - bg_) * dt).matrix(); // theta 对 thetaF.template block<3, 3>(6, 9) = -Mat3T::Identity() * dt; // theta 对 bg// mean and cov predictiondx_ = F * dx_; // 这行其实没必要算,dx_在重置之后应该为零,因此这步可以跳过,但F需要参与Cov部分计算,所以保留cov_ = F * cov_.eval() * F.transpose() + Q_;current_time_ = imu.timestamp_;return true;
}
3. 以下是速度量测,主要是H矩阵的构建
template <typename S>
bool ESKF<S>::ObserveWheelSpeed(const Odom& odom) {assert(odom.timestamp_ >= current_time_);// odom 修正以及雅可比// 使用三维的轮速观测,H为3x18,大部分为零Eigen::Matrix<S, 3, 18> H = Eigen::Matrix<S, 3, 18>::Zero();H.template block<3, 3>(0, 3) = Mat3T::Identity();// 卡尔曼增益Eigen::Matrix<S, 18, 3> K = cov_ * H.transpose() * (H * cov_ * H.transpose() + odom_noise_).inverse();// velocity obsdouble velo_l = options_.wheel_radius_ * odom.left_pulse_ / options_.circle_pulse_ * 2 * M_PI / options_.odom_span_;double velo_r = options_.wheel_radius_ * odom.right_pulse_ / options_.circle_pulse_ * 2 * M_PI / options_.odom_span_;double average_vel = 0.5 * (velo_l + velo_r);VecT vel_odom(average_vel, 0.0, 0.0);VecT vel_world = R_ * vel_odom;dx_ = K * (vel_world - v_);//v_是状态递推后的速度// update covcov_ = (Mat18T::Identity() - K * H) * cov_;UpdateAndReset();return true;
}
4. 以下是GPS的量测,主要任然是H矩阵的构建
template <typename S>
bool ESKF<S>::ObserveGps(const GNSS& gnss) {/// GNSS 观测的修正assert(gnss.unix_time_ >= current_time_);if (first_gnss_) {R_ = gnss.utm_pose_.so3();p_ = gnss.utm_pose_.translation();first_gnss_ = false;current_time_ = gnss.unix_time_;return true;}assert(gnss.heading_valid_);ObserveSE3(gnss.utm_pose_, options_.gnss_pos_noise_, options_.gnss_ang_noise_);current_time_ = gnss.unix_time_;return true;
}template <typename S>
bool ESKF<S>::ObserveSE3(const SE3& pose, double trans_noise, double ang_noise) {/// 既有旋转,也有平移/// 观测状态变量中的p, R,H为6x18,其余为零Eigen::Matrix<S, 6, 18> H = Eigen::Matrix<S, 6, 18>::Zero();H.template block<3, 3>(0, 0) = Mat3T::Identity(); // P部分H.template block<3, 3>(3, 6) = Mat3T::Identity(); // R部分(3.66)// 卡尔曼增益和更新过程Vec6d noise_vec;noise_vec << trans_noise, trans_noise, trans_noise, ang_noise, ang_noise, ang_noise;Mat6d V = noise_vec.asDiagonal();Eigen::Matrix<S, 18, 6> K = cov_ * H.transpose() * (H * cov_ * H.transpose() + V).inverse();// 更新x和covVec6d innov = Vec6d::Zero();innov.template head<3>() = (pose.translation() - p_); // 平移部分innov.template tail<3>() = (R_.inverse() * pose.so3()).log(); // 旋转部分(3.67)dx_ = K * innov;cov_ = (Mat18T::Identity() - K * H) * cov_;UpdateAndReset();return true;
}
IMU预积分
- 书中有IMU预积分所有的公式推导,包括了预积分计算公式,预积分相较于状态量的雅克比矩阵公式,误差传播公式等等
/*** IMU 预积分器** 调用Integrate来插入新的IMU读数,然后通过Get函数得到预积分的值* 雅可比也可以通过本类获得,可用于构建g2o的边类*/
class IMUPreintegration {public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW/// 参数配置项/// 初始的零偏需要设置,其他可以不改struct Options {Options() {}Vec3d init_bg_ = Vec3d::Zero(); // 初始零偏Vec3d init_ba_ = Vec3d::Zero(); // 初始零偏double noise_gyro_ = 1e-2; // 陀螺噪声,标准差double noise_acce_ = 1e-1; // 加计噪声,标准差};IMUPreintegration(Options options = Options());/*** 插入新的IMU数据* @param imu imu 读数* @param dt 时间差*/void Integrate(const IMU &imu, double dt);/*** 从某个起始点开始预测积分之后的状态* @param start 起始时时刻状态* @return 预测的状态*/NavStated Predict(const NavStated &start, const Vec3d &grav = Vec3d(0, 0, -9.81)) const;/// 获取修正之后的观测量,bias可以与预积分时期的不同,会有一阶修正SO3 GetDeltaRotation(const Vec3d &bg);Vec3d GetDeltaVelocity(const Vec3d &bg, const Vec3d &ba);Vec3d GetDeltaPosition(const Vec3d &bg, const Vec3d &ba);public:double dt_ = 0; // 整体预积分时间Mat9d cov_ = Mat9d::Zero(); // 累计噪声矩阵Mat6d noise_gyro_acce_ = Mat6d::Zero(); // 测量噪声矩阵// 零偏Vec3d bg_ = Vec3d::Zero();Vec3d ba_ = Vec3d::Zero();// 预积分观测量SO3 dR_;Vec3d dv_ = Vec3d::Zero();Vec3d dp_ = Vec3d::Zero();// 雅可比矩阵Mat3d dR_dbg_ = Mat3d::Zero();Mat3d dV_dbg_ = Mat3d::Zero();Mat3d dV_dba_ = Mat3d::Zero();Mat3d dP_dbg_ = Mat3d::Zero();Mat3d dP_dba_ = Mat3d::Zero();
};