Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

EKF: Problem with quaternion covariance reset #541

Closed
priseborough opened this issue Dec 20, 2018 · 34 comments
Closed

EKF: Problem with quaternion covariance reset #541

priseborough opened this issue Dec 20, 2018 · 34 comments
Assignees

Comments

@priseborough
Copy link
Collaborator

priseborough commented Dec 20, 2018

When doing a yaw reset using the Ekf::initialiseQuatCovariances(Vector3f &rot_vec_var) method (see https://github.com/PX4/ecl/blob/master/EKF/ekf_helper.cpp#L1429) if the vehicle has a large roll or pitch angle (eg tailsitter), the use of a rotation vector variance to represent the uncertainty does not enable the uncertainty in yaw to be changed without affecting the tilt (roll/pitch) variance.

A better method is required.

@priseborough
Copy link
Collaborator Author

priseborough commented Dec 20, 2018

I've derived equations that should enable the yaw uncertainty to be increased. This will eliminate the need for a all of nothing reset of quaternion covariances following a reset of the yaw angle.

See https://github.com/PX4/ecl/tree/pr-ekfYawCovReset

In the process I found some inconsistencies with the way resets were being handled in different places in code that could affect the switchover between position aiding sources or when recovering from a reset following takeoff near a large magnetic anomaly. This is related to #510

This work will be continued in the new year.

@priseborough priseborough changed the title EKF: Problem with quaternion covariance reset at large tilt angles EKF: Problem with quaternion covariance reset Dec 23, 2018
@priseborough
Copy link
Collaborator Author

priseborough commented Dec 23, 2018

Further investigation of the code showed that this part of a wider issue that can affect all vehicle types that need to use an in-air reset to recover from a ground based magnetic yaw error. This is due to:

  1. The preservation of off-diagonal elements in the covariance matrix in the 0-3 (quaternion state index) rows and columns that can lead to incorrect fusion of GPS and other NE frame observations following a state reset
  2. The failure to preserve exisiting correlation information in the [4x4] quaternion state covariance sub-matrix when a full reset is applied.

Fundamentally the exisiting approach to handling the reset of covariances after a yaw reset was 'all or nothing'.

The investigation also revealed that the reset of the yaw angle to enable recovery from ground based magnetic anomally is conditional on switching to 3-axis fusion and can therefore fail to execute depending on the setting of EKF2_MAG_TYPE and the flight profile. A PR to fix this has been submitted #544.

A PR will be submitted following further testing. To assist with this a modification to the sitl_gazebo simulaton is being used that includes a crude model of a ground based magnetic anomally - see https://github.com/priseborough/sitl_gazebo/tree/earthFrameMagError

@priseborough priseborough pinned this issue Dec 23, 2018
@priseborough
Copy link
Collaborator Author

@priseborough
Copy link
Collaborator Author

priseborough commented Dec 24, 2018

I was able to simulate a large tilt angle at the time of the yaw reset by using the 'make posix_sitl gazebo_tailsitter' option, however this did require disabling the "Preflight Fail: Airspeed Sensor stuck" check in the commander and changing the step size and real time update rate in tailsitter.world to 250 Hz, eg <max_step_size>0.004</max_step_size>
<real_time_update_rate>250</real_time_update_rate>

Result with only critical yaw reset bug fix applied: https://logs.px4.io/plot_app?log=7e5f40a7-00d7-49c7-a0a6-8d422144a907.

Note that the reset has the effect of reducing overall angular uncertainty (sum of variances), which is not the correct result.

Innovations:
screen shot 2018-12-24 at 11 00 50 am

Quaternion Variances:
screen shot 2018-12-24 at 11 01 32 am

Test with all https://github.com/PX4/ecl/tree/pr-ekfYawCovReset changes: https://logs.px4.io/plot_app?log=69dd8220-570d-4897-be79-e2290eedcbd7

Note that the q(0) term increases when the reset occurs, but the other terms are largely unmodified. This makes sense for a vehicle that has the X axis pointing up with an additional yaw uncertainty.

Innovations:
screen shot 2018-12-24 at 11 05 09 am

Quaternion Variances:
screen shot 2018-12-24 at 11 05 55 am

@CarlOlsson
Copy link
Contributor

@priseborough when testing this branch I found this issue
#548

@CarlOlsson
Copy link
Contributor

CarlOlsson commented Jan 4, 2019

@priseborough
I tested this for a quad in sitl
image

As can be seen the variance for the q[0] and q[3] is increased which is correct.

I was able to simulate a large tilt angle at the time of the yaw reset by using the 'make posix_sitl gazebo_tailsitter' option

Are you sure about this? As far as I know standard PX4 uses a convention for tailsitters such that the coordinate frame is similar to that of a quad in hover. I.e. in cruise the tailsitter flies along -z direction.

Without doing a hand held test with a real system I am not sure how to test the reset at high pitch angles in SITL. However, I pulled in the changes to our code base and this is the result

image

This seems incorrect since the variance of all quaternion states increased with a similar amount
Edit: Actually when rotating a quaternion around the x axis all elements of the quaternion change so this might be all correct. We will have to convert the covariance to a yaw variance and verify the calculations

@CarlOlsson
Copy link
Contributor

@priseborough
I tested this code by adding the entire 4x4 quaternion covariance matrix to logging and running

make px4_sitl gazebo

Link to logfile

I then converted the quaternion covariance matrix to an Euler covariance matrix following these equations
(They are the same as the ones used here)

However, the result is not as expected, the blue and green lines overlap so the largest increase in variance is in the middle diagonal element (pitch).
image

@priseborough
Copy link
Collaborator Author

priseborough commented Jan 8, 2019

However if you plot the quaternion state variances, the biggest increase is in q3 and less for q0 with q1 and q2 variance increase being negligible which is consistent with an increase in yaw uncertainty for a vehicle that is close to zero roll and pitch.

screen shot 2019-01-08 at 7 08 06 pm

screen shot 2019-01-08 at 7 08 25 pm

@CarlOlsson
Copy link
Contributor

Yes, which indicates that the code is working as intended. I just need to figure out why the conversion to Euler angles does not give the expected result

@TSC21
Copy link
Member

TSC21 commented Jan 8, 2019

@CarlOlsson I think it's related with the 321 and 312 variations that Paul spoke of and I think I know how to handle those on the covariance law. I will do an update later today.

@CarlOlsson
Copy link
Contributor

CarlOlsson commented Jan 8, 2019

Yes me too, I just did a test with a quad. Hover for some time, increase heading noise, hover, decrease heading noise
image

The middle element in the covariance matrix is definitely yaw and not pitch

@CarlOlsson
Copy link
Contributor

CarlOlsson commented Jan 9, 2019

Okey, so the confusion around the quaternion convention is sorted out in #543

What is left for this is to verify that the yaw uncertainty is increasing correctly for all attitudes, e.g. at pitch 90 degrees.
In order to do that I calculated the Jacobian of the Euler angles to quaternions for both a 321 and 312 sequence.
Comparing the yaw variance on the logfile described here gives the following result

image

This seems correct.

I then simulated a flight where the vehicle flew in a square (standard attitude, so body z facing down). In the middle of every leg I triggered uncorrelateQuatStates() and increaseQuatYawErrVariance()

This is the result
image

As can be seen the variance from the 321 sequence seems correct. but for the 312 sequence something is strange, for the east and west direction no increase of uncertainty is observed

@CarlOlsson
Copy link
Contributor

For reference, here is the G matrices

    #  Yaw using 321 Euler sequence
    G321[0][0] = 2*q4*(-2*q3**2 - 2*q4**2 + 1)/((2*q1*q4 + 2*q2*q3)**2 + (-2*q3**2 - 2*q4**2 + 1)**2)
    G321[0][1] = 2*q3*(-2*q3**2 - 2*q4**2 + 1)/((2*q1*q4 + 2*q2*q3)**2 + (-2*q3**2 - 2*q4**2 + 1)**2)
    G321[0][2] = 2*q2*(-2*q3**2 - 2*q4**2 + 1)/((2*q1*q4 + 2*q2*q3)**2 + (-2*q3**2 - 2*q4**2 + 1)**2) - 4*q3*(-2*q1*q4 - 2*q2*q3)/((2*q1*q4 + 2*q2*q3)**2 + (-2*q3**2 - 2*q4**2 + 1)**2)
    G321[0][3] = 2*q1*(-2*q3**2 - 2*q4**2 + 1)/((2*q1*q4 + 2*q2*q3)**2 + (-2*q3**2 - 2*q4**2 + 1)**2) - 4*q4*(-2*q1*q4 - 2*q2*q3)/((2*q1*q4 + 2*q2*q3)**2 + (-2*q3**2 - 2*q4**2 + 1)**2)
    
    # Yaw using 312 Euler sequence
    G312[0][0] = 2*q1*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) + 2.0*q4*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)
    G312[0][1] = -2*q2*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) - 2.0*q3*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)
    G312[0][2] = -2.0*q2*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) + 2*q3*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)
    G312[0][3] = 2.0*q1*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) - 2*q4*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)

