人物を切り抜いて画面に表示するKinectサンプル

2012年8月2日(木)
薬師寺 国安

※前ページからの続きです。

RGBカメラや距離カメラ、スケルトンのデータが更新された時に発生するイベント

Kinect センサーを取得します。e.OpenColorImageFrameで、新しいフレームのRGBカメラの情報を取得し、e.OpenDepthImageFrameで、新しいフレームの距離カメラの情報を取得します。
これらのメソッドで取得するColorImagerFrameやDepthImageFrameは、Usingで括るか、明示的にDisposeする必要があります。

RGBカメラと距離カメラの両方に情報があった場合は、バイト配列型の変数getBackgroundMaskを宣言して、BackgroundMask関数を実行して戻り値を取得します。BackgroundMask関数には引数として、Kinect センサー、RGBカメラのフレーム情報、距離カメラのフレーム情報を渡します。

ImageコントロールのSourceプロパティに、ピクセルデータをビットマップに変換して指定します。BitmapSource.Createメソッドの書式は下記の通りです。

BitmapSource.Create(RGBカメラで取得したフレーム幅,RGBカメラで取得したフレームの高さ,ビットマップの水平ドット(dpi),ビットマップの垂直ドット(dpi),ビットマップのピクセルフォーマット,ビットマップのパレット,ビットマップイメージのコンテンツを表すバイト配列,ビットマップのストライド)

ビットマップのピクセル形式には、PixelFormats.Bgra32を指定します。Bgra32 は、bits per pixel (BPP) が 32 の sRGB 形式です。各カラー チャネル(青、緑、赤、およびアルファ)に割り当てられるbits per pixel (BPP) は 8 です。ここに、Brg32を指定し、BackgroundMask関数の中で、
 If player 0 Then
  opacityColor(colorIndex) = colorPixel(colorIndex)
  opacityColor(colorIndex + 1) = colorPixel(colorIndex + 1)
  opacityColor(colorIndex + 2) = colorPixel(colorIndex + 2)
 End If
と記述すると、背景が透明化されませんので注意してください。

ここでは、Brg32ではなく、Bgra32を指定し、BackgroundMask関数の中で、
 If player 0 Then
  opacityColor(colorIndex) = colorPixel(colorIndex)
  opacityColor(colorIndex + 1) = colorPixel(colorIndex + 1)
  opacityColor(colorIndex + 2) = colorPixel(colorIndex + 2)
  opacityColor(colorIndex + 3) = &HFF
 End If
と指定することで、プレイヤーの背景が透明化されます。詳細はBackgroundMask関数の中で解説しています。
ビットマップイメージのコンテンツを表すバイト配列にBackgroundMaskの戻り値である、バイト配列を指定します。

ビットマップのストライドは1ラインあたりのバイト数を表しますので、この場合、colorFrame.Width * colorFrame.BytesPerPixelと指定します。BytesPerPixelプロパティでRGBカメラの1ピクセルあたりのバイト数を取得して、それにRGBカメラで取得されたフレームの幅を乗算した値を指定します。

BytesPerPixelは1ピクセルあたりのビット数が32bitのRGB形式で、先頭から1バイト(8bit)ずつに青、緑、赤、(残りの1バイト(8bit)はAlphaチャネル等に利用されます)の情報が入っています。
1バイトは8ビットですので32÷8=4バイトの値になります。よって640×4=2560となり、2560の値を直接指定しても問題ありません。

Private Sub kinect_AllFramesReady(sender As Object, e As AllFramesReadyEventArgs)
    Try
      Dim kinect As KinectSensor = DirectCast(sender, KinectSensor)
      If kinect Is Nothing Then
        Return
      End If
  
      Using colorFrame As ColorImageFrame = e.OpenColorImageFrame()
        Using depthFrame As DepthImageFrame = e.OpenDepthImageFrame()
          If (colorFrame IsNot Nothing) AndAlso (depthFrame IsNot Nothing) Then
            Dim getBackgroundMask As Byte() = BackgroundMask(kinect, colorFrame, depthFrame)
            Image1.Source = BitmapSource.Create(colorFrame.Width, colorFrame.Height, 96, 96, PixelFormats.Bgra32, Nothing, getBackgroundMask, colorFrame.Width * colorFrame.BytesPerPixel)
          End If
        End Using
      End Using
    Catch ex As Exception
      MessageBox.Show(ex.Message)
    End Try
  End Sub

表示用のコンテンツを表すバイト配列を取得する関数

RGBカメラの処理を行うクラスのインスタンスを取得します。同様に、距離カメラの処理を行うクラスのインスタンスを取得します。バイト配列変数colorPixelに、RGBカメラのピクセルデータのバイト長分の配列を作成します。CopyPixelDataToメソッドで、RGBカメラのフレームのピクセルのデータを取得します。

次に、Short配列変数depthPixelに距離カメラのピクセルデータのバイト長分の配列を作成します。CopyPixelDataToメソッドで、距離カメラのフレームのピクセルデータを取得します。
DepthImageFrame.CopyPixelDataToメソッドで取得できる距離データはshort型の16ビットの値です。この16ビットのデータは、上位13ビットが距離データ、下位3ビットがプレイヤーIDで構成されていますので(図2参照)、ここからピクセルごとに距離データを画像データとして作成していきます。

※深度情報は、1ピクセルあたり2バイト(short)。画像情報はフルカラーなので1ピクセルあたり4バイト(byte)が必要です。

RGBデータのピクセル座標を表すColorImagePoint型のcolorPoint配列変数を宣言し、距離カメラのピクセルデータのバイト長分の配列で初期化します。
MapDepthFrameToColorFrameで、距離カメラのX,Y座標に対応する、RGBカメラのX,Y座標を取得します。これは、深度情報を実画像に変換してくれるメソッドです。書式は下記の通りです。

KinectSensor.MapDepthFrameToColorFrame(距離カメラのフォーマット,距離カメラのピクセルデータ,RGBカメラのフォーマット,距離カメラの座標に対応するRGBカメラの座標)

Dim opacityColor As Byte() = New Byte(colorPixel.Length - 1) {}で、RGBカメラの画像を基に表示用のバッファを作成します。

変数indexを、0から、距離カメラのピクセルデータのバイト長分の配列を保持している、depthPixel配列変数の長さ分、反復処理を行い、下記の処理を実行します。

繰り返し変数indexに対応する距離カメラのピクセルデータのバイト配列と、距離カメラのフレームデータから、PlayerIndexBitmaskで取得された、ビットマスクに含まれているプレイヤーIDとで論理積演算を行います。

ビット演算 AND は、2 つのビットを比較し、両方のビットの値が 1 の場合にのみ、値 1 を結果に代入します。そうでない場合は、結果のビットに 0 をセットします。
AND 演算子は、その他すべてのビット演算子と同様、オペランドとして数値のみを取ります。 And演算子に付いては下記のURLを参照してください。また、AndとOr演算子の結果については図3を参照してください。
→ And 演算子(msdn)

繰り返し変数indexに対応するRGBデータのピクセル座標を表す、ColorImagePointの配列変数colorPointのXとY座標を取得します。 RGBデータのY座標に 距離カメラのフレームデータの幅を乗算して、RGBデータのX座標を加算し、その値にメンバ変数BrgPixelの 4(ビット)を乗算して変数colorIndexに格納しておきます。

プレイヤーを検出した座標だけ、RGBカメラの画像を使用します。変数colorIndexに対応する、Byte型の配列変数であるRGBカメラの画像(opacityColor)に、青、緑、赤、Alphaを指定していきます。32ビット(4バイト)分確保された配列で、最後の余った8ビット(1バイト)をAlphaチャネルに使用しています。

opacityColor(colorIndex + 3) = &HFFと指定しAlphaを指定することで背景が透明化されます。

戻り値は、表示用のコンテンツを表すバイト配列を取得したopacityColorです。

  Private Function BackgroundMask(kinect As KinectSensor, colorFrame As ColorImageFrame, depthFrame As DepthImageFrame) As Byte()
    Dim myColorStream As ColorImageStream = kinect.ColorStream
    Dim myDepthStream As DepthImageStream = kinect.DepthStream
 
    Dim colorPixel As Byte() = New Byte(colorFrame.PixelDataLength - 1) {}
    colorFrame.CopyPixelDataTo(colorPixel)
 
    Dim depthPixel As Short() = New Short(depthFrame.PixelDataLength - 1) {}
    depthFrame.CopyPixelDataTo(depthPixel)
 
    Dim colorPoint As ColorImagePoint() = New ColorImagePoint(depthFrame.PixelDataLength - 1) {}
 
  kinect.MapDepthFrameToColorFrame(myDepthStream.Format, depthPixel, myColorStream.Format, colorPoint)
    Dim opacityColor As Byte() = New Byte(colorPixel.Length - 1) {}
 
    For index As Integer = 0 To depthPixel.Length - 1
      Dim player As Integer = depthPixel(index) And DepthImageFrame.PlayerIndexBitmask
      Dim x As Integer = colorPoint(index).X
      Dim y As Integer = colorPoint(index).Y
      Dim colorIndex As Integer = ((y * depthFrame.Width) + x) * BgrPixel
 
      If player <> 0 Then
        opacityColor(colorIndex) = colorPixel(colorIndex) 'Blue
        opacityColor(colorIndex + 1) = colorPixel(colorIndex + 1) 'Green
 
        opacityColor(colorIndex + 2) = colorPixel(colorIndex + 2) 'Red
        opacityColor(colorIndex + 3) = &HFF 'Alpha
      End If
    Next
    Return opacityColor
  End Function
  • 人物だけを切り抜いて表示するKinectサンプル

薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています