TensorFlow/Tensorflow Lite

Tensorflow Lite 실행하기(PC/C++)

kim선달 2021. 8. 30. 03:38
Select Language

Tensorflow Lite 실행하기(PC/C++)


C++라이브러리를 빌드하는법은 여기를 참고해주세요!

이 글에서는 CMake를 사용해서 C++프로젝트를 빌드하였습니다.


 

1. CMake 프로젝트 생성

우선, CMake를 이용한 C++프로젝트를 생성합니다.

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(20210829)

set(CMAKE_CXX_STANDARD 14)

add_executable(20210829 main.cpp)

 

2. Tensorflow Lite를 CMake 라이브러리로 만들기

여기에 Tensorflow Lite를 CMake 라이브러리로 연결을 해 볼 겁니다.

위 사진처럼 정석적인 CMake 라이브러리 구조를 만들어 줍니다.

이제 include 안에는 Tensorflow 헤더를 넣고, lib 안에는 빌드된 라이브러리를 넣으면 됩니다.

 

2-1. Include 파일 복사

저희는 Tensorflow 가 아닌 Tensorflow Lite를 사용할거라 third_party와 tools는 필요 없습니다.

이전 글을 진행하면서 다운로드 받은 파일 안에서 tensorflow 폴더만 통채로 복사해줍니다.

주의하실점은 이전 글에서 언급된 Download dependencies를 실행한 후에 복사를 해야 합니다.

그래야 알맞은 서드파티도 함께 들어가기 때문이죠

 

복사한 후, 일단 한눈에 봐도 C/C++과 관련 없어보이는것들은 지워줍니다.

 

주의하실점은 Tensorflow Lite가 빌드할 때 자동으로 생성된 파일을 include하는 경우가 있어, 확장자가 .h가 아닌 파일을 모조리 날리시면 안됩니다.

자동 생성된 파일은 보통 확장자가 pb (protobuf의 약자 - Google이 만든 코드 생성기)긴 합니다만, 혹여 에러가 날 가능성이 있으니 일단은 나머지는 그대로 둔 채로 진행을 하겠습니다.

 

어차피 CMake에서 프로젝트를 INTERFACE로 설정할 시 실제로 사용되지 않는 파일들은 최종 실행 바이너리의 성능이나 용량에 영향을 미치지 않습니다.

 

만약 C로 빌드하신 경우에는, 필요한 헤더 파일은 십수개로 줄어들고 서드파티를 include 해주지 않아도 되는 장점이 있습니다.

Tensorflow Lite C 사용법은 여기를 참고해주세요

 

2-2. 라이브러리 복사

이제 이전 글에서 빌드한 라이브러리 파일을 복사해서 마찬가지로 lib디렉토리 밑에 붙여넣어줍니다.

2-3. CMakeLists.txt 작성

project(tflite)
add_library(tflite INTERFACE)

set(THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/tensorflow/lite/tools/make/downloads)

target_include_directories(tflite INTERFACE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${THIRD_PARTY_DIR}/flatbuffers/include
        )

target_link_libraries(tflite INTERFACE
        ${CMAKE_CURRENT_SOURCE_DIR}/lib/libtensorflowlite.dylib)

데스크톱에서 Tensorflow C++를 사용하면 flatbuffers 서드파티를 연결해 줘야 합니다.

 

여기서는 (CPU를 사용하는) 기본 라이브러리만 추가하였습니다.

다른 delegate들을 사용하려면 라이브러리 링크와 필요한 서드파티들을 include 탐색 경로에 추가하시면 됩니다.

 

3. 실행

Google에서 뿌리는 Object Detection 모델을 이용해 아래 샘플 이미지에서 물체들을 식별해 보겠습니다.

ssd_mobilenet_v1_1_metadata_1.tflite
3.99MB

 

모델을 구동시키기 위해 OpenCV역시 사용하였습니다.

아래는 Tensorflow Lite C++ API를 사용해서 구동시키는 예제 코드입니다.

#include <iostream>
#include <string>
#include <memory>
#include <utility>

#include "opencv2/opencv.hpp"

#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"

const char* pwd = "/Users/yonggyulee/CLionProjects/tflite";

const std::vector<std::string> labels = {
  "???", "person", "bicycle", "car", "motorcycle",
  "airplane", "bus", "train", "truck", "boat",
  "traffic light", "fire hydrant", "???", "stop sign",
  "parking meter", "bench", "bird", "cat", "dog",
  "horse", "sheep", "cow", "elephant", "bear",
  "zebra", "giraffe", "???", "backpack", "umbrella",
  "???", "???", "handbag", "tie", "suitcase",
  "frisbee", "skis", "snowboard", "sports ball", "kite",
  "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket",
  "bottle", "???", "wine glass", "cup", "fork",
  "knife", "spoon", "bowl", "banana", "apple",
  "sandwich", "orange", "broccoli", "carrot", "hot dog",
  "pizza","donut","cake","chair","couch",
  "potted plant","bed","???","dining table","???",
  "???","toilet","???","tv","laptop",
  "mouse","remote","keyboard","cell phone","microwave",
  "oven","toaster","sink","refrigerator","???",
  "book","clock","vase","scissors","teddy bear",
  "hair drier","toothbrush"
};

int main() {

  std::string modelname = pwd + std::string("/detect.tflite");
  cv::Mat image = cv::imread(pwd + std::string("/airport.jpg"));
  cv::Mat cvt;
  cv::resize(image, cvt, {300, 300});

  // Load the model
  std::unique_ptr<tflite::FlatBufferModel> model =
    tflite::FlatBufferModel::BuildFromFile(modelname.c_str());

  // Build the interpreter
  tflite::ops::builtin::BuiltinOpResolver resolver;
  std::unique_ptr<tflite::Interpreter> interpreter;
  tflite::InterpreterBuilder(*model, resolver)(&interpreter);

  // Resize input tensors, if desired.
  interpreter->AllocateTensors();

  // Fill `input`.
  auto* input = interpreter->typed_input_tensor<unsigned char>(0);
  std::memcpy(input, cvt.data, 300*300*3);

  interpreter->Invoke();

  auto rects = interpreter->typed_output_tensor<float>(0);
  auto classes = interpreter->typed_output_tensor<float>(1);
  auto scores = interpreter->typed_output_tensor<float>(2);
  auto numDetect = interpreter->typed_output_tensor<float>(3);


  const auto size = interpreter->output_tensor(0)->dims->size;
  for(int i=0; i<size; ++i){
    cv::Point2d tr{rects[i*4+1] * image.cols, rects[i*4] * image.rows};

    cv::rectangle(image,
                  cv::Point2d{rects[i*4+1] * image.cols, rects[i*4] * image.rows},
                  cv::Point2d{rects[i*4+3] * image.cols, rects[i*4+2] * image.rows},
                  {255,0,0}, 2);

    char buf[10];
    std::sprintf(buf, "(%.1f%%)",scores[i]*100);
    cv::putText(image,
                labels[std::floor(classes[i]+1.5)] + std::string(buf),
                cv::Point2d{rects[i*4+1] * image.cols, rects[i*4] * image.rows - 4},
                cv::FONT_ITALIC, 0.6, {0,0,0}, 2);
  }

  cv::imshow("Result", image);
  cv::waitKey(0);

  return 0;
}