FRDM-IMX95ボードを使って、カメラで取得した画像をAIアクセラレータのNPUに渡し、物体認識をさせてみます。使うモデルはmobilenetです。
[FRDM-IMX95] i.MX 95 カメラアプリケーションの導入 (日本語ブログ)の続きですので、カメラの動作確認ができている前提でご覧ください。
(作業時間:15分) *LinuxイメージがFRDM-IMX95に書き込めている前提、Neutron SDKダウンロード時間を除く
一番シンプルなNPUの動作確認用として、Linux BSPにモデルが同梱されています。
これは224x224ピクセルの画像に対して、用意されているラベルの中から一番近しい物体の名前を推論するモデルです。検出された物体に対してバウンディングボックスで囲む、というようなことはせず、あくまで1枚のフレーム全体に対して何?という結果を出すモデルです。
Tensorflow Liteで提供されているモデルを、i.MX 95内蔵のNPU(=eIQ Neutron)用に変換する必要があります。変換にはNXPが提供するeIQ Neutron SDKを使う必要があります。また、変換に使用したeIQ Neutron SDKに含まれるNeutron firmware / Neutron driver / TensorFlow neutron delegateを、FRDM-IMX95へコピーする必要があります。
詳細はeIQ Neutron SDKのdoc/NeutronSDKUserGuide.mdをご参照ください。
今回はLinux L6.18.2_1.0.0とeIQ Neutron SDK v3.0.1の組み合わせを使用しています。
1.1 SDKの準備
まずはeIQ Neutron SDKをダウンロードします。Windows版、Linux版の2種類があります。私はLinux版をWSL2で動かして確認しています。
$ mkdir eiq-neutron-sdk-3.0.1
$ cd eiq-neutron-sdk-3.0.1
$ unzip /path/to/eiq-neutron-sdk-3.0.1.zip
$ ls bin/
neutron-converter tflite-profiler tflite-quantizer
のように、展開先のbinの下に、今回使うneutron-converterがあればOKです。
1.2 モデルをホストへコピー
ターゲットのRoot File Systemの以下パスに、変換前のモデルがあります。
/usr/bin/tensorflow-lite-2.19.0/examples/mobilenet_v1_1.0_224_quant.tflite
これをeIQ Neutron SDKをインストールしたホスト側にコピーしてきます。FRDM-IMX95をネットワークでつなげていれば、scpコマンドを使うのが一番楽です。
$ scp root@<IP addr>:/usr/bin/tensorflow-lite-2.19.0/examples/mobilenet_v1_1.0_224_quant.tflite /path/to/eiq-neutron-sdk-3.0.1
1.3 モデルの変換
以下を実行します。
$ cd eiq-neutron-sdk-3.0.1
$ bin/neutron-converter --input mobilenet_v1_1.0_224_quant.tflite --output mobilenet_v1_1.0_224_quant_converted.tflite --target imx95
この結果、以下のようなログの出力がでて、目的の"mobilenet_v1_1.0_224_quant_converted.tflite"が生成されます。
Converting model with the following options:
Input = mobilenet_v1_1.0_224_quant.tflite
Output = mobilenet_v1_1.0_224_quant_converted.tflite
Target = imx95
Starting Tile scheduling (OFast optimization level).
[================================================================================] 100 %
[================================================================================] 100 %
[================================================================================] 100 %
Tile scheduling done.
Starting TCM allocation (OFast optimization level).
[===============================================================================>] 98 %
TCM allocation done.
[================================================================================] 100 %
Starting microcode generation. This might take a while.
[================================================================================] 100 %
=======================================================================================================================
Statistics for NeutronGraph "subgraph_032":
Operators:
Number of Neutron operators = 30
Number of builtin operators = 45
Memory:
Inputs = 150,528 (bytes)
Microcode = 28,680 (bytes)
Weights = 4,290,272 (bytes)
Kernels = 13,840 (bytes)
Outputs = 1,001 (bytes)
Scratch = 229,376 (bytes) (Allocation efficiency: 1)
Total data = 380,905 (bytes) (Inputs + Outputs + Scratch)
Total weights = 4,332,792 (bytes) (Microcode + Weights + Kernels)
Total size = 4,713,697 (bytes) (All)
Latency:
Cycle estimation = 1,076,979 (cycles)
Latency estimation = 1.077 (ms) (@ 1000.000 MHz)
========================================================================================================================
Overall statistics for graph "":
Operators:
Number of operators after import = 31
Number of operators after optimize = 47
Number of operators after extract = 3
Number of Neutron graphs = 1
Number of operators total = 47
Number of operators converted = 45
Number of operators NOT converted = 2
Operator conversion ratio = 45 / 47 = 0.957447
Operators converted = 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,
Memory:
Total data = 380,912 (bytes) (Inputs + Outputs + Intermediate Variable Tensors)
Total weights = 4,332,800 (bytes) (Weights)
Total size = 4,713,712 (bytes) (All)
Latency:
Cycle estimation = 1,076,979 (cycles) (NPU only)
Latency estimation = 1.077 (ms) (@ 1000.000 MHz) (NPU only)
========================================================================================================================
Conversion time:
Optimization = 1.50862 (seconds)
Extraction = 0.0245362 (seconds)
Generation = 1.09866 (seconds)
Total = 2.63182 (seconds)
========================================================================================================================
パスは後ほど作成するソースコードで指定できるのでどこでも構わないですが、もともとの変換前モデルがあった場所に置くようにします。
$ scp mobilenet_v1_1.0_224_quant_converted.tflite root@<IP addr>:/usr/bin/tensorflow-lite-2.19.0/examples
2. Pythonコード
このコードでは、カメラ入力のセットアップを行った後、カメラからの入力フレームをmobilenetのモデルに渡し、一番可能性の高い認識結果をラベルファイルから引っ張ってきてカメラ画像にオーバーレイさせています。
#!/usr/bin/env python3
import cv2
import numpy as np
import time
import tflite_runtime.interpreter as tflite
import os
os.environ["LIBCAMERA_IPA_MODULE_PATH"] = "/usr/lib/libcamera/ipa-nxp-neo-uguzzi/"
os.environ["LIBCAMERA_PIPELINES_MATCH_LIST"] = "nxp/neo,imx8-isi,uvc"
# =========================
# 設定
# =========================
MODEL_PATH = "/usr/bin/tensorflow-lite-2.19.0/examples/mobilenet_v1_1.0_224_quant_converted.tflite"
LABEL_PATH = "/usr/bin/tensorflow-lite-2.19.0/examples/labels.txt"
DELEGATE_LIB = "libneutron_delegate.so"
# カメラ (libcamerasrc) -> GStreamer -> OpenCV
GST_PIPELINE = (
"libcamerasrc camera-name=/base/soc/bus@42000000/i2c@42540000/os08a20_mipi@36 ! "
"video/x-raw,format=YUY2,framerate=30/1,width=3840,height=2160 ! "
"imxvideoconvert_g2d ! "
"video/x-raw,width=1280,height=720,format=BGRA ! "
"appsink drop=true max-buffers=1"
)
INPUT_SIZE = 224 # 224x224 mobilenet
# =========================
# ラベル読み込み
# =========================
def load_labels(path):
with open(path, "r") as f:
return [line.strip() for line in f.readlines()]
labels = load_labels(LABEL_PATH)
# =========================
# TFLite Interpreter (NPU delegate)
# =========================
delegates = [tflite.load_delegate(DELEGATE_LIB)]
interpreter = tflite.Interpreter(model_path=MODEL_PATH,
experimental_delegates=delegates)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# 量子化モデル前提 (uint8)
input_index = input_details[0]["index"]
input_height = input_details[0]["shape"][1]
input_width = input_details[0]["shape"][2]
# =========================
# カメラ初期化
# =========================
cap = cv2.VideoCapture(GST_PIPELINE, cv2.CAP_GSTREAMER)
if not cap.isOpened():
raise RuntimeError("Failed to open camera. Check GStreamer pipeline or camera connection.")
prev_time = time.time()
fps = 0.0
print("Press 'q' to quit")
while True:
ret, frame = cap.read()
if not ret:
print("Failed to read frame")
break
# FPS 計算
now = time.time()
dt = now - prev_time
if dt > 0:
fps = 1.0 / dt
prev_time = now
# =========================
# 前処理: 224x224 RGB へ
# =========================
# 画像をリサイズ (アスペクト比は簡略化のため無視して 224x224 に変形)
img = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE))
# BGR -> RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 量子化 uint8 モデル用にそのまま [0,255] の uint8 として使う
input_data = np.expand_dims(img_rgb, axis=0).astype(np.uint8)
# =========================
# 推論 (NPU)
# =========================
interpreter.set_tensor(input_index, input_data)
interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]["index"])[0]
# =========================
# 推論結果の処理
# =========================
# top-1 クラス
top_k = 1
top_indices = output_data.argsort()[-top_k:][::-1]
idx = int(top_indices[0])
score = float(output_data[idx]) / 255.0 # uint8 スコアを 0–1 に正規化(必要なら)
label = labels[idx] if idx < len(labels) else f"class_{idx}"
text = f"{label}: {score:.2f} FPS: {fps:.1f}"
# =========================
# 画面へのオーバーレイ
# =========================
# 元のフレーム (1280x720) にテキストを描画
cv2.putText(frame, text, (20, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow("i.MX95 NPU (MobileNet via TFLite + OpenCV)", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
こちらをrun-npu.pyとしてFRDM-IMX95へ保存してください。
3. 実機動作
root@imx95-15x15-lpddr4x-frdm:~# python3 run-npu.py
FRDM-IMX95のカメラでノートパソコンを映しているところのビデオです。
後半の方ではlaptopとして認識されていることがわかります。
おわりに
NXPのLinux BSPに、NPUを使うベーシックな物体認識モデルとして含まれるmobilenetをカメラのライブ入力と合わせて実装してみました。フローもシンプルですので、AIモデルの導入として触ってみるのにちょうどいいのではないかと思います。
※この記事には、AI生成コードをベースに投稿者が内容を確認した内容が含まれます。
=========================
本投稿の「Comment」欄にコメントをいただいても、現在返信に対応しておりません。
お手数をおかけしますが、お問い合わせの際には「NXPへの技術質問 - 問い合わせ方法 (日本語ブログ)」をご参照ください。
(既に弊社NXP代理店、もしくはNXPとお付き合いのある方は、直接担当者へご質問いただいてもかまいません。)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.