2021年1月31日日曜日

MetadataをTensorFlow Lite モデルに追加してTensorFlow Lite Support Task Libraryで推論する

 目的


TensorFlow Lite SupportTFLite Model Metadataを使ってTensorFlow Lite モデルにMetadataを追加する。Metadataを追加したモデルでTensorFlow Lite Support Task Libraryで推論してみる。


前回まで


前回のブログ「Raspberry PiでTensorFlow Lite Support Task Libraryをやってみる。」ではTensorFlow Lite Support Task Library (C++)をつかってRaspberry Pi 4で動くカメラキャプチャのサンプルを作った。

このときに使ったTensorFlow Lite モデル(TF-Lite モデル)はmetadataが追加されたモデルを使用した(TensorFlow Hubから入手したモデル)。

今回はTF-Lite モデルにmetadataを自分で追加して、TensorFlow Lite Support Task Library(TF Lite Support Task Library)で推論することをやってみる。


Metadataとは?


公式の「Adding metadata to TensorFlow Lite models」を参照。
既にあるTF-Lite モデルに補足の情報を追加することができる。


追加できる情報


以下の情報が追加できる。
  • モデル全体の説明(概要、著作権、作成者などの情報)
  • 入力の説明(画像フォーマット、正規化、入力幅の情報)
  • 出力の説明とラベル(説明やラベルとのマッピング)

追加した情報は「Netron」や「AndroidStudio」で参照できる。


Metaデータを追加する利点


  • 配布するモデルファイルの作成者、著作権を明示できる。
    TF-Lite モデルは端末にダウンロードが前提。
    公開・非公開に関わらず情報があるとありがたい。
  • TF Lite Support Task Libraryを使うと入力、出力情報から必要な前・後処理を行ってくれる。
    • 前処理: リサイズ、正規化、モデルのInput(Float, Int)
    • 後処理: ラベルとのマッピング
  • 推論コードのラッパーを自動生成してくれる。
    TensorFlowのドキュメント「メタデータを使用してモデルインターフェイスを生成する
    (まだよく理解していないので後で試す)


Medadataを追加するには?


TF Lite SupportのAPI(Python)を利用する。
Pythonのpipパッケージはtflite-supportが利用できる。


Object detectionモデルにmetadataを組み込む


TensorFlow 1 Detection Model Zooのpre-trainedモデル「ssd_mobiledet_cpu_coco」にMetadataを追加する。
このモデルはFloatモデルで、InputもFloat32である。

なお、Metadataを追加しない状態でTF Lite Support Task Libraryで推論するとエラーとなってしまう。
これは、TF Lite Support Task LibraryがInputの正規化するための情報が無いため。

$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/object_detector_capture \
    --model_path=/home/pi/ssdlite_mobiledet_cpu.tflite \
    --num_thread=4 --score_threshold=0.5
Detection failed: Input tensor has type kTfLiteFloat32: it requires specifying NormalizationOptions metadata to preprocess input images.

モデルにmetadataを追加して推論ができることまで確認する。


参考のコード・サンプル


Metadetaを追加するライブラリはここで実装。

MetadataはFlatBuffersのスキーマで定義。

よく使われるモデルのラッパーの定義。
(Image classification、Object detection、Image segmentation)
使い方はドキュメントはテストコードを参照。


作成したサンプル


Object detectionモデルにmetadataを書き込むサンプルを作成した。

前回のtflite-supportのforkリポジトリに追加。



実装


Pythonでの実装の概要を説明。

必要なimport
from tensorflow_lite_support.metadata import metadata_schema_py_generated as _metadata_fb
from tensorflow_lite_support.metadata.python.metadata_writers import metadata_info
from tensorflow_lite_support.metadata.python.metadata_writers import object_detector
from tensorflow_lite_support.metadata.python.metadata_writers import writer_utils

サンプルではMetadataPopulatorForObjectDetectorクラスの_create_metadataメソッドでmetadataを生成。

metadata_info.GeneralMdでモデル全体の情報を生成。
    # Creates model info.
    self.general_md = metadata_info.GeneralMd(
      name=self.model_info.name,
      version=self.model_info.version,
      description=("Identify which of a known set of objects might be present "
                   "and provide information about their positions within the "
                   "given image or a video stream."),
      author="Test",
      licenses=("Apache License. Version 2.0 "
                 "http://www.apache.org/licenses/LICENSE-2.0.")
    )

metadata_info.InputImageTensorMdでモデルの入力を生成。
  • norm_mean、norm_stdで正規化のパラメータを指定。
  • tensor_typeは入力モデルから取得(writer_utils.get_input_tensor_types)。
  • モデルはtensorflow.python.platform.resource_loaderを使って読み込み。
    # Load model to buffer.
    self.model_buffer = self._load_file(self.model_file_path)

    # Creates input info.
    self.input_md = metadata_info.InputImageTensorMd(
      name="normalized_input_image_tensor",
      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 {2} and {3}.".format(
                     self.model_info.image_width, self.model_info.image_height,
                     self.model_info.image_min, self.model_info.image_max)),
        norm_mean=self.model_info.mean,
        norm_std=self.model_info.std,
        color_space_type=_metadata_fb.ColorSpaceType.RGB,
        tensor_type=writer_utils.get_input_tensor_types(self.model_buffer)[0])

metadata_info.CategoryTensorMdでモデルの出力を生成。
  • ラベルファイルのパスを渡してあげると、ラベルも一緒に追加してくれる。
    # Creates output info.
    self.output_category_md = metadata_info.CategoryTensorMd(
        name="category",
        description="The categories of the detected boxes.",
        label_files=[
            metadata_info.LabelFileMd(file_path=file_path)
            for file_path in self.label_file_path
        ])

object_detector.MetadataWriter.create_from_metadata_infoで生成したMetadataをモデルに追加する。
戻り値のMetadataWriterのpopulateメソッドを呼び出すことでMetadataを追加したモデルを得ることができる。あとはバイナリファイルに書き込むだけ。
    self.writer = object_detector.MetadataWriter.create_from_metadata_info(
        model_buffer=self.model_buffer, general_md=self.general_md,
        input_md=self.input_md, output_category_md=self.output_category_md)
    model_with_metadata = self.writer.populate()

    with open(self.export_model_path, "wb") as f:
      f.write(model_with_metadata)

ラッパーを使っているので簡単にmetadataを追加できる。

また、MetadataWriterのget_metadata_jsonメソッドで追加したMetadataをJSONフォーマットで得ることができる。
  def get_metadata_json(self):
    return self.writer.get_metadata_json()


環境の準備


ホストPCでMetadataを書き込む(Windows、LinuxどちらもOK)。
今回はWindowsで実施。

TF Lite Support Task LibraryのPythonパッケージは0.1.0が公開されている(2021.01.30時点)。
しかし、metadata_writersのラッパーは0.1.0には含まれていないため、tflite-support-nightlyを使う。
TensorFlowはCPUのみで問題ない。

$ pip install tflite-support-nightly
$ pip install tensorflow


Metadataを組み込むモデル、ラベルファイル


モデル


ssd_mobiledet_cpu_coco」をダウンロードして目的のTF-Liteモデルを得る。
model.tfliteをssdlite_mobiledet_cpu.tfliteにリネームして使用。
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ mv ssdlite_mobiledet_cpu_320x320_coco_2020_05_19/model.tflite ssdlite_mobiledet_cpu.tflite

ラベル


coco datasetsのラベルを記載したファイル(labelmap.txt)を用意する。
今回はTF Lite Supportのテストデータを使用する。


Metadataを組み込む


リポジトリをclone後、サンプルのスクリプトを実行する。
引数は追加するモデル、ラベルファイル、出力ディレクトリ。
$ git clone https://github.com/NobuoTsukamoto/tflite-support.git
$ cd tensorflow_lite_support\examples\metadata
$ mkdir model_with_metadata
$ python metadata_writer_for_object_detection.py \
    --model_file=PATH_TO\ssdlite_mobiledet_cpu.tflite \
    --label_file=PATH_TO\labelmap.txt \
    --export_directory=.\model_with_metadata

model_with_metadataディレクトリに以下が生成される。
  • ssdlite_mobiledet_cpu.tflite 👈 metadataを追加したモデル
  • ssdlite_mobiledet_cpu.json 👈 追加したmetadataのJSONファイル


Metadataを確認


Netronと生成したJSONファイルを確認。

Netron


Input(normalized_input_image_tensor)を選択すると追加したmetadataの情報が表示される。
Metadataを追加したモデル

