日期:2014-05-17 浏览次数:20746 次
上文的代码中,对于每一个彩色图像帧,都会创建一个新的Bitmap对象。由于Kinect视频摄像头默认采集频率为每秒30幅,所以应用程序每秒会创建30个bitmap对象,产生30次的Bitmap内存创建,对象初始化,填充像素数据等操作。这些对象很快就会变成垃圾等待垃圾回收器进行回收。对数据量小的程序来说可能影响不是很明显,但当数据量很大时,其缺点就会显现出来。
改进方法是使用WriteableBitmap对象。它位于System.Windows.Media.Imaging命名空间下面,该对象被用来处理需要频繁更新的像素数据。当创建WriteableBitmap时,应用程序需要指定它的高度,宽度以及格式,以使得能够一次性为WriteableBitmap创建好内存,以后只需根据需要更新像素即可。
使用WriteableBitmap代码改动地方很小。下面的代码中,首先定义三个新的成员变量,一个是实际的WriteableBitmap对象,另外两个用来更新像素数据。每一幅图像的大小都是不变的,因此在创建WriteableBitmap时只需计算一次即可。
InitializeKinect方法中加粗的部分是更改的代码。创建WriteableBitmap对象,准备接收像素数据,图像的范围同时也计算了。在初始化WriteableBitmap的时候,同时也绑定了UI元素(名为ColorImageElement的Image对象)。此时WriteableBitmap中没有像素数据,所以UI上是空的。
private WriteableBitmap _ColorImageBitmap; private Int32Rect _ColorImageBitmapRect; private int _ColorImageStride; private byte[] _ColorImagePixelData; if (kinectSensor != null) { ColorImageStream colorStream=kinectSensor.ColorStream; colorStream.Enable(); this.colorImageBitMap = new WriteableBitmap(colorStream.FrameWidth, colorStream.FrameHeight, 96, 96, PixelFormats.Bgr32, null); this.colorImageBitmapRect = new Int32Rect(0, 0, colorStream.FrameWidth, colorStream.FrameHeight); this.colorImageStride = colorStream.FrameWidth * colorStream.FrameBytesPerPixel; ColorImageElement.Source = this.colorImageBitMap; kinectSensor.ColorFrameReady += kinectSensor_ColorFrameReady; kinectSensor.Start(); }
还需要进行的一处改动是,对ColorFrameReady事件响应的代码。如下图。首先删除之前创建Bitmap那部分的代码。调用WriteableBitmap对象的WritePixels方法来更新图像。方法使用图像的矩形范围,代码像素数据的数组,图像的Stride,以及偏移(offset).偏移量通常设置为0。
private void Kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) { using (ColorImageFrame frame = e.OpenColorImageFrame()) { if (frame != null) { byte[] pixelData = new byte[frame.PixelDataLength]; frame.CopyPixelDataTo(pixelData); this._ColorImageBitmap.WritePixels(this._ColorImageBitmapRect, pixelData, this._ColorImageStride, 0); } } }
基于Kinect的应用程序在无论是在显示ColorImageStream数据还是显示DepthImageStream数据的时候,都应该使用WriteableBitmap对象来显示帧影像。在最好的情况下,彩色数据流会每秒产生30帧彩色影像,这意味着对内存资源的消耗比较大。WriteableBitmap能够减少这种内存消耗,减少需要更新影响带来的内存开辟和回收操作。毕竟在应用中显示帧数据不是应用程序的最主要功能,所以在这方面减少内像存消耗显得很有必要。
每一帧ColorImageFrame都是以字节序列的方式返回原始的像素数据。应用程序必须以这些数据创建图像。这意味这我们可以对这些原始数据进行一定的处理,然后再展示出来。下面来看看如何对获取的原始数据进行一些简单的处理。
void kinectSensor_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) { using (ColorImageFrame frame = e.OpenColorImageFrame()) { if (frame != null) { byte[] pixelData = new byte[frame.PixelDataLength]; frame.CopyPixelDataTo(pixelData); for (int i = 0; i<pixelData.Length; i += frame.BytesPerPixel) { pixelData[i] = 0x00;//蓝色 pixelData[i + 1] = 0x00;//绿色 } this.colorImageBitMap.WritePixels(this.colorImageBitmapRect, pixelData,this.colorImageStride,0); } } }
以上的实验关闭了每个像素点的蓝色和绿色通道。for循环遍历每个像素,使得i的起始位置重视该像素的第一个字节。由于数据的格式是Bgr32,即RGB32位(一个像素共占4个字节,每个字节8位),所以第一个字节是蓝色通道,第二个是绿色,第三个是红色。循环体类,将第一个和第二个通道设置为0.所以输出的代码中只用红色通道的信息。这是最基本的图像处理。
代码中对像素的操作和像素着色函数相识,可以通过很复杂的算法来进行。大家可以试试对这些像素赋予一些其它的值然后再查看图像的显示结果。这类操作通常很消耗计算资源。像素着色通常是GPU上的一些很基础的操作。下面有一些简单的算法用来对像素进行处理。