STM32で人物検出する推論ネットワークの実装

概要

カメラで画像データを取得し,学習済みネットワークを利用して人物検出を行います。
ここではSTM32リーフ上にネットワークを実装する方法を説明します。
実装の際にはカメラやディスプレイが必要ですが,最小限の説明に留めていますので予めご了承ください。

使用するリーフ

以下のリーフを使用します。

Type Name Q’ty
AZ62 Connector Cover 2
AZ01 USB 1
AP04 STM32 MCU 2Bus 1
AI02 SP&PIR 1
AX08 29pin header 2
AZ63 Nut Plate 2
M2*10mm screw 4

事前準備

カメラの準備

Leafonyにはカメラ専用コネクタがありません。
今回はSPI通信でデータ通信が可能なArduCAM Mini (2メガピクセル)を用意しました。

ディスプレイの準備

カメラから正しくデータを受け取れているかを確認するためにディスプレイを用意します。
今回はSPI通信でデータを表示することが可能なPmod MTDSを利用しました。

深層学習ネットワークの準備

今回の人物検出はSTマイクロ社が用意されている学習済みネットワークを利用します。
まずは,FP-AI-VISION1をダウンロードします。

各種ドライバの準備

カメラのドライバ

githubにArduCAMがArduino環境用に用意されたライブラリがアップロードされています。
今回はHALライブラリを利用するのでSPIを通信する部分を少し書き換える必要があります
STM32用のライブラリもアップロードされていますが,Standard Peripheral Libraryを利用しており,今回は使えません。

ディスプレイのドライバ

digilent社の公式のgithubにディスプレイをコントロールする関数が公開されています。
こちらもHALライブラリを利用するのでSPIの通信に関する関数を書き換える必要があります。

STMマイコンの基本的な設定

ピン設定

ターゲットマイコンにSTM31L452RETxを選択

推論ネットワークの初期化コードの設定

STMマイコンに深層学習を実装するにはSTマイクロ社が用意しているツール(X-CUBE-AI)を利用するのが一番手軽に実装できます。
さらに同社からAIアプリケーション用の便利な関数パック(FP-AI-VISION1)が配布されています。
この中にExampleとして人物検出用の学習済みネットワークが含まれていますので,これを今回の推論ネットワークとして利用しました。

X-CUBE-AIのインストール

X-CUBE-AIはオプションのツールなので,IDEへ導入する必要があります。

  • はじめにManage Software Packsをクリックしてください。
  • Emmbedded Software Packages Managerというウィンドウが開かれます。
  • STMicroelectronicsというタブを選択します。
  • X-CUBE-AIをクリックすると様々なバージョンのArtificial Intelligenceが表示されますので,導入したいバージョンのチェックボックをクリックし,下のInstall Nowボタンをクリックしてインストールしてください。

ニューラルネットワーク用の設定

ダウンロードしたFP_AI_VISION1の中に人物検出ネットワークが入っています。

  • FP_AI_VISION1/Utilities/AI_resources/PersonDetection/Google_Model/person_detect.tflite

これを次の手順でX-CUBE-AIに読みこませます。

  • Pinout & Configurationのタブをクリック
  • Software Packsを選択
  • Configurationが開かれるので,networkのタブをクリック
  • Model inputsにネットワーク情報を入力します
    • choose model: TFLite
    • Saved Model
    • Model: 先ほどのFP_AI_VISION1内のネットワークファイルを指定
  • ネットワークの圧縮と検証用の設定を入力します
    • Compression: None
    • Validation inputs: Random numbers
    • Validation outputs: None
  • Analyzeボタンをクリックすると解析が始まります
  • 以下の画面が出れば完了です
  • 最後に,設定した内容でコードが生成されるようにModeで以下の箇所にチェックボックスにチェックを入れます
    • Artificial Intelligence X-CUBE-AI
    • Artificial Intelligence Application

初期化コードの生成

設定が完了したのでコードを生成します。
Project -> Generate Codeをクリックして生成してください。
(Alt+Kでも生成できます。)

プロジェクトの構成

はじめにドライバや画像処理用のファイルを追加します。
STM32CubeIDEのProject Explorerを見ると次のフォルダが自動生成されていることが分かります。

  • Core
  • Drivers
  • X-CUBE-AI

まずArduCAMPmod MTDS制御用のコードをDriverに配置します。
次にFP_AI_VISION1内の画像処理用の便利な関数を利用したいので,以下のフォルダを作成します。

  • Middlewares

