🍃
【Flutter】OCRを使ってみた
はじめに
今回は、FlutterとML Kitを用いて、OCRアプリを作ってみたいと思います。
手順
1. Flutterプロジェクトを作成
flutter create <プロジェクト名>
2. packageをダウンロード
flutter pub add camera google_mlkit_text_recognition permission_handler
以下がpackageのサイトです。
- camera | Flutter package
- google_mlkit_text_recognition | Flutter package
- permission_handler | Flutter package
3. 一旦、コードをシンプルにしておく
「デモアプリ」と表示されるだけのアプリです。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text(
'デモアプリ',
style: TextStyle(fontSize: 24),
),
),
),
);
}
}
4. AndroidManifest.xml
<manifest>
の下に以下を記述してください。
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
5. コードを書く
- main.dart(編集)
- result_screen.dart(新しく作成)
main.dart
なお、<プロジェクト名>
っていうところは、自身で作成したプロジェクト名に変えてください。
import 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart'; import 'package:<プロジェクト名>/result_screen.dart'; import 'package:permission_handler/permission_handler.dart'; void main() { runApp(const App()); } class App extends StatelessWidget { const App({super.key}); Widget build(BuildContext context) { return MaterialApp( title: 'Text Recognition Flutter', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MainScreen(), ); } } class MainScreen extends StatefulWidget { const MainScreen({super.key}); State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver { bool _isPermissionGranted = false; late final Future<void> _future; CameraController? _cameraController; final textRecognizer = TextRecognizer(); void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _future = _requestCameraPermission(); } void dispose() { WidgetsBinding.instance.removeObserver(this); _stopCamera(); textRecognizer.close(); super.dispose(); } void didChangeAppLifecycleState(AppLifecycleState state) { if (_cameraController == null || !_cameraController!.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { _stopCamera(); } else if (state == AppLifecycleState.resumed && _cameraController != null && _cameraController!.value.isInitialized) { _startCamera(); } } Widget build(BuildContext context) { return FutureBuilder( future: _future, builder: (context, snapshot) { return Stack( children: [ if (_isPermissionGranted) FutureBuilder<List<CameraDescription>>( future: availableCameras(), builder: (context, snapshot) { if (snapshot.hasData) { _initCameraController(snapshot.data!); return Center(child: CameraPreview(_cameraController!)); } else { return const LinearProgressIndicator(); } }, ), Scaffold( appBar: AppBar( title: const Text('Text Recognition Sample'), ), backgroundColor: _isPermissionGranted ? Colors.transparent : null, body: _isPermissionGranted ? Column( children: [ Expanded( child: Container(), ), Container( padding: const EdgeInsets.only(bottom: 30.0), child: Center( child: ElevatedButton( onPressed: _scanImage, child: const Text('Scan text'), ), ), ), ], ) : Center( child: Container( padding: const EdgeInsets.only(left: 24.0, right: 24.0), child: const Text( 'Camera permission denied', textAlign: TextAlign.center, ), ), ), ), ], ); }, ); } Future<void> _requestCameraPermission() async { final status = await Permission.camera.request(); _isPermissionGranted = status == PermissionStatus.granted; } void _startCamera() { if (_cameraController != null) { _cameraSelected(_cameraController!.description); } } void _stopCamera() { if (_cameraController != null) { _cameraController?.dispose(); } } void _initCameraController(List<CameraDescription> cameras) { if (_cameraController != null) { return; } // 最初のリアカメラを選択します CameraDescription? camera; for (var i = 0; i < cameras.length; i++) { final CameraDescription current = cameras[i]; if (current.lensDirection == CameraLensDirection.back) { camera = current; break; } } if (camera != null) { _cameraSelected(camera); } } Future<void> _cameraSelected(CameraDescription camera) async { _cameraController = CameraController( camera, ResolutionPreset.max, enableAudio: false, ); await _cameraController!.initialize(); await _cameraController!.setFlashMode(FlashMode.off); if (!mounted) { return; } setState(() {}); } Future<void> _scanImage() async { if (_cameraController == null) return; final navigator = Navigator.of(context); try { final pictureFile = await _cameraController!.takePicture(); final file = File(pictureFile.path); final inputImage = InputImage.fromFile(file); final recognizedText = await textRecognizer.processImage(inputImage); // 撮影後、画面遷移して次のページへ値を渡す await navigator.push( MaterialPageRoute( builder: (BuildContext context) => ResultScreen(text: recognizedText.text), ), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('An error occurred when scanning text'), ), ); } } }
result_screen.dart
import 'package:flutter/material.dart'; class ResultScreen extends StatelessWidget { final String text;// カメラのテキスト情報を受け取るコンストラクタ const ResultScreen({super.key, required this.text}); Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: const Text('Result'), ), body: Container( padding: const EdgeInsets.all(30.0), child: Text(text), ), ); }
6. 実行結果
以下のように、OCRを使ってみることができました!
終わりに
今回は、Flutterで、OCRを使ってみました。
ほぼコピペだったので、Flutterについて理解を深めて、コードの意味だったり、もう少し詳しく理解していきたいと思いました。
また、日本語では上手く読み込まれないので、日本語対応もしてみたいと思いました。
参考
引用
Discussion
Thank you Mr. haru.
Thank you for letting me use your article as a reference.
It was very helpful!