💌

iOSで文字認識(Text Recognition)

2023/11/11に公開

iOS 13以降で、待望だった「文字認識」機能が使えるようになりました。カメラなどで撮影した画像内にある文字を読み取る [1] ことができます。

iOS 9からあった「文字検出」との違い

文字認識は、Visionフレームワークの一機能として追加されました。

一方、Core ImageのCIDetectorというクラスでは、CIDetectorTypeTextというタイプを指定でき、テキストを検出することができます。
このCIDetectorTypeTextCIFeatureTypeTextはiOS 9からあるものです。

しかしこちらは文字の「領域」を検出する機能です。何が書いてあるか、までは認識できませんでした。

そこで今まではTesseract[2]というオープンソースのOCRエンジンや、SwiftOCR[3]という(おそらく個人がメンテしている)OSSしか選択肢がなかったのですが、ついに標準で文字認識機能が提供されるようになったというわけです。

対応言語

サポートしている言語の一覧を次のメソッドを呼ぶことで取得できます。

class func supportedRecognitionLanguages(
    for recognitionLevel: VNRequestTextRecognitionLevel, 
    revision requestRevision: Int) throws -> [String]

第1引数にはVNRequestTextRecognitionLevel型で精度優先(.accurate)か速度優先(.fast)かを指定できます。

これを用いて次のようにサポート言語一覧をprintする処理を書き、

let accurateLangs = 
    try! VNRecognizeTextRequest.supportedRecognitionLanguages(
        for: .accurate, 
        revision: revision)
print("accurateLangs:\(accurateLangs)")

let fastLangs = 
    try! VNRecognizeTextRequest.supportedRecognitionLanguages(
        for: .fast, 
        revision: revision)
print("fastLangs:\(fastLangs)")

VNRecognizeTextRequestRevision1 でのサポート言語一覧

iOS 13.1 Beta 3が入ったiPhone XSで動かしてみると、次のような結果となりました。

accurateLangs:["en-US"]

fastLangs:["en-US"]

米国の英語のみサポートされているようです。

しかし数字やアルファベットは日本語においても多く用いますし、電話番号や郵便番号、クレジットカードの番号、メールアドレス等、用途はいくらでも考えられます。日本語サポートを期待しつつ、こういう機能が存在することと、使い方ぐらいは把握しておいて損はないでしょう。
(後述の通りiOS 16以降で現在利用可能なリビジョンでは日本語もサポートされています)

VNRecognizeTextRequestRevision2 でのサポート言語一覧

iOS 14.0より、VNRecognizeTextRequestRevision2 が利用可能になりました。

サポート言語一覧を出力してみます。

if #available(iOS 14.0, *) {
    let accurateLangs = try! VNRecognizeTextRequest.supportedRecognitionLanguages(for: .accurate, revision: VNRecognizeTextRequestRevision2)
    print("accurateLangs:\(accurateLangs)")

    let fastLangs = try! VNRecognizeTextRequest.supportedRecognitionLanguages(for: .fast, revision: VNRecognizeTextRequestRevision2)
    print("fastLangs:\(fastLangs)")
}

accurateLangs:["en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant"]

fastLangs:["en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR"]

大幅に対応言語が増えています。

VNRecognizeTextRequestRevision3 でのサポート言語一覧

iOS 16.0より、VNRecognizeTextRequestRevision3 が利用可能になりました。

サポート言語一覧を出力してみます。

if #available(iOS 16.0, *) {
    let accurateLangs = try! VNRecognizeTextRequest.supportedRecognitionLanguages(for: .accurate, revision: VNRecognizeTextRequestRevision3)
    print("accurateLangs:\(accurateLangs)")

    let fastLangs = try! VNRecognizeTextRequest.supportedRecognitionLanguages(for: .fast, revision: VNRecognizeTextRequestRevision3)
    print("fastLangs:\(fastLangs)")
}

accurateLangs:["en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR", "zh-Hans", "zh-Hant", "yue-Hans", "yue-Hant", "ko-KR", "ja-JP", "ru-RU", "uk-UA", "th-TH", "vi-VT"]

fastLangs:["en-US", "fr-FR", "it-IT", "de-DE", "es-ES", "pt-BR"]

.accurate では日本語もサポートされました🎉

.fast の対応言語は増えていませんね。

文字認識の最小実装

ここでは文字認識の最小実装を示します。なお、入力画像をどう渡すかといった従来からある機能を使った実装部分は省略し、ここでは新APIの使い方に関わる部分だけを抜粋します。

文字認識機能はVisionフレームワークに追加されたものです。まず、文字認識を実装するクラスでVisionをimportしておきます。

import Vision

1. リクエストを作成する

まず、VNRecognizeTextRequestオブジェクトを初期化します。VNRecognizeTextRequestクラスはVNRequestを継承しており、次のようなイニシャライザを持ちます。引数には認識完了後の処理ブロックを渡します。

init(completionHandler: VNRequestCompletionHandler? = nil)

認識完了後の処理は後のステップで実装することにして、ここでは次のように初期化だけしておきます。

let request = VNRecognizeTextRequest { [weak self] (request, error) in
    // 認識完了後の処理
    ...
}

2. リクエストハンドラを作成する

認識対象となる画像データをイニシャライザに渡してVNImageRequestHandlerオブジェクトを作成します。引数には1で作成したリクエストオブジェクトを配列で渡します。

let requestHandler = VNImageRequestHandler(
    cvPixelBuffer: pixelBuffer, 
    orientation: CGImagePropertyOrientation.up, 
    options: [:])

