Kinectによる深度データの取得・表示と、モーターを動かすサンプル

2012年7月9日(月)
薬師寺 国安

今回は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で取得した深度データを表示するサンプル

薬師寺国安事務所

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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