zoukankan      html  css  js  c++  java
  • Kinect 深度测量原理

    和其他摄像机一样,近红外摄像机也有视场。Kinect摄像机的视野是有限的,如下图所示:

         如图,红外摄像机的视场是金字塔形状的。离摄像机远的物体比近的物体拥有更大的视场横截面积。这意味着影像的高度和宽度,比如640X480和摄像机视场的物理位置并不一一对应。但是每个像素的深度值是和视场中物体离摄像机的距离是对应的。深度帧数据中,每个像素占16位,这样BytesPerPixel属性,即每一个像素占2个字节。每一个像素的深度值只占用了16个位中的13个位。如下图:

         获取每一个像素的距离很容易,但是要直接使用还需要做一些位操作。可能大家在实际编程中很少情况会用到位运算。如上图所示,深度值存储在第3至15位中,要获取能够直接使用的深度数据需要向右移位,将游戏者索引(Player Index)位移除。后面将会介绍游戏者索引位的重要性。下面的代码简要描述了如何获取像素的深度值。代码中pixelData变量就是从深度帧数据中获取的short数组。PixelIndex基于待计算像素的位置就算出来的。SDK在DepthImageFrame类中定义了一个常量PlayerIndexBitmaskWidth,它定义了要获取深度数据值需要向右移动的位数。在编写代码时应该使用这一常量而不是硬编码,因为未来随着软硬件水平的提高,Kinect可能会增加能够同时识别人数的个数,从而改变PlayerIndexBitmaskWidth常量的值。

    1 Int32 pixelIndex = (Int32)(p.X + ((Int32)p.Y * frame.Width));
    2 Int32 depth = this.depthPixelDate[pixelIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth;

    显示深度数据最简单的方式是将其打印出来。我们要将像素的深度值显示到界面上,当鼠标点击时,显示鼠标点击的位置的像素的深度值。第一步是在主UI界面上添加一个TextBlock:

    <Window x:Class="KinectDepthImageDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="KinectDepthImage" Height="600" Width="1280" WindowStartupLocation="CenterScreen">
        <Grid>
            <StackPanel Orientation="Horizontal">
                <TextBlock x:Name="PixelDepth" FontSize="48" HorizontalAlignment="Left"  />
                <Image x:Name="DepthImage"  Width="640" Height="480" ></Image>
            </StackPanel>
        </Grid>
    </Window>

    接着我们要处理鼠标点击事件。在添加该事件前,需要首先添加一个私有变量lastDepthFrame来保存每一次DepthFrameReady事件触发时获取到的DepthFrame值。因为我们保存了对最后一个DepthFrame对象的引用,所以事件处理代码不会马上释放该对象。然后,注册DepthFrame 图像控件的MouseLeftButtonUp事件。当用户点击深度图像时,DepthImage_MouseLeftButtonUp事件就会触发,根据鼠标位置获取正确的像素。最后一步将获取到的像素值的深度值显示到界面上,代码如下:

    void kinectSensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
    {
       if (lastDepthFrame!=null)
       {
           lastDepthFrame.Dispose();
           lastDepthFrame = null;
       }
        lastDepthFrame = e.OpenDepthImageFrame();
        if (lastDepthFrame != null)
        {
            depthPixelDate = new short[lastDepthFrame.PixelDataLength];
            lastDepthFrame.CopyPixelDataTo(depthPixelDate);
            depthImageBitMap.WritePixels(depthImageBitmapRect, depthPixelDate, depthImageStride, 0);
        }
    }
    private void DepthImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        Point p = e.GetPosition(DepthImage);
        if (depthPixelDate != null && depthPixelDate.Length > 0)
        {
            Int32 pixelIndex = (Int32)(p.X + ((Int32)p.Y * this.lastDepthFrame.Width));
            Int32 depth = this.depthPixelDate[pixelIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth;
            Int32 depthInches = (Int32)(depth * 0.0393700787);
            Int32 depthFt = depthInches / 12;
            depthInches = depthInches % 12;
            PixelDepth.Text = String.Format("{0}mm~{1}'{2}", depth, depthFt, depthInches);
        }
    }

     有一点值得注意的是,在UI界面中Image空间的属性中,宽度和高度是硬编码的。如果不设置值,那么空间会随着父容器(From窗体)的大小进行缩放,如果空间的长宽尺寸和深度数据帧的尺寸不一致,当鼠标点击图片时,代码就会返回错误的数据,在某些情况下甚至会抛出异常。像素数组中的数据是固定大小的,它是根据DepthImageStream的Enable方法中的DepthImageFormat参数值来确定的。如果不设置图像控件的大小,那么他就会根据Form窗体的大小进行缩放,这样就需要进行额外的计算,将鼠标的在Form中的位置换算到深度数据帧的维度上。这种缩放和空间转换操作很常见,在后面的文章中我们将会进行讨论,现在为了简单,对图像控件的尺寸进行硬编码。

        结果如下图,由于截屏时截不到鼠标符号,所以用红色点代表鼠标位置,下面最左边图片中的红色点位于墙上,该点距离Kinect 2.905米,中间图的点在我的手上,可以看出手离Kinect距离为1.221米,实际距离和这个很相近,可见Kinect的景深数据还是很准确的。

         上面最右边图中白色点的深度数据为-1mm。这表示Kinect不能够确定该像素的深度。在处理上数据时,这个值通常是一个特殊值,可以忽略。-1深度值可能是物体离Kinect传感器太近了的缘故。

  • 相关阅读:
    LeetCode 16 3sum closest
    LeetCode 15 3Sum
    LeetCode 14 Longest Common Prefix
    jQuery数组遍历
    jQuery DOM对象与jQuery对象转换
    jQuery选择器
    jQuery类和样式操作
    js节点操作
    js全选反选
    innerHTML和innerTEXT的区别
  • 原文地址:https://www.cnblogs.com/shouhouxiaomuwu/p/3389589.html
Copyright © 2011-2022 走看看