Kinect SDK 1.0 – 4 – Kinect in depth with the DepthStream

20. April 2012 12:04 by Renaud in   //  Tags:   //   Comments (15)
 1. Introduction to the API
 2. Use the ColorImageStream
 3. Track the users with the SkeletonStream
 4. Kinect in depth!
 5. Speech recognition

This article will show you how to use one of the notable properties of the Kinect, which is its ability to give you a 3-dimensional view of the space.

Maybe it seems trivial today, because you can just go to the cinema to watch a 3D movie, but the technology used by the Kinect isn't the same than for the cinema. For a 3D movie, you'll need two cameras, to capture two images from different places, just like your eyes do. Then your brain will process those images and you'll know that there is an idea of depth in there.

But the Kinect can't give you two images! However it can use its infrared sensor to calculate the depth of each pixel that it sees.

So with the small project below, we will see how to get the DepthImageFrame, and how to use it to produce a 3-dimensional video. At the same time, we will see how to use the polling instead of the event model as in the previous examples.

You can download the sources right here:

[caption id="attachment_126" align="aligncenter" width="132" caption="XnaKinect - 3D vision using DepthStream"][/caption]

For that project, we will use the XNA 4.0 Framework. If you're not familiar with it, it's not a problem: me neither ;)

What you need to know is that an XNA application is executed into a loop, so we don't have to add some eventhandlers here to receives the frame. We will ask the Kinect directly each time we go through the loop.

3D scene: the principle

So the idea as explained above is to produce a video in 3 dimensions based on the information received from the Kinect. Then, when the video will be rendered, we will be able to move the position of the camera virtually: it means that the Kinect will stay at the same place, but we will see the scene from a different point of view!

Intialize the Kinect sensor

Once again, we will simply take the first connected Kinect and activate the DepthStream and the ColorStream :

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            // Use the first Connected Kinect
            Kinect = KinectSensor.KinectSensors.FirstOrDefault(k => k.Status == KinectStatus.Connected);
            if (Kinect != null)
            {
                // Activate the Near mode
                Kinect.DepthStream.Range = DepthRange.Near;
                // Enable the DepthStream
                Kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
                // ... and instantiate the needed arrays to store some data
                depthPixelData = new short[Kinect.DepthStream.FramePixelDataLength];
                colorCoordinates = new ColorImagePoint[depthPixelData.Length];
                vertexPositionColors = new VertexPositionColor[depthPixelData.Length * 2];

                // Enable the Color Stream
                Kinect.ColorStream.Enable(ColorImageFormat.RgbResolution1280x960Fps12);
                colorFramePixelData = new byte[Kinect.ColorStream.FramePixelDataLength];

                Kinect.Start();
            }
        }

In the Update method, we will retrieve the color and depth frames :

            if (Kinect != null)
            {
                // Ask for a DepthFrame
                using (DepthImageFrame depthFrame = Kinect.DepthStream.OpenNextFrame(0))
                {
                    if (depthFrame != null)
                    {
                        // Copy the data
                        depthFrame.CopyPixelDataTo(depthPixelData);

                        // And match each point of the depthframe to a position
                        // in the colorframe that we are about to receive
                        Kinect.MapDepthFrameToColorFrame(depthFrame.Format, depthPixelData, Kinect.ColorStream.Format, colorCoordinates);

                        using (ColorImageFrame colorFrame = Kinect.ColorStream.OpenNextFrame(0))
                        {
                            if (colorFrame != null)
                            {
                                // Copy the data
                                colorFrame.CopyPixelDataTo(colorFramePixelData);
                            }
                        }
                    }
                }
            }

Here, we are using the OpenNextFrame method directly on the streams to retrieve the frames. The value passed in parameter indicates how long in milliseconds you are willing to wait for the information. If the time is elapsed, you'll receive a null value.

Notice that after getting the depthFrame, we use the MapDepthFrameToColorFrame to have an array containing for each depth pixel the coordinates of the corresponding color pixel in a color frame with the given format!

Then, in the draw method, we will create the points we want to draw :

                // For each pixel index in the colorCoordinates array
                for (int i = 0; i < colorCoordinates.Length; i++)
                {
                    // If the coordinates are in the range of the colorstream frame size
                    if (colorCoordinates[i].X < Kinect.ColorStream.FrameWidth
                        && colorCoordinates[i].Y < Kinect.ColorStream.FrameHeight)
                    {
                        // calculate the X,Y coordinates of the pixel in the depthstream frame
                        var pixelY = (i) / Kinect.DepthStream.FrameHeight;
                        var pixelX = (i) % Kinect.DepthStream.FrameWidth;

                        // Find the corresponding value in the Skeleton referential
                        var skeletonPoint = Kinect.MapDepthToSkeletonPoint(Kinect.DepthStream.Format, pixelX, pixelY, depthPixelData[i]);

                        // Retrieve the first index of the fourth-bytes pixel in the ColorFrame.
                        var baseIndex = (colorCoordinates[i].Y * Kinect.ColorStream.FrameWidth + colorCoordinates[i].X) * 4;

                        // And finally we update the corresponding 
                        vertexPositionColors[i * 2].Color.R = colorFramePixelData[baseIndex + 2];
                        vertexPositionColors[i * 2].Color.G = colorFramePixelData[baseIndex + 1];
                        vertexPositionColors[i * 2].Color.B = colorFramePixelData[baseIndex + 0];
                        vertexPositionColors[i * 2].Position.X = skeletonPoint.X;
                        vertexPositionColors[i * 2].Position.Y = skeletonPoint.Y;
                        vertexPositionColors[i * 2].Position.Z = skeletonPoint.Z;

                        // Use another point right behind the first one
                        vertexPositionColors[i * 2 + 1] = vertexPositionColors[i * 2];
                        vertexPositionColors[i * 2 + 1].Position.Z = skeletonPoint.Z + 0.05f;
                    }
                }

In the above code, for each pixel that we want to draw, we will find its position in the Skeleton referential thanks to the MapDepthToSkeletonPoint method.

You have to know that in XNA 4.0, you can't draw a "point". So, what we are doing is to draw small lines, based on the actual pixel point, and a second point 5 cm behind the first one.

There are probably much more efficient ways to achieve what we are doing here, but I didn't find them. It's just for fun of course :)

And finally, draw a list of lines based on the points we just defined.

                foreach (EffectPass effectPass in basicEffect.CurrentTechnique.Passes)
                {
                    effectPass.Apply();

                    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                        PrimitiveType.LineList,
                        vertexPositionColors,
                        0,
                        vertexPositionColors.Length / 2
                    );
                }

Move the camera position

First, we initialize the position at the application startup:

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            basicEffect = new BasicEffect(GraphicsDevice);

            // The position of the camera
            cameraPosition = new Vector3(0, 0, -1);
            // creates the view based on the camera position, the target position, and the up orientation.
            viewMatrix = Matrix.CreateLookAt(cameraPosition, new Vector3(0, 0, 2), Vector3.Up);
            basicEffect.View = viewMatrix;
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f), GraphicsDevice.Viewport.AspectRatio, 1f, 1000f);
            basicEffect.VertexColorEnabled = true;
        }

And to be able to move it, we add the following code in the Update method:

           foreach (Keys key in Keyboard.GetState().GetPressedKeys())
            {
                if (key == Keys.D)
                {
                    cameraPosition.X -= 0.01f;
                }
                if (key == Keys.Q)
                {
                    cameraPosition.X += 0.01f;
                }
                if (key == Keys.Z)
                {
                    cameraPosition.Y += 0.01f;
                }
                if (key == Keys.S)
                {
                    cameraPosition.Y -= 0.01f;
                }
                if (key == Keys.E)
                {
                    cameraPosition.Z += 0.01f;
                }
                if (key == Keys.X)
                {
                    cameraPosition.Z -= 0.01f;
                }
            }
            viewMatrix = Matrix.CreateLookAt(cameraPosition, new Vector3(0, 0, 2), Vector3.Up);
            basicEffect.View = viewMatrix;

Those lines will change the camera position if you press a key. For example, if you press Z, it will go up on the Y axis, and if you press S it will go down.  

Kinect SDK 1.0 - 2 - Use the ColorStream

19. April 2012 16:13 by Renaud in   //  Tags:   //   Comments (1)
 1. Introduction to the API
 2. Use the ColorImageStream
 3. Track the users with the SkeletonStream
 4. Kinect in depth!
 5. Speech recognition

In the first blog post talking about Kinect,  I introduced to you the Kinect SDK 1.0. For that second part, I'd like to talk about the ColorStream. That stream will allow us to get and display a video from the Kinect. At the end of this article, you will know how to realize a simple WPF application to:

  • Display the video from the Kinect camera
  • Allow the user to select the image format
  • Take a snapshot and save it on your hard drive
  • Transform the video image in real-time

If you want to try it, you can download this sample project :

What is the ColorStream?

This is the data stream that will allow us to retrieve what the Kinect sees. There are 3 main streams we can play with:

  • ColorStream: gives access to the color image.
  • DepthStream: gives information about the distance between the Kinect and a point in the space.
  • SkeletonStream: gives information about people standing in front of the Kinect sensor.

Basically, all those streams are used in the same way (because they all inherit from the same base classe: ImageStream). We will talk about the two others in the next blog posts, but for now, let's focus on the simplest one: the ColorStream.

Initialize the stream

To start a stream (and thus start receiving information), we have to activate it explictly. In the code below, we enable the ColorStream with a given ColorImageFormat:

        /// <summary>
        /// Opens the kinect.
        /// </summary>
        /// <param name="newKinect">The new kinect.</param>
        private void OpenKinect(KinectSensor newKinect)
        {
            // Enables the ColorStream with the default format
            newKinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
            // Listen to the ColorFrameReady event to know when data is available
            newKinect.ColorFrameReady += newKinect_ColorFrameReady;
            // Starts the kinect sensor
            newKinect.Start();
        }

Then we will have to process the data!

Data processing

We need some fields to store the useful information:

        /// <summary>
        /// The length of the pixels data
        /// </summary>
        private int pixelDataLength;

        /// <summary>
        /// this array will contain the data provided by the Kinect
        /// </summary>
        private byte[] pixelData;

        /// <summary>
        /// Indicates wether the selected format is supported or not
        /// </summary>
        private bool isFormatSupported;

        /// <summary>
        /// the zone to update in the writeableBitmap
        /// </summary>
        private Int32Rect int32Rect;

        /// <summary>
        /// The amounts of bytes for one row
        /// </summary>
        private int stride;

        /// <summary>
        /// Gets or sets the output image.
        /// </summary>
        /// <value>
        /// The output image.
        /// </value>
        public WriteableBitmap OutputImage { get; set; }

Then, we implement the ColorFrameReady event handler:

        /// <summary>
        /// Handles the ColorFrameReady event of the newKinect control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.Kinect.ColorImageFrameReadyEventArgs"/> instance containing the event data.</param>
        void newKinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            // Add some code here
        }

