Unreal Engine 4で作ったバーチャル空間上で3Dモデルがぬるぬるアニメーションしているところを複数視点でcubemosの骨格検知にかけてみた

アニメーションする人間の3Dモデルを配置したUnreal Engine 4プロジェクトを自作して、UnrealCVでバーチャル空間のRGBイメージを連続的に取得しつつcubemosの骨格検知にかけてみました。
2020.06.05

前回の記事では、UnrealCVプラグインが組み込まれたUE4デモアプリでUnrealCVを触ってみました。 今回は、アニメーションする人間の3Dモデルを配置したUnreal Engine 4(UE4)プロジェクトを自作して、UnrealCVでバーチャル空間のRGBイメージを連続的に取得しつつcubemosの骨格検知にかけてみます。

準備

配布されているコンパイル済みのUnrealCVプラグインが対応しているのはUE4.16までです。新しめのバージョンのUE4を使いたいので、githubからリポジトリをクローンしてプラグインをビルドしてみます。使用するUE4のバージョンは今回はUE4.20としました。本記事執筆時に使用した主なツール・動作確認環境は以下のとおりです。

  • Windows 10
  • Python 3.7.7
  • UnrealCV Pythonパッケージ 0.4.0 (pip install unrealcv)
  • Unreal Engine 4.20.3
  • VC2017 15.9.23 (UnrealCVプラグインのビルドに使用)

Unreal Engine 4とVisual Studioのインストール

Unreal Engine公式サイトでユーザー登録をすませ、ダウンロードとインストールを済ませておきます。プラグインのビルドにはVisual Studio(VS)が必要なので、こちらもインストールを済ませておきます。執筆時はVS2017のバージョン15.9.23を使用しました。

参考:

 

UnrealCVプラグインのビルド

githubからリポジトリをクローンしてプラグインをビルドします。

git clone https://github.com/unrealcv/unrealcv.git
cd unrealcv
python build.py --UE4 "D:¥Epic Games¥UE4_20"

ビルドに成功したら、unrealcvのフォルダにPluginsというフォルダが作成されます。

実験用UE4プロジェクトの作成

"Epic Games Launcher"で、インストールしたUE4.20を起動します。

空のブループリントを選択してプロジェクトを作成します。

エディタウィンドウが起動したらプロジェクトの作成完了です。

プロジェクトにUnrealCVプラグインを追加

プロジェクトが作成されたので、UnrealCVプラグインをプロジェクトに追加します。 UE4自体にプラグインを追加することもできますが、今回はプロジェクトに追加するかたちで利用することにします。

unrealcvフォルダでビルドした結果できたPluginsフォルダを中身ごと、作成したUE4プロジェクトのフォルダにコピーします。 コピーしたら、プラグインを有効化するために、一度エディターウィンドウを閉じてプロジェクトを開き直します。

エディターウィンドウのメニューから[Edit]->[Plugins]と選択してプラグインブラウザーを開きます。

プラグインブラウザーウィンドウが開くと、UnrealCVプラグインがインストールされて有効化された状態になっていることが確認できるはずです。

UnrealCVプラグインの動作確認

エディターウィンドウ上部の[Play]ボタンをクリックします。UnrealCVはPlay状態でないと動作しません。

ウィンドウ中央のゲームレベルが表示されている領域をクリックしてから、バッククオートキーを2回押下して、UE4コンソールをコマンドの実行結果が表示される状態で開きます。 UnrealCVのコマンドをいくつか実行して、動作していることを確認します(コマンドのリストはここにあります)。

バーチャル空間上の人間の3Dモデルに対するcubemos骨格検知の実行

自作のUE4プロジェクトにUnrealCVプラグインが導入できたので、バーチャル空間上に人間の3Dモデルを配置して、cubemosの骨格検知を実行してみます。 まず、アニメーションつきの人間の3Dモデルをバーチャル空間に配置します。

アニメーションつきの人間の3Dモデルの入手と配置

アニメーションつきの人間の3DモデルをMixamoから入手します。 利用には無料のユーザー登録が必要です。

ログインしたら、適当なキャラクターとアニメーションを選んで、"Download"をクリックします。 執筆した時は"Joe"というモデルにサンバダンスのアニメーションを組み合わせてみました。

