LoginSignup
2
0

【Flutter】google_mlkit_text_recognitionを使ってレシートを読み取る

Posted at

はじめに

今回は、google_mlkit_text_recognitionを使ってレシートを読み取る方法を紹介します。

開発環境

  • Flutter 3.7.11
  • Dart 2.19.6
  • camera 0.10.5+5
  • google_mlkit_text_recognition 0.11.0

開発手順

1. ライブラリのインストール

文字認識をするパッケージを追加します。

カメラを使うために、こちらのパッケージを追加します。

ターミナルに以下のコマンドを使い、ライブラリを追加する。

flutter pub add google_mlkit_text_recognition camera

2. Androidの設定

まずは、Androidの設定をしていきます。
android/app/build.gradleを設定していきます。

android {

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.flutter_ocr_app"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
-       minSdkVersion flutter.minSdkVersion
-       targetSdkVersion flutter.minSdkVersion
+       minSdkVersion 22
+       targetSdkVersion 32
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
    
}

google_mlkit_text_recognitionでは、ラテン文字の認識のみをサポートします。そのため以下を追加して日本語を認識できるようにする必要があります。その他言語も同様

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+   implementation "com.google.mlkit:text-recognition-japanese:16.0.0"
}

Androidでカメラを使用するためにAndroidManifest.xmlに権限を追加します。(android/app/src/main/AndroidManifest.xml)

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_ocr_app">

    <!-- この下の1行追加 -->
+  <uses-permission android:name="android.permission.CAMERA" />

   <application
        android:label="flutter_ocr_app"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        
    ~~~~~~~~~~~ 中略 ~~~~~~~~~~~

        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

3. iOSの設定

iOSでカメラを使うために、info.plistにアクセス権限許可の追加します。(ios/Runner/Info.plist)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	~~~~~~~~~~~ 中略 ~~~~~~~~~~~
+	<key>NSCameraUsageDescription</key>
+	<string>your usage description here</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>your usage description here</string>
</dict>
</plist>

google_mlkit_text_recognitionの他言語の認識のために以下をios/Podfileに追加

pod 'GoogleMLKit/TextRecognitionJapanese', '~> 4.0.0'

4. 機能の実装

main.dart
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ocr_app/camera_page.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final cameras = await availableCameras();
  final firstCamera = cameras.first;

  runApp(MyApp(camera: firstCamera));
}

class MyApp extends StatelessWidget {
  const MyApp({
    super.key,
    required this.camera,
  });

  final CameraDescription camera;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CameraPage(camera: camera),
    );
  }
}
camera_page.dart
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ocr_app/result_page.dart';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';

class CameraPage extends StatefulWidget {
  const CameraPage({
    super.key,
    required this.camera,
  });

  final CameraDescription camera;

  @override
  State<CameraPage> createState() => _CameraPageState();
}

class _CameraPageState extends State<CameraPage> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;

  final textRecognizer = TextRecognizer(script: TextRecognitionScript.japanese);

  @override
  void initState() {
    super.initState();

    // カメラの設定
    _controller = CameraController(
      widget.camera,
      ResolutionPreset.max,
    );

    // コントローラーを初期化
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    _controller.dispose();
    textRecognizer.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // FutureBuilder で初期化を待ってからプレビューを表示(それまではインジケータを表示)
    return Scaffold(
      appBar: AppBar(
        title: const Text('レシートを読み取って下さい'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(48.0),
        child: Center(
          child: FutureBuilder<void>(
            future: _initializeControllerFuture,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                return CameraPreview(_controller);
              } else {
                return const Center(child: CircularProgressIndicator());
              }
            },
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          try {
            // 写真を撮る
            final image = await _controller.takePicture();

            final file = File(image.path);

            final inputImage = InputImage.fromFile(file);

            final recognizedText =
                await textRecognizer.processImage(inputImage);

            await Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => ResultPage(text: recognizedText.text),
                fullscreenDialog: true,
              ),
            );
          } catch (e) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('失敗'),
              ),
            );
          }
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}

レシート撮影後、画面遷移し抽出したTextを表示します。

result_page.dart
import 'package:flutter/material.dart';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';

class ResultPage extends StatelessWidget {
  final RecognizedText recognizedText;

  const ResultPage({super.key, required this.recognizedText});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('結果'),
      ),
      body: Container(
        padding: const EdgeInsets.all(30.0),
        child: Text(recognizedText.text),
      ),
    );
  }
}

実際に動かした時にスクショ

Screenshot_20231224_021421_Gallery.jpg Screenshot_20231224_021409_Gallery.jpg

正しく抽出できていない箇所が少しありますが、基本的には認識できています。
レシートの情報としては、加工の過程を省いているので、これからの加工の仕方で、使えるデータになってくると思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0