/* * 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 #include #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::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