Kinectで結成したマイ・ダンスチームを、サンプルを見ながら実際の背景に合成してみよう
前ページからの続きです。
ウィンドウが読み込まれた時の処理
ピクセル深度の値を2000(2m)で初期化しておきます。よって、Kinectセンサーからの距離を2mに保ってください。Kinectセンサーを取得し、ビットマップデータを初期化し、プレイヤーの画層を初期化するinit_kinectプロシージャを実行します。
構成ツリーのオブジェクトがレンダリングされる直前に発生する、CompositionTarget.Renderingイベントにイベントハンドラを指定します。イベントハンドラ内では以下の処理を行います。
OpenNextFrame(100)メソッドで、KinectからRGBデータの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。同様に、Kinectから深度データの次のフレームを開きます。次のフレームがなかった場合のタイムアウトを100ミリセコンドと指定しています。スケルトンフレームに対しても同様の処理を行います。
背景を描画し距離データを取得するRenderScreenプロシージャを実行します。引数として、Kinectのストリーミング用RGBデータのバッファと深度データのバッファを含んでいる、colorFrameとdepthFrameを渡しています。RGBデータのバッファ、深度データのバッファ、スケルトンデータのバッファを引数に、SaveBufferプロシージャを実行します。
DataContextプロパティにMainWindow自身のインスタンスを指定します。この処理を行わないとプレイヤーが表示されませんので注意してください。
Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded targetDepth = 2000 init_kinect() AddHandler CompositionTarget.Rendering, Sub(renderSender As Object, renderArgs As EventArgs) Using colorFrame As ColorImageFrame = Kinect.ColorStream.OpenNextFrame(100) Using depthFrame As DepthImageFrame = Kinect.DepthStream.OpenNextFrame(100) Using mySkeletonFrame As SkeletonFrame = Kinect.SkeletonStream.OpenNextFrame(100) RenderScreen(colorFrame, depthFrame) SaveBuffer(colorFrame, depthFrame, mySkeletonFrame) End Using End Using End Using End Sub DataContext = Me End Sub
Kinectセンサーを取得し、ビットマップデータを初期化し、プレイヤーの画層を初期化する処理
Kinectセンサーを取得します。DepthStream.Enableメソッドで距離カメラの表示サイズを320×240、1秒あたり30フレームで、動作を開始します。表示サイズを640×480に設定すると描画が重くて動かなくなるので注意してください。
同様にColorStream.Enableメソッドで、RGBカメラを、RGBフォーマット、表示サイズ640×480、1秒あたり30フレームで、動作を開始します。プレイヤーおよびスケルトンの認識も開始します。
骨格生成を行う場合は、深度センサー(距離カメラ)を有効にする必要があります。Dim depthStream As DepthImageStream = Kinect.DepthStreamで、距離カメラの処理を行うクラスのインスタンスを取得します。
Short配列変数myDepthPixelDataに距離カメラのピクセルデータのバイト長分の配列を作成します。320×240の表示サイズの場合は、myDepthPixelData=New Short(76800-1){}分の配列を確保することになります。-1しているのは配列のインデックスは0から始まるためです。
バイト配列変数myColorPixelDataにRGBカメラのピクセルデータのバイト長分の配列を作成します。
WriteableBitmapクラス型のmyOverrayBitmapを下記の書式で初期化します。
New WriteableBitmap(距離カメラのフレーム幅,距離カメラのフレーム高さ, ビットマップの水平値, ビットマップの垂直値,ビットマップのピクセルフォーマット,ビットマップのビットマップパレット)
「ビットマップのピクセルフォーマット」に指定する、PixelFormats.Bgra32は、Bgra32 ピクセル形式を取得します。ビットマップのピクセル形式には、PixelFormats.Bgra32を指定します。Bgra32 は、bits per pixel(BPP)が 32 の sRGB 形式です。
各カラー チャネル(青、緑、赤、およびアルファ)に割り当てられるbits per pixel(BPP)は 8 です。ここに、Brg32を指定すると背景が透明化されず真っ黒になってしまいますので注意してください。
ここは、myOverrayBitmap = New WriteableBitmap(320 240, 96, 96, PixelFormats.Bgra32, Nothing)
と指定しても同じです。
IntRect32構造体のmyScreenImageRectを下記の書式で初期化し、領域を指定します。
New IntRect32(新しいInt32Rect のインスタンスの X座標,新しいInt32Rect のインスタンスの Y座標, 四角形の幅を指定した新しいInt32Rect のインスタンスの幅, 四角形の高さを指定した新しい Int32Rect のインスタンスの高さ)
「新しいInt32Rect のインスタンスの X座標」と「新しいInt32Rect のインスタンスの Y座標」には0を指定します。「四角形の幅を指定した新しい Int32Rect のインスタンスの幅」には、myOverrayBitmapオブジェクトの幅を指定し、「Int32Rect のインスタンスの高さ」には、myOverrayBitmapオブジェクトの高さを指定します。
結局は、
myScreenImageRect = New Int32Rect(0, 0, 320, 240)
と指定しても同じことです。
リングバッファを初期化します。RingBuffer構造体のinit_ringbuffer関数を使用します。書式は下記の通りです。
init_ringbuffer(秒数分のバッファ領域(この場合は3), RGBストリームの画素数, 深度ストリームの画素数) As Boolean
WriteableBitmapクラス型のRoom_Bitmapを初期化し、メンバ変数myScreenImageRect2をRoom_Bitmap.WidthとRoom_Bitmap.Heightで初期化します。
myScreenImageRect2 = New Int32Rect(0, 0, 320, 240)と同じです。
Kinectセンサーを動作します。
Int32Rect構造体のmyImageSizeのWidthプロパティにRGBストリームのフレームの幅(640)を指定します。同じくHeightプロパティにはRGBストリームのフレームの高さ(480)を指定します。
Private Sub init_kinect() Try If KinectSensor.KinectSensors.Count = 0 Then MessageBox.Show("KINECTが接続されておりません。") Exit Sub Else Kinect = KinectSensor.KinectSensors(0) End If Kinect.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30) '640×480では重くて動かない。 Kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30) Kinect.SkeletonStream.Enable() Dim depthStream = Kinect.DepthStream myDepthPixelData = New Short(Kinect.DepthStream.FramePixelDataLength - 1) {} myColorPixelData = New Byte(Kinect.ColorStream.FramePixelDataLength - 1) {} myOverrayBitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) myScreenImageRect = New Int32Rect(0, 0, CInt(Math.Ceiling(myOverrayBitmap.Width)), CInt(Math.Ceiling(myOverrayBitmap.Height))) myRingBuffer.init_ringbuffer(bufferSecond, Kinect.ColorStream.FramePixelDataLength, Kinect.DepthStream.FramePixelDataLength) Room_Bitmap = New WriteableBitmap(depthStream.FrameWidth, depthStream.FrameHeight, 96, 96, PixelFormats.Bgra32, Nothing) myScreenImageRect2 = New Int32Rect(0, 0, CInt(Math.Ceiling(Room_Bitmap.Width)), CInt(Math.Ceiling(Room_Bitmap.Height))) Kinect.Start() myImageSize.Width = Kinect.ColorStream.FrameWidth myImageSize.Height = Kinect.ColorStream.FrameHeight Catch ex As Exception MessageBox.Show(ex.Message) Exit Sub End Try End Sub
背景を描画し距離データを取得する処理
RGBカメラの1ラインあたりのバイト数を表すcolorStride変数にcolorFrame.BytesPerPixel *colorFrame.Width と指定します。RGBカメラの1ピクセルあたりのバイト数を取得して、RGBカメラのフレームの幅に乗算します。RGBカメラの1ラインあたりのバイト数が取得できます。
32ビットを1バイト(8ビット)で除算すると4バイトとなり、それにcolorFrame.Widthの640を乗算します。4×640=2560となります。直接この値を指定しても問題はありません。
また、RGBカメラのピクセルあたりのバイト数を取得するBytesPerPixelと、距離カメラのストリームフレーム幅を乗算して、取得した距離カメラの1ラインあたりのストリームバイト数を変数screenImageStrideに格納しておきます。これは4×320=1280となり、直接この値を指定しても問題ありません。
Byte型の配列変数bytePlayer2変数を、距離カメラのフレームの高さと、距離カメラの1ラインあたりのストリームバイト数を保持しているscreenImageStrideで乗算した値で初期化します。つまり、depthFrame.Heightの320に、screenImageStrideに設定した、colorFrame.BytesPerPixel * depthFrame.Width (4(バイト)×320)の値である1280を乗算した(320×1280)409600-1の値で初期化されたバイト配列型のbytePlayer2オブジェクトが作成されます。
同様にbytePlayer1とbyteRoomバイト型配列変数も同じ値で初期化しておきます。
CopyPixelDataToメソッドで、距離カメラのピクセルごとのデータを取得します。CopyPixelDataToメソッドは、ピクセルデータの長さを使用して、事前に割り当てられた配列へ、ピクセルごとの深度データやRGBデータをコピーします。同様にRGBカメラのピクセルごとのデータも取得します。これで、実際のデータを取り出します。
繰り返し変数depthYで、0から距離カメラのフレームの高さ分反復処理を行います。反復処理内では以下の処理を行います。
depthPixelIndex変数に1ずつ加算される変数depthXの値と繰り返し変数depthYに距離カメラのフレーム幅(320)を乗算した値を加算して、格納しておきます。
init_kinectプロシージャで確保された、myDepthPixelDataのShort型の配列で、depthPixelIndex変数に対応する値と、距離カメラのフレームデータから、PlayerIndexBitmaskでビットマスクに含まれているプレイヤーIDとで論理積演算を行います。距離カメラのピクセルデータを反復処理し、プレイヤーのインデックス値を抽出します。
ビット演算 AND は、2 つのビットを比較し、両方のビットの値が 1 の場合にのみ、値 1 を結果に代入します。そうでない場合は、結果のビットに 0 をセットします。AND 演算子は、その他すべてのビット演算子と同様、オペランドとして数値のみを取ります。And演算子に付いては下記のURLを参照してください。
→ And 演算子(msdn)
RGBカメラのX-Y座標データを表すColorImagePointクラスのcolorPoint変数に、MapDepthToColorImagePointで、距離カメラの座標に対応する、RGBカメラの座標を取得します。深度情報を実画像に変換してくれます。書式は下記の通りです。
KinectSensor. MapDepthToColorImagePoint(距離カメラの フォーマット,距離カメラのX 座標,XとY座標の距離データ(Short型),RGBカメラのフォーマット)
変数colorPixelIndexに、RGBカメラのX座標とRGBカメラのピクセルあたりのバイト数を取得して乗算し、RGBカメラのY座標とRGBカメラの1ラインあたりのバイト数を表すcolorStrideを乗算して、これらを加算した値を格納しておきます。
プレイヤーが存在していない場合は、背景イメージを描画します。
変数depthXを1ずつ加算し、ImageIndex変数にRGBカメラのピクセルあたりのバイト数を加算していきます。
ImageIndex = ImageIndex + colorFrame.BytesPerPixel は
ImageIndex = ImageIndex + 4 としても同じです。
BytesPerPixelは1ピクセルあたりのバイト数を取得しますので、1ピクセルは8ビットでフルカラーの場合は32ビットになるため、32÷8=4バイトとなり、colorFrame.BytesPerPixelは4と同じになります。
この値が、Byte型の配列変数(byteRoom)のインデックスに対応します。ImageIndexに対応する、Byte型の配列変数である距離データを画像化していきます。
WriteableBitmap型の変数Room_BitmapにWritePixelsメソッドで、byteからビットマップへ書き出します。ビットマップの指定した領域内に更新したデータを格納します。書式は下記の通りです。このRoom_Bitmapプロパティの値を、ImageコントロールのSourceプロパティにバインドします。これで背景イメージが表示されます。
WritePixels(更新するWriteableBitmapの四角形,ビットマップの更新に使用するピクセル配列,pixel内の更新領域のストライド,入力バッファのオフセット)
Private Sub RenderScreen(colorFrame As ColorImageFrame, depthFrame As DepthImageFrame) If Kinect Is Nothing OrElse depthFrame Is Nothing OrElse colorFrame Is Nothing Then Return End If Dim depthPixelIndex As Integer Dim playerIndex As Integer Dim colorPixelIndex As Integer Dim colorPoint As ColorImagePoint Dim colorStride As Integer = colorFrame.BytesPerPixel * colorFrame.Width screenImageStride = colorFrame.BytesPerPixel * depthFrame.Width '1280 Dim ImageIndex As Integer = 0 Dim bytePlayer2 As Byte() = New Byte(depthFrame.Height * screenImageStride - 1) {} Dim bytePlayer1 As Byte() = New Byte(depthFrame.Height * screenImageStride - 1) {} Dim byteRoom As Byte() = New Byte(depthFrame.Height * screenImageStride - 1) {} depthFrame.CopyPixelDataTo(myDepthPixelData) colorFrame.CopyPixelDataTo(myColorPixelData) For depthY As Integer = 0 To depthFrame.Height - 1 Dim depthX As Integer = 0 While depthX < depthFrame.Width depthPixelIndex = depthX + (depthY * depthFrame.Width) playerIndex = myDepthPixelData(depthPixelIndex) And DepthImageFrame.PlayerIndexBitmask '人のID取得 colorPoint = Kinect.MapDepthToColorImagePoint(depthFrame.Format, depthX, depthY, myDepthPixelData(depthPixelIndex), colorFrame.Format) colorPixelIndex = (colorPoint.X * colorFrame.BytesPerPixel) + (colorPoint.Y * colorStride) If playerIndex = 0 Then '人以外は背景イメージへ描画 byteRoom(ImageIndex) = myColorPixelData(colorPixelIndex) 'Blue byteRoom(ImageIndex + 1) = myColorPixelData(colorPixelIndex + 1) 'Green byteRoom(ImageIndex + 2) = myColorPixelData(colorPixelIndex + 2) 'Red 'Alpha byteRoom(ImageIndex + 3) = &HFF 'Else ' Exit Sub End If depthX = depthX + 1 ImageIndex = ImageIndex + colorFrame.BytesPerPixel End While Next Room_Bitmap.WritePixels(myScreenImageRect2, byteRoom, screenImageStride, 0) End Sub
マイダンスグループを実際の背景に合成するサンプル
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Kinectの音声認識を使って、プレイヤーを分離、結合させるデモを試してみる
- Kinectを使って、画面上の赤い輪をくぐるサンプル
- これであなたもダンスグループの一員!?Kinectで自分を分身させるプログラムを作る
- Kinectを使って、自分の手のひらに小さな分身を出現させてみる
- 人体の連続した動作を音声でキャプチャするKinectのサンプルプログラム
- プレイヤーの身体パーツを判別するKinectサンプル
- Kinectで距離カメラの値を取得して、指定した距離で人物が背景に溶け込むサンプル
- 人物を切り抜いて画面に表示するKinectサンプル
- Kinectを使って、顔の動きを認識して画面に表示する
- Kinect v2のカメラから画像を取り込んで表示する基本プログラム