iOS 14 新功能: Vision Contour Detection 讓你建構電腦視覺處理 App


本篇原文(標題:New in iOS 14: Vision Contour Detection )刊登於作者的 Medium,由 Anupam Chugh 所著,並授權翻譯及轉載。

Apple 在 WWDC 2020(線上版)開發者大會中響起了平地一聲雷,釋出了許多讓人驚喜的新功能,(延伸閱讀:Apple’s own silicon chips for Macs),包括 SwiftUI、ARKit、PencilKit、Create ML 還有 Core ML。但是其中,對我來說最突出的是電腦視覺處理 (computer vision)。

Apple 推出了一系列新 API 之後,Vision 框架得到了更完善的支援。這些新 API 提供了一個更直接的方式,來執行複雜且重要的電腦視覺處理演算法。

從 iOS 14 開始,Vision 框架支援手部及身體的姿勢估計 (Hand and Body Pose Estimation)、光流 (Optical Flow)、軌跡偵測 (Trajectory Detection)、以及輪廓偵測 (Contour Detection)。

現在,先讓我們看看其中一個有趣的新功能 ── 視覺輪廓偵測 (Vision Contour Detection) 吧。

目標

  • 了解 Vision 的輪廓偵測請求。
  • 在 iOS 14 SwiftUI App 執行一個輪廓偵測請求,來偵測硬幣的輪廓。
  • 把圖像傳遞給 Vision 請求之前,先利用 Core Image 濾鏡對圖像進行前處理(pre-process) 來簡化輪廓。我們會嘗試在圖像加上遮罩來降低材質的雜訊,讓輪廓顯得更清楚。

視覺輪廓偵測 (Vision Contour Detection)

輪廓偵測的做法,是偵測圖像邊緣的形狀。實務上來說,它會連接所有相同顏色跟密度的連續點,來組成完整的輪廓。

這個電腦視覺處理功能,非常適用於形狀分析、邊緣偵測,而且如果需要從一張圖像裡面,找到相似形狀物件的情況下,這個功能就非常有用。

硬幣偵測與分割這項處理在 OpenCV 非常常見,現在我們使用 Vision 框架新的 VNDetectContoursRequest,就可以輕易地在 iOS App 裡執行一樣的功能(不需要第三方套件)。

要處理圖像或畫面,Vision 框架需要將 VNRequest 傳給圖像請求處理器 (image request handler),或連續請求處理器 (sequence request handler),然後我們會得到一個回傳的 VNObservation 類別。

你可以依照執行的請求型別,來決定使用哪個 VNObservation 子類別。在我們這個範例中,我們將使用 VNContoursObservation 來取得所有在圖像中偵測到的輪廓。

我們可以從 VNContoursObservation 查看下列各個參數:

  • normalizedPath:它會以標準化座標回傳偵測到的輪廓路徑,我們需要將這個座標轉成 UIKit 的座標,我們在下文將會再說明這一點。
  • contourCount:這是透過視覺請求 (Vision request) 所偵測到的輪廓數量。
  • topLevelContours:這是未包含在任何輪廓內的 VNContours 陣列 。
  • contour(at:):透過這個方法,我們可以傳入其索引值或 IndexPath,來取得一個子輪廓。
  • confidence:這是整個 VNContoursObservation 的信心程度。

請注意:利用 topLevelContours 以及存取子輪廓等方法,可以讓我們很方便地從最後的觀測結果中修改/移除子輪廓。

現在,初步了解了視覺輪廓偵測的請求後,讓我們來看看如何在 iOS 14 的 App 上執行吧!

開始動手

在開始之前,你最少要有 Xcode 12 beta 版本,如此一來,你就可以在 SwiftUI Previews 中直接執行視覺圖像請求 (Vision image request)。

利用 Xcode Wizard 建立一個新的 SwiftUI App,你會看到新的 SwiftUI App 的 Life Cycle:

iOS-14-Vision-Contour-Detection

完成專案建置之後,你會得到下列的程式碼:

@main
struct iOS14VisionContourDetection: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

請注意:從 iOS 14 開始,SceneDelegate 在 SwiftUI 的 App protocol 已經是過時的用法了,特別是在使用 SwiftUI 為基礎的 App。現在已經改成在 struct 的上面加上了 @main 註釋,來表明是 App 的進入點。

使用視覺輪廓請求來偵測硬幣

讓我們快速建立一個 SwiftUI 視圖,來執行視覺請求:

struct ContentView: View {

    @State var points : String = ""
    @State var preProcessImage: UIImage?
    @State var contouredImage: UIImage?

    var body: some View {

        VStack{

            Text("Contours: \(points)")

            Image("coins")
            .resizable()
            .scaledToFit()

            if let image = preProcessImage{
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            }

            if let image = contouredImage{
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            }

            Button("Detect Contours", action: {
                detectVisionContours()
            })
        }
    }

    func detectVisionContours(){
        //TODO: 
    }

}

在上面的程式碼中,我們使用了 SwiftUI 在 iOS 14 才開放的新語法 if let。讓我們先忽略 preprocessImage 這個狀態物件;現在先來看看 detectVisionContoursss 這個函數,它會依照視覺請求的結果來更新 outputImage

