やってみよう!Kinectアプリ開発 - 第12回 FaceTracking(前編)

2012-08-17 17:27

FaceTrackingライブラリの追加

FaceTrackingは、実行時にFaceTrackLib.dllとFaceTrackData.dllの2つのダイナミックリンクライブラリを使用します。これらは.NET frameworkのマネージドコードではなく、アンマネージドコード(ネイティブコード)のDLLです。アンマネージドコードのDLLは、実行時にWin32 API経由で読み込むため、実行可能(.exe)ファイルと同じディレクトリなどアプリケーションから読み込める場所に置いておく必要があります。このためのプロジェクトへの追加手順は以下の通りです。

  1. ExplorerでKinect for Windows Developer Toolkitをインストールしたディレクトリ(デフォルトだと” C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.5.0”になります)を開きます。
  2. "Developer Toolkit v1.5.0"の下の"Redist\x86"を開きます。
  3. "FaceTrackLib.dll"と"FaceTrackData.dll"の2つのライブラリがあるので、これらをコピーしてVisual C#の”FaceTrackingSample”プロジェクトにペーストします。
  4. Visual C#上で、"FaceTrackLib.dll"と"FaceTrackData.dll"の2つを選択し、右クリックし[プロパティ]を選択します。
  5. [プロパティ]で[出力ディレクトリにコピー]から”新しい場合はコピーする”を選択します。

メインウィンドウへのコントロールの追加

今回のアプリケーションでは、メインウィンドウにはビットマップを1つ描画するだけです。連載第3回[コントロールの追加]の手順で、Imageコントロールを追加しましょう。プロパティの設定は以下の通りです。

  • 名前: “rgbImage”、Height: “480”、Width: “640”
  • 次のプロパティは値をリセット: HorizontalAlignment, Margin, Stretch, VerticalAlignment

メインウィンドウのプロパティ設定

メインウィンドウのプロパティとイベントハンドラを設定します。手順は連載第3回[イベントハンドラの設定]を参照してください。設定する内容は以下の通りです。

  • プロパティ
    • SizeToContent: "WidthAndHeight"
    • 次のプロパティは値をリセット: Height, Width
  • イベント
    • Loaded="WindowLoaded"、 Closed="WindowClosed"

ソースコードの修正

MainWindow.xaml.csの修正は以下の通りです。FaceTracking以外の部分はこれまでの連載と同様ですので、ポイントだけ説明します。

  • フィールド
    これまでのサンプルアプリケーションと比べ、新しい点はほとんどありません。初期化・終了処理用のKinectSensorChooser、Kinectセンサーからの情報を保持するバッファ、画面に表示するビットマップはこれまで説明した通りです。なお、FaceTrackerオブジェクトをフィールドとして追加していますが、これは今回だけの処置で、本来は先に説明したようにプレイヤー毎に動的に作成するべきです。
  • 初期化、終了処理(WindowLoaded、WindowClosed、KinectChanged、InitKinectSensor、UninitKinectSensor)
    これらもこれまでのサンプルアプリケーションと同様です。
  • イベントハンドラ(AllFramesReady)
    ここでは主に以下の3つの処理を行います。
    • Kinectセンサーからバッファへ読み込み
      ここもこれまでのアプリケーションと同様です。Kinectセンサーからの出力をpixelBuffer, depthBuffer, skeletonBufferに読み込みます。
    • FaceTrackingを行い顔の各点を取得
      FaceTrackerオブジェクトによるFaceTrackingは、フレーム毎に画像・深度・骨格情報をTrackメソッドに渡すだけで行えます。ただし、内部的に骨格情報から頭の位置を取得して使っているため、骨格情報のTrackingStateがTrackedのときに限定しています。Trackメソッドの戻り値のFaceTrackFrameオブジェクトからFaceTrackingの結果を取得できますので、今回はFaceTrackingの成功・失敗(TrackSuccessful)、顔の各点の座標(GetProjected3DShape)を取得しています。
      (前述の通り、本当はプレイヤー毎にFaceTrackerオブジェクトを分けるべきですが、今回はプレイヤー1人という前提で、1つのFaceTrackerオブジェクトで処理しています)
    • 背景に描画
      背景の描画処理では以下の2つの処理を行います。
      1. Kinectセンサーの画像情報をビットマップに描画
      2. FaceTrackingで取得した顔の各点に赤い点を描画

作成したソースコード

