😎

google_mlkit_text_recognitionで写真撮影&ライブラリから取得した画像からテキストを読み取ってみた📸(OCR)

2023/12/04に公開

ドコドア株式会社のwutchyです🐈。アドベントカレンダー初参加です。
記念すべき初投稿の記事は業務でFLutterアプリのOCR機能を実装したので、それから得た知見を書きたいと思います🙌

OCRとは?

「Optical Character Recognition」の略で、文字をイメージスキャナやデジタルカメラによって読みとってコンピュータが利用できるデジタルの文字コードに変換する技術です。

今回はFlutterアプリでカメラなどを使用して文字を読み取ってみます。

使用ライブラリ

この記事では
・カメラ機能は「camera」
https://pub.dev/packages/camera
・ライブラリ機能には「image_picker」
https://pub.dev/packages/image_picker
・OCR機能には「google_mlkit_text_recognition」を使用しています。
https://pub.dev/packages/google_mlkit_text_recognition

「google_ml_kit」なるパッケージもあるのですが、そちらはBarcode Scanning(バーコードのスキャン)Face Detection(顔の検出)などといったml_kitでできることてんこ盛りなパッケージでテキスト認識以外の不要な機能も含まれているので、必要なパッケージを選択して導入することをおすすめされています。
https://pub.dev/packages/google_ml_kit

顔検出とか面白そうなのでいつか使ってみたい。

いざ実装

iOSの準備

Podfile
// 最小のiOSバージョンを「12.0」に設定
platform :ios,'12.0'
<省略>
target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  // 言語パッケージの依存関係の追加
  pod 'GoogleMLKit/TextRecognitionJapanese', '~> 4.0.0'
  target 'RunnerTests' do
    inherit! :search_paths
  end
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    
    // 追加
    // 筆者の環境では各ビルド構成のxcconfigファイルからDT_TOOLCHAIN_DIRの文字列を検索し、TOOLCHAIN_DIRに置換している
    target.build_configurations.each do |config|
      xcconfig_path = config.base_configuration_reference.real_path
      xcconfig = File.read(xcconfig_path)
      xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
      File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
    end
  end
end

Info.plist
// アプリがカメラ・ライブラリにアクセスするための理由を記述
<key>NSPhotoLibraryUsageDescription</key>
<string>アプリに表示・使用するための写真をライブラリから選択する目的でリクエストします。</string>
<key>NSCameraUsageDescription</key>
<string>アプリに表示・使用するための写真を撮影する目的でリクエストします。</string>
<key>NSMicrophoneUsageDescription</key>
<string>アプリでカメラを起動する目的でリクエストします。</string>

androidの準備

build.gradle
android {
        // compileSdkVersionを33に指定
    compileSdkVersion 33
    <省略>
        defaultConfig {
                        <省略>
	        // 最小SDKバージョンとターゲットSDKバージョンを指定
                minSdkVersion 22 // 21以上であれば良い
                targetSdkVersion 33
	        ・・・・
            }
}
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // 言語パッケージの依存関係の追加
    implementation 'com.google.mlkit:text-recognition-japanese:16.0.0
}

カメラで撮影した画像からテキストを読み取る

takePhotoScreen
class TakePhotoScreen extends ConsumerWidget {
  const TakePhotoScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final cameraControllerAsync = ref.watch(cameraControllerInitProvider);
    final recognizeTextNotifier = ref.watch(textRecognitionProvider.notifier);
    final recognizeText = ref.watch(textRecognitionProvider);

    return Scaffold(
      body: cameraControllerAsync.when(
        data: (data) {
          return Center(
            child: Column(
              children: [
                // カメラ正常時
                if (data != null)
                  Column(
                    children: [
                      CameraPreview(data),
                      Gap(20.h),
                      FilledButton(
                        style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all(
                            AppColorSchemes.lightColorScheme.primary,
                          ),
                        ),
                        onPressed: () async {
                          try {
                            await scanImage(
                              data,
                              context,
                              recognizeTextNotifier,
                            );
                          } 
                          <省略>
                        },
                        child: const Text('テキストを読み取る'),
                      ),
                      Gap(20.h),
                      Text(
                        recognizeText ?? 'ここにテキストが表示されます。',
                        style: const TextStyle(color: Colors.black),
                      ),
                    ],
                  ),
		  <省略>

  Future<void> scanImage(
    CameraController cameraController,
    BuildContext context,
    TextRecognition recognizeTextNotifier,
  ) async {
    // 写真を撮影する
    final pictureFile = await cameraController.takePicture();
    final file = File(pictureFile.path);
    // 撮影した写真を読み込む
    final inputImage = InputImage.fromFile(file);
    // TextRecognizerの初期化(scriptで日本語の読み取りを指定しています※androidは日本語指定は失敗するのでデフォルトで使用すること)
    final textRecognizer = TextRecognizer(script: TextRecognitionScript.japanese);
    // 画像から文字を読み取る(OCR処理)
    final recognizedText = await textRecognizer.processImage(inputImage);
    recognizeTextNotifier.setRecognizedText(recognizedText.text);
  }
}

画面はこんな感じ

読み取る前

読み取り後

おお!ちゃんと読み取れていますね!!

ライブラリから読み取る

sample
// 例えばボタンを用意して...
onPressed: () async {
  // ライブラリから写真を取得
  final image = await picker.pickImage(
    source: ImageSource.gallery,
  );

  if (image == null) {
    // ユーザが選択をキャンセルした場合
    logger.i('ユーザが画像の選択をキャンセルしました');
    return;
  }
  // File型に変換
  final file = File(image.path);
  // ml-kitで画像を読み込む
  final inputImage = InputImage.fromFile(file);
  // 画像から文字を読み取る(OCR処理)
  final recognizedData =
      await textRecognizer.processImage(inputImage);
  print('OCRで取得したテキスト:${recognizedText.text}');
}

ライブラリからも問題なさそう!!まあ、肝となる処理は同じなので。

まとめ

という感じで、google_mlkit_text_recognitionでテキストを読み取ってみました。
裏で長いレシートや本を読み取ってみたのですが、日本語の文字はところどころ読めていない感じもしました。
(丸っこい文字やそれに近いフォントはあまり正確ではなかった。まだプラグインもbeta版だからか...多分)
でもラテン語であれば問題なく長文も抽出できているし、TextFormFieldに読み取った結果を表示して誤って抽出された文字を手修正などしてあげることもできるので、簡単なアプリとして作るだけなら十分OCRは機能しているかなと思います。

カメラを通して・ライブラリの画像を通して文字を取得するって、意外とワクワクしました。
今後もgoogle_mlkit_text_recognitionをチェックして、androidでも日本語が読み取れるようになったら記事を更新したいと思います。

来年はもっと奥深いところを突くような記事を書けるように頑張ります。
読んでいただきありがとうございました!

2023・12・6 追記 
日本語読み取りのサンプル

読み取れた文字
ゴシック系統は問題なさそう


読み取れた文字
POP体の中でも丸くて太めな文字はところどころ誤って認識している
「佃」はどうやら「個」として認識されていそう

Discussion