多传感器融合定位(4-基于滤波的2融合方法)2-使用仿真数据进行imu-gnss eskf和时变系统下的可观测性分析

本次作业摘自 张松鹏大哥的优秀作业
代码下载 https://github.com/rainbowrooster/Multi—sensor-fusion-and-positioning/tree/main/chapter4/Q2
下载说明:chapter4/Q2 中 , gnss-ins-sim 优秀代码为鹏哥自定义仿真场景,和使用gnss-ins-sim生成仿真IMU数据的py文件;ins里 ins.cpp 为 鹏哥的eskf 模型和 可观性分析源码,eskf_ins.cpp 为参鹏哥代码自打代码,包含自己理解的注解;matplolib 里 plot_data.m 为修改后适应matlab2016a的可视化代码,plot_data2.m为鹏哥的原可视化代码。

主要公式:

kalman 五大重要公式

姿态误差、速度误差、位置误差


eskf 状态方程


eskf 观测方程

离散时间滤波器


部分代码片 eskf_ins.cpp:

同步GPS 和 IMU 数据

在同步数据中,分为 未初始化传感器数据同步(IMU数据第一帧) 和 初始化传感器后的数据同步(多帧IMU数据) ;时间同步以gnss数据为准。

以gnss数据为同步基准

    /*以gnss时间为准*/if (gnss_buff.empty()){return false;}current_gnss = gnss_buff.front();   //读取当前gnss队列第一个数据double sync_time = current_gnss.time;

GroundTruth 时间 数据 同步

/*sync groundtruth 数据*/
//gt时间同步while (gt_buff.size() > 1){if(gt_buff[1].time < sync_time){gt_buff.pop_front();       //将不对齐的时间删除}else{break;}}
//gt数据同步
if(gt_buff.size() > 1)
{   PoseData front_data = gt_buff.at(0);    //上一帧数据PoseData back_data = gt_buff.at(1);double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time); double back_scale = (sync_time -front_data.time) /  (back_data.time - front_data.time);current_gt.time = sync_time ;//插值current_gt.state.p = front_data.state.p  * front_scale  + back_data.state.p *  back_scale;current_gt.state.v = front_data.state.v   * front_scale  + back_data.state.v *  back_scale;current_gt.state.q= front_data.state.q.slerp(front_scale,back_data.state.q);current_gt.state.bg = front_data.state.bg *  front_scale  + back_data.state.bg * back_scale;current_gt.state.ba = front_data.state.ba * front_scale   + back_data.state.ba * back_scale;
}else{return false;
}

IMU 时间 数据 同步

// imu 数据同步
if (imu_buff.size() > 1)
{/*IMU 未初始化的数据同步*/if (!inited){current_imu.clear();IMUData front_data = imu_buff.at(0);IMUData back_data = imu_buff.at(1);IMUData synced_data;double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time);double back_scale = (sync_time - front_data.time) / (back_data.time - front_data.time);synced_data.time = sync_time;synced_data.acc = front_data.acc * front_scale + back_data.acc * back_scale;synced_data.gyro = front_data.gyro * front_scale + back_data.gyro * back_scale;current_imu.push_back(synced_data);imu_buff.pop_front();gnss_buff.pop_front();// std::cout << std::setprecision(12) << "sync_time " << sync_time//           << " current_imu.time " << current_imu.front().time//           << "  " << current_imu.back().time << std::endl;return true;}if (imu_buff.back().time < sync_time){return false;}while (current_imu.size() > 1){current_imu.pop_front();}while (imu_buff.front().time < sync_time){IMUData temp = imu_buff.front();imu_buff.pop_front();current_imu.push_back(temp);     // 将新的IMU数据放在deque队列末尾}IMUData front_data = current_imu.back();IMUData back_data = imu_buff.at(0);      //取 刚好比 sync_time 大的imu 数据IMUData synced_data;//插值double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time);double back_scale = (sync_time - front_data.time) / (back_data.time - front_data.time);synced_data.time = sync_time;synced_data.acc = front_data.acc * front_scale  +  back_data.acc  * back_scale; synced_data.gyro = front_data.gyro * front_scale  + back_data.gyro * back_scale;current_imu.push_back(synced_data);gnss_buff.pop_front();return true;
}else{return false;}

传感器初始化

