概要
2018/4/18にHoloLensRS4勉強会が行われ,WindowsMLとResearch modeについて紹介しました.
「HoloLens RS4 Preview公開記念勉強会!@アカツキ」で話しました
そこでHoloLensを使ったスライド操作を行うデモを行いました.(誤認識してうまくいかなかった)
今回はHoloLensRS4を用いたハンドジェスチャーコントローラーの作り方を紹介します.
開発環境
- Windows10 Insider Preview
- Visual Studio2017
- Unity 2017.4.0f1
- CustomVision(要アカウント登録)
- HoloLens RS4
機能紹介
HoloLensで取得したハンドジェスチャーポーズでパソコンのスライドを操作します.
ハンドジェスチャーポーズの取得にHoloLensRS4のResearch modeから取得した深度情報を利用しています.
取得した深度情報をWindowsMLを用いてどのハンドジェスチャーか判別します.
判別後Wifi経由でパソコンにスライド操作のコマンドを送り,受信ソフトがパソコンを操作します.(勉強会ではPowerPointを操作)
勉強会ではHoloLens首にかけた状態で話をしたかったので,首にかけた状態でも動作できるように調整を行っています.
以下のソフトを作成します.
- HoloLens深度情報取得用アプリ
- HoloLensハンドジェスチャー認識アプリ
- パソコン側コマンド受信用ソフト
WindowsMLとResearch modeの説明に関しては以下の記事を参照してください.
注意
HoloLensRS4のResearch modeは研究用,調査用機能のためバッテリーの消費が大きくなり動作が不安定になります.
ハンドジェスチャーの認識精度は学習データや実行環境などによって大きく変わります.
データ収集
HoloLensRS4のResearch modeを利用してジェスチャーポーズを取得します.
取得センサーは短距離Depthセンサを利用します.
短距離Depthセンサは手のシルエットを取得できるように調整されているため,手のシルエットのみを抜き出して保存することができます.
|  1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
 | private async void InitSensor()
    {
        var mediaFrameSourceGroupList = await MediaFrameSourceGroup.FindAllAsync();
        var mediaFrameSourceGroup = mediaFrameSourceGroupList[0];
        var mediaFrameSourceInfo = mediaFrameSourceGroup.SourceInfos[0];
        var mediaCapture = new MediaCapture();
        var settings = new MediaCaptureInitializationSettings()
        {
            SourceGroup = mediaFrameSourceGroup,
            SharingMode = MediaCaptureSharingMode.SharedReadOnly,
            StreamingCaptureMode = StreamingCaptureMode.Video,
            MemoryPreference = MediaCaptureMemoryPreference.Cpu,
        };
        try
        {
            await mediaCapture.InitializeAsync(settings);
            var mediaFrameSource = mediaCapture.FrameSources[mediaFrameSourceInfo.Id];
            var mediaframereader = await mediaCapture.CreateFrameReaderAsync(mediaFrameSource, mediaFrameSource.CurrentFormat.Subtype);
            mediaframereader.FrameArrived += FrameArrived;
            await mediaframereader.StartAsync();
        }
        catch (Exception e)
        {
            UnityEngine.WSA.Application.InvokeOnAppThread(() => { Debug.Log(e); }, true);
        }
    }
    private void FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
    {
        var mediaframereference = sender.TryAcquireLatestFrame();
        if (mediaframereference != null)
        {
            var videomediaframe = mediaframereference?.VideoMediaFrame;
            var softwarebitmap = videomediaframe?.SoftwareBitmap;
            if (softwarebitmap != null)
            {
                softwarebitmap = SoftwareBitmap.Convert(softwarebitmap, BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
                int w = softwarebitmap.PixelWidth;
                int h = softwarebitmap.PixelHeight;
                if (bytes==null)
                {
                    bytes = new byte[w * h * 4];
                    savebytes = new byte[w * h / 2 * 4];
                }
                softwarebitmap.CopyToBuffer(bytes.AsBuffer());
                softwarebitmap.Dispose();
                for (int i = 0; i < bytes.Length / 4; ++i)
                {
                    byte b = bytes[i * 4];
                    bytes[i * 4 + 0] = 0;
                    bytes[i * 4 + 1] = 0;
                    bytes[i * 4 + 2] = 0;
                    bytes[i * 4 + 3] = 0;
                    if (b == 0)
                    {
                        bytes[i * 4 + 0] = 255;
                        bytes[i * 4 + 1] = 255;
                        bytes[i * 4 + 2] = 255;
                        bytes[i * 4 + 3] = 255;
                    }
                    if (b == 1)
                    {
                        bytes[i * 4 + 0] = 255;
                        bytes[i * 4 + 1] = 255;
                        bytes[i * 4 + 2] = 255;
                        bytes[i * 4 + 3] = 255;
                    }
                }
                var buf = new byte[bytes.Length];
                for (int i = 0; i < h; i++)
                {
                    for (int j = 0; j < w; j++)
                    {
                        buf[(w * (h - 1 - i) + j) * 4 + 0] = bytes[(w * i + j) * 4 + 0];
                        buf[(w * (h - 1 - i) + j) * 4 + 1] = bytes[(w * i + j) * 4 + 1];
                        buf[(w * (h - 1 - i) + j) * 4 + 2] = bytes[(w * i + j) * 4 + 2];
                        buf[(w * (h - 1 - i) + j) * 4 + 3] = bytes[(w * i + j) * 4 + 3];
                    }
                }
                for (int i = 0; i < savebytes.Length; i++)
                {
                    savebytes[i] = buf[buf.Length / 2 + i];
                }
                UnityEngine.WSA.Application.InvokeOnAppThread(() => {
                    if (tex == null)
                    {
                        tex = new Texture2D(w, h/2, TextureFormat.RGBA32, false);
                        GetComponent<Renderer>().material.mainTexture = tex;
                    }
                    tex.LoadRawTextureData(savebytes);
                    tex.Apply();
                }, true);
            }
            mediaframereference.Dispose();
        }
    }
 | 
 
InitSensor関数でDepthカメラの設定を行い, FrameArrived関数にてDepthデータの取得と画像変換を行っています.
その後,取得したDepthデータを画像として保存します.
今回は以下のようにポーズを左右の手に分けてそれぞれデータを取得しています.
またHoloLensを首にかけて使用するため画像の下半分に体が映りこんでしまいます.
そのため画像の上半分のみを保存するようにしています.
- 人差し指を立てた状態
- 親指を立てた状態
- 人差し指と親指を立てた状態
- 上記以外
取得した画像データは機械学習のためフォルダ毎に分けて整理しておきます.
HoloLensで保存した画像データをフォルダ分けするまでの手順は以下の記事でも紹介しています.
HoloLensとWindowsMLとCustomVisionを使って手書き認識をしてみる(データ収集編)
機械学習
HoloLensから取得した画像データを整理,分類してCustomvisionによる機械学習を行います.
プロジェクトを作成してフォルダごとに画像をアップロードし機械学習を行います.
学習後mlmodel形式でエクスポートしパソコンに保存します.
保存後にmlmoelからonnx形式に変換します.
変換にはpythonを利用するのでBash on WindowsからUbuntuと変換ツールの構築をおこなってください.
CustomVisionの使い方とonnx形式への変換方法は以下の記事で紹介しています.
HoloLensとWindowsMLとCustomVisionを使って手書き認識をしてみる(学習データ利用編)
ハンドジェスチャコントローラー作成
HoloLens側実装
CustomVisionから作成したonnxファイルを使ってHololensのWindowsMLでジェスチャーの判別を行います.
入力画像は学習データ取得時と同様にResearch modeの短距離Depthセンサを利用します.
ジェスチャー判別後はネットワーク上のパソコンへスライド操作用のコマンドを送信します.(今回はUDPで送っています)
HololensでのWindowsMLの使い方は以下の記事にて紹介しています.
HoloLensとWindowsMLとCustomVisionを使って手書き認識をしてみる(学習データ利用編)
PC側実装
HoloLensから送信されたスライド操作用コマンドを受信してパソコン上のスライドを操作します.
今回は簡略化のため以下のような設定になっています.
- コマンドは矢印キーの入力としてパソコンを操作する.
- とりあえずスライドを進めるために「→」キーのみ操作するようにする.
まとめ
- HoloLensRS4でWindowsMLとResearch modeを利用したアプリを作った.
- 学習データと実行環境が十分でなかったので誤動作が多かった.