本トピックスではHumanoidの顔を動かしてはいません。 顔も動かしたい場合は 「 Kinect V2 で骨格と顔を認識してUnityの人型キャラクタをリアルタイムで動かす 」 を参照して下さい。
目次へ
上記のzipファイルを展開して、フォルダ名を CheckNtKinectDll6/ と変更します。
上のメニュー -> File -> New Scene
上のメニュー -> File -> Save Scene as ... -> humanoid.scene
ここでは 「 MakeHuman: Unity5 で使用する人型モデルを作成する 」 で作成したデータ makehuman.zip を用います。
上記ファイルを展開するとexports/の下に AsianBoy.fbx と textures/ があるはずです。 これを Assets/Models/ の下にimportします。
[注意]上の操作は
Assets -> Import New Asset... -> AsianBody.fbxから行なってもよいのですが、これだとAsianBoy.fbx に必要なtextureが 自動ではimportされず、モデルが真っ白になってしまいます。 この場合は Assets/Models/Materials/に生成された白いMaterialに対応する Textureを手動でimportしなくてはいけません。



必須なBoneが割り当てられていないと "Configure" にチェックがつかないのでわかりますが、 本プロジェクトでは Optional な Bone も利用しています。 Neck と UpperChest にも Bone が割り当てられていることを確認して下さい。


から Resetを選択して、Position (x,y,z)=(0,0,0)とします。