First, we attempt to open a ColorImageFrame. It may happen that the frame is null, for example when it's open too much time after the event is raised, or when the Kinect is stopped.

            // Open the received frame
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                // Could be null if we opened it too late
                if (colorFrame == null)
                     return;

                // Add some code here
            }

If the frame isn't null, then we will compare the data size with the pixelDataLength private field (which should be 0 at the beginning).

If the sizes are different, it means that we are going through that method for the first time, so we will initialize some of the objects based on the ColorImageFormat used to enable the stream. We will create a byte array with the right size (to calculate the size, you can use some properties of the ColorImageFrame: the array should have a length equals to FrameWidth x FrameHeigth x FrameBytesPerPixel). Then we will also create a WriteableBitmap. This is just like a BitmapImage, except that you can update the pixels of the region you want at any time, and it will update the UI automatically (exactly what we need in this application). Moreover it keeps us from instantiating a new BitmapImage 30 times per second.

                    // Checks if the length has changed (means the format has changed)
                    if (pixelDataLength != colorFrame.PixelDataLength)
                    {
                        // creates a new buffer long enough to receives all the data of a frame
                        pixelData = new byte[colorFrame.PixelDataLength];
                        pixelDataLength = colorFrame.PixelDataLength;

                        // Use a WriteableBitmap because it's better to re-write some pixels
                        // of a WriteabeBitmap than creating a new BitmapImage for each new frame.
                        OutputImage = new WriteableBitmap(
                                             colorFrame.Width,
                                             colorFrame.Height,
                                             96, // Standard dpi
                                             96,
                             // All the formats provided by the kinect are 4-bytes per pixel 
                             // (except the RawYuv which is not supported)
                                             PixelFormats.Bgr32,
                                             null);

                        // The rectangle that we will update in the WriteableBitmap 
                        // (has same dimensions as OutputImage)
                        int32Rect = new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height);

                        // how many bytes we need to represent one pixel
                        stride = colorFrame.Width * colorFrame.BytesPerPixel;

                        // Set the new WriteableBitmap as the KinectImage source
                        KinectImage.Source = OutputImage;
                    }

KinectImage is an Image control that you have to define in the XAML part of the MainWindow class:

 <Image x:Name="KinectImage" Width="640" Height="480" />

Update the image

So now we have a WriteableBitmap ready to receive our data, and we just have to update it each time we receive a new frame.

The CopyPixelDataTo method will copy the content of the frame to the given byte array. Then we have to update the OutputImage:

                    // Copies the data from the fram to the pixelData array.
                    colorFrame.CopyPixelDataTo(pixelData);

                    // Update the writeable bitmap.
                    OutputImage.WritePixels(
                        // Zone that will be updated
                        int32Rect,
                        // new data
                        pixelData,
                        // stride = number of pixels for one line of the image
                        stride,
                        // starting index = 0
                        0);

You can run the application now, and see the result! :)

Change the format !

At any time, you can see the current ColorImageFormat using the property Format of the ColorStream. That value gives two informations:

  • How is represented each pixel
  • The amount of frames per second
We need to add a combobox to the UI.
            <ComboBox Width="200" x:Name="FormatComboBox" SelectionChanged="FormatComboBox_SelectionChanged" />

Then in the constructor of MainWindows, we initialize the combobox values :

            // Fill in the Format Combobox with the available formats
            IList<ColorImageFormat> colorImageFormat = new List<ColorImageFormat> 
                                                        { ColorImageFormat.RgbResolution640x480Fps30,
                                                        ColorImageFormat.RgbResolution1280x960Fps12,
                                                        ColorImageFormat.RawYuvResolution640x480Fps15,
                                                        ColorImageFormat.YuvResolution640x480Fps15
                                                        };

            // Populate the combobox with the new List
            FormatComboBox.ItemsSource = colorImageFormat;

And finally, we implement the FormatComboBox_SelectionChanged eventhandler. It's not possible to modify directly the Format property because it is read-only. To change the format, you do have to pass it as a parameter in the Enable(...) method.

        private void FormatComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0
                && Kinect != null
                && Kinect.Status == KinectStatus.Connected)
            {
                // Disabled the current stream
                Kinect.ColorStream.Disable();
                // Retrieves the new format
                var newFormat = (ColorImageFormat)e.AddedItems[0];

                // Enable the stream with the new format
                Kinect.ColorStream.Enable(newFormat);
            }
        }

When the stream is re-enabled, the ColorFrameReady fires again, and the image is updated.

Take a picture!

A nice feature to have in your Kinect application is a the possibility to take a picture!

There are different ways to achieve this, and I'm going to show you two possibilities: an easy one, and a very easy one!

First, let's add a new button to start the snapshot task:

 <Button Content="Take a picture!" x:Name="PictureTask" Click="PictureTask_Click" />

Then, implement the button click event handler. We open a new SaveDialog to ask for where to save the picture. If a file already exists at the given path, then it will be overwritten.

        /// <summary>
        /// Handles the Click event of the PictureTask button.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PictureTask_Click(object sender, RoutedEventArgs e)
        {
            string fileName = null;
            SaveFileDialog saveDialog = new SaveFileDialog();
            if (saveDialog.ShowDialog().Value)
            {
                fileName = saveDialog.FileName;
            }

            if (string.IsNullOrWhiteSpace(fileName))
                return;

            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }

            // Add some logic to take a picture here..

        }

Now you need to add the logic to take a picture and save it to your hard drive. Here are the two solutions that I propose you:

The easy way
            using (FileStream savedSnapshot = new FileStream(fileName, FileMode.CreateNew))
            {
                BitmapSource image = (BitmapSource)KinectImage.Source;
                JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
                jpgEncoder.QualityLevel = 70;
                jpgEncoder.Frames.Add(BitmapFrame.Create(image));
                jpgEncoder.Save(savedSnapshot);
                savedSnapshot.Flush();
                savedSnapshot.Close();
                savedSnapshot.Dispose();
            }
The even easier way (Coding4Fun Toolkit)

Add to your project a reference to the Coding4Fun's Kinect Toolkit:

[caption id="attachment_1709" align="aligncenter" width="635" caption="Add a reference to the toolkit using NuGet"][/caption]

Add a new using instruction to be able to use the extension methods:

using Coding4Fun.Kinect.Wpf;

And finally, you just need those two lines:

            // Coding4Fun helper's method:
            BitmapSource image = (BitmapSource)KinectImage.Source;
            image.Save(fileName, ImageFormat.Jpeg);

The Kinect Toolkit allows you to call a Save method on a BitmapSource and passing two parameters: the file path, and the outpout format.

Transform the output image

The final part of this article will talk about how you can manipulate the color image frame before you display it. As you know, the color frame is nothing more than a bunch of bytes stored in an array called pixelData.

Let's take, say, the ColorImageFormat.RgbResolution640x480Fps30:

pixelData contains 640 x 480 x 4 = 1228800 bytes. It means 4 bytes for each pixel in the  color frame. The first three bytes correspond to the colors Blue, Green, Red. The fourth one isn't used but could store the alpha (transparency) channel in a Bgra format.

If we simply try to invert the bytes values before we update the WriteableBitmap, it will result in a "negative" effect!

                    for (int i = 0; i < pixelData.Length - 3; i +=4)
                    {
                        // Transformation 1:
                        // ======================================= //
                        // ( ~ ) inverts the bits 
                        pixelData[i] = (byte)~pixelData[i];
                        pixelData[i + 1] = (byte)~pixelData[i + 1];
                        pixelData[i + 2] = (byte)~pixelData[i + 2];
                    }
The tild  (~) operator is used to invert each bit in a numeric value.

You can now easily imagine a lot of transformations. You could set the Blue and Green values to 0 to display a Red image. To create a gray scale, each byte (B, G, and R) must have the same value.

Thank you for reading! In the next post, we will talk about the SkeletonStream!

 

Kinect SDK 1.0 - 3 - Track bodies with the SkeletonStream

19. April 2012 16:10 by Renaud in   //  Tags:   //   Comments (0)
 1. Introduction to the API
 2. Use the ColorImageStream
 3. Track the users with the SkeletonStream
 4. Kinect in depth!
 5. Speech recognition

In the previous posts, we saw how to connect and use your Kinect within your app, and how to use the ColorStream. That was just enough to use your Kinect as a webcam, but not much more.

In this post, we are going to empower your application with the Skeleton tracking provided by the Kinect !

What is the SkeletonStream ?

This is a data stream provided by the Kinect which allows you to know the position of a user in front of the Kinect, and even to know the positions of up to 20 Joints on the user's body.

 

The following figure shows you the Joints (yellow points) tracked by the Kinect in the 3-dimensional referential.

The skeleton of a user and all the tracked points are stored in an object called Skeleton (MSDN).

Skeleton in details

The Skeleton doesn't expose a lot of properties, but they are very useful :

  • TrackingState : The status tells you whether the Skeleton is tracked (Tracked) or not (NotTracked), or if we only have the global position of the user (PositionOnly). The Kinect is able to follow up to 6 users at the same time: 2 in Tracked mode, and 4 in PositionOnly mode. The Tracked mode gives you access to the full skeleton and the 20 Joints.
  • Joints : A Joint collection representing the body as shown on the above image (only available if Tracked).
  • TrackingId :  Identifies a Skeleton, so that you can track a particular user over the time.
  • Position : The global position of a user (available if Tracked or PositionOnly).
  • ClippedEdges : Indicates whether one part of the user's body is clipped by the edges (not in the field of view).

It's important to know that all the positions are represented by a SkeletonPosition object which has 3 properties: X, Y, Z. The distances are expressed in meters and the referential is as shown in the above schema: X goes from left to right, Y goes from up to down and Z is the depth. So basically, the Kinect is the point (0, 0, 0).

[caption id="attachment_126" align="aligncenter" width="92" caption="WpfKinect 3 - SkeletonStream"][/caption]

Track and draw the skeleton !

What we do want now is to use that SkeletonStream. We will first activate it, as we did for the ColorStream in the previous post :

/// <summary>
/// This array will store the Skeletons
/// received with each SkeletonFrame
/// </summary>
private Skeleton[] skeletonData = new Skeleton[6];

/// <summary>
/// Starts the Kinect sensor.
/// </summary>
/// <param name="newKinect">Le nouveau capteur.</param>
private void OpenKinect(KinectSensor newKinect)
{
    Kinect = newKinect;
    // Activate the SkeletonStream with default smoothing.
    newKinect.SkeletonStream.Enable();
    // Subscribe to the SkeletonFrameReady event to know when data is available
    newKinect.SkeletonFrameReady += Kinect_SkeletonFrameReady;
    // Starts the sensor
    newKinect.Start();
}

Then you implement the eventhandler :

        void Kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            // Opens the received SkeletonFrame
            using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
            {
                // Skeleton frame might be null if we are too late or if the Kinect has been stopped
                if (skeletonFrame == null)
                    return;

                // Copies the data in a Skeleton array (6 items) 
                skeletonFrame.CopySkeletonDataTo(skeletonData);

                // TODO : Do something with the skeletons

            }
        }

Now you can start processing the information using the received Skeletons. We will start by drawing the skeletons of the two tracked users (if any).

To make it properly, I created a SkeletonDrawing helper class, which will allow me to draw a skeleton in a canvas based on the Joints. You can see the source code here: SkeletonDrawing.cs

After the todo in the Kinect_SkeletonFrameReady eventhandler, we will add a a few lines of code to retrieve the two tracked skeletons and update the corresponding SkeletonDrawing object.

First, add a dictionary to keep track of the drawings:

        // The Skeletons drawing on the scene
        private Dictionary<int, SkeletonDrawing> drawnSkeletons;

Then, add the logic to look for the tracked skeletons and update them:

                // Retrieves Skeleton objects with Tracked state
                var trackedSkeletons = skeletonData.Where(s => s.TrackingState == SkeletonTrackingState.Tracked);

                // By default, assume all the drawn skeletons are inactive
                foreach (SkeletonDrawing skeleton in drawnSkeletons.Values)
                    skeleton.Status = ActivityState.Inactive;

                foreach (Skeleton trackedSkeleton in trackedSkeletons)
                {
                    SkeletonDrawing skeletonDrawing;
                    // Checks if the tracked skeleton is already drawn.
                    if (!drawnSkeletons.TryGetValue(trackedSkeleton.TrackingId, out skeletonDrawing))
                    {
                        // If not, create a new drawing on our canvas
                        skeletonDrawing = new SkeletonDrawing(this.SkeletonCanvas);
                        drawnSkeletons.Add(trackedSkeleton.TrackingId, skeletonDrawing);
                    }

                    // Update the drawing
                    Update(trackedSkeleton, skeletonDrawing);
                    skeletonDrawing.Status = ActivityState.Active;
                }

                foreach (SkeletonDrawing skeleton in drawnSkeletons.Values)
                {
                    // Erase all the still inactive drawings. It means they are not tracked anymore.
                    if (skeleton.Status == ActivityState.Inactive)
                        skeleton.Erase();
                }

Then, the update method, which is going to make some transformations on the SkeletonPoint positions and update the drawn skeleton :

        /// <summary>
        /// Updates the specified drawn skeleton with the new positions
        /// </summary>
        /// <param name="skeleton">The skeleton source.</param>
        /// <param name="drawing">The target drawing.</param>
        private void Update(Skeleton skeleton, SkeletonDrawing drawing)
        {
            foreach (Joint joint in skeleton.Joints)
            {
                // Transforms a SkeletonPoint to a ColorImagePoint
                var colorPoint = Kinect.MapSkeletonPointToColor(joint.Position, Kinect.ColorStream.Format);
                // Scale the ColorImagePoint position to the current window size
                var point = new Point((int)colorPoint.X / 640.0 * this.ActualWidth, (int)colorPoint.Y / 480.0 * this.ActualHeight);
                // update the position of that joint
                drawing.Update(joint.JointType, point);
            }
        }

Notice the MapSkeletonPointToColor method!  The KinectSensor class provides some helper methods to map a point from a stream to another.

Here are all the available methods:

The reason why those methods exist is simple:

The SkeletonStream gives information in a different referential than the ColorStream and the DepthStream. For the last two, you have a 2D position for each pixel of the frame, with the top-left corner as the origin (0,0).

The SkeletonPoints are placed in a 3D referential. Now if you want to display the ColorStream, and overlay with the Skeleton, you'll have to convert the skeleton positions to make sure they fit with the ColorStream image. The MapSkeletonPointToColor does the job for you and give you a ColorImagePoint based on a SkeletonPoint!

The case of the depth and color points is a little bit different. The depth sensor is not at the exact same place than the color sensor. It means that there is a small gap between the two frames (we will see it in the next post!).

Until now, we didn't use the Z value but we will see how we can add it to scale the size of the shapes, and show the proximity.

Postures and Gestures

Let's talk about the gestures. There is nothing in the SDK to easily detect gestures, and you'll have to implement everything by yourself! There are some projects out there to help you achieve that job, but it's still complicated.

If you plan to use postures or gestures in your application, you should definitely check the KinectToolbox project on codeplex:

http://kinecttoolbox.codeplex.com/

Kinect SDK 1.0 - 3 - Tracker les mouvements avec le SkeletonStream

19. April 2012 13:04 by Renaud in   //  Tags:   //   Comments (1)
 1. Introduction à l'API
 2. Utilisation du ColorImageStream
 3. Tracker le squelette avec le SkeletonStream
 4. Kinect en profondeur avec le DepthStream
 5. Reconnaissance vocale

Dans les articles précédents, on a vu comment se connecter à une Kinect, et comment se servir du ColorStream. Globalement, avec ça, on n'a pas vraiment utilisé la Kinect différemment d'une bête webcam...

Mais cette fois-ci on entre dans le vif du sujet! On va parler du SkeletonStream...

Le SkeletonStream, c'est quoi?

C'est un flux de données que renvoie la Kinect et qui vous permet de connaître la position d'un utilisateur face à la Kinect, mais pas seulement: on peut obtenir jusqu'à 20 points (Joint) du corps, positionnés dans un espace à 3 dimensions!

L'image suivante vous montre l'ensemble des 20 points qui peuvent être trackés!

Le squelette d'un utilisateur et l'ensemble de ses points sont représentés par un objet de type Skeleton (MSDN).

Le Skeleton en détails

Le Skeleton ne présente pas beaucoup de propriétés mais elles sont très utiles :

  • TrackingState : Le statut vous indique si le Skeleton est tracké (Tracked) ou non (NotTracked), ou si on a que la position globale de l'utilisateur (PositionOnly). La Kinect peut suivre jusqu'à 6 joueurs maximum dont 2 en mode Tracked et 4 en mode PositionOnly. Le mode Tracked permet d'avoir accès au squelette complet (20 Joint).
  • Joints : Une collection de Joint représentant les 20 points du corps humain comme montré sur l'image ci-dessus. (Tracked)
  • TrackingId :  Identifie un Skeleton sur la durée.
  • Position : La position centrale d'un squelette (Tracked et PositionOnly).
  • ClippedEdges : Indique si des parties du corps de l'utilisateur se trouver en dehors du champs de vision.

Il est important de noter que pour le Skeleton et chacun des Joint, les positions sont représentées par un objet de types SkeletonPosition qui a 3 propriétés X, Y et Z. Les distances sont exprimées en mètres, et le référentiel est comme indiqué sur le schéma ci-dessus: X va de la gauche vers la droite, Y du bas vers le haut et Z représente la profondeur. Concrètement, la Kinect est le point (0,0,0).

[caption id="attachment_126" align="aligncenter" width="92" caption="WpfKinect 3 - SkeletonStream"][/caption]

Tracker et dessiner le squelette!

Ce qu'on veut désormais, c'est utiliser le SkeletonStream. On va commencer par l'activer, de la même manière qu'on activait le ColorStream dans les articles précédents :

/// <summary>
/// Ce tableau va contenir les Skeleton
/// reçus avec chaque nouvelle SkeletonFrame
/// </summary>
private Skeleton[] skeletonData = new Skeleton[6];

/// <summary>
/// Démarrer le capteur Kinect.
/// </summary>
/// <param name="newKinect">Le nouveau capteur.</param>
private void OpenKinect(KinectSensor newKinect)
{
    Kinect = newKinect;
    // Active le SkeletonStream avec les paramètres par défaut.
    newKinect.SkeletonStream.Enable();
    // On s'abonne au SkeletonFrameReady pour savoir quand des données sont disponibles
    newKinect.SkeletonFrameReady += Kinect_SkeletonFrameReady;
    // Démarre le capteur
    newKinect.Start();
}

Ensuite on implémente l'eventhandler qui va bien:

        void Kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            // On ouvre la SkeletonFrame reçue
            using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
            {
                // Il se peut que la frame soit null, si on arrive 
                // trop tard ou si la Kinect a été stoppée.
                if (skeletonFrame == null)
                    return;

                // On copie les infos dans un tableau de Skeleton (6 éléments)
                skeletonFrame.CopySkeletonDataTo(skeletonData);

                // TODO : Faire quelque chose avec les Skeletons

            }
        }

