MediaPipeのpre/post-processメモ

GoogleのMediaPipe良いですね。コア部分はTensorFlowLiteでできていて、Pre/Postプロセス部分をUnityで書き直すことで移植できました。まだ勉強中ですがメモ。

github.com

↓ こんな感じでUnity上で動作してます。

Face Tracking and Blaze Face

Blaze Pose

Hand Tracking

全体

MediaPipeのBlaze Face, Balze Pose, Hand Trackingは細かい違いはあれど、かなり似ていて。大きく分けて2つTensorFlow Liteのモデルが動いています。

  1. SSD Detection: オブジェクトの矩形エリアを推定します。
  2. Landmark Detection: オブジェクトの画像からランドマーク(骨格など)を推定します。

MediaPipeはWebで処理を見ることができます。以下はBlaze Faceの例です。Max/MSPのノードのような感じで全体を俯瞰できるので、まずここから概要を掴みました。

f:id:asus4:20200904221738p:plain
MediaPipe Web Capture
https://viz.mediapipe.dev/demo/face_detection

その他のモデルもこちらから見れます。

それぞれのTFLiteについて見ていきます。

1. SSD Detection

SSD(Single Shot Detetor)でオブジェクトの矩形エリアを返します。

Pre Process

とくに難しいことはなく、入力画像のサイズにトリムしてfloatの配列に変換するだけです。

がPC上のWebカメラでやるときは簡単ですが、スマートフォンではWebcamTextureは回転とフリップしてるんですよね…。とても面倒くさいです。私の場合は入力画像とWebカメラの回転の正規化を一度にするシェーダーを用意しました。

Post Process

とりあえず重なる部分も含めて多めに返して、CPU側でNon Max Suppressionというアルゴリズムを使って精度を上げています。Courseraの動画で概要を理解しました。最近他のライブラリにも追加されてるの見ました。

通常のSSDモデルは推定された矩形エリアを返すだけですが。これはキーポイントと呼ばれる特徴点も返します。これは次のプロセスで使います。

2. Landmark Detection

Pre Process

カメラ画像から対象のオブジェクト部分をトリムします。トリムするときに、キーポイントの情報をもとに回転を直します。これは頭良いなーと。回転を直してからLandmarkを検出するモデルに画像を入力することで精度が上がるとのこと。当初バグで、間違った回転をした画像をLandmark Detectionのモデルに入力したのですが、精度が全然違いました。

youtu.be

緑色が矩形エリアとキーポイント。赤が回転とクロップする範囲。右上のちっこい手が実際のLandmark Detectionへの入力画像です。手が横向きになっても入力画像は上向きなのがわかると思います。

Post Process

FaceMeshとHand Landmarkは出力されたランドマークをそのまま表示しています。BlazePoseはすこし値が暴れるのかRelative Velocity Filterというものを使っています。移動が少ないときは強めのローパスフィルタ。移動が多いときはフィルタを弱めるというもののようです。

返されるランドマークは、入力画像に対しての値です。それを、2段階の画像入力で使ったMatrix4x4の逆行列をかけて、Unityの座標に戻しています。この変換も結構ややこしかったです。

まとめ

MediaPipeの処理はかなり似ていて、一度作るとHandTracking, BlazePose, BlazeFaceも大体同じプログラムが適用できました。機械学習の勉強のために初めてTensorFlow LiteのUnity移植ですが、共通する処理が結構出てきて。自分のためになっている気がします。

参考

TensorFlow Lite界隈は日本人が多く活躍していますね。@PINTO03091さん、@terrykyさんのTwitter, GitHubをよく参考にさせていただいております。

NeumorphismなUIをUnity uGUI上につくる

DrribleやPinterestでよく見るデザイントレンドになっているNeumorphism。 とてもおしゃれに見えますが、みんなMaterial Designに飽きてきて、目新しさ目当てで流行ってるだけちゃうのという意見もちらほら目にします。私も懐疑派でした。

一方でこんな意見も。

note.com

なるほど。今後xRなアプリケーションも同時に開発するようになったときには、ライティングや立体感が重要も再び重要になってくるのかもと。 まだ答えは出ていませんが、、。

静的にfigmaPhotoshopのエフェクトで作った立体感ではなく、動的にユーザーの動作に合わせて自然に変化すると面白いんじゃないかと…。個人的な実験の意味も込めて以下のようなデモを作って見ました。

gif

UnityのシェーダーでNeumorphismを再現して。リアルタイムに影や凹凸を変化させられるようにしています。更に画像をかなり拡大してもエッジはきれいなまま。

Neumorphismを解析する

Unityで実装を説明する前にまずNeumorphismがどうやって作られているかFigmaで公開してくれているサンプルを分析してみました。こちらを参考にしました。

dribbble.com

一番シンプルそうなパネルをFigmaで見てみます。(CSSで描くのは簡単だ…。)

f:id:asus4:20200811220919p:plain
figma-ui

  • 光の方向は左上から右下に。
  • 全体にうっすらとグラデーション
  • 右下に暗い色のドロップシャドウ
  • 左上に明るい色のドロップシャドウ
  • 縁を細いベベルでグラデーション

ざっくりこんな感じでしょうか。これを参考にしながらUnity上でできる範囲で作っていきます。

SDFで拡大に強くきれいな線を描く

Unityで作るスマホゲームでUIをピクセルパーフェクトで作ってるゲームってあるんでしょか?@1x @2x @3xなテクスチャを用意してるゲーム。実際 iPhone SE(640x1136)からiPad Pro 12.9(2048x2732)までサポートするようなゲームで無理…ですよね。FullHDでデザインして拡縮している現場が多いように思います。Vector Graphicsモジュールもありますが、UI全部に使ってしまうと重いですね。

今回はTextMesh Proでも使われているSDF(signed distance field)を使ってみました。SDFはパスからの距離をテクスチャに書き込むことでテクスチャ解像度が小さくてもきれいなエッジを再現できます。私が仕事で開発に参加してるLyric SpeakerでもSDFを使ってきれいなフォントを描画しています。

さらに今回はSDFの改良版アルゴリズムのMSDFを使ってみました。詳しくはGitHubリポジトリと、こちらの論文PDFが詳しいです。

github.com

https://user-images.githubusercontent.com/18639794/86908136-5fdd4780-c116-11ea-96c5-4f58a42043a4.pnghttps://user-images.githubusercontent.com/18639794/86908146-6370ce80-c116-11ea-87ee-95bfb699665c.pnghttps://user-images.githubusercontent.com/18639794/86908155-65d32880-c116-11ea-9583-1b45f806bbd9.png msdfgenより引用

簡単に言うとSDFではグレースケールでエッジとの距離を書き込んでいましたが、MSDFではRGBチャンネルそれぞれに角度ごとに分けた距離を書き込むことで、小さい解像度でも角がきれいに出るように改良したようです。

反面、テクスチャ圧縮をかけてしまうとmsdfはきれいに表示されないので、無圧縮に設定する必要があります。容量削減の意味では、効果は少ないです。

実際にshaderのコードを簡略化するとこんな感じになります。

inline float msdf(sampler2D tex, float2 uv)
{
    // MSDFのテクスチャ取得
    half3 c = tex2D(tex, uv);
    // RGB平均値の取得
    return max(min(c.r, c.g), min(max(c.r, c.g), c.b)) - 0.5;
}

fixed4 frag(v2f IN) : SV_Target
{
   // パスからの距離をfloatで
   float sdf = msdf(_MainTex, IN.texcoord);

   // UIのTint colorを取得
   half4 color = IN.color;

   // パスの境界線より外側のアルファを0に
   float2 sdfUnit = _PixelRange / _MainTex_TexelSize.zw;
   float clipSdf = sdf * max(dot(sdfUnit, 0.5 / fwidth(IN.texcoord)), 1);
   color.a *= saturate(clipSdf + 0.5);
   
   return color;
}

肝の部分はRGB平均値を取得する部分と、クリッピングする部分だと思いますが、とてもシンプルで良いですね。

SDFでドロップシャドウを描く

Unity UI付属の公式Dropshadow エフェクトはただ同じ画像をコピーしてオフセットかけているだけですが、
SDFはきれいな線を描くだけではなく、パス境界との距離と、勾配がわかるので、ドロップシャドウなどのエフェクトにも使えます。TextMeshProのエフェクト設定動画がわかりやすいです。

www.youtube.com

Normalを取得するコードは以下のようになります。

