An effective way to apply the mirror effect when turning a quaternion?

Quaternions are rotations - they do not contain information about scaling or mirroring. However, one can still reflect the effect of rotation.

Consider the mirror image on the xy plane (we can also call this mirroring along the z axis). A rotation around the x axis reflected on the xy plane would be negative. Similarly with rotation around the y axis. However, rotation about the z axis would remain unchanged.

Another example: a rotation of 90º around the axis (1,1,1), reflected in the xy plane, will give a rotation of -90º around (1,1, -1). To help your intuition, if you can visualize an image of an axis and a circular arrow indicating a rotation, then mirroring this visualization indicates what should be a new rotation.

I found a way to compute this rotation mirroring, for example:

  • Get an idea of ​​the quaternion along the axis of the angles.
  • For each of the x, y, and z axes.
    • If the scaling is negative (mirror image) along this axis:
      • Reject both the angle and the axis.
  • Get the updated quaternion from the changed angle and axis.

This only supports mirroring along the major x, y, and z axes, since that's all I need. However, it works for arbitrary turns.

However, transitions from the quaternion to the angular axis and vice versa from the angular axis to the quaternion are expensive. I wonder if there is a way to do the conversion directly on the quaternion itself, but my understanding of the mathematics of the quaternion is not enough to get anywhere.

( StackOverflow, - .)

+4
6

, .

, . , R1to2 C1 C2, C1 C2 (, , , , , ).

:

R_mirroredC1_to_mirroredC2 = M_mirrorC2 * R_C1_to_C2 * M_mirrorC1

R_C1_to_C2, R_mirroredC1_to_mirroredC2 , , , q_mirroredC1_to_mirroredC2 q_C1_to_C2?

, q_C1_to_C2=[w,x,y,z]:

  • C1 C2 X (.. M_mirrorC1=M_mirrorC2=diag_3x3(-1,1,1)), q_mirroredC1_to_mirroredC2=[w,x,-y,-z]
  • C1 C2 Y (.. M_mirrorC1=M_mirrorC2=diag_3x3(1,-1,1)), q_mirroredC1_to_mirroredC2=[w,-x,y,-z]
  • C1 C2 Z (.. M_mirrorC1=M_mirrorC2=diag_3x3(1,1,-1)), q_mirroredC1_to_mirroredC2=[w,-x,-y,z]

C1 C2 :

  • C1 X C2 Y (.. M_mirrorC1=diag_3x3(-1,1,1) M_mirrorC2=diag_3x3(1,-1,1)), q_mirroredC1_to_mirroredC2=[z,y,x,w]
  • C1 X C2 Z (.. M_mirrorC1=diag_3x3(-1,1,1) M_mirrorC2=diag_3x3(1,1,-1)), q_mirroredC1_to_mirroredC2=[-y,z,-w,x]

  • C1 Y C2 X (.. M_mirrorC1=diag_3x3(1,-1,1) M_mirrorC2=diag_3x3(-1,1,1)), q_mirroredC1_to_mirroredC2=[z,-y,-x,w]

  • C1 Y C2 Z ( M_mirrorC1=diag_3x3(1,-1,1) M_mirrorC2=diag_3x3(1,1,-1)), q_mirroredC1_to_mirroredC2=[x,w,z,y]

  • C1 Z C2 X ( M_mirrorC1=diag_3x3(1,1,-1) M_mirrorC2=diag_3x3(-1,1,1)), q_mirroredC1_to_mirroredC2=[y,z,w,x]

  • C1 Z C2 Y (.. M_mirrorC1=diag_3x3(1,1,-1) M_mirrorC2=diag_3x3(1,-1,1)), q_mirroredC1_to_mirroredC2=[x,w,-z,-y]

++, OpenCV, :

#include <opencv2/opencv.hpp>
#define CST_PI 3.1415926535897932384626433832795

// Random rotation matrix uniformly sampled from SO3 (see "Fast random rotation matrices" by J.Arvo)
cv::Matx<double,3,3> get_random_rotmat()
{
    double theta1 = 2*CST_PI*cv::randu<double>();
    double theta2 = 2*CST_PI*cv::randu<double>();
    double x3 = cv::randu<double>();
    cv::Matx<double,3,3> R(std::cos(theta1),std::sin(theta1),0,-std::sin(theta1),std::cos(theta1),0,0,0,1);
    cv::Matx<double,3,1> v(std::cos(theta2)*std::sqrt(x3),std::sin(theta2)*std::sqrt(x3),std::sqrt(1-x3));
    return -1*(cv::Matx<double,3,3>::eye()-2*v*v.t())*R;
}

