うなぎ、せんべい、水羊羹

がんばってぶったおれてがんばるひとのブログ

Unity + Kinect, Realsenseとかで自給自足フルトラッキング環境

静大情報LT Advent Calender 2019 Day:5の記事です。 adventar.org 実装間に合わなくてめっちゃ遅れました!ごめん… 文体等微修正しました。

前説

最近はxRサークルの立ち上げをしたり色々多忙にしていたのですが、 VR周りの開発をしたりAtcoderしたりベース弾いたりしてます。

2020年更新:
Valve Index買えました!やったね!

記事の内容は自己責任でやってね!

やたら前説が長いので本題から読みたい人は 準備 から読み始めてください。

バ美肉したいね

…ということはさておき、やっぱりVTuberになるにはLive2Dも魅力的なんですが、 私にはお絵かきの才がないのでVRoid Studioでいい感じに作るのが精一杯です。

となると3Dモデルになっちゃうんですが、Oculus Rift + Touchコントローラでは "頭"・"左手"・"右手"の三点しか取れず、どうもぎこちない動きになります。

いい感じにするためにフルトラッキングしてみたい欲もありますが、 苦学生がPerception Neuronなんか買った日には自己破産待ったなしです。

VIVEユーザでしたらトラッカー買えば全身動かせるのでサイコー!ってなるのですが あいにくいつも借りてる機材がOculus Riftなので、合計金額で余裕で20万越します。

コストダウンの戦略を考える

Oculus Riftを使いつつ最安値でフルトラッキングする方法はなんだろう…と考えたのですが、 以前UnityでKinect V2を使ったことがあり、Kinectで体の位置を取れればいいのでは?と思いつきました。 これは天才です。

VRモーションキャプチャといえばやっぱりVirtual Motion Capture(以下VMC)でしょう。

sh-akira.github.io

これを使うことによってSteamVRでVR機器つないでおけば超簡単にモーションキャプチャできます。 トラッカーも簡単に割り当てられるし身長とかの調整もできるのでマジ便利。 指までちゃんと動くし、LIVとも連携できるのでBeatSaberとかも簡単に自キャラと合わせて動かせます。 サイコー!ありがとー!!マジで神!!

しかもKinect V2をトラッカーとして認識させるためのツール、KinectToVRがあります。

github.com

こいつを噛ませることでOculus Rift + Kinect V2の構成で全身のトラッキングができることになります。 この辺の話はこちらのブログがとても分かりやすくまとめて下さっているのでもし試したい人はぜひ参考にしてみてください。

bironist.hatenablog.com

自作の機運

しかしOculus Rift + Kinect V2 + VMC + LIV + KinectToVR ⇒ Beatsaberの構成で遊んでいると、 何故かBeatsaberの起動時に左右のTouchコントローラが認識されなくなってしまうことがあります。 おそらくトラッカー周りの順番がずれてコントローラに当たっていないのかなと思います…

そのためKinectToVRを落としておいた状態でBeatsaberを起動し、 ゲームが起動したらKinectToVRを起動させることによってこの現象を回避することができるのですが、 毎度毎度やるのは本当にめんどくさいったらありゃしません。 でもKinectToVRがめんどくさいよって言って放棄したら肝心のフルトラができなくなっちゃうので、 それはそれで非常にうーん…って感じです。

とはいえVMCは残念ながらKinectやRealsenseといった深度カメラには対応していないため、 現状無料でなんとかする方法がないような状態です…

ない?なら作ればいいのでは…? しばらくUE4にお熱でUnityも触っていなかったのでいい機会だと思い、 せっかくなので勉強がてら作ってみることにします。

準備

準備する機材

以下のものを使います。

  • Oculus Rift (HMD)
  • Oculus Touch x2 (Controller, 左手・右手の2点)
  • Kinect V2, Intel Realsense etc...(腰・左足・右足の3点)
  • Unity, C#に対するある程度の知識
  • Unityに屈しない心

HMDに関しては今回はOculus Riftを使っていますが、 SteamVR叩いてるだけなのでVIVEとかIndexでもOKです。

HMD+コントローラの3点以外のトラッキングKinect V2を使いましたが、 後述のNuitrackを使用した関係でIntel Realsenseでも大丈夫だと思います。

ソフトウェア周り

ソフトウェア周りで使うのは以下の構成です。

  • Unity 2019.x
    • SteamVR Plugin
    • Final IK
    • UniVRM
  • Nuitrack
  • 適当なVRMモデル

