Yusuke Saitoの雑記

センサー類, Python, Unity(C#), Unreal Engine, 機械学習とかについてのメモ

UEのMVVMについて触ってみる

はじめに

UE記事一発目、UEでのMVVMについて触れていこうかと思います。 過去、お仕事でUnityにおいてC#でMVPパターンにて設計を行ったことがあるのですが、特に継続性のあるプロジェクトや複数デバイス/プラットフォームでの実装などを見越している場合では、かなりの効果を発揮すると実感しています。 今回、MVPではなくMVVMパターンに焦点を当てていきますが、UEでの設計の勉強の一つとして書いていきます。

参考

今回、alweiさんのMVVMの記事を前提とします。 - UE5 UMG ViewModelを利用してBPオンリーのMVVMをしてみる https://unrealengine.hatenablog.com/entry/2023/09/28/233801

MVVMについて

動機と方針

まず記事を書こうと思った動機としては、View-ViewModel側やModel側にバリエーションを持たせたとき、実際にどう作っていくのがよいかと疑問に思ったためです。

alweiさんの参考記事の場合、最終的には図のようにゲージが減少していくLevelが作成できます。

この場合、View, ViewModelおよびModelの関係性の概念図としては下図のようになります。

この応用として、まずModel層側のもう一つのパターンとして、ゲージを減少させるのではなく増加させるパターンを作ります。さらに、View層のもう一つのパターンとして、円形のゲージを扱うパターンを作成します。

すなわち、今回行いたいことを先ほどの概念図に追記すると、下図のようになります。

Model側:ゲージを増加させるパターンを作る

  • 今回、Model側でのパターン作成はシンプルなものになります。まず、参考記事におけるThirdPersonCharactorをRenameし、「BP_ReduceLifeThirdPersonCharacter」とします。次に、「BP_ReduceLifeThirdPersonCharacter」をCopyし、「BP_IncreaseLifeThirdPersonCharacter」と名前を付けます。

  • BP_ReduceLifeThirdPersonCharacterにおけるEventTick内での処理は、図のようになっているはずです。

  • なので、増加させるパターンの場合、図のようにノードを組み替えれば「BP_IncreaseLifeThirdPersonCharacter」が作成できたことになります。

  • 最後に、ContentsBrowserのModel層のファイルは図の二つになっています。これで、Model層での準備は完了です。

View側:円形ゲージのパターンを作る

次に、View側で円形ゲージのパターンを作ります。円形ゲージ用の「M_CircleGauge」を作成します。(下記記事を参考にしています。)

また、「M_CircleGauge」のMaterialGraphはこのようになります。

そして、「M_CircleGauge」を親としたMaterialInstanceである「M_CircleGauge_Inst」を作成しておきます。

参考記事におけるWBP_LifeGaugeをRenameし、「WBP_LifeGauge_Straight」とします。次に、新しくWidgetBlueprintを作成し、「WBP_LifeGauge_Circle」と名前を付けます。そして、下図の状態になるように「WBP_LifeGauge_Circle」の状態を設定していきます。

  • Canvas->SizeBox->ImageとなるようにHIerarchyを設定していきます。Imgaeを配置したのち、名前を「Image_Life」をしておきます。
  • Appearanceビューにおいて、MaterialInstanceがBrush内のImageに割り当てられるようにします。 「WBP_LifeGauge_Circle」のEventGraphを図のようにし、PreConstructでMaterialDynamicInstanceが割り当てられるようにします。

最後に、ViewBindeingsビューにて、図のようにBP_ViewModelのSetLifePercent関数を、Image_LifeのSetOpacityにBindします。最終的に、ContentsBrowserのView層のファイルは図のようにになっています。これで、View層での準備は完了です。

まず動かしてみる

では、動かしてみましょう。まず、NewLevelからBasicを選択し、新しいLevel「SampleMap」を作成します。そして、原点位置にPlayerStartを配置します。

次に、このLevelのLevelBlueprintを編集していきます。編集結果として、図のようなLevelBluePrintを組んでください。

また、WorldSettingsビューにて、DesualtPawnClassに対し、LevelBlueprintでの実装と同じCharactorクラスを割り当ててください(この場合、「BP_ReduceLifeThirdPersonCharacter」を割り当ててください)

今回はLife量は減少、ゲージは円形、といった形になります。実行すると、図のような動きになるはずです。

Model, Viewの切り替えについて

ここで少しLevelBlueprintについて解説してきます。まず、Construct BPViewModelノードにて、BP_ViewModelのインスタンスを作成します。

次に、Model層のCharactorクラスの取得と、ViewModelへのアタッチを行います。この際、下記図のように減少させる場合と増加させる場合の両パターンについてノードを組んでおき、切り替えられるようにしておきます。

そして、WidgetBlueprintのインスタンスを作成することで、これをBP_ViewModelに紐づくViewとします。 ここで、下図のように、WidgetBlueprintを「WBP_LifeGauge_Straight」か「WBP_LifeGauge_Circle」かを切り替えることで、円形ゲージか直線ゲージかを切り替えられる形になります。

一つ別の組み合わせとして、Life量が増加していき、直線ゲージの場合、動かすと下図のようになります。

また、そのときのLelevBluePrintは下記のようになります。

おわりに

以上で、MVVMについて、UMG ViewModelによるViewとModelのパターンの拡張と切り替えを行ってみました。 この先としては、Charactor以外のModel層の例や、ViewやModelにInterfaceを使う形でDI (Dependency Injection)をする形について模索するところかなと思いますが、まぁ気が向いたらってことになりそうです。

また、サンプルのプロジェクトをgithubに公開しています。 github.com

2023年振り返り

ホロのPさん(Yusuke Saito Ph.D)です。 今年も振り返りを書きます。

仕事面

今年は開発面だけでなく、より営業/PM面に踏み込むということをしてきました。 その中で、社外とのやり取りだけでなく、社内的にもどうしていくか、といった面に対して、自分の能力の課題が見つかりました。

また、今後はどう効率的に物事をこなしていくか(もちろん質は下げずに)、そこがテーマになってくるような気がしています。

研究面

幸せなことに、わりと研究よりのお仕事に携わることが多く、以前より自分の興味領域でお仕事をさせてもらっています。 ただアカデミック的な活動はどうかというと、あまりできていない面があります。こちらの時間も作っていかないとというのが正直な気持ちです。

私生活

家族内に大きなことが発覚し、色々と大変ではありますが、なんとかやっていかなければという所存です。

おわりに

まぁ今年もなんとか生きていきますw

2022年振り返り

ホロのPさん(Yusuke Saito Ph.D)です。 そういえばブログ全然更新していないなー 今年はダイエット頑張りました。仕事も頑張りました。ほい。

というわけで、もう少し具体的に振り返ろうかなと思います。

仕事面

エンジニアとして

今年は開発面において、コーディングもやりますが、開発管理であったりお客様と話すことが多くなりました。 ある意味で裁量も増えている認識です。 あとは技術の範囲も多岐にわたっています。GNSS系だったりNeRFなどの機械学習だったり。 広く浅く、みたいになってますが、深く突っ込んでいかないととも思いつつ。

あとUnrealEngine始めました。んー、ちゃんと開発面でのセオリーだったりを学ぶ必要があるなぁと感じています。 例えばどういったときにBPで書いて、とどういったときにC++で書いて、という分け方。 BPよくわからんのでC++で書いていましたが、この辺結構明確に決めて開発しないとーとは思います。

リサーチャとして

IDW'22というWorkshopで発表してきました。現地開催(福岡)ということで、コロナ禍で英語力も上げないとなー、という悩みも出てきました。 どんどん学会発表も多くしていきたいし、そのための研究としてアウトプットを出していきたい所存です。

私生活

150kg -> 120kg程までダイエットできました!ダイエットというか、筋トレ+食事の見直し、そして徹底。 筋トレが楽しくなってきたので、来年も続けて一旦90kgくらいまでを目標として続けたいと思います。

あとがき

来年も頑張っていきますw

2021年振り返り

前置き

ホロのPさん(Yusuke Saito Ph.D)です。 今年も一年、徐々にアウトプットを出せているような気がしています。良いこと。 自分の時間も作れています。良いこと。 ダイエットは諦めています。悪いことw

というわけで、もう少し具体的に振り返ろうかなと思います。

仕事面

今年はエンジニアとして、またR&Dでリサーチャとして、兼任で動きました。

エンジニアとして

エンジニアとして一番大きかったのは、とあるプロジェクトの見直し(設計、開発、自動テストの記述)を行い、 また関連アプリケーション(PC)を追加でリリースしたことです。特に設計面、Model層をちゃんと切り分けて共通化・モジュール化して使いまわせるような形で作り、HoloLensアプリでも、PCアプリでもほぼ同じコードで動くようにできたのはよかったかな、と感じています。

ただ反省面もあって、引継プロジェクトの負債部分を甘く見ていて、結果的に作業量の見積もり精度が低かったのは反省点ですね。

リサーチャとして

一年を通して、主に空間位置合わせの研究をして、たまに機械学習の記事を書いて、といったような形でしょうか。

空間位置合わせの研究として、アウトプットを出せたのはよかったかなと思います。 ただ、欲を言えば、現場で使えるレベルまで昇華させていきたい、というのが本音であり、今後の目標でもあります。 blog.hololab.co.jp

また、上記内容をVirtual学会で発表できたことも良かったこととして挙げられます。 sites.google.com Virtual空間上で学会を行う、というとても興味深い試みをされており、またそこで発表できたのはとても貴重な経験になりました。今後も学術面でのアウトプットも出していきたいなと考えています。

あとがき

一年を通して、徐々にではありますが、アウトプットを出せているのは良いことだと思います。来年は、さらにアウトプットに質と量を上げていく、ただあせらずに、というスタンスでやっていこうかなと考えています。 駄文ではありますが、ここまで読んでいただきありがとうございました。 来年もよろしくお願いします。

HoloLens2でWindows.AI.MachineLearningを使って手の動作認識を行う

はじめに

昨年7月にHoloLensMeetupで発表した手の動作認識では、機械学習のライブラリとしてOpenCVForUnityを用いていました。 画像処理の機能が使える反面、OpenCVForuUnityを使用する場合だとライセンス料がかかってしまう、という欠点があります。

そこで、HoloLens2でWindows.AI.MachineLearningを使って手の動作認識をしてみました。 UWP標準の機能を用いることで、特別にライセンス料などのコストをかけることがなく機械学習を用いることができます。

youtu.be

コードはGithubに上げてあります。 ポイントをいくつか挙げて解説していきます。

Github github.com

Windows.AI.MachineLearningとは?

https://docs.microsoft.com/ja-jp/windows/ai/

Microsoft公式のWindowsアプリに機械学習を実装を行うことができます。UWPアプリにも対応しています。

動作認識モデル(onnxファイル)の準備

モデルファイルは下記を参考にしてください stopengin0012.hatenablog.com

HoloLens上で動作認識モデル(onnxファイル)をロードする

Capabilityの設定

アップしたコード上では、HoloLens実機のピクチャフォルダ以下を参照するようにしています。

        private string dirName = "HandGestureModel";
        private string fileName = "handgesture_v001.onnx";
        private async Task LoadModelAsync()
        {
#if WINDOWS_UWP
            var dir = await KnownFolders.PicturesLibrary.GetFolderAsync(dirName);
            var file = await dir.GetFileAsync(fileName);
            // モデルのロード
            handGestureModelGen = await HandGestureModel.CreateFromStreamAsync(file as IRandomAccessStreamReference);
            if (handGestureModelGen == null)
            {
                Debug.LogError("モデルデータの読み込みに失敗しました. ");
            }
#endif
        }

したがって、下記図に示すように、ビルド時にCapabilityを設定してあげる必要があります。

f:id:stopengin0012:20210210031731p:plain

.onnxファイルを有効にする

また、(結構忘れそうな事項として)onnx拡張子のファイルへの参照を有効にします。 下記の図のように、サポートされるファイルの種類に「.onnx」を指定してください。

f:id:stopengin0012:20210210031839p:plain

ロードしたモデルを利用する

基本的には、先日の記事で実行確認した時と同じです。 入力を合わせることに注意してください。

... (using参照)
#if WINDOWS_UWP
using Windows.AI.MachineLearning;
using Windows.Storage;
using Windows.Storage.Streams;
#endif
...

        private async Task<string> RecognizeGesture()
        {
#if WINDOWS_UWP
            ...

            float[] data = ReadToFloatArray(handJointController.handGestureData);
            handGestureInput.Input120 = TensorFloat.CreateFromArray(new long[] { 1, columns * frameCount }, data);

            //Evaluate the model
            var handGestureOutput = await handGestureModelGen.EvaluateAsync(handGestureInput);
            IList<string> stringList = handGestureOutput.label.GetAsVectorView().ToList();
            //Debug.Log($"判定された動作ラベル:{stringList[0]}"); //IL2CPP 確認用
            var label = stringList[0];
            return label;
#endif
        }

おわりに

HoloLens2でWindows.AI.MachineLearningを使って手の動作認識を実装しました。 その上で気をつけるポイントをいくつかまとめました。

追記

昨年7月のスライドです

www.slideshare.net

sklearnで作成したRansomForestモデルをONNXに変換してwinML(UWP)で利用する

はじめに

sklearnで作成したRansomForestモデルをONNXファイルに変換し、Windows Machine Learningで利用する際に気を付けることを書いていきます。

サンプルに、HoloLens2取得したHandGestureを分類するコードをGithubに挙げました。 github.com

環境(python)

  • python 3.6 (winmltoolsがpython3.7以降をサポートしていないため)
  • winmltools 1.5.1
  • skl2onnx 1.7.1 (whlファイルからインポートしてください)
  • その他 (pandas, sklearnなど)

環境(uwp)

1. sklearnで学習 -> ONNXファイルに変換する時

1-1. zipmapの削除

Windows Machine LearningのUWPアプリでONNXモデルを利用する際、どうやらモデルに型不明な経路(?)があると、ロードに失敗します。 f:id:stopengin0012:20210207141018p:plain

これを防ぐために、convert_sklearn関数でinitial_typesやfinal_typesを指定します。 ここでfinal_types指定の際、zipmap出力の型(map(string,tensor(float))の指定方法がわからず、方法を模索していたところ、zipmap変換をしない方法にたどり着きました。

options = {id(pipeline): {'zipmap': False}}

上記のoptionsをconvert_sklearnの引数に指定します。

from skl2onnx import convert_sklearn
from winmltools.convert.common.data_types import FloatTensorType, StringTensorType, Int64TensorType
options = {id(pipeline): {'zipmap': False}}
rf_onnx = convert_sklearn(pipeline,
                            target_opset=7,
                            options=options,
                            name = 'RandomForestClassifier',
                            initial_types=[('input', FloatTensorType([1, 120]))],
                            )

出力されたONNXモデルは以下のようになります。 f:id:stopengin0012:20210207141445p:plain

1-2. convert_sklearnはskl2onnxのものを使う

ONNXのバージョンに気をつける必要はありますが、とりあえずwinmltoolsのコンバータを使用せず、最新のskl2onnxを使うことができました。

from skl2onnx import convert_sklearn

2. Windows Machine Learning(UWP)で利用する時

2-1. 入力・出力をONNXと一致させる

当然といえば当然ですが、入力・出力の型を合わせる必要があります。skleanrからのコンバート時にinitial_typesを指定するなども、その一環として行っています。

...
# データ入力時
handGestureInput input = new handGestureInput();
input.Input120 = TensorFloat.CreateFromArray(new long[] { 1 , 120 }, feature);
...

public sealed class handGestureInput
{
    public TensorFloat Input120;
}

2-2. TensorStringの出力の仕方

TensorString型の取り出しには、一癖あるなぁと感じます。 取り出し方としては、TensorString.GetAsVectorView().ToList()で、string型のListに変換し、その要素を取り出します。

//Evaluate the model
var handGestureOutput = await handGestureModelGen.EvaluateAsync(input);
IList<string> stringList = handGestureOutput.label.GetAsVectorView().ToList();
Debug.WriteLine($"判定された動作ラベル:{stringList[0]}");

終わりに

sklearnで作成したRansomForestモデルをONNXファイルに変換し、Windows Machine Learningで利用する際に気を付けることを書きました。 (HoloLens2上で実行できたらいいな....)

HoloLens 2のResearchModeのStreamRecorderを試す

HoloLens 2のResearchMode とは?

簡単にいうと、HoloLens2に内蔵されているセンサー類のストリームデータを可視化・保存できるモードのことです。 もちろん、研究用途のためなので、動作保証とかないです。(なので、お仕事で使う場合は気をつけてください) また、Preview版なので、変更される可能性が高いです。 詳しくは、下記ドキュメントをご覧になってください。 docs.microsoft.com

HoloLens 2のOSをInsiderPreviewにする

ドキュメント通りやれば大丈夫なはず。 OSのversionが19041.1356 以降となってれば使えるはずです。 また、デバイスポータルでReserchmodeを有効にすることを忘れずに。

ResearchModeのサンプルコードを試す

公式にサンプルコードが公開されています. https://github.com/microsoft/HoloLens2ForCV

本記事では、Samples/StreamRecorder以下を参照します。 ここには、記録するためのプロジェクト(C++)と、保存したデータをロードするコード類(Python)が入っています - StreamRecorderApp : 記録するためのプロジェクト(C++) - StreamRecorderConverter : 保存したデータをロードするコード類(Python)

1. StreamRecorderを試す

まず、ソリューションファイルStreamRecorderApp/StreamRecorder.sln を開きます。

1-1. StreamRecorderのコードを変更する

(20200912時点)現在のコードだと、RGBカメラの情報やEyeTrackingの情報を記録するモードになっていません。 これを有効にするために、以下の手順を踏んでください。

AppMain.cppファイルのコードを少し変えます。 //Set streams to capture となっている部分に以下を追記(20200912時点)してください。

std::vector<ResearchModeSensorType> AppMain::kEnabledRMStreamTypes = { ResearchModeSensorType::DEPTH_LONG_THROW };
std::vector<StreamTypes> AppMain::kEnabledStreamTypes = { StreamTypes::PV , StreamTypes::EYE };

1-2. 実行

Debugビルド、ARM64で実行すると、Start・Stopボタンが表示されます。 Start押し、適当な時間の後Stopを押して記録が完了するのを待ちます。 記録が完了すると、以下の図ようにHandの位置などが可視化された状態になります。

f:id:stopengin0012:20200911175840j:plain

2.StreamRecorderConverterを試す

2-1.Anaconda3, opencv-python. open3dのインストール

仮想環境を作成し、python3, numpy, opencv-python. open3dをインストールしてください。 この辺は省略します。

2-2.実機から保存したデータをタウンロードする

  • <output_folder> : 出力先のフォルダを指定してください
  • user : デバイスポータルに入る際に入力するuserを指定してください
  • password : デバイスポータルに入る際に入力するpasswordを指定してください
python StreamRecorderConverter/recorder_console.py --workspace_path <output_folder>  --dev_portal_username <user> --dev_portal_password <password>

すると、下記のようなコンソール画面になります。download_allを入力してEnterを押すと、実機からダウンロードされます。

f:id:stopengin0012:20200912104924p:plain

データには、HandやGazeのデータがCSV形式になっていたり、またCameraパラメータなどもあるようです。

2-3.データを読み込むコードを動かす

下記コマンドで、ダウンロードしたデータを、いくつかのスクリプトで読み込める形にできます。 - <path_to_capture_folder> : 実機からダウンロードしたフォルダのパスを指定してください("2020-09-11-175712"のように日付の形式になっているフォルダです)

python process_all.py --recording_path <path_to_capture_folder>

色付きPointCloudの生成

python convert_images.py --recording_path <path_to_capture_folder>

f:id:stopengin0012:20200912103754p:plain

Hand・Eye追跡画像の生成

python project_hand_eye_to_pv.py --recording_path <path_to_capture_folder>

青がGaze, 緑がHandかな??

f:id:stopengin0012:20200912104350p:plain

空間Meshの生成

  • <path_to_pinhole_projected_camera> : <path_to_capture_folder>以下にある、pinhole_projectionフォルダを指定してください
python tsdf-integration.py --pinhole_path <path_to_pinhole_projected_camera>

tsdf-mesh.plyが作成されます.

f:id:stopengin0012:20200912104206p:plain