1

I have a followup question from this post : Extracting Yaw from a Quaternion .

Alike the OP, I want to move away from Euler rotations and use Quaternions. My scenario is as follows:

I am wearing an Apple smartwatch on my left hand and performing the bicep curls exercise. I do a few reps in the first set, and stop. I then rotate my body 90 degrees to the right in my room, and do another set of a few repetitions. I do this 4 times, and store the quaternions to disk using the xArbitraryCorrectedZVertical frame. When I use Euler rotations to describe pitch, using below code, I see a sinusoidal shape. When I want to reproduce the same pattern using quaternions, I get close, except the signal drifts. Here is my code:

clc;
clear;
quaternions_table = readtable(strcat("quaternions"), 'Delimiter',',');
attitude = quaternion(quaternions_table.w, quaternions_table.x, quaternions_table.y, quaternions_table.z);

quats = zeros(length(attitude), 1);
eulers = zeros(length(attitude), 1);
for i = 1:length(attitude)
    [w,x,y,z] = parts(attitude(i) * quaternion(0,0,0,1));
    quats(i) = rad2deg(atan2(z,w));

    [w,x,y,z] = parts(attitude(i));
    eulers(i) = rad2deg(asin(2*(y * w - z * x)));
end

figure(1);
clf;
hold on;
plot(quats, '.r');
plot(eulers, '.b');
legend('Q', 'E');
hold off;

Here is a plot of the difference:

enter image description here

How can I fix my code so that the quaternion generated pattern is equivalent to the euler's pitch, regardless of my "body" rotation?

Some sample data: https://filebin.net/02k2323ccbct1n38/quaternions

angryip
  • 2,140
  • 5
  • 33
  • 67
  • You refer to YAW but you seem to be plotting the PITCH… – picchiolu Dec 28 '22 at 12:48
  • 1
    @picchiolu sorry about that, updated the Q – angryip Dec 28 '22 at 13:08
  • So, as you are looking for the PITCH, could you explain the use of ```[w,x,y,z] = parts(attitude(i) * quaternion(0,0,0,1));``` followed by ```quats(i) = rad2deg(atan2(z,w));```? – picchiolu Dec 28 '22 at 15:15
  • I'd compute the angle between ```attitude``` and the conjugate quaternion for a rotation around the Y-axis, which should involve, in pseudo-code, ```Qxa = conj(quaternion(0,0,1,0))*attitude```, – picchiolu Dec 28 '22 at 15:38
  • @picchiolu i modified the suggestion in the referenced comment a bit : https://stackoverflow.com/questions/5782658/extracting-yaw-from-a-quaternion#comment68512414_5783030 . i multiplied by quaternion(0,0,0,1) to essentially "push" the angle such that it is all visible on the plot. otherwise, it wraps around the poles of the atan2 function ( as expected ). – angryip Dec 28 '22 at 15:58
  • I am not sure I agree/understand the rationale behind the multiplication by ```quaternion(0,0,0,1)``` you provided. I thought you were trying to compute the instantaneous angle between two quaternions in order to extract the angle in between... (i.e. the pitch) – picchiolu Dec 28 '22 at 16:04
  • @picchiolu i'm really in the hunt to reproduce the signal so that I can get away from ever using euler rotations. my solution is definitely wrong ( as seen in the graph ), i just don't know the solution – angryip Dec 28 '22 at 16:08
  • @picchiolu i have, the pattern is actually even more different. i tried all combinations to include atan2(x,w), atan2(y,w), and atan2(z,w). atan2(z,w) seems to be the closest, which would make sense given the orientation frame being Z vertical. – angryip Dec 28 '22 at 16:17
  • So, you have tried atan2(z,w) without multiplying attitude by quaternion(0,0,0,1), correct? – picchiolu Dec 28 '22 at 16:22
  • yep, i sure have. – angryip Dec 28 '22 at 16:24
  • One last question: as we can see that the quaternions capture the attitude correctly (otherwise you wouldn't get the correct PITCH angle using the conversion to Euler's angles), why would you need to recompute the PITCH in a different way? I don't understand what you are trying to accomplish. The angle you compute in quats can never be just the PITCH, 'cause it is actually the principal angle of the rotation that would produce the i-th attitude, and it's certainly not an angle expressing a pure rotation around the Z-axis. – picchiolu Dec 28 '22 at 16:42
  • The argument i have seen thus far is to move away from euler rotations and go to quaternions since they do not suffer from singularities. Consider the code here that performs handling around the poles: http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/ . Since the argument is to move towards quaternions, I wanted to reproduce the same patterns i get with euler rotations ( ex: pitch ), move on with my life and not worry about it again. – angryip Dec 28 '22 at 16:51
  • The only way to extract the correct pitch from the quaternions without using euler angles would require to offset (subtract) the pure yaw rotation when it occurs (i.e. between the curls)... so that you are sure the principal angle is actually the pitch angle... – picchiolu Dec 28 '22 at 17:09
  • ooo the idea makes sense. can you share some code? that would help me see if it works. – angryip Dec 28 '22 at 17:34
  • mm i think i have an idea of what you mean. will try something in a bit and report back – angryip Dec 28 '22 at 19:23
  • did you mean something of this nature? ( re-normalize a quaternion without the z component, and apply it to the original to subtract? ) % remove everything but the z [w,x,y,~] = parts(attitude(i)); norm = w^2 + x^2 + y^2; w = w / norm; x = x / norm; y = y / norm; qWithNoise = quaternion(w,x,y,0); qOrig = attitude(i); qRes = qWithNoise * qOrig * quatinv(qWithNoise); [w,~,~,z] = parts(qRes); quats(i) = rad2deg(atan2(z,w)); qRes = qYaw * qOrig * quatinv(qYaw); [w,x,y,z] = parts(qRes); quats(i) = rad2deg(atan2(z,w)); – angryip Dec 28 '22 at 21:14
  • ( the pattern still has drift, but less, so it is likely that the path is correct ) – angryip Dec 28 '22 at 21:15
  • Turns out I was wrong. I posted an answer with my best attempt at tackling the problem... – picchiolu Dec 28 '22 at 23:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/250731/discussion-between-angryip-and-picchiolu). – angryip Dec 29 '22 at 08:36

