小嶋秀樹 | 授業情報 | 研究室
日本語 | English

このページは,諸般の事情で Mac のみに対応した新しい内容にしてあります.少し古い内容のWindows 向けページもありますが,動作未保証です.

openFrameworks 応用編(3mac)

ここでは,より高度なアドオンの扱いかたを,ofxOpenCv と ofxOpenNI を題材として学んでいきます.ofxOpenCv は openFrameworks に標準添付された画像処理用のアドオンです.ofxOpenNI は(前回に取り上げた ofxTrueTypeFontUC と同じように)外部から入手(ダウンロード)した Kinect 用のアドオンです.

注意:準備作業が抜けている人をよく見かけます.Xcode を正しくインストールしてください.Apple の Developer サイトから無料で入手できます.openFrameworks は最新版をダウンロードしてください.ここでは Xcode 8.1 + openFrameworks 0.9.8 の利用を想定しています.

ofxOpenCv の扱いかた

ofxOpenCv は openFrameworks から OpenCV を利用できるようにするアドオンです.OpenCV は広く普及している画像処理ライブラリです.顔検出をはじめ,輪郭抽出やノイズ除去など,さまざまな画像処理に関する関数からなります.

【サンプルプログラムを動かす】

Xcode を起動し,OF_PATH > examples > addons > opencvExample > opencvExample.xcodeproj を開いてください.下図のように,すでに addons・ ofxOpenCv などがプロジェクトに組み込まれています.またインクルードファイル(そしてライブラリファイル)の参照先もすでに登録されています.

デバッグ開始(緑矢印のボタン)を押せば,コンパイルに多少時間がかかるかもしれませんが,やがて,以下のようなアプリケーションが動作するはずです.

このプログラムは,手の動きを影絵のようにカメラで撮影した動画ファイルを読み込み,それを再生(左上)しながら,グレースケール画像に変換(右上)し,あらかじめ記録しておいた背景画像(左中)との差分を2値化(右中)し,さらに右下に,その輪郭を緑色で,またその全体を包み込む長方形を赤色で表示しています.

発展課題: ofApp.h の 7 行目の行頭にある // を外し,この行(#define _USE_LIVE_VIDEO)を有効にして,再度プログラムを実行してください.カメラが必要です.この行によって _USE_LIVE_VIDEO が「定義」されます.すると,ofApp.h や ofApp.cpp の中で,

#ifdef _USE_LIVE_VIDEO
    この部分がプログラムとして有効になる
#else
    この部分は無視される
#endif

