Find me - 用 ARKit 找人

引言

从 Apple 发布 ARKit 框架起,我就一直想学习并作点好玩的东西,后来就勾搭了滑滑鸡大佬来上海 Code 沙龙第八次活动讲他作的 ARGitHubCommits,学习了一些 ARKit 的基础知识,后来持续跟进了一波,看了不少张嘉夫大佬的 ARKit 文章,也看了一些 ARKit 开源的项目。那会微博上有一个 ARKit 不停的弹 Windows 告警对话框的动态图特别火,看到的时候我就想,弹出来的这一堆对话框好像一条路径啊,这也是 Find me 这个 App 最初的灵感来源。node

探索

后来就想利用这个特性作一些寻路的方面的探索,最开始是想作在家里找东西,脑洞以下:git

  • 在家里找一个位置固定不变的物品(好比电视机)作为参考点。
  • 为每件物品录制一条路径到这个参考点。
  • 想找某件物品的时候先找到这个参考点,而后就能够经过以前录制的路径找到这件物品了。

可是发现有两个特别大的痛点:github

  • 东西位置常常会变,路径也要跟着变。
  • 找不到的东西每每是乱放的东西,因此固然也没有路径记录一说。

因此这个脑洞就被我 Pass 了,项目也搁置了一段时间。swift

而后有一天,我约了朋友去商场吃饭。他先到了店里,可是我确始终找不到这个店,问了半天才在一个很隐蔽的角落找到了这个店。约完回家以后灵感突发,这个场景彻底能够用以前找东西的思路来作啊:bash

  • 先到商场的人找一个好找的位置,好比商场北门。
  • 从这个位置开始记录一条到门店的路径,并分享给后来的朋友。
  • 后来的朋友找到这条路径的起点,也就是以前说的商场北门,打开先到的人分享的路径便可沿着路径找到约好见面的门店。

关键问题:从使用场景来讲,没有痛点。session

这也是我今天文章内容的主角 - Find me 实现的功能。并发

目前这个 App 已经发布上架:Find me,并且代码已经开源了,地址在:mmoaay/Findme(喜欢的话记得点个 Star),为何选择开源呢?由于只是一个创意,并无太多的技术壁垒,并且目前在技术上确实存在两个问题:app

  • 路径记录和寻找过程当中 ARKit 的 Session 不能被打断,由于恢复以后的虚拟坐标会产生极大误差,致使虚拟路径进入不可控状态。
  • ARKit 目前不稳定,虚拟坐标系会抖动,这样就会致使虚拟路径有偏移,并且距离越远,误差越大。

产生这两个问题的缘由主要都在虚拟世界坐标上,咱们都知道,ARKit 初始化的时候,会基于你当前位置为世界原点,创建了一个虚拟世界坐标系:框架

因此,Find me 记录并分享出来的路径,对 ARKit 虚拟世界的坐标系有两个基本要求:学习

  • 记录路径和根据路径找人时虚拟世界的原点必定要映射到现实世界中的同一个点,也就是分享和寻找的起点位置要是同一个。
  • 虚拟世界的水平垂直方向要和现实世界同样。

优化

对于这两个问题,我也作了一些优化。

目前失败的两个优化

基于定位优化

思路很简单:根据定位将路径分段记录,并修正虚拟路径。至关于给虚拟路径加一个现实世界的坐标修正。

实践以后发现:定位比 ARKit 还不许,尤为在商场内,基于 WiFi 的定位基本上能让你的位置处处跳。Pass!

基于距离优化

这个方案的思路是:根据你在虚拟世界移动的距离分段记录路径。

实践以后问题也来了:ARKit 的初始化太慢了,在个人 iPhone 7 上须要的时间足足有 3 秒…并且 ARKit 的 Seesion 还不能并发,由于摄像头只有一个,只有上一个结束了,下一个才能开始,最后发现路径根本无法记录…

成功优化

ARKit 使用优化

设置 ARWorldTrackingConfigurationworldAlignment.gravityAndHeading

首先咱们来看一下 WorldAlignment 类型:

/**
Enum constants for indicating the world alignment.
*/
@available(iOS 11.0, *)
public enum WorldAlignment : Int {

        
    /** Aligns the world with gravity that is defined by vector (0, -1, 0). */
    case gravity

        
    /** Aligns the world with gravity that is defined by the vector (0, -1, 0)
     and heading (w.r.t. True North) that is given by the vector (0, 0, -1). */
    case gravityAndHeading

        
    /** Aligns the world with the camera’s orientation. */
    case camera
}
复制代码
  • .gravity:只有重力,也就是坐标系的垂直方向和真实世界一致,可是水平方向不定。
  • .gravityAndHeading:重力和指北,垂直和水平方向都和真实世界一致。
  • .camera:摄像头方向,也就是坐标系和手机保持一致。

因此这就是咱们选择 .gravityAndHeading 的缘由。这样一来,根据路径找人的那我的就只须要找到路径起始点的真实位置便可,手机的方向就不重要了,极大下降的使用门槛。

提供路径起始点图片

这个优化的内容是:分享路径的时候提供一张起始点的照片。这样拿到路径的人就拿图片和本身所在的场景作一个大体的比对来肯定分享路径的人当时的位置,看一下使用效果:

这张照片咱们直接用 ARKitsceneView.session.currentFrame 属性获取,以下:

if let currentFrame = sceneView.session.currentFrame, let image = UIImage(pixelBuffer: currentFrame.capturedImage, context:CIContext()) {
}
复制代码

使用建议

基于上面说的状况,若是你在平常生活中确实想使用 Find me,下面是一些很是重要的使用建议

  • 若是在记录或者寻路过程当中,有新消息,千万不要切出去看,毕竟商场内导航也就是 10 分钟左右的事情,正常晚 10 分钟回复消息也没事。
  • 寻路的人必定要找准路径初始点!很是重要!这个直接决定了路径终点位置的准确度。

其余一些比较有趣的技术点

路径中的箭头实现

先看一下效果:

这个技术点包含两个部分:

  • 怎么绘制箭头?
  • 若是让箭头指向下一个点的位置?

怎么绘制箭头?

首先,咱们生成一个以下图形状的 UIBezierPath

代码以下:

private static func vertexCoordinates() -> [CGPoint] {
    return [CGPoint(x: 0, y: 0),
            CGPoint(x: 20, y: 0),
            CGPoint(x: 20, y: 10),
            CGPoint(x: 10, y: 10),
            CGPoint(x: 10, y: 20),
            CGPoint(x: 0, y: 20)
    ]
}
    
private static func arrowPath() -> UIBezierPath {
    let path = UIBezierPath()
    let points = NodeUtil.vertexCoordinates()
        
    var count = 0
    for point in points {
        if 0 == count {
            path.move(to: point)
        } else {
            path.addLine(to: point)
        }
        count += 1
    }
        
    path.close()
        
    return path
}
复制代码

而后用下面的代码生成 SCNNode

let path = NodeUtil.arrowPath()
let shape = SCNShape(path: path, extrusionDepth: 2)

let node = SCNNode(geometry: shape)
复制代码

这样一个箭头节点就生成了。

若是让箭头指向下一个点的位置?

其实就是要把这个箭头旋转必定的角度。这里涉及到一个数学知识:根据两个点算它们的连线与某个坐标轴的角度。我数学很差,原理就不讲了,主要讲一下 SceneKit 中如何旋转 SCNNode

首先咱们须要两个点,当前点和上一次的点,因此咱们经过一个 SCNVector3 类型的 last 变量来记录上一次的点,SCNVector3 包含 x、y、z 三个属性,分别对应了 x、y、z 轴的值。

而后根据当前点和上一次点的位置获得角度,用 SCNAction.rotateBy 来生成一个旋转动做,再使用 SCNNoderunAction 方法来执行这个动做便可。

最终代码以下:

node.runAction(SCNAction.rotateBy(x: CGFloat(Float.pi/2.0*3.0), y:CGFloat(Float.pi/4.0+atan2(current.x-last.x, current.z-last.z)), z: 0.0, duration: 0.0))
复制代码

文件分享的一个坑

Find me 目前分享路径采用的方式是文件分享,分享出去采用的是 UIDocumentInteractionController,接受别人分享的路径主要经过 func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool 回调获取,这里它会经过 url 参数返回分享文件的路径,这里有个点,若是你直接打开这个文件,会发现这个文件并不存在…

解决方法比较有趣:经过这个路径把文件拷贝到另一个路径,在打开这个文件就能够了。

总结

ARKit 作为 Apple 新发布的框架,后期必定会进行更深刻的优化,因此 Find me 路径的准确度将来仍是很值得期待的,固然我也会对 Find me 作持续的改进,欢迎你们关注。

另外我的感受 ARKit 框架的学习门槛确实不高,主要门槛反而在 SceneKit 或者 SpriteKit 上,你们有兴趣也能够看看张嘉夫大佬的一些教程,质量很高。

相关文章
相关标签/搜索