Nuitrack 1.5.0
3D スケルトン トラッキング ミドルウェア
 すべて クラス 名前空間 関数 変数 Typedefs 列挙型 列挙子 プロパティ イベント グループ ページ
Nuitrack を使用して動く絵文字を作成

このチュートリアルでは、Nuitrack とブレンドシェイプからの顔情報を使用して動く絵文字を作成する方法を紹介します。複数のスケルトン (最大数は 6) のトラッキングができ、ユーザーの顔の代わりに、キツネの動く絵文字が表示されます。キツネの顔が、ユーザーの顔の動きを連動して変化します。ユーザーが頭を動かすと、キツネの頭も同様に動きます。キツネの顔が揺れる際、キツネの毛がわずかに揺れていることにお気づきかもしれません。キツネの顔はズームイン、ズームアウトでき、ユーザーが近いほどキツネの顔は大きくなりますし、その逆も同様です。

このプロジェクトを作成するために必要なものは以下の通りです。

完成済みプロジェクトは、Nuitrack SDK: Unity 3D > NuitrackSDK.unitypackage > Tutorials > Animated Emoji にあります。

Uanimoji_1.gif

シーンのセットアップ

  1. NuitrackSDK.unitypackageNuitrack SDK からプロジェクトにダウンロード/インポートします (NuitrackSDK/Tutorials/Animated Emoji/Final Assets フォルダーは、すぐに使えるスクリプトが含まれているので除外)。
  2. NuitrackScripts プレハブをシーンにドラッグ アンド ドロップします。必要な Nuitrack モジュール、Color Module On (センサーからの RGB イメージの出力)、Skeleton Tracker Module On (スケルトン トラッキング) を選択します。

    Uanimoji_2.png

  3. 新しいキャンバスを作成します。[Create] > [UI] > [Canvas]を選択し、Face Canvas と名前を付けます。Sort Order を 2 に設定します。このキャンバスをキツネの顔を表示するために使用します (キャンバスの上に RGB を表示し、スケルトンを表示)。

    Uanimoji_3.png
    注意
    このプロジェクトでは、キツネの顔を 2D で表示します。この画像形式であれば、サポートしているすべてのセンサーで正しく画像を表示できます。キツネの顔を 3D で表示する場合、どのセンサーを使用し、その解像度を知っている必要があります。Nuitrack がそれらの情報を提供することは、現時点ではありません。
  4. キャンバスに子オブジェクトを作成します。[Create] > [UI] > [Raw Image]を選択し、Fox Face と名前を付けます。このオブジェクトの設定を開き、[RectTransform]で、Width = 1、Height = 1 に設定します。[Scale]を適切な値に設定します (以下は一例)。X:100, Y:100, Z:1これは、ピクセルで表されたイメージ サイズで、これを基にキツネの顔が作成されます。1:1 の縦横比を必ず維持してください。そうしないなら、キツネの顔が縦/横に引き伸ばされてしまいます。サイズを小さく設定しすぎると、この段階でプロジェクトを実行した場合に、キツネの顔が見えなくなるので、注意してください。

    Uanimoji_4.png

  5. Fox Face Model プレハブをシーンにドラッグ アンド ドロップします (保存先: NuitrackSDK/Tutorials/Animated Emoji/Prefabs)。
  6. 新しいテクスチャを Animated Emoji フォルダーに作成するため、 [Create] > [Render Texture]を選択し、Face と名前を付けます。このテクスチャは、センサーからのイメージのレンダリングを行います。
  7. Fox Face オブジェクトの設定を開き、Face テクスチャを Texture フィールドにドラッグ アンド ドロップします。

    Uanimoji_5.png

  8. Camera オブジェクトの設定 (すぐに使用できるよう用意されている Fox Face Model プレハブ内) を開き、Face テクスチャを Target Texture フィールドにドラッグ アンド ドロップします。

    Uanimoji_6.png