のように,Xcode から見たプログラムを変えることができます.カメラの有る無しで,プログラムを自動的に変更しているわけです.(#で始まる行は,Cプログラムの一部ではなく,Cコンパイラに送り込むソースプログラムを改変するための,プリプロセッサ指令と呼ばれるものです.)

カメラを有効にしたプログラムを実行し,背景だけを撮影している状態でスペースを押してください.これで背景(左中)が登録されます.手や顔を撮影し,+ / − のキーで2値化のための閾値を調整してください.

【顔検出に挑戦】

OpenCV を代表する機能のひとつに,Haar 検出器を使った顔検出があげられます.まずは,サンプルプログラムを動かしてみましょう.今回は,dojoApps にコピーして動かします.OF_PATH > examples > addons > opencvHaarFinderExample を dojoApp にコピーし,Xcode から開いて,実行してみてください.

ほぼ9割以上の確率で,人間の顔(正立した正面顔)を検出できています.誤り検出も比較的少ない感じです.Haar 検出器は,顔の部分的な明暗パターンをチェックし,それを顔の各部分について逐次実行していきます.ハズレが出たら顔でないとして終了,最後までチェックにパスしたら顔です.このような処理を,画像の各位置について,さまざまな大きさの顔を想定して,実行しています.

つぎに,このプログラムをカメラ入力に対応するように改造してみましょう.ofApp.h と ofApp.cpp をつぎのように変更します.

#pragma once
#include "ofMain.h"
#include "ofxCvHaarFinder.h"
class ofApp : public ofBaseApp {
private:
    ofVideoGrabber camera;
    ofImage image;
    ofxCvHaarFinder finder;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    ofSetWindowShape(320, 240);
    camera.setDeviceID(0);
    camera.initGrabber(320, 240);
    finder.setup("haarcascade_frontalface_default.xml");
}
void ofApp::update() {
    camera.update();
    //  camera -> image
    image.setFromPixels(camera.getPixels().getData(), 320, 240, OF_IMAGE_COLOR);
    //  face detection
    finder.findHaarObjects(image, 40, 40);
}
void ofApp::draw() {
    //  draw image
    ofSetColor(255, 255, 255);
    image.draw(0, 0);
    //  draw markers
    ofSetColor(255, 0, 0); 
    ofSetLineWidth(3);
    ofNoFill();
    for(int i = 0; i < finder.blobs.size(); i++) {
        ofRectangle cur = finder.blobs[i].boundingRect;
        ofDrawRectangle(cur.x, cur.y, cur.width, cur.height);
    }
}
(... 以下変更なし ...)

finder は Haar 検出器(ofxCvHaarFinder クラスのインスタンス)です.ofApp::setup() の中で,顔の特徴を記述した xml ファイルを読み込んでいます.このファイルを入れ替えれば,任意の物体(たとえば歩行者や車)などを検出することも可能です.

320x240 という小さめの画像ですが,顔検出は計算量が大きいため,せいぜい毎秒あたり数フレームの処理速度になっていると思います.ちなみに,finder.findHaarObjects(image, 40, 40); の 40 は,検出すべき顔の最小の幅と高さです.40x40 よりも小さな顔は検出対象になりません.デフォルトは 0x0 なので,この変更により計算量はかなり減らしています.また,finder.blobs.size() によって検出された顔の数を,また finder.blobs[i].boundingRect は i 番目の顔の矩形領域(ofRectangle)を返します.ofRectangle は,メンバ変数として float x, y, width, height をもつクラスです.

発展課題1: 毎秒あたりに処理されるフレーム数(fps: frames per second)を計算し,画面に表示するように改造します.ofGetElapsedTimeMillis() は,プログラムが起動してからの経過時間をミリ秒(ms)を単位とする整数値で返す関数です.

#pragma once
#include "ofMain.h"
#include "ofxCvHaarFinder.h"
class ofApp : public ofBaseApp {
private:
    ofVideoGrabber camera;
    ofImage image;
    ofxCvHaarFinder finder;
    int msec;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    (... 変更なし ...)
    finder.setup("haarcascade_frontalface_default.xml");
    msec = ofGetElapsedTimeMillis();
}
void ofApp::update() {
    (... 変更なし ...)
}
void ofApp::draw() {
    //  draw image
    (... 変更なし ...)
    //  compute fps
    int msecNow = ofGetElapsedTimeMillis();
    float fps = 1000.0 / (msecNow - msec);
    msec = msecNow;
    //  draw fps and markers
    ofSetColor(255, 0, 0); 
    char buf[100];
    sprintf(buf, "%5.2f fps", fps);
    ofDrawBitmapString(buf, 20, 20);
    ofSetLineWidth(3);
    ofNoFill();
    for(int i = 0; i < finder.blobs.size(); i++) {
        ofRectangle cur = finder.blobs[i].boundingRect;
        ofDrawRectangle(cur.x, cur.y, cur.width, cur.height);
    }
}
(... 以下変更なし ...)

発展課題2: このままではあまり芸がないので,顔を検出したら,その目の位置あたりを塗りつぶすように改造します.以下のように ofApp::draw() を改造してみてください.

void ofApp::draw () {
    //  draw image
    (... 変更なし ...)
    //  compute fps
    (... 変更なし ...)
    //  draw fps and markers
    (... 変更なし ...)
    //ofNoFill();
    for(int i = 0; i < finder.blobs.size(); i++) {
        ofRectangle cur = finder.blobs[i].boundingRect;
        ofDrawRectangle(cur.x, cur.y + cur.height * 0.3, 
                        cur.width, cur.height * 0.2);
    }
}
【ofxOpenCv アプリケーションの開発】

ofxOpenCv を使ったアプリケーションを新しく開発するには,OF_PATH > projectGenerator_osx > projectGenerator.app を使うと便利です.これを起動し,プロジェクトの名前を決め,必要なアドオンにチェックを入れ,最後に "GENERATE PROJECT" のボタンを押します.すると OF_PATH > apps > myApps の中に新しいプロジェクトのフォルダが用意され,その中に必要なファイルが生成されます.

たとえば,カメラ画像を取り込み,そのネガ(ピクセルごとに RGB の各値を 255 から減じたもの)を表示することを考えます.下図の左が元のカメラ画像,右が反転した画像です.

プログラム(ofApp.h, ofApp.cpp)は,つぎのようになります.ofApp.h でインクルードするファイルは "ofxOpenCv.h" とするのがよいでしょう.ofxOpenCv における画像クラスが,ofImage ではなく,ofxCvColorImage や ofxCvGrayscaleImage となることに注意してください.ofVideoGrabber や ofImage との画像のやりとりは,setFromPixels メソッドを使うとよいでしょう.

#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
class ofApp : public ofBaseApp {
private:
    ofVideoGrabber camera;
    ofxCvColorImage image;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofSetWindowShape(1280, 480);
    //  camera
    camera.initGrabber(640, 480);
    //  allocate images
    image.allocate(640, 480);
}
void ofApp::update() {
    //  camera
    camera.update();
    image.setFromPixels(camera.getPixels().getData(), 640, 480);
    //  inversion
    image.invert();
}
void ofApp::draw() {
    camera.draw(0, 0, 640, 480);
    image.draw(640, 0, 640, 480);
}
(... 以下変更なし ...)

もうひとつの例として,ofxOpenCv の内部にある OpenCV の機能を利用するプログラムを紹介します.カメラ画像をグレースケール画像に変換し,その各ピクセルを4階調に変換します.さらに Canny フィルタによってエッジを検出し,そのエッジを黒線で上書きすることで,マンガ調の画像をつくりだすというものです.

そのプログラムは,下のようになります.cvCanny() は OpenCV の関数です.画像オブジェクトのメソッドの形式ではなく,画像データ等を引数とした関数となっている点に注意してください.ofxOpenCv の画像オブジェクト(grayImage など)を OpenCV の関数に渡すには,grayImage.getCvImage() とする必要があります.Canny() は,grayImage.getCvImage() を入力画像データとし,エッジ検出した結果(黒背景にエッジ部分が白線で描かれたもの)が edgeImage.getCvImage() に出力されます.edgeImage.dialate() はそのエッジを太くするためのものです.また,for 文の中では,4階調への変換とエッジの描画(黒線として描画)を行っています.ピクセルデータへのアクセス方法は,ofImage の場合と同じです.

#pragma once
#include "ofMain.h"
#include "ofxOpenCv.h"
class ofApp : public ofBaseApp {
private:
    ofVideoGrabber camera;
    ofxCvColorImage colorImage;
    ofxCvGrayscaleImage grayImage, edgeImage;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofSetWindowShape(1280, 480);
    //  camera
    camera.initGrabber(640, 480);
    //  allocate images
    grayImage.allocate(640, 480);
    edgeImage.allocate(640, 480);
}
void ofApp::update() {
    //  camera
    camera.update();
    colorImage.setFromPixels(camera.getPixels().getData(), 640, 480);
    grayImage = colorImage;
    grayImage.blur();
    //  canny filter
    cvCanny(grayImage.getCvImage(), edgeImage.getCvImage(), 20, 100);
    edgeImage.flagImageChanged();
    edgeImage.dilate();
    //  posterization
    unsigned char *grayData = grayImage.getPixels().getData();;
    unsigned char *edgeData = edgeImage.getPixels().getData();;
    for (int i = 0; i < 640*480; i++) {
        if (edgeData[i] == 0)
            grayData[i] = (grayData[i] / 64) * 64 + 63;
        else
            grayData[i] = 0;
    }
    grayImage.flagImageChanged();
}
void ofApp::draw() {
    //  draw camera image
    camera.draw(0, 0, 640, 480);
    //  draw manga image
    grayImage.draw(640, 0, 640, 480);
}
(... 以下変更なし ...)

ofImage のピクセルデータを書き換えた場合,最後に update() を呼び出す必要がありましたが,同様に,ofxOpenCv の画像オブジェクトを OpenCV 関数によって変更したり,あるいはピクセルデータを直接書き換えた場合は,最後に flagImageChanged() メソッドを呼び出す必要があります.

【ofxOpenCv をもっと活用する】

ofxOpenCv で提供されているのは OpenCV の一部ですが,顔検出や輪郭検出などを簡単に利用できます.一方,OpenCV が提供する多彩な画像演算機能(フィルタなど cv で始まる関数群)も,上述した方法によって利用することができます.これらを組み合わせて,魅力的なアプリケーションを開発してください.日本語で書かれた OpenCV に関するウェブサイトとして,OpenCV.jp があります.マニュアルやサンプルプログラムなど豊富ですので,参考にしてください.

ofxOpenNI による Kinect の利用
図

Kinect はピクセルごとの深度(対象物までの距離)を計測できる特殊なカメラです.深度画像のほかに,通常のカラー画像を取得するカメラもあり,その両方を同時に取得することができます.実売1万円強の安価なデバイスですが,インタラクティブなシステムをつくる上でさまざまな活用が可能です.ここでは,ofxOpenNI というアドオンを使って,Kinect から深度画像・カラー画像を取得する方法や,深度画像からスケルトン情報(人物の各関節の位置情報)を取得する方法などを解説します.

【準備編】

Mac OS から ofxOpenNI を利用するには,以下の作業を(やはり安定したインターネット接続環境のもとで)行なってください.

  1. gameoverhack/ofxOpenNI をダウンロードし解凍する.
  2. 解凍したらフォルダ名を ofxOpenNI として OF_PATH > addons の中に入れる.
【ofxOpenNI アプリケーションの開発】

ちょっと面倒ですが,つぎにあげる手順にしたがって作業を進めれば,ofxOpenNI を使ったアプリケーションを製作できます.

  1. projectGenerator(OF_PATH > projectGenerator_osx > projectGenerator.app を開き,ofxOpenNI に組み込んだ新しいプロジェクト kinectExample1 を生成します.
  2. このプロジェクトフォルダ(OF_PATH > apps > myApps > kinectExample1)の中の,bin/data の中に,OF_PATH > addons > ofxOpenNI > examples > opeNI-SimpleExample > bin/data の中にある openni をコピーします.
  3. また,OF_PATH > addons > ofxOpenNI > mac > copy_to_data_openni_path > lib フォルダを,OF_PATH > apps > myApps > kinectExample1 > bin/data/openni の中にコピーします.この bin/dta/openni の中には,lib と config の2つのフォルダが入っているはずです.
  4. つぎに,OF_PATH > apps > myApps > kinectExample1 > kinectExample1.xcodeproj を開き,左端カラムにある「Project Navigator」にある addons の三角を解きます.addons の中に ofxOpenNI フォルダがあり,さらにその中に(閉じていれば開くと)src フォルダが入っています.この ofxOpenNI フォルダを右クリックし,2つの外部フォルダを登録("Add Files to kinectExample1")します.このとき,Options ボタンを押して,"Create groups" がオンになっていることを確認してください.加えるフォルダは,ひとつは OF_PATH > addons > ofxOpenNI > include フォルダです.もうひとつは OF_PATH > apps > myApps > kinectExample1 > bin/data/openni の中にある lib フォルダです.
  5. さらに,左端カラムにある「Project Navigator」の上段にある kinectExample1 をクリックし,"Build Settings" を開きます."Search Path" の欄まで降りていき,"Library Search Path" の三角を解きます.内部に "Debug" と "Release" があるので,それぞれをダブルクリックし,ポップアップしたリストの最下段までスクロールして,$(BUILT_PRODUCTS_DIR)/data/openni/lib の1行を書き加えます.すでに加えられていた場合は,スラッシュが抜けていないか確認してください.
  6. 最後に,ofxOpenNI に含まれる古い関数名 ofGetGLTypeFromPixelFormat を新しいもの ofGetGLFormatFromPixelFormat に修正します.修正箇所は,以下のとおりです.
    • OF_PATH > addons > ofxOpenNI > src > ofxOpenNI.cpp の 1189行目・1191行目・1219行目・1221行目
    • OF_PATH > addons > ofxOpenNI > src > ofxOpenNITypes.cpp の 274行目・680行目

これで準備完了です.あとは,以下にあげる ofApp.h と ofApp.cpp 等の内容を書いていけば,オリジナルの ofxOpenNI アプリケーションを作成することができます.つぎにあげる

【ofxOpenNI を活用する:その1「距離画像の取得」】

ofxOpenNI の最もシンプルな利用は,距離画像の取得です.Kinect は,ミリメートルを単位として,画像(640x480)の各ピクセル位置について,物体までの距離をセンシングすることができます.その結果を表示するプログラム(ofApp.h, ofApp.cpp)は,つぎのようになります.

kinectExample1
#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofBackground(0, 0, 0);
    ofSetWindowShape(640, 480);
    ofSetFrameRate(30);
    //  setup ofxOpenNI
    kinect.setup();
    kinect.setRegister(true);
    kinect.setMirror(true);             //  flip horizontally
    kinect.addDepthGenerator();         //  required for depth image
    //  start kinect
    kinect.start();
}
void ofApp::update() {
    //  you need to call update()
    kinect.update();
}
void ofApp::draw() {
    //  draw depth image
    kinect.drawDepth(0, 0, 640, 480);   //  depth image (in color)
}
(... 以下変更なし ...)

まず,ofApp::setup() の中で kinect.setup() と kinect.setRegistered(true) を呼び出します.kinect.setMirror() での設定(鏡像=左右反転)はお好み次第です.kinect.addDepthGenerator() によって,距離情報の取得が kinect に組み込まれます.以上の設定の後,kinect.start() を呼び出してください.これで Kinect が動作を始めます.その後は,kinect.update() を呼び出すたびに,距離画像が取得され,kinect.draw() によって疑似カラーをつけて距離画像が描画されます.

【ofxOpenNI を活用する:その2「距離の測定」】

Kinect による距離画像を活用する一例として,マウスクリックした位置について物体までの距離を表示することを考えます.文字表示には ofTrueTypeFont を使います.あらかじめ,Macintosh HD > Library > Fonts > Microsoft > Arial.ttf を bin/data にコピーしておいてください.プログラム(ofApp.h, ofApp.cpp)の変更点は,つぎのとおりです.

#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
    ofTrueTypeFont font;
    char buffer[100];
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofBackground(0, 0, 0);
    ofSetWindowShape(640, 480);
    ofSetFrameRate(30);
    //  setup ofxOpenNI
    kinect.setup();
    kinect.setRegister(true);
    kinect.setMirror(true);
    kinect.addDepthGenerator();
    //  start kinect
    kinect.start();
    //  font and buffer
    font.loadFont("Arial.ttf", 20);
    sprintf(buffer, "");
}
void ofApp::update() {
    kinect.update();
}
void ofApp::draw() {
    //  draw depth image
    ofSetColor(255, 255, 255);
    kinect.drawDepth(0, 0, 640, 480);
    //  draw depth data
    ofSetColor(255, 127, 127);
    font.drawString(buffer, 20, 40);
}
(... 途中省略 ...)
void ofApp::mousePressed(int x, int y, int button){
    unsigned short *depthData = kinect.getDepthRawPixels().getData();
    unsigned short depthMM = depthData[y * 640 + x];
    sprintf(buffer, "%d mm", depthMM);
}
(... 以下変更なし ...)

