Kinect for Windows SDK Programming Guide
上QQ阅读APP看书,第一时间看更新

Kinect Info Box – your first Kinect application

Let's start developing our first application. We will call this application Kinect Info Box. To start development, the first thing we are going to build is an application that reads the device information from a Kinect sensor. The Info Box application is self-explanatory. The following screenshot shows a running state of the application, which shows the basic device information such as Connection ID, Device ID, and Status. The Info Box also shows the currently active stream channel and the sensor angle. You can also start or stop the sensor using the buttons at the bottom of the window.

Kinect Info Box – your first Kinect application

We will build this application in a step-by-step manner and explore the different APIs used along with the basic error-handling mechanisms that need to be taken care of while building a Kinect application.

Creating a new Visual Studio project

  1. Start a new instance of Visual Studio.
  2. Create a new project by navigating to File | New Project. This will open the New Project window.
  3. Choose Visual C# from the installed templates and select the WPF Application, as shown in the following screenshot:
    Creating a new Visual Studio project

    Note

    You can select the Windows Forms Application template instead of WPF Application template. In this book, all the sample applications will be developed using WPF. The underlying Kinect SDK API is the same for both Windows Forms Application and WPF Application.

  4. Give it the name KinectInfoBox, and then click on OK to create a new Visual studio project.

Adding the Kinect libraries

The next thing you need to do is add the Kinect libraries to the Visual Studio project using the following steps:

  1. From the Solution Explorer window, right-click on the References folder and select Add Reference…, as shown in the following screenshot:
    Adding the Kinect libraries
  2. This will launch the Add Reference window. Then, search for the Microsoft.Kinect.dll file within the Kinect SDK folder. Select Microsoft.Kinect.dll and click on OK, as shown in the following screenshot:
    Adding the Kinect libraries
  3. This will add Microsoft.Kinect.dll as a reference assembly into your project, which you can see within the References folder, as shown in the following screenshot:
    Adding the Kinect libraries

We now have a default project ready with the Kinect library added. The next thing to do is to access the library APIs for our application.

Getting the Kinect sensor

The KinectSensor class is provided as part of the SDK libraries, which are responsible for most of the operation with the Kinect sensor. You need to create an instance of the KinectSensor class and then use this to control the Kinect sensor and read the sensor information.

While writing code, the first thing you need to do is to add the using directives, which will enable the program to use the Kinect SDK reference. Open the MainWindow.xaml.cs file from Solution Explorer, and then add the following line of code at the top of your program with the other using statement:

using Microsoft.Kinect;

The Kinect sensor

In a Kinect application, each Kinect device represents an instance of the Microsoft.Kinect.KinectSensor class. This represents the complete runtime pipeline for the sensor during the life span of the application.

The following diagram illustrates the usage of Kinect sensors over a life span of an application:

The Kinect sensor

Defining the Kinect sensor

Defining the sensor objects is as simple as defining other class objects. The defined object will come into action only when you initialize for a specific Kinect operation, such as color image streaming and depth image streaming. We can define a Kinect sensor object using the following code snippet:

public partial class MainWindow : Window
{
 KinectSensor sensor;
    // remaining code goes here
}

The sensor objects need a reference to the Kinect device that is connected with the system and can be used by your application. You can't instantiate the KinectSensor object as it does not have a public constructor. Instead, the SDK creates KinectSensor objects when it detects a Kinect device attached to your system.

The collection of sensors

The KinectSensor class has a static property of the KinectSensorCollection type, named KinectSensors, which consists of the collection of sensors that are connected with your system. The KinectSensor.KinectSensors collection returns the collection of Kinect devices connected with your system. KinectSensorCollection is a read-only collection of the KinectSensor type. Each KinectSensorCollection class consists of an indexer of the KinectSensor object and an event named StatusChanged. The following code block shows the definition of the KinectSensorCollection class:

public sealed class KinectSensorCollection : ReadOnlyCollection<KinectSensor>, IDisposable
    {
        public KinectSensor this[string instanceId] { get; }
        public event EventHandler<StatusChangedEventArgs>StatusChanged;
        public void Dispose();
    }

As this returns a collection of Kinect devices, you can use any index of that collection to get the reference of a particular sensor. As an example, if you have two devices connected with your system, KinectSensor.KinectSensors[0] will represent the first Kinect device and KinectSensor.KinectSensors[1] will represent the second Kinect device.

Consider that you have connected a device, so you will get a reference of this connected sensor as shown in this code:

this.sensor = KinectSensor.KinectSensors[0];

Once you have the sensor object for the Kinect device, invoke the KinectSensor.Start() method to start the sensor.

Tip

Check whether any device is connected before you start the sensor

It is always good practice to first check whether there is any sensor connected with the system before carrying out any operation with the KinectSensor objects. KinectSensors holds the reference to all connected sensors, and as this is a collection, it has a Count property. You can use KinectSensors.Count to check the number of devices.

int deviceCount = KinectSensor.KinectSensors.Count;
if (deviceCount > 0)
{
    this.sensor = KinectSensor.KinectSensors[0];
    // Rest operation here 
}
else
{
    // No sensor connected. Take appropriate action
}

Starting up Kinect

Starting up Kinect means initializing the Kinect sensors with different data streams. The sensor has to first start before reading from itself. You can write a method, such as the following one, that handles the sensor start:

private void StartSensor()
{
    if (this.sensor != null && !this.sensor.IsRunning)
    {
 this.sensor.Start();
    }
}

Note

The KinectSensor class has a property named IsRunning, which returns true if the sensor is running. You can take advantage of this property to check whether the sensor is running or not. When you call the sensor.Start() method, the SDK checks the current sensor status internally before it actually starts the sensor. So, if you forgot to check sensor.IsRunning, the SDK will take care of this automatically; however, checking it well in advance will save an unnecessary call. Similarly, calling the Start() method multiple times won't cause any problems as the SDK will start the sensor only if it is not running.

In this application, we are instantiating the sensor in the Window_Loaded event, as shown in the following code snippet. This will start the sensor when the application starts; however, you can take the connected sensor as a reference and can start it anywhere in your application, based on your requirements.

protected void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    if (KinectSensor.KinectSensors.Count > 0)
    {
        this.sensor = KinectSensor.KinectSensors[0];
        this.StartSensor();
    }
    else
    {
        MessageBox.Show("No device is connected with system!");
        this.Close();
    }
}

In the preceding code block, you can see that, first of all, we check the count of the connected sensors and proceed if the number of the connected devices is greater than 0, otherwise we prompt a message to the user and close the application. This is always a good practice. Once we have connected the sensor, we take the sensor as a reference and start the sensor by calling the StartSensor method that we defined earlier.

Inside the sensor.Start() method

Before taking any further action, the sensor.Start() method first checks for the status of the sensor(with the Status term). The initialization of the sensor happens only if the sensor is connected. If the sensor is not connected and you are trying to start the sensor, it will throw an InvalidOperationException object with the KinectNotReady message.

If the sensor is connected, the Start() method initializes the sensor and then tries to open the color, depth, and skeleton data stream channels with the default values if they are set to Enable.

Note

Initialization of different stream channels does not mean to start sending data from the sensor. You have to explicitly enable the channel and register an appropriate event to feed data from the sensor.

The initialization of the sensor happens with a set of enumeration flags, which provides the type of channel that needs to be initialized. The following table lists the types of initialization options and their descriptions:

The initialization of the stream channel is achieved by setting the options internally. From a developer's perspective, you just need to call the Start() method, and the rest will be taken care of by the SDK.

Enabling the data streams

The KinectSensor class provides specific events that help to enable and to subscribe image stream, depth stream, and skeleton stream data. We will be dealing with details of each and every data stream in subsequent chapters. As of now, we will learn how to enable stream data in our application.

Within the Kinect for Windows SDK, the color, depth, and skeleton data streams are represented by the types of ColorImageStream, DepthImageStream, and SkeletonStream methods respectively. Each of them has an Enable method that opens up the stream pipeline. For example, to enable the ColorStream type, you need to write the following line of code:

this.sensor.ColorStream.Enable();

You can explicitly enable the stream only after the StartSensor() method, as shown in the following code snippet. In our Info Box application, we did the same by enabling the color, depth, and skeleton data streams after starting the sensor.

if (KinectSensor.KinectSensors.Count > 0)
{
    this.sensor = KinectSensor.KinectSensors[0];
    this.StartSensor();
 this.sensor.ColorStream.Enable();
 this.sensor.DepthStream.Enable();
 this.sensor.SkeletonStream.Enable();
}

Identifying the Kinect sensor

Each Kinect sensor can be identified by the DeviceConnectionId property of the KinectSensor object. The connection ID of this device returns the Device Instance Path of the USB port on which it is connected.