bool InitSensor()          //初始化 传感器
{while (!gnss_buff.empty()){if (imu_buff.front().time >  gnss_buff.front().time){gnss_buff.pop_front();       //删除时间不匹配的 gnss 数据,保证 imu的数据在前}else{return true;}}return  false;
}

位姿初始化

bool InitPose()
{static bool pose_inited = false;if (pose_inited){return true;}if (!SyncData(false)){return false;}current_pose.time = current_gt.time;current_pose.state.p = current_gt.state.p;current_pose.state.q = current_gt.state.q;current_pose.state.v = current_gt.state.v;current_pose.state.bg = current_gt.state.bg;current_pose.state.ba = current_gt.state.ba;current_error_state.x.setZero();current_error_state.p.setZero();current_error_state.p.block<3, 3>(0, 0) = Eigen::Matrix3d::Identity() * init_noise[0];current_error_state.p.block<3, 3>(3, 3) = Eigen::Matrix3d::Identity() * init_noise[1];current_error_state.p.block<3, 3>(6, 6) = Eigen::Matrix3d::Identity() * init_noise[2];current_error_state.p.block<3, 3>(9, 9) = Eigen::Matrix3d::Identity() * init_noise[3];current_error_state.p.block<3, 3>(12, 12) = Eigen::Matrix3d::Identity() * init_noise[4];pose_inited = true;return true;
}

基于误差的卡尔曼滤波

/*滤波*/
bool Filter()
{Predict();if (correct){Correct();}return true;
}

eskf 预测

bool Predict()  // 预测
{current_pose.time  = current_gt.time;Eigen::Vector3d  pp =  current_pose.state.p;Eigen::Vector3d  vv  =  current_pose.state.v;Eigen::Quaterniond qq = current_pose.state.q;double w = 7.27220521664304e-05;    // 地球自转速度Eigen::Vector3d gn(0, 0, -9.79484197226504);    // 重力加速度Eigen::Vector3d w_ie_n(0, w * std::cos(current_gnss.lla[0] * M_PI / 180),w * std::sin(current_gnss.lla[0] * M_PI / 180));double rm = 6353346.18315;       // 短半轴double rn = 6384140.52699;        // 长半轴Eigen::Vector3d w_en_n(-vv[1] / (rm + current_gnss.lla[2]),vv[0] / (rn + current_gnss.lla[2]),vv[0] / (rn + current_gnss.lla[2]) * std::tan(current_gnss.lla[0] * M_PI / 180));Eigen::Vector3d w_in_n = w_ie_n + w_en_n;for (int i = 1; i < current_imu.size(); ++i){double dt = current_imu[i].time - current_imu[i - 1].time;Eigen::Vector3d wtemp = w_in_n * dt;double angle = wtemp.norm();Eigen::Quaterniond qn(1, 0, 0, 0);if (angle != 0){wtemp = wtemp / angle;wtemp = std::sin(angle / 2) * wtemp;qn = Eigen::Quaterniond(std::cos(angle / 2), wtemp[0], wtemp[1], wtemp[2]);}qn.normalize();   // 地球自转的角增量Eigen::Vector3d wb = 0.5* current_imu[i - 1].gyro  + 0.5*current_imu[i].gyro;wb = wb + current_pose.state.bg;wb = wb * dt;angle = wb.norm();Eigen::Quaterniond qb(1,0,0,0);if (angle != 0){wb = wb / angle;wb = std::sin(angle / 2) * wb;qb = Eigen::Quaterniond(std::cos(angle / 2),wb[0],wb[1],wb[2] );}qb.normalize();     //载体角增量Eigen::Quaterniond  qq2 = qn.inverse() * qq * qb;   // 下一时刻的旋转矩阵Eigen::Vector3d f1 = current_imu[i-1].acc;f1 = f1 + current_pose.state.ba;Eigen::Vector3d f2 = current_imu[i].acc;f2 = f2 + current_pose.state.ba;Eigen::Vector3d vv2 = vv + dt * (0.5* (qq * f1 + qq * f2) + gn);  //下一时刻的速度Eigen::Vector3d pp2 = pp + 0.5*dt* (vv + vv2);pp = pp2;vv  = vv2;qq = qq2;
}
current_pose.state.p = pp;    //更新当前位置
current_pose.state.v =  vv;
current_pose.state.q = qq;Ft = Eigen::Matrix<double,15,15>::Zero();    // 状态转移矩阵
Ft.block<3,3>(0,3) = Eigen::Matrix<double, 3 ,3>::Identity();
Eigen::Matrix<double,3,3>temp = Eigen::Matrix<double,3,3>::Zero();
Eigen::Vector3d ff = current_imu.back().acc;
// F23
ff = qq*ff;
temp(0,1) = -ff[2];
temp(0,2) = ff[1];
temp(1,0) = ff[2];
temp(1, 2) = -ff[0];
temp(2, 0) = -ff[1];
temp(2, 1) = ff[0];
Ft.block<3,3>(3,6) = temp;
// Cb_n
Ft.block<3,3>(3,12) = qq.toRotationMatrix();
temp.setZero();
// F33
temp(0, 1) = w_ie_n(2);
temp(0, 2) = -w_ie_n(1);
temp(1, 0) = -w_ie_n(2);
temp(2, 0) = w_ie_n(1);
Ft.block<3,3>(6,6)  = temp;
//- Cb_n
Ft.block<3,3>(6,9) =  -Ft.block<3,3>(3,12);
Eigen::Matrix<double,15,6>  Bt = Eigen::Matrix<double,15,6>::Zero();
Bt.block<3,3>(3,3) = Ft.block<3,3>(3,12);
Bt.block<3,3>(6,0) = Ft.block<3, 3>(6, 9);
T = current_imu.back().time  - current_imu.front().time;
//   Qso 离散滤波器
Ft = Eigen::Matrix<double,15 ,15>::Identity() + Ft * T ;      // Fk-1 ,上一时刻的状态转移矩阵
Bt = Bt * T ;                                                                                           // Bk-1 ,上一时刻的观测矩阵
Eigen::Matrix<double,6,1> W = Eigen::Matrix<double,6,1>::Zero();    //器件噪声 ,一般指 IMU的零偏不稳定系
// eskf 先验
current_error_state.x   = Ft * current_error_state.x  + Bt * W;
Eigen::Matrix<double, 6, 6> Q = Eigen::Matrix<double, 6, 6>::Identity();                //  惯性噪声
Q.block<3,3>(0,0) = Eigen::Matrix<double,3,3>:: Identity()* gyro_noise * gyro_noise;
Q.block<3,3>(3,3) = Eigen::Matrix<double,3,3>:: Identity()* acc_noise  * acc_noise;
current_error_state.p = Ft * current_error_state.p * Ft.transpose() + Bt * Q * Bt.transpose();
return  true ;
}

eskf 修正

// 修正融合
bool Correct()
{double geo_x, geo_y, geo_z;geo_converter.Forward(current_gnss.lla(0), current_gnss.lla(1),current_gnss.lla(2), geo_x, geo_y, geo_z);      // 将经纬度处理为  x y zEigen::Vector3d gnss_xyz(geo_x, geo_y, geo_z);Y.block<3,1>(0,0) = current_pose.state.p -  gnss_xyz;  // 观测误差Gt = Eigen::Matrix<double,3,15>::Zero();Gt.block<3,3>(0,0) = Eigen::Matrix<double,3,3>::Identity();Eigen::Matrix<double,3,3> Ct = Eigen::Matrix<double,3,3>::Identity();Eigen::Matrix<double, 3, 3> R = Eigen::Matrix<double, 3, 3>::Identity();    // 观测噪声, gps噪声R.block<3, 3>(0, 0) = Eigen::Matrix3d::Identity() * dp_noise * dp_noise;Eigen::Matrix<double, 15, 3> K = current_error_state.p * Gt.transpose() * (Gt * current_error_state.p * Gt.transpose() + Ct * R * Ct.transpose()).inverse();  // kf 增益// 计算后验current_error_state.p = (Eigen::Matrix<double, 15, 15>::Identity() - K * Gt) * current_error_state.p;     // 后验方差current_error_state.x = current_error_state.x + K * (Y - Gt * current_error_state.x);                                      // 后验状态// 位姿、姿态 update current_pose.state.p = current_pose.state.p - current_error_state.x.block<3,1>(0,0);current_pose.state.v = current_pose.state.v - current_error_state.x.block<3,1>(3,0);Eigen::Vector3d dphi_dir = current_error_state.x.block<3, 1>(6, 0);double dphi_norm = dphi_dir.norm();if (dphi_norm != 0){dphi_dir = dphi_dir / dphi_norm;dphi_dir = dphi_dir * std::sin(dphi_norm / 2);}Eigen::Quaterniond temp2(std::cos(dphi_norm / 2), dphi_dir[0], dphi_dir[1], dphi_dir[2]);current_pose.state.q = temp2 * current_pose.state.q;     // 更新旋转矢量current_pose.state.q.normalize();current_pose.state.bg = current_pose.state.bg - current_error_state.x.block<3, 1>(9, 0);current_pose.state.ba = current_pose.state.ba - current_error_state.x.block<3, 1>(12, 0);current_error_state.x.setZero();        // 清空误差return true;
}

保存观测度分析所需的F G 和 Y

/*保存观测度分析所需的F G 和 Y*/
bool SaveFG()
{if (FGs.size() > FGsize){return true;}if (FGs.empty()){FG fg;fg.time = current_gt.time;// fg.F = Ft;fg.F = Ft - Eigen::Matrix<double, 15, 15>::Identity();// fg.F = (Ft - Eigen::Matrix<double, 15, 15>::Identity()) / T;fg.G = Gt;fg.Y.push_back(Y);FGs.push_back(fg);}else{if (FGs.back().Y.size() == 15){if (current_gt.time - FGs.back().time < time_interval || FGs.size() >= FGsize){return true;}FG fg;fg.time = current_gt.time;// fg.F = Ft;fg.F = Ft - Eigen::Matrix<double, 15, 15>::Identity();// fg.F = (Ft - Eigen::Matrix<double, 15, 15>::Identity()) / T;fg.G = Gt;fg.Y.push_back(Y);FGs.push_back(fg);}else{FGs.back().Y.push_back(Y);}}return true;
}

保存位姿信息

void SavePose(std::ofstream &save_points, PoseData &pose)
{Eigen::Quaterniond qtemp = pose.state.q;// if (qtemp.w() < 0)// {//     qtemp.coeffs() = -1.0 * qtemp.coeffs();// }double angle = std::acos(qtemp.w()) * 2;double sin_angle = std::sin(angle / 2);Eigen::Vector3d dir(0, 0, 0);if (sin_angle != 0){dir(0) = qtemp.x() / sin_angle;dir(1) = qtemp.y() / sin_angle;dir(2) = qtemp.z() / sin_angle;dir = dir * angle;}save_points.precision(12);save_points << pose.time<< "," << pose.state.p(0)<< "," << pose.state.p(1)<< "," << pose.state.p(2)<< "," << pose.state.v(0)<< "," << pose.state.v(1)<< "," << pose.state.v(2)// << "," << pose.state.q.x()// << "," << pose.state.q.y()// << "," << pose.state.q.z()// << "," << pose.state.q.w()<< "," << dir(0)<< "," << dir(1)<< "," << dir(2)<< "," << pose.state.bg(0)<< "," << pose.state.bg(1)<< "," << pose.state.bg(2)<< "," << pose.state.ba(0)<< "," << pose.state.ba(1)<< "," << pose.state.ba(2)<< std::endl;
}

可观测性分析

    Eigen::MatrixXd  Qso(3*15*FGs.size(),15);Eigen::MatrixXd  Ys(3*15*FGs.size(),1);for (int i = 0; i < FGs.size(); ++i){Eigen::Matrix<double,15,15> Fn = Eigen::Matrix<double,15,15>::Identity();for (int j = 0; j < 15; j++){if (j > 0){Fn = Fn * FGs[i].F;}Ys.block<3, 1>(i*3*15+3*j, 0) = FGs[i].Y[j];Qso.block<3, 15>(i*3*15+3*j, 0) = FGs[i].G * Fn ;    // 构建可观测性矩阵}}
    Eigen::JacobiSVD<Eigen::MatrixXd> svd(Qso, Eigen::ComputeFullU | Eigen::ComputeFullV);std::cout << Qso.rows() << ", " << Qso.cols() << std::endl;// std::cout << svd.singularValues() << std::endl;for (int i = 0; i < 15; ++i){double temp = (svd.matrixU().row(i) * Ys)[0] / svd.singularValues()[i];Eigen::MatrixXd Xi = temp * svd.matrixV().col(i);// std::cout << Xi.transpose() << std::endl;Eigen::MatrixXd::Index maxRow, maxCol;Xi = Xi.cwiseAbs();   //  Xi的绝对值double maxvalue = Xi.maxCoeff(&maxRow, &maxCol);std::cout << svd.singularValues()(i) / svd.singularValues()(0) << "," << maxRow << std::endl;sv_ofs << svd.singularValues()(i) / svd.singularValues()(0) << "," << maxRow << std::endl;}

仿真与分析

采用 imu_data7.py 生成数据

    # imu_errimu_err = {'gyro_b': np.array([1e-2, 2e-2, 3e-2]) / D2R * 3600 * 0,'gyro_arw': np.array([1e-5, 1e-5, 1e-5]) / D2R * 60 * 0,'gyro_b_stability': np.array([1e-5, 1e-5, 1e-5]) / D2R * 3600 * 1e-0,'gyro_b_corr': np.array([100.0, 100.0, 100.0]),'accel_b': np.array([2.0e-3, 1.0e-3, 5.0e-3]) * 0,'accel_vrw': np.array([1e-4, 1e-4, 1e-4]) * 60.0 * 0,'accel_b_stability': np.array([1e-4, 1e-4, 1e-4]) * 1.0 * 1e0,'accel_b_corr': np.array([200.0, 200.0, 200.0]),#    'mag_std': np.array([0.2, 0.2, 0.2]) * 1.0}# generate GPS and magnetometer datagps_err = {'stdp': np.array([1, 1, 1]) * 1e-6,'stdv': np.array([0, 0, 0]),}imu = imu_model.IMU(accuracy=imu_err, axis=6, gps=True, gps_opt=gps_err)

其中陀螺仪零偏稳定性为 1e-5rad/s(约 2deg/h),加速度计零偏稳定性为 1e-4m/s^2,GPS 位置误
差为 1e-6m/s,其它误差量均设置为 0。运行 ins 生成数据后,采用 plot_data.m 来绘制各状态量和真实
值的数据,采用 plot_data2.m 来绘制各状态量与真实值相减之后的数据,以分析收敛情况,具体代码见
附件。在可观测度分析中,各状态量对应的编号从 0 到 14。

运行 ./ins 或 ./eskf_ins 对 四种场景进行可观测性分析 eg. 结果总结部分摘自张松鹏优秀作业讲解
a) 静止状态
如下图终端打印显示为,生成的 在时变系统下 Qso矩阵的 450行,15列; 第二行-第16行为对Qso矩阵进行SVD分解后得出的降序排列奇异值和对应的状态量,可观测度一般低于 1e-4 或 1e-5 认为不可观。

运行 plot_data.m 生成可视化分析
各状态量的收敛情况见下图,从图中可看出,航向角(8),而这这个状态量对应的可观测度系数均非常小,为e-05量级,另外,z 轴角速度零偏的收敛速度很慢,直到 10分钟后才逐渐收敛,它对应的可观测度也很低,在 1.2e-7 左右,由于其仍然能够收敛,因此认为它是可观的。

通调整 FGsize,加大时变系统下的方程组数,可使变量可观

b) 匀速状态 -摘自 松鹏大哥作业
仿真命令参见附件文件 imu_def8.csv,b 系沿前向(y 轴正向)做匀速运动。各状态量的收敛情况见下图,可观测度系数见下表。从图中可看出,收敛情况与静止状态完全相同,航向角,x 轴加速度零偏和 y 轴加速度零偏均未收敛,这 3 个状态量对应的可观测度系数均很小,在 1e-33 到 1e-28 量级,因此 3 个状态量是不可观的。z 轴角速度零偏收敛速度很慢,对应的可观测度也很低,在 6e-8 左右,但仍然能够收敛,因此是可观的。


c) 加减速状态 -摘自 松鹏大哥作业
仿真命令参见附件文件 imu_def9.csv,b 系沿 x 轴和 y 轴两个方向分别做加减速运动,具体运动参考下图中的位置和速度变化曲线。可观测度系数见下表。各状态量对应的可观测度系数均在 7.9e-4 以上,而图中各状态量的误差值均在 0 值附近波动,可认为达到收敛状态。但由于 IMU 数据存在误差,各状态量均存在一定程度的误差。在加减速变换时,角度和零偏的误差值均有一定波动。可观测度系数中最小的 3 个是 z 轴角速度零偏,x 轴加速度零偏,y 轴加速度零偏,均在 1e-3 量级,3 个状态量观测度较低,而收敛
速度也较慢,特别是 2 个加速度水平零偏,在 20s 之后误差值才逐渐减小,因此对应的可观测度也最低。航向角的误差值在几次加减速后逐渐下降了一个数量级,对应的可观测度较前面 2 种情况也有大幅提高,约 0.012。



d) 转向状态 -摘自 松鹏大哥作业
仿真命令参见附件文件 imu_def10.csv,b 系做绕 8 字运动,具体运动情况见下图的位置和姿态变化。各状态量可观测度系数见下表。各状态量对应的可观测度系数均在 8.7e-4 以上,而图中各状态量的误差值均在 0 值附近波动,可认为达到收敛状态。但由于 IMU 数据存在误差,各状态量均存在一定程度的误差。在做转向变换时,角度和零偏的误差值均有一定波动。可观测度系数小于或者等于 0.01 量级的有 7 个状态量,包括航向角和 6 个零偏参数,这 7 个参数均在开始有所波动,但经过几十秒后收敛。