追加前と比べるとよくわかる。
Metadataを追加する前のオリジナルのモデル



JSON


出力したmetadataのJSON。
Netronでは表示できない内容も確認できる。
{
  "name": "SSDLite with MobileDet-CPU",
  "description": "Identify which of a known set of objects might be present and provide information about their positions within the given image or a video stream.",
  "version": "v1",
  "subgraph_metadata": [
    {
      "input_tensor_metadata": [
        {
          "name": "normalized_input_image_tensor",
          "description": "Input image to be classified. The expected image is 320 x 320, with three channels (red, blue, and green) per pixel. Each value in the tensor is a single byte between 0 and 255.",
          "content": {
            "content_properties_type": "ImageProperties",
            "content_properties": {
              "color_space": "RGB"
            }
          },
          "process_units": [
            {
              "options_type": "NormalizationOptions",
              "options": {
                "mean": [
                  127.5
                ],
                "std": [
                  127.5
                ]
              }
            }
          ],
          "stats": {
            "max": [
              1.0
            ],
            "min": [
              -1.0
            ]
          }
        }
      ],
      "output_tensor_metadata": [
        {
          "name": "location",
          "description": "The locations of the detected boxes.",
          "content": {
            "content_properties_type": "BoundingBoxProperties",
            "content_properties": {
              "index": [
                1,
                0,
                3,
                2
              ],
              "type": "BOUNDARIES"
            },
            "range": {
              "min": 2,
              "max": 2
            }
          },
          "stats": {
          }
        },
        {
          "name": "category",
          "description": "The categories of the detected boxes.",
          "content": {
            "content_properties_type": "FeatureProperties",
            "content_properties": {
            },
            "range": {
              "min": 2,
              "max": 2
            }
          },
          "stats": {
          },
          "associated_files": [
            {
              "name": "labelmap.txt",
              "description": "Labels for categories that the model can recognize.",
              "type": "TENSOR_VALUE_LABELS"
            }
          ]
        },
        {
          "name": "score",
          "description": "The scores of the detected boxes.",
          "content": {
            "content_properties_type": "FeatureProperties",
            "content_properties": {
            },
            "range": {
              "min": 2,
              "max": 2
            }
          },
          "stats": {
          }
        },
        {
          "name": "number of detections",
          "description": "The number of the detected boxes.",
          "content": {
            "content_properties_type": "FeatureProperties",
            "content_properties": {
            }
          },
          "stats": {
          }
        }
      ],
      "output_tensor_groups": [
        {
          "name": "detection_result",
          "tensor_names": [
            "location",
            "category",
            "score"
          ]
        }
      ]
    }
  ],
  "author": "Test",
  "license": "Apache License. Version 2.0 http://www.apache.org/licenses/LICENSE-2.0."
}

Metadataを組み込んだモデルを使ってみる


前回のブログで使ったサンプルプログラム(Object Detector)を使って推論してみる。
モデルをRaspberry Pi 4に転送して推論する。
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/object_detector_capture \
    --model_path=/home/pi/ssdlite_mobiledet_cpu.tflite \
    --num_thread=4 --score_threshold=0.5
Detection failed: Input tensor has type kTfLiteFloat32: it requires specifying NormalizationOptions metadata to preprocess input images.

今度はエラーがなく、推論できる👍


感想


推論のコードが変更せずに利用できることはとても良いことだと思う。
(ただ、個人で使う場合、metadataを追加するコストを考えるとあまりメリットがない気も、、、Input, Outputを統一してしまえばいいし、、、)
次は「メタデータを使用してモデルインターフェイスを生成する」を使ってAndroidアプリを試してみよう。

2021年1月17日日曜日

Raspberry PiでTensorFlow Lite Support Task Libraryをやってみる。

目的


TensorFlow Lite Support Task Library (C++)をつかってRaspberry Pi4で動くカメラキャプチャのサンプルを作ってみる。
TensorFlow LiteのAPIと比べて簡単に実装できるのかを確認してみる。


動機


2020年9月ぐらいから tensorflow / tflite-support のリポジトリに気がついたり、公式ブログでもアナウンスがあったりしたので気になっていた。
ようやく年末年始の休みにやってみることにした。




TensorFlow Lite Support Task Libraryとは?


