Livox Lidar 特征提取方式总结

2023-05-16

传统的 旋转式雷达,激光固定在雷达的旋转部件上, 依靠转子的转动而获得360的旋转视野,由于旋转部件的存在,为了获得更加精准的数据,需要跟多的人工校准和更复杂的设计,也因此推高了其价格。 不过因为很直观的数据方式,所以 edge 特征和 plane 特征也比较直观。

Livox 雷达推出的特有的非重复扫描方式,使得特征提取变得不那么直观,而且LIvox 推出的自由的数据结构方式,加上ROS原有的 Pointcloud2 使得很多开源的SLAM 算法只支持一种数据格式, 导致很多辛辛苦苦采集的数据不能直接使用。

接下来就自己总结一下 Livox 激光雷达的一些特征

开源SLAM算法

Horizon 发布时间较早, 相关算法支持也最好。

  • LIO-Livox : Lidar-Inertial Odometry, 使用了内置的 6 轴IMU, 目前只支持 horizon 雷达, 雷达数据结构只支持 livox_ros_driver/CustomMsg. LINK
  • Livox-mapping: 仅使用激光雷达的建图包, 同时支持了 Mid 系列和 horizon 系统的扫描方式, 但Horizon 数据结构还是必须为 livox_ros_driver/CustomMsg, Mid 系列的数据类型为 sensor_msgs::PointCloud。LINK
  • hku-mars/loam_livox: Lidar only 的建图包, 只支持 Mid 系列(sensor_msgs::PointCloud2) 格式的数据。 LINK
  • BALM ,使用了bundle adjustment 的仅使用激光的建图包, 同时支持三种, horizon 支持 livox_ros_driver/CustomMsg 格式,Mid 系列的数据类型为 sensor_msgs::PointCloud, 也提供了velodyne 激光的 sensor_msgs::PointCloud 格式。link
  • KIT-ISAS/lili-om: LINK LiDAR-Inertial Odometry, 但此处没有使用内置的IMU, 而是使用的的 9 轴 Xsens MTi-670 (200 Hz) IMU, Livox雷达内置的是一个 6 轴激光雷达, 支持Horizon 和 Velodyne 雷达 LINK

Horzion 特征提取

目前能找到的开源算法中, 都只支持 livox_ros_driver/CustomMsg 数据格式, 其内容为:
link

## livox_ros_driver/CustomMsg
# Livox publish pointcloud msg format.
Header header             # ROS standard message header
uint64 timebase           # The time of first point
uint32 point_num          # Total number of pointclouds
uint8  lidar_id           # Lidar device id number
uint8[3]  rsvd            # Reserved use
CustomPoint[] points      # Pointcloud data

## livox_ros_driver/CustomPoint
# Livox costom pointcloud format.
uint32 offset_time      # offset time relative to the base time
float32 x               # X axis, unit:m
float32 y               # Y axis, unit:m
float32 z               # Z axis, unit:m
uint8 reflectivity      # reflectivity, 0~255
uint8 tag               # livox tag
uint8 line              # laser number in lidar

其特点是, 我们能够根据 激光数据的 timebase 和每个激光数据点的 offset_time 推出每个点的时间先后顺序,同时此数据结构也提供了 每个点所在的激光序号, Horizon中有五个激光束。

以官方的Livox_mapping 为例:
首先其使用 livox_repub.cpp 文件,将 livox livox_ros_driver::CustomMsg 数据转换成 sensor_msgs::PointCloud2 数据结构:

ros::Subscriber sub_livox_msg1 = nh.subscribe<livox_ros_driver::CustomMsg>(
      "/livox/lidar", 100, LivoxMsgCbk1);
pub_pcl_out1 = nh.advertise<sensor_msgs::PointCloud2>("/livox_pcl0", 100);

ros::Publisher pub_pcl_out0, pub_pcl_out1;
uint64_t TO_MERGE_CNT = 1; 
constexpr bool b_dbg_line = false;
std::vector<livox_ros_driver::CustomMsgConstPtr> livox_data;

void LivoxMsgCbk1(const livox_ros_driver::CustomMsgConstPtr& livox_msg_in) {
  livox_data.push_back(livox_msg_in);
  if (livox_data.size() < TO_MERGE_CNT) return;

  pcl::PointCloud<PointType> pcl_in;

  for (size_t j = 0; j < livox_data.size(); j++) {
    auto& livox_msg = livox_data[j];
    auto time_end = livox_msg->points.back().offset_time;
    for (unsigned int i = 0; i < livox_msg->point_num; ++i) {
      PointType pt;
      pt.x = livox_msg->points[i].x;
      pt.y = livox_msg->points[i].y;
      pt.z = livox_msg->points[i].z;
      float s = livox_msg->points[i].offset_time / (float)time_end;

      pt.intensity = livox_msg->points[i].line +livox_msg->points[i].reflectivity /10000.0 ; // The integer part is line number and the decimal part is timestamp
      pt.curvature = s*0.1;
      pcl_in.push_back(pt);
    }
  }

  unsigned long timebase_ns = livox_data[0]->timebase;
  ros::Time timestamp;
  timestamp.fromNSec(timebase_ns);

  sensor_msgs::PointCloud2 pcl_ros_msg;
  pcl::toROSMsg(pcl_in, pcl_ros_msg);
  pcl_ros_msg.header.stamp.fromNSec(timebase_ns);
  pcl_ros_msg.header.frame_id = "/livox";
  pub_pcl_out1.publish(pcl_ros_msg);
  livox_data.clear();
}

其中主要部分是

auto time_end = livox_msg->points.back().offset_time;

