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

2012-08-24 18:37

サンプルアプリケーションの作成

ここで紹介したDetectFaces APIの利用方法をもとに、サンプルアプリケーションを作成してみましょう。このアプリケーションは、Kinectセンサーからの画像情報をウィンドウに表示しつつ、DetectFaces APIによる顔検出を行い、顔の部分を黒い四角で塗りつぶします。ただし、DetectFaces APIの処理は30fpsでリアルタイムに実行できるほどには速くないため、ボタンを押したときだけ顔検出を行うようにします。

このアプリケーションは、前回(連載第12回)のアプリケーションを修正して作成します。手順に関して新しい点はないので省略します。ソースコードを掲載するので、必要に応じて利用してください。以下では各変更点を説明します。

ソースコードの修正(FaceTracker.cs)

DetectFacesを利用する方法として、FtInterop.csを修正する(自プロジェクトに取り込む、またはinternal宣言されたインタフェースをpublicにする)、またはFaceTracker.csを修正する、の2通りの方法がありますが、ここではFaceTracker.csを修正する方法を紹介します。

  • WeightedRect構造体の追加
    第3, 4引数の説明で見つけたC++におけるFT_WEIGHTED_RECT構造体と同等の構造体をC#で定義します。DetectFacesの結果はこれに格納します。
  • DetectFacesメソッドの追加
    FaceTrackerクラスにDetectFacesメソッドを追加します。このメソッドは、第1~4引数を準備し、IFTFaceTrackerインタフェースのDetectFacesメソッドを呼び出します(IFTFaceTrackerインタフェースは、FaceTrackerクラスのFaceTrackerPtrプロパティがそのまま使えます)。処理結果は第3, 4引数経由で返されますので、それをWeightedRect構造体の配列にコピーして返します。

ソースコードの修正(MainWindow.xaml)

MainWindow.xamlの修正はボタンの追加のみです。

ソースコードの修正(MainWindow.xaml.cs)

MainWindow.xaml.csでは初期化・終了処理、FrameReadyイベントで画像情報の描画、ボタン押下時の(DetectFacesメソッドによる)顔検出処理と処理結果の描画を行います。

  • フィールド
    bool型のdetectFacesを追加します。この変数はアプリケーションが顔検出を行っている状態を管理します。また、今回は不要となる骨格情報関連のバッファ(skeletonBuffer)は削除しています。
  • 初期化・終了処理
    基本的には前回と同様ですが、こちらも不要な骨格情報関連の初期化処理は削除しています。
  • 描画処理(drawBitmap)
    カメラから取得した画像情報を背景のビットマップに描画する処理です。前回イベントハンドラで行っていた処理と同様ですが、2か所から利用するためメソッドに切り出しています。
  • FrameReadyイベントハンドラ(AllFramesReady)
    フレーム毎にdrawBitmapメソッドを使ってKinectセンサーからの画像情報を背景のビットマップに描画します。ただし、顔検出中(detectFacesがtrue)は何もしません。
  • ボタンのイベントハンドラ(button1_Click)
    以下の処理を行います
    • 顔検出中(detectFacesがtrue)
      ⇒ 顔検出終了(detectFacesをfalseにする)
    • 顔検出中でない(detectFacesがfalse)
      1. 顔検出開始(detectFacesをtrueにする)
      2. 画像・深度情報を取得
      3. DetectFacesの呼び出し
      4. 検出した顔毎にその領域を黒く描画

作成したソースコード

