書籍転載:Intel RealSense SDKセンサープログラミング(6)
Visual Studioで活用するRealSenseセンシング機能
― Chapter 9 Visual Studioで作る「WPFデスクトップ」アプリケーション ―
RealSenseとVisual Studioを使ったサンプルアプリケーションで、RealSenseのセンサーから情報を取得してみる。
前回は、RealSenseとVisual Studioを使ったサンプルアプリケーションを実際に作成しました。今回はその続きです。
書籍転載について
本コーナーは、翔泳社発行の書籍『Intel RealSense SDKセンサープログラミング』の中から、特にBuild Insiderの読者に有用だと考えられる項目を編集部が選び、同社の許可を得て転載したものです。
『Intel RealSense SDKセンサープログラミング』の詳細や購入は翔泳社のサイトや目次ページをご覧ください。プログラムのダウンロードも、翔泳社のサイトから行えます。
ご注意
本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。
■
9-5 センシングロジック
RealSenseからアプリが必要な情報を取得する方法を説明します。
9-5-1 センシングロジック用クラスの作成
RealSenseとやりとりするためのクラスを作成しましょう。ソリューションエクスプローラでModelsフォルダを右クリックして[追加]→[クラス]と選択し、ダイアログで「RSModel.cs」という名前のクラスを追加します。
 このアプリには心臓部といえる部分が2つあり、1つは先に説明したCameraPageで、もう1つがこのRSModuleクラスです。本節では、このクラスの内容を順番に説明していきます。
9-5-2 RSModelの外部仕様
 RSModelクラスの外部仕様を説明します。RealSenseから得られた値は、この外部仕様を通してのみ、のぞき見ることができます。
>画像データを出力するImageSourceプロパティの定義
RSModel.csの中で、RealSenseからの画像データを出力するプロパティを定義します。
| 
 private ImageSource _ColorImageElement; 
public ImageSource ColorImageElement 
{ 
  get { return this._ColorImageElement; } 
  set 
  { 
    this._ColorImageElement = value; 
    OnPropertyChanged(); 
  } 
} 
 | 
>表情データを出力するResultプロパティの定義
RSModel.csの中で、RealSenseからの表情データを出力するプロパティを定義します。
| 
 private TResult _Result = null; 
public TResult Result 
{ 
  get { return this._Result; } 
  set 
  { 
    this._Result = value; 
    OnPropertyChanged(); 
    this.IsResult = (this._Result != null); 
  } 
} 
 | 
 TResult型はリスト9.8のように定義しておきます。
| 
 public class TResult 
{ 
  public int Face { get; set; } 
  public int Score { get; set; } 
  public int Sentiment { get; set; } 
  public int SScore { get; set; } 
} 
 | 
>クラス生成時の初期処理
本サンプルでは、定期的にRealSenseからのデータを取得する方法を採用しています。初期処理では定期的に処理をするためのタイマーの準備を行います。タイマー値は30ミリ秒間隔とします。
| 
 private DispatcherTimer Timer = new DispatcherTimer(); 
public RSModel() 
{ 
  this.Timer.Interval = new TimeSpan(0, 0, 0, 0, 30); 
  this.Timer.Tick += Timer_Tick; 
} 
 | 
>センシング開始処理
RSStartメソッドを実行するとセンシング開始するように実装します。
| 
 private PXCMSenseManager SenseManager; 
private PXCMFaceData FaceData; 
public void RSStart() 
{ 
  try 
  { 
    this.SenseManager = PXCMSenseManager.CreateInstance(); // ……1 
    /* */ 
    if (InitializeFace() >= pxcmStatus.PXCM_STATUS_NO_ERROR) // ……2 
    { 
      this.Timer.Start(); // ……3 
    } 
  } 
  catch (Exception ex) 
  { 
    this.Message = ex.Message; 
  } 
} 
 | 
 CreateInstanceでRealSenseと接続します(1)。InitializeFace内部メソッド*2の中でセンシング機能を有効化し(2)、有効化が成功したらタイマーをスタートして定期的にセンシングデータを取得します(3)。
- *2 センシング機能の有効化は9-5-3項で詳しく説明する。
 
PXCMSenseManagerは、libpxccle.cs.dllに定義されたクラスです。このクラスを通して、インテル RealSense SDKの本体である「libpxccpp2c.dll」を経由してRealSenseにつながります。
>センシング終了処理
センシングする必要がなくなったらセンシング終了処理を行います。このサンプルでは、タイマーの停止やRealSenseとの切断、画面表示用データのクリアなどを行っています。
| 
 public void RSStop() 
{ 
  this.Timer.Stop(); 
  this.SenseManager.Dispose(); 
  this.Result = null; 
  this.ColorImageElement = null; 
} 
 | 
9-5-3 センシング機能を有効にする
 センシング開始時の機能有効化の部分を少し詳しく見ていきましょう。InitializeFace内部メソッドは大きく分けて次のような処理の流れになっています。
>顔検出を有効にする
 今回のアプリでは表情検出が必要です。そのためには顔検出が必要なので、顔検出を有効にします。顔検出を有効にするには、EnableFaceメソッドを実行します。
| 
 result = this.SenseManager.EnableFace(); 
 | 
>表情検出を有効にする
 顔検出が有効にできたら、次に表情検出を有効にします。表情検出を有効にするには、EnableEmotionメソッドを実行します。
| 
 result = this.SenseManager.EnableEmotion(); 
 | 
>パイプラインを初期化する
検出するものを有効化したら、パイプラインを初期化してRealSenseからの測定値受信の準備を行います。
| 
 result = this.SenseManager.Init(); 
 | 
>ミラー表示にする
今回のサンプルは対面で使うため、鏡と同じように顔の右側が画面に向かって右側に来るようにデータをSDK側でミラー表示に変更するように指示します。
| 
 this.SenseManager.QueryCaptureManager().QueryDevice(). 
  SetMirrorMode(PXCMCapture.Device.MirrorMode.MIRROR_MODE_HORIZONTAL); 
 | 
>顔検出器を設定する
顔検出した結果を受信するための顔検出器を生成して設定を行います。
| 
 //顔検出器を生成する 
var faceModule = this.SenseManager.QueryFace(); 
// 顔検出器の設定 
var device = this.SenseManager.QueryCaptureManager().QueryDevice(); 
PXCMCapture.DeviceInfo info = null; 
device.QueryDeviceInfo(out info); 
if (info.model == PXCMCapture.DeviceModel.DEVICE_MODEL_IVCAM) 
{ 
  device.SetDepthConfidenceThreshold(1); 
  device.SetIVCAMFilterOption(6); 
  device.SetIVCAMMotionRangeTradeOff(21); 
} 
 | 
>検出モードをカラー画像モードに設定
 顔のトラッキングをカラー画像で行う旨を指定します。今回のアプリではカラー画像からでも十分な精度でしたが、アプリの特性によってはFACE_MODE_COLOR_PLUS_DEPTHを指定することも検討してください。
| 
 var config = faceModule.CreateActiveConfiguration(); 
config.SetTrackingMode(PXCMFaceConfiguration.TrackingModeType.FACE_MODE_COLOR); 
config.ApplyChanges(); 
config.Update(); 
this.FaceData = faceModule.CreateOutput(); 
 | 
9-5-4 センシングデータ取得処理
今回のサンプルではタイマーを使って30ミリ秒ごとにセンシングデータを取得しています。
| 
 private void UpdateFrame() 
{ 
  // フレームを取得する 
  if (this.SenseManager.AcquireFrame(false) >= pxcmStatus.PXCM_STATUS_NO_ERROR) //……1 
  { 
    // フレームデータを取得する 
    var sample = this.SenseManager.QuerySample(); //……2 
    if (sample != null) 
    { 
      // 各データを表示する 
      UpdateColorImage(sample.color);   //……3 
    } 
    updateFaceFrame();                  //……4 
    // フレームを解放する 
    this.SenseManager.ReleaseFrame();   //……5 
  } 
} 
 | 
 AcquireFrameメソッドでその時点のセンシングフレームを取得します(1)。QuerySampleでフレーム中のセンシングデータを取得します(2)。UpdateColorImage内部メソッドで画像データを保存します(3)。UpdateFaceFrame内部メソッドを呼び出して表情データを保存します(4)。ReleaseFrameメソッドでフレームを解放します(5)。
9-5-5 画像データの保存
 画像データを保存するUpdateColorImage内部メソッドは、リスト9.19のようになっています。
| 
 private void UpdateColorImage(PXCMImage colorFrame) 
{ 
  if (colorFrame != null) 
  { 
    PXCMImage.ImageData data = null; 
    var ret = colorFrame.AcquireAccess(PXCMImage.Access.ACCESS_READ, PXCMImage.PixelFormat.PIXEL_FORMAT_RGB24, out data); //……1 
    if (ret >= pxcmStatus.PXCM_STATUS_NO_ERROR) 
    { 
      var info = colorFrame.QueryInfo(); 
      var length = data.pitches[0] * info.height; 
      var buffer = data.ToByteArray(0, length); 
      this.ColorImageElement = BitmapSource.Create( 
        info.width, info.height, 96, 
        96, 
        PixelFormats.Bgr24, 
        null, 
        buffer, 
        data.pitches[0]); 
      colorFrame.ReleaseAccess(data); 
    } 
  } 
} 
 | 
 UpdateColorImage内部メソッドでは、画像フレームデータからAcquireAccessメソッドによりRGB24(8ビット×3原色)の画像データを取り出して、ビットマップに変換してColorImageElementプロパティを更新します(1)。
 ColorImageElementプロパティを更新するとMainViewModelクラス経由でカメラ画面の表示に反映されます。これでRealSenseから送られてきた画像が本アプリのカメラ画面で表示できます。
9-5-6 表情データの保存
 UpdateFaceFrame内部メソッドの中では、表情データを取り出して保存するために図9.13のような流れで処理を行います。
それぞれ、以下のようなコードで実装しています(リスト9.20~9.22)。
| 
 var emotionDet = this.SenseManager.QueryEmotion(); 
 | 
| 
 this.FaceData.Update(); 
 | 
| 
 PXCMEmotion.EmotionData[] datas; 
emotionDet.QueryAllEmotionData(index, out datas); 
 | 
 リスト9.22で使用しているQueryAllEmotionDataメソッドでは、検出した顔の1つを指定して、表情や感情の推定値をdatas配列に格納します。datas配列の内容は表9.1のようになります。
| 要素位置 | 列挙値 | 意味 | 
|---|---|---|
| 0 | EMOTION_PRIMARY_ANGER | 怒っている状態 | 
| 1 | EMOTION_PRIMARY_CONTEMPT | 侮辱している状態 | 
| 2 | EMOTION_PRIMARY_DISGUST | 嫌悪している状態 | 
| 3 | EMOTION_PRIMARY_FEAR | 恐怖している状態 | 
| 4 | EMOTION_PRIMARY_JOY | 楽しんでいる状態 | 
| 5 | EMOTION_PRIMARY_SADNESS | 悲しんでいる状態 | 
| 6 | EMOTION_PRIMARY_SURPRISE | 驚いている状態 | 
| 7 | EMOTION_SENTIMENT_POSTIVE | 前向きの表情を浮かべている状態 | 
| 8 | EMOTION_SENTIMENT_NEGATIVE | 後向きの表情を浮かべている状態 | 
| 9 | EMOTION_SENTIMENT_NEUTRAL | 前向きでも後ろ向きでもない表情を浮かべている状態 | 
>一番強い表情の保存
取得した指定値の要素位置0~6の中で値が一番大きいものを表情として保存します。
| 
 PXCMEmotion.EmotionData[] datas; 
emotionDet.QueryAllEmotionData(index, out datas); 
//追加:表情(PRIMARY)を推定するif (datas != null) 
{ 
  int primaryDataInxex = int.MinValue; 
  float maxscoreI = 0; 
  for (var emotionIndex = 0; emotionIndex <= NUM_PRIMARY_EMOTIONS - 1; emotionIndex++) 
  { 
    if (datas[emotionIndex].intensity > maxscoreI) 
    { 
      maxscoreI = datas[emotionIndex].intensity; 
      primaryDataIndex = emotionIndex; 
    } 
  } 
  if (primaryDataIndex >=0) 
  { 
    this.Result = new TResult 
    { 
      Face = primaryDataIndex, 
      Score = (int)Math.Truncate((maxscoreI * 100)) 
    }; 
  } 
} 
 | 
>一番強い感情の保存
取得した指定値の要素位置7~9の中で値が一番大きいものを感情として保存します。
| 
 if (Result != null) 
{ 
  //追加:感情(Sentiment)を推定する 
  //表情(PRIMARY)の推定と同様なので、コメントは省略 
  int primaryDataIndex = int.MinValue; 
  float maxscoreI = 0; 
  for (var sentimentIndex = NUM_PRIMARY_EMOTIONS; 
       sentimentIndex <= NUM_PRIMARY_EMOTIONS + NUM_SENTIMENT_EMOTIONS - 1; 
       sentimentIndex++) 
  { 
    if (datas[sentimentIndex].intensity > maxscoreI) 
    { 
      maxscoreI = datas[sentimentIndex].intensity; 
      primaryDataIndex = sentimentIndex - NUM_PRIMARY_EMOTIONS; 
    } 
  } 
  if (primaryDataIndex >= 0) 
  { 
    this.Result.Sentiment = primaryDataIndex; 
    this.Result.SScore = (int)Math.Truncate((maxscoreI * 100)); 
  } 
} 
 | 
9-5-7 まとめ
以上でSensor & Coffeeアプリの完成です。紙面の都合上、コードを抜粋した部分もありますし、デザイン時のサンプルデータなどの説明も省略しています。ぜひ、サンプルコードをVisual Studioで読み込んで、実際に見て頂き理解を深めて頂ければ幸いです。
■
次回は、インテル RealSenseを活用したサンプルアプリケーション開発の第3弾として、openFrameworksとRealSenseを組み合わせる方法を紹介します。
※以下では、本稿の前後を合わせて5回分(第4回~第8回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
5. WPF(Visual Studio)+RealSenseで作る表情感知アプリ
インテル RealSenseを活用したサンプルアプリケーション開発の第2弾として、Visual StudioとRealSenseを組み合わせる方法を紹介する。
6. 【現在、表示中】≫ Visual Studioで活用するRealSenseセンシング機能
RealSenseとVisual Studioを使ったサンプルアプリケーションで、RealSenseのセンサーから情報を取得してみる。
7. openFrameworksで作成するメディアアート入門
インテル RealSenseを活用したサンプルアプリケーション開発の第3弾として、openFrameworksとRealSenseを組み合わせる方法を紹介する。
8. openFrameworksによるメディアアートとRealSenseを組み合わせよう!
RealSenseとopenFrameworksを使ったサンプルアプリケーションで、RealSenseのセンサーから情報を取得してみる。