面向增强现实的自主导航介绍

下载 PDF
下载代码示例

Ryan Measel 和 Ashwin Sinha

1. 简介

感知计算代表着人机交互的最新发展。 它囊括了能够感知并理解物理环境的各项技术,包括手势、语音识别、面部识别、运动追踪和环境重构。 高级英特尔® 实感™ 摄像头 F200 和 R200 处于感知计算领域的最前沿。 深度感知功能支持 F200 和 R200 重构 3D 环境,并跟踪设备相对于环境的运动。 环境重构结合运动跟踪,可支持虚拟资产与现实环境实现相互融合,从而提供出色的增强现实体验。 

尽管英特尔实感摄像头可提供数据支持增强现实应用,但沉浸式体验的创建依然取决于开发人员。 使用自主代理是创造真实环境的方法之一。 自主代理指使用人工智能独立行动的实体。 人工智能定义操作参数以及代理必须遵守的规则。 代理以实时的方式动态响应环境,因此即使是简单设计也可创建复杂行为。

自主代理的存在方式有多种;但此处为了便于介绍,我们仅介绍用于移动和导航的代理。 这类代理的例子非常丰富,包括视频游戏中的非玩家角色 (NPC) 和教育动画中的鸟群。 代理的目的因应用的不同而有所差异,但其移动和导航原则大体相同。

本文旨在介绍自主导航,以及如何将其应用于增强现实应用。 我们开发一个采用英特尔实感摄像头 R200 和 Unity* 3D 游戏引擎的示例。 最好对英特尔® 实感™ SDK 和 Unity 有一定的了解。 关于集成英特尔实感 SDK 和 Unity 的信息,请参阅: “Unity* 和英特尔® 实感™ 3D 摄像头助力游戏开发”和“第一印象: 英特尔® 实感™ R200 助力 Unity 中的增强现实”。

2. 自主导航

从实施和计算的角度来说,处理基于代理的导航的方法多种多样,从简单到复杂,各种方法一应俱全。 简单方法即为代理定义路径。 选择一个坐标点,代理按照该坐标点直线移动。 尽管实施起来非常简单,但该方法存在许多问题。 最明显的是:如果代理和坐标点之间不存在直线路径该怎么办(图 1)?

An agent moves along a straight path towards the target

图 1. 代理沿着直线路径朝目标移动,但该路径可能被障碍物阻挡。 注: 本次介绍的内容适用于 2D 和 3D 空间导航,但 2D 仅用于说明。

我们需要增加其他坐标点以绕过该障碍物(图 2)。

Additional waypoints are added to allow the agent to navigate around obstacles

图 2. 增加其他坐标点使代理绕过障碍物。

在障碍物多的大型地图中,通常会有更多的坐标点和路径。 另外,坐标点越密集(图 3),越能提高路径的效率(缩短到达目的地的行程)。

the number of waypoints and possible paths increases

图 3. 随着地图的扩大,坐标点和路径的数量也会大幅增加。

数量庞大的坐标点要求必须能够在非相邻坐标点之间找到一条路径。 这一问题被称为路径查找。 路径查找与图论息息相关。除了导航之外,路径查找还可广泛应用于多个领域。 因此,人们对该主题的研究也非常深入,开发了许多算法以解决相关问题。 A* 是其中最为突出的一种路径查找算法。 简言之,该算法穿梭于前往目的地的相邻坐标点,并将所访问的坐标点及其与之相连的坐标点绘制成地图。 到达目的地后,该算法使用它生成的地图计算路径。 之后代理就可按照该路径移动。 A* 算法不搜索整个空间,因此无法确保最佳路径, 尽管在计算方面非常高效。

The A* algorithm traverses a map searching for a route to the target

图 4. A* 算法穿梭于地图之中搜索目标路线。动画,Subh83 / CC BY 3.0 制作

A* 算法无法适应环境中的动态变化,比如添加/移除障碍物、移动边界等等。 从本质上来说,增强现实环境具有动态性,因为它们的创建和更改以用户的移动和物理空间为基础。

对动态环境来说,最好让代理实时制定决策,以便将对环境的最新了解融入到决策之中。 因此必须制定行为框架,支持代理实时制定决策并采取行动。 就导航而言,将行为框架分成三层不仅非常方便,而且非常普遍:

  1. 行动选择,包括设定目标和决定如何实现目标。 例如,一只小兔子正在四处寻找食物,看到附近出现了捕食者,决定立即逃走。 状态机可用于再现这种行为,因为该设备可定义代理的状态以及状态变化的前提条件。
  2. 操控指根据代理目前的状态计算移动。 如果捕食者追赶小兔子,小兔子应想方设法摆脱捕食者。 操控用于计算这种运动力的强度和方向。
  3. 移动指代理移动时所遵循的机制。 兔子、人类、汽车和宇宙飞船,其移动方式各不相同。 移动规定代理的移动方式(比如用腿、车轮、推进器等)以及运动参数(比如质量、最大速度、最大力等)。

这些层级共同组成了代理的人工智能。 第 3 节将展示一个 Unity 示例,以介绍这些层级的实施方法。 第 4 节介绍如何将自主导航集成至使用 R200 的增强现实应用。

3. 实施自主导航

本节将从移动开始,详细介绍 Unity 场景中面向自主导航的行为框架。

移动

代理的移动以牛顿运动定律(力是物体产生加速度的原因)为基础。 我们将使用质量分布均匀的简单模型,它能够使力作用于物体的各个方向。 为了限制移动,必须定义最大力和最大速度(表 1)。

public float mass = 1f;            // Mass (kg)
public float maxSpeed = 0.5f;      // Maximum speed (m/s)
public float maxForce = 1f;        // "Maximum force (N)

表 1. 代理移动模型。

该代理必须包含在启动阶段进行了初始化的刚体组件和对撞机组件(表 2)。 为简化模型,消除了刚体中的重力,但它是可以融入的。

private void Start () {
    
	// Initialize the rigidbody
	this.rb = GetComponent<rigidbody> ();
	this.rb.mass = this.mass;
	this.rb.useGravity = false;

	// Initialize the collider
	this.col = GetComponent<collider> ();
}

表 2. 在 Start() 上初始化刚体和对撞机。

在 FixedUpdate() 步骤中,力作用于刚体,可使代理移动(表 3)。 FixedUpdate() 与 Update() 类似,但它能够确保始终以相同的间隔执行(而 Update() 无法做到这点)。 FixedUpdate() 步骤完成后,Unity 引擎开始执行物理计算(有关刚体的运算)。

private void FixedUpdate () {

	Vector3 force = Vector3.forward;

	// Upper bound on force
	if (force.magnitude > this.maxForce) {
		force = force.normalized * this.maxForce;
	}

	// Apply the force
	rb.AddForce (force, ForceMode.Force);      

	// Upper bound on speed
	if (rb.velocity.magnitude > this.maxSpeed) {
		rb.velocity = rb.velocity.normalized * this.maxSpeed;
	}
}

表 3. FixedUpdate() 步骤中力作用于刚体。 该示例沿着 Z 轴的方向移动代理。

如果力的大小超过了代理的最大受力,将调整至与最大受力相同(方向不变)。 AddForce () 函数采用数值积分法施加力:

方程 1. 速度数值积分。 AddForce() 函数执行该计算。

其中 表示新速度, 表示之前的速度, 表示力, 表示质量, 表示更新之间的时步(Unity 默认固定时步为 0.02 秒)。 如果速度超出代理的最大速度,将调整至与最大速度相同。

操控

操控计算提供给移动模型的力。 将实施三种操控行为:靠近、抵达和避障。

靠近

“靠近”行为尝试以最快的速度向目标前进。 该行为的预期速度以最大速度靠近目标。 操控力即代理的预期速度与当前速度之间的差额(图 5)。

The Seek behavior applies a steering force

图 5. “靠近”行为将操控力从当前速度作用于预期速度。

该实施(表 4)首先计算预期矢量,计算方法为归一代理与目标之间的偏差,然后乘以最大速度。 返回的操控力为预期速度减去当前速度,即刚体的速度。

private Vector3 Seek () {

	Vector3 desiredVelocity = (this.seekTarget.position - this.transform.position).normalized * this.maxSpeed;
	return desiredVelocity - this.rb.velocity;
}

表 4. “靠近”操控行为。

在 FixedUpdate() 步骤中计算力时,代理通过调用 Seek() 使用“靠近”行为(表 5)。

private void FixedUpdate () {

	Vector3 force = Seek ();
	...

表 5. 在 FixedUpdate () 中调用 Seek()。

视频 1 所示为作用中的“靠近”行为示例。 代理的蓝色箭头表示刚体的当前速度,红色箭头应用于该时步中的操控力。

视频 1. 由于代理的最初速度与目标方向彼此正交,所以其移动轨迹为曲线。

原视频链接地址:ttps://www.youtube.com/watch?v=HMjut52zxgI

抵达

“靠近”行为超越并围绕目标波动,因为它正以尽可能最快的速度抵达目标。 “抵达”行为与“靠近”行为类似,唯一的不同点是它会尝试在目标点完全停下来。 “减速半径”参数定义代理离目标多远时开始减速。 如果代理位于减速半径之中,预期速度调整为与代理与目标之间的距离成反比的速度。 代理可能无法完全停止,这取决于最大力、最大速度和减速半径。

“抵达”行为(表 6)首先计算代理与目标之间的距离。 调整速度即根据距离除以减速半径后的数据所调整的最大速度。 预期速度将视作调整速度与最大速度的最小值。 因此,如果与目标的距离小于减速半径,预期速度将为调整速度。 否则,预期速度将为最大速度。 该函数的其余部分的执行方式与使用预期速度的“靠近”行为几乎相同。

// Arrive deceleration radius (m)
public float decelerationRadius = 1f;

private Vector3 Arrive () {

	// Calculate the desired speed
	Vector3 targetOffset = this.seekTarget.position - this.transform.position;
	float distance = targetOffset.magnitude;
	float scaledSpeed = (distance / this.decelerationRadius) * this.maxSpeed;
	float desiredSpeed = Mathf.Min (scaledSpeed, this.maxSpeed);

	// Compute the steering force
	Vector3 desiredVelocity = targetOffset.normalized * desiredSpeed;
	return desiredVelocity - this.rb.velocity;
}

表 6. “抵达”操控行为。

视频 2. 代理达到目标时抵达行为开始减速。

原视频链接地址:https://www.youtube.com/watch?v=LsUDMG8KLn8

避障

“抵达”行为和“靠近”行为适合到达目的地,但不适合处理障碍。 在动态环境中,代理需要能够随时躲避出现的障碍物。 在预期路径中,“避障”行为比代理看得更远,并确定前方是否有需要躲避的障碍物。 如果发现障碍物,该行为计算将改变代理前进路径以躲避障碍物的力(图 6)。

图 6. 如果在当前前进轨迹中发现障碍物,将返回能够防止碰撞的力。

避障实施(表 7)通过球体投射来检测碰撞。 球体投射沿着刚体的当前速度矢量投射一个球体,并返回有关碰撞的 RaycastHit。 该球体从代理中心出发,其半径等于代理的对撞机半径加上“躲避半径”参数。 躲避半径支持用户定义代理周围的空隙。 该球体只能按照“前方检测”参数规定的距离前进。

// Avoidance radius (m). The desired amount of space between the agent and obstacles.
public float avoidanceRadius = 0.03f;
// Forward detection radius (m). The distance in front of the agent that is checked for obstacles.
public float forwardDetection = 0.5f;

private Vector3 ObstacleAvoidance () {

	Vector3 steeringForce = Vector3.zero;

	// Cast a sphere, that bounds the avoidance zone of the agent, to detect obstacles
	RaycastHit[] hits = Physics.SphereCastAll(this.transform.position, this.col.bounds.extents.x + this.avoidanceRadius, this.rb.velocity, this.forwardDetection); 
	
	// Compute and sum the forces across all hits
	for(int i = 0; i < hits.Length; i++)    {

		// Ensure that the collidier is on a different object
		if (hits[i].collider.gameObject.GetInstanceID () != this.gameObject.GetInstanceID ()) {

			if (hits[i].distance > 0) {
				
				// Scale the force inversely proportional to the distance to the target
				float scaledForce = ((this.forwardDetection - hits[i].distance) / this.forwardDetection) * this.maxForce;
				float desiredForce = Mathf.Min (scaledForce, this.maxForce);

				// Compute the steering force
				steeringForce += hits[i].normal * desiredForce;
			}
		}
	}                

	return steeringForce;
}

表 7. “避障”操控行为。

球体投射返回 RaycastHit 对象阵列。 RaycastHit 包含有关碰撞的信息,包括碰撞距离和碰撞表面的法线。 法线是与表面正交的矢量, 可用于使代理偏离碰撞点。 通过将最大力调整为与碰撞距离成反比的数值来确定力的大小。 每次碰撞的力相加,所得结果为单个时步的总操控力。

不同行为可进行组合,形成更复杂的行为(表 8)。 避障仅在与其他行为配合使用时才能发挥作用。 在本示例(视频 3)中,“避障”行为与“抵达”行为配合使用。 只需将它们各自的力相加,即可配合实施这两种行为。 我们还可创建更加复杂的方案,通过融入启发法,确定力的优先权系数。

private void FixedUpdate () {

	// Calculate the total steering force by summing the active steering behaviors
	Vector3 force = Arrive () + ObstacleAvoidance();
	...

表 8. 将“抵达”行为和“避障”行为的力相加,从而配合实施这两种行为。

视频 3. 代理配合使用“抵达”行为和“避障”行为。

原视频链接地址:https://www.youtube.com/watch?v=p_3-zU5GBEI

行动选择

行动选择指代理设定最高目标和制定决策。 通过组合“抵达”和“避障”行为,代理实施可融入简单的行动选择模型。 代理尝试着到达目的地,如果检测到障碍物,将调整其前进轨迹。 “避障”行为的“躲避半径”和“前方检测”参数会定义何时采取行动。

4. 集成 R200

由于代理能够自主导航,因此能够与增强现实应用相集成。

以下示例在附带英特尔实感 SDK 的“场景感知”示例的顶部构建。 该应用将使用“场景感知”创建一个网格,以便用户能够在该网格上设定和移动目标。 之后代理将围绕生成的网格进行导航,以到达目的地。

场景管理器

场景管理器脚本初始化场景并处理用户输入。 Touch up(或鼠标点击版,如果设备不支持触控功能)是唯一的输入。 触控点的光线投射将决定是否在生成的网格上进行触控。 首次触控在网格上生成目标;第二次触控生成代理;后续每次触控将移动目标的位置。 状态机处理控制逻辑(表 9)。

// State machine that controls the scene:
//         Start => SceneInitialized -> TargetInitialized -> AgentInitialized
private enum SceneState {SceneInitialized, TargetInitialized, AgentInitialized};
private SceneState state = SceneState.SceneInitialized;    // Initial scene state.

private void Update () {

	// Trigger when the user "clicks" with either the mouse or a touch up gesture.
	if(Input.GetMouseButtonUp (0)) {
		TouchHandler ();
	}
}

private void TouchHandler () {

	RaycastHit hit;

	// Raycast from the point touched on the screen
	if (Physics.Raycast (Camera.main.ScreenPointToRay (Input.mousePosition), out hit)) {
	
	 // Only register if the touch was on the generated mesh
		if (hit.collider.gameObject.name == "meshPrefab(Clone)") {
		
			switch (this.state) {
			case SceneState.SceneInitialized:
				SpawnTarget (hit);
				this.state = SceneState.TargetInitialized;
				break;
			case SceneState.TargetInitialized:
				SpawnAgent (hit);
				this.state = SceneState.AgentInitialized;
				break;
			case SceneState.AgentInitialized:
				MoveTarget (hit);
				break;
			default:
				Debug.LogError("Invalid scene state.");
				break;
			}
		}
	}
}

表 9. 面向该示例应用的触控处理程序和状态机。

“场景感知”特性可生成多个小网格。 这些网格通常包含不到 30 个顶点。 顶点的位置容易受到偏差的影响,因此有的网格会与它驻留的表面形成不同的角度。 如果对象位于网格顶部(比如目标或代理),该对象将位于错误的方向。 为解决该问题,我们使用网格的平均法线(表 10)。

private Vector3 AverageMeshNormal(Mesh mesh) {
                
	Vector3 sum = Vector3.zero; 

	// Sum all the normals in the mesh
	for (int i = 0; i < mesh.normals.Length; i++){
		sum += mesh.normals[i];
	}

	// Return the average
	return sum / mesh.normals.Length; 
}

表 10. 计算网格的平均法线。

构建应用

Github 提供适用于该示例的所有代码。

以下指令可将场景管理器和代理实施集成至英特尔® 实感™ 应用。

  1. 打开英特尔实感 SDK 文件夹“RSSDK\framework\Unity”中的“RF_ScenePerception”示例。
  2. 下载并导入 AutoNavAR Unity 文件包。
  3. 打开“Assets/AutoNavAR/Scenes/”文件夹中的“RealSenseExampleScene”。
  4. 在兼容英特尔实感摄像头 R200 的设备上构建并运行。

视频 4. 完成与英特尔® 实感™ 摄像头 R200 的集成。

原视频链接地址:https://www.youtube.com/watch?v=k-WwQwPgSIo

5. 有关自主导航的其他信息

我们开发了一个示例,展示如何在使用 R200 的增强现实应用中实施自主代理。 我们能够采用多种方式扩展该示例,从而增强代理的智能性和实施效果。

该代理拥有一种质量均匀并且没有方向移动限制的简单机械模型。 我们也可以开发质量分布不均并限制物体作用力(比如包含不同加速度和制动力的汽车、包含主推进器和侧推进器的宇宙飞船)的高级移动模型。 机械模型越精准,移动效果越真实。

Craig Reynolds 率先深入探讨了动画和游戏环境中的操控行为。 本示例所述的“靠近”、“抵达”和“避障”行为均源自他的作品。 Reynolds 还介绍了其他行为,包括离开、追逐、徘徊、探索、避障和路径跟随。 还探讨了分离、聚集、队列等组行为。 Mat Buckland 编写的《游戏人工智能编程案例精粹》同样介绍了如何实施这些行为,以及大量的相关概念(包括状态机和路径查找),非常实用。

在本示例中,“抵达”和“避障”操控行为可同时用于代理。 许多行为都可通过该方式协同工作,以创建更复杂的行为。 例如,可通过组合分离、聚集和队列来构建群集行为。 有时,组合行为可导致不太直观的结果。 我们可以试验不同类型的行为及参数,发现更多可能性。

另外,许多路径查找技巧都可用于动态环境。 D* 算法与 A* 类似,不过它能够根据最新的观察结果(比如添加/移除障碍)更新路径。 D* Lite 的原理与 D* 相同,但实施方法更加简单。 通过设置坐标点和支持通过操控来导航这些坐标点,可以结合使用路径查找和操控行为。

本文不涉及行动选择,博弈论对其进行了详细介绍。 博弈论主要研究策略和决策制定背后的机制。 它包含涉及多个领域的应用,比如经济学、政治科学、心理学等等。 关于自主代理,博弈论会告知如何以及何时作出决策。 William Spaniel 编写的《博弈论: 完整教程》非常适合入门,并附带 YouTube 系列

6. 结论

您可以使用大量工具定制代理的移动、行为和行动。 自主导航特别适用于动态环境,比如增强现实应用中的英特尔实感摄像头所生成的环境。 即使采用简单的移动模型和操控行为,也可在不事先了解环境的情况下创建复杂行为。 大量可用的模型和算法有利于灵活实施面向几乎任何应用的自主解决方案。

关于作者

Ryan Measel 是 Fantasmo Studios 的联合创始人兼首席技术官。 Ashwin Sinha 是创办团队成员和开发人员。 Fantasmo Studios 创办于 2014 年,是一家以提供混合现实应用内容和服务为主的技术娱乐公司。

有关编译器优化的更完整信息,请参阅优化通知