ARKit和CoreLocation:第一部分

演示代码 ARKit和CoreLocation:第一部分 ARKit和CoreLocation:第二部分 ARKit和CoreLocation:第三部分

背景

自从我写了一篇新的博客帖子以来,已经有一段时间了,这有所不同。这篇文章和下一篇文章是关于我使用ARKitCoreLocation进行实验的两部分系列!第一部分将介绍ARKit的基础知识,从MapKit获取方向以及触摸矩阵变换的基础知识。在第二部分将讨论计算两个位置,以及如何利用位置数据,并翻译成在ARKit场景的位置之间的轴承。

介绍

image.png

提到“增强现实”,跳入大多数人头脑的第一件事是PokemonGO。如果你像大多数人一样,你可能已经玩了一两次(或者说是痴迷。)PokemonGO证明了在设置时,没有什么能比我们的世界更好。像PokemonGO一样令人敬畏,它只是对增强现实体验的深度和潜力的一瞥。

Apple文档

增强现实(AR)描述了用户体验,这些体验将2D或3D元素从设备的相机添加到实时视图中,使得这些元素看起来居住在现实世界中。ARKit结合了设备运动跟踪,摄像机场景捕捉,高级场景处理和显示便利性,简化了构建AR体验的任务。

iOS 11中,Apple已经将ARKit的强大功能释放到了iOS开发社区。我们还有几个星期的iOS 11上线,但我们已经看到的可能会重新定义移动用户体验的可能性。

首先,一些基础知识

个人项目 - 8月20日

那么,它真的很棒吗?我不想成为那个说这个的人,但不是,这只是数学。所以,如果它不是魔术,他们怎么把它拉下来?视觉惯性测距!(快说十倍。)

定义

视觉惯性测距(VIO):ARKit分析手机摄像头和运动数据,以便跟踪周围的世界。计算机视觉记录了环境中的显着特征,无论iPhone的移动如何,都能够保持对现实世界中位置的了解。

Apple非常喜欢围绕会话组织代码。会话是一种封装应用程序活动的定义时段内包含的逻辑和数据的方法。使用URLSession时,这是应用程序发送网络请求并接收数据作为回报时的逻辑和数据。

ARSession在ARKit中,ARSession协调创建增强现实体验所需的逻辑和数据。这包括摄像机和运动数据以及在周围移动时跟踪世界所需的计算。

ARFrame:** ARFrame**包含视频帧数据和位置跟踪数据,这些数据将传递到currentFrame属性中的ARSession。ARKit将图像数据与运动跟踪数据结合起来,以计算iPhone的位置。

