KinectとC#でFace Trackerを動かすin development

 
date:2013.05.30   posted by:sakuma
 

佐久間です。

ここ最近弊社ではKinectを使用したインスタレーションの制作に積極的に取り組んでおり、その一環として3月に開催されたリテールテックにおいてKinectの顔認識機能を利用したサービス「えがおdeクーポン」を出展させていただきました。

先日のMicrosoftのXBOX発表会で新型Kinectが発表されたこともあり、今後もますます注目を集めていくであろうKinectですが、開発に関してはまだまだ日本語のドキュメントが少なく情報共有を広める必要がある段階です。この記事では「えがおdeクーポン」で使用しているKINECT for Windows SDKのFaceTrackerについての基本的な使用方法を解説させていただきたいと思います。

■FaceTrackerとは?

FaceTrackerはKINECT for WIndows SDK 1.5から追加された機能です。FaceTrackerを使うことで、トラッキングユーザーの頭部の位置や傾きに加え、ユーザーの表情に関する情報を取得することができます。

具体的には以下のような情報が取得可能です。

・顔のカメラ上での位置
・顔の回転度数
・顔面の領域
・顔の各ポイントの座標
・表情を表すパラメーター値(Animation Unit)

この記事では、Visual Studio上でC#を使った開発でFaceTrackerを利用する方法を解説します。

■FaceTrackerの使用方法

プロジェクト内でFaceTrackerを使用するには以下の手順が必要です。

1.Microsoft.Kinect.Toolkitの取り込み
Kinect Toolkit Browserを開き、ComponentsタブからMicrosoft.Kinect.Toolkitの項目を見つけInstallボタンを押します。保存用のウィンドウが表示されるので、適当な場所を指定して保存します(Visial Studioのフォルダ内などが適切かと思います)。
Visual Studioのソリューションエクスプローラーでソリューションを右クリックし、「追加→既存のプロジェクト」で先ほど保存したKinect.Toolkitのプロジェクトを取り込みます。

2.Microsoft.Kinect.Toolkit.FaceTrackingの取り込み
Kinect Toolkit Browserを開き、ComponentsタブからMicrosoft.Kinect.Toolkit.FaceTrackingの項目を見つけInstallボタンを押します。保存用のウィンドウが表示されるので、適当な場所を指定して保存します(Visial Studioのフォルダ内などが適切かと思います)。

Visual Studioのソリューションエクスプローラーでソリューションを右クリックし、「追加→既存のプロジェクト」で先ほど保存したKinect.Toolkit.FaceTrackingのプロジェクトを取り込みます。

3.参照の追加
プロジェクトの参照設定を右クリックし、「参照の追加」を開き、「ソリューション→プロジェクト」に表示されているMicrosoft.Kinect.ToolkitとMicrosoft.Kinect.Toolkit.FaceTrakerにそれぞれチェックを入れます。また新規プロジェクトとして作成して場合はMicrosoft.Kinectへの参照も忘れず追加してください。

以上でVisual Studioプロジェクト内でFaceTrackerが使えるようになります。

■FaceTrackerを動かす最小コード

以下がFaceTrackerを利用するコードのテンプレートになります。
allFrameReadyがいわゆるメインループになり、主にこの中に各描画処理や表情判別コードなどを追加していきます。

また以下のコードでは1つのFaceTrackerオブジェクトを使い回していますが、複数ユーザーを対象とする場合は人数分のFaceTrackerオブジェクトを用意するのがベターなようです。

namespace FaceTrackerTest
{

    public partial class MainWindow : Window
    {
        private KinectSensorChooser kinectChooser = new KinectSensorChooser();

        private FaceTracker faceTracker = null;

        private byte[] pixelBuffer = null;
        private short[] depthBuffer = null;
        private DepthImagePixel[] depthPixels = null;
        private Skeleton[] skeletonBuffer = null;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            kinectChooser.KinectChanged += kinectChanged;
            kinectChooser.Start();
        }

        private void WindowClosed(object sender, EventArgs e)
        {
            kinectChooser.Stop();
        }

        /*
         *  キネクトの初期化
         */

        private void initKinectSensor(KinectSensor kinect)
        {
            ColorImageStream colorStream = kinect.ColorStream;
            colorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);

