Friday, 26 August 2011

Kinect SDK: Gesture Recognition Pt III

Introduction

In my previous blog post I discussed the development of a robust and extensible PostureRecognizer class. The class is used to recognize the start of a gesture. As a reminder, my high-level approach to the gesture recognition process is as follows:

  • Detect whether the user is moving or stationary.
  • Detect the start of a gesture (a posture).
  • Capture the gesture.
  • Detect the end of a gesture (a posture).
  • Identify the gesture.

This blog post will focus on capturing the gesture that occurs once the starting posture has been identified.

Implementation

The UI is unchanged from my previous post, with the exception of another Canvas being added to draw the captured gesture on.

The StreamManager constructor creates an instance of the PostureRecognizer class and registers an event handler for the PostureDetected event. It then registers the path where the gesture data will be saved, via a stream. An instance of the GestureRecognizer class is then created, and the name of the canvas to draw the captured gesture on is passed to the DrawGesture method.

        public StreamManager(Canvas canvas)
        {
            this.PostureRecognizer = new PostureRecognizer();
            this.PostureRecognizer.PostureDetected += 
               new Action<Posture>(OnPostureDetected);
            
            this.gestureCanvas = canvas;
            this.path = Path.Combine(Environment.CurrentDirectory, @"data\gestures.dat");
            this.stream = File.Open(this.path, FileMode.OpenOrCreate);
            this.gestureDetector = new GestureRecognizer(this.stream);
            this.gestureDetector.DrawGesture(this.gestureCanvas, Colors.Red);
        }

The StartGestureCapture and EndGestureCapture methods are shown below. The StartGestureCapture method invokes the StartGestureCapture method in the GestureRecognizer class, and if a gesture is already being captured, stops the capture. The EndGestureCapture method invokes the EndGestureCapture method in the GestureRecognizer class, and invokes the SaveGesture method in the GestureRecognizer class, provided that a gesture is being captured. 
        private void StartGestureCapture()
        {
            this.gestureDetector.GestureName = this.GestureName;
            if (this.gestureDetector.IsRecordingGesture)
            {
                this.gestureDetector.EndGestureCapture();
                return;
            }
            this.gestureDetector.StartGestureCapture();
        }
        private void EndGestureCapture()
        {
            if (this.gestureDetector.IsRecordingGesture)
            {
                this.gestureDetector.EndGestureCapture();
                this.gestureDetector.SaveGesture(stream);
            }
        }

The GetSkeletonStream method is as in the previous post, and invokes the TrackPostures method in the PostureRecognizer class. If a posture is identified, the RaisePostureDetected method is invoked which sets the CurrentPosture property to the identified posture, and invokes the PostureDetected event. The handler for the event is the OnPostureDetected method in the StreamManager class and was registered in the StreamManager constructor.
        public event Action<Posture> PostureDetected;
        private void RaisePostureDetected(Posture posture)
        {
            if (this.currentPosture != posture)
            {
                this.CurrentPosture = posture;
            }
            if (this.PostureDetected != null)
            {
                this.PostureDetected(posture);
            }
        }

The OnPostureDetected method is shown below. If a gesture is not being captured, it invokes the StartGestureCapture method, and stores the identified posture in the startPosture variable. If a gesture is being captured and the posture is different to the posture stored in the startPosture variable, the EndGestureCapture method is invoked. The detected Joints are then enumerated, and the Add method in the GestureRecognizer class is invoked if the gesture involves the right hand.
        private void OnPostureDetected(Posture posture)
        {
            if (this.gestureDetector.IsRecordingGesture == true)
            {
                if ((posture != Posture.None) &&
                    (posture != startPosture))
                {
                    this.EndGestureCapture();
                }
            }
            else
            {
                this.StartGestureCapture();
                this.startPosture = posture;
            }
            foreach (Joint joint in this.skeleton.Joints)
            {
                if (joint.Position.W < 0.9f || 
                    joint.TrackingState != JointTrackingState.Tracked)
                {
                    continue;
                }
                if (joint.ID == JointID.HandRight)
                {
                    this.gestureDetector.Add(joint.Position, 
                                             this.KinectRuntime.SkeletonEngine);
                }
            }
        }

The GestureData class is used to model the gesture data. It contains the a Position and the Time the Position was captured.
    public class GestureData
    {
        public Vector Position { get; set; }
        public DateTime Time { get; set; }
    }