Maintenant on peut commencer à faire des traitements en utilisant les infos reçues! On va commencer par dessiner les squelettes des deux joueurs qui seront entièrement trackés (s'il y en a). Pour faire ça proprement, j'ai créé une classe SkeletonDrawing, qui va me permettre de dessiner un squelette à partir de tous les Joints. Le code source est disponible dans le projet! Dans le todo de l'eventhandler Kinect_SkeletonFrameReady, on va ajouter quelques lignes de codes pour récupérer les squelettes trackés et mettre à jour les SkeletonDrawing correspondants:

D'abord, on ajoute une variable d'instance pour garder une trace des squelettes dessinées:

        // Les squelettes dessinés sur la scène
        private Dictionary<int, SkeletonDrawing> drawnSkeletons;

Ensuite on peut ajouter la logique pour chercher les squelettes trackés, et les représenter ou les mettre à jour sur l'écran:

// Récupère les Skeleton avec le statut Tracked
var trackedSkeletons = skeletonData.Where(s => s.TrackingState == SkeletonTrackingState.Tracked);

// Par défaut, on indique que tous les squelettes sont inactifs
foreach (SkeletonDrawing skeleton in drawnSkeletons.Values)
    skeleton.Status = ActivityState.Inactive;

// On parcours chaque item de la liste des Skeleton trackés (2 max.)
foreach (Skeleton trackedSkeleton in trackedSkeletons)
{
    SkeletonDrawing skeletonDrawing;
    // Et on regarde s'il est déjà dessiné
    if (!drawnSkeletons.TryGetValue(trackedSkeleton.TrackingId, out skeletonDrawing))
    {
        // Si pas, on crée un nouveau dessin
        skeletonDrawing = new SkeletonDrawing(this.SkeletonCanvas);
        drawnSkeletons.Add(trackedSkeleton.TrackingId, skeletonDrawing);
    }

    // et on met à jour le dessin du squelette
    Update(trackedSkeleton, skeletonDrawing);
    skeletonDrawing.Status = ActivityState.Active;
}

foreach (SkeletonDrawing skeleton in drawnSkeletons.Values)
{
    // On peut ensuite effacer tous les dessins de squelettes avec un statut Inactive
    if (skeleton.Status == ActivityState.Inactive)
        skeleton.Erase();
}

Et la méthode Update qui va mettre à jour la position de chaque points du squelette dessiné:

private void Update(Skeleton skeleton, SkeletonDrawing drawing)
{
    // On parcourt la liste des Joint du Skeleton
    foreach (Joint joint in skeleton.Joints)
    {
        // On récupère la position correspondante dans un repère où le coin topleft est (0,0)
        var colorPoint = Kinect.MapSkeletonPointToColor(joint.Position, Kinect.ColorStream.Format);
        // On met les points à l'échelle compte tenu de la taille de la fenêtre
        var point = new Point((int)colorPoint.X / 640.0 * this.ActualWidth, 
                              (int)colorPoint.Y / 480.0 * this.ActualHeight);
        // On met à jour le Joint correspondant du dessin
        drawing.Update(joint.JointType, point);
    }
}

Notez l'utilisation de la méthode MapSkeletonPointToColor!  La classe KinectSensor expose quelques méthodes d'aide pour convertir la position d'un point d'une frame vers un point d'une autre frame.

Voici l'ensemble des méthodes disponibles :

Ces méthodes vont vous éviter d'attraper mal à la tête :)

Comme vous le savez, le SkeletonStream donne des informations dans un référentiel différent du ColorStream et du DepthStream. Pour les deux derniers, vous aurez des tableaux de pixels, et donc chaque pixel aura une position dans un espace à deux dimensions avec le point top-left ayant les coordonnées (0,0).

Les SkeletonPoints  quant à eux sont placés dans un référentiel à 3 axes. Maintenant, si vous voulez replacer le squelette dans le référentiel 2D du ColorStream, vous devrez convertir les positions pour être sûre que les points correspondent. C'est à ça que sert la méthode MapSkeletonPointToColor, qui vous retournera une ColorImagePoint basé sur un SkeletonPoint et le ColorImageFormat cible.

Le cas de la conversion depuis une DepthImageFrame vers une ColorImageFrame est un peu différent. Le capteur de profondeur n'est pas tout à fait à la même place que le capteur de couleur. Il y a donc une petite différence entre les positions (mais on en parlera dans le prochain article ) !

Jusqu'à maintenant on a pas utilisé la valeur de Z, mais si vous téléchargez les sources vous verrez que l'on peut également l'utiliser pour modifier l'échelle des formes dessinées et ainsi donner une impression de perspective !

Postures et Gestures

Les gestures, pour ceux qui ne savent pas, c'est un mouvement identifiable que l'utilisateur fait et qui permet de déclencher une action. Si vous êtes familier avec le développement Windows Phone, vous avez certainement déjà entendu parler du Flick, du Pinch, etc...  Ces mouvements sont courants sur des devices prévus pour le touch.

Si vous pensez à la Kinect, vous pensez naturellement à un mouvement de balaiement avec la main, par exemple pour faire défiler une liste horizontale. Ce ce qu'on appelle le Swipe.

J'ai été surpris en découvrant le SDK de voir que rien n'était prévu pour la détection de gestures. Ce n'est pas une tâche facile, parce que contrairement à un mouvement fait avec les doigts sur  un écran tactile, il y a plein de façons différentes de faire le même mouvement, et il faut pouvoir différencier un mouvement fait volontairement d'un simple déplacement de l'utilisateur devant la Kinect. Il y a heureusement quelques projets que vous pouvez trouver sur le web que vous pouvez réutiliser, comme l'excellent Kinect Toolbox.

Kinect SDK 1.0 - 2 - Utilisation du ColorStream

19. April 2012 10:04 by Renaud in   //  Tags:   //   Comments (0)
 1. Introduction à l'API
 2. Utilisation du ColorImageStream
 3. Tracker le squelette avec le SkeletonStream
 4. Kinect en profondeur avec le DepthStream
 5. Reconnaissance vocale

Dans la première partie,  je vous présentais le Kinect SDK 1.0.

Pour cette deuxième partie, on va se concentrer sur l'utilisation du ColorStream. Le ColorStream, c'est ce qui va vous permettre d'accéder au flux vidéo de la Kinect. A la fin de cette article, vous saurez comment réaliser une application très simple pour pouvoir:

  • Afficher l'image de la caméra
  • Sélectionner le type de format d'image souhaité
  • Prendre une photo et la sauvegarder
  • Modifier le rendu de l'image en temps réel

Pour suivre cet article je vous propose de télécharger le projet d'example:

Qu'est-ce que le ColorStream?

C'est le flux de données qui va nous permettre de récupérer l'image que voit la Kinect. Il y a 3 flux principaux avec lesquels on peut jouer:

  • ColorStream: accès à l'image, comme une webcam.
  • DepthStream: accès à des informations sur la distance entre la Kinect et un point
  • SkeletonStream: accès à des informations sur les personnes debout devant le capteur

Globalement, tous ces flux s'utilisent de la même façon (puisqu'ils héritent tous de la même classe de base qui est ImageStream). On parlera des deux autres dans les prochains articles, mais pour le moment concentrons-nous sur le plus simple!

Initialisation du flux

Pour démarrer un flux (et donc commencer à en tirer des informations), il va falloir l'activer explicitement:

        /// <summary>
        /// Démarrer le capteur Kinect.
        /// </summary>
        /// <param name="newKinect">The new kinect.</param>
        private void OpenKinect(KinectSensor newKinect)
        {
            // Active le ColorStream avec un format donné (optionnel)
            newKinect.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
            // On s'abonne au ColorFrameReady pour savoir quand des données sont disponibles
            newKinect.ColorFrameReady += newKinect_ColorFrameReady;
            // Démarre le capteur
            newKinect.Start();
        }

Ensuite, il va falloir traiter ses données !

Traitement de l'info

On va avoir besoin de quelques variables d'instance pour stocker les infos utiles:

        /// <summary>
        /// La longueur du tableau de données.
        /// </summary>
        private int pixelDataLength;

        /// <summary>
        /// Ce tableau contiendra les données de l'image retournée par la Kinect.
        /// </summary>
        private byte[] pixelData;

        /// <summary>
        /// Zone que l'on mettra à jour dans le writeableBitmap
        /// </summary>
        private Int32Rect int32Rect;

        /// <summary>
        /// le nombre d'octets pour une ligne de pixels dans le writeableBitmap
        /// </summary>
        private int stride;

        /// <summary>
        /// Gets or sets the output image.
        /// </summary>
        /// <value>
        /// L'image de sortie que l'on va afficher.
        /// </value>
        public WriteableBitmap OutputImage { get; set; };

Ensuite on implémente l'event handler pour ColorFrameReady:

        /// <summary>
        /// Gère l'event ColorFrameReady du contrôle newKinect.
        /// </summary>
        /// <param name="sender">La source de l'event.</param>
        /// <param name="e">L'instance de <see cref="Microsoft.Kinect.ColorImageFrameReadyEventArgs"/> contenant les données de l’évènement.</param>
        void newKinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            // On va ajouter du code ici !
        }

D'abord, on tente d'ouvrir une ColorImageFrame. Il se peut parfois que la frame soit null, par exemple dans le cas où on ouvrirait la frame trop longtemps après que l'event ait été déclenché. Ou lors d'un event déclenché juste au moment où l'on stoppe le capteur.

            // Ouvrir la frame reçue
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {
                // si colorFrame est null, on n'a pas de donnée.
                if (colorFrame == null)
                    return;
            }

Si ce n'est pas le cas, on va comparer la taille du résultat obtenu, avec la taille que l'on connait (et qui est à 0 par défaut).

Si les tailles sont différentes, c'est que l'on passe dans cette méthode pour la première fois, on va donc initialiser les objets nécessaires. On va instancier un tableau contenant les données des pixels, et un WriteableBitmap. Ces objets ne vont pas changer entre chaque frame, ils vont garder les mêmes dimensions. C'est pour cela qu'on utilise des variables d'instances plutôt que de les créer inutilement à chaque nouvelle frame.

                    // Vérifie si la taille a changé (signifie que l'on passe
                    // pour la première fois ou que le format a changé)
                    if (pixelDataLength != colorFrame.PixelDataLength)
                    {
                        // On créé un nouveau tableau assez long pour
                        // pour contenir toutes les infos de la frame
                        pixelData = new byte[colorFrame.PixelDataLength];
                        // On stocke la nouvelle taille
                        pixelDataLength = colorFrame.PixelDataLength;

                        // On utilise un WriteableBitmap plutôt que de créer un nouveau
                        // BitmapImage pour chaque frame, ce qui serait moins efficient
                        // (à cause de la création d'un nouvel objet 30 fois par seconde )
                        OutputImage = new WriteableBitmap(
                                             // dimensions de l'image
                                             colorFrame.Width,
                                             colorFrame.Height,
                                             // Nombre de points par pouce
                                             96, // Valeur par défaut
                                             96, 
                                             // Le format attendu
                                             PixelFormats.Bgr32,
                                             null);

                        // La zone qui sera éditable dans le WriteableBitmap que l'on vient de créer.
                        int32Rect = new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height);

                        // Nombre d'octets pour une ligne de l'image
                        stride = colorFrame.Width * colorFrame.BytesPerPixel;

                        // On affecte le WriteableBitmap comme source au contrôle Image
                        KinectImage.Source = OutputImage;
                    }

KinectImage est un contrôle qui doit être définit dans le XAML.

 <Image x:Name="KinectImage" Width="640" Height="480" />

Ces opérations ne sont donc effectuées qu'une fois, lors du premier déclenchement de l'event.

Mise à jour de l'image

Ensuite, il faut mettre à jour l'image affichée avec les infos reçues à chaque nouvelle frame.

La méthode CopyPixelDataTo va simplement copier le contenu de la frame dans le tableau de bytes donné en paramètre. Il ne reste plus qu'à mettre à jour le WriteableBitmap qui sert de source au contrôle Image définit dans le XAML.

                    // Copie les données des pixels de la frame dans le tableau pixelData.
                    colorFrame.CopyPixelDataTo(pixelData);

                    // Met à jour le WriteableBitmap.
                    OutputImage.WritePixels(
                        // La zone qui va être mise à jour (ici, la totalité)
                        new Int32Rect(0, 0, colorFrame.Width, colorFrame.Height),
                        // nouvelles données
                        pixelData,
                        // stride = nombre de pixels pour une ligne de l'image
                        colorFrame.Width * colorFrame.BytesPerPixel,
                        // index de départ = 0
                        0);

Changez le format !

A tout moment, vous savez que vous pouvez voir le contenu de la propriété Format du ColorStream. Elle contient un enum de type ColorImageFormat. Cette valeur indique deux choses:

  • Comment est représenté chaque pixel.
  • Le nombre d'image par seconde.
On va ajouter une ComboBox à l'interface graphique:
            <ComboBox Width="200" x:Name="FormatComboBox" SelectionChanged="FormatComboBox_SelectionChanged" />

