Quantcast
Channel: 英特尔开发人员专区文章
Viewing all articles
Browse latest Browse all 154

使用英特尔® 实感™ SDK 手标模块创建虚拟摇杆

$
0
0

摘要

本文演示了使用英特尔® 实感™ SDK中包含的全新手标模块,创建虚拟摇杆应用的代码编写过程。 本项目使用 C#/XAML 开发,并可以使用 Microsoft Visual Studio* 2015构建。


图 1:控制 Google Earth* 飞行模拟器的 RS 摇杆应用。

简介

英特尔实感 SDK 的 R5 中推出了对全新 英特尔® 实感™SR300型摄像头的支持。 SR300 是 F200 型的后续产品,提供了一组改进,以及被称为手标模块的全新功能。

正如 SDK 文档中所介绍的,手标模块会返回手上的单个点,实现准确、响应迅捷的跟踪。 其目的是,促进基于手部的 UI 控制使用案例,以及对有限手势集的支持。

本文介绍的摇杆模拟器应用 RS Joystick 将 SDK 提供的 3D 手部数据映射到虚拟摇杆控件,无需手即可与摇杆控制器使用的软件应用交互。

RS Joystick 应用充分利用了以下手标模块功能:

  • 体侧类型– 该应用会基于近远进入顺序,通知用户正在控制虚拟摇杆的手。
  • 鼠标单击手势– 用户可以做手指点击手势,切换虚拟摇杆控制器按钮 1 的开-关状态。
  • 自适应点跟踪– 应用会在手标模块定义的想象“边界框”内显示基准 3D 点,并使用此数据控制虚拟摇杆的 x、y 和 z 轴。
  • 提醒数据– 当用户的手超出 SR300 摄像头的范围时,应用会使用未检测到光标光标脱离光标在边界之外提醒,并将摇杆边界从绿色变成红色。

(有关手标模块的更多信息,请访问“您可以使用英特尔实感光标模式做什么?”)

前提条件

您应拥有一些 C# 知识,同时了解 Visual Studio 的基本操作,如生成可执行文件。 拥有添加第三方库到自定义软件项目的经验非常有用,但本演示会提供详细的步骤(如果还不熟悉)。 您的系统需要安装前置 SR300 摄像头、最新版本的 SDK 和英特尔® 实感™ Depth Camera Manager (DCM),同时必须满足此处列出的硬件要求。 最后,您的系统必须运行 Microsoft Windows* 10 Threshold 2。

第三方软件

除了英特尔实感 SDK之外,该项目还包含了名为vJoy* 的第三方虚拟摇杆设备驱动程序,以及一些动态链接库 (DLL)。 这些软件组件不是本定制项目相关的任何分布式代码的一部分,因此以下提供了下载和安装设备驱动程序的详细信息。

安装英特尔实感 SDK

https://software.intel.com/zh-cn/intel-realsense-sdk/download下载并安装所需 DCM 和 SDK。 编写本文时,这些组件的当前版本是:

  • 英特尔实感 Depth Camera Manager (SR300) v3.1.25.1077
  • 英特尔实感 SDK v8.0.24.6528

安装 vJoy 设备驱动程序和 SDK

下载并安装 vJoy 设备驱动程序:http://vjoystick.sourceforge.net/site/index.php/download-a-install/72-download。 当收到指示完成安装时,重启计算机。

安装后,vJoy 设备驱动程序会显示在设备管理器中的人机界面设备下(参见图 2)。


图 2:设备管理器。

接下来,打开 Windows 10 开始菜单并选择所有应用。 您会找到几个已安装的 vJoy 组件,如图 3 所示。


图 3:Windows 开始菜单。

要打开默认浏览器并转到下载页面,单击 vJoy SDK按钮。

下载后,复制 .zip 文件到临时文件夹,解压缩,然后找到 \SDK\c#\x86 中的 C# DLL。

一旦创建,我们将添加这些 DLL 到 Visual Studio 项目,如同后续步骤所述。

创建一个新的 Visual Studio 项目

  • 启动 Visual Studio 2015。
  • 从菜单栏选择文件新建,项目…。
  • 在“新建项目”屏幕中,展开“模板”并选择 Visual C#Windows
  • 选择 WPF 应用
  • 指定新项目的位置及其名称。 对于本项目,我们的位置是 C:\,同时应用名称是 RsJoystick

图 4 显示了本项目使用的新项目设置。


图 4:Visual Studio* 新项目设置。

单击“确定”,创建项目。

复制库到项目

