見出し画像

D-Robotics RDK X3 入門 (9) - モデル推論

「RDK X3」の「モデル推論」についてまとめました。

Algorithm Application Development - Model Inference Examples


前回

1. hobot_dnn

hobot_dnn」は、モデル推論ライブラリです。「RDK X3」のUbuntuシステムにプリインストールされています。

モジュールのインポートして使用することができます。

from hobot_dnn import pyeasy_dnn as dnn

「hobot_dnn」で使用される主なクラスとインターフェイスは、次のとおりです。

・Model : AIアルゴリズムモデルクラス。アルゴリズムモデルの読み込みと推論計算の実行に使用。
・pyDNNTensor : AIアルゴリズムの入出力データテンソルクラス。
・TensorProperties : モデルの入力テンソルのプロパティのクラス。
・load : アルゴリズムモデルをロード。

サンプルコードは「/app/pydev_demo/」に含まれています。

2. 静的画像推論

2-1. 画像分類 - Mobilenet v1

この例では、次の機能を実装しています。

(1) mobilenet v1 をロード
(2) zebra_cls.jpg をモデルの入力として読み込む
(3) モデル出力を解析して画像の分類結果を取得

・実行方法

cd /app/pydev_demo/01_basic_sample/
sudo python3 ./test_mobilenetv1.py
========== Classification result ==========
cls id: 340 Confidence: 0.991851

・コード説明
(1) 「hobot_dnn」「numpy」「opencv」のインポート。

from hobot_dnn import pyeasy_dnn as dnn
import numpy as np
import cv2

(2) モデルをロード。
mobilenetv1 の入力は、NCHW 形式の 1x3x224x224 データです。出力は 1000 カテゴリの信頼度を表す 1000 データのリストです。

models = dnn.load('../models/mobilenetv1_224x224_nv12.bin')

(3) モデルの入出力パラメータを出力する print_properties 関数を使用。

# print properties of input tensor
print_properties(models[0].inputs[0].properties)
# print properties of output tensor
print_properties(models[0].outputs[0].properties)

(4) データの前処理。
画像を読み込み、モデルの入力サイズ (244 x 224) に合わせてリサイズします。

# open image
img_file = cv2.imread('./zebra_cls.jpg')
# get the input tensor size
h, w = models[0].inputs[0].properties.shape[2], models[0].inputs[0].properties.shape[3]
print("input tensor size: %d x %d" % (h, w))
des_dim = (w, h)
# resize image to input size
resized_data = cv2.resize(img_file, des_dim, interpolation=cv2.INTER_AREA)

(5) bgr2nv12_opencv 関数を使用して、画像をBGR形式からモデル入力と互換性のあるNV12形式に変換。

nv12_data = bgr2nv12_opencv(resized_data)

(6) モデル推論。
Modelのforwardインターフェイスを呼び出し、1000個のカテゴリの予測確率を表す1000個の値のリストを取得します。

outputs = models[0].forward(nv12_data)

(7) 後処理。
モデルの出力は、目的のクラス、検出ボックス、その他の情報を取得するために後処理する必要があります。今回は、モデル出力は1000カテゴリに対応するため、正しい結果を得るには信頼度に基づいてフィルタ処理する必要があります。

print("=" * 10, "Classification result", "=" * 10)
np.argmax(outputs[0].buffer)
# output target id and confidence
print("cls id: %d Confidence: %f" % (np.argmax(outputs[0].buffer), outputs[0].buffer[0][np.argmax(outputs[0].buffer)]))

実行時の正しい結果は次のとおりです。

========== Classification result ==========
cls id: 340 Confidence: 0.991851

2-2. 物体検出 - YOLO v3

この例では、次の機能を実装しています。

(1) yolov3_416x416_nv12 を読み込む。
(2) kite.jpg をモデルの入力として読み込む。
(3) モデル出力を解析して物体検出結果をレンダリング。