最後に、今回のサンプルアプリケーションのソースコードを示します。

  • FaceTracker.cs (追加部分のみ)
    ※DetectFacesメソッドはFaceTrackerクラス内に定義し、WeightedRect構造体はnamespace Microsoft.Kinect.Toolkit.FaceTracking内に定義します。手順としては、FaceTracker.csのソースの最後の"{"を削り以下を張り付けてください("..."は不要です)。
    …
            public WeightedRect[] DetectFaces(byte[] colorImage,
                                              short[] depthImage)
            {
                // 第1引数
                colorFaceTrackingImage.CopyFrom(colorImage);
                depthFaceTrackingImage.CopyFrom(depthImage);
                var sensorData = new SensorData(colorFaceTrackingImage,
                                                depthFaceTrackingImage,
                                                DefaultZoomFactor, Point.Empty);
                var ftSensorData = sensorData.FaceTrackingSensorData;
    
                // 第2引数
                Rect rect = new Rect(0, 0, 640, 480);
    
                // 第4引数
                uint numFaces = 32;
    
                // 第3引数
                int wrSize = (int)Marshal.SizeOf(typeof(WeightedRect));
                var ptrToFaceBuf = Marshal.AllocHGlobal((int)(wrSize * numFaces));
    
                int err = FaceTrackerPtr.DetectFaces(ref ftSensorData, ref rect,
                                                     ptrToFaceBuf, ref numFaces);
    
                // 第3, 4引数経由で返される処理結果を配列に詰めなおす
                var faces = new WeightedRect[numFaces];
                for (int idx = 0; idx < numFaces; ++idx)
                {
                    IntPtr ptr = (IntPtr)((int)ptrToFaceBuf + wrSize * idx);
                    faces[idx] = (WeightedRect)Marshal.PtrToStructure(
                            ptr, typeof(WeightedRect));
                }
                Marshal.FreeHGlobal(ptrToFaceBuf);
                return faces;
            }
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct WeightedRect
        {
            public float Weight;
            public Rect Rect;
        }
    }
    
  • 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" />
            <Button Content="顔検出" Height="23" HorizontalAlignment="Right" Name="button1" VerticalAlignment="Bottom" Width="75" Click="button1_Click" />
        </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
    {
        /// 
        /// MainWindow.xaml の相互作用ロジック
        /// 
        public partial class MainWindow : Window
        {
            // 解像度・フレームレート
            private const 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;
    
            // 画面に表示するビットマップ
            private RenderTargetBitmap bmpBuffer = null;
    
            // ビットマップへの描画用DrawingVisual
            private DrawingVisual drawVisual = new DrawingVisual();
    
            // FaceTrackerオブジェクト
            private FaceTracker faceTracker = null;
    
            // 顔検出中の場合true
            private bool detectFaces = false;
    
            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);
    
                // バッファの初期化
                pixelBuffer = new byte[clrStream.FramePixelDataLength];
                depthBuffer = new short[depthStream.FramePixelDataLength];
    
                // 画面に表示するビットマップの初期化
                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 イベントのハンドラ
            // (顔検出中のときは何もしない、それ以外のときはカメラの映像を描画)
            private void AllFramesReady(object sender, AllFramesReadyEventArgs e)
            {
                // 顔検出中は画像を更新しない
                if (detectFaces)
                    return;
    
                using (ColorImageFrame colorImageFrame = e.OpenColorImageFrame())
                {
                    if (colorImageFrame == null)
                        return;
                    colorImageFrame.CopyPixelDataTo(pixelBuffer);
    
                    // 画面に表示するビットマップに描画
                    var drawContext = drawVisual.RenderOpen();
                    drawBitmap(drawContext, colorImageFrame);
                    drawContext.Close();
                    bmpBuffer.Render(drawVisual);
                }
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                if (detectFaces) {
                    // 顔検出中の場合は元に戻す
                    detectFaces = false;
                } else {
                    detectFaces = true;
                    // 画像・深度情報を取得
                    ColorImageFrame colorFrame
                        = kinectChooser.Kinect.ColorStream.OpenNextFrame(1000);
                    DepthImageFrame depthFrame
                        = kinectChooser.Kinect.DepthStream.OpenNextFrame(1000);
                    colorFrame.CopyPixelDataTo(pixelBuffer);
                    depthFrame.CopyPixelDataTo(depthBuffer);
    
                    // DetectFacesの呼び出し
                    WeightedRect[] faces
                        = faceTracker.DetectFaces(pixelBuffer, depthBuffer);
    
                    // 描画処理
                    var drawContext = drawVisual.RenderOpen();
                    drawBitmap(drawContext, colorFrame);
                    // 検出した顔毎に領域を黒く塗りつぶす
                    for (int faceIdx = 0; faceIdx < faces.Length; ++faceIdx) {
                        var faceRect = faces[faceIdx].Rect;
                        var drawRect = new System.Windows.Rect(
                                faceRect.Left, faceRect.Top,
                                faceRect.Right - faceRect.Left,
                                faceRect.Bottom - faceRect.Top);
                        drawContext.DrawRectangle(Brushes.Black, null, drawRect);
                    }
                    drawContext.Close();
                    bmpBuffer.Render(drawVisual);
                }
            }
    
            private void drawBitmap(DrawingContext drawContext,
                                    ColorImageFrame colorImageFrame)
            {
                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);
            }
        }
    }
    

ビルドと実行

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

起動後Kinectセンサーが認識されると、連載第3回で作成したサンプルアプリケーションのように、カメラの映像がリアルタイムにアプリケーションに表示されます。画面内に人物が入っている状態で右下の[顔検出]ボタンを押すと認識した顔の領域が黒く塗りつぶされるはずです(この状態で再度[顔検出]ボタンを押すと、再度カメラの映像がリアルタイムに表示されるようになります)。

なお、DetectFaces APIの現在の実装では深度情報を使っていないこともあり、うまく検出されないこともあります。この場合は[顔検出]ボタンを再度押して検出をやり直してみてください。余談ですが、深度情報を使っていないメリットとして、写真に写った顔も検出することができます。

まとめ

本連載では、これまで以下の項目を紹介してきました。

