코딩뚠뚠

[머신러닝 공부] Tiny ML -14 / 인체감지 어플리케이션-2 본문

공부/ML&DL

[머신러닝 공부] Tiny ML -14 / 인체감지 어플리케이션-2

로디네로 2022. 2. 26. 01:42
반응형

 

 

 

Chapter14. 인체감지 어플리케이션

 

"

카메라로 인체를 감지해보자 (CNN)

"

 

목차 : 

  • 개요 -> 이전장
  • 만들고자하는 -> 이전장
  • 어플리케이션 아키텍처 -> 이전장
  • 코드 기본흐름 
  • 핵심 함수 분석 
  • 마이크로컨트롤러 배포 -> 다음장

 

이전장 링크

 

[머신러닝 공부] Tiny ML -13 / 인체감지 어플리케이션-1

Chapter13. 인체감지 어플리케이션 " 카메라로 인체를 감지해보자 (CNN) " 목차 : 개요 만들고자하는 시스템 어플리케이션 아키텍처 코드 기본흐름 -> 다음장 핵심 함수 분석 -> 다음장 마이크로컨트

dbstndi6316.tistory.com

 

 


 

 

참고 repo

 

GitHub - tensorflow/tensorflow: An Open Source Machine Learning Framework for Everyone

An Open Source Machine Learning Framework for Everyone - GitHub - tensorflow/tensorflow: An Open Source Machine Learning Framework for Everyone

github.com

 


 

코드 기본흐름 :  

  • person_detection_test.cc
  • image_provider.h
  • detection_responder.h
  • detection_responder_test.cc

 

- person_detection_test.cc

 

 

▼ 모델에 적합한 크기의 tensor_arena를 정의한다.

constexpr int tensor_arena_size = 73 * 1024;
uint8_t tensor_arena[tensor_arena_size];

 

▼ 인터프리터를 준비한다.

▼ MicroMutableOpResolver를 사용해 필요한 Op를 등록한다.

  // 로깅 설정
  tflite::MicroErrorReporter micro_error_reporter;
  tflite::ErrorReporter* error_reporter = &micro_error_reporter;

  // 모델을 사용가능한 데이터 구조에 매핑한다.
  const tflite::Model* model = ::tflite::GetModel(g_person_detect_model_data);
  if (model->version() != TFLITE_SCHEMA_VERSION) {
    error_reporter->Report(
        "Model provided is schema version %d not equal "
        "to supported version %d.\n",
        model->version(), TFLITE_SCHEMA_VERSION);
  }

  // Op구현을 가져온다 (필요한것만)
  tflite::MicroMutableOpResolver micro_mutable_op_resolver;
  micro_mutable_op_resolver.AddBuiltin(
      tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
      tflite::ops::micro::Register_DEPTHWISE_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D,
                                       tflite::ops::micro::Register_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(
      tflite::BuiltinOperator_AVERAGE_POOL_2D,
      tflite::ops::micro::Register_AVERAGE_POOL_2D());

  // 모델을 실행할 인터프리터를 빌드한다
  tflite::MicroInterpreter interpreter(model, micro_mutable_op_resolver,
                                       tensor_arena, tensor_arena_size,
                                       error_reporter);
  interpreter.AllocateTensors();

 

▼ 입력 텐서를 검사한다.

  TfLiteTensor* input = interpreter.input(0);

  TF_LITE_MICRO_EXPECT_NE(nullptr, input);
  TF_LITE_MICRO_EXPECT_EQ(4, input->dims->size);
  TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]);
  TF_LITE_MICRO_EXPECT_EQ(kNumRows, input->dims->data[1]);
  TF_LITE_MICRO_EXPECT_EQ(kNumCols, input->dims->data[2]);
  TF_LITE_MICRO_EXPECT_EQ(kNumChannels, input->dims->data[3]);
  TF_LITE_MICRO_EXPECT_EQ(kTfLiteUInt8, input->type);

> 위를 통해 입력이 5D Tensor 구조임을 확인할 수 있다.

 

 

▼ for문을 통해 테스트 이미지를 입력텐서에(메모리영역으로) 복사해준다.

  for (int i = 0; i < input->bytes; ++i) {
    input->data.uint8[i] = person_data[i];
  }

 

▼ 입력텐서를 채운 후 추론을 실행한다.

  TfLiteStatus invoke_status = interpreter.Invoke();
  if (invoke_status != kTfLiteOk) {
    error_reporter->Report("Invoke failed\n");
  }
  TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status);

 