・実行方法

cd /app/pydev_demo/06_yolov3_sample/
sudo python3 ./test_yolov3.py

成功すると、次のように物体検出結果が出力され、「result.jpg」に保存されます。

2-3. 物体検出 - YOLO v5

この例では、次の機能を実装しています。

(1) yolov5s_672x672_nv12 をロード。
(2) kite.jpg をモデルの入力として読み込む。
(3) モデル出力を解析して物体検出結果をレンダリング。

・実行方法

cd /app/pydev_demo/07_yolov5_sample/
sudo python3 ./test_yolov5.py

成功すると、次のように、物体検出結果が出力され、result.jpg に保存されます。

2-4. 画像セグメンテーション - UNet

この例では、次の機能を実装しています。

(1) mobilenet_unet_1024x2048_nv12 をロード。
(2) segmentation.png をモデルの入力として読み込む。
(3) モデル出力を解析してセグメンテーション結果をレンダリング。

・実行方法

cd /app/pydev_demo/04_segment_sample/
sudo python3 ./test_mobilenet_unet.py

成功すると、次のように、画像のセグメンテーション結果が出力され、segment_result.png に出力されます。

3. MIPIカメラ推論

3-1. 物体検出 - FCOS

この例では、次の機能を実装しています。

(1) fcos をロード。
(2) MIPIカメラからビデオ画像を読み取り推論実行。
(3) モデル出力を解析して結果を元のビデオストリームにレンダリング。
(4) レンダリングしたビデオストリームをHDMIインターフェイス経由で出力。

・実行方法
MIPI Camera AI Inference」を参照してください。

・コード説明
(1) モジュールのインポート。

import numpy as np
import cv2
import colorsys

from hobot_dnn import pyeasy_dnn as dnn
from hobot_vio import libsrcampy as srcampy

(2) モデルをロード。
load メソッドを呼び出してモデルをロードし、hobot_dnn.pyeasy_dnn.Model のリストを返します。

models = dnn.load('../models/fcos_512x512_nv12.bin')

(3) モデルの入出力パラメータを出力する print_properties 関数を使用。

# print properties of input tensor
print_properties(models[0].inputs[0].properties)
# print properties of output tensor
print(len(models[0].outputs))
for output in models[0].outputs:
    print_properties(output.properties)

(4) データの前処理
srcampy.Camera の get_cam インターフェイスを呼び出して、MIPIカメラからリアルタイム画像を取得し、モデルの入力テンソルのサイズに合わせてリサイズします。

# create Camera object
cam = srcampy.Camera()
h, w = get_hw(models[0].inputs[0].properties)
# open MIPI Camera, set 30fps, solution 1920 x 1080, 512 x 512
cam.open_cam(0, 1, 30, [1920, w], [1080, h])
# get the image, solution 512x512
img = cam.get_img(2, 512, 512)
# transform data to np format
img = np.frombuffer(img, dtype=np.uint8)

(5) データストリームバインディング
画像データのデータコピーを減らすために、この例では画像データの入力モジュールと出力モジュールをバインドし、カメラからの画像データを下位レベルの表示モジュールに直接送信できるようにします。

disp = srcampy.Display()
# For the meaning of parameters, please refer to the relevant documents of HDMI display
disp.display(0, 1920, 1080)

# bind camera directly to display
srcampy.bind(cam, disp)

カメラの使用方法については詳しくは「Camera」を参照してください。

(6) モデル推論
推論のために Model の forward インターフェイスを呼び出します。このモデルは、検出された物体検出ボックスを表す15セットのデータを出力します。

outputs = models[0].forward(nv12_data)

(7) 後処理
この例の後処理関数 postprocess は、モデル出力からの物体カテゴリ、検出ボックス、信頼度の情報を処理します。

# do postprocess
prediction_bbox = postprocess(outputs, input_shape, origin_img_shape=(1080,1920))

