MRが楽しい

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

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

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

Chapter 9:Create the CustomVisionTrainer class

4つ目に作成するスクリプトは VoiceRecognizer クラスです。
このクラスは Custom Vison Service をトレーニングする一連のWeb呼び出しを行います。

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

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

// 名前空間の追加
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

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

    /// <summary>
    /// Custom Vision Service URL root
    /// Custom Vision Service のルートURL
    /// </summary>
    private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/";

    /// <summary>
    /// Insert your prediction key here
    /// ここに prediction key を挿入する
    /// </summary>
    private string trainingKey = "- Insert your key here -";

    /// <summary>
    /// Insert your Project Id here
    /// ここに Project Id を挿入する
    /// </summary>
    private string projectId = "- Insert your Project Id here -";

    /// <summary>
    /// Byte array of the image to submit for analysis
    /// 分析のために提出する画像のバイト配列
    /// </summary>
    internal byte[] imageBytes;

    /// <summary>
    /// The Tags accepted
    /// 利用タグの種類
    /// </summary>
    internal enum Tags { Mouse, Keyboard }

    /// <summary>
    /// The UI displaying the training Chapters
    /// トレーニングの段階を表示するUI
    /// </summary>
    private TextMesh trainingUI_TextMesh;

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

    /// <summary>
    /// Runs at initialization right after Awake method
    /// StartメソッドはAwakeメソッドの直後の初期化時に実行されます
    /// </summary>
    private void Start()
    {
        trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false);
    }

    /// <summary>
    /// イメージがデバイスにキャプチャされて格納され、Custom Vision Service に提出してトレーニングする準備が整ったときに最初に呼び出されます。
    /// このメソッドは、トレーニングUIに、ユーザーがキャプチャされた画像に対して、タグを付けに使用できるキーワードのセットを表示します。
    /// また、VoiceRecognizerクラスに、音声入力のためにユーザーの聴取を開始するよう通知します。
    /// </summary>
    internal void RequestTagSelection()
    {
        trainingUI_TextMesh.gameObject.SetActive(true);
        trainingUI_TextMesh.text = " \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard";

        VoiceRecognizer.Instance.keywordRecognizer.Start();
    }

    /// <summary>
    /// Verify voice input against stored tags.
    /// If positive, it will begin the Service training process.
    /// 保存されたタグとの音声入力を確認します。
    /// 有効なタグの場合は、サービストレーニングプロセスが開始されます。
    /// </summary>
    internal void VerifyTag(string spokenTag)
    {
        if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString())
        {
            string tagText = string.Format("Tag chosen: {0}", spokenTag);
            trainingUI_TextMesh.text = tagText;
            VoiceRecognizer.Instance.keywordRecognizer.Stop();
            StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag));
        }
    }

    /// <summary>
    /// Call the Custom Vision Service to submit the image.
    /// Custom Vision Service を呼び出して画像を送信してください。
    /// </summary>
    public IEnumerator SubmitImageForTraining(string imagePath, string tag)
    {
        yield return new WaitForSeconds(2);
        string uiText = string.Format("Submitting Image \nwith tag: {0} \nto Custom Vision Service", tag);
        trainingUI_TextMesh.text = uiText;
        string imageId = string.Empty;
        string tagId = string.Empty;

        // Retrieving the Tag Id relative to the voice input
        // 音声入力に対するタグIDの取得
        string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId);
        using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint))
        {
            www.SetRequestHeader("Training-Key", trainingKey);
            www.downloadHandler = new DownloadHandlerBuffer();
            yield return www.SendWebRequest();
            string jsonResponse = www.downloadHandler.text;

            Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse);

            foreach (TagOfProject tOP in tagRootObject.Tags)
            {
                if (tOP.Name == tag)
                {
                    tagId = tOP.Id;
                }
            }
        }

        // Creating the image object to send for training
        // トレーニングのために送信するイメージオブジェクトの作成
        List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>();
        MultipartObject multipartObject = new MultipartObject();
        multipartObject.contentType = "application/octet-stream";
        multipartObject.fileName = "";
        multipartObject.sectionData = GetImageAsByteArray(imagePath);
        multipartList.Add(multipartObject);

        string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId);

        using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList))
        {
            // Gets a byte array out of the saved image
            // 保存されたイメージからバイト配列を取得します。
            imageBytes = GetImageAsByteArray(imagePath);

            //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
            www.SetRequestHeader("Training-Key", trainingKey);

            // The upload handler will help uploading the byte array with the request
            // アップロードハンドラはリクエストと共にバイト配列をアップロードします
            www.uploadHandler = new UploadHandlerRaw(imageBytes);

            // The download handler will help receiving the analysis from Azure
            // ダウンロードハンドラは、Azureから分析を受け取ります
            www.downloadHandler = new DownloadHandlerBuffer();

            // Send the request
            // リクエストを送信する
            yield return www.SendWebRequest();

            string jsonResponse = www.downloadHandler.text;

            ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse);
            imageId = m.Images[0].Image.Id;
        }
        trainingUI_TextMesh.text = "Image uploaded";
        StartCoroutine(TrainCustomVisionProject());
    }

    /// <summary>
    /// Call the Custom Vision Service to train the Service.
    /// It will generate a new Iteration in the Service
    /// カスタムビジョンサービスに電話をかけて、サービスをトレーニングしてください。
    /// このサービスでは新しい学習サイクルが生成されます
    /// </summary>
    public IEnumerator TrainCustomVisionProject()
    {
        yield return new WaitForSeconds(2);

        trainingUI_TextMesh.text = "Training Custom Vision Service";

        WWWForm webForm = new WWWForm();

        string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId);

        using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm))
        {
            www.SetRequestHeader("Training-Key", trainingKey);
            www.downloadHandler = new DownloadHandlerBuffer();
            yield return www.SendWebRequest();
            string jsonResponse = www.downloadHandler.text;
            string debugLog = string.Format("Training - JSON Response: {0}", jsonResponse);
            Debug.Log(debugLog);

            // A new iteration that has just been created and trained
            // 訓練された新しい学習サイクル
            Iteration iteration = new Iteration();
            iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse);

            if (www.isDone)
            {
                trainingUI_TextMesh.text = "Custom Vision Trained";

                // Since the Service has a limited number of iterations available,
                // we need to set the last trained iteration as default
                // and delete all the iterations you dont need anymore
                // サービスでは利用可能な学習サイクルの回数が限られているため、
                // 最後の訓練された学習をデフォルトとして設定する必要があります
                // あなたがもう必要としないすべての学習サイクルを削除します
                StartCoroutine(SetDefaultIteration(iteration));
            }
        }
    }

    /// <summary>
    /// Set the newly created iteration as Default
    /// 新しく作成された学習サイクルをデフォルトに設定する
    /// </summary>
    private IEnumerator SetDefaultIteration(Iteration iteration)
    {
        yield return new WaitForSeconds(5);
        trainingUI_TextMesh.text = "Setting default iteration";

        // Set the last trained iteration to default
        // 最後に訓練された学習サイクルをデフォルトに設定する
        iteration.IsDefault = true;

        // Convert the iteration object as JSON
        // 学習オブジェクトをJSONとして変換する
        string iterationAsJson = JsonConvert.SerializeObject(iteration);
        byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson);

        string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}",
                                                        url, projectId, iteration.Id);

        using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes))
        {
            www.method = "PATCH";
            www.SetRequestHeader("Training-Key", trainingKey);
            www.SetRequestHeader("Content-Type", "application/json");
            www.downloadHandler = new DownloadHandlerBuffer();

            yield return www.SendWebRequest();

            string jsonResponse = www.downloadHandler.text;

            if (www.isDone)
            {
                trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration";
                StartCoroutine(DeletePreviousIteration(iteration));
            }
        }
    }

    /// <summary>
    /// Delete the previous non-default iteration.
    /// デフォルト以外の既定の学習サイクルを削除します。
    /// </summary>
    public IEnumerator DeletePreviousIteration(Iteration iteration)
    {
        yield return new WaitForSeconds(5);

        trainingUI_TextMesh.text = "Deleting Unused \nIteration";

        string iterationToDeleteId = string.Empty;

        string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId);

        using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint))
        {
            www.SetRequestHeader("Training-Key", trainingKey);
            www.downloadHandler = new DownloadHandlerBuffer();
            yield return www.SendWebRequest();

            string jsonResponse = www.downloadHandler.text;

            // The iteration that has just been trained
            // 訓練された学習サイクル
            List<Iteration> iterationsList = new List<Iteration>();
            iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse);

            foreach (Iteration i in iterationsList)
            {
                if (i.IsDefault != true)
                {
                    string logText = string.Format("Cleaning - Deleting iteration: {0}, {1}", i.Name, i.Id);
                    Debug.Log(logText);
                    iterationToDeleteId = i.Id;
                    break;
                }
            }
        }

        string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId);

        using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint))
        {
            www2.SetRequestHeader("Training-Key", trainingKey);
            www2.downloadHandler = new DownloadHandlerBuffer();
            yield return www2.SendWebRequest();
            string jsonResponse = www2.downloadHandler.text;

            trainingUI_TextMesh.text = "Iteration Deleted";
            yield return new WaitForSeconds(2);
            trainingUI_TextMesh.text = "Ready for next \ncapture";

            yield return new WaitForSeconds(2);
            trainingUI_TextMesh.text = "";
            ImageCapture.Instance.ResetImageCapture();
        }
    }

    /// <summary>
    /// Returns the contents of the specified image file as a byte array.
    /// 指定されたイメージファイルの内容をバイト配列として返します。
    /// </summary>
    static byte[] GetImageAsByteArray(string imageFilePath)
    {
        FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
        BinaryReader binaryReader = new BinaryReader(fileStream);
        return binaryReader.ReadBytes((int)fileStream.Length);
    }
}

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

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

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