How to use JointOrientation ?

元ネタ

JointOrientation のワールド座標ってどうなってるの?

KINECTで取得できるデータの1つに JointOrientations (関節のねじれ具合)があります。

var orientations = body.JointOrientations;
// -> 左手首のねじれ具合(X, Y, Z, W)を取得する
var wristLeftOrientation = orientations[JointType.WristLeft]; 

JointOrientation はワールド座標(W)を持つ3次元のベクトルとして表現されます。このワールド座標はすべての関節でそれぞれ異なっていますので、実際には CoordinateMapper などを使って CameraSpacePoint 座標に変換して利用します。

しかし、CameraSpacePointに変換せずに利用する方が簡単なケース *1 もありますので、それぞれのワールド座標がどうなっているのかを知っておいて損はありません。

じゃあ実際どんなルールなのか

回答候補にあるように、それぞれのワールド座標は以下の様なルールに則って設定されています。

  • Bone direction(Y green) - always matches the skeleton.
  • Normal(Z blue) - joint roll, perpendicular to the bone
  • Binormal(X orange) - perpendicular to the bone and normal

適当翻訳をするとだいたい以下の様な意味でしょうか。

  • 各々のワールド座標系はSpineBaseを出発点として親から(SpineBaseに近い側の)子に向かう方向が子のY座標系(親側が0、子側が正)になります
  • Z座標はY座標系に対して鉛直かつ関節の順回転方向になります
  • X座標系はY座標系とZ座標系で構成される平面の従法線になります

日本語でも調べている人が

このブログでは、実際に「気をつけ」の姿勢をして(1,0,0),(0,1,0),(0,0,1)を各関節の四元数で回転させた方向を算出しています。

四元数とKinect その3

JointOrientationの使用例

例えばKINECT SDKに付属しているFace APIのサンプルでは顔の向きを認識するために首のOrientaionを利用しています。

/// <summary>
/// Converts rotation quaternion to Euler angles 
/// And then maps them to a specified range of values to control the refresh rate
/// </summary>
/// <param name="rotQuaternion">face rotation quaternion</param>
/// <param name="pitch">rotation about the X-axis</param>
/// <param name="yaw">rotation about the Y-axis</param>
/// <param name="roll">rotation about the Z-axis</param>
private static void ExtractFaceRotationInDegrees(Vector4 rotQuaternion, out int pitch, out int yaw, out int roll)
{
     double x = rotQuaternion.X;
     double y = rotQuaternion.Y;
     double z = rotQuaternion.Z;
     double w = rotQuaternion.W;

     // convert face rotation quaternion to Euler angles in degrees
     double yawD, pitchD, rollD;
     pitchD = Math.Atan2(2 * ((y * z) + (w * x)), (w * w) - (x * x) - (y * y) + (z * z)) / Math.PI * 180.0;
     yawD = Math.Asin(2 * ((w * y) - (x * z))) / Math.PI * 180.0;
     rollD = Math.Atan2(2 * ((x * y) + (w * z)), (w * w) + (x * x) - (y * y) - (z * z)) / Math.PI * 180.0;

     // clamp the values to a multiple of the specified increment to control the refresh rate
     double increment = FaceRotationIncrementInDegrees;
     pitch = (int)(Math.Floor((pitchD + ((increment / 2.0) * (pitchD > 0 ? 1.0 : -1.0))) / increment) * increment);
     yaw = (int)(Math.Floor((yawD + ((increment / 2.0) * (yawD > 0 ? 1.0 : -1.0))) / increment) * increment);
     roll = (int)(Math.Floor((rollD + ((increment / 2.0) * (rollD > 0 ? 1.0 : -1.0))) / increment) * increment);
}

これで JointOrientationCameraSpacePoint 座標以外でも利用することができそうです。 やりましたね :)

*1:例えば、「腕が体の前方向にあるか、後ろ方向にあるか」は両肩のJointOrientationをそのまま利用したほうが簡単でしょう