前回、TensorFlow Liteのモデルを使ってもTensorFlowのモデルと比べて全く速くならなかったのは、ひょっとしてTensorFlowが内部でuint8をfloat32に変換して処理してるとか、TensorFlowモデルと同じくらいのメモリを使うのでキャッシュのヒット率が上がらないとかが原因で、ニューラルネットワークの実行環境としてTensorFlowでなくTensorFlow Liteを使えば速くなるのではないかと思って、やってみたら、Inception_V3_quantのtfliteモデルの1フレーム当たりの処理時間が、シングルコア(1スレッド実行)で約4.2秒(Raspberry Pi 2 v1.2(Cortex A53)、前回のTensorFlowだと約4.7秒)だった。使用したコードは後述のコードとほとんど同じなので省略する。少し速くなったが、少なくとも、TensorFlow Liteでもfloat32をuint8にすれば2倍とかのレベルで計算が速くなるという訳では無いようだ。
また、マルチコア(4スレッド実行)にすると約1.7秒だった。4コアの並列実行でCPU負荷が3.5倍になっても、2.4倍しか速くなっていない。単純なモデルの変換や実行環境の変更による高速化はこれくらいが限界なのだろうか。
とりあえず、前回と同じく、TensorFlow LiteのガイドのObject Detectionのstarter modelを動かしてみたので、やったことを記録する。
●TensorFlow Liteのビルド
TensorFlow公式のBuild TensorFlow Lite for Raspberry Piのページに従って、C++ static libraryをビルドした。
(今日見ると"install just the Python interpreter API"というリンクがあるが、筆者が先月見た時は無かったか、気付かなかったので、C++用のlibtensorflow-lite.aを作るしかないと思った。)
なお、スワップ領域が100MBだとメモリ不足で失敗したので、1GB追加した。git clone https://github.com/tensorflow/tensorflow cd tensorflow git checkout v1.13.1 sudo apt-get install crossbuild-essential-armhf ./tensorflow/lite/tools/make/download_dependencies.sh ./tensorflow/lite/tools/make/build_rpi_lib.sh
sudo su - dd if=/dev/zero of=/swapfile bs=1024 count=1048576 mkswap /swapfile chmod 0600 /swapfile swapon /swapfile
●ヘッダファイルとライブラリのインストール
/home/pi/tensorflow-lite/にインストールすることにした。
mkdir -p /home/pi/tensorflow-lite/include/tensorflow/lite cd tensorflow/lite/ cp --parents $(find . -name "*.h*") /home/pi/tensorflow-lite/include/tensorflow/lite cd ../.. cp -r tensorflow/lite/tools/make/downloads/flatbuffers/include/flatbuffers /home/pi/tensorflow-lite/include/ mkdir /home/pi/tensorflow-lite/lib cp tensorflow/lite/tools/make/gen/rpi_armv7l/lib/libtensorflow-lite.a /home/pi/tensorflow-lite/lib/
●今回作ったC++のプログラム(main.cc)
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <cstdint>
#include <tensorflow/lite/model.h>
#include <tensorflow/lite/interpreter.h>
#include <tensorflow/lite/kernels/register.h>
const char* WINNAME = "Capture";
const int CAPTURE_WIDTH = 1280;
const int CAPTURE_HEIGHT = 720;
//const int CAPTURE_WIDTH = 640;
//const int CAPTURE_HEIGHT = 480;
const int DISPLAY_WIDTH = 480;
const int DISPLAY_HEIGHT = 480;
const int colors[10][3] = {
{255, 255, 0}, {0, 255, 255}, {128, 256, 128}, {64, 192, 255}, {128, 128, 255},
{255, 255, 0}, {0, 255, 255}, {128, 256, 128}, {64, 192, 255}, {128, 128, 255}};
int main(int argc, char *argv[])
{
const char *model_file = "coco_ssd_mobilenet_v1_1.0_quant/detect.tflite";
const char *label_file = "coco_ssd_mobilenet_v1_1.0_quant/labelmap.txt";
// TensorFlow Lite things
std::unique_ptr<tflite::FlatBufferModel> model =
tflite::FlatBufferModel::BuildFromFile(model_file);
if (!model) {
std::cerr << "FlatBufferModel::BuildFromFile(\"" << model_file << "\") failed." << std::endl;
return -1;
}
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::InterpreterBuilder(*model, resolver)(&interpreter);
interpreter->AllocateTensors();
interpreter->SetNumThreads(4);
// Read class labels
std::ifstream ifs(label_file);
if (!ifs) {
std::cerr << "ifstream(\"" << label_file << "\") failed." << std::endl;
return -1;
}
std::vector<std::string> class_names;
std::string line;
while (std::getline(ifs, line)) {
class_names.push_back(line);
}
// Camera settings
cv::VideoCapture cap;
while (!cap.isOpened()) {
cap.open(0);
}
std::cout << "Camera is opened." << std::endl;
cap.set(CV_CAP_PROP_FRAME_WIDTH, CAPTURE_WIDTH);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, CAPTURE_HEIGHT);
// Main loop
int key;
do {
cv::Mat img;
cap >> img;
// Crop center square
img = img(cv::Rect(
(CAPTURE_WIDTH - CAPTURE_HEIGHT) / 2,
0,
CAPTURE_HEIGHT,
CAPTURE_HEIGHT
));
// Make resized image for input
cv::Mat X(300, 300, img.type());
cv::resize(img, X, X.size(), cv::INTER_CUBIC);
// Copy X to input_tensor
uint8_t *input_tensor = interpreter->typed_tensor<uint8_t>(interpreter->inputs()[0]);
int i = 0;
for (int y = 0; y < 300; y++) {
for (int x = 0; x < 300; x++) {
input_tensor[i++] = X.data[y * X.step + x * 3 + 2]; //BGR->RGB
input_tensor[i++] = X.data[y * X.step + x * 3 + 1];
input_tensor[i++] = X.data[y * X.step + x * 3 + 0];
}
}
// Execute inference
interpreter->Invoke();
// Get result
float *result1 = interpreter->typed_output_tensor<float>(0); //Locations (Top, Left, Bottom, Right)
float *result2 = interpreter->typed_output_tensor<float>(1); //Classes (0=Person)
float *result3 = interpreter->typed_output_tensor<float>(2); //Scores
float *result4 = interpreter->typed_output_tensor<float>(3); //Number of detections
// Draw result
cv::resize(img, img, cv::Size(DISPLAY_WIDTH, DISPLAY_HEIGHT), cv::INTER_CUBIC);
for (int i = result4[0] - 1; i >= 0; i--) {
int top = result1[10*i + 0] * 300;
int left = result1[10*i + 1] * 300;
int bottom = result1[10*i + 2] * 300;
int right = result1[10*i + 3] * 300;
#define SWAP(X,Y) {(X)+=(Y); (Y)=(X)-(Y); (X)-=(Y);}
if (left > right) SWAP(left, right);
if (top > bottom) SWAP(top, bottom);
std::string class_name = class_names[result2[i]+1];
float score = result3[i];
if (score < 0.5) continue;
std::cout << "Location=(" << left << "," << top << ")-(" << right << "," << bottom << "), ";
std::cout << "Class=" << class_name << ", ";
std::cout << "Score=" << score << ", ";
std::cout << std::endl;
left = left * DISPLAY_WIDTH / 300;
right = right * DISPLAY_WIDTH / 300;
top = top * DISPLAY_HEIGHT / 300;
bottom = bottom * DISPLAY_HEIGHT / 300;
cv::rectangle(img, cv::Point(left, top), cv::Point(right, bottom), cv::Scalar(colors[i][0], colors[i][1], colors[i][2]), 1);
cv::rectangle(img, cv::Point(left, top+20), cv::Point(left+160, top), cv::Scalar(colors[i][0], colors[i][1], colors[i][2]), CV_FILLED);
cv::putText(img, class_name + " (" + std::to_string(score).substr(0, 5) + ")",
cv::Point(left, top+15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
}
cv::imshow(WINNAME, img);
cv::moveWindow(WINNAME, 0, 0);
key = cv::waitKey(1);
if (key == 's')
cv::imwrite("result.jpg", img);
} while (key != 'q');
cv::destroyAllWindows();
cap.release();
return 0;
}
参考URLTensorFlow Lite公式ガイドのAPI解説
https://www.tensorflow.org/lite/guide/inference
TensorFlow Lite C++ APIリファレンス
https://www.tensorflow.org/lite/api_docs/cc/namespace/tflite
●Makefile
all: main
TENSORFLOW_LITE_INCLUDE_DIR = $(HOME)/tensorflow-lite/include
TENSORFLOW_LITE_LIB_DIR = $(HOME)/tensorflow-lite/lib
main: main.o
g++ -o main -L$(TENSORFLOW_LITE_LIB_DIR) `pkg-config --libs opencv` main.o -ltensorflow-lite -lpthread -ldl
main.o: main.cc
g++ -I$(TENSORFLOW_LITE_INCLUDE_DIR) `pkg-config --cflags opencv` -c main.cc
clean:
rm -f main main.o
test: main
./main
●実行結果
前回と同じなので省略
カメラからの入力画像サイズは、より小さくすると全体のフレームレートが上がるが、筆者のRaspberry Piの画面をVNCで表示している環境では、640x480とかにするとウィンドウの表示更新がなされないことがしばしば発生するので、敢えて1280x720で動作確認している。
コメント