ダウンロードしたFP_AI_VISION1内の{FP_AI_VISION1}/Middlewares/ST/STM32_Imageを作成したMiddlewaresにインポートしてください。
グレイスケールからRGB画像データに変換する関数や画サイズを変換する関数が用意されています。

また,私は人物検出のネットワークのファイルを以下のように作成して入れました。
これは読み込ませたニューラルネットワークのファイルを分かりやすい場所においておきたいだけで必須ではありません。

  • Utilities

推論実行部のコード

自動生成されたコードであるX-CUBE-AI/App内のapp_x-cube-ai.cのコード内にMX_X_CUBE_AI_Processという関数があります。
この関数内部で以下の関数が重要です。

  • acquire_and_process_data()
    • ネットワークに入力するデータを取得する
  • ai_run()
    • 推論を実行する
  • post_process()
    • 推論実行後のデータに対する対応を記述

それぞれ以下のように実装しましょう
ai_runはメモリ確保された入力データと出力データのポインタを渡して上げるだけで,自動生成されたコードだけで実行されます。
ですからほぼ修正する必要がありません。とても便利ですね。

int acquire_and_process_data(uint8_t *data)
{
  uint32_t sTime;
  uint32_t eTime;
  uint8_t eMsg[256] = {0};
  extern Image_t cameraImg;
  extern Image_t resizedImg;
  extern Image_t inputImg;

  sTime = HAL_GetTick();

  captureBMP565();

  eTime = HAL_GetTick();

  sprintf(eMsg, " Done : %lums\r\n", eTime-sTime);
  HAL_UART_Transmit(&huart2, (uint8_t *)eMsg, sizeof(eMsg), 0xFFFF);

  if ((cameraImg.width != inputImg.width) || (cameraImg.height != inputImg.height))
  {
    ImgResize(&cameraImg, &resizedImg, NEAREST);
    ImgToGrayscale(&resizedImg, &inputImg);
  }
  else
  {
    ImgToGrayscale(&cameraImg, &inputImg);
  }
  memcpy(data, inputImg.pData, INPUT_IMG_HEIGHT*INPUT_IMG_WIDTH);

  return 0;
}
static int ai_run(void *data_in, void *data_out)
{
  ai_i32 batch;

  ai_buffer *ai_input = network_info.inputs;
  ai_buffer *ai_output = network_info.outputs;

  ai_input[0].data = AI_HANDLE_PTR(data_in);
  ai_output[0].data = AI_HANDLE_PTR(data_out);

  batch = ai_network_run(network, ai_input, ai_output);
  if (batch != 1) {
    ai_log_err(ai_network_get_error(network),
        "ai_network_run");
    return -1;
  }

  return 0;
}
int post_process(uint8_t *data)
{
  uint32_t sTime;
  uint32_t eTime;
  uint8_t eMsg[256] = {0};
  extern Image_t inputImg;
  extern Image_t cameraImg;

#if defined(USE_PMODMTDS)
  sTime = HAL_GetTick();

  displayReInit();
  displayImage(&cameraImg, 120-cameraImg.height/2, 160-cameraImg.width/2);

  eTime = HAL_GetTick();
  sprintf(eMsg, "Done : %lums\r\n", eTime-sTime);
  HAL_UART_Transmit(&huart2, (uint8_t *)eMsg, sizeof(eMsg), 0xFFFF);
#endif

  if ((data[1] > data[2])) {
    char msg[] = "\r\n______ Person Detected!!\r\n\r\n";
    HAL_UART_Transmit(&huart2, (uint8_t *)msg, sizeof(msg), 0xFFFF);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
#if defined(USE_BUZZER)
    myPWMStart(10, 1000, 500);
    HAL_Delay(100);
    myPWMStop();
    HAL_Delay(100);
    myPWMStart(10, 1000, 500);
    HAL_Delay(100);
    myPWMStop();
#endif
#if defined(USE_PMODMTDS)
    displayPersonDetected();
#endif
  } else {
    char msg[] = "\r\n______ No Person\r\n\r\n";
    HAL_UART_Transmit(&huart2, (uint8_t *)msg, sizeof(msg), 0xFFFF);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
#if defined(USE_BUZZER)
    myPWMStop();
#endif
#if defined(USE_PMODMTDS)
    displayNoPersonDetected();
#endif
  }

  return 0;
}

この3つの関数は自動生成されたコードに修正を加えなくてもmain.cppから勝手に呼び出され,ずっとループ実行されます。
各種インタフェースのドライバの初期化コードをmain.cppに追加し,以上のようにデータの取得時の動作と推論実行後の動作を記述してあげるだけで,実装は終了です。


最終更新 August 11, 2021