[ARAncho - R ****:一种ARAnchor是在保持了不管相机(理论上)的运动或位置的真实世界的位置。它固定在一个特定的位置,并且大部分将保留在那里。

ARConfiguration

image.png

ARWorldTrackingConfiguration用于跟踪设备方向,位置和检测相机记录的特征点(如相机记录的表面)的配置。ARConfigurations根据摄像机和运动数据,将您和手机所在的物理世界与手机生成的虚拟坐标空间相连接。

worldAlignment:****ARSession上的worldAlignment属性定义ARSession如何在3D坐标映射系统上解释ARFrame的运动数据,该系统用于跟踪世界并构建增强现实体验。

image.png

worldAlignment - Apple Docs

创建AR体验取决于能够构建用于将对象放置在虚拟3D世界中的坐标系,该虚拟3D世界映射到设备的真实位置和运动。运行会话配置时,ARKit会根据设备的位置和方向创建场景坐标系; ARAnchor您创建的任何对象或AR会话检测到的对象都是相对于该坐标系定位的

**重力:**通过将对齐设置为**重力,** ARKit将y轴与重力平行对齐,z轴和x轴沿着设备的原始方向定向

image.png

worldAlignment.gravity - Apple Docs

首次运行会​​话配置时设备的位置和方向决定了坐标系的其余部分:对于z轴,ARKit选择(0,0,-1)指向设备摄像机面向和垂直于重力轴的方向的基矢量。ARKit使用右手规则选择基于z轴和y轴的x轴 - 也就是说,基矢量(1,0,0)与其他两个轴正交,并且(对于在负z方向上看的观察者)指向正确的。

image.png

gravityAndHeading:通过将对齐设置为**gravityAndHeading ** ARKit将y轴与重力平行对齐,z轴和x轴朝向罗盘方向。原点位于设备的初始位置。虽然这在大多数情况下都是准确的,但它的精确度并不高得多,因此创建沉浸式增强现实体验同时完全依赖于这些数据可能会非常棘手。</pre>

资源

worldAlignment.gravityAndHeading - Apple Docs

虽然此选项固定方向的三个坐标轴,以真实世界的方向,该位置的坐标系的原点仍是相对于设备,匹配当会话配置是第一次运行的设备的位置。

SceneKit

关于ARKit最酷的事情之一是它与Apple现有的图形渲染引擎很好地集成:SpriteKit,Metal和SceneKit。我最常用的是SceneKit,它用于渲染3D对象。

个人项目 - 8月11日

定义

ARSCNView ARSCNView是SCNView的子类,它是用于渲染3D内容的标准SceneKit视图。因为它专门用于ARKit,它具有一些非常酷的功能。例如,它可以无缝访问手机的相机。甚至更酷,视图的SceneKit场景的世界坐标系统直接响应由会话配置建立的AR世界坐标系。它还会自动移动SceneKit相机以匹配iPhone的实际移动。

个人项目 - 8月12日

ARSCN查看文档

因为ARKit自动将SceneKit空间与现实世界匹配,所以放置虚拟对象以使其看起来保持真实世界位置只需要适当地设置该对象的SceneKit位置。

您不一定需要使用ARAnchor该类来跟踪添加到场景中的对象的位置,但通过实现ARSCNViewDelegate方法,您可以将SceneKit内容添加到ARKit自动检测到的任何锚点。

将节点添加到场景

https://developer.apple.com/documentation/scenekit/scnsphere

在我们继续之前,让我们先做一些基本的事情。让我们构建我们的第一个增强现实体验!为此,我们将在相机前放置1米蓝色球。

定义

SCNSphere一个球体定义一个表面,其每个点与其中心等距离,该中心位于其局部坐标空间的原点。您可以使用其 radius 属性在所有三个维度中定义球体的大小。

SCNGeometry可以在场景中显示的三维形状(也称为模型或网格),附加材料定义其外观。

SphereNode球形码

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {
    
   @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // setup
    }
    
    // viewWillAppear & viewWillDisappear
    
    func createSphereNode(with radius: CGFloat, color: UIColor) -> SCNNode {
        let geometry = SCNSphere(radius: radius)
        geometry.firstMaterial?.diffuse.contents = color
        let sphereNode = SCNNode(geometry: geometry)
        return sphereNode
    }
}
sceneView.scene = SCNScene()        
// Add Scene
let circleNode = createSphereNode(with: 0.2, color: .blue)  
// Add Sphere
     
circleNode.position = SCNVector3(0, 0, -1) // 1 meter in front 
// Give sphere position    
   
sceneView.scene.rootNode.addChildNode(circleNode)
// Add to scene as childNode of rootNode

把它放在一起

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.delegate = self
        sceneView.showsStatistics = true
        sceneView.scene = SCNScene()
        let circleNode = createSphereNode(with: 0.2, color: .blue)
        circleNode.position = SCNVector3(0, 0, -1) // 1 meter in front of camera 
        sceneView.scene.rootNode.addChildNode(circleNode)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }
    
    func createSphereNode(with radius: CGFloat, color: UIColor) -> SCNNode {
        let geometry = SCNSphere(radius: radius)
        geometry.firstMaterial?.diffuse.contents = color
        let sphereNode = SCNNode(geometry: geometry)
        return sphereNode
    }
}

SceneKit和ARKit坐标以米为单位进行量化。当我们将SCNVector3上的最后一个属性设置为-1时,我们将z轴设置为摄像机前面的一米。如果一切按计划进行(应该),屏幕将显示如下内容:

image.png

目前这种方法很好。我们的球体将自动显示为跟踪真实世界的位置,因为ARKit将SceneKit空间与真实世界空间相匹配。如果我们想要使用坐标,我们可能需要找到一些持久的东西来锚定提示我们的节点将来。

向量,矩阵和线性代数,哦不!

一个二乘四的矩阵

如果你还记得回到数学课,那么矢量有一个幅度和方向。

在数学,物理和工程中,欧几里德矢量(有时称为几何或空间矢量,或者 - 在这里 - 简称矢量)是具有幅度(或长度)和方向的几何对象。

在编程时,矢量只是一个数字数组。每个数字是向量的“维度”。

简单地说,我们的向量使用2乘1矩阵。让我们给它一个x = 1的值。矢量(1,0)的图形看起来像:

image.png

我们可以在一个非常简单的矩阵中表达相同的向量(1,0):

