はじめに
最近、MicrosoftはKinectツールキットのベータ版であるMicrosoft Research Kinect SDKをリリースしました。 ツールには、ヘッダーファイル、ライブラリ、およびC ++アプリケーションでの使用例が含まれています。 ただし、SDK自体が存在するだけでは、わかりやすい例やドキュメントがないという問題は解決しません。 Microsoftが.NET開発者により重点を置いていることは注目に値します。したがって、たとえば公式フォーラムでは、トピックの大部分はC#に関連しており、KinectのAPIの説明を見つけようとするGoogle検索は、いくつかのリンクのみを提供しています-公式ドキュメントへのリンクです。
この記事では、Microsoft Kinectを使用するためのオプション、および上記のC ++アプリケーションでwxWidgetsライブラリと組み合わせたソフトウェアツールについて説明します。
はじめに
開始するには、少なくとも開発ツールをダウンロードする必要があります。 これは、 Microsoft Research Kinect SDKページで実行できます。
また、wxWidgetsライブラリも必要です。 公式WebサイトまたはSVNリポジトリからダウンロードすることができます (私はSVN HEADを好みます -公式リリースにはない多くの新しい有用なものが含まれていますが、バグが頻繁に表示されます)。
「明るい」側を好み、無料のツールのみを使用してアプリケーションを開発することに熱心な場合は、 Visual C ++ 2010 Express 、およびWindows 7 (またはそれ以降) 用のMicrosoft Windows SDKをダウンロードし、それなしでVisual C ++ ExpressでwxWidgetsを構築し、ほとんどの場合失敗します。
wxWidgetsの収集
Visual StudioとKinect SDKのインストールプロセスは考慮しません。インストールウィザードの[次へ]ボタンを数回クリックするだけです。しかし、wxWidgetsビルドプロセスをより詳細に見ていきます。 アプリケーション開発の次のステップはそれに依存します。
wxWidgetsソースコードをダウンロードして別のフォルダーに解凍したら、 WXWIN環境変数を追加し、その値にwxWidgetsソースコードフォルダーへのパスを指定する必要があります。
SVNのソースコードを使用する場合、 %WXWIN%/ include / msw / setup0.hファイルを%WXWIN%/ include / msw / setup.hにコピーする必要があります。
デフォルトでは、wxWidgetsにはいくつかの構成があります(図1)。
- デバッグする
- リリース
- DLLデバッグ
- DLLリリース
最初の2つの構成では、静的ライブラリの形式でwxWidgetsを構築できます。後者の場合-動的にロードされるいくつかのモジュールの形式で。

静的ライブラリの構築
静的ライブラリ(デバッグおよびリリース構成)を組み立てる前に、ソリューション内のすべてのプロジェクトのプロパティで、 C / C ++->コード生成->ランタイムライブラリパラメーターをそれぞれマルチスレッドデバッグとマルチスレッドに設定します(図2)。

これらのコンパイルオプションを設定すると、アプリケーションと一緒にエンドユーザーマシンにVisual C ++ Redistributableをインストールする必要がなくなります。 コンパイルオプションを設定したら、ソリューションを構築できます。 結果として、lib / vc_libサブディレクトリにいくつかの.libファイルを作成する必要があります。これは、後でアプリケーションで使用されます。
動的ライブラリを構築する
動的ライブラリを構築するには、コンパイラ設定で何も変更する必要はありません。 ただし、別の問題があります。ソリューションには依存関係がないため、ビルドプロセスを数回再起動する必要があります。 一部のライブラリをリンクするとエラーが発生します。 アセンブリ後、lib / vc_dllサブディレクトリにいくつかの.DLLおよび.LIBファイルを作成する必要があります。
ライブラリのバージョンを収集(デバッグ)およびデバッグおよび最適化(リリース)する必要があることに注意してください。
テストアプリケーションを作成する
したがって、現時点では次のとおりです。
- Visual Studio 2010
- Microsoft Research Kinect SDK
- コンパイルされたwxWidgetsライブラリ
- 静的
- ダイナミック
アプリケーションの作成を開始できます。
テストアプリケーションには次のものがあります。
- アプリケーションクラス(
wxApp
からwxApp
) - メインフォームクラス(
wxFrame
からwxFrame
) - Canvasクラス(Kinectセンサーからの画像を表示する
wxWindow
から派生したコントロール)
KinectTestApp.h
#ifndef _KINECTTESTAPP_H_
#define _KINECTTESTAPP_H_
#include "wx/image.h"
#include "KinectTestMainFrame.h"
class KinectTestApp: public wxApp
{
DECLARE_CLASS( KinectTestApp )
DECLARE_EVENT_TABLE()
public :
KinectTestApp();
void Init();
virtual bool OnInit();
virtual int OnExit();
};
DECLARE_APP(KinectTestApp)
#endif
KinectTestApp.cpp
...
bool KinectTestApp::OnInit()
{
#if wxUSE_LIBPNG
wxImage::AddHandler( new wxPNGHandler);
#endif
#if wxUSE_LIBJPEG
wxImage::AddHandler( new wxJPEGHandler);
#endif
KinectTestMainFrame* mainWindow = new KinectTestMainFrame( NULL );
mainWindow->Show( true );
return true ;
}
KinectTestMainFrame.h
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
DECLARE_CLASS( KinectTestMainFrame )
DECLARE_EVENT_TABLE()
public :
KinectTestMainFrame();
KinectTestMainFrame( wxWindow* parent,
wxWindowID id = SYMBOL_KINECTTESTMAINFRAME_IDNAME,
const wxString& caption = SYMBOL_KINECTTESTMAINFRAME_TITLE,
const wxPoint& pos = SYMBOL_KINECTTESTMAINFRAME_POSITION,
const wxSize& size = SYMBOL_KINECTTESTMAINFRAME_SIZE,
long style = SYMBOL_KINECTTESTMAINFRAME_STYLE );
bool Create( wxWindow* parent,
wxWindowID id = SYMBOL_KINECTTESTMAINFRAME_IDNAME,
const wxString& caption = SYMBOL_KINECTTESTMAINFRAME_TITLE,
const wxPoint& pos = SYMBOL_KINECTTESTMAINFRAME_POSITION,
const wxSize& size = SYMBOL_KINECTTESTMAINFRAME_SIZE,
long style = SYMBOL_KINECTTESTMAINFRAME_STYLE );
~KinectTestMainFrame();
void Init();
void CreateControls();
wxBitmap GetBitmapResource( const wxString& name );
wxIcon GetIconResource( const wxString& name );
virtual wxThread::ExitCode Entry();
wxGridBagSizer* m_MainSizer;
wxListBox* m_DeviceListBox;
KinectCanvas* m_Canvas;
};
#endif
KinectTestMainFrame.cpp
...
void KinectTestMainFrame::CreateControls()
{
KinectTestMainFrame* itemFrame1 = this ;
m_MainSizer = new wxGridBagSizer(0, 0);
m_MainSizer->SetEmptyCellSize(wxSize(10, 20));
itemFrame1->SetSizer(m_MainSizer);
wxArrayString m_DeviceListBoxStrings;
m_DeviceListBox = new wxListBox( itemFrame1,
ID_DEVICE_LISTBOX, wxDefaultPosition,
wxDefaultSize, m_DeviceListBoxStrings,
wxLB_SINGLE );
m_MainSizer->Add(m_DeviceListBox,
wxGBPosition(0, 0), wxGBSpan(1, 1),
wxGROW|wxGROW|wxALL, 5);
m_Canvas = new KinectCanvas( itemFrame1,
ID_KINECT_CANVAS, wxDefaultPosition,
wxSize(320, 240), wxSIMPLE_BORDER );
m_MainSizer->Add(m_Canvas, wxGBPosition(0, 1),
wxGBSpan(1, 1), wxALIGN_CENTER_HORIZONTAL|
wxALIGN_CENTER_VERTICAL|wxALL, 5);
m_MainSizer->AddGrowableCol(1);
m_MainSizer->AddGrowableRow(0);
}
...
wxThread::ExitCode KinectTestMainFrame::Entry()
{
return NULL;
}
KinectCanvas.h
...
class KinectCanvas: public wxWindow
{
DECLARE_DYNAMIC_CLASS( KinectCanvas )
DECLARE_EVENT_TABLE()
public :
KinectCanvas();
KinectCanvas(wxWindow* parent,
wxWindowID id = ID_KINECTCANVAS,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxSize(100, 100),
long style = wxSIMPLE_BORDER);
bool Create(wxWindow* parent,
wxWindowID id = ID_KINECTCANVAS,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxSize(100, 100),
long style = wxSIMPLE_BORDER);
~KinectCanvas();
void Init();
void CreateControls();
void OnPaint( wxPaintEvent& event );
wxImage * GetCurrentImage() const { return m_CurrentImage ; }
void SetCurrentImage(wxImage * value ) { m_CurrentImage = value ; }
wxBitmap GetBitmapResource( const wxString& name );
wxIcon GetIconResource( const wxString& name );
wxImage * m_CurrentImage;
};
#endif
Kinectcanvas.cpp
...
IMPLEMENT_DYNAMIC_CLASS( KinectCanvas, wxWindow )
BEGIN_EVENT_TABLE( KinectCanvas, wxWindow )
EVT_PAINT( KinectCanvas::OnPaint )
END_EVENT_TABLE()
...
void KinectCanvas::OnPaint( wxPaintEvent& event )
{
wxAutoBufferedPaintDC dc( this );
if (!m_CurrentImage)
{
dc.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
dc.Clear();
dc.DrawLabel(_( "No image" ), wxRect(dc.GetSize()),
wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL);
}
else
{
wxBitmap bmp(*m_CurrentImage);
dc.DrawBitmap(bmp,
(dc.GetSize().GetWidth()-bmp.GetWidth())/2,
(dc.GetSize().GetHeight()-bmp.GetHeight())/2);
}
}
上記のソースコードには、いくつかの空のメソッドと、明確ではないいくつかのメソッドがあります(たとえば、
GetIconResource()
、
GetBitmapResource()
、
Init()
)。 これはすべて、 DialogBlocksフォームデザイナがアプリケーションフレームワークの作成に使用されたためです。 これは有料のツールですが、試用版の機能でアプリケーションを作成できます。
アプリケーションをビルドする前に、wxWidgetsビルドオプションと一致するようにプロジェクト設定を変更する必要があります。 つまり、静的wxWidgetsライブラリを使用する場合は、 C / C ++->コード生成->ランタイムライブラリパラメーターのプロジェクトプロパティで、デバッグとリリースの構成に同じ値を設定する必要があります。 wxWidgetsダイナミックライブラリを使用する必要がある場合は、 C / C ++->プリプロセッサ->プリプロセッサ定義パラメータのプロジェクト設定で、
WXUSINGDLL
マクロを追加する必要があります。 このマクロは、動的なwxWidgetsライブラリの構築にも使用されるため、プロジェクトとwxWidgetsの設定は一致します(図3)。

また、アプリケーションのデバッグバージョンでは、リソースコンパイラ設定のプリプロセッサディレクティブにマクロ
wxUSE_NO_MANIFEST=1
を追加する必要があります。 これは、wxWidgetsリソースファイル( %WXWIN%/ include / msw / wx.rc )で指定されたマニフェストおよびVisual Studioがアプリケーションに自動的に追加するマニフェストと競合しないことを保証するためです。
上記の手順を実行した後、アプリケーションをビルドできます。 その結果、次のような結果が得られます(図4)。

Microsoft Research Kinect SDKを使用する
Kinect SDKをインストールすると、環境変数%MSRKINECTSDK%がシステムに表示され、SDKがインストールされたフォルダーへのパスが含まれます。 このフォルダーには、ヘッダーファイルを含むincサブディレクトリと、ライブラリを含むlibがあります。 ヘッダーファイルへのパスは、テストアプリケーションのコンパイラー設定、リンカー設定へのライブラリーへのパスに追加する必要があります。
デバイスのリストを取得する
現時点では、収集されたすべての依存関係とアプリケーションテンプレートがあります。 これで、Kinect SDKを使用してコードを直接書き始めることができます。
Kinect SDKには、1台のコンピューターに接続された複数のKinectデバイスを操作できる機能があります。 これは、アプリケーションを開発する際のより普遍的なソリューションです。 必要なデバイスの数は事前にわかりません。 したがって、この特定のAPIを使用することがより望ましいでしょう。
デバイスのリストを取得するには、
MSR_NuiGetDeviceCount()
関数を使用します。この関数は、整数変数へのポインターをパラメーターとして受け取り、成功すると、使用可能なセンサーの数が書き込まれます。
NUIAPI HRESULT MSR_NuiGetDeviceCount(
int * pCount
);
Kinectデバイスにはそれぞれ固有の識別子があり、これは
INuiInstance::MSR_NuiGetPropsBlob()
メソッドを使用して取得できます。 このメソッドはパラメーターとして受け取ります。
- プロパティ識別子(SDKのベータ版では、値を1つだけ持つことができます
INDEX_UNIQUE_DEVICE_NAME
) - 結果が書き込まれる変数へのポインター
- 結果の記録に使用できるメモリの量(たとえば、行の長さ)。 SDKのベータ版はこのパラメーターを使用しません。
virtual bool MSR_NuiGetPropsBlob(
MsrNui::NUI_PROPSINDEX Index,
void * pBlob,
DWORD * pdwInOutSize
);
新たに習得した知識を活用して、アプリケーションでデバイスのリストの受信を実装できます。
wxKinectHelper.h
#pragma once
#include <vector>
interface INuiInstance;
class KinectHelper
{
protected :
typedef std::pair<INuiInstance *, HANDLE> InstanceInfo;
typedef std::vector<InstanceInfo> InstanceVector;
public :
KinectHelper();
virtual ~KinectHelper();
size_t GetDeviceCount();
wxString GetDeviceName(size_t index);
bool IsDeviceOK(size_t deviceIndex);
protected :
InstanceVector m_Instances;
void Finalize();
InstanceInfo * GetInstanceByIndex(size_t index);
};
wxKinectHelper.cpp
#include <wx/wx.h>
#include "msr_nuiapi.h"
#include "KinectHelper.h"
KinectHelper::KinectHelper()
{
}
KinectHelper::~KinectHelper()
{
Finalize();
}
size_t KinectHelper::GetDeviceCount()
{
int result(0);
if (FAILED(MSR_NUIGetDeviceCount(&result))) return 0;
return (size_t)result;
}
KinectHelper::InstanceInfo * KinectHelper::GetInstanceByIndex(size_t index)
{
INuiInstance * instance = NULL;
for (InstanceVector::iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
instance = (*i).first;
if (instance->InstanceIndex() == ( int )index) return &(*i);
}
if (!instance)
{
if (!FAILED(MSR_NuiCreateInstanceByIndex(( int )index, &instance)))
{
InstanceInfo info;
info.first = instance;
info.second = NULL;
m_Instances.push_back(info);
return &(m_Instances.at(m_Instances.size()-1));
}
}
return NULL;
}
void KinectHelper::Finalize()
{
for (InstanceVector::const_iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
if ((*i).first && (*i).second)
{
(*i).first->NuiShutdown();
MSR_NuiDestroyInstance((*i).first);
}
}
}
wxString KinectHelper::GetDeviceName(size_t index)
{
BSTR result;
DWORD size;
InstanceInfo * info = GetInstanceByIndex(index);
if (info != NULL)
{
INuiInstance * instance = info->first;
if (instance != NULL && instance->MSR_NuiGetPropsBlob(
MsrNui::INDEX_UNIQUE_DEVICE_NAME, &result, &size))
{
wxString name = result;
SysFreeString(result);
return name;
}
}
return wxT( "Unknown Kinect Sensor" );
}
bool KinectHelper::IsDeviceOK(size_t deviceIndex)
{
return GetInstanceByIndex(deviceIndex) != NULL;
}
InstanceInfo
構造には、
INuiInstance
インスタンスへのポインタが含まれています。これにより、デバイスの名前と、イメージがキャプチャされるストリームへのハンドルを取得できます(後述)。
wxKinectHelper
クラスには、
InstanceInfo
構造のベクトルと、デバイスの数と各デバイスの名前を取得するためのメソッドが含まれています。
wxKinectHelper
クラスのデストラクタで、
Finalize()
メソッドが呼び出され、開いているすべての画像キャプチャスレッドが閉じられ、
INuiInstance
すべてのインスタンスが削除されます。
次に、デバイスのリストを取得する機能をアプリケーションに追加する必要があります。
wxKinectHelperMainFrame.h
...
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
...
void ShowDevices();
...
KinectHelper * m_KinectHelper;
}
...
wxKinectHelperMainFrame.cpp
...
void KinectTestMainFrame::ShowDevices()
{
size_t count = m_KinectHelper->GetDeviceCount();
m_DeviceListBox->Clear();
for (size_t i = 0; i < count; ++i)
{
int item = m_DeviceListBox->Append(
m_KinectHelper->GetDeviceName(i));
m_DeviceListBox->SetClientData(item, ( void *)i);
}
}
その結果、アプリケーションを起動した後、利用可能なKinectデバイスのリストを取得します(図5):

