版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2021.03.10 星期三 |
前言
iOS 11+
和macOS 10.13+
新出了Vision
框架绽昼,提供了人臉識(shí)別念链、物體檢測蒂培、物體跟蹤等技術(shù)沙合,它是基于Core ML的奠伪。可以說是人工智能的一部分首懈,接下來幾篇我們就詳細(xì)的解析一下Vision框架绊率。感興趣的看下面幾篇文章。
1. Vision框架詳細(xì)解析(一) —— 基本概覽(一)
2. Vision框架詳細(xì)解析(二) —— 基于Vision的人臉識(shí)別(一)
3. Vision框架詳細(xì)解析(三) —— 基于Vision的人臉識(shí)別(二)
4. Vision框架詳細(xì)解析(四) —— 在iOS中使用Vision和Metal進(jìn)行照片堆疊(一)
5. Vision框架詳細(xì)解析(五) —— 在iOS中使用Vision和Metal進(jìn)行照片堆疊(二)
6. Vision框架詳細(xì)解析(六) —— 基于Vision的顯著性分析(一)
7. Vision框架詳細(xì)解析(七) —— 基于Vision的顯著性分析(二)
8. Vision框架詳細(xì)解析(八) —— 基于Vision的QR掃描(一)
9. Vision框架詳細(xì)解析(九) —— 基于Vision的QR掃描(二)
開始
首先看下主要內(nèi)容:
在
Vision
框架的幫助下究履,了解如何檢測顯示在相機(jī)上的手指的數(shù)量即舌。內(nèi)容來自翻譯。
接著看下寫作環(huán)境:
Swift 5, iOS 14, Xcode 12
下面就是正文啦挎袜。
機(jī)器學(xué)習(xí)(Machine learning)
無處不在顽聂,因此當(dāng)Apple
在2017年宣布其Core ML
框架時(shí),這并不奇怪盯仪。CoreML
附帶了許多工具紊搪,包括Vision
(圖像分析框架)。視覺分析靜止圖像以檢測面部全景,讀取條形碼耀石,跟蹤物體等。多年來爸黄,Apple在此框架中添加了許多很酷的功能滞伟,包括2020
年引入的Hand and Body Detection API
。在本教程中炕贵,您將使用Vision
框架中的這些Hand and Body Detection API
為您帶來魔力一個(gè)名為StarCount
的游戲梆奈。您將用手和手指計(jì)算從天上掉下來的星星的數(shù)量。
注意:此
Vision
教程假定您具有SwiftUI
称开,UIKit
和Combine
的工作知識(shí)亩钟。有關(guān)SwiftUI
的更多信息乓梨,請(qǐng)參見SwiftUI: Getting Started。
StarCount
需要具有前置攝像頭的設(shè)備才能運(yùn)行清酥,因此您不能隨身攜帶模擬器扶镀。最后,如果您可以將設(shè)備支撐在某個(gè)地方焰轻,那將很有幫助臭觉,您將需要雙手來匹配這些高數(shù)字!
在Xcode中打開starter
項(xiàng)目辱志。
構(gòu)建并運(yùn)行胧谈。 點(diǎn)擊左上角的Rain
,欣賞場景荸频。 不要忘了對(duì)那些星星的祝福!
星星下雨的魔力在StarAnimatorView.swift
中客冈。它使用UIKit Dynamics API
旭从。如果您有興趣,請(qǐng)隨時(shí)查看场仲。
該應(yīng)用程序看起來不錯(cuò)和悦,但可以想象一下,如果在后臺(tái)顯示您的實(shí)時(shí)視頻渠缕,效果會(huì)更好鸽素!如果手機(jī)看不到手指,Vision
無法計(jì)數(shù)手指亦鳞。
Getting Ready for Detection
Vision
使用靜止圖像進(jìn)行檢測馍忽。信不信由你,您在相機(jī)取景器中看到的實(shí)際上是一堆靜止圖像燕差。在檢測到任何東西之前遭笋,您需要將攝影機(jī)會(huì)話集成到游戲中。
1. Creating the Camera Session
要在應(yīng)用程序中顯示攝像機(jī)預(yù)覽徒探,請(qǐng)使用CALayer
的子類AVCaptureVideoPreviewLayer
瓦呼。您可以將此預(yù)覽層與capture session
結(jié)合使用。
由于CALayer
是UIKit
的一部分测暗,因此您需要?jiǎng)?chuàng)建一個(gè)包裝器才能在SwiftUI
中使用它央串。幸運(yùn)的是,Apple
提供了一種使用UIViewRepresentable
和UIViewControllerRepresentable
的簡便方法碗啄。
實(shí)際上质和,StarAnimator
是一個(gè)UIViewRepresentable
,因此您可以在SwiftUI
中使用StarAnimatorView
(UIView
的子類)稚字。
注意:您可以在以下精彩的視頻課程中了解有關(guān)將
UIKit
與SwiftUI
集成的更多信息:Integrating UIKit & SwiftUI侦另。
您將在以下部分中創(chuàng)建三個(gè)文件:CameraPreview.swift
,CameraViewController.swift
和CameraView.swift
。 從CameraPreview.swift
開始褒傅。
CameraPreview
在StarCount
組中創(chuàng)建一個(gè)名為CameraPreview.swift
的新文件弃锐,然后添加:
// 1
import UIKit
import AVFoundation
final class CameraPreview: UIView {
// 2
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
// 3
var previewLayer: AVCaptureVideoPreviewLayer {
layer as! AVCaptureVideoPreviewLayer
}
}
在這里,您:
- 1) 由于
CameraPreview
是UIView
的子類殿托,因此請(qǐng)導(dǎo)入UIKit
霹菊。 您還可以導(dǎo)入AVFoundation
,因?yàn)?code>AVCaptureVideoPreviewLayer是此模塊的一部分支竹。 - 2) 接下來旋廷,您覆蓋靜態(tài)
layerClass
。 這使得該視圖的根層類型為AVCaptureVideoPreviewLayer
礼搁。 - 3) 然后饶碘,創(chuàng)建一個(gè)稱為
PreviewLayer
的計(jì)算屬性,并將此視圖的根層強(qiáng)制轉(zhuǎn)換為您在第二步中定義的類型馒吴。 現(xiàn)在扎运,當(dāng)您以后需要使用它時(shí),可以使用此屬性直接訪問該層饮戳。
接下來豪治,您將創(chuàng)建一個(gè)視圖控制器來管理CameraPreview
。
CameraViewController
AVFoundation
的相機(jī)捕獲代碼旨在與UIKit
配合使用扯罐,因此要使其在您的SwiftUI應(yīng)用中正常工作负拟,您需要制作一個(gè)視圖控制器并將其包裝在UIViewControllerRepresentable
中。
在StarCount
組中創(chuàng)建CameraViewController.swift
并添加:
import UIKit
final class CameraViewController: UIViewController {
// 1
override func loadView() {
view = CameraPreview()
}
// 2
private var cameraView: CameraPreview { view as! CameraPreview }
}
在這里你:
- 1) 重寫
loadView
以使視圖控制器將CameraPreview
用作其根視圖歹河。 - 2) 創(chuàng)建一個(gè)名為
cameraPreview
的計(jì)算屬性掩浙,以CameraPreview
的身份訪問根視圖。 您可以安全地在此處強(qiáng)制賦值秸歧,因?yàn)槟罱诘谝徊街蟹峙淞?code>CameraPreview實(shí)例給view
涣脚。
現(xiàn)在,您將制作一個(gè)SwiftUI
視圖以包裝新的視圖控制器寥茫,以便可以在StarCount
中使用它遣蚀。
CameraView
在StarCount
組中創(chuàng)建CameraView.swift
并添加:
import SwiftUI
// 1
struct CameraView: UIViewControllerRepresentable {
// 2
func makeUIViewController(context: Context) -> CameraViewController {
let cvc = CameraViewController()
return cvc
}
// 3
func updateUIViewController(
_ uiViewController: CameraViewController,
context: Context
) {
}
}
這就是上面的代碼中發(fā)生的事情:
- 1) 您創(chuàng)建一個(gè)名為
CameraView
的結(jié)構(gòu)體,該結(jié)構(gòu)體符合UIViewControllerRepresentable
纱耻。 這是用于制作包裝UIKit
視圖控制器的SwiftUI View
類型的協(xié)議芭梯。 - 2) 您實(shí)現(xiàn)第一個(gè)協(xié)議方法,
makeUIViewController
弄喘。 在這里玖喘,您將初始化CameraViewController
的實(shí)例,并執(zhí)行一次僅一次的設(shè)置蘑志。 - 3)
updateUIViewController(_:context :)
是該協(xié)議的另一個(gè)必需方法累奈,您可以在其中基于SwiftUI
數(shù)據(jù)或?qū)哟谓Y(jié)構(gòu)的更改對(duì)視圖控制器進(jìn)行任何更新贬派。 對(duì)于此應(yīng)用,您無需在此處做任何事情澎媒。
完成所有這些工作之后搞乏,該在ContentView
中使用CameraView
了。
打開ContentView.swift
戒努。 在body
中ZStack
的開頭插入CameraView
:
CameraView()
.edgesIgnoringSafeArea(.all)
那是一個(gè)很長的部分请敦。 構(gòu)建并運(yùn)行以查看您的相機(jī)預(yù)覽。
所有的工作都沒有改變储玫! 為什么侍筛? 在相機(jī)預(yù)覽工作之前,還需要添加另一個(gè)難題撒穷,即AVCaptureSession
匣椰。 接下來,您將添加該內(nèi)容端礼。
2. Connecting to the Camera Session
您將在此處進(jìn)行的更改似乎很長禽笑,但是請(qǐng)不要害怕。 它們大多是樣板代碼齐媒。
打開CameraViewController.swift
。 在import UIKit
之后添加以下內(nèi)容:
import AVFoundation
然后纷跛,在類內(nèi)添加AVCaptureSession
類型的實(shí)例屬性:
private var cameraFeedSession: AVCaptureSession?
最好在此視圖控制器出現(xiàn)在屏幕上時(shí)運(yùn)行capture session
喻括,并在視圖不再可見時(shí)停止session
,因此添加以下內(nèi)容:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
do {
// 1
if cameraFeedSession == nil {
// 2
try setupAVSession()
// 3
cameraView.previewLayer.session = cameraFeedSession
cameraView.previewLayer.videoGravity = .resizeAspectFill
}
// 4
cameraFeedSession?.startRunning()
} catch {
print(error.localizedDescription)
}
}
// 5
override func viewWillDisappear(_ animated: Bool) {
cameraFeedSession?.stopRunning()
super.viewWillDisappear(animated)
}
func setupAVSession() throws {
}
以下是代碼細(xì)分:
- 1) 在
viewDidAppear(_ :)
中贫奠,檢查是否已經(jīng)初始化了cameraFeedSession
唬血。 - 2) 您調(diào)用
setupAVSession()
,該函數(shù)目前為空唤崭,但很快就會(huì)實(shí)現(xiàn)拷恨。 - 3) 然后,將會(huì)話設(shè)置為
cameraView
的PreviewLayer
的會(huì)話谢肾,并設(shè)置視頻的調(diào)整大小模式腕侄。 - 4) 接下來,您開始運(yùn)行會(huì)話芦疏。 這使
camera feed
可見冕杠。 - 5) 在
viewWillDisappear(_ :)
中,關(guān)閉camera feed
以延長電池壽命酸茴。
現(xiàn)在分预,您將添加缺少的代碼以準(zhǔn)備相機(jī)。
Preparing the Camera
為調(diào)度隊(duì)列添加一個(gè)新屬性薪捍,Vision
將在該屬性上處理攝像機(jī)采樣:
private let videoDataOutputQueue = DispatchQueue(
label: "CameraFeedOutput",
qos: .userInteractive
)
添加擴(kuò)展以使視圖控制器符合AVCaptureVideoDataOutputSampleBufferDelegate
:
extension
CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
}
有了這兩件事之后笼痹,您現(xiàn)在可以替換空的setupAVSession()
了:
func setupAVSession() throws {
// 1
guard let videoDevice = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: .front)
else {
throw AppError.captureSessionSetup(
reason: "Could not find a front facing camera."
)
}
// 2
guard
let deviceInput = try? AVCaptureDeviceInput(device: videoDevice)
else {
throw AppError.captureSessionSetup(
reason: "Could not create video device input."
)
}
// 3
let session = AVCaptureSession()
session.beginConfiguration()
session.sessionPreset = AVCaptureSession.Preset.high
// 4
guard session.canAddInput(deviceInput) else {
throw AppError.captureSessionSetup(
reason: "Could not add video device input to the session"
)
}
session.addInput(deviceInput)
// 5
let dataOutput = AVCaptureVideoDataOutput()
if session.canAddOutput(dataOutput) {
session.addOutput(dataOutput)
dataOutput.alwaysDiscardsLateVideoFrames = true
dataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
} else {
throw AppError.captureSessionSetup(
reason: "Could not add video data output to the session"
)
}
// 6
session.commitConfiguration()
cameraFeedSession = session
}
在您上面的代碼中:
- 1) 檢查設(shè)備是否帶有前置攝像頭配喳。 如果不是,則拋出錯(cuò)誤凳干。
- 2) 接下來晴裹,檢查是否可以使用相機(jī)創(chuàng)建捕捉設(shè)備輸入
(capture device input)
。 - 3) 創(chuàng)建一個(gè)
capture session
并開始使用高質(zhì)量預(yù)設(shè)(high quality preset)
進(jìn)行配置纺座。 - 4) 然后檢查會(huì)話是否可以
capture device
輸入息拜。 如果是,請(qǐng)將您在第二步中創(chuàng)建的輸入添加到會(huì)話中净响。 您需要輸入和輸出才能使會(huì)話正常工作少欺。 - 5) 接下來,創(chuàng)建數(shù)據(jù)輸出并將其添加到會(huì)話中馋贤。 數(shù)據(jù)輸出將從相機(jī)源中獲取圖像樣本赞别,并將它們提供給您在先前設(shè)置的已定義調(diào)度隊(duì)列中的委托中。
- 6) 最后配乓,完成配置會(huì)話并將其分配給您之前創(chuàng)建的屬性仿滔。
構(gòu)建并運(yùn)行。 現(xiàn)在犹芹,您可以看到自己在雨星的背后崎页。
注意:您需要用戶權(quán)限才能訪問設(shè)備上的相機(jī)。 首次啟動(dòng)攝像頭會(huì)話時(shí)腰埂,iOS會(huì)提示用戶授予對(duì)攝像頭的訪問權(quán)限飒焦。 您必須向用戶說明您希望獲得攝像頭許可的原因。
Info.plist
中的鍵值對(duì)存儲(chǔ)原因屿笼。 在入門項(xiàng)目中已經(jīng)存在牺荠。
有了這一點(diǎn)之后,就該轉(zhuǎn)移到Vision
了驴一。
Detecting Hands
要在Vision
中使用任何算法休雌,通常需要遵循以下三個(gè)步驟:
- 1) Request:您通過定義請(qǐng)求特征來請(qǐng)求框架為您檢測到某些東西。 您使用
VNRequest
的適當(dāng)子類肝断。 - 2) Handler:接下來杈曲,您要求框架在請(qǐng)求完成執(zhí)行或處理請(qǐng)求之后執(zhí)行一種方法。
- 3) Observation:最后胸懈,您可以獲得潛在的結(jié)果或觀察結(jié)果鱼蝉。 這些觀察是基于您的請(qǐng)求的
VNObservation
的實(shí)例。
您將首先處理該請(qǐng)求箫荡。
1. Request
用于檢測手的請(qǐng)求的類型為VNDetectHumanHandPoseRequest
魁亦。
仍在CameraViewController.swift
中,在import AVFoundation
之后添加以下內(nèi)容以訪問Vision
框架:
import Vision
然后羔挡,在類定義內(nèi)洁奈,創(chuàng)建以下實(shí)例屬性:
private let handPoseRequest: VNDetectHumanHandPoseRequest = {
// 1
let request = VNDetectHumanHandPoseRequest()
// 2
request.maximumHandCount = 2
return request
}()
在這里你:
- 1) 創(chuàng)建一個(gè)檢測人手的請(qǐng)求间唉。
- 2) 將要檢測的最大手?jǐn)?shù)設(shè)置為兩個(gè)。
Vision
框架功能強(qiáng)大利术。 它可以檢測到圖像中的許多手呈野。 由于最多十顆星落在任何一滴中,因此兩只手和十根手指就足夠了印叁。
現(xiàn)在被冒,是時(shí)候設(shè)置處理handler
和 observation
了。
2. Handler and Observation
您可以使用AVCaptureVideoDataOutputSampleBufferDelegate
從采集流中獲取樣本并開始檢測過程轮蜕。
在您之前創(chuàng)建的CameraViewController
擴(kuò)展中實(shí)現(xiàn)此方法:
func captureOutput(
_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection
) {
// 1
let handler = VNImageRequestHandler(
cmSampleBuffer: sampleBuffer,
orientation: .up,
options: [:]
)
do {
// 2
try handler.perform([handPoseRequest])
// 3
guard
let results = handPoseRequest.results?.prefix(2),
!results.isEmpty
else {
return
}
print(results)
} catch {
// 4
cameraFeedSession?.stopRunning()
}
}
以下是代碼細(xì)分:
1) 只要有采樣昨悼,就會(huì)調(diào)用
captureOutput(_:didOutput:from :)
。在此方法中跃洛,您將創(chuàng)建一個(gè)handler
率触,這是使用Vision
所需的第二步。您將獲得的樣本緩沖區(qū)(sample buffer)
作為輸入?yún)?shù)傳遞汇竭,以對(duì)單個(gè)圖像執(zhí)行請(qǐng)求葱蝗。2) 然后,您執(zhí)行請(qǐng)求细燎。如果有任何錯(cuò)誤两曼,此方法將引發(fā)錯(cuò)誤,因此它位于
do-catch
塊中玻驻。執(zhí)行請(qǐng)求是同步操作悼凑。還記得您提供給代理回調(diào)的調(diào)度隊(duì)列嗎?這樣可以確保您不會(huì)阻塞主隊(duì)列击狮。Vision
完成了該后臺(tái)隊(duì)列上的檢測過程佛析。3) 您可以使用請(qǐng)求的
results
獲得檢測結(jié)果或觀察結(jié)果益老。在這里彪蓬,您可以獲得前兩項(xiàng),并確保結(jié)果數(shù)組不為空捺萌。當(dāng)您在創(chuàng)建請(qǐng)求時(shí)只要求兩只手時(shí)档冬,這是一種額外的預(yù)防措施,可確保您得到的結(jié)果項(xiàng)不超過兩個(gè)桃纯。接下來酷誓,將結(jié)果打印到控制臺(tái)。4) 如果請(qǐng)求失敗态坦,則意味著發(fā)生了一些不好的事情盐数。在生產(chǎn)環(huán)境中,您可以更好地處理此錯(cuò)誤伞梯。目前玫氢,您可以停止攝像頭會(huì)話帚屉。
構(gòu)建并運(yùn)行。將您的手放在相機(jī)前面漾峡,然后查看Xcode控制臺(tái)攻旦。
在控制臺(tái)中,您將看到可見的VNHumanHandPoseObservation
類型的觀察對(duì)象生逸。 接下來牢屋,您將從這些觀察結(jié)果中提取手指數(shù)據(jù)。 但是首先槽袄,您需要閱讀一下解剖學(xué)烙无!
3. Anatomy to the Rescue!
Vision
框架會(huì)詳細(xì)檢測手。 查看以下插圖:
此圖像上的每個(gè)圓圈都是一個(gè)Landmark
掰伸。Vision
可以檢測到每只手的21
個(gè)landmarks
:每個(gè)手指四個(gè)皱炉,拇指四個(gè)和手腕一個(gè)。
這些手指中的每個(gè)手指都在一個(gè)Joints Group
中狮鸭,由VNHumanHandPoseObservation.JointsGroupName
中的API將其描述為:
.thumb
.indexFinger
.middleFinger
.ringFinger
.littleFinger
在每個(gè)關(guān)節(jié)組中合搅,每個(gè)關(guān)節(jié)都有一個(gè)名稱:
- TIP:指尖。
- DIP:指間遠(yuǎn)端關(guān)節(jié)或指尖后的第一個(gè)關(guān)節(jié)歧蕉。
- PIP:指間近關(guān)節(jié)或中間關(guān)節(jié)灾部。
- MIP:掌指關(guān)節(jié)位于手指底部,與手掌相連惯退。
拇指有點(diǎn)不同赌髓。 它有一個(gè)TIP
,但其他關(guān)節(jié)具有不同的名稱:
- TIP:拇指尖催跪。
- IP:指間關(guān)節(jié)至拇指尖后的第一個(gè)關(guān)節(jié)锁蠕。
- MP:掌指關(guān)節(jié)位于拇指底部,與手掌相連懊蒸。
- CMC:腕掌關(guān)節(jié)在手腕附近荣倾。
許多開發(fā)人員認(rèn)為自己的職業(yè)不需要數(shù)學(xué)。 誰會(huì)想到解剖學(xué)也是前提骑丸?
了解了解剖結(jié)構(gòu)舌仍,是時(shí)候檢測指尖了。
4. Detecting Fingertips
為簡單起見通危,您將檢測到指尖并在頂部繪制一個(gè)覆蓋圖铸豁。
在CameraViewController.swift
中,將以下內(nèi)容添加到captureOutput(_:didOutput:from :)
的頂部:
var fingerTips: [CGPoint] = []
這將存儲(chǔ)檢測到的指尖菊碟。 現(xiàn)在节芥,將您在上一步中添加的print(results)
替換為:
var recognizedPoints: [VNRecognizedPoint] = []
try results.forEach { observation in
// 1
let fingers = try observation.recognizedPoints(.all)
// 2
if let thumbTipPoint = fingers[.thumbTip] {
recognizedPoints.append(thumbTipPoint)
}
if let indexTipPoint = fingers[.indexTip] {
recognizedPoints.append(indexTipPoint)
}
if let middleTipPoint = fingers[.middleTip] {
recognizedPoints.append(middleTipPoint)
}
if let ringTipPoint = fingers[.ringTip] {
recognizedPoints.append(ringTipPoint)
}
if let littleTipPoint = fingers[.littleTip] {
recognizedPoints.append(littleTipPoint)
}
}
// 3
fingerTips = recognizedPoints.filter {
// Ignore low confidence points.
$0.confidence > 0.9
}
.map {
// 4
CGPoint(x: $0.location.x, y: 1 - $0.location.y)
}
在這里你:
- 1) 獲取所有手指的分?jǐn)?shù)。
- 2) 尋找尖點(diǎn)逆害。
- 3) 每個(gè)
VNRecognizedPoint
都有一個(gè)置信度confidence
头镊。 您只需要具有高置信度的觀察值增炭。 - 4)
Vision
算法使用左下原點(diǎn)的坐標(biāo)系,并返回相對(duì)于輸入圖像像素尺寸的歸一化值拧晕。AVFoundation
坐標(biāo)具有左上角的原點(diǎn)隙姿,因此您可以轉(zhuǎn)換y
坐標(biāo)。
您需要使用這些指尖進(jìn)行操作厂捞,因此將以下內(nèi)容添加到CameraViewController
中:
// 1
var pointsProcessorHandler: (([CGPoint]) -> Void)?
func processPoints(_ fingerTips: [CGPoint]) {
// 2
let convertedPoints = fingerTips.map {
cameraView.previewLayer.layerPointConverted(fromCaptureDevicePoint: $0)
}
// 3
pointsProcessorHandler?(convertedPoints)
}
在這里你:
- 1) 為閉包添加一個(gè)屬性输玷,以在框架檢測到點(diǎn)時(shí)運(yùn)行。
- 2) 從
AVFoundation
相對(duì)坐標(biāo)轉(zhuǎn)換為UIKit
坐標(biāo)靡馁,以便可以在屏幕上繪制它們欲鹏。 您使用layerPointConverted
,這是AVCaptureVideoPreviewLayer
中的一種方法臭墨。 - 3) 您可以使用轉(zhuǎn)換后的點(diǎn)來調(diào)用閉包赔嚎。
在captureOutput(_:didOutput:from :)
中,在聲明fingerTips
屬性之后胧弛,添加:
defer {
DispatchQueue.main.sync {
self.processPoints(fingerTips)
}
}
方法完成后尤误,這會(huì)將您的指尖發(fā)送到主隊(duì)列中進(jìn)行處理。
是時(shí)候向用戶展示這些指尖了结缚!
5. Displaying Fingertips
pointsProcessorHandler
將在屏幕上獲取檢測到的指紋损晤。 您必須將閉包從SwiftUI
傳遞到此視圖控制器。
返回CameraView.swift
并添加一個(gè)新屬性:
var pointsProcessorHandler: (([CGPoint]) -> Void)?
這為您提供了在視圖中存儲(chǔ)閉包的位置红竭。
然后通過在return
語句之前添加以下行來更新makeUIViewController(context :)
:
cvc.pointsProcessorHandler = pointsProcessorHandler
這會(huì)將閉包傳遞給視圖控制器尤勋。
打開ContentView.swift
并將以下屬性添加到視圖定義:
@State private var overlayPoints: [CGPoint] = []
此狀態(tài)變量將保存在CameraView
中獲取的點(diǎn)。 用以下內(nèi)容替換CameraView()
行:
CameraView {
overlayPoints = $0
}
該閉包是您之前添加的pointsProcessorHandler
茵宪,當(dāng)您檢測到點(diǎn)時(shí)會(huì)調(diào)用該閉包最冰。 在閉包中,將點(diǎn)分配給overlayPoints
稀火。
最后暖哨,在edgesIgnoringSafeArea(.all)
修飾符之前添加此修飾符:
.overlay(
FingersOverlay(with: overlayPoints)
.foregroundColor(.orange)
)
您正在將疊加層修改器添加到CameraView
。 在該修飾符內(nèi)憾股,使用檢測到的點(diǎn)初始化FingersOverlay
并將顏色設(shè)置為橙色鹿蜀。FingersOverlay.swift在啟動(dòng)項(xiàng)目中箕慧。 它的唯一工作是在屏幕上繪制點(diǎn)服球。
構(gòu)建并運(yùn)行。 檢查手指上的橙色點(diǎn)颠焦。 移動(dòng)您的手斩熊,并注意點(diǎn)跟隨您的手指。
注意:如果需要伐庭,可以隨時(shí)在
.overlay
修改器中更改顏色粉渠。
終于可以添加游戲邏輯了分冈。
Adding Game Logic
游戲的邏輯很長,但是非常簡單霸株。
打開GameLogicController.swift
并將類實(shí)現(xiàn)替換為:
// 1
private var goalCount = 0
// 2
@Published var makeItRain = false
// 3
@Published private(set) var successBadge: Int?
// 4
private var shouldEvaluateResult = true
// 5
func start() {
makeItRain = true
}
// 6
func didRainStars(count: Int) {
goalCount = count
}
// 7
func checkStarsCount(_ count: Int) {
if !shouldEvaluateResult {
return
}
if count == goalCount {
shouldEvaluateResult = false
successBadge = count
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.successBadge = nil
self.makeItRain = true
self.shouldEvaluateResult = true
}
}
}
這是一個(gè)細(xì)分:
- 1) 此屬性存儲(chǔ)掉落的星星數(shù)雕沉。玩家必須通過顯示適當(dāng)?shù)氖种笖?shù)來猜測該值。
- 2) 每當(dāng)將發(fā)布的屬性設(shè)置為
true
時(shí)去件,StarAnimator
就會(huì)開始下雨坡椒。 - 3) 如果玩家正確猜測掉落的星星數(shù)量,則可以為此分配目標(biāo)數(shù)尤溜。該值出現(xiàn)在屏幕上倔叼,指示成功。
- 4) 此屬性可防止過多的評(píng)估宫莱。如果玩家正確猜出了該值丈攒,則此屬性將使評(píng)估停止。
- 5) 游戲就是這樣開始的授霸。當(dāng)出現(xiàn)開始屏幕時(shí)巡验,您可以調(diào)用此命令。
- 6) 當(dāng)
StarAnimator
落下特定數(shù)量的星星時(shí)碘耳,它將調(diào)用此方法以將目標(biāo)計(jì)數(shù)保存在游戲引擎中深碱。 - 7) 這就是魔術(shù)發(fā)生的地方。只要有新的點(diǎn)藏畅,就可以調(diào)用此方法敷硅。首先檢查是否可以評(píng)估結(jié)果。如果猜測值正確愉阎,它將停止評(píng)估绞蹦,設(shè)置成功標(biāo)志值,并在三秒鐘后將引擎狀態(tài)重置為初始值榜旦。
打開ContentView.swift
連接GameLogicController
幽七。
將對(duì)StarAnimator
的調(diào)用(包括其結(jié)尾的閉包)替換為:
StarAnimator(makeItRain: $gameLogicController.makeItRain) {
gameLogicController.didRainStars(count: $0)
}
此代碼向游戲引擎報(bào)告下雨天的數(shù)量。
接下來溅呢,您將告知玩家正確的答案澡屡。
1. Adding a Success Badge
為successBadge
添加計(jì)算的屬性,如下所示:
@ViewBuilder
private var successBadge: some View {
if let number = gameLogicController.successBadge {
Image(systemName: "\(number).circle.fill")
.resizable()
.imageScale(.large)
.foregroundColor(.white)
.frame(width: 200, height: 200)
.shadow(radius: 5)
} else {
EmptyView()
}
}
如果游戲邏輯控制器的successBadge
具有值咐旧,則可以使用SFSymbols
中可用的系統(tǒng)映像來創(chuàng)建映像驶鹉。 否則,您將返回EmptyView
铣墨,這意味著什么都沒有繪制室埋。
將這兩個(gè)修飾符添加到根ZStack
中:
.onAppear {
// 1
gameLogicController.start()
}
.overlay(
// 2
successBadge
.animation(.default)
)
這是您添加的內(nèi)容:
- 1) 當(dāng)游戲的開始頁面出現(xiàn)時(shí),您開始游戲。
- 2) 您將
success badge
置于一切之上姚淆。 接下來是successBadge
實(shí)現(xiàn)孕蝉。
接下來,刪除Rain
的疊加層腌逢,因?yàn)楝F(xiàn)在它會(huì)自動(dòng)下雨降淮。
2. Final Step
要使游戲正常運(yùn)行,您需要將檢測到的點(diǎn)數(shù)傳遞給游戲引擎搏讶。 更新在ContentView
中初始化CameraView
時(shí)傳遞的閉包:
CameraView {
overlayPoints = $0
gameLogicController.checkStarsCount($0.count)
}
構(gòu)建并運(yùn)行骤肛。 玩的開心。
More Use Cases
您幾乎剛剛涉及到Vision
中的Hand and Body Detection APIs
窍蓝。 該框架可以檢測到多個(gè)body landmarks
腋颠,如下所示:
以下是您可以使用這些API進(jìn)行操作的一些示例:
- 使用
Vision
框架在您的應(yīng)用中安裝UI控件。 例如吓笙,某些相機(jī)應(yīng)用程序包含一些功能淑玫,可讓您顯示手勢來拍照。 - 構(gòu)建一個(gè)有趣的表情符號(hào)應(yīng)用程序面睛,使用戶可以用手顯示表情符號(hào)絮蒿。
- 構(gòu)建一個(gè)鍛煉分析應(yīng)用程序,用戶可以在其中找到他或她是否在執(zhí)行特定的操作叁鉴。
- 構(gòu)建一個(gè)音樂應(yīng)用程序土涝,教用戶彈吉他或夏威夷四弦琴。
Vision
和這些特定的API有很多很棒的資源幌墓。 要更深入地探討此主題但壮,請(qǐng)嘗試:
- WWDC 2020 Video on Body and Hand Detection APIs:該視頻包含更多示例。
-
Vision Framework Documentation:
Vision
框架中所有可用功能的概述常侣。 - Detecting Human Body Poses in Images:Apple提供的文檔和示例應(yīng)用程序蜡饵。
- raywenderlich.com Forums:向我們很棒的社區(qū)尋求幫助。
后記
本篇主要講述了基于
Vision
的Body Detect
和Hand Pose
胳施,感興趣的給個(gè)贊或者關(guān)注~~~