@TSC21
Copy link
Member

TSC21 commented Jan 9, 2019

@CarlOlsson have you tried to use the same G matrix for both 321 and 312 rotation orders? I tried to use the same for these and I seemed to get the same results on plots.

Update: nevermind, sorry. I used for 321 and 123, not 312. We probably need to face that case as well, though I am not sure how we identify the rotation order on the function so to apply the proper G matrix.

@CarlOlsson
Copy link
Contributor

@CarlOlsson have you tried to use the same G matrix for both 321 and 312 rotation orders? I tried to use the same for these and I seemed to get the same results on plots.

I don't understand what you mean. The Jacobian (G matrix) is defined by the way you calculate the yaw angle which depends on the Euler sequence. How can you have different Euler sequences with the same Jacobian?

@TSC21
Copy link
Member

TSC21 commented Jan 9, 2019

I can't really tell but for the 321 and 123 orders, applying the same G matrix, I get the same results. I can try to verify that on math. But I suppose that doesn't apply for a 312 rotation case.

@CarlOlsson
Copy link
Contributor

We probably need to face that case as well, though I am not sure how we identify the rotation order on the function so to apply the proper G matrix.

You can probably use the same method as here

For the logfile mentioned above, here is the raw quaternions
East
image

North
image

West
image

South
image