cv::Matx<double,4,1> rotmat2quatwxyz(const cv::Matx<double,3,3> &R)
{
    // Implementation from Ceres 1.10
    const double trace = R(0,0) + R(1,1) + R(2,2);
    cv::Matx<double,4,1> quat_wxyz;
    if (trace >= 0.0) {
        double t = sqrt(trace + 1.0);
        quat_wxyz(0) = 0.5 * t;
        t = 0.5 / t;
        quat_wxyz(1) = (R(2,1) - R(1,2)) * t;
        quat_wxyz(2) = (R(0,2) - R(2,0)) * t;
        quat_wxyz(3) = (R(1,0) - R(0,1)) * t;
    } else {
        int i = 0;
        if (R(1, 1) > R(0, 0))
            i = 1;
        if (R(2, 2) > R(i, i))
            i = 2;

        const int j = (i + 1) % 3;
        const int k = (j + 1) % 3;
        double t = sqrt(R(i, i) - R(j, j) - R(k, k) + 1.0);
        quat_wxyz(i + 1) = 0.5 * t;
        t = 0.5 / t;
        quat_wxyz(0)     = (R(k,j) - R(j,k)) * t;
        quat_wxyz(j + 1) = (R(j,i) + R(i,j)) * t;
        quat_wxyz(k + 1) = (R(k,i) + R(i,k)) * t;
    }
    // Check that the w element is positive
    if(quat_wxyz(0)<0)
        quat_wxyz *= -1;    // quat and -quat represent the same rotation, but to make quaternion comparison easier, we always use the one with positive w
    return quat_wxyz;
}

cv::Matx<double,4,1> apply_quaternion_trick(const unsigned int item_permuts[4], const int sign_flips[4], const cv::Matx<double,4,1>& quat_wxyz)
{
    // Flip the sign of the x and z components
    cv::Matx<double,4,1> quat_flipped(sign_flips[0]*quat_wxyz(item_permuts[0]),sign_flips[1]*quat_wxyz(item_permuts[1]),sign_flips[2]*quat_wxyz(item_permuts[2]),sign_flips[3]*quat_wxyz(item_permuts[3]));
    // Check that the w element is positive
    if(quat_flipped(0)<0)
        quat_flipped *= -1; // quat and -quat represent the same rotation, but to make quaternion comparison easier, we always use the one with positive w
    return quat_flipped;
}

void detect_quaternion_trick(const cv::Matx<double,4,1> &quat_regular, const cv::Matx<double,4,1> &quat_flipped, unsigned int item_permuts[4], int sign_flips[4])
{
    if(abs(quat_regular(0))==abs(quat_flipped(0))) {
        item_permuts[0]=0;
        sign_flips[0] = (quat_regular(0)/quat_flipped(0)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(0))==abs(quat_flipped(1))) {
        item_permuts[1]=0;
        sign_flips[1] = (quat_regular(0)/quat_flipped(1)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(0))==abs(quat_flipped(2))) {
        item_permuts[2]=0;
        sign_flips[2] = (quat_regular(0)/quat_flipped(2)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(0))==abs(quat_flipped(3))) {
        item_permuts[3]=0;
        sign_flips[3] = (quat_regular(0)/quat_flipped(3)>0 ? 1 : -1);
    }
    if(abs(quat_regular(1))==abs(quat_flipped(0))) {
        item_permuts[0]=1;
        sign_flips[0] = (quat_regular(1)/quat_flipped(0)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(1))==abs(quat_flipped(1))) {
        item_permuts[1]=1;
        sign_flips[1] = (quat_regular(1)/quat_flipped(1)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(1))==abs(quat_flipped(2))) {
        item_permuts[2]=1;
        sign_flips[2] = (quat_regular(1)/quat_flipped(2)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(1))==abs(quat_flipped(3))) {
        item_permuts[3]=1;
        sign_flips[3] = (quat_regular(1)/quat_flipped(3)>0 ? 1 : -1);
    }
    if(abs(quat_regular(2))==abs(quat_flipped(0))) {
        item_permuts[0]=2;
        sign_flips[0] = (quat_regular(2)/quat_flipped(0)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(2))==abs(quat_flipped(1))) {
        item_permuts[1]=2;
        sign_flips[1] = (quat_regular(2)/quat_flipped(1)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(2))==abs(quat_flipped(2))) {
        item_permuts[2]=2;
        sign_flips[2] = (quat_regular(2)/quat_flipped(2)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(2))==abs(quat_flipped(3))) {
        item_permuts[3]=2;
        sign_flips[3] = (quat_regular(2)/quat_flipped(3)>0 ? 1 : -1);
    }
    if(abs(quat_regular(3))==abs(quat_flipped(0))) {
        item_permuts[0]=3;
        sign_flips[0] = (quat_regular(3)/quat_flipped(0)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(3))==abs(quat_flipped(1))) {
        item_permuts[1]=3;
        sign_flips[1] = (quat_regular(3)/quat_flipped(1)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(3))==abs(quat_flipped(2))) {
        item_permuts[2]=3;
        sign_flips[2] = (quat_regular(3)/quat_flipped(2)>0 ? 1 : -1);
    }
    else if(abs(quat_regular(3))==abs(quat_flipped(3))) {
        item_permuts[3]=3;
        sign_flips[3] = (quat_regular(3)/quat_flipped(3)>0 ? 1 : -1);
    }
}

