Kinect beta2 clean wpf code and getting started

posted in: Uncategorized | 0

Since the update to Beta2 of the Kinect for Windows SDK all our Kinect samples are out of date so I thought I’d update them to the latest and greatest and post them all here.

kinectcode1

To begin with I want a nice clean framework that is both easy for the first time Kinect coder to understand but also makes use of the new abilities of the Kinect SDK. What I’ve come up with achieves the following 3 goals:

  1. Support all combinations of Kinects be attached/detached/powered off/multiple.
  2. Exposes a single Kinect Runtime as a property called Nui (It is null if there isn’t a Kinect attached).
  3. Obvious location to wire up event model (and remove events if Kinect is removed)

This is meant to be a good place to start with simple examples with all the code clear in the one file.

    public partial class MainWindow : Window
    {
        private Runtime nui;
        public Runtime Nui
        {
            get { return nui; }
            set
            {
                if (nui != null)
                {
                    //dispose all events and unintialise

                    nui.Uninitialize();
                }
                nui = value;

                if (nui != null)
                {
                    //wire up events and initialise

                }

            }
        }

        public MainWindow()
        {
            InitializeComponent();
            Closed += WindowClosed;
            Loaded += WindowLoaded;
        }

        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            Runtime.Kinects.StatusChanged += KinectsStatusChanged;
            Nui = Runtime.Kinects.Where(k => k.Status == KinectStatus.Connected).FirstOrDefault();
        }

        void KinectsStatusChanged(object sender, StatusChangedEventArgs e)
        {
            //ignore the case where a 2nd kinect is connected while we are using the 1st kinect.
            if (Nui != null && Nui.InstanceName != e.KinectRuntime.InstanceName) return;

            Nui = Runtime.Kinects.Where(k => k.Status == KinectStatus.Connected).FirstOrDefault();
        }

        void WindowClosed(object sender, EventArgs e)
        {
            Runtime.Kinects.StatusChanged -= KinectsStatusChanged;
            Nui = null;
        }

    }

Here is our classic first demo from our talks where we wire up the video, tilt slider, depth and skeleton using this new framework.

WPF XAML:

<Window x:Class="NuiExample1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:Skeleton="clr-namespace:SoulSolutions.Kinect.Controls.Skeleton;assembly=SoulSolutions.Kinect.Controls.Skeleton" 
        Title="Kinect Nui Example 1" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Image x:Name="colourImage" />
        <Image x:Name="depthImage" Grid.Column="1" />
        <Skeleton:SkeletonControl x:Name="skeleton" Grid.Row="1" />
        <StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal">
            <Slider x:Name="tiltSlider" Orientation="Vertical" Margin="40" Maximum="27" Minimum="-27" SmallChange="1" TickPlacement="TopLeft" Interval="1" IsSnapToTickEnabled="True" />
            <Button x:Name="tiltButton" Content="Set Tilt" Height="30" Width="200" Click="TiltButtonClick" />
        </StackPanel>
    </Grid>
</Window>

C#:

using System;
using System.Linq;
using System.Windows;
using Coding4Fun.Kinect.Wpf;
using Microsoft.Research.Kinect.Nui;

namespace NuiExample1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private InteropBitmapHelper imageHelper;

        private Runtime nui;
        public Runtime Nui
        {
            get { return nui; }
            set
            {
                if (nui != null)
                {
                    //dispose all events and unintialise
                    nui.VideoFrameReady -= NuiVideoFrameReady;
                    nui.DepthFrameReady -= NuiDepthFrameReady;
                    nui.SkeletonFrameReady -= NuiSkeletonFrameReady;

                    nui.Uninitialize();
                }
                nui = value;

                if (nui != null)
                {
                    //wire up events and initialise

                    //step 1 events
                    nui.VideoFrameReady += NuiVideoFrameReady;
                    nui.DepthFrameReady += NuiDepthFrameReady;
                    nui.SkeletonFrameReady += NuiSkeletonFrameReady;

                    //step 2 initialise
                    nui.Initialize(RuntimeOptions.UseColor | RuntimeOptions.UseDepth | RuntimeOptions.UseSkeletalTracking);

                    //step 3 open streams / settings
                    nui.VideoStream.Open(ImageStreamType.Video, 2, ImageResolution.Resolution1280x1024, ImageType.Color);
                    nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution640x480, ImageType.Depth);

                    //Must set to true and set after call to Initialize
                    nui.SkeletonEngine.TransformSmooth = true;

                    //Use to transform and reduce jitter
                    var parameters = new TransformSmoothParameters
                                         {
                                             Smoothing = 0.75f,
                                             Correction = 0.0f,
                                             Prediction = 0.0f,
                                             JitterRadius = 0.05f,
                                             MaxDeviationRadius = 0.04f
                                         };

                    nui.SkeletonEngine.SmoothParameters = parameters;

                }

            }
        }

        void NuiSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame allSkeletons = e.SkeletonFrame;

            if (e.SkeletonFrame == null) return;

            //get the first tracked skeleton
            var skeletonData = (from s in allSkeletons.Skeletons
                                where s.TrackingState == SkeletonTrackingState.Tracked
                                select s).FirstOrDefault();

            if (skeletonData == null) return;

            skeleton.SkeletonData = skeletonData;

        }

        void NuiDepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            //Use Coding4Fun extension method on ImageFrame class for Depth
            depthImage.Source = e.ImageFrame.ToBitmapSource();
        }

        void NuiVideoFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            PlanarImage planarImage = e.ImageFrame.Image;

            //An interopBitmap is a WPF construct that enables resetting the Bits of the image.
            //This is more efficient than doing a BitmapSource.Create call every frame.
            if (imageHelper == null)
            {
                imageHelper = new InteropBitmapHelper(planarImage.Width, planarImage.Height, planarImage.Bits);
                colourImage.Source = imageHelper.InteropBitmap;
            }
            else
            {
                imageHelper.UpdateBits(planarImage.Bits);
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            Closed += WindowClosed;
            Loaded += WindowLoaded;
        }

        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            Runtime.Kinects.StatusChanged += KinectsStatusChanged;
            Nui = Runtime.Kinects.Where(k => k.Status == KinectStatus.Connected).FirstOrDefault();
        }

        void KinectsStatusChanged(object sender, StatusChangedEventArgs e)
        {
            //ignore the case where a 2nd kinect is connected while we are using the 1st kinect.
            if (Nui != null && Nui.InstanceName != e.KinectRuntime.InstanceName) return;

            Nui = Runtime.Kinects.Where(k => k.Status == KinectStatus.Connected).FirstOrDefault();
        }

        void WindowClosed(object sender, EventArgs e)
        {
            Runtime.Kinects.StatusChanged -= KinectsStatusChanged;
            Nui = null;
        }

        private void TiltButtonClick(object sender, RoutedEventArgs e)
        {
            //Set angle to slider1 value
            Nui.NuiCamera.ElevationAngle = (int) tiltSlider.Value;
        }
    }
}

Download this full sample here.

What do you think? What kinect code are you looking for? You can catch me on http://twitter.com/soulsolutions