ブログ@kaorun55

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

Kinect for Windows SDK beta で遊んでみた 〜 検出した顔に追従してカメラの首をふる 〜 #shibuya_ni

これが面白そうだったので、やってみました。
Kinect SDK XNA 取得した骨格情報に追従してカメラ角度を変更 - Kosaka laboratory Interactive System tips


今回はせっかくOpenCVを使っているので、手ではなく、顔を検出して上下させてみました。

やったこと

  • 顔を検出する
  • 検出した顔が出るのは恥ずかしいので、lena様に変える
  • 検出した顔の位置(上中下の三分割)座標から、Kinectのアングルを変更する
ソース
// 顔を検出して、カメラの角度を動かすサンプル
#include <iostream>
#include <sstream>

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

#include <opencv2/opencv.hpp>

const char* FACE_CASCADE_PATH = "C:/OpenCV2.2/data/haarcascades/haarcascade_frontalface_alt.xml";

void main()
{
    try {
        kinect::nui::Kinect kinect;
        kinect.Initialize( NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON );

        kinect::nui::ImageStream& video = kinect.VideoStream();
        video.Open( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480 );

        kinect::nui::ImageStream& depth = kinect.DepthStream();
        depth.Open( NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_320x240 );

        // OpenCVの初期設定
        char* windowName = "camera_elevation";
        ::cvNamedWindow( windowName );
        cv::Ptr< IplImage > videoImg = ::cvCreateImage( cvSize(video.Width(), video.Height()), IPL_DEPTH_8U, 4 );

        // 画像を読み込んでKinectにあわせて変換する
        cv::Ptr< IplImage > face = ::cvLoadImage( "lena.jpg" );
        cv::Ptr< IplImage > faceImg = ::cvCreateImage( cvSize(face->width, face->height), IPL_DEPTH_8U, 4 );
        ::cvCvtColor( face, faceImg, CV_RGB2RGBA ); 

        // 検出器のロード
        CvHaarClassifierCascade* faceCascade = (CvHaarClassifierCascade*)::cvLoad( FACE_CASCADE_PATH, 0, 0, 0 );
        if( !faceCascade ) {
            throw std::runtime_error("error : cvLoad");
        }

        // 顔検出用のストレージ
        CvMemStorage* storage = ::cvCreateMemStorage();
        bool isDetected = true;

        // 前回の検出時間
        DWORD prevTime = ::GetTickCount();

        while ( 1 ) {
            // データの更新を待って、次のフレームを取得する
            kinect.WaitAndUpdateAll();
            kinect::nui::VideoFrame videoMD( video );

            // データのコピー
            memcpy( videoImg->imageData, (BYTE*)videoMD.Bits(), videoImg->widthStep * videoImg->height );

            // 顔の検出
            if (isDetected) {
                // モータを回しすぎるといかんので、1秒に一回
                if ( (::GetTickCount() - prevTime) > 1000 ) {
                    prevTime = ::GetTickCount();

                    // 顔の検出
                    cvClearMemStorage(storage);
                    CvSeq* faces = ::cvHaarDetectObjects( videoImg, faceCascade, storage );
                    for ( int i = 0; i < faces->total; ++i ) {
                        // 検出した座標の取得
                        CvRect rect = *(CvRect*)::cvGetSeqElem( faces, i );

                        // 検出した顔に画像を貼り付ける
                        ::cvSetImageROI( videoImg, rect );
                        cv::Ptr< IplImage >  resizeImg = ::cvCreateImage( cvSize( rect.width, rect.height), faceImg->depth, faceImg->nChannels );
                        ::cvResize( faceImg, resizeImg );
                        ::cvCopy( resizeImg, videoImg );
                        ::cvResetImageROI( videoImg );

                        // 中心点と、表示領域を三分割したときの座標
                        POINT c = { rect.x + (rect.width / 2), rect.y + (rect.height / 2) };
                        RECT place[] = {
                            { 0, 0, video.Width(), video.Height() / 3 },
                            { 0, video.Height() / 3, video.Width(), video.Height() / 3 * 2 },
                            { 0, video.Height() / 3 * 2, video.Width(), video.Height() },
                        };

                        // 顔の位置によって、Kinectの首を動かす
                        // 上1/3にいれば、上に動かす
                        LONG angle = kinect.GetAngle();
                        if ( ::PtInRect( &place[0], c ) ) {
                            angle += 5;
                            if ( angle < kinect.CAMERA_ELEVATION_MAXIMUM ) {
                                kinect.SetAngle( angle );
                            }
                        }
                        // 中1/3にいれば、何もしない
                        else if ( ::PtInRect( &place[1], c ) ) {
                        }
                        // 下1/3にいれば、下に動かす
                        else if ( ::PtInRect( &place[2], c ) ) {
                            angle -= 5;
                            if ( angle > kinect.CAMERA_ELEVATION_MINIMUM ) {
                                kinect.SetAngle( angle );
                            }
                        }
                    }
                }
            }

            ::cvShowImage( windowName, videoImg );

            int key = ::cvWaitKey( 10 );
            if ( key == 'q' ) {
                break;
            }
            else if (key == 'd') {
                isDetected = !isDetected;
            }
        }

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

まとめ

Kinectの角度変更は、時間をおかないといけないらしいので、1秒に一回くらいで回してます。といっても、OpenCVの顔検出が一回1秒以上かかっているようなので、この場合はあまり気にしなくても大丈夫そうです。
そろそろ音に移ろうかと思ってます。

バックログ

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