iOS計算機視覺—ARKit

ARKit介紹

AR 全稱 Augmented Reality(加強現實)是一種在視覺上呈現虛擬物體與現實場景結合的技術。Apple 公司在 2017 年 6 月正式推出了 ARKit,iOS 開發者能夠在這個平臺上使用簡單便捷的 API 來開發 AR 應用程序。爲了得到 ARKit 的完整功能,須要 A9 及以上芯片。其實也就是大部分運行 iOS 11 的設備,包括 iPhone 6S。數組

研究過程當中,作了一個捲尺的Demo,如今介紹下項目中用到的技術點。bash

項目實踐

iOS 平臺的 AR 應用一般由 ARKit 和渲染引擎兩部分構成:微信

下面主要說說ARKit的功能點和渲染部分SceneKit兩個方面。session

ARKit

ARKit 的 ARSession 負責管理每一幀的信息。ARSession 作了兩件事:拍攝圖像並獲取傳感器數據;對數據進行分析處理後逐幀輸出。以下圖:dom

設備追蹤

設備追蹤確保了虛擬物體的位置不受設備移動的影響。在啓動 ARSession 時須要傳入一個 ARSessionConfiguration 的子類對象,以區別三種追蹤模式:ui

  • ARFaceTrackingConfiguration
  • ARWorldTrackingConfiguration
  • AROrientationTrackingConfiguration

其中 ARFaceTrackingConfiguration 能夠識別人臉的位置、方向以及獲取拓撲結構。此外,還能夠探測到預設的 52 種豐富的面部動做,如眨眼、微笑、皺眉等等。ARFaceTrackingConfiguration 須要調用支持 TrueDepth 的前置攝像頭進行追蹤。 本項目主要是使用ARWorldTrackingConfiguration進行追蹤,獲取特徵點。spa

追蹤步驟
// 建立一個 ARSessionConfiguration.
// 暫時無需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
複製代碼

從上面的代碼看,運行一個 ARSession 的過程是很簡單的,那麼 ARSession 的底層如何進行世界追蹤的呢?3d

  • 首先,ARSession 底層使用了 AVCaputreSession 來獲取攝像機拍攝的視頻(一幀一幀的圖像序列)。
  • 其次,ARSession 底層使用了 CMMotionManager 來獲取設備的運動信息(好比旋轉角度、移動距離等)
  • 最後,ARSession 根據獲取的圖像序列以及設備的運動信息進行分析,最後輸出 ARFrame,ARFrame 中就包含有渲染虛擬世界所需的全部信息。

追蹤信息點

AR-World 的座標系以下,當咱們運行 ARSession 時設備所在的位置就是 AR-World 的座標系原點。code

在這個 AR-World 座標系中,ARKit 會追蹤如下幾個信息:orm

  • 追蹤設備的位置以及旋轉,這裏的兩個信息均是相對於設備起始時的信息。
  • 追蹤物理距離(以「米」爲單位),例如 ARKit 檢測到一個平面,咱們但願知道這個平面有多大。
  • 追蹤咱們手動添加的但願追蹤的點,例如咱們手動添加的一個虛擬物體。
追蹤如何工做

蘋果文檔中對世界追蹤過程是這麼解釋的:ARKit使用視覺慣性測距技術,對攝像頭採集到的圖像序列進行計算機視覺分析,而且與設備的運動傳感器信息相結合。ARKit 會識別出每一幀圖像中的特徵點,而且根據特徵點在連續的圖像幀之間的位置變化,而後與運動傳感器提供的信息進行比較,最終獲得高精度的設備位置和偏轉信息。

  • 上圖中劃出曲線的運動的點表明設備,能夠看到以設備爲中心有一個座標系也在移動和旋轉,這表明着設備在不斷的移動和旋轉。這個信息是經過設備的運動傳感器獲取的。
  • 動圖中右側的黃色點是 3D 特徵點。3D特徵點就是處理捕捉到的圖像獲得的,能表明物體特徵的點。例如地板的紋理、物體的邊邊角角均可以成爲特徵點。上圖中咱們看到當設備移動時,ARKit 在不斷的追蹤捕捉到的畫面中的特徵點。
  • ARKit 將上面兩個信息進行結合,最終獲得了高精度的設備位置和偏轉信息。
