PR

Kinect v2を使った「じゃんけんゲーム」を作る

2014年12月16日(火)
薬師寺 国安

音声認識処理

リスト6 (MainWindow.xaml.vbの一部、リスト5の続き)

    Private Sub speechEngine_SpeechRecognized(sender As Object, e As SpeechRecognizedEventArgs)
        messageTextBlock.Text = String.Empty
        Rnd = New Random
        RandomNumber = Rnd.Next(0, 2)   (1)
        Const myConfidence As Double = 0.4   (2)
        If e.Result.Confidence >= myConfidence Then
            Select Case e.Result.Text
                Case “じゃんけんぽん”
                    computerJankenImage.Source = Nothing

                    JyankenImage = xmldoc.Descendants(“画像名”)(RandomNumber).Value   (3)
                    computerJankenImage.Source = New BitmapImage(New Uri(“Images/” & JyankenImage, UriKind.Relative))
                    If JyankenImage = “グー.png” AndAlso flagImage = “グー.png” Then   (4)
                        messageTextBlock.Text = “あいこです”
                    ElseIf JyankenImage = “パー.png” AndAlso flagImage = “パー.png” Then
                        messageTextBlock.Text = “あいこです”
                    ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “チョキ.png” Then
                        messageTextBlock.Text = “あいこです”
                    ElseIf JyankenImage = “グー.png” AndAlso flagImage = “パー.png” Then
                        messageTextBlock.Text = “あなたの勝です”
                        player_Score += 1
                    ElseIf JyankenImage = “パー.png” AndAlso flagImage = “グー.png” Then
                        messageTextBlock.Text = “コンピューターの勝です”
                        computer_Score += 1
                    ElseIf JyankenImage = “パー.png” AndAlso flagImage = “チョキ.png” Then
                        messageTextBlock.Text = “あなたの勝です”
                        player_Score += 1
                    ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “パー.png” Then
                        messageTextBlock.Text = “コンピューターの勝です”
                        computer_Score += 1
                    ElseIf JyankenImage = “グー.png” AndAlso flagImage = “チョキ.png” Then
                        messageTextBlock.Text = “コンピューターの勝です”
                        computer_Score += 1
                    ElseIf JyankenImage = “チョキ.png” AndAlso flagImage = “グー.png” Then
                        messageTextBlock.Text = “あなたの勝です”
                        player_Score += 1
                    End If
                    playerScoreTextBlock.Text = “あなたの得点=” & player_Score
                    computerScoreTextBlock.Text = “コンピューターの得点=” & computer_Score
            End Select
        End If
    End Sub
  1. 0~2までの3つの乱数を発生します。
  2. Confidenceプロパティで音声認識の信頼度を設定します。-1~1の値で指定し、数字が大きいほど信頼度が高くなります。標準は「0」です。「-1」を指定すると、どんな言葉でも反応してしまう可能生があり、「1」を指定すると、なかなか認識されません。今回は信頼度が「0.4」より大きい場合に言葉を認識するよう指定しています。
    以下、プレイヤーが「じゃんけんぽん」とセンサーに向かって発生した場合の処理です。
  3. 乱数に該当する位置にある「画像名」要素の値を取得します。
  4. コンピューターが表示する「グー」「チョキ」「パー」のイメージ画像によって条件分岐を行います。JyanlenImageとflagImageの画像を比較して勝ち負けを表示しています。結果をmessageTextBlockに表示し、プレイヤーが勝ったら変数player_Scoreを、コンピューターが勝ったらcomputer_Scoreを1ずつ加算して表示します。

新しいボディフレームの準備ができているときに発生するイベント処理