int main(int argc, char **argv)
{
    cv::Matx<double,3,3> M_xflip(-1,0,0,0,1,0,0,0,1);
    cv::Matx<double,3,3> M_yflip(1,0,0,0,-1,0,0,0,1);
    cv::Matx<double,3,3> M_zflip(1,0,0,0,1,0,0,0,-1);

    // Let the user choose the configuration
    char im,om;
    std::cout << "Enter the axis (x,y,z) along which input ref is flipped:" << std::endl;
    std::cin >> im;
    std::cout << "Enter the axis (x,y,z) along which output ref is flipped:" << std::endl;
    std::cin >> om;
    cv::Matx<double,3,3> M_iflip,M_oflip;
    if(im=='x') M_iflip=M_xflip;
    else if(im=='y') M_iflip=M_yflip;
    else if(im=='z') M_iflip=M_zflip;
    if(om=='x') M_oflip=M_xflip;
    else if(om=='y') M_oflip=M_yflip;
    else if(om=='z') M_oflip=M_zflip;

    // Generate random quaternions until we find one where no two elements are equal
    cv::Matx<double,3,3> R;
    cv::Matx<double,4,1> quat_regular,quat_flipped;
    do {
        R = get_random_rotmat();
        quat_regular = rotmat2quatwxyz(R);
    } while(quat_regular(0)==quat_regular(1) || quat_regular(0)==quat_regular(2) || quat_regular(0)==quat_regular(3) ||
            quat_regular(1)==quat_regular(2) || quat_regular(1)==quat_regular(3) ||
            quat_regular(2)==quat_regular(3));

    // Determine and display the appropriate quaternion trick
    quat_flipped = rotmat2quatwxyz(M_oflip*R*M_iflip);
    unsigned int item_permuts[4]={0,1,2,3};
    int sign_flips[4]={1,1,1,1};
    detect_quaternion_trick(quat_regular,quat_flipped,item_permuts,sign_flips);
    char str_quat[4]={'w','x','y','z'};
    std::cout << std::endl << "When iref is flipped along the " << im << "-axis and oref along the " << om << "-axis:" << std::endl;
    std::cout << "resulting_quat=[" << (sign_flips[0]>0?"":"-") << str_quat[item_permuts[0]] << ","
                                    << (sign_flips[1]>0?"":"-") << str_quat[item_permuts[1]] << ","
                                    << (sign_flips[2]>0?"":"-") << str_quat[item_permuts[2]] << ","
                                    << (sign_flips[3]>0?"":"-") << str_quat[item_permuts[3]] << "], where initial_quat=[w,x,y,z]" << std::endl;

    // Test this trick on several random rotation matrices
    unsigned int n_errors = 0, n_tests = 10000;
    std::cout << std::endl << "Performing " << n_tests << " tests on random rotation matrices:" << std::endl;
    for(unsigned int i=0; i<n_tests; ++i) {

        // Get a random rotation matrix and the corresponding quaternion
        cv::Matx<double,3,3> R = get_random_rotmat();
        cv::Matx<double,4,1> quat_regular = rotmat2quatwxyz(R);
        // Get the quaternion corresponding to the flipped coordinate frames, via the sign trick and via computation on rotation matrices
        cv::Matx<double,4,1> quat_tricked = apply_quaternion_trick(item_permuts,sign_flips,quat_regular);
        cv::Matx<double,4,1> quat_flipped = rotmat2quatwxyz(M_oflip*R*M_iflip);
        // Check that both results are identical
        if(cv::norm(quat_tricked-quat_flipped,cv::NORM_INF)>1e-6) {
            std::cout << "Error (idx=" << i << ")!"
                      << "\n   quat_regular=" << quat_regular.t()
                      << "\n   quat_tricked=" << quat_tricked.t()
                      << "\n   quat_flipped=" << quat_flipped.t() << std::endl;
            ++n_errors;
        }
    }
    std::cout << n_errors << " errors on " << n_tests << " tests." << std::endl;
    system("pause");
    return 0;
}
+7