ARWorldTrackingConfiguration

ARWorldTrackingConfiguration 提供 6DoF(Six Degree of Freedom)的設備追蹤。包括三個姿態角 Yaw(偏航角)、Pitch(俯仰角)和 Roll(翻滾角),以及沿笛卡爾座標系中 X、Y 和 Z 三軸的偏移量:

不只如此,ARKit 還使用了 VIO(Visual-Inertial Odometry)來提升設備運動追蹤的精度。在使用慣性測量單元(IMU)檢測運動軌跡的同時,對運動過程當中攝像頭拍攝到的圖片進行圖像處理。將圖像中的一些特徵點的變化軌跡與傳感器的結果進行比對後,輸出最終的高精度結果。 從追蹤的維度和準確度來看,ARWorldTrackingConfiguration 很是強悍。但如官方文檔所言,它也有兩個致命的缺點:

  • 受環境光線質量影響
  • 受劇烈運動影響

因爲在追蹤過程當中要經過採集圖像來提取特徵點,因此圖像的質量會影響追蹤的結果。在光線較差的環境下(好比夜晚或者強光),拍攝的圖像沒法提供正確的參考,追蹤的質量也會隨之降低。

追蹤過程當中會逐幀比對圖像與傳感器結果,若是設備在短期內劇烈的移動,會很大程度上干擾追蹤結果。

追蹤狀態

世界追蹤有三種狀態,咱們能夠經過 camera.trackingState 獲取當前的追蹤狀態。

從上圖咱們看到有三種追蹤狀態:

  • Not Available:世界追蹤正在初始化,還未開始工做。
  • Normal: 正常工做狀態。
  • Limited:限制狀態,當追蹤質量受到影響時,追蹤狀態可能會變爲 Limited 狀態。

與 TrackingState 關聯的一個信息是 ARCamera.TrackingState.Reason,這是一個枚舉類型:

  • case excessiveMotion:設備移動過快,沒法正常追蹤。
  • case initializing:正在初始化。
  • case insufficientFeatures:特徵過少,沒法正常追蹤。
  • case none:正常工做。

咱們能夠經過 ARSessionObserver 協議去獲取追蹤狀態的變化,比較簡單,能夠直接查看接口文檔。

ARFrame

ARFrame 中包含有世界追蹤過程獲取的全部信息,ARFrame 中與世界追蹤有關的信息主要是:anchors 和 camera:

  • camera: 含有攝像機的位置、旋轉以及拍照參數等信息。
var camera: [ARCamera]
複製代碼
  • ahchors: 表明了追蹤的點或面。
var anchors: [ARAnchor]
複製代碼
ARAnchor

  • ARAnchor 是空間中相對真實世界的位置和角度。
  • ARAnchor 能夠添加到場景中,或是從場景中移除。基本上來講,它們用於表示虛擬內容在物理環境中的錨定。因此若是要添加自定義 anchor,添加到 session 裏就能夠了。它會在 session 生命週期中一直存在。但若是你在運行諸如平面檢測功能,ARAnchor 則會被自動添加到 session 中。
  • 要響應被添加的 anchor,能夠從 current ARFrame 中得到完整列表,此列表包含 session 正在追蹤的全部 anchor。
  • 或者也能夠響應 delegate 方法,例如 add、update 以及 remove,session 中的 anchor 被添加、更新或移除時會通知。
ARCamera

