Skip to content

Commit d07902d

Browse files
committed
clean up range-filter fusion step
1 parent 92ad8ac commit d07902d

File tree

10 files changed

+104
-72
lines changed

10 files changed

+104
-72
lines changed

msg/EstimatorStatusFlags.msg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ bool cs_opt_flow_terrain # 40 - true if we are fusing flow data for terra
4848
bool cs_valid_fake_pos # 41 - true if a valid constant position is being fused
4949
bool cs_constant_pos # 42 - true if the vehicle is at a constant position
5050
bool cs_baro_fault # 43 - true when the current baro has been declared faulty and is no longer being used
51-
bool cs_rng_kin_unknown # 43 - true when the range finder kinematic consistency check is not running
51+
bool cs_rng_kin_unknown # 44 - true when the range finder kinematic consistency check is not running
5252

5353
# fault status
5454
uint32 fault_status_changes # number of filter fault status (fs) changes

src/modules/ekf2/EKF/aid_sources/range_finder/range_finder_consistency_check.cpp

Lines changed: 82 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,84 +36,125 @@
3636
*/
3737

3838
#include <aid_sources/range_finder/range_finder_consistency_check.hpp>
39-
#include "ekf_derivation/generated/range_validation_filter.h"
39+
#include "ekf_derivation/generated/range_validation_filter_h.h"
4040

4141
using namespace matrix;
4242

4343
void RangeFinderConsistencyCheck::init(const float &z, const float &z_var, const float &dist_bottom,
4444
const float &dist_bottom_var)
4545
{
46-
float p[4] = {z_var, z_var, z_var, z_var + dist_bottom_var};
47-
_P = SquareMatrix<float, RangeFilter::size>(p);
48-
_H = sym::RangeValidationFilter<float>();
46+
// assume no correlation between z and terrain on initialization
47+
_P(RangeFilter::z.idx, RangeFilter::z.idx) = z_var;
48+
_P(RangeFilter::z.idx, RangeFilter::terrain.idx) = 0.f;
49+
_P(RangeFilter::terrain.idx, RangeFilter::z.idx) = 0.f;
50+
_P(RangeFilter::terrain.idx, RangeFilter::terrain.idx) = z_var + dist_bottom_var;
51+
52+
_Ht = sym::RangeValidationFilterH<float>();
53+
4954
_x(RangeFilter::z.idx) = z;
5055
_x(RangeFilter::terrain.idx) = z - dist_bottom;
5156
_initialized = true;
5257
_state = KinematicState::UNKNOWN;
53-
_test_ratio_lpf.reset(2.f);
58+
_test_ratio_lpf.reset(0);
5459
_t_since_first_sample = 0.f;
55-
_test_ratio_lpf.setAlpha(0.2f);
5660
}
5761