            DepthImageStream depthStream = kinect.DepthStream;
            depthStream.Enable(DepthImageFormat.Resolution320x240Fps30);

            SkeletonStream skeletonStream = kinect.SkeletonStream;

            // NearModeをON
            kinect.DepthStream.Range = DepthRange.Near;
            skeletonStream.EnableTrackingInNearRange = true;

            // TrackingModeをSeatedに設定
            skeletonStream.TrackingMode = SkeletonTrackingMode.Seated;

            skeletonStream.Enable();

            pixelBuffer = new byte[colorStream.FramePixelDataLength];
            depthBuffer = new short[depthStream.FramePixelDataLength];
            depthPixels = new DepthImagePixel[depthStream.FramePixelDataLength];
            skeletonBuffer = new Skeleton[skeletonStream.FrameSkeletonArrayLength];

            kinect.AllFramesReady += allFrameReady;
        }

        private void uninitKinectSensor(KinectSensor kinect)
        {
            kinect.AllFramesReady -= allFrameReady;
        }

        private void kinectChanged(object sender, KinectChangedEventArgs e)
        {
            if (e.OldSensor != null) uninitKinectSensor(e.OldSensor);
            if (e.NewSensor != null) initKinectSensor(e.NewSensor);
        }

        /*
         *  Kinectのメインループ
         */

        private void allFrameReady(object sender, AllFramesReadyEventArgs e)
        {
            KinectSensor kinect = sender as KinectSensor;

            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame())
            using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
            {
                if (colorFrame == null || depthImageFrame == null || skeletonFrame == null) return;

                colorFrame.CopyPixelDataTo(pixelBuffer);
                depthImageFrame.CopyPixelDataTo(depthBuffer);
                skeletonFrame.CopySkeletonDataTo(skeletonBuffer);

                // ユーザー数のカウント

                int trackedUserNum = 0;   // トラッキング中のユーザー数

                foreach (Skeleton skeleton in skeletonBuffer.Where(s => s.TrackingState == SkeletonTrackingState.Tracked))
                {
                    trackedUserNum++;
                }

                // トラックユーザー数が0の場合FaceTrackerをリセット

                if (trackedUserNum == 0)
                {
                    if (faceTracker != null)
                    {
                        faceTracker.Dispose();
                        faceTracker = null;
                    }
                    return;
                }

                // スケルトンごとに処理

                foreach (Skeleton skeleton in skeletonBuffer)
                {
                    if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                    {
                        // FaceTrackerの開始
                        FaceTrackFrame ftFrame = faceTracker.Track(ColorImageFormat.RgbResolution640x480Fps30,
                                                           pixelBuffer,
                                                           DepthImageFormat.Resolution320x240Fps30,
                                                           depthBuffer,
                                                           skeleton);

                        if (ftFrame.TrackSuccessful)
                        {
                            /******************/
                            /* ここで色々する */
                            /******************/
                        }
                        else {
                            /********************************/
                            /* 表情が取得できない場合の処理 */
                            /********************************/
                        }
                    }
                }
            }
        }
    }
}

■FaceTrackerからの情報取得

上記コードのallFrameReady内「ここで色々する」の中で、FaceTrackFrameオブジェクトから情報を取り出せます。実際に取得できるのは以下のような情報です。

・FaceRect
カメラ領域上の座標に顔面の領域をRect形式で返します。

・3DShape
FaceTrakerが取得した顔情報の各ポイントの座標を三次元座標で返します。

・Projected3DShape
FaceTrakerが取得した顔情報の各ポイントの座標を二次元座標で返します。
FaceRectと同様カメラ領域上の座標に投影した値になります。

・Triangles
FaceTrakerが取得した顔面モデルのポリゴン情報を返します。

・Rotation
顔面モデルのX、Y、Z軸に対する回転度数を返します。

・Transition
顔面モデルのカメラからの一座標をメートル単位で返します。
Z軸の距離(奥行き)はマイナスの値で返ります。

・AnimationUnitCoefficients
認識した顔情報のAnimation Unit値を返します(各AU値の詳細については後述)。
FaceTrackerからは6種類のAU値が参照でき、これらの値を組み合わせて表情を判別します。

■Animation Unitを利用して表情判別

Animation Unitに関する詳細については以下のページに解説があります(英語)。