マウスクリックされた位置 (x, y) について,Kinect から得られる距離データ(unsigned short)を取得しています.mm を単位とする非負整数で,10000mm まで計測できるはずです.この数値を文字配列 buffer に書き入れ,ofApp::draw() で画面表示するようにしています.画面表示が見づらい場合は,以下のように ofApp::draw() を書き換えてみてください.文字の周りに黒い縁取りを与えています.

void ofApp::draw () {
    //  draw depth image
    ofSetColor(255, 255, 255);
    kinect.drawImage(0, 0, 640, 480);
    //  draw depth data
    ofSetColor(0, 0, 0);
    for (int y = -2; y <= 2; y++)
        for (int x = -2; x <= 2; x++)
            font.drawString(buffer, 20 + x, 40 + y);
    ofSetColor(255, 127, 127);
    font.drawString(buffer, 20, 40);
}
【ofxOpenNI を活用する:その3「人物の切り抜き」】

つぎに,距離による人物の切り抜き手法を紹介します.これは,たとえば Kinect から 1m 以上,2m 未満の距離にある人物(あるいは物体)の映像だけを切り出し,あらかじめ用意しておいた背景に描き入れるというものです.まず最初に,距離画像と RGB 画像を並べて表示するプログラムをつくります.

#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofBackground(0, 0, 0);
    ofSetWindowShape(1280, 480);
    ofSetFrameRate(30);
    //  setup ofxOpenNI
    kinect.setup();
    kinect.setRegister(true);
    kinect.setMirror(true);
    kinect.addDepthGenerator();     //  required for depth image
    kinect.addImageGenerator();     //  required for RGB image
    //  start kinect
    kinect.start();
}
void ofApp::update() {
    kinect.update();
}
void ofApp::draw() {
    kinect.drawDepth(0, 0, 640, 480);
    kinect.drawImage(640, 0, 640, 480);
}
(... 以下変更なし ...)