SteamVR PluginはSteamVRのインプット周りを叩くのに使います。 HMD/コントローラの入力周りに使います。

assetstore.unity.com

Final IKは3Dモデルを動かす時にIK(Inverse Kinematics)を簡単に扱えるUnityのアセットです。

assetstore.unity.com

UniVRMはいつも通りVRMをUnityで読み込むアレ。

Nuitrackは深度カメラを利用して人体をトラッキングすることができるミドルウェアです。 Unity / UE4 といったゲームエンジンでも利用することができ、 カメラ側もKinect V2だけでなく、Kinect V1, Intel Realsense D415/435, Asus Xtion 2などに対応しているため、 こいつを噛ませることで後からKinectやめてRealsenseに乗り換えてもそのまま使えます。 公式サイトで対応しているデバイスの一覧があるので確認してみてください。

nuitrack.com

Realsenseは特にKinect V2のSDKのように最初っからボディトラッキングできるAPIがないのでNuitrackを使う人が多いらしいです。 ちなみにVR周りだと、3teneがRealsense + Nuitrackのトラッキングに対応しています。

VRMモデルはニコニ立体ちゃんが無料で配布されているので、 ない場合はとりあえずそれを使って動かしちゃいましょう。

実装編

3点トラッキングで動かせる環境を作る

まずは何にせよ、Unityでキャラクターを動かせるような環境が必要です。 1から説明してもいいのですがもっと分かりやすく解説してくれている先人がいるので、 こちらを読んで進めてもらえるといいと思います。VMCの作者さんが丁寧に解説してくれています、圧倒的感謝です…

qiita.com

このページの "手の位置と向きを直す" の項までやればOKです。

NuitrackをUnityで使う準備

まずはNuitrackを使う環境構築をします。 使用するカメラがRealsense・Kinect V2などの機種によって違うので、 各種自分の使用するカメラをNuitrackで使う系の記事を参照してみてください。

自分の使用した環境のKinect V2ではKinect SDKをインストール後、 Nuitrackをインストールしてnuitrack/nuitrack/activation_tool/nuitrack.exeを起動してTestボタンを押し動作確認をしました。 ここでTestを押しても画面が真っ黒で何も出ない場合、Visual C++のRuntimeが入っていない or そもそもKinectが動いてない可能性があります。 前者の場合インストールして、後者の場合本当に動いているかKinect Studio v2.0を起動して試してみましょう。

Nuitrack APIをUnityに追加

Nuitrack自体の動作が確認出来たら、今度はUnity上でNuitrackが正常に動作させられるか確認します。 公式ホームページのトップページに行き、"Nuitrack API"のSDK & Sampleからダウンロードします。 f:id:ysenbei:20191205201210p:plain

ダウンロードしてきたファイルを解凍、プロジェクトを開いた状態でNuitrackSDK/Unity3D/NuitrackSDK.unipackageをダブルクリック、 NuitrackのアセットをUnityにインポートします。

Nuitrackのトラッキング情報をUnityに反映する

アセットをインポートしたらNuitrackのトラッキング情報をC#スクリプトを書いて取得してみます。 まずまとめ用の空のGameObjectを[CameraRig]の子要素として追加し、 実際に肘や膝といった位置を反映させるために空のGameObject Nuitrackを作ります。 後に動かすスクリプトの関係上、Head~RightFootの順番は画像と同じにしておいてください f:id:ysenbei:20191206022634p:plain

加えてNuitrackの初期化のために各種スクリプトを読ませる必要があります。 Assets/NuitrackSDK/NuitrackScripts.prefab をScene上に追加します。 f:id:ysenbei:20191206005335p:plain

まとめたGameObjectのInspector上でAdd Componentを押し、 Nuitrackの情報を受け取るためのC#スクリプトを作成・アタッチします。 f:id:ysenbei:20191206005947p:plain