最後に、これまで作成したソースコードを示します。

  • MainWindow.xaml
    <Window x:Class="FaceTrackingSample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" SizeToContent="WidthAndHeight" Loaded="WindowLoaded"
            Closed="WindowClosed" >
        <Grid>
            <Image Name="rgbImage" Height="480" Width="640" />
        </Grid>
    </Window>
    
  • MainWindow.xaml.cs
    using System;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Kinect;
    using Microsoft.Kinect.Toolkit;
    using Microsoft.Kinect.Toolkit.FaceTracking;
    
    namespace FaceTrackingSample
    {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            // 解像度・フレームレート
            private ColorImageFormat rgbFormat
                = ColorImageFormat.RgbResolution640x480Fps30;
            private const DepthImageFormat depthFormat
                = DepthImageFormat.Resolution320x240Fps30;
    
            // KinectSensorChooser
            private KinectSensorChooser kinectChooser = new KinectSensorChooser();
    
            // Kinectセンサーからの画像情報を受け取るバッファ
            private byte[] pixelBuffer = null;
    
            // Kinectセンサーからの深度情報を受け取るバッファ
            private short[] depthBuffer = null;
    
            // Kinectセンサーからの骨格情報を受け取るバッファ
            private Skeleton[] skeletonBuffer = null;
    
            // 画面に表示するビットマップ
            private RenderTargetBitmap bmpBuffer = null;
    
            // ビットマップへの描画用DrawingVisual
            private DrawingVisual drawVisual = new DrawingVisual();
    
            // FaceTrackerオブジェクト
            private FaceTracker faceTracker = null;
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            // 初期化処理(Kinectセンサーやバッファ類の初期化)
            private void WindowLoaded(object sender, RoutedEventArgs e)
            {
                kinectChooser.KinectChanged += KinectChanged;
                kinectChooser.Start();
            }
    
            // 終了処理
            private void WindowClosed(object sender, EventArgs e)
            {
                kinectChooser.Stop();
            }
    
            // Kinectセンサーの挿抜イベントに対し、初期化/終了処理を呼び出す
            private void KinectChanged(object sender, KinectChangedEventArgs args)
            {
                if (args.OldSensor != null)
                    UninitKinectSensor(args.OldSensor);
    
                if (args.NewSensor != null)
                    InitKinectSensor(args.NewSensor);
            }
    
            // Kinectセンサーの初期化
            private void InitKinectSensor(KinectSensor kinect)
            {
                // ストリームの有効化
                ColorImageStream clrStream = kinect.ColorStream;
                clrStream.Enable(rgbFormat);
                DepthImageStream depthStream = kinect.DepthStream;
                depthStream.Enable(depthFormat);
                SkeletonStream skelStream = kinect.SkeletonStream;
                kinect.DepthStream.Range = DepthRange.Near;
                skelStream.EnableTrackingInNearRange = true;
                skelStream.TrackingMode = SkeletonTrackingMode.Seated;
                skelStream.Enable();
    
                // バッファの初期化
                pixelBuffer = new byte[clrStream.FramePixelDataLength];
                depthBuffer = new short[depthStream.FramePixelDataLength];
                skeletonBuffer = new Skeleton[skelStream.FrameSkeletonArrayLength];
    
                // 画面に表示するビットマップの初期化
                bmpBuffer = new RenderTargetBitmap(clrStream.FrameWidth,
                                                   clrStream.FrameHeight,
                                                   96, 96, PixelFormats.Default);
                rgbImage.Source = bmpBuffer;
    
                // イベントハンドラの登録
                kinect.AllFramesReady += AllFramesReady;
    
                faceTracker = new FaceTracker(kinect);
            }
    
            // Kinectセンサーの終了処理
            private void UninitKinectSensor(KinectSensor kinect)
            {
                if (faceTracker != null)
                {
                    faceTracker.Dispose();
                    faceTracker = null;
                }
                kinect.AllFramesReady -= AllFramesReady;
            }
    
            // FrameReady イベントのハンドラ
            // (Kinectセンサーの情報をもとにFaceTrackingを行い、
            //  認識した顔の各点に赤い点を描画)
            private void AllFramesReady(object sender, AllFramesReadyEventArgs e)
            {
                KinectSensor kinect = sender as KinectSensor;
    
                using (ColorImageFrame colorImageFrame = e.OpenColorImageFrame())
                using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame())
                using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
                {
                    if (colorImageFrame == null || depthImageFrame == null
                        || skeletonFrame == null)
                        return;
    
                    // 顔の各点の座標を保持するバッファ
                    EnumIndexableCollection<FeaturePoint, PointF> facePoints = null;
    
                    colorImageFrame.CopyPixelDataTo(pixelBuffer);
                    depthImageFrame.CopyPixelDataTo(depthBuffer);
                    skeletonFrame.CopySkeletonDataTo(skeletonBuffer);
    
                    foreach (Skeleton skeleton in skeletonBuffer)
                    {
                        // トラックできている骨格だけを対象とする
                        if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                        {
                            // 今回のフレームにFaceTrackingを適用
                            FaceTrackFrame frame
                                = faceTracker.Track(rgbFormat, pixelBuffer,
                                                    depthFormat, depthBuffer,
                                                    skeleton);
                            // FaceTrackingが成功したら顔の各点を取得
                            if (frame.TrackSuccessful)
                            {
                                facePoints = frame.GetProjected3DShape();
                                break;
                            }
                        }
                    }
    
                    // 描画の準備
                    var drawContext = drawVisual.RenderOpen();
                    int frmWidth = colorImageFrame.Width;
                    int frmHeight = colorImageFrame.Height;
    
                    // カメラの画像情報から背景のビットマップを作成し描画
                    var bgImg = new WriteableBitmap(frmWidth, frmHeight, 96, 96,
                                                    PixelFormats.Bgr32, null);
                    bgImg.WritePixels(new Int32Rect(0, 0, frmWidth, frmHeight),
                                                    pixelBuffer, frmWidth * 4, 0);
                    var rect = new System.Windows.Rect(0, 0, frmWidth, frmHeight);
                    drawContext.DrawImage(bgImg, rect);
    
                    if (facePoints != null)
                    {
                        // 取得した顔の各点に赤い点を描画
                        foreach (var facePt in facePoints)
                        {
                            var pt = new System.Windows.Point(facePt.X, facePt.Y);
                            drawContext.DrawEllipse(Brushes.Red, null, pt, 5, 5);
                        }
                    }
                    // 画面に表示するビットマップに描画
                    drawContext.Close();
                    bmpBuffer.Render(drawVisual);
                }
            }
        }
    }
    

