gyroscope_bias_estimator.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. /*
  2. * Copyright 2019 Google Inc. All Rights Reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "gyroscope_bias_estimator.h"
  17. #include <algorithm>
  18. #include <chrono> // NOLINT
  19. #include "../util/rotation.h"
  20. #include "../util/vector.h"
  21. namespace {
  22. // Cutoff frequencies in Hertz applied to our various signals, and their
  23. // corresponding filters.
  24. const float kAccelerometerLowPassCutOffFrequencyHz = 1.0f;
  25. const float kRotationVelocityBasedAccelerometerLowPassCutOffFrequencyHz = 0.15f;
  26. const float kGyroscopeLowPassCutOffFrequencyHz = 1.0f;
  27. const float kGyroscopeBiasLowPassCutOffFrequencyHz = 0.15f;
  28. // Note that MEMS IMU are not that precise.
  29. const double kEpsilon = 1.0e-8;
  30. // Size of the filtering window for the mean and median filter. The larger the
  31. // windows the larger the filter delay.
  32. const int kFilterWindowSize = 5;
  33. // Threshold used to compare rotation computed from the accelerometer and the
  34. // gyroscope bias.
  35. const double kRatioBetweenGyroBiasAndAccel = 1.5;
  36. // The minimum sum of weights we need to acquire before returning a bias
  37. // estimation.
  38. const float kMinSumOfWeightsGyroBiasThreshold = 25.0f;
  39. // Amount of change in m/s^3 we allow on the smoothed accelerometer values to
  40. // consider the phone static.
  41. const double kAccelerometerDeltaStaticThreshold = 0.5;
  42. // Amount of change in radians/s^2 we allow on the smoothed gyroscope values to
  43. // consider the phone static.
  44. const double kGyroscopeDeltaStaticThreshold = 0.03;
  45. // If the gyroscope value is above this threshold, don't update the gyroscope
  46. // bias estimation. This threshold is applied to the magnitude of gyroscope
  47. // vectors in radians/s.
  48. const float kGyroscopeForBiasThreshold = 0.30f;
  49. // Used to monitor if accelerometer and gyroscope have been static for a few
  50. // frames.
  51. const int kStaticFrameDetectionThreshold = 50;
  52. // Minimum time step between sensor updates.
  53. const double kMinTimestep = 1; // std::chrono::nanoseconds(1);
  54. } // namespace
  55. namespace cardboard {
  56. // A helper class to keep track of whether some signal can be considered static
  57. // over specified number of frames.
  58. class GyroscopeBiasEstimator::IsStaticCounter {
  59. public:
  60. // Initializes a counter with the number of consecutive frames we require
  61. // the signal to be static before IsRecentlyStatic returns true.
  62. //
  63. // @param min_static_frames_threshold number of consecutive frames we
  64. // require the signal to be static before IsRecentlyStatic returns true.
  65. explicit IsStaticCounter(int min_static_frames_threshold)
  66. : min_static_frames_threshold_(min_static_frames_threshold)
  67. , consecutive_static_frames_(0)
  68. {
  69. }
  70. // Specifies whether the current frame is considered static.
  71. //
  72. // @param is_static static flag for current frame.
  73. void AppendFrame(bool is_static)
  74. {
  75. if (is_static) {
  76. ++consecutive_static_frames_;
  77. } else {
  78. consecutive_static_frames_ = 0;
  79. }
  80. }
  81. // Returns if static movement is assumed.
  82. bool IsRecentlyStatic() const
  83. {
  84. return consecutive_static_frames_ >= min_static_frames_threshold_;
  85. }
  86. // Resets counter.
  87. void Reset() { consecutive_static_frames_ = 0; }
  88. private:
  89. const int min_static_frames_threshold_;
  90. int consecutive_static_frames_;
  91. };
  92. GyroscopeBiasEstimator::GyroscopeBiasEstimator()
  93. : accelerometer_lowpass_filter_(kAccelerometerLowPassCutOffFrequencyHz)
  94. , simulated_gyroscope_from_accelerometer_lowpass_filter_(
  95. kRotationVelocityBasedAccelerometerLowPassCutOffFrequencyHz)
  96. , gyroscope_lowpass_filter_(kGyroscopeLowPassCutOffFrequencyHz)
  97. , gyroscope_bias_lowpass_filter_(kGyroscopeBiasLowPassCutOffFrequencyHz)
  98. , accelerometer_static_counter_(new IsStaticCounter(kStaticFrameDetectionThreshold))
  99. , gyroscope_static_counter_(new IsStaticCounter(kStaticFrameDetectionThreshold))
  100. , current_accumulated_weights_gyroscope_bias_(0.f)
  101. , mean_filter_(kFilterWindowSize)
  102. , median_filter_(kFilterWindowSize)
  103. , last_mean_filtered_accelerometer_value_({ 0, 0, 0 })
  104. {
  105. Reset();
  106. }
  107. GyroscopeBiasEstimator::~GyroscopeBiasEstimator() { }
  108. void GyroscopeBiasEstimator::Reset()
  109. {
  110. accelerometer_lowpass_filter_.Reset();
  111. gyroscope_lowpass_filter_.Reset();
  112. gyroscope_bias_lowpass_filter_.Reset();
  113. accelerometer_static_counter_->Reset();
  114. gyroscope_static_counter_->Reset();
  115. }
  116. void GyroscopeBiasEstimator::ProcessGyroscope(
  117. const Vector3& gyroscope_sample, uint64_t timestamp_ns)
  118. {
  119. // Update gyroscope and gyroscope delta low-pass filters.
  120. gyroscope_lowpass_filter_.AddSample(gyroscope_sample, timestamp_ns);
  121. const auto smoothed_gyroscope_delta
  122. = gyroscope_sample - gyroscope_lowpass_filter_.GetFilteredData();
  123. gyroscope_static_counter_->AppendFrame(
  124. Length(smoothed_gyroscope_delta) < kGyroscopeDeltaStaticThreshold);
  125. // Only update the bias if the gyroscope and accelerometer signals have been
  126. // relatively static recently.
  127. if (gyroscope_static_counter_->IsRecentlyStatic()
  128. && accelerometer_static_counter_->IsRecentlyStatic()) {
  129. // Reset static counter when updating the bias fails.
  130. if (!UpdateGyroscopeBias(gyroscope_sample, timestamp_ns)) {
  131. // Bias update fails because of large motion, thus reset the static
  132. // counter.
  133. gyroscope_static_counter_->AppendFrame(false);
  134. }
  135. } else {
  136. // Reset weights, if not static.
  137. current_accumulated_weights_gyroscope_bias_ = 0;
  138. }
  139. }
  140. void GyroscopeBiasEstimator::ProcessAccelerometer(
  141. const Vector3& accelerometer_sample, uint64_t timestamp_ns)
  142. {
  143. // Get current state of the filter.
  144. const uint64_t previous_accel_timestamp_ns
  145. = accelerometer_lowpass_filter_.GetMostRecentTimestampNs();
  146. const bool is_low_pass_filter_init = accelerometer_lowpass_filter_.IsInitialized();
  147. // Update accel and accel delta low-pass filters.
  148. accelerometer_lowpass_filter_.AddSample(accelerometer_sample, timestamp_ns);
  149. const auto smoothed_accelerometer_delta
  150. = accelerometer_sample - accelerometer_lowpass_filter_.GetFilteredData();
  151. accelerometer_static_counter_->AppendFrame(
  152. Length(smoothed_accelerometer_delta) < kAccelerometerDeltaStaticThreshold);
  153. // Rotation from accel cannot be differentiated with only one sample.
  154. if (!is_low_pass_filter_init) {
  155. simulated_gyroscope_from_accelerometer_lowpass_filter_.AddSample({ 0, 0, 0 }, timestamp_ns);
  156. return;
  157. }
  158. // No need to update the simulated gyroscope at this point because the motion
  159. // is too large.
  160. if (!accelerometer_static_counter_->IsRecentlyStatic()) {
  161. return;
  162. }
  163. median_filter_.AddSample(accelerometer_lowpass_filter_.GetFilteredData());
  164. // This processing can only be started if the buffer is fully initialized.
  165. if (!median_filter_.IsValid()) {
  166. mean_filter_.AddSample(accelerometer_lowpass_filter_.GetFilteredData());
  167. // Update the last filtered accelerometer value.
  168. last_mean_filtered_accelerometer_value_ = accelerometer_lowpass_filter_.GetFilteredData();
  169. return;
  170. }
  171. mean_filter_.AddSample(median_filter_.GetFilteredData());
  172. // Compute a mock gyroscope value from accelerometer.
  173. const int64_t diff = timestamp_ns - previous_accel_timestamp_ns;
  174. const double timestep = static_cast<double>(diff);
  175. simulated_gyroscope_from_accelerometer_lowpass_filter_.AddSample(
  176. ComputeAngularVelocityFromLatestAccelerometer(timestep), timestamp_ns);
  177. last_mean_filtered_accelerometer_value_ = mean_filter_.GetFilteredData();
  178. }
  179. Vector3 GyroscopeBiasEstimator::ComputeAngularVelocityFromLatestAccelerometer(double timestep) const
  180. {
  181. if (timestep < kMinTimestep) {
  182. return { 0, 0, 0 };
  183. }
  184. const auto mean_of_median = mean_filter_.GetFilteredData();
  185. // Compute an incremental rotation between the last state and the current
  186. // state.
  187. //
  188. // Note that we switch to double precision here because of precision problem
  189. // with small rotation.
  190. const auto incremental_rotation = Rotation::RotateInto(
  191. Vector3(last_mean_filtered_accelerometer_value_[0],
  192. last_mean_filtered_accelerometer_value_[1], last_mean_filtered_accelerometer_value_[2]),
  193. Vector3(mean_of_median[0], mean_of_median[1], mean_of_median[2]));
  194. // We use axis angle here because this is how gyroscope values are stored.
  195. Vector3 incremental_rotation_axis;
  196. double incremental_rotation_angle;
  197. incremental_rotation.GetAxisAndAngle(&incremental_rotation_axis, &incremental_rotation_angle);
  198. incremental_rotation_axis *= incremental_rotation_angle / timestep;
  199. return { static_cast<float>(incremental_rotation_axis[0]),
  200. static_cast<float>(incremental_rotation_axis[1]),
  201. static_cast<float>(incremental_rotation_axis[2]) };
  202. }
  203. bool GyroscopeBiasEstimator::UpdateGyroscopeBias(
  204. const Vector3& gyroscope_sample, uint64_t timestamp_ns)
  205. {
  206. // Gyroscope values that are too big are potentially dangerous as they could
  207. // originate from slow and steady head rotations.
  208. //
  209. // Therefore we compute an update weight which:
  210. // * favors gyroscope values that are closer to 0
  211. // * is set to zero if gyroscope values are greater than a threshold.
  212. //
  213. // This way, the gyroscope bias estimation converges faster if the phone is
  214. // flat on a table, as opposed to held up somewhat stationary in the user's
  215. // hands.
  216. // If magnitude is too big, don't update the filter at all so that we don't
  217. // artificially increase the number of samples accumulated by the filter.
  218. const float gyroscope_sample_norm2 = Length(gyroscope_sample);
  219. if (gyroscope_sample_norm2 >= kGyroscopeForBiasThreshold) {
  220. return false;
  221. }
  222. float update_weight
  223. = std::max(0.0f, 1 - gyroscope_sample_norm2 / kGyroscopeForBiasThreshold);
  224. update_weight *= update_weight;
  225. gyroscope_bias_lowpass_filter_.AddWeightedSample(
  226. gyroscope_lowpass_filter_.GetFilteredData(), timestamp_ns, update_weight);
  227. // This counter is only partially valid as the low pass filter drops large
  228. // samples.
  229. current_accumulated_weights_gyroscope_bias_ += update_weight;
  230. return true;
  231. }
  232. Vector3 GyroscopeBiasEstimator::GetGyroscopeBias() const
  233. {
  234. return gyroscope_bias_lowpass_filter_.GetFilteredData();
  235. }
  236. bool GyroscopeBiasEstimator::IsCurrentEstimateValid() const
  237. {
  238. // Remove any bias component along the gravity because they cannot be
  239. // evaluated from accelerometer.
  240. const auto current_gravity_dir = Normalized(last_mean_filtered_accelerometer_value_);
  241. const auto gyro_bias_lowpass = gyroscope_bias_lowpass_filter_.GetFilteredData();
  242. const auto off_gravity_gyro_bias
  243. = gyro_bias_lowpass - current_gravity_dir * Dot(gyro_bias_lowpass, current_gravity_dir);
  244. // Checks that the current bias estimate is not correlated with the
  245. // rotation computed from accelerometer.
  246. const auto gyro_from_accel
  247. = simulated_gyroscope_from_accelerometer_lowpass_filter_.GetFilteredData();
  248. const bool isGyroscopeBiasCorrelatedWithSimulatedGyro
  249. = (Length(gyro_from_accel) * kRatioBetweenGyroBiasAndAccel
  250. > (Length(off_gravity_gyro_bias) + kEpsilon));
  251. const bool hasEnoughSamples
  252. = current_accumulated_weights_gyroscope_bias_ > kMinSumOfWeightsGyroBiasThreshold;
  253. const bool areCountersStatic = gyroscope_static_counter_->IsRecentlyStatic()
  254. && accelerometer_static_counter_->IsRecentlyStatic();
  255. const bool isStatic
  256. = hasEnoughSamples && areCountersStatic && !isGyroscopeBiasCorrelatedWithSimulatedGyro;
  257. return isStatic;
  258. }
  259. } // namespace cardboard