書籍転載:Intel RealSense SDKセンサープログラミング(8)
openFrameworksによるメディアアートとRealSenseを組み合わせよう!
― Chapter 10 openFrameworksで作る「メディアアート」アプリ ―
RealSenseとopenFrameworksを使ったサンプルアプリケーションで、RealSenseのセンサーから情報を取得してみる。
前回は、openFrameworksを使ったサンプルアプリケーションを実際に作成しました。今回はその続きです。
書籍転載について
本コーナーは、翔泳社発行の書籍『Intel RealSense SDKセンサープログラミング』の中から、特にBuild Insiderの読者に有用だと考えられる項目を編集部が選び、同社の許可を得て転載したものです。
『Intel RealSense SDKセンサープログラミング』の詳細や購入は翔泳社のサイトや目次ページをご覧ください。プログラムのダウンロードも、翔泳社のサイトから行えます。
ご注意
本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。
■
10-3-4 インテル RealSense SDKを使うための設定
いよいよ、インテル RealSense SDKをopenFrameworksのプログラムに仕込んでいきます。
>インクルードパスの設定
Visual StudioのmySketch2プロジェクトのプロパティから[C/C++]→[全般]を選択し、[追加のインクルードディレクトリ]にて、C++ のインクルードパスに$(RSSDK_DIR)/include
を追加します(図10.13)。
>ライブラリパスの設定
同様に、mySketch2プロジェクトのプロパティから[リンカー]→[全般]を選択し、[追加のライブラリディレクトリ]にて、C++ のライブラリパスに$(RSSDK_DIR)/lib/$(PlatformName)
を追加します(図10.14)。
>ライブラリの設定
さらに、mySketch2プロジェクトのプロパティから、[リンカー]→[入力]を選択し、[追加の依存ファイル]にて、デバッグビルドの時はlibpxc_d.libを追加、リリースビルドの時はlibpxc.libを追加します(図10.15)。
10-3-5 インテル RealSense SDKのコードを追加する
準備ができたので、いよいよインテル RealSense SDKを使うコードをアプリに追加していきます。今回使うコードは「Chapter 5 手指の検出」(※転載対象外)でサンプルとして使われているコードとほとんど同じです。
>インクルードファイルの追加
インテル RealSense SDKを使うためのインクルードファイルをofApp.cppに追加します。
#include "pxccapture.h"
#include "pxchandmodule.h"
#include "pxchandconfiguration.h"
#include "pxchanddata.h"
#include "pxcsensemanager.h"
|
>手の解析に使うクラスのインスタンスの追加
続いて、手の解析に使うクラスのインスタンスをofApp.cppに追加します。
PXCSenseManager* senseManager = 0;
PXCHandModule* handAnalyzer = 0;
PXCHandData* handData = 0;
|
>変数の追加
さらに、Depthストリームの定義用変数、手の情報を保存する変数、openFrameworkで描画する為の変数を、ofApp.cppに追加します。
const int DEPTH_WIDTH = 640;
const int DEPTH_HEIGHT = 480;
const int DEPTH_FPS = 30;
|
int sides[2];
float openesses[2];
|
ofPoint centers[2];
ofColor circleColor;
ofColor clearWhiteColor;
|
以上で、インクルードファイル、グローバル変数の追加は終了です。まとめると、追加したコードは、リスト10.13のようになっています。
#include "pxccapture.h"
#include "pxchandmodule.h"
#include "pxchandconfiguration.h"
#include "pxchanddata.h"
#include "pxcsensemanager.h"
PXCSenseManager* senseManager = 0;
PXCHandModule* handAnalyzer = 0;
PXCHandData* handData = 0;
const int DEPTH_WIDTH = 640;
const int DEPTH_HEIGHT = 480;
const int DEPTH_FPS = 30;
int sides[2];
float openesses[2];
ofPoint centers[2];
ofColor circleColor;
ofColor clearWhiteColor;
|
>セットアップのコードを追加
続いて、インテル RealSense SDKを使って手の解析をするためのセットアップ、解析した手のデータを表示するためのセットアップのコードを、setup()
に追加していきます。以降に挙げるコードを10-3-3項でやったGPUパーティクルのセットアップのコード(リスト10.4)の後に、それぞれ追加します。
● 解析した手のデータを表示するためのセットアップ
今回のデモは手の重心を赤い円で表示するので、openFrameworksで赤い色を使えるように赤色を定義しておきます。
circleColor.r = 255;
circleColor.g = 0;
circleColor.b = 0;
circleColor.a = 128;
|
● インテル RealSense SDKのセットアップ
インテル RealSense SDKのセットアップをするためのコードを追加します。
senseManager = PXCSenseManager::CreateInstance();
if (senseManager == 0) {
throw std::runtime_error("SenseManagerの生成に失敗しました");
}
// Depthストリームを有効にする
auto sts = senseManager->EnableStream(PXCCapture::StreamType::STREAM_TYPE_DEPTH, DEPTH_WIDTH, DEPTH_HEIGHT, DEPTH_FPS);
if (sts < PXC_STATUS_NO_ERROR) {
throw std::runtime_error("Depthストリームの有効化に失敗しました");
}
// 手の検出を有効にする
sts = senseManager->EnableHand();
if (sts < PXC_STATUS_NO_ERROR) {
throw std::runtime_error("手の検出の有効化に失敗しました");
}
// パイプラインを初期化する
sts = senseManager->Init();
if (sts < PXC_STATUS_NO_ERROR) {
throw std::runtime_error("パイプラインの初期化に失敗しました");
}
initializeHandTracking();
|
● 手の認識のセットアップ
インテル RealSense SDKの手の認識セットアップのコードとして、initializeHandTracking()
を追加します。
void ofApp::initializeHandTracking()
{
// 手の検出器を取得する
handAnalyzer = senseManager->QueryHand();
if (handAnalyzer == 0) {
throw std::runtime_error("手の検出器の取得に失敗しました");
}
// 手のデータを作成する
handData = handAnalyzer->CreateOutput();
if (handData == 0) {
throw std::runtime_error("手の検出器の作成に失敗しました");
}
PXCCapture::Device *device = senseManager->QueryCaptureManager()->QueryDevice();
PXCCapture::DeviceInfo dinfo;
device->QueryDeviceInfo(&dinfo);
if (dinfo.model == PXCCapture::DEVICE_MODEL_IVCAM) {
device->SetDepthConfidenceThreshold(1);
//device->SetMirrorMode( PXCCapture::Device::MIRROR_MODE_DISABLED );
device->SetIVCAMFilterOption(6);
}
// 手の検出の設定
PXCHandConfiguration* config = handAnalyzer->CreateActiveConfiguration();
config->EnableSegmentationImage(true);
config->ApplyChanges();
config->Update();
}
|
ここまでできると、setup()
関数はリスト10.17のようになっています。
void ofApp::setup(){
ofBackground(0);
ofSetFrameRate(60);
// 1,000,000 particles
unsigned w = 1000;
unsigned h = 1000;
particles.init(w, h);
// initial positions
// use new to allocate 4,000,000 floats on the heap rather than
// the stack
float* particlePosns = new float[w * h * 4];
for (unsigned y = 0; y < h; ++y)
{
for (unsigned x = 0; x < w; ++x)
{
unsigned idx = y * w + x;
particlePosns[idx * 4] = 400.f * x / (float)w - 200.f; // particle x
particlePosns[idx * 4 + 1] = 400.f * y / (float)h - 200.f; // particle y
particlePosns[idx * 4 + 2] = 0.f; // particle z
particlePosns[idx * 4 + 3] = 0.f; // dummy
}
}
particles.loadDataTexture(ofxGpuParticles::POSITION, particlePosns);
delete[] particlePosns;
// initial velocities
particles.zeroDataTexture(ofxGpuParticles::VELOCITY);
// listen for update event to set additonal update uniforms
ofAddListener(particles.updateEvent, this, &ofApp::onParticlesUpdate);
// ▼▼ここから追加▼▼
circleColor.r = 255;
circleColor.g = 0;
circleColor.b = 0;
circleColor.a = 128;
senseManager = PXCSenseManager::CreateInstance();
if (senseManager == 0) {
throw std::runtime_error("SenseManagerの生成に失敗しました");
}
// Depthストリームを有効にする
auto sts = senseManager->EnableStream(
PXCCapture::StreamType::STREAM_TYPE_DEPTH,
DEPTH_WIDTH, DEPTH_HEIGHT, DEPTH_FPS);
if (sts < PXC_STATUS_NO_ERROR) {
throw std::runtime_error("Depthストリームの有効化に失敗しました");
}
// 手の検出を有効にする
sts = senseManager->EnableHand();
if (sts < PXC_STATUS_NO_ERROR) {
throw std::runtime_error("手の検出の有効化に失敗しました");
}
// パイプラインを初期化する
sts = senseManager->Init();
if (sts < PXC_STATUS_NO_ERROR) {
throw std::runtime_error("パイプラインの初期化に失敗しました");
}
initializeHandTracking();
// ▲▲ここまで追加▲▲
}
|
>フレームごとのトラッキング処理を追加
フレームごとに手をトラッキングするための処理をofApp::update()
に追加します。
void ofApp::update(){
// ▼▼ここから追加▼▼
pxcStatus sts = senseManager->AcquireFrame(false);
if (sts < PXC_STATUS_NO_ERROR) {
return;
}
// 手のデータを更新する
updateHandFrame();
// フレームを解放する
senseManager->ReleaseFrame();
// ▲▲ここまで追加▲▲
ofSetWindowTitle(ofToString(ofGetFrameRate(), 2));
particles.update();
}
|
>手のデータの更新処理を追加
今回は手の重心と、手の開閉度を取得します。そのためのコードを更新処理updateHandFrame()
として追加します。
void ofApp::updateHandFrame(){
handData->Update();
// 検出した手の数を取得する
auto numOfHands = handData->QueryNumberOfHands();
for (int i = 0; i < numOfHands; i++) {
// 手を取得する
pxcUID handID;
PXCHandData::IHand* hand;
auto sts = handData->QueryHandData(
PXCHandData::AccessOrderType::ACCESS_ORDER_BY_ID, i, hand);
if (sts < PXC_STATUS_NO_ERROR) {
continue;
}
auto side = hand->QueryBodySide();
auto openness = hand->QueryOpenness();
auto center = hand->QueryMassCenterImage();
if (i < 2){
sides[i] = side;
openesses[i] = openness;
centers[i].x = center.x;
centers[i].y = center.y;
}
}
}
|
これで、openFrameworksのアプリケーションにおいて、RealSenseによる手のトラッキングができるようになりました。
10-3-6 トラッキングしている手の情報の表示
続いて、インテル RealSenseカメラでトラッキングした手の情報をopenFrameworksのアプリ上で表示してみましょう。今回はofEasyCam
という簡単に3D表示ができるクラスを使っています。デフォルトでは画面中央が(0,0)となっています。
また、RealSenseの解析で定義している範囲が、X方向にDEPTH_WIDTH
、Y方向にDEPTH_HEIGHT
なので、手の重心の値はその範囲で返ってくるのを、openFrameworksアプリの画面のサイズに合わせています。さらに、X方向の動きに-1
をかけることでミラーリングしています。
手の開閉度は、円の大きさで表しています。
リスト10.20のようにofApp::draw()
にコードを追加します。
void ofApp::draw(){
cam.begin();
ofEnableBlendMode(OF_BLENDMODE_ADD);
particles.draw();
ofDisableBlendMode();
// ▼▼ここから追加▼▼
// 円の位置の計算
ofVec3f handPoint(
-1.0f * (centers[0].x * ofGetWidth() / DEPTH_WIDTH - .5f * ofGetWidth()),
.5f * ofGetHeight() - (centers[0].y * ofGetHeight() / DEPTH_HEIGHT), 0.f);
// 円の色の指定
ofSetColor(circleColor);
// アウトラインのみ描画
ofNoFill();
// 円の描画
ofCircle(handPoint.x, handPoint.y, 2.0f * openesses[0]);
// ▲▲ここまで追加▲▲
cam.end();
}
|
10-3-7 手でパーティクルを動かそう!
さて、いよいよサンプルも最終段階です。RealSenseでトラッキングした手でGPUパーティクルを動かすようにします。ofApp::onParticlesUpdate
関数を少し修正して、手の位置、開閉度をシェーダーに送ります。
下記の行を書き換えます。
ofVec3f mouse(ofGetMouseX() - .5f * ofGetWidth(), .5f * ofGetHeight() - ofGetMouseY(), 0.f);
|
リスト10.21のように書き換えます。座標の変換は10-3-6項で手の位置の表示に使ったロジック(リスト10.20)と全く同じです。
ofVec3f mouse(
-1.0f * ( centers[0].x * ofGetWidth() / DEPTH_WIDTH - .5f * ofGetWidth()),
.5f * ofGetHeight() - (centers[0].y * ofGetHeight() / DEPTH_HEIGHT ), 0.f);
|
これで、パーティクルを手のトラッキングで動かすことができるようになりました。次に、手の開閉値でパーティクルの集まり具合を変化させます。手の開閉値の最大値は
100
です。手をすぼめたらパーティクルが集まり、手を開いたらパーティクルが広がるようするので、手の開閉値を100
で割った値を1.0
から引くことで、手が広がるとパーティクルの集まる力が弱くなり、手を閉じるとパーティクルの集まる力が強くなるようにします。
下記の行を書き換えます。
shader.setUniform1f("radiusSquared", 200.f * 200.f);
|
リスト10.22のように書き換えます。
float openForRad;
if (openesses[0] < 10.0f){
openForRad = 10.0f;
}
else if (openesses[0] > 100.0f){
openForRad = 100.0f;
}
else{
openForRad = openesses[0];
}
openForRad = 1.0f - ( openForRad / 100.0f ) + 0.01f ;
shader.setUniform1f("radiusSquared", 200.f * 200.f * 10.0f * openForRad );
|
すべての修正が終わるとofApp::onParticlesUpdate()
関数は、リスト10.23のようになります。
void ofApp::onParticlesUpdate(ofShader& shader)
{
// 手の位置をシェーダーの座標系に合わせて計算
ofVec3f mouse(
-1.0f * (centers[0].x * ofGetWidth() / DEPTH_WIDTH - .5f * ofGetWidth()),
.5f * ofGetHeight() - (centers[0].y * ofGetHeight() / DEPTH_HEIGHT), 0.f);
// 計算した手の位置をシェーダーにセット
shader.setUniform3fv("mouse", mouse.getPtr());
shader.setUniform1f("elapsed", ofGetLastFrameTime());
// 手の開閉値でパーティクルの集まり具合を変化させる
float openForRad;
if (openesses[0] < 10.0f){
openForRad = 10.0f;
}
else if (openesses[0] > 100.0f){
openForRad = 100.0f;
}
else{
openForRad = openesses[0];
}
openForRad = 1.0f - (openForRad / 100.0f) + 0.01f;
shader.setUniform1f("radiusSquared", 200.f * 200.f * 10.0f * openForRad);
}
|
ビルドすると、手の動きでパーティクルがコントロールできるようになりました! 手を閉じるとパーティクルが集まり、開くとパーティクルが拡散します。
>Special Thanks!
今回はNeil Mendozaさんの作ったofxGpuParticlesを使わせていただきました。
■
以上で、書籍『Intel RealSense SDKセンサープログラミング』からの全8本の記事転載は終了です。
※以下では、本稿の前後を合わせて5回分(第4回~第8回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
5. WPF(Visual Studio)+RealSenseで作る表情感知アプリ
インテル RealSenseを活用したサンプルアプリケーション開発の第2弾として、Visual StudioとRealSenseを組み合わせる方法を紹介する。
6. Visual Studioで活用するRealSenseセンシング機能
RealSenseとVisual Studioを使ったサンプルアプリケーションで、RealSenseのセンサーから情報を取得してみる。
7. openFrameworksで作成するメディアアート入門
インテル RealSenseを活用したサンプルアプリケーション開発の第3弾として、openFrameworksとRealSenseを組み合わせる方法を紹介する。