Vision框架詳細(xì)解析(十六) —— 基于Vision的輪廓檢測(一)

版本記錄

版本號 時(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 連接到 ContentViewModelimage 屬性幽崩。 如果您打開 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可以是兩種類型之一论笔,VNImageRequestHandlerVNSequenceRequestHandler
  • 最后千所,結(jié)果resultVNObservation 的子類狂魔,作為原始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)志位筒占。 由于contourscalculating都是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ú)的contourVNContour 描述并按層次組織巍实。 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.03.0,默認(rèn)值為 2.0酣胀。該值越高刁赦,對圖像應(yīng)用的對比度就越高,從而更容易檢測到一些輪廓闻镶。
  • 2) contrastPivot:算法如何知道圖像的哪個(gè)部分應(yīng)該被視為暗與亮甚脉?這就是對比度樞軸(contrast pivot)的用武之地。它是一個(gè)可選的 NSNumber 屬性铆农,范圍從 0.01.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ì)detectDarkOnLightmaximumImageDimension 屬性,只使用它們的默認(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 上的 contrastPivotcontrastAdjustment,并使用一些額外的邏輯來允許您將 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)) ?? []
}

這兩行新代碼為 contrastPivotcontrastAdjustment 賦值切诀。

構(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)遍歷的 contourPivotcontourAdjustment 值的步幅茶鹃。
  • 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 unionIoU 是兩個(gè)邊界框的交集面積除以它們的并集面積。

當(dāng) IoU1.0 時(shí)盗痒,邊界框完全相同蚂蕴。 如果 IoU0.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ì)提供這些東西夺克;查看:

后記

本篇主要講述了基于Vision的輪廓檢測,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嚎朽,一起剝皮案震驚了整個(gè)濱河市铺纽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哟忍,老刑警劉巖狡门,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锅很,居然都是意外死亡其馏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門粗蔚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尝偎,“玉大人,你說我怎么就攤上這事鹏控≈鲁叮” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵当辐,是天一觀的道長抖僵。 經(jīng)常有香客問我,道長缘揪,這世上最難降的妖魔是什么耍群? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮找筝,結(jié)果婚禮上蹈垢,老公的妹妹穿的比我還像新娘。我一直安慰自己袖裕,他們只是感情好曹抬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著急鳄,像睡著了一般谤民。 火紅的嫁衣襯著肌膚如雪堰酿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天张足,我揣著相機(jī)與錄音触创,去河邊找鬼。 笑死为牍,一個(gè)胖子當(dāng)著我的面吹牛哼绑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吵聪,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼凌那,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吟逝?” 一聲冷哼從身側(cè)響起帽蝶,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎块攒,沒想到半個(gè)月后励稳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囱井,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年驹尼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庞呕。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡新翎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出住练,到底是詐尸還是另有隱情地啰,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布讲逛,位于F島的核電站亏吝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盏混。R本人自食惡果不足惜蔚鸥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望许赃。 院中可真熱鬧止喷,春花似錦、人聲如沸混聊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽技羔。三九已至僵闯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藤滥,已是汗流浹背鳖粟。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拙绊,地道東北人向图。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像标沪,于是被迫代替她去往敵國和親榄攀。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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