【TensorFlow x Flutter】で

11:00

前回Firebaseにtfliteファイルをアップロードするとこまでやった。
ガイドラインに従ってFlutter側で実装していく。


  initialize() async {
    _cameras = await availableCameras();
    controller = CameraController(_cameras[0], ResolutionPreset.medium);
    await controller?.initialize();
    controller?.startImageStream((image) {
      currentimage = image;
    });
    
    final _model = await FirebaseModelDownloader.instance.getModel(
        "detector", FirebaseModelDownloadType.localModelUpdateInBackground);
    setState(() {
      model = _model;
    });
    try {
      final options = LocalLabelerOptions(
        confidenceThreshold: 0.4,
        modelPath: model!.file.path
      );
      timer = Timer.periodic(const Duration(seconds: 2), (timer) async {
        if (currentimage == null) return;
        var inputImage = createImputImage(currentimage!);
        var detector = ImageLabeler(options: options);
        var labels = await detector.processImage(inputImage);
        print("labels ${labels.map((e) => '${e.label}: ${e.confidence}')}");
      });
    } catch (exception) {
      print('TFLite.failed: $exception');
      rethrow;
    }
    setState(() {});
  }

するとこんなエラーが吐かれた。

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(Error 5, com.google.visionkit.pipeline.error, Pipeline failed to fully start:
CalculatorGraph::Run() failed in Run:
Calculator::Open() for node "ClassifierClientCalculator" failed: #vk Input tensor has type kTfLiteFloat32: it requires specifying NormalizationOptions metadata to preprocess input images., null)

Metadataを追加しないといけないらしい。
https://stackoverflow.com/questions/65446130/it-requires-specifying-normalizationoptions-metadata-to-preprocess-input-images
公式のガイドラインのload_associated_filesに何を入れればいいかわからない。
空にしても適当なファイルを入れても、
populator.load_associated_files([])
とか
populator.load_associated_files(['label_file.txt'])

以下エラー

…..\x00\x00serving_default_sequential_input:0\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\xb4\x00\x00\x00\xb4\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00$\x00\x00\x00\x14\x00\x00\x00\x04\x00\x00\x00\xdc\xff\xff\xff\t\x00\x00\x00\x00\x00\x00\t\xe8\xff\xff\xff\x16\x00\x00\x00\x00\x00\x00\x16\xf4\xff\xff\xff\x11\x00\x00\x00\x00\x00\x00\x11\x0c\x00\x0c\x00\x0b\x00\x00\x00\x00\x00\x04\x00\x0c\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03'', does not exist.

ん?

“画像関連のTensorFlow Liteモデルには、すでにメタデータが添付されています。”

https://note.com/npaka/n/n5841a1500e7e

じゃあMetadataいらないのか?

12:30

「Metadataを追加しろ」しか見つけられなかった。

Firebaseからダウンロードしてるのが良くないかもしれないローカルで試してみる。

final options = LocalLabelerOptions(
        confidenceThreshold: 0.4,
        modelPath: 'lib/images/OUTPUT-MODEL.tflite'
      );

*** Terminating app due to uncaught exception 'MLKInvalidLocalModel', reason: 'Local model path (lib/images/OUTPUT-MODEL.tflite) is invalid.'

と言われてしまった。

ちゃんとReadmeに従おう。

final modelPath = await getModelPath('lib/images/OUTPUT-MODEL.tflite');
      final options = LocalLabelerOptions(
        confidenceThreshold: 0.4,
        modelPath: modelPath//model!.file.path
      );

エラー

TFLite.failed: FileSystemException: Cannot open file, path = '/var/mobile/Containers/Data/Application/CB720111-3448-4F85-AE26-2122D8EC273C/Library/Application Support/lib/images/OUTPUT-MODEL.tflite' (OS Error: Is a directory, errno = 21)
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: FileSystemException: Cannot open file, path = '/var/mobile/Containers/Data/Application/CB720111-3448-4F85-AE26-2122D8EC273C/Library/Application Support/lib/images/OUTPUT-MODEL.tflite' (OS Error: Is a directory, errno = 21)

ええ?ディレクトリじゃないよ〜。。
どうしようもないのでとりあえずissueを開いて、

firebaseの方を使う。

final options = FirebaseLabelerOption(
  confidenceThreshold: 0.4,
  modelName: "cup",
);

dameda….

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(ERROR_MISSING_MLKIT_FIREBASE_MODELS, You must define MLKIT_FIREBASE_MODELS=1 in your Podfile., null, null)

 また Readme見てなかった

Podfileに2箇所追加して。。

platform :ios, '12.0'

$iOSVersion = '12.0' 

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  # Enable firebase-hosted models #
  pod 'GoogleMLKit/LinkFirebase'
  pod 'Firebase'

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  #target 'RunnerTests' do
  #  inherit! :search_paths
  #end
end

post_install do |installer|

  installer.pods_project.build_configurations.each do |config|
    config.build_settings["EXCLUDED_ARCHS[sdk=*]"] = "armv7"
    config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
  end

  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

      target.build_configurations.each do |config|

        # Enable firebase-hosted ML models
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',
        'MLKIT_FIREBASE_MODELS=1',
      ]

      	if Gem::Version.new($iOSVersion) > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
          config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = $iOSVersion
      end
    end
  end
