Web で MediaPipe を使って 3D モデル VRM をうごかしてみる

に公開

内容

ブラウザ上で MediaPipe を使って VRM フォーマット の 3D モデルを動かす。

あらかじめ忠告であるが、
今回のサンプルは基本的なところはドキュメントを見ながら手で実装しつつ、繋ぎ込みや関係ないところは Claude に任せた。
しかし MediaPipe の座標情報を 3D モデルに渡すのは Claude ではうまく書いてくれない。
ベクトル計算など細かく指定してどうにか動かしている。
そのため「LLM に任せればうまくやってくれそうだから自分でも試そう」と思ったのであれば難しいと言っておこう。

妥協点として首の z 軸と体は固定、手はトラッキングしていないので腕を上げたらピース (まあ腕を下げると描画外なのであまり意味はないのだが)というところに落ち着いている。
アルゴリズム次第でもう少しいい感じに動かすことはできるのだろうがグラフィックス系初心者の私としてはパッと試してみた感じ結構厳しかった。

サンプル

React で書いているが描画部分は普通に TypeScript (JavaScript) なのでそこは本質ではない。
そもそも私は普段 Vue を使っていて React は勉強中である。

https://github.com/Creanciel/web_avatar

MediaPipe

MediaPipe とは Google が開発している Web でも利用可能なリアルタイム機械学習ツールキットである。
ベースは C++ で書かれており、Web 向けには Emscripten で WebAssembly にコンパイルされているため、ブラウザからも利用できる。

とはいえ MediaPipe にはいろいろ種類があり、一例をあげると

  • 顔のランドマーク検出
  • 手のランドマーク検出
  • ポーズのランドマーク検出
  • テキスト分類
  • 音声分類

などがある。

https://ai.google.dev/edge/mediapipe/solutions/guide?hl=ja

今回は ポーズのランドマーク検出 (Pose Landmarker) を用いて大きな体の動きなどを検知する。

https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker?hl=ja

VRM

3D における JPEG を目指して Khronos Group が設計した glTF の 2.0 を拡張してドワンゴが策定した 3D ヒューマノイドアバターを扱うためのフォーマット。

glTF に比べ、 3D ヒューマノイドのためのボーンや表情などが追加されている。

Pixiv の @pixiv/three-vrm が主なパーサーになる。
またここでは扱わないが Rust でも vrm-spec というクレートを出している。

https://www.npmjs.com/package/@pixiv/three-vrm

https://crates.io/crates/vrm-spec

サンプル

MediaPipe

サンプルとしては practice-mediapipe にあたる。

下記のドキュメントを参考にモデルをダウンロードするようにする。モデルは Lite / Full / Heavy があるが、実用的には Full を使うことになるだろう。

https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/web_js?hl=ja#create_the_task

https://github.com/Creanciel/web_avatar/blob/a44b81b9a285b357478bf5fd20941d2223c836ff/app/apps/practice-mediapipe/src/Canvas.tsx#L122-L128

あとは PoseLandmarker に video 要素のカメラ映像を流し込んで DrawingUtils を使い描画を行っている。

ただ、そのまま描画すると結構ガクガク動いてしまうため One Euro Filter というものを使う。
ローパスフィルターらしく速度に応じて滑らかさを変えてくれる。
1 € filter: a simple speed-based low-pass filter for noisy input in interactive systems と調べればショートペーパーが出てくるので暇なときに読んでみるとしよう。

公式ページにあった通り、Pose Landmarker は33個の体のランドマークの位置座標を返してくるので、それぞれの位置を何回かサンプリングして返す。

https://github.com/Creanciel/web_avatar/blob/a44b81b9a285b357478bf5fd20941d2223c836ff/app/apps/practice-mediapipe/src/oneEuroFilter.ts#L28

体感これで結構動きは滑らかになるので入れておいたほうがいい。

VRM

サンプルは practice-vrm

前述通り読み込みは @pixiv/three-vrm を使い、 three.js でレンダリングを行う。

https://github.com/Creanciel/web_avatar/blob/a44b81b9a285b357478bf5fd20941d2223c836ff/app/apps/practice-vrm/src/Canvas.tsx#L10-L21

three.js の GLTFLoader を使い、 @pixiv/three-vrm の VRMLoaderPlugin を登録して読み込んでいる。

