NativeArray<T>を高速にシリアライズ/デシリアライズ

高速化TIPSです。以前、ARKitの顔の頂点のデータをUnityEditorに送るプログラムを書きました。

mobile.twitter.com この様なことができます。

UnityEditorには毎フレーム以下のデータを送っています。

public NativeArray<Vector3> vertices; 
public NativeArray<Vector3> normals; 
public NativeArray<int> indices; 
public NativeArray<Vector2> uvs;

頂点の数が多くシリアライズ、デシリアライズがパフォーマンスのネックになっていました。直接ポインタをゴニョゴニョすると行けるのではないかと調べているとNativeSliceにSliceConvertという型を変換できるメソッドがあることに気づきました。これを使うと以下の様なことができます。

gist.github.com

使い方です。

NativeArray<Vector3> vertices;

// シリアライズ
byte[] verticesBytes = vertices.ToRawBytes();

// デシリアライズ
NativeArray<Vector3> vertices2 = NativeArrayExtension.FromRawBytes<Vector3>(verticesBytes, Allocator.Temp);

Tensorflow LiteをUnityで動かす

前回のBarracudaに続きTensorflow LiteをUnityで動かしてみました。

Unity上で学習するのはML-Agentで十分で。私の用途では、軽量、複数のプラットフォーム(iOS, Android, macOS, Windows)で動作可能なUnityのプラグインが必要でした。Tensorflow Liteはコア部分はシンプルなC言語APIで用途にあっていそうです。

Tensorflow Lite サイトより引用

Tensorflowリポジトリの奥深くに、Unity Pluginが隠されています。 tensorflow/tensorflow/lite/experimental/examples/unity/TensorFlowLitePlugin at master · tensorflow/tensorflow · GitHub

macOS, iOSで動作確認をしてみました。 以下、MNISTとSSDの動作サンプルです。

Barracudaとは別のMNISTモデルをつかったので、純粋な比較ではないのですが、Barracudaよりも高速でした。

GitHubリポジトリはこちら。 GitHub - asus4/tf-lite-unity-sample

Tensorflow Lite のUnity プラグインは非常にシンプルで。*.tfliteモデルのInputs/Outputsの型を確認するAPIがないのですが、NetronというElectron製アプリでいろいろなモデルを読み込み確認できて便利でした。

f:id:asus4:20191118183116p:plain
netron

github.com

またTensorflow LiteにはGPUデリゲートという機能があり、iOSはMetal, AndroidOpenGL ES 3.1 で計算することができます。macOSでもMetalで行けるはずなのですが、ソースコード中にUIKitに依存する場所があり、ちょっと手直し中です。できたらお知らせします。

WindowsGPU Delegate対応Unity Pluginはt-takasakaさんが作っていました。

github.com

追記

その後、macOSでもGPU Delegate動かすことに成功しました。

Add Build Setting TFLite Metal Delegate for macOS bundle · asus4/tensorflow@d0b45bd · GitHub

Unity製MLライブラリBarracuda

Unity上でMachine Learningについてリサーチしています。Unity上でMLというとUnity社が開発しているML-Agentsが最初に出てくると思います。Tensorflowとの連携ができ強化学習などのサンプルが多く見れます。

GitHub - Unity-Technologies/ml-agents: Unity Machine Learning Agents Toolkit

そのml-agentsの子プロジェクトとして最近Barracudaというものをハッケンしました。

ml-agents/Barracuda.md at master · Unity-Technologies/ml-agents · GitHub

説明を読むに、TensorFlowの学習済ファイル (*.pb)やONNXのモデルをBarracudaで読み込んで、Unityで動かしているようです。CPUもサポートしていますが、GPUは全部ComputeShaderで頑張っている模様。実際にMNISTを動作させるサンプルを作ってみました。しかしいまいちパフォーマンスがでず…。ひとまずソースコード乗っけます。

ソースコードはこちら↓

GitHub - asus4/barracuda-samples

MNIST.pbモデルをBarracudaのnnフォーマットに変換するGoogle Colab

colab.research.google.com

