Kinect 开发 —— 进阶指引(上)

本文将会介绍一些第三方类库如何来帮助处理Kinect传感器提供的数据。使用不同的技术进行Kinect开发,可以发掘出Kinect应用的强大功能。另一方面如果不使用这些为了特定处理目的而开发的一些类库,而是自己实现这些逻辑的话,代码可能会比较混乱和不稳定。本文只是简单的介绍这些第三方类库并给以适当的引导。Kinect开发最大的困难不是什么技术,而是知道什么样技术能够被用到Kinect开发中。一旦了解了什么技术能够使用,Kinect可能开发的应用就会出现巨大的潜力。

本文及下篇文章将会介绍几个有用的工具及类库,包括Coding4Fun Kinect Toolkit,Emgu(OpenCV计算机视觉库的C#版本)和Blender。只是非常简单的介绍了Unity 3D游戏框架,FAAST手势识别中间件以及Microsoft Robotics Developer Studio


影像帮助类库

有很多影像处理相关的类库可以使用。单单在.NET Framework中,就有PresentationCore.dll中的System.Windows.Media.Drawing抽象类以及System.Drawing.dll中System.Drawing命名空间下的类可以使用。更复杂的是,在System.Windows和System.Drawing命名空间下有一些相互独立的处理形状(shape)和颜色(color)的类。有时候一个类库中的方法能够进行一些图像处理而其它类库中却没有类似的方法。为了方便,各种图形对象之间的转换显得很有必要

当引入Kinect后,情况变得更加复杂。Kinect有自己的影像数据流,如ImageFrame。为了能够使Kinect这些专有的影像对象能够和WPF一同使用,ImageFrame对象必须转换为ImageSource类型,该对象在System.Windows.Media.Imaging命名空间中。第三方影像处理库并不知道System.Windows.Media命名空间中的对象,但是知道System.Drawing命名空间,为了能够使用Emgu处理Kinect中产生的数据,需要将Microsoft.Kinect中的某些数据类型转化为System.Drawing类型,然后将System.Drawing类型转换到Emgu中的类型,在Emgu中处理完之后,再转换回System.Drawing类型,最后再转换为System.Windows.Media类型来共WPF使用。

Coding4Fun

Coding4Fun Kinect Toolkit为将一些类型从一种类库转换到其他类库中的对应类型的提供了一些便利。这个工具集可以从该开源工具集官网 http://c4fkinect.codeplex.com/ 上下载。它包括3个独立的dll。其中Coding4Fun.Kinect.Wpf.dll提供了一系列扩展方法来在Microsoft.Kinect和System.Windows.Media之间进行转换。而Coding4Fun.Kinect.WinForm.dll提供了一系列扩展方法来在Microsoft.Kinect和System.Drawing之间进行转换。System.Drawing是.NET图形库中的dll。他包含了WinForm中用来进行绘图和展现所需的元素,而WPF中所需要的展现元素包含在System.Windows.Media中。

遗憾的是Coding4Fun Kinect Toolkit并没有提供在System.Drawing命名空间和System.Windows.Media命名空间之间对应对象的转换方法。这是因为Toolkit的最初目的是方便简单的编写Kinect Demo程序而不是提供一个通用的在不同的图像类型之间进行转换的类库。所以,一些可能在WPF中要用到的方法可能存在于WinForm的dll中。一些非常有用的,复杂的处理景深数据流中景深影像数据的方法被封装到了一些简单的将Kinect图像类型转换为WPF ImageSource的对象中去了。

下图是Coding4Fun Kinect Toolkit中的一些扩展方法,使用这些方法可以简化我们的Kinect开发。但是在开发实践中,我们应该考虑建立我们自己的帮助方法类库。这可以扩充Coding4Fun类库中所没有提供的功能。更重要的是,因为Coding4Fun的一些方法隐藏了处理深度影像数据的复杂性,有时候可能并不像你所期望的那样工作。隐藏复杂性是这些类库设计的初衷,但是当你使用时可能会感到困惑,比如当你使用Coding4Fun Toolkit中提供的方法来处理景深数据流时,e.ImageFrame.ToBitmapSource()返回的值可能和e.ImageFrame.Bits.ToBitmapSource(e.ImageFrame.Image.With,e.ImageFrame.Image.Height)产生的返回值不同。可以建立自己的扩展方法类库来方便Kinect开发,可以使你明确的使用自己的扩展方法来达到自己想要的结果。

Coding4Funw

在项目中创建一个新的名为ImageExtensions.cs的类来包含扩展方法。这个类的实际名字并不重要,需要用到的只是命名空间。在下面的代码中,我们使用的命名空间是ImageManipulationExtensionMethods。另外,还需要添加对System.Drawing.dll的引用。如前所述,System.Drawing和System.Windows.Media中有些类的名称是一样的。为了消除命名空间的冲突,我们必须选取一个作为默认的命名空间。在下面的代码中,我们将System.Drawing作为默认的命名空间,而给System.Windows.Media起了一个名为Media的别名。最后,我们为最重要的两个图像转换创建了扩展方法。一个是将字节序列(byte array)转换为Bitmap对象,另一个是将字节序列(byte array)转换为BitmapSource对象。这两个扩展方法会用到彩色图像的显示中。我们还创建了另外两个扩展方法,通过这些方法中的字节序列替换短字节序列(short array) 来对深度影像进行变换,因为深度影像数据是由short类型而不是byte类型组成的。

namespace ImageManipulationExtensionMethods
{
    public static class ImageExtensions
    {
        public static Bitmap ToBitmap(this byte[] data, int width, int height
            , PixelFormat format)
        {
            var bitmap = new Bitmap(width, height, format);

            var bitmapData = bitmap.LockBits(
                new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                ImageLockMode.WriteOnly,
                bitmap.PixelFormat);
            Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
            bitmap.UnlockBits(bitmapData);
            return bitmap;
        }

        public static Bitmap ToBitmap(this short[] data, int width, int height
            , PixelFormat format)
        {
            var bitmap = new Bitmap(width, height, format);

            var bitmapData = bitmap.LockBits(
                new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                ImageLockMode.WriteOnly,
                bitmap.PixelFormat);
            Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
            bitmap.UnlockBits(bitmapData);
            return bitmap;
        }

        public static Media.Imaging.BitmapSource ToBitmapSource(this byte[] data
            , Media.PixelFormat format, int width, int height)
        {
            return Media.Imaging.BitmapSource.Create(width, height, 96, 96
                , format, null, data, width * format.BitsPerPixel / 8);
        }

        public static Media.Imaging.BitmapSource ToBitmapSource(this short[] data
        , Media.PixelFormat format, int width, int height)
        {
            return Media.Imaging.BitmapSource.Create(width, height, 96, 96
                , format, null, data, width * format.BitsPerPixel / 8);
        }

    }
}

创建扩展方法

现在影像流和深度流数据的字节序列都可以通过从ColorImageFrame和DepthImageFrame类型获取。我们还可以创建一些额外的扩展方法来处理这些类型而不是字节序列。

    获取bit序列数据并将其转换为Bitmap或者BitmapSource类型过程中,最重要的因素是考虑像元的格式。影像数据流返回的是一系列32位的RGB影像,深度数据流返回的是一系列16位的RGB影像。在下面的代码中,我们使用没有透明值的32个字节的影像数据作为默认的数据类型,也就是说,影像数据流可以简单的调用ToBitmap或者ToBitmapSource方法,其他扩展方法的名称应该给予影像数据格式一些提示。

// bitmap methods
public static Bitmap ToBitmap(this ColorImageFrame image, PixelFormat format)
{
    if (image == null || image.PixelDataLength == 0)
        return null;
    var data = new byte[image.PixelDataLength];
    image.CopyPixelDataTo(data);
    return data.ToBitmap(image.Width, image.Height
        , format);
}

public static Bitmap ToBitmap(this DepthImageFrame image, PixelFormat format)
{
    if (image == null || image.PixelDataLength == 0)
        return null;
    var data = new short[image.PixelDataLength];
    image.CopyPixelDataTo(data);
    return data.ToBitmap(image.Width, image.Height
        , format);
}

public static Bitmap ToBitmap(this ColorImageFrame image)
{
    return image.ToBitmap(PixelFormat.Format32bppRgb);
}

public static Bitmap ToBitmap(this DepthImageFrame image)
{
    return image.ToBitmap(PixelFormat.Format16bppRgb565);
}

// bitmapsource methods

public static Media.Imaging.BitmapSource ToBitmapSource(this ColorImageFrame image)
{
    if (image == null || image.PixelDataLength == 0)
        return null;
    var data = new byte[image.PixelDataLength];
    image.CopyPixelDataTo(data);
    return data.ToBitmapSource(Media.PixelFormats.Bgr32, image.Width, image.Height);
}

public static Media.Imaging.BitmapSource ToBitmapSource(this DepthImageFrame image)
{
    if (image == null || image.PixelDataLength == 0)
        return null;
    var data = new short[image.PixelDataLength];
    image.CopyPixelDataTo(data);
    return data.ToBitmapSource(Media.PixelFormats.Bgr555, image.Width, image.Height);
}

public static Media.Imaging.BitmapSource ToTransparentBitmapSource(this byte[] data
    , int width, int height)
{
    return data.ToBitmapSource(Media.PixelFormats.Bgra32, width, height);
}

注意到上面的代码中有三种不同的像元格式。Bgr32格式就是32位彩色影像,它由RGB三个通道。Bgra32也是32位,但是她使用了第四个称之为alpha的通道,用来表示透明度。最后Bgr555是16位影像格式。在之前的深度影像处理那篇文章中,深度影像中的每一个像元都代表2个字节,555代表红绿蓝三个通道每一个通道占用5位。在深度影像处理中,也可以使用Bgr565格式,这种格式中绿色通道占用6位,你也可以添加一个使用这种格式的扩展方法。


调用扩展方法

为了在之前的MainWindows的后台代码中使用之前写的扩展方法,我们首先需要添加ImageManipulationExtensionMethods命名空间,这是扩展方法所在的命名空间。现在利用这些扩展方法,我们能够方便的将影像和深度数据转换为字节数组了,如下代码:

void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
{
    this.depthImage.Source = e.OpenDepthImageFrame().ToBitmap().ToBitmapSource();
}

void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
    this.rgbImage.Source = e.OpenColorImageFrame().ToBitmapSource();
}

编写转换方法

我们需要一些方法在不同的类型之间进行转换。如果能够从System.Windows.Media.Imaging.BitmapSource对象转换到System.Drawing.Bitmap对象,或者相反方向转换,这对我们的开发将会很有用处。下面的代码展示了这两个类型之间的转换。这些很有用处的帮助方法添加到了自己的类库中,就能够测试他们了。例如,可以将depthImage.Source赋值为e.Image.Frame.Image.ToBitmapSource().ToBitmap().ToBitmapSource()这样来进行测试。

// conversion between bitmapsource and bitmap
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);

public static Media.Imaging.BitmapSource ToBitmapSource(this Bitmap bitmap)
{
    if (bitmap == null) return null;
    IntPtr ptr = bitmap.GetHbitmap();
    var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
    ptr,
    IntPtr.Zero,
    Int32Rect.Empty,
    Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
    DeleteObject(ptr);
    return source;
}

public static Bitmap ToBitmap(this Media.Imaging.BitmapSource source)
{
    Bitmap bitmap;
    using (MemoryStream outStream = new MemoryStream())
    {
        var enc = new Media.Imaging.PngBitmapEncoder();
        enc.Frames.Add(Media.Imaging.BitmapFrame.Create(source));
        enc.Save(outStream);
        bitmap = new Bitmap(outStream);
    }
    return bitmap;
}