end

Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.
To update the CocoaPods specs, run:
pod repo update

🙃
何もうまくいかない。
pod repo updateは大体エラーの解消に関係ないので、他の解決法を探す

Scope外とのこと。無理ぽい。

去年11月に作られたスレッドらしいが、Googleからのアップデートもまだない状態??やめとこう。

やはりローカルで試す。 

'lib/images/OUTPUT-MODEL.tflite'を
指定するとディレクトリだと言い張るので、本当のディレクトリを指定してみる。

final modelPath = await getModelPath('lib/images');

手前のrootBundle.load(asset);でエラーが出た。pathの問題ではなさそう?

TFLite.failed: Unable to load asset: "lib/images".
The asset does not exist or has empty data.
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: Unable to load asset: "lib/images".

とりあえずpathは'lib/images/OUTPUT-MODEL.tflite'に戻して。
公式のコードだから、コードに問題はないと思うが一応writeAsBytesについて調べてみる。

writeAsBytesの前にawait file.create();が必要?

Future<String> getModelPath(String asset) async {
    final path = '${(await getApplicationSupportDirectory()).path}/$asset';
    await Directory(dirname(path)).create(recursive: true);
    print("getModelPath.d ${dirname(path)}");
    final file = File(path);
    print("getModelPath.f $file");
    await file.create();
      print("does not exists");
      final byteData = await rootBundle.load(asset);
      print("did.load $byteData");
      await file.writeAsBytes(byteData.buffer
          .asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
      print("did.writeAsBytes");
    
    return file.path;
  }

await file.create();でおなじエラーになった

TFLite.failed: FileSystemException: Cannot create file, path = '/var/mobile/Containers/Data/Application/0758A811-724B-4F79-B2FF-FE8E2391059D/Library/Application Support/lib/images/OUTPUT-MODEL.tflite' (OS Error: Is a directory, errno = 21)
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: FileSystemException: Cannot create file, path = '/var/mobile/Containers/Data/Application/0758A811-724B-4F79-B2FF-FE8E2391059D/Library/Application Support/lib/images/OUTPUT-MODEL.tflite' (OS Error: Is a directory, errno = 21)

tfliteをassetsフォルダーに移動して、パスもそれに応じて変えてみた。
getModelPath('assets/OUTPUT-MODEL.tflite');

エラーが変わった!!

*** Terminating app due to uncaught exception 'MLKInvalidLocalModel', reason: 'Local model path (/var/mobile/Containers/Data/Application/3782B87E-963D-4BD9-9EE3-D6CF05740C38/Library/Application Support/assets/OUTPUT-MODEL.tflite) is invalid.'

pubspec.yamlにassetsパスを追加してなかった。。
追加して再トライ。

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(Error 5, com.google.visionkit.pipeline.error, Pipeline failed to fully start:
CalculatorGraph::Run() failed in Run:
Calculator::Open() for node "ClassifierClientCalculator" failed: #vk Input tensor has type kTfLiteFloat32: it requires specifying NormalizationOptions metadata to preprocess input images., null)

わーい🙃最初のエラーに戻ったぞー。

14:00

やっぱりメタデータが必要だ。 
main.pyに戻る。
load_associated_filesでエラーが出てると思ってたんだけど、1行づつコメントアウトして確認してみたら違った。

populator = _metadata.MetadataPopulator.with_model_file(tflite_model)

これがダメらしい。作ったモデルをそのまま参照してるけど、そうじゃなくて一回保存しないといけない?

add_metadata.pyを作って作業を別にした。


import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
import pathlib
from pathlib import Path
import imghdr

from tflite_support import flatbuffers
from tflite_support import metadata as _metadata
from tflite_support import metadata_schema_py_generated as _metadata_fb

# Metadata

model_file = os.path.abspath("./OUTPUT-MODEL.tflite")

# Creates model info.
model_meta = _metadata_fb.ModelMetadataT()
model_meta.name = "Cup classifier"
model_meta.description = ("Identify a cup")
model_meta.version = "v1"
model_meta.author = "Integro"
model_meta.license = ("Apache License. Version 2.0 "
                      "http://www.apache.org/licenses/LICENSE-2.0.")

# Creates input info.
input_meta = _metadata_fb.TensorMetadataT()

# Creates output info.
output_meta = _metadata_fb.TensorMetadataT()

input_meta.name = "image"
input_meta.description = (
    "Input image to be classified. The expected image is {0} x {1}, with "
    "three channels (red, blue, and green) per pixel. Each value in the "
    "tensor is a single byte between 0 and 255.".format(160, 160))
input_meta.content = _metadata_fb.ContentT()
input_meta.content.contentProperties = _metadata_fb.ImagePropertiesT()
input_meta.content.contentProperties.colorSpace = (
    _metadata_fb.ColorSpaceType.RGB)
input_meta.content.contentPropertiesType = (
    _metadata_fb.ContentProperties.ImageProperties)
input_normalization = _metadata_fb.ProcessUnitT()
input_normalization.optionsType = (
    _metadata_fb.ProcessUnitOptions.NormalizationOptions)
input_normalization.options = _metadata_fb.NormalizationOptionsT()
input_normalization.options.mean = [127.5]
input_normalization.options.std = [127.5]
input_meta.processUnits = [input_normalization]
input_stats = _metadata_fb.StatsT()
input_stats.max = [255]
input_stats.min = [0]
input_meta.stats = input_stats



# Creates output info.
output_meta = _metadata_fb.TensorMetadataT()
output_meta.name = "probability"
output_meta.description = "Probabilities of the 1001 labels respectively."
output_meta.content = _metadata_fb.ContentT()
output_meta.content.content_properties = _metadata_fb.FeaturePropertiesT()
output_meta.content.contentPropertiesType = (
    _metadata_fb.ContentProperties.FeatureProperties)
output_stats = _metadata_fb.StatsT()
output_stats.max = [1.0]
output_stats.min = [0.0]
output_meta.stats = output_stats
label_file = _metadata_fb.AssociatedFileT()
label_file.name = 'label_file.txt'
label_file.description = "Labels for objects that the model can recognize."
label_file.type = _metadata_fb.AssociatedFileType.TENSOR_AXIS_LABELS
output_meta.associatedFiles = [label_file]



# Creates subgraph info.
subgraph = _metadata_fb.SubGraphMetadataT()
subgraph.inputTensorMetadata = [input_meta]
subgraph.outputTensorMetadata = [output_meta]
model_meta.subgraphMetadata = [subgraph]

b = flatbuffers.Builder(0)
b.Finish(
    model_meta.Pack(b),
    _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER)
metadata_buf = b.Output()

populator = _metadata.MetadataPopulator.with_model_file(model_file)
populator.load_metadata_buffer(metadata_buf)
populator.load_associated_files([os.path.abspath("./label_file.txt")])
populator.populate()

トライ!$ python3 add_metadata.py
できた!!!

新しいtfliteファイルをassets内に移動して、
$ flutter run

Calculator::Open() for node "ClassifierClientCalculator" failed: #vk Expected non-empty labels file.

エラー変わってる!labelファイルが空だと言ってる。
labelファイルには何を書けばいいんだ?
https://github.com/ankdesh/tflite/blob/master/Android-TensorFlow-Lite-Example/app/src/main/assets/labels.txt

上記の感じでラベル名を改行分けで書いてみた。 
再度python3 main.pyからやり直す。 

Calculator::Open() for node "ClassifierClientCalculator" failed: #vk Got 11 class(es) for output index 0, expected 12 according to the label map.

11クラスしかない??
トレーニングデータからnormal18が抜けてた。

データを直して、python3 main.pyからやり直す。 

Calculator::Open() for node "ClassifierClientCalculator" failed: #vk Got 12 class(es) for output index 0, expected 13 according to the label map., null)

えええ。label.txtに改行を加えてた。。
改行を消してまたpython3 main.pyからやり直す。 

14:47

labels (normal24: 3.4254889488220215, normal20: 3.358473062515259, normal2: 2.1738414764404297, ..., normal14: 1.4549726247787476, normal8: 0.4090881943702698)

できたーーーーーーーーーーーーー!!!!!!!!!!!!!!
ありがとうーーーーーーーーーー!

この記事が気に入ったらサポートをしてみませんか?