Alex Vakulenko | e4eec20 | 2017-01-27 14:41:04 -0800 | [diff] [blame] | 1 | #include "sensor_fusion.h" |
| 2 | |
| 3 | #include <algorithm> |
| 4 | #include <cmath> |
| 5 | |
| 6 | #include <private/dvr/eigen.h> |
| 7 | |
| 8 | namespace android { |
| 9 | namespace dvr { |
| 10 | |
| 11 | namespace { |
| 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 | |
| 19 | inline Matrix3d Inverse(const Matrix3d& matrix) { return matrix.inverse(); } |
| 20 | inline Matrix3d Transpose(const Matrix3d& matrix) { return matrix.transpose(); } |
| 21 | inline Matrix3d RotationMatrixNH(const Rotationd& rotation) { |
| 22 | return rotation.toRotationMatrix(); |
| 23 | } |
| 24 | inline double Length(const Vector3d& vector) { return vector.norm(); } |
| 25 | |
| 26 | using uint64 = uint64_t; |
| 27 | |
| 28 | // --- end of added bits for porting to eigen |
| 29 | |
| 30 | static const double kFiniteDifferencingEpsilon = 1e-7; |
| 31 | static const double kEpsilon = 1e-15; |
| 32 | // Default gyroscope frequency. This corresponds to 200 Hz. |
| 33 | static const double kDefaultGyroscopeTimestep_s = 0.005f; |
| 34 | // Maximum time between gyroscope before we start limiting the integration. |
| 35 | static const double kMaximumGyroscopeSampleDelay_s = 0.04f; |
| 36 | // Compute a first-order exponential moving average of changes in accel norm per |
| 37 | // frame. |
| 38 | static 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. |
| 42 | static const double kMinAccelNoiseSigma = 0.75; |
| 43 | static const double kMaxAccelNoiseSigma = 7.0; |
| 44 | // Initial value for the diagonal elements of the different covariance matrices. |
| 45 | static const double kInitialStateCovarianceValue = 25.0; |
| 46 | static const double kInitialProcessCovarianceValue = 1.0; |
| 47 | // Maximum accelerometer norm change allowed before capping it covariance to a |
| 48 | // large value. |
| 49 | static const double kMaxAccelNormChange = 0.15; |
| 50 | // Timestep IIR filtering coefficient. |
| 51 | static const double kTimestepFilterCoeff = 0.95; |
| 52 | // Minimum number of sample for timestep filtering. |
| 53 | static const uint32_t kTimestepFilterMinSamples = 10; |
| 54 | |
| 55 | // Z direction in start space. |
| 56 | static 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. |
| 62 | static 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 | |
| 72 | namespace 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. |
| 82 | Rotationd 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 | |
| 104 | SensorFusion::SensorFusion() |
| 105 | : execute_reset_with_next_accelerometer_sample_(false) { |
| 106 | ResetState(); |
| 107 | } |
| 108 | |
| 109 | void SensorFusion::Reset() { |
| 110 | execute_reset_with_next_accelerometer_sample_ = true; |
| 111 | } |
| 112 | |
| 113 | void 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. |
| 146 | PoseState SensorFusion::GetLatestPoseState() const { |
| 147 | std::unique_lock<std::mutex> lock(mutex_); |
| 148 | return current_state_; |
| 149 | } |
| 150 | |
| 151 | void 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. |
| 204 | Vector3d 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 | |
| 214 | void 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 | |
| 232 | void 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 | |
| 295 | void SensorFusion::UpdateStateCovariance(const Matrix3d& motion_update) { |
| 296 | state_covariance_ = |
| 297 | motion_update * state_covariance_ * Transpose(motion_update); |
| 298 | } |
| 299 | |
| 300 | void 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 | |
| 320 | void 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 |