ソースコードはこんな感じです。

  • int jointIndex (line:10)
    実際にトラッキングに使用する部分のジョイントの番号だけ予め格納しておいた配列です。 体の部位との対応は nuitrack.JointType の方からチェックできるのでVisual Studioなどでチェックしてみてください。 先程作った各トラッキング用ポイントの順番通りに設定してあります。

  • GameObject[] debugSphere (line:14)
    実際のトラッキング時にはモデル以外が表示されてしまうと邪魔になってしまいますが、 デバッグのときに何も表示しないとわからなくなってしまうのでここで動的に作成しています。 動作確認してデバッグ表示が不要になったらこの行と20-27行目を消せば生成されなくなります。

  • trackPoints[i+1].position (line:40)
    ここがUnityの厄介なところなのですが、18行目でGetComponentsInChildren()をすると帰ってくる配列が、 親オブジェクトが[0]にまず格納され、そこから子オブジェクトが順番に格納されていくという仕様になっています。 そのため trackPoints.Length は jointIndex + 1 となるため留意が必要です。ここでは1ずらして対応しています。 また、-pos.zとしていますがこれはカメラから見た向きになっているため、トラッキングに利用するために左右反転させています。

これでUnityでRunしてやれば、こんな感じでScene上にトラッキングしたポイントが表示されます。 f:id:ysenbei:20191206013350p:plain

Calibrationの実装

とはいえ明らかに位置ズレを起こしてしまっているのも事実です。 このままではFinal IKに入れてもまともに使えないので、キャリブレーションを実装します。

キャリブレーションに必要となるのは、

  1. Y方向のスケールを調整してキャラの身長と自分の身長が合うようにする
  2. X/Z方向のスケールを調整してキャラの手の長さと自分の手の長さを合わせる

の1点です。

キャラの身長・腕の長さを取得し、自分がTポーズをとって手の長さを計測する事によって 差が求められるためそこからスケール値を算出することが可能です。 これに関しては一見Y方向がわかれば全部同じ比率で良いのではないか?とも考えられますが、 意外と身長に関わらず腕が長い・短いという個人差があるため、うまくモデルが動かない場合がありました。 そのため別でスケールを設定する形にしています。

またNuitrackの Y=0 座標は恐らくカメラの位置が原点となっており カメラを上に置けば置くほどオブジェクトが下の方に偏るという現象が起こります。 またSteamVRで設定されている座標とも初期値が異なっているため、 今回は頭の座標を利用してオフセットを算出します。

Tポーズをとった状態でキャリブレーションを行いたいので、 SteamVRを叩いてコントローラのボタンが押されたときに調整が行われるように実装します。

キャリブレーションを追加していい感じになったソースコードです。

  • offset = Vector3.zero ~(line:92-94)
    キャリブレーション前に一度オフセットやスケールについて初期化を行っています。 手のポジションなどを利用してスケールの計算を行っているため、再キャリブレーションを行うと スケール変更後の値が適応されてしまい結果としてどんどん値がずれていってしまうため ここで一度リセットする必要があります。
    またリセットするだけでなく実際にScene上のポジションを更新するために一度UpdatePosition()を呼び出しています。

  • modelWidth = ~(line:97-104)
    Nuitrackで取得した値とSteamVRで取得した値で一番問題になるのがルームスケールの違いになります。 Unity内での値は本来1 = 1mの扱いになっているはず…なのですが、Nuitrackが明らかにどう考えてもそれに比べて大きな値になっているため、 このズレを補正してやらないとSteamVRのHMDとNuitrackの足の位置を同じ量だけ動かしてやってもきちんと動いてくれません。
    今回はこれをTポーズをとりながらNuitrack上の左右の手のDistanceや身長を取ることによってスケール比を算出しています。

  • private void AttachIK()
    キャリブレーション完了後に実際にVRIKにNuitrackで取得した各ポイントをアタッチします。 Nuitrackを使用しないときと共存させるためにスクリプト側から動的にアタッチするように実装しています。
    今回は腰・左足・右足の3点のトラッキングを利用しましたが、 必要なら他部位もアタッチすることで膝や肘のポイントも利用できます。 (ただし膝とかが伸ばしていても曲がってしまったりということがあるので要修正です…)

またVRIK側のWeightなど設定も以下のように修正しておきます。 (個人の設定なのでいい感じに微調整してみてください。)

f:id:ysenbei:20191216222504p:plain f:id:ysenbei:20191216222514p:plain

動かすとこんなかんじ。

f:id:ysenbei:20191216223141p:plain

Kawaii! しかも歩ける!!!!

おわりに

Unity自体の扱いに手をこまねいてしまいだいぶ時間がかかってしまったのですが、 結果的にかわいい女の子になったので細かいことはよしとしましょう…

ラッキングの精度としては流石にVIVEトラッカーには負けてしまいますが、 とりあえず深度カメラがあれば全身使えるという意味ではかなり低コストで実装できます。
機材を持ってる人はぜひやってみてください!

おわりっ!