每一個 ARFrame 都會包含一個 ARCamera。ARCamera 對象表示虛擬攝像頭。虛擬攝像頭就表明了設備的角度和位置。

  • ARCamera 提供了一個 transform。transform 是一個 4x4 矩陣。提供了物理設備相對於初始位置的變換。
  • ARCamera 提供了追蹤狀態(tracking state),通知你如何使用 transform,這個在後面會講。
  • ARCamera 提供了相機內部功能(camera intrinsics)。包括焦距和主焦點,用於尋找投影矩陣。投影矩陣是 ARCamera 上的一個 convenience 方法,可用於渲染虛擬你的幾何體。
場景解析

場景解析主要功能是對現實世界的場景進行分析,解析出好比現實世界的平面等信息,可讓咱們把一些虛擬物體放在某些實物處。ARKit 提供的場景解析主要有平面檢測、場景交互以及光照估計三種,下面逐個分析。

平面檢測(Plane detection)
  • ARKit 的平面檢測用於檢測出現實世界的水平面。

上圖中能夠看出,ARkit 檢測出了兩個平面,圖中的兩個三維座標系是檢測出的平面的本地座標系,此外,檢測出的平面是有一個大小範圍的。

  • 平面檢測是一個動態的過程,當攝像機不斷移動時,檢測到的平面也會不斷的變化。下圖中能夠看到當移動攝像機時,已經檢測到的平面的座標原點以及平面範圍都在不斷的變化。

  • 此外,隨着平面的動態檢測,不一樣平面也可能會合併爲一個新的平面。下圖中能夠看到已經檢測到的平面隨着攝像機移動合併爲了一個平面。

  • 開啓平面檢測

開啓平面檢測很簡單,只須要在 run ARSession 以前,將 ARSessionConfiguration 的 planeDetection 屬性設爲 true 便可。

// Create a world tracking session configuration.
let configuration = ARWorldTrackingSessionConfiguration()
configuration.planeDetection = .horizontal
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
複製代碼
  • 平面的表示方式

當 ARKit 檢測到一個平面時,ARKit 會爲該平面自動添加一個 ARPlaneAnchor,這個 ARPlaneAnchor 就表示了一個平面。

  • 當 ARKit 系統檢測到新平面時,ARKit 會自動添加一個 ARPlaneAnchor 到 ARSession 中。咱們能夠經過 ARSessionDelegate 獲取當前 ARSession 的 ARAnchor 改變的通知,主要有如下三種狀況:

新加入了 ARAnchor

func session(_ session: ARSession, didAdd anchors: [ARAnchor])
複製代碼

對於平面檢測來講,當新檢測到某平面時,咱們會收到該通知,通知中的 ARAnchor 數組會包含新添加的平面,其類型是 ARPlaneAnchor,咱們能夠像下面這樣使用:

func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
    for anchor in anchors {
        if let anchor = anchor as? ARPlaneAnchor {
            print(anchor.center)
            print(anchor.extent)
        }
    }
}
複製代碼

ARAnchor 更新

func session(_ session: ARSession, didUpdate anchors: [ARAnchor])
複製代碼

從上面咱們知道當設備移動時,檢測到的平面是不斷更新的,當平面更新時,會回調這個接口。

刪除 ARAnchor

func session(_ session: ARSession, didRemove anchors: [ARAnchor])
複製代碼

當手動刪除某個 Anchor 時,會回調此方法。此外,對於檢測到的平面來講,若是兩個平面進行了合併,則會刪除其中一個,此時也會回調此方法。

場景交互(Hit-testing)

Hit-testing 是爲了獲取當前捕捉到的圖像中某點擊位置有關的信息(包括平面、特徵點、ARAnchor 等)。

原理圖以下

當點擊屏幕時,ARKit 會發射一個射線,假設屏幕平面是三維座標系中的 xy 平面,那麼該射線會沿着 z 軸方向射向屏幕裏面,這就是一次 Hit-testing 過程。這次過程會將射線遇到的全部有用信息返回,返回結果以離屏幕距離進行排序,離屏幕最近的排在最前面。

ARFrame 提供了 Hit-testing 的接口:

func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]
複製代碼

上述接口中有一個 types 參數,該參數表示這次 Hit-testing 過程須要獲取的信息類型。ResultType 有如下四種:

  • featurePoint