リスト7 (MainWindow.xaml.vbの一部、リスト6の続き)

    Private Sub myBodyFrameReader_FrameArrived(sender As Object, e As BodyFrameArrivedEventArgs)
        Using frame = e.FrameReference.AcquireFrame   (1)
            If frame Is Nothing = False Then
                Using myDataContext = myBodyDrawingGroup.Open
                    myDataContext.DrawRectangle(Brushes.Black, Nothing, myRect)
                    frame.GetAndRefreshBodyData(myBodies)   (2)
                    For i As Integer = 0 To Me.myBodies.Length - 1   (3)
                        If myBodies(i).IsTracked = True Then
                            Dim joints As IReadOnlyDictionary(Of JointType, Joint) = myBodies(i).Joints   (4)
                            Dim points As New Dictionary(Of JointType, Point) (5)
                            For Each joint In joints.Keys  (6)
                                Dim pos = myKinect.CoordinateMapper.MapCameraPointToDepthSpace(joints(joint).Position)   (7)
                                points(joint) = New Point(pos.X, pos.Y)   (8)
                                If joint = JointType.HandLeft Then   (9)
                                    DrawHandState(myBodies(i).Joints(JointType.HandLeft), myBodies(i).HandLeftState, myBodies(i).HandLeftConfidence)
                                ElseIf joint = JointType.HandRight Then
                                    DrawHandState(myBodies(i).Joints(JointType.HandRight), myBodies(i).HandRightState, myBodies(i).HandRightConfidence)
                                End If
                            Next
                        End If
                    Next
                    Me.myBodyDrawingGroup.ClipGeometry = New RectangleGeometry(myRect)   (10)
                End Using
            End If
        End Using
    End Sub
  1. e.FrameReference.AcquireFrameメソッドで、ボディフレームを取得し、Frameで参照します。
  2. GetAndRefreshBodyDataメソッドで、更新されたボディデータを取得します。
  3. ボディデータの中を、反復処理を行います。
  4. ボディデータが追跡されている場合は、ボディデータの関節を取得し、変数jointsで参照します。
  5. JointTypeのキーとPointの値で初期化された新しい、Dictionaryのインスタンスである、pointsオブジェクトを作成します。
  6. 各関節をjoint変数で取得していきます。
  7. CoordinateMapper.MapCameraPointToDepthSpaceメソッドで、関節の位置を、カメラ空間から距離空間へのポイントにマップし、変数posで参照します。CoordinateMapper.MapCameraPointToDepthSpaceメソッドの書式は下記の通りです。
    CoordinateMapper.MapCameraPointToDepthSpace(cameraPoint)
    「cameraPoint」にはカメラ空間からマップするポイントCameraSpacePointを指定します。この場合はカメラ空間における関節の位置を指定しています。
  8. 距離空間にマップされた関節のXとY座標で初期化された、新しいPointのインスタンスpoints(joint)オブジェクトを作成します。
  9. 関節のタイプが「左手」や「右手」であった場合は、DrawHandStateを実行します。
  10. DrawingGroup のクリップ領域にmyRectで初期化された2次元の四角形を設定します。

ウインドウが閉じられる場合の処理

リスト8 (MainWindow.xaml.vbの一部、リスト7の続き)

    Private Sub MainWindow_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
        If myKinect Is Nothing = False Then
            myKinect.Close()
            myKinect = Nothing
        End If
    End Sub

Kinectが動作している場合は、Kinectを閉じ、全ての関連付けから解放します。

カラーフレーム到着時のイベント

