目的
動機
TensorFlow / exampleに追加されたサンプル、task libraryってあるけど、tflite-supportとの関係はあるの?
— nb.o (@Nextremer_nb_o) September 30, 2020
うーん。ちゃんと見ようかな...https://t.co/D466nvVvWv
⬇️でやってた、TensorFlow Lite SupportのTFLite Task library - C++を使ってRaspberry Piで動くサンプル(Object detection)をアップした。
— nb.o (@Nextremer_nb_o) January 3, 2021
TFLite Model MetadataとVision Task Librariesで少ないコードで実現できるのが魅力。input shapeや型、labelを意識しなくていい。https://t.co/vKqUObGp8J https://t.co/0Ao7LuLzwG
TensorFlow Lite Support Task Libraryとは?
- TensorFlow Lite APIよりも簡単に扱うことができるAPIを用意。
- タスク(画像分類、物体検出、自然言語処理、...etc)ごとにAPIを用意。
- モデルのInput / Outputの形式(Float, INT, Shape...)を気にしなくてよい。
- TensorFlow Lite Support Library
- TensorFlow Lite Model Metadata
- TensorFlow Lite Support Codegen Tool
- TensorFlow Lite Support Task Library ← 今回はこれ
サポート言語
- Java
- C++ (WIP)
- Swift (WIP)
用意されているタスク
- ImageClassifier(画像分類タスク)
- ObjectDetector(物体検出タスク、SSD系のモデルが動作)
- ImageSegmenter(Image Segmantationタスク、DeepLabが動作)
-
NLClassifier
(どんなモデルか不明。直ダウンロードリンクで詳細がよくわからなかった) -
BertNLCLassifier
(こちらもBertのどのようなモデルまでかは詳細がTODOとなっている。) -
BertQuestionAnswerer
(Mobile BERT Q&A modelが動作)
タスクを自作するには?
モデル
-
Task LibraryがInput, Outputの違い(型、サイズ)を吸収してくれる
(アプリがリサイズ、型変換を意識しなくてよい) -
Labelファイルが不要となる(モデルに埋め込める)。
(モデルとラベルを一元管理でき、Task Libraryが結果からラベルを返してくれる)
ラズパイ4でカメラキャプチャのサンプルを作る
用意したサンプル
環境
- Raspberry Pi 4 4GB
- Raspberry Pi OS 64bit
- Raspberry Pi Camera Module V2.1(UVCカメラでもOKなはず)
ビルド環境の準備と必要なモジュールのインストール
# 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 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
実行
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
ハマったこと
/usr/include/c++/8/cstdlib:75:15: fatal error: stdlib.h: No such file or directory #include_next <stdlib.h>
TensorFlow Lite Support Task Libraryの使いやすさ
モデルのロード(タスクの生成)
// Build ObjectDetector.
const ObjectDetectorOptions& options = BuildOptions();
ASSIGN_OR_RETURN(std::unique_ptr<ObjectDetector> object_detector,
ObjectDetector::CreateFromOptions(options));
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;
}
入力(画像)
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});
推論
// Run object detection and draw results on input image.
ASSIGN_OR_RETURN(DetectionResult result,
object_detector->Detect(*frame_buffer));
- RGBAやYUVなどの形式の場合はRGBに変換される。
- アスペクトを維持せず、モデルの入力サイズにリサイズ。
(アスペクト比を維持しないので要注意) - Orientationのパラメータによって、画像を回転して推論。
出力
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();
}
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;
}
感想
使ってみた感想
- TensorFlow Liteモデルの入出力の型、サイズを意識しなくてよいので実装がとても楽。
- TensorFlow Lite APIを使た場合と比べて1/2~1/3の実装で済む。
-
OpenCVを使っても実装が楽。とくにInputをFlatBufferへの変換。
(たぶんこれはAndroid、iOSの場合もそうかも?)
-
Bazelを使ったビルド(これは自分が慣れていないせいもある)。
とくにOpenCVを追加したらなぜかビルドエラー。。。 -
APIのリファレンスがまだ整備されていない。
まだ正式リリースでもない状態なので仕方がない。今後に期待。
MediaPipeとは何が違うの?
- MediaPipeは画像系のタスクに特化、Task Libraryは画像以外のタスクも可能。
-
MediaPipeは推論部分だけでなくて、前処理、後処理も含めてのフレームワーク。
作成したコンポーネントを再利用可能として、開発を容易とする。
Task Libraryは推論部分の実装を容易とする。
Coralとは何が違うの
- Task LibraryはEdgeTPU delegateができない。
- Pythonで扱うことができるAPIはPyCoralのみ。





