65.9K
CodeProject 正在变化。 阅读更多。
Home

Kinect – 入门 – 变身不可思议的绿巨人

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (89投票s)

2011 年 6 月 18 日

Ms-PL

6分钟阅读

viewsIcon

309910

downloadIcon

16168

Kinect 入门 - 创建项目、控制摄像头角度并使用骨骼跟踪

引言

2011 年 6 月 16 日,微软发布了 Kinect .NET SDK Beta,我立即下载并尝试了一下,效果惊人!!!

在本文中,我将向您展示如何开始使用 Kinect SDK,然后我们将继续介绍如何使用 SDK 控制摄像头角度,最后我将通过一个不错的示例展示如何 变身令人难以置信的绿巨人

背景

Kinect for Windows SDK Beta 是一个面向应用程序开发者的编程工具包。它使学术界和爱好者社区能够轻松访问连接到运行 Windows 7 操作系统的计算机的 Microsoft Kinect 设备所提供的功能。

Kinect for Windows SDK Beta 包含驱动程序、用于原始传感器流和人体运动跟踪的丰富 API、安装文档和资源材料。它通过 Microsoft Visual Studio 2010 为使用 C++、C# 或 Visual Basic 构建应用程序的开发者提供了 Kinect 功能。

步骤 1 – 准备您的环境

为了使用 Kinect .NET SDK,您需要满足以下要求:

支持的操作系统和架构

  • Windows 7 (x86 或 x64)

硬件要求

  • 配备双核、2.66 GHz 或更快的处理器
  • 支持 Microsoft® DirectX® 9.0c 功能的 Windows 7 兼容显卡
  • 2 GB RAM
  • Kinect for Xbox 360® 传感器—零售版,包括专用的 USB/电源线

软件要求

步骤 2:创建新的 WPF 项目

添加对 Microsoft.Research.Kinect.Nui 的引用(位于 - C:\Program Files (x86)\Microsoft Research KinectSDK)并确保示例的解决方案文件目标为 **x86 平台**,因为此 Beta SDK 仅包含 x86 库。

2.png

应用程序必须通过调用 Runtime.Initialize 来初始化 Kinect 传感器,然后才能在 Runtime 对象上调用任何其他方法。Runtime.Initialize 会初始化内部帧捕获引擎,该引擎会启动一个线程,用于从 Kinect 传感器检索数据,并在帧准备好时向应用程序发出信号。它还会初始化收集和处理传感器数据的子系统。如果 Initialize 方法未能找到 Kinect 传感器,它将抛出 InvalidOperationException,因此 Runtime.Initialize 的调用出现在 try/catch 块中。

创建 Windows 加载事件并调用 InitializeNui

private void InitializeNui()
{
    try
    {
        //Declares _kinectNui as a Runtime object, 
        //which represents the Kinect sensor instance.
        _kinectNui = new Runtime();

        //Open the video and depth streams, and sets up the event handlers 
        //that the runtime calls when a video, depth, or skeleton frame is ready
        //An application must initialize the Kinect sensor by calling 
        //Runtime.Initialize before calling any other methods on the Runtime object. 
        _kinectNui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex |
                        RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);

        //To stream color images:
        //  •	The options must include UseColor.
        //  •	Valid image resolutions are Resolution1280x1024 and Resolution640x480.
        //  •	Valid image types are Color, ColorYUV, and ColorYUVRaw.
        _kinectNui.VideoStream.Open(ImageStreamType.Video, 2,
                                ImageResolution.Resolution640x480, ImageType.ColorYuv);

        //To stream depth and player index data:
        //  •	The options must include UseDepthAndPlayerIndex.
        //  •	Valid resolutions for depth and player index data are 
        //Resolution320x240 and Resolution80x60.
        //  •	The only valid image type is DepthAndPlayerIndex.
        _kinectNui.DepthStream.Open(ImageStreamType.Depth, 2, 
		ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);

        lastTime = DateTime.Now;

        _kinectNui.VideoFrameReady += 
		new EventHandler<ImageFrameReadyEventArgs>(NuiVideoFrameReady);
        _kinectNui.DepthFrameReady += 
		new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);
    }
    catch (InvalidOperationException ex)
    {
        MessageBox.Show(ex.Message);
    }
} 

步骤 3:显示视频

Video Depth 都返回 PlanarImage ,我们只需要创建一个新的 Bitmap 并在 UI 上显示。

视频帧就绪事件处理程序

 void NuiVideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
    PlanarImage Image = e.ImageFrame.Image;

    image.Source = BitmapSource.Create(
        Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, null,
        Image.Bits, Image.Width * Image.BytesPerPixel);

    imageCmyk32.Source = BitmapSource.Create(
        Image.Width, Image.Height, 96, 96, PixelFormats.Cmyk32, null,
        Image.Bits, Image.Width * Image.BytesPerPixel);
} 

深度帧就绪事件处理程序