RigBone クラスは Humanoid と Boneが与えられると、 Humanoid に付加されている Animator コンポーネントから Bone の transform を取り出して管理するクラスです。
| RigBone.cs |
/*
* Copyright (c) 2017 Yoshihisa Nitta
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*/
/* http://nw.tsuda.ac.jp/lec/unity5/ */
/* version 1.1: 2017/08/05 */
/* version 1.0: 2017/08/02 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RigBone {
public GameObject gameObject;
public HumanBodyBones bone;
public bool isValid;
public Transform transform {
get { return animator.GetBoneTransform(bone); }
}
Animator animator;
Quaternion savedValue;
public RigBone(GameObject g, HumanBodyBones b) {
gameObject = g;
bone = b;
isValid = false;
animator = gameObject.GetComponent<Animator>();
if (animator == null) {
Debug.Log("no Animator Component");
return;
}
Avatar avatar = animator.avatar;
if (avatar == null || !avatar.isHuman || !avatar.isValid) {
Debug.Log("Avatar is not Humanoid or it is not valid");
return;
}
isValid = true;
savedValue = animator.GetBoneTransform(bone).localRotation;
}
public void set(float a, float x, float y, float z) {
set(Quaternion.AngleAxis(a, new Vector3(x,y,z)));
}
public void set(Quaternion q) {
animator.GetBoneTransform(bone).localRotation = q;
savedValue = q;
}
public void mul(float a, float x, float y, float z) {
mul(Quaternion.AngleAxis(a, new Vector3(x,y,z)));
}
public void mul(Quaternion q) {
Transform tr = animator.GetBoneTransform(bone);
tr.localRotation = q * tr.localRotation;
}
public void offset(float a, float x, float y, float z) {
offset(Quaternion.AngleAxis(a, new Vector3(x,y,z)));
}
public void offset(Quaternion q) {
animator.GetBoneTransform(bone).localRotation = q * savedValue;
}
public void changeBone(HumanBodyBones b) {
bone = b;
savedValue = animator.GetBoneTransform(bone).localRotation;
}
}
|
CharacterSkeleton クラスは、一人分の Humanoid のデータを管理し、 与えられた関節データを用いてポーズを変化させるクラスです。
set()メソッドでは、Kinect V2 で取得した関節データが渡されます。 最大で6人分の関節データがまとめて渡されてくるので、 現在着目している骨格のインデックスが第3引数 offset で 0 から 5 までの整数で指定されます。
人間の体の向きをHumanoid の向きに反映するように変更しています。 人間の骨盤の向き(y軸回りの回転)がHumanoid 全体の向きに、 人間の両肩の向きがHumanoid の上半身の向きになります。
| CharacterSkeleton.cs |
/*
* Copyright (c) 2017 Yoshihisa Nitta
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*/
/* http://nw.tsuda.ac.jp/lec/kinect2/ */
/* version 1.3: 2017/08/11 */
/* version 1.2: 2017/08/10 */
/* version 1.1: 2017/08/07 */
/* version 1.0: 2017/08/06 */
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
class CharacterSkeleton {
public const int
// JointType
JointType_SpineBase= 0,
JointType_SpineMid= 1,
JointType_Neck= 2,
JointType_Head= 3,
JointType_ShoulderLeft= 4,
JointType_ElbowLeft= 5,
JointType_WristLeft= 6,
JointType_HandLeft= 7,
JointType_ShoulderRight= 8,
JointType_ElbowRight= 9,
JointType_WristRight= 10,
JointType_HandRight= 11,
JointType_HipLeft= 12,
JointType_KneeLeft= 13,
JointType_AnkleLeft= 14,
JointType_FootLeft= 15,
JointType_HipRight= 16,
JointType_KneeRight= 17,
JointType_AnkleRight= 18,
JointType_FootRight= 19,
JointType_SpineShoulder= 20,
JointType_HandTipLeft= 21,
JointType_ThumbLeft= 22,
JointType_HandTipRight= 23,
JointType_ThumbRight= 24,
// TrackingState
TrackingState_NotTracked= 0,
TrackingState_Inferred= 1,
TrackingState_Tracked= 2,
// Number
bodyCount = 6,
jointCount = 25;
private static int[] jointSegment = new int[] {
JointType_SpineBase, JointType_SpineMid, // Spine
JointType_Neck, JointType_Head, // Neck
// left
JointType_ShoulderLeft, JointType_ElbowLeft, // LeftUpperArm
JointType_ElbowLeft, JointType_WristLeft, // LeftLowerArm
JointType_WristLeft, JointType_HandLeft, // LeftHand
JointType_HipLeft, JointType_KneeLeft, // LeftUpperLeg
JointType_KneeLeft, JointType_AnkleLeft, // LeftLowerLeg6
JointType_AnkleLeft, JointType_FootLeft, // LeftFoot
// right
JointType_ShoulderRight, JointType_ElbowRight, // RightUpperArm
JointType_ElbowRight, JointType_WristRight, // RightLowerArm
JointType_WristRight, JointType_HandRight, // RightHand
JointType_HipRight, JointType_KneeRight, // RightUpperLeg
JointType_KneeRight, JointType_AnkleRight, // RightLowerLeg
JointType_AnkleRight, JointType_FootRight, // RightFoot
};
public Vector3[] joint = new Vector3[jointCount];
public int[] jointState = new int[jointCount];
Dictionary<HumanBodyBones,Vector3> trackingSegment = null;
Dictionary<HumanBodyBones, int> trackingState = null;
private static HumanBodyBones[] humanBone = new HumanBodyBones[] {
HumanBodyBones.Hips,
HumanBodyBones.Spine,
HumanBodyBones.UpperChest,
HumanBodyBones.Neck,
HumanBodyBones.Head,
HumanBodyBones.LeftUpperArm,
HumanBodyBones.LeftLowerArm,
HumanBodyBones.LeftHand,
HumanBodyBones.LeftUpperLeg,
HumanBodyBones.LeftLowerLeg,
HumanBodyBones.LeftFoot,
HumanBodyBones.RightUpperArm,
HumanBodyBones.RightLowerArm,
HumanBodyBones.RightHand,
HumanBodyBones.RightUpperLeg,
HumanBodyBones.RightLowerLeg,
HumanBodyBones.RightFoot,
};
private static HumanBodyBones[] targetBone = new HumanBodyBones[] {
HumanBodyBones.Spine,
HumanBodyBones.Neck,
HumanBodyBones.LeftUpperArm,
HumanBodyBones.LeftLowerArm,
HumanBodyBones.LeftHand,
HumanBodyBones.LeftUpperLeg,
HumanBodyBones.LeftLowerLeg,
HumanBodyBones.LeftFoot,
HumanBodyBones.RightUpperArm,
HumanBodyBones.RightLowerArm,
HumanBodyBones.RightHand,
HumanBodyBones.RightUpperLeg,
HumanBodyBones.RightLowerLeg,
HumanBodyBones.RightFoot,
};
public GameObject humanoid;
private Dictionary<HumanBodyBones, RigBone> rigBone = null;
private bool isSavedPosition = false;
private Vector3 savedPosition;
private Quaternion savedHumanoidRotation;
public CharacterSkeleton(GameObject h) {
humanoid = h;
rigBone = new Dictionary<HumanBodyBones, RigBone>();
foreach (HumanBodyBones bone in humanBone) {
rigBone[bone] = new RigBone(humanoid,bone);
}
savedHumanoidRotation = humanoid.transform.rotation;
trackingSegment = new Dictionary<HumanBodyBones,Vector3>(targetBone.Length);
trackingState = new Dictionary<HumanBodyBones, int>(targetBone.Length);
}
private void swapJoint(int a, int b) {
Vector3 tmp = joint[a]; joint[a] = joint[b]; joint[b] = tmp;
int t = jointState[a]; jointState[a] = jointState[b]; jointState[b] = t;
}
public void set(float[] jt, int[] st, int offset, bool mirrored, bool move) {
if (isSavedPosition == false && jointState[JointType_SpineBase] != TrackingState_NotTracked) {
isSavedPosition = true;
int j = offset * jointCount + JointType_SpineBase;
savedPosition = new Vector3(jt[j*3],jt[j*3+1],jt[j*3+2]);
}
for (int i=0; i<jointCount; i++) {
int j = offset * jointCount + i;
if (mirrored) {
joint[i] = new Vector3(-jt[j*3], jt[j*3+1], -jt[j*3+2]);
} else {
joint[i] = new Vector3(jt[j*3], jt[j*3+1], savedPosition.z*2-jt[j*3+2]);
}
jointState[i] = st[j];
}
if (mirrored) {
swapJoint(JointType_ShoulderLeft, JointType_ShoulderRight);
swapJoint(JointType_ElbowLeft, JointType_ElbowRight);
swapJoint(JointType_WristLeft, JointType_WristRight);
swapJoint(JointType_HandLeft, JointType_HandRight);
swapJoint(JointType_HipLeft, JointType_HipRight);
swapJoint(JointType_KneeLeft, JointType_KneeRight);
swapJoint(JointType_AnkleLeft, JointType_AnkleRight);
swapJoint(JointType_FootLeft, JointType_FootRight);
swapJoint(JointType_HandTipLeft, JointType_HandTipRight);
swapJoint(JointType_ThumbLeft, JointType_ThumbRight);
}
for (int i=0; i<targetBone.Length; i++) {
int s = jointSegment[2*i], e = jointSegment[2*i+1];
trackingSegment[targetBone[i]] = joint[e] - joint[s];
trackingState[targetBone[i]] = System.Math.Min(jointState[e],jointState[s]);
}
Vector3 waist = joint[JointType_HipRight] - joint[JointType_HipLeft];
waist = new Vector3(waist.x, 0, waist.z);
Quaternion rot = Quaternion.FromToRotation(Vector3.right,waist);
Quaternion rotInv = Quaternion.Inverse(rot);
Vector3 shoulder = joint[JointType_ShoulderRight] - joint[JointType_ShoulderLeft];
shoulder = new Vector3(shoulder.x, 0, shoulder.z);
Quaternion srot = Quaternion.FromToRotation(Vector3.right,shoulder);
Quaternion srotInv = Quaternion.Inverse(srot);
humanoid.transform.rotation = Quaternion.identity;
foreach (HumanBodyBones bone in targetBone) {
rigBone[bone].transform.rotation = rotInv * Quaternion.FromToRotation(Vector3.up,trackingSegment[bone]);
}
rigBone[HumanBodyBones.UpperChest].offset(srot);
Quaternion bodyRot = rot;
if (mirrored) {
bodyRot = Quaternion.AngleAxis(180,Vector3.up) * bodyRot;
}
humanoid.transform.rotation = bodyRot;
if (move == true) {
Vector3 m = joint[JointType_SpineBase];
if (mirrored) m = new Vector3(-m.x, m.y, -m.z);
humanoid.transform.position = m;
}
}
}
|
| RigControl.cs |
/*
* Copyright (c) 2017 Yoshihisa Nitta
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*/
/* http://nw.tsuda.ac.jp/lec/kinect2/ */
/* version 1.0: 2017/08/06 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
public class RigControl : MonoBehaviour {
[DllImport ("NtKinectDll")] private static extern System.IntPtr getKinect();
[DllImport ("NtKinectDll")] private static extern int setSkeleton(System.IntPtr kinect, System.IntPtr data, System.IntPtr state, System.IntPtr id);
int bodyCount = 6;
int jointCount = 25;
private System.IntPtr kinect;
public GameObject humanoid;
public bool mirror = true;
public bool move = true;
CharacterSkeleton skeleton;
void Start () {
kinect = getKinect();
skeleton = new CharacterSkeleton(humanoid);
}
void Update () {
float[] data = new float[bodyCount * jointCount * 3];
int[] state = new int[bodyCount * jointCount];
int[] id = new int[bodyCount];
GCHandle gch = GCHandle.Alloc(data,GCHandleType.Pinned);
GCHandle gch2 = GCHandle.Alloc(state,GCHandleType.Pinned);
GCHandle gch3 = GCHandle.Alloc(id,GCHandleType.Pinned);
int n = setSkeleton(kinect,gch.AddrOfPinnedObject(),gch2.AddrOfPinnedObject(),gch3.AddrOfPinnedObject());
gch.Free();
gch2.Free();
gch3.Free();
if (n > 0) {
skeleton.set(data,state,0,mirror,move);
}
}
}
|
から Resetを選択して、Position (x,y,z)=(0,0,0)とします。


Transform Position (x,y,z) = (0, 1, -2)
RigController に付加されている "Rig Control (Script)" コンポーネントの mirrored がチェックされていると、 人間の鏡像の動きに追随します。 mirrored のチェックをはずすと Humanoid はカメラに背を向けて、人間の動きに そのまま追随するようになります。 (注)実行中のUnityのウィンドウの右に小さく表示されているKinect V2の認識中の画面は すでに鏡像になったものです。
Humanoid の transform.rotation の値をプログラムで変更するので、 AsianBoy の Transform の Rotation の初期値はなんであっても構いません。
実行中のキャプチャ画面はこちら CheckNtKinectDll6d.mp4。実行の途中で mirrored のチェックをはずしています。
また、moveのチェックをはずすと人間の場所の移動には追随しなくなります。


[注意] 骨格や顔の認識状態を表示するためにDLL内でOpenCVのウィンドウを生成しています。 後から生成されたこのウィンドウにフォーカスがあるときは (= Unityのウィンドウにフォーカスがない場合は) Unityの画面は変化しないので注意して下さい。 Unityのウィンドウの上部をクリックしてUnityのウィンドウにフォーカスが ある状態で動作を試して下さい。
[注意2] CharacterSkeleton.cs の中でオプショナルなBoneである HumanBodyBones.Neck および HumanBodyBones.UpperChest にアクセスしています。 もしもお使いのHumanoidデータでこのBoneが設定できていない場合は、エラーとなります。
[注意3] このプログラムでは顔の向きが変化しません。 顔認識をして得られた顔の向きを HumanBodyBones.Head の向きに反映させた方がよいと思われますが、 ここでは説明を簡単にするために省略しています。
6人までの Humanoid を同時に動かすプロジェクトを作成します。
File -> Save Scence as -> humanoid2.unity
ここでは makehuman で作成した次のデータ makehuman2.zipを使います。 AfricanBoy, AfricanGirl, AsianGirl, CaucasianBoy, CaucasianGirl および texture/ をそれぞれimport して、Animation Type を Humanoid に変更します。

Position や Rotation をそれぞれ次のように設定します。 Position の Z 座標を -10 として、カメラに写らなくしているだけです。
| Model Name | Position | Rotation | ||||
|---|---|---|---|---|---|---|
| X | Y | Z | X | Y | Z | |
| AsianBoy | 0 | 0 | -10 | 0 | 0 | 0 |
| AsianGirl | 0 | 0 | -10 | 0 | 0 | 0 |
| AfricanBoy | 0 | 0 | -10 | 0 | 0 | 0 |
| AfricanGirl | 0 | 0 | -10 | 0 | 0 | 0 |
| CaucasianBoy | 0 | 0 | -10 | 0 | 0 | 0 |
| CaucasianGirl | 0 | 0 | -10 | 0 | 0 | 0 |

| RigControl2.cs |
/*
* Copyright (c) 2017 Yoshihisa Nitta
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*/
/* http://nw.tsuda.ac.jp/lec/kinect2/ */
/* version 1.1: 2017/08/10 */
/* version 1.0: 2017/08/06 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
public class RigControl2 : MonoBehaviour {
[DllImport ("NtKinectDll")] private static extern System.IntPtr getKinect();
[DllImport ("NtKinectDll")] private static extern int setSkeleton(System.IntPtr kinect, System.IntPtr data, System.IntPtr state, System.IntPtr id);
int bodyCount = 6;
int jointCount = 25;
private System.IntPtr kinect;
public GameObject[] humanoid = new GameObject[] {null, null, null, null, null, null};
public bool[] mirror = new bool[] {true, true, true, true, true, true};
public bool[] move = new bool[] {true, true, true, true, true, true};
CharacterSkeleton[] skeleton = new CharacterSkeleton[] {null, null, null, null, null, null};
void Start () {
kinect = getKinect();
for (int i=0; i<bodyCount; i++) {
if (humanoid[i] != null) {
skeleton[i] = new CharacterSkeleton(humanoid[i]);
}
}
}
void Update () {
float[] data = new float[bodyCount * jointCount * 3];
int[] state = new int[bodyCount * jointCount];
int[] id = new int[bodyCount];
GCHandle gch = GCHandle.Alloc(data,GCHandleType.Pinned);
GCHandle gch2 = GCHandle.Alloc(state,GCHandleType.Pinned);
GCHandle gch3 = GCHandle.Alloc(id,GCHandleType.Pinned);
int n = setSkeleton(kinect,gch.AddrOfPinnedObject(),gch2.AddrOfPinnedObject(),gch3.AddrOfPinnedObject());
gch.Free();
gch2.Free();
gch3.Free();
for (int i=0; i<bodyCount; i++) {
if (i < n && skeleton[i] != null) {
skeleton[i].set(data,state,i,mirror[i],move[i]);
} else {
humanoid[i].transform.position = new Vector3(0,0,-10);
}
}
}
}
|