// さっきと同じ平均値を返すだけ。
inline float msdf_median(float3 c)
{
    return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
}
 
// ノーマル画像を返す
inline float3 msdf_normal(sampler2D tex, float4 texel, float2 uv)
{
    texel *= 2;
    float left = msdf_median(tex2D(tex, float2(uv.x - texel.x, uv.y)));
    float right = msdf_median(tex2D(tex, float2(uv.x + texel.x, uv.y)));
    float bottom = msdf_median(tex2D(tex, float2(uv.x, uv.y - texel.y)));
    float top = msdf_median(tex2D(tex, float2(uv.x, uv.y + texel.y)));
    return float3(left - right, bottom - top, 0);
}

以上のように隣接ピクセルからの勾配情報をNormalテクスチャのような感じで取得します。これをもとにブラーやベベルを書き込んでいます。

Unity uGUI上に組み込む

実際にUnity uGUI上にNeumorphismデザインを実装してみます。

ドロップシャドウをuGUIのエフェクトとして使う。

github.com

UIEffectで行われているUIMeshの拡張を参考にしています。

同じ画像をコピーしても作れるのですが、一個の画像を作るのにレイヤー3枚…。などは避けたかったため。

すべてのNeumorphismコンポーネントの中ですべての頂点を一つにまとめ、uv2の中に、オフセット上をを埋め込み。Vertex shaderでDirectional Lightの方向にオフセットをかけています。

f:id:asus4:20200811222851p:plain
fig uv2

こんな感じです。

改善点

SDFでは実際にはテクスチャサイズより小さな領域が描画されます。配置のときに手動で直してましたが、ライブラリ側でサイズの計算を吸収できるのがベストです。

まとめ

このNeumorphism UIはGitHub上で公開しています。

github.com

またNeumorphismにしなくてもMSDFを使ったUIは使い所がありそうです。

ほっといても実践で使われることはなさそうなので、先日公開した自分のアプリでこのシェーダーを使っています。

asus4.hatenablog.com

おまけで:実験的に加速度センサでライティングの向きが変わる機能を搭載してたりします。 mobile.twitter.com

TensorFlow LiteとUnityでカメラアプリを作って公開しました。

こちらからダウンロードできます!

AnyFilter

AnyFilter

  • Koki Ibukuro
  • Photo & Video
  • Free
apps.apple.com

去年の年末頃からUnity上でTensorFlow Liteを動かして遊んでいます。その中でできたプロトタイプをTwitterで公開したところ↓思いの外評判が良かったので、誰でも試せるようにiOSアプリとして公開してみました。

コアの機能はGitHubで公開してます。 github.com

普段の受託案件では自分でUIデザインすることはまれですが、一人プロジェクトだったためUIもつくりました。
UnityでゲームぽくないUIをデザインするときに、Unityで複数の解像度に対応するためにpixel perfectをあきらめることが多かったため、UIを全部SDF(Signed Distance Field) Textureを使って作ってみました。更に実験でNeumorphismをuGUI上で再現しています。
加速度センサでドロップシャドウの向きが変わったりします。

といった感じで個人的な実験を公開してみました。良ければダウンロードしてみてください!

AR FoundationをEditorでも動かす その2

【unityプロ技】 Advent Calendar 2019 - Qiita 6日目の記事です。*1

img

Imgur

はじめに - ARFoundationとは

スマートフォンでのAR楽しいですね。それぞれiOSはARKit、AndroidではARCoreというAR用開発キットが公開されています。Unity用SDKもそれぞれUnity-ARKit-Plugin,Google ARCore SDK for Unityと開発がされてきました。今まではそれぞれのSDKをインストールして頑張ってきたのですが、ARCore, ARKit, HoloLens, Magic LeapSDKをラップして、同じAPIでアクセスできるようにする、ARFoundationが発表されました。

ARFoundationの導入は、安定のテラシュールブログさんを御覧ください。 tsubakit1.hateblo.jp

何を作った?

