2011年12月19日月曜日

KINECT SDK Beta2 でスケルトンデータを扱う( VB+ WPF )

このエントリはKINECT SDK Advent Calendar : ATNDの12月19日分です。
kaorun55氏の「KINECT SDK Beta2 でスケルトンデータを扱う( C# + WPF ) のVB版になります。

いつもと同じようにXAMLソースから
--------------------------------------------------------------------------------

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="521" Width="661">
    <Grid>
        <Image Height="480" Name="Image1" Width="640" />
    </Grid>
</Window>
--------------------------------------------------------------------------------
特別なところはありません。Imageコントロールが1つだけです。

メインソースです。
--------------------------------------------------------------------------------
Imports System.Threading
Imports System.Windows.Threading
Imports System.Windows.Media
Imports Microsoft.Research.Kinect.Nui

Class MainWindow
    Inherits Window

    Private readerThread As Thread
    Private shouldRun As Boolean
    Private kinect As Runtime
    Private usercolor() As Color = {Color.FromRgb(0, 0, 0), Colors.Red, Colors.Green, Colors.Blue, Colors.Yellow, Colors.Magenta, Colors.Pink}

    Public Sub New()

        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。

        If Runtime.Kinects.Count > 0 Then
            ''KINECT初期化
            kinect = Runtime.Kinects(0)
            kinect.Initialize(RuntimeOptions.UseColor Or RuntimeOptions.UseDepthAndPlayerIndex Or RuntimeOptions.UseSkeletalTracking)
            kinect.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution640x480, ImageType.Color)
            kinect.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex)

            ''スレッドの開始
            shouldRun = True
            readerThread = New Thread(New ThreadStart(AddressOf RenderThread))
            readerThread.Start()

        End If

    End Sub

    Sub RenderThread()

        While (shouldRun)

            ''タイムアウトは100ms
            Dim video As ImageFrame = kinect.VideoStream.GetNextFrame(100)
            Dim depth As ImageFrame = kinect.DepthStream.GetNextFrame(100)
            Dim skeleton As SkeletonFrame = kinect.SkeletonEngine.GetNextFrame(100)

            ''メインスレッドにイメージデータの書き込みを指示
            Me.Dispatcher.BeginInvoke(DispatcherPriority.Background, New Action(
                Sub()
                    Dim drawingVisual As New DrawingVisual

                    Using drawingContext As DrawingContext = drawingVisual.RenderOpen()
                        drawingContext.DrawImage(DrawPixels(kinect, video, depth), New Rect(0, 0, video.Image.Width, video.Image.Height))

                        ''骨格の部位ごとに座標を求める
                        For Each s In skeleton.Skeletons
                            If s.TrackingState = SkeletonTrackingState.Tracked Then
                                For Each j In s.Joints
                                    Dim point As Point = GetVideoPoint(j)

                                    '円を描く
                                    drawingContext.DrawEllipse(New SolidColorBrush(Colors.Red), New Pen(Brushes.Red, 1), New Point(point.X, point.Y), 5, 5)

                                Next
                            End If
                        Next
                    End Using

                    '描画可能なビットマップを作る
                    Dim bitmap As RenderTargetBitmap = New RenderTargetBitmap(video.Image.Width, video.Image.Height, 96, 96, PixelFormats.Default)
                    bitmap.Render(drawingVisual)

                    Image1.Source = bitmap

                End Sub
            ))

        End While

    End Sub

    Function DrawPixels(ByVal kinect As Runtime, ByVal video As ImageFrame, ByVal depth As ImageFrame) As WriteableBitmap
        Dim x, y, index As Integer
        Dim playerIndex, distance As Integer
        Dim videoX, videoY, videoIndex As Integer
        Dim byte0, byte1 As Byte

        'ピクセルごとのユーザーID
        For y = 0 To depth.Image.Height - 1
            For x = 0 To depth.Image.Width - 1
                index = (x + (y * depth.Image.Width)) * 2
                byte0 = depth.Image.Bits(index)
                byte1 = depth.Image.Bits(index + 1)

                'ユーザーIDと距離
                playerIndex = byte0 And &H7
                distance = byte1 << 5 Or byte0 >> 3

                If playerIndex <> 0 Then
                    videoX = 0
                    videoY = 0

                    ''深度データの座標からカラーデータの座標へ変換
                    kinect.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(ImageResolution.Resolution640x480, New ImageViewArea(), x, y, 0, videoX, videoY)
                    videoIndex = (videoX + (videoY * video.Image.Width)) * video.Image.BytesPerPixel
                    videoIndex = Math.Min(videoX, video.Image.Bits.Length - video.Image.BytesPerPixel)
                    video.Image.Bits(videoIndex) = usercolor(playerIndex).R
                    video.Image.Bits(videoIndex + 1) = usercolor(playerIndex).G
                    video.Image.Bits(videoIndex + 2) = usercolor(playerIndex).B
                End If

            Next
        Next

        'バイト列をビットマップに展開
        '描画可能なビットマップを作る
        Dim bitmap As WriteableBitmap = New WriteableBitmap(video.Image.Width, video.Image.Height, 96, 96, PixelFormats.Bgr32, Nothing)
        bitmap.WritePixels(New Int32Rect(0, 0, video.Image.Width, video.Image.Height), video.Image.Bits, video.Image.Width * video.Image.BytesPerPixel, 0)

        Return bitmap

    End Function

    Function GetVideoPoint(ByVal joint As Joint) As Point

        Dim kinect As Runtime = Runtime.Kinects(0)
        Dim depthX, depthY As Single
        Dim videoX, videoY As Integer

        depthX = 0
        depthY = 0

        ''骨格データ(ベクタ座標)から深度データの座標へ変換
        kinect.SkeletonEngine.SkeletonToDepthImage(joint.Position, depthX, depthY)
        depthX = Math.Min(depthX * kinect.DepthStream.Width, kinect.DepthStream.Width)
        depthY = Math.Min(depthY * kinect.DepthStream.Height, kinect.DepthStream.Height)

        videoX = 0
        videoY = 0

        ''深度データの座標をカラーデータの座標に変換
        kinect.NuiCamera.GetColorPixelCoordinatesFromDepthPixel(ImageResolution.Resolution640x480, New ImageViewArea(), CInt(depthX), CInt(depthY), 0, videoX, videoY)

        Return New Point(Math.Min(videoX, kinect.VideoStream.Width), Math.Min(videoY, kinect.VideoStream.Height))

    End Function

    Private Sub Window_Unloaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Unloaded

        shouldRun = False

    End Sub
End Class
--------------------------------------------------------------------------------

Skeletonの座標データはKINECTを中心にXYZの3D座標になります。
XYは-1~1までの単精度浮動小数になるのでこれを2Dの座標に変換しなければいけないのですが、手順としてSkeleton座標>Depth(深度)座標>Video(カラー)座標の順番に変換しています。

Skeleton>Depthの変換はSkeletonToDepthImageで変換します。
Depth>Videoの変換はGetColorPixelCoordinatesFromDepthPixelを使って変換しています。

変換した座標をもとにShapeを描画すればVideo,Depthのイメージに骨格情報を重ねて表示できます。

0 件のコメント:

コメントを投稿