func detectVisionContours(){

        let context = CIContext()
        if let sourceImage = UIImage.init(named: "coin")
        {
            var inputImage = CIImage.init(cgImage: sourceImage.cgImage!)

            let contourRequest = VNDetectContoursRequest.init()
            contourRequest.revision = VNDetectContourRequestRevision1
            contourRequest.contrastAdjustment = 1.0
            contourRequest.detectDarkOnLight = true            
            contourRequest.maximumImageDimension = 512


            let requestHandler = VNImageRequestHandler.init(ciImage: inputImage, options: [:])

            try! requestHandler.perform([contourRequest])
            let contoursObservation = contourRequest.results?.first as! VNContoursObservation

            self.points  = String(contoursObservation.contourCount)
            self.contouredImage = drawContours(contoursObservation: contoursObservation, sourceImage: sourceImage.cgImage!)

        } else {
            self.points = "Could not load image"
        }
}

從上面的程式碼中,我們已經在 VNDetectContoursRequest 當中設定了 contrastAdjustment (以加強影像)、以及 detectDarkOnLight (以在明亮的背景上更好地進行輪廓偵測)這兩個參數。

執行 VNImageRequestsHandler 並且傳入一個圖像後(存在 Assets 資料夾中),我們會得到一個 VNContoursObservation

最後,我們在傳入的影像上畫出 normalizedPoints 圖層。

在圖像上畫出輪廓

以下是 drawContours 函數的程式碼:

public func drawContours(contoursObservation: VNContoursObservation, sourceImage: CGImage) -> UIImage {
        let size = CGSize(width: sourceImage.width, height: sourceImage.height)
        let renderer = UIGraphicsImageRenderer(size: size)

        let renderedImage = renderer.image { (context) in
        let renderingContext = context.cgContext

        let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height)
        renderingContext.concatenate(flipVertical)

        renderingContext.draw(sourceImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

        renderingContext.scaleBy(x: size.width, y: size.height)
        renderingContext.setLineWidth(5.0 / CGFloat(size.width))
        let redUIColor = UIColor.red
        renderingContext.setStrokeColor(redUIColor.cgColor)
        renderingContext.addPath(contoursObservation.normalizedPath)
        renderingContext.strokePath()
        }

        return renderedImage
    }

上述的程式碼會回傳一個 UIImage,我們把圖像設定給 contouredImage 這個 SwiftUI 狀態物件之後,畫面就會隨之更新:

contoured-image

如果我們用模擬器來執行是的話,這個結果算是不錯,但是如果我們使用 iOS 14 裝置,有了手機的神經網路引擎 (Neural Engine),結果肯定會更好。

不過,這張圖上對我們而言有太多輪廓了(主要是因為硬幣的材質)。我們可以透過前處理,讓這張圖的輪廓更簡潔(又或是減少這些輪廓)。

使用 Core Image 來為視覺影像請求做前處理

Core Image 是 Apple 一個影像處理及分析的框架。雖然這個框架可以偵測簡單的面部和條形碼,但卻不適用於複雜的電腦視覺處理。

不過,這個框架有超過兩百個圖像濾鏡,在照片 App、或是機器學習模型訓練的資料增強上 (data augmentation) 使用起來也非常方便。

但更重要的是,要為傳給 Vision 框架做分析的圖像做前處理時,Core Image 就大派用場了。

如果你已經看過 WWDC 2020 電腦影像 APIs 的影片,就會看到在演示偵測打孔卡輪廓時,Apple 就利用了 Core Image 的 monochrome 濾鏡為圖像做前處理。

在我們的範例中,要在硬幣上遮罩,使用 monochrome 效果的結果不是很好。特別是對於硬幣這種顏色相似、但又跟背景顏色不同的情況,使用黑白色彩濾鏡會是更好的選擇。

pre-processing

對於上面所使用的前處理型別,我們也設定了高斯濾鏡 (Gaussian filter) 讓圖像更平滑。你可以注意到,使用 monochrome 前處理濾鏡,其實會讓我們得到更多的輪廓。

因此,在進行前處理時,一定要注意需要處理的圖像類型。

經過前處理後,我們把得到的 outputImage 傳給視覺影像請求。GitHub Repository 上有完整的原始碼,你可以在當中找到建立及套用 Core Image 濾鏡的程式碼片段。

分析輪廓

我們可以利用 VNGeometryUtils 類別,來觀察不同的參數,像是輪廓直徑、邊界圓 (Bounding Circle)、面積跟周長、以及長寬比例。我們只需要將要觀察的輪廓物件傳入:

VNGeometryUtils.boundingCircle(for: VNContour)

就可以在一個圖像當中,分辨不同種類的形狀,這開啟了更多新電腦視覺處理的可能性。

更進一步,我們可以在 VNContour 中呼叫 polygonApproximation(withEpsilon:) 方法,將邊緣附近的小雜訊過濾掉來簡化輪廓。

結論

電腦視覺處理在 Apple 未來的混合實境中扮演非常重要的角色,引入了 ARKit 框架的手部和身體姿勢的 API 後,將為建立智慧電腦視覺處理 App 開啟新的可能性。

WWDC 2020 還有很多令人興奮的內容。對於在手機上有機器學習的新可能,我覺得非常興奮。敬請期待之後的更新,謝謝你的閱讀。

本篇原文(標題:New in iOS 14: Vision Contour Detection)刊登於作者的 Medium,由 Anupam Chugh 所著,並授權翻譯及轉載。

作者簡介:Anupam Chugh,深入探索 ML 及 AR 的 iOS Developer。喜愛撰寫關於想法、科技、與程式碼的文章。歡迎到我的 Blog 閱讀更多文章,或在 LinkedIn 上關注我。

譯者簡介:周竑翊 – 只待過新創公司的 iOS 開發者,本職是兩個兒子的爸。有空的時間喜歡看看新的技術跟科技時事。用 Playground 寫寫 Swift,但是 side project 仍然難產。其他興趣喜歡攝影、運動及看電影。歡迎寄信與我聯絡:[email protected]


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This