Flutter : Cloud Vision API を利用して OCRを行う
アプリを作るパターンを、
- モバイル : Xamarin –> Flutter
- サービス : GAE(Spring Boot)
- バックエンド : Firebase
と考えてきていたのだけれど、余暇の作業という時間的制約が大きく、上記のすべてのパーツについて学習やらしていると年が明けてしまう。。。
本当は、中間にサービス層を置いたほうが柔軟に対応できそうとは思いつつも、Flutter + Firebase でなんとか行けそうな気がしてきた。
すべてGoogleというのが若干気になるが。
という流れで、
Flutterアプリから、データの読み書きを直接行う疎通ができた
ので、以前はサービスにやらせていたOCRの機能(GAE + Spring Boot からためした Cloud Vision API) のテキスト認識を Flutterから直接動作させてみる。
ML Kit for firebase を利用すると、Firebaseから機械学習のAPIを簡易に利用できる。
ML Kit は、Google Cloud Vision API、TensorFlow Lite、Android Neural Networks API などの Google の ML テクノロジーを 1 つにまとめた SDK で、アプリで ML テクノロジーを簡単に利用できます。
Androidから利用する概要は、以下の動画で把握。
FlutterFire ライブラリを使用することで、Flutterから簡単にCloud Vision他を利用することが出来る。
その他にも、Flutter.dev 公式 パッケージ がいろいろ。
今回は、
- 画像の選択に、image_picker
- テキスト解析に、firebase_ml_vision
を利用する。
pubspec.yaml に追記
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
cloud_firestore: 0.13.4+2
firebase_ml_vision: 0.9.3+8
image_picker: 0.6.5
完成した画面。
この動画のように動作する。
ソース
import 'dart:io';
import 'package:firebase_ml_vision/firebase_ml_vision.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:image_picker/image_picker.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final message = "Initial Message.";
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Sample',
home: MyPage(message:this.message),
);
}
}
class MyPageState extends State<MyPage>{
String _time;
File _image;
final _stateController = TextEditingController();
final _visionTextController = TextEditingController();
//final TextRecognizer textRecognizer = FirebaseVision.instance.textRecognizer();
final TextRecognizer textRecognizer = FirebaseVision.instance.cloudTextRecognizer();
@override
void initState() {
super.initState();
this._time = "Tap Floating Action Button";
}
@override
void dispose() {
this._stateController.dispose();
super.dispose();
}
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
this._image = image;
});
}
void vision() async {
if (this._image != null) {
FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(this._image);
VisionText visionText = await textRecognizer.processImage(visionImage);
String text = visionText.text;
print(text);
var buf = new StringBuffer();
for (TextBlock block in visionText.blocks) {
final Rect boundingBox = block.boundingBox;
final List<Offset> cornerPoints = block.cornerPoints;
final String text = block.text;
final List<RecognizedLanguage> languages = block.recognizedLanguages;
print(languages);
buf.write("=====================\n");
for (TextLine line in block.lines) {
// Same getters as TextBlock
buf.write("${line.text}\n");
for (TextElement element in line.elements) {
// Same getters as TextBlock
}
}
}
setState(() {
this._visionTextController.text = buf.toString();
});
}
}
void showTime(){
setState(() {
this._time = DateTime.now().toString();
});
}
void loadOnPressed() {
Firestore.instance.document("sample/sandwichData")
.get().then((DocumentSnapshot ds){
setState(() {
this._stateController.text = ds["hotDogStatus"];
});
print("status=$this.status");
});
}
void saveOnPressed() {
Firestore.instance.document("sample/sandwichData")
.updateData({"hotDogStatus":_stateController.text})
.then((value) => print("success"))
.catchError((value) => print("error $value"));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Firebase Sample'),
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
this._time,
style: TextStyle(fontSize: 16.0),
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Flexible(
child: TextField(
controller: _stateController,
),
),
Padding(
padding: EdgeInsets.all(2.0),
child: RaisedButton(
onPressed: saveOnPressed,
child: Text("Save")),
),
Padding(
padding: EdgeInsets.all(2.0),
child: RaisedButton(
onPressed: loadOnPressed,
child: Text("Load"))
)
],
),
Column(
children: <Widget>[
Row(
children: <Widget>[
Padding(
padding: EdgeInsets.all(2.0),
child: RaisedButton(
onPressed: getImage,
child: Text("Pick Image"),
),
),
Padding(
padding: EdgeInsets.all(2.0),
child: RaisedButton(
onPressed: vision,
child: Text("Vision Api"),
),
),
],
),
TextField(
controller: _visionTextController,
minLines: 6,
maxLines: 15,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
Container(
//width: MediaQuery.of(context).size.width,
//height: 300,
child: FittedBox(
fit: BoxFit.fitHeight,
child: _image == null ? Text('No image selected.') : Image.file(_image),
),
),
],
),
],
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: showTime,
child: Icon(Icons.timer),
),
);
}
}
class MyPage extends StatefulWidget {
final String message;
MyPage({this.message}):super() {}
@override
State<StatefulWidget> createState() => new MyPageState();
}
