このエントリはKINECT SDK Advent Calendar 2011 : ATNDの12月21日分です!!
Advent Calendarでの、僕の全プロジェクトはこちらです
今日は、Windows Phone ハッカソンにいってきました。
Windows Phoneのアプリはほとんど初めてですが、僕ができることといったらKINECTが中心なので、KINECTとWindows 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 ); }