![X超过Y(https://upload-images.jianshu.io/upload_images/910914-131876c1ac3bc24b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如上所述:

向量只是一个数字数组

如您所见,矩阵看起来类似于数字数组。虽然它们看起来很吓人,但是在你练习之后,矩阵是一个非常简单的概念并且很容易使用。

OpenGL的定义

简而言之,矩阵是一个数字数组,具有预定义的行数和列数

矩阵用于变换3D坐标。这些包括:

  • 旋转(改变方向)
  • 缩放(大小更改)
  • 翻译(移动位置)

转换

在大多数情况下,转换点可以用以下等式表示:

Transformed Point = Transformation Matrix × Original Point

转换点=转换矩阵×原点

如果您在看过CGAffineTransform之前就已经使用过CoreGraphics了。它制作了一些非常酷的动画。实际上,CGAffineTransform只是一种不同类型的矩阵变换。

仿射变换是一种保留点,直线和平面的线性映射方法。

资源

旋转太空船

image.png

让我们试试转型吧!虽然这与它们用于位置节点的方式不同,但它们足够接近,您可以开始考虑实际应用的原则。为此,使用SceneKit创建一个新的ARKit项目。当你运行它时,屏幕前应该有一个漂浮在你面前的屏幕截图。

环-T-循环

在viewDidLoad下面添加以下行:

class ViewController: UIViewController, ARSCNViewDelegate {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        sceneView.scene.rootNode.childNodes[0].transform = SCNMatrix4Mult(sceneView.scene.rootNode.childNodes[0].transform, SCNMatrix4MakeRotation(Float(Double.pi) / 2, 1, 0, 0))
        sceneView.scene.rootNode.childNodes[0].transform = SCNMatrix4Mult(sceneView.scene.rootNode.childNodes[0].transform, SCNMatrix4MakeTranslation(0, 0, -2))
    }
}

现在当你重新运行它时,宇宙飞船应该仍然出现在你的屏幕上,但是,当你点击它时,它应该循环。继续敲击它直到它回到它开始的位置(粗略地)。

image.png

这是完整的ViewController代码:

class ViewController: UIViewController, ARSCNViewDelegate {
    
    @IBOutlet var sceneView: ARSCNView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set the view's delegate
        sceneView.delegate = self
        
        // Show statistics such as fps and timing information
        sceneView.showsStatistics = true
        
        // Create a new scene
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        sceneView.scene = scene
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // Create a session configuration
        let configuration = ARWorldTrackingConfiguration()
        
        // Run the view's session
        sceneView.session.run(configuration)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        sceneView.scene.rootNode.childNodes[0].transform = SCNMatrix4Mult(sceneView.scene.rootNode.childNodes[0].transform, SCNMatrix4MakeRotation(Float(Double.pi) / 2, 1, 0, 0))
        sceneView.scene.rootNode.childNodes[0].transform = SCNMatrix4Mult(sceneView.scene.rootNode.childNodes[0].transform, SCNMatrix4MakeTranslation(0, 0, -2))
    }
}

如果一切按照计划进行,经过几次触摸后,您的宇宙飞船看起来就会变得光彩夺目:

`

导航

现在我们已经对ARKit的基础知识进行了一些处理,让我们继续进行导航和定位服务。如果我们希望被引导到目的地,我们需要导航服务的一些帮助。

MapKit带有方便的转弯指示API。使用CoreLocation目标和MKDirectionsRequest,我们可以获得一系列导航步骤,将我们引导到特定位置。

import MapKit
import CoreLocation

struct NavigationService {
    
   func getDirections(destinationLocation: CLLocationCoordinate2D, request: MKDirectionsRequest, completion: @escaping ([MKRouteStep]) -> Void) {
        var steps: [MKRouteStep] = []
       
        let placeMark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: destinationLocation.coordinate.latitude, longitude: destinationLocation.coordinate.longitude))
       
        request.destination = MKMapItem.init(placemark: placeMark)
        request.source = MKMapItem.forCurrentLocation()
        request.requestsAlternateRoutes = false
        request.transportType = .walking

        let directions = MKDirections(request: request)
        
        directions.calculate { response, error in
            if error != nil {
                print("Error getting directions")
            } else {
                guard let response = response else { return }
                for route in response.routes {
                    steps.append(contentsOf: route.steps)
                }
                completion(steps)
            }
        }
    }
}

定义

MKPlacemark** s:**包含与特定坐标相关联的城市,州,县或街道地址等信息。

MKRoute请求的起点和终点之间的单一路线。MKRoute对象定义路线的几何形状 - 即,它包含与特定地图坐标相关联的线段。路线对象还可以包括其他信息,例如路线的名称,距离和预期的行驶时间。

print(route.name)
// Broadway
print(route.advisoryNotices)
// []
print(route.expectedTravelTime)
// 2500.0

MKRouteStep是路线的一个部分。每个步骤都包含一条指令,应由用户在两点之间导航完成,以便他们成功完成路线。

print(step.distance)
// 1.0
print(step.instructions)
// Proceed to 7th Ave

MKMapItem地图上的兴趣点。地图项目包括地理位置和可能适用于该位置的任何有趣数据,例如该位置的地址和该地址的企业名称。

MKDirections一个实用程序对象,它根据您提供的路径信息计算方向和行程时间信息。

import SceneKit
import ARKit
import CoreLocation
import MapKit

class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate, LocationServiceDelegate {
    
    @IBOutlet weak var sceneView: ARSCNView!
    
    var steps: [MKRouteStep] = []
    var destinationLocation: CLLocationCoordinate2D!
    var locationService = LocationService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        locationService.delegate = self
        var navService = NavigationService()
        
        self.destinationLocation = CLLocationCoordinate2D(latitude: 40.737512, longitude: -73.980767)
        var request = MKDirectionsRequest()
        
        if destinationLocation != nil {
            navService.getDirections(destinationLocation: destinationLocation, request: request) { steps in
                for step in steps {
                    self.steps.append(step)
                }
            }
        }
    }
}

资料来源:

medium.com - Yat Choi

aviation.stackexchange.com

github.com/ProjectDent/ARKit-CoreLocation

movable-type.co.uk/scripts/latlong.html

gis.stackexchange.com

opengl-tutorial.org

math.stackexchange.com

原文:https://medium.com/journey-of-one-thousand-apps/arkit-and-corelocation-part-one-fc7cb2fa0150 Christopher Webb-Orenstein

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端小吉米

现在做 Web 全景合适吗?

20040
来自专栏机器之心

教程 | 如何优雅而高效地使用Matplotlib实现数据可视化

30150
来自专栏数据小魔方

离散颜色标度连续化的最佳方案

数了一下刚好有一周多没有写新文章了,主要是临近毕业琐事比较多,再也没有像之前那样,拥有大把时间可以用来挥霍和消遣,静下心来写代码了。 毕竟要写一篇技术含量很高而...

38940
来自专栏阿凯的Excel

统计函数与通配符相爱,不是意外!

首插入音乐,功放党请慎点。 海鸟跟鱼相爱,只是一场意外! 但是统计函数和通配符相爱,却是一种必然! 统计函数何许人也:Sumif、Countif、Average...

31460
来自专栏每日一篇技术文章

opengL ES _ 入门_05

ID是漫反射的强度,Ii是光的入射光的强度,和KD的漫反射,是对粗糙松散耦合对象材料。松散的意思是,在许多现实世界的材料,实际表面可能有点抛光,但半透明的,而层...

16730
来自专栏数据结构与算法

CDQZ 0003:jubeeeeeat

总时间限制: 1000ms 内存限制: 256000kB描述 众所周知,LZF很喜欢打一个叫Jubeat的游戏。这是个音乐游戏,游戏界面是4×4的方阵,会根...

33160
来自专栏数据魔术师

干货 | 变邻域搜索算法(VNS)求解TSP(附C++详细代码及注释)

上次变邻域搜索的推文发出来以后,看过的小伙伴纷纷叫好。小编大受鼓舞,连夜赶工,总算是完成了手头上的一份关于变邻域搜索算法解TSP问题的代码。今天,就在此...

1.6K20
来自专栏生信技能树

如何通过Google来使用ggplot2可视化

今天是大年初二,这篇文章我只想传达一点: 没有什么菜鸟级别的生物信息学数据处理是不能通过Google得到解决方案的,如果有,请换个关键词继续Google! 第一...

34180
来自专栏北京马哥教育

Python自然语言处理分析倚天屠龙记

? 转载自:Python中文社区 ID:python-china 最近在了解到,在机器学习中,自然语言处理是较大的一个分支。存在许多挑战。例如: 如何...

49560
来自专栏ATYUN订阅号

赫尔辛基大学AI基础教程:搜索和解决问题(2.1节)

想象一下你在一个外国的城市,在某个地方(比如一家酒店),想用公共交通工具去另一个地方(比如一家不错的餐馆)。你是做什么?如果你会像许多人一样,掏出智能手机,输入...

15460

扫码关注云+社区

领取腾讯云代金券