본문 바로가기
공부/ML&DL

[머신러닝 공부] Tiny ML -19 / 제스처인식 어플리케이션 -3

by 로디네로 2022. 4. 17.
반응형

 

 

Chapter19. 제스처인식 어플리케이션

 

"

자이로센서를 이용해 간단한 제스처를 인식하는 어플리케이션을 만들어보자

"

목차 :

  • 개요 -> 이전장
  • 만들고자 하는 시스템 -> 이전장
  • 기본흐름 코드 -> 이전장
  • 제스처 감지 -> 이전장
  • MCU에 배포
  • 마치며

 

이전장링크

 

[머신러닝 공부] Tiny ML -18 / 제스처인식 어플리케이션 -2

Chapter18. 제스처인식 어플리케이션 " 자이로센서를 이용해 간단한 제스처를 인식하는 어플리케이션을 만들어보자 " 목차 : 개요 -> 이전장 만들고자 하는 시스템 -> 이전장 기본흐름 코드 -> 이전

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

 


 

MCU에 배포

 

MCU는 sparkfun edge를 사용한다.

 

배터리슬롯에 배터리를 넣으면 무선으로 작동가능하기 때문에 이번 프로젝트에 적절한 장치이다.

 

▶ sparkfun_edge/accelerometer_handler.cc

▽ SetupAccelerometer()

▽ initAccelerometer()

TfLiteStatus SetupAccelerometer(tflite::ErrorReporter* error_reporter) {
  // 클록 설정
  am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);

  // 캐시 구성 설정
  am_hal_cachectrl_config(&am_hal_cachectrl_defaults);
  am_hal_cachectrl_enable();

  // 저전력 작동 모드로 설정
  am_bsp_low_power_init();

  // 25Hz로 데이터를 수집
  int accInitRes = initAccelerometer();

 

▽ 가속도계 자체에 위치한 특수 메모리 버퍼인 FIFO 버퍼를 활성화 한다.

// LIS2DH12에 FIFO 버퍼가 있으며 CPU 사용중 데이터를 축적한다.
// 모델의 추론주기가 1/25Hz = 1.28s 보다 빠르게 하여 데이터 덮어쓰기 방지

  if (lis2dh12_fifo_set(&dev_ctx, 1)) {
    error_reporter->Report("Failed to enable FIFO buffer.");
  }

  if (lis2dh12_fifo_mode_set(&dev_ctx, LIS2DH12_BYPASS_MODE)) {
    error_reporter->Report("Failed to clear FIFO buffer.");
    return 0;
  }

  if (lis2dh12_fifo_mode_set(&dev_ctx, LIS2DH12_DYNAMIC_STREAM_MODE)) {
    error_reporter->Report("Failed to set streaming mode.");
    return 0;
  }

  error_reporter->Report("Magic starts!");

  return kTfLiteOk;
}

 

▽ init 끝나면 ReadAccelerometer() 호출하여 데이터 수집

bool ReadAccelerometer(tflite::ErrorReporter* error_reporter, float* input,
                       int length, bool reset_buffer) {
  // 예측 성공 후 필요한 경우 버퍼를 비운다.
  if (reset_buffer) {
    memset(save_data, 0, 600 * sizeof(float));
    begin_index = 0;
    pending_initial_data = true;
    // 정지후 10ms 대기 (대기시간이 없으면 데이터 읽을 시 코드 정지 가능성 있음)
    am_util_delay_ms(10);
  }

 

▽ 새 데이터가 있는지 확인하고, 없다면 함수를 반환한다.

  lis2dh12_fifo_src_reg_t status;
  if (lis2dh12_fifo_status_get(&dev_ctx, &status)) {
    error_reporter->Report("Failed to get FIFO status.");
    return false;
  }

  int samples = status.fss;
  if (status.ovrn_fifo) {
    samples++;
  }

  // Skip this round if data is not ready yet
  if (samples == 0) {
    return false;
  }

 

▽ 데이터를 더 큰 버퍼에 저장한후 함수를 호출하여 다음 센서값을 구조체에 채운다.

  axis3bit16_t data_raw_acceleration;
  for (int i = 0; i < samples; i++) {
    // Zero out the struct that holds raw accelerometer data
    memset(data_raw_acceleration.u8bit, 0x00, 3 * sizeof(int16_t));
    // If the return value is non-zero, sensor data was successfully read
    if (lis2dh12_acceleration_raw_get(&dev_ctx, data_raw_acceleration.u8bit)) {
      error_reporter->Report("Failed to get raw data.");

 

▽ 성공적으로 측정 완료시 부동소수점 값으로 변환하고 save_data[]버퍼에 연속적으로 기록한다.

▽ save_data[]는 3축*200 = 600 count가 되면 begin_index를 다시 0으로 둔다.

    } else {
      // 16비트값을 milli-G단위의 부동소수점으로 변환 및 버퍼의 현재위치에 저장
      save_data[begin_index++] =
          lis2dh12_from_fs2_hr_to_mg(data_raw_acceleration.i16bit[0]);
      save_data[begin_index++] =
          lis2dh12_from_fs2_hr_to_mg(data_raw_acceleration.i16bit[1]);
      save_data[begin_index++] =
          lis2dh12_from_fs2_hr_to_mg(data_raw_acceleration.i16bit[2]);
      // 순환 배열처럼 처음부터 시작
      if (begin_index >= 600) begin_index = 0;
    }
  }

 

▽ 추론전 초기 데이터가 충분한지 확인한다.

  // Check if we are ready for prediction or still pending more initial data
  if (pending_initial_data && begin_index >= 200) {
    pending_initial_data = false;
  }

  // Return if we don't have enough data
  if (pending_initial_data) {
    return false;
  }

 

▽ 데이터가 버퍼에 충분하다면, 버퍼로부터 데이터를 텐서에 대한 포인터로 복사한다.

  for (int i = 0; i < length; ++i) {
    int ring_array_index = begin_index + i - length;
    if (ring_array_index < 0) {
      ring_array_index += 600;
    }
    input[i] = save_data[ring_array_index];
  }
  return true;
}

 