公式のドキュメントは日本語にも翻訳されている。
自分の理解は以下。
  • TensorFlow Lite APIよりも簡単に扱うことができるAPIを用意。
  • タスク(画像分類、物体検出、自然言語処理、...etc)ごとにAPIを用意。
  • モデルのInput / Outputの形式(Float, INT, Shape...)を気にしなくてよい。
TensorFlow Lite APIと比べて扱いやすいAPIを用意することが目的と推測。

また、TensorFlow Lite Support Task Library は、他のライブラリを含めてTensorFlow Lite Supportと称している模様。

TensorFlow Lite Supportに含まれるライブラリは
  • TensorFlow Lite Support Library
  • TensorFlow Lite Model Metadata
  • TensorFlow Lite Support Codegen Tool
  • TensorFlow Lite Support Task Library ← 今回はこれ
があり、これらを使うことで特にモバイルのアプリの作成やデプロイを簡単にしようとする目的だと推測。

以下、簡単な特徴を記載。
これらは今後変更になる可能性があるので注意。


サポート言語


Native、Android、iOSの各プラットフォームで開発できる言語をサポートしている。
  • Java
  • C++ (WIP)
  • Swift (WIP)


用意されているタスク


2021.01.17時点では以下がサポートされている。

画像系

自然言語系


タスクを自作するには?


上記のタスク以外で、独自のタスクを実装することも可能。


モデル


通常のTensorFlow Lite モデルも可能だが、metadataを追加したTensorFlow Lite モデルを扱うことができる。TensorFlow Lite モデルにmetadataを追加することで
  • Task LibraryがInput, Outputの違い(型、サイズ)を吸収してくれる
    (アプリがリサイズ、型変換を意識しなくてよい)
  • Labelファイルが不要となる(モデルに埋め込める)。
    (モデルとラベルを一元管理でき、Task Libraryが結果からラベルを返してくれる)
といった利点が出てくる。

また、metadataにはモデルの説明や著作権などのライセンス情報も埋め込むことができる。これはOn-deviceな実行の場合、モデルが端末に配布(ダウンロード)される。ユーザーにはモデルが見えるので、ライセンスが明示できることは非常にありがたいと思う。Labelもモデルに埋め込めれば管理も容易になる。

TensorFlow Hubではmetadataが組み込まれたTensorFlow Liteモデルがある。例えば、ssd_mobilenet_v1を確認するとmetadetaにはモデルの説明、ライセンス、Input、Outputの詳細が確認できる。

metadataを追加したTensorFlow Lite モデルについては、次回以降にもう少し詳細化してみたい。


ラズパイ4でカメラキャプチャのサンプルを作る


今回は、画像のタスクのサンプル(CLI Demos for C++ Vision Task APIs)を参考に、ラズパイ4(64bit)とPiCameraでカメラキャプチャでタスクを実行するサンプル(C++)を作った。

Task Library(C++)の使い勝手を確認してみる。

元のリポジトリからForkしたリポジトリを作成。




用意したサンプル


画像で用意されているタスクのサンプルを作成してみた。


環境


自分の環境は以下。
  • Raspberry Pi 4 4GB
  • Raspberry Pi OS 64bit
  • Raspberry Pi Camera Module V2.1(UVCカメラでもOKなはず)


ビルド環境の準備と必要なモジュールのインストール


サンプルは元リポジトリと同様Bazelをつかってビルドしている。また、カメラキャプチャするためOpenCVを利用。ビルドはクロスコンパイルしたかったが、今回はHostのラズパイでビルドする。
クロスコンパイルするにはMediaPipeのサンプルと同じようにDokcerで行なえばいいと思う。

# Install required library
$ sudo apt install git libopencv-dev

# Install build tool.
$ wget https://github.com/bazelbuild/bazel/releases/download/3.7.2/bazel-3.7.2-linux-arm64
$ chmod +x bazel-3.7.2-linux-arm64
$ sudo mv bazel-3.7.2-linux-arm64 /usr/local/bin/bazel
$ sudo apt install openjdk-11-jdk


ビルド


リポジトリをClone後、それぞれをビルド。ビルド時間はおおよそ15~20分程度。
# Clone repository
$ git clone https://github.com/NobuoTsukamoto/tflite-support.git
$ cd tflite-support


Image Classifier


