MRが楽しい

MRやVRについて学習したことを書き残す

公式チュートリアル「MR and Azure 302b 10章」を試してみる

本日はチュートリアルの実施枠です。
Academyの「MR and Azure 302b: Custom vision」の実施内容をまとめます。
docs.microsoft.com
前回記事の続きです。
bluebirdofoz.hatenablog.com
今回は「Chapter 10」です。

Chapter 10:Create the SceneOrganiser class

5つ目に作成するスクリプトは SceneOrganiser クラスです。

このクラスは以下の処理を行います。
・メインカメラに接続するCursorオブジェクトを作成します。
・サービスが実際のオブジェクトを認識したときに表示される Label オブジェクトを作成します。
・メインカメラに適切なコンポーネントを取り付けてセットアップします。
・解析モード時、メインカメラの位置に適切なワールド空間の相対的で、実行時にラベルを起動する。
ラベルには Custom Vision Service から受信したデータを表示します。
・トレーニングモード時、トレーニングプロセスの様々な段階を表示するUIを起動する。


1.Script フォルダを開きます。
フォルダ内で右クリックして、Creapte -> C# Script を選択します。
Script の名称は SceneOrganiser に設定します。
f:id:bluebirdofoz:20180827022447j:plain

2.新しいスクリプトをダブルクリックしてVisual Studioで開きます。
3-10.以下の通り、スクリプトを編集します。
・SceneOrganiser.cs

// 名前空間の追加
using UnityEngine;

public class SceneOrganiser : MonoBehaviour {
    // メンバ変数の追加
    /// <summary>
    /// Allows this class to behave like a singleton
    /// クラスがシングルトンのように動作できるようにします 
    /// </summary>
    public static SceneOrganiser Instance;

    /// <summary>
    /// The cursor object attached to the camera
    /// カメラに取り付けられたカーソルオブジェクト
    /// </summary>
    internal GameObject cursor;

    /// <summary>
    /// The label used to display the analysis on the objects in the real world
    /// 現実世界のオブジェクトの分析を表示するために使用されるラベル
    /// </summary>
    internal GameObject label;

    /// <summary>
    /// Object providing the current status of the camera.
    /// カメラの現在の状態を示すオブジェクト
    /// </summary>
    internal TextMesh cameraStatusIndicator;

    /// <summary>
    /// Reference to the last label positioned
    /// 最後に配置されたラベルへの参照
    /// </summary>
    internal Transform lastLabelPlaced;

    /// <summary>
    /// Reference to the last label positioned
    /// 最後に配置されたラベルのテキストへの参照
    /// </summary>
    internal TextMesh lastLabelPlacedText;

    /// <summary>
    /// Current threshold accepted for displaying the label
    /// Reduce this value to display the recognition more often
    /// ラベルを表示するためのしきい値
    /// この値を減らすと認識がより頻繁に表示される
    /// </summary>
    internal float probabilityThreshold = 0.5f;

    /// <summary>
    /// Called on initialization
    /// 初期化処理
    /// </summary>
    private void Awake()
    {
        // Use this class instance as singleton
        // このクラスをシングルトンと同じように動作させます 
        Instance = this;

        // Add the ImageCapture class to this GameObject
        // GameObjectにImageCaptureクラスを追加する
        gameObject.AddComponent<ImageCapture>();

        // Add the CustomVisionAnalyser class to this GameObject
        // GameObjectにCustomVisionAnalyserクラスを追加する
        gameObject.AddComponent<CustomVisionAnalyser>();

        // Add the CustomVisionTrainer class to this GameObject
        // GameObjectにCustomVisionTrainerクラスを追加する
        gameObject.AddComponent<CustomVisionTrainer>();

        // Add the VoiceRecogniser class to this GameObject
        // GameObjectにVoiceRecogniserクラスを追加する
        gameObject.AddComponent<VoiceRecognizer>();

        // Create the camera Cursor
        // カメラのカーソルを作成する
        cursor = CreateCameraCursor();

        // Load the label prefab as reference
        // Labelプレハブを参照としてロードする
        label = CreateLabel();

        // Create the camera status indicator label, and place it above where predictions
        // and training UI will appear.
        // カメラステータスインジケータラベルを作成します。
        // これを予測とトレーニングのUIが表示される場所の上に配置します。
        cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true);