5862
void RangeFinderConsistencyCheck::update(const float &z, const float &z_var, const float &vz, const float &vz_var,
5963
const float &dist_bottom, const float &dist_bottom_var, const uint64_t &time_us)
6064
{
6165
const float dt = static_cast<float>(time_us - _time_last_update_us) * 1e-6f;
6266

63-
if (_time_last_update_us == 0 || dt > 1.f) {
67+
if (dt > 1.f) {
6468
_time_last_update_us = time_us;
69+
_initialized = false;
70+
return;
71+
72+
} else if (!_initialized) {
6573
init(z, z_var, dist_bottom, dist_bottom_var);
74+
_test_ratio_lpf.setParameters(dt, 1.f);
6675
return;
6776
}
6877

78+
// prediction step
6979
_time_last_update_us = time_us;
70-
7180
_x(RangeFilter::z.idx) -= dt * vz;
72-
_P(0, 0) += dt * dt * vz_var + 0.001f;
73-
_P(1, 1) += terrain_process_noise;
81+
_P(RangeFilter::z.idx, RangeFilter::z.idx) += dt * dt * vz_var + 0.001f;
82+
_P(RangeFilter::terrain.idx, RangeFilter::terrain.idx) += terrain_process_noise;
7483

75-
const Vector2f measurements(z, dist_bottom);
84+
// iterate through both measurements (z and dist_bottom)
85+
const Vector2f measurements{z, dist_bottom};
7686

77-
Vector2f Kv{1.f, 0.f};
78-
Vector2f test_ratio{0.f, 0.f};
79-
Vector2f R{z_var, dist_bottom_var};
80-
Vector2f y;
87+
for (int measurement_idx = 0; measurement_idx < 2; measurement_idx++) {
8188

82-
for (int i = 0; i < 2 ; i++) {
83-
y = measurements - _H * _x;
84-
Vector2f H = _H.row(i);
85-
_innov_var = (H.transpose() * _P * H + R(i))(0, 0);
86-
Kv(RangeFilter::terrain.idx) = _P(RangeFilter::terrain.idx, i) / _innov_var;
89+
float hz; // Jacobian in respect to z
90+
float ht; // Jacobian in respect to terrain
91+
float R;
92+
bool reject = false;
8793

88-
Vector2f PH = _P.row(i);
94+
if (measurement_idx == 0) {
95+
// direct state measurement
96+
hz = 1.f;
97+
ht = 0.f;
98+
R = z_var;
8999

90-
for (int u = 0; u < RangeFilter::size; u++) {
91-
for (int v = 0; v < RangeFilter::size; v++) {
92-
_P(u, v) -= Kv(u) * PH(v);
93-
}
100+
} else if (measurement_idx == 1) {
101+
hz = _Ht(0, 0);
102+
ht = _Ht(0, 1);
103+
R = dist_bottom_var;
94104
}
95105

96-
PH = _P.col(i);
97-
98-
for (int u = 0; u < RangeFilter::size; u++) {
99-
for (int v = 0; v <= u; v++) {
100-
_P(u, v) = _P(u, v) - PH(u) * Kv(v) + Kv(u) * R(i) * Kv(v);
101-
_P(v, u) = _P(u, v);
102-
}
106+
// residual
107+
const float measurement_pred = hz * _x(RangeFilter::z.idx) + ht * _x(RangeFilter::terrain.idx);
108+
const float y = measurements(measurement_idx) - measurement_pred;
109+
110+
// innovation variance H * P * H^T + R
111+
const float S = hz * (hz * _P(RangeFilter::z.idx, RangeFilter::z.idx)
112+
+ ht * _P(RangeFilter::terrain.idx, RangeFilter::z.idx))
113+
+ ht * (hz * _P(RangeFilter::z.idx, RangeFilter::terrain.idx)
114+
+ ht * _P(RangeFilter::terrain.idx, RangeFilter::terrain.idx))
115+
+ R;
116+
117+
// kalman gain K = P * H^T / S
118+
float Kz = (hz * _P(RangeFilter::z.idx, RangeFilter::z.idx)
119+
+ ht * _P(RangeFilter::z.idx, RangeFilter::terrain.idx)) / S;
120+
const float Kt = (hz * _P(RangeFilter::terrain.idx, RangeFilter::z.idx)
121+
+ ht * _P(RangeFilter::terrain.idx, RangeFilter::terrain.idx)) / S;
122+
123+
if (measurement_idx == 0) {
124+
Kz = 1.f;
125+
126+
} else if (measurement_idx == 1) {
127+
_innov = y;
128+
const float test_ratio = fminf(sq(y) / (sq(_gate) * S), 4.f); // limit to 4 to limit sensitivity to outliers
129+
_test_ratio_lpf.update(sign(_innov) * test_ratio);
130+
reject = test_ratio > 1.f;
103131
}
104132

105-
test_ratio(i) = fminf(sq(y(i)) / sq(_gate), 4.f);
106-
107-
if (i == (int)RangeFilter::z.idx && test_ratio(1) > 1.f) {
108-
Kv(1) = 0.f;
133+
if (!reject) {
134+
// update step
135+
_x(RangeFilter::z.idx) += Kz * y;
136+
_x(RangeFilter::terrain.idx) += Kt * y;
137+
138+
// covariance update with Joseph form:
139+
// P = (I - K H) P (I - K H)^T + K R K^T
140+
Matrix2f I;
141+
I.setIdentity();
142+
Matrix<float, 2, 1> K;
143+
K(0, 0) = Kz;
144+
K(1, 0) = Kt;
145+
Vector2f H0;
146+
H0(0) = hz;
147+
H0(1) = ht;
148+
Matrix2f IKH0 = I - K * H0.transpose();
149+
_P = IKH0 * _P * IKH0.transpose() + K * R * K.transpose();
109150
}
110-
111-
_x = _x + Kv * y;
112151
}
113152

114-
_innov = y(RangeFilter::terrain.idx);
115-
_test_ratio_lpf.update(sign(_innov) * test_ratio(1));
153+
evaluateState(dt, vz, vz_var);
154+
}
116155

156+
void RangeFinderConsistencyCheck::evaluateState(const float &dt, const float &vz, const float &vz_var)
157+
{
117158
// start the consistency check after 1s
118159
if (_t_since_first_sample + dt > 1.0f) {
119160
_t_since_first_sample = 2.0f;
@@ -147,7 +188,7 @@ void RangeFinderConsistencyCheck::run(const float &z, const float &vz,
147188

148189
if (!_initialized || current_posD_reset_count != _last_posD_reset_count) {
149190
_last_posD_reset_count = current_posD_reset_count;
150-
init(z, z_var, dist_bottom, dist_bottom_var);
191+
_initialized = false;
151192
}
152193

153194
update(z, z_var, vz, vz_var, dist_bottom, dist_bottom_var, time_us);

src/modules/ekf2/EKF/aid_sources/range_finder/range_finder_consistency_check.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,16 @@ class RangeFinderConsistencyCheck final
7979
void update(const float &z, const float &z_var, const float &vz, const float &vz_var, const float &dist_bottom,
8080
const float &dist_bottom_var, const uint64_t &time_us);
8181
void init(const float &z, const float &z_var, const float &dist_bottom, const float &dist_bottom_var);
82+
void evaluateState(const float &dt, const float &vz, const float &vz_var);
8283
matrix::SquareMatrix<float, 2> _P{};
83-
matrix::SquareMatrix<float, 2> _H{};
84+
matrix::Matrix<float, 1, 2> _Ht{};
8485
matrix::Vector2f _x{};
8586
bool _initialized{false};
8687
float _innov{0.f};
8788
float _innov_var{0.f};
8889
uint64_t _time_last_update_us{0};
8990
AlphaFilter<float> _test_ratio_lpf{};
90-
float _gate{1.f};
91+
float _gate{1.0f};
9192
KinematicState _state{KinematicState::UNKNOWN};
9293
float _t_since_first_sample{0.f};
9394
uint8_t _last_posD_reset_count{0};

src/modules/ekf2/EKF/aid_sources/range_finder/range_height_control.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ void Ekf::controlRangeHaglFusion(const imuSample &imu_sample)
217217
}
218218

219219
} else {
220-
ECL_WARN("stoppPing %s fusion, continuing conditions failing", HGT_SRC_NAME);
220+
ECL_WARN("stopping %s fusion, continuing conditions failing", HGT_SRC_NAME);
221221
stopRngHgtFusion();
222222
stopRngTerrFusion();
223223
}

src/modules/ekf2/EKF/common.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ struct parameters {
422422
float range_aid_innov_gate{1.0f}; ///< gate size used for innovation consistency checks for range aid fusion
423423
float range_valid_quality_s{1.0f}; ///< minimum duration during which the reported range finder signal quality needs to be non-zero in order to be declared valid (s)
424424
float range_cos_max_tilt{0.7071f}; ///< cosine of the maximum tilt angle from the vertical that permits use of range finder and flow data
425-
float range_kin_consistency_gate{1.0f}; ///< gate size used by the range finder kinematic consistency check
425+
float range_kin_consistency_gate{0.5f}; ///< gate size used by the range finder kinematic consistency check
426426
float rng_fog{0.f}; ///< max distance which a blocked range sensor measures (fog, dirt) [m]
427427

428428
Vector3f rng_pos_body{}; ///< xyz position of range sensor in body frame (m)
@@ -620,8 +620,8 @@ uint64_t mag_heading_consistent :
620620
uint64_t opt_flow_terrain : 1; ///< 40 - true if we are fusing flow data for terrain
621621
uint64_t valid_fake_pos : 1; ///< 41 - true if a valid constant position is being fused
622622
uint64_t constant_pos : 1; ///< 42 - true if the vehicle is at a constant position
623-
uint64_t baro_fault : 1; ///< 43 - true when the baro has been declared faulty and is no longer being used
624-
uint64_t rng_kin_unknown : 1; ///< xx - true when the range finder kinematic consistency check is not running
623+
uint64_t baro_fault : 1; ///< 43 - true when the baro has been declared faulty and is no longer being used
624+
uint64_t rng_kin_unknown : 1; ///< 44 - true when the range finder kinematic consistency check is not running
625625

626626
} flags;
627627
uint64_t value;

src/modules/ekf2/EKF/python/ekf_derivation/derivation.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -721,17 +721,16 @@ def compute_gravity_z_innov_var_and_h(
721721

722722
return (innov_var, H.T)
723723

724-
def range_validation_filter() -> sf.Matrix:
724+
def range_validation_filter_h() -> sf.Matrix:
725725

726726
state = Values(
727727
z=sf.Symbol("z"),
728728
terrain=sf.Symbol("terrain")
729729
)
730730
dist_bottom = state["z"] - state["terrain"]
731731

732-
H = sf.M22()
733-
H[0, :] = sf.V1(state["z"]).jacobian(state.to_storage(), tangent_space=False)
734-
H[1, :] = sf.V1(dist_bottom).jacobian(state.to_storage(), tangent_space=False)
732+
H = sf.M12()
733+
H[0, :] = sf.V1(dist_bottom).jacobian(state.to_storage(), tangent_space=False)
735734

736735
return H
737736

@@ -766,6 +765,6 @@ def range_validation_filter() -> sf.Matrix:
766765
generate_px4_function(compute_body_vel_innov_var_h, output_names=["innov_var", "Hx", "Hy", "Hz"])
767766
generate_px4_function(compute_body_vel_y_innov_var, output_names=["innov_var"])
768767
generate_px4_function(compute_body_vel_z_innov_var, output_names=["innov_var"])
769-
generate_px4_function(range_validation_filter, output_names=None)
768+
generate_px4_function(range_validation_filter_h, output_names=None)
770769

771770
generate_px4_state(State, tangent_idx)

src/modules/ekf2/EKF/python/ekf_derivation/generated/range_validation_filter.h renamed to src/modules/ekf2/EKF/python/ekf_derivation/generated/range_validation_filter_h.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,26 @@ namespace sym {
1313
/**
1414
* This function was autogenerated from a symbolic function. Do not modify by hand.
1515
*
16-
* Symbolic function: range_validation_filter
16+
* Symbolic function: range_validation_filter_h
1717
*
1818
* Args:
1919
*
2020
* Outputs:
21-
* res: Matrix22
21+
* res: Matrix12
2222
*/
2323
template <typename Scalar>
24-
matrix::Matrix<Scalar, 2, 2> RangeValidationFilter() {
24+
matrix::Matrix<Scalar, 1, 2> RangeValidationFilterH() {
2525
// Total ops: 0
2626

2727
// Input arrays
2828

2929
// Intermediate terms (0)
3030

3131
// Output terms (1)
32-
matrix::Matrix<Scalar, 2, 2> _res;
33-
34-
_res.setZero();
32+
matrix::Matrix<Scalar, 1, 2> _res;
3533

3634
_res(0, 0) = 1;
37-
_res(1, 0) = 1;
38-
_res(1, 1) = -1;
35+
_res(0, 1) = -1;
3936

4037
return _res;
4138
} // NOLINT(readability/fn_size)

src/modules/ekf2/params_range_finder.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ parameters:
113113
...). Note: tune the range finder noise parameters (EKF2_RNG_NOISE and EKF2_RNG_SFE)
114114
before tuning this gate.'
115115
type: float
116-
default: 1.0
116+
default: 0.5
117117
unit: SD
118118
min: 0.1
119119
max: 5.0

src/modules/ekf2/test/test_EKF_height_fusion.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,8 @@ TEST_F(EkfHeightFusionTest, baroRef)
113113

114114
// AND WHEN: the baro data increases
115115
const float baro_increment = 4.f;
116-
const float baro_initial = _sensor_simulator._baro.getData();
117-
const float baro_inc = 0.2f;
118-
119-
// increase slowly, height jump leads to invalid terrain estimate
120-
while (_sensor_simulator._baro.getData() + baro_inc < baro_initial + baro_increment + 0.01f) {
121-
_sensor_simulator._baro.setData(_sensor_simulator._baro.getData() + baro_inc);
122-
_sensor_simulator.runSeconds(3);
123-
}
116+
_sensor_simulator._baro.setData(_sensor_simulator._baro.getData() + baro_increment);
117+
_sensor_simulator.runSeconds(60);
124118

125119
// THEN: the height estimate converges to the baro value
126120
// and the other height sources are getting their bias estimated

src/modules/ekf2/test/test_EKF_terrain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,11 @@ TEST_F(EkfTerrainTest, testRngStartInAir)
259259

260260
const float corr_terrain_vz = P(State::vel.idx + 2,
261261
State::terrain.idx) / sqrtf(_ekf->getVelocityVariance()(2) * var_terrain);
262-
EXPECT_TRUE(corr_terrain_vz > 0.6f);
262+
EXPECT_TRUE(fabsf(corr_terrain_vz) > 0.6f && fabsf(corr_terrain_vz) < 1.f);
263263

264264
const float corr_terrain_z = P(State::pos.idx + 2,
265265
State::terrain.idx) / sqrtf(_ekf->getPositionVariance()(2) * var_terrain);
266-
EXPECT_TRUE(corr_terrain_z > 0.8f);
266+
EXPECT_TRUE(fabsf(corr_terrain_z) > 0.8f && fabsf(corr_terrain_z) < 1.f);
267267

268268
const float corr_terrain_abias_z = P(State::accel_bias.idx + 2,
269269
State::terrain.idx) / sqrtf(_ekf->getAccelBiasVariance()(2) * var_terrain);

0 commit comments

Comments
 (0)