robot_localization多传感器定位-IMU数据的校准与融合

本文详细解析了robot_localization中IMU数据融合的过程,包括数据类型、IMU校准、rikirobot源码分析及四元数计算。介绍了如何从原始IMU数据获取加速度、角速度和磁力计信息,并通过多种滤波算法实现姿态数据融合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

robot_localization多传感器融合中的IMU数据获取分四个部分来说明,分别是数据类型、IMU校准及融合理论、rikirobot源码解析以及优化

第一部分:数据类型

robot_localization多传感器融合中的IMU数据,需要四元数和角速度,因此除了角速度的校准,还需要磁力计来获取融合后的四元数的信息。这个在robot_localization的论文数据表格中可以看出来

第二部分:IMU校准及融合理论

首先引用https://www.cnblogs.com/hiram-zhang/p/10398959.html的理论说明IMU数据融合的算法实现过程

完成数据的校准以后,利用高低通互补滤波、扩展卡尔曼滤波EKF、Mahony滤波中的一种实现姿态数据融合,获取四元数

高低通互补滤波如下所示

第三部分:rikirobot源码解析

rikirobot的IMU数据首先是获取原始里程计数据,然后分别进行标定,使用do_calib节点进行加速度校准,apply_calib节点进行角速度校准,以及原始IMU数据标定后到sensor_msgs/Imu转换,最后imu_filter_madgwick节点融合获取四元数。

3.1 STM32控制板获取IMU原始数据以及发布

void publish_imu()
{
	
    //geometry_msgs::Vector3 acceler, gyro, mag;
    //this function publishes raw IMU reading
    //measure accelerometer
		
    MPU_Get_Accelerometer(&ax,&ay,&az);
	acc.x=ax*Accelerometer_SCALE;
	acc.y=ay*Accelerometer_SCALE;
	acc.z=az*Accelerometer_SCALE;
	raw_imu_msg.linear_acceleration = acc;
		
    //measure gyroscope
    MPU_Get_Gyroscope(&gx,&gy,&gz);
	gyr.x=gx*Gyroscope_SCALE;
	gyr.y=gy*Gyroscope_SCALE;
	gyr.z=gz*Gyroscope_SCALE;	
	raw_imu_msg.angular_velocity = gyr;
		
    //measure magnetometer
    MPU_Get_Magnetometer(&mx,&my,&mz);
	meg.x=mx*Magnetometer_SCALE;
	meg.y=my*Magnetometer_SCALE;
	meg.z=mz*Magnetometer_SCALE;	
	raw_imu_msg.magnetic_field = meg;

    //publish raw_imu_msg object to ROS
		
    raw_imu_pub.publish(&raw_imu_msg);
		

}

 

3.2 do_calib节点获取加速度校准参数

回调函数中通过在xyz的正负方向采样的数据,对加速度进行校准

void DoCalib::imuCallback(riki_msgs::Imu::ConstPtr imu)
{
  bool accepted;

  switch (state_)
  {
  case START:
    calib_.beginCalib(6*measurements_per_orientation_, reference_acceleration_);
    state_ = SWITCHING;
    break;


  case SWITCHING:
    if (orientations_.empty())
    {
      state_ = COMPUTING;
    }
    else
    {
      current_orientation_ = orientations_.front();

      orientations_.pop();
      measurements_received_ = 0;

      std::cout << "Orient IMU with " << orientation_labels_[current_orientation_] << " facing up. Press [ENTER] once done.";
      std::cin.ignore();
      std::cout << "Calibrating! This may take a while...." << std::endl;

      state_ = RECEIVING;
    }
    break;


  case RECEIVING:
    accepted = calib_.addMeasurement(current_orientation_,
                                     imu->linear_acceleration.x,
                                     imu->linear_acceleration.y,
                                     imu->linear_acceleration.z);

    measurements_received_ += accepted ? 1 : 0;
	//每次采集500次数据
    if (measurements_received_ >= measurements_per_orientation_)
    {
      std::cout << " Done." << std::endl;
      state_ = SWITCHING;
    }
    break;


  case COMPUTING:
    std::cout << "Computing calibration parameters...";
    if (calib_.computeCalib())
    {
      std::cout << " Success!"  << std::endl;

      std::cout << "Saving calibration file...";
      if (calib_.saveCalib(output_file_))
      {
        std::cout << " Success!" << std::endl;
      }
      else
      {
        std::cout << " Failed." << std::endl;
      }
    }
    else
    {
      std::cout << " Failed.";
      ROS_ERROR("Calibration failed");
    }
    state_ = DONE;
    break;


  case DONE:
    break;
  }
}

其中computeCalib()函数使用UV分解得到尺度偏差矩阵和零偏参数

bool AccelCalib::computeCalib()
{
  // check status
  if (measurements_received_ < 12)
    return false;

  for (int i = 0; i < 6; i++)
  {
    if (orientation_count_[i] == 0)
      return false;
  }

  // solve least squares
  Eigen::VectorXd xhat = meas_.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(ref_);

  // extract solution
  for (int i = 0; i < 9; i++)
  {
    SM_(i/3, i%3) = xhat(i);
  }

  for (int i = 0; i < 3; i++)
  {
    bias_(i) = xhat(9+i);
  }

  calib_ready_ = true;
  return true;
}

3.3 apply_calib节点的角速度校准

角速度校准在源码的回调函数中只是进行了零偏校准,然后发布了校准后转换的sensor_msgs::Imu数据

void ApplyCalib::rawImuCallback(riki_msgs::Imu::ConstPtr raw)
{
  if (calibrate_gyros_)
  {
    ROS_INFO_ONCE("Calibrating gyros; do not move the IMU");

    // recursively compute mean gyro measurements
    gyro_sample_count_++;
    gyro_bias_x_ = ((gyro_sample_count_ - 1) * gyro_bias_x_ + raw->angular_velocity.x) / gyro_sample_count_;
    gyro_bias_y_ = ((gyro_sample_count_ - 1) * gyro_bias_y_ + raw->angular_velocity.y) / gyro_sample_count_;
    gyro_bias_z_ = ((gyro_sample_count_ - 1) * gyro_bias_z_ + raw->angular_velocity.z) / gyro_sample_count_;

    if (gyro_sample_count_ >= gyro_calib_samples_)
    {
      ROS_INFO("Gyro calibration complete! (bias = [%.3f, %.3f, %.3f])", gyro_bias_x_, gyro_bias_y_, gyro_bias_z_);
      calibrate_gyros_ = false;
    }

    return;
  }

  //input array for acceleration calibration
  double raw_accel[3];
  //output array for calibrated acceleration
  double corrected_accel[3];

  //pass received acceleration to input array
  raw_accel[0] = raw->linear_acceleration.x;
  raw_accel[1] = raw->linear_acceleration.y;
  raw_accel[2] = raw->linear_acceleration.z;

  //apply calibrated 
  calib_.applyCalib(raw_accel, corrected_accel);

  //create calibrated data object
  sensor_msgs::Imu corrected;

  corrected.header.stamp = ros::Time::now();
  corrected.header.frame_id = "imu_link";
  
  //pass calibrated acceleration to corrected IMU data object
  corrected.linear_acceleration.x = corrected_accel[0];
  corrected.linear_acceleration.y = corrected_accel[1];
  corrected.linear_acceleration.z = corrected_accel[2];
  
  //add calibration bias to  received angular velocity and pass to to corrected IMU data object
  corrected.angular_velocity.x = raw->angular_velocity.x - gyro_bias_x_;
  corrected.angular_velocity.y = raw->angular_velocity.y - gyro_bias_y_;
  corrected.angular_velocity.z = raw->angular_velocity.z - gyro_bias_z_;

  //publish calibrated IMU data
  corrected_pub_.publish(corrected);


  sensor_msgs::MagneticField mag_msg;

  mag_msg.header.stamp = ros::Time::now();

  //scale received magnetic (miligauss to tesla)
  mag_msg.magnetic_field.x = raw->magnetic_field.x * 0.000001;
  mag_msg.magnetic_field.y = raw->magnetic_field.y * 0.000001;
  mag_msg.magnetic_field.z = raw->magnetic_field.z * 0.000001;
  
  mag_pub_.publish(mag_msg);
}

3.4 四元数的计算

到校准后的IMU数据,最后进行九轴融合的四元数获取,使用imu_filter_madgwick节点进行计算

