iOSで画像から日本語テキストを読み取る(VNRecognizeTextRequest)

# はじめに

近年、ChatGPT等の盛り上がりにより、テキスト処理の適用範囲が広がっていると思います。
本記事では、iOSのVisionフレームワークを使用して、画像から日本語テキストを抽出する方法を紹介します。VNRecognizeTextRequestは、強力なテキスト認識機能を提供し、多言語のサポートを備えています。

# 事前に用意するもの

- Xcodeの最新バージョン
- iOSデバイスまたはiOSシミュレータ
- 基本的なSwiftとiOS開発の知識

# 実装の流れ

## Visionフレームワークのインポート
```
import Vision
```

## テキスト識別リクエストを生成
画像からテキストを識別し、その結果を返すためのリクエストオブジェクト(VNRecognizeTextRequest)を生成します。
この段階ではまだ識別処理は実行されません。
```
let request = VNRecognizeTextRequest { (request, error) in
    guard let observations = request.results as? [VNRecognizedTextObservation] else {
        return
    }
    // テキスト処理
    for observation in observations {
        // 最も可能性の高いテキスト候補を取得
        if let topCandidate = observation.topCandidates(1).first {
            print("見つかったテキスト: \(topCandidate.string)")
        }
    }
}

// 日本語の認識を指定
request.recognitionLanguages = ["ja"]
```
画像から検出されたテキストはrequest.resultsに格納されます。
observationオブジェクト(VNRecognizedTextObservation)は画像内の個別のテキスト領域に関する情報を表します。
observation.topCandidates(1).firstは最も可能性の高いテキスト候補になります。

## リクエストの実行
カメラやアルバム等から取得した画像に対してテキスト識別を実施します
```
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try? handler.perform([request])
```
うまくテキストが抽出されたら先ほど紹介したVNRecognizeTextRequest内のクロージャーが処理されます。

# サンプルコード
```
import Foundation
import SwiftUI
import Vision

class OcrManager: ObservableObject {
    
    @Published var recognizedStrings: [String] = []
    @Published var joinedString = ""
    
    func textRecognition(image: UIImage) {
        let request = VNRecognizeTextRequest { (request, error) in
            guard let observations = request.results as? [VNRecognizedTextObservation] else {
                return
            }
            DispatchQueue.main.async {
                // テキスト処理
                self.recognizedStrings = observations.compactMap { observation in
                    observation.topCandidates(1).first?.string
                }
                self.joinedString = self.recognizedStrings.joined(separator: " ")
                print(self.recognizedStrings)
            }
        }
        
        request.recognitionLanguages = ["ja-JP"]
        
        guard let cgImage = image.cgImage else {
            return
        }
        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        DispatchQueue.global().async {
            do {
                try handler.perform([request])
                    
            } catch {
                print(error)
            }
        }
    }
}
```

SwiftUIからの呼び出し例
```
import SwiftUI

struct ContentView: View {
    
    @ObservedObject var ocrManager = OcrManager()
    @State var image = UIImage(named: "image_sample")

    var body: some View {
        VStack {
            Image(uiImage: image!)
                .resizable()
                .scaledToFit()
            Text("抽出結果")
            Text(ocrManager.joinedString)
        }
        .onAppear() {
            ocrManager.textRecognition(image: image!)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

# 実行結果
## サンプル画像
![image_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29418/507d791d-ca37-ede8-fe22-9949e2842911.png)

## iPhoneアプリ
![ios_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29418/42d1c581-509f-b957-ef5b-fb4507ab35d3.png)

## VNRecognizeTextRequestから抽出されたテキスト
```
["昔々、ある所に", "お爺さんとお婆さんが", "住んでいました。"]
```