blob: 5663ae4ff78e6dc5f1c864763f547ec8bcd6810e [file] [log] [blame]
Alex Vakulenkoe4eec202017-01-27 14:41:04 -08001#include "sensor_fusion.h"
2
3#include <algorithm>
4#include <cmath>
5
6#include <private/dvr/eigen.h>
7
8namespace android {
9namespace dvr {
10
11namespace {
12
13// --- start of added bits for porting to eigen
14
15// In general, we prefer to add wrappers for things like Inverse() to minimize
16// the changes to the imported code, so that merging in upstream changes becomes
17// simpler.
18
19inline Matrix3d Inverse(const Matrix3d& matrix) { return matrix.inverse(); }
20inline Matrix3d Transpose(const Matrix3d& matrix) { return matrix.transpose(); }
21inline Matrix3d RotationMatrixNH(const Rotationd& rotation) {
22 return rotation.toRotationMatrix();
23}
24inline double Length(const Vector3d& vector) { return vector.norm(); }
25
26using uint64 = uint64_t;
27
28// --- end of added bits for porting to eigen
29
30static const double kFiniteDifferencingEpsilon = 1e-7;
31static const double kEpsilon = 1e-15;
32// Default gyroscope frequency. This corresponds to 200 Hz.
33static const double kDefaultGyroscopeTimestep_s = 0.005f;
34// Maximum time between gyroscope before we start limiting the integration.
35static const double kMaximumGyroscopeSampleDelay_s = 0.04f;
36// Compute a first-order exponential moving average of changes in accel norm per
37// frame.
38static const double kSmoothingFactor = 0.5;
39// Minimum and maximum values used for accelerometer noise covariance matrix.
40// The smaller the sigma value, the more weight is given to the accelerometer
41// signal.
42static const double kMinAccelNoiseSigma = 0.75;
43static const double kMaxAccelNoiseSigma = 7.0;
44// Initial value for the diagonal elements of the different covariance matrices.
45static const double kInitialStateCovarianceValue = 25.0;
46static const double kInitialProcessCovarianceValue = 1.0;
47// Maximum accelerometer norm change allowed before capping it covariance to a
48// large value.
49static const double kMaxAccelNormChange = 0.15;
50// Timestep IIR filtering coefficient.
51static const double kTimestepFilterCoeff = 0.95;
52// Minimum number of sample for timestep filtering.
53static const uint32_t kTimestepFilterMinSamples = 10;
54
55// Z direction in start space.
56static const Vector3d kCanonicalZDirection(0.0, 0.0, 1.0);
57
58// Computes a axis angle rotation from the input vector.
59// angle = norm(a)
60// axis = a.normalized()
61// If norm(a) == 0, it returns an identity rotation.
62static Rotationd RotationFromVector(const Vector3d& a) {
63 const double norm_a = Length(a);
64 if (norm_a < kEpsilon) {
65 return Rotationd::Identity();
66 }
67 return Rotationd(AngleAxisd(norm_a, a / norm_a));
68}
69
70// --- start of functions ported from pose_prediction.cc
71
72namespace pose_prediction {
73
74// Returns a rotation matrix based on the integration of the gyroscope_value
75// over the timestep_s in seconds.
76// TODO(pfg): Document the space better here.
77//
78// @param gyroscope_value gyroscope sensor values.
79// @param timestep_s integration period in seconds.
80// @return Integration of the gyroscope value the rotation is from Start to
81// Sensor Space.
82Rotationd GetRotationFromGyroscope(const Vector3d& gyroscope_value,
83 double timestep_s) {
84 const double velocity = Length(gyroscope_value);
85
86 // When there is no rotation data return an identity rotation.
87 if (velocity < kEpsilon) {
88 return Rotationd::Identity();
89 }
90 // Since the gyroscope_value is a start from sensor transformation we need to
91 // invert it to have a sensor from start transformation, hence the minus sign.
92 // For more info:
93 // http://developer.android.com/guide/topics/sensors/sensors_motion.html#sensors-motion-gyro
94 return Rotationd(AngleAxisd(-timestep_s * velocity,
95 gyroscope_value / velocity));
96}
97
98} // namespace pose_prediction
99
100// --- end of functions ported from pose_prediction.cc
101
102} // namespace
103
104SensorFusion::SensorFusion()
105 : execute_reset_with_next_accelerometer_sample_(false) {
106 ResetState();
107}
108
109void SensorFusion::Reset() {
110 execute_reset_with_next_accelerometer_sample_ = true;
111}
112
113void SensorFusion::ResetState() {
114 current_state_.timestamp_ns = 0;
115 current_state_.sensor_from_start_rotation = Rotationd::Identity();
116 current_state_.sensor_from_start_rotation_velocity = Vector3d::Zero();
117
118 current_accelerometer_timestamp_ns_ = 0;
119
120 state_covariance_ = Matrix3d::Identity() * kInitialStateCovarianceValue;
121 process_covariance_ = Matrix3d::Identity() * kInitialProcessCovarianceValue;
122 accelerometer_measurement_covariance_ =
123 Matrix3d::Identity() * kMinAccelNoiseSigma * kMinAccelNoiseSigma;
124 innovation_covariance_.setIdentity();
125
126 accelerometer_measurement_jacobian_ = Matrix3d::Zero();
127 kalman_gain_ = Matrix3d::Zero();
128 innovation_ = Vector3d::Zero();
129 accelerometer_measurement_ = Vector3d::Zero();
130 prediction_ = Vector3d::Zero();
131 control_input_ = Vector3d::Zero();
132 state_update_ = Vector3d::Zero();
133
134 moving_average_accelerometer_norm_change_ = 0.0;
135
136 is_timestep_filter_initialized_ = false;
137 is_gyroscope_filter_valid_ = false;
138 is_aligned_with_gravity_ = false;
139}
140
141// Here I am doing something wrong relative to time stamps. The state timestamps
142// always correspond to the gyrostamps because it would require additional
143// extrapolation if I wanted to do otherwise.
144// TODO(pfg): investigate about published an updated pose after accelerometer
145// data was used for filtering.
146PoseState SensorFusion::GetLatestPoseState() const {
147 std::unique_lock<std::mutex> lock(mutex_);
148 return current_state_;
149}
150
151void SensorFusion::ProcessGyroscopeSample(float v_x, float v_y, float v_z,
152 uint64 timestamp_ns) {
153 std::unique_lock<std::mutex> lock(mutex_);
154
155 // Don't accept gyroscope sample when waiting for a reset.
156 if (execute_reset_with_next_accelerometer_sample_) {
157 return;
158 }
159
160 // Discard outdated samples.
161 if (current_state_.timestamp_ns >= timestamp_ns) {
162 // TODO(pfg): Investigate why this happens.
163 return;
164 }
165
166 // Checks that we received at least one gyroscope sample in the past.
167 if (current_state_.timestamp_ns != 0) {
168 // TODO(pfg): roll this in filter gyroscope timestep function.
169 double current_timestep_s =
170 static_cast<double>(timestamp_ns - current_state_.timestamp_ns) * 1e-9;
171 if (current_timestep_s > kMaximumGyroscopeSampleDelay_s) {
172 if (is_gyroscope_filter_valid_) {
173 // Replaces the delta timestamp by the filtered estimates of the delta
174 // time.
175 current_timestep_s = filtered_gyroscope_timestep_s_;
176 } else {
177 current_timestep_s = kDefaultGyroscopeTimestep_s;
178 }
179 } else {
180 FilterGyroscopeTimestep(current_timestep_s);
181 }
182
183 // Only integrate after receiving a accelerometer sample.
184 if (is_aligned_with_gravity_) {
185 const Rotationd rotation_from_gyroscope =
186 pose_prediction::GetRotationFromGyroscope(Vector3d(v_x, v_y, v_z),
187 current_timestep_s);
188 current_state_.sensor_from_start_rotation =
189 rotation_from_gyroscope * current_state_.sensor_from_start_rotation;
190 current_state_.sensor_from_start_rotation.normalize();
191 UpdateStateCovariance(RotationMatrixNH(rotation_from_gyroscope));
192 state_covariance_ =
193 state_covariance_ +
194 (process_covariance_ * (current_timestep_s * current_timestep_s));
195 }
196 }
197
198 // Saves gyroscope event for future prediction.
199 current_state_.timestamp_ns = timestamp_ns;
200 current_state_.sensor_from_start_rotation_velocity = Vector3d(v_x, v_y, v_z);
201}
202
203// TODO(pfg): move to rotation object for the input.
204Vector3d SensorFusion::ComputeInnovation(const Rotationd& pose) {
205 const Vector3d predicted_down_direction =
206 RotationMatrixNH(pose) * kCanonicalZDirection;
207
208 const Rotationd rotation = Rotationd::FromTwoVectors(
209 predicted_down_direction, accelerometer_measurement_);
210 AngleAxisd angle_axis(rotation);
211 return angle_axis.axis() * angle_axis.angle();
212}
213
214void SensorFusion::ComputeMeasurementJacobian() {
215 for (int dof = 0; dof < 3; dof++) {
216 // TODO(pfg): Create this delta rotation in the constructor and used unitX..
217 Vector3d delta = Vector3d::Zero();
218 delta[dof] = kFiniteDifferencingEpsilon;
219
220 const Rotationd epsilon_rotation = RotationFromVector(delta);
221 const Vector3d delta_rotation = ComputeInnovation(
222 epsilon_rotation * current_state_.sensor_from_start_rotation);
223
224 const Vector3d col =
225 (innovation_ - delta_rotation) / kFiniteDifferencingEpsilon;
226 accelerometer_measurement_jacobian_(0, dof) = col[0];
227 accelerometer_measurement_jacobian_(1, dof) = col[1];
228 accelerometer_measurement_jacobian_(2, dof) = col[2];
229 }
230}
231
232void SensorFusion::ProcessAccelerometerSample(float acc_x, float acc_y,
233 float acc_z,
234 uint64 timestamp_ns) {
235 std::unique_lock<std::mutex> lock(mutex_);
236
237 // Discard outdated samples.
238 if (current_accelerometer_timestamp_ns_ >= timestamp_ns) {
239 // TODO(pfg): Investigate why this happens.
240 return;
241 }
242
243 // Call reset state if required.
244 if (execute_reset_with_next_accelerometer_sample_.exchange(false)) {
245 ResetState();
246 }
247
248 accelerometer_measurement_ = Vector3d(acc_x, acc_y, acc_z);
249 current_accelerometer_timestamp_ns_ = timestamp_ns;
250
251 if (!is_aligned_with_gravity_) {
252 // This is the first accelerometer measurement so it initializes the
253 // orientation estimate.
254 current_state_.sensor_from_start_rotation = Rotationd::FromTwoVectors(
255 kCanonicalZDirection, accelerometer_measurement_);
256 is_aligned_with_gravity_ = true;
257
258 previous_accelerometer_norm_ = Length(accelerometer_measurement_);
259 return;
260 }
261
262 UpdateMeasurementCovariance();
263
264 innovation_ = ComputeInnovation(current_state_.sensor_from_start_rotation);
265 ComputeMeasurementJacobian();
266
267 // S = H * P * H' + R
268 innovation_covariance_ = accelerometer_measurement_jacobian_ *
269 state_covariance_ *
270 Transpose(accelerometer_measurement_jacobian_) +
271 accelerometer_measurement_covariance_;
272
273 // K = P * H' * S^-1
274 kalman_gain_ = state_covariance_ *
275 Transpose(accelerometer_measurement_jacobian_) *
276 Inverse(innovation_covariance_);
277
278 // x_update = K*nu
279 state_update_ = kalman_gain_ * innovation_;
280
281 // P = (I - K * H) * P;
282 state_covariance_ = (Matrix3d::Identity() -
283 kalman_gain_ * accelerometer_measurement_jacobian_) *
284 state_covariance_;
285
286 // Updates pose and associate covariance matrix.
287 const Rotationd rotation_from_state_update =
288 RotationFromVector(state_update_);
289
290 current_state_.sensor_from_start_rotation =
291 rotation_from_state_update * current_state_.sensor_from_start_rotation;
292 UpdateStateCovariance(RotationMatrixNH(rotation_from_state_update));
293}
294
295void SensorFusion::UpdateStateCovariance(const Matrix3d& motion_update) {
296 state_covariance_ =
297 motion_update * state_covariance_ * Transpose(motion_update);
298}
299
300void SensorFusion::FilterGyroscopeTimestep(double gyroscope_timestep_s) {
301 if (!is_timestep_filter_initialized_) {
302 // Initializes the filter.
303 filtered_gyroscope_timestep_s_ = gyroscope_timestep_s;
304 num_gyroscope_timestep_samples_ = 1;
305 is_timestep_filter_initialized_ = true;
306 return;
307 }
308
309 // Computes the IIR filter response.
310 filtered_gyroscope_timestep_s_ =
311 kTimestepFilterCoeff * filtered_gyroscope_timestep_s_ +
312 (1 - kTimestepFilterCoeff) * gyroscope_timestep_s;
313 ++num_gyroscope_timestep_samples_;
314
315 if (num_gyroscope_timestep_samples_ > kTimestepFilterMinSamples) {
316 is_gyroscope_filter_valid_ = true;
317 }
318}
319
320void SensorFusion::UpdateMeasurementCovariance() {
321 const double current_accelerometer_norm = Length(accelerometer_measurement_);
322 // Norm change between current and previous accel readings.
323 const double current_accelerometer_norm_change =
324 std::abs(current_accelerometer_norm - previous_accelerometer_norm_);
325 previous_accelerometer_norm_ = current_accelerometer_norm;
326
327 moving_average_accelerometer_norm_change_ =
328 kSmoothingFactor * current_accelerometer_norm_change +
329 (1. - kSmoothingFactor) * moving_average_accelerometer_norm_change_;
330
331 // If we hit the accel norm change threshold, we use the maximum noise sigma
332 // for the accel covariance. For anything below that, we use a linear
333 // combination between min and max sigma values.
334 const double norm_change_ratio =
335 moving_average_accelerometer_norm_change_ / kMaxAccelNormChange;
336 const double accelerometer_noise_sigma = std::min(
337 kMaxAccelNoiseSigma,
338 kMinAccelNoiseSigma +
339 norm_change_ratio * (kMaxAccelNoiseSigma - kMinAccelNoiseSigma));
340
341 // Updates the accel covariance matrix with the new sigma value.
342 accelerometer_measurement_covariance_ = Matrix3d::Identity() *
343 accelerometer_noise_sigma *
344 accelerometer_noise_sigma;
345}
346
347} // namespace dvr
348} // namespace android