Kinectによる深度データの取得・表示と、モーターを動かすサンプル
今回はKinectのカメラ系のセンサーを使ったサンプルと、Kinectのモーターを作動させてユーザーを認識する処理について解説していきます。
深度データの表示
1つ目は、「深度データの表示」です。KinectのRGBカメラで撮影した実写データの上に、距離カメラで取得した深度データ(センサーから物体までの距離)の画像を重ねて表示します。
実際の動画は以下のようになります。
実写と深度データ画像を重ねた動画
サンプル一式は、会員限定特典としてダウンロードできます。記事末尾をご確認ください。
プロジェクトの作成
VS 2010のメニューから[ファイル(F)/新規作成(N)/プロジェクト(P)]と選択します。
次に、「WPF アプリケーション」を選択して、「名前(N)」に任意のプロジェクト名を指定します。ここでは「KINECT_ColorDepthFrameShow」という名前を付けています。
ツールボックスからデザイン画面上にImageコントロールを2つ配置し、それぞれcolorImageとdepthImageという名前を付けます。2つのうち、depthImageが手前に来るように配置しますが、リスト1を見るとわかる通り、最後に配置されたコントロールほど手前にくることになります。2つのImageコントロールのサイズと位置は全く同じにします。
XAMLコードはリスト1、レイアウトは図1のようなります。
リスト1 (MainWindow.xaml)
(1)実写の上に深度画像を重ねるため、depthImageという名前のImageコントロールで、Opacityに0.5を指定して、背景の実写が透けて見えるように半透明しておきます。
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="実写の上に深度データ画像を重ねる" Height="480" Width="640"> <Grid> <Image Height="441" HorizontalAlignment="Left" Name="colorImage" Stretch="Fill" VerticalAlignment="Top" Width="618" /> <Image Height="441" HorizontalAlignment="Left" Name="depthImage" Stretch="Fill" VerticalAlignment="Top" Width="618" Opacity="0.5"/> ■(1) </Grid> </Window>
図1:Imageコントロールを2個配置した(クリックで拡大) |
参照の追加
VS2010のメニューから「プロジェクト(P)/参照の追加(R)」と選択して、各種コンポーネントを追加しておきます。今回追加するのは、Microsoft.KinectとCodingFun4.Kinect.Wpf.dllの2つです。.NETタブ内に表示されていないDLLファイルは、「参照」タブからDLLファイルを指定します。
Microsoft.Kinect.dllは、C:\Program Files\Microsoft SDKs\Kinect\v1.5\Assemblies内に存在しますので、これを指定します。CodingFun4.Kinect.Wpf.dllはCoding4Fun.Kinect.Toolkitをダウンロードして解凍したフォルダ内に存在していますので、それを指定してください。
次に、ソリューションエクスプローラー内のMainWindow.xamlを展開して表示される、MainWindow.xaml.vbをダブルクリックしてリスト2のコードを記述します。
ロジックコードを記述する
リスト2 (MainWindow.xaml.vb)
Option Strict On Imports Coding4Fun.Kinect.Wpf Imports Microsoft.Kinect Imports System.Windows.Media.Imaging Class MainWindow
Bgr32形式は1ピクセルあたりのビット数が32bitのRGB形式で、先頭から1バイト(8bit)ずつに、青、緑、赤、の情報が入っています。
各カラーチャネルに割り当てられるbits per pixel(BBP)が8のため、Bgr32を8で除算した4バイトの値をメンバ変数brg32Piexelに格納しておきます。青、緑、赤では24ビットしか使用されませんし、残りの8ビットはAlphaに使用されることが多いです。brg32Piexel メンバ変数に、32ビットを8で除算した4(バイト)を直接指定しても問題ありません。
Dim brg32Piexel As Integer = CInt(PixelFormats.Bgr32.BitsPerPixel / 8) ‘4
一つのKinectセンサーを表すクラスである、KinectSensorクラス用メンバ変数kinectを宣言します。
Dim kinect As KinectSensor
ウィンドウが読み込まれた時の処理
Kinectが接続されているかどうかを確認し、接続されていない場合は警告メッセージを出して処理を抜けます。接続されている場合は、1番目のKinectを利用するよう指定します。
PCには複数のKinectを同時に接続(最大で4台)できますので、複数のKinect接続が確認されている場合は、1番目のKinectを利用します。
次に、ColorStream.EnableメソッドでKinectセンサーのRGBカメラの動作を開始します。ColorImageFormat.RgbResolution640x480Fps30列挙体で「RGBフォーマットで、解像度は640×480、フレームレートは毎秒30フレーム」と設定します。
AddHandlerステートメントで、RGBカメラのフレームが更新されたことを通知するColorFrameReadyイベントにイベントハンドラを指定します。
次に、DepthStream.Enableメソッドで距離カメラの動作を開始し、DepthImageFormat.Resolution640x480Fps30列挙体で、「解像度640×480、フレームレート毎秒30フレーム」と指定します。
AddHandlerステートメントで、距離カメラのフレームが更新されたことを通知するDepthFrameReadyイベントにイベントハンドラを指定します。
StartメソッドでKinectセンサーを開始します。
Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded If KinectSensor.KinectSensors.Count = 0 Then MessageBox.Show("KINECTが接続されておりません。") Exit Sub Else kinect = KinectSensor.KinectSensors(0) kinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30) AddHandler kinect.ColorFrameReady, AddressOf kinect_ColorFrameReady kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30) AddHandler kinect.DepthFrameReady, AddressOf kinect_DepthFrameReady kinect.Start() End If End Sub
RGBカメラのフレームが更新されたことを通知するイベント
colorImageという名前を持つImageコントロールのSourceプロパティに、e.OpenColorImageFrameメソッドで新しいフレームのRGBカメラ情報を取得します。
そして、Coding4Fun.Kinect.Wpfの拡張メソッドであるToBitmapSourceで、myColorImageをBitmapSourceに変換して指定します。
これで、Imageコントロール内(colorImage)にRGBカメラの画像(実写)が表示されます。これらのメソッドで取得するColorImageFrameは、Usingで括るか、明示的にDisposeする必要があります。
Private Sub kinect_ColorFrameReady(sender As Object, e As ColorImageFrameReadyEventArgs) Using myColorImage As ColorImageFrame = e.OpenColorImageFrame colorImage.Source = myColorImage.ToBitmapSource End Using End Sub
距離カメラのフレームが更新されたことを通知するイベント
e.OpenDepthImageFrameで、新しいフレームの距離カメラ情報を取得します。これらのメソッドで取得するDepthImageFrameは、Usingで括るか、明示的にDisposeする必要があります。
depthImageコントロールのSourceプロパティに、ピクセルデータをビットマップに変換して指定します。
BitmapSource.Createメソッドの書式は下記の通りです。
BitmapSource.Create(RGBカメラで取得したフレーム幅, RGBカメラで取得したフレームの高さ,ビットマップの水平ドット(dpi),ビットマップの垂直ドット(dpi),ビットマップのピクセルフォーマット,ビットマップのパレット,ビットマップイメージのコンテンツを表すバイト配列,ビットマップのストライド)
ビットマップのピクセルフォーマットには、PixelFormats.Bgr32を指定します。
ビットマップイメージのコンテンツを表すバイト配列に、距離データを映像化するGetDepthColor関数の戻り値を指定します。
Private Sub kinect_DepthFrameReady(sender As Object, e As DepthImageFrameReadyEventArgs) Using myDepthImage As DepthImageFrame = e.OpenDepthImageFrame depthImage.Source = BitmapSource.Create(myDepthImage.Width, myDepthImage.Height, 96, 96, PixelFormats.Bgr32, Nothing, GetDepthColor(kinect, myDepthImage), myDepthImage.Width * brg32Piexel) End Using End Sub
距離データを映像化する関数
RGBカメラの処理を行うクラスのインスタンスを取得します。同様に距離カメラの処理を行うクラスのインスタンスを取得します。Short配列変数depthDataに距離カメラのピクセルデータの、バイト長分の配列を作成します。CopyPixelDataToメソッドで、距離カメラのフレームのピクセルデータを取得します。myDepthFrame.CopyPixelDataToメソッドで取得できる距離データはshort型の16ビットの値です。この16ビットのデータは、上位13ビットが距離データ、下位3ビットがプレイヤーIDで構成されています(図2参照)ので、ここからピクセルごとに距離データを画像データとして作成していきます。
RGBデータのピクセル座標を表すColorImagePoint型のmapDepthData配列変数を宣言し、距離カメラのピクセルデータの、バイト長分の配列で初期化します。
MapDepthFrameToColorFrameで、距離カメラのX,Y座標に対応する、RGBカメラのX,Y座標を取得します。つまり、距離カメラの画像とRGBカメラの画像の位置を合わせるということです。書式は下記の通りです。
KinectSensor.MapDepthFrameToColorFrame(距離カメラのフォーマット,距離カメラのピクセルデータ,RGBカメラのフォーマット,距離カメラの座標に対応するRGBカメラの座標)
バイト配列変数myDepthColorを、距離カメラのピクセルデータのバイト長に4バイトを乗算した値で初期化します。※深度情報は、1ピクセルあたり2バイト(short)。画像情報はフルカラーなので1ピクセルあたり4バイトが必要です。
変数iを、0から、距離カメラのピクセルデータの、バイト長分の配列を保持しているdepthData配列変数の長さ分、反復処理を行い、下記の処理を実行します。
繰り返し変数iに対応する、距離カメラのピクセルデータのバイト配列と、距離カメラのフレームデータから、深度データが必要な場合は、
Dim depth = depthData(i) >> DepthImageFrame.PlayerIndexBitmaskWidth
と記述して、depthData(i) をDepthImageFrame.PlayerIndexBitmaskWidth 分、右へシフトします。つまり、3ビット(DepthImageFrame.PlayerIndexBitmaskWidth分)右へシフトするとプレイヤーの深度のデータだけを取得できます。
上記は、ビットシフト演算を使用しています。ビットシフトはその名の通り、ビット列をそのまま左右に移動させる演算です。
expression1 >> expression2
expression1
>> は expression1 を expression2 だけ右にシフトします
expression1 と expression2 には、それぞれ式を指定します
ビットごとの右シフト演算子 (>>)やビットごとの左シフト演算子 (>>)については、下記のURLを参照してください。
(ビットごとの右シフト演算子 (>>) )
http://msdn.microsoft.com/ja-jp/library/k2ay192e.aspx
(ビットごとの左シフト演算子 (
http://msdn.microsoft.com/ja-jp/library/8xftzc7e.aspx
繰り返し変数iに対応するRGBデータのピクセル座標を表す、ColorImagePointの配列変数mapDepthDataのXとY座標を取得します。 RGBデータのY座標に、距離カメラのフレームデータの幅を乗算して、RGBデータのX座標を加算します、その値にメンバ変数brg32Piexelの 4(バイト)を乗算して、変数myColorIndexに格納しておきます。
つまり、
Dim myColorIndex As Integer = ((y * myDepthFrame.Width) + x) * brg32Piexel
は
Dim myColorIndex As Integer = ((y * 640) + x) * 4
と同じことです。
取得した深度が正常に取得されない場合myDepthStream.UnknownDepth、近すぎる場合myDepthStream.TooNearDepth、遠すぎの場合のmyDepthStream.TooFarDepth、これら以外の正常に取れた場合に、深度の色を変化させます。
戻り値は、表示用のコンテンツを表すバイト配列を取得したバイト配列のmyDepthColorです。
Private Function GetDepthColor(kinect As KinectSensor, myDepthFrame As DepthImageFrame) As Byte() Dim myColorStream As ColorImageStream = kinect.ColorStream Dim myDepthStream As DepthImageStream = kinect.DepthStream Dim depthData As Short() = New Short(myDepthFrame.PixelDataLength - 1) {} myDepthFrame.CopyPixelDataTo(depthData) Dim mapDepthData As ColorImagePoint() = New ColorImagePoint(myDepthFrame.PixelDataLength - 1) {} kinect.MapDepthFrameToColorFrame(myDepthStream.Format, depthData, myColorStream.Format, mapDepthData) Dim myDepthColor As Byte() = New Byte((myDepthFrame.PixelDataLength) * brg32Piexel) {} For i As Integer = 0 To depthData.Length - 1 Dim depth = depthData(i) >> DepthImageFrame.PlayerIndexBitmaskWidth Dim x As Integer = mapDepthData(i).X Dim y As Integer = mapDepthData(i).Y Dim myColorIndex As Integer = ((y * myDepthFrame.Width) + x) * brg32Piexel If depth = myDepthStream.UnknownDepth Then myDepthColor(myColorIndex) = 66 myDepthColor(myColorIndex + 1) = 66 myDepthColor(myColorIndex + 2) = 33 ElseIf depth = myDepthStream.TooNearDepth Then myDepthColor(myColorIndex) = 0 myDepthColor(myColorIndex + 1) = 255 myDepthColor(myColorIndex + 2) = 0 ElseIf depth = myDepthStream.TooFarDepth Then myDepthColor(myColorIndex) = 66 myDepthColor(myColorIndex + 1) = 0 myDepthColor(myColorIndex + 2) = 66 Else myDepthColor(myColorIndex) = 0 myDepthColor(myColorIndex + 1) = 255 myDepthColor(myColorIndex + 2) = 255 End If Next Return myDepthColor End Function
ウィンドウが閉じられる時に発生するイベント
Kinectセンサーが動作している場合は、各イベントの登録を解除し、StopメソッドでKinectセンサーの動作を停止します。最後にDisposeメソッドでリソースを解放します。
Private Sub MainWindow_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles Me.Closing If kinect Is Nothing = False Then If kinect.IsRunning = True Then RemoveHandler kinect.ColorFrameReady, AddressOf kinect_ColorFrameReady RemoveHandler kinect.DepthFrameReady, AddressOf kinect_DepthFrameReady kinect.Stop() kinect.Dispose() End If End If End Sub End Class
図2:myDepthFrame.CopyPixelDataToメソッドで取得された、short型の16ビットの内訳(クリックで拡大) |
次にKinect センサーのチルトモーターを動作させるサンプルを紹介します。
Kinectで取得した深度データを表示するサンプル
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Kinectで距離カメラの値を取得して、指定した距離で人物が背景に溶け込むサンプル
- 人物を切り抜いて画面に表示するKinectサンプル
- Kinectを使って、顔の動きを認識して画面に表示する
- Kinectを使って、画面上の赤い輪をくぐるサンプル
- Kinectで結成したマイ・ダンスチームを、サンプルを見ながら実際の背景に合成してみよう
- これであなたもダンスグループの一員!?Kinectで自分を分身させるプログラムを作る
- Kinectの音声認識を使って、プレイヤーを分離、結合させるデモを試してみる
- プレイヤーの身体パーツを判別するKinectサンプル
- Kinectを使って、自分の手のひらに小さな分身を出現させてみる
- Kinect v2のカメラから画像を取り込んで表示する基本プログラム