diff --git a/KinectConnection/DepthImageStream.cs b/KinectConnection/DepthImageStream.cs index 1e4d780..0429cff 100644 --- a/KinectConnection/DepthImageStream.cs +++ b/KinectConnection/DepthImageStream.cs @@ -1,5 +1,11 @@ -using System; +using Microsoft.Kinect; +using System; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Windows; using System.Windows.Media; +using System.Windows.Media.Imaging; namespace KinectConnection { @@ -8,16 +14,154 @@ namespace KinectConnection /// public class DepthImageStream : KinectStream { - public override ImageSource Source => throw new NotImplementedException(); + /// + /// Map depth range to byte range + /// + private const int MapDepthToByte = 8000 / 256; + + /// + /// Active Kinect sensor + /// + private KinectSensor kinectSensor = null; + + /// + /// Reader for depth frames + /// + private DepthFrameReader depthFrameReader = null; + + /// + /// Description of the data contained in the depth frame + /// + private FrameDescription depthFrameDescription = null; + + /// + /// Bitmap to display + /// + private WriteableBitmap depthBitmap = null; + + /// + /// Intermediate storage for frame data converted to color + /// + private byte[] depthPixels = null; + + /// + /// Gets the bitmap to display + /// + public override ImageSource Source + { + get + { + return this.depthBitmap; + } + } public override void Start() { - throw new NotImplementedException(); + // get FrameDescription from DepthFrameSource + this.depthFrameDescription = this.KinectSensor.DepthFrameSource.FrameDescription; + + // allocate space to put the pixels being received and converted + this.depthPixels = new byte[this.depthFrameDescription.Width * this.depthFrameDescription.Height]; + + // create the bitmap to display + this.depthBitmap = new WriteableBitmap(this.depthFrameDescription.Width, this.depthFrameDescription.Height, 96.0, 96.0, PixelFormats.Gray8, null); + + if (this.KinectSensor != null) + { + // open the reader for the depth frames + this.depthFrameReader = this.KinectSensor.DepthFrameSource.OpenReader(); + + if (this.depthFrameReader != null) + { + // wire handler for frame arrival + this.depthFrameReader.FrameArrived += this.Reader_FrameArrived; + } + } } public override void Stop() { throw new NotImplementedException(); } + + /// + /// Handles the depth frame data arriving from the sensor + /// + /// object sending the event + /// event arguments + private void Reader_FrameArrived(object sender, DepthFrameArrivedEventArgs e) + { + bool depthFrameProcessed = false; + + using (DepthFrame depthFrame = e.FrameReference.AcquireFrame()) + { + if (depthFrame != null) + { + // the fastest way to process the body index data is to directly access + // the underlying buffer + using (Microsoft.Kinect.KinectBuffer depthBuffer = depthFrame.LockImageBuffer()) + { + // verify data and write the color data to the display bitmap + if (((this.depthFrameDescription.Width * this.depthFrameDescription.Height) == (depthBuffer.Size / this.depthFrameDescription.BytesPerPixel)) && + (this.depthFrameDescription.Width == this.depthBitmap.PixelWidth) && (this.depthFrameDescription.Height == this.depthBitmap.PixelHeight)) + { + // Note: In order to see the full range of depth (including the less reliable far field depth) + // we are setting maxDepth to the extreme potential depth threshold + ushort maxDepth = ushort.MaxValue; + + // If you wish to filter by reliable depth distance, uncomment the following line: + //// maxDepth = depthFrame.DepthMaxReliableDistance + + this.ProcessDepthFrameData(depthBuffer.UnderlyingBuffer, depthBuffer.Size, depthFrame.DepthMinReliableDistance, maxDepth); + depthFrameProcessed = true; + } + } + } + } + + if (depthFrameProcessed) + { + this.RenderDepthPixels(); + } + } + + /// + /// Directly accesses the underlying image buffer of the DepthFrame to + /// create a displayable bitmap. + /// This function requires the /unsafe compiler option as we make use of direct + /// access to the native memory pointed to by the depthFrameData pointer. + /// + /// Pointer to the DepthFrame image data + /// Size of the DepthFrame image data + /// The minimum reliable depth value for the frame + /// The maximum reliable depth value for the frame + private unsafe void ProcessDepthFrameData(IntPtr depthFrameData, uint depthFrameDataSize, ushort minDepth, ushort maxDepth) + { + // depth frame data is a 16 bit value + ushort* frameData = (ushort*)depthFrameData; + + // convert depth to a visual representation + for (int i = 0; i < (int)(depthFrameDataSize / this.depthFrameDescription.BytesPerPixel); ++i) + { + // Get the depth for this pixel + ushort depth = frameData[i]; + + // To convert to a byte, we're mapping the depth value to the byte range. + // Values outside the reliable depth range are mapped to 0 (black). + this.depthPixels[i] = (byte)(depth >= minDepth && depth <= maxDepth ? (depth / MapDepthToByte) : 0); + } + } + + /// + /// Renders color pixels into the writeableBitmap. + /// + private void RenderDepthPixels() + { + this.depthBitmap.WritePixels( + new Int32Rect(0, 0, this.depthBitmap.PixelWidth, this.depthBitmap.PixelHeight), + this.depthPixels, + this.depthBitmap.PixelWidth, + 0); + } } } diff --git a/KinectConnection/InfraredImageStream.cs b/KinectConnection/InfraredImageStream.cs index 6bfed0c..a4a17c8 100644 --- a/KinectConnection/InfraredImageStream.cs +++ b/KinectConnection/InfraredImageStream.cs @@ -1,5 +1,11 @@ -using System; +using Microsoft.Kinect; +using System; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Windows; using System.Windows.Media; +using System.Windows.Media.Imaging; namespace KinectConnection { @@ -8,16 +14,171 @@ namespace KinectConnection /// public class InfraredImageStream : KinectStream { - public override ImageSource Source => throw new NotImplementedException(); + /// + /// Maximum value (as a float) that can be returned by the InfraredFrame + /// + private const float InfraredSourceValueMaximum = (float)ushort.MaxValue; + + /// + /// The value by which the infrared source data will be scaled + /// + private const float InfraredSourceScale = 0.75f; + + /// + /// Smallest value to display when the infrared data is normalized + /// + private const float InfraredOutputValueMinimum = 0.01f; + + /// + /// Largest value to display when the infrared data is normalized + /// + private const float InfraredOutputValueMaximum = 1.0f; + + /// + /// Active Kinect sensor + /// + private KinectSensor kinectSensor = null; + + /// + /// Reader for infrared frames + /// + private InfraredFrameReader infraredFrameReader = null; + + /// + /// Description (width, height, etc) of the infrared frame data + /// + private FrameDescription infraredFrameDescription = null; + + /// + /// Bitmap to display + /// + private WriteableBitmap infraredBitmap = null; + + /// + /// Current status text to display + /// + private string statusText = null; + + /// + /// Gets the bitmap to display + /// + public override ImageSource Source + { + get + { + return this.infraredBitmap; + } + } + + /// + /// Execute shutdown tasks + /// + /// object sending the event + /// event arguments + private void MainWindow_Closing(object sender, CancelEventArgs e) + { + if (this.infraredFrameReader != null) + { + // InfraredFrameReader is IDisposable + this.infraredFrameReader.Dispose(); + this.infraredFrameReader = null; + } + + if (this.kinectSensor != null) + { + this.kinectSensor.Close(); + this.kinectSensor = null; + } + } + + public InfraredImageStream() + { + // get FrameDescription from InfraredFrameSource + this.infraredFrameDescription = this.KinectSensor.InfraredFrameSource.FrameDescription; + } public override void Start() { - throw new NotImplementedException(); + // create the bitmap to display + this.infraredBitmap = new WriteableBitmap(this.infraredFrameDescription.Width, this.infraredFrameDescription.Height, 96.0, 96.0, PixelFormats.Gray32Float, null); + + if (this.KinectSensor != null) + { + // open the reader for the depth frames + this.infraredFrameReader = this.KinectSensor.InfraredFrameSource.OpenReader(); + + if (this.infraredFrameReader != null) + { + // wire handler for frame arrival + this.infraredFrameReader.FrameArrived += this.Reader_InfraredFrameArrived; + } + } } public override void Stop() { throw new NotImplementedException(); } + + /// + /// Handles the infrared frame data arriving from the sensor + /// + /// object sending the event + /// event arguments + private void Reader_InfraredFrameArrived(object sender, InfraredFrameArrivedEventArgs e) + { + // InfraredFrame is IDisposable + using (InfraredFrame infraredFrame = e.FrameReference.AcquireFrame()) + { + if (infraredFrame != null) + { + // the fastest way to process the infrared frame data is to directly access + // the underlying buffer + using (Microsoft.Kinect.KinectBuffer infraredBuffer = infraredFrame.LockImageBuffer()) + { + // verify data and write the new infrared frame data to the display bitmap + if (((this.infraredFrameDescription.Width * this.infraredFrameDescription.Height) == (infraredBuffer.Size / this.infraredFrameDescription.BytesPerPixel)) && + (this.infraredFrameDescription.Width == this.infraredBitmap.PixelWidth) && (this.infraredFrameDescription.Height == this.infraredBitmap.PixelHeight)) + { + this.ProcessInfraredFrameData(infraredBuffer.UnderlyingBuffer, infraredBuffer.Size); + } + } + } + } + } + + /// + /// Directly accesses the underlying image buffer of the InfraredFrame to + /// create a displayable bitmap. + /// This function requires the /unsafe compiler option as we make use of direct + /// access to the native memory pointed to by the infraredFrameData pointer. + /// + /// Pointer to the InfraredFrame image data + /// Size of the InfraredFrame image data + private unsafe void ProcessInfraredFrameData(IntPtr infraredFrameData, uint infraredFrameDataSize) + { + // infrared frame data is a 16 bit value + ushort* frameData = (ushort*)infraredFrameData; + + // lock the target bitmap + this.infraredBitmap.Lock(); + + // get the pointer to the bitmap's back buffer + float* backBuffer = (float*)this.infraredBitmap.BackBuffer; + + // process the infrared data + for (int i = 0; i < (int)(infraredFrameDataSize / this.infraredFrameDescription.BytesPerPixel); ++i) + { + // since we are displaying the image as a normalized grey scale image, we need to convert from + // the ushort data (as provided by the InfraredFrame) to a value from [InfraredOutputValueMinimum, InfraredOutputValueMaximum] + backBuffer[i] = Math.Min(InfraredOutputValueMaximum, (((float)frameData[i] / InfraredSourceValueMaximum * InfraredSourceScale) * (1.0f - InfraredOutputValueMinimum)) + InfraredOutputValueMinimum); + } + + // mark the entire bitmap as needing to be drawn + this.infraredBitmap.AddDirtyRect(new Int32Rect(0, 0, this.infraredBitmap.PixelWidth, this.infraredBitmap.PixelHeight)); + + // unlock the bitmap + this.infraredBitmap.Unlock(); + } } } diff --git a/KinectConnection/KinectConnection.csproj b/KinectConnection/KinectConnection.csproj index 85e64d6..3fdbff9 100644 --- a/KinectConnection/KinectConnection.csproj +++ b/KinectConnection/KinectConnection.csproj @@ -23,6 +23,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -31,6 +32,7 @@ TRACE prompt 4 + true diff --git a/KinectConnection/KinectStreamsFactory.cs b/KinectConnection/KinectStreamsFactory.cs index 63b45b0..d06b010 100644 --- a/KinectConnection/KinectStreamsFactory.cs +++ b/KinectConnection/KinectStreamsFactory.cs @@ -21,6 +21,8 @@ namespace KinectConnection { { KinectStreams.Color, () => new ColorImageStream() }, { KinectStreams.Body, () => new BodyImageStream() }, + { KinectStreams.IR, () => new InfraredImageStream() }, + { KinectStreams.Depth, () => new DepthImageStream() }, // Other streams ... }; } diff --git a/KinectSensorStreams/ViewModel/MainWindowVM.cs b/KinectSensorStreams/ViewModel/MainWindowVM.cs index 024d111..1890e4a 100644 --- a/KinectSensorStreams/ViewModel/MainWindowVM.cs +++ b/KinectSensorStreams/ViewModel/MainWindowVM.cs @@ -56,7 +56,7 @@ namespace KinectSensorStreams.ViewModel // factory KinectStreamsFactory = new KinectStreamsFactory(new KinectManager()); // kinect stream => color stream for now - KinectStream = KinectStreamsFactory[KinectStreams.Body]; + KinectStream = KinectStreamsFactory[KinectStreams.Depth]; StartCommand = new RelayCommand(Start); // [Question] : StartCommand ici peut être mieux que BeginInit() dans MainWindow.xaml.cs ?