MacでKinect v2を使う -2015 AW-

2015/12/15 追記:
“準備"の箇所で、ofxMultiKinectV2が依存しているアドオン"ofxTurboJpeg"の情報が抜けていたので追加しました。

tech.kayac.com Advent Calendar 2015 13日目担当の @seiya_vw です。

昨日担当のドレッドヘア に変にハードルをあげられましたが、気にせず行きます。

はじめに

僕は今はアプリチーム所属ですが、実際にやっている仕事はアプリだけでなく
ハードウェアやインスタレーション的なものも関わっています。
そこで今日は、最近よく使うopenframeworksを用いてkinect v2をmacで動かす話をします。
ありがちな内容ですが、このテーマには一応理由があります。

kinectくらいメジャーなRGB-Dカメラなら使ってみたいと
思ったことがある人も少なくないと思いますが、検索してみても

  • それっぽい記事あっても古い
  • 結局Windows上でしか無理な雰囲気がある

などなど、 macユーザーだから諦めている人もいる気がするので、
わりと新しめの情報として、少しでもためになればと思ってます。

macでどこまでできるかを見ていきたいと思います。
(結論から言うと、OS Xでは100%の機能は利用できないです)
そして最後におまけ的に、結局bootcampのWindowsでvvvvというツールを利用して 簡単にkinect v2を利用する例も載せます。

技術的にはさほど難しくない内容なので
さくっと見ていていただければ幸いです!

準備

環境

  • MacBook Pro (Retina, 13-inch, Mid 2014)
  • Kinect for Windows v2 (今は販売終了、Xbox One版のみのはず)
  • openframeworks ver 0.9.0

oFでkinectを使う準備

kinectを利用するのに、ofxMultiKinectV2というaddonを利用します。

1.ofxMultiKinectV2と、依存しているofxTurboJpegをaddonフォルダにインストール
2. 以下をBuild Phases > Linked Frameworks and Librariesに追加
-OpenCL.framework
-usb-1.0.0-superspeed.a from ofxMultiKinectV2/libs/libusb/lib/osx
-libturbojpeg.dylib from ofxTurboJpeg/libs/turbo-jpeg/lib/osx
3. Build Phases > Copy Files にlibturbojpeg.dylibを追加して、ターゲットを"Framework"に。

と、ここまででOKなはずですが、

ld: library not found for -lturbojpeg

というエラーが出る場合は、
Build Settings > Search Paths > Library Search Paths に
../../addons/ofxTurboJpeg/libs/turbo-jpeg/lib/osx
を追加してライブラリのパスを通して下さい。

ofxMultiKinectV2で提供されている機能

インターフェースを見る限り、kinectとしては

  • depthへのアクセス

が提供されているくらいのよう。それでも十分、面白いことはできると思います。

実装してみる

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxGui.h"
#include "ofxMultiKinectV2.h"

class ofApp : public ofBaseApp{

    // ~~略~~
   
    ofxMultiKinectV2 kinect;
    ofEasyCam cam;
    ofVboMesh mesh;
    
    ofxPanel gui;
    ofxFloatSlider minDistance;
    ofxFloatSlider maxDistance;
 
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    
    ofSetVerticalSync(true);
    ofSetFrameRate(60);
    
    kinect.open(false, true, 0);
    kinect.start();
    
    mesh.setUsage(GL_DYNAMIC_DRAW);
    mesh.setMode(OF_PRIMITIVE_POINTS);
    
    cam.setAutoDistance(false);
    cam.setDistance(200);
    
    minDistance = 10;
    maxDistance = 150;
    gui.setup();
    gui.add(minDistance.setup("minDistance", minDistance, 0, 500));
    gui.add(maxDistance.setup("maxDistance", maxDistance, 0, 500));

}

//--------------------------------------------------------------
void ofApp::update(){
    
    kinect.update();
    
    if (kinect.isFrameNew()) {
        
        mesh.clear();
        
        int st = 2;
        int w = kinect.getDepthPixelsRef().getWidth();
        int h = kinect.getDepthPixelsRef().getHeight();
        int minD = minDistance;
        int maxD = maxDistance;
        
        for (int x = 0; x < w; x+=2) {
            for (int y = 0; y < h; y+=2) {
                float d = kinect.getDistanceAt(x, y);
                if (d > minD && d < maxD) { // adjust depth range as you like
                    ofVec3f p = kinect.getWorldCoordinateAt(x, y, d);
                    mesh.addColor(ofColor(255));
                    mesh.addVertex(p);
                }
            }
        }
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    ofClear(0);
    
    if (mesh.getVertices().size()) {
        ofPushStyle();
            glPointSize(2);
            cam.begin();
                ofPushMatrix();
                    ofTranslate(0, 0, -100);        
                    mesh.draw();
                ofPopMatrix();
            cam.end();
        ofPopStyle();
    }
    
    ofDrawBitmapStringHighlight("fps: " + ofToString(ofGetFrameRate()), ofGetWidth() - 120, 20);
    gui.draw();
}

結果

できた。depth取れてます。

cap1.jpg

遊んでみる

ポイントクラウド的なものがでてもおもんない、って感じなので 無理矢理FFTと組み合わせます。

※無理矢理なのでなんの工夫もありません
※選曲チョイスがめんどうなのでinputはファイルではなくマイクです

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxGui.h"
#include "ofxMultiKinectV2.h"
#include "ofxFFTLive.h"

class ofApp : public ofBaseApp{

     // ~~略~~

    ofxMultiKinectV2 kinect;
    ofEasyCam cam;
    ofVboMesh mesh;
    
    ofxPanel gui;
    int pointSize;
    ofxFloatSlider minDistance;
    ofxFloatSlider maxDistance;
    
    ofxFFTLive fftLive;
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    
    ofSetVerticalSync(true);
    ofSetFrameRate(60);
    
    kinect.open(false, true, 0);
    kinect.start();
    
    mesh.setUsage(GL_DYNAMIC_DRAW);
    mesh.setMode(OF_PRIMITIVE_POINTS);
    
    cam.setAutoDistance(false);
    cam.setDistance(200);
    
    pointSize   = 2;
    minDistance = 10;
    maxDistance = 150;
    gui.setup();
    gui.add(minDistance.setup("minDistance", minDistance, 0, 500));
    gui.add(maxDistance.setup("maxDistance", maxDistance, 0, 500));

    fftLive.setup();
    fftLive.setThreshold(1.0);
    fftLive.setPeakDecay(0.915);
    fftLive.setMaxDecay(0.995);
    fftLive.setMirrorData(true);
}

//--------------------------------------------------------------
void ofApp::update(){
    
    fftLive.update();
    
    kinect.update();
    
    int numOfVerts = mesh.getNumVertices();
    float * audioData = new float[numOfVerts];
    fftLive.getFftPeakData(audioData, numOfVerts);
    float av = fftLive.getAveragePeak();
    pointSize = (int)ofMap(av, 0.0, 0.5, 2.0, 10.0);
    
    if (kinect.isFrameNew()) {
        
        mesh.clear();
        
        int st = pointSize;
        int w = kinect.getDepthPixelsRef().getWidth();
        int h = kinect.getDepthPixelsRef().getHeight();
        int minD = minDistance;
        int maxD = maxDistance;
        
        for (int x = 0; x < w; x+=st) {
            for (int y = 0; y < h; y+=st) {
                float l = audioData[x+y];
                float d = kinect.getDistanceAt(x, y);
                if (d > minD && d < maxD) { // adjust depth range as you like
                    ofVec3f p = kinect.getWorldCoordinateAt(x, y, d);
                    float hue = ofMap(l, 0.0, 1.0, 0.0, 255.0, true); // convert to hue
                    ofColor c;
                    c.setHsb(hue, 255, 255);
                    mesh.addColor(c);
                    mesh.addVertex(p);
                }
            }
        }
    }
    
    delete[] audioData;
}

//--------------------------------------------------------------
void ofApp::draw(){
    
    ofClear(0);
    
    if (mesh.getVertices().size()) {
        ofPushStyle();
            glPointSize(pointSize);
            cam.begin();
                ofPushMatrix();
                    float av = fftLive.getAveragePeak() * 0.3 + 1.0;
                    ofScale(av, av);
                    ofTranslate(0, 0, -100);
                    mesh.draw();
                ofPopMatrix();
            cam.end();
        ofPopStyle();
    }
    
    ofDrawBitmapStringHighlight("fps: " + ofToString(ofGetFrameRate()), ofGetWidth() - 120, 20);
    gui.draw();
}

結果

雑だけど音楽聴きながらだとまあまあ。

vnk9s.gif

おまけ篇 - vvvvでkinect v2

vvvvは、Max/MSPのようなビジュアルプログラミングです。
vimeoに作品もいろいろ上がってます。
最近騒ぎになったウィルスに名前が似ていることから認知度もあがったのではないでしょうか。(参考)
個人的には最近は簡単なビジュアライゼーションのモックなどはこちらで作ることもあります。

上記のMacbook Pro上で、

  • Windows 8.1(Bootcamp)
  • vvvv (64bit)
  • DX11 Pack + Kinect2 Nodes

を利用。

下のキャプチャくらいまで作業するだけで様々なデータが取得できます。 こちらはbodyやskelton, handのAPIも利用できます。

vvvv.jpg

さいごに

いかがでしょうか。というか、結局雑な記事になってしまいすみません。
多少なりとも誰かのためになれば幸いです。
明日の担当は somtd さんです。
乞うご期待!