Raspberry Pi 5 AI Kit向けのPython環境を構築し、HEF形式のResNetで物体検出する

前回の記事でRaspberry Pi 5上にてAI Kitのサンプルプログラムを実行できることを確認できましたので、今回はAI Kit上で実行される推論を定義するHEFファイル周りの動作確認と、開発に必要な仮想環境の作り方についてまとめてみました。前回記事のセットアップが完了しているところ状態からはじめます。

#### 事前準備

https://qiita.com/xtrizeShino/items/6980cd611406ecc74972

#### 参考にさせていただきました記事

https://www.ucl.nuee.nagoya-u.ac.jp/blog/2024/2024-05-14-raspi5hailo/

# Anaconda3を無効化する

HailoはPython3.10に依存しているとのことなので、パッケージ管理をAnacondaからpyenvに切り替えます。下記のコマンドを実行し、Anaconda3を無効化しておきましょう。

```bash
# パッケージ管理ツールcondaの環境から抜ける
(base) $ conda deactivate
# パッケージ管理ツールcondaが自動起動しないように設定
$ conda config --set auto_activate_base false
# 再起動
$ sudo reboot
```

# HailoRTパッケージをダウンロードする

Hailo8Lを制御するためのソフトウェアパッケージは「[hailo.ai/developer-zone/software-downloads/](https://hailo.ai/developer-zone/software-downloads/)」より入手できます。ユーザー登録が必要となりますので、ログイン用のメールアドレスをご準備ください。ログインすると下記画面が表示されます。

![{F2DB2F78-3699-484B-B6BA-8F03C5B91540}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/a6dd317b-029e-99ab-518d-432fefee47a0.png)

ログイン後、Python3.10向けのhailoRTのパッケージを入手しておきましょう。 **「AI Software Suite」->「HailoRT」->「ARM64」->「Linux」->「3.10」->「HailoRT – Python package (whl) for Python 3.10, for aarch64」** をダウンロードします。

![{CAC99453-32F1-4B88-9033-553849888C52}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/dedec763-762b-53d3-3ba8-9e5dd1a79df5.png)

# 開発環境を整備する

### pyenvを入手する

まずPython3.10の仮想環境を定義するためpyenvを入手します。

```bash
# pyenv を ~/.pyenv として取得する 
$ cd ~
$ git clone https://github.com/pyenv/pyenv.git .pyenv
# 必要なパッケージをインストールする
$ sudo apt update
$ sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev curl libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
# インストールしたパッケージ関連の設定を ~/.bashrc へ追記する
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
$ exit
```

### virtualenvを入手する

次にHailoRTが利用するvirtualenvを入手します。

```bash
# virtualenvをpyenv以下に格納する
$ git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv
# virtualenvの設定を ~/.bashrc へ追記する
$ echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc
```

### Python3.10を入手し、仮想環境を定義する

Python3.10をダウンロードした後、virtualenvを利用してPython3.10で動作する仮想環境「envHailo」を定義します。定義した後に、作業ディレクトリを作成し、仮想環境を有効化します。

```bash
# Python3.10をインストールする
$ pyenv install 3.10.14
# Downloading Python-3.10.14.tar.xz...
# -> https://www.python.org/ftp/python/3.10.14/Python-3.10.14.tar.xz
# Installing Python-3.10.14...
# Installed Python-3.10.14 to /home/shino/.pyenv/versions/3.10.14

# 仮想環境 envHailo を作成する
$ pyenv virtualenv 3.10.14 envHailo

# 作業ディレクトリを作成し、移動する
$ mkdir -p ~/hailo-sandbox
$ cd ~/hailo-sandbox

# 仮想環境を有効化する
$ pyenv local envHailo
(envHailo) $
```

### HailoRTを仮想環境へインストールする

先の手順でダウンロードした`hailort-4.19.0-cp310-cp310-linux_aarch64.whl`を仮想環境へインストールします。また、頻出するパッケージも併せてインストールします。

```bash
# hailortをインストールする
# 以下の例ではcp310(Python3.10)向けのHailoRT4.19.0をホームディレクトリに格納している
(envHailo) $ pip install ~/hailort-4.19.0-cp310-cp310-linux_aarch64.whl
# Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
# Processing /home/shino/hailort-4.19.0-cp310-cp310-linux_aarch64.whl
# Collecting netaddr
# ...
# [notice] A new release of pip is available: 23.0.1 -> 24.3.1
# [notice] To update, run: python -m pip install --upgrade pip

# pipが新しくなっているので更新する
(envHailo) $ python -m pip install --upgrade pip

# 機械学習系のアプリケーションでよく利用されるパッケージをインストールする
(envHailo) $ pip install jupyterlab matplotlib pillow
```

以上で開発環境は準備完了です。

## HEFファイルをHailo8Lアクセラレータ上で実行できることを確認する

続いて、実際にアクセラレータへモデルを定義するHEFファイルを投入できることを確認していきましょう。今回は下記リストの **「Classification」->「Hailo-8L(Link)」->「resnet_v1_18」->「Compiled(download)」** に格納されている、学習済みのHEFファイルを入手しました。

https://github.com/hailo-ai/hailo_model_zoo/blob/master/docs/PUBLIC_MODELS.rst

### コマンドラインツールによりHEFファイルをアクセラレータ上に転送できることを確認する

まず、コマンドラインツール`hailortcli run`を使って実行できることを確認します。`hailortcli run`を利用することにより、アクセラレータ上での実行速度を計測することができます。 **ハードウェアに対応した** HEFファイルを準備し`hailortcli run`コマンドでHEFファイルをデバイスへ投入します。※Hailo8向けのものはHailo8Lでは動作しません。

```bash
# 現在のディレクトリ
(envHailo) $ pwd
/home/shino/hailo-sandbox

# Hailo8L向けのHEFファイルをダウンロードする
(envHailo) $ wget https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.13.0/hailo8l/resnet_v1_18.hef

# Hailo-8LにHEFファイルを流し込み、実行速度を計測する
(envHailo) $ hailortcli run resnet_v1_18.hef
# Running streaming inference (resnet_v1_18.hef):
#   Transform data: true
#     Type:      auto
#     Quantized: true
# Network resnet_v1_18/resnet_v1_18: 100% | 4578 | FPS: 914.51 | ETA: 00:00:00
# > Inference result:
#  Network group: resnet_v1_18
#     Frames count: 4578
#     FPS: 914.53
#     Send Rate: 1101.30 Mbit/s
#     Recv Rate: 7.32 Mbit/s
```

### PythonコードからHailo8LアクセラレータへHEFファイルを書き込めることを確認する

続いてPythonコードからHailo8LにHEFファイルを読み込ませることができることを確認します。確認用に下記のPythonコードを用意しました。これはhailo tutorialから一部を変更したものです。hailo tutorial版のソースコードでは配列をnp.emptyで初期化した後にastypeのキャストするのですが、このキャストに失敗することがあるため、配列定義時にnp.uint8を指定するようにしました。

```python
import numpy as np
from hailo_platform import VDevice, HailoSchedulingAlgorithm

timeout_ms = 1000

params = VDevice.create_params()
params.scheduling_algorithm = HailoSchedulingAlgorithm.ROUND_ROBIN

# The vdevice is used as a context manager ("with" statement) to ensure it's released on time.
with VDevice(params) as vdevice:

    # Create an infer model from an HEF:
    infer_model = vdevice.create_infer_model('./resnet_v1_18.hef')

    # Configure the infer model and create bindings for it
    with infer_model.configure() as configured_infer_model:
        bindings = configured_infer_model.create_bindings()

        # Set input and output buffers
        buffer = np.empty(infer_model.input().shape, dtype=np.uint8)
        bindings.input().set_buffer(buffer)

        buffer = np.empty(infer_model.output().shape, dtype=np.uint8)
        bindings.output().set_buffer(buffer)

        # Run synchronous inference and access the output buffers
        configured_infer_model.run([bindings], timeout_ms)
        buffer = bindings.output().get_buffer()

        # Run asynchronous inference
        job = configured_infer_model.run_async([bindings])
        job.wait(timeout_ms)
```

スクリプトを実行し、エラーが出なければOKです。

```bash
# 作業ディレクトリにて作業する
(envHailo) $ pwd
/home/shino/hailo-sandbox

# 前手順でダウンロードした resnet_v1_18.hef があることを確認する
(envHailo) $ ls
resnet_v1_18.hef  test.py

# Pythonのバージョンを確認
(envHailo) $ python --version
# Python 3.10.14

# スクリプトを実行、エラーが無ければOK
(envHailo) $ python test.py
### 何も表示されずにコマンドの実行に成功すればOK
```

# Visual Studio Codeで開発する

Windows上のVisual Studio Codeから、Raspberry Pi 5上のソースコードを開発できるようにしてみましょう。まず、下記の設定を行い、WindowsからRaspberry Pi 5へパスワード無しでssh接続できるようにしておきます。

https://qiita.com/xtrizeShino/items/096fb800891c4525a576

続いて右下の **「><」** をクリックし **「ホストへ接続する」** からRaspberry Pi 5のサーバー名を選択すると、新しいウィンドウが開き、右下に **「SSH:raspi5」** (raspi5の箇所はホスト名により変化)と表示されます。

![{71F9F03D-C049-4E7F-94F3-35914F7E9B25}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/6dedcb8f-2075-4443-7e47-98dbe9afa829.png)

**「フォルダーを開く」** から作業ディレクトリを開きます。

![{880E4399-2239-474F-8910-B31A603FE4E9}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/98c6fdfb-468f-7312-f4c8-5f7ea3f92408.png)

拡張機能から **「Python」「Python Debugger」「Pylance」** をインストールします。

![{F1C88787-8E0A-49DF-B3BB-83382891C8FA}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/0811cc47-ad35-7255-7102-e4532b1d344f.png)


**「Ctrl+@」** を入力してターミナルを開き **「(envHailo) $」** となっていれば仮想環境にログインできています。先の手順で作成した **test.py** を開き **「F5」->「Python Debugger」->「Pythonファイル」** を選択することによりデバッグすることができます。**仮想環境を正しく読み込めていれば from hailo_platform import～文を実行することができます。読み込めていない場合はパッケージが無い旨のエラーが出力されます。** 

![{6C66DEEF-D0F8-4013-93F7-3789BAC3E546}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/7d415d93-db64-7396-dd15-f092d22fe338.png)

上記の箇所のブレークポイントまで進めていれば、仮想環境は有効になっています。

----

ここまでの内容を利用して、ダウンロードしてきたHEF形式のResNetを使って物体検出するデモを作成してみました。作成したスクリプトは下記の通りです。スクリプトと同じディレクトリに、以下の画像（`dog.jpg`）と、imagenetのクラス一覧を`imagenet_classes.txt`（[Download](https://raw.githubusercontent.com/pytorch/hub/refs/heads/master/imagenet_classes.txt)）としてを保存し、実行することで、推論結果 **「dingo:68.6%」** を得ることができます。

このプログラムのポイントは、CPU上での推論と異なり、HEF形式のプログラムが、入力テンソルにRGBの256階調のuint8型（CPUの場合はRGBの0～1のfloat型）を、出力テンソルにuint8型（CPUの場合は0%～100%のfloat型）を選択しているところです。 **ハードウェアに合わせた量子化がされているため、CPU上で実行していた推論プログラムからやや変更する必要があります。**

![dog.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/a094dc26-1599-5ada-5a0c-ef68f84a4073.jpeg)


```python
import numpy as np
from hailo_platform import VDevice, HailoSchedulingAlgorithm
import cv2

timeout_ms = 1000

params = VDevice.create_params()
params.scheduling_algorithm = HailoSchedulingAlgorithm.ROUND_ROBIN

# load resnet classes
with open('./imagenet_classes.txt') as f:
    labels = [line.strip() for line in f.readlines()] 

# The vdevice is used as a context manager ("with" statement) to ensure it's released on time.
with VDevice(params) as vdevice:

    # Create an infer model from an HEF:
    infer_model = vdevice.create_infer_model('./resnet_v1_18.hef')

    # Configure the infer model and create bindings for it
    with infer_model.configure() as configured_infer_model:
        bindings = configured_infer_model.create_bindings()

        im = cv2.imread('dog.jpg')
        input_img = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
        img_detect = cv2.resize(input_img, (224, 224))
        # print(img_detect.shape) -->> (224, 224, 3)

        # Set input and output buffers
        #buffer = np.empty(infer_model.input().shape, dtype=np.uint8)
        buffer = img_detect
        bindings.input().set_buffer(buffer)
        # print(buffer.shape) -->> (224, 224, 3)

        buffer = np.empty(infer_model.output().shape, dtype=np.uint8)
        bindings.output().set_buffer(buffer)
        # print(buffer.shape) -->> (1000,)

        # Run synchronous inference and access the output buffers
        configured_infer_model.run([bindings], timeout_ms)
        buffer = bindings.output().get_buffer()
        # print(buffer.shape) -->> (1000,)

        sorted_val = np.sort(buffer)[::-1]
        sorted_idx = np.argsort(buffer)[::-1]
        print("-->> {0}:{1:.1f}%".format(
            labels[sorted_idx[0]],
            sorted_val[0] / 255 * 100
            ))

        # Run asynchronous inference
        job = configured_infer_model.run_async([bindings])
        job.wait(timeout_ms)
```

----

少しずつHailoの開発のイメージがついてきましたね。次回はONNX形式から、HAR形式に、そしてHEF形式へと変換するあたりに触れてみたいと思います。