▼ 출력텐서를 확인한다.

  TfLiteTensor* output = interpreter.output(0);
  TF_LITE_MICRO_EXPECT_EQ(4, output->dims->size);
  TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[0]);
  TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[1]);
  TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[2]);
  TF_LITE_MICRO_EXPECT_EQ(kCategoryCount, output->dims->data[3]);
  TF_LITE_MICRO_EXPECT_EQ(kTfLiteUInt8, output->type);

> 위를 통해 모델 출력은 4D 임을 알 수 있다.

> 처음 세 차원은 네 번째 차원을 감싸는 래퍼이다. - 사실 결과는 1D여도 되는데 이 모델은 출력이 이렇다.. 그냥 인덱싱해서 사용하면 된다.

 

 

▼ '사람' 과 '사람없음' 점수를 기록 후 '사람' 점수가 더 높은지 확인한다.

  uint8_t person_score = output->data.uint8[kPersonIndex];
  uint8_t no_person_score = output->data.uint8[kNotAPersonIndex];
  error_reporter->Report(
      "person data.  person score: %d, no person score: %d\n", person_score,
      no_person_score);
  TF_LITE_MICRO_EXPECT_GT(person_score, no_person_score);

> 점수의 최소값은 0 최대값은 255이다.

 

 

▼ 이번에는 no_person_data 즉 사람이 없는 이미지도 테스트해본다.

  const uint8_t* no_person_data = g_no_person_data;
  for (int i = 0; i < input->bytes; ++i) {
    input->data.uint8[i] = no_person_data[i];
  }

 

▼ 추론을 실행한 후 '사람없음' 점수가 더 높은지 확인한다.

  person_score = output->data.uint8[kPersonIndex];
  no_person_score = output->data.uint8[kNotAPersonIndex];
  error_reporter->Report(
      "no person data.  person score: %d, no person score: %d\n", person_score,
      no_person_score);
  TF_LITE_MICRO_EXPECT_GT(no_person_score, person_score);

 

테스트를 실행해본다.

make -f tensorflow/lite/micro/tools/make/Makefile test_person_detection_test

 

실행결과

> 맨 아래줄과 같이 테스트가 통과되었음을 알 수 있다.

 

 

 

- image_provider_test.cc

 

카메라에서 데이터를 가져와 모델의 입력 텐서에 쓰기 적합한 형식으로 변환하는 코드이다.

인터페이스는 image_provider.h 에 구현되어있다.

 

▼ 이미지 데이터를 보유할 배열을 생성한다.

uint8_t image_data[kMaxImageSize];

 

▼ 카메라에서 이미지를 캡처한다. (GetImage())

 TfLiteStatus get_status =
      GetImage(error_reporter, kNumCols, kNumRows, kNumChannels, image_data);
  TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, get_status);
  TF_LITE_MICRO_EXPECT_NE(image_data, nullptr);

> 전달하는 인수에는 ErrorReporter 인스턴스, 열 개수, 행 개수, 채널 개수, image_data배열 포인터가 담겨있다.

> 함수는 이미지데이터를 image_data 배열에 쓰고 반환값이 kTLiteError라면 에러, kTLiteOk라면 정상이다.

 

 

▼ 반환된 데이터를 확인한다.

uint32_t total = 0;
  for (int i = 0; i < kMaxImageSize; ++i) {
    total += image_data[i];
  }

 

테스트를 실행해본다.

make -f tensorflow/lite/micro/tools/make/Makefile test_image_provider_test

 

실행결과

> 위와 같이 테스트 PASS를 확인할 수 있었다.

 

 

 

- detection_responder_test.cc

 

detection_responder는 추론 결과를 전달하는 코드이다.

인터페이스는 detection_responder.h에 정의되어있다.

 

▼ 인터페이스는 '사람' 과 '사람없음' 카테고리의 점수를 인수로 전달한다.

void RespondToDetection(tflite::ErrorReporter* error_reporter,
                        uint8_t person_score, uint8_t no_person_score);

 

▼ 테스트는 이 함수를 여러 번 호출하는 코드이다.

  RespondToDetection(error_reporter, 100, 200);
  RespondToDetection(error_reporter, 200, 100);

 

테스트를 실행해본다.

make -f tensorflow/lite/micro/tools/make/Makefile test_detection_responder_test

 

실행결과

역시 만들어져있는 코드라 PASS이다.ㅎㅎ

 

 


 

