摘要
本文演示了使用英特尔® 实感™ 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 是英特尔公司软件和服务部门的软件应用工程师。