iOS AR開發(fā)基礎(chǔ)03 | Hello World (基于AR的平面檢測、人臉檢測和物體識別功能)

本篇寫一個 AR demo,demo包含三個部分的內(nèi)容:

  • 基于后置攝像頭的平面檢測
  • 基于前置攝像頭的人臉追蹤
  • 基于后置攝像頭的圖像識別

寫在前面

本文在一個項(xiàng)目中實(shí)現(xiàn)引言部分提到的三個AR功能,由于是一個AR 的 Hello World 項(xiàng)目,本文只編寫實(shí)現(xiàn) AR 功能的核心代碼,和 3D 渲染相關(guān)的內(nèi)容本文只展示代碼,不會詳細(xì)講解,后面的文章會做系統(tǒng)的講解。

本文內(nèi)容結(jié)構(gòu)如下:

本文內(nèi)容結(jié)構(gòu)

本文源碼地址

1. 搭建第一個AR項(xiàng)目

1.1 搭建過程

本文的開發(fā)環(huán)境:Xcode9.3 + iPhone X真機(jī) iOS 11.3。

打開Xcode > Create a new Xcode project > Augmented Reality App > next,如下圖:

新建項(xiàng)目-1

應(yīng)用名稱命名為 HelloWorld,開發(fā)語言選swift,Content Technology 選擇 SceneKit,然后點(diǎn)擊下一步:

新建項(xiàng)目-2

1.2 項(xiàng)目結(jié)構(gòu)和默認(rèn)添加的源碼解讀

打開剛剛新建的工程,和Single View App模板相比,使用 Augmented Reality App 模板新建的工程,初始化內(nèi)容有如下差異:

  • 添加了 art.scnassets 資源文件夾,里面放著資源文件,打開 ship.scn 文件,是一個3D的飛船模型
  • 打開 Main.storyboard ,默認(rèn)給啟動頁的 ViewController對象 添加了一個ARSCNView實(shí)例
  • ViewController.swift 文件中添加了一些添加3D飛船模型的代碼

現(xiàn)在直接在真機(jī)上 run 這個項(xiàng)目,效果如下:

項(xiàng)目演示

Amazing,飛船渲染在我們的真實(shí)世界中了,看看是怎么通過代碼加載進(jìn)來的。

1. 查看 main.storyboard,發(fā)現(xiàn)系統(tǒng)為我們的 ViewController 實(shí)例的 view 添加了一個ARSCNView類型的subview,并將其設(shè)置為ViewController的屬性。

    @IBOutlet var sceneView: ARSCNView!

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

sceneView.delegate = self

這行代碼給 sceneView 設(shè)置 ARSCNViewDelegate 代理,在ViewController 中就可以獲取 sceneView 的渲染狀態(tài)回調(diào)。

sceneView.showsStatistics = true

showsStatistics 是 SCNView 的一個性能統(tǒng)計(jì)屬性,設(shè)置為 true 之后,sceneView 底部就會顯示一個sceneView 的性能統(tǒng)計(jì)狀態(tài)欄,點(diǎn)擊上面的加號之后,這個狀態(tài)欄會展開,上面的 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 ,把這個文件轉(zhuǎn)換為一個名為 scene 的 SCNScene 實(shí)例,然后將這個場景設(shè)置為 sceneView 的 scene 屬性。
這樣我們加載這個包含飛船的場景到真實(shí)世界中。

3. 查看 ViewController.swift 的 viewWillAppear: 方法,在視圖即將出現(xiàn)的時候,初始化一個 ARWorldTrackingConfiguration 實(shí)例 configuration ,然后用這個configuration 運(yùn)行 ARSession對象。

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

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

4. 查看 ViewController.swift 的 viewWillDisappear:方法,在視圖消失的時候,停止這個session,和 session.run 成對出現(xiàn)。

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

但是!我們發(fā)現(xiàn)這個飛船是在viewDidLoad的時候加載的,并沒有融入對世界理解,也沒有交互!

看完系統(tǒng)默認(rèn)添加的代碼之后,在編寫AR 代碼之前,我們先給我們 Demo 搭建一個簡單的視圖框架。

1.3 利用storyboard快速構(gòu)建Demo視圖層級

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

  • TKWorldTrackingViewController 負(fù)責(zé)實(shí)現(xiàn)平面檢測功能
  • TKFaceTrackingViewController 負(fù)責(zé)實(shí)現(xiàn)人臉檢測相關(guān)功能
  • TKImageRecognizeViewController 負(fù)責(zé)實(shí)現(xiàn)物體識別相關(guān)功能

并利用storyboard快速構(gòu)建整個如圖層級,關(guān)于storyboard的使用,本教程不做闡述,完成之后如下如所示:

利用storyboard快速構(gòu)建demo視圖層級

此時,準(zhǔn)備完畢,下面正式編寫AR代碼!!!

