MRが楽しい

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

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

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

Chapter 6:Create the FaceAnalysis class

最初に作成するスクリプトは FaceAnalysis クラスです。
FaceAnalysisクラスの目的は、Azure Face Recognition Service との通信に必要なメソッドをホストすることです。
・キャプチャ画像を送信した後、それを分析し、その中の顔を識別し、既知の人物に属するかどうかを判断する。
・既知の人物が見つかった場合、このクラスはその名前をシーン内のUIテキストとして表示します。

1.Script フォルダを作成します。
Asset フォルダで右クリックし、Create > Folder を選択します。
f:id:bluebirdofoz:20180924193140j:plain

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

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

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

public class FaceAnalysis : MonoBehaviour {

    // デシリアライズに使用されるオブジェクトクラスの追加
    /// <summary>
    /// The Person Group object
    /// 人物グループオブジェクト
    /// </summary>
    public class Group_RootObject
    {
        public string personGroupId { get; set; }
        public string name { get; set; }
        public object userData { get; set; }
    }

    /// <summary>
    /// The Person Face object
    /// 顔オブジェクト
    /// </summary>
    public class Face_RootObject
    {
        public string faceId { get; set; }
    }

    /// <summary>
    /// Collection of faces that needs to be identified
    /// 特定が必要な顔のコレクション
    /// </summary>
    public class FacesToIdentify_RootObject
    {
        public string personGroupId { get; set; }
        public List<string> faceIds { get; set; }
        public int maxNumOfCandidatesReturned { get; set; }
        public double confidenceThreshold { get; set; }
    }

    /// <summary>
    /// Collection of Candidates for the face
    /// 顔の候補者の集合
    /// </summary>
    public class Candidate_RootObject
    {
        public string faceId { get; set; }
        public List<Candidate> candidates { get; set; }
    }

    public class Candidate
    {
        public string personId { get; set; }
        public double confidence { get; set; }
    }

    /// <summary>
    /// Name and Id of the identified Person
    /// 特定された人の名前とID
    /// </summary>
    public class IdentifiedPerson_RootObject
    {
        public string personId { get; set; }
        public string name { get; set; }
    }

    // メンバ変数の追加
    /// <summary>
    /// Allows this class to behave like a singleton
    /// このクラスをシングルトンと同じように動作させます
    /// </summary>
    public static FaceAnalysis Instance;

    /// <summary>
    /// The analysis result text
    /// 分析結果テキスト
    /// </summary>
    private TextMesh labelText;

    /// <summary>
    /// Bytes of the image captured with camera
    /// カメラで撮影した画像のバイト列
    /// </summary>
    internal byte[] imageBytes;

    /// <summary>
    /// Path of the image captured with camera
    /// カメラで撮影した画像のパス
    /// </summary>
    internal string imagePath;

    /// <summary>
    /// Base endpoint of Face Recognition Service
    /// 顔認識サービスのベースエンドポイント
    /// </summary>
    const string baseEndpoint = "https://westus.api.cognitive.microsoft.com/face/v1.0/";

    /// <summary>
    /// Auth key of Face Recognition Service
    /// 顔認識サービスの認証キー
    /// </summary>
    private const string key = "- Insert your key here -";

    /// <summary>
    /// Id (name) of the created person group 
    /// 作成された人物グループのID(名前)
    /// </summary>
    private const string personGroupId = "- Insert your group Id here -";


    /// <summary>
    /// Initialises this class
    /// 初期化クラス
    /// </summary>
    private void Awake()
    {
        // Allows this instance to behave like a singleton
        // このクラスをシングルトンと同じように動作させます
        Instance = this;

        // Add the ImageCapture Class to this Game Object
        // ゲームオブジェクトに ImageCapture クラスを追加する
        gameObject.AddComponent<ImageCapture>();

        // Create the text label in the scene
        // シーンにテキストラベルを作成する
        CreateLabel();
    }

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

        // Attach the label to the Main Camera
        // メインカメラにラベルを配置する
        newLabel.transform.parent = gameObject.transform;

