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

Kinect V2 で「顔認識」と「顔の詳細情報(HDFace)認識」を併用する


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

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


「顔認識」と「顔の詳細情報の認識」で得られた情報を併用する

USE_FACE 定数を define してから NtKinect.h を include すると、NtKinectの

がどちらも有効になります。

「顔の詳細情報 (HDFace)」の認識においては、個別の顔モデルを作成していない状態では 認識した顔の部品の位置は少しずれていることがあります。 この状態ではHDFaceの情報に基づいて顔の部品を切り出しても、 正しい部分画像は得られないことになります。

一方、「HDFaceではない普通の顔認識」においては、 取得できるのは「左目、右目、鼻、口の左端、口の右端」 の位置だけですが、かなり正確なデータを取得できるようです。 そこで、これらに関しては「顔の詳細情報(HDFace)の認識」で得られた情報を 「顔認識」で得られた情報で補正すればよいのではないか、というアイディアが浮かびます。

本稿では、顔の詳細情報の認識で得られたデータを、顔認識のデータで補正して利用する方法について解説します。

具体的には、目を縦横2倍、つまり面積4倍にした画像を生成するプログラムを示します。 目の画像を切り出す際に、「目の大きさ」は詳細情報(HDFace)の認識で得られたデータをそのまま利用し、 「目の位置」は「顔認識」で得られたデータで補正しています。

[注意] 「顔認識」で得られた情報と「顔の詳細情報(HDFace)認識」で得られた情報を併用するためには、 それが同じ人間の顔の情報であることを

faceTrackingId[i] == hdfaceTrackingId[j] 
で確認する必要があります。


顔の認識

NtKinect

Kinect for Windows SDK 2.0 では顔認識に関しては次のように定義されています。

Kinect for Windows SDK 2.0 の Kinect.Face.h(抜粋)
enum _FacePointType {
    FacePointType_None= -1,
    FacePointType_EyeLeft= 0,
    FacePointType_EyeRight= 1,
    FacePointType_Nose= 2,
    FacePointType_MouthCornerLeft= 3,
    FacePointType_MouthCornerRight= 4,
    FacePointType_Count= ( FacePointType_MouthCornerRight + 1 ) 
};

enum _FaceProperty {
    FaceProperty_Happy= 0,
    FaceProperty_Engaged= 1,
    FaceProperty_WearingGlasses= 2,
    FaceProperty_LeftEyeClosed= 3,
    FaceProperty_RightEyeClosed= 4,
    FaceProperty_MouthOpen= 5,
    FaceProperty_MouthMoved= 6,
    FaceProperty_LookingAway= 7,
    FaceProperty_Count= ( FaceProperty_LookingAway + 1 ) 
};
Kinect for Windows SDK 2.0 の Kinect.h(抜粋)
enum _DetectionResult {
    DetectionResult_Unknown= 0,
    DetectionResult_No= 1,
    DetectionResult_Maybe= 2,
    DetectionResult_Yes= 3
};
Kinect for Windows SDK 2.0 の Kinect.h(抜粋)
typedef struct _PointF {
    float X;
    float Y;
} PointF;

setSkeleton() 関数を呼び出して 骨格情報を取得した後に、setFace()メソッドを呼び出して顔を認識することができます。

NtKinect

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

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 ] です。

顔の詳細情報 HDFace の認識

モーションキャプチャ用に顔の詳細なデータ (HDFace) を取得します。

setSkeleton() 関数を呼び出して 骨格情報を取得した後に、setHDFace()メソッドを呼び出して顔の詳細情報(HDFace)を認識することができます。

NtKinect

NtKinect の顔の詳細情報 HDFace の認識に関するメソッド

返り値の型 メソッド名 説明
void setHDFace() version1.4以降。
setSkeleton()を呼び出した後に呼び出して顔の詳細情報 HDFace を認識することができる。
次のメンバ変数に値が設定される。
変数名説明
vector<vector<CameraSpacePoint>> hdfaceVertices顔の部品の位置
vector<UINT64> hdfaceTrackingId顔に対応する骨格のtrackingIdのベクタ
vector<pair<int,int>> hdfaceStatus顔の詳細認識の状態を表すFaceModelBuilderCollectionStatusとFaceModelBuilderCaptureStatusのペアのベクタ
pair<string,string> hdfaceStatusToString(pair<int,int>) version1.4以降。
顔モデルを作成するのに必要なデータの収集状況 hdfaceStatus[index ] を 引数として渡されると、状態を表す文字列のペアに変換する。
bool setHDFaceModelFlag(bool flag=false) version1.8以降。
「顔モデルを作成するのに必要なデータが十分に収集された時点で 顔のモデルを生成するかどうか」のフラグを設定する。
デフォルト値はfalseで「顔モデルを生成しない」。 関数の返り値はそれまでの設定値である。
この関数を引数trueで呼び出した後で setHDFace()関数を複数回呼び出すと、 必要な情報が収集された時点で個別の顔モデルを生成するようになる。 個別の顔モデルを生成すると詳細な顔(HDFace)認識の精度が上ることが期待される。
プログラムが不安定になることがあるので、この関数は実験的な扱いとする。
NtKinect

