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
まずArduCAM
とPmod 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
に追加し,以上のようにデータの取得時の動作と推論実行後の動作を記述してあげるだけで,実装は終了です。