実行時の距離画像と RGB 画像をよく見比べると,たとえば天井にある黒枠について,両者の位置がわずかにズレているのがわかります.これを補正するには,ofApp::setup() をつぎのように赤字部分を追加してください.オマジナイのように複雑ですが,RGB 画像の「視点」を距離画像にコピーしています.この結果,距離画像の一部が無効(距離 0)になってしまいますが,RGB 画像とのズレはほとんど 0 になるはずです.

void ofApp::setup() {
    //  window
    ofBackground(0, 0, 0);
    ofSetWindowShape(1280, 480);
    ofSetFrameRate(30);
    //  setup ofxOpenNI
    kinect.setup();
    kinect.setRegister(true);
    kinect.setMirror(true);
    kinect.addDepthGenerator();     //  required for depth image
    kinect.addImageGenerator();     //  required for RGB image
    //  align depth image to RGB image
    kinect.getDepthGenerator().GetAlternativeViewPointCap().SetViewPoint(kinect.getImageGenerator());
    //  start kinect
    kinect.start();
}

つぎに kirinuki という RGBA の ofImage オブジェクトをつくり,そこに Kinect からの RGB 画像をコピーするようにします.ただし kirinuki のピクセル型は RGBA,つまりアルファチャネルつきで,1 ピクセル 4 バイトです.実行結果は,上のプログラムと全く変わらないはずです.

#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
    ofImage kirinuki;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    (... 変更なし ...)
    //  start kinect
    kinect.start();
    //  kirinuki image (RGBA)
    kirinuki.allocate(640, 480, OF_IMAGE_COLOR_ALPHA);

}
void ofApp::update() {
    kinect.update();
    //  kirinuki (1000-2000mm)
    unsigned char *kirinukiData = kirinuki.getPixels().getData();
    unsigned char *imageData = kinect.getImagePixels().getData();
    for (int k = 0; k < 640*480; k++) {
        kirinukiData[k * 4 + 0] = imageData[k * 3 + 0];
        kirinukiData[k * 4 + 1] = imageData[k * 3 + 1];
        kirinukiData[k * 4 + 2] = imageData[k * 3 + 2];
        kirinukiData[k * 4 + 3] = 255;
    }
    kirinuki.update();
}
void ofApp::draw() {
    kinect.drawDepth(0, 0, 640, 480);
    kirinuki.draw(640, 0, 640, 480);
}
(... 以下変更なし ...)

各ピクセルのアルファチャネルには 255(不透明)をセットしていますが,これを 0 にすればそのピクセルは透明になります.この選択を,そのピクセルの距離に応じて決めてやればよいでしょう.つぎのように ofApp::update() を変更してください.距離が 1000mm〜2000mm となるピクセルだけを表示するようにしています.

