iOSで文字認識(Text Recognition)
iOS 13以降で、待望だった「文字認識」機能が使えるようになりました。カメラなどで撮影した画像内にある文字を読み取る [1] ことができます。
「文字検出」との違い
文字認識は、Visionフレームワークの一機能として追加されました。
一方、Core ImageのCIDetector
というクラスでは、CIDetectorTypeText
というタイプを指定でき、テキストを検出することができます。
このCIDetectorTypeText
やCIFeatureTypeText
はiOS 9からあるものです。
しかしこちらは文字の「領域」を検出する機能です。何が書いてあるか、までは認識できませんでした。
またiOS 11で登場したVisionフレームワークでは VNDetectTextRectanglesRequest
という文字領域を検出するクラスを当初から利用できましたが、これも文字の「領域」を検出する機能であり、文字の認識は行えませんでした。
そこで今までは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. 実行する
VNImageRequestHandler
のperform
メソッドを呼んで認識を実行します。
try requestHandler.perform([request])
以上の実装で文字認識が実行できるようになります。
4. 認識結果を処理する
文字認識が完了すると、VNRecognizeTextRequest
のイニシャライザのcompletionHandler
引数に渡した処理ブロック(型はVNRequestCompletionHandler
)が呼ばれます。ブロックの引数には、リクエストオブジェクト自身(ここではVNRecognizeTextRequest
オブジェクト)と、Error
オブジェクトが入ってきます。
VNRecognizeTextRequest
のresults
プロパティからはVNRecognizedTextObservation
オブジェクトの配列が得られます。
そしてVNRecognizedTextObservation
クラスのtopCandidates(_:)
メソッドから引数に渡した数の認識結果の候補(型はVNRecognizedText
)が得られ、
func topCandidates(_ maxCandidateCount: Int) -> [VNRecognizedText]
VNRecognizedText
のstring
プロパティから認識結果の文字列と、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)%")
}
}
}
認識結果を表示
またVNRecognizedTextObservation
はVNRectangleObservation
を継承しており、認識結果の文字列を囲む矩形の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]
customWords
認識対象となる単語を追加できます。
var customWords: [String]
固有名詞等を登録しておくことが考えられます。
regionOfInterest
渡す画像内における認識対象とする範囲をCGRect
型で指定します。
var regionOfInterest: CGRect
ただし、左下を原点とし、値は0-1に正規化したものを使用します。
本プロパティは厳密にはVNRecognizeTextRequest
の親クラスであるVNImageBasedRequest
に定義されており、新しいものではないのですが、文字認識においては重要な役割を担います。
認識対象領域を制限することはパフォーマンスを大きく向上させます。画像分類等と比べて、文字認識は画像全体を見る必要がない場面が多いため、本プロパティを使用する場面は多くなるでしょう。
WWDC19でのデモ。認識対象領域を絞っている
minimumTextHeight
認識対象とするテキストの最小の高さを指定します。すなわちここで指定した高さよりも小さいテキストは認識されません。
var minimumTextHeight: Float
とはいっても物理的なサイズを指定するのではなく、画像の高さを1.0
として、認識対象とするテキストの相対的な高さを指定します。例えば、認識を画像の高さの半分のテキストに制限するには、0.5を使用します。
サイズを大きくすると、最小の高さより小さいテキストは無視されるというトレードオフがありますが、メモリ消費量が減り、認識が速くなります。
デフォルトは0.0
(つまりどんな高さのテキストも認識対象とする)となっています。
現在のデフォルト値は 1/32
、つまり 0.03125
です。
usesLanguageCorrection
文章の修正を行うかをBoolで指定します。
var usesLanguageCorrection: Bool
デフォルトはtrue
ですが、これをfalse
にすると修正が行われない生の認識結果が得られる代わりに、パフォーマンスは向上します。電話番号の認識のように、言語的な修正が必要ない場合はfalse
をセットしておいたほうが良いでしょう。
recognitionLevel
認識を速度優先で行うか、精度優先で行うかをVNRequestTextRecognitionLevel
型で指定できます。
var recognitionLevel: VNRequestTextRecognitionLevel
VNRequestTextRecognitionLevel
はenum型で、accurate
(精度優先)とfast
(速度優先)が定義されています。
デフォルト値についてはドキュメントでは明記されていませんでしたが(2024年5月現在)、出力してみたところ accurate
でした。
WWDC19のセッション[4]では次のような比較表が公開されており、
fastとaccurateの比較
こちらの表によると、Fastはリアルタイム用、Accurateは非同期処理用で、メモリ使用量も違うようです。またAccurateは精度だけでなく、サポートするフォントの種類も多く、文字列の回転にもロバストなようです。
また同セッションによるとAccurateの場合はニューラルネットワークベースで文字検出・認識が行われるようです。
automaticallyDetectsLanguage
iOS 16で追加されたプロパティ。文字認識と言語修正(Language Correction)を行う適切なモデルを使用するために、言語の検出を試みるかをブール値で指定できます。
var automaticallyDetectsLanguage: Bool
デフォルト値についてはドキュメントには明記されていませんでしたが(2024年5月現在)、出力してみたところ false
でした。
細かい話
長くなってしまったので別記事に移行しました。
-
VNTextRecognitionRequest (文字認識)と VNDetectTextRectanglesRequest
(文字領域検出)の文字領域検出結果は同じなのか? - Visionの
VNRecognizeTextRequest
とVisionKitのImageAnalyzer
の文字認識の違い
-
こういった機能はOCR(Optical Character Recognition)とも呼ばれます。 ↩︎
-
Text Recognition in Vision Framework: https://developer.apple.com/videos/play/wwdc2019/234/ ↩︎
Discussion