ダウンロードセッティングは、デフォルトのままにしておきます。.fbx形式で3Dモデルをダウンロードします。

ダウンロードした.fbxファイルをUE4プロジェクトに追加します。 まずエディタウィンドウの下部にあるコンテンツブラウザで適当な格納先を作成します。 執筆した時は"mixamo"フォルダを作りその下に"Joe"フォルダを作りました。

"Import"をクリックしてダウンロードした.fbxを選択します。

"FBX Import Options"ウィンドウが表示されるので、"Import Animations"をチェックしてから"Import"をクリックします。

インポートが完了すると、コンテンツブラウザに.fbxの中身が展開されます。"Message Log"にエラーっぽい内容が表示された場合、インポートに失敗しているかもしれません。

展開されたskeletal meshをレベルに配置して、アニメーションの動作確認をします。 一連の操作を動画にしました。

mixamoからダウンロードしたFBXファイルをUE4にインポートする時の注意事項

選択したキャラクター(とアニメーションの組み合わせ?)によっては、下のような感じでFBXをインポートしてskeletal meshをレベルに配置しただけではそのまま使えない状態になってしまいました。 解決の仕方がわかなかったため、こういう状態になってしまった時は素直に別のキャラクターでFBXをダウンロードし直しました。

cubemos骨格検知の実行

プロジェクトの準備ができたので、cubemos骨格検知を実行してみます。 以下のような、UnrealCVでUE4に接続し、 RGBイメージ取得ー>cubemosの骨格検知実行ー>推論結果をRGBイメージ上に描画をループするだけのシンプルなPythonスクリプトを作成しました。コードはここに公開してあります。

cubemosやUnrealCVなどの必要なPythonパッケージはここここを参考にして、事前にインストールを済ませてあるものとします。

cubemos-unrealcv-sample.py

import numpy as np
import cv2
import PIL.Image as Image
from io import BytesIO
import cubemosutil as cm
from unrealcv import client

def color_frame(client):
    res = client.request('vget /camera/0/lit png')
    img = Image.open(BytesIO(res))
    npy = np.asarray(img)[:,:,:3]
    return cv2.cvtColor(npy, cv2.COLOR_RGB2BGR)

if __name__ == "__main__":
    try:
        api = cm.get_api() # Cubemos Skeleton Tracking API

        res = client.connect() # connect to UE4 via UnrealCV
        print(res)

        while True:
            img = color_frame(client)

            skeletons = api.estimate_keypoints(img, 192)
            cm.render_skeletons(skeletons, img)
            cm.render_joints(skeletons, img)

            cv2.imshow('cubemos', img)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

    finally:
        client.disconnect()
        cv2.destroyAllWindows()

スクリプトを実行する前にUE4側で"Play"状態にして、UnrealCVの機能が使用できるようにしておきます。 実行結果は以下のようになりました。骨格検知はできていますが、コマ落ちしてカクカクした表示になってしまいました。

3Dモデルがぬるぬるアニメーションしているところを複数視点でcubmos骨格検知にかける

先程のスクリプトでは、カクカクした表示になってしまったので、少し工夫して、もっとぬるぬるさせてみます。 ついでに、複数の視点を同時に扱えるようにもしてみます。 先に実行結果を載せておきます。

複数視点の実現

UE4上でプロジェクトを"Play"状態にして、UE4コンソール上でUnrealCVコマンドを実行してカメラの位置と回転情報を取得します。 UE4コンソールはバッククオートキーを2回押下して、コマンドの実行結果が表示されるようにします。

カメラの位置と回転情報を取得するUnrealCVコマンドは以下のとおりです。

vget /camera/0/location
vget /camera/0/rotation

カメラを操作して以下のような4つの視点にして、それぞれの視点で上記コマンドを実行してlocationrotationを記録します。

top view

side view

front view

over the shoulder view

フレーム間の同期とフレームレートの向上

以下のような実装で、フレーム間の同期をとりつつ、フレームレートを向上させます。

cubemos-ue4-multiview.py