PointType pt;
pt.x = livox_msg->points[i].x;
pt.y = livox_msg->points[i].y;
pt.z = livox_msg->points[i].z;
float s = livox_msg->points[i].offset_time / (float)time_end;

pt.intensity = livox_msg->points[i].line +livox_msg->points[i].reflectivity /10000.0 ; // The integer part is line number and the decimal part is timestamp
pt.curvature = s*0.1; 
pcl_in.push_back(pt);

其中在这里记录了currature , 是该点在总时间段的比例, 并将其放在 curvature 字段。在 intensity 字段放的 line number 和 反射率。

在 scanRegistration_horizon.cpp 中, 其定义了 horizon的特征提取的方式。

平面点(plane)的提取

    float depth = sqrt(laserCloud->points[i].x * laserCloud->points[i].x +
                       laserCloud->points[i].y * laserCloud->points[i].y +
                       laserCloud->points[i].z * laserCloud->points[i].z);

    // if(depth < 2) depth_threshold = 0.05;
    // if(depth > 30) depth_threshold = 0.1;
    //left curvature
    float ldiffX = 
                laserCloud->points[i - 4].x + laserCloud->points[i - 3].x
                - 4 * laserCloud->points[i - 2].x
                + laserCloud->points[i - 1].x + laserCloud->points[i].x;

    float ldiffY = 
                laserCloud->points[i - 4].y + laserCloud->points[i - 3].y
                - 4 * laserCloud->points[i - 2].y
                + laserCloud->points[i - 1].y + laserCloud->points[i].y;

    float ldiffZ = 
                laserCloud->points[i - 4].z + laserCloud->points[i - 3].z
                - 4 * laserCloud->points[i - 2].z
                + laserCloud->points[i - 1].z + laserCloud->points[i].z;

    float left_curvature = ldiffX * ldiffX + ldiffY * ldiffY + ldiffZ * ldiffZ;
    
 if(left_curvature < 0.01){

      std::vector<PointType> left_list;

      for(int j = -4; j < 0; j++){
        left_list.push_back(laserCloud->points[i+j]);
      }

      if( left_curvature < 0.001) CloudFeatureFlag[i-2] = 1; //surf point flag  && plane_judge(left_list,1000) 
      
      left_surf_flag = true;
    }
    else{
      left_surf_flag = false;
    }

There is same process for right curve

角点Edge特征提取