핵심 함수 분석 :

 

- main_functions.cc :

 

▼ 모델에 필요한 모든 Op를 가져온다.

생략 (#include 등등)

 

▼ 몇 개의 포인터 변수를 선언한다.

tflite::ErrorReporter* error_reporter = nullptr;
const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* input = nullptr;

 

▼ 메모리를 할당한다.

constexpr int kTensorArenaSize = 73 * 1024;
static uint8_t tensor_arena[kTensorArenaSize];

 

▼ setup() 함수 : 오류 리포터 작성, 모델 로드, 인터프리터 인스턴스 설정, 입력텐서에 대한 참조를 가져온다.

void setup() {

 

   ▽ 로깅 설정

  static tflite::MicroErrorReporter micro_error_reporter;
  error_reporter = &micro_error_reporter;

   ▽ 모델을 데이터 구조에 매핑한다

  model = tflite::GetModel(g_person_detect_model_data);
  if (model->version() != TFLITE_SCHEMA_VERSION) {
    error_reporter->Report(
        "Model provided is schema version %d not equal "
        "to supported version %d.",
        model->version(), TFLITE_SCHEMA_VERSION);
    return;
  }

  ▽ 필요한 Op 구현을 가져온다

  static tflite::MicroMutableOpResolver micro_mutable_op_resolver;
  micro_mutable_op_resolver.AddBuiltin(
      tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
      tflite::ops::micro::Register_DEPTHWISE_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D,
                                       tflite::ops::micro::Register_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(
      tflite::BuiltinOperator_AVERAGE_POOL_2D,
      tflite::ops::micro::Register_AVERAGE_POOL_2D());

  ▽ 모델을 실행할 인터프리터를 빌드한다

  static tflite::MicroInterpreter static_interpreter(
      model, micro_mutable_op_resolver, tensor_arena, kTensorArenaSize,
      error_reporter);
  interpreter = &static_interpreter;

  ▽ 모델 텐서에 tensor_arena 메모리를 할당한다

  TfLiteStatus allocate_status = interpreter->AllocateTensors();
  if (allocate_status != kTfLiteOk) {
    error_reporter->Report("AllocateTensors() failed");
    return;
  }

  ▽ 모델의 입력에 사용할 메모리 영역에 대한 정보를 가져온다.

  input = interpreter->input(0);

 

▼ loop() 함수 : 메인루프에서 계속 호출되는 부분의 동작을 정의한다.

void loop() {

 

  ▽ 이미지 추출기로 이미지를 가져온 뒤 참조를 입력텐서로 전달한다.

  if (kTfLiteOk != GetImage(error_reporter, kNumCols, kNumRows, kNumChannels,
                            input->data.uint8)) {
    error_reporter->Report("Image capture failed.");

  ▽ 추론을 실행하고 출력텐서를 가져와 각각의 점수를 읽는다. ('사람' '사람없음' 의)

  if (kTfLiteOk != interpreter->Invoke()) {
    error_reporter->Report("Invoke failed.");
  }

  TfLiteTensor* output = interpreter->output(0);

  // Process the inference results.
  uint8_t person_score = output->data.uint8[kPersonIndex];
  uint8_t no_person_score = output->data.uint8[kNotAPersonIndex];
  RespondToDetection(error_reporter, person_score, no_person_score);

> 이 점수는 RespondToDetection() 으로 전달된ㄷ다.

 


 

main 문 :

#include "tensorflow/lite/micro/examples/person_detection/main_functions.h"

int main(int argc, char* argv[]) {
  setup();
  while (true) {
    loop();
  }
}

 

프로그램을 로컬환경에서 실행해보자.

아래 명령어로 프로그램을 빌드한다.

make -f tensorflow/lite/micro/tools/make/Makefile person_detection

 

예제를 실행한다.

tensorflow/lite/micro/tools/make/gen/linux_x86_64/bin person_detection

> 당연히 받은 사람 이미지가 없으니 다음과 같은 결과가 나온다.

 

 


 

 

이렇게 코드를 살펴볼 수 있었다.

 

정교한 머신러닝 모델을 복잡하지 않은 방법으로 임베디드 기기에 이식할 수 있는것을 알 수 있었다.

 

다음 장에서는 실제로 모델을 MCU에 이식해볼 예정인데..

 

카메라가 해외배송이라 한달후에나 온다고 한다ㅎㅎ

 

오기전까지 다른걸 공부하고 있어야겠다.

 

 

 

 

 

 

반응형