void ofApp::update() {
    kinect.update();
    //  kirinuki (1000-2000mm)
    unsigned char *kirinukiData = kirinuki.getPixels().getData();
    unsigned char *imageData = kinect.getImagePixels().getData();
    unsigned short *depthData = kinect.getDepthRawPixels().getData();
    for (int k = 0; k < 640*480; k++) {
        kirinukiData[k * 4 + 0] = imageData[k * 3 + 0];
        kirinukiData[k * 4 + 1] = imageData[k * 3 + 1];
        kirinukiData[k * 4 + 2] = imageData[k * 3 + 2];
        //  set visible or invisible
        if (1000 <= depthData[k] && depthData[k] < 2000)
            kirinukiData[k * 4 + 3] = 255;  //  visible
        else
            kirinukiData[k * 4 + 3] = 0;    //  invisible
    }
    kirinuki.update();
}

最後に,せっかく透明化したので,適当な背景を入れてみましょう.適当な写真ファイル(例 photo.png)を用意して,bin/data に入れておいてください.プログラムはつぎのように変更します.

#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
    ofImage kirinuki, scenery;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    (... 変更なし ...)
    //  kirinuki image (RGBA)
    kirinuki.allocate(640, 480, OF_IMAGE_COLOR_ALPHA);
    //  background scenery
    scenery.loadImage("photo.png");
}
void ofApp::update() {
    (... 変更なし ...)
}
void ofApp::draw() {
    kinect.drawDepth(0, 0, 640, 480);
    scenery.draw(640, 0, 640, 480);
    kirinuki.draw(640, 0, 640, 480);
}
(... 以下変更なし ...)

背景画像 scenery を描画してから,同じ位置に,距離に応じて切り出した RGB 画像を上書きしています.透明部分(α = 0 の部分)は,ウラ側の背景が透過して見えるはずです.

【ofxOpenNI を活用する:その4「スケルトンを描く」】

Kinect の魅力のひとつは,人のスケルトン(各関節の3次元位置)を取得できることでしょう.ここでは,Kinect から得られたスケルトン情報を,ユーザ独自の方法で利用する方法を紹介します.つぎのプログラムは,RGB 画像の上にスケルトンを上書きするものです.

#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofBackground(0, 0, 0);
    ofSetWindowShape(640, 480);
    ofSetFrameRate(30);
    //  setup ofxOpenNI
    kinect.setup();
    kinect.setRegister(true);
    kinect.setMirror(true);
    kinect.addImageGenerator();     //  required for RGB image
    kinect.addDepthGenerator();     //  required for depth image
    kinect.addUserGenerator();      //  required for skeleton tracking
    kinect.setMaxNumUsers(1);       //  max num of skeleton to track
    //  start kinect
    kinect.start();
}
void ofApp::update() {
    kinect.update();
}
void ofApp::draw() {
    //  draw RGB image (weak)
    ofSetColor(100, 100, 100);
    kinect.drawImage(0, 0, 640, 480);
    //  draw skeleton (strong)
    ofSetColor(255, 255, 255);
    kinect.drawSkeletons(0, 0, 640, 480);
}
(... 以下変更なし ...)

なお,複数ユーザを同時にトラッキングするには,setMaxNumUsers(n) に上限となる人数を与えてください.ユーザごとのスケルトンは,ひとり目は drawSkeleton(x, y, w, h, 0),ふたり目は drawSkeleton(x, y, w, h, 1) のように,描き分けることができます.

スケルトンを自分独自の方法で描画するには,以下のプログラム(ofApp::draw())を参考にしてください.ここでは無背景とし,関節に赤いボールを,関節間に黄色い線分を表示するようにしています.

void ofApp::draw() {
    //  normal color
    ofSetColor(255, 255, 255);
    //  draw depth/RGB/skeletons images
    kinect.drawDepth(640, 0, 320, 240);
    //  draw user
    if (kinect.getNumTrackedUsers() > 0) {
        //  skeleton data
        ofxOpenNIUser user = kinect.getTrackedUser(0);
        //  draw limbs
        ofSetLineWidth(5);
        ofSetColor(255, 255, 127);
        for (int i = 0; i < user.getNumLimbs(); i++) {
            ofxOpenNILimb limb = user.getLimb((enum Limb) i);
            if (limb.isFound()) {
                float x1 = limb.getStartJoint().getProjectivePosition().x;
                float y1 = limb.getStartJoint().getProjectivePosition().y;
                float x2 = limb.getEndJoint().getProjectivePosition().x;
                float y2 = limb.getEndJoint().getProjectivePosition().y;
                ofLine(x1, y1, x2, y2);
            }
        }
        //  draw joints
        ofSetColor(255, 127, 127);
        for (int i = 0; i < user.getNumJoints(); i++) {
            ofxOpenNIJoint joint = user.getJoint((enum Joint) i);
            if (joint.isFound()) {
                float x = joint.getProjectivePosition().x;
                float y = joint.getProjectivePosition().y;
                ofDrawCircle(x, y, 20);
            }
        }
    }
}