To have a look at it, open Control Panel and navigate to Device Manager. Then, change the view of Device Manager to Device by Connection. Select Generic USB Hub for the Kinect for Windows Device node and open the Properties menu. There you will find the same device ID as you have seen previously. See the following screenshot:

Identifying the Kinect sensor

Initializing the sensor using device connection ID

If you know the device connection ID for your Kinect sensor, you can use the same for instantiating the senor instead of using an index. This will make sure that you are initializing the correct device if there are multiple devices and if you are not sure about the device index. In the following code snippet we have used the previously received unique instance ID:

KinectSensor  sensor = KinectSensor.KinectSensors [@" USB\VID_045E&PID_02C2\5&192B533&0&5"];
This.sensor.Start()

As this Connection ID returns the USB hub device instance path ID, it will be changed once you plug the device into a different USB hub.

If you are aware of your device ID, you can always refer to the device using the ID as stated in the following code block:

KinectSensor sensor = KinectSensor.KinectSensors[@"USB\VID_045E&PID_02AE\A00362A01385118A"];
int position = 0;
var collection = KinectSensor.KinectSensors.Where(item => item.DeviceConnectionId == sensor.DeviceConnectionId);
var indexCollection = from item in collection
let row = position++
select new { SensorObject = item, SensorIndex = row };

If it's a single sensor, the index should be 0, but this code block will return the actual position from the list of sensors as well. Currently, we have tested the code with one sensor, so we have the sensor index value 0 as shown in the following screenshot:

Initializing the sensor using device connection ID

Tip

The KinectSensor object has another property, named UniqueKinectId, that returns a unique ID for the Kinect sensor. You can use this ID to identify the sensor uniquely. Also you can use the same to map the index of the sensor.

Stopping the Kinect sensor

You should call the sensor.Stop() method of the KinectSensor class when the sensor finishes its work. This will shut down the instance of the Kinect sensor. You can write a method such as the following that deals with stopping the sensor.

private void StopSensor()
{
    if (this.sensor != null && this.sensor.IsRunning)
    {
 this.sensor.Stop();
    }
}

Note

Like sensor.Start(), the Stop() method also internally checks for the sensor's running state. The call goes to stop the sensor only if it is running.

The Stop() method does the clean-up operation

It's good practice to call Stop() once you are done with the Kinect sensor. This is because the Stop() method does some clean-up work internally before it actually shuts down the device. It completes the following tasks:

  • It stops the depth, color, and skeleton data streams individually by calling the Close method if they are open
  • It checks for the open Kinect audio source and stops it if it's running
  • It kills all the threads that were spawned by events generated by the Kinect device
  • It shuts down the device and sets the sensor initialization option to None

The following diagram shows the actual flow of the Stop() method of a Kinect device:

The Stop() method does the clean-up operation

The Kinect sensor internally uses unmanaged resources to manage the sensor data streams. It sends the stream to the managed application and the KinectSensor class uses it. When we close the application, it cannot dispose of the unmanaged stream automatically if we don't forcefully send a termination request by calling the Stop() method. The managed Dispose method does this as well. So to turn off the device, the application programmer needs to call the Stop() method to clear unmanaged resources before the managed application gets closed.

For instance, if you are running the application while debugging, your application will be directly hosted in your vshost file and the Visual Studio debugger allows you to run your code line by line. If you close the application directly from Visual Studio, it will ensure that the process gets stopped without any code getting executed. To experience this situation, you can stop the application directly from Visual Studio without calling the Stop() method, and you will see the IR light of the device is still on.

Tip

Turning off the IR light forcefully

You can turn off the IR emitter using the KinectSensor.ForceInfraredEmitterOff property. By default, this property is set to false. To turn the IR light off, set the property to true.

You can test this functionality easily by performing the following steps:

  • Once the sensor is started, you will find a red light is turned on in the IR emitter.
  • Set ForceInfraredEmitterOff to true, which will stop the IR emitter; you will find that the IR light is also stopped.

Again, set the ForceInfraredEmitterOff property to false to turn on the emitter.

Displaying information in the Kinect Info Box

So far, you have seen how you can use Kinect libraries in your application and how to identify, stop, and start it. In short, you are almost done with the major part of the application. Now, it's time to look at how to display information in the UI.

Designing the Info Box UI

This application displays information using the System.Windows.Controls.TextBlock class inside a System.Windows.Controls.Grid class. That is, each cell on the grid contains a Textblock component. The following excerpt from the MainWindow.xaml file shows how this is accomplished in XAML:

<TextBlock Text="Connection ID" Grid.Row="1" Grid.Column="0" 
Style="{StaticResource BasicTextStyle}"  />
<TextBlock Text="{Binding ConnectionID}" Grid.Row="1" Grid.Column="1"
Style="{StaticResource BasicContentStyle}" />

As we are going to display the information in text format, we will be splitting the window into a number of columns and rows, where each of the individual rows is responsible for showing information for one single sensor. As you can see in the preceding code, we have two TextBlock controls. One of them shows the label and another is bound to a property that shows the actual data.

Similar to this, we have several TextBlock controls that display the data for different information types. Apart from the text controls, we have button controls to start and stop the sensor.

Binding the data

Data binding in WPF can be done very easily using the property notification API built into WPF applications. INotifyPropertyChanged is a powerful interface in the System.Component namespace and provides a standard way to notify binding to UI on a property change.

Note

Implementing the INotifyPropertyChanged interface is not mandatory for data binding. You can use direct binding of data by just assigning data into control. Implementing INotifyPropertyChanged will allow changes to property values to be reflected in the UI and will help keep your code clean and get the most out of the complex work done by implementing data binding in the UI. As the standard process of data binding, in this section we will give you a step-by-step look into the application as we will be following the same approach across the book for all demo applications.

A quick look at INotifyPropertyChanged

The INotifyPropertyChanged interface itself is a simple. It has no properties and no methods. It has just one event called PropertyChanged with two parameters. Refer to the following code snippet; the first parameter is the sender, and the second parameter is PropertyChangedEventArgs, which has a property named PropertyName:

this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));

Using INotifyPropertyChanged for data binding

Within our Kinect Info Box project, add a new class named MainWindowViewModel.cs and implement the INotifyPropertyChanged interface.

The very first thing we are going to do here is wrap the PropertyChanged event within a generic method so that we can call the same method for every property that needs to send the notifications.

As shown in the following code block, we have a method named OnNotifyPropertyChange, which accepts the propertyName variable as a parameter and passes it within PropertyChangedEventArgs:

public void OnNotifyPropertyChange(string propertyName)
{
    if (this.PropertyChanged != null)
    {
 this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs (propertyName));
    }
}

The class also contains the list of properties with PropertyChanged in the setter block to see if a value is changing. Any changes in the values will automatically notify the UI. This is all very simple and useful. Implementation of any property that needs a notification looks like the following code snippet:

private string connectionIDValue;
public string ConnectionID
{
    get
    {
        return this.connectionIDValue;
    }
    set
    {
        if (this.connectionIDValue != value)
        {
            this.connectionIDValue = value;
 this.OnNotifyPropertyChange("ConnectionID");
        }
    }
}

We need all the properties to be defined in the same way for our MainWindowViewModel class. The following diagram is the class diagram for the MainWindowViewModel class:

Using INotifyPropertyChanged for data binding

Setting the DataContext

Binding of the data occurs when the PropertyChanged event of the MainWindowViewModel class is raised. The DataContext property of the MainWindow class is set when the class is first initialized in the constructor of the MainWindow class:

private MainWindowViewModel viewModel;
public MainWindow()
{
    this.InitializeComponent();
    this.Loaded += this.MainWindow_Loaded;
 this.viewModel = new MainWindowViewModel();
 this.DataContext = this.viewModel;
}

Setting up the information

The last thing we need to do is to fill up the MainWindowViewModel class instance with the values from the sensor object. The SetKinectInfo method does the same job in our Kinect Info Box application. Refer to the following code snippet; we have assigned the DeviceConnectionId value of the sensor object, which is nothing but the currently running sensor, to the connectionID property of the ViewModel object.

private void SetKinectInfo()
{
    if (this.sensor != null)
    {
 this.viewModel.ConnectionID = this.sensor.DeviceConnectionId;
        // Set other property values
    }
}

Whenever the SetKinectInfo method is called, the value of DeviceConnectionId is assigned to ViewModel.connectionID, and it immediately raises the OnNotifyPropertyChange notification, which notifies the UI about the changes and updates the value accordingly.

That's all!

You are done! You have just finished building your first application. Run the application to see the information about the attached Kinect sensor. While starting the application it will automatically start the sensor; however, you can start and stop the sensor by clicking on the Start and Stop buttons. In the code behind these two buttons that were just mentioned, are the SensorStart() and SensorStop() methods, respectively.