お疲れ様です!これで、ユーザーの顔の表情をまねする 2D のキツネの顔が用意できました。

Uanimoji_7.jpg

ユーザーの顔とキツネの顔を切り替え

  1. Fox Face をプレハブとして保存します。Fox FaceFox Face Model プレハブをシーンから削除します。
  2. キツネの顔を管理するための新しいスクリプト FaceAnimController を作成します。UnityEngine.UI 名前空間を追加します。必要なフィールドを追加します (フィールド名で用途がわかるようになっています)。slerpRotation は頭の回転をスムーズにするために使用されます (使用しない場合、顔の回転時、顔が小刻みに動きます)。faceRaw フィールドは、キツネの顔を表示するために使用されます。

    using UnityEngine;
    using UnityEngine.UI;
    public class FaceAnimController :MonoBehaviour
    {
    [SerializeField] Transform headModel;
    [SerializeField] Transform headRoot;
    [SerializeField] RawImage rawImage;
    [SerializeField] Camera faceCamera;
    [SerializeField] SkinnedMeshRenderer faceMeshRenderer;
    [SerializeField] RenderTexture renderTextureSample;
    [SerializeField] float smoothHeadRotation = 5;
    RenderTexture renderTexture;
    RawImage faceRaw;
    }
    注意
    Render Texture の詳細。
  3. Init メソッドで、Canvas を渡すことにより、rawImage を子オブジェクトにします。さらに、最初のセクションで作成したRenderTexture を基にrenderTexture を作成します。renderTextureCamerarawImage オブジェクトのテクスチャに渡します。位置を設定し (お互いから離して)、イメージ (キツネの顔) を画面の高さまで引き伸ばします。縦横比を必ず維持してください。rawImage オブジェクトを生成します。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    [SerializeField] float smoothHeadRotation = 5;
    public void Init(Canvas canvas)
    {
    faceRaw = Instantiate(rawImage, canvas.transform).GetComponent<RawImage>(); // Spawn RawImage
    faceRaw.transform.localScale = Vector2.one * Screen.height;
    renderTexture = new RenderTexture(renderTextureSample);
    faceCamera.targetTexture = renderTexture;
    faceRaw.texture = renderTexture;
    faceRaw.gameObject.SetActive(false);
    }
    }

  4. public メソッド UpdateFace を作成し、必要なパラメーター (JSON からのユーザーの顔に関する情報と頭の関節) を指定します。頭の関節の位置を射影座標で Nuitrack から取得し、これらの座標に頭を設定します (X と Y に画面の幅と高さを掛けます)。headRoot 位置を Z軸に沿って変更します (Nuitrack 空の情報を使用)。これが顔のズームを表し、ユーザーとセンサーの距離によって変化します (ユーザーがセンサーに近いほどキツネの顔が大きくなります)。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    faceRaw.gameObject.SetActive(false);
    }
    public void UpdateFace(Instances instance, nuitrack.Joint headJoint)
    {
    Vector3 headProjPosition = headJoint.Proj.ToVector3();
    faceRaw.transform.position = new Vector2(headProjPosition.x * Screen.width, Screen.height - headProjPosition.y * Screen.height);
    headRoot.localPosition = new Vector3(0, 0, -headJoint.Real.Z * 0.001f);
    }
    }
    注意
    1 Unity 単位が約 1 m であることを考慮し、取得したデータを調整する必要があります。そのためには、取得したデータに 0.001 をかけます (m から mm への変換)。
  5. OnEnableOnDisable メソッドを作成します。顔が有効な場合、rawImage も有効になり、顔のレンダリングができるようになります。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    headRoot.localPosition = new Vector3(0, 0, -headJoint.Real.Z * 0.001f);
    }
    void OnDisable()
    {
    if(faceRaw != null)
    faceRaw.gameObject.SetActive(false);
    }
    void OnEnable()
    {
    if (faceRaw != null)
    faceRaw.gameObject.SetActive(true);
    }
    }

  6. Fox Face Model プレハブをシーンにドラッグ アンド ドロップします。その後、FaceAnimController をプレハブにドラッグ アンド ドロップします。以下の画像に示されているように、フィールドを入力していきます。その後、[Apply]をクリックし、Fox Face Model をシーンから削除します。

    Uanimoji_8.png

  7. 新しいスクリプト、FaceAnimManager を作成します。このスクリプトでは、ユーザーの顔の代わりにキツネの顔を表示する方法を紹介します。nuitrack 名前空間を追加します。キャンバスのための canvas フィールド、キツネの顔のための facePrefab フィールドを追加します。faceCount を 0 から 6 の間で設定します (顔はスケルトンとリンクされており、トラッキング可能なスケルトンの最大数が 6)。faceInfo (Nuitrack からの 構文解析済み JSON、顔に関する情報をすべて取得) と faceAnimControllers の一覧 (顔の一覧) を追加します。

    using UnityEngine;
    using System.Collections.Generic;
    using nuitrack;
    public class FaceAnimManager :MonoBehaviour
    {
    [SerializeField] Canvas canvas;
    [SerializeField] FaceAnimController facePrefab;
    [Range(0, 6)]
    [SerializeField] int faceCount = 6; // Max number of skeletons tracked by Nuitrack
    FaceInfo faceInfo;
    List<FaceAnimController> faceAnimControllers = new List<FaceAnimController>();
    }

  8. Start では、faceCount で設定した数だけ顔を生成します。キツネの顔をシーンに追加後に、faceAnimController を取得し、Init メソッドを呼び出します。キツネの顔はそれぞれ離すことで、カメラが同時に複数の顔をとらえないようにします。そうしないなら、1ユーザーに対して複数のキツネの顔が表示されてしまうことになります。faceAnimControllerfaceAnimControllers の一覧に追加します。トラッキングを行うスケルトン数を必要に応じて設定します (顔とスケルトンはリンク付けされますので、ご注意ください)。OnSkeletonUpdateEvent を登録します。

    public class FaceAnimManager :MonoBehaviour
    {
    ...
    void Start()
    {
    for (int i = 0; i < faceCount; i++)
    {
    GameObject newFace = Instantiate(facePrefab.gameObject, new UnityEngine.Vector3(i*headsDistance,0,0), Quaternion.identity);
    newFace.SetActive(false);
    FaceAnimController faceAnimController = newFace.GetComponent<FaceAnimController>();
    faceAnimController.Init(canvas);
    faceAnimControllers.Add(faceAnimController);
    }
    NuitrackManager.SkeletonTracker.SetNumActiveUsers(faceCount);
    NuitrackManager.SkeletonTracker.OnSkeletonUpdateEvent += OnSkeletonUpdate;
    }
    }

  9. OnSkeletonUpdateEvent メソッドを作成: string json 変数を作成し、JSON からの情報を渡します。JSON からの構文解析済み情報を faceInfo に渡します: 引用符を角括弧に置き換えることで、配列が空 (顔に関する情報がない) の場合のエラーを防ぐことができます。顔の情報がない場合、メソッドの残りの部分は実行されません。

    public class FaceAnimManager :MonoBehaviour
    {
    ...
    NuitrackManager.SkeletonTracker.OnSkeletonUpdateEvent += OnSkeletonUpdate;
    }
    void OnSkeletonUpdate(SkeletonData skeletonData)
    {
    string json = Nuitrack.GetInstancesJson();
    faceInfo = JsonUtility.FromJson<FaceInfo>(json.Replace("\"\"", "[]"));
    if (faceInfo.Instances.Length == 0)
    return;
    }
    }

  10. 顔が取得されると、faceAnimControllers をループ処理します。検出されるスケルトンと同じ数だけ顔をアクティブにし、その他は、アクティブになりません。デフォルトの設定では、起動時に 6つの顔がアクティブになります。skeleton 変数を作成し、顔に対応するスケルトンを保存します (顔の ID とスケルトンの ID は一緒)。スケルトンが検出されたら、スケルトンから headJoint を取得し、信頼度が 0.5 であれば頭の関節をアクティブにします。faceAnimController, の UpdateFace メソッドを呼び出し、Instance (すべての顔パラメーター) を JSON と headJoint から渡します。スケルトンが検出されない場合、顔はアクティブになりません。

    public class FaceAnimManager :MonoBehaviour
    {
    ...
    void OnSkeletonUpdate(SkeletonData skeletonData)
    ...
    return;
    for (int i = 0; i < faceAnimControllers.Count; i++)
    {
    if (i < skeletonData.Skeletons.Length)
    {
    Skeleton skeleton = skeletonData.GetSkeletonByID(faceInfo.Instances[i].id);
    if(skeleton != null)
    {
    nuitrack.Joint headJoint = skeleton.GetJoint(JointType.Head);
    faceAnimControllers[i].gameObject.SetActive(headJoint.Confidence > 0.5f);
    faceAnimControllers[i].UpdateFace(faceInfo.Instances[i], headJoint);
    }
    }
    else
    {
    faceAnimControllers[i].gameObject.SetActive(false);
    }
    }
    }

  11. FaceAnimManager スクリプトを、Face Canvas にドラッグ アンド ドロップします。
  12. Face Canvas を、このオブジェクトの[Canvas]フィールドにドラッグ アンド ドロップします。Fox Face Model プレハブを [Face Prefab]フィールドにドラッグ アンド ドロップします。

    Uanimoji_9.png

  13. Color Frame Canvas プレハブを、NuitrackSDK.unitypackage からシーンにドラッグ アンド ドロップします。
  14. プロジェクトを実行します。ユーザーの顔の代わりに、キツネの顔が表示されます。これも素敵ですが、この段階ではすべて同じ表情です。さらに一歩進んで、キツネに表情を追加して、個性を出しましょう。
