Processing で Kinect や Xtion が使える Simple OpaneNI を試してみました
Procesing から Kinect や Xtion を使えるようにする SImple OpenNI(http://code.google.com/p/simple-openni/)を試してみました。
今回は以前勉強会用に作ったこれ(https://www.digifie.jp/blog/archives/579)を Simple OpenNI 版に改造してみました。
OpenNI 版では…
・Xtion と Flash
Xtion(OpenNI)→ AIR2.0(NativeProcess)を使用して…
Xtion で取得した手の座標を送信する簡易サーバをつくり…
RTMFP で Flash に値を送信しています。
という手順で Flash に Xtion から取得した座標情報を送っていたのですが、
今回の Simple OpenNI 版では…
・Xtion と Flash
Xtion(Simple OpenNI)→ TCPソケットを作って…
ソケット経由 で Flash に値を送信しています。
簡単ですね。
というわけで、こんな感じで動くようになりました。
以前の OpenNI 版と同様 RTMFP 経由で iPhone も使用していますが、この部分については これ と同じです。
そして今回使用した Processing のコードです。
import SimpleOpenNI.*; import processing.net.*; SimpleOpenNI context; // NITE XnVSessionManager sessionManager; XnVFlowRouter flowRouter; PointDrawer pointDrawer; // Server server ; Client client ; Boolean isPointTracking; void setup() { context = new SimpleOpenNI(this); // mirror is by default enabled context.setMirror(true); // enable depthMap generation if(context.enableDepth() == false) { println("Can't open the depthMap, maybe the camera is not connected!"); exit(); return; } // enable the hands + gesture context.enableGesture(); context.enableHands(); // setup NITE sessionManager = context.createSessionManager("Click,Wave", "RaiseHand"); pointDrawer = new PointDrawer(); flowRouter = new XnVFlowRouter(); flowRouter.SetActive(pointDrawer); sessionManager.AddListener(flowRouter); size(context.depthWidth(), context.depthHeight()); smooth(); server = new Server(this, 5204) ; } void draw() { background(200,0,0); // update the cam context.update(); // update nite context.update(sessionManager); // draw depthImageMap image(context.depthImage(),0,0); // draw the list pointDrawer.draw(); } void keyPressed() { switch(key) { case 'e': // end sessions sessionManager.EndSession(); println("end session"); break; } } ///////////////////////////////////////////////////////////////////////////////////////////////////// // session callbacks void onStartSession(PVector pos) { println("onStartSession: " + pos); } void onEndSession() { println("onEndSession: "); } void onFocusSession(String strFocus,PVector pos,float progress) { println("onFocusSession: focus=" + strFocus + ",pos=" + pos + ",progress=" + progress); } ///////////////////////////////////////////////////////////////////////////////////////////////////// // PointDrawer keeps track of the handpoints class PointDrawer extends XnVPointControl { HashMap _pointLists; int _maxPoints; color[] _colorList = { color(255,0,0),color(0,255,0),color(0,0,255),color(255,255,0)}; public PointDrawer() { _maxPoints = 30; _pointLists = new HashMap(); } public void OnPointCreate(XnVHandPointContext cxt) { // create a new list addPoint(cxt.getNID(),new PVector(cxt.getPtPosition().getX(),cxt.getPtPosition().getY(),cxt.getPtPosition().getZ())); println("OnPointCreate, handId: " + cxt.getNID()); } public void OnPointUpdate(XnVHandPointContext cxt) { //println("OnPointUpdate " + cxt.getPtPosition()); addPoint(cxt.getNID(),new PVector(cxt.getPtPosition().getX(),cxt.getPtPosition().getY(),cxt.getPtPosition().getZ())); } public void OnPointDestroy(long nID) { println("OnPointDestroy, handId: " + nID); // remove list if(_pointLists.containsKey(nID)) _pointLists.remove(nID); } public ArrayList getPointList(long handId) { ArrayList curList; if(_pointLists.containsKey(handId)) curList = (ArrayList)_pointLists.get(handId); else { curList = new ArrayList(_maxPoints); _pointLists.put(handId,curList); } return curList; } public void addPoint(long handId,PVector handPoint) { ArrayList curList = getPointList(handId); curList.add(0,handPoint); if(curList.size() > _maxPoints) curList.remove(curList.size() - 1); } public void draw() { if(_pointLists.size() <= 0) { isPointTracking = false; return; } else { isPointTracking = true; } pushStyle(); noFill(); PVector vec; PVector firstVec; PVector screenPos = new PVector(); int colorIndex=0; // draw the hand lists Iterator<Map.Entry> itrList = _pointLists.entrySet().iterator(); while(itrList.hasNext()) { strokeWeight(2); stroke(_colorList[colorIndex % (_colorList.length - 1)]); ArrayList curList = (ArrayList)itrList.next().getValue(); // draw line firstVec = null; Iterator<PVector> itr = curList.iterator(); beginShape(); while (itr.hasNext()) { vec = itr.next(); if(firstVec == null) firstVec = vec; // calc the screen pos context.convertRealWorldToProjective(vec,screenPos); vertex(screenPos.x,screenPos.y); //println("X:" + screenPos.x + " Y:" + screenPos.y); } endShape(); // draw current pos of the hand if(firstVec != null) { strokeWeight(8); context.convertRealWorldToProjective(firstVec,screenPos); point(screenPos.x, screenPos.y); } colorIndex++; } popStyle(); updateSocket(screenPos.x,screenPos.y); } // ----- Socket Sender ------ void updateSocket(float px, float py) { if( !isPointTracking ) return; String str = int(px) + "," + int(py); server.write(str) ; println(str); } // ----- Socket Receiver ------ void receivedSocket() { if (client != null) { String data = client.readString(); if (data != null) { println(data); //rcvString = data; } } } void serverEvent(Server srv, Client clt) { println("connected") ; if (client != null) server.disconnect(client) ; client = clt ; } }
Simple OpenNI に付いていた Hands.pde にソケット送信部分を追加しています。
Flash 側のソケット受信部分はこんな感じ。
private var _sock:Socket; private var magniX:Number = 1024 / 640; private var magniY:Number = 768 / 480; private var _px:int; private var _py:int; // setup private function socketSetup():void { _sock = new Socket(); _sock.addEventListener( IOErrorEvent.IO_ERROR, ioError ); _sock.addEventListener( ProgressEvent.SOCKET_DATA, receive_data ); _sock.connect( "localhost", 5204 ); function ioError( e:IOErrorEvent ):void { trace( e ); } } // receiver private function receive_data(e:ProgressEvent):void { var str:String = _sock.readMultiByte( _sock.bytesAvailable, "utf-8" ); var list:Array = str.split( "," ); if(list[0] >= 0 && list[1] >= 0) { _px = list[0] * magniX; _py = list[1] * magniY; } }
同じような方法でスケルトントラキングで取得した関節の情報も比較的簡単に取得できるので、スケルトンの情報を使用してコンテンツ的に成立するようなネタが出来たら(今はネタがありませんので…)こちらも試してみようと思っています。