| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- /*
- * Copyright 2019 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #include "sensor_fusion_ekf.h"
- #include <algorithm>
- #include <cmath>
- #include "accelerometer_data.h"
- #include "gyroscope_data.h"
- #include "pose_prediction.h"
- #include "../util/matrixutils.h"
- namespace cardboard {
- namespace {
- const double kFiniteDifferencingEpsilon = 1.0e-7;
- const double kEpsilon = 1.0e-15;
- // Default gyroscope frequency. This corresponds to 100 Hz.
- const double kDefaultGyroscopeTimestep_s = 0.01f;
- // Maximum time between gyroscope before we start limiting the integration.
- const double kMaximumGyroscopeSampleDelay_s = 0.04f;
- // Compute a first-order exponential moving average of changes in accel norm per
- // frame.
- const double kSmoothingFactor = 0.5;
- // Minimum and maximum values used for accelerometer noise covariance matrix.
- // The smaller the sigma value, the more weight is given to the accelerometer
- // signal.
- const double kMinAccelNoiseSigma = 0.75;
- const double kMaxAccelNoiseSigma = 7.0;
- // Initial value for the diagonal elements of the different covariance matrices.
- const double kInitialStateCovarianceValue = 25.0;
- const double kInitialProcessCovarianceValue = 1.0;
- // Maximum accelerometer norm change allowed before capping it covariance to a
- // large value.
- const double kMaxAccelNormChange = 0.15;
- // Timestep IIR filtering coefficient.
- const double kTimestepFilterCoeff = 0.95;
- // Minimum number of sample for timestep filtering.
- const int kTimestepFilterMinSamples = 10;
- // Z direction in start space.
- const Vector3 kCanonicalZDirection(0.0, 0.0, 1.0);
- // Computes an axis-angle rotation from the input vector.
- // angle = norm(a)
- // axis = a.normalized()
- // If norm(a) == 0, it returns an identity rotation.
- static inline void RotationFromVector(const Vector3& a, Rotation& r)
- {
- const double norm_a = Length(a);
- if (norm_a < kEpsilon) {
- r = Rotation::Identity();
- return;
- }
- r = Rotation::FromAxisAndAngle(a / norm_a, norm_a);
- }
- } // namespace
- SensorFusionEkf::SensorFusionEkf()
- : execute_reset_with_next_accelerometer_sample_(false)
- , bias_estimation_enabled_(true)
- , gyroscope_bias_estimate_({ 0, 0, 0 })
- {
- ResetState();
- }
- void SensorFusionEkf::Reset() { execute_reset_with_next_accelerometer_sample_ = true; }
- void SensorFusionEkf::ResetState()
- {
- current_state_.sensor_from_start_rotation = Rotation::Identity();
- current_state_.sensor_from_start_rotation_velocity = Vector3::Zero();
- current_gyroscope_sensor_timestamp_ns_ = 0;
- current_accelerometer_sensor_timestamp_ns_ = 0;
- state_covariance_ = Matrix3x3::Identity() * kInitialStateCovarianceValue;
- process_covariance_ = Matrix3x3::Identity() * kInitialProcessCovarianceValue;
- accelerometer_measurement_covariance_
- = Matrix3x3::Identity() * kMinAccelNoiseSigma * kMinAccelNoiseSigma;
- innovation_covariance_ = Matrix3x3::Identity();
- accelerometer_measurement_jacobian_ = Matrix3x3::Zero();
- kalman_gain_ = Matrix3x3::Zero();
- innovation_ = Vector3::Zero();
- accelerometer_measurement_ = Vector3::Zero();
- prediction_ = Vector3::Zero();
- control_input_ = Vector3::Zero();
- state_update_ = Vector3::Zero();
- moving_average_accelerometer_norm_change_ = 0.0;
- is_timestep_filter_initialized_ = false;
- is_gyroscope_filter_valid_ = false;
- is_aligned_with_gravity_ = false;
- // Reset biases.
- gyroscope_bias_estimator_.Reset();
- gyroscope_bias_estimate_ = { 0, 0, 0 };
- }
- // Here I am doing something wrong relative to time stamps. The state timestamps
- // always correspond to the gyrostamps because it would require additional
- // extrapolation if I wanted to do otherwise.
- PoseState SensorFusionEkf::GetLatestPoseState() const { return current_state_; }
- void SensorFusionEkf::ProcessGyroscopeSample(const GyroscopeData& sample)
- {
- // Don't accept gyroscope sample when waiting for a reset.
- if (execute_reset_with_next_accelerometer_sample_) {
- return;
- }
- // Discard outdated samples.
- if (current_gyroscope_sensor_timestamp_ns_ >= sample.sensor_timestamp_ns) {
- current_gyroscope_sensor_timestamp_ns_ = sample.sensor_timestamp_ns;
- return;
- }
- // Checks that we received at least one gyroscope sample in the past.
- if (current_gyroscope_sensor_timestamp_ns_ != 0) {
- double current_timestep_s = std::chrono::duration_cast<std::chrono::duration<double>>(
- std::chrono::nanoseconds(
- sample.sensor_timestamp_ns - current_gyroscope_sensor_timestamp_ns_))
- .count();
- if (current_timestep_s > kMaximumGyroscopeSampleDelay_s) {
- if (is_gyroscope_filter_valid_) {
- // Replaces the delta timestamp by the filtered estimates of the delta time.
- current_timestep_s = filtered_gyroscope_timestep_s_;
- } else {
- current_timestep_s = kDefaultGyroscopeTimestep_s;
- }
- } else {
- FilterGyroscopeTimestep(current_timestep_s);
- }
- if (bias_estimation_enabled_) {
- gyroscope_bias_estimator_.ProcessGyroscope(sample.data, sample.sensor_timestamp_ns);
- if (gyroscope_bias_estimator_.IsCurrentEstimateValid()) {
- // As soon as the device is considered to be static, the bias estimator
- // should have a precise estimate of the gyroscope bias.
- gyroscope_bias_estimate_ = gyroscope_bias_estimator_.GetGyroscopeBias();
- }
- }
- // Only integrate after receiving an accelerometer sample.
- if (is_aligned_with_gravity_) {
- const Rotation rotation_from_gyroscope = pose_prediction::GetRotationFromGyroscope(
- { sample.data[0] - gyroscope_bias_estimate_[0],
- sample.data[1] - gyroscope_bias_estimate_[1],
- sample.data[2] - gyroscope_bias_estimate_[2] },
- current_timestep_s);
- current_state_.sensor_from_start_rotation
- = rotation_from_gyroscope * current_state_.sensor_from_start_rotation;
- UpdateStateCovariance(RotationMatrixNH(rotation_from_gyroscope));
- state_covariance_ = state_covariance_
- + ((current_timestep_s * current_timestep_s) * process_covariance_);
- }
- }
- // Saves gyroscope event for future prediction.
- current_state_.timestamp = sample.system_timestamp;
- current_gyroscope_sensor_timestamp_ns_ = sample.sensor_timestamp_ns;
- current_state_.sensor_from_start_rotation_velocity.Set(
- sample.data[0] - gyroscope_bias_estimate_[0], sample.data[1] - gyroscope_bias_estimate_[1],
- sample.data[2] - gyroscope_bias_estimate_[2]);
- }
- Vector3 SensorFusionEkf::ComputeInnovation(const Rotation& pose)
- {
- const Vector3 predicted_down_direction = pose * kCanonicalZDirection;
- const Rotation rotation
- = Rotation::RotateInto(predicted_down_direction, accelerometer_measurement_);
- Vector3 axis;
- double angle;
- rotation.GetAxisAndAngle(&axis, &angle);
- return axis * angle;
- }
- void SensorFusionEkf::ComputeMeasurementJacobian()
- {
- for (int dof = 0; dof < 3; dof++) {
- Vector3 delta = Vector3::Zero();
- delta[dof] = kFiniteDifferencingEpsilon;
- Rotation epsilon_rotation;
- RotationFromVector(delta, epsilon_rotation);
- const Vector3 delta_rotation
- = ComputeInnovation(epsilon_rotation * current_state_.sensor_from_start_rotation);
- const Vector3 col = (innovation_ - delta_rotation) / kFiniteDifferencingEpsilon;
- accelerometer_measurement_jacobian_(0, dof) = col[0];
- accelerometer_measurement_jacobian_(1, dof) = col[1];
- accelerometer_measurement_jacobian_(2, dof) = col[2];
- }
- }
- void SensorFusionEkf::ProcessAccelerometerSample(const AccelerometerData& sample)
- {
- // Discard outdated samples.
- if (current_accelerometer_sensor_timestamp_ns_ >= sample.sensor_timestamp_ns) {
- current_accelerometer_sensor_timestamp_ns_ = sample.sensor_timestamp_ns;
- return;
- }
- // Call reset state if required.
- if (execute_reset_with_next_accelerometer_sample_.exchange(false)) {
- ResetState();
- }
- accelerometer_measurement_.Set(sample.data[0], sample.data[1], sample.data[2]);
- current_accelerometer_sensor_timestamp_ns_ = sample.sensor_timestamp_ns;
- if (bias_estimation_enabled_) {
- gyroscope_bias_estimator_.ProcessAccelerometer(sample.data, sample.sensor_timestamp_ns);
- }
- if (!is_aligned_with_gravity_) {
- // This is the first accelerometer measurement so it initializes the
- // orientation estimate.
- current_state_.sensor_from_start_rotation
- = Rotation::RotateInto(kCanonicalZDirection, accelerometer_measurement_);
- is_aligned_with_gravity_ = true;
- previous_accelerometer_norm_ = Length(accelerometer_measurement_);
- return;
- }
- UpdateMeasurementCovariance();
- innovation_ = ComputeInnovation(current_state_.sensor_from_start_rotation);
- ComputeMeasurementJacobian();
- // S = H * P * H' + R
- innovation_covariance_ = accelerometer_measurement_jacobian_ * state_covariance_
- * Transpose(accelerometer_measurement_jacobian_)
- + accelerometer_measurement_covariance_;
- // K = P * H' * S^-1
- kalman_gain_ = state_covariance_ * Transpose(accelerometer_measurement_jacobian_)
- * Inverse(innovation_covariance_);
- // x_update = K*nu
- state_update_ = kalman_gain_ * innovation_;
- // P = (I - K * H) * P;
- state_covariance_ = (Matrix3x3::Identity() - kalman_gain_ * accelerometer_measurement_jacobian_)
- * state_covariance_;
- // Updates pose and associate covariance matrix.
- Rotation rotation_from_state_update;
- RotationFromVector(state_update_, rotation_from_state_update);
- current_state_.sensor_from_start_rotation
- = rotation_from_state_update * current_state_.sensor_from_start_rotation;
- UpdateStateCovariance(RotationMatrixNH(rotation_from_state_update));
- }
- void SensorFusionEkf::UpdateStateCovariance(const Matrix3x3& motion_update)
- {
- state_covariance_ = motion_update * state_covariance_ * Transpose(motion_update);
- }
- void SensorFusionEkf::FilterGyroscopeTimestep(double gyroscope_timestep_s)
- {
- if (!is_timestep_filter_initialized_) {
- // Initializes the filter.
- filtered_gyroscope_timestep_s_ = gyroscope_timestep_s;
- num_gyroscope_timestep_samples_ = 1;
- is_timestep_filter_initialized_ = true;
- return;
- }
- // Computes the IIR filter response.
- filtered_gyroscope_timestep_s_ = kTimestepFilterCoeff * filtered_gyroscope_timestep_s_
- + (1 - kTimestepFilterCoeff) * gyroscope_timestep_s;
- ++num_gyroscope_timestep_samples_;
- if (num_gyroscope_timestep_samples_ > kTimestepFilterMinSamples) {
- is_gyroscope_filter_valid_ = true;
- }
- }
- void SensorFusionEkf::UpdateMeasurementCovariance()
- {
- const double current_accelerometer_norm = Length(accelerometer_measurement_);
- // Norm change between current and previous accel readings.
- const double current_accelerometer_norm_change
- = std::abs(current_accelerometer_norm - previous_accelerometer_norm_);
- previous_accelerometer_norm_ = current_accelerometer_norm;
- moving_average_accelerometer_norm_change_ = kSmoothingFactor * current_accelerometer_norm_change
- + (1 - kSmoothingFactor) * moving_average_accelerometer_norm_change_;
- // If we hit the accel norm change threshold, we use the maximum noise sigma
- // for the accel covariance. For anything below that, we use a linear
- // combination between min and max sigma values.
- const double norm_change_ratio
- = moving_average_accelerometer_norm_change_ / kMaxAccelNormChange;
- const double accelerometer_noise_sigma = std::min(kMaxAccelNoiseSigma,
- kMinAccelNoiseSigma + norm_change_ratio * (kMaxAccelNoiseSigma - kMinAccelNoiseSigma));
- // Updates the accel covariance matrix with the new sigma value.
- accelerometer_measurement_covariance_
- = Matrix3x3::Identity() * accelerometer_noise_sigma * accelerometer_noise_sigma;
- }
- bool SensorFusionEkf::IsBiasEstimationEnabled() const { return bias_estimation_enabled_; }
- void SensorFusionEkf::SetBiasEstimationEnabled(bool enable)
- {
- if (bias_estimation_enabled_ != enable) {
- bias_estimation_enabled_ = enable;
- gyroscope_bias_estimate_ = { 0, 0, 0 };
- gyroscope_bias_estimator_.Reset();
- }
- }
- } // namespace cardboard
|