表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的 3D 特徵點。以下圖:

  • estimatedHorizontalPlane

表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的預估平面。預估平面表示 ARKit 當前檢測到一個多是平面的信息,但當前還沒有肯定是平面,因此 ARKit 尚未爲此預估平面添加 ARPlaneAnchor。以下圖:

  • existingPlaneUsingExtent

表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的有大小範圍的平面。

上圖中,若是 Hit-testing 射線通過了有大小範圍的綠色平面,則會返回此平面,若是射線落在了綠色平面的外面,則不會返回此平面。

  • existingPlane

表示這次 Hit-testing 過程但願返回當前圖像中 Hit-testing 射線通過的無限大小的平面。

上圖中,平面大小是綠色平面所展現的大小,但 exsitingPlane 選項表示即便 Hit-testing 射線落在了綠色平面外面,也會將此平面返回。換句話說,將全部平面無限延展,只要 Hit-testing 射線通過了無限延展後的平面,就會返回該平面。

示例代碼以下

// Adding an ARAnchor based on hit-test
let point = CGPoint(x: 0.5, y: 0.5)  // Image center

// Perform hit-test on frame.
let results = frame. hitTest(point, types: [.featurePoint, .estimatedHorizontalPlane])

// Use the first result.
if let closestResult = results.first {
    // Create an anchor for it.
    anchor = ARAnchor(transform: closestResult.worldTransform)
    // Add it to the session.
    session.add(anchor: anchor)
}
複製代碼

上面代碼中,Hit-testing 的 point(0.5, 0.5)表明屏幕的中心,屏幕左上角爲(0, 0),右下角爲(1, 1)。 對於 featurePoint 和 estimatedHorizontalPlane 的結果,ARKit 沒有爲其添加 ARAnchor,咱們可使用 Hit-testing 獲取信息後本身爲 ARSession 添加 ARAnchor,上面代碼就顯示了此過程。

光照估計(Light estimation)

上圖中,一個虛擬物體茶杯被放在了現實世界的桌子上。

當週圍環境光線較好時,攝像機捕捉到的圖像光照強度也較好,此時,咱們放在桌子上的茶杯看起來就比較貼近於現實效果,如上圖最左邊的圖。可是當週圍光線較暗時,攝像機捕捉到的圖像也較暗,如上圖中間的圖,此時茶杯的亮度就顯得跟現實世界格格不入。

針對這種狀況,ARKit 提供了光照估計,開啓光照估計後,咱們能夠拿到當前圖像的光照強度,從而可以以更天然的光照強度去渲染虛擬物體,如上圖最右邊的圖。

光照估計基於當前捕捉到的圖像的曝光等信息,給出一個估計的光照強度值(單位爲 lumen,光強單位)。默認的光照強度爲 1000lumen,當現實世界較亮時,咱們能夠拿到一個高於 1000lumen 的值,相反,當現實世界光照較暗時,咱們會拿到一個低於 1000lumen 的值。

ARKit 的光照估計默認是開啓的,固然也能夠經過下述方式手動配置:

configuration.isLightEstimationEnabled = true
複製代碼

獲取光照估計的光照強度也很簡單,只須要拿到當前的 ARFrame,經過如下代碼便可獲取估計的光照強度:

let intensity = frame.lightEstimate?.ambientIntensity
複製代碼

SceneKit

渲染是呈現 AR world 的最後一個過程。此過程將建立的虛擬世界、捕捉的真實世界、ARKit 追蹤的信息以及 ARKit 場景解析的的信息結合在一塊兒,渲染出一個 AR world。渲染過程須要實現如下幾點才能渲染出正確的 AR world:

  • 將攝像機捕捉到的真實世界的視頻做爲背景。
  • 將世界追蹤到的相機狀態信息實時更新到 AR world 中的相機。
  • 處理光照估計的光照強度。
  • 實時渲染虛擬世界物體在屏幕中的位置。