Alexa-Voice-Service AVS対応スマートスピーカーを作ったときのこと

去年になりますが、Alexa-Voice-Service (以下AVS)対応スマートスピーカーのプロトタイプを作ったのでメイキングです。Alexa-Skills-Kit(ASK)の記事は多く出ていますが、AVSの情報はASKにくらべて少ないので、なにか参考になる部分があれば幸いです。

ハードウェア選定

ある程度のノイズが想定される場所での展示だったため、マイクアレイでの音声方向検出は必須と考え、対応ボードを探しました。マイクアレイと指向性関係はロボスタの記事がまとまっています。(すごい人は自分でつくりますが、スキルも時間も足りないため、ボードを購入します)

robotstart.info

またハードウェアの形に制限がない場合は、AVSのサイトで動作確認済のDevKitを購入するのが良さそうです。

developer.amazon.com

今回の要件では筐体の大きさの制限が厳しく、Raspberry Piでは大きすぎたので、Raspberry Pi Zero x Seeed Studio社のReSpeaker 2 Mics Pi-HATを選定してみました。 2chマイクではありますが、1chよりはましでしょう…。

ソフトウェア選定

ソフトウェア選定にあたりこの記事を参考にしました。

Raspberry PiにAlexaを召喚する3つの方法 - Qiita

今やるならavs-device-sdk一本とのことで、こちらから取り組みはじめました。

github.com

実際に手を付けてみると、avs-device-sdkc++制でソースの量も多く、Raspberry Pi 3でのコンパイルすると1時間くらい。また、Raspberry Pi Zeroの動作は公式にはサポートされていないようでした。サンプルも複雑で、製品組み込みには使えても、Raspberry Pi Zero上での短い時間で細かくイテレーションを繰り返したい、というプロトタイプ用途としては向いていないと判断しました。

いくつかの選択肢を試した中、Seeed Studioの公開しているAVSライブラリはpython製でソースコードもシンプル。RaspberryPi Zeroでも動作したので、こちらを使用しました。(GPLライセンスなので注意)言語設定切り替えに対応していない、access_token取得方法が古いなどいくつかの問題がありましたが、ソースコードが綺麗だったため、修正は比較的簡単にできました。→この辺のやり取り。開発は止まっているようなので、今から始めるとしたら別途選択肢を探すほうがいいかもしれません。

github.com

Wake Word検出ライブラリ

Wake Wordは。Alexaなら「アレクサ」,Google Homeなら「オーケー、グーグル」という、起動時の決り文句です。変更もできます。常時サーバーに音声を送るわけではなく,Wake Wordはローカルで検出します。

  • Sensory: aws-device-sdkデフォルト。精度いまいち
  • Snowboy:精度いい感じ。商用の場合はお金かかるのでプロトタイプとして使うのにオーバースペックかも…。
  • Porcupine:精度いい感じ。カスタムのWake Word登録は商用できないが、デフォルトのモデルは使えるらしい。

以上の3ライブラリから検討し、ライセンスの関係からPorcupineを使っています。

参考にした情報

上記以外に参考にした記事です。

RaspberryPiでGPIO(I2S)を使ってマイクから録音する - キノコの自省録 Snowboy Hotword DetectionをRaspberry Piで動かす - Qiita Pi Zero Wはモバイルバッテリーでどのくらいの時間動作するか | 日記というほどでも

スピーカーづくり

まずはFusion360でスイッチサイエンスなど簡単に購入できるパーツを配置して、大きさを検討します。

あとで請求書つくるときと、自分で今回もこんなに買い物をしてしまった…と見返すためにBOMとまでは行きませんが、パーツリストもちゃんと残します。

Fusion360で最終的なパーツをちまちま作ってレイアウト。パーツを作るのをさぼった部分はなぜか組立工程で問題がでるので…。

青色の部分は、DMM 3Dプリントでナイロン素材で発注しています。この図にはありませんが、外側にプロダクトデザイナーの方が作った筐体がかぶさります。

LED基盤

