カメラ1台でできる完全自動リアルアバター生成パイプラインを作った
以前、CGで作った人の影を利用したインタラクション作品を作った話を書きましたが、その作品制作の中で写真1枚から人物のアバター生成を行うシステムを開発しました。 今回は、その詳細について書いていきます。 以前の記事はこちら (以前の記事を読んでいない方は、どういう作品を作っていたかをご覧になっていただいてからの方がこの記事の詳細がわかると思います。)
概要
アバター生成パイプラインの全体像としては↓のようになっています。
- Kinectでのカラー画像撮影と同時に人骨格点情報を保存
- DeepLab v3のセマンティックセグメンテーションで背景除去
- PIFuHDで3次元メッシュの生成
- メッシュと保存しておいた人骨格点情報からBoneを生成
- リグ付け実行
(作った作品に組み込むことが前提だったので、UnityやらBlenderやらを使っていますが、代替できればなんでもOKだと思います。)
(作ってみた結果として)すごく似ていることをやっている方もいますが、こちらの記事ではメッシュ生成だけでリグ付けされていないので、「アバター」として人の動きを反映させることはできません。 そのため、生成したメッシュと人骨格点情報を実寸大で位置合わせしてからリグ付けするところがポイントとなってきます。 では、各詳細を見ていきます。
Kinectでの撮影
UnityからKinectを動かすために、Kinect for Windows SDKと、KinectのUnityアドオンをこちらから入れます(参考:
UnityでKinectを動かす - Qiita)。
UnityのSceneの任意のオブジェクトにColorSourceManager
とBodySourceManager
スクリプトをアタッチします。
ColorSourceManager
を使って、カラー画像を出力します。
Kinectの画像は上限反転、かつ後述のDeepLab v3用に正方形画像で書き出ししています。
この直後にBodySourceManager
を使って、人骨格点情報をCSVファイルに書き出しています。
CSVファイルのデータは↓のような形になっています。
SpineBase, -0.09998489, -0.21304650, 1.28778700 SpineMid, -0.09860130, 0.11662960, 1.34860800 Neck, -0.09887136, 0.42510210, 1.39316600 Head, -0.06842262, 0.58209430, 1.41956900 ShoulderLeft, -0.25880580, 0.28523010, 1.31506900 ElbowLeft, -0.46841260, 0.29323760, 1.22628400 WristLeft, -0.66610810, 0.31170160, 1.13132700 HandLeft, -0.73187570, 0.31414450, 1.10503800 ShoulderRight, 0.09394829, 0.25767750, 1.43001000 ElbowRight, 0.24875250, 0.14255330, 1.42700800 WristRight, 0.43452660, 0.11693760, 1.43390100 HandRight, 0.48573220, 0.09601390, 1.43826000 HipLeft, -0.18064280, -0.20051540, 1.23052500 KneeLeft, -0.25390640, -0.53377940, 1.14589600 AnkleLeft, -0.06551649, -0.78777150, 0.88342400 FootLeft, 0.00007164, -0.66547630, 0.78344010 HipRight, -0.01332492, -0.21330920, 1.26921400 KneeRight, 0.07572437, -0.54536020, 1.23456700 AnkleRight, -0.27578800, -0.61191820, 1.40525200 FootRight, -0.39934130, -0.55957010, 1.35356100 SpineShoulder, -0.09886321, 0.35052830, 1.38435700 HandTipLeft, -0.79018900, 0.31139960, 1.09132600 ThumbLeft, -0.71317940, 0.34362320, 1.05027300 HandTipRight, 0.56050930, 0.09793835, 1.45274200 ThumbRight, 0.44630720, 0.12957220, 1.42643700
Kinectから取得できる人骨格点の名前、XYZワールド座標となっています。
同時刻のカラー画像と人骨格点情報を保存することで、後述の処理で画像に映った姿でボーンのリグ付けが可能となります。
DeepLab v3とPIFuHDによる3次元メッシュ生成
Kinectの撮影処理の後、カラー画像から撮影対象者のメッシュを作成していきます。 DeepLab v3によって背景を除去し、人領域のみを切り出します。 そして、PIFuHDを使って人領域画像から3次元メッシュのOBJファイルを作成します。
DeepLab v3は、PIFuHDにかける時に背景物体がメッシュとして作られることを防ぐために使っているので、きれいに人領域を抽出できるのであればなんでもOKです。 DeepLab v3、PIFuHDともにPythonで動かしたので、DeepLab v3はPyTorchの機能を、PIFuHDは著者の方が公開しているコードやGoogle Colabのデモなどをそのまま使えば簡単に実装できます。
このあたりのDeepLab v3とPIFuHDを使ってのメッシュ生成は作っていた作品とは別のモジュールとして作成し、gitのsubmodule機能で利用できるようにしました。
あとは、UnityでKinectの撮影が完了後にこのモジュールを呼び出すだけです。 Unity(C#)→Pythonの呼び出しは、C#の外部プロセス呼び出しの機能を使っています。 (参考にしたサイト: C#からPythonを実行する方法【Unity】 - トーフメモ)
コードはこんな感じ↓
using System.IO; using System.Diagnostics; using UnityEngine; ~~~ bool RunProcess(string exe, string args, string workDir){ try{ //外部プロセスの設定 ProcessStartInfo processStartInfo = new ProcessStartInfo() { FileName = exe, //実行するexeファイルのpath UseShellExecute = false,//シェルを使うかどうか CreateNoWindow = true, //ウィンドウを開くかどうか RedirectStandardOutput = true, //テキスト出力をStandardOutputストリームに書き込むかどうか RedirectStandardError = true, //エラー出力をStandardOutputストリームに書き込むかどうか Arguments = args, //実行するスクリプト 引数(複数可) WorkingDirectory = workDir }; var process = new Process(); process.StartInfo = processStartInfo; // 非同期での出力と終了検知の設定 process.EnableRaisingEvents = true; //外部プロセスの開始 process.Start(); //ストリームから出力を得る var output = process.StandardOutput.ReadToEnd(); var error = process.StandardError.ReadToEnd(); var exitCode = process.ExitCode; //外部プロセスの終了待ち process.WaitForExit(); process.Close(); //ログ出力 if(!string.IsNullOrWhiteSpace(output)) UnityEngine.Debug.Log(output); if(!string.IsNullOrWhiteSpace(error)) UnityEngine.Debug.LogError(error); return exitCode == 0; } catch(System.Exception e){ UnityEngine.Debug.LogError(e); return false; } }
メッシュと、保存したCSVファイルからのリグ付け
メッシュ生成が完了したら、Kinectでの撮影時に保存しておいたCSVファイルの人骨格点情報と組み合わせて、リグ付けを行っていきます。 このリグ付けをどうするかをかなり悩んでいたら、どうもPIFuHDが出力するOBJファイルのスケールが実寸のサイズであることに気づきました*。 そのため、せっかくKinectを(作品内で)使っているなら、人骨格点情報と紐づけて自前でリグ付けができるのではないかという考えになりました。 このことが、メッシュ生成元のデータであるカラー画像と、リグ付けするBoneの元データの人骨格点情報を、Kinectでの撮影時に同時に記録している理由になります。
*(もっと正確に言えばKinectの人骨格点情報のポジションをメッシュ上に配置してみると、メッシュの関節部分と見た目上ほぼ一致していたというのに気付いただけなので、PIFuHDが出力するOBJファイルのスケールが実寸のサイズであるという確証はありません。論文をきちんと読めばそのあたりが書かれているかもしれませんが...)
となると、あとは実装するだけです。 リグ付けにはBlenderを使っています。 BlenderではPythonを動かすことができて、手動のGUI操作で行っていることはPythonプログラムファイルを用意してBlender上で同様の操作が可能です(使い方はこの辺とかを見てください)。BlenderのAPIについてはこちら
また、
<Blenderのpath>/blender.exe --background --python hoge.py
のような形でCUI操作で、Blenderのウィンドウを開かずにバックグラウンドモードで指定したPythonスクリプトを実行することもできます。 なので、先ほどのUnity→Python呼び出しと同じようにするだけでUnityからBlenderを呼び出してリグ付けされたFBXファイルを生成することができます。
さて、肝心のリグ付け処理ですが、↓のようになります。
無事にリグ付けが完了したらFBXファイルが作成されます。 開発中の様子は↓。 選択したボーンを動かすとメッシュも動かせています。
Unityに戻す
以上で撮影、メッシュ生成、リグ付けができました。 作った作品では、リアルタイムで撮影、メッシュ生成、リグ付けを行った後、生成したFBXファイルのアバターをUnity上で動かす必要がありました。 そのため、メッシュ生成とリグ付けの外部実行プロセスについては非同期処理でメインスレッドと別スレッドで動かすことで、アバター生成中もUnityが止まらないようにしてチュートリアル動画の閲覧などを可能にしました。
アバター生成が完了したあと、UnityのC#でAssetDatabase.Refresh()メソッドを呼ぶ必要があります。
生成したFBXファイルはUnityのAssets
フォルダに配置しただけではリアルタイム実行中には認識されません。
そのため、AssetDatabase.Refresh()
メソッドを呼ぶことでAssets
フォルダの更新を行います。
また、Assets
フォルダに配置したFBXファイルをHumanoidモデルとして認識させるためにも一苦労必要です。
↓のようなスクリプトをAssets
フォルダにおいておくことで、対象のFBXファイルをHumanoidとして認識させてあげる必要があります。
using UnityEngine; using UnityEditor; public class AvatarImportSetting : AssetPostprocessor { // Unity EditorでAseetsが新規にインポートされたら呼ばれる void OnPreprocessModel() { ModelImporter importer = (ModelImporter) assetImporter; var avatarFbxPath = "Assets/KinectAutoRig/Avatar.fbx"; // Blenderで生成したFBXファイル以外なら破棄 if(avatarFbxPath != importer.assetPath) return; // FBXファイルをHumanoidとして設定 // FBXファイルのImport Setting > RigでHumanoidを選ぶのと同じ importer.animationType = ModelImporterAnimationType.Human; Debug.Log(importer.assetPath + " is imported as Humanoid model."); } }
あとは、AssetDatabase.LoadMainAssetAtPathメソッドを使ってUnityのシーンに配置したり、Kinectでモーションキャプチャをしたりと、好きに使うだけです😊
つまづいたこと
GPUメモリ
Deep Lab v3、PIFuHD、Unityと、GPUを使っての計算が多くなります。 とくにDeep Lab v3、PIFuHDではGPUメモリの容量が十分に大きくないと実行中にメモリ容量が不足してこけます。 私が最初に実行した環境だとRTX2080 superを載せているデスクトップPCなのですが、8GBメモリでも落ちてしまいました。 普段使いの関係ないアプリケーションをすべて終了させて、0.6GBの状態から実行すると最大で7.6GB近くまでメモリ容量を消費したので、メモリ容量が大きいGPUじゃないとつらいです...
安定性
長々と説明していましたが、実はこの方法は100%アバターを作れるほどの精度はないです...。 Kinectでの撮影の環境によって、PIFuHDのメッシュ生成精度やボーンの配置位置が不安定で、リグ付けがうまくいかない時があります...。 これでも、Blenderでの処理で重複頂点を減らしたりして大幅に安定性は向上したのですがね...
いろいろ試していたこと
RigNet
リグ付けをどうやって自動で行うのかをかなり悩んでいたのですが、Kinectの人骨格点情報を使う考えの前はRigNetの使用を考えていました。 RigNetはSIGGRAPHで発表された機械学習手法によってリグ付けを行う手法で、こちらを参考にして試してみました。 RigNetが入力として受け付ける頂点数が少ないためPIFuHDで生成したメッシュの頂点数をかなり減らして入力したためか、あまり精度が出なかったです... あとは、実行時間も4~5分とかなり待ち時間が必要なので、今回制作した作品には使えないと判断しました。
PIFuHDのメッシュだと難しいのかなと思って、VRMモデルを入力したりしてみても、足や頭が正しくリグ付けできていなかったりと、あまり精度はよくない結果になりました(これも頂点数が多すぎかも?)
頂点カラーで色付けしてみたら...
冒頭でも述べたすごく似ていることをやっている方の記事をみたら、メッシュに頂点カラーで色付けをしていました。 自分の手元のPIFuHDで生成しているOBJファイルをテキストエディターなどで覗いてみると、
v -0.5899 0.1797 0.0039 0.2286 0.9002 0.3727 v -0.5898 0.1797 0.0035 0.2273 0.8982 0.3694 v -0.5898 0.1797 0.0039 0.2341 0.9026 0.3689 ...
となっているので、この記事と同じだと思い、自分のパイプラインにも色付けを組み込もうとやってみました。 BlenderでのOBJファイル読み込みの後に、色部分の値を自前でパースして、各頂点に色付け処理を組み込みました。 コードはこちら↓
で、実行してみたら...
なんと、法線だったようです...(笑)
調べてみると、PIFuは頂点カラーのようですが、PIFuHDは法線のようでした...(そもそも、2つは別のものだったと初めて知った...) 私が制作した作品ではシルエットだけが必要だったので問題なかった(興味本位でやっていただけ)ですが、アバター生成パイプラインのPIFuHDのところをPIFuに変えたら、理想的なリアルアバターになりそうですね...
まとめ
ということで、1回写真を撮るだけで完全自動で自分の分身のアバターを生成できるパイプラインを開発しました!! 自動リグ付けで検索すると、Adobe mixamoやAutoRigのようなツールも見つかりますが、これらは肘や膝などの位置を手動で選択する必要があります。 この操作すらもなく、リグ付けできる仕組みは聞いたことがなかったので、個人的にはかなり面白い経験ができました。
Kinectやら、Pythonやら、いろいろツールを使っていますが、各ツールは置き換え可能で、処理の流れ=パイプラインさえ一緒だったらいろんなところで使えるのではないかと思います。とくに、KinectをMediapipe Poseに置き換えたら、誰でも持っているWebカメラで作れるのでかなり便利かと思います。 (IKEPはMediapipe PoseのUnity移植版も開発したので、ぜひ~!←宣伝w)
メタバース時代がやってきているのでもっと精度が上がれば、お気に入りのアバター以外にも、誰もが自分のリアルアバターでメタバース生活を送る選択肢ができそうで非常に楽しみです😊