若是咱們本身處理這個過程,能夠看到仍是比較複雜的,ARKit 爲簡化開發者的渲染過程,爲開發者提供了簡單易用的使用 SceneKit(3D 引擎)以及 SpriteKit(2D 引擎)渲染的視圖ARSCNView以及ARSKView。固然開發者也可使用其餘引擎進行渲染,只須要將以上幾個信息進行處理融合便可。

SceneKit 的座標系

咱們知道 UIKit 使用一個包含有 x 和 y 信息的 CGPoint 來表示一個點的位置,可是在 3D 系統中,須要一個 z 參數來描述物體在空間中的深度,SceneKit 的座標系能夠參考下圖:

這個三維座標系中,表示一個點的位置須要使用(x,y,z)座標表示。紅色方塊位於 x 軸,綠色方塊位於 y 軸,藍色方塊位於 z 軸,灰色方塊位於原點。在 SceneKit 中咱們能夠這樣建立一個三維座標:

let position = SCNVector3(x: 0, y: 5, z: 10)
複製代碼
SceneKit 中的場景和節點

咱們能夠將 SceneKit 中的場景(SCNScene)想象爲一個虛擬的 3D 空間,而後能夠將一個個的節點(SCNNode)添加到場景中。SCNScene 中有惟一一個根節點(座標是(x:0, y:0, z:0)),除了根節點外,全部添加到 SCNScene 中的節點都須要一個父節點。

下圖中位於座標系中心的就是根節點,此外還有添加的兩個節點 NodeA 和 NodeB,其中 NodeA 的父節點是根節點,NodeB 的父節點是 NodeA:

SCNScene 中的節點加入時能夠指定一個三維座標(默認爲(x:0, y:0, z:0)),這個座標是相對於其父節點的位置。這裏說明兩個概念:

  • 本地座標系:以場景中的某節點(非根節點)爲原點創建的三維座標系
  • 世界座標系:以根節點爲原點建立的三維座標系稱爲世界座標系。

上圖中咱們能夠看到 NodeA 的座標是相對於世界座標系(因爲 NodeA 的父節點是根節點)的位置,而 NodeB 的座標表明了 NodeB 在 NodeA 的本地座標系位置(NodeB 的父節點是 NodeA)。

SceneKit 中的攝像機

有了 SCNScene 和 SCNNode 後,咱們還須要一個攝像機(SCNCamera)來決定咱們能夠看到場景中的哪一塊區域(就比如現實世界中有了各類物體,但還須要人的眼睛才能看到物體)。攝像機在 SCNScene 的工做模式以下圖:

上圖中包含如下幾點信息:

  • SceneKit 中 SCNCamera 拍攝的方向始終爲 z 軸負方向。
  • 視野(Field of View)是攝像機的可視區域的極限角度。角度越小,視野越窄,反之,角度越大,視野越寬。
  • 視錐體(Viewing Frustum)決定着攝像頭可視區域的深度(z 軸表示深度)。任何不在這個區域內的物體將被剪裁掉(離攝像頭太近或者太遠),不會顯示在最終的畫面中。

在 SceneKit 中咱們可使用以下方式建立一個攝像機:

let scene = SCNScene()
let cameraNode = SCNNode()
let camera = SCNCamera()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(cameraNode)
複製代碼
SCNView

最後,咱們須要一個 View 來將 SCNScene 中的內容渲染到顯示屏幕上,這個工做由 SCNView 完成。這一步其實很簡單,只須要建立一個 SCNView 實例,而後將 SCNView 的 scene 屬性設置爲剛剛建立的 SCNScene,而後將 SCNView 添加到 UIKit 的 view 或 window 上便可。示例代碼以下:

let scnView = SCNView()
scnView.scene = scene
vc.view.addSubview(scnView)
scnView.frame = vc.view.bounds
複製代碼

關注我

歡迎關注公衆號:jackyshan,技術乾貨首發微信,第一時間推送。

相關文章
相關標籤/搜索