AutoMLVisionEdgeとFlutterで簡単画像分類アプリの作成

# 概要・流れ
都庁ライブカメラに映っている富士山が見えているかどうかを判定するモデルを作り、TensorFlowLite(Edge)でアプリに実装する。
1. 収集・加工 - 都庁ライブカメラの画像を1時間毎に取得しデータストアに保存します。
2. 学習 - 溜まった画像をAutoMLVisionにてラベリング、学習します。
3. 実装 - 出力したTensorFlowLiteモデルをFlutterにて実装します。
4. 判定 - アプリにて、富士山が見えていたか判定します。

# 収集・加工 - 都庁ライブカメラ画像の保存
CloudFunctions(Python37)で作成、CloudSchedulerで定時実行します。毎時0分だと画像が出力されていないケースがあったので日中7～17時の毎時15分にします。CloudStrageに画像保存用のbucketを作成しておきます。

```py:main.py
import io
import urllib.request
import pytz
from datetime import datetime as dt
from google.cloud import storage
from PIL import Image

def url_to_gcs_pubsub(event, context):
    tdatetime = dt.now(pytz.timezone('Asia/Tokyo'))
    fn = tdatetime.strftime('%Y%m%d%H') + '.jpg'

    client = storage.Client()
    bucket = client.get_bucket('[bucket名]')
    blob = bucket.blob('[ディレクトリ]/' + fn)

    # 都庁ライブカメラ画像取得
    url = 'http://www.taiki.kankyo.metro.tokyo.jp/taikikankyo/mnt8/tkn35a/tora/zu1/xg-1/zu1-m/zom8/%s' % fn
    f = io.BytesIO(urllib.request.urlopen(url).read())
    img = Image.open(f)
    im_crop = img.crop((194, 223, 422, 303)) # トリミング

    f = io.BytesIO()
    im_crop.save(f, format='jpeg', quality=95)
    blob.upload_from_string(data=f.getvalue(), content_type="image/jpeg") # store保存

class HTTPResponseWithTell(object):
    def __init__(self, http_response):
        self.http_response = http_response
        self.number_of_bytes_read = 0

    def tell(self):
        return self.number_of_bytes_read

    def read(self, *args, **kwargs):
        buffer = self.http_response.read(*args, **kwargs)
        self.number_of_bytes_read += len(buffer)
        return buffer

if __name__ == '__main__':
    url_to_gcs_pubsub(None,None)
```

![scheduler.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247931/f6d22fa7-c544-7990-140f-8ed82d9fd2c4.png)

# 学習 - AutoMLVision
600枚程度(定点観測なので少ないデータでもいけるかなと)になったところでラベリング＆学習開始します。ラベルはvisibleとinvisibleの2種。全部ブラウザ上でできてしまいます。
![vision.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247931/91f57df5-8c08-7e3f-11cd-83df738fbf8f.png)
![evaluation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247931/00923c42-337b-34fd-aa29-69558bd9567d.png)

# 実装 - Flutter
tfliteファイルをasettsに置きます。tflite_flutterを使いますが、flutterのライブラリがFirebase ML Kit AutoML Vision Edgeに対応したら、そちらの方が良いと思います。
> https://github.com/shaqian/flutter_tflite/issues/55#issuecomment-685001454
> https://pub.dev/packages/tflite_flutter

以下、判定部分とその周辺のみ。

```dart:main.dart
・・・
import 'package:tflite_flutter/tflite_flutter.dart';
import 'package:tflite_flutter_helper/tflite_flutter_helper.dart';
・・・

class _MyHomePageState extends State<MyHomePage> {
  DateTime _ndt = DateTime.now();
  String date = ''; // 日付URL用
  String dateText = 'Select Date'; // 日付表示用
  String imgUrl = ""; // ライブカメラ画像URL
  int hour = 0; // 指定時間
  List<Map<dynamic, dynamic>> _labels;
  img.Image resimg = img.Image(228, 80);
  String msg = "";
  Interpreter _interpreter;
  List<int> _inputShape;
  List<int> _outputShape;
  TfLiteType _outputType = TfLiteType.uint8;
  TensorImage _inputImage;
  TensorBuffer _outputBuffer;
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

  Future classify() async {
    imgUrl = sprintf("http://www.taiki.kankyo.metro.tokyo.lg.jp/taikikankyo/mnt8/tkn35a/tora/zu1/xg-1/zu1-m/zom8/%s%02d.jpg", [date, hour]);
    http.Response response = await http.get(imgUrl);
    img.Image res = await img.decodeImage(response.bodyBytes);
    res = await img.copyCrop(res, 194, 223, 228, 80);
    resimg = await img.Image.fromBytes(228,80,img.encodeJpg(res));

    _inputImage = await TensorImage.fromImage(res);
    _inputImage = await _preProcess();
    await _interpreter.run(_inputImage.buffer, _outputBuffer.getBuffer());

    List<String> labels = ["visible","invisible"];
    TensorLabel tensorLabel = await TensorLabel.fromList(labels, _outputBuffer);
    Map<String, double> doubleMap = await tensorLabel.getMapWithFloatValue();
    msg = await ((doubleMap["visible"] / 256 * 10000).floor() / 100).toString();
  }

  Future loadModel() async {
    // MtFuji_202016101024
    try {
      this._interpreter = await Interpreter.fromAsset("[tflite名].tflite");
      _inputShape = _interpreter.getInputTensor(0).shape; // [1, 257, 257, 3]
      _outputShape = _interpreter.getOutputTensor(0).shape;
      _outputType = _interpreter.getOutputTensor(0).type;
      _outputBuffer = TensorBuffer.createFixedSize(_outputShape, _outputType);
    } catch (e) {
      print('Unable to create interpreter, Caught Exception: ${e.toString()}');
    }
  }
  ・・・
}
```

# 判定 - アプリ実行
日時を指定してClassify!　11/28(土)8時は97.26、10時は11.32と判定。
![Screenshot (2020_12_02 19_57_24).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247931/9a84a70d-ef46-d06c-7cc4-30e2aa59dcf3.png)

# 感想
AutoMLVision簡単で良いですね。TensorFlowLiteの実装はquantum周りで苦戦して、tflite_flutterライブラリに着地しました。Firebase対応に期待です。
Flutter良いかも。setState((){})しないと画面更新されないのにハマった。FlutterFlowも早く試したい。