ARFoundationはUnity-ARKit-PluginのときよりもAPIがシンプルでわかりやすいのですが。以前のこちらの日記でも簡単に書いたように、 Editorで動かない。毎回実機ビルドが必要という問題がありました。Unity-ARKit-Pluginのときはできていたんです…。つい昨日、公式からアナウンスがあり、2020年はじめにリモートでつなげてEditorでも動作できる機能をリリース予定です。とはいえ、今すぐ仕事で使うために、自分で使う一部機能だけでもEditorで動かせると便利だと思ったので、ARKit3の新機能だけでもEditorで動作するものを作りました。*2

asus4.hatenablog.com

github.com

今回エディタ側で対応した機能は、 - 人間の体部分を検出 - Face - 座標検出 - Mesh検出 - ブレンドシェイプ推定 です。 以下は作例です。Editorで動作することで、デプスを送りながらVisual Effect Graphでノードのパラメータをいじるなどといったこともできるようになっています。

使い方

簡単に使い方を説明します。

1. NDIのインストール

iPhoneのカメラで移している映像をUnityに送るためにNDIを使っています。iPhoneで使うためには、NDIを https://ndi.tv/sdk/ からインストールする必要があります。メールアドレスを登録してしばらくするとSDKのダウンロード先URLが送られてきます。

2. iPhoneに送信用アプリをインストール

GitHubからUnityプロジェクトをダウンロードして、 iPhone用にビルド。Xcodeから対応端末 (iPhoneX以降〜)へインストールします。

3. maciPhoneをUSBでつなげる

高速のWi-Fiの場合は良いですが、私のオフィスのネットワーク環境ではiPhonemaciPhone USBネットワークで接続したほうが遅延が少なく動作しました。

Imgur

4. 再生する

iPhoneの送信側と対応するシーンをUnityEditorでも開きます。
iPhoneを再生すると、同じネットワークにNDIが飛んでいる場合、NDIの名前が表示させるので、NDI ReceiverコンポーネントのSource Nameにセットしてください。 Imgur

Unity Editorを再生開始するとiPhoneと同じ画面が表示されると思います。

ARFoundationを中身を見てみよう

この開発をするために、ARFoundationの中身を詳しく知る必要があったので、紹介します。

最新のロードマップ、Unite Copenhagen 2019でのスライドにあるように、AR Foundationを抽象化されたAPIの一個深いレイヤーにはSubsystemというものがあります。平面を検出する機能を例に取ると、

f:id:asus4:20191207001738p:plain
こんな感じ

それぞれのプラットフォームフォームがSubsustemを持っています。対応していないplatformではSubsystemがnullを返します。Manager側が何も動作しないので、シーン中にManagerコンポーネントが配置されていても問題ありません。そして、UnityEditorで実行するときは、Subsystemは存在しないので、エラーは返さないものの、動作しません。

そこでUnity Editor用のSubsystemを追加して、ARFoundationとは別の経路でiPhone実機から同等のデータを送信。このEditor用SubsystemをAR FoundationのハイレベルAPIに同じデータを転送。

f:id:asus4:20191207003135p:plain
エディタのSubsystemを追加

一番上のレイヤーから見ると、動作は何も変わっていないので同じコードで動作する気がします。

Subsystemの作り方

ここまでがわかったいくつかのSubsystemを自分で作ってみました。GitHubのリンクはこちらです

セッション管理をしているXRSesseionSubsystemをEditorで対応する例です。

// 仮想コード
namespace ARKitStream.Internal
{
    // PreserveはCode strippingでコードが消されないようにしてるっぽい。
    [Preserve]
    public class ARKitSessionRemoteSubsystem : XRSessionSubsystem
    {
        protected override Provider CreateProvider() => new ARKitRemoteProvider();

        // サブシステムの初期化時にUnityEngineから実行される
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        static void RegisterDescriptor()
        {
            // UnityEditorのときだけサブシステムを作る
#if UNITY_EDITOR
            const string id = "ARKit-Remote-Session";
            XRSessionSubsystemDescriptor.RegisterDescriptor(new XRSessionSubsystemDescriptor.Cinfo
            {
                id = id,
                subsystemImplementationType = typeof(ARKitSessionRemoteSubsystem),
                supportsInstall = false,
                supportsMatchFrameRate = false
            });
#endif // UNITY_EDITOR
        }
        
        // サブシステム本体
        // 各サブシステムにはProviderと呼ばれる本体のクラスがいる。
        class ARKitRemoteProvider : Provider
        {
            //
        }
    }
}