Dans le constructeur de la MainWindow, on va initialiser le contenu de la combobox:

            // Crée une liste contenant les différents formats
            IList<ColorImageFormat> colorImageFormat = new List<ColorImageFormat> 
                                                        { ColorImageFormat.RgbResolution640x480Fps30,
                                                        ColorImageFormat.RgbResolution1280x960Fps12,
                                                        ColorImageFormat.RawYuvResolution640x480Fps15,
                                                        ColorImageFormat.YuvResolution640x480Fps15
                                                        };
            // Attribue la liste à la combobox
            FormatComboBox.ItemsSource = colorImageFormat;

Et pour finir on implémente l'event hander FormatComboBox_SelectionChanged. Il n'est pas possible de modifier directement le format du ColorStream parce que cette propriété est read-only. Pour utiliser un format, il faut le passer en argument de la méthode Enable(...).

        private void FormatComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // Si un élément est sélectionné 
            // ET qu'on a une Kinect 
            // ET que cette Kinect est connectée
            if (e.AddedItems.Count > 0
                && Kinect != null
                && Kinect.Status == KinectStatus.Connected)
            {
                // On désactive le ColorStream
                Kinect.ColorStream.Disable();

                // On récupère le nouveau format
                var newFormat = (ColorImageFormat)e.AddedItems[0];

                // On réactive le stream avec le format spécifié
                Kinect.ColorStream.Enable(newFormat);
            }
        }

Dès que le stream est réactivé, les déclenchements de l'event ColorFrameReady reprennent et l'image se met de nouveau à jour.

Take a picture!

Un des trucs sympas à faire dans une application Kinect, c'est évidemment de prendre une photo!

Il y a plusieurs façon de le faire. Je vais vous en montrer une simple, et une très simple.

D'abord on ajoute un bouton à l'interface graphique:

 <Button Content="Take a picture!" x:Name="PictureTask" Click="PictureTask_Click" />

Et ensuite on implémente l'event handler. On ouvre une fenêtre de dialogue pour demander où l'utilisateur veut enregistrer le fichier, et s'il existe déjà on supprime l'existant.

        /// <summary>
        /// Handles the Click event of the PictureTask button.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void PictureTask_Click(object sender, RoutedEventArgs e)
        {
            string fileName = null;
            SaveFileDialog saveDialog = new SaveFileDialog();
            if (saveDialog.ShowDialog().Value)
            {
                fileName = saveDialog.FileName;
            }

            if (string.IsNullOrWhiteSpace(fileName))
                return;

            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }

            // Logique pour prendre une photo ici...

        }

A ce code, il faut qu'on a joute la logique de sauvegarde de l'image. Voici les deux méthodes que je vous propose:

Manière simple
            using (FileStream savedSnapshot = new FileStream(fileName, FileMode.CreateNew))
            {
                BitmapSource image = (BitmapSource)KinectImage.Source;
                JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
                jpgEncoder.QualityLevel = 70;
                jpgEncoder.Frames.Add(BitmapFrame.Create(image));
                jpgEncoder.Save(savedSnapshot);
                savedSnapshot.Flush();
                savedSnapshot.Close();
                savedSnapshot.Dispose();
            }
Manière très simple (Coding4Fun Toolkit)

Ajoutez à votre projet une référence au Kinect Toolkit de Coding4Fun:

[caption id="attachment_1709" align="aligncenter" width="635" caption="Ajout du Toolkit via Nuget"][/caption]

Ajoutez également une instruction using pour profiter des méthodes d'extensions:

using Coding4Fun.Kinect.Wpf;

Et pour finir, le tout se résume en deux lignes:

            // Coding4Fun helper's method:
            BitmapSource image = (BitmapSource)KinectImage.Source;
            image.Save(fileName, ImageFormat.Jpeg);

L'intérêt du Toolkit ici est d'ajouter une méthode d'extension à la classe BitmapSource pour pouvoir sauver son contenu avec le chemin et le format spécifié.

Transformer l'image

Pour terminer, voyons comment vous pouvez modifier l'image en temps réel. Comme vous savez, vous avez accès au tableau de bytes représentant l'image, appelé pixelData dans notre exemple.

Prenons le cas du format RgbResolution640x480Fps30:

pixelData contient 640 x 480 x 4 = 1228800 bytes. Cela correspond à 4 bytes par pixel. Les 3 premiers correspondent aux couleurs Bleu, Vert, et Rouge. Le 4ème est inutilisé, et correspondrait au canal alpha (transparence) dans un format Bgra.

Si on essaie maintenant simplement d'inverser toutes les valeurs, avant de mettre à jour le WriteableBitmap, l'image affichée donnera l'effet d'un négatif!

                    for (int i = 0; i < pixelData.Length - 3; i +=4)
                    {
                        // Transformation 1:
                        // ======================================= //
                        // ( ~ ) inverts the bits 
                        pixelData[i] = (byte)~pixelData[i];
                        pixelData[i + 1] = (byte)~pixelData[i + 1];
                        pixelData[i + 2] = (byte)~pixelData[i + 2];
                    }
L'opérateur ~ permet d'inverser tous les bits d'une valeur numérique.

On peut imaginer plein de transformations, en augmentant chacune des valeurs pour donner des couleurs saturées, ou bien en mettant à 0 les canaux Bleu et Vert pour afficher une image de teinte rouge uniquement.

Bref, c'est vous qui voyez!

La suite arrive la semaine prochaine, avec au menu: comment utiliser le SkeletonStream!

 

Kinect pour Windows vs. Kinect pour Xbox

18. April 2012 01:04 by Renaud in   //  Tags:   //   Comments (6)

Dans cet article on va tenter de faire un peu le point sur les différences entre les deux capteurs: Kinect pour Windows et Kinect pour la console Xbox 360.

Histoire (sans rire)

La Kinect pour Xbox est sortie en novembre 2010. Elle a été vendue directement avec la Xbox (qui sont Kinect-ready depuis ce moment-là), ou séparément. Kinect-ready, ça signifie en gros que la puissance du port USB de la Kinect est suffisante pour alimenter la Kinect. Ce qui n'était pas le cas des Xbox d'anciennes générations qui avaient des ports USB standard (comme sur votre PC en somme).

Kinect for Xbox / Credits: AnandTech.com

Kinect for Xbox / Credits: AnandTech.com

Du coup, les Kinect vendues séparément sont fournies avec un adaptateur qui va permettre de brancher la Kinect à un port USB ET à une alimentation secteur.

Côté Kinect pour Windows, il y a eu une première sortie le 1er février 2012, entre autres aux US. La commercialisation chez nous en Belgique est prévue pour juin 2012! Je n'irai pas jusqu'à dire que la plus grosse différence entre les deux appareils concerne l'apparence de la boîte, mais jusqu'à présent on est pas loin. (Heureusement ça va vite changer ;) )

Kinect for Windows

Apparence

Les deux Kinect sont quasiment identiques, si ce n'est l'inscription sur la droite: La Kinect pour Xbox indique logiquement XBOX 360, tandis que la Kinect pour PC arbore un... KINECT.

Différence de prix

Au niveau tarif, la Kinect Xbox coûte environs 130€ avec un jeu, tandis que la Kinect pour Windows est annoncée au prix de 250$ (soit 190€) avec un tarif avantageux de 150$ (115€) pour les étudiants. La différence de prix s'explique notamment par le fait que la Kinect pour Xbox est rentabilisée par la vente de jeux.

Licence

Une autre raison pour la différence de prix est la licence accordée avec la Kinect pour Windows: ça ne se voit peut-être pas sur l'image, mais sur la boîte il est marqué "Pour une utilisation commerciale". En fait, le SDK et le runtime de la Kinect sont gratuits. Tout développeur est libre de créer et commercialiser une application sans que ni lui, ni les utilisateurs aient à payer de frais supplémentaires à l'achat d'une Kinect.

Kinect pour Xbox et SDK 1.0

A la question de savoir si ça fonctionne, la réponse est oui! Si vous avez une Kinect pour Xbox ET l'adaptateur pour pouvoir brancher la Kinect sur secteur, alors vous pourrez la brancher sur votre PC sans problème! Toutefois la Kinect pour Xbox n'a pas été testée sur PC et Microsoft ne donne aucune garantie quant à son bon fonctionnement. Si vous possédez une Kinect pour Xbox, vous pourrez donc l'utiliser pour développer, mais si vous déployez une application (que ce soit à but commercial ou pas), vous devrez utiliser une Kinect pour Windows!

L'utilisation des SDK beta 1 et 2 est toujours permise avec les Kinect pour Xbox, pour les projets à but non-commercial! La licence à d'ailleurs été renouvelée pour durer jusqu'en juin 2016. D'ici là on peut imaginer que plus personne n'utilisera le SDK en version beta!

Différence concrètre: Near Mode

Bon alors oui il y a bien une différence: La Kinect pour Windows a été conçue pour pouvoir déterminer la profondeur dans un range plus proche: c'est ce qu'on appelle le Near Mode. Celui-ci n'est disponible qu'avec la Kinect pour Windows!

 

Toutefois il y a un petit bémol, uniquement lié au SDK 1.0: si le Near Mode est activé, le skeleton tracking ne fonctionne plus de manière totale. Cela signifie qu'on ne peut plus repérer que la position des utilisateurs, et pas les 19 Joints supplémentaires pour représenter le squelette. (MSDN : DepthRange)

KinectSensor.DepthStream.Range = DepthRange.Near;

Donc pour le moment, en NearMode, on peut uniquement utiliser le ColorStream, le DepthStream (avec une distance minimale conseillée de 50 cm, et pouvant aller jusqu'à 40 cm avec une bonne dégradation), et le SkeletonStream avec des Skeleton au statut Position-only.

Near Mode et SDK 1.5

Fort heureusement, le SDK 1.5 va permettre d'utiliser non seulement le tracking du squelette complet en Near Mode, mais en plus, il va permettre de tracker un squelette de 10 Joints, ce qui correspond à une position assise ! C'est donc avec le SDK 1.5 que la Kinect pour Windows va se démarquer et prendre tout son sens ! 

Kinect pour Windows sur Xbox?

Non, il n'est pas possible d'utiliser la Kinect PC sur votre console! D'ailleurs c'est indiqué sur la boîte. Vous ne pourrez donc pas profiter du fameux Near Mode sur la Xbox. Pourtant à la vue de la connectique, on pourrait se poser des questions! Si on regarde les deux câbles, on voit que celui de la Kinect pour PC, c'est celui d'une Kinect Xbox munie de l'adaptateur (celui-même fourni avec la Kinect vendue séparément), à la différence que le tout est enrobé d'un adhésif noir (on y voit presque que du feu!).

A gauche: Kinect pour Windows / A droite: une de chaque

Pour avoir tout de même fait le test, je peux vous confirmer que non: la Kinect pour Windows n'est pas reconnue par la Xbox! La preuve en image:

A gauche: message attendu! (Kinect Xbox) / A droite: message reçu (Kinect Windows)

Conclusion

Si jusqu'à maintenant il n'y avait pas trop de différence pour le développeur curieux de s'essayer à la Kinect, il est maintenant intelligent de se tourner vers la Kinect pour Windows malgré que son prix soit plus élevé, puisqu'elle va clairement offrir de nouvelles possibilités à la sortie du SDK 1.5 en juin 2012!

(source: http://blogs.msdn.com/b/kinectforwindows/archive/2012/01/09/kinect-for-windows-commercial-program-announced.aspx)

Edit from October 4th: I just realized I never translated this article. My apologies, I totally forgot it! Anyway, since the day I initially wrote this post, a new website came out, with nice content written by a belgian developer and Microsoft Student Partner. And he wrote an excellent article about the differences between Kinect for Windows and Kinect for Xbox. So in case you were looking for it, just check it out, and have a look at the rest of the website. It worth it !

Kinect SDK 1.0 - 1 - Introduction to the API

16. April 2012 16:21 by Renaud in   //  Tags:   //   Comments (0)

 

 1. Introduction to the API
 2. Use the ColorImageStream
 3. Track the users with the SkeletonStream
 4. Kinect in depth!
 5. Speech recognition

It's been 2 months now since Kinect for Windows SDK 1.0 is out. We are of course waiting for the next release (SDK 1.5 announced for late May), but for now I'd like to introduce to you the current version, so that you'll be ready to welcome what's coming in the near future !

For this blogpost, I assume you satisfy the following requirements:

  • You should know the basics of C# (VB.NET is working also, but all my project samples are written in C# ! )
  • You need Visual Studio 2010 (if you don't have it yet, install the free Visual Studio 2010 Express edition now)
  • And the last but not least, you need a Kinect device!

 

Hardware overview

I won't go too deep in the details, because that's not really what is important for us, developers. The Kinect is built with a standard camera, and an IR emitter coupled with a receiver. Together they will create a depth map of the environment. At the bottom of the Kinect, you can find 4 microphones. The Kinect sensor stands on a tilt motor, and you can change its elevation angle with a few lines of code.

Download and install the SDK

To go further, download the SDK 1.0 by clicking on the image below.

Télécharger le Kinect SDK 1.0

Before installing it, you should first make sure that you have uninstalled any previous version. If everything went fine during the installation, you should now see 4 new programs in you Control Panel.

Installation completed

Microsoft shipped some good project samples with the SDK. They will be very helpful in a few minutes. If you didn't installed them yet, you can launch the Kinect SDK Sample Browser from the start menu :

You can have a look at all the projects available, but the one we really need for the moment is the KinectExplorer package.

Theoretical basics

The Kinect SDK 1.0 API doesn't contain a lot of classes, and is quite easy to use it. You'll find all the documentation you need in the installation folder : ~ProgramsMicrosoft SDKsKinectv1.0DocumentationKinectSDK.chm or on the MSDN website : Kinect for Windows SDK et Microsoft.Kinect namespace.

The main object in a Kinect project is of course the KinectSensor. One instance of that class gives you control over one Kinect device plugged in to your computer. Then you'll focus on the 3 streams (color, depth, skeleton), that will give you access to all the information that the Kinect collects for you!

First, let's have a look at how to detect if at least one device is plugged in (since the SDK 1.0, it's possible to use up to 4 devices simultaneously) !

Open Visual Studio and create a first WPF project. Add a new reference to the Microsoft.Kinect.dll assembly, and edit the MainWindow class:

MainWindow.xaml.cs
        public KinectSensor Kinect {get; set;}
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // Walk through KinectSensors to find the first one with a Connected status
            var firstKinect = (from k in KinectSensor.KinectSensors
                               where k.Status == KinectStatus.Connected
                               select k).FirstOrDefault();

            if (firstKinect != null)
                SetNewKinect(firstKinect);

            KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
        }

The above code will use the KinectSensors static property of the KinectSensor class to retrieve all the available devices. That property is just a KinectSensorCollection, which is in fact a ReadOnlyCollection<KinectSensor> with an extra event to tell you if the status of one of the KinectSensor changed. The goal is to iterate through the KinectSensor's and find the first one with Status equals to KinectStatus.Connected, which means that it's ready to be used!

If a Kinect is found we will call the SetNewKinect method, described below. In any case, we also add an event handler to the StatusChanged event, so that we will know about any changes. For example if the current Kinect is unplugged, or if a new one is plugged but is still intializing, ...

        private void SetNewKinect(KinectSensor newKinect)
        {
            if (kinect != newKinect)
            {
                if (kinect != null)
                    StopKinect(Kinect);

                if (newKinect != null)
                    OpenKinect(newKinect);
            }

            Kinect = newKinect;
        }

The SetNewKinect method will compare the newKinect with the current one, and if they are different, it will call the StopKinect method with the old sensor, and the OpenKinect with the new one.

        private void OpenKinect(KinectSensor newKinect)
        {
            // TODO: Enable needed streams with desired formats and parameters
            newKinect.Start();
        }

        private void StopKinect(KinectSensor oldKinect)
        {
            oldKinect.Stop();
        }

And finally here is the event handler for the StatusChanged. If we have a newly connected Kinect, we will use to replace the old one. If the current Kinect has a new status (which is not connected), we will replace it by null. So, with this, we are now sure that the Kinect property always contains either null or a fully connected device.

        void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            if (e.Sensor != null && e.Status == KinectStatus.Connected)
            {
                SetNewKinect(e.Sensor);
            }
            else if (e.Sensor == Kinect)
            {
                // The current Kinect isn't connected anymore
                SetNewKinect(null);
            }
        }

That's all ! If you launch the application, it will start/stop your Kinect while you plug it in and out! But yes, that's true, for the moment you just don't see nothing! We will make something better in a few minutes.

Understand and handle the KinectStatus

The following table contains all the possible values for the KinectSensor status, and the meaning of each of them. In a Kinect application, it's really important to give some feedback to the final user about his Kinect device, or even to tell him that your application requires one!

So, now you know how to retrieve a Kinect! But don't worry, you won't have to write the above code every time. You can use the KinectSensorChooser control from the WpfViewers project (that you installed with the Kinect SDK Sample browser). It will do all the logic for you, as I will show you in the next project.

If you are a developer, and that you installed the SDK, be aware that you can use a Kinect for Xbox sensor! The Kinect for PC is advised because it allows you to use all the features of the SDK, such as the "near" mode, but you can already get started with your standard Kinect (read this article to understand the differences between Kinect for Windows and Kinect for Xbox).

Two ways to retrieve data: event-driven vs. polling

Now that you can access your Kinect by code, you probably want to make something cool with it. Then you'll have to use the different streams provided by the kinect, such as the color frame, depth information for each pixels, and of course the skeletons. The Kinect API gives the developer two ways to get data from those streams, and you'll choose one or the other depending of the kind of application you plan to create.

If you develop a desktop event-driven application (the basic windows/textblock/button application), you'll let the Kinect warn you whenever new data is available. At the opposition, if you create a loop-based application (for example an XNA video game), you'll ask yourself for new information when you do need for it! That's what they call "polling".

In the following examples, I'll mainly use the first approach.

First project: Tilt Motor Controller

For easiness, you can already download the source code here: WpfKinect1

To make sure that the Kinect works properly, we will create a first small project right now. The aim of this project is to display the color stream (basically just like a webcam), and also the ElevationAngle property that we are going to update by code.

Let's start with a new WPF project called WpfKinect. Once again, you have to add a reference to the Microsoft.Kinect.dll assembly. This time, add also the existing WpfViewers project to the solution, so that you can reuse some useful controls.

We now have two projects within our solution. In the WpfKinect project, add a reference to the WpfViewers project. After this is done, and if you open the XAML editor, you'll see that the Toolbox contains a few more controls:

Microsoft.Samples.Kinect.WpfViewers

I slightly changed the Xaml code of the MainWindow.xaml page to display the 3 things:

  • A KinectColorViewer that displays what the Kinect sees.
  • A TiltMotorController, to display and change the tilt angle, and that we are going to create in a few minutes.
  • A KinectSensorChooser, which will help us to find a KinectSensor and give feedback about its status to the user.
    <Grid>
        <Grid.ColumnDefinitions >
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <viewers:KinectColorViewer Grid.Column="0" Width="640" Height="480"
                                   Kinect="{Binding ElementName=SensorChooser, Path=Kinect}"/>
        <local:TiltMotorController Grid.Column="1" 
                                   HorizontalAlignment="Center" VerticalAlignment="Center"
                                   Kinect="{Binding ElementName=SensorChooser, Path=Kinect}"/>
        <viewers:KinectSensorChooser Grid.Column="1" x:Name="SensorChooser" 
                                     KinectSensorChanged="SensorChooser_KinectSensorChanged"
                                     VerticalAlignment="Stretch" Background="White" Margin="10" />
    </Grid>

As you can see, both the KinectColorViewer and the TiltMotorController have a Kinect property which is bound to the the Kinect property of the KinectSensorChooser.

In the code-behind, we will just implement the eventhandler SensorChooser_KinectSensorChanged, to start the sensor and activate the needed streams, which means the ColorStream in our case !

        private void SensorChooser_KinectSensorChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null)
            {
                ((KinectSensor)e.OldValue).Stop();
            }

            if (e.NewValue != null)
            {
                var newSensor = (KinectSensor)e.NewValue;
                newSensor.ColorStream.Enable();
                newSensor.Start();
            }
        }

That's basically all you need to do to start playing the Kinect video! Now we are going to build the TiltMotorController.

To be able to set the Kinect property directly in the Xaml of the MainWindow page, we have to add a Dependency Property to the TiltMotorController. For that, we will use the DependyProperty.Register method with the following parameters:

  • The property name (as it will be used in the XAML).
  • The property type.
  • The type of the property's owner.
  • And finally an UIPropertyMetadata that takes two parameters: the default value, and a static callback method used whenever the value changes.
public static readonly DependencyProperty KinectProperty =
            DependencyProperty.Register("Kinect",
            typeof(KinectSensor),
            typeof(TiltMotorController),
            new UIPropertyMetadata(null, new PropertyChangedCallback(KinectChanged)));

A simple Kinect property will help us to access to DependencyProperty value:

        public KinectSensor Kinect
        {
            get { return (KinectSensor)GetValue(KinectProperty); }
            set { SetValue(KinectProperty, value); }
        }
And then, the static callback method, and another private method which will add/remove event handlers to the KinectSensor.
        private static void KinectChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            TiltMotorController tiltMotor = (TiltMotorController)d;
            tiltMotor.OnKinectChanged((KinectSensor)args.OldValue, (KinectSensor)args.NewValue);
        }

        private void OnKinectChanged(KinectSensor oldKinectSensor, KinectSensor newKinectSensor)
        {
            if (oldKinectSensor != null)
            {
                oldKinectSensor.AllFramesReady -= newKinectSensor_AllFramesReady;
            }

            if (newKinectSensor != null && newKinectSensor.Status == KinectStatus.Connected)
            {
                newKinectSensor.AllFramesReady += newKinectSensor_AllFramesReady;
            }
        }