リスト9 (MainWindow.xaml.vbの一部、リスト8の続き)

    Private Sub myColorFrameReader_FrameArrived(sender As Object, e As ColorFrameArrivedEventArgs)
        Using myColorFrame As ColorFrame = e.FrameReference.AcquireFrame (1)
            If myColorFrame Is Nothing = True Then
                Return
            End If
            myColorFrame.CopyConvertedFrameDataToArray(ColorImagePixelData, ColorImageFormat.Bgra)   (2)
        End Using
        colorBitmap.WritePixels(New Int32Rect(0, 0, colorBitmap.PixelWidth, colorBitmap.PixelHeight), ColorImagePixelData, colorBitmap.PixelWidth * BytesPerPixel, 0)   (3)
        Image1.Source = colorBitmap   (4)
    End Sub
  1. e.FrameReference.AcquireFrameメソッドでカラーフレームを取得し、myColorFrameで参照します。
  2. CopyConvertedFrameDataToArrayメソッドで、フレームから「BGRA形式」のバイト列に変換し、バイト配列に格納します。書式は下記の通りです。
    CopyConvertedFrameDataToArray(frameData, colorFomat)
    「frameData」には格納する配列、この場合はバイト配列のColorImagePixelDataを指定しています。「colorFormat」には「色の形式」を指定します。ここでは、「Bgra形式」となります。
  3. colorBitmap(WriteableBitmapクラス)のWritePixelsメソッドで、ビットマップの指定した領域内のピクセルを更新します。書式は下記の通りです。
    WritePixels(sourecRect, pixels, stride, offset)
    「sourceRect」には「Int32Rect型」を指定します。更新するWriteableBitmapの四角形です。「pixels」にはビットマップの更新に使用する「ピクセル配列」を指定します。この場合は、ColorImagePixelDataのバイト列を指定しています。「stride」にはpixels内の更新領域の「ストライド」を指定します。今回はcolorBitmapのPixelWidthに「4」を乗算した値を指定しています。「ストライド」とは、画像データのX座標横一列あたりに用いられるバイト数を示す値です。例えば、横幅が512ピクセルで、RGBA各色8ビット(32ビットカラー)で表現される画像データのストライドは、
    512(ピクセル)×4(バイト)=2048
    となります。今回の場合はRGBカラーの解像度が1920×1080(ピクセル)であるため
    1920×4=7680
    の値が「stride」に適用されることになります。「offset」には入力バッファのオフセットを指定します。今回は「0」を指定しています。
  4. 「Image1」のSourceプロパティにcolorBitmapオブジェクトを指定します。これでRGBカメラからの画像が表示されます。

プレイヤーの手の形によって条件分岐を行う処理

リスト10 (MainWindow.xaml.vbの一部、リスト9の続き)

    Private Sub DrawHandState(joint As Joint, state As HandState, myTrackingConfidence As TrackingConfidence)

        If myTrackingConfidence <> TrackingConfidence.High Then
            Return
        End If

        If state = HandState.Open Then
            jankenImage.Source = New BitmapImage(New Uri("Images/パー.png", UriKind.Relative))
            flagImage = "パー.png"
        ElseIf state = HandState.Closed Then
            jankenImage.Source = New BitmapImage(New Uri("Images/グー.png", UriKind.Relative))
            flagImage = "グー.png"
        ElseIf state = HandState.Lasso Then
            jankenImage.Source = New BitmapImage(New Uri("Images/チョキ.png", UriKind.Relative))
            flagImage = "チョキ.png"
        Else
            jankenImage.Source = Nothing
            Exit Sub
        End If
    End Sub
End Class

手の形がOpen、つまり「パー」の場合は、ソリューションエクスプローラー内のImagesフォルダーから、「パー.png」をjankenImageのSourceプロパティに指定します。flagImageには「パー.png」を代入します。「グー」、「チョキ」パーについても同じ処理を行います。ここでの値が、speechEngine_SpeechRecognizedイベントの音声認識で使用され、「じゃんけんぽん」と言いながら「グー」「チョキ」「パー」のいずれかをセンサーに向けると、コンピューターとの勝負結果が表示されるようになります。

実行すると動画1のようになります。実際には「じゃんけんぽん」と喋っていますが、動画には音声が入っていませんので、ご了承ください。

動画1:Kinect v2センサーが手の形を認識して、じゃんけんができる

Think IT会員限定特典
  • Kinect v2を使った「じゃんけんゲーム」を作るサンプル

    『作りながら学ぶKinect v2プログラミング開発』 第2回のサンプルプログラムです。
薬師寺国安事務所

薬師寺国安事務所代表。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のWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

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