The GestureRecognizer class is shown below. Gestures are stored in a collection named gestures, of type List<GestureData>. An instance of the TemplateRecognizer class is also created, which will use template matching to perform gesture identification. The bulk of the work in this class is performed in the Add method. It stores the passed in Vector as a GestureData object, and adds that to the gestures collection. It then draws the position as a small ellipse on the Canvas contained in the displayCanvas variable. Then, if there is more data in the gestures collection than the value of WindowSize, data is removed from both the start of the collection and the displayCanvas. The position data is then added to an instance of the Gesture class. The StartGestureCapture, EndGestureCapture, and SaveGesture methods largely invoke methods in the TemplateRecognizer class.
    public class GestureRecognizer
    {
        private Gesture gesture;
        private string gestureName; 
        private readonly List<GestureData> gestures = new List<GestureData>();
        private readonly TemplateRecognizer.TemplateRecognizer templateRecognizer;
        private readonly int windowSize;
        private DateTime lastGestureDate = DateTime.Now;
        private Canvas displayCanvas;
        private Colour displayColour;
        protected List<GestureData> Gestures
        {
            get { return this.gestures; }
        }
        public string GestureName
        {
            get { return this.gestureName; }
            set { this.gestureName = value; }
        }
        public bool IsRecordingGesture
        {
            get { return this.gesture != null; }
        }
        public TemplateRecognizer.TemplateRecognizer TemplateRecognizer
        {
            get { return this.templateRecognizer; }
        }
        public int WindowSize
        {
            get { return this.windowSize; }
        }
        public GestureRecognizer(Stream stream, int windowSize = 100)
        {
            this.templateRecognizer = new TemplateRecognizer.TemplateRecognizer(stream);
            this.windowSize = windowSize;
        }
        public GestureRecognizer(string gestureName, Stream stream, int windowSize = 100)
        {
            this.gestureName = gestureName;
            this.templateRecognizer = new TemplateRecognizer.TemplateRecognizer(stream);
            this.windowSize = windowSize;
        }
        public void Add(Vector position, SkeletonEngine engine)
        {
            GestureData newGesture = new GestureData
            {
                Position = position,
                Time = DateTime.Now
            };
            this.gestures.Add(newGesture);
            if (this.displayCanvas != null)
            {
                Ellipse ellipse = new Ellipse
                {
                    HorizontalAlignment = HorizontalAlignment.Left,
                    VerticalAlignment = VerticalAlignment.Top,
                    Height = 8,
                    Width = 8,
                    StrokeThickness = 8,
                    Stroke = new SolidColorBrush(this.displayColour),
                    StrokeLineJoin = PenLineJoin.Round
                };
                float x, y;
                engine.SkeletonToDepthImage(position, out x, out y);
                x = (float)(x * this.displayCanvas.ActualWidth);
                y = (float)(y * this.displayCanvas.ActualHeight);
                Canvas.SetLeft(ellipse, x - ellipse.Width / 2);
                Canvas.SetTop(ellipse, y - ellipse.Height / 2);
                this.displayCanvas.Children.Add(ellipse);
            }
            if (this.gestures.Count > this.WindowSize)
            {
                GestureData gestureToRemove = this.gestures[0];
                if (this.displayCanvas != null)
                {
                    this.displayCanvas.Children.RemoveAt(0);
                }
                this.gestures.Remove(gestureToRemove);
            }
            if (this.gesture != null)
            {
                Vector2 vector = new Vector2
                {
                    X = position.X,
                    Y = position.Y
                };
                this.gesture.Points.Add(vector);
            }
        }
        public void DrawGesture(Canvas canvas, Color colour)
        {
            this.displayCanvas = canvas;
            this.displayColour = colour;
        }
        public void StartGestureCapture()
        {
            this.gesture = new Gesture(this.WindowSize);
        }
        public void EndGestureCapture()
        {
            this.templateRecognizer.Add(gesture);
            this.gesture = null;
        }
        public void SaveGesture(Stream stream)
        {
            this.templateRecognizer.Save(stream);
        }
    }

The Gesture class stores all the positions of the captured gesture in a collection called points, of type List<Vector>.
    [Serializable]
    public class Gesture
    {
        private List<Vector> points;
        private readonly int samplesCount;
        public List<Vector> Points
        {
            get { return this.points; }
            set { this.points = value; }
        }
        public Gesture(int samplesCount)
        {
            this.samplesCount = samplesCount;
            this.points = new List<Vector>();
        }
    }