ビルドと実行

以上でアプリケーションの作成は完了です。メニューから[デバッグ(D)]-[デバッグ実行(S)]を選択し、実行してみましょう。

今回は以下のエラーが出て動かないことがあるかもしれません(ソリューション内のプロジェクトやDLLなどの構成要素の間でターゲットとなるプラットフォーム(32/64bitの違いなど)が異なっていた場合このようなエラーが出ます)

この場合は以下の手順を行ってください。

  1. Visual C#のメニューから[ツール]-[設定]-[上級者設定]を選択します。
  2. ツールバーの2つ並んだドロップダウンリストボックスの右側で”Win32”を選択します。
  3. [ソリューションエクスプローラー]の”FaceTrackingSample”プロジェクトを右クリックし[リビルド]を選択します。

正しく動作すると、以下のように表示され、プレイヤーの顔の各点(頭の一番上、あごの先、左右の目の端などなど)に赤い点が表示されます。

まとめ

今回は、Face Tracking SDKの説明の1回目として、Face Trackingの概要の紹介と、FaceTrackerの基本的な使い方を紹介しました。次回は今回に引き続きFaceTrackerのもう少し詳しい使い方を紹介します。

商標について

Kinect、Visual Studio、Windowsは、米国Microsoft Corporationの米国およびその他の国における登録商標または商標です。

2件の質問スレッド
  • かっつん 2017-07-31 20:30 KINECT初心者です。
    大学ではC++しかまだ習っておらず、C++で表情検出を行いたいです。
    ご指導お願いします。
  • Kinect for XboxによるFaceTrackingについて カナタ 2012-09-16 17:36 Kinect for Xboxでこのプログラムを実行した時
    XamlParseExceptionはハンドルされませんでした。とエラーが出たのでWin32に変えたところ
    MainWindow.xaml.csの80行目
    kinect.DepthStream.Range = DepthRange.Near;でSystem.InvalidOperationException はハンドルされませんでした。
    The feature is not supported by this version of the hardware
    とのエラーが表示されてしまいました。

    NearをDefaultに変えても、他のところで上のエラーが出てしまいます。

    Kinect for XboxによってFaceTrackingを行うにはどうしたらいいんでしょうか?
    1件のコメント
    • kinection.jp管理人 >カナタさん

      Xbox用のKinectセンサーをWindowsに接続し、Kinect for Windows SDKでFaceTrackingを行う、ということでしょうか。

      こちらでは試せていませんが、FaceTrackingは基本的にはSDK側の機能に近いのでXbox用のKinectセンサーでも動くと思います。

      ただし、Xbox用のKinectセンサーはNearモードに対応していないので、
      kinect.DepthStream.Range = DepthRange.Near;
      skelStream.EnableTrackingInNearRange = true;
      の2行は削除してください。
      (おそらく「他のところで上のエラーが出る」というのは、この2行目ではないでしょうか?)
      2012-09-25 10:40

ジグソープレミアムレビュー

  • やってみようKinect(キネクト)アプリ開発 - ラボクルー集まれ!