, , (w, x, y, z) , :

  • , y z .
  • y x z .
  • z, x y .

w .

, , , , , , , .

+2

3D, O (3). +1 -1. +1, -1. O (3) - (x, y, z) → (- x, -y, -z), det -1 3D, . , . , (+1 * +1 = +1), (+1 * -1 = -1) (-1 * -1 = +1).

O (3) , +1 SO (3). .

SO (3), , . , a+b i+c j+d k - , a-b i-c j-d k , ø (b, c, d), - (-b, -c, -d).

, +1, , . .

, . , , . , x = 0 , , y = 0 z = 0. , 180º x. .


, n= (a, b, c). A reflection v (x, y, z)

v - 2 (v. n)/( n. n > ) n

= (x, y, z) - 2 (a x + b y + c z)/(a ​​^ 2 + b ^ 2 + c ^ 2) (a, b, c)

, x-y (0,0,1),

(x, y, z) - 2 z (0,0,1) = (x, y, -z)

.

p = cos (ø/2) + (x i + y j + z k) sin (ø/2)

W + X i + Y j + Z k W = cos (ø/2), X = x sin (ø/2), Y = y sin (ø/2), Z = z sin (ø/2)

, cos ,

p '= cos (ø/2) - (x i + y j + z k) sin (ø/2)

, x-y,

q = cos (ø/2) + (x i + y j - z k) sin (ø/2)

,

q '= cos (ø/2) + (- x i - y j + z k) sin (ø/2 )

= W - X i - Y j + Z k

, , .

(a, b, c). d - (a, b, c). (X, y, z). (x, y, z)

(x, y, z) - 2 d (a, b, c) = (x - 2 d a, y - 2 d b, z - 2 d c)

q = cos (ø/2) - ((x - 2 da) i + ((y - 2 db) j + (z - 2 dc) k) sin (ø/2)

q = cos (ø/2) - (x i + y j + z k) sin (ø/2) + 2 d sin (ø/2) (a i + b j + c k)

= W - X i - Y j - Z k + 2 d (X, Y, Z). (a, b, c) (a i + b j + c k)

+1

, , , , ( ). 3x3

M = I-2(n*nT) 

- 3x3, n - , 3x1, nT - n 1x3 ( n * nT 3x (1x1) x3 = 3x3).

, q, " ", , M * q ( , 3x3, )

+1

, - , :


"p" ax + + cz = 0, :

n = 0+ (a, b, c)
p = 0+ (x, y, z)

'n' - ( , )

p '= npn

p '- .

'm':

p '= mnpnm = (mn) p (mn) ^ *

- .

.


, "Q", "b" ( ), - " s ' s ^ 2. (sqrt (s0) * sqrt (s1)) ^ 2 = s0 * s1, , .

, , , , .

0

. , z (.. z -z) . , . z, , , .

, ( ):

static Quaterniond toQuaternion(double pitch, double roll, double yaw)
{
    Quaterniond q;
    double t0 = std::cos(yaw * 0.5f);
    double t1 = std::sin(yaw * 0.5f);
    double t2 = std::cos(roll * 0.5f);
    double t3 = std::sin(roll * 0.5f);
    double t4 = std::cos(pitch * 0.5f);
    double t5 = std::sin(pitch * 0.5f);

    q.w() = t0 * t2 * t4 + t1 * t3 * t5;
    q.x() = t0 * t3 * t4 - t1 * t2 * t5;
    q.y() = t0 * t2 * t5 + t1 * t3 * t4;
    q.z() = t1 * t2 * t4 - t0 * t3 * t5;
    return q;
}

, sin(-x) = -sin(x) cos(-x) = cos(x). , , t3 t5 . x y .

, z,

Q'(w, x, y, z) = Q(w, -x, -y, z)

Similarly, you can find any other combinations of axis rotation and find an effect on the quaternion.

PS: If someone wonders why anyone will ever need this ... I needed to convert the quaternion from the MavLink / Pixhawk system that controls the drone. The original system uses the NED coordinate system, but conventional 3D environments such as Unreal use the NEU coordinate system, which requires converting the z axis to -z in order to correctly use the quaternion.

0
source

Source: https://habr.com/ru/post/1606254/


All Articles