ARKit:增强现实技术在美团到餐业务的实践

前言

增强现实(Augmented Reality)是一种在视觉上呈现虚拟物体与现实场景结合的技术。Apple 公司在 2017 年 6 月正式推出了 ARKit,iOS 开发者可以在这个平台上使用简单便捷的 API 来开发 AR 应用程序。

本文将结合美团到餐业务场景,介绍一种基于位置服务(LBS)的 AR 应用。使用 AR 的方式展现商家相对用户的位置,这会给用户带来身临其境的沉浸式体验。下面是实现效果:

图1 实现效果图

项目实现

iOS 平台的 AR 应用通常由 ARKit 和渲染引擎两部分构成:

图2 AR 应用的整体架构

ARKit 是连接真实世界与虚拟世界的桥梁,而渲染引擎是把虚拟世界的内容渲染到屏幕上。本部分会围绕这两个方面展开介绍。

ARKit

ARKit 的 ARSession 负责管理每一帧的信息。ARSession 做了两件事:拍摄图像并获取传感器数据;对数据进行分析处理后逐帧输出。如下图:

图3 ARKit 结构图

设备追踪

设备追踪确保了虚拟物体的位置不受设备移动的影响。在启动 ARSession 时需要传入一个 ARSessionConfiguration 的子类对象,以区别三种追踪模式:

  • ARFaceTrackingConfiguration
  • ARWorldTrackingConfiguration
  • AROrientationTrackingConfiguration

其中 ARFaceTrackingConfiguration 可以识别人脸的位置、方向以及获取拓扑结构。此外,还可以探测到预设的 52 种丰富的面部动作,如眨眼、微笑、皱眉等等。ARFaceTrackingConfiguration 需要调用支持 TrueDepth 的前置摄像头进行追踪,显然不能满足我们的需求,这里就不做过多的介绍。下面只针对使用后置摄像头的另外两种类型进行对比。

ARWorldTrackingConfiguration

ARWorldTrackingConfiguration 提供 6DoF(Six Degree of Freedom)的设备追踪。包括三个姿态角 Yaw(偏航角)、Pitch(俯仰角)和 Roll(翻滚角),以及沿笛卡尔坐标系中 X、Y 和 Z 三轴的偏移量:

图4 6DoF

不仅如此,ARKit 还使用了 VIO(Visual-Inertial Odometry)来提高设备运动追踪的精度。在使用惯性测量单元(IMU)检测运动轨迹的同时,对运动过程中摄像头拍摄到的图片进行图像处理。将图像中的一些特征点的变化轨迹与传感器的结果进行比对后,输出最终的高精度结果。

从追踪的维度和准确度来看,ARWorldTrackingConfiguration 非常强悍。但如官方文档所言,它也有两个致命的缺点:

  • 受环境光线质量影响
  • 受剧烈运动影响

由于在追踪过程中要通过采集图像来提取特征点,所以图像的质量会影响追踪的结果。在光线较差的环境下(比如夜晚或者强光),拍摄的图像无法提供正确的参考,追踪的质量也会随之下降。

追踪过程中会逐帧比对图像与传感器结果,如果设备在短时间内剧烈的移动,会很大程度上干扰追踪结果。追踪的结果与真实的运动轨迹有偏差,那么用户看到的商家位置就不准确。

AROrientationTrackingConfiguration

AROrientationTrackingConfiguration 只提供对三个姿态角的追踪(3DoF),并且不会开启 VIO。

Because 3DOF tracking creates limited AR experiences, you should generally not use the AROrientationTrackingConfiguration class directly. Instead, use the subclass ARWorldTrackingConfiguration for tracking with six degrees of freedom (6DOF), plane detection, and hit testing. Use 3DOF tracking only as a fallback in situations where 6DOF tracking is temporarily unavailable.

通常来讲,因为 AROrientationTrackingConfiguration 的追踪能力受限,官方文档不推荐直接使用。但是鉴于:

  1. 对三个姿态角的追踪,已经足以正确的展现商家相对用户的位置了。
  2. ARWorldTrackingConfiguration 的高精度追踪,更适合于距离较近的追踪。比如设备相对桌面、地面的位移。但是商家和用户的距离动辄几百米,过于精确的位移追踪意义不大。
  3. ARWorldTrackingConfiguration 需要规范用户的操作、确保环境光线良好。这对用户来说很不友好。

最终我们决定使用 AROrientationTrackingConfiguration。这样的话,即便是在夜晚,甚至遮住摄像头,商家的位置也能够正确的进行展现。而且剧烈晃动带来的影响很小,商家位置虽然会出现短暂的角度偏差,但是在传感器数值稳定下来后就会得到校准。

坐标轴

ARKit 使用笛卡尔坐标系度量真实世界。ARSession 开启时的设备位置即是坐标轴的原点。而 ARSessionConfiguration 的 worldAlignment 属性决定了三个坐标轴的方向,该属性有三个枚举值:

  • ARWorldAlignmentCamera
  • ARWorldAlignmentGravity
  • ARWorldAlignmentGravityAndHeading

三种枚举值对应的坐标轴如下图所示:

图5 三种枚举值对应的坐标轴

对于 ARWorldAlignmentCamera 来说,设备的姿态决定了三个坐标轴的方向。这种坐标设定适用于以设备作为参考系的坐标计算,与真实地理环境无关,比如用 AR 技术丈量真实世界物体的尺寸。

对于 ARWorldAlignmentGravity 来说,Y 轴方向始终与重力方向平行,而其 X、Z 轴方向仍然由设备的姿态确定。这种坐标设定适用于计算拥有重力属性的物体坐标,比如放置一排氢气球,或者执行一段篮球下落的动画。

对于 ARWorldAlignmentGravityAndHeading 来说,X、Y、Z 三轴固定朝向正东、正上、正南。在这种模式下 ARKit 内部会根据设备偏航角的朝向与地磁真北(非地磁北)方向的夹角不断地做出调整,以确保 ARKit 坐标系中 -Z 方向与我们真实世界的正北方向吻合。有了这个前提条件,真实世界位置坐标才能够正确地映射到虚拟世界中。显然,ARWorldAlignmentGravityAndHeading 才是我们需要的。

商家坐标

商家坐标的确定,包含水平坐标和垂直坐标两部分:

水平坐标

商家的水平位置只是一组经纬度值,那么如何将它对应到 ARKit 当中呢?我们通过下图来说明:

图6 经纬度转换为坐标

借助 CLLocation 中的 distanceFromLocation:location 方法,可以计算出两个经纬度坐标之间的距离,返回值单位是米。我们可以以用户的经度 lng1、商家的纬度 lat2 作一个辅助点(lng1, lat2),然后分别计算出辅助点距离商家的距离 x、辅助点距离用户的距离 z。ARKit 坐标系同样以米为单位,因而可以直接确定商家的水平坐标(x, -z)。

垂直坐标

对商家地址进行中文分词可以提取出商户所在楼层数,再乘以一层楼大概的高度,以此确定商家的垂直坐标 y 值:

图7 高度信息提取

卡片渲染

通常我们想展示的信息,都是通过 UIView 及其子类来实现。但是 ARKit 只负责建立真实世界与虚拟世界的桥梁,渲染的部分还是要交给渲染引擎来处理。Apple 给我们提供了三种可选的引擎:

  • Metal
  • SpriteKit
  • SceneKit

强大的 Metal 引擎包含了 MetalKit、Metal 着色器以及标准库等等工具,可以更高效地利用 GPU,适用于高度定制化的渲染要求。不过 Metal 对于当前需求来说,有些大材小用。

SpriteKit 是 2D 渲染引擎,它提供了动画、事件处理、物理碰撞等接口,通常用于制作 2D 游戏。SceneKit 是 3D 渲染引擎,它建立在 OpenGL 之上,支持多通道渲染。除了可以处理 3D 物体的物理碰撞和动画,还可以呈现逼真的纹理和粒子特效。SceneKit 可以用于制作 3D 游戏,或者在 App 中加入 3D 内容。

虽然我们可以用 SpriteKit 把 2D 的卡片放置到 3D 的 AR 世界中,但是考虑到扩展性,方便之后为 AR 页面添加新的功能,这里我们选用 3D 渲染引擎 SceneKit。

我们可以直接通过创建 ARSCNView 来使用 SceneKit。ARSCNView 是 SCNView 的子类,它做了三件事:

  • 将设备摄像头捕捉的每一帧的图像信息作为 3D 场景的背景。
  • 将设备摄像头的位置作为 3D 场景的摄像头(观察点)位置。
  • 将 ARKit 追踪的真实世界坐标轴与 3D 场景坐标轴重合。

卡片信息

SceneKit 中使用 SCNNode 来管理 3D 物体。设置 SCNNode 的 geometry 属性可以改变物体的外观。系统已经给我们提供了例如 SCNBox、SCNPlane、SCNSphere 等等一些常见的形状,其中 SCNPlane 正是我们所需要的卡片形状。借助 UIGraphics 中的一些方法可以将绘制好的 UIView 渲染成一个 UIImage 对象。根据这张图片创建 SCNPlane,以作为 SCNNode 的外观。

卡片大小

ARKit 中的物体都是近大远小。只要固定好 SCNPlane 的宽高,ARKit 会自动根据距离的远近设置 SCNPlane 的大小。这里列出一个在屏幕上具体的像素数与距离的粗略计算公式,为笔者在开发过程中摸索的经验值:

也就是说,假如 SCNPlane 的宽度为 30,距离用户 100 米,那么在屏幕上看到这个 SCNPlane 的宽度大约为 \(530 / 100 \times 30 = 159\) pt。

卡片位置

对于距离用户过近的商家卡片,会出现两个问题:

  • 由于 ARKit 自动将卡片展现得近大远小,身边的卡片会大到遮住了视野
  • 前文提到的 ARSession 使用 AROrientationTrackingConfiguration 追踪模式,由于没有追踪设备的水平位移,当用户走向商家时,并不会发觉商家卡片越来越近

这里我们将距离用户过近的卡片映射到稍远的位置。如下图所示,距离用户的距离小于 d 的卡片,会被映射到 d-k ~ d 的区间内。

图8 过近卡片位置映射

假设某商家距离用户的真实距离为 x,映射后的距离为 y,映射关系如下:

这样既解决了距离过近的问题,又可以保持卡片之间的远近关系。用户位置发生位移到达一定阈值后,会触发一次新的网络请求,根据新的用户位置来重新计算商家的位置。这样随着用户的移动,卡片的位置也会持续地更新。

卡片朝向

SceneKit 会在渲染每一帧之前,根据 SCNNode 的约束自动调整卡片的各种行为,比如碰撞、位置、速度、朝向等等。SCNConstraint 的子类中 SCNLookAtConstraint 和 SCNBillboardConstraint 可以约束卡片的朝向。

SCNLookAtConstraint 可以让卡片始终朝向空间中某一个点。这样相邻的卡片会出现交叉现象,用户看到的卡片信息很可能是不完整的。使用 SCNBillboardConstraint 可以解决这个问题,让卡片的朝向始终与摄像头的朝向平行。

图9 卡片朝向的两种约束

下面是创建卡片的示例代码:


// 位置
SCNVector nodePosition = SCNVectorMake(-200, 5, -80);// 外观
SCNPlane *plane = [SCNPlane planeWithWidth:image.size.widthheight:image.size.height];
plane.firstMaterial.diffuse.contents = image;// 约束
SCNBillboardConstraint *constraint = [SCNBillboardConstraint billboardConstraint];
constraint.freeAxes = SCNBillboardAxisY;SCNNode *node = [SCNNode nodeWithGeometry:plane];
node.position = nodePosition;
node.constraints = @[constraint];

优化

遮挡问题

如果同一个方向的商家数量有很多,那么卡片会出现互相重叠的现象,这会导致用户只能看到离自己近的卡片。这是个比较棘手的问题,如果在屏幕上平铺卡片的话,既牺牲了对商家高度的感知,又无法体现商家距离用户的远近关系。

点击散开的交互方式

经过漫长的讨论,我们最终决定采取点击重叠区域后,卡片向四周分散的交互方式来解决重叠问题,效果如下:

图10 卡片散开的效果

下面围绕点击和投射两个部分,介绍该效果的实现原理。

点击

熟悉 Cocoa Touch 的朋友都了解,UIView 的层级结构是通过 hit-testing 来判断哪个视图响应事件的,在 ARKit 中也不例外。

ARSCNView 可以使用两种 hit-testing:

  • 来自 ARSCNView 的 hitTest:types: 方法:查找点击的位置所对应的真实世界中的物体或位置
  • 来自 SCNSceneRenderer 协议的 hitTest:options: 方法:查找点击位置所对应的虚拟世界中的内容。

显然,hitTest:options: 才是我们需要的。在 3D 世界中的 hit-testing 就像一束激光一样,向点击位置的方向发射,hitTest:options: 的返回值就是被激光穿透的所有卡片的数组。这样就可以检测到用户点击的位置有哪些卡片发生了重叠。

投射

这里简单介绍一下散开的实现原理。SCNSceneRenderer 协议有两个方法用来投射坐标:

  • projectPoint::将三维坐标系中点的坐标,投射到屏幕坐标系中
  • unprojectPoint::将屏幕坐标系中的点的坐标,投射到三维坐标系中

其中屏幕坐标系中的点也是个 SCNVector3,其 z 坐标代表着深度,从 0.0(近裁面)到 1.0(远裁面)。散开的整体过程如下:

图11 投射过程

散开后,点击空白处会恢复散开的状态,回到初始位置。未参与散开的卡片会被淡化,以突出重点,减少视觉压力。

后台聚类

对于排布比较密集的商家,卡片的重叠现象会很严重。点击散开的卡片数量太多对用户不是很友好。后台在返回用户附近的商家数据时,按照商家的经纬度坐标,使用 K-Means 聚类算法进行二维聚类,将距离很近的商家聚合为一个卡片。由于这些商家的位置大体相同,可以采用一个带有数字的卡片来代表几个商家的位置:

图12 聚合卡片

闪烁问题

实测中发现,距离较近的卡片在重叠区域会发生闪烁的现象:

图13 闪烁

这里要引入一个 3D 渲染引擎普遍要面对的问题——可见性问题。简单来说就是屏幕上哪些物体应该被展示,哪些物体应该被遮挡。GPU 最终应该在屏幕上渲染出所有应该被展示的像素。

可见性问题的一个典型的解决方案就是画家算法,它像一个头脑简单的画家一样,先绘制最远的物体,然后一层层的绘制到最近的物体。可想而知,画家算法的效率很低,绘制较精细场景会很消耗资源。

深度缓冲

深度缓冲

弥补了画家算法的缺陷,它使用一个二维数组来存储当前屏幕中每个像素的深度。如下图所示,某个像素点渲染了深度为 0.5 的像素,并储存该像素的深度:

图14 深度缓冲区

下一帧时,当另外一个物体的某个像素也在这个像素点渲染时,GPU 会对该像素的深度与缓冲区中的深度进行比较,深度小者被保留并被存入缓冲区,深度大者不被渲染。如下图所示,该像素点下一帧要渲染的像素深度为 0.2,比缓冲区存储的 0.5 小,其深度被存储,并且该像素被渲染在屏幕上:

图15 深度小的像素被保留

显然,深度缓冲技术相比画家算法,可以极大地提升渲染效率。但是它也会带来深度冲突的问题。

深度冲突

深度缓冲技术在处理具有相同深度的像素点时,会出现深度冲突(Z-fighting)现象。这些具有相同深度的像素点在竞争中只有一个“胜出”,显示在屏幕上。如下图所示:

图16 深度冲突

如果这两个像素点交替“胜出”,就会出现我们视觉上的闪烁效果。由于每个卡片都被设置了 SCNBillboardConstraint 约束,始终朝向摄像头方向。摄像头轻微的角度变化,都会引起卡片之间出现部分重合。与有厚度的物体不同,卡片之间的深度关系变化很快,很容易出现多个卡片在屏幕同一个位置渲染的情况。所以经常会出现闪烁的现象:

图17 角度变化引起的深度冲突

为了解决这 Bug 般的体验,最终决定牺牲深度缓冲带来的渲染效率。SceneKit 为我们暴露了深度是否写入、读取缓冲区的接口,我们将其禁用即可:

plane.firstMaterial.writesToDepthBuffer = NO;plane.firstMaterial.readsFromDepthBuffer = NO;

由于卡片内容内容相对简单,禁用缓冲区对帧率几乎没什么影响。

总结

在到餐业务场景中,以 AR+LBS 的方式展现商家信息,可以给用户带来沉浸式的体验。本文介绍了 ARKit 的一些使用细节,总结了在开发过程中遇到的问题以及解决方案,希望可以给其他开发者带来一点参考价值。

作者简介

  • 曹宇,美团 iOS 开发工程师。2017年加入美团到店餐饮事业群,参与美团客户端美食频道开发工作。

招聘信息

到店餐饮技术部,负责美团和点评两个平台的美食频道相关业务,服务于数以亿计用户,通过更好的榜单、真实的评价和完善的信息为用户提供更好的决策支持,致力于提升用户体验。我们同时承载所有餐饮商户端线上流量,为餐饮商户提供多种营销工具,提升餐饮商户营销效率,最终达到让国人“Eat Better、Live Better”的美好愿景!我们的团队需要经验丰富的FE方向高级/资深工程师和技术专家,欢迎有兴趣的同学投递简历至wangying49#meituan.com。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/478972.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

腾讯天衍实验室新算法入选国际万维网大会 新冠疫苗AI问答上线

新冠疫苗的效用和安全性如何?怎么预约接种新冠疫苗?哪些人可以接种新冠疫苗?接种新冠疫苗是否收费?进入年底,随着全国新冠疫苗接种工作规范有序开展,各地接种人数在不断增加,但在逐步面向全民推…

LeetCode 965. 单值二叉树

文章目录1. 题目2. 解题2.1 递归2.2 循环1. 题目 如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时,才返回 true;否则返回 false。 2. 解题 2.1 递归 class Solution { public:bool isUnivalT…

论文浅尝 - SIAM ICDM 2020 | 基于图时空网络的知识引导的诊断预测

论文笔记整理:吴锐,东南大学硕士。来源:SIAM ICDM 2020论文下载地址:https://epubs.siam.org/doi/abs/10.1137/1.9781611976236.3 动机基于电子病历(EHR,Electronic Health Records)对患者未来的…

Oceanus:美团HTTP流量定制化路由的实践

背景 Oceanus是美团基础架构部研发的统一HTTP服务治理框架,基于Nginx和ngx_lua扩展,主要提供服务注册与发现、动态负载均衡、可视化管理、定制化路由、安全反扒、session ID复用、熔断降级、一键截流和性能统计等功能。本文主要讲述Oceanus如何通过策略抽…

文本对抗攻击入坑宝典

文 | 阿毅编 | 小轶如果是咱家公众号的忠实粉丝就一定还记得之前咱家一篇关于NLP Privacy的文章,不出意外的话,你们是不是现在依然还担心自己的隐私被输入法窃取而瑟瑟发抖。所以,我们又来了!今天给大家讨论的是NLP Privacy中一个…

LeetCode 350. 两个数组的交集 II(哈希)

文章目录1. 题目2. 解题2.1 hash2.2 数组已排序1. 题目 给定两个数组,编写一个函数来计算它们的交集。 示例 1:输入: nums1 [1,2,2,1], nums2 [2,2] 输出: [2,2] 示例 2:输入: nums1 [4,9,5], nums2 [9,4,9,8,4] 输出: [4,9] 说明:输出结果中每个元…

会议交流 | CCKS2020 第十四届全国知识图谱与语义计算大会

CCKS2020第十四届全国知识图谱与语义计算大会China Conference on Knowledge Graph and Semantic Computing, 2020南昌.江西,11月12日-15日主办: 中国中文信息学会语言与知识计算专业委员会承办: 江西师范大学会议网站:www.sigkg.cn/ccks2020大会主题&a…

用微前端的方式搭建类单页应用

前言 微前端由ThoughtWorks 2016年提出,将后端微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。 美团已经是一家拥有几万人规模的大型互联网公司,提升整体效率至关重要,这需要很…

12种NumpyPandas高效技巧

文 | Kunal Dhariwal本文分享给大家 12 种 Numpy 和 Pandas 函数,这些高效的函数会令数据分析更为容易、便捷。最后,读者也可以在 GitHub 项目中找到本文所用代码的 Jupyter Notebook。项目地址:https://github.com/kunaldhariwal/12-Amazing…

LeetCode 1002. 查找常用字符(哈希)

1. 题目 给定仅有小写字母组成的字符串数组 A,返回列表中的每个字符串中都显示的全部字符(包括重复字符)组成的列表。例如,如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符…

抖音算法推荐机制详解

抖音算法推荐机制详解!(科普向) 众所周知抖音的流量分配是去中心化的,这种去中心化算法,让每个人都有机会爆红,可为什么别人几个粉玩抖音,就能轻松获得10w点赞?而你怒拍几十条也枉然? 抖音的…

论文浅尝 - ICLR2020 | 用于半监督分类的图形推理学习

论文笔记整理:周虹廷,浙江大学研究生。研究方向:知识图谱,图表示学习等。论文链接:https://arxiv.org/pdf/2001.06137.pdf本文是发表在ICLR2020上针对图数据做节点半监督分类任务的论文。现有的算法解决图上节点分类问…

WMRouter:美团外卖Android开源路由框架

WMRouter是一款Android路由框架,基于组件化的设计思路,功能灵活,使用也比较简单。 WMRouter最初用于解决美团外卖C端App在业务演进过程中的实际问题,之后逐步推广到了美团其他App,因此我们决定将其开源,希望…

Android官方开发文档Training系列课程中文版:管理系统UI之变暗系统条

原文地址:http://android.xsoftlab.net/training/system-ui/index.html 引言 系统条(System Bars)是屏幕上的一块显示区域,专门用来显示通知,设备的通讯状态以及设备的导向。典型的System Bars与APP同时显示在屏幕上。APP展示了具体的内容&…

实话实说:中文自然语言处理的N个真实情况

文 | Liu Huanyong按语中文自然语言处理,目前在AI泡沫之下,真假难辨,实战技术与PPT技术往往存在着很大的差异。目前关于AI或者自然语言处理,做的人与讲的人往往是两回事。作者简介Liu Huanyong,就职于中国科学院软件研…

Android官方开发文档Training系列课程中文版:管理系统UI之隐藏状态条

原文地址:http://android.xsoftlab.net/training/system-ui/status.html 这节课将会介绍如何隐藏不同的版本的状态条。隐藏状态条可以使内容展示区域更大,因此可以提供一种更强的身临其境的用户体验。 含有状态条的APP: 隐藏状态条的APP&am…

论文浅尝 - ACL2020 | 用于回答知识库中的多跳复杂问题的查询图生成方法

论文笔记整理:谭亦鸣,东南大学博士。来源:ACL 2020链接:https://www.aclweb.org/anthology/2020.acl-main.91.pdf1.介绍在以往的工作中,知识图谱复杂问答一般被分为两种类型分别处理:其一是带有约束的问题&…

深入理解JSCore

背景 动态化作为移动客户端技术的一个重要分支,一直是业界积极探索的方向。目前业界流行的动态化方案,如Facebook的React Native,阿里巴巴的Weex都采用了前端系的DSL方案,而它们在iOS系统上能够顺利的运行,都离不开一个…

全球44家机构,55位大佬,历时两年,打造最强NLG评测基准!

文 | 小轶(大家好,我是已经鸽了夕总仨月没写文章了的小轶(y)!新的一年一定改过自新,多读paper多写稿,望广大读者敦促(ง •̀_•́)ง)今天要和大家分享的是卖萌屋学术站上的本月最热…

LeetCode 171. Excel表列序号(26进制转10进制)

1. 题目 给定一个Excel表格中的列名称,返回其相应的列序号。 例如,A -> 1B -> 2C -> 3...Z -> 26AA -> 27AB -> 28 输入: "A" 输出: 1输入: "AB" 输出: 28输入: "ZY" 输出: 701来源:力扣&…