void ImuFilterRos::imuCallback(const ImuMsg::ConstPtr& imu_msg_raw)
{
  boost::mutex::scoped_lock lock(mutex_);

  const geometry_msgs::Vector3& ang_vel = imu_msg_raw->angular_velocity;
  const geometry_msgs::Vector3& lin_acc = imu_msg_raw->linear_acceleration;

  ros::Time time = imu_msg_raw->header.stamp;
  imu_frame_ = imu_msg_raw->header.frame_id;

  if (!initialized_ || stateless_)
  {
    geometry_msgs::Quaternion init_q;
    if (!StatelessOrientation::computeOrientation(world_frame_, lin_acc, init_q))
    {
      ROS_WARN_THROTTLE(5.0, "The IMU seems to be in free fall, cannot determine gravity direction!");
      return;
    }
    filter_.setOrientation(init_q.w, init_q.x, init_q.y, init_q.z);
  }

  if (!initialized_)
  {
    ROS_INFO("First IMU message received.");
    check_topics_timer_.stop();

    // initialize time
    last_time_ = time;
    initialized_ = true;
  }

  // determine dt: either constant, or from IMU timestamp
  float dt;
  if (constant_dt_ > 0.0)
    dt = constant_dt_;
  else
  {
    dt = (time - last_time_).toSec();
    if (time.isZero())
      ROS_WARN_STREAM_THROTTLE(5.0, "The IMU message time stamp is zero, and the parameter constant_dt is not set!" <<
                                    " The filter will not update the orientation.");
  }

  last_time_ = time;

  if (!stateless_)
    filter_.madgwickAHRSupdateIMU(
      ang_vel.x, ang_vel.y, ang_vel.z,
      lin_acc.x, lin_acc.y, lin_acc.z,
      dt);

  publishFilteredMsg(imu_msg_raw);//发布imu数据
  if (publish_tf_)
    publishTransform(imu_msg_raw);
}

但是分析代码后发现,将use_mag和use_magnetic_field_msg设置false后不再考虑磁力计信息,使用加速度和角速度获取相应的四元数。

上面的代码运行后将获取相应的IMU数据,将话题数据载入robot_localization作为测量数据对编码器的航迹推算数据进行校正

第四部分:优化

上面的角速度没有进行尺度偏差等校准,缺少磁力计的融合

优化在IMU自动校准论文和开源框架代码部分介绍

https://blog.youkuaiyun.com/qq_29313679/article/details/105752021

开源框架校准结果与rikirobot方法比较

https://blog.youkuaiyun.com/qq_29313679/article/details/106001812

 

 

 

### Robot Localization 中的协方差矩阵使用说明 #### 协方差矩阵的作用 在 `robot_localization` 软件包中,协方差矩阵用于表示状态估计器(如 EKF 或 UKF)的状态不确定性。具体来说,它描述了各个状态变量之间的关系以及它们各自的不确定程度。随着传感器数据的不断输入,滤波器会更新这些值以反映当前的最佳估计及其置信水平。 #### 配置方法 为了正确配置协方差矩阵,在启动节点时需注意以下几个方面: 1. **初始协方差设置** 初始协方差定义了系统开始运行时对各状态量的信任程度。通常情况下,默认值已经经过合理调整,但对于特定应用可能需要手动修改。例如,如果知道某些维度上的初值非常精确,则可减小对应项的数值;反之亦然。 ```yaml initial_estimate_covariance: [1e-9, 0, ..., # Position X variance 0, 1e-9, ..., # Position Y variance ... # Other states... ] ``` 2. **过程噪声协方差 (Process Noise Covariance)** 这部分反映了模型预测阶段引入的新不确定性。较大的值意味着更倾向于信任新观测而非旧估计。同样地,可以根据实际情况微调每一维的过程噪声强度来优化性能[^2]。 ```yaml process_noise_covariance: [0.05, 0, ..., # Process noise on position X 0, 0.05, ..., # Process noise on position Y ... # Rest of the terms... ] ``` 3. **传感器测量频率时延补偿** 当启用 `_differential` 参数时,原始姿态会被转化为速度形式参融合运算。然而正如前面提到过的那样,这种转化可能导致长期积累的方向偏差问题特别严重[^3]。因此建议仅针对那些确实无法获取瞬时速率信号的情况才开启此项功能,并密切监控最终效果如何变化。 4. **多源同步机制** 如果存在多个异构类型的传感设备同时接入到同一个定位框架里头的话,那么就很有必要仔细校准各自的时间戳标记以便实现最佳协同工作表现。任何显著差异都可能会引起不必要的干扰从而降低整体精度。 #### 示例代码片段 下面给出了一段简单的 YAML 文件片断展示上述要点的实际运用方式: ```yaml frequency: 30.0 sensor_timeout: 0.1 two_d_mode: true transform_time_offset: 0.0 print_diagnostics: true # Initial Estimate Parameters initial_estimate_covariance: [1e-9, 0, ... 0, 1e-9, ... # Process Noise Configuration process_noise_covariance: [0.05, 0, ... 0, 0.05, ... ] # Differential Parameter Example imu0_config: [false, false,...] imu0_differential: true # Enable differential mode for IMU data processing. ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值