添加个人注解的 ins.cpp

/*eskf_ins.cpp*/
#include <vector>
#include <Eigen/Core>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <Eigen/Geometry>
#include "Geocentric/LocalCartesian.hpp"
#include <deque>
#include <random>
#include <yaml-cpp/yaml.h>#define D2R  0.017453292519943295       // Degree2Radius
struct  State
{Eigen::Vector3d  p ;          //位置Eigen::Vector3d  v;           //速度Eigen::Quaterniond q;   //位姿Eigen::Vector3d bg ;       // bias-gyroEigen::Vector3d ba;       //  bias-accel
};
//eskf状态方程变量
struct  ErrorState
{Eigen::Matrix<double,15,1> x;    //状态Eigen::Matrix<double,15,15> p ;    // 方差
};
struct  IMUData
{double time;Eigen::Vector3d acc;Eigen::Vector3d gyro;
};
struct  GNSSData
{double  time;Eigen::Vector3d lla;   //经纬度Eigen::Vector3d v;      //速度
};
struct  PoseData
{double  time;State state;
};std::deque<GNSSData>  gnss_buff;
std::deque<IMUData> imu_buff;
std::deque<PoseData> gt_buff;
std::deque<IMUData> current_imu;
GNSSData  current_gnss;
PoseData   current_gt;
PoseData   current_pose;
ErrorState  current_error_state;
GeographicLib::LocalCartesian geo_converter(32, 120, 0);    // 初始经纬度
std::ofstream    gt_ofs;
std::ofstream    pose_ofs;
std::ofstream    sv_ofs;
double gyro_noise  =  1e-6;
double acc_noise = 1e-5;
double  dp_noise = 1e-3;
std::vector<double>  init_noise;
int FGsize = 20;     //Qso  20个时刻
double time_interval = 10;
double end_time = 20;
double T = 0.1;
struct  FG
{double time;Eigen::Matrix<double,15,15> F;                         // 状态转移矩阵Eigen::Matrix<double,3,15> G;                          //观测矩阵std::vector<Eigen::Matrix<double,3,1>> Y;   //观测值
};
std::vector<FG> FGs;                                                  //多个时刻的 FG矩阵
//当前时刻的  F  G  Y 矩阵
Eigen::Matrix<double,15,15> Ft = Eigen::Matrix<double,15,15>::Zero();
Eigen::Matrix<double,3,15>  Gt = Eigen::Matrix<double,3,15>::Zero();
Eigen::Matrix<double,3,1> Y  = Eigen::Matrix<double,3,1>::Zero();
bool correct = true;//读取仿真数据
//   读取仿真数据
bool ReadData(const std::vector<std::string> &path)
{gnss_buff.clear();imu_buff.clear();gt_buff.clear();std::vector<std::ifstream> reads;// int count = 0;for (int i = 0; i < path.size(); ++i){reads.push_back(std::ifstream(path[i]));}for (int i = 0; i < path.size(); ++i){std::string strs;std::getline(reads[i], strs);}std::string strs;while (std::getline(reads[0], strs)){double time = std::stod(strs);std::getline(reads[1], strs);std::string temp = "";std::vector<double> acc;for (int i = 0; i < strs.size(); ++i){if (strs[i] == ','){acc.push_back(std::stod(temp));temp = "";}else{temp = temp + strs[i];}}acc.push_back(std::stod(temp));std::getline(reads[2], strs);temp = "";std::vector<double> gyro;for (int i = 0; i < strs.size(); ++i){if (strs[i] == ','){gyro.push_back(std::stod(temp));temp = "";}else{temp = temp + strs[i];}}gyro.push_back(std::stod(temp));IMUData imu;imu.time = time;imu.acc = Eigen::Vector3d(acc[0], acc[1], acc[2]);imu.gyro = Eigen::Vector3d(gyro[0] * D2R, gyro[1] * D2R, gyro[2] * D2R);imu_buff.push_back(imu);std::getline(reads[5], strs);temp = "";std::vector<double> ref_pos;for (int i = 0; i < strs.size(); ++i){if (strs[i] == ','){ref_pos.push_back(std::stod(temp));temp = "";}else{temp = temp + strs[i];}}ref_pos.push_back(std::stod(temp));std::getline(reads[6], strs);temp = "";std::vector<double> ref_vel;for (int i = 0; i < strs.size(); ++i){if (strs[i] == ','){ref_vel.push_back(std::stod(temp));temp = "";}else{temp = temp + strs[i];}}ref_vel.push_back(std::stod(temp));std::getline(reads[7], strs);temp = "";std::vector<double> ref_att_quat;for (int i = 0; i < strs.size(); ++i){if (strs[i] == ','){ref_att_quat.push_back(std::stod(temp));temp = "";}else{temp = temp + strs[i];}}ref_att_quat.push_back(std::stod(temp));Eigen::Quaterniond q = Eigen::AngleAxisd(90 * D2R, Eigen::Vector3d::UnitZ()) *Eigen::AngleAxisd(0, Eigen::Vector3d::UnitY()) *Eigen::AngleAxisd(180 * D2R, Eigen::Vector3d::UnitX());q = q.inverse();PoseData pose;pose.time = time;double geo_x, geo_y, geo_z;geo_converter.Forward(ref_pos[0], ref_pos[1], ref_pos[2], geo_x, geo_y, geo_z);pose.state.p = Eigen::Vector3d(geo_x, geo_y, geo_z);// pose.state.p = q * Eigen::Vector3d(ref_pos[0], ref_pos[1], ref_pos[2]);pose.state.v = q * Eigen::Vector3d(ref_vel[0], ref_vel[1], ref_vel[2]);pose.state.q = q * Eigen::Quaterniond(ref_att_quat[0], ref_att_quat[1], ref_att_quat[2], ref_att_quat[3]);pose.state.q.normalize();pose.state.bg = Eigen::Vector3d(0, 0, 0);pose.state.ba = Eigen::Vector3d(0, 0, 0);gt_buff.push_back(pose);}while (std::getline(reads[3], strs)){double time = std::stod(strs);std::getline(reads[4], strs);std::string temp = "";std::vector<double> gps;for (int i = 0; i < strs.size(); ++i){if (strs[i] == ','){gps.push_back(std::stod(temp));temp = "";}else{temp = temp + strs[i];}}gps.push_back(std::stod(temp));GNSSData gnss;gnss.time = time;gnss.lla = Eigen::Vector3d(gps[0], gps[1], gps[2]);// 北东地   ->   东北天Eigen::Quaterniond q = Eigen::AngleAxisd(90 * D2R, Eigen::Vector3d::UnitZ()) *Eigen::AngleAxisd(0, Eigen::Vector3d::UnitY()) *Eigen::AngleAxisd(180 * D2R, Eigen::Vector3d::UnitX());q = q.inverse();gnss.v = q * Eigen::Vector3d(gps[3], gps[4], gps[5]);gnss_buff.push_back(gnss);}
}//同步GPS 和IMU 数据 ,   eg 因为 imu 和 GPS的频率不一样所以需要数据同步
bool SyncData(bool inited)
{/*以gnss时间为准*/if (gnss_buff.empty()){return false;}current_gnss = gnss_buff.front();   //读取当前gnss队列第一个数据double sync_time = current_gnss.time;/*sync groundtruth 数据*/
//gt时间同步while (gt_buff.size() > 1){if(gt_buff[1].time < sync_time){gt_buff.pop_front();       //将不对齐的时间删除}else{break;}}
//gt数据同步
if(gt_buff.size() > 1)
{   PoseData front_data = gt_buff.at(0);    //上一帧数据PoseData back_data = gt_buff.at(1);double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time); double back_scale = (sync_time -front_data.time) /  (back_data.time - front_data.time);current_gt.time = sync_time ;//插值current_gt.state.p = front_data.state.p  * front_scale  + back_data.state.p *  back_scale;current_gt.state.v = front_data.state.v   * front_scale  + back_data.state.v *  back_scale;current_gt.state.q= front_data.state.q.slerp(front_scale,back_data.state.q);current_gt.state.bg = front_data.state.bg *  front_scale  + back_data.state.bg * back_scale;current_gt.state.ba = front_data.state.ba * front_scale   + back_data.state.ba * back_scale;
}else{return false;
}/*sync imu 数据*/
// imu  时间同步while (!inited && imu_buff.size() > 1){if (imu_buff[1].time < sync_time){imu_buff.pop_front();}else{break;}}// imu 数据同步
if (imu_buff.size() > 1)
{/*IMU 未初始化的数据同步*/if (!inited){current_imu.clear();IMUData front_data = imu_buff.at(0);IMUData back_data = imu_buff.at(1);IMUData synced_data;double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time);double back_scale = (sync_time - front_data.time) / (back_data.time - front_data.time);synced_data.time = sync_time;synced_data.acc = front_data.acc * front_scale + back_data.acc * back_scale;synced_data.gyro = front_data.gyro * front_scale + back_data.gyro * back_scale;current_imu.push_back(synced_data);imu_buff.pop_front();gnss_buff.pop_front();// std::cout << std::setprecision(12) << "sync_time " << sync_time//           << " current_imu.time " << current_imu.front().time//           << "  " << current_imu.back().time << std::endl;return true;}if (imu_buff.back().time < sync_time){return false;}while (current_imu.size() > 1){current_imu.pop_front();}while (imu_buff.front().time < sync_time){IMUData temp = imu_buff.front();imu_buff.pop_front();current_imu.push_back(temp);     // 将新的IMU数据放在deque队列末尾}IMUData front_data = current_imu.back();IMUData back_data = imu_buff.at(0);      //取 刚好比 sync_time 大的imu 数据IMUData synced_data;//插值double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time);double back_scale = (sync_time - front_data.time) / (back_data.time - front_data.time);synced_data.time = sync_time;synced_data.acc = front_data.acc * front_scale  +  back_data.acc  * back_scale; synced_data.gyro = front_data.gyro * front_scale  + back_data.gyro * back_scale;current_imu.push_back(synced_data);gnss_buff.pop_front();return true;
}else{return false;}
}bool InitSensor()          //初始化 传感器
{while (!gnss_buff.empty()){if (imu_buff.front().time >  gnss_buff.front().time){gnss_buff.pop_front();       //删除时间不匹配的 gnss 数据,保证 imu的数据在前}else{return true;}}return  false;
}bool InitPose()
{static bool pose_inited = false;if (pose_inited){return true;}if (!SyncData(false)){return false;}current_pose.time = current_gt.time;current_pose.state.p = current_gt.state.p;current_pose.state.q = current_gt.state.q;current_pose.state.v = current_gt.state.v;current_pose.state.bg = current_gt.state.bg;current_pose.state.ba = current_gt.state.ba;current_error_state.x.setZero();current_error_state.p.setZero();current_error_state.p.block<3, 3>(0, 0) = Eigen::Matrix3d::Identity() * init_noise[0];current_error_state.p.block<3, 3>(3, 3) = Eigen::Matrix3d::Identity() * init_noise[1];current_error_state.p.block<3, 3>(6, 6) = Eigen::Matrix3d::Identity() * init_noise[2];current_error_state.p.block<3, 3>(9, 9) = Eigen::Matrix3d::Identity() * init_noise[3];current_error_state.p.block<3, 3>(12, 12) = Eigen::Matrix3d::Identity() * init_noise[4];pose_inited = true;return true;
}bool Predict()  // 预测
{current_pose.time  = current_gt.time;Eigen::Vector3d  pp =  current_pose.state.p;Eigen::Vector3d  vv  =  current_pose.state.v;Eigen::Quaterniond qq = current_pose.state.q;double w = 7.27220521664304e-05;    // 地球自转速度Eigen::Vector3d gn(0, 0, -9.79484197226504);    // 重力加速度Eigen::Vector3d w_ie_n(0, w * std::cos(current_gnss.lla[0] * M_PI / 180),w * std::sin(current_gnss.lla[0] * M_PI / 180));double rm = 6353346.18315;       // 短半轴double rn = 6384140.52699;        // 长半轴Eigen::Vector3d w_en_n(-vv[1] / (rm + current_gnss.lla[2]),vv[0] / (rn + current_gnss.lla[2]),vv[0] / (rn + current_gnss.lla[2]) * std::tan(current_gnss.lla[0] * M_PI / 180));Eigen::Vector3d w_in_n = w_ie_n + w_en_n;for (int i = 1; i < current_imu.size(); ++i){double dt = current_imu[i].time - current_imu[i - 1].time;Eigen::Vector3d wtemp = w_in_n * dt;double angle = wtemp.norm();Eigen::Quaterniond qn(1, 0, 0, 0);if (angle != 0){wtemp = wtemp / angle;wtemp = std::sin(angle / 2) * wtemp;qn = Eigen::Quaterniond(std::cos(angle / 2), wtemp[0], wtemp[1], wtemp[2]);}qn.normalize();   // 地球自转的角增量Eigen::Vector3d wb = 0.5* current_imu[i - 1].gyro  + 0.5*current_imu[i].gyro;wb = wb + current_pose.state.bg;wb = wb * dt;angle = wb.norm();Eigen::Quaterniond qb(1,0,0,0);if (angle != 0){wb = wb / angle;wb = std::sin(angle / 2) * wb;qb = Eigen::Quaterniond(std::cos(angle / 2),wb[0],wb[1],wb[2] );}qb.normalize();     //载体角增量Eigen::Quaterniond  qq2 = qn.inverse() * qq * qb;   // 下一时刻的旋转矩阵Eigen::Vector3d f1 = current_imu[i-1].acc;f1 = f1 + current_pose.state.ba;Eigen::Vector3d f2 = current_imu[i].acc;f2 = f2 + current_pose.state.ba;Eigen::Vector3d vv2 = vv + dt * (0.5* (qq * f1 + qq * f2) + gn);  //下一时刻的速度Eigen::Vector3d pp2 = pp + 0.5*dt* (vv + vv2);pp = pp2;vv  = vv2;qq = qq2;
}
current_pose.state.p = pp;    //更新当前位置
current_pose.state.v =  vv;
current_pose.state.q = qq;Ft = Eigen::Matrix<double,15,15>::Zero();    // 状态转移矩阵
Ft.block<3,3>(0,3) = Eigen::Matrix<double, 3 ,3>::Identity();
Eigen::Matrix<double,3,3>temp = Eigen::Matrix<double,3,3>::Zero();
Eigen::Vector3d ff = current_imu.back().acc;
// F23
ff = qq*ff;
temp(0,1) = -ff[2];
temp(0,2) = ff[1];
temp(1,0) = ff[2];
temp(1, 2) = -ff[0];
temp(2, 0) = -ff[1];
temp(2, 1) = ff[0];
Ft.block<3,3>(3,6) = temp;
// Cb_n
Ft.block<3,3>(3,12) = qq.toRotationMatrix();
temp.setZero();
// F33
temp(0, 1) = w_ie_n(2);
temp(0, 2) = -w_ie_n(1);
temp(1, 0) = -w_ie_n(2);
temp(2, 0) = w_ie_n(1);
Ft.block<3,3>(6,6)  = temp;
//- Cb_n
Ft.block<3,3>(6,9) =  -Ft.block<3,3>(3,12);
Eigen::Matrix<double,15,6>  Bt = Eigen::Matrix<double,15,6>::Zero();
Bt.block<3,3>(3,3) = Ft.block<3,3>(3,12);
Bt.block<3,3>(6,0) = Ft.block<3, 3>(6, 9);
T = current_imu.back().time  - current_imu.front().time;
//   Qso 离散滤波器
Ft = Eigen::Matrix<double,15 ,15>::Identity() + Ft * T ;      // Fk-1 ,上一时刻的状态转移矩阵
Bt = Bt * T ;                                                                                           // Bk-1 ,上一时刻的观测矩阵
Eigen::Matrix<double,6,1> W = Eigen::Matrix<double,6,1>::Zero();    //器件噪声 ,一般指 IMU的零偏不稳定系
// eskf 先验
current_error_state.x   = Ft * current_error_state.x  + Bt * W;
Eigen::Matrix<double, 6, 6> Q = Eigen::Matrix<double, 6, 6>::Identity();                //  惯性噪声
Q.block<3,3>(0,0) = Eigen::Matrix<double,3,3>:: Identity()* gyro_noise * gyro_noise;
Q.block<3,3>(3,3) = Eigen::Matrix<double,3,3>:: Identity()* acc_noise  * acc_noise;
current_error_state.p = Ft * current_error_state.p * Ft.transpose() + Bt * Q * Bt.transpose();
return  true ;
}// 修正融合
bool Correct()
{double geo_x, geo_y, geo_z;geo_converter.Forward(current_gnss.lla(0), current_gnss.lla(1),current_gnss.lla(2), geo_x, geo_y, geo_z);      // 将经纬度处理为  东北天Eigen::Vector3d gnss_xyz(geo_x, geo_y, geo_z);Y.block<3,1>(0,0) = current_pose.state.p -  gnss_xyz;  // 观测误差Gt = Eigen::Matrix<double,3,15>::Zero();Gt.block<3,3>(0,0) = Eigen::Matrix<double,3,3>::Identity();Eigen::Matrix<double,3,3> Ct = Eigen::Matrix<double,3,3>::Identity();Eigen::Matrix<double, 3, 3> R = Eigen::Matrix<double, 3, 3>::Identity();    // 观测噪声, gps噪声R.block<3, 3>(0, 0) = Eigen::Matrix3d::Identity() * dp_noise * dp_noise;Eigen::Matrix<double, 15, 3> K = current_error_state.p * Gt.transpose() * (Gt * current_error_state.p * Gt.transpose() + Ct * R * Ct.transpose()).inverse();  // kf 增益// 计算后验current_error_state.p = (Eigen::Matrix<double, 15, 15>::Identity() - K * Gt) * current_error_state.p;     // 后验方差current_error_state.x = current_error_state.x + K * (Y - Gt * current_error_state.x);                                      // 后验状态// 位姿、姿态 update current_pose.state.p = current_pose.state.p - current_error_state.x.block<3,1>(0,0);current_pose.state.v = current_pose.state.v - current_error_state.x.block<3,1>(3,0);Eigen::Vector3d dphi_dir = current_error_state.x.block<3, 1>(6, 0);double dphi_norm = dphi_dir.norm();if (dphi_norm != 0){dphi_dir = dphi_dir / dphi_norm;dphi_dir = dphi_dir * std::sin(dphi_norm / 2);}Eigen::Quaterniond temp2(std::cos(dphi_norm / 2), dphi_dir[0], dphi_dir[1], dphi_dir[2]);current_pose.state.q = temp2 * current_pose.state.q;     // 更新旋转矢量current_pose.state.q.normalize();current_pose.state.bg = current_pose.state.bg - current_error_state.x.block<3, 1>(9, 0);current_pose.state.ba = current_pose.state.ba - current_error_state.x.block<3, 1>(12, 0);current_error_state.x.setZero();        // 清空误差return true;
}/*保存观测度分析所需的F G 和 Y*/
bool SaveFG()
{if (FGs.size() > FGsize){return true;}if (FGs.empty()){FG fg;fg.time = current_gt.time;// fg.F = Ft;fg.F = Ft - Eigen::Matrix<double, 15, 15>::Identity();// fg.F = (Ft - Eigen::Matrix<double, 15, 15>::Identity()) / T;fg.G = Gt;fg.Y.push_back(Y);FGs.push_back(fg);}else{if (FGs.back().Y.size() == 15){if (current_gt.time - FGs.back().time < time_interval || FGs.size() >= FGsize){return true;}FG fg;fg.time = current_gt.time;// fg.F = Ft;fg.F = Ft - Eigen::Matrix<double, 15, 15>::Identity();// fg.F = (Ft - Eigen::Matrix<double, 15, 15>::Identity()) / T;fg.G = Gt;fg.Y.push_back(Y);FGs.push_back(fg);}else{FGs.back().Y.push_back(Y);}}return true;
}/*滤波*/
bool Filter()
{Predict();if (correct){Correct();}return true;
}void SavePose(std::ofstream &save_points, PoseData &pose)
{Eigen::Quaterniond qtemp = pose.state.q;// if (qtemp.w() < 0)// {//     qtemp.coeffs() = -1.0 * qtemp.coeffs();// }double angle = std::acos(qtemp.w()) * 2;double sin_angle = std::sin(angle / 2);Eigen::Vector3d dir(0, 0, 0);if (sin_angle != 0){dir(0) = qtemp.x() / sin_angle;dir(1) = qtemp.y() / sin_angle;dir(2) = qtemp.z() / sin_angle;dir = dir * angle;}save_points.precision(12);save_points << pose.time<< "," << pose.state.p(0)<< "," << pose.state.p(1)<< "," << pose.state.p(2)<< "," << pose.state.v(0)<< "," << pose.state.v(1)<< "," << pose.state.v(2)// << "," << pose.state.q.x()// << "," << pose.state.q.y()// << "," << pose.state.q.z()// << "," << pose.state.q.w()<< "," << dir(0)<< "," << dir(1)<< "," << dir(2)<< "," << pose.state.bg(0)<< "," << pose.state.bg(1)<< "," << pose.state.bg(2)<< "," << pose.state.ba(0)<< "," << pose.state.ba(1)<< "," << pose.state.ba(2)<< std::endl;
}
bool SaveData()    // 保存数据
{SavePose(gt_ofs, current_gt);SavePose(pose_ofs, current_pose);
}int main(int argc, char const *argv[])
{std::vector<std::string> path;path.push_back("../../gnss-ins-sim/imu_data/data7/time.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/accel-0.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/gyro-0.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/gps_time.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/gps-0.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/ref_pos.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/ref_vel.csv");path.push_back("../../gnss-ins-sim/imu_data/data7/ref_att_quat.csv");ReadData(path);gt_ofs.open("../../gnss-ins-sim/imu_data/data7/gt.txt", std::fstream::out);pose_ofs.open("../../gnss-ins-sim/imu_data/data7/pose.txt", std::fstream::out);sv_ofs.open("../../gnss-ins-sim/imu_data/data7/sv.txt", std::fstream::out);FGs.clear();YAML::Node yaml_node = YAML::LoadFile("../param.yaml");gyro_noise = yaml_node["gyro_noise"].as<double>();acc_noise = yaml_node["acc_noise"].as<double>();dp_noise = yaml_node["dp_noise"].as<double>();init_noise = yaml_node["init_noise"].as<std::vector<double>>();FGsize = yaml_node["FGsize"].as<int>();end_time = yaml_node["end_time"].as<double>();time_interval = yaml_node["time_interval"].as<double>();correct = yaml_node["correct"].as<bool>();if (!InitSensor()){std::cerr << "InitSensor Error!!!" << std::endl;return -1;}if (!InitPose()){std::cerr << "InitPose Error!!!" << std::endl;return -1;}SaveData();while (SyncData(true)){Filter();SaveData();SaveFG();if (current_gt.time > end_time){break;}}Eigen::MatrixXd  Qso(3*15*FGs.size(),15);Eigen::MatrixXd  Ys(3*15*FGs.size(),1);for (int i = 0; i < FGs.size(); ++i){Eigen::Matrix<double,15,15> Fn = Eigen::Matrix<double,15,15>::Identity();for (int j = 0; j < 15; j++){if (j > 0){Fn = Fn * FGs[i].F;}Ys.block<3, 1>(i*3*15+3*j, 0) = FGs[i].Y[j];Qso.block<3, 15>(i*3*15+3*j, 0) = FGs[i].G * Fn ;    // 构建可观测性矩阵}}Eigen::JacobiSVD<Eigen::MatrixXd> svd(Qso, Eigen::ComputeFullU | Eigen::ComputeFullV);std::cout << Qso.rows() << ", " << Qso.cols() << std::endl;// std::cout << svd.singularValues() << std::endl;for (int i = 0; i < 15; ++i){double temp = (svd.matrixU().row(i) * Ys)[0] / svd.singularValues()[i];Eigen::MatrixXd Xi = temp * svd.matrixV().col(i);// std::cout << Xi.transpose() << std::endl;Eigen::MatrixXd::Index maxRow, maxCol;Xi = Xi.cwiseAbs();   //  Xi的绝对值double maxvalue = Xi.maxCoeff(&maxRow, &maxCol);std::cout << svd.singularValues()(i) / svd.singularValues()(0) << "," << maxRow << std::endl;sv_ofs << svd.singularValues()(i) / svd.singularValues()(0) << "," << maxRow << std::endl;}return 0;
}

多传感器融合定位(4-基于滤波的2融合方法)2-使用仿真数据进行imu-gnss eskf和时变系统下的可观测性分析相关推荐

  1. 多传感器融合定位:基于滤波的融合方法

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 SLAM 后端的优化方式大体分为滤波和优化.近些年优化越来越成为主 ...

  2. 加权平均法融合图像matlab,基于MATLAB的图像融合算法

    内容介绍 原文档由会员 小甜甜 发布 基于MATLAB的图像融合算法 1.9万字 34页 摘要 图像融合能够将不同类型传感器获取的同一对象的图像数据进行空间配准.并且采用一定的算法将各图像数据所含的信 ...

  3. 多传感器融合定位十四-基于图优化的定位方法

    多传感器融合定位十四-基于图优化的定位方法 1. 基于图优化的定位简介 1.1 核心思路 1.2 定位流程 2. 边缘化原理及应用 2.1 边缘化原理 2.2 从滤波角度理解边缘化 3. 基于kitt ...

  4. 多传感器融合定位三-3D激光里程计其三:点云畸变补偿

    多传感器融合定位三-3D激光里程计其三:点云畸变补偿 1. 产生原因 2. 补偿方法 Reference: 深蓝学院-多传感器融合 多传感器融合定位理论基础 文章跳转: 多传感器融合定位一-3D激光里 ...

  5. 多传感器融合定位五-点云地图构建及定位

    多传感器融合定位五-点云地图构建及定位 1. 回环检测 1.1 基于Scan Context 1.2 基于直方图 2. 后端优化 2.1 后端优化基本原理 2.2 李群.李代数基本知识 2.3 李群. ...

  6. 多传感器融合定位十五-多传感器时空标定(综述)

    多传感器融合定位十五-多传感器时空标定 1. 多传感器标定简介 1.1 标定内容及方法 1.2 讲解思路 2. 内参标定 2.1 雷达内参标定 2.2 IMU内参标定 2.3 编码器内参标定 2.4 ...

  7. 多传感器融合定位七-惯性导航解算及误差分析其一

    多传感器融合定位七-惯性导航解算及误差分析其一 1. 三维运动描述基础知识 1.1 概述 1.2 姿态描述方法 1.2.1 欧拉角 1.2.2 旋转矩阵 1.2.3 四元数 1.2.4 等效旋转矢量 ...

  8. 多传感器融合定位六-惯性导航原理及误差分析

    多传感器融合定位六-惯性导航原理及误差分析 1. 惯性技术简介 1.1 惯性技术发展历史 1.2 惯性器件 1.2.1 机械陀螺(几乎没人用了) 1.2.2 激光陀螺 1.2.3 光纤陀螺 1.2.4 ...

  9. 多传感器融合定位四-3D激光里程计其四:点云线面特征提取

    多传感器融合定位四-3D激光里程计其四:点云线面特征提取 1. 点云线面特征提取 1.1 按线数分割 1.2 计算曲率(重要!) 1.3 按曲率大小筛选特征点 2. 基于线面特征的位姿变化 2.1 帧 ...

最新文章

  1. group by的查询
  2. 网易云音乐刷听歌量_榆林网易云音乐粉丝量主要方式
  3. php 照片变成卡通照片,Photoshop实例:变照片为卡通漫画
  4. javafx swing_JavaFX技巧9:请勿混用Swing / JavaFX
  5. 计算机指令int,汇编入门学习笔记 (十二)—— int指令、端口
  6. matlab编写基差,到期交割临近 基差迟迟不跌
  7. 计算机仿真电路实验感想,电路计算机仿真 实验报告.doc
  8. 设计模式那点事读书笔记(2)----抽象工厂模式
  9. 冰点下载器手机版apk_冰点文库下载器
  10. 计算机232接口接线图,串口线(232接口详细接线图)
  11. python游戏项目练习——逃出生天(2)
  12. Cox模型中的变量选择(1)---自适应Lasso方法
  13. 地震数据剖面图-matlab
  14. 北京游玩攻略,-怎么游玩清华北大
  15. Gradle build failed to produce an .apk file. It‘s likely that this file was generated under XXX
  16. VC++6.0 用gSoap客户端访问WebService
  17. 《转怒为喜---顾客抱怨投诉处理技巧》
  18. 2017淮北计算机会考,2017年安徽淮北高中会考报名网站:淮北教育局
  19. 视觉检测售价_机器人引导的视觉定位系统一般多少价格可靠?
  20. 2021年5月份的月度总结

热门文章

  1. hibernate5.4+mysql8+java8实例
  2. 米家小相机最新固件_能拍4K的米家小相机只要699了,你还要啥自行车?!
  3. vue range 双向滚动 取中间值
  4. ios开发中常用的一些软件
  5. Android开发中onClick事件的几种实现,分析,对比
  6. MAC虚拟机设备无法连接到它的理想主机控制器
  7. redis展示 删除 详情
  8. 全球主要的域名争议解决机构有哪些?
  9. 2018心得随想笔记
  10. java 函数内定义函数_java可以在main中定义函数吗?