NtKinect: Kinect V2 C++ Programming with OpenCV on Windows10

Kinect V2 で顔を認識する(DepthSpace 座標系)


2016.08.12: created by
Japanese English
NtKinect.h version 1.3 以降に対応しているトピックスです。
目次へ

前提として理解しておくべき知識


DepthSpace 座標系での顔の認識

NtKinect version1.2以前では、ColorSpace 座標系における座標としてしか取得できませんでしたが、 version1.3以降では顔の部品の位置を DepthSpace 座標系における座標としても取得できるようになりました。

NtKinect の顔認識に関するメソッド

返り値の型 メソッド名 説明
void setFace() version1.2以前。
setSkeleton()を呼び出した後に呼び出して顔認識をすることができる。
次のメンバ変数に値が設定される。
変数名説明
vector<vector<PointF>> facePoint顔の部品の位置
vector<cv::Rect> faceRect顔の矩形領域
vector<cv::Vec3f> faceDirection顔の向き
vector<vector<DetectionResult>> faceProperty顔の状態
void setFace(bool isColorSpace = true) version1.3以降。
setSkeleton()を呼び出した後に呼び出して顔認識をすることができる。
次のメンバ変数に値が設定される。
変数名説明
vector<vector<PointF>> facePoint顔の部品の位置
vector<cv::Rect> faceRect顔の矩形領域
vector<cv::Vec3f> faceDirection顔の向き
vector<vector<DetectionResult>> faceProperty顔の状態
引数を無し、または、第1引数をtrueで呼び出した場合はColorSpace座標系における位置が変数にセットされる。
第1引数をfalseで呼び出した場合はDepthSpace座標系における位置が変数にセットされる。

NtKinect の顔認識に関するメンバ変数

変数名 説明
vector<vector<PointF>> facePoint 顔の部品の位置。
一人の人間の「左目、右目、鼻、口の左端、口の右端」の位置が vector<PointF> であり、
複数の人間を扱うため vector<vector<PointF>> となる。
(version1.2以前)ColorSpace 座標系における位置である。
(version1.3以降)ColorSpace座標系またはDepthSpace座標系における位置である。
vector<cv::Rect> faceRect 顔の矩形領域のベクタ。
(version 1.2以前)ColorSpace 座標系における位置である。
(version1.3以降)ColorSpace座標系またはDepthSpace座標系における位置である。
vector<cv::Vec3f> faceDirection 顔の向き (pitch, yaw, roll) のベクタ
vector<vector<DetectionResult>> faceProperty 顔の状態。
一人の人間の「笑顔、正面、眼鏡、左目閉じる、右目閉じる、口が開く、口が動く、目をそらす」が
vector<DetectionResult> であり、 複数の人間を扱うため vector<vector<DetectionResult>> となる。
vector<UINT64> faceTrackingId version 1.4 以降で利用できる。
trackingIDのベクタ。
顔情報 faceRect[index ] などに対応する trackingIdは faceTrackingId[index ] です。

Kinect V2 の3種類の座標系

データの種類によって、それを計測するセンサーの位置や解像度が異なります。 そのため、実世界での同じ位置の状態が、センサーによってそれぞれの座標系で表現された値として得られます。 異なるセンサーから得られたデータを同時に利用する場合は、 座標系の変換を行なってどちらかの座標系に合わせる必要があります。

Kinect V2 では、ColorSpace, DepthSpace, CameraSpace という3つの座標系があって、 それぞれの座標を表すデータ型 ColorSpacePoint, DepthSpacePoint, CameraSpacePoint が存在します。

Kinect for Windows SDK 2.0 の Kinect.h(抜粋)
typedef struct _ColorSpacePoint {
    float X;
    float Y;
} ColorSpacePoint;

typedef struct _DepthSpacePoint {
    float X;
    float Y;
} DepthSpacePoint;

typedef struct _CameraSpacePoint {
    float X;
    float Y;
    float Z;
} CameraSpacePoint;

Kinect V2 の座標系とデータ型

RGB画像, Depth画像, 関節情報ではそれぞれ使っている座標系が異なります。 RGB画像の座標系は ColorSpace で、Depth画像の座標系は DepthSpace, 関節情報の座標系は CameraSpace です。

座標系位置を表す型データの種類
ColorSpaceColorSpacePointRGB画像
DepthSpaceDepthSpacePointdepth画像, bodyIndex画像, 赤外線画像
CameraSpaceCameraSpacePointskeleton情報



関節の位置を表すCameraSpace 座標系

CameraSpace 座標系は、

  • 原点にKinect V2 があり、
  • カメラのレンズの向きがz軸の正方向で、
  • 垂直上方向がy軸の正方向である、
  • 右手系
3次元座標系です。 すなわち、CameraSpace, ColorSpace, DepthSpace という3種類の座標系のすべてにおいて、 「Kinect V2 に対面しているユーザから見て左から右への水平方向が x 軸の正方向」になります。 「Kinect V2 に対面しているユーザからみてあたかも鏡に写っている画像のように」 データが取得されて表示されると考えれば分かりやすいと思います。
(2016/11/12 図を変更し、説明を追記しました)。

座標系の変換に関するKinect V2のメソッド

Kinect V2 の ICoordinateMapper クラス が保持する「座標系の変換用メソッド」は次の通りです。

返り値の型 メソッド名 説明
HRESULT MapCameraPointToColorSpace(
    CameraSpacePoint sp ,
    ColorSpacePoint *cp )
CameraSpace 座標系での座標 sp を ColorSpace 座標系での座標に変換してcp にセットする。 返り値はS_OKかエラーコード。
HRESULT MapCameraPointToDepthSpace(
  CameraSpacePoint sp ,
  DelpthSpacePoint *dp )
CameraSpace 座標系での座標 sp を DepthSpace 座標系での座標に変換してdp にセットする。 返り値はS_OKかエラーコード。
HRESULT MapDepthPointToColorSpace(
  DepthSpacePoint dp ,
  UINT16 depth ,
  ColorSpacePoint *cp )
DepthSpace 座標系での座標 dp と距離depth から ColorSpace 座標系での座標に変換してcp にセットする。 返り値はS_OKかエラーコード。
HRESULT MapDepthPointToCameraSpace(
  DepthSpacePoint dp ,
  UINT16 depth ,
  CameraSpacePoint *sp )
DepthSpace 座標系での座標 dp と距離depth から CameraSpace 座標系での座標に変換してsp にセットする。 返り値はS_OKかエラーコード。

座標系の変換に関する NtKinect のメンバ変数

Kinect V2 で座標系の変換に使う ICoordinateMapper クラス のインスタンスは、 NtKinect のメンバ変数 coordinateMapper に保持されています。

変数名 説明
CComPtr<ICoordinateMapper> coordinateMapper 座標変換を行う ICoordinateMapperのインスタンス。

プログラム作成の手順

  1. NtKinect: Kinect V2 で顔認識する(ColorSpace 座標系)」 の Visual Studio のプロジェクト KinectV2_face.zipを用いて作成します。
  2. このプロジェクトは次のように変更されているはずです。

  3. main.cppの内容を以下のように変更します。
  4. Depth画像を取得します。

    Depth画像の各ピクセルの値は CV_16UC1 (16bit幅のunsigned int 1個)で表現されているので、 カラーで書込みできる画像に変換します。 すなわち、一旦4500mmで最高の輝度(255)となるような CV_8UC1 (8bit幅の unsigned int 1個)に変換してから、 CV_8UC3 (8bit幅のunsigned int 3個, BGRフォーマット)に変換しています。

    Depth画像の上に、DepthSpace 座標系で認識された顔の位置を書き込んでいます。

    main.cpp
    #include <iostream>
    #include <sstream>
    
    #define USE_FACE
    #include "NtKinect.h"
    
    using namespace std;
    
    string faceProp[] = {
      "happy", "engaed", "glass", "leftEyeClosed",
      "rightEyeClosed", "mouseOpen", "mouseMoved", "lookingAway"
    };
    string dstate[] = { "unknown", "no", "maybe", "yes" };
    
    void doJob() {
      NtKinect kinect;
      cv::Mat img8;
      cv::Mat img;
      while (1) {
        kinect.setDepth();
        kinect.depthImage.convertTo(img8,CV_8UC1,255.0/4500);
        cv::cvtColor(img8,img,cv::COLOR_GRAY2BGR);
        kinect.setSkeleton();
        for (auto person : kinect.skeleton) {
          for (auto joint : person) {
            if (joint.TrackingState == TrackingState_NotTracked) continue;
            DepthSpacePoint dp;
            kinect.coordinateMapper->MapCameraPointToDepthSpace(joint.Position,&dp);
            cv::rectangle(img, cv::Rect((int)dp.X-5, (int)dp.Y-5,10,10), cv::Scalar(0,0,255),2);
          }
        }
        kinect.setFace(false);
        for (cv::Rect r : kinect.faceRect) {
          cv::rectangle(img, r, cv::Scalar(255, 255, 0), 2);
        }
    
        for (vector<PointF> vf : kinect.facePoint) {
          for (PointF p : vf) {
            cv::rectangle(img, cv::Rect((int)p.X-3, (int)p.Y-3, 6, 6), cv::Scalar(0, 255, 255), 2);
          }
        }
        for (int p = 0; p < kinect.faceDirection.size(); p++) {
          cv::Vec3f dir = kinect.faceDirection[p];
          cv::putText(img, "pitch : " + to_string(dir[0]), cv::Point(200 * p + 50, 30), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,255,255), 1, CV_AA);
          cv::putText(img, "yaw : " + to_string(dir[1]), cv::Point(200 * p + 50, 60), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,255,255), 1, CV_AA);
          cv::putText(img, "roll : " + to_string(dir[2]), cv::Point(200 * p + 50, 90), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,255,255), 1, CV_AA);
        }
        for (int p = 0; p < kinect.faceProperty.size(); p++) {
          for (int k = 0; k < FaceProperty_Count; k++) {
            int v = kinect.faceProperty[p][k];
            cv::putText(img, faceProp[k] +" : "+ dstate[v], cv::Point(200 * p + 50, 30 * k + 120), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,255,255), 1, CV_AA);
    
          }
        }
        cv::imshow("depth", img);
        auto key = cv::waitKey(1);
        if (key == 'q') break;
      }
      cv::destroyAllWindows();
    }
    
    int main(int argc, char** argv) {
      try {
        doJob();
      } catch (exception &ex) {
        cout << ex.what() << endl;
        string s;
        cin >> s;
      }
      return 0;
    }
    
    
  5. プログラムを実行するとDepth画像が表示されます。'q' キーで終了します。
  6. Depth画像の上に、認識された顔情報が表示されます。

    この例では、顔の向きと顔の状態を個人ごとに画面上部に表示します。




    ウィンドウの左上に顔の向き(pitch, yaw, roll) や表情を認識した項目が表示されています。

  7. サンプルのプロジェクトはこちら KinectV2_face3.zip
  8. 上記のzipファイルには必ずしも最新の NtKinect.h が含まれていない場合があるので、 こちらから最新版をダウンロードして 差し替えてお使い下さい。



http://nw.tsuda.ac.jp/