# Build Image Classifier
$ bazel build \
    --verbose_failures \
    tensorflow_lite_support/examples/task/vision/pi/image_classifier_capture


Object Detector


# Build Image Classifier
$ bazel build \
    --verbose_failures \
    tensorflow_lite_support/examples/task/vision/pi/object_detector_capture


Image Segmenter


# Build Image Classifier
$ bazel build \
    --verbose_failures \
    tensorflow_lite_support/examples/task/vision/pi/image_segmenter_capture


実行


TensorFlow Hubからmetadataが組み込まれたTensorFlow Liteモデルをダウンロードして実行する。


Image Classifier


# Download the model
$ curl \
   -L 'https://tfhub.dev/google/lite-model/aiy/vision/classifier/birds_V1/3?lite-format=tflite' \
   -o ./aiy_vision_classifier_birds_V1_3.tflite

# Run the classification tool.
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/image_classifier_capture \
    --model_path=./aiy_vision_classifier_birds_V1_3.tflite \
    --num_thread=4


Object Detector


# Download the model.
$ curl \
   -L 'https://tfhub.dev/tensorflow/lite-model/ssd_mobilenet_v1/1/metadata/2?lite-format=tflite' \
   -o ./ssd_mobilenet_v1_1_metadata_2.tflite

# Run the detection tool.
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/object_detector_capture \
    --model_path=./ssd_mobilenet_v1_1_metadata_2.tflite \
    --score_threshold=0.5 \
    --num_thread=4




Image Segmenter


# Download the model.
$ curl \
    -L 'https://tfhub.dev/tensorflow/lite-model/deeplabv3/1/metadata/1?lite-format=tflite'  \
    -o ./deeplabv3_1_metadata_1.tflite

# Run the segmantation tool.
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/image_segmenter_capture \
    --model_path=./deeplabv3_1_metadata_1.tflite \
    --num_thread=4




ハマったこと


WORKSPACEにOpenCVのBUILDを追加したところビルドエラーが発生。
/usr/include/c++/8/cstdlib:75:15: fatal error: stdlib.h: No such file or directory
#include_next <stdlib.h>

詳細はこのIssueにある通りで、ビルドのパラメータに「build --spawn_strategy=standalone」が指定されていると、/usr/includeのインクルードパスが追加されなくなるためである模様。
ビルドパラメータを削除して対応した。


TensorFlow Lite Support Task Libraryの使いやすさ


ある程度、サンプルを実装してみた感想。画像系のタスクでの結果なので、自然言語系のタスクは触れていないので注意。


モデルのロード(タスクの生成)


モデルのロードはそのタスクのクラスを生成することで実現する。モデルパス、閾値やthread数などのパラメータもオプションとして指定する。

物体検出タスクだとこんな感じでObjectDetectorのインスタンスを生成。
  // Build ObjectDetector.
  const ObjectDetectorOptions& options = BuildOptions();
  ASSIGN_OR_RETURN(std::unique_ptr<ObjectDetector> object_detector,
                   ObjectDetector::CreateFromOptions(options));

オプションの指定(BuildOptions関数)はこんな感じ。
ObjectDetectorOptions BuildOptions() {
  ObjectDetectorOptions options;
  // モデルパスを指定
  options.mutable_model_file_with_metadata()->set_file_name(
      absl::GetFlag(FLAGS_model_path));
  // 出力の最大数
  options.set_max_results(absl::GetFlag(FLAGS_max_results));
  // 推論でのスレッドの並列数
  options.set_num_threads(absl::GetFlag(FLAGS_num_thread));
  // スコアの閾値
  if (absl::GetFlag(FLAGS_score_threshold) >
      std::numeric_limits<float>::lowest()) {
    options.set_score_threshold(absl::GetFlag(FLAGS_score_threshold));
  }
  // 出力クラスのホワイトリスト
  for (const std::string& class_name :
       absl::GetFlag(FLAGS_class_name_whitelist)) {
    options.add_class_name_whitelist(class_name);
  }
  // 出力クラスのブラックリスト
  for (const std::string& class_name :
       absl::GetFlag(FLAGS_class_name_blacklist)) {
    options.add_class_name_blacklist(class_name);
  }
  return options;
}                 