かなりシンプルに書いていますが、このようにRuntimeInitializeOnLoadMethodアトリビュートでSubsystemを管理しているようです。RuntimeInitializeOnLoadMethodは今回始めて使いましたが、UnityEngineネームスペースにあるので、ARFoundation以外にもSubsystemを使っているライブラリがあるのかもしれません。

まとめ

  • AR Foundationを使うと簡単にマルチプラットフォームなARアプリを作成できます。
  • Editorでも一部の機能を動作させることができた。
  • Unity開発チームが来年はじめに、Editorで動くやつを公開してくれる予定。

以上です。

*1:ML-Agents付属のBarracudaのドキュメントが少ないので、詳細を書こうとしたのですが、玉砕したので内容を変更しています

*2:また別の選択肢とて1→10さんが開発しているZIG SIM PROもいいかもしれません。NDIとOSCで、ARKit3のいくつかの機能を送信できます。有料ですが、時間節約できるので一瞬で元取れます。

TensorFlow LiteをUnityから動かす その2

この記事は Unity #2 Advent Calendar 2019 - Qiita の1日目です。他の日程に面白そうなタイトルが沢山並んでいます。

目次

TL;DR;

TensorFlow LiteのUnity Pluginを使い MNIST, SSD, DeepLab, PoseNetなどの基本的なものを動作させるサンプルを作りました。
github.com

TensorFlow Liteとは

TensorFlowをスマートフォンRaspberry Piなどのデバイスで動かすことを目的としています。学習はTensorFlow本体のpythonなどでモデルを作り、それを*.tfliteというファイルに書き出すことで、色々なデバイスで推論部分のみを小さなプログラムサイズで動かせるようになっています。コア部分はC++でUnity Pluginも非常に簡単に作ることができます。

先日この記事を書きました。 asus4.hatenablog.com

その後、GPUで動かすことによる高速化。複数プラットフォーム対応、入力と出力の型が見えないなどの足りない機能を追加して、使えそうな感じになってきました。TensorFlow の本家リポジトリへも PRを送ってやり取り頑張っています。
Pull Requests · tensorflow/tensorflow · GitHub

TensorFlow Lite Unity Pluginの使い方。

MNISTと呼ばれる0から9の手書き数字識別するプログラムの使い方をサンプルコードで説明します。簡略化しているので実際に動作するコードはGitHubを見てください。

    // 入力の画像 28 x 28のグレースケール
    float[,] inputs = new float[28, 28];

   // 出力の10個の数字の確率
    float[] outputs = new float[10];

    void Start()
    {
        // モデルのbyte配列を読み込んで`Interpreter`を作る
        interpreter = new Interpreter(File.ReadAllBytes(path));

        // 入力のバッファを確保
        interpreter.ResizeInputTensor(0, new int[] { 1, 28, 28, 1 });
        interpreter.AllocateTensors();
    }
    
   void Update()
   {
        // 入力データをセット
        interpreter.SetInputTensorData(0, inputs);

        // なんか計算してくれる!
        interpreter.Invoke();
        
        // 出力のデータを取得
        interpreter.GetOutputTensorData(0, outputs);
    }

    void OnDestroy()
    {
        // Interpreterを開放
        interpreter?.Dispose();
    }

とてもシンプルにできています。サンプルでもコードの多くは、画像をinputsに変換する部分だったりします。
また次のコードの用に、コンストラクタのオプションで、マルチスレッドや、GPUを使った高速化などを設定できます。

    void Start()
    {
        // オプション
        var options = new Interpreter.Options()
        {
            // CPU時にマルチスレッドで動作 
            threads = 2,
            // iPhone, macでメタルGPU上で動かす
            gpuDelegate = new MetalDelegate(new MetalDelegate.Options()
            {
                allowPrecisionLoss = false,
                waitType = MetalDelegate.WaitType.Passive,
            })
        };
        
        // option付きのコンストラクタ
        interpreter = new Interpreter(File.ReadAllBytes(path), options);
    }

Android/iOSで書かれた公式のExamplesのいくつかをUnityへ移植したので、それぞれのサンプルの説明をしていきます。

MNIST

MLのHallo World的存在MNISTです。28 x 28 ピクセルの数字を 0 - 9までのどれが書かれたの確率を教えてくれます。

f:id:asus4:20191128200929p:plain
MNIST ネットワーク図
ネットワーク図を見るとぎょっとしますが、レイヤーの機能は理解ぜずとも、inputs/outputsの部分に注目すれば大丈夫です。

入力

index type dimensions 説明
0 float [28,28,1] 28x28のグレースケール画像です。0.0 ~ 1.0へ正規化します。

出力

index type dimensions 説明
0 float [10] 10個の0.0 ~ 1.0までの確率です

SSD - Single Shot MultiBox Detector

画像上の物体の名前と矩形範囲を推定する。サンプルに使ったモデルでは90種類の物体を認識します。

f:id:asus4:20191128201008p:plain
SSD ネットワーク図

入力

index type dimensions 説明
0 sbyte [1,300,300,3] 300x300のRGB画像です。それぞれのピクセルは 0 - 255

出力

認識されたオブジェクト上位10種の情報を返します。

index type dimensions 説明
0 float [10, 4] 10個のバウンディングボックスがtop, left, bottom, rightの順で入っています
1 float [10] 認識した物体の IDが入っています。floatだけど、intにキャストして使えば大丈夫
2 float [10] それぞれの確率が入っています。
3 float [1] 全体で認識した物体の数

DeepLab

SSDと似て、画像の中の物体を推定しますが、矩形ではなく、ピクセル単位で推定します。

f:id:asus4:20191128201042p:plain
DeepLab ネットワーク図

入力

index type dimensions 説明
0 float [1,257,257,3] 257x257のRGB画像です。それぞれのピクセルは 0.0 ~ 1.0 に正規化しています

出力

すべてのピクセルを21種類のラベルに分類します。

index type dimensions 説明
0 float [1,257,257,21] 257x257のピクセル全てにどのラベルの可能性が高いかのスコアがはいっています

PoseNet

画面上の人間の骨格を、2D座標で取得するプログラムです。

f:id:asus4:20191128201139p:plain
PoseNet ネットワーク図
直列のとてもシンプルなネットワーク図に見えます。その分ポストプロセスでやることがあります。

入力

index type dimensions 説明
0 float [1,257,257,3] 257x257のRGB画像です。それぞれのピクセルは 0.0 ~ 1.0 に正規化しています

出力

index type dimensions 説明
0 float [9,9,17] 9x9のブロックに分割したエリアそれぞれに17個のパーツIDの可能性が入っています。
1 float [9,9,34] 9x9のブロックのそれぞれのパーツIDの本当の場所へのオフセット x,y値
2 float [9,9,32] 今回1人の認識では使わない
3 float [9,9,32] 同上

使うモデルによってブロックの分割数、精度が変わります。

出力したデータの整形が一番面倒なモデルです。このブログが一番わかりやすかったです。 medium.com

正確さを犠牲にシンプルに言えば、

  • 9 x 9に分割したブロックそれぞれに、体のパーツ17種の確率が入っている。
  • 一番確率が高いパーツにオフセット値を足して、正しいXY座標を得る
  • 17x17分割のモデルを使うことで精度を上げることが可能。遅くなる

ようです。

Unityでの実装で注意すること。

  • Height, Widthの順番: Unityでは すべてのAPIwidth, heightで表されますが、TF Liteでは height, widthです。
  • モデルの種類によっては、GPUオプションが使えないものもある

TODOs

  • Android版は私の端末Sony Xperia XZ2で動作中にフリーズするので、原因を調査
    • 熱によるカメラのフリーズ問題のよう
  • Windows, Linuxプラグインを作る

まとめ

  • TensorFlow LiteはUnityでも使えそう
  • 公式のモデルをUnityで動くサンプルを作った

TensorFlow Liteの公式サンプルでは、Android/iOSネイティブは全く別の実装をしています。Unity PluginではAndroid/iOS/Unity Editorを同じプログラムで作れるので便利です。今回使ったのは決して最新ではなく、ある程度こなれたモデルですが、Unityで使えるようになることで、ゲーム組み合わせたりするとまた可能性が広がりそうだと思いました。
Texture2DのピクセルC#上で直接触っているところなどがあり、まだ高速化できる余地があるので、もう少し触っていきます。実案件へは未投入ですが、仕事で使いたいって相談もお待ちしています。