NtKinect の顔の詳細情報 HDFace の認識に関するメンバ変数

変数名 説明
vector<vector<CameraSpacePoint>> hdfaceVertices version1.4以降。
CameraSpace座標系における顔の部品の位置。
一人の人間の顔上の1347点の位置が vector<CameraSpacePoint> であり、
複数の人間を扱うため vector<vector<CameraSpacePoint>> となる。
vector<UINT64> hdfaceTrackingId version1.4以降。
trackingIdのベクタ。
hdfaceVertices[index ]に対応するtrackingIdが hdfaceTrackingId[index ]である。
vector<pair<int,int>> hdfaceStatus version1.4以降。
顔の認識状態。
一人の人間の顔情報 HDFace の認識状態が FaceModelBuilderCollectionStatus と FaceModelBuilderCaptureStatus のペアである pair<int,int>であり、 複数の人間を扱うため vector<pair<int,int>> となる。

FaceModelBuilderCollectionStatus

次の状態のORとなる。
FaceModelBuilderCollectionStatus 内の定数名
FaceModelBuilderCollectionStatus_ Complete 0
MoreFramesNeeded 0x2
LeftViewsNeeded 0x4
RightViewsNeeded 0x8
TiltedUpViewsNeeded 0x10

FaceModelBuilderCaptureStatus

次の状態のどれかとなる。
FaceModelBuilderCaptureStatus 内の定数名
FaceModelBuilderCaptureStatus_ GoodFrameCapture 0
OtherViewsNeeded 1
LostFaceTrack 2
FaceTooFar 3
FaceTooNear 4
MovingTooFast 5
SystemError 6

