【SwiftUI】VisionKitで認識してみる
はじめに
VisionKitをインポートします。
画像などからテキストやバーコード、顔などを認識することができる Visionもありますが、今回はVisionKit を使用します。
import VisionKit
Visionについてはこちらで扱っているので参考にもらえると嬉しいです。
実際に試してみる
1. ライブビデオから認識
まず、カメラの使用許可を表示するために、info.plistにKeyとValueを追加します。
// key
Privacy - Camera Usage Description
// value
何に使うかをかく。
struct DataScanner: UIViewControllerRepresentable {
@Binding var isScanning: Bool
func makeUIViewController(context: Context) -> DataScannerViewController {
let controller = DataScannerViewController(recognizedDataTypes: [.text(), .barcode()],
qualityLevel: .balanced,
recognizesMultipleItems: true,
isPinchToZoomEnabled: true,
isHighlightingEnabled: true)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
if isScanning {
do {
try uiViewController.startScanning()
} catch(let error) {
print(error)
}
} else {
uiViewController.stopScanning()
}
}
}
DataScannerViewController
は、カメラのライブビデオをスキャンし、テキストやテキスト内のデータ、機械読み取り可能なコード(QRとか)を読みとります。
スキャナを作成する際に、パラメータを設定することができます。
recognizedDataTypes
スキャナに認識させたいデータの種類を選択します。
.text()、.barcode() どちらかにする場合は配列にする必要はないです。
qualityLevel
スキャンする際の精度と速さの優先順位を選択します。
.balanced:中間をとります。
.fast :認識速度を優先させる代わりに精度が落ちます。
.accurate :精度を優先させる代わりに速度が落ちます。
recognizesMultipleItems
スキャナがライブビデオ内の全てのデータを認識させるか Bool です。
isHighFrameRateTrackingEnabled
スキャナが認識したアイテムのジオメトリを更新する頻度を決定する Bool です。
isPinchToZoomEnabled
2本指のズームジェスチャーを使用できるようにするかの Bool です。
isGuidanceEnabled
アイテムを選択する際にスキャナがユーザーにヘルプを提供するかどうかの Bool です。
isHighlightingEnabled
スキャナが認識されたアイテムの周りをハイライトさせるかどうかの Bool です。
ライブビデオのスキャンを開始させるときは、startScanning()
を使用し、停止させる時は、stopScanning()
を使用します。
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, DataScannerViewControllerDelegate {
var parent: DataScanner
init(_ parent: DataScanner) {
self.parent = parent
}
func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
switch item {
case .text(let text):
print(text.transcript)
case .barcode(let barcode):
if let code = barcode.payloadStringValue {
print(code)
}
default:
break
}
}
}
Coordinatorを作ります。
認識された際に実行される処理を記述していきます。
dataScanner(_:didAdd:allItems:)
スキャナがアイテムを認識し始めたときに処理。
dataScanner(_:didUpdate:allItems:)
スキャナが認識するアイテムのジオメトリを更新するときに処理。
dataScanner(_:didRemove:allItems:)
スキャナがアイテムの認識を停止したときに処理。
など、色々ありますが今回は
dataScanner(_:didTapOn:)
... ユーザがスキャナが認識するアイテムをタップしたときに処理。
を例に使っています。
全体のコード
ライブビデオから認識
struct DataScanner: UIViewControllerRepresentable {
@Binding var isScanning: Bool
func makeUIViewController(context: Context) -> DataScannerViewController {
let controller = DataScannerViewController(recognizedDataTypes: [.text(), .barcode()],
qualityLevel: .balanced,
recognizesMultipleItems: true,
isPinchToZoomEnabled: true,
isHighlightingEnabled: true)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
if isScanning {
do {
try uiViewController.startScanning()
} catch(let error) {
print(error)
}
} else {
uiViewController.stopScanning()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, DataScannerViewControllerDelegate {
var parent: DataScanner
init(_ parent: DataScanner) {
self.parent = parent
}
func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) {
switch item {
case .text(let text):
print(text.transcript)
case .barcode(let barcode):
if let code = barcode.payloadStringValue {
parent.code = code
print(code)
}
default:
break
}
}
func dataScanner(_ dataScanner: DataScannerViewController, becameUnavailableWithError error: DataScannerViewController.ScanningUnavailable) {
switch error {
case .cameraRestricted:
print("カメラの使用を許可してください。")
case .unsupported:
print("このデバイスをはサポートされていません。")
default:
print(error.localizedDescription)
}
}
}
}
2. 画像から認識
@MainActor
struct LiveTextView: UIViewRepresentable {
let image: UIImage
let analyzerConfiguration: ImageAnalyzer.Configuration
let imageView = LiveTextUIImageView()
let interaction = ImageAnalysisInteraction()
func makeUIView(context: Context) -> UIImageView {
imageView.image = image
imageView.addInteraction(interaction)
imageView.contentMode = .scaleAspectFit
return imageView
}
func updateUIView(_ uiView: UIImageView, context: Context) {
let analyzer = ImageAnalyzer()
Task {
if let image = imageView.image {
let analysis = try? await analyzer.analyze(image, configuration: analyzerConfiguration)
if let analysis = analysis {
interaction.analysis = analysis
interaction.preferredInteractionTypes = .automatic
interaction.selectableItemsHighlighted = true
interaction.allowLongPressForDataDetectorsInTextMode = true
}
}
}
}
}
class LiveTextUIImageView: UIImageView {
override var intrinsicContentSize: CGSize {
.zero
}
}
ImageAnalysisInteraction()
は、Analyzerが画像から検出したアイテムに対して、ユーザーがライブテキストアクションを実行できるようにするものです。
色々カスタマイズできますが、今回は
preferredInteractionTypes ... ユーザーが画像で実行できるインタラクションの種類。
.automatic … 画像内の全てに適したものタイプ。
.textSelection … テキストを認識するのに適したタイプ。
.dataDetectors … URL、電子メールアドレス、物理アドレスに適したタイプ。
selectableItemsHighlighted … 認識できたものをハイライトするかどうかの Bool 。
allowLongPressForDataDetectorsInTextMode … 長押ししたものの認識を試すかどうかのBool 。
を使用しています。
ImageAnalyzer()
は、テキストやQRコードなど、ユーザーが操作できる画像内のアイテムを検出するオブジェクトです。
デバイスがライブテキストに対応しているかは、isSupported
認識できる言語は、upportedTextRecognitionLanguages
で確認できます。
また、analyzer が async で宣言されているため Task 、await を使用しています。
final func analyze(
_ image: UIImage,
configuration: ImageAnalyzer.Configuration
) async throws -> ImageAnalysis
最後に
すごく精度が高く、作っていてとても楽しかったです。
サンプルアプリをGithubで公開しています。参考にしてもらえたら嬉しいです。
Discussion