//surf-surf corner feature
    if(left_surf_flag && right_surf_flag){
     debugnum4 ++;

     Eigen::Vector3d norm_left(0,0,0);
     Eigen::Vector3d norm_right(0,0,0);
     for(int k = 1;k<5;k++){
         Eigen::Vector3d tmp = Eigen::Vector3d(laserCloud->points[i-k].x-laserCloud->points[i].x,
                            laserCloud->points[i-k].y-laserCloud->points[i].y,
                            laserCloud->points[i-k].z-laserCloud->points[i].z);
        tmp.normalize();
        norm_left += (k/10.0)* tmp;
     }
     for(int k = 1;k<5;k++){
         Eigen::Vector3d tmp = Eigen::Vector3d(laserCloud->points[i+k].x-laserCloud->points[i].x,
                            laserCloud->points[i+k].y-laserCloud->points[i].y,
                            laserCloud->points[i+k].z-laserCloud->points[i].z);
        tmp.normalize();
        norm_right += (k/10.0)* tmp;
     }

      //calculate the angle between this group and the previous group
      double cc = fabs( norm_left.dot(norm_right) / (norm_left.norm()*norm_right.norm()) );
      //calculate the maximum distance, the distance cannot be too small
      Eigen::Vector3d last_tmp = Eigen::Vector3d(laserCloud->points[i-4].x-laserCloud->points[i].x,
                                                 laserCloud->points[i-4].y-laserCloud->points[i].y,
                                                 laserCloud->points[i-4].z-laserCloud->points[i].z);
      Eigen::Vector3d current_tmp = Eigen::Vector3d(laserCloud->points[i+4].x-laserCloud->points[i].x,
                                                    laserCloud->points[i+4].y-laserCloud->points[i].y,
                                                    laserCloud->points[i+4].z-laserCloud->points[i].z);
      double last_dis = last_tmp.norm();
      double current_dis = current_tmp.norm();

      if(cc < 0.5 && last_dis > 0.05 && current_dis > 0.05 ){ //
        debugnum5 ++;
        CloudFeatureFlag[i] = 150;
      }

完成了 特征提取就是,mapping 的过程, 此部分和 LOAM 部分类似。

IMU对 Lidar 数据矫正

根据IMU 对数据进行矫正是十分有必要的, 依靠IMU 高频率的对角速度和线性加速度的测量 预测每一个点相对初始点的旋转误差和线性误差可以比较充分的纠正位姿:以下是一个纠正前后的对比实例:
在这里插入图片描述
可以看到由于运动扭曲的桌腿得到了恢复, 这对于特征的跟踪十分有帮助。

这里有一个 IMU integrator 对imu 数据进行 PreIntegration link

void IMUIntegrator::GyroIntegration(double lastTime){
  double current_time = lastTime;
  for(auto & imu : vimuMsg){
    Eigen::Vector3d gyr;
    gyr << imu->angular_velocity.x,
            imu->angular_velocity.y,
            imu->angular_velocity.z;
    double dt = imu->header.stamp.toSec() - current_time;
    ROS_ASSERT(dt >= 0);
    // 将旋转角速度转换为 旋转矩阵
    Eigen::Matrix3d dR = Sophus::SO3d::exp(gyr*dt).matrix();
    // 得到此时的 orientation
    Eigen::Quaterniond qr(dq*dR);
    if (qr.w()<0)
      qr.coeffs() *= -1;
    // 正则化, 得到(积分后的)旋转矩阵
    dq = qr.normalized();
    current_time = imu->header.stamp.toSec();
  }
}
void IMUIntegrator::PreIntegration(double lastTime, const Eigen::Vector3d& bg, const Eigen::Vector3d& ba){
  Reset();
  linearized_bg = bg;
  linearized_ba = ba;
  double current_time = lastTime;
  for(auto & imu : vimuMsg){
    Eigen::Vector3d gyr;
    gyr <<  imu->angular_velocity.x,
            imu->angular_velocity.y,
            imu->angular_velocity.z;
    Eigen::Vector3d acc;
    acc << imu->linear_acceleration.x * gnorm,
            imu->linear_acceleration.y * gnorm,
            imu->linear_acceleration.z * gnorm;
    double dt = imu->header.stamp.toSec() - current_time;
    if(dt <= 0 )
      ROS_WARN("dt <= 0");
    gyr -= bg;
    acc -= ba;
    double dt2 = dt*dt;
    Eigen::Vector3d gyr_dt = gyr*dt;
    
    // 将小量李代数 转化到李群
    Eigen::Matrix3d dR = Sophus::SO3d::exp(gyr_dt).matrix();
    Eigen::Matrix3d Jr = Eigen::Matrix3d::Identity();
    double gyr_dt_norm = gyr_dt.norm(); // Return to the suqarnorm
    if(gyr_dt_norm > 0.00001){
      Eigen::Vector3d k = gyr_dt.normalized();
      Eigen::Matrix3d K = Sophus::SO3d::hat(k); //李代数到反对称矩阵
      Jr =   Eigen::Matrix3d::Identity()
             - (1-cos(gyr_dt_norm))/gyr_dt_norm*K
             + (1-sin(gyr_dt_norm)/gyr_dt_norm)*K*K;
    }
    Eigen::Matrix<double,15,15> A = Eigen::Matrix<double,15,15>::Identity();
    A.block<3,3>(0,3) = -0.5*dq.matrix()*Sophus::SO3d::hat(acc)*dt2;
    A.block<3,3>(0,6) = Eigen::Matrix3d::Identity()*dt;
    A.block<3,3>(0,12) = -0.5*dq.matrix()*dt2;
    A.block<3,3>(3,3) = dR.transpose();
    A.block<3,3>(3,9) = - Jr*dt;
    A.block<3,3>(6,3) = -dq.matrix()*Sophus::SO3d::hat(acc)*dt;
    A.block<3,3>(6,12) = -dq.matrix()*dt;
    Eigen::Matrix<double,15,12> B = Eigen::Matrix<double,15,12>::Zero();
    B.block<3,3>(0,3) = 0.5*dq.matrix()*dt2;
    B.block<3,3>(3,0) = Jr*dt;
    B.block<3,3>(6,3) = dq.matrix()*dt;
    B.block<3,3>(9,6) = Eigen::Matrix3d::Identity()*dt;
    B.block<3,3>(12,9) = Eigen::Matrix3d::Identity()*dt;
    jacobian = A * jacobian;
    covariance = A * covariance * A.transpose() + B * noise * B.transpose();
    dp += dv*dt + 0.5*dq.matrix()*acc*dt2;
    dv += dq.matrix()*acc*dt;
    Eigen::Matrix3d m3dR = dq.matrix()*dR;
    Eigen::Quaterniond qtmp(m3dR);
    if (qtmp.w()<0)
      qtmp.coeffs() *= -1;
    dq = qtmp.normalized();
    dtime += dt;
    current_time = imu->header.stamp.toSec();
  }
}
    boost::shared_ptr<std::list<Estimator::LidarFrame>> lidar_list;
	    if(!vimuMsg.empty()){
	    	if(!LidarIMUInited) {
	    		// if get IMU msg successfully, use gyro integration to update delta_Rl
			    lidarFrame.imuIntegrator.PushIMUMsg(vimuMsg);
			    lidarFrame.imuIntegrator.GyroIntegration(time_last_lidar);
			    delta_Rb = lidarFrame.imuIntegrator.GetDeltaQ().toRotationMatrix();
			    delta_Rl = exTlb.topLeftCorner(3, 3) * delta_Rb * exTlb.topLeftCorner(3, 3).transpose();

			    // predict current lidar pose
			    lidarFrame.P = transformAftMapped.topLeftCorner(3,3) * delta_tb
			                   + transformAftMapped.topRightCorner(3,1);
			    Eigen::Matrix3d m3d = transformAftMapped.topLeftCorner(3,3) * delta_Rb;
			    lidarFrame.Q = m3d;

			    lidar_list.reset(new std::list<Estimator::LidarFrame>);
			    lidar_list->push_back(lidarFrame);
		    }else{
			    // if get IMU msg successfully, use pre-integration to update delta lidar pose
			    lidarFrame.imuIntegrator.PushIMUMsg(vimuMsg);
			    lidarFrame.imuIntegrator.PreIntegration(lidarFrameList->back().timeStamp, lidarFrameList->back().bg, lidarFrameList->back().ba);

			    const Eigen::Vector3d& Pwbpre = lidarFrameList->back().P;
			    const Eigen::Quaterniond& Qwbpre = lidarFrameList->back().Q;
			    const Eigen::Vector3d& Vwbpre = lidarFrameList->back().V;

			    const Eigen::Quaterniond& dQ =  lidarFrame.imuIntegrator.GetDeltaQ();
			    const Eigen::Vector3d& dP = lidarFrame.imuIntegrator.GetDeltaP();
			    const Eigen::Vector3d& dV = lidarFrame.imuIntegrator.GetDeltaV();
			    double dt = lidarFrame.imuIntegrator.GetDeltaTime();

			    lidarFrame.Q = Qwbpre * dQ;
			    lidarFrame.P = Pwbpre + Vwbpre*dt + 0.5*GravityVector*dt*dt + Qwbpre*(dP);
			    lidarFrame.V = Vwbpre + GravityVector*dt + Qwbpre*(dV);
			    lidarFrame.bg = lidarFrameList->back().bg;
			    lidarFrame.ba = lidarFrameList->back().ba;

			    Eigen::Quaterniond Qwlpre = Qwbpre * Eigen::Quaterniond(exRbl);
			    Eigen::Vector3d Pwlpre = Qwbpre * exPbl + Pwbpre;

			    Eigen::Quaterniond Qwl = lidarFrame.Q * Eigen::Quaterniond(exRbl);
			    Eigen::Vector3d Pwl = lidarFrame.Q * exPbl + lidarFrame.P;

			    delta_Rl = Qwlpre.conjugate() * Qwl;
			    delta_tl = Qwlpre.conjugate() * (Pwl - Pwlpre);
			    delta_Rb = dQ.toRotationMatrix();
			    delta_tb = dP;

			    lidarFrameList->push_back(lidarFrame);
			    lidarFrameList->pop_front();
			    lidar_list = lidarFrameList;
	    	}
	    }else{
	    	if(LidarIMUInited)
	    	  break;
	    	else{
			    // predict current lidar pose
			    lidarFrame.P = transformAftMapped.topLeftCorner(3,3) * delta_tb
			                   + transformAftMapped.topRightCorner(3,1);
			    Eigen::Matrix3d m3d = transformAftMapped.topLeftCorner(3,3) * delta_Rb;
			    lidarFrame.Q = m3d;

			    lidar_list.reset(new std::list<Estimator::LidarFrame>);
			    lidar_list->push_back(lidarFrame);
	    	}
	    }

	    // remove lidar distortion 矫正运动
	    RemoveLidarDistortion(laserCloudFullRes, delta_Rl, delta_tl);
void RemoveLidarDistortion(pcl::PointCloud<PointType>::Ptr& cloud,
                           const Eigen::Matrix3d& dRlc, const Eigen::Vector3d& dtlc){
  int PointsNum = cloud->points.size();
  for (int i = 0; i < PointsNum; i++) {
    Eigen::Vector3d startP;
    float s = cloud->points[i].normal_x;
    Eigen::Quaterniond qlc = Eigen::Quaterniond(dRlc).normalized();
    Eigen::Quaterniond delta_qlc = Eigen::Quaterniond::Identity().slerp(s, qlc).normalized();
    const Eigen::Vector3d delta_Plc = s * dtlc;
    startP = delta_qlc * Eigen::Vector3d(cloud->points[i].x,cloud->points[i].y,cloud->points[i].z) + delta_Plc;
    Eigen::Vector3d _po = dRlc.transpose() * (startP - dtlc);

    cloud->points[i].x = _po(0);
    cloud->points[i].y = _po(1);
    cloud->points[i].z = _po(2);
    cloud->points[i].normal_x = 1.0;
  }
}

IMU - GTSAM 中的使用

//北向角度、东向角度、地向角度,姿态w,姿态x,姿态y,姿态z,北向速度,东向速度,地向速度
//N,E,D,qx,qy,qz,qw,VelN,VelE,VelD
//i:表示初始化
i,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0
0,0.000100,0.0,0.0,0.0,0.0,0.0
1,0.000000,0.0,0.0,0.0,0.0,0.0,1.0
0,0.000200,0.0,0.0,0.0,0.0,0.0
0,0.000300,0.0,0.0,0.0,0.0,0.0
0,0.000400,0.0,0.0,0.0,0.0,0.0
0,0.000500,0.0,0.0,0.0,0.0,0.0


#include <gtsam/navigation/CombinedImuFactor.h>
#include <gtsam/navigation/GPSFactor.h>
#include <gtsam/navigation/ImuFactor.h>
#include <gtsam/slam/dataset.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/slam/dataset.h>
#include <gtsam/nonlinear/LevenbergMarquardtParams.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
#include <gtsam/inference/Symbol.h>
#include <fstream>
#include <iostream>

using namespace std;
using namespace gtsam;
using symbol_shorthand::X;//用作表示    姿态(x,y,z,r,p,y)
using symbol_shorthand::V;//用表示      速度导数(xdot,ydot,zdot)
using symbol_shorthand::B;//陀螺仪残差  (ax,ay,az,gx,gy,gz)
string inputfile="/home/n1/notes/gtsam/Imu/imuAndGPSdata.csv";
string outputfile="/home/n1/notes/gtsam/Imu/result1.csv";
PreintegrationType *imu_preintegrated_;
//    Matrix3 biasAccCovariance;     3*3矩阵 加速度计的协防差矩阵,(可以根据残差计算加速度雅克比矩阵逐步更新)
//    Matrix3 biasOmegaCovariance;   3*3矩阵 陀螺仪的协防差矩阵, (可以根据残差计算雅克比矩阵递归更新预计分值,防止从头计算)
//    Matrix6 biasAccOmegaInt;       6*6矩阵 位置残关于加速度和速度残差的协防差,用作更新预计分
int main(int argc, const char** argv) {
    FILE* fp=fopen(outputfile.c_str(),"w+");
    //输出
    fprintf(fp,"#time(s),x(m),y(m),z(m),qx,qy,qz,qw,gt_x(m),gt_y(m),gt_z(m),gt_qx,gt_qy,gt_qz,gt_qw\n");


    //解析 CSV
    ifstream file(inputfile.c_str());
    string value;

    Eigen::Matrix<double,10,1> initial_state=Eigen::Matrix<double,10,1>::Zero();
    N,E,D,qx,qy,qz,qw,VelN,VelE,VelD
    getline(file,value,',');
    for(int i=0;i<9;i++){
        getline(file,value,',');
        initial_state(i)=atof(value.c_str());//转为浮点型
    }
    getline(file,value,'\n');//换行
    initial_state(9) = atof(value.c_str());
    Rot3 prior_rotation=Rot3::Quaternion(initial_state(6),initial_state(3),initial_state(4),initial_state(5));
    Point3 prior_point(initial_state(0),initial_state(1),initial_state(2));
    Pose3 prior_pose(prior_rotation,prior_point);                               //初始位姿
    Vector3 prior_velocity(initial_state(7),initial_state(8),initial_state(9)); //初始速度
    imuBias::ConstantBias prior_imu_bias;//残差,默认设为0

    //初始化值
    Values initial_values;
    int correction_count=0;
    //位姿
    initial_values.insert(X(correction_count),prior_pose);
    //速度
    initial_values.insert(V(correction_count),prior_velocity);
    //残差
    initial_values.insert(B(correction_count),prior_imu_bias);
    cout << "initial state:\n" << initial_state.transpose() <<endl;
    //设置噪声模型
    //一般为设置为对角噪声
    noiseModel::Diagonal::shared_ptr pose_noise_model=noiseModel::Diagonal::Sigmas(Vector6(0.01,0.01,0.01,0.5,0.5,0.5));
    noiseModel::Diagonal::shared_ptr velocity_noise_model=noiseModel::Isotropic::Sigma(3,0.1);
    noiseModel::Diagonal::shared_ptr bias_noise_model=noiseModel::Isotropic::Sigma(6,0.001);
    NonlinearFactorGraph *graph = new NonlinearFactorGraph();
    graph->add(PriorFactor<Pose3>(X(correction_count), prior_pose, pose_noise_model));
    graph->add(PriorFactor<Vector3>(V(correction_count), prior_velocity,velocity_noise_model));
    graph->add(PriorFactor<imuBias::ConstantBias>(B(correction_count), prior_imu_bias,bias_noise_model));
    //使用传感器信息构建IMU的噪声模型
    double accel_noise_sigma = 0.0003924;
    double gyro_noise_sigma = 0.000205689024915;
    double accel_bias_rw_sigma = 0.004905;
    double gyro_bias_rw_sigma = 0.000001454441043;

    Matrix33 measured_acc_cov=Matrix33::Identity(3,3)*pow(accel_noise_sigma,2);
    Matrix33 measured_omega_cov=Matrix33::Identity(3,3)*pow(gyro_bias_rw_sigma,2);
    Matrix33 integration_error_cov=Matrix33::Identity(3,3)*1e-8;        //速度积分误差
    Matrix33 bias_acc_cov=Matrix33::Identity(3,3)*pow(accel_bias_rw_sigma,2);
    Matrix33 bias_omega_cov=Matrix33::Identity(3,3)*pow(gyro_bias_rw_sigma,2);
    Matrix66 bias_acc_omega_int=Matrix66::Identity(6,6)*1e-5;           //积分骗到误差

    boost::shared_ptr<PreintegratedCombinedMeasurements::Params> p=PreintegratedCombinedMeasurements::Params::MakeSharedD(0.0);
    //MakeSharedD:NED坐标系,g默认为 9.81,这里设置为0
    //MakeSharedU:NEU坐标系,g默认为 9.81
    
    //设置预积分分参数
    p->accelerometerCovariance=measured_acc_cov;
    p->integrationCovariance=integration_error_cov;
    p->gyroscopeCovariance=measured_omega_cov;

    //预计分测量值
    p->biasAccCovariance=bias_acc_cov;
    p->biasAccOmegaInt=bias_acc_omega_int;
    p->biasOmegaCovariance=bias_omega_cov;
#ifdef USE_COMBINED
  imu_preintegrated_ = new PreintegratedCombinedMeasurements(p, prior_imu_bias);
#else
  imu_preintegrated_ = new PreintegratedImuMeasurements(p, prior_imu_bias);
#endif
    //保存上一次的imu积分值和结果
    NavState prev_state(prior_pose,prior_velocity);
    NavState prop_state=prev_state;
    imuBias::ConstantBias prev_bias=prior_imu_bias;//

    //记录总体误差
    double current_position_error = 0.0, current_orientation_error = 0.0;

    double output_time=0;
    double dt=0.005;    //积分时间

    //使用数据进行迭代
    while(file.good()){
        getline(file,value,',');
        int type=atoi(value.c_str());//字符转为整形
        if (type == 0) { // IMU 测量数据
            Eigen::Matrix<double,6,1> imu = Eigen::Matrix<double,6,1>::Zero();
            //读取imu数据
            for (int i=0; i<5; ++i) {
                getline(file, value, ',');
                imu(i) = atof(value.c_str());
            }
            getline(file, value, '\n');
            imu(5) = atof(value.c_str());
            // 检测测量值加入预计分
            imu_preintegrated_->integrateMeasurement(imu.head<3>(), imu.tail<3>(), dt);

        }else if(type ==1){//Gps测量数据
            Eigen::Matrix<double,7,1> gps=Eigen::Matrix<double,7,1>::Zero();
            for(int i=0;i<6;i++){
                getline(file,value,',');
                gps(i)=atof(value.c_str());
            }
            getline(file, value, '\n');
            gps(6)=atof(value.c_str());
            correction_count++;
        
#ifdef USE_COMBINED
    //预计分测量值
        PreintegratedCombinedMeasurements *preint_imu_combined = dynamic_cast<PreintegratedCombinedMeasurements*>(imu_preintegrated_);
    //IMU 因子
    //typedef NoiseModelFactor6<Pose3, Vector3, Pose3, Vector3,imuBias::ConstantBias, imuBias::ConstantBias>
        CombinedImuFactor imu_factor(X(correction_count-1), V(correction_count-1),
                                   X(correction_count  ), V(correction_count  ),
                                   B(correction_count-1), B(correction_count  ),
                                   *preint_imu_combined);
        graph->add(imu_factor);
#else
    //预计分测量值
        PreintegratedImuMeasurements *preint_imu = dynamic_cast<PreintegratedImuMeasurements*>(imu_preintegrated_);
        ImuFactor imu_factor(X(correction_count-1), V(correction_count-1),
                           X(correction_count  ), V(correction_count  ),
                           B(correction_count-1),
                           *preint_imu);
        graph->add(imu_factor);
        imuBias::ConstantBias zero_bias(Vector3(0, 0, 0), Vector3(0, 0, 0));
        graph->add(BetweenFactor<imuBias::ConstantBias>(B(correction_count-1),
                                                      B(correction_count  ),
                                                      zero_bias, bias_noise_model));
#endif
        noiseModel::Diagonal::shared_ptr correction_noise=noiseModel::Isotropic::Sigma(3,1.0);
        GPSFactor gps_factor(X(correction_count),
                            Point3(gps(0),gps(1),gps(2)),//(N,E,D)
                            correction_noise);
        graph->add(gps_factor);
        //迭代更新求解imu预测值
        prop_state=imu_preintegrated_->predict(prev_state,prev_bias);
        initial_values.insert(X(correction_count), prop_state.pose());
        initial_values.insert(V(correction_count), prop_state.v());
        initial_values.insert(B(correction_count), prev_bias);
        //求解
        LevenbergMarquardtOptimizer optimizer(*graph,initial_values);
        Values result=optimizer.optimize();

        //更新下一步预计分初始值
        //导航状态
        prev_state=NavState(result.at<Pose3>(X(correction_count)),
                            result.at<Vector3>(V(correction_count)));
        //偏导数
        prev_bias=result.at<imuBias::ConstantBias>(B(correction_count));
        //更新预计分值
        imu_preintegrated_->resetIntegrationAndSetBias(prev_bias);
        
        //计算角度误差和误差
        Vector3 gtsam_position=prev_state.pose().translation();
        //位置误差
        Vector3 position_error=gtsam_position-gps.head<3>();
        //误差的范数
        current_position_error=position_error.norm();//归一化
        
        //姿态误差
        Quaternion gtsam_quat=prev_state.pose().rotation().toQuaternion();
        Quaternion gps_quat(gps(6),gps(3),gps(4),gps(5));
        Quaternion quat_error=gtsam_quat*gps_quat.inverse();
        quat_error.normalized();//归一化
        Vector3 euler_angle_error(quat_error.x()*2,quat_error.y()*2,quat_error.z()*2);//转换为欧拉角误差
        current_orientation_error=euler_angle_error.norm();

        //输出误差
        cout << "Position error:" << current_position_error << "\t " << "Angular error:" << current_orientation_error << "\n";
              fprintf(fp, "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n",
              output_time, gtsam_position(0), gtsam_position(1), gtsam_position(2),
              gtsam_quat.x(), gtsam_quat.y(), gtsam_quat.z(), gtsam_quat.w(),
              gps(0), gps(1), gps(2),
              gps_quat.x(), gps_quat.y(), gps_quat.z(), gps_quat.w());

        output_time += 1.0;
        }
        else{
            cerr << "ERROR parsing file\n";
            return 1;
        }
    }
    fclose(fp);
    cout << "完成,结果见:" <<outputfile  << endl;
    return 0;
}

IMU Preintegration

IMU 的预积分已经成为了标准操作。 但是目前使用livox的算法, 只用内置的 6 轴的 IMU 进行激光数据的矫正, 还没有直接使用IMU进行预积分处理, 也没有找到6轴IMU 预计分的代码结果用以对比。 因为9 轴 imu 能够通过 磁力计提供较为准确的 航向角, 而通过angular_veolcity 不可避免的拥有累计误差,所以9轴的确实比较直接快捷。 不知道读者怎么看.

参考资料:

  1. https://github.com/KIT-ISAS/lili-om
  2. https://github.com/smilefacehh/LIO-SAM-DetailedNote
  3. https://github.com/YuePanEdward/MULLS
  4. https://github.com/hyye/lio-mapping
  5. https://github.com/ChaoqinRobotics/LINS—LiDAR-inertial-SLAM
  6. https://github.com/Livox-SDK/LIO-Livox
  7. https://github.com/Livox-SDK/livox_mapping
  8. https://github.com/hku-mars/loam_livox
  9. https://github.com/hku-mars/BALM
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Livox Lidar 特征提取方式总结 的相关文章

  • 关于32位系统中int、float、short、double等占多少个字节

    我用VC编译的 xff0c 测试了一下 xff0c 代码如下 xff1a include 34 iostream 34 using namespace std int main cout lt lt sizeof char lt lt en
  • C语言中关于int、long int、long long 的区别

    1 关于int 和 long int xff08 1 xff09 在VC下没有区别 两种类型均用4个字节存放数据 xff08 2 xff09 VC是后出的编译器 xff0c 之前有很多早期的C编译器 xff0c 在早期编译器下long in
  • arp a命令的通俗解释

    ARP A 查询系统中缓存的ARP表 ARP表用来维护IP地址与MAC地址的一一对应 比方说 xff0c 某推拿室总有30名服务员 xff0c 为了方便管理 xff0c 服务员都编了号 xff0c 由01编到30 客人们都只记得服务员的编号
  • sqlite模糊查询与分页

    sqlite模糊查询与分页 64 param queryWords 要查询的关键词 xff08 中英文都可以 xff09 64 param curItem 当前查询到的item 所在位置 64 param sizeRequest 每次请求查
  • 设计模式-回调函数和观察者模式

    Android的框架中有非常多的地方使用了回调的模式 xff0c 例如Activity的生命周期 xff0c 按钮的点击事件等 下面是回调的基本模型 xff1a java view plain copy public class A pri
  • QT之表格控件

    目录 一 xff1a 表格控件 1 QTableWidget表格的创建 1 QTableWidgetItem单元格 2 表格的信号和槽 3 代码引例 1 在 h文件中定义 2 在main文件中创建并实现 4 结果演示 GIF 一 xff1a
  • sequlize统一格式化时间

    数据查出来的数据格式 xff0c 如图 xff1a 自动格式化时间 xff0c 需要在数据库配置上加上 dateStrings true
  • Node 微信支付 “由于商家传入的H5交易参数有误 该笔交易暂时无法完成 请联系商家解决”

    之前稳定运行一段时间的微信H5支付 xff0c 突然报错 xff1a 由于商家传入的H5交易参数有误 该笔交易暂时无法完成 请联系商家解决 经过多方排查 xff0c 最终确定是微信下单的ip传递有误 上传的ip为 127 0 0 1 xff
  • css data:image/svg+xml 不显示

    原因 xff1a 新版chrome不支持 需要改成 23 如 xff1a test span class token punctuation span content url span class token punctuation spa
  • border-image属性分析

    border image是CSS3的一个属性 xff0c 由于比较复杂 xff0c 总是处于一知半解的状态 xff0c 今天下定决心 xff0c 花时间整理了一下 xff0c 供大家共勉和学习 border image的用处 没用borde
  • 纯CSS绘制箭头

    这几天研究别人的网站 xff0c 发现别人的箭头居然是用纯CSS写的 瞬间觉得高大上 细细研究其中的原理 xff0c 发现其实也很简单 CSS绘制三角形 绘制箭头 xff0c 首先应该懂得如何用纯CSS绘制三角形 我们先做一个宽高为0 xf
  • 如何更新npm至最新版本

    去年曾遇到过执行npm某项命令时提示要求更高版本的npm xff0c 当时百度解决了 xff0c 今天又再次遇到该问题 现记下来 xff0c 以供以后参考 命令行运行 xff1a span class hljs built in npm s
  • .*? 和 .*的区别

    在看js代码的时候 xff0c 有时候会遇到 这样的写法 xff0c 那么连续两个限制符是什么意思呢 xff1f 我们先来回顾一下正则表达式的基本知识 基本语法 符号用法 匹配除 r n 之外的任何单个字符 要匹配包括 r n 在内的任何字
  • 微信清除缓存的两种方法

    网址清除 微信浏览器打开网址 http debugx5 qq com xff0c 勾选如下设置 xff1a 点击 清除 按钮即可 代码清除 如果是自己写的网页 xff0c 在调试阶段希望不要缓存 xff0c 可以在html页面的head中加
  • PHP时间戳和日期转换

    时间戳转日期 date date format timestamp format 必需 规定时间戳的格式 timestamp 可选 规定时间戳 默认是当前时间和日期 例如 xff1a date Y m d H i s 具体时间戳 Y xff
  • 微信H5支付:网络环境未能通过安全验证,请稍后再试

    最近在开发微信H5支付 xff0c 并且已应用到某个网站进行支付 但奇怪的是 xff0c 一模一样的支付代码 xff0c 换了一个网站 xff0c 竟不能支付了 出现如图错误提示 xff1a 百度一圈 xff0c 均是说IP传得不对 微信s
  • 如何比较字符串大小

    今天我分享的是如何比较字符串的大小 xff0c 希望大家看完能有深刻的理解 字符比较 xff08 character comparison xff09 是指按照字典次序对单个字符或字符串进行比较大小的操作 xff0c 一般都是以ASCII码
  • node异步用await和不用await的区别

    最近在用node写项目 新版node异步用的是async await这两个关键字 我们都知道 xff0c 一般这两个关键字要成对出现 但是 xff0c 笔者发现 xff0c 如果不需要等待返回值的话 xff0c await可以不加 那么aw
  • NtripShare Cloud差分数据共享云平台

    RTK差分数据共享猫已更新至1 6 2版本 xff0c APP运行十分稳定 高级版本服务器已升级至华为云 xff0c 独享10M带宽 xff0c 进一步降低数据延迟 近期准备升级普通版服务器 xff0c 升级后将停止对1 6 0版本之前的不
  • RTK差分共享猫APP后台系统已开源

    RTK差分共享猫APP后台系统开源 xff0c 开源地址https gitee com forgy NtripShareCatServer 后台系统基于GUNS 6 0 xff08 https gitee com stylefeng gun

随机推荐

  • RTK差分数据网络播发软件

    解决短基线范围内多台设备同时作业 xff0c CORS差分账号资源不足的问题 基于串口或模拟网络RTK请求 xff0c 将实时差分数据进行转发 xff0c 支持Ntrip协议 xff0c 支持市面所有网络RTK系统 个人作品 xff0c 不
  • STM32使用cubemx的uart空闲中断接收不定长度数据

    void USART3 IRQHandler void USER CODE BEGIN USART3 IRQn 0 USER CODE END USART3 IRQn 0 HAL UART IRQHandler amp huart3 USE
  • 基于ROS平台的移动机器人-8-使用Kinect2导航

    基于ROS平台的移动机器人 8 使用Kinect2导航 ready 终于到写最后一篇了 不是经常写博文的老司机果然伤不起 xff01 在这一篇教程就是利用KinectV2来导航啦 go 1 安装一下所需的包 xff08 1 xff09 cd
  • kali linux学习——安装WingIDE(libqt4-webkit软件依靠问题)

    kali linux 中安装wingide xff08 libqt4 webkit软件依靠问题 xff09 走过的坑缺失的libqt4 webkit成功安装WingIDE 走过的坑 在kali linux上利用命令 dpkg i wingi
  • 华为的OD,值得去吗?

    最近有不少小伙伴接到了华为OD的面试邀约 xff0c 但搞不清楚OD到底怎么回事儿 xff0c 要不要去 所以今天来说说华为的OD到底是怎么回事儿 xff0c 怎么判断是否值得去 1 华为的OD是怎么回事儿 OD xff0c 是Outsou
  • 第01课:技术成长的三阶段模型

    引言 作为整个系统课程的第一部分 xff0c 我想先跟大家分享的是如何选择技术方向 xff0c 我将结合技术成长的三阶段模型 xff0c 讨论在入行 构建技能树 技术转型 团队技术方案选型等常见场景中如何选择适合自己的技术 努力只有在方向正
  • 开篇词 | 程序员的成长课

    大家好 xff0c 我是安晓辉 xff0c 做过开发工程师 研发经理 技术总监等岗位 xff0c 现在自由职业 xff0c 专注写作和开发者生涯咨询 出版过 程序员的成长课 Qt Quick 核心编程 你好哇 xff0c 程序员 解忧程序员
  • const与define的区别

    1 define是预编译指令 xff0c const是普通变量的定义 xff0c define定义的宏是在预处理阶段展开的 xff0c 而const定义的只读变量是在编译运行阶段使用的 2 const定义的是变量 xff0c 而define
  • 如何摆脱CRUD等打杂状态,从事更高价值工作

    每个月都会有十来个来询者向我抱怨工作低端 xff0c 程序员说自己每天CRUD xff0c 重复 枯燥 没技术含量 xff0c 销售助理说自己天天搜集客户信息 打印资料 帮老大带饭 xff0c 繁琐 无聊 不重要 xff0c 他们都说自己整
  • Windows下Qt 5.2 for Android开发入门

    Qt 5 2 发布了 xff0c 支持 Android 平台 xff0c 太好了 之前公司项目 xff0c 为了移植一个依赖 Qt 的程序到安卓平台上 xff0c 我自己交叉编译了 Qt Embedded 4 5 2 xff0c 费了老大劲
  • Qt Quick 之 QML 与 C++ 混合编程详解

    Qt Quick 技术的引入 xff0c 使得你能够快速构建 UI xff0c 具有动画 各种绚丽效果的 UI 都不在话下 但它不是万能的 xff0c 也有很多局限性 xff0c 原来 Qt 的一些技术 xff0c 比如低阶的网络编程如 Q
  • 漫谈程序员系列:一张图道尽程序员的出路

    推背图 相传由唐太宗时期的司天监李淳风和袁天罡合著 xff08 此两人其实是超级武学高手 xff0c 参见小椴的 开唐 xff09 xff0c 推算大唐以后中国两千多年的国运盛衰 xff0c 在中国七大预言书中居首 xff0c 是当之无愧的
  • 漫谈程序员系列:咦,你也在混日子啊

    戳你一下 xff0c 疼吗 xff1f 混日子的定义 来自百度百科的定义 xff1a 生活等方面过得不怎么好 xff0c 无目标 xff0c 混混沌沌 混日子 xff1a 即没有理想 xff0c 没有抱负 xff0c 糊里糊涂地生活 也指工
  • QtAndroid详解(1):QAndroidJniObject

    Qt 5 3之后 xff0c 新增了 QtAndroid 名字空间 xff0c 内有下列四个方法 xff1a QAndroidJniObject AndroidActivity int androidSdkVersion void star
  • freeSWITCH安装、配置与局域网测试

    这次来说说 freeSWITCH 的安装和配置 1 安装 freeSWITCH 下载页面 xff1a https freeswitch org confluence display FREESWITCH Installation 我们 Wi
  • 就 3 点,提升工作效率

    要想提高工作效率 xff0c 不论你看什么书 xff0c 看什么文章 xff0c 用什么工具 xff0c 只有下面这三点最重要 xff1a 动力剖析自己 xff0c 找到改善的切入点付诸行动并且坚持 目标驱动 有目标才能高效 我们爬山 xf
  • Python3 下 ROS 的使用 cv_bridge

    Python 3 下 ROSmsg 转 cv2 项目中用到的 Tensorflow2 4 的环境 xff0c 该环境只支持python3 版本 xff0c 项目中遇到不少需要和 ROS 交互的地方 xff0c 所以不断探索 python3
  • 深度图和RGB图对齐

    深度图 canny RGB canny Alignment xff1a code span class token function import span cv2 span class token function import span
  • 认识romfs文件系统

    1 1 什么是romfs romfs 是一个只读文件系统 xff0c 主要用在 mainly for initial RAM disks of installation disks 使用romfs 文件系统可以构造出一个最小的内核 xff0
  • Livox Lidar 特征提取方式总结

    传统的 旋转式雷达 xff0c 激光固定在雷达的旋转部件上 xff0c 依靠转子的转动而获得360的旋转视野 xff0c 由于旋转部件的存在 xff0c 为了获得更加精准的数据 xff0c 需要跟多的人工校准和更复杂的设计 xff0c 也因