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 上,你們有興趣也能夠看看張嘉夫大佬的一些教程,質量很高。

相關文章
相關標籤/搜索