Download the sources if you want to see the XAML, it's just a few TextBlock, a Slider and some binding. The slider value is bound to the ElevationAngle property of the KinectSensor, and two buttons, Up and Down, will respectively increase or decrease the value by 5°.  If an exception is thrown during the operation, the error message is displayed in a TextBlock.

                if (Kinect != null)
                {
                    try
                    {
                        Kinect.ElevationAngle = value;
                    }
                    catch (Exception e)
                    {
                        AngleException = e.Message;
                        OnNotifyProperty("AngleException");
                    }
                }

Why do we catch the exception? The reason is that it's not really advised to play with the tilt motor! It is not designed for repetitive or long moves. So, as they know we like to use things in a way they are are not build for, they restricted the use of the property setter:

  • You can't edit the angle more than once per second
  • After 15 consecutive calls, you have to wait for 20 seconds at least.
If you don't respect that, an bad exception will be thrown !

Now, if you execute the project and that you take your Kinect sensor in your hand, you'll see that the tilt angle value changes depending of the orientation of the sensor! For the Kinect, the 0° is the horizontal plan.

That's the end of the first part!

We are not that far yet, but at least you know that your Kinect is working! :) In the next posts, we will go further by using the different formats of the ColorStream, we will track the skeleton and the user's position, we will use the depth sensor, and finally we will see how to add some speech recognition to provide an even better user experience!

Kinect SDK 1.0 - 1 - Introduction à l'API

16. April 2012 08:04 by Renaud in   //  Tags:   //   Comments (6)
 1. Introduction à l'API
 2. Utilisation du ColorImageStream
 3. Tracker le squelette avec le SkeletonStream
 4. Kinect en profondeur avec le DepthStream
 5. Reconnaissance vocale

Cela fait maintenant plus de deux mois que le SDK 1.0 de Kinect for Windows a été lâché dans la nature! En attendant la sortie de la version 1.5 (annoncée pour fin mai), je vous propose de faire un petit tour de la version actuelle, histoire de prendre en main la Kinect !

Pour commencer, assurez-vous de remplir les conditions suivantes:

  • Avoir quelques connaissances en C# (ou VB, ça marche aussi, mais les exemples de code que je donnerai seront en C# ;) )
  • Avoir une installation de Visual Studio 2010 (si vous ne l'avez pas, la version Visual Studio 2010 Express gratuite est suffisante)
  • et enfin, the last but not least: avoir une Kinect!

Rapide tour du matériel

Sans trop rentrer dans les détails, la Kinect est pourvue d'une caméra, et d'un couple émetteur/récepteur d'infrarouges qui vous permettra d'obtenir une vision en profondeur de l'environnement.

La Kinect est également équipée de 4 micros, et est posée sur un socle motorisé. Il est possible d'ajuster sa position en la faisant bouger de haut en bas.

Télécharger et installer le SDK

Pour aller plus loin, j'vous invite à cliquer sur l'image suivante pour télécharger la dernière version du SDK:

Télécharger le Kinect SDK 1.0

Si tout se déroule bien, vous devriez trouver les programmes suivants dans Panneaux de configuration >> Programmes et fonctionnalités:

Installation completed

Notez que la reconnaissance vocale n'est disponible qu'en anglais dans le SDK 1.0, mais le français devrait arriver fin mai! ;)

Microsoft a eu la très bonne idée de proposer un ensemble de samples lors de l'installation. Ils vont nous être utiles pour la suite! Si vous les avez manqués, vous pouvez les retrouver dans le menu démarrer:

Une fois lancé, vous pouvez déjà installer la solution KinectExplorer, qui vous donne accès à des contrôles réutilisables pour rapidement prendre en main votre Kinect, et aussi pourquoi pas le jeu ShapeGame, histoire de vous détendre!

Un peu de théorie

L'API du Kinect SDK 1.0 ne contient pas énormément de classes et est plutôt intuitive. Vous trouverez de la doc dans le dossier d'installation: ~ProgramsMicrosoft SDKsKinectv1.0DocumentationKinectSDK.chm ou sur MSDN : Kinect for Windows SDK et Microsoft.Kinect namespace.

La classe centrale d'un projet utilisant la Kinect, est évidemment la classe KinectSensor. Une instance de KinectSensor représente une Kinect (connectée ou non) à votre PC. Cette classe va donc simplement vous permettre de contrôler votre Kinect et de récupérer les informations que vous lui demanderez.

Il y a ensuite principalement 3 classes, héritant toutes de ImageFrame: ColorImageFrame, DepthImageFrame et SkeletonFrame. Chacun de ces objets représentent ce qu'a vu la Kinect à un temps T,  sous différents aspects (et les noms des classes vous laissent supposer lesquels).

Voyons d'abord comment détecter si une Kinect est branchée à votre PC (depuis le SDK 1.0, il est possible de gérer jusqu'à 4 Kinect simultanément) !

Créez un nouveau projet WPF, ajoutez-y une référence à la librairie et éditez la classe MainWindow:

MainWindow.xaml.cs
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // Walk through KinectSensors to find the first one with a Connected status
            var firstKinect = (from k in KinectSensor.KinectSensors
                               where k.Status == KinectStatus.Connected
                               select k).FirstOrDefault();

            if (firstKinect != null)
                SetNewKinect(firstKinect);

            KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
        }

Avec ce code on va chercher une Kinect dès que la page sera chargée. Pour ça, on utilise la propriété statique KinectSensors de la classe KinectSensor. Cette propriété est de type KinectSensorCollection (qui hérite en fait de ReadOnlyCollection<KinectSensor>), et permet de parcourir la liste des capteurs disponibles. On cherche ici un KinectSensor avec le Status KinectStatus.Connected, qui indique que la Kinect est branchée et prête à l'emploi!

Si on en trouve une, on va appeler la méthode SetNewKinect, et dans tous les cas on va s'abonner à l'event StatusChanged de la collection de Kinect. Ca nous permettra par la suite de savoir qu'une nouvelle Kinect vient d'être branchée, ou débranchée !

        private void SetNewKinect(KinectSensor newKinect)
        {
            if (kinect != newKinect)
            {
                if (kinect != null)
                    StopKinect(Kinect);

                if (newKinect != null)
                    OpenKinect(newKinect);
            }

            Kinect = newKinect;
        }

Dans la méthode SetNewKinect, on se charge d'activer et de stopper les KinectSensor et d'attribuer le capteur courant à la propriété Kinect. Pour arrêter la Kinect, la méthode Stop est suffisante pour libérer toutes les ressources. Si vous appelez la méthode Dispose, il faudra débrancher et rebrancher la Kinect pour pouvoir à nouveau l'utiliser!

        private void OpenKinect(KinectSensor newKinect)
        {
            // TODO: Enable needed streams with desired formats and parameters
            newKinect.Start();
        }

        private void StopKinect(KinectSensor oldKinect)
        {
            oldKinect.Stop();
        }

Et pour finir l'event handler qui va comparer la Kinect qui a changé de statut avec celle actuellement utilisée. Si la nouvelle Kinect a le statut "Connected", on remplacera l'existante. Et dans le cas où c'est la Kinect existant qui a changé de statut et qui n'est donc plus connectée, on appellera la méthode SetNewKinect avec la valeur null. De cette manière, on s'assure que la propriété Kinect contient à tout moment soit une Kinect connectée, soit null.

        void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            if (e.Sensor != null && e.Status == KinectStatus.Connected)
            {
                SetNewKinect(e.Sensor);
            }
            else if (e.Sensor == Kinect)
            {
                // The current Kinect isn't connected anymore
                SetNewKinect(null);
            }
        }

C'est tout ce qu'il vous faut pour commencer à utiliser votre Kinect!

Comprendre le KinectStatus

Le tableau suivant reprend l'ensemble des statuts que peuvent avoir un objet de type KinectSensor, et la signification de chacun. Dans une application Kinect, il est important de donner un feedback à l'utilisateur concernant le statut de sa Kinect, ou bien même pour indiquer que votre application en requiert une!

Statuts possibles pour la Kinect

Maintenant que vous avez compris comment ça fonctionne, n'hésitez pas à utiliser le contrôle KinectSensorChooser du projet WpfViewers disponible dans le SDK 1.0. A la fin de cet article, vous verrez comment vous en servir!

Si vous êtes un développeur et que vous avez installé le Kinect SDK, vous pouvez utiliser une Kinect pour Xbox plutôt qu'une Kinect pour PC, mais il est recommandé d'utiliser une Kinect PC pour profiter pleinement du SDK. On reparlera par la suite des différences entre les deux capteurs!

Deux approches: event-driven vs. polling

Maintenant qu'on a récupéré notre Kinect, on aimerait pouvoir en faire quelque chose. Pour ça il faut utiliser les différents flux que le capteur met à notre disposition: couleurs, profondeurs, skeletons. L'API propose deux manières de récupérer ces flux, et vous choisrez l'une ou l'autre en fonction du type d'application que vous faites.

Si vous développez une application classique, par exemple en WPF ou Winforms, dite "event-driven", vous laisserez la Kinect vous avertir à chaque fois que des nouvelles données seront disponibles. Si par contre vous être dans un jeu, qui tourne déjà dans une boucle, vous pourrez vous même réclamer des informations quand vous en avez besoin.

Dans les exemples qui suivront, je vais principalement utiliser la première approche.

Premier projet: contrôler le Tilt Motor

Pour suivre plus facilement, vous pouvez télécharger les sources: WpfKinect1

Pour être sûr que la Kinect répond correctement, on va tout de suite créer un premier projet. Le but ici est d'afficher le stream de la caméra vidéo, et les propriétés du moteur d'inclinaison qu'on va pouvoir modifier dynamiquement.

Créons un nouveau projet WPF, que l'on appellera WpfKinect, avec toujours une référence à Microsoft.Kinect.dll. Cette fois-ci, on va également inclure dans la solution le projet WpfViewers que vous avez dû installer en même temps que le SDK !

Il y a donc maintenant deux projets dans la solution. Dans le projet WpfKinect, ajoutez une référence au projet WpfViewers. Si vous ouvrez l'éditeur XAML, vous constaterez que la Toolbox contient des contrôles en plus. Ce sont ceux définis dans le projet WpfViewers et que vous pouvez réutiliser comme bon vous semble!