Uanimoji_10.gif

キツネを表情豊かに!

  1. まず、[depth-to-color registration]をオンにします。これは、深度マップが RGB イメージとぴったり一致しないので、揃える必要があるからです。depth-to-color registration をオンにするには、nuitrack.config を開き、DepthProvider.Depth2ColorRegistration を true に設定します。
  2. デフォルトの設定では、Nuitrack による顔のトラッキングはオフになっています。有効にするには、nuitrack.config ファイルを開き、 Faces.ToUse を true に設定します。
  3. ブレンドシェイプの ID を FaceAnimController に追加します。ブレンドシェイプの総一覧は、Fox_RigFace オブジェクト設定で確認できます。これらのブレンドシェイプのおかげで、キツネの顔の様々な部分を動かすことができます。このプロジェクトでは、キツネが口を開けたり、瞬きしたり、眉毛を上げ下げしたりできます。キツネの顔が動くとき、頬や耳の部分の毛がわずかに揺れ (動き) ます。見ての通り、このプロジェクトでは、7つのブレンドシェイプしか使っていませんが、Fox_RigFace にあるブレンドシェイプの一覧にはもっと多くあります。ポイントは、現段階で、人体計測ポイントを制限なく受け取れるということです (詳細は、「Nuitrack インスタンス ベース API [ベータ版]」 参照)。そのため、他のブレンドシェイプに関する十分な情報がありません。フィールド baseRotation (headRoot の最初の回転位置)、blendshapeWeights (0 から 100%)、newRotation (現在の頭の回転位置) を追加します。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    [SerializeField] float smoothHeadRotation = 5;
    //Face Animation
    [Header("BlendShapesIds")]
    [SerializeField] int jawOpen = 6;
    [SerializeField] int eyeBlinkLeft = 0;
    [SerializeField] int eyeBlinkRight = 2;
    [SerializeField] int mouthLeft = 10;
    [SerializeField] int mouthRight = 11;
    [SerializeField] int browUpLeft = 17;
    [SerializeField] int browUpRight = 18;
    Quaternion baseRotation;
    BlendshapeWeights blendshapeWeights = new BlendshapeWeights();
    Quaternion newRotation;
    RawImage faceRaw;
    注意
    キツネの耳と毛の動きは、LerpRotation スクリプトで定義します。
  4. Init メソッドで、baseRotation を保存します (最初の頭の回転、これを使用して現在の頭の回転を計算)。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    public void Init(Canvas canvas)
    {
    baseRotation = headRoot.rotation;
    }

  5. UpdateFace で、ローカル変数 face を追加し、JSON から取得した顔を保存します。baseRotationnewRotation 変数に渡します。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    public void UpdateFace(Instances instance, nuitrack.Joint headJoint)
    {
    ...
    headRoot.localPosition = new Vector3(0, 0, -headJoint.Real.Z * 0.001f);
    Face face = instance.face;
    newRotation = baseRotation;
    }

  6. ユーザーの顔に人体計測ポイントが検出されたら、ブレンドシェイプの重さを設定します (検出するには、ポイントがはっきり見え、他のオブジェクトによって隠れていないことが重要です)。すべて同じ方法、 SetBlendShapeWeight メソッドを使って設定されます: インデックスとブレンドシェイプの重さ (0-100) をこのメソッドに渡します。重さは blendshapeWeights クラスからとられ、該当するメソッドが呼び出され (キツネの口を開けるには GetJawOpenメソッド)、face をこのメソッドに渡します。結果として、各ブレンドシェイプの重さ値を取得できます。頭の回転を計算します: baseRotation (最初の回転位置) の生成物と JSON からの回転位置を newRotation 変数に渡します。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    public void UpdateFace(Instances instance, nuitrack.Joint headJoint)
    {
    ...
    newRotation = baseRotation;
    if (instance.face.landmark == null)
    return;
    // Mouth
    faceMeshRenderer.SetBlendShapeWeight(jawOpen, blendshapeWeights.GetJawOpen(face));
    // Eyes
    faceMeshRenderer.SetBlendShapeWeight(eyeBlinkLeft, blendshapeWeights.GetEyeBlinkLeft(face));
    faceMeshRenderer.SetBlendShapeWeight(eyeBlinkRight, blendshapeWeights.GetEyeBlinkRight(face));
    // Smile
    faceMeshRenderer.SetBlendShapeWeight(mouthLeft, blendshapeWeights.GetSmile(face));
    faceMeshRenderer.SetBlendShapeWeight(mouthRight, blendshapeWeights.GetSmile(face));
    // Eyebrows
    faceMeshRenderer.SetBlendShapeWeight(browUpLeft, blendshapeWeights.GetBrowUpLeft(face));
    faceMeshRenderer.SetBlendShapeWeight(browUpRight, blendshapeWeights.GetBrowUpRight(face));
    // Head rotation
    newRotation = baseRotation * Quaternion.Euler(face.angles.yaw, -face.angles.pitch, face.angles.roll);
    }

  7. Update で、頭をスムーズに回転させます。

    public class FaceAnimController :MonoBehaviour
    {
    ...
    faceRaw.gameObject.SetActive(true);
    }
    void Update()
    {
    headRoot.rotation = Quaternion.Slerp(headRoot.rotation, newRotation, slerpRotation * Time.deltaTime);
    }
    }
    注意
    Quaternion.Slerp に関する詳細を確認いただけます。
  8. プロジェクトを実行します。これで、キツネの顔がより生き生きとしています。頭が動き、感情に応じて表情が変化し、耳や毛の動きがあります。このサンプルをより洗練されたプロジェクトにするために、Nuitrack を使って顔のトラッキング、スケルトン トラッキング、動く絵文字も活用できます。お楽しみください!
Uanimoji_1.gif