Skip to content

Add to/from Euler Angles in quaternion #19098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Dec 31, 2020
Merged

Conversation

chargerKong
Copy link
Contributor

We finally support all types of Euler angles to/from the quaternion.

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or other license that is incompatible with OpenCV
  • The PR is proposed to proper branch
  • There is reference to original bug report and related work
  • There is accuracy test, performance test and test data in opencv_extra repository, if applicable
    Patch to opencv_extra has the same branch name.
  • The feature is well documented and sample code can be built with the project CMake

@chargerKong chargerKong changed the title add to/from Euler Angles in quaternion Add to/from Euler Angles in quaternion Dec 13, 2020
{
Quat<T> q1 = Quat<T>::createFromXRot(angles(0) / 2.);
Quat<T> q2 = Quat<T>::createFromYRot(angles(1) / 2.);
Quat<T> q3 = Quat<T>::createFromZRot(angles(2) / 2.);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ 2.

* 0.5f

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! Thanks for your reviewing code for us.

Quat<T> q1 = Quat<T>::createFromXRot(angles(0) / 2.);
Quat<T> q2 = Quat<T>::createFromYRot(angles(1) / 2.);
Quat<T> q3 = Quat<T>::createFromZRot(angles(2) / 2.);
q = q1 * q2 * q3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets reduce amount of generated binary code.

Make private function (detail namespace should be fine)

template <typename T>
Quat<T> createQuatFromRot(int axis, T angle)
{
    if (i == 0)
        return Quat<T>::createFromXRot(angle * 0.5f);
    if (i == 1)
        return Quat<T>::createFromXRot(angle * 0.5f);
    if (i == 2)
        return Quat<T>::createFromXRot(angle * 0.5f);
    CV_Assert(0);
}

Fill axis1,axis2,axis3 (preferable from static const int rotationAxis[][3] = { { i1, i2, i3 }, { i1, i2, i3}, ... }; table):

int axis1, axis2, axis3;  // values are 0/1/2 for X/Y/Z
enum EulerAnglesType
{
    ...
#ifndef CV_DOXYGEN
    EULER_ANGLES_MAX_VALUE
#endif 
};

+ static assert for table size using EULER_ANGLES_MAX_VALUE
+ runtime assert: eulerAnglesType < EULER_ANGLES_MAX_VALUE

Finally, use this common code in epilog:

Quat<T> q1 =  Quat<T>::createQuatFromRot(axis1, angles(0));
Quat<T> q2 =  Quat<T>::createQuatFromRot(axis2, angles(1));
Quat<T> q3 =  Quat<T>::createQuatFromRot(axis3, angles(2));
q = q1 * q2 * q3;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I got it and I will update the implementation of toEulerAngle() in the same way.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alalek The table size of rotationAxis is fixed (24,4). Is it necessary to add static_assert?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static assert is a compilation guard if someone would change/update corresponding enum values.

if (abs(R(0, 2) - 1) < CV_QUAT_CONVERT_THRESHOLD)
{
std::cout << "WARNING: Gimbal Lock will occur. Euler angles are non-unique." << std::endl;
std::cout << "For intrinsic rotations, we set the third angle to zero, and for external rotation, we set the first angle to zero." << std::endl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::cout

Avoid that. There is CV_LOG_WARNING(). However messages probably should be reduced to INFO level.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

*
* The three elemental rotations may be [extrinsic](https://en.wikipedia.org/wiki/Euler_angles#Definition_by_extrinsic_rotations)
* (rotations about the axes *xyz* of the original coordinate system, which is assumed to remain motionless),
* or [intrinsic](https://en.wikipedia.org/wiki/Euler_angles#Definition_by_intrinsic_rotations)(rotations about the axes of the rotating coordinate system *XYZ*, solidary with the moving body, which changes its orientation after each elemental rotation).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the links to paragraphs in that wiki article

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, we will update doc.

* Using intrinsic rotations with Euler angles type XYZ as an example,
* \f$\theta_1 \f$, \f$\theta_2 \f$, \f$\theta_3 \f$ are three angles for Euler angles, the rotation matrix R can be calculated by:\f[R =X(\theta_1)Y(\theta_2)Z(\theta_3)
* ={\begin{bmatrix}\cos\theta_{2}\cos\theta_{3}&-\cos\theta_{2}\sin\theta_{3}&\sin\theta_{2}\\\cos\theta_{1}\sin\theta_{3}+\cos\theta_{3}\sin\theta_{1}\sin\theta_{2}&\cos\theta_{1}\cos\theta_{3}-\sin\theta_{1}\sin\theta_{2}\sin\theta_{3}&-\cos\theta_{2}\sin\theta_{1}\\\sin\theta_{1}\sin\theta_{3}-\cos\theta_{1}\cos\theta_{3}\sin\theta_{2}&\cos\theta_{3}\sin\theta_{1}+\cos\theta_{1}\sin\theta_{2}\sin\theta_{3}&\cos\theta_{1}\cos_{2}\end{bmatrix}}\f]
* Rotation matrix M and R are equal. As long as \f$ s_{2} \neq 1 \f$, by comparing each element of two matrices ,the solution is\f$\begin{cases} \theta_1 = \arctan2(-m_{23},m_{33})\\\theta_2 = arcsin(m_{13}) \\\theta_3 = \arctan2(-m_{12},m_{11}) \end{cases}\f$.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clarify what is s

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks! s_2 mean sin\theta_2, I will fix the issue.

@chargerKong chargerKong requested review from savuor and alalek December 17, 2020 03:12
Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add commits with code optimizations from here: https://github.com/alalek/opencv/commits/pr19098_r

@@ -845,8 +953,8 @@ class Quat
/**
* @brief transform a quaternion to a 4x4 rotation matrix.
* @param assumeUnit if QUAT_ASSUME_UNIT, this quaternion assume to be a unit quaternion and
* this function will save some computations. Otherwise, this function will normalized this
* quaternion at first then to do the transformation.
* this function will save some computations. Otherwise, this function will normaliz this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks for your commits! We have added these codes and removed the typo.

Comment on lines 868 to 869
namespace
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace detail { should be better

namespace
{
template <typename T>
Quat<T> createFromAxisRot(int axis, const T theta)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It make sense to add static to keep this local:

template <typename T> static
Quat<T> createFromAxisRot(int axis, const T theta)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I got it!

Quat<T> q3 = createFromAxisRot(rotationAxis[eulerAnglesType][2], angles(2));
return q1 * q2 * q3;
}
else if (rotationAxis[eulerAnglesType][3]){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
else // (rotationAxis[eulerAnglesType][3])
{

and remove CV_Assert(0).

{R(2, 2), 0, R(1, 0), R(1, 1), CV_PI, R(1, 0), R(0, 0),R(2, 0), R(2, 1), R(2, 2),R(0, 2), -R(1, 2), 1}, //EXT_ZXZ
{R(2, 2), 0, R(1, 0), R(0, 0), CV_PI, R(1, 0), R(0, 0),R(2, 1), -R(2, 0), R(2, 2),R(1, 2), R(0, 2), 1} //EXT_ZYZ
};
if(!rotationR[eulerAnglesType][12]){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this if build dynamic table:

T rotationR[12] through decoding in a loop using the indexes above.

And apply replacement rotationR[eulerAnglesType] => rotationR for code below (it should not have large changes).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! We really appreciate your help.

* For Proper Euler angles,the valid range of \f$\theta_2\f$ is in [0, π]. The solutions of Euler angles are unique in condition of \f$ \theta_2 \in (0, π)\f$ . If \f$\theta_2 =0 \f$ or \f$\theta_2 =π \f$,
* there are infinite solutions and gimbal lock will occur.
*/
enum EulerAnglesType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to avoid placing enums in template classes.

I see these options:

  • base non-template class with such constants (QuatEnum?)
  • external enum with proper prefixes (like QUAT_ASSUME_NOT_UNIT)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, we will create class QuatEnum to place enum EulerAnglesType.

Comment on lines 263 to 265
Quatd qEuler1 = Quatd::createFromEulerAngles(test_angle, Quatd::INT_XYZ);
Quatd qEuler2 = Quatd::createFromEulerAngles(test_angle, Quatd::INT_XZY);
Quatd qEuler3 = Quatd::createFromEulerAngles(test_angle, Quatd::INT_XZX);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using arrays and for loops to process/check values. Like qEuler[24]
This should reduce amount of code.

Use error messages to help with problem investigations. See proposed commits.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, we use for loops and array qEuler[24] in test codes to reduce duplicate code.


TEST_F(QuatTest, EulerAngles){
Vec3d test_angle={0.523598,0.78539,1.04719};
Quatd qEuler0 = Quatd(0, 0, 0, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not transform these huge lists (here and below) to loops?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks, I solved this problem by using the for loop method and array qEuler.

Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress!

Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍

@alalek alalek merged commit e4c7fca into opencv:master Dec 31, 2020
@chargerKong chargerKong deleted the EulerAngle branch January 28, 2021 06:21
@alalek alalek mentioned this pull request Apr 9, 2021
a-sajjad72 pushed a commit to a-sajjad72/opencv that referenced this pull request Mar 30, 2023
* add to/from Euler Angles

* restruct codes

* quat: optimize implementation

* cleanup debug code

* correct spelling errors

* create QuatEnum for enum EulerAnglesType

* use for loop for test_quaternion

* drop template from isIntAngleType & add minimal error information in test_quaternion.cpp

Co-authored-by: ShanChenqi <shanchenqi@huawei.com>
Co-authored-by: Alexander Alekhin <alexander.a.alekhin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants