作者:马宁

    终于可以坐下来继续这个系列了,在微博和博客园里,已经有无数人催过了,实在抱歉,一个人的精力毕竟是有限的,但我会抽出一切可用的时间,尽可能尽快完成。

    这一章我们来说说景深数据Depth Data,这是Kinect的Depth Camera为我们提供的全新功能,以往的技术只能够通过图像识别来完成的一些工作,我们可以借助景深来帮我们完成了。比如,前景与背景的分离,以前只能将背景设置为蓝屏或者绿屏,但是现在有了景深数据,我们就可以很容易地将前景物体从背景中分离出来。当然,需要特别说明的是,Depth Camera技术是由以色列公司PrimeSense提供的。

程序布局

    在这一章里,我们要完成的工作非常简单,根据物体距离Kinect的远近,设置成不同的颜色。首先,我们要创建一个新的WPF工程,然后添加一个Image控件:

  1. <Image Height="240" HorizontalAlignment="Left" Margin="62,41,0,0" Name="p_w_picpath1" Stretch="Fill" VerticalAlignment="Top" Width="320" /> 

    然后是MainWindow.xaml.cs中的核心代码,这部分代码与之前的代码大体一致,所以不做过多解释了:

  1. //Kinect RuntimeRuntime   
  2. Runtime nui = new Runtime();   
  3. private void Window_Loaded(object sender, RoutedEventArgs e)  
  4. {  
  5. //UseDepthAndPlayerIndex and UseSkeletalTracking  
  6. nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking);  
  7. //register for event  
  8. nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);  
  9. //DepthAndPlayerIndex ImageType  
  10. nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240,  
  11. ImageType.DepthAndPlayerIndex);  
  12. }  
  13. private void Window_Closed(object sender, EventArgs e)  
  14. {  
  15. nui.Uninitialize();  

    唯一需要大家注意的是,我们在初始化函数中传递的参数是RuntimeOptions.UseDepthAndPlayerIndex,这表示景深信息中会包含PlayerIndex的信息。接下来,我们就要花点时间来理解DepthAndPlayerIndex的实际结构。

理解DepthAndPlayerIndex

    先来看DepthFrameReady事件处理函数,如下:

  1. void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)  
  2. {  
  3. // Convert depth information for a pixel into color information   
  4. byte[] ColoredBytes = GenerateColoredBytes(e.ImageFrame);   
  5. // create an p_w_picpath based on the colored bytes   
  6. PlanarImage p_w_picpath = e.ImageFrame.Image;  
  7. p_w_picpath1.Source = BitmapSource.Create(   
  8. p_w_picpath.Width, p_w_picpath.Height, 96, 96, PixelFormats.Bgr32,   
  9. null, ColoredBytes, p_w_picpath.Width * PixelFormats.Bgr32.BitsPerPixel / 8);   

    DepthFrameReady事件会返回一个ImageFrame对象,其中会包含一个PlanarImage对象。PlanarImage对象会包含一个byte[]数组,这个数组中包含每个像素的深度信息。这个数组的特点是,从图像左上角开始、从左到右,然后从上到下。

    该数组中每个像素由两个bytes表示,我们需要通过位运算的办法来获取每个像素的位置信息。

    如果是Depth Image Type,将第二个bytes左移8位即可。

  1. Distance (0,0) = (int)(Bits[0] | Bits[1] << 8 ); 

    如果是DepthAndPlayerIndex Image Type,第一个bytes右移三位去掉Player Index信息,第二个bytes左移5位。

  1. Distance (0,0) =(int)(Bits[0] >> 3 | Bits[1] << 5); 

    DepthAndPlayerIndex p_w_picpath类型的第一个bytes前三位包括Player Index信息。Player Index最多包含六个可能值:

0, 没有玩家;1,玩家1;2,玩家2,以此类推。

    我们可以用下面的方法来获取Player Index信息:

  1. private static int GetPlayerIndex(byte firstFrame)  
  2. {  
  3. //returns 0 = no player, 1 = 1st player, 2 = 2nd player...  
  4. return (int)firstFrame & 7;   

设置颜色

    接下来,我们创建一个bytes数组,用来存储颜色值。

  1. if (distance <= 900)  
  2. {  
  3. //we are very close 
  4. colorFrame[index + BlueIndex] = 255;  
  5. colorFrame[index + GreenIndex] = 0;  
  6. colorFrame[index + RedIndex] = 0;  
  7. }  
  8. if (GetPlayerIndex(depthData[depthIndex]) > 0)  
  9. {  
  10. //we are the farthest  
  11. colorFrame[index + BlueIndex] = 0;  
  12. colorFrame[index + GreenIndex] = 255;  
  13. colorFrame[index + RedIndex] = 255;  

    当然,我们最好是设置几个边界值,来定义不同距离的不同颜色:

  1. //equal coloring for monochromatic histogram  
  2. var intensity = CalculateIntensityFromDepth(distance);  
  3. colorFrame[index + BlueIndex] = intensity;  
  4. colorFrame[index + GreenIndex] = intensity;  
  5. colorFrame[index + RedIndex] = intensity;  
  6. const float MaxDepthDistance = 4000; // max value returned  
  7. const float MinDepthDistance = 850; // min value returned  
  8. const float MaxDepthDistanceOffset = MaxDepthDistance - MinDepthDistance;  
  9. public static byte CalculateIntensityFromDepth(int distance)  
  10. {  
  11. //formula for calculating monochrome intensity for histogram  
  12. return (byte)(255 - (255 * Math.Max(distance - MinDepthDistance, 0) / (MaxDepthDistanceOffset)));  

    最后,把所有代码放在一起,我们设定的颜色值为:

蓝色,小于900;900到2000,绿色;大于2000,红色

    代码如下:

  1. private byte[] GenerateColoredBytes(ImageFrame p_w_picpathFrame)  
  2.         {      
  3.             int height = p_w_picpathFrame.Image.Height;      
  4.             int width = p_w_picpathFrame.Image.Width;              
  5.             //Depth data for each pixel      
  6.             Byte[] depthData = p_w_picpathFrame.Image.Bits;                    
  7.             //colorFrame contains color information for all pixels in p_w_picpath      
  8.             //Height x Width x 4 (Red, Green, Blue, empty byte)      
  9.             Byte[] colorFrame = new byte[p_w_picpathFrame.Image.Height * p_w_picpathFrame.Image.Width * 4];                   
  10.             //Bgr32  - Blue, Green, Red, empty byte      
  11.             //Bgra32 - Blue, Green, Red, transparency       
  12.             //You must set transparency for Bgra as .NET defaults a byte to 0 = fully transparent       
  13.             //hardcoded locations to Blue, Green, Red (BGR) index positions             
  14.             const int BlueIndex = 0;      
  15.             const int GreenIndex = 1;      
  16.             const int RedIndex = 2;       
  17.             var depthIndex = 0;                  
  18.             for (var y = 0; y < height; y++)      
  19.             {                  
  20.                 var heightOffset = y * width;           
  21.                   
  22.                 for (var x = 0; x < width; x++)          
  23.                 {              
  24.                     var index = ((width - x - 1) + heightOffset) * 4;                                   
  25.                     //var distance = GetDistance(depthData[depthIndex], depthData[depthIndex + 1]);              
  26.                     var distance = GetDistanceWithPlayerIndex(depthData[depthIndex], depthData[depthIndex + 1]);                      
  27.                     if (distance <= 900)              
  28.                     {                  
  29.                         //we are very close                  
  30.                         colorFrame[index + BlueIndex] = 255;                  
  31.                         colorFrame[index + GreenIndex] = 0;                  
  32.                         colorFrame[index + RedIndex] = 0;                     
  33.                     }              
  34.                     else if (distance > 900 && distance < 2000)              
  35.                     {                  
  36.                         //we are a bit further away                  
  37.                         colorFrame[index + BlueIndex] = 0;                  
  38.                         colorFrame[index + GreenIndex] = 255;                  
  39.                         colorFrame[index + RedIndex] = 0;              
  40.                     }              
  41.                     else if (distance > 2000)              
  42.                     {                  
  43.                         //we are the farthest                  
  44.                         colorFrame[index + BlueIndex] = 0;                  
  45.                         colorFrame[index + GreenIndex] = 0;                  
  46.                         colorFrame[index + RedIndex] = 255;              
  47.                     }                                   
  48.                     //Color a player              
  49.                     if (GetPlayerIndex(depthData[depthIndex]) > 0)              
  50.                     {                  
  51.                         //we are the farthest                  
  52.                         colorFrame[index + BlueIndex] = 0;                  
  53.                         colorFrame[index + GreenIndex] = 255;                  
  54.                         colorFrame[index + RedIndex] = 255;              
  55.                     }                                   
  56.                     //jump two bytes at a time              
  57.                     depthIndex += 2;                  
  58.                 }      
  59.             }                   
  60.             return colorFrame;  
  61.         } 

    程序的现实效果如下图:

Kinect for Windows SDK开发初体验(四)景深数据_休闲

写到最后

    到这里,我们就将Kinect SDK中NUI的基本内容介绍完了,接下来,我们要介绍Audio部分,或者借助一些实际的例子,来看一下Kinect SDK的实际应用。