関節の番号や名前,骨(関節間の線分)の番号や名前は,下表のようになっています.(旧版の ofxOpenNI から変更されているようです.注意してください)

ID 関節(joint)の名前 意味(どこか)
0 JOINT_TORSO へそ(体の中心)
1 JOINT_NECK 首の根元
2 JOINT_HEAD 頭の中心
3 JOINT_LEFT_SHOULDER 左肩
4 JOINT_LEFT_ELBOW 左肘
5 JOINT_LEFT_HAND 左手首
6 JOINT_RIGHT_SHOULDER 右肩
7 JOINT_RIGHT_ELBOW 右肘
8 JOINT_RIGHT_HAND 右手首
9 JOINT_LEFT_HIP 左足(もも)の付け根
10 JOINT_LEFT_KNEE 左膝
11 JOINT_LEFT_FOOT 左足首
12 JOINT_RIGHT_HIP 右足(もも)の付け根
13 JOINT_RIGHT_KNEE 右膝
14 JOINT_RIGHT_FOOT 右足首
ID 骨(limb)の名前 意味(どこか)
0 LIMB_LEFT_UPPER_TORSO 左上ワキ(TORSO-SHOULDER)
1 LIMB_LEFT_SHOILDER 左鎖骨(NECK-SHOULDER)
2 LIMB_LEFT_UPPER_ARM 左上腕(SHOULDER-ELBOW)
3 LIMB_LEFT_LOWER_ARM 左前腕(ELBOW-HAND)
4 LIMB_LEFT_LOWER_TORSO 左下ワキ(TORSO-HIP)
5 LIMB_LEFT_UPPER_LEG 左上腿(HIP-KNEE)
6 LIMB_LEFT_LOWER_LEG 左下腿(KNEE-FOOT)
7 LIMB_RIGHT_UPPER_TORSO 右上ワキ(TORSO-SHOULDER)
8 LIMB_RIGHT_SHOULDER 右鎖骨(NECK-SHOULDER)
9 LIMB_RIGHT_UPPER_ARM 右上腕(SHOULDER-ELBOW)
10 LIMB_RIGHT_LOWER_ARM 右前腕(ELBOW-HAND)
11 LIMB_RIGHT_LOWER_TORSO 右下ワキ(TORSO-HIP)
12 LIMB_RIGHT_UPPER_LEG 右上腿(HIP-KNEE)
13 LIMB_RIGHT_LOWER_LEG 右下腿(KNEE-FOOT)
14 LIMB_NECK 首(NECK-HEAD)
15 LIMB_PELVIS マタ(HIP-HIP)
【ofxOpenNI を活用する:その5「手のトラッキング」】

全身のスケルトンではなく,手の位置だけをセンシングしたいのであれば,もっと手軽(というかロバスト)に実現できるようです.つぎのプログラムを参考にしてください.手の位置に赤いボールが表示されるはずです.

#pragma once
#include "ofMain.h"
#include "ofxOpenNI.h"
class ofApp : public ofBaseApp {
private:    
    ofxOpenNI kinect;
public:
    (... 変更なし ...)
};
#include "ofApp.h"
void ofApp::setup() {
    //  window
    ofBackground(0, 0, 0);
    ofSetWindowShape(640, 480);
    ofSetFrameRate(30);
    //  setup ofxOpenNI
    kinect.setup();
    kinect.setRegister(true);
    kinect.setMirror(true);
    kinect.addImageGenerator();     //  required for RGB image
    kinect.addDepthGenerator();     //  required for depth image
    kinect.addHandsGenerator();      //  required for hand tracking
    kinect.addAllHandFocusGestures();
    kinect.setMaxNumHands(1);       //  max num of skeleton to track
    //  start kinect
    kinect.start();
}
void ofApp::update() {
    kinect.update();
}
void ofApp::draw() {
    ofSetColor(255, 127, 127);
    if (kinect.getNumTrackedHands() > 0) {
        ofxOpenNIHand hand = kinect.getTrackedHand(0);
        ofPoint p = hand.position();
        ofDrawCircle(p.x, p.y, 20);
    }
}
(... 以下変更なし ...)