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,技术干货首发微信,第一时间推送。

相关文章
相关标签/搜索