🕊️

【SwiftUI】VisionKitで認識してみる

2023/05/11に公開

https://developer.apple.com/documentation/visionkit

はじめに

VisionKitをインポートします。
画像などからテキストやバーコード、顔などを認識することができる Visionもありますが、今回はVisionKit を使用します。

import VisionKit

Visionについてはこちらで扱っているので参考にもらえると嬉しいです。
https://zenn.dev/tomo_devl/articles/6c4bf2f718dc9f

実際に試してみる

1. ライブビデオから認識

https://developer.apple.com/documentation/visionkit/datascannerviewcontroller

 
まず、カメラの使用許可を表示するために、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. 画像から認識

https://developer.apple.com/documentation/visionkit/imageanalysisinteraction

@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で公開しています。参考にしてもらえたら嬉しいです。

https://github.com/Tomo-devel/RecognisingObjectsFromImages

Discussion