[iOS][UIKit] 實作 VisionKit 跟 Vision 來掃描圖片跟識別文字
VisionKit & Vision
在 iOS 13 中,蘋果發佈了一個新的套件,名叫 VisionKit,它讓開發者能在 App 裡利用原生的系統掃瞄器去實作文件掃描,它能有效地讓開法者去調整掃描的參數,例如:哪些物件不希望被掃瞄到或是需要倒轉\裁切,如此一來就能更自由的去將它應用到不同的產品需求上。
在 iOS 13 裡,Vision 就已經開始支援 OCR (Optical Character Recognition),也就是常被用來作偵測或是識別文件上的文字的技術。
今天這篇就來跟大家分享在 UIKit 裡面如何製作一個簡單的文件掃瞄器,之後會再有一篇 SwiftUI 的教程,有需要的朋友們可以關注一下這個部落格,謝謝!
第一步 - 建立 project
首先,因為我們只需要時做一個單純的 VisionKit + Vision 的App,因此我們在 Xcode 內建立一個 “Single View App”,並且我們也不需要引入任何第三方套件。
但有個需要特別注意的地方,因為掃瞄器在使用時是需要啟動相機的,因此我們需要在 App 裡面設定相機權限,否則你的 App 會一直爆 crash 給你看 XD,那要怎麼做呢?很簡單,只需要在 project 的 Info.plist 裡面增加一個 key 值叫做 “ NSCameraUsageDescription”,並在後面 value的部分給上權限視窗裡需要告知使用者的文字即可。
掃描文件
Delegates
使用 VisionKit 提供的 View Controller 時,我們必須讓我們自訂的 View Controller 繼承 VCDocumentCameraViewControllerDelegate 來執行使用者取消/完成掃描後獲取物件的 callbacks。
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan)- 表示使用者成功將掃描到的資料儲存成物件。
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error)- 表示掃瞄器在啟用時發生錯誤。
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController)- 表示使用者取消的掃瞄器的操作。
第二步 - 建立 View Controller
接著,我們建立剛剛上面提到的 VNDocumentCameraViewController :
let scanVC = VNDocumentCameraViewController() scanVC.delegate = self present(scanVC, animated: true)
第三步 - 繼承上 delegates
extension ViewController: VNDocumentCameraViewControllerDelegate { ... }並且對每個 delegate function 實作各自的功能,但有一點需要特別注意,就是每個callbacks 被接收之後,表示掃瞄器的功能也相應的結束了,因此我們必須也同時實作 dismiss 的部分:
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) { // available for a document only guard scan.pageCount >= 1 else { controller.dismiss(animated: true) return } // Process the scanned page scanImageView.image = scan.imageOfPage(at: 0) processImage(scan.imageOfPage(at: 0)) controller.dismiss(animated: true) }當使用者成功掃描到物件並且點擊“儲存”後,這個 callback function 會被調用。VNDocumentCameraScan 會包含掃描的物件 (圖片內的文字等),之後需要可以對物件作處理,比方說文字的截取。最後要記得 dismiss 掉 controller。
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) { //Handle properly error, you can print it or do something else controller.dismiss(animated: true) }
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) { controller.dismiss(animated: true) }
第四步 - 從物件中識別出文字
剛剛我們已經得到了我們圖片 (UIImage) 型別的物件,但我們還是沒辦法得到圖片中的文字內容,因此我們需要借助 Vision 的支援來協助識別文字。
我們會為每個圖片物件建立一個物件:VNImageRequestHandler 物件。這個物件主要是將圖片透過 Vision 建立一個請求 (request) 來執行辨識。
接者我們必須透過 VCRecognizeTextRequest ,來請求 Vision 實際執行識別動作。
以上的動作都會透過非同步的方式處理,完成後我們就會得到請求的結果:
- 識別錯誤時會回傳錯誤
- 請求成功時會回傳文字物件
這時我們就可以針對識別出的文字去做邏輯的處理了。
因此我們要先在 View Controller 一開始先創建 request 的物件,他會在每次掃描的時候被重複使用:
private var scanRequest = VNRecognizeTextRequest(completionHandler: nil)
接著實作請求的部分:
scanRequest = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
var ocrText = ""
for observation in observations {
guard let topCandidate = observation.topCandidates(1).first else { return }
ocrText += topCandidate.string + "\n"
}
DispatchQueue.main.async {
self.scanTextView.text = ocrText
self.scanButton.isEnabled = true
}
}
scanRequest.recognitionLevel = .accurate
scanRequest.recognitionLanguages = ["zh-Hant", "en-US"]
scanRequest.usesLanguageCorrection = true
}
在這裏,我們創建了一個 VNRegconizeTextRequest 包含一個 completion handler 來處理完成的請求。我們利用 VNRegconizedObservation 來響應並得到我們的文字,接著我們把得到的文字拆分並賦予在 ocrText 裏面。
第五步 - 自定義選項
- customWords:自訂文字,當你預期的文件中有特定的文字能夠被偵測到,但他可能不屬於任何語言或是詞彙時 (比方說:QQㄋㄟㄋㄟ好喝到咩噗茶),我們可以在 customWords 中加入我們希望被識別到的文字們。
textRecognitionRequest.customWords = ["Levis", "Gucci"] // An array of strings
textRecognitionRequest.minimumTextHeight = 0.03125
textRecognitionRequest.recognitionLevel = .accurate
textRecognitionRequest.recognitionLanguages = ["zh-Hant", "en-US"]
textRecognitionRequest.usesLanguageCorrection = true
第六步 - 建立 request handler
為了要正確執行 Vision 的 request,我們需要建立一個 request handler 去處理即將要被識別的圖片物件,而這個 handler 需要每次掃描時都被建立。
guard let cgImage = image.cgImage else { return } scanTextView.text = "" scanButton.isEnabled = false let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:]) do { try requestHandler.perform([self.scanRequest]) } catch { print(error) }
建立 handler 是個很瑣碎的工作,但一定要記得它需要 CGImage 、CIImage 或是 URL 來運作,因此如果你只有從 VisionKit 那邊得到的 UIImage 物件,你得用圖片的 cgImage 來進行操作。
以上就是一個簡易的 VisionKit + Vision 掃瞄器,說是簡易,但其實蠻多東西需要設定集留意的,因此可能大多數人會在實作時遇到一些問題,因此在這附上我的 Demo Project 供大家取用跟參考。
希望大家可以多多關注,如果有任何問題歡迎到我的IG私訊我或在底下留言告訴我喔!
如果想給我一些支持,也歡迎買杯咖啡給我,謝謝大家!
留言
張貼留言