版本記錄
版本號 | 時(shí)間 |
---|---|
V1.0 | 2022.06.01 星期三 |
前言
iOS 11+
和macOS 10.13+
新出了Vision
框架,提供了人臉識別拌夏、物體檢測、物體跟蹤等技術(shù)站故,它是基于Core ML的∑窠颍可以說是人工智能的一部分橱乱,接下來幾篇我們就詳細(xì)的解析一下Vision框架茶宵。感興趣的看下面幾篇文章。
1. Vision框架詳細(xì)解析(一) —— 基本概覽(一)
2. Vision框架詳細(xì)解析(二) —— 基于Vision的人臉識別(一)
3. Vision框架詳細(xì)解析(三) —— 基于Vision的人臉識別(二)
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掃描(二)
10. Vision框架詳細(xì)解析(十) —— 基于Vision的Body Detect和Hand Pose(一)
11. Vision框架詳細(xì)解析(十一) —— 基于Vision的Body Detect和Hand Pose(二)
12. Vision框架詳細(xì)解析(十二) —— 基于Vision的Face Detection新特性(一)
13. Vision框架詳細(xì)解析(十三) —— 基于Vision的Face Detection新特性(二)
14. Vision框架詳細(xì)解析(十四) —— 基于Vision的人員分割(一)
15. Vision框架詳細(xì)解析(十五) —— 基于Vision的人員分割(二)
開始
首先看下主要內(nèi)容:
了解如何使用
Vision
框架以有趣且藝術(shù)的方式檢測和修改SwiftUI iOS
應(yīng)用程序中的圖像輪廓节预。內(nèi)容來自翻譯叶摄。
接著看下寫作環(huán)境
Swift 5.5, iOS 15, Xcode 13
接著就是正文了。
藝術(shù)是一個(gè)非常主觀的東西安拟。但是蛤吓,編碼不是。當(dāng)然会傲,開發(fā)人員有時(shí)可能會(huì)固執(zhí)己見,但是計(jì)算機(jī)如何解釋您的代碼在很大程度上不是見仁見智的問題蕴纳。
那么,作為開發(fā)人員忱屑,您如何使用代碼來創(chuàng)作藝術(shù)呢?也許這不是你如何編碼麻惶,而是你選擇用它做什么振湾。使用可用的工具獲得創(chuàng)意可以顯著影響輸出。
想想蘋果是如何推動(dòng)計(jì)算攝影的極限的苛白。大多數(shù)數(shù)碼攝影都是關(guān)于對來自傳感器的像素進(jìn)行后處理。不同的算法集會(huì)改變最終輸出的外觀和感覺饮亏。那是藝術(shù)着帽!
您甚至可以使用計(jì)算機(jī)視覺算法為圖像和照片創(chuàng)建令人興奮的濾鏡和效果灵迫。例如舟肉,如果您檢測到圖像中的所有輪廓脏款,您可能有一些很酷的材料來制作具有藝術(shù)感的圖像繪圖。這就是本教程的全部內(nèi)容够吩!
在本教程中闰歪,您將學(xué)習(xí)如何使用 Vision 框架:
- 創(chuàng)建請求以執(zhí)行輪廓檢測。
- 調(diào)整設(shè)置以獲得不同的輪廓。
- 簡化輪廓以創(chuàng)造藝術(shù)效果系忙。
聽起來很有趣,對吧惠豺?沒錯(cuò)……藝術(shù)應(yīng)該很有趣银还!
打開起始項(xiàng)目风宁,啟動(dòng)項(xiàng)目包括一些擴(kuò)展、模型文件和 UI蛹疯。
如果您現(xiàn)在構(gòu)建并運(yùn)行戒财,您將看到點(diǎn)擊屏幕和功能設(shè)置圖標(biāo)的說明。
您可能會(huì)注意到現(xiàn)在點(diǎn)擊屏幕不會(huì)做任何事情捺弦,但在您進(jìn)入本教程的Vision
部分之前饮寞,您需要在屏幕上顯示圖像。
Displaying an Image
在瀏覽啟動(dòng)項(xiàng)目時(shí)列吼,您可能已經(jīng)注意到一個(gè) ImageView
連接到 ContentViewModel
的 image
屬性幽崩。 如果您打開 ContentViewModel.swift
,您會(huì)看到該image
是一個(gè)已發(fā)布的屬性寞钥,但沒有為其分配(assigned)
任何內(nèi)容慌申。
你需要做的第一件事就是改變它!
首先在 ContentViewModel.swift
中定義的三個(gè)已發(fā)布屬性之后直接添加以下代碼:
init() {
let uiImage = UIImage(named: "sample")
let cgImage = uiImage?.cgImage
self.image = cgImage
}
此代碼從資產(chǎn)目錄加載名為 sample.png
的圖像并為其獲取 CGImage
理郑,然后將其分配給image
發(fā)布屬性蹄溉。
通過這個(gè)小改動(dòng),繼續(xù)構(gòu)建并重新運(yùn)行應(yīng)用程序您炉,您將在屏幕上看到下面的圖像:
現(xiàn)在柒爵,當(dāng)您點(diǎn)擊屏幕時(shí),它應(yīng)該在上面的圖像和您最初看到的空白屏幕之間切換邻吭。
空白屏幕最終將包含您使用 Vision
框架檢測到的輪廓餐弱。
Vision API Pipeline
在開始編寫一些代碼來檢測輪廓之前,了解 Vision API
管道會(huì)很有幫助囱晴。 一旦你知道它是如何工作的膏蚓,你就可以很容易地在你未來的項(xiàng)目中包含任何視覺算法; 這很漂亮畸写。
Vision API
管道由三部分組成:
- 第一個(gè)是
request
驮瞧,它是VNRequest
的子類——所有分析請求的基類。 然后將此請求傳遞給handler
枯芬。 - 處理程序
handler
可以是兩種類型之一论笔,VNImageRequestHandler
或VNSequenceRequestHandler
。 - 最后千所,結(jié)果
result
是VNObservation
的子類狂魔,作為原始request
對象的屬性返回。
通常淫痰,很容易判斷哪種結(jié)果類型與哪種請求類型對應(yīng)最楷,因?yàn)樗鼈兊拿Q相似。例如,如果您的請求是 VNDetectFaceRectanglesRequest
籽孙,則返回的結(jié)果將是 VNFaceObservation
烈评。
對于這個(gè)項(xiàng)目,請求將是一個(gè) VNDetectContoursRequest
犯建,它將以 VNContoursObservation
的形式返回結(jié)果讲冠。
無論何時(shí)處理單個(gè)圖像,而不是圖像序列中的幀适瓦,您都將使用 VNImageRequestHandler
竿开。 VNSequenceRequestHandler
在處理圖像序列時(shí)使用,您希望將請求應(yīng)用于一系列相關(guān)圖像犹菇,例如來自視頻流的幀德迹。在這個(gè)項(xiàng)目中芽卿,您將使用前者來處理單個(gè)圖像請求揭芍。
現(xiàn)在您已經(jīng)掌握了背景理論,是時(shí)候?qū)⑵涓吨T實(shí)踐了卸例!
Contour Detection
為了使項(xiàng)目井井有條称杨,在項(xiàng)目導(dǎo)航器中右鍵單擊 Contour Art
組并選擇 New Group
。將新組命名為 Vision
筷转。
右鍵單擊新的 Vision
組并選擇 New File…
姑原。選擇
Swift File
并將其命名為 ContourDetector
。
用以下代碼替換文件的內(nèi)容:
import Vision
class ContourDetector {
static let shared = ContourDetector()
private init() {}
}
這段代碼所做的只是將一個(gè)新的 ContourDetector
類設(shè)置為 Singleton
呜舒。 單例模式并不是絕對必要的锭汛,但它可以確保您只有一個(gè) ContourDetector
實(shí)例在應(yīng)用程序周圍運(yùn)行。
1. Performing Vision Requests
現(xiàn)在是時(shí)候讓檢測器類做點(diǎn)什么了袭蝗。
將以下屬性添加到 ContourDetector
類:
private lazy var request: VNDetectContoursRequest = {
let req = VNDetectContoursRequest()
return req
}()
這將在您第一次需要時(shí)懶惰地創(chuàng)建一個(gè) VNDetectContoursRequest
唤殴。 Singleton
結(jié)構(gòu)還確保只有一個(gè) Vision
請求,可以在應(yīng)用程序的整個(gè)生命周期中重復(fù)使用到腥。
現(xiàn)在添加以下方法:
private func perform(request: VNRequest,
on image: CGImage) throws -> VNRequest {
// 1
let requestHandler = VNImageRequestHandler(cgImage: image, options: [:])
// 2
try requestHandler.perform([request])
// 3
return request
}
這種方法簡單但功能強(qiáng)大朵逝。 你在這里:
- 1) 創(chuàng)建
request handler
并將提供的CGImage
傳遞給它。 - 2) 使用
handler
執(zhí)行request
乡范。 - 3) 返回請求配名,現(xiàn)在已附加結(jié)果。
為了使用請求的結(jié)果晋辆,您需要進(jìn)行一些處理渠脉。 在上一個(gè)方法下面,添加以下方法來處理返回的請求:
private func postProcess(request: VNRequest) -> [Contour] {
// 1
guard let results = request.results as? [VNContoursObservation] else {
return []
}
// 2
let vnContours = results.flatMap { contour in
(0..<contour.contourCount).compactMap { try? contour.contour(at: $0) }
}
// 3
return vnContours.map { Contour(vnContour: $0) }
}
在這種方法中瓶佳,您:
- 1) 檢查
results
是否為VNContoursObservation
對象數(shù)組芋膘。 - 2) 將每個(gè)結(jié)果轉(zhuǎn)換為
VNContours
數(shù)組。-
flatMap
將結(jié)果轉(zhuǎn)換為單個(gè)扁平數(shù)組。 - 使用
compactMap
遍歷contour
中的contours
索赏,以確保僅保留非nil
值盼玄。 - 使用
contour(at:)
檢索指定索引處的輪廓對象。
-
- 3) 將
VNContours
數(shù)組映射到自定義Contour
模型數(shù)組中潜腻。
注意:從
VNContour
轉(zhuǎn)換為Contour
的原因是為了簡化一些SwiftUI
代碼埃儿。Contour
遵循Identifiable
,因此很容易遍歷它們的數(shù)組融涣。 查看ContoursView.swift
以查看實(shí)際情況童番。
2. Processing Images in the Detector
現(xiàn)在您只需要將這兩個(gè)私有方法綁定到某個(gè)可從類外部調(diào)用的地方。 仍然在 ContourDetector.swift
中威鹿,添加以下方法:
func process(image: CGImage?) throws -> [Contour] {
guard let image = image else {
return []
}
let contourRequest = try perform(request: request, on: image)
return postProcess(request: contourRequest)
}
在這里剃斧,您正在檢查是否有圖像,然后使用 perform(request:on:)
創(chuàng)建請求忽你,最后使用 postProcess(request:)
返回結(jié)果幼东。 這將是您的view model
將調(diào)用以檢測圖像輪廓的方法,這正是您接下來要做的科雳。
打開 ContentViewModel.swift
并將以下方法添加到類的末尾:
func asyncUpdateContours() async -> [Contour] {
let detector = ContourDetector.shared
return (try? detector.process(image: self.image)) ?? []
}
在此代碼中根蟹,您將創(chuàng)建一個(gè)異步方法來檢測輪廓。 為什么是異步的糟秘? 雖然檢測輪廓通常相對較快简逮,但您仍然不想在等待 API 調(diào)用結(jié)果時(shí)占用 UI。 如果檢測器未找到任何輪廓尿赚,則異步方法返回一個(gè)空數(shù)組散庶。 此外,spoiler alert
凌净,稍后您將在此處添加更多邏輯悲龟,這將對您的設(shè)備處理器造成負(fù)擔(dān)。
但是泻蚊,您仍然需要從某個(gè)地方調(diào)用此方法躲舌。 找到 updateContours
的方法存根,并用以下代碼填充它:
func updateContours() {
// 1
guard !calculating else { return }
calculating = true
// 2
Task {
// 3
let contours = await asyncUpdateContours()
// 4
DispatchQueue.main.async {
self.contours = contours
self.calculating = false
}
}
}
使用此代碼性雄,您可以:
- 1) 如果我們已經(jīng)在計(jì)算輪廓没卸,什么也不做。 否則設(shè)置一個(gè)標(biāo)志以指示您正在計(jì)算輪廓秒旋。 然后约计,用戶界面將能夠通知用戶,因此他們保持耐心迁筛。
- 2) 創(chuàng)建一個(gè)異步上下文煤蚌,從中運(yùn)行輪廓檢測器。 這對于異步工作是必要的。
- 3) 啟動(dòng)輪廓檢測方法并等待其結(jié)果尉桩。
- 4) 將結(jié)果設(shè)置回主線程并清除
calculating
標(biāo)志位筒占。 由于contours
和calculating
都是published properties
,因此只能在主線程上assigned
它們蜘犁。
這個(gè)更新方法需要從某個(gè)地方調(diào)用翰苫,init
的底部比任何地方都好! 找到 init
并將以下行添加到底部:
updateContours()
現(xiàn)在是構(gòu)建和運(yùn)行您的應(yīng)用程序的時(shí)候了这橙。 應(yīng)用程序加載并看到圖像后奏窑,點(diǎn)擊屏幕以使用默認(rèn)設(shè)置顯示其檢測到的輪廓。
很棒屈扎!
VNContoursObservation and VNContour
在撰寫本文時(shí)埃唯,VNDetectContoursObservation
似乎永遠(yuǎn)不會(huì)在結(jié)果數(shù)組中返回多個(gè) VNContoursObservation
。相反鹰晨,您看到的所有contours
(在上一個(gè)屏幕截圖中共有 43
個(gè))都由單個(gè) VNContoursObservation
引用墨叛。
注意:您編寫的代碼處理多個(gè)
VNContoursObservation
結(jié)果,以防Apple
決定更改其工作方式并村。
每個(gè)單獨(dú)的contour
由 VNContour
描述并按層次組織巍实。 VNContour
可以有子contour
滓技。要訪問它們哩牍,您有兩種選擇:
- 1) 索引
childContours
屬性,它是一個(gè)VNContours
數(shù)組令漂。 - 2) 結(jié)合使用
childContourCount
整數(shù)屬性和childContour(at: Int)
方法來循環(huán)訪問每個(gè)子contour
膝昆。
由于任何 VNContour
都可以有一個(gè)子 VNContour
,因此如果您需要保留層次結(jié)構(gòu)信息叠必,則必須遞歸訪問它們荚孵。
如果您不關(guān)心層次結(jié)構(gòu),VNContoursObservation
為您提供了一種以簡單方式訪問所有輪廓的簡單方法纬朝。 VNContoursObservation
有一個(gè) contourCount
整數(shù)屬性和一個(gè) contour(at: Int)
方法來訪問所有輪廓收叶,就好像它們是一個(gè)平面數(shù)據(jù)結(jié)構(gòu)一樣。
但是共苛,如果層次結(jié)構(gòu)對您很重要判没,則需要訪問 topLevelContours
屬性,它是 VNContours
數(shù)組隅茎。從那里澄峰,您可以訪問每個(gè)輪廓的子輪廓(contours)
。
如果您要編寫一些簡單的代碼來計(jì)算頂級輪廓和子輪廓辟犀,您會(huì)發(fā)現(xiàn)示例圖像在默認(rèn)設(shè)置下具有 4
個(gè)頂級輪廓和 39
個(gè)子輪廓俏竞,總共 43
個(gè)。
VNDetectContoursRequest Settings
到目前為止,您已經(jīng)創(chuàng)建了一個(gè) VNDetectContoursRequest
魂毁,而沒有嘗試各種可用的設(shè)置玻佩。目前,您可以更改四個(gè)屬性以實(shí)現(xiàn)不同的結(jié)果-
- 1) contrastAdjustment:該算法具有在執(zhí)行輪廓檢測之前調(diào)整圖像對比度的內(nèi)置方法席楚。調(diào)整對比度會(huì)嘗試使圖像的暗部變暗并減輕亮部以增大它們的差異夺蛇。此浮點(diǎn)屬性的范圍從
0.0
到3.0
,默認(rèn)值為2.0
酣胀。該值越高刁赦,對圖像應(yīng)用的對比度就越高,從而更容易檢測到一些輪廓闻镶。 - 2) contrastPivot:算法如何知道圖像的哪個(gè)部分應(yīng)該被視為暗與亮甚脉?這就是對比度樞軸
(contrast pivot)
的用武之地。它是一個(gè)可選的NSNumber
屬性铆农,范圍從0.0
到1.0
牺氨,默認(rèn)值為0.5
。低于此值的任何像素都將變暗墩剖,而高于此值的任何像素都將變亮猴凹。您還可以將此屬性設(shè)置為nil
以使Vision
框架自動(dòng)檢測該值“應(yīng)該”是什么。 - 3) detectDarkOnLight:此布爾屬性是對輪廓檢測算法的提示岭皂。默認(rèn)設(shè)置為
true
郊霎,這意味著它應(yīng)該在淺色背景上尋找深色物體。 - 4) maximumImageDimension:由于您可以將任何尺寸的圖像傳遞給請求處理程序
(request handler)
爷绘,因此此整數(shù)屬性允許您設(shè)置要使用的最大圖像尺寸书劝。如果您的圖像尺寸大于此值,API
會(huì)縮放圖像土至,以使兩個(gè)尺寸中較大的一個(gè)等于maximumImageDimension
购对。此屬性的默認(rèn)值為512
。為什么要更改它陶因?輪廓檢測(Contour detection)
需要相當(dāng)多的處理能力——圖像越大骡苞,需要的越多。但是楷扬,圖像越大解幽,它就越準(zhǔn)確。此屬性允許您根據(jù)需要微調(diào)毅否。
Changing the Contrast
現(xiàn)在您了解了可用的設(shè)置唧瘾,您將編寫一些代碼來更改兩個(gè)對比度設(shè)置的值棕硫。在本教程中募舟,您將不理會(huì)detectDarkOnLight
和maximumImageDimension
屬性,只使用它們的默認(rèn)值吞琐。
打開 ContourDetector.swift
并將以下方法添加到 ContourDetector
的底部:
func set(contrastPivot: CGFloat?) {
request.contrastPivot = contrastPivot.map {
NSNumber(value: $0)
}
}
func set(contrastAdjustment: CGFloat) {
request.contrastAdjustment = Float(contrastAdjustment)
}
這些方法分別更改 VNDetectContoursRequest
上的 contrastPivot
和 contrastAdjustment
,并使用一些額外的邏輯來允許您將 contrastPivot
設(shè)置為 nil
然爆。
您會(huì)記得 request
是一個(gè)lazy var
站粟,這意味著如果在您調(diào)用這些方法之一時(shí)它還沒有被實(shí)例化,它將現(xiàn)在實(shí)例化曾雕。
接下來奴烙,打開 ContentViewModel.swift
并找到 asyncUpdateContours
。 更新方法剖张,使其看起來像這樣:
func asyncUpdateContours() async -> [Contour] {
let detector = ContourDetector.shared
// New logic
detector.set(contrastPivot: 0.5)
detector.set(contrastAdjustment: 2.0)
return (try? detector.process(image: self.image)) ?? []
}
這兩行新代碼為 contrastPivot
和 contrastAdjustment
賦值切诀。
構(gòu)建并運(yùn)行應(yīng)用程序并為這些設(shè)置嘗試不同的值(您需要更改這些值,然后再次構(gòu)建并運(yùn)行)搔弄。 以下是不同值的一些截圖:
好的幅虑,現(xiàn)在你得到了一些有趣的結(jié)果。但是顾犹,有點(diǎn)煩人的是倒庵,沒有神奇的設(shè)置可以從圖像中獲取所有輪廓并將它們組合成一個(gè)結(jié)果。
但是……有一個(gè)解決方案炫刷。
在探索入門項(xiàng)目時(shí)擎宝,您可能已經(jīng)點(diǎn)擊了右下角的設(shè)置圖標(biāo)。如果您點(diǎn)擊它浑玛,您會(huì)看到用于最小和最大對比度樞軸和調(diào)整(contrast pivot and adjustment)
的滑塊绍申。
您將使用這些滑塊為這些設(shè)置創(chuàng)建范圍并循環(huán)訪問它們。然后锄奢,您將組合每個(gè)設(shè)置對中的所有輪廓失晴,為圖像創(chuàng)建更完整的輪廓集。
注意:每個(gè)設(shè)置的范圍越大拘央,您運(yùn)行的
Vision
請求就越多。這可能是一個(gè)緩慢的過程书在,除非您非常有耐心灰伟,否則不建議在舊設(shè)備上使用。它在較新的 iPhone儒旬、iPad 和基于 M1 的 Mac 上運(yùn)行良好栏账。
如果您還沒有打開 ContentViewModel.swift
,請繼續(xù)打開它栈源。刪除 asyncUpdateContours
的全部內(nèi)容挡爵,并用以下代碼替換:
// 1
var contours: [Contour] = []
// 2
let pivotStride = stride(
from: UserDefaults.standard.minPivot,
to: UserDefaults.standard.maxPivot,
by: 0.1)
let adjustStride = stride(
from: UserDefaults.standard.minAdjust,
to: UserDefaults.standard.maxAdjust,
by: 0.2)
// 3
let detector = ContourDetector.shared
// 4
for pivot in pivotStride {
for adjustment in adjustStride {
// 5
detector.set(contrastPivot: pivot)
detector.set(contrastAdjustment: adjustment)
// 6
let newContours = (try? detector.process(image: self.image)) ?? []
// 7
contours.append(contentsOf: newContours)
}
}
// 8
return contours
在這個(gè)新版本的 asyncUpdateContours
中,您:
- 1) 創(chuàng)建一個(gè)空的輪廓
Contours
數(shù)組來存儲所有輪廓甚垦。 - 2) 設(shè)置要循環(huán)遍歷的
contourPivot
和contourAdjustment
值的步幅茶鹃。 - 3) 獲取對
ContourDetector
單例的引用涣雕。 - 4) 循環(huán)通過兩個(gè)步驟。 請注意闭翩,這是一個(gè)嵌套循環(huán)挣郭,因此每個(gè)
contourPivot
值都將與每個(gè)contourAdjustment
值配對。 - 5) 使用您創(chuàng)建的訪問器方法更改
VNDetectContoursRequest
的設(shè)置疗韵。 - 6) 通過
Vision
輪廓檢測器API
運(yùn)行圖像兑障。 - 7) 將結(jié)果附加到輪廓
Contours
列表并... - 8) 返回此輪廓列表。
已經(jīng)太多了蕉汪,但它會(huì)是值得的流译。 繼續(xù)構(gòu)建并運(yùn)行應(yīng)用程序并更改設(shè)置菜單中的滑塊。 通過向下滑動(dòng)或在其外部點(diǎn)擊關(guān)閉設(shè)置菜單后者疤,它將開始重新計(jì)算輪廓先蒋。
以下屏幕截圖中使用的范圍是:
Contrast Pivot: 0.2 - 0.7
Contrast Adjustment: 0.5 - 3.0
真的很酷!
Thinning the Contours
這是一個(gè)很酷的效果宛渐,但你可以做得更好竞漾!
您可能會(huì)注意到一些輪廓現(xiàn)在看起來很厚,而另一些則很薄窥翩。 “厚”輪廓實(shí)際上是同一區(qū)域的多個(gè)輪廓业岁,但由于對比度的調(diào)整方式而彼此略有偏移。
如果您可以檢測到重復(fù)的輪廓寇蚊,您就可以刪除它們笔时,這應(yīng)該會(huì)使線條看起來更細(xì)。
確定兩個(gè)輪廓是否相同的一種簡單方法是查看它們有多少重疊仗岸。 它并不完全是 100% 準(zhǔn)確的允耿,但它是一個(gè)相對較快的近似值。 要確定重疊扒怖,您可以計(jì)算它們的邊界框的交集较锡。
Intersection over union
或 IoU
是兩個(gè)邊界框的交集面積除以它們的并集面積。
當(dāng) IoU
為 1.0
時(shí)盗痒,邊界框完全相同蚂蕴。 如果 IoU
為 0.0
,則兩個(gè)邊界框之間沒有重疊俯邓。
您可以將其用作閾值來過濾掉看起來“足夠接近”的邊界框骡楼。
回到 ContentViewModel.swift
中的 asyncUpdateContours
,在 return
語句之前添加以下代碼:
// 1
if contours.count < 9000 {
// 2
let iouThreshold = UserDefaults.standard.iouThresh
// 3
var pos = 0
while pos < contours.count {
// 4
let contour = contours[pos]
// 5
contours = contours[0...pos] + contours[(pos+1)...].filter {
contour.intersectionOverUnion(with: $0) < iouThreshold
}
// 6
pos += 1
}
}
使用此代碼稽鞭,您可以:
- 1) 僅當(dāng)輪廓數(shù)少于
9,000
時(shí)運(yùn)行鸟整。 這可能是整個(gè)函數(shù)中最慢的部分,所以盡量限制它何時(shí)可以使用朦蕴。 - 2) 抓取
IoU
閾值設(shè)置篮条,可以在設(shè)置屏幕中更改弟头。 - 3) 循環(huán)遍歷每個(gè)輪廓。 您在此處使用
while
循環(huán)兑燥,因?yàn)槟鷮?dòng)態(tài)更改輪廓數(shù)組亮瓷。 您不希望意外地在數(shù)組大小之外進(jìn)行索引! - 4) 索引輪廓數(shù)組以獲取當(dāng)前輪廓降瞳。
- 5) 只保留當(dāng)前輪廓之后的輪廓嘱支,其
IoU
小于閾值。 請記住挣饥,如果IoU
大于或等于閾值除师,則您已確定它與當(dāng)前輪廓相似,應(yīng)將其刪除扔枫。 - 6) 增加索引位置汛聚。
注意:可能有一種更有效的方法來完成此操作,但這是解釋該概念的最簡單方法短荐。
繼續(xù)構(gòu)建并運(yùn)行應(yīng)用程序倚舀。
注意有多少厚輪廓現(xiàn)在明顯變薄了!
Simplifying the Contours
您可以使用另一種技巧為您的輪廓藝術(shù)添加藝術(shù)感忍宋。 您可以簡化輪廓痕貌。
VNContour
有一個(gè)名為 polygonApproximation(epsilon:)
的成員方法,它就是這樣做的糠排。 該方法的目的是返回具有較少點(diǎn)的相似輪廓舵稠。 這是輪廓的近似值。
epsilon
的選擇將決定返回輪廓的簡化程度入宦。 較大的 epsilon
將導(dǎo)致具有較少點(diǎn)的更簡單的輪廓哺徊,而較小的 epsilon
將返回更接近原始輪廓的輪廓。
打開 ContourDetector.swift
乾闰。 在 ContourDetector
的頂部落追,添加以下屬性:
private var epsilon: Float = 0.001
接下來,在 ContourDetector
的底部汹忠,添加以下方法:
func set(epsilon: CGFloat) {
self.epsilon = Float(epsilon)
}
仍然在同一個(gè)類中淋硝,找到 postProcess(request:)
并將方法底部的 return
語句替換為以下代碼:
let simplifiedContours = vnContours.compactMap {
try? $0.polygonApproximation(epsilon: self.epsilon)
}
return simplifiedContours.map { Contour(vnContour: $0) }
此代碼在返回之前根據(jù) epsilon
的當(dāng)前值簡化每個(gè)檢測到的輪廓。
在嘗試這個(gè)新功能之前宽菜,您需要將 epsilon
設(shè)置連接到 ContourDetector
。 您只需從設(shè)置屏幕寫入的 UserDefaults
中讀取它竿报。
打開 ContentViewModel.swift
并再次找到 asyncUpdateContours
铅乡。 然后,在定義detector
常數(shù)的行下方烈菌,添加以下行:
detector.set(epsilon: UserDefaults.standard.epsilon)
這將確保檢測器在每次需要更新顯示的輪廓時(shí)獲得最新的 epsilon
值阵幸。
最后一次花履,繼續(xù)構(gòu)建并運(yùn)行!
此示例將值 0.01
用于多邊形近似 Epsilon (Polygon Approximation Epsilon)
設(shè)置挚赊。
現(xiàn)在诡壁,這是具有風(fēng)格的輪廓藝術(shù)。
通過了解 Vision API
管道的工作原理荠割,您現(xiàn)在可以在 Vision
框架中使用 Apple
提供的任何其他算法妹卿。想想可能性!
如果您對有關(guān) Vision API 的更多教程感興趣蔑鹦,我們會(huì)提供這些東西夺克;查看:
- Face Detection Tutorial Using the Vision Framework for iOS
- Photo Stacking in iOS with Vision and Metal
- Saliency Analysis in iOS using Vision
- Core ML and Vision Tutorial: On-device training on iOS
- Person Segmentation in the Vision Framework
- Vision Tutorial for iOS: Detect Body and Hand Pose
后記
本篇主要講述了基于
Vision
的輪廓檢測,感興趣的給個(gè)贊或者關(guān)注~~~