ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

KINECT SDK Beta2 で、挿抜状態に応じたアプリの動作をする #kinectsdk_ac

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


KINECT SDK Beta2 に追加された、KINECTの挿抜状態を通知するイベントを試してみました。
複数KINECTや商用KINECTアプリを、実用的に利用するためには必須の機能だと思います。
動画も載せてみました。KINECTを抜いたり刺したりしています。


見た目の解説

一つのKINECTにつき一つのImageで、4つのKINECTまで表示できます。

<Window x:Class="CameraImageWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="577" Width="669">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="149*" />
            <RowDefinition Height="389*" />
        </Grid.RowDefinitions>
        <TextBox Name="kinectCount" Margin="0,0,0,97" FontSize="18" Text="Text" TextAlignment="Center" FontWeight="Bold" FontStretch="Normal" TextWrapping="NoWrap" VerticalContentAlignment="Center" />
        <Image Name="image1" Height="240" Width="320" Margin="0,50,332,248" Grid.RowSpan="2" />
        <Image Height="240" Margin="326,50,6,248" Name="image2" Width="320" Grid.RowSpan="2" />
        <Image Height="240" Margin="0,149,332,0" Name="image3" Width="320" Grid.Row="1" />
        <Image Height="240" Margin="326,149,6,0" Name="image4" Width="320" Grid.Row="1" />
    </Grid>
</Window>

中身の解説

いろいろ試しながら、こんな感じになりました。

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using Microsoft.Research.Kinect.Nui;
using System.Windows.Controls;

namespace CameraImageWPF
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            images = new Image[] {
                image1, image2, image3, image4
            };

            // 挿抜状態の変化を通知する
            Runtime.Kinects.StatusChanged +=
                new EventHandler<StatusChangedEventArgs>( Kinects_StatusChanged );

            ShowKinectCount();

            // 最初に刺されている分を初期化する
            foreach ( Runtime kinect in Runtime.Kinects ) {
                InitKinect( kinect );
            }
        }

        private void InitKinect( Runtime kinect )
        {
            kinect.Initialize( RuntimeOptions.UseColor );
            kinect.VideoStream.Open( ImageStreamType.Video, 2,
                ImageResolution.Resolution640x480, ImageType.Color );
            kinect.VideoFrameReady +=
                    new EventHandler<ImageFrameReadyEventArgs>( kinect_VideoFrameReady );
        }

        // KINECTの状態が変わった
        void Kinects_StatusChanged( object sender, StatusChangedEventArgs e )
        {
            // KINECTの数を表示
            ShowKinectCount();

            // KINECTが刺された
            if ( e.Status == KinectStatus.Connected ) {
                InitKinect( e.KinectRuntime );
            }
            // KINECTが抜かれた
            else if ( e.Status == KinectStatus.Disconnected ) {
                // インスタンス数が切りつめられるので、インスタンス数の最後のImageをnullにする
                // 一度に何個も抜かれるかもなので、最後までnullにしちゃう
                for ( int i = Runtime.Kinects.Count; i < images.Length; i++ ) {
                    images[i].Source = null;
                }

                // 抜かれたKINECTのイベント削除と、インスタンスの破棄
                e.KinectRuntime.VideoFrameReady -=
                    new EventHandler<ImageFrameReadyEventArgs>( kinect_VideoFrameReady );
                e.KinectRuntime.Uninitialize();
            }
        }

        private void ShowKinectCount()
        {
            kinectCount.Text = Runtime.Kinects.Count + "台のKINECTが有効です";
        }

        void kinect_VideoFrameReady( object sender, ImageFrameReadyEventArgs e )
        {
            // 抜かれた瞬間のKINECTは、InstanceIndex が 0 になる
            Runtime kinect = sender as Runtime;
            if ( (kinect != null) && (kinect.InstanceIndex >= 0) ) {
                PlanarImage srouce = e.ImageFrame.Image;
                Image dest = images[kinect.InstanceIndex];
                dest.Source = BitmapSource.Create( srouce.Width, srouce.Height, 96, 96,
                    PixelFormats.Bgr32, null, srouce.Bits, srouce.Width * srouce.BytesPerPixel );
            }
        }

        Image[] images;
    }
}
挿抜状態のイベントを登録する

Runtime.Kinects.StatusChangedイベントで、KINECTの挿抜状態が変わったら、イベントが来るようになりました。

// 挿抜状態の変化を通知する
Runtime.Kinects.StatusChanged +=
    new EventHandler<StatusChangedEventArgs>( Kinects_StatusChanged );
起動時のKINECTの初期化

Runtime.KinectsがRuntimeのコレクションになっているので、最初に接続されているKINECTをすべて初期化します

// 最初に刺されている分を初期化する
foreach ( Runtime kinect in Runtime.Kinects ) {
    InitKinect( kinect );
}
KINECTの挿抜状態の変更イベント

今回のキモです。

  • KINECTが刺された場合
    • KINECTの初期化を行います。
  • KINECTが抜かれた場合
    • すでにコレクションからは、削除されたKINECTはいないので、現在のインスタンス数から後ろのImageを無効にする(複数KINECT同時抜きに対応するため)
    • RGBカメラの更新イベントの削除
    • KINECTの解放処理
// KINECTの状態が変わった
void Kinects_StatusChanged( object sender, StatusChangedEventArgs e )
{
    // KINECTの数を表示
    ShowKinectCount();

    // KINECTが刺された
    if ( e.Status == KinectStatus.Connected ) {
        InitKinect( e.KinectRuntime );
    }
    // KINECTが抜かれた
    else if ( e.Status == KinectStatus.Disconnected ) {
        // インスタンス数が切りつめられるので、インスタンス数の最後のImageをnullにする
        // 一度に何個も抜かれるかもなので、最後までnullにしちゃう
        for ( int i = Runtime.Kinects.Count; i < images.Length; i++ ) {
            images[i].Source = null;
        }

        // 抜かれたKINECTのイベント削除と、インスタンスの破棄
        e.KinectRuntime.VideoFrameReady -=
            new EventHandler<ImageFrameReadyEventArgs>( kinect_VideoFrameReady );
        e.KinectRuntime.Uninitialize();
    }
}
カメラ画像の更新処理

少し変わっています。
ImageをInstanceIndexで取得しますが、KINECTが抜かれたのと、画像の更新が重なった場合、InstanceIndexが-1になります。そのため、今回は、インスタンスのnullとInstanceIndexの-1をチェックしています。

void kinect_VideoFrameReady( object sender, ImageFrameReadyEventArgs e )
{
    // 抜かれた瞬間のKINECTは、InstanceIndex が 0 になる
    Runtime kinect = sender as Runtime;
    if ( (kinect != null) && (kinect.InstanceIndex >= 0) ) {
        PlanarImage srouce = e.ImageFrame.Image;
        Image dest = images[kinect.InstanceIndex];
        dest.Source = BitmapSource.Create( srouce.Width, srouce.Height, 96, 96,
            PixelFormats.Bgr32, null, srouce.Bits, srouce.Width * srouce.BytesPerPixel );
    }
}

以上で、複数KINECTや、KINECT状態による処理の変更ができるようになりました。
KINECTが刺されていなくても、アプリが動作可能になるので、OpenNIに対する優位点になりそうですね。