ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

Kinect for Windows SDK beta で遊んでみた 〜 C++で複数のKinectを扱う 〜 #shibuya_ni


ドキュメントを読んでいて、公式SDKは簡単に複数Kinectを扱えるようなので試してみました。
今のところの制約事項。

  • 複数Kinectを扱う場合は、カメラのみ(距離、ユーザー、骨格は使えない)
  • 最大8台まで接続可能(ヘッダファイルの定数)

ソース

ざっとこんな感じで、ソースが適当なのはご愛嬌。ここではINuiInstanceをラップしたクラスを使ってるので、INuiInstanceをベタで使った場合のサンプルも作った作ろう(たぶん。。。)
複数Kinectを扱う場合は、C++ではINuiInstanceを使うようだが、C#を見る限りINuiInstance一択でも良さそう。ただ、ドキュメントには、単体の場合はNuiXxxを使った方がいいよ的な記述も見られます(自分の英語力では読み取れない)。

手順(ProgrammingGuide_KinectSDK.pdfのP16を参照)
  1. MSR_NuiDeviceCountでアクティブなKinectの数を取得する
  2. MSR_NuiCreateInstanceByIndexでINuiInstanceのインスタンスを生成する
  3. INuiInstance::NuiInitializeで初期化する
  4. INuiInstance::NuiXxxのメソッドを使ってKinectを操作する
  5. 終了時にはINuiInstance::NuiShutdownで後始末する
  6. インスタンスの破棄には、MSR_NuiDestroyInstanceを使用する

素のAPI

ソースはこちら

// 複数のKinectのカメラ画像を表示する
#include <iostream>
#include <sstream>
#include <vector>

// MSR_NuiApi.hの前にWindows.hをインクルードする
#include <Windows.h>
#include <MSR_NuiApi.h>

#include <opencv2/opencv.hpp>

#define ERROR_CHECK( ret )  \
    if ( ret != S_OK ) {    \
        std::cout << "failed " #ret " " << ret << std::endl;    \
        exit( 1 );          \
    }

struct Runtime
{
    INuiInstance*       kinect;         // Kinectのインスタンス
    HANDLE              imageEvent;     // データの更新イベントハンドル
    HANDLE              streamHandle;   // 画像データのハンドル
    std::string         windowName;     // 表示するウィンドウの名前
    cv::Ptr< IplImage > image;          // 表示データ
};

void main()
{
    try {
        const NUI_IMAGE_RESOLUTION resolution = NUI_IMAGE_RESOLUTION_640x480;

        // アクティブなKinectの数を取得する
        int kinectCount = 0;
        ERROR_CHECK( ::MSR_NUIGetDeviceCount( &kinectCount ) );

        // Kinectのインスタンスを生成する
        typedef std::vector< Runtime > Runtimes;
        Runtimes runtime( kinectCount );
        for ( int i = 0; i < runtime.size(); ++i ) {
            // Kinectのインスタンス生成と初期化
            ERROR_CHECK( ::MSR_NuiCreateInstanceByIndex( i, &runtime[i].kinect ) );

            runtime[i].kinect->NuiInitialize( NUI_INITIALIZE_FLAG_USES_COLOR );

            runtime[i].imageEvent = ::CreateEvent( 0, TRUE, FALSE, 0 );
            ERROR_CHECK( runtime[i].kinect->NuiImageStreamOpen( NUI_IMAGE_TYPE_COLOR, resolution,
                            0, 2, runtime[i].imageEvent, &runtime[i].streamHandle ) );
 
            // ウィンドウ名を作成
            std::stringstream ss;
            ss << "malti_kinect " << (i + 1);
            runtime[i].windowName = ss.str();

            // 画面サイズを取得
            DWORD x = 0, y = 0;
            ::NuiImageResolutionToSize( resolution, x, y );

            // OpenCVの初期設定
            runtime[i].image = ::cvCreateImage( cvSize( x, y ), IPL_DEPTH_8U, 4 );
            ::cvNamedWindow( runtime[i].windowName.c_str() );
        }

        bool continue_ = true;
        while ( continue_ ) {
            for ( Runtimes::iterator it = runtime.begin(); it != runtime.end(); ++it ) {
                // データの更新を待つ
                ::WaitForSingleObject( it->imageEvent, INFINITE );

                // カメラデータの取得
                CONST NUI_IMAGE_FRAME *imageFrame = 0;
                ERROR_CHECK( it->kinect->NuiImageStreamGetNextFrame( it->streamHandle, 0, &imageFrame ) );

                // 画像データの取得
                KINECT_LOCKED_RECT rect;
                imageFrame->pFrameTexture->LockRect( 0, &rect, 0, 0 );

                // データのコピーと表示
                memcpy( it->image->imageData, (BYTE*)rect.pBits, it->image->widthStep * it->image->height );
                ::cvShowImage( it->windowName.c_str(), it->image );

                // カメラデータの解放
                ERROR_CHECK( it->kinect->NuiImageStreamReleaseFrame( it->streamHandle, imageFrame ) );

                int key = ::cvWaitKey( 10 );
                if ( key == 'q' ) {
                    continue_ = false;
                }
            }
        }

        // 終了処理
        for ( Runtimes::iterator it = runtime.begin(); it != runtime.end(); ++it ) {
            it->kinect->NuiShutdown();
        }

        ::cvDestroyAllWindows();
    }
    catch ( std::exception& ex ) {
        std::cout << ex.what() << std::endl;
    }
}