using Media = System.Windows.Media; // 指定默认

namespace ImageManipulationExtensionMethods
{
    public static class ImageExtensions
    {
        public static Bitmap ToBitmap(this byte[] data, int width, int height
            , PixelFormat format)
        {
            var bitmap = new Bitmap(width, height, format);

            var bitmapData = bitmap.LockBits(
                new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                ImageLockMode.WriteOnly,
                bitmap.PixelFormat);
            Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);   // 内存数据复制
            bitmap.UnlockBits(bitmapData);
            return bitmap;
        }

        public static Bitmap ToBitmap(this short[] data, int width, int height
            , PixelFormat format)
        {
            var bitmap = new Bitmap(width, height, format);

            var bitmapData = bitmap.LockBits(
                new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                ImageLockMode.WriteOnly,
                bitmap.PixelFormat);
            Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
            bitmap.UnlockBits(bitmapData);
            return bitmap;
        }

        public static Media.Imaging.BitmapSource ToBitmapSource(this byte[] data
            , Media.PixelFormat format, int width, int height)
        {
            return Media.Imaging.BitmapSource.Create(width, height, 96, 96
                , format, null, data, width * format.BitsPerPixel / 8);
        }

        public static Media.Imaging.BitmapSource ToBitmapSource(this short[] data
        , Media.PixelFormat format, int width, int height)
        {
            return Media.Imaging.BitmapSource.Create(width, height, 96, 96
                , format, null, data, width * format.BitsPerPixel / 8);
        }

        // bitmap methods

        public static Bitmap ToBitmap(this ColorImageFrame image, PixelFormat format)
        {
            if (image == null || image.PixelDataLength == 0)
                return null;
            var data = new byte[image.PixelDataLength];
            image.CopyPixelDataTo(data);
            return data.ToBitmap(image.Width, image.Height
                , format);
        }

        public static Bitmap ToBitmap(this DepthImageFrame image, PixelFormat format)
        {
            if (image == null || image.PixelDataLength == 0)
                return null;
            var data = new short[image.PixelDataLength];
            image.CopyPixelDataTo(data);
            return data.ToBitmap(image.Width, image.Height
                , format);
        }

        public static Bitmap ToBitmap(this ColorImageFrame image)
        {
            return image.ToBitmap(PixelFormat.Format32bppRgb);
        }

        public static Bitmap ToBitmap(this DepthImageFrame image)
        {
            return image.ToBitmap(PixelFormat.Format16bppRgb565);
        }

        // bitmapsource methods

        public static Media.Imaging.BitmapSource ToBitmapSource(this ColorImageFrame image)
        {
            if (image == null || image.PixelDataLength == 0)
                return null;
            var data = new byte[image.PixelDataLength];
            image.CopyPixelDataTo(data);
            return data.ToBitmapSource(Media.PixelFormats.Bgr32, image.Width, image.Height);
        }

        public static Media.Imaging.BitmapSource ToBitmapSource(this DepthImageFrame image)
        {
            if (image == null || image.PixelDataLength == 0)
                return null;
            var data = new short[image.PixelDataLength];
            image.CopyPixelDataTo(data);
            return data.ToBitmapSource(Media.PixelFormats.Bgr555, image.Width, image.Height);
        }

        public static Media.Imaging.BitmapSource ToTransparentBitmapSource(this byte[] data
            , int width, int height)
        {
            return data.ToBitmapSource(Media.PixelFormats.Bgra32, width, height);
        }



        // conversion between bitmapsource and bitmap

        [DllImport("gdi32")]
        private static extern int DeleteObject(IntPtr o);

        public static Media.Imaging.BitmapSource ToBitmapSource(this Bitmap bitmap)
        {
            if (bitmap == null) return null;
            IntPtr ptr = bitmap.GetHbitmap();
            var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            ptr,
            IntPtr.Zero,
            Int32Rect.Empty,
            Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            DeleteObject(ptr);
            return source;
        }

        public static Bitmap ToBitmap(this Media.Imaging.BitmapSource source)
        {
            Bitmap bitmap;
            using (MemoryStream outStream = new MemoryStream())
            {
                var enc = new Media.Imaging.PngBitmapEncoder();
                enc.Frames.Add(Media.Imaging.BitmapFrame.Create(source));
                enc.Save(outStream);
                bitmap = new Bitmap(outStream);
            }
            return bitmap;
        }

    }
}


void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            this.depthImage.Source = e.OpenDepthImageFrame().ToBitmap().ToBitmapSource();
        }

        void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
        {
            this.rgbImage.Source = e.OpenColorImageFrame().ToBitmapSource();
        }
原文地址:https://www.cnblogs.com/sprint1989/p/3859035.html