プログラム作成の手順

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

  3. main.cppの内容を以下のように変更します。
  4. 青い文字部分が今回の内容に直接関係している部分ですので、 ここをよく読んで理解して下さい。 緑色の文字部分は"work"画面の表示に関する部分ですので、 実行が確認できた後は消しても構いません。

    main.cpp
    #include <iostream>
    #include <sstream>
    
    #define USE_FACE
    #include "NtKinect.h"
    
    using namespace std;
    
    int getFaceIndex(NtKinect& kinect, UINT64 trackingId) {
      for (int i=0; i< kinect.faceTrackingId.size(); i++) {
        if (kinect.faceTrackingId[i] == trackingId) return i;
      }
      return -1;
    }
    void copyRect(cv::Mat& src, cv::Mat& dst, int sx, int sy, int w, int h, int dx, int dy) {
      if (sx+w < 0 || sx >= src.cols || sy+h < 0 || sy >= src.rows) return;
      if (sx < 0) { w += sx; dx -= sx; sx=0; }
      if (sx+w > src.cols) w = src.cols - sx; 
      if (sy < 0) { h += sy; dy -= sy; sy=0; }
      if (sy+h > src.rows) h = src.rows - sy;
    
      if (dx+w < 0 || dx >= dst.cols || dy+h < 0 || dy >= dst.rows) return;
      if (dx < 0) { w += dx; sx -= dx; dx = 0; }
      if (dx+w > dst.cols) w = dst.cols - dx;
      if (dy < 0) { h += dy; sy -= dy; dy = 0; }
      if (dy+h > dst.rows) h = dst.rows - dy;
    
      cv::Mat roiSrc(src,cv::Rect(sx,sy,w,h));
      cv::Mat roiDst(dst,cv::Rect(dx,dy,w,h));
      roiSrc.copyTo(roiDst);
    }
    void bigEye(NtKinect& kinect,cv::Mat& result,cv::Mat& work,vector<CameraSpacePoint>& hdEye,PointF& fEye) {
      cv::Rect rect = kinect.boundingBoxInColorSpace(hdEye);
      double cx = rect.x + rect.width/2, cy = rect.y + rect.height/2;
      double dx = fEye.X - cx, dy = fEye.Y - cy;
      cv::Rect rect2((int)(rect.x+dx), (int)(rect.y+dy), rect.width, rect.height);
      double margin = 0.5, mw = rect2.width * margin, mh = rect2.height * margin;
      cv::Rect rect3 ((int)(rect2.x-mw/2), (int)(rect2.y-mh/2), (int)(rect2.width+mw), (int)(rect2.height+mh));
      if (rect3.x < 0 || rect3.y < 0 || rect3.x+rect3.width >= kinect.rgbImage.cols || rect3.y+rect3.height >= kinect.rgbImage.rows) {
        cerr << "rect3: " << rect3 << endl;
        return;
      }
      cv::Mat eyeImg(kinect.rgbImage, rect3);
      double scale = 2.0;
      cv::resize(eyeImg,eyeImg,cv::Size((int)(eyeImg.cols*scale), (int)(eyeImg.rows*scale)));
      copyRect(eyeImg, result, 0, 0, eyeImg.cols, eyeImg.rows, (int)(rect3.x-(scale-1)*rect3.width/2), (int)(rect3.y-(scale-1)*rect3.height/2));
      cv::rectangle(work, rect, cv::Scalar(0,255,0), 2);
      cv::rectangle(work, rect2, cv::Scalar(0,0,255), 2);
      cv::rectangle(work, rect3, cv::Scalar(255,0,0), 2);
      cv::rectangle(work, cv::Rect((int)(fEye.X-2), (int)(fEye.Y-2), 4, 4), cv::Scalar(0,255,255), -1);
      cv::rectangle(work, cv::Rect((int)(cx-2), (int)(cy-2), 4, 4), cv::Scalar(255,0,255), -1);
    }
    void doJob() {
      NtKinect kinect;
      while (1) {
        kinect.setRGB();
        kinect.setSkeleton();
        kinect.setFace();
        kinect.setHDFace();
        cv::Mat result = kinect.rgbImage.clone();
        cv::Mat work = kinect.rgbImage.clone();
        for (int i=0; i<kinect.hdfaceTrackingId.size(); i++) {
          int idx = getFaceIndex(kinect,kinect.hdfaceTrackingId[i]);
          if (idx < 0) continue;
          auto& hdFace = kinect.hdfaceVertices[i];
          vector<CameraSpacePoint> hdLeft({
            hdFace[HighDetailFacePoints_LefteyeInnercorner],
            hdFace[HighDetailFacePoints_LefteyeOutercorner],
            hdFace[HighDetailFacePoints_LefteyeMidtop],
            hdFace[HighDetailFacePoints_LefteyeMidbottom]
          });
          vector<CameraSpacePoint> hdRight({
            hdFace[HighDetailFacePoints_RighteyeInnercorner],
            hdFace[HighDetailFacePoints_RighteyeOutercorner],
            hdFace[HighDetailFacePoints_RighteyeMidtop],
            hdFace[HighDetailFacePoints_RighteyeMidbottom]
          });
          bigEye(kinect,result,work,hdLeft,kinect.facePoint[idx][0]); // left eye
          bigEye(kinect,result,work,hdRight,kinect.facePoint[idx][1]); // right eye
        }
        for (int i=0; i<kinect.hdfaceVertices.size(); i++) {
          for (CameraSpacePoint sp : kinect.hdfaceVertices[i]) {
            ColorSpacePoint cp;
            kinect.coordinateMapper->MapCameraPointToColorSpace(sp,&cp);
            cv::rectangle(work, cv::Rect((int)cp.X-1, (int)cp.Y-1, 2, 2), cv::Scalar(0,192, 0), 1);
          }
        }
        cv::resize(work,work,cv::Size(work.cols/2,work.rows/2));
        cv::imshow("work", work);
        cv::imshow("result", result);
        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. プログラムを実行するとRGB画像が表示されます。'q' キーで終了します。
  6. 2個のウィンドウが表示され、一方は "work" という名前で認識状態が、 他方は "result" という名前で目が拡大された画像が示されます。

    "work" ウィンドウには、

    が表示されます。あまり重要ではないので、表示直前に画像のサイズを小さくしています。

    "result" ウィンドウには、"work" の青の長方形領域のRGB画像を取り出して、 縦横2倍に拡大(面積は4倍に拡大)してから貼り付け直した画像が表示されます。 貼り付ける前に、拡大した目の画像のアルファ値を周辺領域だけ小さくしておき それを反映するようにブレンドすると自然な合成画像ができますが、 わかりやすさを優先して上記のプログラムではそのような処理は省略しています。

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



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