        // Set camera status indicator to loading.
        // カメラステータスインジケータを"Loading"に設定します。
        SetCameraStatus("Loading");
    }

    /// <summary>
    /// Spawns cursor for the Main Camera
    /// メインカメラのカーソルを生成する
    /// </summary>
    private GameObject CreateCameraCursor()
    {
        // Create a sphere as new cursor
        // 新しいカーソルとしてSphereオブジェクトを作成する
        GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);

        // Attach it to the camera
        // カーソルをカメラに取り付けます
        newCursor.transform.parent = gameObject.transform;

        // Resize the new cursor
        // カーソルのサイズを変更する
        newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);

        // Move it to the correct position
        // カーソルを正面位置に移動する
        newCursor.transform.localPosition = new Vector3(0, 0, 4);

        // Set the cursor color to red
        // カーソルの色を赤に設定する
        newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse"));
        newCursor.GetComponent<Renderer>().material.color = Color.green;

        return newCursor;
    }

    /// <summary>
    /// Create the analysis label object
    /// 分析ラベルオブジェクトを作成する
    /// </summary>
    private GameObject CreateLabel()
    {
        // Create a sphere as new cursor
        // ラベルとしてオブジェクトを作成する
        GameObject newLabel = new GameObject();

        // Resize the new cursor
        // オブジェクトのサイズを変更する
        newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);

        // Creating the text of the label
        // ラベルのテキストを作成する
        TextMesh t = newLabel.AddComponent<TextMesh>();
        t.anchor = TextAnchor.MiddleCenter;
        t.alignment = TextAlignment.Center;
        t.fontSize = 50;
        t.text = "";

        return newLabel;
    }

    /// <summary>
    /// Set the camera status to a provided string. Will be coloured if it matches a keyword.
    /// カメラの状態を指定された文字列で表示します。キーワードと一致する場合は色を設定します。
    /// </summary>
    /// <param name="statusText">Input string</param>
    public void SetCameraStatus(string statusText)
    {
        if (string.IsNullOrEmpty(statusText) == false)
        {
            string message = "white";

            switch (statusText.ToLower())
            {
                case "loading":
                    message = "yellow";
                    break;

                case "ready":
                    message = "green";
                    break;

                case "uploading image":
                    message = "red";
                    break;

                case "looping capture":
                    message = "yellow";
                    break;

                case "analysis":
                    message = "red";
                    break;
            }

            string labelText = string.Format("Camera Status:\n<color={0}>{1}..</color>", message, statusText);
            cameraStatusIndicator.GetComponent<TextMesh>().text = labelText;
        }
    }

    /// <summary>
    /// Instantiate a label in the appropriate location relative to the Main Camera.
    /// メインカメラに対して適切な場所にラベルを設定します。
    /// </summary>
    public void PlaceAnalysisLabel()
    {
        lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
        lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
    }

    /// <summary>
    /// Set the Tags as Text of the last label created. 
    /// 作成された最後のラベルのテキストにタグを設定します。
    /// これによりシーンにデータが表示されます
    /// </summary>
    public void SetTagsToLastLabel(AnalysisObject analysisObject)
    {
        lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();

        if (analysisObject.Predictions != null)
        {
            foreach (Prediction p in analysisObject.Predictions)
            {
                if (p.Probability > 0.02)
                {
                    string labelText = string.Format("Detected: {0} {1:.00}\n", p.TagName, p.Probability);
                    lastLabelPlacedText.text += labelText;
                    Debug.Log(labelText);
                }
            }
        }
    }

    /// <summary>
    /// Create a 3D Text Mesh in scene, with various parameters.
    /// さまざまなパラメータを使用して、シーン内に3Dテキストメッシュを作成します。
    /// </summary>
    /// <param name="name">name of object (オブジェクト名)</param>
    /// <param name="scale">scale of object (i.e. 0.04f) (オブジェクトサイズ)</param>
    /// <param name="yPos">height above the cursor (i.e. 0.3f) (カーソルの高さ)</param>
    /// <param name="zPos">distance from the camera (カメラからの距離)</param>
    /// <param name="setActive">whether the text mesh should be visible when it has been created (テキストメッシュが作成されたときに表示されるかどうか)</param>
    /// <returns>Returns a 3D text mesh within the scene (シーン内の3Dテキストメッシュを返します)</returns>
    internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive)
    {
        GameObject display = new GameObject(name, typeof(TextMesh));
        display.transform.parent = Camera.main.transform;
        display.transform.localPosition = new Vector3(0, yPos, zPos);
        display.SetActive(setActive);
        display.transform.localScale = new Vector3(scale, scale, scale);
        display.transform.rotation = new Quaternion();
        TextMesh textMesh = display.GetComponent<TextMesh>();
        textMesh.anchor = TextAnchor.MiddleCenter;
        textMesh.alignment = TextAlignment.Center;
        return textMesh;
    }
}

※ この時点でエラーが表示されます(「The name‘ImageCapture’does not exist...」)。
これは、コードが ImageCapture クラスを参照するためです。ImageCapture クラスは、次の章で作成します
f:id:bluebirdofoz:20180827022527j:plain

11.Visual Studio で変更を保存して Unity に戻ります。
f:id:bluebirdofoz:20180827022541j:plain

12.これで SceneOrganiser クラスを作成しました。
Chapter 6 で作成した CustomVisionAnalyser クラスの以下の行についてコメントアウトを解除します。

  AnalysisObject analysisObject = new AnalysisObject();
  analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
  SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);

f:id:bluebirdofoz:20180827022604j:plain

Chapter 10 はここまでです。
次回は Chapter 11 を実施します。
bluebirdofoz.hatenablog.com