The TemplateRecognizer class will perform the bulk of the processing required for gesture identification, which will be covered in a future blog post. At the moment the class simply handles serialization and deserialization of the gesture data, by using a BinaryFormatter. The constructor deserializes the gesture data if it exists, while the Save method serializes the gesture data to a file.
    public class TemplateRecognizer
    {
        private readonly List<Gesture> gestures;
        public TemplateRecognizer(Stream stream)
        {
            if (stream == null || stream.Length == 0)
            {
                this.gestures = new List<Gesture>();
                return;
            }
            BinaryFormatter formatter = new BinaryFormatter();
            this.gestures = (List<Gesture>)formatter.Deserialize(stream);
        }
        public void Add(Gesture gesture)
        {
            this.gestures.Add(gesture);
        }
        public void Save(Stream stream)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, this.gestures);
        }
    }

The application is shown below. It determines whether the user is moving or stationary, and recognizes the start of a gesture – in this case, a posture. Once a posture is identified (currently hard coded to RightHello) the gesture points are stored in a collection and drawn on the canvas. When a different posture is identified the gesture capture stops.

gesture3<

Conclusion


The Kinect for Windows SDK beta from Microsoft Research is a starter kit for application developers. It allows access to the Kinect sensor, and experimentation with its features. My gesture recognition process now determines whether the user is moving or stationary, recognizes a posture and then captures the gesture that follows, before saving it to a file. The next step of the process will be to scale the gesture data to a reference, before saving it to a file.

Thursday, 11 August 2011

Kinect SDK: Gesture Recognition Pt II

Introduction

In my previous blog post I outlined the approach I’d be taking to gesture recognition, and discussed how I’d detect whether the user is moving or stationary. As a reminder, my high-level approach to the gesture recognition process is as follows:

  • Detect whether the user is moving or stationary.
  • Detect the start of a gesture (a posture).
  • Capture the gesture.
  • Detect the end of a gesture (a posture).
  • Identify the gesture.

This blog post will focus on detecting the start of a gesture – in this case, a posture. I’d previously written a PoseRecognition class which I’ve since refactored to be more robust and extensible. The refactored PostureRecognizer class recognises three poses – the left hand waving hello, the right hand waving hello, and both hands being clasped together. New poses can easily be added to the class.

Implementation

The XAML for the UI is shown below. An Image shows the video stream from the sensor, with a Canvas being used to overlay the skeleton of the user on the video stream. A Slider controls the elevation of the sensor via bindings to a Camera class, thus eliminating some of the references to the UI from the code-behind file. TextBlocks are bound to IsSkeletonTracking, IsStable, and PostureRecognizer.CurrentPosture, to show if the skeleton is being tracked, if it is stable, and the current posture, respectively.

<Window x:Class="KinectDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:conv="clr-namespace:KinectDemo.Converters"
        Title="Gesture Recognition" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
        Loaded="Window_Loaded" Closed="Window_Closed">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="300" />
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0">
            <TextBlock HorizontalAlignment="Center"
                       Text="Tracking..." />
            <Viewbox>
                <Grid ClipToBounds="True">
                    <Image Height="300"
                           Margin="10,0,10,10"
                           Source="{Binding ColourBitmap}"
                           Width="400" />
                    <Canvas x:Name="skeletonCanvas" />
                </Grid>
            </Viewbox>
        </StackPanel>
        <StackPanel Grid.Column="1">
            <GroupBox Header="Motor Control"
                      Height="100"
                      VerticalAlignment="Top"
                      Width="290">
                    <Slider x:Name="elevation"
                            AutoToolTipPlacement="BottomRight"
                            IsSnapToTickEnabled="True"
                            LargeChange="10"
                            Maximum="{Binding ElevationMaximum}"
                            Minimum="{Binding ElevationMinimum}"
                            HorizontalAlignment="Center"
                            Orientation="Vertical"
                            SmallChange="3"
                            TickFrequency="3"
                            Value="{Binding Path=ElevationAngle, Mode=TwoWay}" />
            </GroupBox>
            <GroupBox Header="Information"
                      Height="150"
                      VerticalAlignment="Top"
                      Width="290">
                <GroupBox.Resources>
                    <conv:BooleanToStringConverter x:Key="boolStr" />
                    <conv:PostureToStringConverter x:Key="postureStr" />
                </GroupBox.Resources>
                <StackPanel>
                    <StackPanel Orientation="Horizontal" 
                                Margin="10">
                        <TextBlock Text="Frame rate: " />
                        <TextBlock Text="{Binding FramesPerSecond}"
                                   VerticalAlignment="Top"
                                   Width="50" />
                    </StackPanel>
                    <StackPanel Margin="10,0,0,0" 
                                Orientation="Horizontal">
                        <TextBlock Text="Tracking skeleton: " />
                        <TextBlock Text="{Binding IsSkeletonTracking, 
                                          Converter={StaticResource boolStr}}"
                                   VerticalAlignment="Top"
                                   Width="30" />
                    </StackPanel>
                    <StackPanel Orientation="Horizontal"
                                Margin="10">
                        <TextBlock Text="Stable: " />
                        <TextBlock Text="{Binding Path=IsStable, 
                                                  Converter={StaticResource boolStr}}"
                                   VerticalAlignment="Top"
                                   Width="30" />
                    </StackPanel>
                    <StackPanel Orientation="Horizontal" 
                                Margin="10,0,0,0">
                        <TextBlock Text="Current posture: " />
                        <TextBlock Text="{Binding Path=PostureRecognizer.CurrentPosture, 
                                                  Converter={StaticResource postureStr}}"
                                   VerticalAlignment="Top"
                                   Width="160" />
                    </StackPanel>
                </StackPanel>
            </GroupBox>
        </StackPanel>
    </Grid>
