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 APITensorFlow LiteAndroid Neural Networks API などの Google の ML テクノロジーを 1 つにまとめた SDK で、アプリで ML テクノロジーを簡単に利用できます。

Androidから利用する概要は、以下の動画で把握。

https://youtu.be/_qrI1JUCMjI

FlutterFire ライブラリを使用することで、Flutterから簡単にCloud Vision他を利用することが出来る。

その他にも、Flutter.dev 公式 パッケージ がいろいろ。

今回は、

を利用する。

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

完成した画面。

flutter_vision

この動画のように動作する。

ソース

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();
}

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です