1 Answers1

1

DISCLAIMER 1: this is a first attempt at putting together an answer. What follows is by no means conclusive. Any feedback aimed at improving this answer is sincerely welcome.

DISCLAIMER 2: I don't have access to any toolboxes featuring functions related to quaternions, so I relied on what's available on Mathworks's File Exchange, to which I added a function that builds quaternions by extracting their components from a table. Using the quaternion-related functions provided in Matlab's toolboxes, the OP should be able to replicate the calculations showcased hereinafter.

DISCLAIMER 3: the selection of the point to which the rotations are applied matters. Picking a different P will affect the resulting plot. In particular, with points far from P(1,0,0) the 3D map of the successive positions in space doesn't appear to be compatible with the physics of the OP moving the smart-watch in the way described in the question.

With the sample data provided by the OP, I was able to apply the quaternions to the point with coordinates P(1,0,0) i.e. a point on the global x-axis, using (pseudo-code):

% Pseudo code
rotatedP = Qi * P * conjugate(Qi);

where Qi is the i-th quaternion extracted from the sample data.

Here's a plot showing the positions in space assumed by the point after being rotated:

enter image description here

Once the coordinates of the point are available, calculating the pitch could be done by computing the acos of the dot product between the unit vector parallel to OP (O being the global origin) and the unit vector parallel to the z-axis (with components (0,0,1)), in pseudo-code:

% Pseudo code
pitch = acos(OP.z/norm(OP))

Here's a plot of the pitch shifted using the first value of the array, which is extremely close to what was obtained by the OP using Euler's angles:

enter image description here

Matlab code

Here's the code I used to generate the plots. As mentioned erlier, I relied on Przemyslaw Baranski's Quaternions. Please, note that my code is a very crude attempt at tackling the problem, with basically no optimization. Uncommenting the lines for the 3D plot slows down the process considerably:

function draw_quats()

% point to be rotated
P = [ 1, 0, 0 ];

% Import quaternion components
q_tab = readtable(strcat("quaternions_data"), 'Delimiter',',');

pitch = [];
pp = 1;

for k = 1:1:numel(q_tab.w)
    % create a quaternion for rotation
    Qrot = qGetQuaternionFromComponents(q_tab.x(k), q_tab.y(k), q_tab.z(k), q_tab.w(k));
    
    % rotate point
    Prot = qRotatePoint( P, Qrot );
    Pnorm = sqrt(Prot(1)^2 + Prot(2)^2 + Prot(3)^2);
    
%     % display axes
%     quiver3( 0,0,0, 1,0,0 );
%     hold on;
%     quiver3( 0,0,0, 0,1,0 );
%     quiver3( 0,0,0, 0,0,1 );
%     
%     % display point
%     plot3( Prot(1), Prot(2), Prot(3), 'b.' );
%     
%     % setup the plot
%     grid on;
%     axis equal;
%     axis([-1  1.0 -1 1.0 -1 1.0]);
%     xlabel( 'x' );
%     ylabel( 'y' );
%     zlabel( 'z' );
%     
%     view( 45, 20 );
%     drawnow;
    
    % Compute pitch
    pitch(pp) = acos(Prot(3)/Pnorm)*180/pi;
    pp = pp + 1;
end

figure
plot(pitch-pitch(1),'.k');
end

And this is the definition of qGetQuaternionFromComponents:

function Q = qGetQuaternionFromComponents( x, y, z, w )
    Q = [w x y z]';
end

In the event you have some of the toolboxes by MATLAB, you can do something like this:

for i = 1:length(attitude)
    qPoint = quaternion(0,-1,0,0);
    q = attitude(i);
    [~,x,y,z] = parts(q * qPoint * conj(q));
    Pnorm = sqrt(x^2 + y^2 + z^2);
    quats(i) = asind(z/Pnorm);
end

References

Euclideanspace.com

Mathworks' FileExchange

picchiolu
  • 1,120
  • 5
  • 20
  • If necessary, I can post the code I have, although it references functions downloaded from Mathworks' FileExchange. – picchiolu Dec 28 '22 at 23:37
  • can you share the code? it’s fine if it references the exchange. i can edit it – angryip Dec 29 '22 at 07:10
  • 1
    our thoughts align. I originally used the same approach to rotate a point about the axis, but for some reason, i could never get the same signal as i can after your answer. perhaps a typo somewhere. the first graph was very, very useful! thank you for your help, patience, and visualization. – angryip Dec 29 '22 at 08:21
  • no objection. would probably need to change the qPoint to be (0,1,0,0). Swapping the sign and the trig function eliminates the need to do pitch-pitch(1). – angryip Dec 29 '22 at 08:30