読者です 読者をやめる 読者になる 読者になる

Natural Software

KinectなどのDepthセンサーを中心に活動しています

KINECT と Windows Phone を連携する #kinectsdk_ac

Kinect

このエントリはKINECT SDK Advent Calendar 2011 : ATNDの12月21日分です!!
Advent Calendarでの、僕の全プロジェクトはこちらです


今日は、Windows Phone ハッカソンにいってきました。
Windows Phoneのアプリはほとんど初めてですが、僕ができることといったらKINECTが中心なので、KINECTWindows Phoneを連携させて見ました。今日の成果はこんな感じです。
KINCET SDKで取得したスケルトンをWindows Phoneで表示しています。これで何がうれしいかはナゾですが、とりあえずできました。
ちなみに、Windows Phone以外のデバイスと連携させたアプリケーションは、審査通らないみたいなので、公開するには一工夫必要のようです。


やってることは、KINECT SDKで取得したスケルトンをIPマルチキャストで垂れ流して、それをWindows Phoneが拾う。という形です。

KINECT

プロジェクトはこちら

先日のスケルトン表示を使い、データの配信部分を追加しています

以降、追加部分を示します。

ソケットの設定

IPマルチキャストでは、グループに属する必要があるとのことで、通常のUDPソケットのほかにグループIPとポートを用意します。UDPマルチキャストはこちらを参考にしました。

Socket server = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp );
IPEndPoint iep = new IPEndPoint( IPAddress.Parse( "224.100.0.1" ), 9050 );
データの送信

ケルトンデータの送信です。スケルトンデータはdouble型のX,Y座標なので、それをintに丸めて、バイト列にしています。JointID.Countは関節の数で"20"となっています。変換が力技なので、スマートな方法があれば教えてください。

if ( s.TrackingState == SkeletonTrackingState.Tracked ) {
    byte[] p = new byte[(int)JointID.Count * 2 * 4];
    int count = 0;

    foreach ( Joint j in s.Joints ) {
        var point = GetVideoPoint( j );

        // 座標をバイト列にする
        p[count++] = (byte)point.X;
        p[count++] = (byte)((int)point.X >> 8);
        p[count++] = (byte)((int)point.X >> 16);
        p[count++] = (byte)((int)point.X >> 24);

        p[count++] = (byte)point.Y;
        p[count++] = (byte)((int)point.Y >> 8);
        p[count++] = (byte)((int)point.Y >> 16);
        p[count++] = (byte)((int)point.Y >> 24);

        // 円を書く
        drawingContext.DrawEllipse( new SolidColorBrush( Colors.Red ),
            new Pen( Brushes.Red, 1 ), new Point( point.X, point.Y ), 5, 5 );
    }

    // 送信する
    server.SendTo( p, iep );
}

Windows Phone側

つづいてWindows Phone側です。こちらでは、受信したスケルトンデータを復元し、ポイントに円を描いています。プロジェクトはこちら。

using System;
using System.Net;
using System.Net.Sockets;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

namespace PhoneApp1
{
    public partial class MainPage : PhoneApplicationPage
    {
        UdpAnySourceMulticastClient myClient = new UdpAnySourceMulticastClient( IPAddress.Parse( "224.100.0.1" ), 9050 );

        // コンストラクター
        public MainPage()
        {
            InitializeComponent();

            try {
                canvas1.Width = width;
                canvas1.Height = height;

                myClient.BeginJoinGroup(
                    result =>
                    {
                        try {
                            myClient.EndJoinGroup( result );
                            myClient.MulticastLoopback = true;
                        }
                        catch ( Exception ex ) {
                            MessageBox.Show( "Join succeeded. but something wrong. " + ex.Message );
                        }
                    }, null
                );

                Receive();
            }
            catch ( Exception ex ) {
                MessageBox.Show( "Join failed. " + ex.Message );
            }
        }

        int count = 0;
        //const int width = 640 / 8;
        //const int height = 480 / 8;
        //byte[] receiveBuffer = new byte[width * height * 4];
        const int width = 640;
        const int height = 480;
        byte[] receiveBuffer = new byte[20 * 2 * 4];
        int[] skeleton = new int[20 * 2];

