2011-12-19

OpenNI/NITEのボーントラッキングが劇的に進化していた件

第3回Kinectハッカソン無事に開催できました。レポートっぽい物はShibuya.NIのサイトにアップするとして、OpenNI + Xtion Pro liveで今日試した事など。

ポーズ不要

NITEを1.5.0 unstableにすると、ユーザー検出からポーズ検出をすっ飛ばしてボーントラッキングが開始できる様になった、以前まで必要だった学習データも不要。適当に動かしてみたら、たまたまその場にいた4人をいきなりトラッキングして普通に動いていた。重すぎて描画が遅れるという事も無かった。動画撮ればよかった。

Kinectポーズという単語は最早死語、「OpenNIはポーズを取らないと骨格取れないんですよ」とドヤ顔をする事はもうできませんね。

上半身のみボーントラッキングが動作した

トラッキング開始時にSkeletonCapabilityに渡すXnSkeletonProfileについて。大抵はXN_SKEL_PROFILE_ALLを渡して頭から足までのジョイントを得ると思うが、自分は 机に座る→作業→退席 の動作を正面からトラッキングしたかったので上半身だけで十分。下半身は見えないだろうし。
そこで、上半身のみのジョイントをトラッキングするXN_SKEL_PROFILE_UPPERを使うとこうなった。

f:id:hagino_3000:20111219020843p:image:w360

頭と手だけもいける

頭の位置を取りたいだけならXN_SKEL_PROFILE_HEAD_HANDSで十分だった。ユーザー検出と同時に頭部の中心座標が取れるので、用途によっては顔認識よりも便利。横顔でも後ろ頭でもいける。始終静止状態の人は検出できないけど。つまり、全身を入れてポーズを取るのが不要になったおかげで、上半身のみや、下半身のみしか見えない状況においてジョイントのトラッキングが可能になった。

正面を向いてるかどうか

OpenCVの物体検出で正面顔の学習データを使ってみた。頭の位置は既にわかっているので、うまくやれば計算量は減らせるはず。cvHaarDetectObjectsに検索領域とか指定できればと思ったけど、そんなオプション無かった。

顔の向き

頭部の深度の平均値を使ってなんとなく傾きを得る方法を@molant3さんに教えてもらった。

Xtion Pro liveは家電量販店でも買えます

中の人曰くそういう事だった。輸入した時は190ドルだったけど、日本価格25,000円なり。 Kinectよりも高いけど、重くてでかいKinectを毎日持ち歩きたくはないので自分はXtion Pro liveを使っています。

Mac App StoreにKinectアプリが増えていた。

3D Scannerというアプリ。Kinectが接続されていない、とかいうエラーが出て動作しない!! 170JPYが無駄に……!!

f:id:hagino_3000:20111219023812p:image:w360



このエントリーをはてなブックマークに追加

2011-12-09

WebSocketでバイナリデータを送受信してみる



この記事はHTML5 Advent Calendarの8日目です。

いつの間にか手元の環境(Chrome17 dev + Node 0.6.3)においてWebSocketでバイナリデータが扱える様になっていたので何か作ってみようかと。

まず、NodeでWebSocketを使おうとしたらそれ用のライブラリを使う事になるのだが現時点でバイナリデータが扱えるのはWebSocket-Nodeのみだった。

当初はサーバーから画像のRAWデータをガンガンクライアントに送りまくるというのを作ろうとしたのだがnode-pngがNodeの0.6系に対応していなかったので断念。CanvasのgetImageDataで得られるデータをそのままサーバーに投げて、サーバー側で加工した物を受け取るサンプルを作った。

f:id:hagino_3000:20111209034908p:image:w500

バイナリデータの送信


クライアントのコード(client.js)

var socket = null;

function bootstrap() {

// 適当な図形を描画
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
// (中略)

// Socketの初期化
socket = new WebSocket('ws://localhost:8082');
socket.binaryType = 'arraybuffer';
socket.onopen = function() {
send(ctx);
}
socket.onmessage = handleReceive;
};

function send(ctx) {
// RAWデータをそのまま送信
var data = ctx.getImageData(0, 0, 200, 200).data;
var byteArray = new Uint8Array(data);
socket.send(byteArray.buffer);
}


CanvasのgetImageDataで得られるデータは8bit4チャンネルのRGBAの数値配列。1ピクセルあたり要素は4つ、それぞれが0-255の値なので、これをそのままUint8Arrayのコンストラクタに渡した。見なれないのは次の行ですね。

socket.binaryType = 'arraybuffer';


サーバーでRAWデータを加工


サーバーのコード(server.js)

#!/usr/bin/env node
var WebSocketServer = require('websocket').server,
http = require('http'),
fs = require('fs');

// (中略)

wsServer = new WebSocketServer({
httpServer: server,
// デフォルトでは65535byte以上受けつけないので
// 値を増やしてみる
maxReceivedFrameSize: 0x1000000,
autoAcceptConnections: false
});

wsServer.on('request', function(request) {

var connection = request.accept(null, request.origin);

connection.on('message', function(message) {
if (message.type === 'utf8') {
// テキストデータを受信した場合
}
else if (message.type === 'binary') {
// バイナリデータを受信した場合
var data = message.binaryData;
var len = data.length;

// 受信したRAW画像をグレースケールにする
var buf = new Buffer(len);
for (var i = 0; i < len; i+=4 ) {
var r = data.readUInt8(i);
var g = data.readUInt8(i+1);
var b = data.readUInt8(i+2);
var y = Math.floor((77*r + 28*g + 151*b)/255);

// Canvasにそのまま投入するために
// 4チャンネル8ビットのRGBAにする
var v = y + (y << 8) + (y << 16) + (0xFF << 24);
buf.writeInt32LE(v, i);
}
// グレースケールにした物をクライアントに送信する
connection.sendBytes(buf);
}
});

connection.on('close', function(reasonCode, description) {
// (略)
});
});


受信したデータを8bitずつ読んで、それぞれRGBAとして処理する。加工したデータはNodeのBufferオブジェクトに値を書き込んで connection.sendBytesメソッドに渡す。writeInt32LEを使ってバッファに書き込んでいるのはビット演算子が使いたかっただけ。writeUInt8を4回やってもいい。

バイナリデータの受信


再びクライアントのコード(client.js)

WebSocketのonmessageイベントハンドラにセットした関数。

function handleReceive(message) {
// 受信したRAWデータをcanvasに
var c = resultCanvas = document.getElementById('result');
var ctx = c.getContext('2d');
var imageData = ctx.createImageData(200, 200);
var pixels = imageData.data;

var buffer = new Uint8Array(message.data);
for (var i=0; i < pixels.length; i++) {
pixels[i] = buffer[i];
}
ctx.putImageData(imageData, 0, 0);
}


サーバーでやったのと同様に8bitずつ値を取得したかったので、Uint8Arrayを使った。

まとめ


結果は上のスクリーンショットの通り。全てのコードはgistにアップしました。




WebSocket with binary data ― Gist

https://gist.github.com/1447986


とりあえずNodeのBufferオブジェクトとTyped Arrayの使い方を覚えればなんとかなりそう。これからは送信する前にbase64する手間が省けて嬉しいですね。





このエントリーをはてなブックマークに追加