http://msdn.microsoft.com/en-us/library/jj130970.aspx

AU値をFaceTrackFrameから取り出すには以下のようなコードを使います。

EnumIndexableCollection faceAUs = ftFrame.GetAnimationUnitCoefficients();
float au0 = faceAUs[AnimationUnit.LipRaiser];
float au1 = faceAUs[AnimationUnit.JawLower];
float au2 = faceAUs[AnimationUnit.LipStretcher];
float au3 = faceAUs[AnimationUnit.BrowLower];
float au4 = faceAUs[AnimationUnit.LipCornerDepressor];
float au5 = faceAUs[AnimationUnit.BrowRaiser];

AU値は-1.0~1.0の間の値で返ります。0がニュートラル状態での値、1.0かー1.0に振れるにつれ表情の度合いが強くなっていきます。

以下で大まかに各値がどのような表情を表すかを説明します。

・AU0 “Upper Lip Raiser”
上唇が開いている度合いを表します。
0が口を閉じている状態、1が歯を完全に見せている状態です(マイナスの値は基本的には返りません)。

・AU1 “Jaw Lowerer”
口が開いている度合いを表します。
0が閉じている状態、1が完全に口を開けきっている状態です(マイナスの値は基本的には返りません)。

・AU2 “Lip Stretcher”
唇を横に引き伸ばしている度合いを表します。
0がニュートラルの状態、1が横に伸ばしきっている状態、-1は口をすぼませている状態です。

・AU3 “Brow Lowerer”
眉毛の位置を表します。
0がニュートラルの状態、-1が眉毛を上に上げている状態、1が眉をしかめている状態です。

・AU4 “Lip Corner Depressor”
口元が上がっている、下がっている度合を表します。
0がニュートラルの状態、1が口元が下がっている状態、-1が口元が上がっている状態です。

・AU5 “Outer Brow Raiser”
眉毛の角度を表します。
0がニュートラルの状態、1が眉尻が上がっている状態、-1は眉尻が下がっている(ハの字)状態です。

以上の値を参照することで、ユーザーがどのような表情をとっているかを判別することができます。
例えばAU5がマイナス値(ハの字状態)であれば悲しそうな顔をしていると予想できます。さらにAU2やAU4の値なども合わせて参照することで、判別の精度を高めることができます。

実際のところは、現状FaceTrackerが返すAU値はかなり確度が低く、値によってはばらつきが大きく、正確な判別に使用するには難しいものもあります。

実際に使ってみた上で、上記の6つの中である程度信頼できる値はAU1、AU2、AU3辺りかなと思います。この3つはKinectの設置状況が正しければかなり正確な値を返しますが、他の3つについてはあまり信用出来る値とは言えず、現状は使用する場合も補助的な利用に留めておいた方が良さそうです。新型Kinectではこのあたりの精度もアップしていることを期待したいです。

また各参照値に関して、SkeletonやJointの値はデフォルトで平滑化フィルターが適用され、イレギュラーな値をあらかじめ弾いてくれるのですが、FaceTrackerの値は各フレームごとにそのまま生の値を返すので、値の平滑化に関しては自前で実装する必要があります。Median(中央値)フィルターなどが実装しやすく手頃だと思います。

■FaceTrackerの使用上の注意

FaceTrackerを実際に活用する上での注意点などをまとめました。

・そこそこの3D処理能力が必要
FaceTrackerを動かすPCは、出来る限りGPUを積んだPCを使用するのが好ましいです。低スペックのPCではフレーム落ちが発生し、取得値のばらつきなどが発生します。

・判別できる顔の角度はカメラに対して45度以内
カメラ正面から45度以上傾けた顔に関してはFaceTrackerで情報を取得することができません。基本的にユーザーには正面を向いてもらう前提にするのが確実です。

・カメラの設置位置はユーザーの顔に近い高さに
Kinectに2~3段階以上のチルトをかけないと顔が映らないような設置状況の場合、正しい表情認識がかなり困難になります。Kinectはできるだけユーザーの目線の高さに設置し、チルトをかけない状態で使用できるようにするのが好ましいです。

以上、参考にしていただけましたら幸いです。

 
Copyright © TheDesignium inc. powered by WordPress & mootools.
Relative Keyword|none