(8) 検出結果の可視化
この例では、アルゴリズムの結果と元のビデオストリームをレンダリングし、HDMIインターフェイス経由で出力します。ユーザーはディスプレイ上でリアルタイムに効果をプレビューできます。表示部分は hobot_vio モジュールの Display 関数を使用します。このモジュールについて詳しくは「Display」を参照してください。

for index, bbox in enumerate(prediction_bbox):
...
    if index == 0:
        disp.set_graph_rect(coor[0], coor[1], coor[2], coor[3], 2, 1,
                            box_color_ARGB)
        disp.set_graph_word(coor[0], coor[1] - 2, bbox_string, 2, 1,
                            box_color_ARGB)
    else:
        disp.set_graph_rect(coor[0], coor[1], coor[2], coor[3], 2, 0,
                            box_color_ARGB)
        disp.set_graph_word(coor[0], coor[1] - 2, bbox_string, 2, 0,
                            box_color_ARGB)

3-2. 物体検出のWeb視覚化

この例では、次の機能を実装しています。

(1) fcos をロード。
(2) MIPIカメラからビデオ画像を読み取り推論実行。
(3) モデル出力を解析
(4) アルゴリズムの結果とビデオストリームをWeb側にプッシュ。

・実行方法
(1) web_service を開始。
Webサービスを使用する前に、開発ボードとコンピュータが同じネットワークにあり、相互に ping できることを確認してください。

cd /app/pydev_demo/05_web_display_camera_sample/
sudo sh ./start_nginx.sh
sudo python3 ./mipi_camera_web.py 

start_nginx.sh の実行中に次のエラーが発生した場合は、デバイス上で httpd サービスがすでに実行されており、TCPポート 80 が占有されていることを意味します。

この場合、ポート 80 を占有しているプロセスを見つけて終了する必要があります。コマンド lsof -i:80 を実行してポートを占有しているプロセスの PID を取得し、kill -9 PID を使用してプロセスを終了できます。

・コード説明
(1) Protobufのシリアル化。
Webクライアントは、Protobuf を使用してシリアル化されたデータを受信します。開発ボードはサーバとして、特定のデータ形式に従ってモデルの出力をシリアル化する必要があります。この例では、シリアル化操作は、serialize 関数を使用して実行されます。

def serialize(FrameMessage, prediction_bbox):
    if (prediction_bbox.shape[0] > 0):
        for i in range(prediction_bbox.shape[0]):
            # get class name
            Target = x3_pb2.Target()
            id = int(prediction_bbox[i][5])
            Target.type_ = classes[id]
            Box = x3_pb2.Box()
            Box.type_ = classes[id]
            Box.score_ = prediction_bbox[i][4]

            Box.top_left_.x_ = prediction_bbox[i][0]
            Box.top_left_.y_ = prediction_bbox[i][1]
            Box.bottom_right_.x_ = prediction_bbox[i][2]
            Box.bottom_right_.y_ = prediction_bbox[i][3]

            Target.boxes_.append(Box)
            FrameMessage.smart_msg_.targets_.append(Target)
    prot_buf = FrameMessage.SerializeToString()
    return prot_buf

(2) Protobuf データの送信。
開発ボード上のWebサーバは、WebSocket プラグインを使用してデータを送信し、ローカルデバイスのIPアドレスを取得する必要があります。

# call ifconfig cmd, to get device ip
ifconfig_cmd = subprocess.check_output("ifconfig | grep broadcast | awk '{print $2}'", shell=True)
board_ip = str(ifconfig_cmd, 'UTF-8')[:-1]

(3) WebSocket を開始し、web_service 関数を使用してデータを送信。

