原創(chuàng):知識(shí)探索型文章
創(chuàng)作不易,請(qǐng)珍惜鬼悠,之后會(huì)持續(xù)更新删性,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈焕窝,這些文章記錄了我的IOS成長(zhǎng)歷程蹬挺,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn),大家可通過(guò)command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容
目錄
- 一它掂、AR
- 1巴帮、概念
- 2、設(shè)備追蹤
- 3虐秋、ARFrame
- 4晰韵、場(chǎng)景解析
- 5、SceneKit
- 二熟妓、二維碼識(shí)別
- 1、設(shè)備的配置流程
- 2栏尚、CameraController 中的二維碼識(shí)別方法
- 3起愈、PreviewView中的二維碼識(shí)別方法
- 三、人臉識(shí)別
- 1、人臉識(shí)別簡(jiǎn)介
- 2抬虽、使用 CoreImage 實(shí)現(xiàn)靜態(tài)人臉識(shí)別
- 3官觅、使用 Vision 實(shí)現(xiàn)靜態(tài)人臉識(shí)別
- 4、使用 OpenCV 實(shí)現(xiàn)靜態(tài)人臉識(shí)別
- 5阐污、使用 AVFoundation 實(shí)現(xiàn)動(dòng)態(tài)人臉識(shí)別
- Demo
- 參考文獻(xiàn)
一休涤、AR
1、概念
AR 全稱 Augmented Reality
(增強(qiáng)現(xiàn)實(shí))是一種在視覺(jué)上呈現(xiàn)虛擬物體與現(xiàn)實(shí)場(chǎng)景結(jié)合的技術(shù)笛辟。Apple 公司在 2017 年正式推出了ARKit
功氨,iOS 開發(fā)者可以在這個(gè)平臺(tái)上使用簡(jiǎn)單便捷的 API 來(lái)開發(fā) AR 應(yīng)用程序。為了獲得 ARKit
的完整功能手幢,需要A9
及以上芯片捷凄。
AR 的全稱是增強(qiáng)現(xiàn)實(shí),通過(guò)顯示屏與各類傳感器围来,能讓虛擬世界中創(chuàng)作出的物品與現(xiàn)實(shí)世界產(chǎn)生交互跺涤。
技術(shù)方面,現(xiàn)有 AR 解決方案已經(jīng)可以檢測(cè)并記錄虛擬物品在現(xiàn)實(shí)環(huán)境中的準(zhǔn)確位置监透、分辨虛擬物品與現(xiàn)實(shí)中物品的遮擋關(guān)系桶错、為虛擬物體增加在現(xiàn)實(shí)中的光源與陰影、讓虛擬物品與現(xiàn)實(shí)中的物品產(chǎn)生交互等胀蛮。就像其它新技術(shù)一樣院刁,AR 的具體使用場(chǎng)景仍處于探索階段,下面是我在關(guān)注的幾個(gè)使用角度醇滥。
游戲 - 沉浸與代入感:說(shuō)到虛擬現(xiàn)實(shí)的應(yīng)用黎比,許多人可能都會(huì)想到游戲。起先我并沒(méi)太注意這些所謂 AR 游戲與直接在手機(jī)里玩有什么區(qū)別鸳玩,直到前一陣子阅虫,精靈寶可夢(mèng)支持了 AR 模式,允許你把寶可夢(mèng)放在世界中
在真實(shí)世界中與寶可夢(mèng)互動(dòng)的體驗(yàn)讓我意識(shí)到不跟,在手機(jī)中與模型交互颓帝,給人直觀的感覺(jué)始終是在和屏幕互動(dòng),無(wú)論模型和場(chǎng)景搭建的有多精致窝革,用戶感知到的始終只是屏幕购城。將模型放在現(xiàn)實(shí)世界中,無(wú)論你怎么移動(dòng)手機(jī)虐译,它還在那里瘪板。雖然仍舊摸不到,但能看到它出現(xiàn)在日常生活中漆诽,熟悉的現(xiàn)實(shí)里侮攀,帶來(lái)的沉浸與代入感完全不同锣枝。
當(dāng)我打開 AR 應(yīng)用時(shí),恍然意識(shí)到三維模型本就應(yīng)該空間概念兰英。建筑學(xué)用二維的三視圖來(lái)避免圖紙與模型潛在的不確定因素撇叁,呈現(xiàn)在空間中的三維模型則可以徹底避免這個(gè)問(wèn)題,人可以自然地走動(dòng)來(lái)確定所有細(xì)節(jié)畦贸。
若你將 AR 的技術(shù)開發(fā)進(jìn)度想象成建造一輛車陨闹,ARKit
的定位便是配件,之后的所有功能都建立在這個(gè)基礎(chǔ)之上薄坏。ARKit
中提供一些與 AR 相關(guān)的核心能力趋厉,如:提供景深信息來(lái)判斷物體在空間中的位置、用空間錨點(diǎn)來(lái)記錄虛擬物品在真實(shí)世界中的經(jīng)緯度及海拔坐標(biāo)颤殴、用攝像頭對(duì)面部信息進(jìn)行追蹤等觅廓。下圖中,圖標(biāo)下方的陰影及切換角度后物品位置不變的特性涵但,便是 ARKit
提供的基礎(chǔ)能力杈绸。
原深感鏡頭組
許多人了解這個(gè)鏡頭組件是因?yàn)?iPhone X 發(fā)布會(huì)上的面容 ID,替換了傳統(tǒng)指紋識(shí)別來(lái)用于安全的驗(yàn)證身份矮瘟。實(shí)際上原深感鏡頭組用途不僅于身份驗(yàn)證瞳脓,它會(huì)投射 30,000 多個(gè)不可見(jiàn)的點(diǎn)來(lái)來(lái)獲取準(zhǔn)確的深度數(shù)據(jù),這些數(shù)據(jù)對(duì) AR 很有幫助澈侠。前置攝像頭所做的攝像時(shí)背景替換劫侧,臉上試戴墨鏡這類 AR 應(yīng)用,許多都是通過(guò) ARKit
調(diào)用原深感攝像頭組件的硬件來(lái)實(shí)現(xiàn)的哨啃。
針對(duì) AR 模型的技術(shù)準(zhǔn)備 USDZ
市面上 3D 建模軟件眾多烧栋,各家選用的導(dǎo)出格式也差異非常大。為解決素材格式不統(tǒng)一的問(wèn)題拳球,Apple 與皮克斯合作推出了 USDZ
模型封裝格式审姓。此后 AR 場(chǎng)景所采用的素材均需為 USDZ
,避免了項(xiàng)目中各類文件混雜的情況祝峻。USDZ
自身也是個(gè)實(shí)力派魔吐,支持將模型的動(dòng)畫,紋理材質(zhì)等眾多與模型相關(guān)的內(nèi)容莱找,全部封裝在一個(gè)文件中酬姆。
空間感知芯片 U 系列
U 系列是在 iPhone 設(shè)備中新增的超寬帶 UWB 芯片,它能提供在較大空間內(nèi)極度精準(zhǔn)定位的能力奥溺。舉個(gè)例子辞色,若你的 AirPods Pro 找不到了,配備 U 系列芯片的手機(jī)可以準(zhǔn)確指向該耳機(jī)的準(zhǔn)確方向與距離「《ǎ現(xiàn)有 AR 技術(shù)主要基于攝像頭淫僻,當(dāng)用戶脫離攝像頭區(qū)域后诱篷,便會(huì)丟失目標(biāo),若用戶攜帶 Air Tag 定位器雳灵,則可能用 U 芯片進(jìn)行定位,來(lái)實(shí)現(xiàn)許多之前無(wú)法做到的場(chǎng)景闸盔。雖 Apple 暫時(shí)還沒(méi)開放它在 AR 場(chǎng)景的應(yīng)用悯辙,但 U 系列芯片給開發(fā)者提供了許多想象力。
iOS 平臺(tái)的 AR 應(yīng)用通常由 ARKit
和渲染引擎兩部分構(gòu)成:
ARKit
的 ARSession
負(fù)責(zé)管理每一幀的信息迎吵。ARSession
做了兩件事躲撰,拍攝圖像并獲取傳感器數(shù)據(jù),對(duì)數(shù)據(jù)進(jìn)行分析處理后逐幀輸出击费。如下圖:
2拢蛋、設(shè)備追蹤
a、啟動(dòng) ARSession
設(shè)備追蹤確保了虛擬物體的位置不受設(shè)備移動(dòng)的影響蔫巩。在啟動(dòng) ARSession
時(shí)需要傳入一個(gè) ARSessionConfiguration
的子類對(duì)象谆棱,以區(qū)別三種追蹤模式:ARFaceTrackingConfiguration
、ARWorldTrackingConfiguration
圆仔、AROrientationTrackingConfiguration
贡翘。其中 ARFaceTrackingConfiguration
可以識(shí)別人臉的位置雪隧、方向以及獲取拓?fù)浣Y(jié)構(gòu)。此外,還可以探測(cè)到預(yù)設(shè)的 52 種豐富的面部動(dòng)作再芋,如眨眼、微笑竣蹦、皺眉等等橄霉。ARFaceTrackingConfiguration
需要調(diào)用支持 TrueDepth
的前置攝像頭進(jìn)行追蹤。以ARWorldTrackingConfiguration
為例進(jìn)行追蹤沪曙,獲取特征點(diǎn)奕污。
// 創(chuàng)建一個(gè) ARSessionConfiguration
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session
let session = ARSession()
// Run
session.run(configuration)
b、ARSession 底層如何進(jìn)行世界追蹤
-
ARSession
底層使用了AVCaputreSession
來(lái)獲取攝像機(jī)拍攝的視頻(一幀一幀的圖像序列)珊蟀。 -
ARSession
底層使用了CMMotionManager
來(lái)獲取設(shè)備的運(yùn)動(dòng)信息(比如旋轉(zhuǎn)角度菊值、移動(dòng)距離等) -
ARSession
根據(jù)獲取的圖像序列以及設(shè)備的運(yùn)動(dòng)信息進(jìn)行分析,最后輸出ARFrame
育灸,ARFrame
中就包含有渲染虛擬世界所需的所有信息腻窒。
c、追蹤信息點(diǎn)
AR-World
的坐標(biāo)系如下磅崭,當(dāng)我們運(yùn)行 ARSession
時(shí)設(shè)備所在的位置就是 AR-World
的坐標(biāo)系原點(diǎn)儿子。
在這個(gè) AR-World
坐標(biāo)系中,ARKit
會(huì)追蹤以下幾個(gè)信息:
- 追蹤設(shè)備的位置以及旋轉(zhuǎn)砸喻,這里的兩個(gè)信息均是相對(duì)于設(shè)備起始時(shí)的信息柔逼。
- 追蹤物理距離(以“米”為單位)蒋譬,例如
ARKit
檢測(cè)到一個(gè)平面,我們希望知道這個(gè)平面有多大愉适。 - 追蹤我們手動(dòng)添加的希望追蹤的點(diǎn)犯助,例如我們手動(dòng)添加的一個(gè)虛擬物體。
d维咸、追蹤如何工作
ARKit
使用視覺(jué)慣性測(cè)距技術(shù)剂买,對(duì)攝像頭采集到的圖像序列進(jìn)行計(jì)算機(jī)視覺(jué)分析,并且與設(shè)備的運(yùn)動(dòng)傳感器信息相結(jié)合癌蓖。ARKit
會(huì)識(shí)別出每一幀圖像中的特征點(diǎn)瞬哼,并且根據(jù)特征點(diǎn)在連續(xù)的圖像幀之間的位置變化,然后與運(yùn)動(dòng)傳感器提供的信息進(jìn)行比較租副,最終得到高精度的設(shè)備位置和偏轉(zhuǎn)信息坐慰。
上圖中劃出曲線的運(yùn)動(dòng)的點(diǎn)代表設(shè)備,可以看到以設(shè)備為中心有一個(gè)坐標(biāo)系也在移動(dòng)和旋轉(zhuǎn)用僧,這代表著設(shè)備在不斷的移動(dòng)和旋轉(zhuǎn)结胀。這個(gè)信息是通過(guò)設(shè)備的運(yùn)動(dòng)傳感器獲取的。
動(dòng)圖中右側(cè)的黃色點(diǎn)是 3D 特征點(diǎn)永毅。3D特征點(diǎn)就是處理捕捉到的圖像得到的把跨,能代表物體特征的點(diǎn)。例如地板的紋理沼死、物體的邊邊角角都可以成為特征點(diǎn)着逐。上圖中我們看到當(dāng)設(shè)備移動(dòng)時(shí),ARKit
在不斷的追蹤捕捉到的畫面中的特征點(diǎn)意蛀。
ARKit
將上面兩個(gè)信息進(jìn)行結(jié)合耸别,最終得到了高精度的設(shè)備位置和偏轉(zhuǎn)信息。
e县钥、ARWorldTrackingConfiguration
ARWorldTrackingConfiguration
提供 6DoF
(Six Degree of Freedom
)的設(shè)備追蹤秀姐。包括三個(gè)姿態(tài)角 Yaw
(偏航角)、Pitch
(俯仰角)和 Roll
(翻滾角)若贮,以及沿笛卡爾坐標(biāo)系中 X
省有、Y
和 Z
三軸的偏移量。
不僅如此谴麦,ARKit
還使用了 VIO
(Visual-Inertial Odometry
)來(lái)提高設(shè)備運(yùn)動(dòng)追蹤的精度蠢沿。在使用慣性測(cè)量單元(IMU
)檢測(cè)運(yùn)動(dòng)軌跡的同時(shí),對(duì)運(yùn)動(dòng)過(guò)程中攝像頭拍攝到的圖片進(jìn)行圖像處理匾效。將圖像中的一些特征點(diǎn)的變化軌跡與傳感器的結(jié)果進(jìn)行比對(duì)后舷蟀,輸出最終的高精度結(jié)果。
從追蹤的維度和準(zhǔn)確度來(lái)看,ARWorldTrackingConfiguration
非常強(qiáng)悍野宜。但它也有兩個(gè)致命的缺點(diǎn):受環(huán)境光線質(zhì)量影響和受劇烈運(yùn)動(dòng)影響扫步。由于在追蹤過(guò)程中要通過(guò)采集圖像來(lái)提取特征點(diǎn),所以圖像的質(zhì)量會(huì)影響追蹤的結(jié)果匈子。在光線較差的環(huán)境下(比如夜晚或者強(qiáng)光)河胎,拍攝的圖像無(wú)法提供正確的參考,追蹤的質(zhì)量也會(huì)隨之下降旬牲。追蹤過(guò)程中會(huì)逐幀比對(duì)圖像與傳感器結(jié)果仿粹,如果設(shè)備在短時(shí)間內(nèi)劇烈的移動(dòng),會(huì)很大程度上干擾追蹤結(jié)果原茅。
e、追蹤狀態(tài)
世界追蹤有三種狀態(tài)堕仔,我們可以通過(guò) camera.trackingState
獲取當(dāng)前的追蹤狀態(tài)擂橘。
從上圖我們看到有三種追蹤狀態(tài):
Not Available// 世界追蹤正在初始化,還未開始工作
Normal// 正常工作狀態(tài)
Limited// 限制狀態(tài)摩骨,當(dāng)追蹤質(zhì)量受到影響時(shí)通贞,追蹤狀態(tài)可能會(huì)變?yōu)?Limited 狀態(tài)
與 TrackingState
關(guān)聯(lián)的一個(gè)信息是ARCamera.TrackingState.Reason
,這是一個(gè)枚舉類型:
case excessiveMotion// 設(shè)備移動(dòng)過(guò)快恼五,無(wú)法正常追蹤
case initializing// 正在初始化
case insufficientFeatures// 特征過(guò)少昌罩,無(wú)法正常追蹤
case none// 正常工作
我們可以通過(guò) ARSessionObserver
協(xié)議去獲取追蹤狀態(tài)的變化。
3灾馒、ARFrame
a茎用、ARFrame
ARFrame
中包含有世界追蹤過(guò)程獲取的所有信息,ARFrame
中與世界追蹤有關(guān)的信息主要是:anchors
和 camera
睬罗。
//camera: 含有攝像機(jī)的位置轨功、旋轉(zhuǎn)以及拍照參數(shù)等信息
var camera: [ARCamera]
//ahchors: 代表了追蹤的點(diǎn)或面
var anchors: [ARAnchor]
b、ARAnchor
ARAnchor
是空間中相對(duì)真實(shí)世界的位置和角度容达。ARAnchor
可以添加到場(chǎng)景中古涧,或是從場(chǎng)景中移除』ㄑ危基本上來(lái)說(shuō)羡滑,它們用于表示虛擬內(nèi)容在物理環(huán)境中的錨定,所以如果要添加自定義 anchor
算芯,添加到 session
里就可以了柒昏,它會(huì)在 session
生命周期中一直存在。但如果你在運(yùn)行諸如平面檢測(cè)功能也祠,ARAnchor
則會(huì)被自動(dòng)添加到 session
中昙楚。
要響應(yīng)被添加的 anchor
,可以從 current ARFrame
中獲得完整列表诈嘿,此列表包含 session
正在追蹤的所有 anchor
堪旧∠鞔校或者也可以響應(yīng) delegate
方法,例如 add
淳梦、update
以及 remove
析砸,session
中的 anchor
被添加、更新或移除時(shí)會(huì)通知爆袍。
c首繁、ARCamera
每個(gè) ARFrame
都會(huì)包含一個(gè) ARCamera
。ARCamera
對(duì)象表示虛擬攝像頭陨囊。虛擬攝像頭就代表了設(shè)備的角度和位置弦疮。
-
ARCamera
提供了一個(gè)transform
。transform
是一個(gè)4x4
矩陣蜘醋。提供了物理設(shè)備相對(duì)于初始位置的變換胁塞。 -
ARCamera
提供了追蹤狀態(tài)(tracking state
),通知你如何使用transform
压语。 -
ARCamera
提供了相機(jī)內(nèi)部功能(camera intrinsics
)啸罢。包括焦距和主焦點(diǎn),用于尋找投影矩陣胎食。投影矩陣是ARCamera
上的一個(gè)convenience
方法扰才,可用于渲染虛擬你的幾何體。
4厕怜、場(chǎng)景解析
場(chǎng)景解析主要功能是對(duì)現(xiàn)實(shí)世界的場(chǎng)景進(jìn)行分析衩匣,解析出比如現(xiàn)實(shí)世界的平面等信息,可以讓我們把一些虛擬物體放在某些實(shí)物處酣倾。ARKit
提供的場(chǎng)景解析主要有平面檢測(cè)舵揭、場(chǎng)景交互以及光照估計(jì)三種,下面逐個(gè)分析躁锡。
a午绳、平面檢測(cè)(Plane detection)
ARKit
的平面檢測(cè)用于檢測(cè)出現(xiàn)實(shí)世界的水平面。
? 上圖中可以看出映之,ARkit
檢測(cè)出了兩個(gè)平面拦焚,圖中的兩個(gè)三維坐標(biāo)系是檢測(cè)出的平面的本地坐標(biāo)系,此外杠输,檢測(cè)出的平面是有一個(gè)大小范圍的赎败。
? 平面檢測(cè)是一個(gè)動(dòng)態(tài)的過(guò)程,當(dāng)攝像機(jī)不斷移動(dòng)時(shí)蠢甲,檢測(cè)到的平面也會(huì)不斷的變化僵刮。下圖中可以看到當(dāng)移動(dòng)攝像機(jī)時(shí),已經(jīng)檢測(cè)到的平面的坐標(biāo)原點(diǎn)以及平面范圍都在不斷的變化。
? 開啟平面檢測(cè)很簡(jiǎn)單搞糕,只需要在 run ARSession
之前勇吊,將 ARSessionConfiguration
的planeDetection
屬性設(shè)為 true
即可。
// Create a world tracking session configuration.
let configuration = ARWorldTrackingSessionConfiguration()
configuration.planeDetection = .horizontal
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
? 平面的表示方式:當(dāng)ARKit
檢測(cè)到一個(gè)平面時(shí)窍仰,ARKit
會(huì)為該平面自動(dòng)添加一個(gè) ARPlaneAnchor
汉规,這個(gè) ARPlaneAnchor
就表示了一個(gè)平面。
? 當(dāng) ARKit
系統(tǒng)檢測(cè)到新平面時(shí)驹吮,ARKit
會(huì)自動(dòng)添加一個(gè) ARPlaneAnchor
到 ARSession
中针史。我們可以通過(guò) ARSessionDelegate
獲取當(dāng)前 ARSession
的 ARAnchor
改變的通知,主要有以下三種情況:
新加入了 ARAnchor:對(duì)于平面檢測(cè)來(lái)說(shuō)碟狞,當(dāng)新檢測(cè)到某平面時(shí)啄枕,我們會(huì)收到該通知,通知中的 ARAnchor
數(shù)組會(huì)包含新添加的平面族沃,其類型是 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更新:從上面我們知道當(dāng)設(shè)備移動(dòng)時(shí)射亏,檢測(cè)到的平面是不斷更新的,當(dāng)平面更新時(shí)竭业,會(huì)回調(diào)這個(gè)接口。
func session(_ session: ARSession, didUpdate anchors: [ARAnchor])
刪除 ARAnchor:當(dāng)手動(dòng)刪除某個(gè) Anchor
時(shí)就會(huì)回調(diào)此方法及舍。此外未辆,對(duì)于檢測(cè)到的平面來(lái)說(shuō),如果兩個(gè)平面進(jìn)行了合并锯玛,則會(huì)刪除其中一個(gè)咐柜,此時(shí)也會(huì)回調(diào)此方法。
func session(_ session: ARSession, didRemove anchors: [ARAnchor])
b攘残、場(chǎng)景交互(Hit-testing)
Hit-testing
是為了獲取當(dāng)前捕捉到的圖像中某點(diǎn)擊位置有關(guān)的信息(包括平面拙友、特征點(diǎn)、ARAnchor
等)歼郭。原理圖如下:
當(dāng)點(diǎn)擊屏幕時(shí)遗契,ARKit
會(huì)發(fā)射一個(gè)射線,假設(shè)屏幕平面是三維坐標(biāo)系中的 xy
平面病曾,那么該射線會(huì)沿著 z
軸方向射向屏幕里面牍蜂,這就是一次 Hit-testing
過(guò)程。此次過(guò)程會(huì)將射線遇到的所有有用信息返回泰涂,返回結(jié)果以離屏幕距離進(jìn)行排序鲫竞,離屏幕最近的排在最前面。
ARFrame
提供了Hit-testing
的接口:
func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]
上述接口中有一個(gè) types
參數(shù)逼蒙,該參數(shù)表示此次 Hit-testing
過(guò)程需要獲取的信息類型从绘,ResultType
有以下四種。
featurePoint:表示此次 Hit-testing
過(guò)程希望返回當(dāng)前圖像中Hit-testing
射線經(jīng)過(guò)的 3D 特征點(diǎn)。
estimatedHorizontalPlane:表示此次 Hit-testing
過(guò)程希望返回當(dāng)前圖像中 Hit-testing
射線經(jīng)過(guò)的預(yù)估平面僵井。預(yù)估平面表示 ARKit
當(dāng)前檢測(cè)到一個(gè)可能是平面的信息陕截,但當(dāng)前尚未確定是平面,所以 ARKit
還沒(méi)有為此預(yù)估平面添加 ARPlaneAnchor
驹沿。
existingPlaneUsingExtent:表示此次Hit-testing
過(guò)程希望返回當(dāng)前圖像中Hit-testing
射線經(jīng)過(guò)的有大小范圍的平面艘策。
existingPlane:表示此次Hit-testing
過(guò)程希望返回當(dāng)前圖像中Hit-testing
射線經(jīng)過(guò)的無(wú)限大小的平面。
上圖中渊季,平面大小是綠色平面所展示的大小朋蔫,但exsitingPlane
選項(xiàng)表示即使Hit-testing
射線落在了綠色平面外面,也會(huì)將此平面返回却汉。換句話說(shuō)驯妄,將所有平面無(wú)限延展,只要 Hit-testing 射線經(jīng)過(guò)了無(wú)限延展后的平面合砂,就會(huì)返回該平面青扔。
Demo演示:Hit-testing
的point(0.5, 0.5)
代表屏幕的中心,屏幕左上角為(0, 0)
翩伪,右下角為(1, 1)
微猖。 對(duì)于 featurePoint
和 estimatedHorizontalPlane
的結(jié)果,ARKit
沒(méi)有為其添加ARAnchor
,我們可以使用Hit-testing
獲取信息后自己為ARSession
添加 ARAnchor
,下面代碼就顯示了此過(guò)程熔恢。
// 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)
}
c、光照估計(jì)(Light estimation)
下圖中犁珠,一個(gè)虛擬物體茶杯被放在了現(xiàn)實(shí)世界的桌子上。
當(dāng)周圍環(huán)境光線較好時(shí)互亮,攝像機(jī)捕捉到的圖像光照強(qiáng)度也較好犁享,此時(shí),我們放在桌子上的茶杯看起來(lái)就比較貼近于現(xiàn)實(shí)效果豹休,如上圖最左邊的圖炊昆。但是當(dāng)周圍光線較暗時(shí),攝像機(jī)捕捉到的圖像也較暗慕爬,如上圖中間的圖窑眯,此時(shí)茶杯的亮度就顯得跟現(xiàn)實(shí)世界格格不入。
針對(duì)這種情況医窿,ARKit
提供了光照估計(jì)磅甩,開啟光照估計(jì)后,我們可以拿到當(dāng)前圖像的光照強(qiáng)度姥卢,從而能夠以更自然的光照強(qiáng)度去渲染虛擬物體卷要,如上圖最右邊的圖渣聚。
光照估計(jì)基于當(dāng)前捕捉到的圖像的曝光等信息,給出一個(gè)估計(jì)的光照強(qiáng)度值(單位為lumen
僧叉,光強(qiáng)單位)奕枝。默認(rèn)的光照強(qiáng)度為 1000lumen
,當(dāng)現(xiàn)實(shí)世界較亮?xí)r瓶堕,我們可以拿到一個(gè)高于 1000lumen
的值隘道,相反,當(dāng)現(xiàn)實(shí)世界光照較暗時(shí)郎笆,我們會(huì)拿到一個(gè)低于 1000lumen
的值谭梗。
ARKit
的光照估計(jì)默認(rèn)是開啟的,當(dāng)然也可以通過(guò)下述方式手動(dòng)配置:
configuration.isLightEstimationEnabled = true
獲取光照估計(jì)的光照強(qiáng)度也很簡(jiǎn)單宛蚓,只需要拿到當(dāng)前的 ARFrame
激捏,通過(guò)以下代碼即可獲取估計(jì)的光照強(qiáng)度:
let intensity = frame.lightEstimate?.ambientIntensity
5、SceneKit
a凄吏、簡(jiǎn)介
渲染是呈現(xiàn)AR world
的最后一個(gè)過(guò)程远舅。此過(guò)程將創(chuàng)建的虛擬世界、捕捉的真實(shí)世界痕钢、ARKit
追蹤的信息以及 ARKit
場(chǎng)景解析的的信息結(jié)合在一起图柏,渲染出一個(gè) AR world
。渲染過(guò)程需要實(shí)現(xiàn)以下幾點(diǎn)才能渲染出正確的 AR world
:
- 將攝像機(jī)捕捉到的真實(shí)世界的視頻作為背景任连。
- 將世界追蹤到的相機(jī)狀態(tài)信息實(shí)時(shí)更新到 AR world 中的相機(jī)爆办。
- 處理光照估計(jì)的光照強(qiáng)度。
- 實(shí)時(shí)渲染虛擬世界物體在屏幕中的位置课梳。
如果我們自己處理這個(gè)過(guò)程,可以看到還是比較復(fù)雜的余佃,ARKit
為簡(jiǎn)化開發(fā)者的渲染過(guò)程暮刃,為開發(fā)者提供了簡(jiǎn)單易用的使用SceneKit
(3D 引擎)以及 SpriteKit
(2D 引擎)渲染的視圖ARSCNView
以及ARSKView
。當(dāng)然開發(fā)者也可以使用其他引擎進(jìn)行渲染爆土,只需要將以上幾個(gè)信息進(jìn)行處理融合即可椭懊。
b、SceneKit 的坐標(biāo)系
我們知道 UIKit
使用一個(gè)包含有 x
和 y
信息的 CGPoint
來(lái)表示一個(gè)點(diǎn)的位置步势,但是在 3D 系統(tǒng)中氧猬,需要一個(gè)z
參數(shù)來(lái)描述物體在空間中的深度,SceneKit
的坐標(biāo)系可以參考下圖:
這個(gè)三維坐標(biāo)系中坏瘩,表示一個(gè)點(diǎn)的位置需要使用(x,y,z)
坐標(biāo)表示盅抚。紅色方塊位于x
軸,綠色方塊位于 y
軸倔矾,藍(lán)色方塊位于z
軸妄均,灰色方塊位于原點(diǎn)柱锹。在SceneKit
中我們可以這樣創(chuàng)建一個(gè)三維坐標(biāo):
let position = SCNVector3(x: 0, y: 5, z: 10)
c、SceneKit 中的場(chǎng)景和節(jié)點(diǎn)
我們可以將 SceneKit
中的場(chǎng)景(SCNScene
)想象為一個(gè)虛擬的 3D 空間丰包,然后可以將一個(gè)個(gè)的節(jié)點(diǎn)(SCNNode
)添加到場(chǎng)景中禁熏。SCNScene
中有唯一一個(gè)根節(jié)點(diǎn)(坐標(biāo)是(x:0, y:0, z:0)
),除了根節(jié)點(diǎn)外邑彪,所有添加到 SCNScene
中的節(jié)點(diǎn)都需要一個(gè)父節(jié)點(diǎn)瞧毙。
下圖中位于坐標(biāo)系中心的就是根節(jié)點(diǎn),此外還有添加的兩個(gè)節(jié)點(diǎn) NodeA
和 NodeB
寄症,其中 NodeA
的父節(jié)點(diǎn)是根節(jié)點(diǎn)宙彪,NodeB
的父節(jié)點(diǎn)是 NodeA
。
SCNScene
中的節(jié)點(diǎn)加入時(shí)可以指定一個(gè)三維坐標(biāo)(默認(rèn)為(x:0, y:0, z:0)
)瘸爽,這個(gè)坐標(biāo)是相對(duì)于其父節(jié)點(diǎn)的位置您访。這里說(shuō)明兩個(gè)概念:
- 本地坐標(biāo)系:以場(chǎng)景中的某節(jié)點(diǎn)(非根節(jié)點(diǎn))為原點(diǎn)建立的三維坐標(biāo)系
- 世界坐標(biāo)系:以根節(jié)點(diǎn)為原點(diǎn)創(chuàng)建的三維坐標(biāo)系稱為世界坐標(biāo)系。
上圖中我們可以看到 NodeA
的坐標(biāo)是相對(duì)于世界坐標(biāo)系(由于NodeA
的父節(jié)點(diǎn)是根節(jié)點(diǎn))的位置剪决,而NodeB
的坐標(biāo)代表了 NodeB
在 NodeA
的本地坐標(biāo)系位置(NodeB
的父節(jié)點(diǎn)是NodeA
)灵汪。
d、SceneKit 中的攝像機(jī)
有了 SCNScene
和SCNNode
后柑潦,我們還需要一個(gè)攝像機(jī)(SCNCamera
)來(lái)決定我們可以看到場(chǎng)景中的哪一塊區(qū)域(就好比現(xiàn)實(shí)世界中有了各種物體享言,但還需要人的眼睛才能看到物體)。攝像機(jī)在 SCNScene
的工作模式如下圖:
-
SceneKit
中SCNCamera
拍攝的方向始終為z
軸負(fù)方向渗鬼。 - 視野(
Field of View
)是攝像機(jī)的可視區(qū)域的極限角度览露。角度越小,視野越窄譬胎,反之差牛,角度越大,視野越寬堰乔。 - 視錐體(
Viewing Frustum
)決定著攝像頭可視區(qū)域的深度(z 軸表示深度)偏化。任何不在這個(gè)區(qū)域內(nèi)的物體將被剪裁掉(離攝像頭太近或者太遠(yuǎn)),不會(huì)顯示在最終的畫面中镐侯。
在SceneKit
中我們可以使用如下方式創(chuàng)建一個(gè)攝像機(jī):
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)
e侦讨、SCNView
最后,我們需要一個(gè) View
來(lái)將 SCNScene
中的內(nèi)容渲染到顯示屏幕上苟翻,這個(gè)工作由 SCNView
完成韵卤。這一步其實(shí)很簡(jiǎn)單,只需要?jiǎng)?chuàng)建一個(gè)SCNView
實(shí)例崇猫,然后將 SCNView
的scene
屬性設(shè)置為剛剛創(chuàng)建的 SCNScene
沈条,然后將 SCNView
添加到UIKit
的view
或window
上即可。示例代碼如下:
let scnView = SCNView()
scnView.scene = scene
vc.view.addSubview(scnView)
scnView.frame = vc.view.bounds
二诅炉、二維碼識(shí)別
運(yùn)行效果
2020-12-21 16:11:05.525933+0800 CodeKamera[18444:3128121] <AVMetadataMachineReadableCodeObject: 二維碼信息:0x281251580, type="org.iso.QRCode", bounds={ 0.1,0.0 0.2x0.3 }>corners { 0.1,0.3 0.4,0.3 0.4,0.0 0.2,0.0 }, time 441939661045708, stringValue "謝佳培"
1拍鲤、設(shè)備的配置流程
默認(rèn)使用后置攝像頭進(jìn)行掃描贴谎,使用AVMediaTypeVideo
表示視頻
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
設(shè)備輸入初始化
self.input = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil];
設(shè)備輸出初始化,并設(shè)置代理和回調(diào)季稳。當(dāng)設(shè)備掃描到數(shù)據(jù)時(shí)通過(guò)該代理輸出隊(duì)列,一般輸出隊(duì)列都設(shè)置為主隊(duì)列景鼠,也是設(shè)置了回調(diào)方法執(zhí)行所在的隊(duì)列環(huán)境仲翎。
self.output = [[AVCaptureMetadataOutput alloc] init];
[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
會(huì)話初始化,并設(shè)置采樣質(zhì)量為高
self.session = [[AVCaptureSession alloc] init];
[self.session setSessionPreset:AVCaptureSessionPresetHigh];
通過(guò)會(huì)話連接設(shè)備的輸入輸出
if ([self.session canAddInput:_input])
{
[self.session addInput:_input];
}
if ([self.session canAddOutput:_output])
{
[self.session addOutput:_output];
}
指定設(shè)備的識(shí)別類型铛漓,這里只指定二維碼識(shí)別這一種類型 AVMetadataObjectTypeQRCode
溯香。指定識(shí)別類型這一步一定要在輸出添加到會(huì)話之后,否則設(shè)備的可識(shí)別類型會(huì)為空浓恶,程序會(huì)出現(xiàn)崩潰玫坛。
[self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
設(shè)置掃描信息的識(shí)別區(qū)域,以下代碼設(shè)置為正中央的一塊正方形區(qū)域包晰,識(shí)別區(qū)域越小識(shí)別效率越高湿镀,所以不設(shè)置整個(gè)屏幕。
[self.output setRectOfInterest:CGRectMake(x, y, width, height)];
預(yù)覽層初始化伐憾,self.session
負(fù)責(zé)驅(qū)動(dòng)input
進(jìn)行信息的采集勉痴,layer
負(fù)責(zé)把圖像渲染顯示。預(yù)覽層的區(qū)域設(shè)置為整個(gè)屏幕树肃,這樣可以方便我們進(jìn)行移動(dòng)二維碼到掃描區(qū)域蒸矛。
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
self.previewLayer.frame = self.view.bounds;
self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:self.previewLayer];
重寫代理的回調(diào)方法,獲取后置攝像頭掃描到二維碼的信息胸嘴,實(shí)現(xiàn)我們?cè)诔晒ψR(shí)別二維碼之后要實(shí)現(xiàn)的功能雏掠。
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// 停止掃描
[self.session stopRunning];
if (metadataObjects.count >= 1)
{
// 數(shù)組中包含的都是AVMetadataMachineReadableCodeObject類型的對(duì)象,該對(duì)象中包含解碼后的數(shù)據(jù)
AVMetadataMachineReadableCodeObject *QRObject = [metadataObjects lastObject];
// 拿到掃描內(nèi)容在這里進(jìn)行個(gè)性化處理
NSString *result = QRObject.stringValue;
// 解析數(shù)據(jù)進(jìn)行處理并實(shí)現(xiàn)相應(yīng)的邏輯...
NSLog(@"掃描到的二維碼的信息:%@",result);
}
}
2劣像、CameraController 中的二維碼識(shí)別方法
a磁玉、設(shè)置會(huì)話的輸入設(shè)備
設(shè)置相機(jī)自動(dòng)對(duì)焦,這樣可以在任何距離都可以進(jìn)行掃描驾讲。 因?yàn)閽呙钘l碼,距離都比較近席赂,所以AVCaptureAutoFocusRangeRestrictionNear
通過(guò)縮小距離吮铭,來(lái)提高識(shí)別成功率。
- (BOOL)setupSessionInputs:(NSError *__autoreleasing *)error
{
//設(shè)置相機(jī)自動(dòng)對(duì)焦颅停,這樣可以在任何距離都可以進(jìn)行掃描
BOOL success = [super setupSessionInputs:error];
if(success)
{
//判斷是否能自動(dòng)聚焦
if (self.activeCamera.autoFocusRangeRestrictionSupported)
{
//鎖定設(shè)備
if ([self.activeCamera lockForConfiguration:error])
{
self.activeCamera.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
//釋放排他鎖
[self.activeCamera unlockForConfiguration];
}
}
}
return YES;
}
b谓晌、設(shè)置會(huì)話輸出設(shè)備
- (BOOL)setupSessionOutputs:(NSError **)error
{
//獲取輸出設(shè)備
self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
//判斷是否能添加輸出設(shè)備
if ([self.captureSession canAddOutput:self.metadataOutput])
{
//添加輸出設(shè)備
[self.captureSession addOutput:self.metadataOutput];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//設(shè)置委托代理
[self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
//指定掃描對(duì)是OR碼(移動(dòng)營(yíng)銷) & Aztec 碼(登機(jī)牌)
NSArray *types = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeAztecCode,AVMetadataObjectTypeDataMatrixCode,AVMetadataObjectTypePDF417Code];
self.metadataOutput.metadataObjectTypes = types;
}
else
{
//錯(cuò)誤時(shí),存儲(chǔ)錯(cuò)誤信息
NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Faild to add metadata output."};
*error = [NSError errorWithDomain:THCameraErrorDomain code:THCameraErrorFailedToAddOutput userInfo:userInfo];
return NO;
}
return YES;
}
c癞揉、處理二維碼的委托方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
if (metadataObjects.count > 0)
{
NSLog(@"二維碼信息:%@",metadataObjects[0]);
}
//通過(guò)委托將二維碼傳遞到preview
[self.codeDetectionDelegate didDetectCodes:metadataObjects];
}
3纸肉、PreviewView中的二維碼識(shí)別方法
- (void)didDetectCodes:(NSArray *)codes
{
//保存轉(zhuǎn)換完成的元數(shù)據(jù)對(duì)象
NSArray *transformedCodes = [self transformedCodesFromCodes:codes];
//從codeLayers字典中獲得key溺欧,用來(lái)判斷那個(gè)圖層應(yīng)該在方法尾部移除
NSMutableArray *lostCodes = [self.codeLayers.allKeys mutableCopy];
//遍歷數(shù)組
for (AVMetadataMachineReadableCodeObject *code in transformedCodes)
{
//獲得code.stringValue
NSString *stringValue = code.stringValue;
if (stringValue)
{
[lostCodes removeObject:stringValue];
}
else
{
continue;
}
......
}
//遍歷lostCodes
for (NSString *stringValue in lostCodes)
{
//將里面的條目圖層從 previewLayer 中移除
for (CALayer *layer in self.codeLayers[stringValue])
{
[layer removeFromSuperlayer];
}
//數(shù)組條目中也要移除
[self.codeLayers removeObjectForKey:stringValue];
}
}
根據(jù)當(dāng)前的 stringValue
查找圖層。如果沒(méi)有對(duì)應(yīng)的類目柏肪,則進(jìn)行新建圖層姐刁。
//根據(jù)當(dāng)前的 stringValue 查找圖層
NSArray *layers = self.codeLayers[stringValue];
//如果沒(méi)有對(duì)應(yīng)的類目
if (!layers)
{
//新建圖層 方、圓
layers = @[[self makeBoundsLayer],[self makeCornersLayer]];
//將圖層以stringValue 為key 存入字典中
self.codeLayers[stringValue] = layers;
//在預(yù)覽圖層上添加 圖層0烦味、圖層1
[self.previewLayer addSublayer:layers[0]];
[self.previewLayer addSublayer:layers[1]];
}
//創(chuàng)建一個(gè)和對(duì)象的bounds關(guān)聯(lián)的UIBezierPath
//畫方框
CAShapeLayer *boundsLayer = layers[0];
boundsLayer.path = [self bezierPathForBounds:code.bounds].CGPath;
//對(duì)于cornersLayer 構(gòu)建一個(gè)CGPath
CAShapeLayer *cornersLayer = layers[1];
cornersLayer.path = [self bezierPathForCorners:code.corners].CGPath;
三聂使、人臉識(shí)別
1、人臉識(shí)別簡(jiǎn)介
計(jì)算機(jī)視覺(jué)
計(jì)算機(jī)視覺(jué)指用攝影機(jī)和計(jì)算機(jī)代替人眼對(duì)目標(biāo)進(jìn)行識(shí)別谬俄、跟蹤和測(cè)量等機(jī)器視覺(jué)柏靶,并進(jìn)一步做圖像處理。計(jì)算機(jī)視覺(jué)系統(tǒng)的具體實(shí)現(xiàn)方法由其功能決定——是預(yù)先固定的抑或是在運(yùn)行過(guò)程中自動(dòng)學(xué)習(xí)調(diào)整溃论。盡管如此屎蜓,計(jì)算機(jī)視覺(jué)的都需要具備以下處理步驟:
人臉識(shí)別
人臉識(shí)別是計(jì)算機(jī)視覺(jué)的一種應(yīng)用,指利用分析比較人臉視覺(jué)特征信息進(jìn)行身份鑒別的計(jì)算機(jī)技術(shù)钥勋,包括人臉圖像采集炬转、人臉定位、人臉識(shí)別預(yù)處理笔诵、身份確認(rèn)以及身份查找等返吻。iOS中常用的有四種實(shí)現(xiàn)方式:CoreImage
、Vision
乎婿、OpenCV
测僵、AVFoundation
。
2谢翎、使用 CoreImage 實(shí)現(xiàn)靜態(tài)人臉識(shí)別
CoreImage 實(shí)現(xiàn)靜態(tài)人臉識(shí)別的關(guān)鍵類
操作部分
-
濾鏡(CIFliter):
CIFilter
產(chǎn)生一個(gè)CIImage
捍靠,接受一到多的圖片作為輸入,經(jīng)過(guò)一些過(guò)濾操作森逮,產(chǎn)生指定輸出的圖片榨婆。 -
檢測(cè)(CIDetector):
CIDetector
檢測(cè)處理圖片的特性,如使用來(lái)檢測(cè)圖片中人臉的眼睛褒侧、嘴巴等等良风。 -
特征(CIFeature):
CIFeature
代表由detector
處理后產(chǎn)生的特征。
圖像部分
-
畫布(CIContext):畫布類可被用來(lái)處理
Quartz 2D
或者OpenGL
闷供,也可以用它來(lái)關(guān)聯(lián)CoreImage
類烟央,進(jìn)行濾鏡、顏色等渲染處理歪脏。 - 顏色(CIColor): 圖片的關(guān)聯(lián)與畫布疑俭、圖片像素顏色的處理。
- 向量(CIVector): 圖片的坐標(biāo)向量等幾何方法處理婿失。
- 圖片(CIImage):代表一個(gè)圖像钞艇,可代表關(guān)聯(lián)后輸出的圖像啄寡。
使用CoreImage 實(shí)現(xiàn)靜態(tài)人臉識(shí)別的Demo演示
a、運(yùn)行效果
2020-08-27 10:02:46.840694+0800 FaceRecognitionDemo[16278:796341] Metal API Validation Enabled
人臉區(qū)域?yàn)椋篎ound bounds are (533.2980717893497, 906.6676903116021, 636.9456902844558, 625.2230840751323)
左眼位置: (747.0441848951896, 1391.4379881573832)
右眼位置: (1054.0997744993274, 1418.2982372168135)
b哩照、檢測(cè)人臉進(jìn)行識(shí)別的方法
func detect()
{
.....
}
獲取人像圖片
guard let personciImage = CIImage(image: personPic.image!) else { return }
設(shè)置人臉檢測(cè)的精確度
let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
進(jìn)行人臉檢測(cè)
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
在人像圖片中鎖定人臉挺物,可能有多個(gè)人
let faces = faceDetector?.features(in: personciImage)
轉(zhuǎn)換坐標(biāo)系
let ciImageSize = personciImage.extent.size
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -ciImageSize.height)
c、針對(duì)每張人臉進(jìn)行處理
for face in faces as! [CIFaceFeature]
{
print("人臉區(qū)域?yàn)椋篎ound bounds are \(face.bounds)")
....
if face.hasLeftEyePosition
{
print("左眼位置: \(face.leftEyePosition)")
}
if face.hasRightEyePosition
{
print("右眼位置: \(face.rightEyePosition)")
}
}
在圖像視圖中計(jì)算矩形的實(shí)際位置和大小
// 應(yīng)用變換轉(zhuǎn)換坐標(biāo)
var faceViewBounds = face.bounds.applying(transform)
// 在圖像視圖中計(jì)算矩形的實(shí)際位置和大小
let viewSize = personPic.bounds.size
let scale = min(viewSize.width / ciImageSize.width, viewSize.height / ciImageSize.height)
let offsetX = (viewSize.width - ciImageSize.width * scale) / 2
let offsetY = (viewSize.height - ciImageSize.height * scale) / 2
faceViewBounds = faceViewBounds.applying(CGAffineTransform(scaleX: scale, y: scale))
faceViewBounds.origin.x += offsetX
faceViewBounds.origin.y += offsetY
添加人臉邊框
let faceBox = UIView(frame: faceViewBounds)
faceBox.layer.borderWidth = 3
faceBox.layer.borderColor = UIColor.red.cgColor
faceBox.backgroundColor = UIColor.clear
personPic.addSubview(faceBox)
3葡秒、使用 Vision 實(shí)現(xiàn)靜態(tài)人臉識(shí)別
Vision 實(shí)現(xiàn)靜態(tài)人臉識(shí)別的關(guān)鍵類
Vision:是 Apple 在 WWDC 2017 推出的圖像識(shí)別框架姻乓,它基于 Core ML
。
Vision支持多種圖片類型:CIImage
眯牧、NSURL
蹋岩、NSData
、CGImageRef
学少、CVPixelBufferRef
剪个。
Vision使用的角色有:Request
,RequestHandler
版确,results
和results
中的Observation
數(shù)組扣囊。
Request類型:比如圖中列出的 人臉識(shí)別、特征識(shí)別绒疗、文本識(shí)別侵歇、二維碼識(shí)別等。
我們?cè)谑褂眠^(guò)程中是給各種功能的 Request
提供給一個(gè) RequestHandler
吓蘑,Handler
持有需要識(shí)別的圖片信息惕虑,并將處理結(jié)果分發(fā)給每個(gè) Request
的 completion Block
中,可以從 results
屬性中得到 Observation
數(shù)組磨镶。
observations
數(shù)組中的內(nèi)容根據(jù)不同的request
請(qǐng)求返回了不同的observation
溃蔫,如:VNFaceObservation
、VNTextObservation
琳猫、VNBarcodeObservation
伟叛、VNHorizonObservation
,不同的Observation
都繼承于VNDetectedObjectObservation
脐嫂,而VNDetectedObjectObservation
則是繼承于VNObservation
统刮。每種Observation
有boundingBox
,landmarks
等屬性账千,存儲(chǔ)的是識(shí)別后物體的坐標(biāo)侥蒙,點(diǎn)位等,我們拿到坐標(biāo)后蕊爵,就可以進(jìn)行一些UI
繪制。
使用 Vision 實(shí)現(xiàn)靜態(tài)人臉識(shí)別的Demo演示
a桦山、運(yùn)行效果
b攒射、檢測(cè)人臉
func detect()
{
let handler = VNImageRequestHandler.init(cgImage: (imageView.image?.cgImage!)!, orientation: CGImagePropertyOrientation.up)
// 創(chuàng)建檢測(cè)人臉邊框的請(qǐng)求
let request = detectRequest()
DispatchQueue.global(qos: .userInteractive).async {
do
{
try handler.perform([request])
}
catch
{
print("出錯(cuò)了")
}
}
}
c醋旦、創(chuàng)建檢測(cè)人臉邊框的請(qǐng)求
func detectRequest() -> VNDetectFaceRectanglesRequest
{
let request = VNDetectFaceRectanglesRequest { (request, error) in
DispatchQueue.main.async {
if let result = request.results
{
.......
}
}
}
return request
}
對(duì)結(jié)果進(jìn)行旋轉(zhuǎn)變換
let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -self.imageView!.frame.size.height)
對(duì)結(jié)果進(jìn)行平移變換
let translate = CGAffineTransform.identity.scaledBy(x: self.imageView!.frame.size.width, y: self.imageView!.frame.size.height)
遍歷所有識(shí)別結(jié)果,創(chuàng)建人臉邊框会放,調(diào)整人臉邊框位置饲齐,再將移動(dòng)和旋轉(zhuǎn)的仿射變化運(yùn)用到邊框上,即是人臉邊框的最終位置咧最。
//遍歷所有識(shí)別結(jié)果
for item in result
{
// 創(chuàng)建人臉邊框
let faceRect = UIView(frame: CGRect.zero)
faceRect.layer.borderWidth = 3
faceRect.layer.borderColor = UIColor.red.cgColor
faceRect.backgroundColor = UIColor.clear
self.imageView!.addSubview(faceRect)
// 調(diào)整人臉邊框位置
if let faceObservation = item as? VNFaceObservation
{
// 將移動(dòng)和旋轉(zhuǎn)的仿射變化運(yùn)用到邊框上
let finalRect = faceObservation.boundingBox.applying(translate).applying(transform)
// 人臉邊框的最終位置
faceRect.frame = finalRect
}
}
4捂人、使用 OpenCV 實(shí)現(xiàn)靜態(tài)人臉識(shí)別
OpenCV 的概念
OpenCV
(Open Source Computer Vision Library
)是一個(gè)在BSD
許可下發(fā)布的開源庫(kù)卸留,因此它免費(fèi)提供給學(xué)術(shù)和商業(yè)用途垛玻。有C++
、C
兔毒、Python
和Java
接口捣鲸,支持Windows
瑟匆、Linux
、MacOS
栽惶、iOS
和Android
等系統(tǒng)愁溜。OpenCV
是為計(jì)算效率而設(shè)計(jì)的,而且密切關(guān)注實(shí)時(shí)應(yīng)用程序的發(fā)展和支持外厂。該庫(kù)用優(yōu)化的C/C++
編寫冕象,可以應(yīng)用于多核處理≈基于OpenCV
渐扮,iOS應(yīng)用程序可以實(shí)現(xiàn)很多有趣的功能,也可以把很多復(fù)雜的工作簡(jiǎn)單化穿仪。
- 對(duì)圖片進(jìn)行灰度處理
- 人臉識(shí)別席爽,即特征跟蹤
- 訓(xùn)練圖片特征庫(kù)(可用于模式識(shí)別)
- 提取特定圖像內(nèi)容(根據(jù)需求還原有用圖像信息)
使用 OpenCV 實(shí)現(xiàn)靜態(tài)人臉識(shí)別的關(guān)鍵類
-
core:簡(jiǎn)潔的核心模塊,定義了基本的數(shù)據(jù)結(jié)構(gòu)啊片,包括稠密多維數(shù)組
Mat
和其他模塊需要的基本函數(shù)只锻。 - imgproc:圖像處理模塊,包括線性和非線性圖像濾波紫谷、幾何圖像轉(zhuǎn)換 (縮放齐饮、仿射與透視變換、一般性基于表的重映射)笤昨、顏色空間轉(zhuǎn)換祖驱、直方圖等等。
- video:視頻分析模塊瞒窒,包括運(yùn)動(dòng)估計(jì)捺僻、背景消除、物體跟蹤算法。
- calib3d:包括基本的多視角幾何算法匕坯、單體和立體相機(jī)的標(biāo)定束昵、對(duì)象姿態(tài)估計(jì)、雙目立體匹配算法和元素的三維重建葛峻。
- features2d:包含了顯著特征檢測(cè)算法锹雏、描述算子和算子匹配算法。
- objdetect:物體檢測(cè)和一些預(yù)定義的物體的檢測(cè) (如人臉术奖、眼睛礁遵、杯子、人采记、汽車等)佣耐。
- ml:多種機(jī)器學(xué)習(xí)算法,如 K 均值挺庞、支持向量機(jī)和神經(jīng)網(wǎng)絡(luò)晰赞。
- highgui:一個(gè)簡(jiǎn)單易用的接口,提供視頻捕捉选侨、圖像和視頻編碼等功能掖鱼,還有簡(jiǎn)單的 UI 接口 (iOS 上可用的僅是其一個(gè)子集)。
-
gpu:
OpenCV
中不同模塊的GPU
加速算法 (iOS 上不可用)援制。 -
ocl:使用
OpenCL
實(shí)現(xiàn)的通用算法 (iOS 上不可用)戏挡。 - 一些其它輔助模塊,如
Python
綁定和用戶貢獻(xiàn)的算法晨仑。
cv::Mat
是 OpenCV
的核心數(shù)據(jù)結(jié)構(gòu)褐墅,用來(lái)表示任意 N 維矩陣。因?yàn)閳D像只是 2 維矩陣的一個(gè)特殊場(chǎng)景洪己,所以也是使用 cv::Mat
來(lái)表示的妥凳。也就是說(shuō),cv::Mat
將是你在 OpenCV
中用到最多的類答捕。
如前面所說(shuō)逝钥,OpenCV
是一個(gè) C++
的 API
,因此不能直接在 Swift
和 Objective-C
代碼中使用拱镐,但能在 Objective-C++
文件中使用艘款。
Objective-C++
是 Objective-C
和 C++
的混合物,讓你可以在 Objective-C
類中使用 C++
對(duì)象沃琅。clang
編譯器會(huì)把所有后綴名為.mm
的文件都當(dāng)做是 Objective-C++
哗咆。一般來(lái)說(shuō),它會(huì)如你所期望的那樣運(yùn)行益眉,但還是有一些使用 Objective-C++
的注意事項(xiàng)晌柬。內(nèi)存管理是你最應(yīng)該格外注意的點(diǎn)姥份,因?yàn)?ARC
只對(duì) Objective-C
對(duì)象有效。當(dāng)你使用一個(gè) C++
對(duì)象作為類屬性的時(shí)候年碘,其唯一有效的屬性就是 assign
殿衰。因此,你的 dealloc
函數(shù)應(yīng)確保 C++
對(duì)象被正確地釋放了盛泡。
第二重要的點(diǎn)就是,如果你在 Objective-C++
頭文件中引入了 C++
頭文件娱颊,當(dāng)你在工程中使用該 Objective-C++
文件的時(shí)候就泄露了 C++
的依賴傲诵。任何引入你的 Objective-C++
類的 Objective-C
類也會(huì)引入該 C++
類,因此該 Objective-C
文件也要被聲明為 Objective-C++
的文件箱硕。這會(huì)像森林大火一樣在工程中迅速蔓延拴竹。所以,應(yīng)該把你引入C++
文件的地方都用 #ifdef __cplusplus
包起來(lái)剧罩,并且只要可能栓拜,就盡量只在.mm
實(shí)現(xiàn)文件中引入 C++
頭文件。
使用 OpenCV 實(shí)現(xiàn)靜態(tài)人臉識(shí)別的Demo演示
a惠昔、運(yùn)行效果
b幕与、導(dǎo)入頭文件和擴(kuò)展里面的屬性
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#endif
#import "ViewController.h"
#import <opencv2/imgcodecs/ios.h>
using namespace cv;
using namespace std;
@interface ViewController ()
{
CascadeClassifier _faceDetector;
vector<cv::Rect> _faceRects;
vector<cv::Mat> _faceImgs;
}
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
b、viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
//預(yù)設(shè)置face探測(cè)的參數(shù)
[self preSetFace];
//image轉(zhuǎn)mat
cv::Mat mat;
UIImageToMat(self.imageView.image, mat);
//執(zhí)行face
[self processImage:mat];
}
c镇防、preSetFace
- (void)preSetFace
{
NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_alt2"
ofType:@"xml"];
const CFIndex CASCADE_NAME_LEN = 2048;
char *CASCADE_NAME = (char *) malloc(CASCADE_NAME_LEN);
CFStringGetFileSystemRepresentation( (CFStringRef)faceCascadePath, CASCADE_NAME, CASCADE_NAME_LEN);
_faceDetector.load(CASCADE_NAME);
free(CASCADE_NAME);
}
d啦鸣、processImage
- (void)processImage:(cv::Mat&)inputImage
{
// Do some OpenCV stuff with the image
cv::Mat frame_gray;
//轉(zhuǎn)換為灰度圖像
cv::cvtColor(inputImage, frame_gray, CV_BGR2GRAY);
//圖像均衡化
cv::equalizeHist(frame_gray, frame_gray);
//分類器識(shí)別
_faceDetector.detectMultiScale(frame_gray, _faceRects,1.1,2,0,cv::Size(30,30));
vector<cv::Rect> faces;
faces = _faceRects;
// 在每個(gè)人臉上畫一個(gè)紅色四方形
for(unsigned int i= 0;i < faces.size();I++)
{
const cv::Rect& face = faces[I];
cv::Point tl(face.x,face.y);
cv::Point br = tl + cv::Point(face.width,face.height);
// 四方形的畫法
cv::Scalar magenta = cv::Scalar(255, 0, 0, 255);
cv::rectangle(inputImage, tl, br, magenta, 3, 8, 0);
}
UIImage *outputImage = MatToUIImage(inputImage);
self.imageView.image = outputImage;
}
使用 OpenCV 遇到的問(wèn)題
無(wú)法將項(xiàng)目上傳到Github上,因?yàn)?code>OpenCL框架太大了来氧,超出了上傳的最大限制100MB诫给,我嘗試了兩種方案來(lái)解決都無(wú)法順利搞定,最后只能上傳了一份不帶該框架的源碼Demo
啦扬。
xiejiapei@xiejiapeis-iMac dafsfs % git push
Enumerating objects: 286, done.
Counting objects: 100% (286/286), done.
Delta compression using up to 6 threads
Compressing objects: 100% (267/267), done.
Writing objects: 100% (283/283), 120.12 MiB | 331.00 KiB/s, done.
Total 283 (delta 59), reused 0 (delta 0)
remote: Resolving deltas: 100% (59/59), completed with 2 local objects.
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
remote: error: Trace: 7470123636eba7f03883923fcf95870e
remote: error: See http://git.io/iEPt8g for more information.
remote: error: File OpenCVTest/Pods/OpenCV/opencv2.framework/Versions/A/opencv2 is 261.37 MB; this exceeds GitHub's file size limit of 100.00 MB
To https://github.com/xiejiapei-creator/dafsfs.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://github.com/xiejiapei-creator/dafsfs.git'
解決方案一:通過(guò).gitignore
不上傳pod
庫(kù)中狂,結(jié)果報(bào)同樣的錯(cuò)。
解決方案二:當(dāng)我擁有像opencv2
這樣的大型框架文件時(shí)扑毡,如何將更改推送到GitHub
胃榕。谷歌上這類資料比較少且零散,在一篇日文博客上看到了類似情況僚楞,總結(jié)解決步驟如下:安裝lfs
(git install lfs
)勤晚,將大文件復(fù)制到倉(cāng)庫(kù)目錄中,讓lfs
跟蹤文件泉褐,然后赐写,提交并推送。實(shí)踐證明膜赃,該方案不可挺邀,真不可。勸你別瞎折騰了,很無(wú)用功很耗時(shí)端铛。
xiejiapei@xiejiapeis-iMac DesignPatternsDemo % cd /Users/xiejiapei/Desktop/Github/Multi-MediaDemo
xiejiapei@xiejiapeis-iMac Multi-MediaDemo % git lfs install
Updated git hooks.
Git LFS initialized.
xiejiapei@xiejiapeis-iMac Multi-MediaDemo % git lfs track "Pods/OpenCV/opencv2.framework/Versions/A/opencv2"
Tracking "Pods/OpenCV/opencv2.framework/Versions/A/opencv2"
xiejiapei@xiejiapeis-iMac Multi-MediaDemo % git add .gitattributes
xiejiapei@xiejiapeis-iMac Multi-MediaDemo % git push
5泣矛、使用 AVFoundation 實(shí)現(xiàn)動(dòng)態(tài)人臉識(shí)別
實(shí)現(xiàn)動(dòng)態(tài)人臉識(shí)別的簡(jiǎn)介
a、運(yùn)行效果
AVFoundation 支持最多同時(shí)識(shí)別10張人臉禾蚕。
b您朽、動(dòng)態(tài)人臉識(shí)別的簡(jiǎn)介
從AVFoundation
框架搭建之初照片和視頻捕捉功能就是它的強(qiáng)項(xiàng)。 通過(guò)一個(gè)特定的AVCaptureOutput
類型的AVCaptureMetadataOutput
可以實(shí)現(xiàn)人臉檢測(cè)功能换淆。支持硬件加速以及同時(shí)對(duì)10個(gè)人臉進(jìn)行實(shí)時(shí)檢測(cè).
當(dāng)使用人臉檢測(cè)時(shí)哗总,會(huì)輸出一個(gè)具體的子類類型AVMetadataFaceObject
,該類型定義了多個(gè)用于描述被檢測(cè)到的人臉的屬性倍试,包括人臉的邊界(設(shè)備坐標(biāo)系)讯屈、斜傾角(roll angle
:表示人頭部向肩膀方向的側(cè)傾角度)、偏轉(zhuǎn)角(yaw angle
:表示人臉繞Y軸旋轉(zhuǎn)的角度)县习。
c涮母、動(dòng)態(tài)人臉識(shí)別的步驟
- 視頻采集
- 為
session
添加一個(gè)元數(shù)據(jù)的輸出AVCaptureMetadataOutput
- 設(shè)置元數(shù)據(jù)的范圍(人臉數(shù)據(jù)、二維碼數(shù)據(jù)....)
- 開始捕捉(設(shè)置捕捉完成代理)
- 在代理方法
didoutputMetadataObjects
中可以獲取到捕捉人臉的相關(guān)信息躁愿,進(jìn)行對(duì)人臉數(shù)據(jù)的處理
d叛本、 AVFoundation實(shí)現(xiàn)視頻捕捉的步驟
捕捉設(shè)備:AVCaptureDevice
為攝像頭、麥克風(fēng)等物理設(shè)備提供接口彤钟。大部分我們使用的設(shè)備都是內(nèi)置于MAC
或者iPhone
炮赦、iPad
上的,當(dāng)然也可能出現(xiàn)外部設(shè)備样勃。AVCaptureDevice
針對(duì)物理設(shè)備提供了大量的控制方法吠勘,比如控制攝像頭聚焦、曝光峡眶、白平衡剧防、閃光燈等。
捕捉設(shè)備的輸入:為捕捉設(shè)備添加輸入辫樱,不能添加到AVCaptureSession
中峭拘,必須通過(guò)將它封裝到一個(gè)AVCaptureDeviceInputs
實(shí)例中,這個(gè)對(duì)象在設(shè)備輸出數(shù)據(jù)和捕捉會(huì)話間扮演接線板的作用狮暑。
捕捉設(shè)備的輸出:AVCaptureOutput
是一個(gè)抽象類鸡挠,用于為捕捉會(huì)話得到的數(shù)據(jù)尋找輸出的目的地“崮校框架定義了一些抽象類的高級(jí)擴(kuò)展類拣展,例如使用 AVCaptureStillImageOutput
和 AVCaptureMovieFileOutput
類來(lái)捕捉靜態(tài)照片、視頻缔逛,使用 AVCaptureAudioDataOutput
和 AVCaptureVideoDataOutput
來(lái)直接訪問(wèn)硬件捕捉到的數(shù)字樣本备埃。
捕捉連接:AVCaptureConnection
類捕捉會(huì)話會(huì)先確定由給定捕捉設(shè)備輸入渲染的媒體類型姓惑,并自動(dòng)建立其到能夠接收該媒體類型的捕捉輸出端的連接。
捕捉預(yù)覽:如果不能在影像捕捉中看到正在捕捉的場(chǎng)景按脚,那么應(yīng)用程序用戶體驗(yàn)就會(huì)很差于毙。幸運(yùn)的是框架定義了AVCaptureVideoPreviewLayer
類來(lái)滿足該需求,這樣就可以對(duì)捕捉的數(shù)據(jù)進(jìn)行實(shí)時(shí)預(yù)覽辅搬。
e唯沮、擴(kuò)展里面的屬性和委托
@interface THCameraController ()<AVCaptureMetadataOutputObjectsDelegate>
@property(nonatomic,strong)AVCaptureMetadataOutput *metadataOutput;
@end
使用 AVFoundation 實(shí)現(xiàn)動(dòng)態(tài)人臉識(shí)別簡(jiǎn)易版的Demo演示
a、導(dǎo)入框架和擴(kuò)展中的屬性
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()<AVCaptureMetadataOutputObjectsDelegate>
@property (nonatomic,strong) AVCaptureSession *session;
// 對(duì)捕捉的數(shù)據(jù)進(jìn)行實(shí)時(shí)預(yù)覽
@property (nonatomic,strong) AVCaptureVideoPreviewLayer *previewLayer;
// 所有臉龐
@property (nonatomic,copy) NSMutableArray *facesViewArray;
@end
b堪遂、viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
_facesViewArray = [NSMutableArray arrayWithCapacity:0];
//1.獲取輸入設(shè)備(攝像頭)
NSArray *devices = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack].devices;
AVCaptureDevice *deviceFace = devices[0];
//2.根據(jù)輸入設(shè)備創(chuàng)建輸入對(duì)象
AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:deviceFace error:nil];
//3.創(chuàng)建原數(shù)據(jù)的輸出對(duì)象
AVCaptureMetadataOutput *metaout = [[AVCaptureMetadataOutput alloc] init];
//4.設(shè)置代理監(jiān)聽(tīng)輸出對(duì)象輸出的數(shù)據(jù)烂翰,在主線程中刷新
[metaout setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//5.設(shè)置輸出質(zhì)量(高像素輸出)
self.session = [[AVCaptureSession alloc] init];
if ([self.session canSetSessionPreset:AVCaptureSessionPreset640x480])
{
[self.session setSessionPreset:AVCaptureSessionPreset640x480];
}
//6.添加輸入和輸出到會(huì)話
[self.session beginConfiguration];
if ([self.session canAddInput:input])
{
[self.session addInput:input];
}
if ([self.session canAddOutput:metaout])
{
[self.session addOutput:metaout];
}
[self.session commitConfiguration];
//7.告訴輸出對(duì)象要輸出什么樣的數(shù)據(jù),識(shí)別人臉蚤氏,最多可識(shí)別10張人臉
[metaout setMetadataObjectTypes:@[AVMetadataObjectTypeFace]];
//8.創(chuàng)建預(yù)覽圖層
AVCaptureSession *session = (AVCaptureSession *)self.session;
_previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:_previewLayer above:0];
//9.設(shè)置有效掃描區(qū)域(默認(rèn)整個(gè)屏幕區(qū)域)(每個(gè)取值0~1, 以屏幕右上角為坐標(biāo)原點(diǎn))
metaout.rectOfInterest = self.view.bounds;
//10.前置攝像頭一定要設(shè)置一下,要不然畫面是鏡像
for (AVCaptureVideoDataOutput *output in session.outputs)
{
for (AVCaptureConnection *connection in output.connections)
{
//判斷是否是前置攝像頭狀態(tài)
if (connection.supportsVideoMirroring)
{
//鏡像設(shè)置
connection.videoOrientation = AVCaptureVideoOrientationPortrait;
}
}
}
//11.開始掃描
[self.session startRunning];
}
c踊兜、AVCaptureMetadataOutputObjectsDelegate
//當(dāng)檢測(cè)到了人臉會(huì)走這個(gè)回調(diào)
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
//移除舊畫框
for (UIView *faceView in self.facesViewArray)
{
[faceView removeFromSuperview];
}
[self.facesViewArray removeAllObjects];
//將臉龐數(shù)據(jù)轉(zhuǎn)換為臉圖像竿滨,并創(chuàng)建臉?lè)娇? for (AVMetadataFaceObject *faceobject in metadataObjects)
{
//轉(zhuǎn)換為臉圖像
AVMetadataObject *face = [self.previewLayer transformedMetadataObjectForMetadataObject:faceobject];
//創(chuàng)建臉?lè)娇? UIView *faceBox = [[UIView alloc] initWithFrame:face.bounds];
faceBox.layer.borderWidth = 3;
faceBox.layer.borderColor = [UIColor redColor].CGColor;
faceBox.backgroundColor = [UIColor clearColor];
[self.view addSubview:faceBox];
[self.facesViewArray addObject:faceBox];
}
}
為捕捉會(huì)話添加輸出設(shè)備
為捕捉會(huì)話添加輸出設(shè)備,報(bào)錯(cuò)則打印錯(cuò)誤信息捏境。
- (BOOL)setupSessionOutputs:(NSError **)error
{
self.metadataOutput = [[AVCaptureMetadataOutput alloc]init];
if ([self.captureSession canAddOutput:self.metadataOutput])
{
[self.captureSession addOutput:self.metadataOutput];
......
return YES;
}
else
{
//報(bào)錯(cuò)
if (error)
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey:@"Failed to still image output"};
*error = [NSError errorWithDomain:THCameraErrorDomain code:THCameraErrorFailedToAddOutput userInfo:userInfo];
}
return NO;
}
}
設(shè)置 metadataObjectTypes
指定對(duì)象輸出的元數(shù)據(jù)類型于游,支持多種元數(shù)據(jù)。限制檢查到的元數(shù)據(jù)類型集合可以屏蔽我們不感興趣的對(duì)象數(shù)量垫言。這里只保留人臉元數(shù)據(jù)贰剥。
NSArray *metadatObjectTypes = @[AVMetadataObjectTypeFace];
self.metadataOutput.metadataObjectTypes = metadatObjectTypes;
創(chuàng)建主隊(duì)列。因?yàn)槿四槞z測(cè)用到了硬件加速筷频,而且許多重要的任務(wù)都在主線程中執(zhí)行蚌成,所以需要為這次參數(shù)指定主隊(duì)列。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
通過(guò)設(shè)置AVCaptureVideoDataOutput
的代理凛捏,就能獲取捕獲到一幀一幀數(shù)據(jù)担忧,檢測(cè)每一幀中是否包含人臉數(shù)據(jù),如果包含則調(diào)用回調(diào)方法坯癣。
[self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
捕捉到人臉數(shù)據(jù)調(diào)用代理方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
.......
}
MetadataObject
包含了捕獲到的人臉數(shù)據(jù)瓶盛。人臉數(shù)據(jù)可能存在重復(fù),因?yàn)槿四樔绻麤](méi)有移動(dòng)的話示罗,下一秒還是會(huì)捕獲到上一秒的信息惩猫。使用循環(huán),打印人臉數(shù)據(jù)蚜点。
for (AVMetadataFaceObject *face in metadataObjects)
{
NSLog(@"Face detected with ID:%li",(long)face.faceID);
NSLog(@"Face bounds:%@",NSStringFromCGRect(face.bounds));
}
已經(jīng)獲取到視頻中人臉的位置和個(gè)數(shù)轧房,現(xiàn)在進(jìn)行對(duì)人臉數(shù)據(jù)的處理。在預(yù)覽圖層 THPreviewView
上進(jìn)行人臉的處理绍绘,所以需要通過(guò)代理將捕捉到的人臉元數(shù)據(jù)傳遞給 THPreviewView
锯厢。
@protocol THFaceDetectionDelegate <NSObject>
- (void)didDetectFaces:(NSArray *)faces;
@end
@property (weak, nonatomic) id <THFaceDetectionDelegate> faceDetectionDelegate;
[self.faceDetectionDelegate didDetectFaces:metadataObjects];
@interface THPreviewView : UIView <THFaceDetectionDelegate>
創(chuàng)建人臉圖層
- (void)setupView
{
.....
}
初始化faceLayers
屬性皮官,其為一個(gè)字典,用來(lái)存儲(chǔ)每張人臉的圖層实辑,faceID
和人臉圖層一一對(duì)應(yīng)捺氢。
self.faceLayers = [NSMutableDictionary dictionary];
設(shè)置 videoGravity
。使用 AVLayerVideoGravityResizeAspectFill
鋪滿整個(gè)預(yù)覽層的邊界范圍剪撬。
self.overlayLayer = [CALayer layer];
一般在 previewLayer
上添加一個(gè)透明的圖層摄乒。設(shè)置它的frame
和previewLayer
的bounds
相同。
self.overlayLayer = [CALayer layer];
self.overlayLayer.frame = self.bounds;
假設(shè)你的圖層上的圖像會(huì)發(fā)生3D變換残黑,則需要設(shè)置其投影方式馍佑。
self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
將子圖層添加到預(yù)覽圖層來(lái)。
[self.previewLayer addSublayer:self.overlayLayer];
將檢測(cè)到的人臉進(jìn)行可視化
- (void)didDetectFaces:(NSArray *)faces
{
.......
}
獲取到的人臉數(shù)據(jù)位置信息是攝像頭坐標(biāo)系的梨水,需要將其轉(zhuǎn)化為屏幕坐標(biāo)系拭荤。創(chuàng)建一個(gè)本地?cái)?shù)組,保存轉(zhuǎn)換后的人臉數(shù)據(jù)疫诽。
NSArray *transformedFaces = [self transformedFacesFromFaces:faces];
如果這個(gè)人臉從攝像頭中消失掉了舅世,則需要根據(jù)faceID
刪除掉它的圖層。因?yàn)橹С滞瑫r(shí)識(shí)別多張人臉奇徒,所以可以獲取faceLayers
的allKey
雏亚,假定剛開始所有的人臉都需要?jiǎng)h除,然后再一一從刪除列表中進(jìn)行移除摩钙。
NSMutableArray *lostFaces = [self.faceLayers.allKeys mutableCopy];
遍歷每個(gè)轉(zhuǎn)換的人臉對(duì)象罢低,獲取其關(guān)聯(lián)的faceID
。這個(gè)屬性唯一標(biāo)識(shí)一個(gè)檢測(cè)到的人臉胖笛。
for (AVMetadataFaceObject *face in transformedFaces)
{
NSNumber *faceID = @(face.faceID);
.....
}
如果faceID
存在网持,則表示人臉沒(méi)有從攝像頭中移除,不需要進(jìn)行刪除长踊,將其從待移除人臉列表中移出翎碑。
[lostFaces removeObject:faceID];
如果是之前舊的人臉,則從人臉圖層字典中獲取到舊的人臉圖層之斯。拿到當(dāng)前faceID
對(duì)應(yīng)的layer
日杈。
CALayer *layer = self.faceLayers[faceID];
如果給定的faceID
沒(méi)有找到對(duì)應(yīng)的圖層,說(shuō)明是新的人臉佑刷,需要?jiǎng)?chuàng)建人臉圖層莉擒,再將新的人臉圖層添加到 overlayLayer
上,最后將layer
加入到字典中瘫絮,更新字典涨冀。
if (!layer)
{
//調(diào)用makeFaceLayer 創(chuàng)建一個(gè)新的人臉圖層
layer = [self makeFaceLayer];
//將新的人臉圖層添加到 overlayLayer上
[self.overlayLayer addSublayer:layer];
//將layer加入到字典中
self.faceLayers[faceID] = layer;
}
圖層的大小 = 人臉的大小,所以根據(jù)人臉的bounds
設(shè)置layer
的frame
麦萤。
layer.frame = face.bounds;
人臉識(shí)別是3D動(dòng)態(tài)識(shí)別鹿鳖,屬于立體圖像扁眯,涉及到歐拉角。歐拉角的Yaw
表示繞Y軸旋轉(zhuǎn)的角度(人臉左右旋轉(zhuǎn))翅帜,Pitch
表示繞X軸旋轉(zhuǎn)角度(人臉左右搖擺)姻檀,Roll
表示繞Z軸旋轉(zhuǎn)的角度(人臉上下?lián)u擺)。設(shè)置圖層的transform
屬性為 CATransform3DIdentity
涝滴,使用Core Animation
對(duì)圖層進(jìn)行變化绣版。
layer.transform = CATransform3DIdentity;
判斷人臉對(duì)象是否具有有效的斜傾交(人的頭部向肩部方向傾斜)。如果為YES歼疮,則獲取相應(yīng)的CATransform3D
值杂抽,將它與標(biāo)識(shí)變化關(guān)聯(lián)在一起,并設(shè)置transform
屬性韩脏。實(shí)現(xiàn)當(dāng)人臉傾斜的時(shí)候缩麸,捕獲人臉的紅框也發(fā)生同樣的傾斜。
if (face.hasRollAngle)
{
//如果為YES赡矢,則獲取相應(yīng)的CATransform3D 值
CATransform3D t = [self transformForRollAngle:face.rollAngle];
//將它與標(biāo)識(shí)變化關(guān)聯(lián)在一起杭朱,并設(shè)置transform屬性
layer.transform = CATransform3DConcat(layer.transform, t);
}
判斷人臉對(duì)象是否具有有效的偏轉(zhuǎn)角。如果為YES济竹,則獲取相應(yīng)的 CATransform3D
值,再將它與標(biāo)識(shí)變化關(guān)聯(lián)在一起霎槐,并設(shè)置transform
屬性送浊。
if (face.hasYawAngle)
{
//如果為YES,則獲取相應(yīng)的CATransform3D 值
CATransform3D t = [self transformForYawAngle:face.yawAngle];
//將它與標(biāo)識(shí)變化關(guān)聯(lián)在一起,并設(shè)置transform屬性
layer.transform = CATransform3DConcat(layer.transform, t);
}
處理那些已經(jīng)從鏡頭中消失的人臉圖層丘跌。遍歷數(shù)組袭景,將剩下的人臉I(yè)D集合從上一個(gè)圖層和faceLayers
字典中移除。
for (NSNumber *faceID in lostFaces)
{
CALayer *layer = self.faceLayers[faceID];
[layer removeFromSuperlayer];
[self.faceLayers removeObjectForKey:faceID];
}
輔助方法
? 攝像頭坐標(biāo)系轉(zhuǎn)化為屏幕坐標(biāo)系
將攝像頭的人臉數(shù)據(jù)轉(zhuǎn)換為視圖上的可展示的數(shù)據(jù)闭树。因?yàn)?code>UIKit的坐標(biāo)與攝像頭坐標(biāo)系統(tǒng)(0耸棒,0)-(1,1)不一樣报辱,所以需要轉(zhuǎn)換与殃,轉(zhuǎn)換成功后证逻,加入到數(shù)組中俱诸。
- (NSArray *)transformedFacesFromFaces:(NSArray *)faces
{
NSMutableArray *transformeFaces = [NSMutableArray array];
for (AVMetadataObject *face in faces)
{
AVMetadataObject *transformedFace = [self.previewLayer transformedMetadataObjectForMetadataObject:face];
[transformeFaces addObject:transformedFace];
}
return transformeFaces;
}
? 調(diào)用makeFaceLayer 創(chuàng)建一個(gè)新的人臉圖層
- (CALayer *)makeFaceLayer
{
//創(chuàng)建一個(gè)layer
CALayer *layer = [CALayer layer];
//邊框?qū)挾葹?.0f
layer.borderWidth = 5.0f;
//邊框顏色為紅色
layer.borderColor = [UIColor redColor].CGColor;
// 為view添加背景圖片
layer.contents = (id)[UIImage imageNamed:@"551.png"].CGImage;
//返回layer
return layer;
}
? 將投影方式修改為透視投影
self.overlayLayer.sublayerTransform = CATransform3DMakePerspective(1000);
static CATransform3D CATransform3DMakePerspective(CGFloat eyePosition)
{
//CATransform3D 圖層的旋轉(zhuǎn)槽袄,縮放缀皱,偏移辆影,歪斜
//CATransform3DIdentity是單位矩陣洞斯,該矩陣沒(méi)有縮放周蹭,旋轉(zhuǎn)绰筛,歪斜慢睡,透視逐工。該矩陣應(yīng)用到圖層上铡溪,就是設(shè)置默認(rèn)值
CATransform3D transform = CATransform3DIdentity;
//透視效果(就是近大遠(yuǎn)小)泪喊,是通過(guò)設(shè)置m34 m34 = -1.0/D 默認(rèn)是0.D越小透視效果越明顯
//D:eyePosition 觀察者到投射面的距離
transform.m34 = -1.0/eyePosition;
return transform;
}
? 為設(shè)備方向計(jì)算一個(gè)相應(yīng)的旋轉(zhuǎn)變換
- (CATransform3D)orientationTransform
{
CGFloat angle = 0.0;
//拿到設(shè)備方向
switch ([UIDevice currentDevice].orientation)
{
//方向:下
case UIDeviceOrientationPortraitUpsideDown:
angle = M_PI;
break;
//方向:右
case UIDeviceOrientationLandscapeRight:
angle = -M_PI / 2.0f;
break;
//方向:左
case UIDeviceOrientationLandscapeLeft:
angle = M_PI /2.0f;
break;
//其他
default:
angle = 0.0f;
break;
}
return CATransform3DMakeRotation(angle, 0.0f, 0.0f, 1.0f);
}
? 將 RollAngle 的 rollAngleInDegrees 值轉(zhuǎn)換為 CATransform3D
static CGFloat THDegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
}
- (CATransform3D)transformForRollAngle:(CGFloat)rollAngleInDegrees
{
//將人臉對(duì)象得到的RollAngle 單位“度” 轉(zhuǎn)為Core Animation需要的弧度值
CGFloat rollAngleInRadians = THDegreesToRadians(rollAngleInDegrees);
//rollAngle圍繞Z軸旋轉(zhuǎn)
//將結(jié)果賦給CATransform3DMakeRotation x,y,z軸為0棕硫,0,1 得到繞Z軸傾斜角旋轉(zhuǎn)轉(zhuǎn)換
return CATransform3DMakeRotation(rollAngleInRadians, 0.0f, 0.0f, 1.0f);
}
? 將 YawAngle 的 yawAngleInDegrees 值轉(zhuǎn)換為 CATransform3D
將 YawAngle
的 yawAngleInDegrees
值轉(zhuǎn)換為 CATransform3D
窘俺。將角度轉(zhuǎn)換為弧度值饲帅,再將弧度值結(jié)果進(jìn)行CATransform3DMakeRotation
。x,y,z
軸為0瘤泪,-1灶泵,0 得到繞Y軸旋轉(zhuǎn)結(jié)果。因?yàn)閼?yīng)用程序的界面固定為垂直方向对途,所以需要為設(shè)備方向計(jì)算一個(gè)相應(yīng)的旋轉(zhuǎn)變換赦邻,最后將設(shè)備的旋轉(zhuǎn)變換和人臉的旋轉(zhuǎn)變化進(jìn)行矩陣相乘得到最后結(jié)果,如果不這樣实檀,會(huì)造成人臉圖層的偏轉(zhuǎn)效果不正確惶洲。
- (CATransform3D)transformForYawAngle:(CGFloat)yawAngleInDegrees
{
CGFloat yawAngleInRaians = THDegreesToRadians(yawAngleInDegrees);
CATransform3D yawTransform = CATransform3DMakeRotation(yawAngleInRaians, 0.0f, -1.0f, 0.0f);
return CATransform3DConcat(yawTransform, [self orientationTransform]);
}
Demo
Demo在我的Github上,歡迎下載膳犹。
Multi-MediaDemo