このエントリはOpenNI Advent Calendar 2011 : ATNDの12月23日分です!!
OpenframeworksでOpenNIの連携について書きます。
Openframeworks ( http://www.openframeworks.cc/ )(以下、oF)とは、メディアアート界隈で使われている描画や動画の扱いやプログラムの書き方がprocessingっぽく作られている、クロスプラットフォームで動作するC++のフレームワークです。
oFはたくさんの有志により様々なアドオンが配布されており、それを使うとソケットやOSC、OpenCVなどの機能を簡単に使う事ができます。
oFで使えるアドオン
oFで使えるKinect関連のアドオンがいくつか、下記のものがあります。
・ofxKinect: ( https://github.com/ofTheo/ofxKinect )
→深度情報とRGBが取得できます。スケルトンの取得機能はありません。
・ofxOpenNI:( https://github.com/gameoverhack/ofxOpenNI )
→OpenNIをoFで使えるように作ったもの。
ただ、OpenNI自体バージョンアップが早い事もあるので今回は普通のOpenNIをoFで使う方法を紹介します。
oFを使えるようにする
oFのWebサイトから必要なファイルをダウンロードします。私の場合はMacなのでosxバージョンをダウンロードします。
http://www.openframeworks.cc/download/
ダウンロードしたファイルを解凍すると下記のフォルダ構成になっています。
- add ons / oFのアドオンが格納されています。
- apps / サンプルプログラムがたくさん入っています。実際にoFでアプリを開発する場合はこの中のexamples/EmptyExampleをベースにします。
- libs / oF自体のライブラリやoFが使っているライブラリが格納されています。
では、oFの基本を理解するために EmptyExampleをみてみます。
testApp.cppをみてみましょう。
はじめに定義されているメソッドの実装を進めて開発を進めます。
- setup() :
- アプリケーションの起動時あるいはnewした直後に呼ばれる関数です。初期化などはここで行います。
- update():
- 毎フレーム毎draw()の前に呼ばれます。ここでデータや座標計算の更新をすると良いでしょう。
- draw():
- 毎フレーム毎、update()の後に呼ばれます。ここには描画処理をかきます。
基本的にはこれらを使うのですが、マウスやキーボードのユーザー入力にも対応しています。
- keyPressed, keyReleased:
- キーボードを押したとき、離したときに呼ばれます。
- mouseMoved, mouseDragged, mousePressed, mouseReleased:
- マウスの移動、ドラッグ、マウスダウン、マウスを離した時に呼ばれます。
OpenNIの設定:
準備
今回はとにかく簡単に使えるようになる事を考えて説明します。
まず、OpenNIのIncludeとLibフォルダをプロジェクト直下に”OpenNI”というフォルダを作成し、そこに移動します。
次にプロジェクトにヘッダー参照パスとライブラリパスの設定をします。
プロジェクト設定の “Build Settings” → “Search Paths”の
Header Search Pathsに以下を設定します。
“$(SRCROOT)/../OpenNI/Include
Library Search Pathsに以下を設定します。
“$(SRCROOT)/../OpenNI/Lib
※設定後、自動的に別の項目に変更される場合がありますが、ビルドが通れば問題はありません。
私の環境では、
HEADER_SEARCH_PATHS_QUOTED_FOR_TARGET_1
LIBRARY_SEARCH_PATHS_QUOTED_FOR_TARGET_1
の項目が追加されていました。
さて、それではOpenNIのコードを書いてみましょう。NiSimpleViewerをパク、、いやもとにして、深度情報を表示させてみましょう。
表示にはoFTextureクラスを使い、テクスチャとして表示させます。
今回は簡単に移植するため、NiSimpleViewerからコピペする事にします。
まず、
testApp.h
testApp.hに下記を追加します。
#include <XnCppWrapper.h> using namespace xn;
//--------------------------------------------------------------------------- // Defines //--------------------------------------------------------------------------- #define SAMPLE_XML_PATH "../../../data/SamplesConfig.xml" #define DISPLAY_MODE_DEPTH 2 #define DEFAULT_DISPLAY_MODE DISPLAY_MODE_DEPTH #define MAX_DEPTH 10000
SamplesConfig.xmlはbin/data/以下に設置したため、
SAMPLE_XML_PATHを変更しています。
——-
testApp.cpp
testApp.cppに”Globals”の内容をもとに移植します。
//--------------------------------------------------------------------------- // Globals //--------------------------------------------------------------------------- float g_pDepthHist[MAX_DEPTH]; XnRGB24Pixel* g_pTexMap = NULL; unsigned int g_nTexMapX = 0; unsigned int g_nTexMapY = 0; unsigned int g_nViewState = DEFAULT_DISPLAY_MODE; Context g_context; ScriptNode g_scriptNode; DepthGenerator g_depth; DepthMetaData g_depthMD;
——–
NiSimpleViewerのmain関数が初期化に関する内容なので、setupに移植します。
XnStatus rc; EnumerationErrors errors; rc = g_context.InitFromXmlFile(SAMPLE_XML_PATH, g_scriptNode, &errors); if (rc == XN_STATUS_NO_NODE_PRESENT) { XnChar strError[1024]; errors.ToString(strError, 1024); printf("%s\n", strError); return ; } else if (rc != XN_STATUS_OK) { printf("Open failed: %s\n", xnGetStatusString(rc)); return; } rc = g_context.FindExistingNode(XN_NODE_TYPE_DEPTH, g_depth); if (rc != XN_STATUS_OK) { printf("No depth node exists! Check your XML."); return; } g_depth.GetMetaData(g_depthMD); // Texture map init g_nTexMapX = (((unsigned short)(g_depthMD.FullXRes()-1) / 512) + 1) * 512; g_nTexMapY = (((unsigned short)(g_depthMD.FullYRes()-1) / 512) + 1) * 512; g_pTexMap = (XnRGB24Pixel*)malloc(g_nTexMapX * g_nTexMapY * sizeof(XnRGB24Pixel));
——-
setup関数の最後にofTextureクラスとofTextureで表示させるためのunsigned char*のデータをを初期化します。
pixels = (unsigned char*)malloc(640*480*3*sizeof(unsigned char)); tex.allocate(640, 480, GL_RGB);
——-
OpenNIでのデータを更新する処理をupdateに移植します。
XnStatus rc = XN_STATUS_OK; // Read a new frame rc = g_context.WaitAnyUpdateAll(); if (rc != XN_STATUS_OK) { printf("Read failed: %s\n", xnGetStatusString(rc)); return; } g_depth.GetMetaData(g_depthMD); //g_image.GetMetaData(g_imageMD); const XnDepthPixel* pDepth = g_depthMD.Data(); // Calculate the accumulative histogram (the yellow display...) xnOSMemSet(g_pDepthHist, 0, MAX_DEPTH*sizeof(float)); unsigned int nNumberOfPoints = 0; for (XnUInt y = 0; y < g_depthMD.YRes(); ++y) { for (XnUInt x = 0; x < g_depthMD.XRes(); ++x, ++pDepth) { if (*pDepth != 0) { g_pDepthHist[*pDepth]++; nNumberOfPoints++; } } } for (int nIndex=1; nIndex<MAX_DEPTH; nIndex++) { g_pDepthHist[nIndex] += g_pDepthHist[nIndex-1]; } if (nNumberOfPoints) { for (int nIndex=1; nIndex<MAX_DEPTH; nIndex++) { g_pDepthHist[nIndex] = (unsigned int)(256 * (1.0f - (g_pDepthHist[nIndex] / nNumberOfPoints))); } } xnOSMemSet(g_pTexMap, 0, g_nTexMapX*g_nTexMapY*sizeof(XnRGB24Pixel)); // check if we need to draw depth frame to texture const XnDepthPixel* pDepthRow = g_depthMD.Data(); XnRGB24Pixel* pTexRow = g_pTexMap + g_depthMD.YOffset() * g_nTexMapX; for (XnUInt y = 0; y < g_depthMD.YRes(); ++y) { const XnDepthPixel* pDepth = pDepthRow; XnRGB24Pixel* pTex = pTexRow + g_depthMD.XOffset(); for (XnUInt x = 0; x < g_depthMD.XRes(); ++x, ++pDepth, ++pTex) { int idx = (x + y * g_depthMD.XRes()) * 3; if (*pDepth != 0) { int nHistValue = g_pDepthHist[*pDepth]; pixels[idx] = nHistValue; pixels[idx+1] = nHistValue; pixels[idx+2] = nHistValue; } else { pixels[idx] = 0; pixels[idx+1] = 0; pixels[idx+2] = 0; } } pDepthRow += g_depthMD.XRes(); pTexRow += g_nTexMapX; } tex.loadData(pixels, 640, 480, GL_RGB);
ofTextureのloadDataメソッドを使い、深度データをテクスチャとして読み込ませます。
——
最後にdrawメソッドにofTexetureのdrawを使って実際に描画します。
引数は、x座標、y座標、幅、高さを示しています。
—–
これでビルドすると表示できるようになります。
たくさんの警告はありますが、ライブラリ内部的なものもあるので無視する事にします。
ファイル一式をこちらにアップしましたのでダウンロードして試してみてください。
https://github.com/mmlemon/OpenNI_Advent_oFSample
※OpenNIは1.4.0.2、oFは007で記述しています。
3 Comments