start_server = websockets.serve(web_service, board_ip, 8080)
async def web_service(websocket, path):
    while True:
        # create protobuf message object
        FrameMessage = x3_pb2.FrameMessage()
        # set frame solution and format
        FrameMessage.img_.height_ = 1080
        FrameMessage.img_.width_ = 1920
        FrameMessage.img_.type_ = "JPEG"

        # get camera image for inference
        img = cam.get_img(2, 512, 512)
        img = np.frombuffer(img, dtype=np.uint8)
        outputs = models[0].forward(img)
        # do postprocess
        prediction_bbox = postprocess(outputs, input_shape, origin_img_shape=(1080, 1920))
        print(prediction_bbox)

        # get camera image for render
        origin_image = cam.get_img(2, 1920, 1080)
        # encode image to mjpeg
        enc.encode_file(origin_image)
        FrameMessage.img_.buf_ = enc.get_img()
        FrameMessage.smart_msg_.timestamp_ = int(time.time())
        # serialize data
        prot_buf = serialize(FrameMessage, prediction_bbox)
        # send data
        await websocket.send(prot_buf)
    cam.close_cam()

(4) Web上で確認。
Chromeブラウザに開発ボードのIPアドレスを入力して、リアルタイムでレンダリングされたビデオ画像をプレビューします。

4. USBカメラ推論

4-1. 物体検出 - FCOS

この例では、次の機能を実装しています。

(1) fcos をロード。
(2) USBカメラからビデオ画像を読み取り推論実行。
(3) モデル出力を解析して結果を元のビデオストリームにレンダリング。
(4) レンダリングしたビデオストリームをHDMIインターフェイス経由で出力。

・実行方法
USB Camera AI Inference」を参照してください。

・コード説明
(1) モジュールのインポート。

from hobot_dnn import pyeasy_dnn as dnn
from hobot_vio import libsrcampy as srcampy
import numpy as np
import cv2
import colorsys

(2) モデルをロード。
load メソッドを呼び出してモデルをロードし、hobot_dnn.pyeasy_dnn.Model のリストを返します。

models = dnn.load('../models/fcos_512x512_nv12.bin')

(3) モデルの入出力パラメータを出力する print_properties 関数を使用。

# print properties of input tensor
print_properties(models[0].inputs[0].properties)
# print properties of output tensor
print(len(models[0].outputs))
for output in models[0].outputs:
    print_properties(output.properties)

(4) データの前処理
OpenCVを使用してUSBカメラデバイスノード「/dev/video8」を開き、リアルタイム画像を取得し、モデルの入力テンソルサイズに合わせてリサイズします。

# open usb camera: /dev/video8
cap = cv2.VideoCapture(8)
if(not cap.isOpened()):
    exit(-1)
print("Open usb camera successfully")
# set the output of usb camera to MJPEG, solution 640 x 480
codec = cv2.VideoWriter_fourcc( 'M', 'J', 'P', 'G' )
cap.set(cv2.CAP_PROP_FOURCC, codec)
cap.set(cv2.CAP_PROP_FPS, 30) 
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

(5) BGR形式の画像をモデル入力に適合するNV12形式に変換。

nv12_data = bgr2nv12_opencv(resized_data)

(6) モデル推論
推論のために Model の forward インターフェイスを呼び出します。このモデルは、検出された物体検出ボックスを表す15セットのデータを出力します。

outputs = models[0].forward(nv12_data)

(7) 後処理
この例の後処理関数 postprocess は、モデル出力からの物体カテゴリ、検出ボックス、信頼度の情報を処理します。

prediction_bbox = postprocess(outputs, input_shape, origin_img_shape=(1080,1920))

(8) 検出結果の可視化
この例では、アルゴリズムの結果と元のビデオストリームをレンダリングし、HDMIインターフェイス経由で出力します。ユーザーはディスプレイ上でリアルタイムに効果をプレビューできます。表示部分は hobot_vio モジュールの Display 関数を使用します。このモジュールについて詳しくは「Display」を参照してください。

# create display object
disp = srcampy.Display()
# set solution to 1920 x 1080
disp.display(0, 1920, 1080)

