Skip to content

Kinectでキミもアイアンマンになれる!?(実装編)/バイナリとソース付き

先日予告編を公開したKinectでMacを操作するアプリの開発記事がいよいよ登場だ。

実行形式はもちろん、ソースコードも開発者の田中君が解説してくれるぞ!

手のひらをかざせばマウスカーソルが動き、グーでクリック。グーのまま移動させればドラッグだ。

手を素早く前に押し出すとExposé。このアプリを起動してKinectをUSB端子に接続するだけで、君もアイアンマンになった気分を味わえる!?

—————————————————————————————————————————————-

こんにちは。東京大学教養学部の田中諒です。

さっそくですが、予告のあったKinectアプリの全容を解説しましょう。ofxKinectに入っているexampleをベースに作っていきます。

まず手の位置の認識から。

Kinectからグレースケール画像として深度マップを取得し、OpenCVを使って解析します。しきい値を使うと特定の深度の区間において物体が存在するかをチェックできるので、手前側から区間を少しづつずらしながらチェックを繰り返すことで手とおぼしき物体を検出することができます。

grayImage.setFromPixels(kinect.getDepthPixels(), kinect.width, kinect.height);

for (depth = 255; depth > 220; depth -= 5) {
grayThreshFar = grayImage;
grayThreshNear = grayImage;
grayThreshNear.threshold(depth, true);
grayThreshFar.threshold(depth-15);
cvAnd(grayThreshNear.getCvImage(), grayThreshFar.getCvImage(), grayThresh.getCvImage(), NULL);
grayThresh.flagImageChanged();
contourFinder.findContours(grayThresh, 1000, (kinect.width*kinect.height)/20, 3, false);

if (contourFinder.nBlobs) {
break;
}
}

さて、手の位置がわかったら、それにもとづいてマウスを動してみましょう。

blobの座標をスクリーンの座標に置き換えてOS側にMouseEventを投げればいいのですが、そのまま座標を送るとかなりがたついてしまうので移動平均を利用します。

int dispWidth = CGDisplayPixelsWide(kCGDirectMainDisplay);
int dispHeight = CGDisplayPixelsHigh(kCGDirectMainDisplay);

CGPoint point;
point.x = blob.centroid.x * dispWidth / kinect.width;
point.y = blob.centroid.y * dispHeight / kinect.height;
if (point.x < 0) point.x = 0;
if (point.y < 0) point.y = 0;
if (point.x >= dispWidth) point.x = dispWidth-1;
if (point.y >= dispHeight) point.y = dispHeight-1;

if (int n = trail.size()) {
point.x *= n+1;
point.y *= n+1;
for (int i = 0; i < n; i++) {
point.x += trail[i].x * (i+1);
point.y += trail[i].y * (i+1);
}
point.x /= (n+1)*(n+2)/2;
point.y /= (n+1)*(n+2)/2;
}

trail.push_back(point);
if (trail.size() > 8) trail.pop_front();

ドラッグもできると素敵ですね。

正方形に近くかつ中が詰まってる状態のblobを握りこぶしと判断することで、手を握ったり開いたりという動作でドラッグアンドドロップするようにします。

float ratio   = blob.boundingRect.height / blob.boundingRect.width;
float density = blob.area / blob.boundingRect.width / blob.boundingRect.height;
if (!drag) {
if (0.8 < ratio && ratio < 1.3 && density > 0.65) {
CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, point, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
drag = true;
} else {
CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, point, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
}
} else {
if (density > 0.5) {
CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDragged, point, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
} else {
CGEventRef event = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, point, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, event);
drag = false;
}
}

さらに、手を突き出すだけでExposéしたら楽しそうです。やってみましょう。

depths.push_back(depth);
if (depths.size() > 8) depths.pop_front();
if (!exposed && depth > 240 && depth - depths.front() > 15) {
exposed = depth;
expose();
}
if (exposed && exposed - depth > 10) {
exposed = 0;
expose();
}

expose()では最終手段AppleScriptでExposéを発動させています(KeyBoardEventだとうまくいかなかった…)。

NSAppleScript*          script;
NSAppleEventDescriptor* desc;
NSDictionary*           error;
script = [[NSAppleScript alloc] initWithSource:[NSString stringWithCString:"tell application \"System Events\" to key code 101"]];
desc = [script executeAndReturnError:&error];
[script release];

そしてできたものが以下です。Kinectをつないで起動すればあなたもアイアンマンに!

文・田中諒

このエントリーをはてなブックマークに追加
はてなブックマーク - Kinectでキミもアイアンマンになれる!?(実装編)/バイナリとソース付き
Post to Google Buzz
Share on GREE

Related posts:

  1. kinectでキミもアイアンマンになれる!?(予告編)

Comments Closed