備忘録 blog

Docker/Machine Learning/Linux

3Dで使われるオイラー角とクォータニオンとは何か?

tl;dr

3次元上の回転について調べたのでまとめる備忘録。

復習: 2次元の場合はどうだったか

2次元座標上で回転をどう定義するか、ということについては、ここでは詳説しない。

  1. 回転行列
  2. オイラー
  3. 複素数

これらの表現が、3次元上ではどうなるのかについて考える。

3次元上の回転行列

回転を表現するだけであれば3x3行列で十分すぎるはずである。9セルあるので、自由度が9であるように思われがちだが、それぞれのベクトルが単位ベクトルであり、直交しているという制約があるので、実質的に自由度は3である。このため、他の表現よりメモリを必要とすることと、無効となる表現が多いというのが欠点としてあげられる。

しばしば回転は、平行移動などと共通に扱いたいとされる。しかし、3x3行列のままでは、原点に対してはどのような3x3行列との積をとっても原点のままになってしまう。平行移動では、アフィン変換になるので、そこで、  {p = (x, y, z)} に対して斉次座標を利用してもう1次元追加した、  {p = (x, y, z, 1)} を考え、これに対する4x4行列による写像を、回転と平行移動を同時に表現できるフォーマットとして利用することがある。このとき、4x4行列の左上の3x3成分だけを回転行列として扱うことができる。

3次元上のオイラー

2次元におけるx,y軸の拡張であるオイラー角は、一般に

{ \displaystyle
(\theta, \phi, \psi)
}

として表現される。

これは、例えばヨー、ピッチ、ロールの3軸としたとき、それらに対して同時にその角度回転する、というわけではない。最初にある軸に対して回転し、その次は、回転した後の向きに対して改めてx,y,z軸を定義し、次の軸で回転し、最後にまた回転後の向きに対して軸を再定義して回転する、という順番で回転したものの合成として表現される。つまり、この回転の順序のやり方によって、最初の回転軸と最後の回転軸が同じになるパターン*1を含めて12パターンのオイラー角を考えることができる。実際は1つの順序(ZYXやYXZの順など)に決め打ちするものが多い。一方で、回転する前の初期状態のxyz軸に対しての回転を行うという流派もあるようだが、ここではそれは考慮しない。

これによって、3次元上の回転は3つの自由度で表現することができるはずである。利点として、何より我々にとってこの表現方法は直感的であるということと、それぞれの軸における回転は何度回ったとしても表現できるので、180°より大きい回転角を表現することも可能であるということが挙げられる。

しかし実際にこの系を用いると、ジンバルロックとよばれる問題が生じる。これは何か。ジンバルと呼ばれる、三次元のジャイロを考えてみよう。最初の状態では、XYZ軸に対応するそれぞれの軸は、どの2つをとっても垂直になるように交わっている。回転によって、軸間の角度がかわると、場合によっては最も外側の軸と、最も内側の軸が水平になってしまうことがある。このとき、2方向にしか動かすことができなくなってしまう*2

回転を実行することと、その回転を書き下すことの間には、本来は両矢印を引くことができて欲しいのだが、その矢印の両方に問題が残っている。それは、ある回転が与えられたとき、その角度をオイラー角で書き下そうとすると、複数の角度の組み合わせが考えられてしまうこと。これについては、角度の値域を固定することで解決することができる。逆に、ある状態に対して、ある回転を実行しようとするときに、状態によっては、先ほどのように2つの軸が同じ向きになってしまうと自由度が2になってしまい、自由度がない向きに直接回転できなくなってしまうことがある。この後者がジンバルロックであり、これは簡単に回避することが難しい。

この性質から、ある2点のあいだの回転を「補間」することが難しくなってしまう。単純に、2点の角度の相加平均をとったとしても、その補間点の前後での角速度が変わってしまったり、

そこで、実は3つの数字で表現するのではなく、4つの数字で表現すれば、仮に2つの数字が従属関係となり、自由度が1つ減ったとしてもなお、3軸の向きの回転を表現できるということが考えられる。これが、次に関係してくる話題となる。

3次元上の複素表現: 四元数

2次元における複素数の拡張であるから、3次元の表現において、実部が1つ、虚部が2つの三元数というものが存在すれば良いのではないか、と直感的に思うだろう。

{ \displaystyle
t = a + bi + cj (a,b,c \in \mathbb{R}, i^{2} = j^{2} = -1)
}

このとき、2つの三元数の積を考えると、この積には  {ij} となる項が含まれるが、それは元の定義には存在しない項である。つまり、この定義では、積について閉じていないことがわかる。そこで、新しい虚数軸として  {k} を導入する。

{ \displaystyle
q = a + bi + cj + dk (a,b,c,d \in \mathbb{R}, i^{2} = j^{2} = k^{2} = ijk = -1)
}

これで定義された  {q}四元数クォータニオン)と呼び、複素数に期待される種々の性質を満たしていることが計算によって示される。 このクォータニオンを利用することの利点は、先ほどのオイラー角のような特異点が存在しないので、なめらかに2点間の回転移動を記述することができる。また回転行列に比べて、演算の回数が減らせるというのも大きな利点だ。行列は先述の通り、自由度に比べて扱うセルの個数が大きいので、和や積など様々な演算で、スカラー同士の積和の演算回数も多くなってしまう。

しかしながら、クォータニオンを利用する際にも欠点は存在し、オイラー角のように270°の回転などを表現することはできず、最短経路での回転として一意に表現されてしまう。また、最大の欠点は、その値をみても、どういう回転の処理に対応するのかよく分からないというところである。

Blender のリグでオイラー角とクォータニオンのどちらを使うべきか

参考文献

qiita.com

www.opengl-tutorial.org

qiita.com

el-ement.com

*1:XZZのように

*2:軸がない方向に回転させるすべがないためである