MMM(MikuMikuMoving)でPerception Neuronを使う

**※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
MikuMikuMoving v1.2.8.0で公式にPerception Neuron対応されましたので、
現在こちらのプラグインを使用する必要はありません
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※**

#Perception Neuronとは
[![](http://img.youtube.com/vi/7tDxBbH9JMU/0.jpg)](http://www.youtube.com/watch?v=7tDxBbH9JMU)
中国Noitom社によって開発された低価格モーションキャプチャシステム。公式サイトは[こちら](https://neuronmocap.com/)。最大32個のNeuronと呼ばれる9軸センサモジュール（3軸ジャイロ＋3軸加速度＋3軸磁気）を用いて指先を含めた全身のキャプチャが可能。Axis Neuronという専用ソフトからTCP/UDPによるBVHフォーマットのモーションデータを送信することができます。システム一式（Neuron32個付属）で$1,499。Axis Neuronは[こちら](https://neuronmocap.com/downloads)からFree版をダウンロードすることができます。Perception Neuronを持っていなくてもサンプルのモーションデータを再生させることができます。

#MMM(MikuMikuMoving)とは

![001.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/c260316a-57ee-aa35-3d07-dca7c50b5780.png)

Mogg氏により開発されているMikuMikuDance(MMD)互換ソフト。公式サイトは[こちら](https://sites.google.com/site/mikumikumoving/)。MMD用のモデルやモーションデータが使用可能。プラグイン機能を持っており、ユーザーが自由に機能を追加することができます。

今回、Axis Neuronから送信されたモーションデータをMMMで受信し、MMDモデルにモーションを反映させるプラグインを作成しました。実装した内容について書きたいと思います。

#MMMのプラグイン開発
MMMのプラグインの開発に必要なソフトウェアと環境は以下になります。
> 開発に必要なソフトウェア
> ・Microsoft Visual C# 2010 Express もしくはこれに準じるソフトウェア
>
> 必要環境
> ・Windows XP以降
> ・.NET Framework 2.0
> ・DirectX 9.0c Runtime 2010 Feb以降 (SDKは必要ありません)

※MMM公式サイト [17.プラグイン](https://sites.google.com/site/mikumikumoving/manual/plugin)より引用

プラグインには、ボタンを押したときに実行されるコマンドプラグインと、毎フレーム実行される常駐型プラグインの２種類のタイプがあります。これらのプラグインでは、モデル、アクセサリ、エフェクト、カメラやライトに対して、キーフレーム操作やモーション操作等が可能です。プラグイン開発については詳しくはMMM公式サイトの[17.プラグイン](https://sites.google.com/site/mikumikumoving/manual/plugin)をご確認ください。今回は常駐型プラグインで開発しました。

#モーションデータの受信

##NeuronDataReaderライブラリ

Axis NeuronはTCP/UDPによるBVHフォーマットのモーションデータを送信することができます。NeuronDataReaderというC/C++用ライブラリが提供されており、これを利用することでBVHデータの受信を簡単に行うことができます。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/1f4aa9a2-f09c-29e6-95b1-ee020cddb3d9.png)
※画像は「Neuron Data Reader Runtime API Documentation」1.1. NeuronDataReader frameworkより引用

##BVHデータフォーマット

毎フレームのデータにはBVHヘッダとBVHモーションデータが含まれます。BVHヘッダには、位置情報の有り無しやリファレンスデータの有り無しなどの基本的な情報が含まれます。

```csharp
// Header format of BVH data
typedef struct _BvhDataHeader 
{
 uint16_t Token1; // Package start token: 0xDDFF
 DATA_VER DataVersion; // Version of community data format. e.g.: 1.0.0.2
 uint16_t DataCount; // Values count
 uint8_t WithDisp; // With/out displacement
 uint8_t WithReference; // With/out reference bone data at first
 uint32_t AvatarIndex; // Avatar index
 uint8_t AvatarName[32]; // Avatar name
 uint32_t FrameIndex; // Frame data index
 uint32_t Reserved; // Reserved, only enable this package has 64bytes length
 uint32_t Reserved1; // Reserved, only enable this package has 64bytes length
 uint32_t Reserved2; // Reserved, only enable this package has 64bytes length
 uint16_t Token2; // Package end token: 0xEEFF 
}BvhDataHeader;
```
※コードは「Neuron Data Reader Runtime API Documentation」2.1.4. Header of BVH data streamより引用

BVHモーションデータは連続したfloat値になっています。リファレンスデータ有りの場合、最初の6つのfloat値はリファレンスボーンの位置と回転になります。位置情報有りの場合、すべてのボーンが6つのfloat値(位置xyzの3つと回転xyzの3つ)を持ちます。位置情報無しの場合、ルートボーンのみ6つのfloat値を持ち(位置xyzの3つと回転xyzの3つ)、それ以外のボーンは3つのfloat値を持ちます（回転xyzの3つ）。

![009.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/85b02485-d9bd-f45f-85a9-2d165ed83656.png)
※画像は「PERCEPTION NEURON UNITY HANDBOOK」APPENDIX D: BINARY DATA SEQUENCEより引用

回転の順序はYXZです（Axis Neuron側の設定で変更可能）。
また、ボーンの階層構造は「Neuron Data Reader Runtime API Documentation」Appendix B: BVH header templateに載っています。

##C#でNeuronDataReaderを利用する
[公式サイト](https://neuronmocap.com/downloads)からダウンロードできるNeuronDataReader SDKにはdemo_csというC#で開発されたサンプルプロジェクトがついてきます。このサンプルプロジェクトにはNeuronDataReaderWraperというC#からNeuronDataReaderを使うためのラッパクラスが含まれています。C#でNeuronDataReaderを利用する場合はこちらを使わせてもらいましょう。

また、[公式サイト](https://neuronmocap.com/downloads)からダウンロードできるUnity IntegrationというUnity向けのパッケージに含まれているNeuronActor.csやNeuronTransformsInstance.cs、NeuronAnimatorInstance.csに、受信したデータから各ボーンの位置と回転を取り出したり、取り出した位置と回転を受信側のモデルのボーンに適用する処理が書かれています。こちらも参考になるかと思います（位置と回転はUnityの座標系に合わせて変換されているので注意してください）。

#それぞれの座標系

##Perception Neuronで使用される座標系
xの正方向が右、yの正方向が上、zの正方向が手前の右手系です。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/3f7e326a-a810-97e3-d700-35d603dbbabf.png)
※画像は「Neuron Data Reader Runtime API Documentation」Appendix D: Skelton Graphより引用

##MMMで使用される座標系
xの正方向が右、yの正方向が上、zの正方向が奥の左手系です。
MMMの画面上の座標軸はzが手前に伸びてますがマイナス方向なので注意です。

![005.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/95f634bf-1ccb-31d2-0cc5-05e6aaa3f247.png)

##座標系の変換
以上より、位置に関してはzの値をマイナスに、回転に関してはxとyの回転を逆に回転させることで、Perception Neuron側の値をMMM側の座標系に合わせる事ができます。

#Perception NeuronとMMMのボーンの対応
Perception NeuronとMMMのボーンは以下のように対応させました。MMDモデルのボーン構造はモデルによって違いがあったりしますが、標準モデルをベースに対応付けています。Perception Neuronからは各ボーンの位置と回転が得られますが、Hipsの位置をセンターの位置に使用している以外は、位置は使わず回転のみ使っています。上半身については、Perception Neuron側のSpine、Spine1、Spine2、Spine3の四つのボーンの回転を合計した値をMMM側の上半身ボーンに使っています。上半身２を持っているモデルの場合は、Spineを上半身、Spine1、Spine2、Spine3の合計を上半身２に使う等すると良いかと思います。また、RightHandIndex1とRightInHandIndexの回転を合計した値を右人指１ボーンに使っています。他の指も同様です。（このあたりはUnity IntegrationのNeuronAnimatorInstance.csのやり方を参考にしました）

|Perception Neuronのボーン|MMMのボーン|
|:-----------|:------------|
|Hips(位置)|センター(位置)|
|Hips|下半身|
|RightUpLeg|右足|
|RightLeg|右ひざ|
|RightFoot|右足首|
|LeftUpLeg|左足|
|LeftLeg|左ひざ|
|LeftFoot|左足首|
|Spine+Spine1＋Spine2＋Spine3|上半身|
|Neck|首|
|Head|頭|
|RightShoulder|右肩|
|RightArm|右腕|
|RightForeArm|右ひじ|
|RightHand|右手首|
|RightHandThumb1|右親指１|
|RightHandThumb2+RightHandThumb3|右親指２|
|RightHandIndex1+RightInHandIndex|右人指１|
|RightHandIndex2|右人指２|
|RightHandIndex3|右人指３|
|RightHandMiddle1+RightInHandMiddle|右中指１|
|RightHandMiddle2|右中指２|
|RightHandMiddle3|右中指３|
|RightHandRing1+RightInHandRing|右薬指１|
|RightHandRing2|右薬指２|
|RightHandRing3|右薬指３|
|RightHandPinky1+RightInHandPinky|右小指１|
|RightHandPinky2|右小指２|
|RightHandPinky3|右小指３|
|LeftShoulder|左肩|
|LeftArm|左腕|
|LeftForeArm|左ひじ|
|LeftHand|左手首|
|LeftHandThumb1|左親指１|
|LeftHandThumb2+LeftHandThumb3|左親指２|
|LeftHandIndex1+LeftInHandIndex|左人指１|
|LeftHandIndex2|左人指２|
|LeftHandIndex3|左人指３|
|LeftHandMiddle1+LeftInHandMiddle|左中指１|
|LeftHandMiddle2|左中指２|
|LeftHandMiddle3|左中指３|
|LeftHandRing1+LeftInHandRing|左薬指１|
|LeftHandRing2|左薬指２|
|LeftHandRing3|左薬指３|
|LeftHandPinky1+LeftInHandPinky|左小指１|
|LeftHandPinky2|左小指２|
|LeftHandPinky3|左小指３|

#Perception NeuronのモーションをMMMに適用させる
Perceoption NeuronのモーションをMMMに適用させるために、プラグイン内で以下のような処理を行っています。

##デフォルトポーズの違い
Perception NeuronのデフォルトポーズはTスタンスですが、MMMのデフォルトポーズはAスタンスです。MMM側でTスタンス用のモーションデータを使えるように、モデルのポーズをAスタンスからTスタンスに変換します。肩、腕のボーンが地面と平行になるようにz軸回転させます。

はじめに、肩ボーンの、グローバル座標系のx軸に対する角度を求めます。次に、腕ボーンの、肩ボーンに対する角度を求めます。肩と腕それぞれ求めた角度分戻すとTスタンスになります。

![006.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/a489ce56-da5f-51d6-da98-162cbffba6c8.png)

![007.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/3c3c2c0d-9bb2-1d2c-c869-1a656d3114cc.png)

また、親指が、Perception Neuron側では閉じているのに対し、MMM側では開いています。
![032.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/bae8084c-93d9-5c23-db29-a56ae3d3a1cf.png)

Tスタンスに変換後、グローバル座標系のx軸に沿うように親指１ボーンをY軸回転させて、親指を閉じさせます。
![033.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/fd1151da-c3ce-a7a2-bc7c-913876a75d39.png)

##ボーンの回転軸の違い
Axis Neuronから送信されるモーションデータの回転角はボーンのローカル座標系における回転角ですが、MMM側で回転を指定する際にはデフォルトポーズ時におけるMMMのグローバル座標系に一致したローカル座標系で指定することになります。
![010.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/0a69439c-c8ac-c0c7-eed6-aabeb85de34b.png)

そのため、肩、腕、ひじ、手首、指についてはAxis Neuron側の回転をかける際に、Axis Neuron側とMMM側で回転軸が合うようにボーンを回転させておく必要があります。Tスタンスへの変換と合わせると、行列の計算は以下のようになります。

*Tスタンスへの回転行列×「回転軸を合わせるための回転行列」の逆行列×肩、腕、ひじ、手首、指ボーンの回転行列×回転軸を合わせるための回転行列*

親指１ボーンの場合は、上記計算に親指を閉じさせる回転を加えます。

*親指を閉じる回転×Tスタンスへの回転行列×「回転軸を合わせるための回転行列」の逆行列×親指１ボーンの回転行列×回転軸を合わせるための回転行列*

##Perception NeuronのSpineボーンとMMMの上半身ボーンの違い
Perception NeuronのSpineボーンはHipsボーンの子ですが、MMMの上半身ボーンは下半身ボーンと親子関係にありません。MMM側で、Hipsボーンの回転を上半身ボーンにも適用させる必要があります。行列の計算は以下のようになります。

*Hipsボーンの回転行列×上半身ボーンの回転行列*

##センター位置のリセット
直立時にセンターボーンのY座標が0になるように、直立時のPerception NeuronのHipsボーンのY座標の値をオフセット値として使っています。

#プラグインの使い方
今回作成したプラグイン(NeuronReceiver.dll)を[こちら](https://bowlroll.net/user/121423)で配布しています。Perception Neuronの[公式サイト](https://neuronmocap.com/downloads)からAxis NeuronとNeuronDataReader SDKをダウンロードしてください。MMMは[こちら](https://sites.google.com/site/mikumikumoving/)からダウンロードできます。（上で説明した内容をプラグインの内部で処理しています。このプラグインを使うにあたって、モデルのTスタンスへの修正などは必要ありません）

##Axis Neuronの設定
File->Settings->Broadcastingを選びます。TCPを選択し、BVHにチェックをいれてください。Portは7001にしておきます。
![013.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/64ad8551-43c3-7efb-da80-3681b6adbe87.png)

File->Settings->Output Formatを選びます。Frequency reducingは1、Block typeはBinary、RotationはYXZを選んでください。
![014.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/b4e79de5-e8dd-c7be-11df-1912ed356cee.png)

動作確認用に適当なサンプルモーションを再生しておきます。
![015.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/da8fc068-d5c6-df08-ee26-9787a430abc6.png)

##プラグインの導入
NeuronDataReader SDKのWindows\lib\x64\NeuronDataReader.dllをMMMの実行ファイルと同じフォルダなどパスの通っている場所にコピーしてください（32bit版のMMMを使用する場合はWindows\lib\x86\NeuronDataReader.dllを使用してください）。

<img width="497" alt="011.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/26034/51619024-ebd7-3226-3147-c1269de6ed20.png">

Pulginsフォルダにプラグイン以外のdllを入れるとMMM起動時に以下のエラーになってしまうので注意してください。(対処法をサメジ部長 ([@samezi](https://twitter.com/samezi?lang=ja))さんに教えていただきました。ありがとうございました)

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/971b3d7a-aefb-37f3-acad-cc89612d4d06.png)

NeuronReceiver.dllをMMMのPluginsフォルダにコピーしてください。
<img width="497" alt="011_1.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/26034/514674c2-5a9b-716d-96a0-ec5ab1e67968.png">

MMMを起動してください。起動時に以下のエラーが出ることがあるようです。その場合は、[こちら](http://ch.nicovideo.jp/geruP/blomaga/ar646294)の対処方法を参考にしてください。

![c0e7f37306d61dc29d020f218057df34.png](https://qiita-image-store.s3.amazonaws.com/0/26034/af438384-c1a8-5d3f-59ba-50ae6d38225b.png)

モデルを読み込みます。
![016.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/28c7b914-432b-1468-d8a2-095dafcc0ab1.png)

足のIKをすべて切ります。
![017.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/288481a1-d0a1-5b63-7328-7b42e8149035.png)

NeuronReceiverプラグインを有効化します。
![018.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/801daa37-be8c-7872-3070-a46723f4e27b.png)

NeuronReceiverプラグインのConnectボタンを押します。
![019.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/58bbca91-27c4-399e-3949-0e447c826663.png)

Axis Neuronへの接続が成功すると以下のダイアログが表示されます。
![020.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/38297be5-8d0d-7503-7a55-4f92ba7918c5.png)

接続に失敗すると以下のダイアログが表示されます。Axis Neuronが起動しているか確認してください。起動している場合は、Broadcastingの設定内容を確認してください。
![021.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/b1f14bb3-dedd-021e-0664-4cf14ef7429f.png)

接続直後はモデルが宙に浮いています。
![022.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/0e7bf418-0f47-52f0-74d8-d814b71f2a3b.png)

直立時に、NeuronReceiverプラグインのセンター位置リセットボタンを押してください。モデルが接地します。
![023.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/a1f67b67-24db-caff-bf70-ee3ef17fad9f.png)

MMDモデルの体型によっては、移動時に足が滑ったり、しゃがんだときに地面に潜り込むことがあります。NeuronReceiverプラグインのY軸スケール係数とXZ平面スケール係数を調整することで、ある程度改善させることができます。
![024.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/79de4d35-d6e6-d946-afc6-bc8301f5fca8.png)

物理演算を有効化します。
![026.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/15c7f13b-27f1-3d87-1ebb-febd8fe2c7ab.png)

こんな感じで動かすことができます。
<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">ヤバイめっさ楽しい <a href="https://t.co/MBzfSeNtGE">pic.twitter.com/MBzfSeNtGE</a></p>&mdash; aimino (@minoaimino) <a href="https://twitter.com/minoaimino/status/678993975378776064">2015, 12月 21</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

##モーションの記録
はじめに、0フレーム目に足のIKを切った状態をキーフレーム登録しておきます。NeuronReceiverプラグインのRecordボタンを押すと、モーションの記録を開始します。
![025.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/cbbb727e-466c-3f9c-66b5-2158dfa85a06.png)

Stopボタンを押すと、モーションの記録を停止します。
![027.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/a31d9b83-9b88-d09c-2a56-4cf33b6adede.png)

タイムラインにキーフレームが登録されます。キーフレームは全フレームに登録されます。
![030.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/0e2d115d-7691-c851-90bb-ae0683238b4b.png)

記録したモーションをMMM上で再生する場合は、NeuronReceiverのプラグインを無効化してから再生してください。また、PCの性能にもよりますが、大量にキーフレームを登録しているためか500～600フレームを超えたあたりからフレームレートが低下し、キーフレーム登録が飛び飛びになってしまうことが確認されています。

##記録したモーションをMMDで使う
VMDフォーマットで保存します。
![028.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/4ec4c484-d268-cc41-d406-dd1fe3d15733.png)

保存したVMDファイルを、MMDで読み込むことができます。
![029.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/978da748-13dd-378f-5748-3cd1596fe31d.png)

##記録したモーションをUnityで使う
Noraさん([@Stereoarts](https://twitter.com/Stereoarts))のMMD4Mechanimを使うことで、UnityでMMDモデルやMMDで作成したモーションを使うことができます。MMD4Mechanimは[Stereoarts Homepage](http://stereoarts.jp/)からダウンロードすることができます。詳しい使い方は[こちら](http://stereoarts.jp/MMD4Mecanim%20%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%88%E5%9F%BA%E6%9C%AC%E7%B7%A8%EF%BC%89.pdf)に載っています。

![031.PNG](https://qiita-image-store.s3.amazonaws.com/0/26034/c905e49d-aabf-e871-5d44-943fcf6d2227.png)

#課題

腰の位置を基準として各ボーンに回転の値をセットしているだけなので、Perception Neuron側とMMM側とでモデルの体型が異なる場合、足先の位置が一致しません。そのため屈伸や前屈のような本来足先が動かない動作で、足先が動いてしまうことがあります。

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">Axis NeuronとUnityのサンプルとMMMで比較。足先が動いちゃうのどうにかしたいな。。 <a href="https://t.co/sSBu6XT3Pb">pic.twitter.com/sSBu6XT3Pb</a></p>&mdash; aimino (@minoaimino) <a href="https://twitter.com/minoaimino/status/675720788838735874">2015, 12月 12</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

また、Axis NeuronとUnity Integrationのサンプルとで比較すると、Unityの方では足先が動いてしまっているので、体型の違い以外にも何か原因があるのかもしれません。(Axis Neuronでは足の接地判定を行って足を固定している？)
<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">Axis NeuronとUnityのサンプルの足の動き比較 <a href="https://t.co/rqL33H9D5k">pic.twitter.com/rqL33H9D5k</a></p>&mdash; aimino (@minoaimino) <a href="https://twitter.com/minoaimino/status/675359049743904768">2015, 12月 11</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>