2. 開發(fā) 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 已經(jīng)初始化完成,現(xiàn)在需要運(yùn)行 sceneView 的 AR會話,我們希望在 當(dāng)前 view 出現(xiàn)的時候運(yùn)行會話。在 viewWillAppear:中創(chuàng)建一個ARWorldTrackingConfiguration 實(shí)例 configuration ,然后用 configuration 運(yùn)行 AR session。

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

在當(dāng)前 view 消失的時候,在viewWillDisappear:中停止AR session。

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

到目前為止,AR session 已經(jīng)成功運(yùn)行了,接下來要做的就是,在檢測到平面之后,接收并處理ARKit發(fā)出來的通知。

實(shí)現(xiàn)思路如下:

  • 上一遍文章關(guān)于世界追蹤的描述中有提到過,ARKit 檢測到平面后,會在場景中添加錨點(diǎn)
  • 遵守 sceneView 的 ARSCNViewDelegate ,實(shí)現(xiàn) renderer(: didAdd: for:)方法,當(dāng)場景中添加錨點(diǎn)的時候,viewcontroller 就可以收到通知
  • 判斷新添加錨點(diǎn)的類型,如果是 ARPlaneAnchor 類型,就認(rèn)為檢測到平面了

TKWorldTrackingViewController 遵守 ARSCNViewDelegate

class TKWorldTrackingViewController: UIViewController,ARSCNViewDelegate

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

sceneView.delegate = self

并實(shí)現(xiàn)代理方法,判斷當(dāng)前新增的錨點(diǎn)類型,如果是 ARPlaneAnchor,就在當(dāng)前錨點(diǎn)出添加一個 box。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
     // 1. 判斷當(dāng)前新增的錨點(diǎn)類型
     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)
    
}

此時,代碼寫完了,在真機(jī)上 run 項(xiàng)目,點(diǎn)擊 “Demo1:平面檢測” 按鈕,效果如下:

World Tracking 平面檢測演示

3. 開發(fā) Face Tracking 功能

TKFaceTrackingViewController 中引入 ARKit、添加 sceneView 屬性、在 viewDidLoad: 中初始化 sceneView 的代碼,和上一節(jié)“開發(fā) World Tracking 功能”中一樣。

運(yùn)行 session 代碼如下:

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

獲取人臉和添加box方法和上一節(jié)講的類似,代碼如下:

    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)
    }

在真機(jī)上 run 項(xiàng)目,點(diǎn)擊 “Demo2:人臉檢測” 按鈕,效果如下:

Face Tracking演示

4. 開發(fā)基于AR的圖像識別功能

4.1 將需要識別的2D圖片導(dǎo)入到項(xiàng)目中

在 Assets.xcassets 文件目錄下新建一個 AR Resource Group 類型的目錄。


新建AR Resource Group.gif

然后將要識別的對象,對應(yīng)的2D圖片拖拽到如下圖片中的紅框位置。

將要識別的2D圖片添加到project中

接下來的步驟很重要,查看圖片的 Show the Attributes inspector,給圖片設(shè)置大小,這個值是我們需要識別的物體在真實(shí)世界中的大小!!!

這個值的精度直接決定了識別效果。

經(jīng)過測量,我需要識別的企鵝,高度約為13cm,這里的單位選 Meters,設(shè)置如下。

企鵝高度參數(shù)設(shè)置

4.2 加載上面導(dǎo)入的圖片

TKImageRecognizeViewController 中引入 ARKit、添加 sceneView 屬性、在 viewDidLoad: 中初始化 sceneView 的代碼,和前面“開發(fā) World Tracking 功能”中一樣。

運(yùn)行 session 代碼稍有變化。

ARReferenceImage 提供的 referenceImages(:)方法可以導(dǎo)入項(xiàng)目中 AR Resources 文件夾下的所有圖片,如果項(xiàng)目中沒有這個文件,會拋出異常。在viewWillApper: 中添加如下代碼。

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

接著,新建一個ARWorldTrackingConfiguration實(shí)例,將 referenceImages 賦給 detectionImages屬性。

        let configuration = ARWorldTrackingConfiguration()
        configuration.detectionImages = referenceImages

用上面的 configuration 運(yùn)行AR 會話。

sceneView.session.run(configuration)

4.3 添加圖像識別代碼

renderer(: didAdd: for:)方法中處理圖像識別結(jié)果的回調(diào),在識別到圖像的位置添加一個平面,做識別結(jié)果可視化標(biāo)識。

    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添加到檢測到的圖像錨點(diǎn)處
        node.addChildNode(planeNode)  
    }

在真機(jī)上 run 項(xiàng)目,點(diǎn)擊 “Demo3:物體識別” 按鈕,效果如下:

物體識別演示

至此,我們完成了關(guān)于我們第一個AR項(xiàng)目,接下來會圍繞SceneKit,系統(tǒng)的介紹和 3D 內(nèi)容渲染相關(guān)的內(nèi)容。

感謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內(nèi)容