iOS AR开发基础03 | Hello World (基于AR的平面检测、人脸检测和物体识别功能)

本篇写一个 AR demo,demo包含三个部分的内容:

  • 基于后置摄像头的平面检测
  • 基于前置摄像头的人脸追踪
  • 基于后置摄像头的图像识别

写在前面

本文在一个项目中实现引言部分提到的三个AR功能,由于是一个AR 的 Hello World 项目,本文只编写实现 AR 功能的核心代码,和 3D 渲染相关的内容本文只展示代码,不会详细讲解,后面的文章会做系统的讲解。

本文内容结构如下:

本文内容结构

本文源码地址

1. 搭建第一个AR项目

1.1 搭建过程

本文的开发环境:Xcode9.3 + iPhone X真机 iOS 11.3。

打开Xcode > Create a new Xcode project > Augmented Reality App > next,如下图:

新建项目-1

应用名称命名为 HelloWorld,开发语言选swift,Content Technology 选择 SceneKit,然后点击下一步:

新建项目-2

1.2 项目结构和默认添加的源码解读

打开刚刚新建的工程,和Single View App模板相比,使用 Augmented Reality App 模板新建的工程,初始化内容有如下差异:

  • 添加了 art.scnassets 资源文件夹,里面放着资源文件,打开 ship.scn 文件,是一个3D的飞船模型
  • 打开 Main.storyboard ,默认给启动页的 ViewController对象 添加了一个ARSCNView实例
  • ViewController.swift 文件中添加了一些添加3D飞船模型的代码

现在直接在真机上 run 这个项目,效果如下:

项目演示

Amazing,飞船渲染在我们的真实世界中了,看看是怎么通过代码加载进来的。

1. 查看 main.storyboard,发现系统为我们的 ViewController 实例的 view 添加了一个ARSCNView类型的subview,并将其设置为ViewController的属性。

    @IBOutlet var sceneView: ARSCNView!

2. 查看 ViewController.swift 的 viewDidLoad: 方法:

sceneView.delegate = self

这行代码给 sceneView 设置 ARSCNViewDelegate 代理,在ViewController 中就可以获取 sceneView 的渲染状态回调。

sceneView.showsStatistics = true

showsStatistics 是 SCNView 的一个性能统计属性,设置为 true 之后,sceneView 底部就会显示一个sceneView 的性能统计状态栏,点击上面的加号之后,这个状态栏会展开,上面的 gif 有展示这个过程。

// Create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
      
 // Set the scene to the view       
sceneView.scene = scene

这两行代码从我们资源文件夹 art.scnassets 中读取资源文件 ship.scn ,把这个文件转换为一个名为 scene 的 SCNScene 实例,然后将这个场景设置为 sceneView 的 scene 属性。
这样我们加载这个包含飞船的场景到真实世界中。

3. 查看 ViewController.swift 的 viewWillAppear: 方法,在视图即将出现的时候,初始化一个 ARWorldTrackingConfiguration 实例 configuration ,然后用这个configuration 运行 ARSession对象。

// Create a session configuration
let configuration = ARWorldTrackingConfiguration()

// Run the view's session
sceneView.session.run(configuration)

4. 查看 ViewController.swift 的 viewWillDisappear:方法,在视图消失的时候,停止这个session,和 session.run 成对出现。

 // Pause the view's session
 sceneView.session.pause()

但是!我们发现这个飞船是在viewDidLoad的时候加载的,并没有融入对世界理解,也没有交互!

看完系统默认添加的代码之后,在编写AR 代码之前,我们先给我们 Demo 搭建一个简单的视图框架。

1.3 利用storyboard快速构建Demo视图层级

新建三个ViewController,继承自UIViewController,每一个控制器代表一个功能:

  • TKWorldTrackingViewController 负责实现平面检测功能
  • TKFaceTrackingViewController 负责实现人脸检测相关功能
  • TKImageRecognizeViewController 负责实现物体识别相关功能

并利用storyboard快速构建整个如图层级,关于storyboard的使用,本教程不做阐述,完成之后如下如所示:

利用storyboard快速构建demo视图层级

此时,准备完毕,下面正式编写AR代码!!!

2. 开发 World Tracking 功能

首先在 TKWorldTrackingViewController 中引入 ARKit。

import ARKit

添加sceneView属性,用来展示AR视图。

 var sceneView : SCNView!

viewDidLoad: 中初始化 sceneView,并作为 subview 添加到view上。

 override func viewDidLoad() {
        super.viewDidLoad()

        sceneView = SCNView(frame: view.bounds)
        view.addSubview(sceneView)
        
    }

sceneView 已经初始化完成,现在需要运行 sceneView 的 AR会话,我们希望在 当前 view 出现的时候运行会话。在 viewWillAppear:中创建一个ARWorldTrackingConfiguration 实例 configuration ,然后用 configuration 运行 AR session。

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
      
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = .horizontal
        sceneView.session.run(configuration)
    }