# if the solution of image is not 1920 x 1080, do resize
if frame.shape[0]!=1080 and frame.shape[1]!=1920:
    frame = cv2.resize(frame, (1920,1080), interpolation=cv2.INTER_AREA)

# render the detection results to image
box_bgr = draw_bboxs(frame, prediction_bbox)

# convert BGR to NV12
box_nv12 = bgr2nv12_opencv(box_bgr)
# do display
disp.set_img(box_nv12.tobytes())

5. モデル推論インターフェース

5-1. モデル推論インターフェース

「RDK X3」のUbuntuシステムには、モデル推論モジュール「pyeasy_dnn」のPython版がプリインストールされています。モデルをロードしてModelを作成することで、モデル推論やデータ解析などの機能が完成します。

モジュール推論プロセスは、「モデルの読み込み」「画像推論」「データ解析」の3つのステップに分けられます。

from hobot_dnn import pyeasy_dnn as dnn

#create model object
models = model.load('./model.bin')

#do inference with image
outputs = models[0].forward(image)

for item in outputs:
    output_array.append(item.buffer)
post_process(output_array)

Modelは、モデルが読み込まれるときに作成されます。これには、「inputs」「outputs」「forward」などのメンバーとメソッドが含まれています。

5-2. inputs

・関数説明
Modelのテンソル入力情報を返します。特定の入力はインデックスで指定できます。inputs[0] は 0 番目の入力を表します。

・関数宣言

Model.inputs(tuple(pyDNNTensor))

・パラメータ説明

・index : 入力テンソルのインデックスを表す。

・使用方法

def print_properties(pro):
    print("tensor type:", pro.tensor_type)
    print("data type:", pro.dtype)
    print("layout:", pro.layout)
    print("shape:", pro.shape)

models = dnn.load('../models/fcos_512x512_nv12.bin')
input = models[0].inputs[0]

print_properties(input.properties)

・戻り値
pyDNNTensor 型のオブジェクトを返します。

・properties : テンソルのプロパティを表す。
・buffer : データをテンソルで numpy 形式で表す。
・name : テンソル内の名前を表す。

5-3. outputs

・関数説明
Modelのテンソル出力情報を返します。特定の出力はインデックスで指定できます。outputs[0] は 0 番目の出力を表します。

・関数宣言

Model.outputs(tuple(pyDNNTensor))

・パラメータ説明

・index : 出力テンソルのインデックスを表す。

・使用方法

def print_properties(pro):
    print("tensor type:", pro.tensor_type)
    print("data type:", pro.dtype)
    print("layout:", pro.layout)
    print("shape:", pro.shape)

models = dnn.load('../models/fcos_512x512_nv12.bin')
output = models[0].outputs[0]

print_properties(output.properties)

・戻り値
pyDNNTensor 型のオブジェクトを返します。

・properties : テンソルのプロパティを表す。
・buffer : データをテンソルで numpy 形式で表す。
・name : テンソル内の名前を表す。

5-4. forward

・関数説明
指定された入力に基づいてモデル推論を実行します。

・関数定義

Model.forward(args &args, kwargs &kwargs)

・パラメータ説明

・args : 推論用の入力データ
 ・numpy: single model input
 ・list[numpy, numpy, ...]: multiple model inputs
・kwargs : core_id は、モデル推論のコア IDを表す。
 ・0: automatic allocation
 ・1: core0
 ・2: core1
・kwargs : priority は、現在のモデル推論タスクの優先度を表す。
 ・0~255 (数値が大きいほど優先度が高くなる)

・使用方法

img = cam.get_img(2, 512, 512)

img = np.frombuffer(img, dtype=np.uint8)
outputs = models[0].forward(img)

・戻り値
出力オブジェクトを返します。

5-5. コード例

詳しくは「Model Inference Example」を参照してください。

次回



いいなと思ったら応援しよう!