在 C# 中创建英特尔® 实感™ 应用需要两个 DLL:

  • libpxcclr.cs.dll – 托管 C# 接口 DLL
  • libpxccpp2c.dll – 非托管 C++ P/Invoke DLL

类似地,需要两个 DLL 以便应用与 vJoy 设备驱动程序进行通信:

  • vJoyInterface.dll – C 语言 API 库
  • vJoyInterfaceWrap.dll – 针对 C 语言 API 库的 C# 打装程序

为简化项目的整体结构,我们将直接复制所有这四个文件到项目文件夹:

  • 右键单击 RsJoystick 项目并选择添加现有项目…
  • 导航到 vJoy DLL 的位置(即 \SDK\c#\x86)并同时选择 vJoyInterface.dll 和 vJoyInterfaceWrap.dll。 注意:您可能需要为文件类型指定所有文件 (*.*) 以便 DLL 可见。
  • 单击添加按钮。

类似地,复制英特尔实感 SDK DLL 到项目:

  • 右键单击 RsJoystick 项目,然后选择添加现有项目…
  • 导航到 x86 库所在的位置,在默认 SDK 安装中,这是 C:\Program Files (x86)\Intel\RSSDK\bin\win32。
  • 同时选择 libpxcclr.cs.dll 和 libpxccpp2c.dll。
  • 单击添加按钮。

现在,所有四个文件应在 RsJoystick 项目下的 Solution Explorer 中可见。

创建库的引用

物理复制所需库文件到 Visual Studio 项目后,您必须创建托管 (.NET) DLL 的参考,以便它们可以被您的应用使用。 右键单击引用(位于 RsJoystick 项目下)并选择 Reference Manager 窗口中的添加引用…,单击浏览按钮并导航到项目文件夹 (c:\RsJoystick\RsJoystick)。 同时选择 libpxcclr.cs.dll 和 vJoyInterfaceWrap.dll 文件,然后单击添加按钮。 单击 Reference Manager 中的确定按钮。

要让托管打包程序 DLL 正常运行,运行应用之前,您需要确保非托管 DLL 被复制到项目的输出文件夹。 在 Solution Explorer 中,单击 libpxccpp2c.dll 以选择。 “属性”屏幕将显示 libpxccpp2c.dll 的文件属性。 找到复制到输出目录字段,并使用下拉列表选择始终复制。 为 vJoyInterface.dll 重复本步骤。 这可确保当生成应用时,非托管 DLL 会被复制到项目输出文件夹。

此时,您可能会看到警告,显示正在生成项目的处理器架构和参考库的处理器架构不匹配。 要清除本警告,请完成以下操作:

  • 在菜单栏的下拉列表找到 Configuration Manager 的链接(参见图 5)。
  • 选择 Configuration Manager
  • 在 Configuration Manager 屏幕中,展开平台列中的下拉列表,然后选择新建
  • x86选为新平台,然后单击确定
  • 关闭 Configuration Manager 屏幕。


图 5:Configuration Manager。

此时,应可以生成和运行项目,不会出现任何错误或警告。 此外,如果查看输出文件夹 (c:\RsJoystick\RsJoystick\bin\x86\Debug) 的内容,您应看到所有四个 DLL 也被复制。

用户界面

用户界面(参见图 6)显示以下信息:

  • 基于近远进入顺序控制虚拟摇杆的用户的手(即,最靠近摄像头的手是控制手)。
  • 虚拟摇杆控制器上的按钮 1 的开-关状态,通过做手指单击手势来控制。
  • 椭圆跟踪用户的手在 x 和 y 轴的相对位置,并基于 z 轴更改直径,从而指示手部距摄像头的距离。
  • x、y 和 z 轴自适应点数据来自 SDK,这将在零至一的范围内显示为基准值。
  • 当用户的手远离 SR300 摄像头范围时,彩色边框将从绿色变成红色。
  • 滑块控件允许调整每根轴的灵敏度。


图 6:用户界面。

表 1 列出了完整 XAML 源代码列表。 这可直接复制并粘贴到创建项目时自动生成的 MainWindow.xaml 代码。

表 1:XAML 源代码列表: MainWindow.xaml。

<Window x:Class="RsJoystick.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RsJoystick"
        mc:Ignorable="d"
        Title="RSJoystick" Height="420" Width="420" Background="#FF222222" Closing="Window_Closing"><Window.Resources><Style x:Key="TextStyle" TargetType="TextBlock"><Setter Property="Foreground" Value="White"/><Setter Property="FontSize" Value="14"/><Setter Property="Text" Value="-"/><Setter Property="Margin" Value="4"/><Setter Property="HorizontalAlignment" Value="Center"/></Style></Window.Resources><StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Width="320"><TextBlock x:Name="uiBodySide" Style="{StaticResource TextStyle}"/><TextBlock x:Name="uiButtonState" Style="{StaticResource TextStyle}"/><Border x:Name="uiBorder" BorderThickness="2" Width="200" Height="200" BorderBrush="Red" Margin="4"><Canvas x:Name="uiCanvas" ClipToBounds="True"><Ellipse x:Name="uiCursor" Height="10" Width="10" Fill="Yellow"/><Ellipse Height="50" Width="50" Stroke="Gray" Canvas.Top="75" Canvas.Left="75"/><Rectangle Height="1" Width="196" Stroke="Gray" Canvas.Top="100"/><Rectangle Height="196" Width="1" Stroke="Gray" Canvas.Left="100"/></Canvas></Border><StackPanel Orientation="Horizontal" HorizontalAlignment="Center"><TextBlock x:Name="uiX" Style="{StaticResource TextStyle}" Width="80"/><Slider x:Name="uiSliderX" Width="150" ValueChanged="sldSensitivity_ValueChanged" Margin="4"/></StackPanel><StackPanel Orientation="Horizontal" HorizontalAlignment="Center"><TextBlock x:Name="uiY" Style="{StaticResource TextStyle}" Width="80"/><Slider x:Name="uiSliderY" Width="150" ValueChanged="sldSensitivity_ValueChanged" Margin="4"/></StackPanel><StackPanel Orientation="Horizontal" HorizontalAlignment="Center"><TextBlock x:Name="uiZ" Style="{StaticResource TextStyle}" Width="80"/><Slider x:Name="uiSliderZ" Width="150" ValueChanged="sldSensitivity_ValueChanged" Margin="4"/></StackPanel></StackPanel></Window>

程序源代码

表 2 显示了 RSJoystick 应用的完整 C# 源代码列表。 这可直接复制并粘贴到创建项目时自动生成的 MainWindow.xaml.cs 代码。

表 2.C# 源代码列表: MainWindow.xaml.cs

//--------------------------------------------------------------------------------------
// Copyright 2016 Intel Corporation
// All Rights Reserved
//
// Permission is granted to use, copy, distribute and prepare derivative works of this
// software for any purpose and without fee, provided, that the above copyright notice
// and this statement appear in all copies.  Intel makes no representations about the
// suitability of this software for any purpose.  THIS SOFTWARE IS PROVIDED "AS IS."
// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY,
// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE,
// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  Intel does not
// assume any responsibility for any errors which may appear in this software nor any
// responsibility to update it.
//--------------------------------------------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using vJoyInterfaceWrap;
using System.Threading;
using System.Windows.Shapes;

namespace RsJoystick
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private PXCMSenseManager sm;
        private PXCMHandCursorModule cursorModule;
        private PXCMCursorConfiguration cursorConfig;
        private vJoy joystick;
        private Thread update;
        private double joySensitivityX;
        private double joySensitivityY;
        private double joySensitivityZ;
        private const uint joyID = 1;
        private const uint MaxSensitivity = 16384;

        public MainWindow()
        {
            InitializeComponent();

            // Configure the sensitivity controls
            uiSliderX.Maximum = MaxSensitivity;
            uiSliderY.Maximum = MaxSensitivity;
            uiSliderZ.Maximum = MaxSensitivity;
            joySensitivityX = uiSliderX.Value = MaxSensitivity / 2;
            joySensitivityY = uiSliderY.Value = MaxSensitivity / 2;
            joySensitivityZ = uiSliderZ.Value = MaxSensitivity / 2;

            // Create an instance of the joystick
            joystick = new vJoy();
            joystick.AcquireVJD(joyID);

            // Configure the cursor mode module
            ConfigureRealSense();

            // Start the Update thread
            update = new Thread(new ThreadStart(Update));
            update.Start();
        }

        public void ConfigureRealSense()
        {
            // Create an instance of the SenseManager
            sm = PXCMSenseManager.CreateInstance();

            // Enable cursor tracking
            sm.EnableHandCursor();

            // Get an instance of the hand cursor module
            cursorModule = sm.QueryHandCursor();

            // Get an instance of the cursor configuration
            cursorConfig = cursorModule.CreateActiveConfiguration();

            // Make configuration changes and apply them
            cursorConfig.EnableEngagement(true);
            cursorConfig.EnableAllGestures();
            cursorConfig.EnableAllAlerts();
            cursorConfig.ApplyChanges();

            // Initialize the SenseManager pipeline
            sm.Init();
        }

        private void Update()
        {
            bool handInRange = false;
            bool joyButton = false;

            // Start AcquireFrame-ReleaseFrame loop
            while (sm.AcquireFrame(true).IsSuccessful())
            {
                PXCMCursorData cursorData = cursorModule.CreateOutput();
                PXCMPoint3DF32 adaptivePoints = new PXCMPoint3DF32();
                PXCMCursorData.BodySideType bodySide;

                // Retrieve the current cursor data
                cursorData.Update();

                // Check if alert data has fired
                for (int i = 0; i < cursorData.QueryFiredAlertsNumber(); i++)
                {
                    PXCMCursorData.AlertData alertData;
                    cursorData.QueryFiredAlertData(i, out alertData);

                    if ((alertData.label == PXCMCursorData.AlertType.CURSOR_NOT_DETECTED) ||
                        (alertData.label == PXCMCursorData.AlertType.CURSOR_DISENGAGED) ||
                        (alertData.label == PXCMCursorData.AlertType.CURSOR_OUT_OF_BORDERS))
                    {
                        handInRange = false;
                    }
                    else
                    {
                        handInRange = true;
                    }
                }

                // Check if click gesture has fired
                PXCMCursorData.GestureData gestureData;

                if (cursorData.IsGestureFired(PXCMCursorData.GestureType.CURSOR_CLICK, out gestureData))
                {
                    joyButton = !joyButton;
                }

                // Track hand cursor if it's within range
                int detectedHands = cursorData.QueryNumberOfCursors();

                if (detectedHands > 0)
                {
                    // Retrieve the cursor data by order-based index
                    PXCMCursorData.ICursor iCursor;
                    cursorData.QueryCursorData(PXCMCursorData.AccessOrderType.ACCESS_ORDER_NEAR_TO_FAR,
                                               0,
                                               out iCursor);

                    adaptivePoints = iCursor.QueryAdaptivePoint();

                    // Retrieve controlling body side (i.e., left or right hand)
                    bodySide = iCursor.QueryBodySide();

                    // Control the virtual joystick
                    ControlJoystick(adaptivePoints, joyButton);
                }
                else
                {
                    bodySide = PXCMCursorData.BodySideType.BODY_SIDE_UNKNOWN;
                }

                // Update the user interface
                Render(adaptivePoints, bodySide, handInRange, joyButton);

                // Resume next frame processing
                cursorData.Dispose();
                sm.ReleaseFrame();
            }
        }

        private void ControlJoystick(PXCMPoint3DF32 points, bool buttonState)
        {
            double joyMin;
            double joyMax;

            // Scale x-axis data
            joyMin = MaxSensitivity - joySensitivityX;
            joyMax = MaxSensitivity + joySensitivityX;
            int xScaled = Convert.ToInt32((joyMax - joyMin) * points.x + joyMin);

            // Scale y-axis data
            joyMin = MaxSensitivity - joySensitivityY;
            joyMax = MaxSensitivity + joySensitivityY;
            int yScaled = Convert.ToInt32((joyMax - joyMin) * points.y + joyMin);

            // Scale z-axis data
            joyMin = MaxSensitivity - joySensitivityZ;
            joyMax = MaxSensitivity + joySensitivityZ;
            int zScaled = Convert.ToInt32((joyMax - joyMin) * points.z + joyMin);

            // Update joystick settings
            joystick.SetAxis(xScaled, joyID, HID_USAGES.HID_USAGE_X);
            joystick.SetAxis(yScaled, joyID, HID_USAGES.HID_USAGE_Y);
            joystick.SetAxis(zScaled, joyID, HID_USAGES.HID_USAGE_Z);
            joystick.SetBtn(buttonState, joyID, 1);
        }

        private void Render(PXCMPoint3DF32 points,
                            PXCMCursorData.BodySideType bodySide,
                            bool handInRange,
                            bool buttonState)
        {
            Dispatcher.Invoke(delegate
            {
                // Change drawing border to indicate if the hand is within range
                uiBorder.BorderBrush = (handInRange) ? Brushes.Green : Brushes.Red;

                // Scale cursor data for drawing
                double xScaled = uiCanvas.ActualWidth * points.x;
                double yScaled = uiCanvas.ActualHeight * points.y;
                uiCursor.Height = uiCursor.Width = points.z * 100;

                // Move the screen cursor
                Canvas.SetRight(uiCursor, (xScaled - uiCursor.Width / 2));
                Canvas.SetTop(uiCursor, (yScaled - uiCursor.Height / 2));

                // Update displayed data values
                uiX.Text = string.Format("X Axis: {0:0.###}", points.x);
                uiY.Text = string.Format("Y Axis: {0:0.###}", points.y);
                uiZ.Text = string.Format("Z Axis: {0:0.###}", points.z);
                uiBodySide.Text = string.Format("Controlling Hand: {0}", bodySide);
                uiButtonState.Text = string.Format("Button State (use 'Click' gesture to toggle): {0}",
                                                    buttonState);
            });
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            update.Abort();
            cursorConfig.Dispose();
            cursorModule.Dispose();
            sm.Dispose();
            joystick.ResetVJD(joyID);
            joystick.RelinquishVJD(joyID);
        }

        private void sldSensitivity_ValueChanged(object sender,
                                                 RoutedPropertyChangedEventArgs<double> e)
        {
            var sliderControl = sender as Slider;

            switch (sliderControl.Name)
            {
                case "uiSliderX":
                    joySensitivityX = sliderControl.Value;
                    break;
                case "uiSliderY":
                    joySensitivityY = sliderControl.Value;
                    break;
                case "uiSliderZ":
                    joySensitivityZ = sliderControl.Value;
                    break;
            }
        }
    }
}

代码细节

为保持本代码尽可能简单,所有方法都包含在单个类中。 正如表 2 中显示的源代码一样,MainWindow 类由以下方法组成:

  • MainWindow()– 在 MainWindow 类的开始声明几个私有对象和成员变量。 实例化这些对象并在 MainWindow 构建器中初始化变量。
  • ConfigureRealSense()– 本方法处理创建 SenseManager 对象和手标模块,以及配置光标模块的详细信息。
  • Update()– 正如英特尔实感 SDK 参考手册中介绍的那样,SenseManager 接口可被程序调用或事件回调使用。 在 RSJoystick 应用中,我们正在使用程序调用作为选定接口技术。 获取/释放帧循环在 Update() 线程中运行,独立于主 UI 线程。 此线程连续运行,并可在此获取手标数据、手势和提醒数据。
  • ControlJoystick()– 当检测到用户的手时,将从 Update() 线程调用此方法。 自适应点数据被传输给此方法,包括虚拟摇杆按钮的状态(通过 CURSOR_CLICK 手势切换)。 使用来自灵敏度滑块控件的值扩展自适应点数据。 滑块控件和缩放计算让用户能够为发送到 vJoy SetAxis() 方法的值选择完全范围,预期是 0 至 32768 范围的值。 通过设为最大的灵敏度滑块,相应光标数据点会转换为 0 至 32768 范围的值。 对于同一手轨迹,更低的灵敏度设置会缩小这一范围。 例如: 8192 至 24576。
  • Render()– 从 Update() 线程调用此方法,并使用 Dispatcher.Invoke() 方法执行要在 UI 线程上执行的操作。 这包括更新画布控件上的椭圆位置,以及在 TextBlock 控件中显示的数据值。
  • sldSensitivity_ValueChanged() – 只要调整任何滑块控件,此事件处理程序就会发出。

使用应用

您可从 Windows 10 开始菜单运行 vJoy Monitor 来测试应用(参见图 3)。 正如图 7 所示,您可以监控在三根轴中移动手,并执行单击手势以切换按钮 1 时的效果。


图 7:使用 vJoy Monitor 测试应用。

为实现更加有趣和实用的使用,您可以运行 Google Earth 提供的飞行模拟器*(参见图 1)。 据其网站介绍,“Google Earth 让您可在地球的任何地方飞行,查看卫星图像、地图、地形、3D 建筑,从外太空的星系到大海的峡谷。” (https://www.google.com/earth)。

下载并安装 Google Earth 后,请参见此处的说明来运行飞行模拟器。 从降低 RSJoystick 中的 x 和 y 轴灵敏度控件以减少部手动作对飞机的影响开始,并将 z 轴滑块设到最大位置。 经过一些尝试后,您应能够以轻柔的手部动作控制飞机。

总结

本文提供了简单的演示,介绍如何从头开始生成支持 英特尔实感 SDK 的摇杆模拟器应用,以及如何使用 SR300 摄像头支持的手标模块。

关于英特尔实感技术

如欲了解更多有关面向 Windows 的英特尔实感软件开发套件的信息,请访问:https://software.intel.com/zh-cn/intel-realsense-sdk

关于作者

Bryan Brown 是英特尔公司软件和服务部门的软件应用工程师。


Viewing all articles
Browse latest Browse all 154

Trending Articles