ai cast 向けサンプルコード
ai cast 向けサンプルコード
サンプルコードの入手
ai cast 向けの サンプルコードはこちらにあります。
$ git clone https://github.com/Idein/actcast-app-examples.git
サンプルコードの実行
アプリケーション開発チュートリアル に記載の方法で開発環境をセットアップします。
サンプルコードの imagenet-classification-for-aicast
ディレクトリへ移動し以下を実行します。
$ make
ローカルデバイス上でのアプリケーションテスト 記載の内容でセットアップした ai cast を利用しサンプルコードの動作を確認します。
サンプルコード補足事項
共有ライブラリを Python から呼び出しています。 各ファイルの概要について以下に記載します。
app/resnet_v1_18.hef
resnet_v1_18.onnx から hef ファイル (Hailo にロードされるバイナリファイル) に変換するまでの例を記載します。
他のモデルの変換方法やより詳細な情報は Hailo Developer Zone で配布されているドキュメント ( Hailo DataflowCompiler User Guide ) をご参照ください。ダウンロードのためには https://hailo.ai/ でアカウントを作成し Developer Zone Access を申請する必要があります。
変換時のエラー等は Hailo へ Contact Customer Support からお問い合わせください。
Hailo ドキュメント ( Hailo DataflowCompiler User Guide ) の記載内容の通り、Dataflow Compiler をインストールします。
onnx から har への変換
Netron で INPUTS , OUTPUTS を確認し start-node-names input.1 , end-node-names 191 をそれぞれ指定しています。
$ hailo parser onnx resnet_v1_18.onnx --start-node-names input.1 --end-node-names 191 --hw-arch hailo8
Allocator Script (ALLS) ファイルの用意
Allocator Script(もしくは Model Script)とは Hailo チップのリソースをどのように割り当てる(Allocate する)かの指示を記述するファイルです。 Hailo チップで行える処理には、Neural Network の実行以外に例えば入力に対するリサイズ、正規化、RGB->YUV 変換などの前処理を行うことが可能です。こういった処理を Hailo 上で実行する為には ALLS ファイルにその指示を記述する必要があります。
normalization1 = normalization([123.675, 116.28, 103.53], [58.395, 57.12, 57.375])
model_optimization_config(calibration, batch_size=1, calibset_size=10000)
Calibration Dataset の用意
32-bit float f から 8-bit(もしくは 4-bit) int i への量子化は以下の式によって行われます。
f = scale * (i - bias)
scale と bias を決めるためにサンプルとなる入力データ ( Calibration Dataset ) を渡す必要があります。
imagenette を用い .npy として Calibration Dataset (calib_set.npy) を作成します。ResNet-V1-18 の場合は 224x224 の RGB 画像を入力に取るため(N, 224, 224, 3)
の配列を作成します。
import numpy as np
import glob
from PIL import Image
images = []
for f in glob.glob('imagenette2-320/**/*.JPEG', recursive=True):
image = Image.open(f).convert('RGB').resize((224, 224))
images.append(np.asarray(image))
np.save('calib_set.npy', np.stack(images, axis=0))
モデルの量子化を実行
以下のように Allocator Script ( resnet_v1_18.alls ) , Calibration Dataset ( calib_set.npy ) を指定してモデルの量子化を実行します。量子化後のモデルは resnet_v1_18_quantized.har として保存されます。
$ hailo optimize resnet_v1_18.har --calib-set-path calib_set.npy --model-script resnet_v1_18.alls --hw-arch hailo8
hef へのコンパイル
以下を実行して Hailo にロードされるバイナリファイル ( .hef ) を生成します。
$ hailo compiler resnet_v1_18_quantized.har --hw-arch hailo8
Dockerfile
Hailo 用にモデルをコンパイルするための Docker イメージを作ります。
FROM idein/cross-rpi:armv6-slim
ENV SYSROOT /home/idein/x-tools/armv6-rpi-linux-gnueabihf/armv6-rpi-linux-gnueabihf/sysroot
ADD root.tar $SYSROOT
RUN sudo rm $SYSROOT/usr/lib/libhailort.so
RUN sudo ln -s $SYSROOT/usr/lib/libhailort.so.4.10.0 $SYSROOT/usr/lib/libhailort.so
Manifesto
devices
に"/dev/hailo0"
を追加します。- manifesto ファイルで
"hailort_version": "4.10.0"
を指定します。- 詳細は リファレンス を参照して下さい。
{
"version": 2,
"target_type": "raspberrypi-bullseye",
"boards": ["RSPiCM4"],
"hailort_version": "4.10.0",
"devices": [
{
"type": "camera"
},
{
"type": "videocore"
},
{
"type": "other",
"device": ["/dev/hailo0"]
}
]
}
src/resnet_v1_18.c
#include <assert.h>
#include "hailo/hailort.h"
#define MAX_EDGE_LAYERS (16)
#define HEF_FILE ("resnet_v1_18.hef")
static hailo_vdevice vdevice = NULL;
static hailo_hef hef = NULL;
static hailo_configure_params_t config_params = {0};
static hailo_configured_network_group network_group = NULL;
static size_t network_group_size = 1;
static hailo_input_vstream_params_by_name_t input_vstream_params[MAX_EDGE_LAYERS] = {0};
static hailo_output_vstream_params_by_name_t output_vstream_params[MAX_EDGE_LAYERS] = {0};
static hailo_activated_network_group activated_network_group = NULL;
static size_t vstreams_infos_size = MAX_EDGE_LAYERS;
static hailo_vstream_info_t vstreams_infos[MAX_EDGE_LAYERS] = {0};
static hailo_input_vstream input_vstreams[MAX_EDGE_LAYERS] = {NULL};
static hailo_output_vstream output_vstreams[MAX_EDGE_LAYERS] = {NULL};
static size_t input_vstreams_size = MAX_EDGE_LAYERS;
static size_t output_vstreams_size = MAX_EDGE_LAYERS;
int infer(unsigned char *input0, float *out0)
{
unsigned char q_out0[1000];
hailo_status status = HAILO_UNINITIALIZED;
/* Feed Data */
status = hailo_vstream_write_raw_buffer(input_vstreams[0], input0, 224 * 224 * 3);
assert(status == HAILO_SUCCESS);
status = hailo_flush_input_vstream(input_vstreams[0]);
assert(status == HAILO_SUCCESS);
status = hailo_vstream_read_raw_buffer(output_vstreams[0], q_out0, 1000);
assert(status == HAILO_SUCCESS);
/* dequantize */
float scale = vstreams_infos[1].quant_info.qp_scale;
float zp = vstreams_infos[1].quant_info.qp_zp;
for (int i = 0; i < 1000; i++)
out0[i] = scale * (q_out0[i] - zp);
return status;
}
int init()
{
hailo_status status = HAILO_UNINITIALIZED;
status = hailo_create_vdevice(NULL, &vdevice);
assert(status == HAILO_SUCCESS);
status = hailo_create_hef_file(&hef, HEF_FILE);
assert(status == HAILO_SUCCESS);
status = hailo_init_configure_params(hef, HAILO_STREAM_INTERFACE_PCIE, &config_params);
assert(status == HAILO_SUCCESS);
status = hailo_configure_vdevice(vdevice, hef, &config_params, &network_group, &network_group_size);
assert(status == HAILO_SUCCESS);
status = hailo_make_input_vstream_params(network_group, true, HAILO_FORMAT_TYPE_AUTO,
input_vstream_params, &input_vstreams_size);
assert(status == HAILO_SUCCESS);
status = hailo_make_output_vstream_params(network_group, true, HAILO_FORMAT_TYPE_AUTO,
output_vstream_params, &output_vstreams_size);
assert(status == HAILO_SUCCESS);
status = hailo_hef_get_all_vstream_infos(hef, NULL, vstreams_infos, &vstreams_infos_size);
assert(status == HAILO_SUCCESS);
status = hailo_create_input_vstreams(network_group, input_vstream_params, input_vstreams_size, input_vstreams);
assert(status == HAILO_SUCCESS);
status = hailo_create_output_vstreams(network_group, output_vstream_params, output_vstreams_size, output_vstreams);
assert(status == HAILO_SUCCESS);
status = hailo_activate_network_group(network_group, NULL, &activated_network_group);
assert(status == HAILO_SUCCESS);
return status;
}
void destroy() {
(void) hailo_deactivate_network_group(activated_network_group);
(void) hailo_release_output_vstreams(output_vstreams, output_vstreams_size);
(void) hailo_release_input_vstreams(input_vstreams, input_vstreams_size);
(void) hailo_release_hef(hef);
(void) hailo_release_vdevice(vdevice);
}
Makefile
SOURCE=resnet_v1_18.c
TARGET=libresnet_v1_18.so
all: app/$(TARGET)
app/$(TARGET): src/$(SOURCE)
docker build -t cross-rpi .
docker run -it --rm -d --name cross cross-rpi /bin/bash
docker cp src cross:/home/idein/src
docker exec -it cross armv6-rpi-linux-gnueabihf-gcc -W -Wall -Wextra -Werror -O2 -pipe -fPIC -std=c99 -I src src/$(SOURCE) -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -shared -o $(TARGET) -lhailort -lpthread
docker cp cross:/home/idein/$(TARGET) app/$(TARGET)
docker stop cross
root.tar
Actcast アプリケーションから Hailo にアクセスするためのファイル群です。
アプリのルートディレクトリに root.tar という名前のファイルがある場合にファイル内をアプリの docker image に含める actdk の機能を利用します。
app/model.py
from ctypes import cdll
import numpy as np
class Model:
def __init__(self):
self.lib = cdll.LoadLibrary('./libresnet_v1_18.so')
self.lib.init()
def __del__(self):
self.lib.destroy()
def infer(self, image):
out = np.zeros(1000, dtype=np.float32)
self.lib.infer(
image.ctypes.data,
out.ctypes.data
)
return out,
app/main (一部)
#!/usr/bin/python3
import argparse
import subprocess
import time
import os
from PIL import Image, ImageDraw, ImageFont
import actfw_core
from actfw_core.task import Pipe, Consumer
from actfw_core.capture import V4LCameraCapture
import actfw_raspberrypi
from actfw_raspberrypi.vc4 import Display
import numpy as np
from model import Model
from configuration import *
(CAPTURE_WIDTH, CAPTURE_HEIGHT) = (224, 224) # capture image size
(DISPLAY_WIDTH, DISPLAY_HEIGHT) = (640, 480) # display area size
class Classifier(Pipe):
def __init__(self, settings, capture_size):
super(Classifier, self).__init__()
self.model = Model()
self.settings = settings
self.capture_size = capture_size
def proc(self, frame):
captured_image = Image.frombuffer('RGB', self.capture_size, frame.getvalue(), 'raw', 'RGB')
if self.settings['resize_method'] == 'crop':
if self.capture_size != (CAPTURE_WIDTH, CAPTURE_HEIGHT):
w, h = self.capture_size
captured_image = captured_image.crop((w // 2 - CAPTURE_WIDTH // 2, h // 2 - CAPTURE_HEIGHT // 2, w // 2 + CAPTURE_WIDTH // 2, h // 2 + CAPTURE_HEIGHT // 2))
else:
w, h = self.capture_size
l = min(w, h)
captured_image = captured_image.crop((w // 2 - l // 2, h // 2 - l // 2, w // 2 + l // 2, h // 2 + l // 2))
captured_image = captured_image.resize((CAPTURE_WIDTH, CAPTURE_HEIGHT))
input_image = np.asarray(captured_image)
probs, = self.model.infer(input_image)
return (captured_image, probs)