</Window>

The StreamManager class (inside my KinectManager library) contains a property to access to the PostureRecognizer class, which is initialized in the StreamManager constructor.
        public PostureRecognizer PostureRecognizer { get; private set; }

The SkeletonFrameReady event handler hooks into a method in the StreamManager class called GetSkeletonStream. It retrieves a frame of skeleton data and processes it as follows: e.SkeletonFrame.Skeletons is an array of SkeletonData structures, each of which contains the data for a single skeleton. If the TrackingState field of the SkeletonData structure indicates that the skeleton is being tracked, the IsSkeletonTracking property is set to true, and the position of the skeleton is added to a collection in the BaryCenter class. Then the IsStable method of the BaryCenter class is invoked to determine if the user is stable or not. If the user is stable, the TrackPostures method from the PostureRecognizer class is invoked. Finally, the IsStable property is updated.
        public void GetSkeletonStream(SkeletonFrameReadyEventArgs e)
        {
            bool stable = false;
            foreach (var skeleton in e.SkeletonFrame.Skeletons)
            {
                if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
                {
                    this.IsSkeletonTracking = true;
                    this.baryCenter.Add(skeleton.Position, skeleton.TrackingID);
                    stable = this.baryCenter.IsStable(skeleton.TrackingID) ? true : false;
                    if (stable)
                    {
                        this.PostureRecognizer.TrackPostures(skeleton);
                    }
                }
            }
            this.IsStable = stable;
        }

The Posture enumeration, used by the PostureRecognizer class, is shown below.
    public enum Posture
    {
        None,
        HandsClasped,
        LeftHello,
        RightHello
    }

The PostureRecognizer class defines a property called CurrentPosture, along with a backing store variable, and two constants.
        private const float Epsilon = 0.1f;
        private const float MaxRange = 0.25f;
        private Posture currentPosture = Posture.None;
        public Posture CurrentPosture
        {
            get { return this.currentPosture; }
            set
            {
                this.currentPosture = value;
                this.OnPropertyChanged("CurrentPosture");
            }     
        }

The PostureRecognizer.TrackPostures method is shown below. It enumerates the Joints in the skeleton and gets the positions of the head, left and right hands and stores them as Vectors. It then calls two helper methods to determine if the user’s hands are clapsed or if the user is waving hello with their left or right hand. If a posture is identified, the RaisePostureDetected method is invoked. If a posture is not identified, the CurrentPosture property is set to Posture.None.
        public void TrackPostures(SkeletonData skeleton)
        {
            if (skeleton.TrackingState != SkeletonTrackingState.Tracked)
            {
                return;
            }
            Vector? headPosition = null;
            Vector? leftHandPosition = null;
            Vector? rightHandPosition = null;
            foreach (Joint joint in skeleton.Joints)
            {
                if (joint.Position.W < 0.8f || 
                    joint.TrackingState != JointTrackingState.Tracked)
                {
                    continue;
                }
                switch (joint.ID)
                {
                    case JointID.Head:
                        headPosition = joint.Position;
                        break;
                    case JointID.HandLeft:
                        leftHandPosition = joint.Position;
                        break;
                    case JointID.HandRight:
                        rightHandPosition = joint.Position;
                        break;
                }
            }
            if (this.AreHandsClasped(rightHandPosition, leftHandPosition))
            {
                this.RaisePostureDetected(Posture.HandsClasped);
                return;
            }
            if (this.IsHello(headPosition, leftHandPosition))
            {
                this.RaisePostureDetected(Posture.LeftHello);
                return;
            }
            if (this.IsHello(headPosition, rightHandPosition))
            {
                this.RaisePostureDetected(Posture.RightHello);
                return;
            }
            this.CurrentPosture = Posture.None;
        }

The IsHello and AreHandsClasped helper methods are shown below. They both examine the Vector parameters to see if they have values, and if they do, perform simple arithmetic to determine whether the hello posture has been made, or whether the user’s hands are clasped together. In particular, the AreHandsClasped method uses the Vector extension methods I developed in my previous blog post.
        private bool IsHello(Vector? headPosition, Vector? handPosition)
        {
            if (!headPosition.HasValue || !handPosition.HasValue)
                return false;
            if (Math.Abs(handPosition.Value.X - headPosition.Value.X) < MaxRange)
                return false;
            if (Math.Abs(handPosition.Value.Y - headPosition.Value.Y) > MaxRange)
                return false;
            if (Math.Abs(handPosition.Value.Z - headPosition.Value.Z) > MaxRange)
                return false;
            return true;
        }
        private bool AreHandsClasped(Vector? leftHandPosition, Vector? rightHandPosition)
        {
            if (!leftHandPosition.HasValue || !rightHandPosition.HasValue)
                return false;
            Vector result = leftHandPosition.Value.Subtract(rightHandPosition.Value);
            float distance = result.Length();
            if (distance > Epsilon)
                return false;
            return true;
        }

The RaisePostureDetected method is shown below. It simply updates the CurrentPosture property to the identified posture, providing that the two are not identical. The method name could be considered curious, as the method is not raising an event. However, the point of recognizing a posture is that it will be the start or end point of a gesture. Therefore, this method will eventually fire a PostureDetected event that will capture a gesture if a start posture has been identified, or stop the gesture capture if an end posture has been identified. I will return to this topic in my next blog post.
        private void RaisePostureDetected(Posture posture)
        {
            if (this.currentPosture != posture)
            {
                this.CurrentPosture = posture;
            }
        }

The UI uses the PostureToStringConverter class (code not shown) to convert the Posture enumeration to it’s string representation for display on the UI.

The application is shown below. It uses skeleton tracking to determine whether the user is moving or stationary, and identifies the user’s pose if they are stationary.

posturerecognizer

Conclusion


The Kinect for Windows SDK beta from Microsoft Research is a starter kit for application developers. It allows access to the Kinect sensor, and experimentation with its features. My gesture recognition process now determines whether the user is moving or stationary, and recognizes the start of a gesture – in this case, a posture. The next step in the process will be to capture the gesture that occurs between the starting posture and the ending posture.

Monday, 8 August 2011

Surface 2.0 Hands On Labs

When Microsoft Surface 2.0 was released, Microsoft also included a set of hands on labs to help developers get started developing applications for the Surface platform. The labs are meant to augment Designing and Developing Microsoft Surface Applications online course.


There are 11 labs available when you download the Microsoft Surface 2.0 Hands on Labs.zip file. Each lab covers a distinct element of developing for Microsoft Surface 2.0:



  1. Introduction Lab

  2. ScatterView lab

  3. Vision Based Object Recognition lab

  4. Using the Microsoft Surface Input Simulator lab

  5. Drag and Drop APIs lab

  6. Using the Library Container Controls lab

  7. Windows 7 Touch Enabled Applications lab

  8. Creating a 360 Degree User Interface lab

  9. Stylizing a Surface Application lab

  10. Developing for Direct Touch lab

  11. Developing for Multiple Users and Objects lab

Each lab contains a Lab document and lab solution files. Some, but not all of the labs also contain lab starter files. You can download the hands on labs here.


I ran through each of these labs with an eye to call out anything exceptional in them that can provide you pointers in how to develop Surface 2.0 applications. The rest of today’s post will speak to the Introduction Lab, which I’ll simply summarize.


This lab walks you through how to create a Surface application solution in Visual Studio 2010, then has you review the C# code that is generated by the Surface 2.0 Visual Studio templates. The third exercise shows you how to edit the XAML and C# files in the solution, and the fourth exercise introduces some of the commonly used Surface controls.


Though you can use controls as you see fit in your applications, the Surface design guidance recommends that you only use controls as necessary. You should never use controls in place of people directly touching your applications’ content. For example, if you include images in your application, do not require people to touch a SurfaceButton control instead of directly touching an image displayed by the application.


In my next post I’ll go into Lab 2, which is about the ScatterView control.