MRが楽しい

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

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

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

Chapter 11:Create the ImageCapture class

最後に作成するスクリプトは ImageCapture クラスです。

このクラスは以下の処理を行います。
・HoloLensカメラを使用して画像をキャプチャし、Appフォルダに保存します。
・ユーザーからのタップジェスチャーの処理を受け取ります。
・アプリケーションを分析モードまたはトレーニングモードで実行するかどうかを決定するEnum値を保持します。

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

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

/// 名前空間の追加
using System;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.XR.WSA.Input;
using UnityEngine.XR.WSA.WebCam;

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

    /// <summary>
    /// Keep counts of the taps for image renaming
    /// イメージの名前を変更するためのタップ数を維持する
    /// </summary>
    private int captureCount = 0;

    /// <summary>
    /// Photo Capture object
    /// フォトキャプチャオブジェクト
    /// </summary>
    private PhotoCapture photoCaptureObject = null;

    /// <summary>
    /// Allows gestures recognition in HoloLens
    /// HoloLensでジェスチャー認識を可能にする
    /// </summary>
    private GestureRecognizer recognizer;

    /// <summary>
    /// Loop timer
    /// ループタイマ
    /// </summary>
    private float secondsBetweenCaptures = 10f;

    /// <summary>
    /// Application main functionalities switch
    /// アプリケーションの主な機能の切り替え
    /// </summary>
    internal enum AppModes { Analysis, Training }

    /// <summary>
    /// Local variable for current AppMode
    /// 現在のアプリモード
    /// </summary>
    internal AppModes AppMode { get; private set; }

    /// <summary>
    /// Flagging if the capture loop is running
    /// キャプチャループが実行中の場合にフラグを立てる
    /// </summary>
    internal bool captureIsActive;

    /// <summary>
    /// File path of current analysed photo
    /// 現在分析されている写真のファイルパス
    /// </summary>
    internal string filePath = string.Empty;

    /// <summary>
    /// Called on initialization
    /// 初期化処理
    /// </summary>
    private void Awake()
    {
        Instance = this;

        // Change this flag to switch between Analysis Mode and Training Mode 
        // 分析モードとトレーニングモードを切り替えるには、このフラグを変更します
        AppMode = AppModes.Training;
    }

    /// <summary>
    /// Runs at initialization right after Awake method
    /// StartメソッドはAwakeメソッドの直後の初期化時に実行されます
    /// </summary>
    void Start()
    {
        // Clean up the LocalState folder of this application from all photos stored
        // このアプリケーションのLocalStateフォルダ(保存されたすべての写真)をクリーンアップします。
        DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
        var fileInfo = info.GetFiles();
        foreach (var file in fileInfo)
        {
            try
            {
                file.Delete();
            }
            catch (Exception)
            {
                Debug.LogFormat("Cannot delete file: ", file.Name);
            }
        }

        // Subscribing to the Hololens API gesture recognizer to track user gestures
        // ユーザーのジェスチャーを追跡するため、Hololens APIジェスチャー認識を開始する
        recognizer = new GestureRecognizer();
        recognizer.SetRecognizableGestures(GestureSettings.Tap);
        recognizer.Tapped += TapHandler;
        recognizer.StartCapturingGestures();

        SceneOrganiser.Instance.SetCameraStatus("Ready");
    }

    /// <summary>
    /// Respond to Tap Input.
    /// タップ入力に応答する
    /// タップ操作は写真キャプチャ・ループを開始または停止するためのスイッチとして作用します
    /// トレーニングモードでは、カメラから画像をキャプチャします
    /// 解析モードではカーソルが緑色の場合は、カメラが画像を撮影することができます
    /// カーソルが赤色のときは、カメラがビジーであることを示します
    /// </summary>
    private void TapHandler(TappedEventArgs obj)
    {
        switch (AppMode)
        {
            case AppModes.Analysis:
                if (!captureIsActive)
                {
                    captureIsActive = true;

                    // Set the cursor color to red
                    // カーソルの色を赤に設定する
                    SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;

                    // Update camera status to looping capture.
                    // カメラの状態を"Looping Capture"に更新します
                    SceneOrganiser.Instance.SetCameraStatus("Looping Capture");

                    // Begin the capture loop
                    // キャプチャループを開始する
                    InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures);
                }
                else
                {
                    // The user tapped while the app was analyzing 
                    // therefore stop the analysis process
                    // アプリの分析中にユーザーがタップしたケース
                    // 分析プロセスを停止する
                    ResetImageCapture();
                }
                break;

            case AppModes.Training:
                if (!captureIsActive)
                {
                    captureIsActive = true;

                    // Call the image capture
                    // 画像キャプチャを呼び出す
                    ExecuteImageCaptureAndAnalysis();

                    // Set the cursor color to red
                    // カーソルの色を赤に設定する
                    SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;

                    // Update camera status to uploading image.
                    // カメラのステータスを"Uploading Image"に更新します
                    SceneOrganiser.Instance.SetCameraStatus("Uploading Image");
                }
                break;
        }
    }

    /// <summary>
    /// Begin process of Image Capturing and send To Azure Custom Vision Service.
    /// 画像キャプチャのプロセスを開始し、Azure Custom Vision Service に送信します
    /// </summary>
    private void ExecuteImageCaptureAndAnalysis()
    {
        // Update camera status to analysis.
        // カメラのステータスを"Analysis"に更新します
        SceneOrganiser.Instance.SetCameraStatus("Analysis");

        // Create a label in world space using the SceneOrganiser class 
        // Invisible at this point but correctly positioned where the image was taken
        // SceneOrganiserクラスを使用してワールド空間にラベルを作成する
        // この時点では目に見えないが、画像が撮影された場所に正しく配置されている
        SceneOrganiser.Instance.PlaceAnalysisLabel();

        // Set the camera resolution to be the highest possible
        // カメラの解像度を可能な限り高く設定する
        Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();

        Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);

        // Begin capture process, set the image format
        // キャプチャプロセスを開始する。イメージフォーマットの設定を行う。
        PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
        {
            photoCaptureObject = captureObject;

            CameraParameters camParameters = new CameraParameters
            {
                hologramOpacity = 0.0f,
                cameraResolutionWidth = targetTexture.width,
                cameraResolutionHeight = targetTexture.height,
                pixelFormat = CapturePixelFormat.BGRA32
            };

            // Capture the image from the camera and save it in the App internal folder
            // カメラから画像をキャプチャして、それをAppの内部フォルダに保存します
            captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
            {
                string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                filePath = Path.Combine(Application.persistentDataPath, filename);
                captureCount++;
                photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
            });
        });
    }

    /// <summary>
    /// Register the full execution of the Photo Capture. 
    /// フォトキャプチャの完了時処理を登録します
    /// </summary>
    void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
    {
        // Call StopPhotoMode once the image has successfully captured
        // イメージが正常に取得されたらStopPhotoModeを呼び出します
        photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
    }


    /// <summary>
    /// The camera photo mode has stopped after the capture.
    /// Begin the Image Analysis process.
    /// キャプチャ後にカメラ写真モードが停止しました
    /// 画像解析プロセスを開始します
    /// </summary>
    void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
    {
        Debug.LogFormat("Stopped Photo Mode");

        // Dispose from the object in memory and request the image analysis 
        // メモリ内のオブジェクトを破棄し、画像解析を要求する
        photoCaptureObject.Dispose();
        photoCaptureObject = null;

        switch (AppMode)
        {
            case AppModes.Analysis:
                // Call the image analysis
                // 画像解析を呼び出す
                StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath));
                break;

            case AppModes.Training:
                // Call training using captured image
                // キャプチャされた画像を使用したトレーニングを呼び出す
                CustomVisionTrainer.Instance.RequestTagSelection();
                break;
        }
    }

    /// <summary>
    /// Stops all capture pending actions
    /// すべてのキャプチャ保留中のアクションを停止します
    /// </summary>
    internal void ResetImageCapture()
    {
        captureIsActive = false;

        // Set the cursor color to green
        // カーソルの色を緑に設定する
        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;

        // Update camera status to ready.
        // カメラの状態を準備完了に更新します
        SceneOrganiser.Instance.SetCameraStatus("Ready");

        // Stop the capture loop if active
        // 処理がアクティブな場合はキャプチャループを停止する
        CancelInvoke();
    }
}

f:id:bluebirdofoz:20180828031846j:plain

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

12.これで全てのスクリプトが完成しました。
Scripts フォルダから SceneOrganiserクラスを Hierarchy パネルの MixedRealityCamera オブジェクトにアタッチします。
f:id:bluebirdofoz:20180828031908j:plain

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