-
Notifications
You must be signed in to change notification settings - Fork 14k
New filter for distance sensor validation #24106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
47b163c
5a9da7d
bd746d7
16eeddc
43ed8cb
2964f4d
abb0b4c
0fa4cd4
c8e43db
f81b88e
512e555
3d14318
adad028
dda3f24
4c0e03f
10adbb3
a872c30
bd8e457
88cb887
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -36,56 +36,128 @@ | |||||
*/ | ||||||
|
||||||
#include <aid_sources/range_finder/range_finder_consistency_check.hpp> | ||||||
#include "ekf_derivation/generated/range_validation_filter_h.h" | ||||||
#include "ekf_derivation/generated/range_validation_filter_P_init.h" | ||||||
|
||||||
void RangeFinderConsistencyCheck::update(float dist_bottom, float dist_bottom_var, float vz, float vz_var, | ||||||
bool horizontal_motion, uint64_t time_us) | ||||||
using namespace matrix; | ||||||
|
||||||
void RangeFinderConsistencyCheck::init(const float z, const float z_var, const float dist_bottom, | ||||||
const float dist_bottom_var) | ||||||
{ | ||||||
if (horizontal_motion) { | ||||||
_time_last_horizontal_motion = time_us; | ||||||
} | ||||||
_P = sym::RangeValidationFilterPInit(z_var, dist_bottom_var); | ||||||
_Ht = sym::RangeValidationFilterH<float>(); | ||||||
|
||||||
_x(RangeFilter::z.idx) = z; | ||||||
_x(RangeFilter::terrain.idx) = z - dist_bottom; | ||||||
_initialized = true; | ||||||
_test_ratio_lpf.reset(0.f); | ||||||
_t_since_first_sample = 0.f; | ||||||
} | ||||||
|
||||||
void RangeFinderConsistencyCheck::update(const float z, const float z_var, const float vz, const float vz_var, | ||||||
const float dist_bottom, const float dist_bottom_var, const uint64_t time_us) | ||||||
{ | ||||||
const float dt = static_cast<float>(time_us - _time_last_update_us) * 1e-6f; | ||||||
|
||||||
if ((_time_last_update_us == 0) | ||||||
|| (dt < 0.001f) || (dt > 0.5f)) { | ||||||
if (dt > 1.f) { | ||||||
_time_last_update_us = time_us; | ||||||
_dist_bottom_prev = dist_bottom; | ||||||
_initialized = false; | ||||||
return; | ||||||
|
||||||
} else if (!_initialized) { | ||||||
init(z, z_var, dist_bottom, dist_bottom_var); | ||||||
return; | ||||||
} | ||||||
|
||||||
const float vel_bottom = (dist_bottom - _dist_bottom_prev) / dt; | ||||||
_innov = -vel_bottom - vz; // vel_bottom is +up while vz is +down | ||||||
// prediction step | ||||||
_time_last_update_us = time_us; | ||||||
_x(RangeFilter::z.idx) -= dt * vz; | ||||||
_P(RangeFilter::z.idx, RangeFilter::z.idx) += dt * dt * vz_var; | ||||||
_P(RangeFilter::terrain.idx, RangeFilter::terrain.idx) += _terrain_process_noise; | ||||||
|
||||||
// Variance of the time derivative of a random variable: var(dz/dt) = 2*var(z) / dt^2 | ||||||
const float var = 2.f * dist_bottom_var / (dt * dt); | ||||||
_innov_var = var + vz_var; | ||||||
// iterate through both measurements (z and dist_bottom) | ||||||
const Vector2f measurements{z, dist_bottom}; | ||||||
|
||||||
const float normalized_innov_sq = (_innov * _innov) / _innov_var; | ||||||
_test_ratio = normalized_innov_sq / (_gate * _gate); | ||||||
_signed_test_ratio_lpf.setParameters(dt, _signed_test_ratio_tau); | ||||||
const float signed_test_ratio = matrix::sign(_innov) * _test_ratio; | ||||||
_signed_test_ratio_lpf.update(signed_test_ratio); | ||||||
for (int measurement_idx = 0; measurement_idx < 2; measurement_idx++) { | ||||||
|
||||||
updateConsistency(vz, time_us); | ||||||
// dist_bottom | ||||||
Vector2f H = _Ht; | ||||||
float R = dist_bottom_var; | ||||||
|
||||||
_time_last_update_us = time_us; | ||||||
_dist_bottom_prev = dist_bottom; | ||||||
// z, direct state measurement | ||||||
if (measurement_idx == 0) { | ||||||
H(RangeFilter::z.idx) = 1.f; | ||||||
H(RangeFilter::terrain.idx) = 0.f; | ||||||
R = z_var; | ||||||
|
||||||
} | ||||||
|
||||||
// residual | ||||||
const float measurement_pred = H * _x; | ||||||
const float y = measurements(measurement_idx) - measurement_pred; | ||||||
|
||||||
// for H as col-vector: | ||||||
// innovation variance S = H^T * P * H + R | ||||||
// kalman gain K = P * H / S | ||||||
const float S = (H.transpose() * _P * H + R)(0, 0); | ||||||
Vector2f K = (_P * H / S); | ||||||
|
||||||
if (measurement_idx == 0) { | ||||||
K(RangeFilter::z.idx) = 1.f; | ||||||
|
||||||
} else if (measurement_idx == 1) { | ||||||
_innov = y; | ||||||
const float test_ratio = fminf(sq(y) / (sq(_gate) * S), 4.f); // limit to 4 to limit sensitivity to outliers | ||||||
_test_ratio_lpf.setParameters(dt, _t_to_init); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can set the time constant when declaring the AlphaFilter and then call the |
||||||
_test_ratio_lpf.update(sign(_innov) * test_ratio); | ||||||
} | ||||||
|
||||||
// update step | ||||||
_x(RangeFilter::z.idx) += K(RangeFilter::z.idx) * y; | ||||||
_x(RangeFilter::terrain.idx) += K(RangeFilter::terrain.idx) * y; | ||||||
|
||||||
// covariance update with Joseph form: | ||||||
// P = (I - K H) P (I - K H)^T + K R K^T | ||||||
Matrix2f I; | ||||||
I.setIdentity(); | ||||||
Matrix2f IKH = I - K.multiplyByTranspose(H); | ||||||
_P = IKH * _P * IKH.transpose() + (K * R).multiplyByTranspose(K); | ||||||
} | ||||||
|
||||||
evaluateState(dt, vz, vz_var); | ||||||
} | ||||||
|
||||||
void RangeFinderConsistencyCheck::updateConsistency(float vz, uint64_t time_us) | ||||||
void RangeFinderConsistencyCheck::evaluateState(const float dt, const float vz, const float vz_var) | ||||||
{ | ||||||
if (fabsf(_signed_test_ratio_lpf.getState()) >= 1.f) { | ||||||
if ((time_us - _time_last_horizontal_motion) > _signed_test_ratio_tau) { | ||||||
_is_kinematically_consistent = false; | ||||||
_time_last_inconsistent_us = time_us; | ||||||
// start the consistency check after 1s | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Avoid mentioning numerical values defined elsewhere as ppl will change them without updating the comments |
||||||
if (_t_since_first_sample > _t_to_init) { | ||||||
if (fabsf(_test_ratio_lpf.getState()) < 1.f) { | ||||||
const bool vertical_motion = sq(vz) > fmaxf(vz_var, 0.1f); | ||||||
|
||||||
if (!horizontal_motion && vertical_motion) { | ||||||
_state = KinematicState::CONSISTENT; | ||||||
|
||||||
} else if (_state == KinematicState::CONSISTENT || vertical_motion) { | ||||||
_state = KinematicState::UNKNOWN; | ||||||
} | ||||||
|
||||||
} else { | ||||||
_t_since_first_sample = 0.f; | ||||||
_state = KinematicState::INCONSISTENT; | ||||||
} | ||||||
|
||||||
} else { | ||||||
if ((fabsf(vz) > _min_vz_for_valid_consistency) | ||||||
&& (_test_ratio < 1.f) | ||||||
&& ((time_us - _time_last_inconsistent_us) > _consistency_hyst_time_us) | ||||||
) { | ||||||
_is_kinematically_consistent = true; | ||||||
} | ||||||
_t_since_first_sample += dt; | ||||||
} | ||||||
} | ||||||
|
||||||
void RangeFinderConsistencyCheck::run(const float z, const float z_var, const float vz, const float vz_var, | ||||||
const float dist_bottom, const float dist_bottom_var, const uint64_t time_us) | ||||||
{ | ||||||
if (!_initialized || current_posD_reset_count != _last_posD_reset_count) { | ||||||
_last_posD_reset_count = current_posD_reset_count; | ||||||
_initialized = false; | ||||||
} | ||||||
|
||||||
update(z, z_var, vz, vz_var, dist_bottom, dist_bottom_var, time_us); | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -48,36 +48,62 @@ class RangeFinderConsistencyCheck final | |||||
RangeFinderConsistencyCheck() = default; | ||||||
~RangeFinderConsistencyCheck() = default; | ||||||
|
||||||
void update(float dist_bottom, float dist_bottom_var, float vz, float vz_var, bool horizontal_motion, uint64_t time_us); | ||||||
enum class KinematicState : int { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
INCONSISTENT = 0, | ||||||
CONSISTENT = 1, | ||||||
UNKNOWN = 2 | ||||||
}; | ||||||
|
||||||
void setGate(float gate) { _gate = gate; } | ||||||
float getTestRatioLpf() const { return _initialized ? _test_ratio_lpf.getState() : 0.f; } | ||||||
float getInnov() const { return _initialized ? _innov : 0.f; } | ||||||
float getInnovVar() const { return _initialized ? _innov_var : 0.f; } | ||||||
bool isKinematicallyConsistent() const { return _state == KinematicState::CONSISTENT && _t_since_first_sample > _t_to_init; } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't the |
||||||
bool isNotKinematicallyInconsistent() const { return _state != KinematicState::INCONSISTENT && _t_since_first_sample > _t_to_init; } | ||||||
void setGate(const float gate) { _gate = gate; } | ||||||
void run(float z, float z_var, float vz, float vz_var, | ||||||
float dist_bottom, float dist_bottom_var, uint64_t time_us); | ||||||
void set_terrain_process_noise(float terrain_process_noise) { _terrain_process_noise = terrain_process_noise; } | ||||||
void reset() | ||||||
{ | ||||||
if (_initialized && _state == KinematicState::CONSISTENT) { | ||||||
_state = KinematicState::UNKNOWN; | ||||||
} | ||||||
|
||||||
float getTestRatio() const { return _test_ratio; } | ||||||
float getSignedTestRatioLpf() const { return _signed_test_ratio_lpf.getState(); } | ||||||
float getInnov() const { return _innov; } | ||||||
float getInnovVar() const { return _innov_var; } | ||||||
bool isKinematicallyConsistent() const { return _is_kinematically_consistent; } | ||||||
_initialized = false; | ||||||
} | ||||||
|
||||||
private: | ||||||
void updateConsistency(float vz, uint64_t time_us); | ||||||
|
||||||
uint64_t _time_last_update_us{}; | ||||||
float _dist_bottom_prev{}; | ||||||
|
||||||
float _test_ratio{}; | ||||||
AlphaFilter<float> _signed_test_ratio_lpf{}; // average signed test ratio used to detect a bias in the data | ||||||
float _gate{.2f}; | ||||||
float _innov{}; | ||||||
float _innov_var{}; | ||||||
|
||||||
bool _is_kinematically_consistent{true}; | ||||||
uint64_t _time_last_inconsistent_us{}; | ||||||
uint64_t _time_last_horizontal_motion{}; | ||||||
uint8_t current_posD_reset_count{0}; | ||||||
bool horizontal_motion{false}; | ||||||
Comment on lines
+75
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except if not possible otherwise, member variables should always be private and set/access with setter and getter functions. (and prefixed with |
||||||
|
||||||
static constexpr float _signed_test_ratio_tau = 2.f; | ||||||
private: | ||||||
|
||||||
static constexpr float _min_vz_for_valid_consistency = .5f; | ||||||
static constexpr uint64_t _consistency_hyst_time_us = 1e6; | ||||||
void update(float z, float z_var, float vz, float vz_var, float dist_bottom, | ||||||
float dist_bottom_var, uint64_t time_us); | ||||||
void init(float z, float z_var, float dist_bottom, float dist_bottom_var); | ||||||
void evaluateState(float dt, float vz, float vz_var); | ||||||
float _terrain_process_noise{0.0f}; | ||||||
matrix::SquareMatrix<float, 2> _P{}; | ||||||
matrix::Vector2f _Ht{}; | ||||||
matrix::Vector2f _x{}; | ||||||
bool _initialized{false}; | ||||||
float _innov{0.f}; | ||||||
float _innov_var{0.f}; | ||||||
uint64_t _time_last_update_us{0}; | ||||||
static constexpr float time_constant{1.f}; | ||||||
AlphaFilter<float> _test_ratio_lpf{time_constant}; | ||||||
float _gate{1.0f}; | ||||||
KinematicState _state{KinematicState::UNKNOWN}; | ||||||
float _t_since_first_sample{0.f}; | ||||||
uint8_t _last_posD_reset_count{0}; | ||||||
static constexpr float _t_to_init{1.f}; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
}; | ||||||
|
||||||
namespace RangeFilter | ||||||
{ | ||||||
struct IdxDof { unsigned idx; unsigned dof; }; | ||||||
static constexpr IdxDof z{0, 1}; | ||||||
static constexpr IdxDof terrain{1, 1}; | ||||||
static constexpr uint8_t size{2}; | ||||||
} | ||||||
|
||||||
#endif // !EKF_RANGE_FINDER_CONSISTENCY_CHECK_HPP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.