@TSC21
Copy link
Member

TSC21 commented Jan 9, 2019

We probably need to face that case as well, though I am not sure how we identify the rotation order on the function so to apply the proper G matrix.

You can probably use the same method as here

For the logfile mentioned above, here is the raw quaternions
East
image

North
image

West
image

South
image

Yeah actually @mhkabir pointed me that as well. I'm just trying to figure how it would apply here.

@CarlOlsson
Copy link
Contributor

CarlOlsson commented Jan 10, 2019

I tested this code again, this time on a sitl quad.
I planned a square mission (log), all waypoints are 45 second loiter
image

Then at every waypoint I triggered uncorrelateQuatStates() and increaseQuatYawErrVariance() two times

This time I sometimes got
EKF mag yaw fusion numerical error - covariance reset
when I triggered it.

Comparing the yaw variance calculated using the 321 and 312 Euler sequence looks like this
image

As can be seen they give similar result for the north/south direction but not for east/west. I don't know why at the moment.

yaw for the 312 calculation is given by
yaw = atan2(-2.0 * (q2 * q3 - q1 * q4), q1 * q1 - q2 * q2 + q3 * q3 - q4 * q4)

and the G matrix is:

G[0][0] = 2*q1*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) + 2.0*q4*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)
G[0][1] = -2*q2*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) - 2.0*q3*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)
G[0][2] = -2.0*q2*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) + 2*q3*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)
G[0][3] = 2.0*q1*(q1**2 - q2**2 + q3**2 - q4**2)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2) - 2*q4*(-2.0*q1*q4 + 2.0*q2*q3)/((2.0*q1*q4 - 2.0*q2*q3)**2 + (q1**2 - q2**2 + q3**2 - q4**2)**2)

