EasyAR 稠密空间地图

功能简介

EasyAR稠密空间地图利用RGB相机图像对周围环境进行三维稠密重建,得到稠密的点云地图和网格地图。利用稠密空间地图让虚拟物体更好的融入真实环境之中,用以实现真实物体和虚拟物体正确遮挡、碰撞等AR应用。 EasyAR稠密空间地图需要基于稳定的运动估计系统提供六自由度的相机位置和姿态,可以从EasyAR运动跟踪模块或者ARKit/ARCore等获取。目前EasyAR稠密空间地图本身不提供重定位功能,如果需要持久化AR需求,建议配合EaSYAR稀疏空间地图(SparseSpatialMap)使用.

EasyAR稠密空间地图采用的是右手坐标系:当用户手持设备面向前方时,x轴指向用户的右侧,y轴指向上方,z轴指向用户。这种坐标系的定义和OpenGL,ARKit,ARCore等API一致。Unity中使用的是左手坐标系,它的z轴朝向与右手坐标系相反,因此在Unity中使用时需要进行坐标转换,转换方式可以查看EasyAR提供的Unity demo。

网格地图模型介绍

由于稠密的三维重建对大众来说属于比较陌生的领域,涉及比较多的基础知识,请您先阅读下面列出的相关的文档资料以便更好的理解后面的内容。

https://en.wikipedia.org/wiki/Triangle_mesh

对于Unity用户请阅读:

https://docs.unity3d.com/ScriptReference/Mesh.html

下面仅对EasyAR Sense中采用的网格地图模型做简要的介绍。下图是一张网格地图的示意图。

../_images/mesh.jpg

图1. 网格地图示意图

网格地图是对三维物体表面的一种近似,实际的三维物体可以看成分辨率无穷大,但为了计算存储和使用,只能将分辨率降低,将三维物体表面抽象成许多相互连接的三角面,每个三角面由物体表面上的三个顶点构成。因此网格地图就是由一些顶点(vertex)和面(face)组成的。顶点就是三维点,面就是三个索引(index),这三个索引描述了哪三个顶点构成了一个平面。

在DenseSpatialMap的API中,一个顶点由6个浮点数描述:(x,y,z,nx,ny,nz),其中(x,y,z)是顶点的三维坐标,(nx,ny,nz)是这个顶点处的法向量。顶点的法向量表示的是顶点所在的表面的朝向,可以用来做光照渲染或者物理碰撞。一个面由三个整数描述:(i1,i2,i3),这三个数字表示的是顶点的索引,与顶点在顶点数组中的存储顺序相关。(i1,i2,i3)三个顶点是按逆时针排序的。

Mesh Buffer

为了传输和使用方便,我们将网格地图的内容分成了三个Buffer:Vertex Buffer, Normal Buffer和Index Buffer,分别存储所有的顶点位置,顶点法向量和三角面的顶点索引。

在Vertex Buffer和Normal Buffer中数据以32位浮点数存储,每个数值占用4个字节。Vertex Buffer中每三个连续的数值构成一个顶点的三维坐标,因此一个顶点占用4*3=12个字节。Normal Buffer中每三个连续的数值构成一个顶点的法向量,因此一个顶点占用4*3=12个字节。顶点在Vertex Buffer和Normal Buffer中存储的顺序是一致的,例如Vertex Buffer中第37~48个字节存储的数据是第4个顶点的位置信息,Normal Buffer中第37~48个字节存储的数据是第4个顶点的法向量信息,这两组数据共同描述了第4个顶点的全部信息。

在Index Buffer中数据以32位整数存储,每个数值占用4个字节。一个三角面由3个index构成,因此一个面占用4*3=12个字节。这里面的index值就是前面Vertex Buffer中存储的顺序,例如第一个三角面的三个索引是(1,4,9),那么对应的三个顶点分别是Vertex Buffer中的第1,4,9个顶点。

这里面的Vertex Buffer,Normal Buffer和Index Buffer与OpenGL,Unity等引擎中的定义是类似的,开发者可以直接套用或者参照我们提供的demo中的使用方法。

Block Info

由于重建过程是渐进式的,地图大小会逐渐增加,如果每次都更新都所有的地图数据,对数据传输要求会越来越高,对计算碰撞体这类处理也会产生很大的计算负担,因此我们选择了将地图分割,按需更新的方式。

../_images/mesh_block1.png

图2. 由3个Mesh Block组成的二维网格地图

../_images/mesh_block2.png

图3. 图2所示的网格地图对应的Block Info、Vertex Buffer和Index Bufer内数据之间的联系

如图2所示,我们将空间划分成一个一个的立方体,称为Mesh Block,一个Mesh Block类似于一个小的的网格地图,也是由顶点和面构成的。图2中不同的颜色代表不同的Mesh Block,有些空间中没有任何物体,因此这部分地方不会生成Mesh Block以节省内存。为了让Mesh Block之间相互独立,可以单独进行渲染等操作,Mesh Block之间可能包含重复的顶点,不过事实上这部分重复顶点所占的比例非常小。

一个Mesh Block里面包含的信息用 BlockInfo 来描述。BlockInfo中有8个元素(x,y,z,numOfVertex,startPointOfVertex,numOfIndex,startPointOfIndex,version),下面分别解释这几个元素的含义。

首先我们需要知道每一个Mesh Block在空间中的位置,用(x,y,z)三个数来索引,x,y,z三个数都是整数,用(x,y,z)乘以Mesh Block的边长,就等于Mesh Block在空间中的物理位置了,当地图很大时可以根据这个空间位置对Mesh Block进行过滤。

一个Mesh Block中有多少顶点和面(每三个索引构成一个面,因此面由索引来表征),由numOfVertex和numOfIndex来描述,面的个数等于numOfIndex/3。

当我们拿到一个Mesh Block信息的时候,要判断这个Mesh Block相对之前是否有更新,用version来表征,当version大于上一次缓存的Mesh Block的version时,表示这个Mesh Block已经更新了,我们需要更新缓存中的内容。

startPointOfVertex和startPointOfIndex,是两个冗余的辅助信息。当我们调用getVerticesIncremental等函数时,返回的是一整块连续的内存,里面所有的Mesh Block的顶点位置信息是连续存储的,您需要自己将每个Mesh Block中的数据提取出来。由于已经有了每个Mesh Block的顶点数量和索引数量,您可以自己累加计算出每个Mesh Block的顶点位置、顶点法向量、索引在Buffer中的位置,不过我们已经计算好了顶点和索引在Buffer中的起始位置,您可以根据起始位置和大小直接将Mesh Block的内容从Buffer中提取出来。

请注意,startPointOfVertex和startPointOfIndex是以Buffer的原点为起点计算的,而Index Buffer中的index却不是以Buffer的原点为起始点的,而是以Block Mesh内部原点为起始点的。总的来说Index Buffer中的索引值是一种local性质的索引,因此每个Block Mesh是可以进行单独渲染的。如果想将多个Block Mesh中的内容进行合并,需要将Index Buffer中的索引都进行修改。

Incremental Update

在某些情况下,我们需要边构建地图边使用地图,但也有些时候我们会先构建地图,构建完成后再直接使用完整的地图。为了适应这两种应用场景,我们提供了两套获取稠密地图的函数。其中一套以All作为后缀,例如 getVerticesAllgetIndicesAll 等等,另一套以Incremental作为后缀,例如 getVerticesIncrementalgetIndicesIncremental 等等,前者代表一次性拿到所有的地图数据,后者只拿到最近更新的数据。请注意,前者拿到的数据是整个地图的所有数据,没有进行分割,而后者拿到的数据是按照前文所述的Mesh Block进行分割后的,因此Incremental的方法多了一个 getBlocksInfoIncremental 函数用来获取所有Mesh Block的信息。

每调用一次 updateSceneMesh 会更新一次 SceneMesh 中的内容,当调用`updateSceneMesh`_ 的时候传入的参数是True时,会刷新所有的地图和最近更新的地图到SceneMesh中,当传入的参数是False时,只会刷新最近更新的地图到SceneMesh中。这里的“最近”指的是从上一次调用updateSceneMesh到本次调用updateSceneMesh之间这段时间。

使用技巧

要想得到比较好的重建结果,开发者可以提示用户按照如下方式进行操作: 1. 尽量多的横移手机,避免原地旋转手机; 2. 不要在大片的白色、黑色区域,以及对带有反光的物体(例如镜子和光滑的金属)进行重建,在这些区域进行重建可能会出来一些悬浮在空中的碎片; 3. 由于重建过程是依赖于运动跟踪系统的,快速的移动或遮挡摄像头可能会影响运动跟踪的结果,从而影响稠密空间地图的结果,因此用户应尽量避免快速的移动设备或遮挡摄像头。

平台限制

运行平台需要支持EasyAR Motion Tracking或者ARKit或者ARCore。 建议设备的CPU的计算能力不低于苹果的A10/高通的Snapdragon835/华为的Kiri970处理器。