https://github.com/Creanciel/web_avatar/blob/a44b81b9a285b357478bf5fd20941d2223c836ff/app/apps/practice-vrm/src/Canvas.tsx#L36-L37

今回モデル的には上半身だけほしかったのでカメラの位置を少し上にしている。

https://github.com/Creanciel/web_avatar/blob/a44b81b9a285b357478bf5fd20941d2223c836ff/app/apps/practice-vrm/src/Canvas.tsx#L46

全体的に暗かったので DirectionLight (太陽光のような平行な光源) を強めに設定している。グラフィックスに疎いのでこの光源を強くすべきかはわからない。

あとは

https://github.com/Creanciel/web_avatar/blob/a44b81b9a285b357478bf5fd20941d2223c836ff/app/apps/practice-vrm/src/Canvas.tsx#L77

とするだけで基本的な描画は行うことができる。本格的に大変なのはアニメーション部分のはずだが。

MediaPipe の動きを VRM に反映する。

web-avatar

最終的にはサンプルの2つをつなぎ合わせた。全体としては

  • 背景にアウトカメラの映像
  • 画面下に VRM の 3D アバター

というような構成のものになっている。GitHub Pages にデプロイをしようかとも思ったがリポジトリに .vrm を push する気が乗らなかったので動画で我慢していただきたい。

demo

コードについて説明したいところであるがかなりごちゃごちゃしているので対応した内容を言葉でまとめておく。

ポーズ ランドマークモデル のドキュメントにあるように顔の向きを判定するのは 0 から 8 の目のランドマークから割り出さなくてはならない。
サンプルでは 0-8 のベクトルと 0-7 のベクトルを使って顔の向きの判定をした。ただこのモデルが x, y, z の座標情報を返すとはいえあまり顔の深度がはっきりしていないせいかとりあえず z 軸を捨てて x, y のみで顔の方向判定のみにとどめた。

腕・手

このモデルのランドマークでは細かな手の情報などは取得できない。別途 Hand Landmarker を使えば取得できるのだろうがここではとりあえず腕を上げたらピースにするというものにした。
肩の傾きなどの状態は見ていないのでややぎこちなくなるがとりあえず認識されれば腕の上がり具合などは再現できるようになっている。

まとめ

以上、MediaPipe と VRM のサンプルを順々にこなしつつ繋ぎ込んで最低限 3D モデルを動かせるようにした。
ここまで触ってみた感想であるが想像以上に発見があったと思っている。

最初、この題材を扱うにあたり、practice-mediapipe, practice-vrm に関しては手で書いてライブラリの使い方がわかったら繋ぎ込みは Claude に任せようという方針で進めていた。
だが Claude に任せるまでもなく MediaPipe に関しても VRM やその描画に関しても結構ライブラリが複雑なことはラップしてくれている。
世の中の Web サイトで 3D キャラが動くようなインタラクティブなものを見かける印象はあまりない(デザイン的にうるさいからやらないだけだろうが)ので複雑なのかと考えていたがモデルを描画するだけなら造作もない。
これなら残りは Claude が良しなに繋ぎ込んでくれるだろうと。

しかし繋ぎ込んでみるとそうでもない。先ほどまでしっかりと T ポーズを決め込んでいたモデルが驚きの複雑骨折をしているではないか。
まあ流石に少し調整は必要だろうと壊れている箇所を Claude に指示しつつ修正を促すが一向に改善されず、素晴らしいジョジョ立ちやシャフ度をキメこんでくる。
任せていても改善されないので具体的なベクトルやボーンの指示などを始めた。これでおおよそ改善されるのであるが一方の手では指が逆方向に折れ曲がっている。左右は合わせて修正してくれているのかと思ったがしてくれなかった。

思った以上に Claude はグラフィックス周りのアルゴリズムを書いてくれない。
教師データが足りていない領域だったのかもしれないがやはりグラフィックスはフィードバックが難しいのが原因であろう。かなり three.js などのコードを雰囲気で読んでいる可能性がある。
私の指示が悪いというのも否定はできないものの、普段コードを書かせていて「ああ、その観点が抜けていた」となることはよくあるが、ここでのコードは本当に指示をした内容を最低限実装しているだけのように見えた。

LLM に職を奪われると感じる昨今だが、グラフィックス屋はしばらく大丈夫だろうと期待しておく。

Discussion