深度不同,因为返回的图像是 16 位,我们需要将其转换为 32 位,我使用了与 SDK 中相同的方法。

 void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
    var Image = e.ImageFrame.Image;
    var convertedDepthFrame = convertDepthFrame(Image.Bits);

    depth.Source = BitmapSource.Create(
        Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, 
			null, convertedDepthFrame, Image.Width * 4);

    CalculateFps();
}

// Converts a 16-bit grayscale depth frame which includes player 
// indexes into a 32-bit frame
// that displays different players in different colors
byte[] convertDepthFrame(byte[] depthFrame16)
{
    for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && 
		i32 < depthFrame32.Length; i16 += 2, i32 += 4)
    {
        int player = depthFrame16[i16] & 0x07;
        int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);
        // transform 13-bit depth information into an 8-bit intensity appropriate
        // for display (we disregard information in most significant bit)
        byte intensity = (byte)(255 - (255 * realDepth / 0x0fff));

        depthFrame32[i32 + RED_IDX] = intensity;
        depthFrame32[i32 + BLUE_IDX] = intensity;
        depthFrame32[i32 + GREEN_IDX] = intensity;
    }
    return depthFrame32;
}        

void CalculateFps()
{
    ++totalFrames;

    var cur = DateTime.Now;
    if (cur.Subtract(lastTime) > TimeSpan.FromSeconds(1))
    {
        int frameDiff = totalFrames - lastFrames;
        lastFrames = totalFrames;
        lastTime = cur;
        frameRate.Text = frameDiff.ToString() + " fps";
    }
}  

1.png

步骤 4:控制摄像头角度

现在,我将展示控制 Kinect 摄像头角度(更改摄像头位置)有多么容易。

您可以控制的最小和最大角度是存在的,但正如您从最后一张图片(右侧)中看到的,您可以手动移动 Kinect 传感器,角度会自动更改。

3.png 4.png 5.png

在初始化之后,创建一个 Kinect Nui 对象。

private Camera _cam;

_cam = _kinectNui.NuiCamera;
txtCameraName.Text = _cam.UniqueDeviceName; 

这是摄像头定义

namespace Microsoft.Research.Kinect.Nui
{
    public class Camera
    {
        public static readonly int ElevationMaximum;
        public static readonly int ElevationMinimum;

        public int ElevationAngle { get; set; }
        public string UniqueDeviceName { get; }

        public void GetColorPixelCoordinatesFromDepthPixel
			(ImageResolution colorResolution, ImageViewArea viewArea,
                             int depthX, int depthY, short depthValue, out int colorX,
                             out int colorY);
    }
} 

步骤 5:上下移动

现在,当您执行此操作时,可以按如下方式控制摄像头角度:
要增加摄像头角度,您只需要增加摄像头的 ElevationAngle,摄像头有 Min Max 角度可以控制,所以不要害怕将其推得太远。

private void BtnCameraUpClick(object sender, RoutedEventArgs e)
{
    try
    {
        _cam.ElevationAngle = _cam.ElevationAngle + 5;
    }
    catch (InvalidOperationException ex)
    {
        MessageBox.Show(ex.Message);
    }
    catch (ArgumentOutOfRangeException outOfRangeException)
    {
        //Elevation angle must be between Elevation Minimum/Maximum"
        MessageBox.Show(outOfRangeException.Message);
    }
} 

以及向下

private void BtnCameraDownClick(object sender, RoutedEventArgs e)
{
    try
    {
        _cam.ElevationAngle = _cam.ElevationAngle - 5;
    }
    catch (InvalidOperationException ex)
    {
        MessageBox.Show(ex.Message);
    }
    catch (ArgumentOutOfRangeException outOfRangeException)
    {
        //Elevation angle must be between Elevation Minimum/Maximum"
        MessageBox.Show(outOfRangeException.Message);
    }
} 

背景:通过骨骼跟踪变身令人难以置信的绿巨人

Kinect for Windows SDK 的一个主要优势在于它能够快速识别站在传感器前的人体骨骼关节,并且无需任何训练即可使用。

NUI Skeleton API 提供有关站在 Kinect 传感器阵列前的最多两个玩家的位置信息,并附带详细的位置和方向信息。

数据以一组点(称为骨骼位置)的形式提供给应用程序代码,这些点组成一个骨骼,如下图所示。此骨骼代表用户当前的位置和姿势。

使用骨骼数据的应用程序必须在 NUI 初始化时指明这一点,并必须启用骨骼跟踪。

《维特鲁威人》有 20 个点,在 Kinect SDK 中称为 Joints。

7.png 8.png

步骤 6:注册到 SkeletonFrameReady

确保您使用 **UseSkeletalTracking** 进行 Initialize ,否则骨骼跟踪将不起作用。

_kinectNui.Initialize(RuntimeOptions.UseColor | 
	RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
_kinectNui.SkeletonFrameReady += 
	new EventHandler<SkeletonFrameReadyEventArgs>(SkeletonFrameReady); 

Kinect NUI 最多只能跟踪 2 个骨骼,

if (SkeletonTrackingState.Tracked != data.TrackingState) continue;   

表示该骨骼正在被跟踪,未被跟踪的骨骼仅提供其位置而不提供关节信息;如果全身都适合画面,则会渲染骨骼。

为 Kinect 开发时,调试并非易事——每次想要测试时都需要站起来。

骨骼关节由 TrackingID enum 标记,该枚举定义了其参考位置

namespace Microsoft.Research.Kinect.Nui
{
    public enum JointID
    {
        HipCenter,
        Spine,
        ShoulderCenter,
        Head,
        ShoulderLeft,
        ElbowLeft,
        WristLeft,
        HandLeft,
        ShoulderRight,
        ElbowRight,
        WristRight,
        HandRight,
        HipLeft,
        KneeLeft,
        AnkleLeft,
        FootLeft,
        HipRight,
        KneeRight,
        AnkleRight,
        FootRight,
        Count,
    }
}

步骤 7:获取关节位置

Joint 的位置定义在 Camera Space 中,我们需要将其转换为我们的 Size Position

深度图像空间

深度图的图像帧大小为 640x480、320x240 或 80x60 像素,每个像素代表在特定 x 和 y 坐标处到最近对象的距离(以毫米为单位)。像素值为 0 表示传感器在此位置未找到任何对象。图像帧的 x 和 y 坐标不代表房间中的物理单位,而是深度成像传感器上的像素。x 和 y 坐标的解释取决于光学和成像传感器的具体细节。出于讨论目的,此投影空间被称为深度图像空间。

骨骼空间

玩家骨骼位置以 x、y 和 z 坐标表示。与深度图像空间的坐标不同,这三个坐标以表示。x、y 和 z 轴是深度传感器的身体轴。这是一个右手坐标系,将传感器阵列置于原点,正 z 轴沿传感器阵列指向的方向延伸。正 y 轴向上延伸,正 x 轴向左延伸(相对于传感器阵列),如图 5 所示。出于讨论目的,此坐标表示法被称为骨骼空间。

9.png

private Point getDisplayPosition(Joint joint)
{
    float depthX, depthY;
    _kinectNui.SkeletonEngine.SkeletonToDepthImage(joint.Position, out depthX, out depthY);
    depthX = Math.Max(0, Math.Min(depthX * 320, 320));  //convert to 320, 240 space
    depthY = Math.Max(0, Math.Min(depthY * 240, 240));  //convert to 320, 240 space
    int colorX, colorY;
    ImageViewArea iv = new ImageViewArea();
    // only ImageResolution.Resolution640x480 is supported at this point
    _kinectNui.NuiCamera.GetColorPixelCoordinatesFromDepthPixel
	(ImageResolution.Resolution640x480, iv, (int)depthX, (int)depthY, 
	(short)0, out colorX, out colorY);

    // map back to skeleton.Width & skeleton.Height
    return new Point((int)(imageContainer.Width * colorX / 640.0) - 30, 
	(int)(imageContainer.Height * colorY / 480) - 30);
} 

步骤 8:根据关节类型放置图像

类型为 Vector4 的位置(xyzw — 前三个属性定义了在相机空间中的位置。最后一个属性(w)给出位置的质量级别(范围在 0-1 之间),表示该骨骼的重心。

此值是唯一可用于被动玩家的位置值。

void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
    foreach (SkeletonData data in e.SkeletonFrame.Skeletons)
    {
        //Tracked that defines whether a skeleton is 'tracked' or not. 
        //The untracked skeletons only give their position. 
        if (SkeletonTrackingState.Tracked != data.TrackingState) continue;

        //Each joint has a Position property that is defined by a Vector4: (x, y, z, w). 
        //The first three attributes define the position in camera space. 
        //The last attribute (w)
        //gives the quality level (between 0 and 1) of the 
        foreach (Joint joint in data.Joints)
        {
            if (joint.Position.W < 0.6f) return;// Quality check 
            switch (joint.ID)
            {
                case JointID.Head:
                    var heanp = getDisplayPosition(joint);

                    Canvas.SetLeft(imgHead, heanp.X);
                    Canvas.SetTop(imgHead, heanp.Y);

                    break;
                case JointID.HandRight:
                    var rhp = getDisplayPosition(joint);

                    Canvas.SetLeft(imgRightHand, rhp.X);
                    Canvas.SetTop(imgRightHand, rhp.Y);
                    break;
                case JointID.HandLeft:
                    var lhp = getDisplayPosition(joint);

                    Canvas.SetLeft(imgLefttHand, lhp.X);
                    Canvas.SetTop(imgLefttHand, lhp.Y);
                    break;
            }
        }
    }
} 

6.png

尽情享用!

历史

  • 2011 年 6 月 18 日:初始发布
© . All rights reserved.