Microsoft.Samples.Kinect.WpfViewers J'ai légèrement modifié le code XAML de la page MainWindow pour afficher 3 contrôles:

  • Un KinectColorViewer qui va afficher l'image de la caméra de la Kinect
  • Un TiltMotorController qu'on va créer juste après pour changer l'angle d'inclinaison de la Kinect.
  • Un KinectSensorChooser qui va grandement nous faciliter les choses pour trouver une Kinect connectée!
    <Grid>
        <Grid.ColumnDefinitions >
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <viewers:KinectColorViewer Grid.Column="0" Width="640" Height="480"
                                   Kinect="{Binding ElementName=SensorChooser, Path=Kinect}"/>
        <local:TiltMotorController Grid.Column="1" 
                                   HorizontalAlignment="Center" VerticalAlignment="Center"
                                   Kinect="{Binding ElementName=SensorChooser, Path=Kinect}"/>
        <viewers:KinectSensorChooser Grid.Column="1" x:Name="SensorChooser" 
                                     KinectSensorChanged="SensorChooser_KinectSensorChanged"
                                     VerticalAlignment="Stretch" Background="White" Margin="10" />
    </Grid>

Au niveau de l'utilisation des contrôles du projet WpfViewers, c'est assez simple. Le KinectSensorChooser possède une propriété Kinect de type KinectSensor. Le KinectColorViewer a besoin pour fonctionner de recevoir également une Kinect. Donc ce qu'on va simplement faire, c'est utiliser le binding pour faire en sorte que le color viewer utilise la propriété Kinect du KinectSensorChooser.

Dans le code-behind on va juste implémenter l'event handler SensorChooser_KinectSensorChanged, pour démarrer le capteur et activer les flux dont on a besoin, c'est-à-dire dans ce cas-ci le ColorStream.

        private void SensorChooser_KinectSensorChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null)
            {
                ((KinectSensor)e.OldValue).Stop();
            }

            if (e.NewValue != null)
            {
                var newSensor = (KinectSensor)e.NewValue;
                newSensor.ColorStream.Enable();
                newSensor.Start();
            }
        }

Le contrôle TiltMotorController va être construit de la même façon, et vous allez donc voir au passage comment créer une propriété utilisable directement dans le XAML. Ce n'est pas une simple propriété mais une DependencyProperty.

Pour enregistrer une nouvelle dependency property, on donne les 4 paramètres suivants:

  • Le nom de la propriété (qui sera utilisable ensuite dans le XAML)
  • Le type de la propriété
  • Le type parent de la propriété, donc celui auquel on veut ajouter cette propriété
  • Et enfin un objet UIPropertyMetadata qui prend comme paramètre la valeur par défaut de la propriété, et un callback(static) appelé lorsque la valeur de la propriété est modifiée.
public static readonly DependencyProperty KinectProperty =
            DependencyProperty.Register("Kinect",
            typeof(KinectSensor),
            typeof(TiltMotorController),
            new UIPropertyMetadata(null, new PropertyChangedCallback(KinectChanged)));

Une propriété va nous permettre d'accéder/modifier la valeur de la dependency property.

        public KinectSensor Kinect
        {
            get { return (KinectSensor)GetValue(KinectProperty); }
            set { SetValue(KinectProperty, value); }
        }
Et finalement, le callback, qui doit être une méthode statique, et une méthode qui va ajouter/retirer les event handlers sur les capteurs.
        private static void KinectChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            TiltMotorController tiltMotor = (TiltMotorController)d;
            tiltMotor.OnKinectChanged((KinectSensor)args.OldValue, (KinectSensor)args.NewValue);
        }

        private void OnKinectChanged(KinectSensor oldKinectSensor, KinectSensor newKinectSensor)
        {
            if (oldKinectSensor != null)
            {
                oldKinectSensor.AllFramesReady -= newKinectSensor_AllFramesReady;
            }

            if (newKinectSensor != null && newKinectSensor.Status == KinectStatus.Connected)
            {
                newKinectSensor.AllFramesReady += newKinectSensor_AllFramesReady;
            }
        }

Pour voir le XAML du contrôle TiltMotorController, j'vous invite à télécharger la solution. Mais globalement ce n'est rien de très compliqué: j'affiche un slider, dont la valeur reflète la propriété ElevationAngle (degrés) de la Kinect. Deux boutons, Up et Down, vont respectivement incrémenter et décrémenter la valeur de l'angle de 5°. Si une exception est lancée lorsqu'on tente de modifier la valeur de l'angle, elle est affichée dans un TextBlock.

                if (Kinect != null)
                {
                    try
                    {
                        Kinect.ElevationAngle = value;
                    }
                    catch (Exception e)
                    {
                        AngleException = e.Message;
                        OnNotifyProperty("AngleException");
                    }
                }

Pourquoi catcher une éventuelle exception? En fait, il n'est pas conseillé d'utiliser le moteur de la Kinect, tout simplement parce qu'il n'est pas conçu pour effectuer des mouvements longs ou répétés. Pour limiter le risque de mauvaise utilisation, l'API impose quelques limitations:

  • L'angle est modifiable tout au plus une fois par seconde
  • Après 15 appels consécutifs, un temps d'attente de 20 secondes est obligatoire
Dans le cas du non-respect de l'une de ces conditions, une exception sera lancée.

Si vous exécutez le projet, et que vous prenez votre Kinect en main, vous constaterez que la valeur de l'angle varie! C'est parce que pour la Kinect, l'angle de 0 degré correspond au plan horizontal, et pas à une position fixe dans son référentiel à elle.

Fin de cette première partie!

Nous ne sommes pas encore allés très loin, mais vous savez maintenant que votre Kinect est bien fonctionnelle, ou bien ça vous a laissé le temps de vous en procurer une! Dans les prochains articles, on verra comment utiliser les différents formats du ColorStream, tracker le squelette et la position des utilisateurs, utiliser le capteur de profondeur et finalement comment ajouter de la reconnaissance vocale dans votre application! ;)

Techdays Be 2012 - Azure, ASP.NET MVC4, Single Page Applications et le futur du C#

15. February 2012 01:02 by Renaud in   //  Tags:   //   Comments (0)

First of all, je dois remercier RealDolmen, qui m'a permis de participer aux Techdays durant mes heures de boulot. Pour ceux qui viennent d'une autre planète, les Techdays c'est L'Événement auquel il faut assister si vous êtes développeur (et que vous n'êtes pas allergique au monde Microsoft). Contrairement à ce qu'on pourrait croire, on n'y parle pas uniquement de technologies MS, mais également d'HTML5/CSS, javascript, et bonnes pratiques en général. Et puis, c'est l'occasion de rencontrer les experts locaux, et internationaux!

Cette année, c'est dans le sud du pays que ça se passe, au Kinepolis Imagibraine plus exactement. Hormis le fait qu'on est un peu à l'étroit pendant les breaks, les salles sont sympas et il y a plein de goodies à ramasser :) mais le plus important reste les sessions! On a commencé ce matin par l'opening de Katrien De Graeve, suivie de Scott Guthrie () qui nous a parlé de Windows Azure.

Représentation schématique de vos 92 instances qui tournent dans le cloud! (crédits @kRapaille)


Après ça, puisque j'avais déjà vu et revu des sessions parlant de l'interface Metro, je suis allé à la session CRM/Sharepoint. Ensuite lunch-time, et j'ai à nouveau rejoint l'audience de Scott Guthrie pour une présentation d'ASP.NET MVC4, dont la sortie est prévue pour le courant de la semaine! Excellente session... Après Scott, je suis allé écouter Bart de Smet nous parler du futur (très proche) du C#, avec notamment l'utilisation des mots clés async et await qui permettent des gérer des traitements asynchrones avec une facilité déconcertante :) Y'a plus de challenge, mais les utilisateurs apprécieront! ^^ Il nous a également parlé d'un projet sur lequel il travaille: Roslyn (comme ça, ça sonne un peu comme le prénom de votre arrière grand-mère), mais en fait c'est un projet qui consiste à proposer des APIs pour exposer le compilateur. On a eu droit à deux petites démos: une fenêtre interactive dans laquelle on peut taper du code C# et voir le résultat instantanément, on the fly. La deuxième démo montrait comment étendre les fonctionnalités de Visual Studio, comme par exemple en ajoutant un nouveau refactoring, pour réduire une full property avec un private field à une simple propriété automatique.

Ensuite, un peu d'ALM avec Brian Keller et Visual Studio 11. J'apprécie énormément l'intégration des outils de tracking avec TFS dans Visual Studio. En voyant ça, je me dis que je n'utilise même pas TFS à 10% de ses capacités :)

Et pour terminer, last but not least, Single Page Application par Steven Sanderson. Choix difficile parce que la session était en même temps que Mastering advanced concepts in Silverlight par Gill Cleeren, et Introducing Windows Runtime in Windows 8 par Bart de Smet (mais en y réfléchissant, une session level 200 pour Bart, c'est pas une vrai session de Bart ^^ ). Bref, je suis finalement allé voir de quoi parlaient les SPAs, et ça a été la claque!

Incroyable de voir avec quelle facilité on peut créer une application web avec support du mode offline, navigation côté client, et qui s'intègre à un Ipad comme une app officielle. Quelques librairies plus qu'intéressantes: UpShot.js pour gérer les requêtes asynchrones, le mode offline, le tracking des modifications sur le modèle pour le cas où l'on voudrait faire un rollback. Et KnockOut.js pour la réalisation d'une appli html5/javascript en MVVM avec binding des données, avec navigation côté client sans effectuer aucune requête. On peut mettre toutes les ressources nécessaires en cache, et au final, les seules requêtes effectuées sont celles pour communiquer avec la WebAPI REST. Le Single Page Application est un template fourni avec ASP.NET MVC4. A tester bientôt donc ;)

Bref, excellente première journée aux Techdays!

[CRM 2011] Avoid using the same name for a property and its containing type

8. February 2012 16:26 by Renaud in   //  Tags:   //   Comments (0)

CRM 2011 allows you to use the same name for a property and its containing entity. For example, you could imagine creating a custom entity like this:

Example of bad practice

However, even if it is possible, it's not a good idea because it's not allowed by the .NET Framework.

This code doesn't compile !

If you create a .NET project and try to add a new servicereference to the OrganizationData (ie: http://server/organization/XRMServices/2011/OrganizationData.sv ), an exception will be launched. The code generation will fail and you'll see the following error:

Error provided by Visual Studio 2010

By the way, if you generate the context through the crmsvcutil.exe tool, you won't face that issue. It will rename the property (ie: all_person => all_person1). It isn't possible to rename a property after it has been created, so you should consider this when starting a new Dynamics CRM 2011 project ;) It could save you some time!

TextBox

About the author

I'm a developer, blog writer, and author, mainly focused on Microsoft technologies (but not only Smile). I'm Microsoft MVP Client Development since July 2013.

Microsoft Certified Professional

I'm currently working as an IT Evangelist with an awesome team at the Microsoft Innovation Center Belgique, where I spend time and energy helping people to develop their projects. I also give training to enthusiastic developers and organize afterworks with the help of the Belgian community.

MIC Belgique

Take a look at my first book (french only): Développez en HTML 5 pour Windows 8

Développez en HTML5 pour Windows 8

Membre de l'association Fier d'être développeur

TextBox

Month List