Orange Pi5のNPUを使用してyolo(高速?)を動かしてみる(rknn-toolkit2)
記事を書くきっかけになった経緯 📺
Orange Pi5を早期予約購入して、何かやろうと思っていたら、6か月経っていた。
最近は開発環境が整ってきており、インターネットの記事をみると面白うなことをやっている人がけっこういます。 私も、何か人がやっていないことをやりたいなーと思い、日々すごしていたら非常に興味のそそられる動画に会いました。 Orange Pi5でYoloを動かしていてfps値が60ぐらいでています。どうやら、Orange Pi5に搭載されているNPUを使用している感じです。香橙派5(中国語でOrange Pi5を意味するらしい)これは面白そう!と思い、どうやるのかを調べてみても日本語のページはおろか英語のページすらなかなかでてこない。香橙派5をキーワードに中国語の記事をあさってみる。
そして、いろいろ調べて結果、中国語技術サイトcsdnでそれらしき記事たちを発見!
サイトとDeepL翻訳を駆使しながらやってみましたが、かなり失敗ました。環境構築に失敗ため、WSLの再インストールやツールのverによるエラー。中国のミラーサイトからダウンロードをしているようだがアクセスが出来ずにダウンロードできないなど、かなり踏んだり蹴ったりでした。
そのため、Orange Pi5のNPUを用いたyoloアルゴリズム実装を日本語で記載を行い、多くの人にOrange Pi5と機械学習(yolo)の可能性を知っていただきたい。そして、私がまだ実装できていないmodelをOrange Pi5に実装する一助になれば幸いです。
必要なもの 🎓
- Orange Pi5本体
- SDカード32GB
- OSはOrange 1.1.4 Jammy
- Ubuntu 22.04.2 LTS環境(公式ではUbuntu 18.04 python 3.6 / Ubuntu 20.04 python 3.8をサポートしてるらしい。私はWSL2を使って環境を作りました。)
実装手順概要 🔨
- Orange Pi5にOSをインストール
- Ubuntu22.04.2 LTS(WSL2)上でyoloのモデルをonnx形式からrknn形式に変換する
- Orange Pi5にrknn形式のモデルを実装する
1. Orange Pi5にOSをインストール 🍘
Orange Pi5にOSをインストールします。使用するソフトウェアはbalenaEtcherを使用します。各自にあった環境をインストールしてください。
OSをOrange Pi5の公式からインストールします。Downloadのとこから、Ubuntu Imageを選択してもらいOrangepi5_1.1.4_ubuntu_jammy_desktop_xfce_linux5.10.110.7zをダウンロードします。
imgファイルをbalenaEtcherで書き込んでください。
書き込みが終了したら、SDカードをOrange Pi5に差し込みUbuntuを起動!起動できたら成功です。
とりあえず、初期設定とかネットワークの設定を行ってください。私は毎回windowsからファイルをscp転送したかっため、固定ipにしてipが毎回変わらないようにしました。
とりあえず、updateとupgradeをしておきます。
sudo apt-get update
sudo apt-get upgrade
ひとまず、Orange Pi5のセットアップは終了です。
2. Ubuntu22.04.2 LTS(WSL2)上でyoloのモデルをonnx形式からrknn形式に変換する🍘
Ubuntuのインストール(WSL2)
Ubuntu22.04.2 LTS環境を用意します。私はメインPCがWindows 11なので、WSL2を使用してUbuntu環境を構築しました。まず、WSL2でUbuntu22.04.2 LTS(WSL2)をインストールします。Win11上でWSLを使うにはMicrosoft StoreでUbuntuをダウンロードして、インストールすれば終了です。2023/5現在では、ただのUbuntuをインストールすれば、Ubuntu22.04.2 LTSが手に入ります。初回起動に下記のメッセージが表示されたら、下記サイトに従って、「Windows の機能の有効化または無効化」を開き、「Linux 用 Windows サブシステム」を有効にして下さい。
このアプリケーションには、Linux 用 Windows サブシステムオプション コンポーネントが必要です。
変更を有効にするには、システムの再起動が必要な場合があります。
Ubuntuのインストールが終了したら、Anaconda環境でPython環境を構築していきます。参考にしたサイトは下記サイトです。
2023/5現在では下記のような感じで、ダウンロードできる。
wget https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh
下記コマンドでインストール用シェルを実行。流れにそってインストールする。
bash Anaconda3-2023.03-1-Linux-x86_64.sh
インストールが終了したらパスに追加して実行できるようにする
echo "export PATH=~/anaconda3/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
rknn-toolkit2の環境構築
次に、rknn-toolkit2の環境を構築していきます。まず最初にanacondaで仮想環境を作っていきます。仮想環境はPython=3.8で構築しました。
rknn-toolkit2をcloneします。
git clone https://github.com/rockchip-linux/rknn-toolkit2.git
念のためにcpでコピーして別の名前にして使用します。
cp -r rknn-toolkit2/ rknn
仮想環境を用意します。
conda create -n rknn python=3.8
conda activate rknn
docのフォルダに移動して、requirement.txtを利用してpythonのライブラリををインストールすると中国の記事には書いてある。(しかしながら、これが動かない。)
cd rknn/doc
pip install -r requirements_cp38-1.4.0.txt -i https://mirror.
baidu.com/pypi/simple
ミラーを外してやってみたり、pip3でやってみたり小細工をしてみたがtensorflowのインストールでエラー吐いたり、subprocess-exited-with-errorが生じたり、解決することが出来なかった。
そのため、下記の順番でrequirements_cp38-1.4.0.txtに書かれているものをインストールしたら、動作することを確認できた。なぜかはわからない。tensorflowとかは最新のを使用しようしている?ため、必要なライブラリが色々と複雑?であるため、一番初めにインストールしておく。
pip install tensorflow==2.6.2
conda install pytorch==1.10.1 torchvision==0.11.2 torchaudio==0.10.1 cpuonly -c pytorch
pip install onnx==1.9.0
pip install onnxoptimizer==0.2.7
pip install onnxruntime==1.10.0
pip install opencv-python==4.5.5.64
pip install bfloat16
pip install cmake
sudo apt-get install gcc libpq-dev -y
sudo apt-get install python-dev python-pip -y
sudo apt-get install python3-dev python3-pip python3-venv python3-wheel -y
pip install bfloat16==1.1
pip install tqdm==4.64.0
pip install scipy==1.5.4
pip install ruamel.yaml==0.17.4
pip install psutil==5.9.0
pip install requests==2.27.1
pip install flatbuffers==1.12
pip install protobuf==3.12.2
pip install numpy==1.19.5
2回目のpip install bfloat16==1.1はいらないかもしれませんが、とりあえずやってみて下さい。
全て、インストールし終えたら、Packageに移動します。
cd ~/rknn/packages
pip install rknn_toolkit2-1.4.0_22dcfef4-cp38-cp38-linux_x86_64.whl
これで、rknn_toolkit2がインストールできたと思います。
Installing collected packages: typing-extensions, rknn-toolkit2
Attempting uninstall: typing-extensions
Found existing installation: typing_extensions 4.5.0
Uninstalling typing_extensions-4.5.0:
Successfully uninstalled typing_extensions-4.5.0
Successfully installed rknn-toolkit2-1.4.0-22dcfef4 typing-extensions-3.7.4.3
ここで、ライブラリインストールは終わりではありません。わたしはこの後、rknnをファイルを生成するためにエラーに引っ掛かり、かなりの時間を要してしまいました。これも理由がわからないのですが、Pythonのライブラリのver関係上このまま動かすとエラーが生じます。やってみたい人は次のステップを飛ばして、rknnモデル生成をやってみると下記のようなエラーが生じると思います。
raise InvalidVersion(f"Invalid version: '{version}'")
pkg_resources.extern.packaging.version.InvalidVersion: Invalid version: '1.4.0-22dcfef4'
上記のエラーは調べてもでてこなかったのですが、近しい質問を調べているとどうやら、setuptoolsが関係してそうな感じでした。そのため、setuptoolsのverをダウングレードして、動作させるようにしました。
pip uninstall setuptools
pip install setuptools==65.0.0
最終的にはcondaの環境はこんな形のライブラリ構成になると思います。
# Name Version Build Channel
_libgcc_mutex 0.1 main
_openmp_mutex 5.1 1_gnu
absl-py 0.15.0 pypi_0 pypi
astunparse 1.6.3 pypi_0 pypi
bfloat16 1.1 pypi_0 pypi
blas 1.0 mkl
bzip2 1.0.8 h7b6447c_0
ca-certificates 2023.01.10 h06a4308_0
cachetools 4.2.4 pypi_0 pypi
certifi 2023.5.7 pypi_0 pypi
charset-normalizer 2.0.12 pypi_0 pypi
clang 5.0 pypi_0 pypi
cmake 3.26.3 pypi_0 pypi
cpuonly 2.0 0 pytorch
ffmpeg 4.3 hf484d3e_0 pytorch
flatbuffers 1.12 pypi_0 pypi
freetype 2.12.1 h4a9f257_0
gast 0.4.0 pypi_0 pypi
giflib 5.2.1 h5eee18b_3
gmp 6.2.1 h295c915_3
gnutls 3.6.15 he1e5248_0
google-auth 1.35.0 pypi_0 pypi
google-auth-oauthlib 0.4.6 pypi_0 pypi
google-pasta 0.2.0 pypi_0 pypi
grpcio 1.54.0 pypi_0 pypi
h5py 3.1.0 pypi_0 pypi
idna 3.4 pypi_0 pypi
importlib-metadata 6.6.0 pypi_0 pypi
intel-openmp 2023.1.0 hdb19cb5_46305
jpeg 9e h5eee18b_1
keras 2.6.0 pypi_0 pypi
keras-preprocessing 1.1.2 pypi_0 pypi
lame 3.100 h7b6447c_0
lcms2 2.12 h3be6417_0
ld_impl_linux-64 2.38 h1181459_1
lerc 3.0 h295c915_0
libdeflate 1.17 h5eee18b_0
libffi 3.4.4 h6a678d5_0
libgcc-ng 11.2.0 h1234567_1
libgomp 11.2.0 h1234567_1
libiconv 1.16 h7f8727e_2
libidn2 2.3.4 h5eee18b_0
libpng 1.6.39 h5eee18b_0
libstdcxx-ng 11.2.0 h1234567_1
libtasn1 4.19.0 h5eee18b_0
libtiff 4.5.0 h6a678d5_2
libunistring 0.9.10 h27cfd23_0
libuv 1.44.2 h5eee18b_0
libwebp 1.2.4 h11a3e52_1
libwebp-base 1.2.4 h5eee18b_1
lz4-c 1.9.4 h6a678d5_0
markdown 3.4.3 pypi_0 pypi
markupsafe 2.1.2 pypi_0 pypi
mkl 2023.1.0 h6d00ec8_46342
mkl-service 2.4.0 py38h5eee18b_1
mkl_fft 1.3.6 py38h417a72b_1
mkl_random 1.2.2 py38h417a72b_1
ncurses 6.4 h6a678d5_0
nettle 3.7.3 hbbd107a_1
numpy 1.19.5 pypi_0 pypi
oauthlib 3.2.2 pypi_0 pypi
onnx 1.9.0 pypi_0 pypi
onnxoptimizer 0.2.7 pypi_0 pypi
onnxruntime 1.10.0 pypi_0 pypi
opencv-python 4.5.5.64 pypi_0 pypi
openh264 2.1.1 h4ff587b_0
openssl 1.1.1t h7f8727e_0
opt-einsum 3.3.0 pypi_0 pypi
pillow 9.4.0 py38h6a678d5_0
pip 23.0.1 py38h06a4308_0
protobuf 3.12.2 pypi_0 pypi
psutil 5.9.0 pypi_0 pypi
pyasn1 0.5.0 pypi_0 pypi
pyasn1-modules 0.3.0 pypi_0 pypi
python 3.8.16 h7a1cb2a_3
pytorch 1.10.1 py3.8_cpu_0 pytorch
pytorch-mutex 1.0 cpu pytorch
readline 8.2 h5eee18b_0
requests 2.27.1 pypi_0 pypi
requests-oauthlib 1.3.1 pypi_0 pypi
rknn-toolkit2 1.4.0-22dcfef4 pypi_0 pypi
rsa 4.8 pypi_0 pypi
ruamel-yaml 0.17.4 pypi_0 pypi
ruamel-yaml-clib 0.2.7 pypi_0 pypi
scipy 1.5.4 pypi_0 pypi
setuptools 65.0.0 pypi_0 pypi
six 1.15.0 pypi_0 pypi
sqlite 3.41.2 h5eee18b_0
tbb 2021.8.0 hdb19cb5_0
tensorboard 2.6.0 pypi_0 pypi
tensorboard-data-server 0.6.1 pypi_0 pypi
tensorboard-plugin-wit 1.8.1 pypi_0 pypi
tensorflow 2.6.2 pypi_0 pypi
tensorflow-estimator 2.6.0 pypi_0 pypi
termcolor 1.1.0 pypi_0 pypi
tk 8.6.12 h1ccaba5_0
torchaudio 0.10.1 py38_cpu [cpuonly] pytorch
torchvision 0.11.2 py38_cpu [cpuonly] pytorch
tqdm 4.64.0 pypi_0 pypi
typing-extensions 3.7.4.3 pypi_0 pypi
urllib3 1.26.15 pypi_0 pypi
werkzeug 2.3.4 pypi_0 pypi
wheel 0.38.4 py38h06a4308_0
wrapt 1.12.1 pypi_0 pypi
xz 5.4.2 h5eee18b_0
zipp 3.15.0 pypi_0 pypi
zlib 1.2.13 h5eee18b_0
zstd 1.5.5 hc292b87_0
最後にrknnの動作確認をします。
pythonの実行環境に入り、from rknn.api import RKNNをインポートできれば環境構築成功です!
(rknn) test@DESKTOP-6ILOAAU:~/rknn/packages$ python
Python 3.8.16 (default, Mar 2 2023, 03:21:46)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from rknn.api import RKNN
>>>
onnxモデルからrknnモデルの生成を行う
いよいよ機会学習モデルを生成します。中国のサイトでは独自にyoloのonnxモデルを生成していますが、今回はライブラリにあるモデルを使用します(本当は自分でモデルを作りrknnに変換したかったが、変換がうまくいかずにライブラリにあるモデルを使っている)。exapmleにあるyolov5に移動します。
cd ~/rknn/examples/onnx/yolov5
移動したら下記のようなファイル構成になっていると思います。
(rknn) test@DESKTOP-6ILOAAU:~/rknn/examples/onnx/yolov5$ ls
bus.jpg dataset.txt test.py yolov5s.onnx
test.pyの中身を確かめます。
(rknn) test@DESKTOP-6ILOAAU:~/rknn/examples/onnx/yolov5$ vi test.py
import os
import urllib
import traceback
import time
import sys
import numpy as np
import cv2
from rknn.api import RKNN
ONNX_MODEL = 'yolov5s.onnx'
RKNN_MODEL = 'yolov5s.rknn'
IMG_PATH = './bus.jpg'
DATASET = './dataset.txt'
以下プログラムが続く
ONNX_MODEL = 'yolov5s.onnx'が現在フォルダにあるonnxモデル
RKNN_MODEL = 'yolov5s.rknn'が生成するrknnモデルになります。
またorange Pi5に動作させるために一部プログラムを変更してください。
target_platform='rk3588'をtest.pyに追加してください。
# pre-process config
print('--> Config model')
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]],target_platform='rk3588')#変更箇所
print('done')
それではプログラムを動作させます。
(rknn) test@DESKTOP-6ILOAAU:~/rknn/examples/onnx/yolov5$ python test.py
I rknn buiding done
done
--> Export rknn model
done
--> Init runtime environment
W init_runtime: Target is None, use simulator!
done
--> Running model
Analysing : 100%|███████████████████████████████████████████████| 146/146 [00:00<00:00, 3723.17it/s]
Preparing : 100%|████████████████████████████████████████████████| 146/146 [00:00<00:00, 235.03it/s]
W inference: The dims of input(ndarray) shape (640, 640, 3) is wrong, expect dims is 4! Try expand dims to (1, 640, 640, 3)!
done
class: person, score: 0.8253369927406311
box coordinate left,top,right,down: [211.98967677354813, 246.17457234859467, 283.707863509655, 515.0830570459366]
class: person, score: 0.822945237159729
box coordinate left,top,right,down: [473.26746129989624, 231.93780636787415, 562.1268258094788, 519.7597033977509]
class: person, score: 0.7970155477523804
box coordinate left,top,right,down: [114.68497347831726, 238.2831370830536, 207.21904873847961, 541.6129200458527]
class: person, score: 0.4426550567150116
box coordinate left,top,right,down: [79.09243053197861, 339.18039524555206, 121.60037952661514, 514.2349489927292]
class: bus , score: 0.7698131203651428
box coordinate left,top,right,down: [102.34192335605621, 134.41848754882812, 540.0445512533188, 460.4184875488281]
rknnモデルの生成とフォルダに入っているbus.jpgのyolo detectionを実行したような形になります。全てプログラムを見てもらうとわかりますが、imshow()はコメントアウトされているため、結果は数値でのみ出力されます。
そしてフォルダを見てみるとyolov5s.rknnモデルが生成されているのがわかります。
(rknn) test@DESKTOP-6ILOAAU:~/rknn/examples/onnx/yolov5$ ls
bus.jpg onnx_yolov5_0.npy onnx_yolov5_2.npy yolov5s.onnx
dataset.txt onnx_yolov5_1.npy test.py yolov5s.rknn
今回使用するのはyolov5s.rknnのみになります。
3. Orange Pi5にrknn形式のモデルを実装する
Orange Pi5にモデルを移動させます。
私はscpコマンドで転送をしました。(記事を書きながら、思ったのですがわざわざwindowsにもってきてからscpやらずとも仮想環境上でやればいいということに気が付きました。)
とりあえずデスクトップに置いておきます。
scp -r yolov5s.rknn オレンジパイのユーザー名@IPアドレス:/home/orangepi/Desktop
Minicondaのインストール
Minicondaのインストールを行います。minicondaのインストールは公式のホームページから
Miniconda3 Linux-aarch64 64-bitをDLする
ダウンロードしたらダウンロードしたディレクトリに移動して実行する。そして、インストールとPATHに追加を行う。conda -Vでverが確認できたら、無事インストール完了
bash Miniconda3-py39_23.3.1-0-Linux-aarch64.sh
export PATH=~/miniconda3/bin/conda:$PATH
source ~/.bashrc
conda -V
rknn_toolkit_lite2のインストール
まず、仮想環境を作成します。PythonのverはPython=3.9.1をインストールします。
conda create --name rknn python==3.9.1
conda activate rknn
rknn_toolkit_lite2(rknn_toolkit_lite2-1.4.0-cp39-cp39-linux_aarch64.whl)をインストールするのですが手法は二つあります。一つ目はrknn-toolkit2のgitからorangepiに同じようにcloneして、rknn-toolkit2/rknn_toolkit_lite2/packages/のディレクトリにあるwhlファイルをインストールする。二つ目はrknnを生成したUbuntuからscpコマンドでwhlファイルだけ持ってくる。どちらでもいいと思います。中国のサイトではscpで転送していたため、私も転送しました。
###WSL2のUbuntu
cd ~/rknn/rknn_toolkit_lite2/packages
scp -r rknn_toolkit_lite2-1.4.0-cp39-cp39-linux_aarch64.whl オレンジパイのユーザー名@IPアドレス:/home/orangepi/Desktop
デスクトップ上に転送したため、デスクトップのディレクトリに移動してインストールをすすめていきます、
中国のサイトから清華大学のリポジトリ?からインストールしたい感じだったため、同じように設定して実行しました
cd ~/Desktop
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install rknn_toolkit_lite2-1.4.0-cp39-cp39-linux_aarch64.whl
インストールが完了したら、opencvを入れます。
pip install opencv-python
下記のdeploy.pyをDesktop上に置きます。
import numpy as np
import cv2
from rknnlite.api import RKNNLite
RKNN_MODEL = 'yolov5s.rknn'
IMG_PATH = 'bus.jpg'
QUANTIZE_ON = True
OBJ_THRESH = 0.25
NMS_THRESH = 0.45
IMG_SIZE = 640
CLASSES = ("person", "bicycle", "car", "motorbike ", "aeroplane ", "bus ", "train", "truck ", "boat", "traffic light",
"fire hydrant", "stop sign ", "parking meter", "bench", "bird", "cat", "dog ", "horse ", "sheep", "cow", "elephant",
"bear", "zebra ", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite",
"baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife ",
"spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza ", "donut", "cake", "chair", "sofa",
"pottedplant", "bed", "diningtable", "toilet ", "tvmonitor", "laptop ", "mouse ", "remote ", "keyboard ", "cell phone", "microwave ",
"oven ", "toaster", "sink", "refrigerator ", "book", "clock", "vase", "scissors ", "teddy bear ", "hair drier", "toothbrush ")
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def xywh2xyxy(x):
# Convert [x, y, w, h] to [x1, y1, x2, y2]
y = np.copy(x)
y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
return y
def process(input, mask, anchors):
anchors = [anchors[i] for i in mask]
grid_h, grid_w = map(int, input.shape[0:2])
box_confidence = sigmoid(input[..., 4])
box_confidence = np.expand_dims(box_confidence, axis=-1)
box_class_probs = sigmoid(input[..., 5:])
box_xy = sigmoid(input[..., :2])*2 - 0.5
col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
grid = np.concatenate((col, row), axis=-1)
box_xy += grid
box_xy *= int(IMG_SIZE/grid_h)
box_wh = pow(sigmoid(input[..., 2:4])*2, 2)
box_wh = box_wh * anchors
box = np.concatenate((box_xy, box_wh), axis=-1)
return box, box_confidence, box_class_probs
def filter_boxes(boxes, box_confidences, box_class_probs):
"""Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
# Arguments
boxes: ndarray, boxes of objects.
box_confidences: ndarray, confidences of objects.
box_class_probs: ndarray, class_probs of objects.
# Returns
boxes: ndarray, filtered boxes.
classes: ndarray, classes for boxes.
scores: ndarray, scores for boxes.
"""
boxes = boxes.reshape(-1, 4)
box_confidences = box_confidences.reshape(-1)
box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])
_box_pos = np.where(box_confidences >= OBJ_THRESH)
boxes = boxes[_box_pos]
box_confidences = box_confidences[_box_pos]
box_class_probs = box_class_probs[_box_pos]
class_max_score = np.max(box_class_probs, axis=-1)
classes = np.argmax(box_class_probs, axis=-1)
_class_pos = np.where(class_max_score >= OBJ_THRESH)
boxes = boxes[_class_pos]
classes = classes[_class_pos]
scores = (class_max_score* box_confidences)[_class_pos]
return boxes, classes, scores
def nms_boxes(boxes, scores):
"""Suppress non-maximal boxes.
# Arguments
boxes: ndarray, boxes of objects.
scores: ndarray, scores of objects.
# Returns
keep: ndarray, index of effective boxes.
"""
x = boxes[:, 0]
y = boxes[:, 1]
w = boxes[:, 2] - boxes[:, 0]
h = boxes[:, 3] - boxes[:, 1]
areas = w * h
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x[i], x[order[1:]])
yy1 = np.maximum(y[i], y[order[1:]])
xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
inter = w1 * h1
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= NMS_THRESH)[0]
order = order[inds + 1]
keep = np.array(keep)
return keep
def yolov5_post_process(input_data):
masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
[59, 119], [116, 90], [156, 198], [373, 326]]
boxes, classes, scores = [], [], []
for input, mask in zip(input_data, masks):
b, c, s = process(input, mask, anchors)
b, c, s = filter_boxes(b, c, s)
boxes.append(b)
classes.append(c)
scores.append(s)
boxes = np.concatenate(boxes)
boxes = xywh2xyxy(boxes)
classes = np.concatenate(classes)
scores = np.concatenate(scores)
nboxes, nclasses, nscores = [], [], []
for c in set(classes):
inds = np.where(classes == c)
b = boxes[inds]
c = classes[inds]
s = scores[inds]
keep = nms_boxes(b, s)
nboxes.append(b[keep])
nclasses.append(c[keep])
nscores.append(s[keep])
if not nclasses and not nscores:
return None, None, None
boxes = np.concatenate(nboxes)
classes = np.concatenate(nclasses)
scores = np.concatenate(nscores)
return boxes, classes, scores
def draw(image, boxes, scores, classes):
"""Draw the boxes on the image.
# Argument:
image: original image.
boxes: ndarray, boxes of objects.
classes: ndarray, classes of objects.
scores: ndarray, scores of objects.
all_classes: all classes name.
"""
for box, score, cl in zip(boxes, scores, classes):
top, left, right, bottom = box
print('class: {}, score: {}'.format(CLASSES[cl], score))
print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))
top = int(top)
left = int(left)
right = int(right)
bottom = int(bottom)
cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
(top, left - 6),
cv2.FONT_HERSHEY_SIMPLEX,
0.6, (0, 0, 255), 2)
def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
# Resize and pad image while meeting stride-multiple constraints
shape = im.shape[:2] # current shape [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# Scale ratio (new / old)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
# Compute padding
ratio = r, r # width, height ratios
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
dw /= 2 # divide padding into 2 sides
dh /= 2
if shape[::-1] != new_unpad: # resize
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
return im, ratio, (dw, dh)
if __name__ == '__main__':
# Create RKNN object
rknn = RKNNLite()
# load RKNN model
print('--> Load RKNN model')
ret = rknn.load_rknn(RKNN_MODEL)
print(ret)
# Init runtime environment
print('--> Init runtime environment')
ret = rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2) #使用0 1 2三个NPU核心
# ret = rknn.init_runtime('rk3566')
if ret != 0:
print('Init runtime environment failed!')
exit(ret)
print('done')
# Set inputs
img = cv2.imread(IMG_PATH)
# img, ratio, (dw, dh) = letterbox(img, new_shape=(IMG_SIZE, IMG_SIZE))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
# Inference
outputs = rknn.inference(inputs=[img])
# post process
input0_data = outputs[0]
input1_data = outputs[1]
input2_data = outputs[2]
input0_data = input0_data.reshape([3, -1]+list(input0_data.shape[-2:]))
input1_data = input1_data.reshape([3, -1]+list(input1_data.shape[-2:]))
input2_data = input2_data.reshape([3, -1]+list(input2_data.shape[-2:]))
input_data = list()
input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))
boxes, classes, scores = yolov5_post_process(input_data)
img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
if boxes is not None:
draw(img_1, boxes, scores, classes)
# show output
cv2.imshow("post process result", img_1)
cv2.waitKey(0)
cv2.destroyAllWindows()
rknn.release()
Desktop上にWSL2上で作成したyolov5s.rknnファイルを持ってきます
###WSL2のUbuntu
cd ~/rknn/examples/onnx/yolov5
scp -r yolov5s.rknn オレンジパイのユーザー名@IPアドレス:/home/orangepi/Desktop
検証用で使用するbus.jpgとdataset.txtもDesktopに持っていきます。dataset.txtはいらないかもしれません。
###WSL2のUbuntu
cd ~/rknn/examples/onnx/yolov5
scp -r bus.jpg オレンジパイのユーザー名@IPアドレス:/home/orangepi/Desktop
scp -r dataset.txt オレンジパイのユーザー名@IPアドレス:/home/orangepi/Desktop
動作確認
動作確認していきましょう。
Desktop上に全て置いたため、デスクトップに移動して、実行します。
###Orange pi5上
python deploy.py
バスと人を検出できましたね。おめでとうございます!Orange Pi5上でrknnモデルを動かせました!!
USBカメラテスト
USBカメラでrknnモデルを動かすサンプルを載せておきます。
import os
import urllib
import traceback
import time
import datetime as dt
import sys
import numpy as np
import cv2
from rknnlite.api import RKNNLite
RKNN_MODEL = 'yolov5s.rknn'
DATASET = './dataset.txt'
QUANTIZE_ON = True
OBJ_THRESH = 0.25
NMS_THRESH = 0.45
IMG_SIZE = 640
CLASSES = ("person", "bicycle", "car", "motorbike ", "aeroplane ", "bus ", "train", "truck ", "boat", "traffic light",
"fire hydrant", "stop sign ", "parking meter", "bench", "bird", "cat", "dog ", "horse ", "sheep", "cow", "elephant",
"bear", "zebra ", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite",
"baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife ",
"spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza ", "donut", "cake", "chair", "sofa",
"pottedplant", "bed", "diningtable", "toilet ", "tvmonitor", "laptop ", "mouse ", "remote ", "keyboard ", "cell phone", "microwave ",
"oven ", "toaster", "sink", "refrigerator ", "book", "clock", "vase", "scissors ", "teddy bear ", "hair drier", "toothbrush ")
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def xywh2xyxy(x):
# Convert [x, y, w, h] to [x1, y1, x2, y2]
y = np.copy(x)
y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
return y
def process(input, mask, anchors):
anchors = [anchors[i] for i in mask]
grid_h, grid_w = map(int, input.shape[0:2])
box_confidence = sigmoid(input[..., 4])
box_confidence = np.expand_dims(box_confidence, axis=-1)
box_class_probs = sigmoid(input[..., 5:])
box_xy = sigmoid(input[..., :2])*2 - 0.5
col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
grid = np.concatenate((col, row), axis=-1)
box_xy += grid
box_xy *= int(IMG_SIZE/grid_h)
box_wh = pow(sigmoid(input[..., 2:4])*2, 2)
box_wh = box_wh * anchors
box = np.concatenate((box_xy, box_wh), axis=-1)
return box, box_confidence, box_class_probs
def filter_boxes(boxes, box_confidences, box_class_probs):
"""Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
# Arguments
boxes: ndarray, boxes of objects.
box_confidences: ndarray, confidences of objects.
box_class_probs: ndarray, class_probs of objects.
# Returns
boxes: ndarray, filtered boxes.
classes: ndarray, classes for boxes.
scores: ndarray, scores for boxes.
"""
boxes = boxes.reshape(-1, 4)
box_confidences = box_confidences.reshape(-1)
box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])
_box_pos = np.where(box_confidences >= OBJ_THRESH)
boxes = boxes[_box_pos]
box_confidences = box_confidences[_box_pos]
box_class_probs = box_class_probs[_box_pos]
class_max_score = np.max(box_class_probs, axis=-1)
classes = np.argmax(box_class_probs, axis=-1)
_class_pos = np.where(class_max_score >= OBJ_THRESH)
boxes = boxes[_class_pos]
classes = classes[_class_pos]
scores = (class_max_score* box_confidences)[_class_pos]
return boxes, classes, scores
def nms_boxes(boxes, scores):
"""Suppress non-maximal boxes.
# Arguments
boxes: ndarray, boxes of objects.
scores: ndarray, scores of objects.
# Returns
keep: ndarray, index of effective boxes.
"""
x = boxes[:, 0]
y = boxes[:, 1]
w = boxes[:, 2] - boxes[:, 0]
h = boxes[:, 3] - boxes[:, 1]
areas = w * h
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x[i], x[order[1:]])
yy1 = np.maximum(y[i], y[order[1:]])
xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
inter = w1 * h1
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= NMS_THRESH)[0]
order = order[inds + 1]
keep = np.array(keep)
return keep
def yolov5_post_process(input_data):
masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
[59, 119], [116, 90], [156, 198], [373, 326]]
boxes, classes, scores = [], [], []
for input, mask in zip(input_data, masks):
b, c, s = process(input, mask, anchors)
b, c, s = filter_boxes(b, c, s)
boxes.append(b)
classes.append(c)
scores.append(s)
boxes = np.concatenate(boxes)
boxes = xywh2xyxy(boxes)
classes = np.concatenate(classes)
scores = np.concatenate(scores)
nboxes, nclasses, nscores = [], [], []
for c in set(classes):
inds = np.where(classes == c)
b = boxes[inds]
c = classes[inds]
s = scores[inds]
keep = nms_boxes(b, s)
nboxes.append(b[keep])
nclasses.append(c[keep])
nscores.append(s[keep])
if not nclasses and not nscores:
return None, None, None
boxes = np.concatenate(nboxes)
classes = np.concatenate(nclasses)
scores = np.concatenate(nscores)
return boxes, classes, scores
def draw(image, boxes, scores, classes, fps):
"""Draw the boxes on the image.
# Argument:
image: original image.
boxes: ndarray, boxes of objects.
classes: ndarray, classes of objects.
scores: ndarray, scores of objects.
fps: int.
all_classes: all classes name.
"""
for box, score, cl in zip(boxes, scores, classes):
top, left, right, bottom = box
print('class: {}, score: {}'.format(CLASSES[cl], score))
print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))
top = int(top)
left = int(left)
right = int(right)
bottom = int(bottom)
cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
(top, left - 6),
cv2.FONT_HERSHEY_SIMPLEX,
0.6, (0, 0, 255), 2)
def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
# Resize and pad image while meeting stride-multiple constraints
shape = im.shape[:2] # current shape [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
# Scale ratio (new / old)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
# Compute padding
ratio = r, r # width, height ratios
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
dw /= 2 # divide padding into 2 sides
dh /= 2
if shape[::-1] != new_unpad: # resize
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
return im, ratio, (dw, dh)
# ==================================
# 如下为改动部分,主要就是去掉了官方 demo 中的模型转换代码,直接加载 rknn 模型,并将 RKNN 类换成了 rknn_toolkit2_lite 中的 RKNNLite 类
# ==================================
rknn = RKNNLite()
# load RKNN model
print('--> Load RKNN model')
ret = rknn.load_rknn(RKNN_MODEL)
# Init runtime environment
print('--> Init runtime environment')
# use NPU core 0 1 2
ret = rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
if ret != 0:
print('Init runtime environment failed!')
exit(ret)
print('done')
# Create a VideoCapture object and read from input file
# If the input is the camera, pass 0 instead of the video file name
cap = cv2.VideoCapture(0)
# Check if camera opened successfully
if (cap.isOpened()== False):
print("Error opening video stream or file")
# Read until video is completed
while(cap.isOpened()):
start = dt.datetime.utcnow()
# Capture frame-by-frame
ret, img = cap.read()
if not ret:
break
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
# Inference
print('--> Running model')
outputs = rknn.inference(inputs=[img])
print('done')
# post process
input0_data = outputs[0]
input1_data = outputs[1]
input2_data = outputs[2]
input0_data = input0_data.reshape([3, -1]+list(input0_data.shape[-2:]))
input1_data = input1_data.reshape([3, -1]+list(input1_data.shape[-2:]))
input2_data = input2_data.reshape([3, -1]+list(input2_data.shape[-2:]))
input_data = list()
input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))
boxes, classes, scores = yolov5_post_process(input_data)
duration = dt.datetime.utcnow() - start
fps = round(10000000 / duration.microseconds)
# draw process result and fps
img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
cv2.putText(img_1, f'fps: {fps}',
(20, 20),
cv2.FONT_HERSHEY_SIMPLEX,
0.6, (0, 125, 125), 2)
if boxes is not None:
draw(img_1, boxes, scores, classes, fps)
# show output
cv2.imshow("post process result", img_1)
# Press Q on keyboard to exit
if cv2.waitKey(25) & 0xFF == ord('q'):
break
# When everything done, release the video capture object
cap.release()
# Closes all the frames
cv2.destroyAllWindows()
Discussion
NPUで推論する情報を探していたところたどり着きました。貴重な情報を有難うございます!