0

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);
        }
  • First thing I would do is get rid of the threshold stuff, at least for testing. Go ahead and integrate all the data, even if it is noisy. And then the order of arguments to the CreateFromYawPitchRoll method is confusing to me. Online doc says yaw is Y, pitch is X, and roll is Z. But typically I would have assumed yaw is Z, pitch is Y, and roll is X. You might investigate this ordering. Also the delta angles should be used as simultaneous. If this method uses them as an Euler sequence, then the resulting quaternion will be slightly off. Last thing to double check is the order of quat multiply. – James Tursa Jun 10 '22 at 21:01
  • P.S. I suspect the CreateFromYawPitchRoll method assumes Euler angle sequence, since there is a different method called CreateFromAxisAngle that assumes simultaneous angles, and would probably be more appropriate for you to use. – James Tursa Jun 10 '22 at 21:20
  • @JamesTursa thank you very much for the reply, I appreciate it. I tried without the thresholds, but the sensor is pretty noisy. The notation of the CreateFromYawPitchRoll strikes me as pretty odd, too. Very strange. Still, when I rotate the sensor, the rotation seems to be correct with respect to the direction; it's the magnitude or rather rate of rotation that is severly lagging behind. I'll definitely look at the CreateFromAxisAngle in any case. I don't quite understand how to get the axis from the rotations though. Any pointers? – Lukas Pezenka Jun 11 '22 at 07:36
  • The axis is simply deltaTRotation turned into a unit vector. The angle is simply the magnitude of deltaTRotation. Assuming these are angles measured in a body-fixed frame, the resulting quaternion will be body-to-body. I don't know your quaternion convention, so that is why I suggested you double check the order of the quaternion multiply. Finally, can you show specifically the formulae you are using to calculate rotation angle from the quaternion? – James Tursa Jun 12 '22 at 00:45
  • @JamesTursa thank you. Using CreateFromAxisAngle yields the same results as CreateFromYawPitchRoll does. Same behaviour. As for the formula: I am just converting the quaternion to a rotationmatrix (var m = Matrix4x4.CreateFromQuaternion(q)) which I am applying to a cube in my rendering window. No real math here. – Lukas Pezenka Jun 12 '22 at 08:06

0 Answers0