光のアニメーションを凝りたいとのオーダーだったので、LEDを贅沢に配置しています。ReSpeakerには、GroveタイプのI2C端子がついていたので、それにつなげる想定の基盤のKiCadつくっていきます。回路設計は素人ですが…こんな感じです。

Gerberは紙にレイヤー毎に印刷してチェックすると何故かミスが見つかります。アナログチェックは重要。

基盤はPCBGoGoで発注。一週間もかからずに届きました。私の用途ではクオリティも十分です。

ちっちゃい…。中国でiPhone修理する人とかすごすぎる…。

Imgur

全部実装して、動作確認。うごいた!

シュミレーター

LEDのアニメーションは、普段よく使っているUnityでシュミレーターを作成してつくりました。

完成

Imgur

組み立ててこんな感じ。最終的には外側にプロダクトデザイナーの方が作った筐体がハマります。

以上こんなこともやってます。というメイキングでした。

Unity AR FoundationをEditor上でシミュレーションする方法

iOS13が公開されたことで、beta版をインストールしなくてもUnityでARKit3の新規のHuman Segmentaionなどが使えるようになりました。対応端末はiPhone X以降です。

実験としてこのようなアプリを作ってみました。

が、開発中、実機で確認するのがとても面倒でした。ARKitはiPhone搭載のカメラのセンサを使うため、実機じゃないと動きません。毎回Unityからビルド→Xcodeプロジェクトに書き出し→実機で確認。のステップを踏まなくてはいけません。毎回実機確認に5分くらいかかります。ARKit2まではARKit Remote for Unityで、Unity Editorとつなげてデバックができました。とところが、ARKit3の機能に対応しら新しいUnityライブラリである、AR Foundation (動作時点ではver 3.0.0-preview.3)ではリモートデバックができないようです。公式スレッドによれば開発中だけど難航しているようです。

なにかを作るときにはツールから作るのをモットーにしているので、Unity Editorにリモートで送信できるツールを作ってみました。現在はHuman Segmentation関係の機能しか用意していませんが、普通に動きそうです。

ソースコードはこちら。

github.com

仕組みはシンプルで、iPhoneからARに必要な情報を送ります。

  • NDI
    • RGBカメラ(YCbCr色空間) 映像
    • Depth 映像
    • Human Segmentation 映像
  • WebSocket
    • カメラmatrixとかの情報

NDIはWi-Fi経由だと遅いのですが、USB経由で接続すると0.3秒くらいの遅延で収まりました。

Hack C#でprivateプロパティだけなstructを作る

シミュレータを作るときに困った点を一つ。ARFoundationはコア部分はネイティブC++プラグイン。そちらで生成したstructをやり取りしているので、C#から見えるstructはプロパティが全部プライベートでした。

[StructLayout(LayoutKind.Sequential)]
public struct XRCameraFrame
{
    public long timestampNs
    {
        get { return m_TimestampNs; }
    }
    long m_TimestampNs;

    public Matrix4x4 projectionMatrix
    {
        get { return m_ProjectionMatrix; }
    }
    Matrix4x4 m_ProjectionMatrix;
    
    ...
}

↑このようなクラス構造。Unityでもunsafe{}を使えばこのようなstructでも作れますが、ライブラリ化を考えるとなるべくunsafe以外の方法でstructを作ったほうが良いため、以下の様な共用体をつくることで解決しました。c#で共用体って作れるんですね…。

[StructLayout(LayoutKind.Sequential)]
public struct MyCameraFrame
{
    public long timestampNs;
    public Matrix4x4 projectionMatrix;
    ...
}

[StructLayout(LayoutKind.Explicit)]
public struct CameraFrameUnion
{
    [FieldOffset(0)] public MyCameraFrame a;
    [FieldOffset(0)] public XRCameraFrame b;
}

void main()
{
    var union = new CameraFrameUnion()
    {
         a = new MyCameraFrame()
         {
               // これはいじれる!
               timestampeNS = 1000,
         }
    }
    
    // 共用体なので、aを作るとbもできる!
    XRCameraFrame b = union.b;
}