なんかいろいろしてみます

Apr 1, 2018 - 3 minute read - HoloLens

HoloLensでWindowsMLを試してみる(Unity利用編)

HoloLens RS4 PreviewでWindowsMLの動作が確認できたので利用しやすいようにUnityに組み込んで動作を確認します.

WindowsMLをHoloLensで動作されるまでは前の記事に書いてます.

概要

Unityを利用してHoloLens上で画像認識を行うサンプルを作成します.

利用するサンプルはGithub上の

https://github.com/Microsoft/Windows-Machine-Learning/tree/master

のSqueezeNetObjectDetection(画像認識デモ)を参考にします.

実行環境

  • 開発環境
    • Windows10 Insider Preview 17127
    • Windows10 SDK Insider Preview 17115
    • Visual Studio2017
    • Unity 2017.4.0f1
  • 実行環境
    • HoloLens RS4 Preview 17123

プロジェクト作成

Unityプロジェクト

UWPプロジェクト

  • UnityでビルドしたプロジェクトファイルをVisualStudio2017で開き,ソリューションプラットフォームをx86,実行先を「Device」に変更します.
  • 内部の処理は「WindowsML_Demo.cs」にて行っています.
 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233
    void Start()
    {
        // 学習モデルとラベルデータのローカルフォルダへの移動
        File.Copy(Application.streamingAssetsPath + "\\" + "SqueezeNet.onnx", Application.persistentDataPath + "\\" + "SqueezeNet.onnx", true);
        File.Copy(Application.streamingAssetsPath + "\\" + "Labels.json", Application.persistentDataPath + "\\" + "Labels.json", true);

        // Webカメラの取得と録画の開始
        webcam = new WebCamTexture("MN34150", 896, 504,15);
        Tex2d = new Texture2D(896, 504, TextureFormat.RGBA32, false);
        webcam.Play();
 
#if UNITY_UWP
        // WindowsMLのモデル読み込みの開始
        Task.Run(async () =>
        {
            await LoadModelAsync();
            await ObjectDetectation();
        });
#endif
        StartCoroutine(GetWebCamData());
    }

    private IEnumerator GetWebCamData()
    {
        while (true)
        {
            // Webカメラからの画像取得をbyte配列への変換
            Tex2d.SetPixels32(webcam.GetPixels32());
            yield return null;
            bytes = Tex2d.GetRawTextureData();
            yield return null;
        }
    }
  • 今回はUnityのWebCamTextureを利用しています.
  • UWP側にもカメラ制御の関数は用意されているのでそちらを使うこともできます.
  • カメラデータはコルーチンでbyte配列に変換しています.

 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536373839404142
    private async Task LoadModelAsync()
    {
        try
        {
            // ラベルデータの読み込み
            var file = await ApplicationData.Current.LocalFolder.GetFileAsync("Labels.json");
            using (var inputStream = await file.OpenReadAsync())
            using (var classicStream = inputStream.AsStreamForRead())
            using (var streamReader = new StreamReader(classicStream))
            {
                string line = "";
                char[] charToTrim = { '\"', ' ' };
                while (streamReader.Peek() >= 0)
                {
                    line = streamReader.ReadLine();
                    line.Trim(charToTrim);
                    var indexAndLabel = line.Split(':');
                    if (indexAndLabel.Count() == 2)
                    {
                        _labels.Add(indexAndLabel[1]);
                    }
                }
            }

            // 学習モデルの読み込み
            var modelFile = await ApplicationData.Current.LocalFolder.GetFileAsync("SqueezeNet.onnx");
            _model = await LearningModelPreview.LoadModelFromStorageFileAsync(modelFile);

            List<ILearningModelVariableDescriptorPreview> inputFeatures = _model.Description.InputFeatures.ToList();
            List<ILearningModelVariableDescriptorPreview> outputFeatures = _model.Description.OutputFeatures.ToList();
            _inputImageDescription = inputFeatures.FirstOrDefault(feature => feature.ModelFeatureKind == LearningModelFeatureKindPreview.Image) as ImageVariableDescriptorPreview;
            _outputTensorDescription = outputFeatures.FirstOrDefault(feature => feature.ModelFeatureKind == LearningModelFeatureKindPreview.Tensor) as TensorVariableDescriptorPreview;
        }
        catch (Exception ex)
        {
            _model = null;
            UnityEngine.WSA.Application.InvokeOnAppThread(() =>
            {
                text.text = ex.ToString();
            }, true);
        }
    }

  • 個々ではWindowsMLを使いラベルデータ,学習済みデータ(.onnx)の読み込みを行っています.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
    private async Task ObjectDetectation()
    {
        while (true)
        {
            if (bytes!=null)
            {
                // UnityのWebカメラは上下反転しているので入れ替え処理
                var buf = new byte[bytes.Length];
                for (int i = 0; i < 504; i++)
                {
                    for (int j = 0; j < 896; j++)
                    {
                        buf[(896 * (504 - 1 - i) + j) * 4 + 0] = bytes[(896 * i + j )* 4 + 0];
                        buf[(896 * (504 - 1 - i) + j) * 4 + 1] = bytes[(896 * i + j )* 4 + 1];
                        buf[(896 * (504 - 1 - i) + j) * 4 + 2] = bytes[(896 * i + j )* 4 + 2];
                        buf[(896 * (504 - 1 - i) + j) * 4 + 3] = bytes[(896 * i + j )* 4 + 3];
                    }
                }

                // 入力画像から物体認識を行う
                try
                {
                    SoftwareBitmap softwareBitmap;
                    softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Rgba8, 896, 504);
                    softwareBitmap.CopyFromBuffer(buf.AsBuffer());
                    bytes = null;
                    buf = null;
                    softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
                    VideoFrame inputFrame = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);

                    // WindowsMLに入力,出力形式を設定する
                    LearningModelBindingPreview binding = new LearningModelBindingPreview(_model as LearningModelPreview);
                    binding.Bind(_inputImageDescription.Name, inputFrame);
                    binding.Bind(_outputTensorDescription.Name, _outputVariableList);

                    // Process the frame with the model
                    LearningModelEvaluationResultPreview results = await _model.EvaluateAsync(binding, "test");
                    List<float> resultProbabilities = results.Outputs[_outputTensorDescription.Name] as List<float>;

                    // 認識結果から適合率の高い上位3位までを選択
                    List<float> topProbabilities = new List<float>() { 0.0f, 0.0f, 0.0f };
                    List<int> topProbabilityLabelIndexes = new List<int>() { 0, 0, 0 };
                    for (int i = 0; i < resultProbabilities.Count(); i++)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            if (resultProbabilities[i] > topProbabilities[j])
                            {
                                topProbabilityLabelIndexes[j] = i;
                                topProbabilities[j] = resultProbabilities[i];
                                break;
                            }
                        }
                    }

                    // 結果を出力する
                    string message = "Predominant objects detected are:";
                    for (int i = 0; i < 3; i++)
                    {
                        message += $"\n{ _labels[topProbabilityLabelIndexes[i]]} with confidence of { topProbabilities[i]}";
                    }
                    softwareBitmap.Dispose();
                    UnityEngine.WSA.Application.InvokeOnAppThread(() =>
                    {
                        text.text = message;
                    }, true);
                }
                catch (Exception ex)
                {
                    UnityEngine.WSA.Application.InvokeOnAppThread(() =>
                    {
                        text.text = ex.ToString();
                    }, true);
                }
            }
        }
    }

  • Unityのwebcamtextureから取得できるbyte配列は上下が反転しているため,WindowsMLに渡す前に反転させます.
  • UWP側にbyte配列を渡しWindowsMLに渡します.
  • WindowsMLに認識結果はList形式で帰ってくるためListないから適合率の高いデータを取得します.
  • 認識結果は文字列にした後Unity側のtextに渡して表示します.

実行結果

  • 以下の動画のようにHoloLensのカメラが認識した物体の情報が表示されます.
  • Unityからカメラを利用しているためデバイスポータルでの録画やキャプチャなどはできません.

まとめ

  • UnityプロジェクトでHoloLensのWindowsMLプロジェクトを動作させることができました.
  • 学習済みデータの利用はできそうなので,次回は学習データの作成からWindowsMLを利用した認識までの流れを確認していきます.