スコアの閾値や出力数の個数はアプリ側で制御すると煩雑になりがちなのでオプションで指定できるのは便利。また、画像分類、物体検出タスクでは出力のホワイトリスト、ブラックリストが指定できる。TensorFlow Hubなどで用意されたPre-trainedモデルを使う場合で、必要なクラスを制御したいときは便利だと思う。

各クラスのオプションはprotoとして記述されている。以下を参照。

画像系

自然言語系にはオプションはないように見える?


入力(画像)


TF-Liteで面倒なのは画像の入力だと思う。

モデルにあわせてリサイズや型(Float、INT)の変換、標準化が必要。必要な情報はモデルから取得できるのだが、すべての形式にあわせようとするとかなり冗長なコードとなってしまう。自分はFloat、INTモデルに限らず、モデルの入力はINT8で統一してしまうことでリサイズだけ意識するようにしている。ただ、Floatモデルの場合はINT⇒Floatへのdequantizedを挟むのでほんの少しだけもったいない気がする。

Task Libraryではモデルの入力を意識する必要がない(Libraryが吸収してくれる)。
アプリは画像データをFlatBuffer形式するだけ。各入力データの形式(Gray, RGB, RGA, YUV, Raw)からFlatBufferに変換するIFが用意されている。

OpenCVでカメラキャプチャしたデータの場合、BGR⇒RGBに変換、CreateFromRgbRawBufferを使ってFlatBufferを生成すればよい。CreateFromRgbRawBufferの引数にはcv::MatのサイズとRawデータを与える。
    cap >> frame; // capture frame.
    cv::cvtColor(frame, input_im, cv::COLOR_BGR2RGB); // BGR to RGB

    // Frame in a FrameBuffer.
    std::unique_ptr<FrameBuffer> frame_buffer;
    frame_buffer = CreateFromRgbRawBuffer(input_im.data, {input_im.cols, input_im.rows});                

2,3行で入力データが生成できるのはとてもありがたい。

また、CreateFromRgbRawBufferの引数には画像の向きも指定できる(引数のFrameBuffer::Orientation orientation)。
モバイルの場合、9軸センサーの値などから、スマホの向きを考慮して画像の向きを考える必要がある。Task Libraryではアプリで画像を回転する必要がなく、ライブラリ内部で回転してくれる。

画像のデータだけでなくて、サイズや向き、フォーマットを指定するのでFlatBufferで入力データを指定するということで理解した。


推論


推論自身は各タスクのメソッド(Classify、Detect、Segment、etc...)を呼び出し、入力データのFlatBufferを指定してあげる。
さほどTensorFlow Lite APIのinvokeと変わらない。
    // Run object detection and draw results on input image.
    ASSIGN_OR_RETURN(DetectionResult result,
                     object_detector->Detect(*frame_buffer));          

入力したデータはどのように変換されるかはここに記載がある。
  • RGBAやYUVなどの形式の場合はRGBに変換される。
  • アスペクトを維持せず、モデルの入力サイズにリサイズ。
    (アスペクト比を維持しないので要注意)
  • Orientationのパラメータによって、画像を回転して推論。


出力


画像分類、物体検出タスクの場合、それぞれ結果はClassificationResult、DetectionResult
として取得することができる。生のOutputTensorを意識する必要はない。

物体検出タスクの場合は下記のようにBoundingBoxの位置やサイズ、クラスのラベル名が取得できる。モデルにラベルが組み込まれていれば、出力クラスのindexから該当のラベルの文字列をとってくる処理も不要になる。これはほんとに便利。
DrawCaptionはOpenCVの文字列描画を行う独自の関数)
absl::Status EncodeResultToMat(const DetectionResult& result,
                               cv::Mat& image) {
  for (int index = 0; index < result.detections_size(); ++index) {
    // Get bounding box as left, top, right, bottom.
    const BoundingBox& box = result.detections(index).bounding_box();
    const Detection& detection = result.detections(index);
    const int x = box.origin_x();
    const int y = box.origin_y();
    const int width = box.width();
    const int height = box.height();

    // Draw. Boxes might have coordinates outside of [0, w( x [0, h( so clamping
    // is applied.
    cv::rectangle(image, cv::Rect(x, y, width, height), kBuleColor, kLineThickness);

    // Draw. Caption.
    std::ostringstream caption;

    if (detection.classes_size() == 0) {
      caption << "  No top-1 class available";
    } else {
      const Class& classification = detection.classes(0);

      if (classification.has_class_name()) {
        caption << classification.class_name();
      } else {
        caption << classification.index();
      }
      caption << " (" << std::fixed << std::setprecision(2) << classification.score() << ")";
      DrawCaption(image, cv::Point(x-3, y), caption.str());
    }
  }

  return absl::OkStatus();
}           

