I'm working on getting the orientation of an IMU in space, following the instructions from https://ahrs.readthedocs.io/en/latest/filters/madgwick.html. I can already determine the orientation on the basis of the magnetometer (this suffers badly from very fast movements though, so I want to go the gyro route).
The rotation basically does what it is supposed to do (I use the rotation matrix from the quaternion to render a colored box in real time), but at a much lower rate. In essence, the rotation that results from my code is approximately (but not quite) 1/2 as fast as the one in the real world.
Simply multiplying the angle with 2
- does not quite solve the problem (there still results a certain lag)
- does not help me in understanding the problem
Is what I am doing conceptually sound? Is there a major issue I just don't see? As I need this stuff for my master's thesis (in exercise science), any help is greatly appreciated.
UPDATE: I have implemented the advice by @JamesTursa and switched to Quaternion.CreateFromAxisAngle() instead of Quaternion.CreateFromYawPitchRoll(). Unfortunately, this did not solve the issue. I have also implemented a unit test for the Quaternion class to check whether or not it behaves as expected. It does, so I doubt there's an error with that.
After reading through Efficient quaternion angular velocity, I have also tried using the half angle, but this does exactly what I supposed and simply slows the rotation further.
So, I am still stuck with a rotation that basically does approximately (but not quite) half of what it is supposed to. Again, any help is eppreciated.
/// <summary>
/// Numerically integrate angular velocities in x,y,z to get orientation in x,y,z
/// Does not work. Rotation happens at around (but not quite) half the rate as in the real world.
/// </summary>
/// <param name="dt">time in ms since last invocation</param>
private void UpdateAngularVelocities(double dt)
{
// rotation threshold (°/sec) to minimize noise
// set to 0 for the time being
double threshold = 0.0;
// iterative quaternion multiplication based on madgwick filter
// https://ahrs.readthedocs.io/en/latest/filters/madgwick.html
// keep an AngularOrientation Vector for illustration purposes
// apply rotation velocity threshold
// ie, discard any rotation that is slower than the defined threshold
var wtx = (Math.Abs(w[0]) > threshold) ? w[0] : 0.0;
var wty = (Math.Abs(w[1]) > threshold) ? w[1] : 0.0;
var wtz = (Math.Abs(w[2]) > threshold) ? w[2] : 0.0;
// rotation velocities, stored as a Vector for easier use
// theses will be used to onstruct the Quaternion
Vector3 deltaTRotation = new Vector3((float)ConvertToRadians(wtx), (float)ConvertToRadians(wty), (float)ConvertToRadians(wtz));
Vector3 deltaAxis = deltaTRotation;
// scale rotation with time
// dt is supplied in ms, we need s to get from °/s to °
// use half angle because of
// https://stackoverflow.com/questions/24197182/efficient-quaternion-angular-velocity/24201879#24201879
deltaTRotation.X *= (float)(dt / 1000.0) * 0.5f;
deltaTRotation.Y *= (float)(dt / 1000.0) * 0.5f;
deltaTRotation.Z *= (float)(dt / 1000.0) * 0.5f;
// build quaternion from scaled rotations
// use Quaternion.CreateFromAxisAngle() to get simultaneous angles
// https://stackoverflow.com/questions/72578478/imu-orientation-from-gyro
// Quaternion qDelta = Quaternion.CreateFromYawPitchRoll(deltaTRotation.Y, deltaTRotation.X, deltaTRotation.Z);
Quaternion qDelta = Quaternion.CreateFromAxisAngle(Vector3.Normalize(deltaAxis), deltaTRotation.Length());
// deal with NaN, in case no Rotation takes place and hence, ||qCurrent|| = 0 which makes Normalize throw NaN
if (qDelta.Length() > 0.0)
{
// combine new rotation with previous rotation
qRotation = qDelta * qRotation;
//AdditiveOrientation += 0.5f * Vector4.Normalize(new Vector4(deltaTRotation.X, deltaTRotation.Y, deltaTRotation.Z, deltaTRotation.W)) * qDelta.Length();
}
// https://ahrs.readthedocs.io/en/latest/filters/madgwick.html suggests an addition of the old state and the new state.
// this results in the quaternion not being a unit quaternion / versor anymore. Ditch the addition and stick with simple multiplication
//qRotation = qRotation + Quaternion.Multiply(qCurrent * qRotation, (float)(0.5));// * dt / 1000.0));
//qRotation = Quaternion.Normalize(qRotation);
// rotate magnetic orientation for illustration purposes
// always start rotation from (arbitrary) [1,0,0] and apply most recent quaternion
// does not work yet
// rotates roughly half the angle it should
AngularOrientation = Vector4.Transform(new Vector4(1.0f,0.0f,0.0f,1.0f), qRotation);
// save angular velocities for later use
OmegaXData.Add(deltaTRotation.X);
OmegaYData.Add(deltaTRotation.Y);
OmegaZData.Add(deltaTRotation.Z);
}