# top view (location, rotation)
cam1 = ('-2101.312 762.003 364.453', '288.216 270.963 0.000')
# over the shoulder view
cam2 = ('-1825.573 421.577 168.139', '340.495 136.159 0.000')
# front view
cam3 = ('-2094.729 955.863 159.293', '349.620 272.152 0.000')
# side view
cam4 = ('-1718.284 659.636 156.002', '342.875 181.467 0.000')

fouorcc = cv2.VideoWriter_fourcc(*'mp4v')
vid_writer = cv2.VideoWriter("out.mov", fouorcc, fps, (1280*2,720*2))
vid_writer_org = cv2.VideoWriter("org.mov", fouorcc, fps, (1280*2,720*2))
frame_count = fps * rec_len

client.request(f'vrun slomo {slomo}')
while frame_count > 0:
    client.request('vset /action/game/pause')

    client.request(f'vset /camera/0/location {cam1[0]}') # x y z
    client.request(f'vset /camera/0/rotation {cam1[1]}') # pitch yaw roll
    color1 = color_frame(client) # for waiting the camera location settle
    color1 = color_frame(client)

    client.request(f'vset /camera/0/location {cam2[0]}') # x y z
    client.request(f'vset /camera/0/rotation {cam2[1]}') # pitch yaw roll
    color2 = color_frame(client) # for waiting the camera location settle
    color2 = color_frame(client)

    client.request(f'vset /camera/0/location {cam3[0]}') # x y z
    client.request(f'vset /camera/0/rotation {cam3[1]}') # pitch yaw roll
    color3 = color_frame(client) # for waiting the camera location settle
    color3 = color_frame(client)

    client.request(f'vset /camera/0/location {cam4[0]}') # x y z
    client.request(f'vset /camera/0/rotation {cam4[1]}') # pitch yaw roll
    color4 = color_frame(client) # for waiting the camera location settle
    color4 = color_frame(client)

    client.request('vset /action/game/pause')

    h1 = np.hstack((color1, color2))
    h2 = np.hstack((color3, color4))
    images = np.vstack((h1, h2))
    vid_writer_org.write(images)

    #perform inference
    skeletons = api.estimate_keypoints(color1, 192)
    cm.render_skeletons(skeletons, color1)
    cm.render_joints(skeletons, color1)

    skeletons = api.estimate_keypoints(color2, 192)
    cm.render_skeletons(skeletons, color2)
    cm.render_joints(skeletons, color2)

    skeletons = api.estimate_keypoints(color3, 192)
    cm.render_skeletons(skeletons, color3)
    cm.render_joints(skeletons, color3)

    skeletons = api.estimate_keypoints(color4, 192)
    cm.render_skeletons(skeletons, color4)
    cm.render_joints(skeletons, color4)

    h1 = np.hstack((color1, color2))
    h2 = np.hstack((color3, color4))
    images = np.vstack((h1, h2))
    vid_writer.write(images)

まず、UnrealCVのvrun slomoコマンドで1フレームあたりの進行時間を遅らます。 次に、vset /action/game/pauseコマンドでアニメーションを一時停止させてから、vset /camera/0/locationコマンドとvset /camera/0/rotationコマンドでカメラの位置をセットして、RGBイメージを取得します。これを4つの視点分繰り返します。 ポーズ中にvset /action/game/pauseコマンドを再び実行すると、アニメーションが再開します。

完全なコードはここにあります。

課題

上記実装には、以下2つの課題を抱えています。

  • ゲーム側のフレーム進行とは完全に同期できていない

UE4側には、アニメーション(ゲームの進行)をポーズ中に1フレーム単位でスキップさせる機能があるのですが、 これをUnrealCV呼び出すことができそうになかったため、アニメーションの進行をスローモーションにしてからポーズー>再開を繰り返す、 という対応になりました。

  • 処理にめっちゃ時間がかかる

8th gen i7搭載ラップトップを使用して、3秒分のフレームを処理するのに8分くらいかかります。

まとめ

Unreal Engine 4で作ったバーチャル空間上で3Dモデルがぬるぬるアニメーションしているところを複数視点でcubemosの骨格検知にかけてみました。 出力結果はぬるぬるさせられたものの、数秒分のフレームを処理するのに分単位で時間がかかってしまうのが辛いので、でできれば改善したいところです。

参考