▶ sparkfun_edge/output_handler.cc > 출력핸들러

LED 출력모드로 설정

void HandleOutput(tflite::ErrorReporter* error_reporter, int kind) {
  static bool is_initialized = false;
  if (!is_initialized) {
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_RED, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_BLUE, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_GREEN, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_YELLOW, g_AM_HAL_GPIO_OUTPUT_12);
    is_initialized = true;
  }
  // 추론 수행될때마다 노란 LED를 토글
  static int count = 0;
  ++count;
  if (count & 1) {
    am_hal_gpio_output_set(AM_BSP_GPIO_LED_YELLOW);
  } else {
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_YELLOW);
  }

 

▽ 감지된 제스처를 시리얼로 확인하는 코드 (노가다로 별표 찍는 코드)

  if (kind == 0) {
    error_reporter->Report(
        "WING:\n\r*         *         *\n\r *       * *       "
        "*\n\r  *     *   *     *\n\r   *   *     *   *\n\r    * *       "
        "* *\n\r     *         *\n\r");
    am_hal_gpio_output_set(AM_BSP_GPIO_LED_RED);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_BLUE);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_GREEN);
  } else if (kind == 1) {
    error_reporter->Report(
        "RING:\n\r          *\n\r       *     *\n\r     *         *\n\r "
        "   *           *\n\r     *         *\n\r       *     *\n\r      "
        "    *\n\r");
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_RED);
    am_hal_gpio_output_set(AM_BSP_GPIO_LED_BLUE);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_GREEN);
  } else if (kind == 2) {
    error_reporter->Report(
        "SLOPE:\n\r        *\n\r       *\n\r      *\n\r     *\n\r    "
        "*\n\r   *\n\r  *\n\r * * * * * * * *\n\r");
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_RED);
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_BLUE);
    am_hal_gpio_output_set(AM_BSP_GPIO_LED_GREEN);
  }
}

 

▶ 예제 실행하기

 

▽ 저장소 복제 (구버전이기 때문에 clone 보다는 위 참고 repo에서 zip을 다운받아 풀어 사용하기)

git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow

 

▽ 바이너리 빌드 

make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge magic_wand_bin
tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/magic_wand.bin

 

▽ 바이너리 서명

cp tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/ \
apollo3_scripts/keys_info0.py tensorflow/lite/micro/tools/make/downloads/ \
AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py

python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/ \
apollo3_scripts/create_cust_image_blob.py --bin tensorflow/lite/micro/tools/ \
make/gen/sparkfun_edge_cortex-m4/bin/magic_wand.bin --load-address 0xC000 \
--magic-num 0xCB -o main_nonsecure_ota --version 0x0

 

플래시 할 파일의 최종 버전을 생성

python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/ \
tools/apollo3_scripts/create_cust_wireupdate_blob.py --load-address 0x20000 \
--bin main_nonsecure_ota.bin -i 6 -o main_nonsecure_wire --options 0x1

 

바이너리파일 플래시

ls /dev/tty*

export DEVICENAME=/dev/ttyUSB0
export BAUD_RATE=921600
sudo chmod 666 /dev/ttyUSB0

python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/ \
tools/apollo3_scripts/uart_wired_update.py -b ${BAUD_RATE} ${DEVICENAME} \
-r 1 -f main_nonsecure_wire.bin -i 6

 

결과 확인

 

- 대기상태

- 제스처를 화면에 띄우기

screen ${DEVICENAME} 115200

 

- 움직임 (반 삼각형)

 

 

 

- 제스처 출력

반 삼각형 모양의 슬로프가 출력되었다.

 

여기까지 제스처 인식 모델을 MCU에서 실행시켜봤다.

 

다음장에서는 지금까지 사용했던 TF Lite에 대해서 조금 더 자세히 알아보도록 하자.

 

 

반응형

댓글