        // Resize and position the new cursor
        // 新しいカーソルのサイズを変更して配置する
        newLabel.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f);
        newLabel.transform.position = new Vector3(0f, 3f, 60f);

        // Creating the text of the Label
        // ラベルのテキストの作成
        labelText = newLabel.AddComponent<TextMesh>();
        labelText.anchor = TextAnchor.MiddleCenter;
        labelText.alignment = TextAlignment.Center;
        labelText.tabSize = 4;
        labelText.fontSize = 50;
        labelText.text = ".";
    }


    /// <summary>
    /// Detect faces from a submitted image
    /// 送信された画像から顔を検出する
    /// </summary>
    internal IEnumerator DetectFacesFromImage()
    {
        WWWForm webForm = new WWWForm();
        string detectFacesEndpoint = string.Format("{0}detect", baseEndpoint);

        // Change the image into a bytes array
        // イメージをバイト配列に変更する
        imageBytes = GetImageAsByteArray(imagePath);

        using (UnityWebRequest www =
            UnityWebRequest.Post(detectFacesEndpoint, webForm))
        {
            www.SetRequestHeader("Ocp-Apim-Subscription-Key", key);
            www.SetRequestHeader("Content-Type", "application/octet-stream");
            www.uploadHandler.contentType = "application/octet-stream";
            www.uploadHandler = new UploadHandlerRaw(imageBytes);
            www.downloadHandler = new DownloadHandlerBuffer();

            yield return www.SendWebRequest();
            string jsonResponse = www.downloadHandler.text;
            Face_RootObject[] face_RootObject =
                JsonConvert.DeserializeObject<Face_RootObject[]>(jsonResponse);

            List<string> facesIdList = new List<string>();
            // Create a list with the face Ids of faces detected in image
            // 画像で検出された顔の顔IDを含むリストを作成する
            foreach (Face_RootObject faceRO in face_RootObject)
            {
                facesIdList.Add(faceRO.faceId);
                Debug.Log(string.Format("Detected face - Id: {0}", faceRO.faceId));
            }

            StartCoroutine(IdentifyFaces(facesIdList));
        }
    }

    /// <summary>
    /// Returns the contents of the specified 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);
    }

    /// <summary>
    /// Identify the faces found in the image within the person group
    /// 人物グループの中から画像に含まれる顔を特定する
    /// 要求は識別された人の ID を返すが、名前は返さない
    /// </summary>
    internal IEnumerator IdentifyFaces(List<string> listOfFacesIdToIdentify)
    {
        // Create the object hosting the faces to identify
        // 識別する顔を送信するオブジェクトを作成する
        FacesToIdentify_RootObject facesToIdentify = new FacesToIdentify_RootObject();
        facesToIdentify.faceIds = new List<string>();
        facesToIdentify.personGroupId = personGroupId;
        foreach (string facesId in listOfFacesIdToIdentify)
        {
            facesToIdentify.faceIds.Add(facesId);
        }
        facesToIdentify.maxNumOfCandidatesReturned = 1;
        facesToIdentify.confidenceThreshold = 0.5;

        // Serialise to Json format
        // Json形式にシリアライズ
        string facesToIdentifyJson = JsonConvert.SerializeObject(facesToIdentify);
        // Change the object into a bytes array
        // オブジェクトをバイト配列に変更する
        byte[] facesData = Encoding.UTF8.GetBytes(facesToIdentifyJson);

        WWWForm webForm = new WWWForm();
        string detectFacesEndpoint = string.Format("{0}identify", baseEndpoint);

        using (UnityWebRequest www = UnityWebRequest.Post(detectFacesEndpoint, webForm))
        {
            www.SetRequestHeader("Ocp-Apim-Subscription-Key", key);
            www.SetRequestHeader("Content-Type", "application/json");
            www.uploadHandler.contentType = "application/json";
            www.uploadHandler = new UploadHandlerRaw(facesData);
            www.downloadHandler = new DownloadHandlerBuffer();

            yield return www.SendWebRequest();
            string jsonResponse = www.downloadHandler.text;
            Debug.Log(string.Format("Get Person - jsonResponse: {0}", jsonResponse));
            Candidate_RootObject[] candidate_RootObject = JsonConvert.DeserializeObject<Candidate_RootObject[]>(jsonResponse);

            // For each face to identify that ahs been submitted, display its candidate
            // 提出された顔が識別できるように、その候補を表示する
            foreach (Candidate_RootObject candidateRO in candidate_RootObject)
            {
                StartCoroutine(GetPerson(candidateRO.candidates[0].personId));

                // Delay the next "GetPerson" call, so all faces candidate are displayed properly
                // 次の「GetPerson」コールを遅らせることで、すべての顔候補が正しく表示される
                yield return new WaitForSeconds(3);
            }
        }
    }

    /// <summary>
    /// Provided a personId, retrieve the person name associated with it
    /// ID を指定すると、それに関連付けられた人名(name)を取得する
    /// </summary>
    internal IEnumerator GetPerson(string personId)
    {
        string getGroupEndpoint = string.Format("{0}persongroups/{1}/persons/{2}", baseEndpoint, personGroupId, personId);
        WWWForm webForm = new WWWForm();

        using (UnityWebRequest www = UnityWebRequest.Get(getGroupEndpoint))
        {
            www.SetRequestHeader("Ocp-Apim-Subscription-Key", key);
            www.downloadHandler = new DownloadHandlerBuffer();
            yield return www.SendWebRequest();
            string jsonResponse = www.downloadHandler.text;

            Debug.Log(string.Format("Get Person - jsonResponse: {0}", jsonResponse));
            IdentifiedPerson_RootObject identifiedPerson_RootObject = JsonConvert.DeserializeObject<IdentifiedPerson_RootObject>(jsonResponse);

            // Display the name of the person in the UI
            // ユーザーの名前をUIに表示する
            labelText.text = identifiedPerson_RootObject.name;
        }
    }
}

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

key と personGroupId を、第1章と第2章で作成したグループのサービスキーと ID で置き換えます。
f:id:bluebirdofoz:20180924193206j:plain

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

15.Hierarchy パネルの MixedRealityCameraParent を開きます。直下に MixedRealityCamera オブジェクトがあります。
FaceAnalysis スクリプトをこの MixedRealityCameraオブジェクトにドラッグして追加します。
f:id:bluebirdofoz:20180924193225j:plain

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