本連載は今回で終了ですが、Kinect for Windows SDKには、Face Trackingで紹介しきれなかったものを始め、他にもさまざまな機能があります。興味のある方は調べてみてください。

商標について

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

4件の質問スレッド
  • ハンドルされていない例外が発生 tokiko 2013-07-05 11:29 いつもお世話になっています。
    いまさらながら、このサイトでKinectについて勉強させていただき、やっと最後のここまでたどり着くことができました。

    ところがこの回まで来て、うまくいかず行き詰っています。
    ビルトして実行し、”顔検出”のボタンをクリックすると、以下のようなエラーが出ます。

    --------------------------------------------------------------------------
    'System.InvalidCastException' のハンドルされていない例外が Microsoft.Kinect.dll で発生しました。

    追加情報: 型 'System.__ComObject' の COM オブジェクトをインターフェイス型 'Microsoft.Kinect.Interop.INuiFrameTexture' にキャストできません。IID '{13EA17F5-FF2E-4670-9EE5-1297A6E880D1}' が指定されたインターフェイスの COM コンポーネント上での QueryInterface 呼び出しのときに次のエラーが発生したため、この操作に失敗しました: インターフェイスがサポートされていません (HRESULT からの例外: 0x80004002 (E_NOINTERFACE))。
    ---------------------------------------------------------------------------

    MainWindow.xaml.csのbutton1_Clickの中の、
    DepthImageFrame depthFrame
    = kinectChooser.Kinect.DepthStream.OpenNextFrame(1000);

    のところで引っかかっているようです。
    解決方法があればご教示いただければ幸いです。

    ちなみに、Kinect for SDKのバージョンは1.7です。
    ヘルプファイルでは、DepthImageFrame Membersについては、1.5、1.6、1.7共通で書かれているので、違うところに
    原因があるのかな、と考えています。

    よろしくお願いいたします。
    1件のコメント
    • drksugi 私もこの問題で困っています. スレッド絡みの問題で厄介です. 有効な解決法がなく本当に困ります. 2014-09-22 20:09
  • 画面内にボタンで出てこない 某支配人 2012-08-28 23:32 ソースコードの修正(MainWindow.xaml)
    MainWindow.xamlの修正はボタンの追加のみです。


    において、コピペしてもボタンが表示されません。
    1件のコメント
    • kinection.jp管理人 ちょっと見てみないとわからないのですが、例えばVisual C#上で、MainWindow.xamlを開き[デザイン]ビュー上では表示されているでしょうか?表示されていない場合、XAMLソース上で"Button"タグをクリックしてプロパティは表示されているでしょうか?プロパティが見える場合は画面外に出ている可能性もあるので、HorizontalAlignmentとVerticalAlignmentを確認してみてください。 2012-08-29 15:45
  • ソースへの追加場所 某支配人 2012-08-24 23:56 第13回講義に於いて
    FaceTracker.cs (追加部分のみ)のソースへの追加場所がよく解りません。

    {}の数も合わないためにいまいち推測がつきません。

    もしかすると最後の}を2つ消してから、
    今回のソースを全部追加するのでしょうか?

    お手数ですがお教え願います。

    3件のコメント (すべて表示)
    • kinection.jp管理人 ご指摘ありがとうございます。分かりにくかったようで申し訳ありません。
      説明を追加しておきましたが、"}"を1つ削除してソースを貼り付けでいけるかと思いますがいかがでしょうか。
      2012-08-29 15:33
    • 某支配人 自己完結は勘違いでした。
      申し訳ありません。

      とりあえずFaceTracker.cs については問題はなさそうなのですが、正式な回答をいただきたいです。

      また2点、動作がおかしいので別途質問スレッドを建てます。
      2012-08-28 22:29
    • 某支配人 自己完結しました。

      FaceTracker.cs の追加部分は最後の}}を消して、
      掲載されているソースを全部追加で問題なく動作しました。

      私の確認時には}の消し方を間違えていたようでエラーが出まくりでしたが、再度元ソースをいじったら通りました。
      2012-08-27 11:07
  • コンテキストが無い 某支配人 2012-08-28 23:37 MainWindow.xaml.cs
    にて
    名前'InitializeComponent'は現在のコンテキスト内に存在しません。
    名前'rgbImage'は現在のコンテキスト内に存在しません。

    ともでます。


    namespace FaceTrackingSample_01


    この末尾_01を消すとエラーは出なくなります。

    前回のソースの流用ってことですが、フォルダ名の変更指示等が無いため、ネームスペースの方の_01が余分かと思います。

    立て続けに質問して申し訳ありませんが、なにとぞ回答のほどよろしくお願いします。
    1件のコメント
    • kinection.jp管理人 ご指摘ありがとうございます。"_01"は余分です。
      ソースの方を修正しました。
      2012-08-29 15:19

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

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