Click here to Skip to main content
15,881,864 members
Articles / Desktop Programming / WPF

Smoothing Kinect Depth Frames in Real-Time

Rate me:
Please Sign up or sign in to vote.
4.91/5 (39 votes)
24 Jan 2012CPOL11 min read 282.2K   8.2K   58  
Removing noise from the Kinect Depth Frames in real-time using pixel filters and weighted moving average techniques.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Research.Kinect.Nui;

namespace KinectDepthSmoothing
{
    /// <summary>
    /// This program was created by Karl Sanford for submission to The Code Project in conjunction with an article
    /// explaining the techniques used within.  This program will smooth Depth Images from the Kinect using two techniques:
    /// Pixel Filtering and Weighted Moving Average.  It will also allow users of the program to change parameters of these
    /// techniques in the UI to see their effect on the resulting images.
    /// 
    /// I have documented the code where I thought it might help people, but if you have any questions/comments/concerns about
    /// this program, please feel free to contact me in the comments section for this article on The Code Project.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        #region Private Fields
        // The Kinect runtime used to recieve depth data
        private Runtime runtime;

        // Fields used to adjust the filtering feature
        // Initial values are specified in XAML
        private bool useFiltering;
        // Will specify how many non-zero pixels within a 1 pixel band
        // around the origin there should be before a filter is applied
        private int innerBandThreshold;
        // Will specify how many non-zero pixels within a 2 pixel band
        // around the origin there should be before a filter is applied
        private int outerBandThreshold;

        // Fields used to adjust the averaging feature
        // Initial values are specified in XAML
        private bool useAverage;
        // Will specify how many frames to hold in the Queue for averaging
        private int averageFrameCount;
        // The actual Queue that will hold all of the frames to be averaged
        private Queue<short[]> averageQueue = new Queue<short[]>();

        // Fields used for FPS calculations
        private int totalFrames;
        private int lastFrames;
        private DateTime lastTime;
        
        // Constants used to address the individual color pixels for generating images
        private const int RedIndex = 2;
        private const int GreenIndex = 1;
        private const int BlueIndex = 0;

        // Constants used to map value ranges for distance to pixel intensity conversions
        private const int MaxDepthDistance = 4000;
        private const int MinDepthDistance = 850;
        private const int MaxDepthDistanceOffset = 3150;
        #endregion Private Fields

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // If you don't make sure the Kinect is plugged in and working before trying to use it, the app will crash
            if (Runtime.Kinects.Count > 0)
            {
                runtime = Runtime.Kinects[0];

                runtime.Initialize(RuntimeOptions.UseDepth);

                runtime.DepthFrameReady += runtime_DepthFrameReady;

                runtime.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.Depth);
            }
            else
                MessageBox.Show("Sorry, your Kinect could not be found.");
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            // If you don't uninitialize on closing, sometimes the Kinect is not available
            // the next time you run the program and you have to re-attach the Kinect.
            if (runtime != null)
                runtime.Uninitialize();
        }

        private void runtime_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            // The Raw Image is simply the raw depth data from the Kinect displayed as an image
            // This is the method I had been using for a while, but was unsatisfied with, which
            // lead me to create the next method.
            ImageRaw.Source = CreateImageFromDepthImage(e.ImageFrame);

            // The Smoothed Image will apply both a filter and a weighted moving average over the
            // depth data from the Kinect (depending on UI selections)
            ImageSmooth.Source = CreateSmoothImageFromDepthArray(e.ImageFrame);

            // Update the FPS after every frame so we can monitor our performance and make sure
            // that the cost of aesthetics doesn't override performance
            UpdateFps();
        }

        private void UpdateFps()
        {
            totalFrames++;
            DateTime current = DateTime.Now;
            if (lastTime == DateTime.MaxValue || current.Subtract(lastTime) > TimeSpan.FromSeconds(1))
            {
                TextblockFps.Text = (totalFrames - lastFrames).ToString();
                lastFrames = totalFrames;
                lastTime = current;
            }
        }

        private short CalculateDistanceFromDepth(byte first, byte second)
        {
            // Please note that this would be different if you use Depth and User tracking rather than just depth
            return (short)(first | second << 8);
        }

        private byte CalculateIntensityFromDistance(int distance)
        {
            // This will map a distance value to a 0 - 255 range
            // for the purposes of applying the resulting value
            // to RGB pixels.
            int newMax = distance - MinDepthDistance;
            if (newMax > 0)
                return (byte)(255 - (255 * newMax
                / (MaxDepthDistanceOffset)));
            else
                return (byte)255;
        }

        #region UI Event Handlers
        private void CheckboxUseFiltering_Checked(object sender, RoutedEventArgs e)
        {
            useFiltering = ((CheckBox)sender).IsChecked.Value;
        }

        private void SliderInnerBand_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            innerBandThreshold = (int)e.NewValue;
        }

        private void SliderOuterBand_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            outerBandThreshold = (int)e.NewValue;
        }

        private void CheckboxUseAverage_Checked(object sender, RoutedEventArgs e)
        {
            useAverage = ((CheckBox)sender).IsChecked.Value;

            // If you don't clear out the Queue when you turn off this feature, then you turn it back on
            // you will get a few frames of very odd results if you are moving around in the frames.
            if (!useAverage)
                averageQueue.Clear();
        }

        private void SliderAverage_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            averageFrameCount = (int)e.NewValue;
        }
        #endregion UI Event Handlers
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Open Systems Technologies
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions