Kinect で遊ぼう
はじめに
Kinect とは
Microsoft の Xbox というゲーム機には Kinect(キネクト)というデバイスが用意されています。「カラダまるごとコントローラー」というコンセプトで、ボタンなどを使わずに全身を使ってゲームを操作します。主にダンスやスポーツ系のゲームで使われます。
Kinect は体の動きを測る「モーションキャプチャ・システム」です。映画などでリアルに動く CG のキャラクターは、モーションキャプチャ・システムで測った役者の動きを CG にあてはめたものです。このようなプロ用のモーションキャプチャ・システムは数百万円~数千万円しますが、Kinect は 1.5万円くらいです。ゲームのコントローラとしては高価ですが、モーションキャプチャ・システムとしては激安です(性能はプロ用に及びませんが)。さらに Kinect は Windows のパソコンでも使えるため、体の動きを使った楽しいソフトウェアやアート作品などを作ることができます。
ここで使う Kinect
Kinect には二種類のバージョンがあります。旧型の Xbox 360 Kinect センサー(通称 Kinect v1)と、現行型の Xbox One Kinect センサー(通称 Kinect v2)です。
ここでは旧型の v1 を使います。v2 のほうが高性能ですが、Windows パソコンにつなぐためのアダプターが入手困難です。実は Kinect は v1 も v2 も「いつ無くなってもおかしくない」状態ですが、さいわいまだ v1 も入手可能です(例えばコチラ)。中古品もそこそこ流通しています(約 4,000円~)。
Kinect を使ったプログラムは C++ や C# という言語で作るのが一般的ですが、Processing で使えるようにした便利なライブラリがあります。遊んでみましょう!
今回の内容
目標
Kinect で人物の位置と全身の関節の位置を測ります。最終的に、手からオーラのような光を出せるようになります。
使うもの
- Windows のパソコン(Windows 7 以上)
- Xbox 360 Kinect センサー(通称 Kinect v1)(例えばコチラ)
- インターネット環境(いくつかソフトウェアをダウンロードするため)
- Processing(3.0 以上、コチラを参考にインストール)
ステップ
1. Kinect を使う準備
Windows パソコンで Kinect を使うためのドライバと、Processing で Kinect を扱うためのライブラリをインストールします。ドライバをインストールするので、管理者権限のあるユーザとして Windows にサインインしておきます。
ドライバのインストール
Kinect v1 を使うためのドライバ(Kinect for Windows SDK)のバージョンは 1.8 です。2.0 というバージョンもありますが、これは Kinect v2 用で、v1 は使えません。Microsoft のドライバのダウンロードページにアクセスします。
「Continue」ボタンをクリックすると以下のように「登録のおすすめ」という画面になりますが、登録しなくても問題ありません。「● No, I do not want to register」を選び「Next」をクリックするとダウンロードが始まります。
ダウンロードされた「KinectSDK-v1.8-Setup.exe」を実行すればインストールできます。なお、インストールの最後の画面で「次の手順: 開発者ツールキットをダウンロードする」と書かれていますが、必要ありません。
Kinect4WinSDK ライブラリのインストール
Processing で Kinect を扱うために「Kinect4WinSDK」というライブラリを使います。Processing を起動して、「スケッチ」メニュー -> ライブラリをインポート -> ライブラリを追加 を選びます。
出てきた「Contribution Manager」というウィンドウの「Libraries」タブの左上のテキストボックスに「kinect」と入力すると、ライブラリがいくつか表示されます。その中の「Kinect4WinSDK」を選択して、右下の「Install」をクリックします。するとダウンロードされ、自動的にインストールされます。
最後に、Kinect をパソコンに接続しましょう。
これで準備 OK です。
2. 人物を抜き出す
まずは Kinect の基本、人物を抜き出すプログラムを作ります。
Kinect のカメラ画像を取得する
Processing に以下のコードを入力して、kinect_image.pde という名前で保存しましょう。それぞれのコードが何をしているのか、コード中のコメント文を見て考えながら入力していきましょう。
import kinect4WinSDK.*; // kinect4WinSDK ライブラリを使う Kinect kine = new Kinect(this); // Kinect オブジェクト "kine" の宣言 void setup() { size(640, 480); // ウィンドウのサイズ設定 } void draw() { background(0); // 背景を黒く塗りつぶす image(kine.GetImage(), 0, 0, 640, 480); // Kinect のカラー画像を描画 }
入力し終えたら実行してください(▶をクリック)。以下のように Kinect のカメラの映像が表示されれば成功です。
Kinect のマスク画像を取得する
Kinect はこの映像を分析して人物だけを抜き出してくれます。以下のようにコードを書き換えましょう。書き換えるのは 11行目だけ、GetImage を GetMask にするだけです。
import kinect4WinSDK.*; // kinect4WinSDK ライブラリを使う Kinect kine = new Kinect(this); // Kinect オブジェクト "kine" の宣言 void setup() { size(640, 480); // ウィンドウのサイズ設定 } void draw() { background(0); // 背景を黒く塗りつぶす image(kine.GetMask(), 0, 0, 640, 480); // Kinect の人物マスク画像を描画 }
入力し終えたら実行してください(▶をクリック)。Kinect の前に(1.5m くらい離れて)立つと、以下のように、人物の部分だけが抜き出された画像が表示されます。
人物と背景画像の合成
Kinect はこの抜き出した人物画像をさらに分析して、全身の関節の位置などを検出してくれます。が、その前にちょっとお遊びします。以下のようにコードを追加しましょう。追加するのは、4行目、8行目、13行目です。
import kinect4WinSDK.*; // kinect4WinSDK ライブラリを使う Kinect kine = new Kinect(this); // Kinect オブジェクト "kine" の宣言 PImage backImage; // 背景画像用の PImage "backImage" の宣言 void setup() { size(640, 480); // ウィンドウのサイズ設定 backImage = loadImage("back.jpg"); // 背景画像の読み込み } void draw() { background(0); // 背景を黒く塗りつぶす image(backImage, 0, 0, 640, 480); // 背景画像を描画 image(kine.GetMask(), 0, 0, 640, 480); // Kinect の人物マスク画像を描画 }
実行する前に、なんでもよいので画像ファイル(jpg)を用意してください。写真でも、ネットからダウンロードした画像でも構いません。無ければこれをダウンロードして使ってください。
kinect_image.pde があるフォルダの中に data という名前のフォルダを作って、用意した画像を入れてください。画像のファイル名は "back.jpg" にしてください。
準備ができたら実行してください(▶をクリック)。Kinect の前に(1.5m くらい離れて)立つと、以下のように合成されます。楽しいですね。
3. 人物の位置の検出
Kinect で人物を抜き出せたので、次はその人物がいる位置を測ってみましょう。
プログラミング
さきほどの kinect_image.pde を「別名で保存」で kinect_position.pde という名前で保存しましょう。そして以下のようにコードを追加しましょう。
一気に長くなりましたが、頑張りましょう。それぞれのコードが何をしているのか、コード中のコメント文を見て考えながら入力していきましょう。
import kinect4WinSDK.*; // kinect4WinSDK ライブラリを使う Kinect kine = new Kinect(this); // Kinect オブジェクト "kine" の宣言 SkeletonData user; // SkeletonData オブジェクト "user" の宣言 PImage backImage; // 背景画像用の PImage "backImage" の宣言 void setup() { size(640, 480); // ウィンドウのサイズ設定 backImage = loadImage("back.jpg"); // 背景画像の読み込み user = new SkeletonData(); // SkeletonData のインスタンス化 } void draw() { background(0); // 背景を黒く塗りつぶす image(backImage, 0, 0, 640, 480); // 背景画像を描画 image(kine.GetMask(), 0, 0, 640, 480); // Kinect の人物マスク画像を描画 drawPosition(); // 関数 drawPosition() を実行 } // 人物が検出された時に実行される appearEvent 関数 void appearEvent(SkeletonData sd) { user = sd; // 検出された人物をuserに入れる } // 人物がいなくなった時に実行される disappearEvent 関数 void disappearEvent(SkeletonData sd) { user = null; // userを空(null)にする } // 人物の位置を描画する drawPosition 関数 void drawPosition() { if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } // 人物の位置に円を描く colorMode(RGB); // 色の指定を RGB 形式にする noStroke(); // 線は描かない fill(255, 127, 0); // 塗りつぶす色の指定 (Red, Green, Blue) ellipse(user.position.x * width, // 円の中心のx座標(userのx座標は0~1で得られる) user.position.y * height, // 円の中心のy座標(userのy座標は0~1で得られる) 30, 30); // 円の幅, 高さ // 人物の位置情報をテキストで描く textSize(48); // 文字サイズの設定 text("ID:" + user.dwTrackingID + "/ " + // userのID nf(user.position.x, 1, 2) + ", " + // userのx座標(nfで小数第二位まで表示) nf(user.position.y, 1, 2) + ", " + // userのy座標(nfで小数第二位まで表示) round(user.position.z / 100) + "cm", // userのz座標(距離:x100cmで得られるので100で割り四捨五入) 20, 450); // 文字列の表示位置 (x, y) }
入力し終えたら実行してください(▶をクリック)。Kinect の前に(1.5m くらい離れて)立つと、以下のように人物のヘソのあたりにオレンジ色の円が描かれます。また、ウィンドウの下のほうに、人物の ID, X 座標, Y 座標, Kinect からの距離が表示されます。
解説
Kinect が検出した人物に関する様々なデータは SkeletonData というオブジェクトに中に入ってきます。4行目で user という名前の SkeletonData オブジェクトを作っています。また、10行目でその user を初期化しています。初期化したばかりの user の中身は空っぽ(null)です。
appearEvent という関数は、Kinect が人物を検出すると自動で実行されます。人物が検出されたら、検出された人物のデータを user に入れています。これでこの人物の様々なデータを取得できるようになります。
また、disappearEvent という関数は、Kinect のカメラから人物がいなくなった時に自動で実行されます。ここで user を空っぽ(null)に戻しています。
17行目で drawPosition という関数(31~49行目)を呼んでいます。drawPosition 関数は検出された人物(user)の位置を描画しています。人物の位置(座標値)は SkeletonData(user)の position というプロパティに入っています。position.x(横方向)と position.y(縦方向)の値は 0~1 の範囲です。これらの値は以下の図のように、Kinect のカメラ画像の左上が 0 で、x は右端が1、y は下端が 1 です。position.x にウィンドウの幅の値(width)、position.y にウィンドウの高さの値(height)を掛けることで、ウィンドウ上の座標値に変換することができます。
position.z は Kinect から人物までの距離です。例えば 1m なら 10000 という値が入ってきます。100 で割って cm 単位にして表示しています(10 で割れば mm に、1000 で割れば m にできます)。
dwTrackingID は検出している人物の ID です。Kinect は複数の人物を検出することができて、検出した人物に別々の ID を割り当てます。この ID を指定することで特定の人物の情報を取得できます。ただ、このページのプログラムは一人の人物しか扱わない仕様です。複数人のデータを扱ってみたい場合は、Kinect4WinSDK ライブラリのサンプルやライブラリ作者のサイトで調べてみましょう。
4. 関節(骨格)の検出
ようやくここから Kinect の本領発揮です。人物の関節(骨格)を検出して、全身の様々な部位の位置を測ってみましょう。
プログラミング
さきほどの kinect_position.pde を「別名で保存」で kinect_skeleton.pde という名前で保存しましょう。そして以下のようにコードを追加しましょう。追加するのは、18行目と、53~96行目です。頑張りましょう!
import kinect4WinSDK.*; // kinect4WinSDK ライブラリを使う Kinect kine = new Kinect(this); // Kinect オブジェクト "kine" の宣言 SkeletonData user; // SkeletonData オブジェクト "user" の宣言 PImage backImage; // 背景画像用の PImage "backImage" の宣言 void setup() { size(640, 480); // ウィンドウのサイズ設定 backImage = loadImage("back.jpg"); // 背景画像の読み込み user = new SkeletonData(); // SkeletonData のインスタンス化 } void draw() { background(0); // 背景を黒く塗りつぶす image(backImage, 0, 0, 640, 480); // 背景画像を描画 image(kine.GetMask(), 0, 0, 640, 480); // Kinect の人物マスク画像を描画 drawPosition(); // 関数 drawPosition() を実行 drawSkeleton(); // 関数 drawSkeleton() を実行 } // 人物が検出された時に実行される appearEvent 関数 void appearEvent(SkeletonData sd) { user = sd; // 検出された人物をuserに入れる } // 人物がいなくなった時に実行される disappearEvent 関数 void disappearEvent(SkeletonData sd) { user = null; // userを空(null)にする } // 人物の位置を描画する drawPosition 関数 void drawPosition() { if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } // 人物の位置に円を描く colorMode(RGB); // 色の指定を RGB 形式にする noStroke(); // 線は描かない fill(255, 127, 0); // 塗りつぶす色の指定 (Red, Green, Blue) ellipse(user.position.x * width, // 円の中心のx座標(userのx座標は0~1で得られる) user.position.y * height, // 円の中心のy座標(userのy座標は0~1で得られる) 30, 30); // 円の幅, 高さ // 人物の位置情報をテキストで描く textSize(48); // 文字サイズの設定 text("ID:" + user.dwTrackingID + "/ " + // userのID nf(user.position.x, 1, 2) + ", " + // userのx座標(nfで小数第二位まで表示) nf(user.position.y, 1, 2) + ", " + // userのy座標(nfで小数第二位まで表示) round(user.position.z / 100) + "cm", // userのz座標(距離:x100cmで得られるので100で割り四捨五入) 20, 450); // 文字列の表示位置 (x, y) } // 関節(骨格)描画する drawPosition 関数 void drawSkeleton() { if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } colorMode(RGB); // 色の指定を RGB 形式にする noStroke(); // 線は描かない fill(255, 255, 255); // 塗りつぶす色の指定 (Red, Green, Blue) // 関節の位置に円を描く(右肩、右肘、右手首、右手) ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].y * height, 10, 10); ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].y * height, 10, 10); ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].y * height, 10, 10); ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height, 10, 10); // 関節の間に線を描く stroke(255, 255, 255); // 線を描く (Red, Green, Blue) strokeWeight(3); // 先の太さ noFill(); // 塗りつぶさない line(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].y * height, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].y * height); line(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].y * height, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].y * height); line(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].y * height, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height); // 右手の位置に skeletonPositions 情報を表示 textSize(30); // 文字サイズ text(nf(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x, 1, 2) + ", " + nf(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y, 1, 2) + ", " + round(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].z / 100) + "cm", user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height); }
入力し終えたら実行してください(▶をクリック)。Kinect の前に(1.5m くらい離れて)立つと、以下のように右腕の関節(肩、肘、手首、手)に白い円が示され、骨のように線でつながれて表示されます。また、右手の位置に、右手の X 座標, Y 座標, Kinect からの距離が表示されます。
解説
drawSkeleton 関数(53~96行目)で、右肩、右肘、右手首、右手の四つの関節に白い円(ellipse)を描いています。また、それらをつなぐ骨のような線(line)を描いています。さらに、右手の位置に右手の座標をテキスト(text)で表示しています。
Kinect が検出した関節の情報は、SkeletonData(user)の中の skeletonPositions[関節] プロパティに入っていて、[ ] 内に関節名または ID を指定することで取得できます。例えば関節名 Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT (ID: 8) を指定すると右肩の位置の情報が取得できます。右手は Kinect.NUI_SKELETON_POSITION_HAND_RIGHT (ID: 11) です。関節名で指定するとコードが長くなりますが意味が分かりやすく、ID で指定するとコードは短くなりますが意味が分かりにくくなります。Kinect が検出できる 20個の関節(骨格)の一覧は以下です。
ID | 部位 | 関節名 |
---|---|---|
0 | 腰中心 | NUI_SKELETON_POSITION_HIP_CENTER |
1 | 脊柱 | NUI_SKELETON_POSITION_SPINE |
2 | 肩中心 | NUI_SKELETON_POSITION_SHOULDER_CENTER |
3 | 頭 | NUI_SKELETON_POSITION_HEAD |
4 | 左肩 | NUI_SKELETON_POSITION_SHOULDER_LEFT |
5 | 左肘 | NUI_SKELETON_POSITION_ELBOW_LEFT |
6 | 左手首 | NUI_SKELETON_POSITION_WRIST_LEFT |
7 | 左手 | NUI_SKELETON_POSITION_HAND_LEFT |
8 | 右肩 | NUI_SKELETON_POSITION_SHOULDER_RIGHT |
9 | 右肘 | NUI_SKELETON_POSITION_ELBOW_RIGHT |
10 | 右手首 | NUI_SKELETON_POSITION_WRIST_RIGHT |
11 | 右手 | NUI_SKELETON_POSITION_HAND_RIGHT |
12 | 左腰 | NUI_SKELETON_POSITION_HIP_LEFT |
13 | 左膝 | NUI_SKELETON_POSITION_KNEE_LEFT |
14 | 左足首 | NUI_SKELETON_POSITION_ANKLE_LEFT |
15 | 左足 | NUI_SKELETON_POSITION_FOOT_LEFT |
16 | 右腰 | NUI_SKELETON_POSITION_HIP_RIGHT |
17 | 右膝 | NUI_SKELETON_POSITION_KNEE_RIGHT |
18 | 右足首 | NUI_SKELETON_POSITION_ANKLE_RIGHT |
19 | 右足 | NUI_SKELETON_POSITION_FOOT_RIGHT |
skeletonPositions[関節] に入っているデータの種類は人物の位置(position)と同じです。skeletonPositions[関節].x にはその関節の横方向の値(0~1)、skeletonPositions[関節].y には縦方向の値(0~1)、skeletonPositions[関節].z には Kinect からその関節までの距離(1m なら 10000)が入っています。
5. 手からオーラの光を出す
関節の位置まで検出できたので、あとはアイデア次第で楽しいものを作ってください。
と、いきなり放置ではなんですので、例えばの遊びとして、右手からオーラのような光を発するアニメーションを付けた事例(kinect_aura.pde)を紹介します。上で作った kinect_skeleton.pde を「別名で保存」してから始めると効率的でしょう。詳しくは解説しませんので、コードとコメント文から解読してください。以下は完成イメージです。
import kinect4WinSDK.*; // kinect4WinSDK ライブラリを使う Kinect kine = new Kinect(this); // Kinect オブジェクト "kine" の宣言 SkeletonData user; // SkeletonData オブジェクト "user" の宣言 PImage backImage; // 背景画像用の PImage "backImage" の宣言 int auraNum = 60; // 表示するオーラ(円)の数 int auraColor = 0; // オーラの色(HSB の H:色相) PVector[] aura; // オーラの座標(x,y)と色(z)を格納する3次元ベクトルデータの配列 void setup() { size(640, 480); // ウィンドウのサイズ設定 backImage = loadImage("back.jpg"); // 背景画像の読み込み user = new SkeletonData(); // SkeletonData のインスタンス化 aura = new PVector[auraNum]; // オーラデータ配列の要素を auraNum 個作る for(int i = 0; i < auraNum; i++) { // すべてのオーラデータ配列要素について aura[i] = new PVector(0, 0, 0); // オーラデータの値の初期化 } } void draw() { background(0); // 背景を黒く塗りつぶす image(backImage, 0, 0, 640, 480); // 背景画像を描画 image(kine.GetMask(), 0, 0, 640, 480); // Kinect の人物マスク画像を描画 drawPosition(); // 関数 drawPosition() を実行 drawSkeleton(); // 関数 drawSkeleton() を実行 drawAura(); // 関数 drawAura() を実行 } // 人物が検出された時に実行される appearEvent 関数(変更なし) void appearEvent(SkeletonData sd) { user = sd; // 検出された人物をuserに入れる } // 人物がいなくなった時に実行される disappearEvent 関数(変更なし) void disappearEvent(SkeletonData sd) { user = null; // userを空(null)にする } // 人物の位置を描画する drawPosition 関数(変更なし) void drawPosition() { if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } // 人物の位置に円を描く colorMode(RGB); // 色の指定を RGB 形式にする noStroke(); // 線は描かない fill(255, 127, 0); // 塗りつぶす色の指定 (Red, Green, Blue) ellipse(user.position.x * width, // 円の中心のx座標(userのx座標は0~1で得られる) user.position.y * height, // 円の中心のy座標(userのy座標は0~1で得られる) 30, 30); // 円の幅, 高さ // 人物の位置情報をテキストで描く textSize(48); // 文字サイズの設定 text("ID:" + user.dwTrackingID + "/ " + // userのID nf(user.position.x, 1, 2) + ", " + // userのx座標(nfで小数第二位まで表示) nf(user.position.y, 1, 2) + ", " + // userのy座標(nfで小数第二位まで表示) round(user.position.z / 100) + "cm", // userのz座標(距離:x100cmで得られるので100で割り四捨五入) 20, 450); // 文字列の表示位置 (x, y) } // 関節(骨格)描画する drawPosition 関数(変更なし) void drawSkeleton() { if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } colorMode(RGB); noStroke(); fill(255, 255, 255); // 関節の位置に円を描く(右肩、右肘、右手首、右手) ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].y * height, 10, 10); ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].y * height, 10, 10); ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].y * height, 10, 10); ellipse(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height, 10, 10); // 関節の間に線を描く stroke(255, 255, 255); strokeWeight(3); noFill(); line(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_SHOULDER_RIGHT].y * height, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].y * height); line(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_ELBOW_RIGHT].y * height, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].y * height); line(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_WRIST_RIGHT].y * height, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height); // 右手の位置に skeletonPositions 情報を表示 textSize(30); text(nf(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x, 1, 2) + ", " + nf(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y, 1, 2) + ", " + round(user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].z / 100) + "cm", user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width, user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height); } // オーラを描く drawAura 関数(新規) void drawAura() { if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } noStroke(); // 線は描かない for(int i = auraNum - 1; i >= 1; i--) { // オーラデータを1個前のデータに置き換える aura[i].x = aura[i-1].x; // つまりデータが次々後ろの要素に移動していく aura[i].y = aura[i-1].y; // 古いデータを次々と後ろに移動させていって aura[i].z = aura[i-1].z; // 最後には古いデータから順番に消える } // オーラデータの最初の要素 [0] に最新データを入れる aura[0].x = user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].x * width; aura[0].y = user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].y * height; colorMode(HSB); // 色の指定を HSB 形式にする auraColor += 1; // オーラの色を H(色相)1個分変える if(auraColor > 255) { // 色相の指定は 0-255 なので auraColor = 0; // 255 を超えていたらまた 0 に戻す } aura[0].z = auraColor; // 新しいオーラの色をオーラデータに代入 for(int i = auraNum - 1; i >= 0; i--) { // auraNum 個分のオーラ(円を描く)処理 if(user == null) { // userが空なら return; // 何もしないで関数を抜ける } fill(aura[i].z, 255, 255, 10 - int(10/auraNum)*i); // 古いオーラほど薄くする(不透明度を下げる) ellipse(aura[i].x, aura[i].y, // ↓ 近いほど大きな円で描く 300 - user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].z / 100, 300 - user.skeletonPositions[Kinect.NUI_SKELETON_POSITION_HAND_RIGHT].z / 100); } }
まとめ
Kinect のプログラミング、いかがでしたか?
プログラムが長いと感じたかもしれませんが、紹介したプログラム中のほとんどは円や線を描くための処理でした。人物の位置や関節(骨格)の位置は、Kinect オブジェクトと SkeletonData オブジェクトのインスタンス(kine や user)を作ったら自動的に取得できてしまいます。あとは本当に工夫次第といったところです。ぜひいろんなアイデアを試してみましょう。
例えば、
- 画面の中にピアノの画像を置いて、どの鍵盤のところに手があるかを判定して音を鳴らす
- 画面の中でアイテムの画像を移動させて、アイテムに手や足が触れたら得点が得られる
- 関節の相対的な位置関係を比較すればポーズが分かりますから(例えば右手が頭より上にあるポーズなら「手を挙げた」と分かりますから)、ポーズに応じたアニメーションや魔法効果を出す
- かめはめ波を撃つ
- 手が一定値以上近くに来たら空中の仮想のボタンを押したりなど、体でパソコンを操作する
- 全身の形や体格が分かりますから、頭に CG のお面をかぶせたり服を着せるなど、変身する
- (今回は紹介しませんでしたが)複数の人物を検出できるので、対戦ゲームにする
- 普通のカメラ画像も撮れるので、AR と組み合わせてみる
などなど、楽しい応用を考えてみましょう。
実際に Kinect を使ったインタラクティブアート作品が数多く発表されています。単にゲームのコントローラとして使えるだけでなく、ちょっと不思議な体験が得られるデバイスですから、アーティストも Kinect をよく使っています。
ネットには Kinect を使った様々なサンプルの紹介があります。多くは C++ や C# で書かれたサンプルかもしれません。でも、やっていることをなんとな~く理解して、Processing でやるならどうすればいいだろう?と、自分で翻訳してみましょう。
最初にも書きましたが、Kinect はもう「いつ無くなってもおかしくない」状態、つまり、本体が生産中止になったり、ドライバ配付などのサポートが無くなってしまう可能性があります。しかし、こんな楽しいデバイスなので、ぜひ遊び続けましょう!
補足
Kinect は開発言語だけでなくドライバにも様々な種類があります。例えばネットの記事で、今回使った Microsoft の「Kinect for Windows SDK v1.8」というドライバでなく、「OpenNI」というドライバを使うものが多くあります。以前は OpenNI しかなく(Microsoft 純正のドライバが無く)、皆 OpenNI を使っていました。今は Microsoft のドライバを使いましょう。両方のドライバをインストールしてしまうと Kinect は動かないので注意しましょう。また、今回用いた Kinect4WinSDK ライブラリは Microsoft のドライバでしか動作しません。
Kinect で「手の指」までは取得できません(Kinect v2 では「親指」と「手先」の座標が取得できます)。手指の動きを測るには LeapMotion(リープ・モーション)などの別のセンサを使います。Kinect と組み合わせると、指先の動きも使ってもっと面白いものができるかもしれません。また、Kinect では顔の表情なども取得できません。ただし、普通のカメラ画像が取得できますから、顔画像から表情を分析するプログラムを作れば(何らかのサンプルを参考にして)、体だけでなく表情も使った面白いものができるかもしれません。
なお、上で作ったコードはすべてこちら(GitHub)に置いてありますから、参考にしてください。