@priseborough
Copy link
Collaborator Author

Where are you getting the derivation for the conversion from a quaternion rotation to a 312 Tait-Bryan sequence and the derivation for the associated partial derivative matrix?

@CarlOlsson
Copy link
Contributor

The conversion from quaternion rotation to a rotation matrix comes from here, and then to 312 Tait-Bryan yaw comes from here

The derivation for the partial derivative matrix comes from the following python script

from sympy import *
q1, q2, q3, q4 = symbols('q1 q2 q3 q4')

yaw = atan2(-2.0 * (q2 * q3 - q1 * q4), q1 * q1 - q2 * q2 + q3 * q3 - q4 * q4)
yaw_q1 = diff(yaw, q1)
yaw_q2 = diff(yaw, q2)
yaw_q3 = diff(yaw, q3)
yaw_q4 = diff(yaw, q4)

print('G[0][0] = ' + str(yaw_q1))
print('G[0][1] = ' + str(yaw_q2))
print('G[0][2] = ' + str(yaw_q3))
print('G[0][3] = ' + str(yaw_q4))

@priseborough
Copy link
Collaborator Author

priseborough commented Jan 14, 2019

I've done further investigation today and the "EKF mag yaw fusion numerical error - covariance reset" could be the result of setting the off-diagonals to zero and having fusion take place before the next covariance prediction. How were you triggering uncorrelateQuatStates() and increaseQuatYawErrVariance() ? I will add code to the branch so that the fusion is blocked until after the next covariance prediction.

Using Matlab I independently derived the Tait-Bryan first rotation (yaw) in a 312 sequence as a function of the quaternions and got

yaw = atan2(2q0q3 - 2q1q2, q0^2 - q1^2 + q2^2 - q3^2);

d_yaw/d_q0 =

(((2q3)/(q0^2 - q1^2 + q2^2 - q3^2) - (2q0*(2q0q3 - 2q1q2))/(q0^2 - q1^2 + q2^2 - q3^2)^2)(q0^2 - q1^2 + q2^2 - q3^2)^2)/((2q0q3 - 2q1*q2)^2 + (q0^2 - q1^2 + q2^2 - q3^2)^2);

d_yaw/d_q1 =

-(((2q2)/(q0^2 - q1^2 + q2^2 - q3^2) - (2q1*(2q0q3 - 2q1q2))/(q0^2 - q1^2 + q2^2 - q3^2)^2)(q0^2 - q1^2 + q2^2 - q3^2)^2)/((2q0q3 - 2q1*q2)^2 + (q0^2 - q1^2 + q2^2 - q3^2)^2);

d_yaw/d_q2 =

-(((2q1)/(q0^2 - q1^2 + q2^2 - q3^2) + (2q2*(2q0q3 - 2q1q2))/(q0^2 - q1^2 + q2^2 - q3^2)^2)(q0^2 - q1^2 + q2^2 - q3^2)^2)/((2q0q3 - 2q1*q2)^2 + (q0^2 - q1^2 + q2^2 - q3^2)^2);

d_yaw/d_q3 =

(((2q0)/(q0^2 - q1^2 + q2^2 - q3^2) + (2q3*(2q0q3 - 2q1q2))/(q0^2 - q1^2 + q2^2 - q3^2)^2)(q0^2 - q1^2 + q2^2 - q3^2)^2)/((2q0q3 - 2q1*q2)^2 + (q0^2 - q1^2 + q2^2 - q3^2)^2);

TaitBryan312.m.txt

Quat2Tbn.m.txt

@CarlOlsson
Copy link
Contributor

CarlOlsson commented Jan 14, 2019

I compared these equations to the previous results from sympy, the outcome is identical

Green and orange overlap
image

