クオータニオンからオイラー角に戻せるcv::Quatクラス

# はじめに

- この記事は[OpenCVアドベントカレンダー2023](https://qiita.com/advent-calendar/2023/opencv) 24日目の記事です。その他の記事は[目次](https://qiita.com/advent-calendar/2023/opencv)から参照可能です

# TL;DR


:::note info
- あんな、回転行列って6種類ぐらいあると言ったな、あれは嘘だ。
- 回転角を計算する際に軸の順序を指定するオプションは存在しないと言ったな、あれは嘘だ。
:::

# OpenCVのAPIについて

- [2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)で以下の通り紹介した

> 回転角を計算する際に軸の順序を指定するオプションは存在しない

- が、あのあとど真ん中のAPIを発見してしまい、知らなかったとは言え嘘を書いてしまったので、「謝罪記事」の体で本日の記事である。
- OpenCVには`cv::Quat<_Tp>`クラスが存在する。一般的には`cv::Quatd`クラスを使い、これは`cv::Quat<double>`の別名である。
- 参考までに[OpenCV4.8.0時点での該当ドキュメント](https://docs.opencv.org/4.8.0/da/d4a/classcv_1_1Quat.html)
- このクラスには[`toEulerAngle`関数が存在し、クオータニオンを回転角に変換してくれる]( https://docs.opencv.org/4.8.0/da/d4a/classcv_1_1Quat.html#ab492aa67bb7803caec230a12c612c4e6)
- しかも、回転角を計算する際に回転軸の順序をオプションとして取る。

# OpenCV 4.5.1から登場
- Gitの履歴を紐解くに、[commit hash 11cfa64]( https://github.com/opencv/opencv/commit/11cfa64a10a8aa791df5783dcee0a500c7bc4eb1)、[PR 18335](https://github.com/opencv/opencv/pull/18335)で今から約３年前の2020年11月20日にマージされた

# 6種類じゃきかなかった回転行列
- [2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)で以下の通り紹介した

> 前述したように、回転行列は回転軸の順序によって6種類存在する

- が、`cv::Quatd::toEulerAngle`メンバ関数のドキュメントを読むと、引数として、回転軸の順序を指定するために`cv::QuatEnum::EulerAnglesType` を指定するパラメタが存在し、この`enum`は、以下の24通り（！）存在する

```cpp
    enum EulerAnglesType
    {
        INT_XYZ, ///< Intrinsic rotations with the Euler angles type X-Y-Z
        INT_XZY, ///< Intrinsic rotations with the Euler angles type X-Z-Y
        INT_YXZ, ///< Intrinsic rotations with the Euler angles type Y-X-Z
        INT_YZX, ///< Intrinsic rotations with the Euler angles type Y-Z-X
        INT_ZXY, ///< Intrinsic rotations with the Euler angles type Z-X-Y
        INT_ZYX, ///< Intrinsic rotations with the Euler angles type Z-Y-X
        INT_XYX, ///< Intrinsic rotations with the Euler angles type X-Y-X
        INT_XZX, ///< Intrinsic rotations with the Euler angles type X-Z-X
        INT_YXY, ///< Intrinsic rotations with the Euler angles type Y-X-Y
        INT_YZY, ///< Intrinsic rotations with the Euler angles type Y-Z-Y
        INT_ZXZ, ///< Intrinsic rotations with the Euler angles type Z-X-Z
        INT_ZYZ, ///< Intrinsic rotations with the Euler angles type Z-Y-Z

        EXT_XYZ, ///< Extrinsic rotations with the Euler angles type X-Y-Z
        EXT_XZY, ///< Extrinsic rotations with the Euler angles type X-Z-Y
        EXT_YXZ, ///< Extrinsic rotations with the Euler angles type Y-X-Z
        EXT_YZX, ///< Extrinsic rotations with the Euler angles type Y-Z-X
        EXT_ZXY, ///< Extrinsic rotations with the Euler angles type Z-X-Y
        EXT_ZYX, ///< Extrinsic rotations with the Euler angles type Z-Y-X
        EXT_XYX, ///< Extrinsic rotations with the Euler angles type X-Y-X
        EXT_XZX, ///< Extrinsic rotations with the Euler angles type X-Z-X
        EXT_YXY, ///< Extrinsic rotations with the Euler angles type Y-X-Y
        EXT_YZY, ///< Extrinsic rotations with the Euler angles type Y-Z-Y
        EXT_ZXZ, ///< Extrinsic rotations with the Euler angles type Z-X-Z
        EXT_ZYZ, ///< Extrinsic rotations with the Euler angles type Z-Y-Z
        #ifndef CV_DOXYGEN
            EULER_ANGLES_MAX_VALUE // 24だよっ！
        #endif
    };
```

- `INT`と`EXT`の接頭辞はそれぞれ回転軸の順序を逆順に並べ替えることに相当する
    - 例：[2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)では`XYZ`は`Z軸`→`Y軸`→`X軸`の順に回転させる、と解説した。これは`INT`接頭辞がつく方の説明に相当する
    - `EXT`接頭辞がつくパターンは`XYZ`は`X軸`→`Y軸`→`Z軸`の順に回転させる、
- また、[2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)では触れなかった`INT_XYX`なるパターンも存在する
    - これはどんな回転行列でも、回転軸2つの回転各を適切にとることで、`XYX`のように、3軸のうち2軸だけの回転だけで回転行列を表す組み合わせが存在する（ただし、その際2軸のうち1軸を2階適用する)
    - ここについては私も理解が不十分なので、詳細は割愛します
 - すくなくともOpenCVでの実装は、[2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)で紹介した範囲より、4倍広い範囲をカバーしているといえる

# ジンバルロック
- [2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)で、用語の裏付けを取る余裕がなくて敢えて触れなかったのだが、

> 回転角は特定の条件下で回転角2つが混ざった状態でしか復元できなくなる。

- この特定条件には**ジンバルロック(Gimbal Lock)** と名前が付いている
- `toEulerAngles`メンバ関数内ではジンバルロックが発生しているかチェックし、発生した場合にはコンソールに以下の警告メッセージが表示される

```
[ WARN:0@87.260] global quaternion.inl.hpp:1030 toEulerAngles Gimbal Lock occurs. Euler angles are non-unique, we set the third angle to 0
```

- 余談ではあるがこのクラス、関数の存在に気づいたのはこのログメッセージがきっかけである

# おわりに
- [1日目の記事](https://qiita.com/tomoaki_teshima/items/0fc58637a8b223879d9f)のみならず、[2日目の記事](https://qiita.com/tomoaki_teshima/items/143c730261675e117314)にも嘘が存在していました。
- しかも24日担当がギリギリになってしまいまいました。ｔｈｐｒ。
- 明日は25日目、hon_no_mushiさんの「OpenCV Advent Calendar 2023まとめ」です