        private void Receive()
        {
            myClient.BeginReceiveFromGroup( receiveBuffer, 0, receiveBuffer.Length,
                result =>
                {
                    try {
                        IPEndPoint source;
                        myClient.EndReceiveFromGroup( result, out source );
                        string sourceIPAddress = source.Address.ToString();

                        Dispatcher.BeginInvoke(() =>
                            {
                                count++;
                                textBlock1.Text = count.ToString();

                                //var b = new WriteableBitmap( width, height );
                                //for ( int i = 0; i < b.Pixels.Length; i++ ) {
                                //    int index = i * 4;
                                //    b.Pixels[i] = receiveBuffer[index + 0] << 24 | 
                                //                  receiveBuffer[index + 1] << 16 |
                                //                  receiveBuffer[index + 2] << 8 |
                                //                  receiveBuffer[index + 3];
                                //}
                                //image1.Source = b;

                                var b = new WriteableBitmap( width, height );

                                for ( int i = 0; i < skeleton.Length; i++ ) {
                                    int index = i * 4;
                                    skeleton[i] = receiveBuffer[index + 3] << 24 | 
                                                  receiveBuffer[index + 2] << 16 |
                                                  receiveBuffer[index + 1] << 8 |
                                                  receiveBuffer[index + 0];
                                }

                                canvas1.Children.Clear();
                                for ( int i = 0; i < skeleton.Length; i += 2 ) {
                                    int x = i;
                                    int y = i + 1;

                                    var e = new Ellipse()
                                    {
                                        Fill = new SolidColorBrush()
                                        {
                                            Color = Colors.Red
                                        },
                                        Width = 10,
                                        Height = 10,
                                    };

                                    Canvas.SetLeft( e, skeleton[x] );
                                    Canvas.SetTop( e, skeleton[y] );
                                    canvas1.Children.Add( e );
                                }
                            } );

                        Receive();
                    }
                    catch ( Exception ex ) {
                        Receive();

                        Dispatcher.BeginInvoke( () =>
                        {
                            MessageBox.Show( "recv error " + ex.Message );
                        } );
                    }
                },
                null
            );
        }
    }
}
IPマルチキャストの設定

Windows Phoneには、IPマルチキャスト専用の「UdpAnySourceMulticastClient」クラスが用意されているようなので、それを使います。送信側と同じ、グループのIPアドレスとポート番号が必要です。

UdpAnySourceMulticastClient myClient = new UdpAnySourceMulticastClient( IPAddress.Parse( "224.100.0.1" ), 9050 );
グループへの参加

続いてグループへの参加です。このメソッドは非同期で実行され、result => 以降はグループ参加の結果がわかり次第実行されます。それと平行して受信処理を行います。

myClient.BeginJoinGroup(
    result =>
    {
        try {
            myClient.EndJoinGroup( result );
            myClient.MulticastLoopback = true;
        }
        catch ( Exception ex ) {
            MessageBox.Show( "Join succeeded. but something wrong. " + ex.Message );
        }
    }, null
);

Receive();
受信処理

データの受信処理です。KINECTアプリ側から送られてきたスケルトンデータを、組みなおして円を描いています。ここでもBeginReceiveFromGroupは非同期で実行されるので、result => 以降は受信したら動き出します。この中でReceiveを呼び出していますが、スレッドが異なるため、再帰にはならないようです。また、UIスレッドではないので、UIへのアクセスはBeginInvokeを使用しています。

int count = 0;
const int width = 640;
const int height = 480;
byte[] receiveBuffer = new byte[20 * 2 * 4];
int[] skeleton = new int[20 * 2];

private void Receive()
{
    myClient.BeginReceiveFromGroup( receiveBuffer, 0, receiveBuffer.Length,
        result =>
        {
            try {
                IPEndPoint source;
                myClient.EndReceiveFromGroup( result, out source );
                string sourceIPAddress = source.Address.ToString();

                Dispatcher.BeginInvoke(() =>
                    {
                        var b = new WriteableBitmap( width, height );

                        for ( int i = 0; i < skeleton.Length; i++ ) {
                            int index = i * 4;
                            skeleton[i] = receiveBuffer[index + 3] << 24 | 
                                            receiveBuffer[index + 2] << 16 |
                                            receiveBuffer[index + 1] << 8 |
                                            receiveBuffer[index + 0];
                        }

                        canvas1.Children.Clear();
                        for ( int i = 0; i < skeleton.Length; i += 2 ) {
                            int x = i;
                            int y = i + 1;

                            var e = new Ellipse()
                            {
                                Fill = new SolidColorBrush()
                                {
                                    Color = Colors.Red
                                },
                                Width = 10,
                                Height = 10,
                            };

                            Canvas.SetLeft( e, skeleton[x] );
                            Canvas.SetTop( e, skeleton[y] );
                            canvas1.Children.Add( e );
                        }
                    } );

                Receive();
            }
            catch ( Exception ex ) {
                Receive();

                Dispatcher.BeginInvoke( () =>
                {
                    MessageBox.Show( "recv error " + ex.Message );
                } );
            }
        },
        null
    );
}

まとめ

データの受け渡しさえできれば、比較的簡単にできました。最初はRGBカメラでやろうとしたのですが、データ量が大きすぎて送信できませんでした。
使い道も、いくつか思いついたのでちょっとやってみようかと思います。


また、今日のハッカソンのおかげでWindows Phoneの最初の一歩を踏み出せたので、少しずつやってこうと思います。
次はAzureをやろうかな!