在当前 view 消失的时候,在viewWillDisappear:中停止AR session。

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Pause the view's session
        sceneView.session.pause()
    }

到目前为止,AR session 已经成功运行了,接下来要做的就是,在检测到平面之后,接收并处理ARKit发出来的通知。

实现思路如下:

  • 上一遍文章关于世界追踪的描述中有提到过,ARKit 检测到平面后,会在场景中添加锚点
  • 遵守 sceneView 的 ARSCNViewDelegate ,实现 renderer(: didAdd: for:)方法,当场景中添加锚点的时候,viewcontroller 就可以收到通知
  • 判断新添加锚点的类型,如果是 ARPlaneAnchor 类型,就认为检测到平面了

TKWorldTrackingViewController 遵守 ARSCNViewDelegate

class TKWorldTrackingViewController: UIViewController,ARSCNViewDelegate

然后在viewDidLoad:中添加如下代码:

sceneView.delegate = self

并实现代理方法,判断当前新增的锚点类型,如果是 ARPlaneAnchor,就在当前锚点出添加一个 box。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
     // 1. 判断当前新增的锚点类型
     guard anchor is ARPlaneAnchor else { return }        

     // 2. 在检测到的平面处添加 box
     let box = SCNBox(width: 0.08, height: 0.08, length: 0.08, chamferRadius: 0)
     let boxNode = SCNNode(geometry: box)
     node.addChildNode(boxNode)
    
}

此时,代码写完了,在真机上 run 项目,点击 “Demo1:平面检测” 按钮,效果如下:

World Tracking 平面检测演示

3. 开发 Face Tracking 功能

TKFaceTrackingViewController 中引入 ARKit、添加 sceneView 属性、在 viewDidLoad: 中初始化 sceneView 的代码,和上一节“开发 World Tracking 功能”中一样。

运行 session 代码如下:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
  
        let configuration = ARFaceTrackingConfiguration()
        sceneView.session.run(configuration)
    }

获取人脸和添加box方法和上一节讲的类似,代码如下:

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        
        // 1. 判断anchor是否为ARFaceAnchor
        guard anchor is ARFaceAnchor else { return }
        
        // 2. 在检测到的人脸处添加 box
        let box = SCNBox(width: 0.08, height: 0.08, length: 0.08, chamferRadius: 0)
        let boxNode = SCNNode(geometry: box)
        node.addChildNode(boxNode)
    }

在真机上 run 项目,点击 “Demo2:人脸检测” 按钮,效果如下:

Face Tracking演示

4. 开发基于AR的图像识别功能

4.1 将需要识别的2D图片导入到项目中

在 Assets.xcassets 文件目录下新建一个 AR Resource Group 类型的目录。


新建AR Resource Group.gif

然后将要识别的对象,对应的2D图片拖拽到如下图片中的红框位置。

将要识别的2D图片添加到project中

接下来的步骤很重要,查看图片的 Show the Attributes inspector,给图片设置大小,这个值是我们需要识别的物体在真实世界中的大小!!!

这个值的精度直接决定了识别效果。

经过测量,我需要识别的企鹅,高度约为13cm,这里的单位选 Meters,设置如下。

企鹅高度参数设置

4.2 加载上面导入的图片

TKImageRecognizeViewController 中引入 ARKit、添加 sceneView 属性、在 viewDidLoad: 中初始化 sceneView 的代码,和前面“开发 World Tracking 功能”中一样。

运行 session 代码稍有变化。

ARReferenceImage 提供的 referenceImages(:)方法可以导入项目中 AR Resources 文件夹下的所有图片,如果项目中没有这个文件,会抛出异常。在viewWillApper: 中添加如下代码。

    guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
            fatalError("AR Resources 资源文件不存在 。")
        }

接着,新建一个ARWorldTrackingConfiguration实例,将 referenceImages 赋给 detectionImages属性。

        let configuration = ARWorldTrackingConfiguration()
        configuration.detectionImages = referenceImages

用上面的 configuration 运行AR 会话。

sceneView.session.run(configuration)

4.3 添加图像识别代码

renderer(: didAdd: for:)方法中处理图像识别结果的回调,在识别到图像的位置添加一个平面,做识别结果可视化标识。

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        // 1. 判断anchor是否为ARImageAnchor
        guard let imageAnchor = anchor as? ARImageAnchor else { return }
        
        // 2. 在检测到的物体图像处添加 plane
        let referenceImage = imageAnchor.referenceImage
        let plane = SCNPlane(width: referenceImage.physicalSize.width,
                             height: referenceImage.physicalSize.height)
        
        let planeNode = SCNNode(geometry: plane)
        planeNode.eulerAngles.x = -.pi / 2
        
        // 3. 将plane添加到检测到的图像锚点处
        node.addChildNode(planeNode)  
    }

在真机上 run 项目,点击 “Demo3:物体识别” 按钮,效果如下:

物体识别演示

至此,我们完成了关于我们第一个AR项目,接下来会围绕SceneKit,系统的介绍和 3D 内容渲染相关的内容。

感谢
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容