The reset was triggered in ekf2_main.cpp from the console
using this

if (trigger_yaw_var) {
	_ekf.uncorrelateQuatStates();
	float add_uncertainty = fmaxf(_params->mag_heading_noise, 1.0e-2f);
	_ekf.increaseQuatYawErrVariance(add_uncertainty * add_uncertainty);
	trigger_yaw_var = false;
	_yaw_var_inc_counter++;
}

This was added before the _ekf.update() function so at least one covariance prediction step should happen before a measurement fusion is triggered.
The branch used to generate the logfile is here:
https://github.com/CarlOlsson/Firmware/tree/yawCovReset_testing

@priseborough
Copy link
Collaborator Author

OK, so I will focus the investigation for the time being on the cause of the 'EKF mag yaw fusion numerical error - covariance reset' events. One thing to try is to ensure the covariance prediction runs before the next fusion step.

I would like to be able to reproduce your results in my testing using the same mission plan and firmware modifications.

@priseborough
Copy link
Collaborator Author

Thank you to @CarlOlsson and Sebastian for spotting a sign error that was introduced when the auto-code had been cleaned up by hand in the increaseQuatYawErrVariance function. The https://github.com/PX4/ecl/tree/pr-ekfYawCovReset branch has been rebased has been rebased and the error fixed here:
69c29ad

A compile error introduced when the a rebase conflict was resolved has also been fixed - see
47cf16f

@CarlOlsson
Copy link
Contributor

@priseborough As discussed I pushed two commits to this branch.
3d716c4 Also fill in the lower part of the covariance matrix
d36be5b Limit the yaw variance increase to 0.01 rad^2

@priseborough
Copy link
Collaborator Author

priseborough commented Jan 17, 2019

@CarlOlsson I've pushed three commits to the branch which should address the issue of unstable declination immediately following a field reset when using 3-axis fusion and the loss of declination covariance information when switching in and out of 3-axis fusion in auto mode in into 3-axis fusion with EKf2_MAG_TYPE = 0

7ff0ba6
fa5dee7
3d2c38c

@priseborough
Copy link
Collaborator Author

Currently investigating the first switch into 3-axis fusion with EKF2_MAG_TYPE=0.

The mag field variances appear to be initialising to zero.

screen shot 2019-01-17 at 4 27 12 pm

@CarlOlsson
Copy link
Contributor

Actually not only this problem seems to be initializing the covariances to zero

There is also a problem when we call fuseDeclination() in resetMagYaw(). When we call this function the first time we activate 3d fusion we also call fixCovarianceErrors(), and here we set the mag field state covariances to zero since _control_status.flags.mag_3D is false

@priseborough
Copy link
Collaborator Author

The covariance reset to 0 bug has been fixed here:
ad9414e

SITL log: https://logs.px4.io/plot_app?log=39f319e4-847f-467c-9056-78fb917c0515

@LorenzMeier
Copy link
Member

@priseborough I've put up a PR for easier reference to that commit: #556

@CarlOlsson
Copy link
Contributor

@priseborough Some comments to the updated code in this branch

  1. The fuseDeclination() function in resetMagHeading() is never triggered here because _control_status.flags.mag_3D is always false.

  2. This is actually also on master but since EKF: Critical yaw reset bug fix #544 the reset logic in controlMagFusion() is never triggered
    https://github.com/PX4/ecl/blob/ad9414e4dadc6a7a25d33b7edc219775d8a88086/EKF/control.cpp#L1455-L1470
    since _control_status.flags.mag_align_complete is always set to true here

@priseborough
Copy link
Collaborator Author

priseborough commented Jan 21, 2019

@CarlOlsson I will try another approach to simplify the way fuseDeclination() is only called, either immediately before fuseMag() if it hasn't been called since the last yaw reset or immediately after if _control_status.flags.mag_dec is true. This will simplify the overall logic.

Discussion continued in #544.

@mhkabir
Copy link
Member

mhkabir commented Feb 1, 2019

Fixed on master.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants