ARKit 初探

作者: 音视频直播技术专家 | 来源:发表于2017-12-05 17:17 被阅读234次

    今年7月Apple推出了AR工具ARKit,着实闪着了大家的眼睛。从目前的评测可以知道 ARKit已经非常成熟,完全可以进行商用了。

    在iOS中,增强现实由ARKit和渲染两部分组成。ARKit主要负责AR计算,它将ARCamera捕获的视频帧当作背景,使用视觉惯性测距(VIO)来精确跟踪周围的世界,进行坐标转换,场景搭建及平面的捕获;然后,通过 SceneKit(3D)/SpritKit(2D) 或 Metal 库进行渲染,使虚拟物体与真实世界相接合,达到增强现实的目的。

    今天我们就来详细的了解一下 ARKit,看看 Apple 为我们提供了怎样强大的工具,可以让我们迅速的构建一个AR应用程序。

    在讲解我们的 AR 程序之前,我们先要了解几个ARKit的基本概念。只有这几个基本概念了解清楚之后,我们才能清楚的知道如何去写一个AR程序。

    几个重要概念

    • 空间定位与方向追踪 这个是通过 ARCamera 获取到视频帧,再经过 VIO 计算出来的。
    • 场景理解,平台检测 ,点击检测,光照检测 这是通过ARSession管理的内部模块计算得出的。
    • 渲染层 可以通过 SceneKit/SpritKit 或 Metal/OpenGL 进行渲染。今天主要介绍 SceneKit进行渲染。

    什么是特征点

    AR 的目标是往真实世界中的特定点插入虚拟内容,并且在真实世界中移动时还能对此虚拟内容保持追踪。

    ARKit 从视频帧中获得某张图片的特征后,就可以从多个帧中追踪这些特征。随着用户在真实世界中的移动,就可以利用相应的特征点来估算 3D 姿态信息。用户移动地越多,就会获得越多的特征,并优化这些估算的 3D 姿态信息。

    有没有可能检测不出特征点的情况呢?当然有,可能检测不出特征点的情况如下:

    • 光线差 没有足够的光或光线过强的镜面反光。尝试避免这些光线差的环境。
    • 缺少纹理 如果摄像头指向一面白墙,那也没法获得特征,ARKit 也去无法找到并追踪用户。尝试避免看向纯色、反光表面等地方。
    • 快速移动 通常情况下检测和估算 3D 姿态只会借助图片,如果摄像头移动太快图片就会糊,从而导致追踪失败。但 ARKit 会利用视觉惯性里程计,综合图片信息和设备运动传感器来估计用户转向的位置。因此 ARKit 在追踪方面非常强大。

    什么是平面检测

    ARKit 的平面检测用于检测出现实世界的水平面,也就是在 3D 空间中,Y值为0的一个区域。平面检测是一个动态的过程,当摄像机不断移动时,检测到的平面也会不断的变化。此外,随着平面的动态检测,不同平面也可能会合并为一个新的平面。

    只有检测真实世界有水平面之后,才能找到锚定点,并将虚拟物体放到这个锚定点上。

    什么是点击检测

    除了平台检测外,还有点击检测。顾名思意,就是当用户点击屏幕时,ARKit 将点击屏幕的2D空间位置转换为ARKit 通过 ARCamera 捕获到的视频帧的 3D 空间位置。并在这个位置检测是否有平面。

    什么是世界追踪

    世界追踪都追踪什么呢?ARKit 会追踪以下几个信息:

    • 追踪设备的位置以及旋转,这两个信息均是相对于设备起始时的信息。
    • 追踪物理距离(以“米”为单位),例如 ARKit 检测到一个平面,我们希望知道这个平面有多大。
    • 追踪我们手动添加的希望追踪的点,例如我们手动添加的一个虚拟物体

    ARKit 使用视觉惯性测距技术,对摄像头采集到的图像序列进行计算机视觉分析,并且与设备的运动传感器信息相结合。ARKit 会识别出每一帧图像中的特征点,并且根据特征点在连续的图像帧之间的位置变化,然后与运动传感器提供的信息进行比较,最终得到高精度的设备位置和偏转信息。

    除了上面这几个概念外,我们还需要知道ARKit提供的一些基本知识。

    ARSession

    ARSession 是 ARkit 的核心。它是连接ARCamera与ARACNView之间的桥梁。像捕获视频,与 CoreMotion 的数据整合,场景的理解,平面检测等等都需要 ARSession 来协调各模块进行协同处理。

    另外,ARSession 有两种获取 ARFrame 的方法:

    • push 实时不断的获取相机位置,由ARSession主动告知用户。通过实现ARSession的代理来获取。

      (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
      
    • pull 用户想要时,主动去获取。通过 ARSession的属性currentFrame来获取。

    ARConfiguration

    该类的作用是设置一些 ARSession 相关的配置,如是否使用平面检测就是通过这个参数来设置的。

    ARSCNView

    ARSCNView 继承自 SceneKit 中的 SCNView。ARSCNView是一个非常复杂的类,它不仅拥有SCNView的功能,而且它还管理着 ARSession。如下图所示:

    ARKit.png

    SceneKit 的主要作用是将虚拟物体展示在3D场景中。每个虚拟物体都可以用 SCNNode 来代表,SCNNode 在 SCNScene 中展现,而无数SCNScene 组成 3D 世界。

    它有几个重要的方法需要特别强调一下:

    • hitTest 方法

      - (NSArray<ARHitTestResult *> *)hitTest:(CGPoint)point types:(ARHitTestResultType)types;
      

      point:2D坐标点(手机屏幕某一点);

      ARHitTestResultType:捕捉类型是 点 还是 面;

      NSArray<ARHitTestResult *> *:追踪结果数组。数组的结果排序是由近到远。

      根据2D坐标点搜索3D模型位置。当我们在手机屏幕点击某一个点的时候,可以捕捉到这一个点所在的3D模型的位置。为什么返回值是一个数组呢?这是因为手机屏幕一个是长方形的二维空间,而相机捕捉到的是一个由这个二维空间映射出去的长方体。我们点击屏幕一个点,可以理解为在这个长方体的边缘射出一条线,这一条线上可能会有多个3D物体模型。

    • renderer 方法

      (void)renderer:(id<SCNSceneRenderer>)renderer updateAtTime:(NSTimeInterval)time
      

      它是 ARSCNViewDelegate 中的回调方法,每次 3D 引擎要渲染新的视频帧时都会调用该方法。

    • 重载 renderer 方法

      - (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
      

      它是 ARSCNViewDelegate 中的回调方法,每次 ARKit 检测到了平面时都会调用此方法。我们可以通过这个代理方法得知我们添加一个虚拟物体到AR场景下的锚点(AR现实世界中的坐标)

    SCNNode

    SCNNode代表一个虚拟物体。通过 SCNNode 可以对虚拟物体进行变换和旋转,还可以做几何变换,光照等操作。

    SCNScene

    在ARKit中它代表一个场景。SCNScene 包括背景 和 虚似物体。其中背景可以是从 ARCamera捕获的视频帧。而虚拟物体由 rootNode 存储,它就是前面介绍的 SCNNode。

    ARAnchor

    包含真实世界位置和方向的信息。通过它可以轻松地将虚拟物体添加,更新或从会话中删除。

    ARCamera

    ARCamera 用于捕捉视频流。一般我们无需去创建一个ARCamera,因为在初始化 AR 时,它就帮我们将ARCamera创建好了。另外,我们一般也不直接使用 ARCamera 的 API,默认都是设置好的。

    ARFrame

    摄像头视频帧的包装类。从 ARCamera 中获取的每一幅视频帧都被封装成 ARFrame。它包含位置追踪信息、环境参数、视频帧。重点是它包含了苹果检测的特征点,通过rawFeaturePoints可以获取,不过只是特征的位置,具体的特征向量并没有开放。

    SCNMaterial

    使用 SCNMaterial 可以对虚拟物体 SCNNode 进行贴图。

    AR 任意门的实现

    所谓任意门就是在真实环境中虚拟一扇门,当走进这扇门后,可以看到另外一个世界。

    实现

    初始化ARKit

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // Set the view's delegate
        self.sceneView.delegate = self;
        
        // Show statistics such as fps and timing information
        self.sceneView.showsStatistics = YES;
        
        // Create a new scene
        SCNScene *scene = [SCNScene new];
        
        // Set the scene to the view
        self.sceneView.scene = scene;
        
        //Grid to identify plane detected by ARKit
        _gridMaterial = [SCNMaterial material];
        _gridMaterial.diffuse.contents = [UIImage imageNamed:@"art.scnassets/grid.png"];
        //when plane scaling large, we wanna grid cover it over and over
        _gridMaterial.diffuse.wrapS = SCNWrapModeRepeat;
        _gridMaterial.diffuse.wrapT = SCNWrapModeRepeat;
        
        _planes = [NSMutableDictionary dictionary];
        
        //tap gesture
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(placeTransDimenRoom:)];
        [self.sceneView addGestureRecognizer:tap];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        
        // Create a session configuration
        ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfiguration new];
        configuration.planeDetection = ARPlaneDetectionHorizontal;
        // Run the view's session
        [self.sceneView.session runWithConfiguration:configuration];
        
    }
    
    

    处理ARKit检测到的平面

    用于提示可以用于交互的平面,后期模拟物理世界也要用到。

    - (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
        if ([anchor isKindOfClass:[ARPlaneAnchor class]] && !_stopDetectPlanes){
            NSLog(@"detected plane");
            [self addPlanesWithAnchor:(ARPlaneAnchor*)anchor forNode:node];
            [self postInfomation:@"touch ground to place room"];
        }
    }
    - (void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
        if ([anchor isKindOfClass:[ARPlaneAnchor class]]){
            NSLog(@"updated plane");
            [self updatePlanesForAnchor:(ARPlaneAnchor*)anchor];
        }
    }
    - (void)renderer:(id<SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
        if ([anchor isKindOfClass:[ARPlaneAnchor class]]){
            NSLog(@"removed plane");
            [self removePlaneForAnchor:(ARPlaneAnchor*)anchor];
        }
    }
    

    放置transDimenRoom

    对于隐藏空间,抽象成两个类来表达:transDimenRoom,transDimenStruct。

    后者用于提供一些平板等基础结构,前者将这些结构拼成一个房间,留一个门框出来让用户能够看见里面。

    当需要放置任意门时,就用+transDimenRoomAtPosition:方法创建一个transDimenRoom,当用户走进去时,用 -hideWalls: 隐藏四周的墙壁,切换成全景背景。

    @interface transDimenRoom : SCNNode
    @property (nonatomic, strong) SCNNode *walls;
    
    +(instancetype)transDimenRoomAtPosition:(SCNVector3)position;
    //TODO:check  if user in room
    -(BOOL)checkIfInRoom:(SCNVector3)position;
    
    -(void)hideWalls:(BOOL)hidden;
    @end
    
    

    检测到用户走进房间

    目前为了简单起见,是判断用户与房间中心的距离,当距离小于1时,就认为用户进入了房间。这里的逻辑以后会收归到transDimenRoom中。

    - (void)renderer:(id<SCNSceneRenderer>)renderer updateAtTime:(NSTimeInterval)time{
        if (_room.presentationNode) {
            
            SCNVector3 position = self.sceneView.pointOfView.presentationNode.worldPosition;
            
            SCNVector3 roomCenter = _room.walls.worldPosition;
            
            CGFloat distance = GLKVector3Length(GLKVector3Make(position.x - roomCenter.x, 0, position.z - roomCenter.z));
            
            if (distance < 1){
                NSLog(@"In room");
                [self handleUserInRoom:YES];
                return;
            }
            
            [self handleUserInRoom:NO];
            
        }
    }
    

    小结

    今天首先向大家介绍了一下 ARKit 的基本知识,然后通过 任意门 这个实例告诉了大家如何写一个 ARKit 程序。这个 任意门 可以应用在很多场景中,大家可以通过这个实例进行扩展,充份发挥自己的想像力。

    其实本节最最关键的是让大家知道 ARKit中的那些基本概念。ARSession是它的核心,它协调内部模块进行场景的各种计算。而 ARSCNView 只是渲染技术中的一种,我们完成可以通过 OpenGL/Metal 来替换掉它。

    希望本文能对你所有帮助,谢谢!

    参考

    http://www.jianshu.com/p/b377e8b8a8f2

    相关文章

      网友评论

        本文标题:ARKit 初探

        本文链接:https://www.haomeiwen.com/subject/isbcixtx.html