ラッパー版

ソースはこちら

// 複数のKinectのカメラ画像を表示する
#include <iostream>
#include <sstream>
#include <vector>

#include "kinect\nui\kinect.h"
#include "kinect\nui\ImageFrame.h"

#include <opencv2/opencv.hpp>

struct Runtime
{
    std::shared_ptr< kinect::nui::Kinect > kinect;
    std::string         windowName;
    cv::Ptr< IplImage > image;

    Runtime( int index, const std::string& name )
        : kinect( new kinect::nui::Kinect( index ) )
        , windowName( name )
    {
    }
};

void main()
{
    try {
        std::vector< Runtime > runtime;
        for ( int i = 0; i < kinect::nui::Kinect::GetActiveCount(); ++i ) {
            std::stringstream ss;
            ss << "malti_kinect " << (i + 1);
            Runtime instance( i, ss.str().c_str() );

            instance.kinect->Initialize( NUI_INITIALIZE_FLAG_USES_COLOR );
            instance.kinect->VideoStream().Open( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480 );

            instance.image = ::cvCreateImage( cvSize(instance.kinect->VideoStream().Width(), 
                                                     instance.kinect->VideoStream().Height()),
                                              IPL_DEPTH_8U, 4 );
            ::cvNamedWindow( instance.windowName.c_str() );

            runtime.push_back( instance );
        }

        bool continue_ = true;
        while ( continue_ ) {
            for ( std::vector< Runtime >::iterator it = runtime.begin(); it != runtime.end(); ++it ) {
                // データの更新を待つ
                it->kinect->WaitAndUpdateAll();

                // 次のフレームのデータを取得して表示する
                kinect::nui::VideoFrame videoMD( it->kinect->VideoStream() );
                memcpy( it->image->imageData, (BYTE*)videoMD.Bits(), videoMD.Pitch() * videoMD.Height() );
                ::cvShowImage( it->windowName.c_str(), it->image );

                int key = ::cvWaitKey( 10 );
                if ( key == 'q' ) {
                    continue_ = false;
                }
            }
        }

        ::cvDestroyAllWindows();
    }
    catch ( std::exception& ex ) {
        std::cout << ex.what() << std::endl;
    }
}

バックログ

  • 複数Kinectの扱い
  • CのAPI、COMインタフェースを生で使うサンプル
  • OpenCV 2.3のOpenNI対応の調査
  • KinectラッパーのDoxygen作成
  • 取得した骨格情報に追従してカメラ角度を変更
  • 音関係