Kinectで画像を取得する
デバイスから画像のキャプチャを開始する前に、初期化する必要があります。 これは
INuiInstance::NuiInitialize()
メソッドを使用して行われます。このメソッドは、使用する予定のデバイスサブシステム(深度センサー、カメラ、またはビデオ上のプレーヤーの検索)のリストを記述するビットマスクをパラメーターとして受け取ります。
HRESULT NuiInitialize(
DWORD dwFlags,
);
Kinectで画像を受信するには、画像キャプチャストリームを初期化する必要があります。 これらの目的のために、
INuiInstance:: NuiImageStreamOpen()
メソッドが使用されます。
- 画像タイプ(カラー画像、深度バッファなど)
- 解像度(80x60から1280x1024)
- 画像処理フラグ(SDKのベータ版では使用されません)
- キャッシュされたフレームの数(
NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM
の最大値は現在4です) - 新しいフレームが受信されたときに発生するイベントへのハンドル(オプションのパラメーター。ただし、
NULL
を渡すと、キャプチャストリームが開始されないことが判明しました) - 関数が正常に完了したときに画像キャプチャストリームへのハンドルが書き込まれる変数へのポインター。
デバイスからの画像のキャプチャを停止するには、
INuiInstance::NuiShutdown()
メソッドを呼び出す必要があり、
INuiInstance
インスタンスでの作業が終了し
INuiInstance
、
MSR_NuiDestroyInstance()
関数を使用して、
INuiInstance
オブジェクトへのポインターを渡すパラメーターにメモリを解放
INuiInstance
必要があります。
深度バッファの取得
深度バッファの受信を開始するには、
INuiInstance:: NuiImageStreamOpen()
メソッドを呼び出して、
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX
または
NUI_IMAGE_TYPE_DEPTH
フラグを含む値を最初のパラメーターとして
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX
必要があります。 後続の処理に最適なバッファーは、
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX
フラグを使用して
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX
。 ソースコードでは、同様の呼び出しは次のようになります。
if (FAILED(info->first->NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
NUI_IMAGE_RESOLUTION_320x240, 0,
3,
hDepthFrameEvent,
&info->second))) { /* Handle error here */ }
上記の呼び出しの結果、変数
info->second
は、画像キャプチャストリームへのハンドルがあります。
hDepthFrameEvent
イベント
hDepthFrameEvent
は、
CreateEvent()
関数を使用して作成できます。
新しい画像が利用可能になると、
hDepthFrameEvent
イベントが
hDepthFrameEvent
ます。 このイベントの待機は、
WaitForMultipleObjects()
または
WaitForSingleObject()
関数を使用して実装できます。
パラメーターとして渡す必要がある
NuiImageStreamGetNextFrame()
メソッドを使用して、デバイスからバッファー自体を取得できます。
- キャプチャストリーム記述子
- ミリ秒のバッファー待機期間
- 受信したバッファーに関する情報が書き込まれるNUI_IMAGE_FRAME構造体へのポインター
virtual HRESULT NuiImageStreamGetNextFrame(
_In_ HANDLE hStream,
_In_ DWORD dwMillisecondsToWait,
_Deref_out_ CONST NUI_IMAGE_FRAME **ppcImageFrame
);
結果の
NUI_IMAGE_FRAME
インスタンス
NUI_IMAGE_FRAME
は、現在、
NuiImageBuffer *pFrameTexture
最も関心があり
NuiImageBuffer *pFrameTexture
。
バッファデータを直接操作するには、
LockRect()
メソッドを呼び出す必要があります。
LockRect()
メソッドに
LockRect()
4つのパラメーターがあり、そのうち2つはベータ版のAPIで使用されます。
最初のパラメーターとして、0を2番目のパラメーターとして
KINECT_LOCKED_RECT
構造体へのポインターに渡す必要があります。このポインターには、関数が正常に完了した後、バッファーを操作するためのデータが書き込まれます。
NULL
を3番目のパラメーターとして、0を4番目のパラメーターとして渡し
NULL
。
STDMETHODIMP LockRect(
UINT Level,
KINECT_LOCKED_RECT* pLockedRect,
CONST RECT* pRectUsuallyNull,
DWORD Flags
);
さらに、
KINECT_LOCKED_RECT
構造体では、深度データを直接含む
pBits
フィールドに関心があります。 バッファ内の各ピクセルには、2バイトが割り当てられます。 公式フォーラムのFAQから判断すると、データ形式は次のとおりです。
•
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX
フラグを使用する場合、12個の最下位ビットが深度値に割り当てられ、残りの3ビットがプレーヤーインデックスに割り当てられ、最上位ビットは使用されません。
•
NUI_INITIALIZE_FLAG_USES_DEPTH
フラグを使用する場合、下位12ビットが深度値に割り当てられ、残りは使用されません。
グレーの濃淡で画像を取得するには、0〜255の範囲の値を取得するように深度値を正規化する必要があります。次のように実行できます。
USHORT RealDepth = (s & 0xfff8) >> 3;
BYTE l = 255 - (BYTE)(256*RealDepth/0x0fff);
RGBQUAD q;
q.rgbRed = q.rgbBlue = q.rgbGreen = l;
return q;
受信したイメージを使用して作業を完了するには、バッファに割り当てられたメモリを解放する必要があります。 これは、ストリーム記述子と
NUI_IMAGE_FRAME
インスタンスへのポインターを
NUI_IMAGE_FRAME
として取る
NuiImageStreamReleaseFrame()
メソッドを使用して
NuiImageStreamReleaseFrame()
でき
NUI_IMAGE_FRAME
。
私たちが今持っているものを要約しましょう:
- キャプチャを開始するには、
NuiInitialize()
メソッドを使用してデバイスを初期化する必要があります。 - 次に、
NuiImageStreamOpen()
メソッドを使用してキャプチャストリームを開始する必要があります。 - 新しい画像が受信されると、その記述子が
NuiImageStreamOpen()
渡されたイベントが発生します。 - イベントが呼び出された後、
NuiImageStreamGetNextFrame()
メソッドを使用してフレームを取得できます。 - 次に、
NuiImageBuffer::LockRect()
メソッドを使用してバッファーをキャプチャします。 - その後、バッファを通過して各ピクセルの色を取得し、深度値を正規化します。
-
NuiImageStreamReleaseFrame()
メソッドを使用してバッファをNuiImageStreamReleaseFrame()
ます。 - デバイスからの画像のキャプチャを停止するには、
NuiShutdown()
メソッドを使用してNuiShutdown()
解除する必要があります。
では、これらすべてをどのように実践できるかを見てみましょう。
wxKinectHelper.h
class KinectHelper
{
...
const wxSize & GetFrameSize();
BYTE * CreateDataBuffer();
void FreeDataBuffer(BYTE * data);
size_t GetDataBufferLength();
bool StartGrabbing(size_t deviceIndex, HANDLE hDepthFrameEvent);
bool ReadKinectFrame(size_t deviceIndex, BYTE * data);
bool IsDeviceOK(size_t deviceIndex);
bool IsGrabbingStarted(size_t deviceIndex);
static RGBQUAD Nui_ShortToQuad_Depth( USHORT s );
protected :
InstanceVector m_Instances;
wxSize m_FrameSize;
...
};
wxKinectHelper.cpp
...
void ReadLockedRect(KINECT_LOCKED_RECT & LockedRect, int w, int h, BYTE * data)
{
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
// draw the bits to the bitmap
USHORT * pBufferRun = (USHORT*) pBuffer;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD quad = KinectHelper::Nui_ShortToQuad_Depth( *pBufferRun );
pBufferRun++;
int offset = (w * y + x) * 3;
data[offset + 0] = quad.rgbRed;
data[offset + 1] = quad.rgbGreen;
data[offset + 2] = quad.rgbBlue;
}
}
}
}
...
BYTE * KinectHelper::CreateDataBuffer()
{
size_t length = GetDataBufferLength();
BYTE * result = (BYTE*)CoTaskMemAlloc(length);
memset(result, 0, length);
return result;
}
size_t KinectHelper::GetDataBufferLength()
{
return m_FrameSize.GetWidth() * m_FrameSize.GetHeight() * 3;
}
void KinectHelper::FreeDataBuffer(BYTE * data)
{
CoTaskMemFree((LPVOID)data);
}
void KinectHelper::Finalize()
{
for (InstanceVector::const_iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
if ((*i).first && (*i).second)
{
(*i).first->NuiShutdown();
MSR_NuiDestroyInstance((*i).first);
}
}
}
bool KinectHelper::StartGrabbing(size_t deviceIndex, HANDLE hDepthFrameEvent)
{
do
{
InstanceInfo * info = GetInstanceByIndex(deviceIndex);
if (!info || !info->first) break ;
if (FAILED(info->first->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX))) break ;
if (FAILED(info->first->NuiImageStreamOpen(
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
NUI_IMAGE_RESOLUTION_320x240, 0,
3,
hDepthFrameEvent,
&info->second))) break ;
}
while ( false );
return false ;
}
bool KinectHelper::IsDeviceOK(size_t deviceIndex)
{
return GetInstanceByIndex(deviceIndex) != NULL;
}
bool KinectHelper::IsGrabbingStarted(size_t deviceIndex)
{
InstanceInfo * info = GetInstanceByIndex(deviceIndex);
return (info != NULL && info->first != NULL && info->second != NULL);
}
bool KinectHelper::ReadKinectFrame(size_t deviceIndex, BYTE * data)
{
do
{
if (deviceIndex < 0) break ;
InstanceInfo * info = GetInstanceByIndex((size_t)deviceIndex);
if (!info || !info->second) break ;
const NUI_IMAGE_FRAME * pImageFrame;
if (FAILED(NuiImageStreamGetNextFrame(
info->second, 200, &pImageFrame))) break ;
NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
KINECT_LOCKED_RECT LockedRect;
pTexture->LockRect( 0, &LockedRect, NULL, 0 );
ReadLockedRect(LockedRect, m_FrameSize.GetWidth(),
m_FrameSize.GetHeight(), data);
NuiImageStreamReleaseFrame(info->second, pImageFrame);
return true ;
}
while ( false );
return false ;
}
RGBQUAD KinectHelper::Nui_ShortToQuad_Depth( USHORT s )
{
USHORT RealDepth = (s & 0xfff8) >> 3;
BYTE l = 255 - (BYTE)(256*RealDepth/0x0fff);
RGBQUAD q;
q.rgbRed = q.rgbBlue = q.rgbGreen = l;
return q;
}
KinectTestMainFrame.h
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
...
void OnDEVICELISTBOXSelected( wxCommandEvent& event );
...
void ShowDevices();
void StopGrabbing();
HANDLE m_NewDepthFrameEvent;
KinectHelper * m_KinectHelper;
BYTE * m_pDepthBuffer;
wxImage * m_CurrentImage;
int m_SelectedDeviceIndex;
};
KinectTestMainFrame.cpp
...
BEGIN_EVENT_TABLE( KinectTestMainFrame, wxFrame )
EVT_LISTBOX( ID_DEVICE_LISTBOX, KinectTestMainFrame::OnDEVICELISTBOXSelected )
END_EVENT_TABLE()
...
void KinectTestMainFrame::Init()
{
m_NewDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_KinectHelper = new KinectHelper;
m_pDepthBuffer = m_KinectHelper->CreateDataBuffer();
m_CurrentImage = new wxImage(
m_KinectHelper->GetFrameSize().GetWidth(),
m_KinectHelper->GetFrameSize().GetHeight(),
m_pDepthBuffer, true );
m_SelectedDeviceIndex = -1;
m_MainSizer = NULL;
m_DeviceListBox = NULL;
m_Canvas = NULL;
}
...
KinectTestMainFrame::~KinectTestMainFrame()
{
StopGrabbing();
wxDELETE(m_CurrentImage);
m_KinectHelper->FreeDataBuffer(m_pDepthBuffer);
wxDELETE(m_KinectHelper);
}
...
wxThread::ExitCode KinectTestMainFrame::Entry()
{
while (!GetThread()->TestDestroy())
{
int mEventIndex = WaitForMultipleObjects(
1, &m_NewDepthFrameEvent, FALSE, 100);
switch (mEventIndex)
{
case 0:
{
wxCriticalSectionLocker lock (m_CS);
m_KinectHelper->ReadKinectFrame(
m_SelectedDeviceIndex, m_pDepthBuffer);
m_Canvas->Refresh();
}
break ;
default :
break ;
}
}
return NULL;
}
...
void KinectTestMainFrame::OnDEVICELISTBOXSelected( wxCommandEvent& event )
{
do
{
StopGrabbing();
size_t deviceIndex =
(size_t)m_DeviceListBox->GetClientData( event .GetInt());
if (deviceIndex < 0 || deviceIndex >
m_KinectHelper->GetDeviceCount()) break ;
m_SelectedDeviceIndex = deviceIndex;
if (!m_KinectHelper->IsDeviceOK(deviceIndex)) break ;
if (!m_KinectHelper->IsGrabbingStarted(deviceIndex))
{
m_KinectHelper->StartGrabbing(
deviceIndex, m_NewDepthFrameEvent);
if (CreateThread() != wxTHREAD_NO_ERROR) break ;
m_Canvas->SetCurrentImage(m_CurrentImage);
GetThread()->Run();
}
}
while ( false );
}
void KinectTestMainFrame::StopGrabbing()
{
if (GetThread())
{
if (GetThread()->IsAlive())
{
GetThread()->Delete();
}
if (m_kind == wxTHREAD_JOINABLE)
{
if (GetThread()->IsAlive())
{
GetThread()->Wait();
}
wxDELETE(m_thread);
}
else
{
m_thread = NULL;
}
}
}
アプリケーション
wxKinectHelper
と、
wxKinectHelper
オブジェクトは、解像度(320x240x24)に応じて、深度バッファーにメモリを割り当てます。 次に、割り当てられたメモリ領域がRGBバッファとして
m_CurrentImage
オブジェクトに転送されます。
使用可能なデバイスのリストでデバイスが
m_CurrentImage
れると、デバイスからの画像キャプチャストリームが開始され、
m_CurrentImage
オブジェクト
m_CurrentImage
キャンバスに関連付けられます。
Entry()
メソッドは、デバイスからの新しいイメージを待ちます。 画像が利用可能になると、RGBバッファが新しい値で満たされ、キャンバスが再描画されます。
その結果、アプリケーションを起動してリスト内のデバイス名をクリックすると、次のようなものが表示されます(図6)。

カメラからカラー画像を取得する
デバイスのカメラから画像を取得するには、
NuiInitialize()
メソッドを呼び出すときに
NUI_INITIALIZE_FLAG_USES_COLOR
フラグを指定し、
NuiInitialize()
メソッドを呼び出すときに少なくとも640x480の解像度を指定する必要があります。
その結果、コードは次のようになります。
if (FAILED(info->first->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX|
NUI_INITIALIZE_FLAG_USES_COLOR))) break ;
if (FAILED(info->first->NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,
NUI_IMAGE_RESOLUTION_640x480, 0,
3,
hDepthFrameEvent,
&info->second))) break ;
したがって、
KINECT_LOCKED_RECT
構造のデータはRGBA形式に含まれています(SDKで利用可能な
RGBQUAD
構造は、データにアクセスするのに非常に適しています)。 したがって、RGBバッファーを取得するコードは次のようになります。
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD * quad = ((RGBQUAD*)pBuffer) + x;
int offset = (w * y + x) * 3;
data[offset + 0] = quad->rgbRed;
data[offset + 1] = quad->rgbGreen;
data[offset + 2] = quad->rgbBlue;
}
pBuffer += LockedRect.Pitch;
}
}
プレイヤーの位置の追跡(スケルトントラッキング)
プレイヤーの「スケルトン」のセグメントを取得して表示するアルゴリズムは、通常の画像とは異なります。
スケルトンセグメントを取得する可能性を有効にするには、
NuiInitialize()
メソッドが値
NUI_INITIALIZE_FLAG_USES_SKELETON
を含むフラグを渡し、次に
NuiSkeletonTrackingEnable()
メソッドを呼び出す必要があります。 2番目のパラメーターとして、一連のフラグ(SDKのベータ版ではこのパラメーターが無視されるため、0を渡すことができます)。
スケルトンセグメントの受信フローを完了するには、
NuiSkeletonTrackingDisable()
メソッドを呼び出す必要があります。
コードでは、次のようになります。
if (FAILED(info->first->NuiSkeletonTrackingEnable(hSkeletonFrameEvent, 0)))
{ /* error */ };
パラメーターとして
NuiSkeletonGetNextFrame()
メソッドを使用して、プレーヤーの位置に関する情報を含むデータバッファーを取得できます。
- バッファー待機期間(ミリ秒)
-
NUI_SKELETON_FRAME
構造体へのポインター。関数が正常に完了した場合、データバッファーへのポインターが含まれます。
NuiSkeletonGetNextFrame()
メソッドを呼び出した後、NUI_SKELETON_FRAME構造体のインスタンスを取得します。 もっと詳しく見てみましょう。
struct _NUI_SKELETON_FRAME {
LARGE_INTEGER liTimeStamp;
DWORD dwFrameNumber;
DWORD dwFlags;
Vector4 vFloorClipPlane;
Vector4 vNormalToGravity;
NUI_SKELETON_DATA SkeletonData[NUI_SKELETON_COUNT];
} NUI_SKELETON_FRAME;
-
liTimeStamp
スケルトンセグメントが取得された深度バッファを受信した日付\時間。 -
dwFlag
はフラグを含むビットマスクです。 -
vFloorClipPlane
床下のすべてをクリップするために使用された床座標(ライブラリ内で計算)。 -
vNormalToGravity
法線ベクトル。 -
dwFrameNumber
フレーム番号。 -
SkeletonData
-NUI_SKELETON_DATA
構造の配列。各構造には、1人のプレーヤーのスケルトンセグメントに関するデータが含まれます。
NUI_SKELETON_FRAME
構造体の説明から
NUI_SKELETON_FRAME
ように、限られた数のプレーヤーがサポートされています(現在のSDKバージョンでは、
NUI_SKELETON_COUNT
の値は6です)。
ここで、
NUI_SKELETON_DATA
構造を検討し
NUI_SKELETON_DATA
。
struct _NUI_SKELETON_DATA {
NUI_SKELETON_TRACKING_STATE eTrackingState;
DWORD dwTrackingID;
DWORD dwEnrollmentIndex;
DWORD dwUserIndex;
Vector4 Position;
Vector4 SkeletonPositions[NUI_SKELETON_POSITION_COUNT];
NUI_SKELETON_POSITION_TRACKING_STATE
eSkeletonPositionTrackingState[NUI_SKELETON_POSITION_COUNT];
DWORD dwQualityFlags;
} NUI_SKELETON_DATA;
-
eTrackingState
-NUI_SKELETON_TRACKING_STATE
列挙からの値。 プレーヤーが見つからなかったこと、プレーヤーの座標だけが見つかった(スケルトンセグメントなし)、または座標とスケルトンセグメントが見つかったことを示します。 -
dwEnrollmentIndex
ドキュメント(p。20)から判断すると、現在のバージョンでは使用されていません。 -
dwUserIndex
現在のSDKバージョンでは、常にXUSER_INDEX_NONE
と等しくなりXUSER_INDEX_NONE
。 -
dwTrackingID
追跡されるプレーヤーの番号。 -
Position
-プレイヤーの座標。 -
SkeletonPositions
スケルトンセグメントのジョイントの座標のリスト -
eSkeletonPositionTrackingState
スケルトンセグメントのアーティキュレーションが見つかったかどうかを示すフラグのリスト。
NUI_SKELETON_DATA
構造体の説明から
NUI_SKELETON_DATA
ように、サポートされるジョイントジョイントの数は、
NUI_SKELETON_POSITION_COUNT
等しい数によって制限されます。
次に、上記のAPIを使用してプレーヤーの座標を取得する実装を検討します。
KinectHelper.h
...
struct KinectStreams
{
HANDLE hDepth;
HANDLE hColor;
HANDLE hSkeleton;
KinectStreams() : hDepth(NULL), hColor(NULL), hSkeleton(NULL) {}
};
...
KinectHelper.cpp
void KinectHelper::Finalize()
{
for (InstanceVector::const_iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
if ((*i).first)
{
...
if ((*i).second.hSkeleton != NULL)
{
(*i).first->NuiSkeletonTrackingDisable();
}
MSR_NuiDestroyInstance((*i).first);
}
}
}
bool KinectHelper::StartGrabbing(size_t deviceIndex,
HANDLE hDepthFrameEvent,
HANDLE hColorFrameEvent,
HANDLE hSkeletonFrameEvent)
{
do
{
if (hDepthFrameEvent == NULL &&
hColorFrameEvent == NULL &&
hSkeletonFrameEvent == NULL) break ;
InstanceInfo * info = GetInstanceByIndex(deviceIndex);
if (!info || !info->first) break ;
if (FAILED(info->first->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX |
NUI_INITIALIZE_FLAG_USES_COLOR |
NUI_INITIALIZE_FLAG_USES_SKELETON))) break ;
...
if (hSkeletonFrameEvent != NULL)
{
if (FAILED(info->first->NuiSkeletonTrackingEnable(
hSkeletonFrameEvent, 0))) break ;
info->second.hSkeleton = hSkeletonFrameEvent;
}
}
while ( false );
return false ;
}
void * KinectHelper::ReadSkeletonFrame(size_t deviceIndex)
{
do
{
if (deviceIndex < 0) break ;
InstanceInfo * info = GetInstanceByIndex((size_t)deviceIndex);
if (!info || !info->second.hColor) break ;
NUI_SKELETON_FRAME * frame = new NUI_SKELETON_FRAME;
if (FAILED(info->first->NuiSkeletonGetNextFrame(200, frame))) break ;
return frame;
}
while ( false );
return NULL;
}
プレイヤースケルトンレンダリング
現時点では、次の方法に関する情報があります。
- プレイヤーの位置を取得するためにデバイスを初期化します
- プレイヤーのキャプチャ位置のストリームを開始します
- プレイヤーのスケルトンセグメントの座標を含むバッファーを取得します
- バッファ内の目的のデータを取得します
- プレーヤーの位置を取得するフローを停止します。
ここで、アプリケーションで受信したデータを何らかの方法で表示する必要があります。
NUI_SKELETON_FRAME
データを使用して何かを行う前に、それらを前処理に送信する必要があります。 前処理は
NuiTransformSmooth()
メソッドによって実行されます-
NuiTransformSmooth()
や突然の動きを避けるために、セグメントの座標をフィルタリングします。 パラメーターとして、
NuiTransformSmooth()
メソッドは、
NUI_SKELETON_FRAME
構造体へのポインター、およびオプションで、前処理パラメーターを含む
NUI_TRANSFORM_SMOOTH_PARAMETERS
オブジェクトへのポインターを
NUI_TRANSFORM_SMOOTH_PARAMETERS
ます。
HRESULT NuiTransformSmooth(
NUI_SKELETON_FRAME *pSkeletonFrame,
CONST NUI_TRANSFORM_SMOOTH_PARAMETERS *pSmoothingParams
);
スケルトンセグメントを表示するには、それらの座標を画像座標に変換する必要があります。 これは、パラメーターとして
NuiTransformSkeletonToDepthImageF()
メソッドを使用して
NuiTransformSkeletonToDepthImageF()
できます。
-
Vector4
構造の形式でのスケルトンセグメントのジョイントポイントの座標。 - X座標が書き込まれる変数へのポインター。
- Y座標が書き込まれる変数へのポインター。
VOID NuiTransformSkeletonToDepthImageF(
Vector4 vPoint,
_Out_ FLOAT *pfDepthX,
_Out_ FLOAT *pfDepthY
);
すべてのアーティキュレーションポイントの座標を受け取ったら、通常のグラフィックプリミティブを使用してそれらをキャンバスに表示できます。
スケルトンセグメントを表示するための実際のコードは次のとおりです。
SkeletonPainter.h
#pragma once
#include <wx/wx.h>
class SkeletonPainterImpl;
class SkeletonPainter
{
public :
SkeletonPainter();
~SkeletonPainter();
void DrawSkeleton(wxDC & dc, void * data);
private :
SkeletonPainterImpl * m_Impl;
};
SkeletonPainter.cpp
#include "SkeletonPainter.h"
#if defined(__WXMSW__)
#include "SkeletonPainterImplMSW.h"
#endif
SkeletonPainter::SkeletonPainter()
{
#if defined(__WXMSW__)
m_Impl = new SkeletonPainterImplMSW;
#else
m_Impl = NULL;
#endif
}
SkeletonPainter::~SkeletonPainter()
{
wxDELETE(m_Impl);
}
void SkeletonPainter::DrawSkeleton(wxDC & dc, void * data)
{
if (m_Impl)
{
m_Impl->DrawSkeleton(dc, data);
}
}
SkeletonPainterImpl.h
#pragma once
#include <wx/wx.h>
class SkeletonPainterImpl
{
public :
virtual ~SkeletonPainterImpl() {}
virtual void DrawSkeleton(wxDC & dc, void * data) = 0;
};
SkeletonPainterImplMSW.h
#pragma once
#include "SkeletonPainterImpl.h"
#include "msr_nuiapi.h"
class SkeletonPainterImplMSW : public SkeletonPainterImpl
{
public :
~SkeletonPainterImplMSW();
void DrawSkeleton(wxDC & dc, void * data);
private :
void Nui_DrawSkeleton(wxDC & dc, NUI_SKELETON_DATA * data, size_t index);
void Nui_DrawSkeletonSegment(wxDC & dc, wxPoint * points, int numJoints, ... );
static wxPen m_SkeletonPen[6];
};
SkeletonPainterImplMSW.cpp
#include "SkeletonPainterImplMSW.h"
wxPen SkeletonPainterImplMSW::m_SkeletonPen[6] =
{
wxPen(wxColor(255, 0, 0), wxSOLID),
...
};
SkeletonPainterImplMSW::~SkeletonPainterImplMSW()
{
}
void SkeletonPainterImplMSW::DrawSkeleton(wxDC & dc, void * data)
{
do
{
NUI_SKELETON_FRAME * frame =
reinterpret_cast<NUI_SKELETON_FRAME*>(data);
if (!frame) break ;
int skeletonCount(0);
for ( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
{
if ( frame->SkeletonData[i].eTrackingState ==
NUI_SKELETON_TRACKED )
{
skeletonCount++;
}
}
if (!skeletonCount) break ;
NuiTransformSmooth(frame, NULL);
for (size_t i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
{
if (frame->SkeletonData[i].eTrackingState ==
NUI_SKELETON_TRACKED)
{
Nui_DrawSkeleton(dc, &frame->SkeletonData[i], i );
}
}
}
while ( false );
}
void SkeletonPainterImplMSW::Nui_DrawSkeleton(wxDC & dc,
NUI_SKELETON_DATA * data, size_t index)
{
wxPoint points[NUI_SKELETON_POSITION_COUNT];
float fx(0), fy(0);
wxSize imageSize = dc.GetSize();
for (size_t i = 0; i < NUI_SKELETON_POSITION_COUNT; i++)
{
NuiTransformSkeletonToDepthImageF(
data->SkeletonPositions[i], &fx, &fy);
points[i].x = ( int ) ( fx * imageSize.GetWidth() + 0.5f );
points[i].y = ( int ) ( fy * imageSize.GetHeight() + 0.5f );
}
Nui_DrawSkeletonSegment(dc,points,4,
NUI_SKELETON_POSITION_HIP_CENTER,
NUI_SKELETON_POSITION_SPINE,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_HEAD);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_SHOULDER_LEFT,
NUI_SKELETON_POSITION_ELBOW_LEFT,
NUI_SKELETON_POSITION_WRIST_LEFT,
NUI_SKELETON_POSITION_HAND_LEFT);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_SHOULDER_RIGHT,
NUI_SKELETON_POSITION_ELBOW_RIGHT,
NUI_SKELETON_POSITION_WRIST_RIGHT,
NUI_SKELETON_POSITION_HAND_RIGHT);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_HIP_CENTER,
NUI_SKELETON_POSITION_HIP_LEFT,
NUI_SKELETON_POSITION_KNEE_LEFT,
NUI_SKELETON_POSITION_ANKLE_LEFT,
NUI_SKELETON_POSITION_FOOT_LEFT);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_HIP_CENTER,
NUI_SKELETON_POSITION_HIP_RIGHT,
NUI_SKELETON_POSITION_KNEE_RIGHT,
NUI_SKELETON_POSITION_ANKLE_RIGHT,
NUI_SKELETON_POSITION_FOOT_RIGHT);
}
void SkeletonPainterImplMSW::Nui_DrawSkeletonSegment(wxDC & dc,
wxPoint * points, int numJoints, ...)
{
va_list vl;
va_start(vl,numJoints);
wxPoint segmentPositions[NUI_SKELETON_POSITION_COUNT];
for ( int iJoint = 0; iJoint < numJoints; iJoint++)
{
NUI_SKELETON_POSITION_INDEX jointIndex =
va_arg(vl,NUI_SKELETON_POSITION_INDEX);
segmentPositions[iJoint].x = points[jointIndex].x;
segmentPositions[iJoint].y = points[jointIndex].y;
}
dc.SetPen(*wxBLUE_PEN);
dc.DrawLines(numJoints, segmentPositions);
va_end(vl);
}
アプリケーションで
SkeletonPainter
クラスを使用すると、次のようになります。
KinectTestMainFrame.h
...
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
...
HANDLE m_NewSkeletonFrameEvent;
wxImage m_SkeletonImage;
...
};
...
KinectTestMainFrame.cpp
...
wxThread::ExitCode KinectTestMainFrame::Entry()
{
HANDLE eventHandles[3];
eventHandles[0] = m_NewDepthFrameEvent;
eventHandles[1] = m_NewColorFrameEvent;
eventHandles[2] = m_NewSkeletonFrameEvent;
SkeletonPainter painter;
while (!GetThread()->TestDestroy())
{
int mEventIndex = WaitForMultipleObjects(
_countof(eventHandles), eventHandles, FALSE, 100);
switch (mEventIndex)
{
...
case 2:
{
void * frame = m_KinectHelper->ReadSkeletonFrame(
m_SelectedDeviceIndex);
if (frame)
{
wxBitmap bmp(
m_SkeletonImage.GetWidth(),
m_SkeletonImage.GetHeight());
wxMemoryDC dc(bmp);
painter.DrawSkeleton(dc, frame);
m_KinectHelper->ReleaseSkeletonFrame(frame);
dc.SelectObject(wxNullBitmap);
m_SkeletonImage = bmp.ConvertToImage();
m_SkeletonCanvas->Refresh();
}
}
break ;
default :
break ;
}
}
return NULL;
}
上記のアクションの結果、次のような結果が得られます(図7)。

アプリケーション内のプラットフォーム固有のコードを取り除く
上記の例は、プロジェクトがクロスプラットフォームライブラリを使用してユーザーインターフェイスを開発し、GUIコードの一部がWindows専用のAPIを使用して記述されていることを除いて、すべての人に適しています。
libfreenectやOpenNIなど、Kinectを操作するためのサードパーティライブラリがいくつかありますが、すでにこの段階で、アプリケーションコードがMicrosoftのSDKの使用に結び付けられている状況がありました。
この迷惑な誤解を解決するには、別のグラバークラスのデバイスから画像を受信することに関連するコードを削除し、
KinectHelper
クラスの機能をデバイスのリストの受信とグラバーインスタンスの作成に制限します。
KinectGrabberBase.h
#pragma once
#include <wx/wx.h>
class KinectGrabberBase
{
public :
KinectGrabberBase(wxEvtHandler * handler);
virtual ~KinectGrabberBase();
virtual bool GrabDepthFrame(unsigned char * data) = 0;
virtual bool GrabColorFrame(unsigned char * data) = 0;
virtual void * GrabSkeletonFrame() = 0;
virtual bool Start() = 0;
virtual bool Stop() = 0;
virtual bool IsStarted() = 0;
const wxSize & GetDepthFrameSize();
const wxSize & GetColorFrameSize();
protected :
wxSize m_DepthFrameSize;
wxSize m_ColorFrameSize;
wxEvtHandler * m_Handler;
};
BEGIN_DECLARE_EVENT_TYPES()
DECLARE_LOCAL_EVENT_TYPE(KINECT_DEPTH_FRAME_RECEIVED, -1)
DECLARE_LOCAL_EVENT_TYPE(KINECT_COLOR_FRAME_RECEIVED, -1)
DECLARE_LOCAL_EVENT_TYPE(KINECT_SKELETON_FRAME_RECEIVED, -1)
END_DECLARE_EVENT_TYPES()
KinectGrabberBase.cpp
#include "KinectGrabberBase.h"
DEFINE_EVENT_TYPE(KINECT_DEPTH_FRAME_RECEIVED)
DEFINE_EVENT_TYPE(KINECT_COLOR_FRAME_RECEIVED)
DEFINE_EVENT_TYPE(KINECT_SKELETON_FRAME_RECEIVED)
...
KinectGrabberMSW.h
#pragma once
#include "KinectGrabberBase.h"
#include "MSR_NuiApi.h"
class KinectGrabberMSW : public KinectGrabberBase, public wxThreadHelper
{
...
private :
virtual wxThread::ExitCode Entry();
BYTE * CreateDepthDataBuffer();
BYTE * CreateColorDataBuffer();
size_t GetDepthDataBufferLength();
size_t GetColorDataBufferLength();
void FreeDataBuffer(BYTE * data);
bool ReadDepthFrame();
bool ReadColorFrame();
bool ReadSkeletonFrame();
void ReadDepthLockedRect(KINECT_LOCKED_RECT & LockedRect,
int w, int h, BYTE * data);
void ReadColorLockedRect(KINECT_LOCKED_RECT & LockedRect,
int w, int h, BYTE * data);
static RGBQUAD Nui_ShortToQuad_Depth( USHORT s );
void ResetEvents();
void StopThread();
bool CopyLocalBuffer(BYTE * src, BYTE * dst, size_t count);
HANDLE m_NewDepthFrameEvent;
HANDLE m_NewColorFrameEvent;
HANDLE m_NewSkeletonFrameEvent;
HANDLE m_DepthStreamHandle;
HANDLE m_ColorStreamHandle;
BYTE * m_DepthBuffer;
BYTE * m_ColorBuffer;
INuiInstance * m_Instance;
size_t m_DeviceIndex;
NUI_SKELETON_FRAME m_SkeletonFrame;
};
KinectGrabberMSW.cpp
#include "KinectGrabberMSW.h"
KinectGrabberMSW::KinectGrabberMSW(wxEvtHandler * handler, size_t deviceIndex)
: KinectGrabberBase(handler), m_DeviceIndex(deviceIndex), m_Instance(NULL)
{
m_DepthBuffer = CreateDepthDataBuffer();
m_ColorBuffer = CreateColorDataBuffer();
ResetEvents();
do
{
if (FAILED(MSR_NuiCreateInstanceByIndex(( int )m_DeviceIndex, &m_Instance))) break ;
if (FAILED(m_Instance->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX |
NUI_INITIALIZE_FLAG_USES_COLOR |
NUI_INITIALIZE_FLAG_USES_SKELETON))) break ;
}
while ( false );
}
...
void * KinectGrabberMSW::GrabSkeletonFrame()
{
do
{
if (!GetThread() || !GetThread()->IsAlive() ||
!m_Instance || !m_NewSkeletonFrameEvent) break ;
return &m_SkeletonFrame;
}
while ( false );
return NULL;
}
bool KinectGrabberMSW::Start()
{
do
{
if (!m_Instance) break ;
if (GetThread() && GetThread()->IsAlive()) break ;
if (CreateThread() != wxTHREAD_NO_ERROR) break ;
m_NewDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_NewColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_NewSkeletonFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
if (FAILED(m_Instance->NuiImageStreamOpen(
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
NUI_IMAGE_RESOLUTION_320x240, 0,
3,
m_NewDepthFrameEvent,
&m_DepthStreamHandle))) break ;
if (FAILED(m_Instance->NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,
NUI_IMAGE_RESOLUTION_640x480, 0,
4,
m_NewColorFrameEvent,
&m_ColorStreamHandle))) break ;
if (FAILED(m_Instance->NuiSkeletonTrackingEnable(
m_NewSkeletonFrameEvent, 0))) break ;
GetThread()->Run();
return true ;
}
while ( false );
return false ;
}
...
wxThread::ExitCode KinectGrabberMSW::Entry()
{
HANDLE eventHandles[3];
eventHandles[0] = m_NewDepthFrameEvent;
eventHandles[1] = m_NewColorFrameEvent;
eventHandles[2] = m_NewSkeletonFrameEvent;
while (!GetThread()->TestDestroy())
{
int mEventIndex = WaitForMultipleObjects(
_countof(eventHandles), eventHandles, FALSE, 100);
switch (mEventIndex)
{
case 0: ReadDepthFrame(); break ;
case 1: ReadColorFrame(); break ;
case 2: ReadSkeletonFrame(); break ;
default :
break ;
}
}
return NULL;
}
...
void KinectGrabberMSW::StopThread()
{
if (GetThread())
{
if (GetThread()->IsAlive())
{
GetThread()->Delete();
}
if (m_kind == wxTHREAD_JOINABLE)
{
if (GetThread()->IsAlive())
{
GetThread()->Wait();
}
wxDELETE(m_thread);
}
else
{
m_thread = NULL;
}
}
wxYield();
}
bool KinectGrabberMSW::ReadDepthFrame()
{
do
{
if (m_DeviceIndex < 0 || !m_Instance) break ;
const NUI_IMAGE_FRAME * pImageFrame;
if (FAILED(NuiImageStreamGetNextFrame(
m_DepthStreamHandle, 200, &pImageFrame))) break ;
NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
KINECT_LOCKED_RECT LockedRect;
pTexture->LockRect( 0, &LockedRect, NULL, 0 );
ReadDepthLockedRect(LockedRect,
m_DepthFrameSize.GetWidth(),
m_DepthFrameSize.GetHeight(),
m_DepthBuffer);
NuiImageStreamReleaseFrame(m_DepthStreamHandle, pImageFrame);
if (m_Handler)
{
wxCommandEvent e(KINECT_DEPTH_FRAME_RECEIVED, wxID_ANY);
e.SetInt(m_DeviceIndex);
m_Handler->AddPendingEvent(e);
}
return true ;
}
while ( false );
return false ;
}
bool KinectGrabberMSW::ReadColorFrame()
{
do
{
if (m_DeviceIndex < 0 || !m_Instance) break ;
const NUI_IMAGE_FRAME * pImageFrame;
if (FAILED(NuiImageStreamGetNextFrame(
m_ColorStreamHandle, 200, &pImageFrame))) break ;
NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
KINECT_LOCKED_RECT LockedRect;
pTexture->LockRect( 0, &LockedRect, NULL, 0 );
ReadColorLockedRect(LockedRect,
m_ColorFrameSize.GetWidth(),
m_ColorFrameSize.GetHeight(),
m_ColorBuffer);
NuiImageStreamReleaseFrame(m_ColorStreamHandle, pImageFrame);
if (m_Handler)
{
wxCommandEvent e(KINECT_COLOR_FRAME_RECEIVED, wxID_ANY);
e.SetInt(m_DeviceIndex);
m_Handler->AddPendingEvent(e);
}
return true ;
}
while ( false );
return false ;
}
bool KinectGrabberMSW::ReadSkeletonFrame()
{
do
{
if (m_DeviceIndex < 0 || !m_Instance) break ;
if (FAILED(m_Instance->NuiSkeletonGetNextFrame(200, &m_SkeletonFrame))) break ;
if (m_Handler)
{
wxCommandEvent e(KINECT_SKELETON_FRAME_RECEIVED, wxID_ANY);
e.SetInt(m_DeviceIndex);
m_Handler->AddPendingEvent(e);
}
return true ;
}
while ( false );
return false ;
}
void KinectGrabberMSW::ReadDepthLockedRect(KINECT_LOCKED_RECT & LockedRect, int w, int h, BYTE * data)
{
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
USHORT * pBufferRun = (USHORT*) pBuffer;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD quad = KinectGrabberMSW::Nui_ShortToQuad_Depth( *pBufferRun );
pBufferRun++;
int offset = (w * y + x) * 3;
data[offset + 0] = quad.rgbRed;
data[offset + 1] = quad.rgbGreen;
data[offset + 2] = quad.rgbBlue;
}
}
}
}
void KinectGrabberMSW::ReadColorLockedRect(KINECT_LOCKED_RECT & LockedRect, int w, int h, BYTE * data)
{
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD * quad = ((RGBQUAD*)pBuffer) + x;
int offset = (w * y + x) * 3;
data[offset + 0] = quad->rgbRed;
data[offset + 1] = quad->rgbGreen;
data[offset + 2] = quad->rgbBlue;
}
pBuffer += LockedRect.Pitch;
}
}
}
...
KinectHelper.h
#pragma once
class KinectGrabberBase;
class KinectHelper
{
public :
KinectHelper();
~KinectHelper();
size_t GetDeviceCount();
wxString GetDeviceName(size_t index);
KinectGrabberBase * CreateGrabber(wxEvtHandler * handler, size_t index);
};
KinectHelper.cpp
...
wxString KinectHelper::GetDeviceName(size_t index)
{
BSTR result;
DWORD size;
INuiInstance * instance(NULL);
wxString name = wxT( "Unknown Kinect Sensor" );
if (!FAILED(MSR_NuiCreateInstanceByIndex(index, &instance)))
{
if (instance != NULL)
{
if (instance->MSR_NuiGetPropsBlob(
MsrNui::INDEX_UNIQUE_DEVICE_NAME,
&result, &size))
{
name = result;
SysFreeString(result);
}
MSR_NuiDestroyInstance(instance);
}
}
return name;
}
KinectGrabberBase * KinectHelper::CreateGrabber(wxEvtHandler * handler, size_t index)
{
#if defined(__WXMSW__)
return new KinectGrabberMSW(handler, index);
#else
return NULL;
#endif
}
...
RGBバッファのメモリの割り当てと、別のストリームで画像をキャプチャするためのコードは、フォームクラスから削除できます。 これで、フォームクラスは次のようになります。
KinectTestMainFrame.h
class KinectTestMainFrame: public wxFrame
{
...
void OnDepthFrame(wxCommandEvent & event );
void OnColorFrame(wxCommandEvent & event );
void OnSkeletonFrame(wxCommandEvent & event );
...
wxImage m_CurrentImage;
int m_SelectedDeviceIndex;
wxImage m_ColorImage;
wxImage m_SkeletonImage;
KinectGrabberBase * m_Grabber;
...
};
KinectTestMainFrame.cpp
...
BEGIN_EVENT_TABLE( KinectTestMainFrame, wxFrame )
...
EVT_COMMAND (wxID_ANY, KINECT_DEPTH_FRAME_RECEIVED, \
KinectTestMainFrame::OnDepthFrame)
EVT_COMMAND (wxID_ANY, KINECT_COLOR_FRAME_RECEIVED, \
KinectTestMainFrame::OnColorFrame)
EVT_COMMAND (wxID_ANY, KINECT_SKELETON_FRAME_RECEIVED, \
KinectTestMainFrame::OnSkeletonFrame)
END_EVENT_TABLE()
...
void KinectTestMainFrame::OnDEVICELISTBOXSelected( wxCommandEvent& event )
{
do
{
size_t deviceIndex =
(size_t)m_DeviceListBox->GetClientData( event .GetInt());
if (deviceIndex < 0 ||
deviceIndex > m_KinectHelper->GetDeviceCount()) break ;
m_SelectedDeviceIndex = deviceIndex;
StartGrabbing();
}
while ( false );
}
void KinectTestMainFrame::StartGrabbing()
{
StopGrabbing();
m_Grabber = m_KinectHelper->CreateGrabber( this , m_SelectedDeviceIndex);
m_CurrentImage = wxImage(
m_Grabber->GetDepthFrameSize().GetWidth(),
m_Grabber->GetDepthFrameSize().GetHeight());
m_ColorImage = wxImage(
m_Grabber->GetColorFrameSize().GetWidth(),
m_Grabber->GetColorFrameSize().GetHeight());
m_SkeletonImage = wxImage(
m_Grabber->GetDepthFrameSize().GetWidth(),
m_Grabber->GetDepthFrameSize().GetHeight());
m_DepthCanvas->SetCurrentImage(&m_CurrentImage);
m_ColorCanvas->SetCurrentImage(&m_ColorImage);
m_SkeletonCanvas->SetCurrentImage(&m_SkeletonImage);
if (!m_Grabber->Start())
{
StopGrabbing();
}
}
...
void KinectTestMainFrame::OnDepthFrame(wxCommandEvent & event )
{
do
{
if (!m_Grabber) break ;
m_Grabber->GrabDepthFrame(m_CurrentImage.GetData());
m_DepthCanvas->Refresh();
}
while ( false );
}
void KinectTestMainFrame::OnColorFrame(wxCommandEvent & event )
{
do
{
if (!m_Grabber) break ;
m_Grabber->GrabColorFrame(m_ColorImage.GetData());
m_ColorCanvas->Refresh();
}
while ( false );
}
void KinectTestMainFrame::OnSkeletonFrame(wxCommandEvent & event )
{
do
{
if (!m_Grabber) break ;
SkeletonPainter painter;
wxBitmap bmp(m_SkeletonImage.GetWidth(), m_SkeletonImage.GetHeight());
wxMemoryDC mdc(bmp);
painter.DrawSkeleton(mdc, m_Grabber->GrabSkeletonFrame());
mdc.SelectObject(wxNullBitmap);
m_SkeletonImage = bmp.ConvertToImage();
m_SkeletonCanvas->Refresh();
}
while ( false );
}
コードからわかるように、グラバークラスは新しいフレームを受信すると、
wxEvtHandler
オブジェクトに通知を送信します(wxWidgetsの
wxFrame
クラスは
wxFrame
から派生し
wxEvtHandler
)。 フォームには、グラバーからの通知を受信したときに呼び出されるイベントハンドラーがあります。
KinectGrabberBase::GrabSkeletonFrame()
メソッドが
void*
返す理由も非常に単純です-さまざまなSDK(非公式のものを含む)を使用して画像キャプチャを実装する場合、これらすべてのSDKがプレーヤーの位置に関する情報を受け取るという事実ではありません同一のデータ構造の形式。 いずれにしても、座標は後処理のために送信する必要があります。 この場合、グラバーからポインターを受け取るコードは、それがどのタイプのデータに変換される必要があるかを自分で知っています。 グラフィカルインターフェイスは、グラバーの内部構造について知る必要はありません。
結論として
結論として、Microsoft SDKはベータ状態ですが、Kinect管理機能は完全には実装されていませんが、非常に使用可能です(たとえば、libfreenectでは、デバイス上のLEDと公式SDKをドキュメントによって判断できますが、できません)。 ライブラリは驚くほど安定して動作します。 開発者がメモリリークを避けるために注意を払ったことは注目に値します。 たとえば、終了時にストリームを閉じるのを忘れた場合、Visual Studioデバッガーはメモリリークを報告しません。ほとんどの場合、すべてが正しく終了し、ライブラリがアンロードされるとメモリから削除されます。
テストアプリケーションとライブラリのソースコードは、Google Code- wxKinectHelperにあります。
プロジェクトの開発とグラバーの新しい実装の追加を楽しみにしています。 現在、libfreenectを飼いならそうとしています。 箱から出して、予備のシャーマニズムなしで、画像を取得することを除いてすべてを開始することが可能でした-LEDインジケータが点滅し、エンジンが完全にブザーします。 OpenNIでも同じことをしようとしています。
便利なリンク
Microsoft Research Kinect SDK
Kinect SDK
SDK
, Kinect SDK
C++ Kinect SDK ( ) , — .