また、Image Segmenterの場合、ループ処理でOutputとのマスクをとる必要がある。OpenCVを利用している場合はcv::Mat::forEachでループしてあげれば並列化も期待できると思う。なお、このサンプルではcolored_labelsでLabelごとのcolormap(いわゆるPASCAL VOCのcolormap)で色付けしている。
std::unique_ptr<cv::Mat> EncodeMaskToMat(const SegmentationResult& result) {
  if (result.segmentation_size() != 1) {
    std::cout << "Image segmentation models with multiple output segmentations are not "
        "supported by this tool." << std::endl;
    return nullptr;
  }
  const Segmentation& segmentation = result.segmentation(0);
  // Extract raw mask data as a uint8 pointer.
  const uint8* raw_mask =
      reinterpret_cast<const uint8*>(segmentation.category_mask().data());

  // Create RgbImageData for the output mask.
  auto seg_im = std::make_unique<cv::Mat>(cv::Size(segmentation.width(), segmentation.height()), CV_8UC3);
  auto wdith = seg_im->cols;
  seg_im->forEach<cv::Vec3b>([&](cv::Vec3b &src, const int position[2]) -> void {
    size_t index = position[0] * wdith + position[1];
    Segmentation::ColoredLabel colored_label =
        segmentation.colored_labels(raw_mask[index]);
        src[0] = colored_label.b();
        src[1] = colored_label.g();
        src[2] = colored_label.r();
    });
  
  return seg_im;
}        


感想


使ってみた感想


Native C++しか使っていないが、

使いやすい点
  • TensorFlow Liteモデルの入出力の型、サイズを意識しなくてよいので実装がとても楽。
  • TensorFlow Lite APIを使た場合と比べて1/2~1/3の実装で済む。
  • OpenCVを使っても実装が楽。とくにInputをFlatBufferへの変換。
    (たぶんこれはAndroid、iOSの場合もそうかも?)

使いにくかった点
  • Bazelを使ったビルド(これは自分が慣れていないせいもある)。
    とくにOpenCVを追加したらなぜかビルドエラー。。。
  • APIのリファレンスがまだ整備されていない。
    まだ正式リリースでもない状態なので仕方がない。今後に期待。


MediaPipeとは何が違うの?


MediaPipeもTensorFlow Lite モデルを扱うことができるクロスプラットフォームなライブラリである。
MediaPipeとTask Libraryを比べた場合、
  • MediaPipeは画像系のタスクに特化、Task Libraryは画像以外のタスクも可能。
  • MediaPipeは推論部分だけでなくて、前処理、後処理も含めてのフレームワーク。
    作成したコンポーネントを再利用可能として、開発を容易とする。
    Task Libraryは推論部分の実装を容易とする。
で、それぞれ目的が異なると思う。
TF-Liteモデルを使ってお手軽にアプリを実装したい場合は、Task Libraryを使うほうが良いと思う。MediaPipeは簡単にという訳にはいかない。ある程度、MediaPipeのFrameworkとしての内容を理解していないと難しいと思う。


Coralとは何が違うの


Coral EdgeTPUのPyCoral API(Python)libcoral API(C++)もTF-Liteモデルを扱うことができる(EdgeTPU delegateだけでなくて)。どちらもTensorFlow Lite モデルをより簡単に扱うためのAPIを提供しているようにも見える。
  • Task LibraryはEdgeTPU delegateができない。
  • Pythonで扱うことができるAPIはPyCoralのみ。
で、EdgeTPUを扱う場合はPyCoral、libcoarl APIを扱う以外はないのが現状。2つのライブラリがわかれているのがもったいない気もするけど、、、


次は?


今回はTensorFlow Lite Support Task LibraryのNative C++を扱ってみた。楽に実装ができるのはいいねと思うけど、あとはBazelとかドキュメントが充実してくるといいなぁと思う。

つぎはTask Libraryとセットで必要になるTensorFlow Liteモデルへのmetadataの組み込みをやってみよう。