なお、VNImageRequestHandler自体はiOS 11でVisionフレームワークが新規追加されたときから存在する、画像を扱うリクエストをハンドルするためのクラスなので、本手順は従来通りです。

3. 実行する

VNImageRequestHandlerperformメソッドを呼んで認識を実行します。

try requestHandler.perform([request])

以上の実装で文字認識が実行できるようになります。

4. 認識結果を処理する

文字認識が完了すると、VNRecognizeTextRequestのイニシャライザのcompletionHandler引数に渡した処理ブロック(型はVNRequestCompletionHandler)が呼ばれます。ブロックの引数には、リクエストオブジェクト自身(ここではVNRecognizeTextRequestオブジェクト)と、Errorオブジェクトが入ってきます。

VNRecognizeTextRequestresultsプロパティからはVNRecognizedTextObservationオブジェクトの配列が得られます。

そしてVNRecognizedTextObservationクラスのtopCandidates(_:)メソッドから引数に渡した数の認識結果の候補(型はVNRecognizedText)が得られ、

func topCandidates(_ maxCandidateCount: Int) -> [VNRecognizedText]

VNRecognizedTextstringプロパティから認識結果の文字列と、confidenceプロパティから認識結果の信頼度が得られます。

let request = VNRecognizeTextRequest { [weak self] (request, error) in
    // --- 認識完了後の処理 ---
    
    // VNRecognizedTextObservationの配列が得られる
    guard let results = request.results as? [VNRecognizedTextObservation]
        else { return }
    
    for textObservation in results {
    
        // numCandidates個のVNRecognizedTextを取得
        let candidates = textObservation.topCandidates(self.numCandidates)
        
        // 結果の文字列と信頼度を出力
        for recognizedText in candidates {
            let confidence = recognizedText.confidence*100
            print("\(recognizedText.string), \(confidence)%")
        }
    }
}

scale=0.37
認識結果を表示

またVNRecognizedTextObservationVNRectangleObservationを継承しており、認識結果の文字列を囲む矩形の4頂点がプロパティから得られます。

var topLeft: CGPoint { get }        // 左上
var topRight: CGPoint { get }       // 右上
var bottomLeft: CGPoint { get }     // 左下
var bottomRight: CGPoint { get }    // 右下

なお、VNRectangleObservationはiOS 11からあるAPIです。矩形をCGRectではなく4つのCGPoint型で規定する理由は、この矩形(ここでは文字領域を囲む矩形)がスクリーンに対して辺が平行とは限らないので、原点からの幅・高さだけで表現できないためです。

文字認識のカスタマイズ

VNRecognizeTextRequestの各種プロパティを使用して、文字認識をカスタマイズすることができます。以下で解説していきます。

recognitionLanguages

認識対象の言語を文字列で指定します。

var recognitionLanguages: [String]

前述の通り、今のところは"en-US"しかないため、指定する必要はありません。

customWords

認識対象となる単語を追加できます。

var customWords: [String]

固有名詞等を登録しておくことが考えられます。

regionOfInterest

渡す画像内における認識対象とする範囲をCGRect型で指定します。

var regionOfInterest: CGRect

ただし、左下を原点とし、値は0-1に正規化したものを使用します。

本プロパティは厳密にはVNRecognizeTextRequestの親クラスであるVNImageBasedRequestに定義されており、新しいものではないのですが、文字認識においては重要な役割を担います。

認識対象領域を制限することはパフォーマンスを大きく向上させます。画像分類等と比べて、文字認識は画像全体を見る必要がない場面が多いため、本プロパティを使用する場面は多くなるでしょう。

scale=0.4
WWDC19でのデモ。認識対象領域を絞っている

minimumTextHeight

認識対象とするテキストの最小の高さを指定します。すなわちここで指定した高さよりも小さいテキストは認識されません。

var minimumTextHeight: Float

とはいっても物理的なサイズを指定するのではなく、画像の高さを1.0として、認識対象とするテキストの相対的な高さを指定します。デフォルトは0.0(つまりどんな高さのテキストも認識対象とする)となっています。

usesLanguageCorrection

文章の修正を行うかをBoolで指定します。

var usesLanguageCorrection: Bool

デフォルトはtrueですが、これをfalseにすると修正が行われない生の認識結果が得られる代わりに、パフォーマンスは向上します。電話番号の認識のように、言語的な修正が必要ない場合はfalseをセットしておいたほうが良いでしょう。

recognitionLevel

認識を速度優先で行うか、精度優先で行うかをVNRequestTextRecognitionLevel型で指定できます。

var recognitionLevel: VNRequestTextRecognitionLevel

VNRequestTextRecognitionLevelはenum型で、accurate(精度優先)とfast(速度優先)が定義されています。

WWDC19のセッション[4]では次のような比較表が公開されており、


fastとaccurateの比較

こちらの表によると、Fastはリアルタイム用、Accurateは非同期処理用で、メモリ使用量も違うようです。またAccurateは精度だけでなく、サポートするフォントの種類も多く、文字列の回転にもロバストなようです。

また同セッションによるとAccurateの場合はニューラルネットワークベースで文字検出・認識が行われるようです。

脚注
  1. こういった機能はOCR(Optical Character Recognition)とも呼ばれます。 ↩︎

  2. https://github.com/tesseract-ocr/tesseract ↩︎

  3. https://github.com/garnele007/SwiftOCR ↩︎

  4. Text Recognition in Vision Framework: https://developer.apple.com/videos/play/wwdc2019/234/ ↩︎

Discussion