アプリケーションの実行

以下のプログラムで生成された検出結果を、元の画像に重ねて表示します。
これは、小さなアプリケーションとして実行するのと同じYOLOv8推論です。Model.run(...)を直接呼び出す代わりに(モデルを実行するの場合と同様)、モデルをGraph(入力、モデル、および出力を持つ名前付きグラフフロー)に組み込み、それを構築してプッシュ/プルします。PythonとC++で同じプログラムを使用できます。各コードブロックで言語タブを選択してください。
この最初のアプリケーションでは、意図的に単純な構造にしています。
- 名前付きの_入力_ (
nodes.input("image"))は、データがアプリケーションにどこから入力されるかを示します。 - モデル (
graph.add(model))は、グラフ内の1つのステップとしてモデルを実行します。 - 名前付きの_出力_ (
nodes.output("detections"))は、アプリケーションが結果を読み取る場所を示します。
同じAPIは、後により複雑なアプリケーションにも拡張できます。ここでは、基本的な構成パターンを理解することが目的です。
任意のコードブロックにある「Python / C++」タブを使用してください。選択はサイト全体の言語セレクターに従うため、すべてのコードスニペットとプログラム全体がまとめて切り替わります。
プロジェクトのセットアップ
このセクションはスキップできます。ここでは、同じassets/ディレクトリ、モデルパッケージ、およびサンプル画像を使用します。直接、コードをステップごとに確認するに進んでください。
- モデルと入力画像用のアセットディレクトリを作成します。
mkdir -p assets
- モデルをダウンロードします:
sima-cli modelzoo -v 2.0.0 get yolo_v8ssima-cli モデルのダウンロード
sima-cliがモデルをassetsディレクトリ以外の場所に書き込む場合は、そのファイルをassets/yolo_v8s_mpk.tar.gzにコピーしてください。
:::
3. サンプル画像をダウンロードし、assets/tutorial_sample_image.png として保存します。
コードの解説
このプログラムは、短い8つの部分で構成されています。各ブロックで言語タブを切り替えてください。
1. 画像を読み込む
#include <opencv2/opencv.hpp>
cv::Mat bgr = cv::imread("assets/tutorial_sample_image.png");
cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
OpenCVはBGR形式で画像を読み込みますが、YOLOv8はRGB形式を想定しています。この処理は必須ではありません。 Neat —アプリケーションは、ファイル、カメラ、またはデコーダーからピクセルデータを取得します。 Neat 次のステップで入力します。
2. モデルのオプションについて説明します。
#include <neat.h>
namespace neat = simaai::neat;
neat::Model::Options opt;
opt.preprocess.kind = neat::InputKind::Image;
opt.preprocess.preset = neat::NormalizePreset::COCO_YOLO;
opt.decode_type = neat::BoxDecodeType::YoloV8;
opt.score_threshold = 0.25f;
opt.nms_iou_threshold = 0.45f;
opt.top_k = 100;
ModelOptions は、1つのオブジェクトでモデルの処理方法を宣言します。具体的には、Neat が入力ピクセルをどのように前処理し、検出器の出力をどのようにデコードするかを定義します。
| フィールド | 設定内容 |
|---|---|
preprocess.kind = Image | 入力は、あらかじめ整形されたテンソルではなく、生のピクセルです。 |
preprocess.preset = COCO_YOLO | リサイズとレターボックス処理を行い、モデルの入力に合うように調整します。RGB形式で、1/255 でスケールし、平均値の引き算は行いません。 |
decode_type = YoloV8 | 検出ヘッドデコーダーのファミリー。 |
score_threshold / nms_iou_threshold / top_k | 信頼度の閾値、NMSのオーバーラップ、および保持する最大ボックス数。 |
3. モデルをロードします
neat::Model model("assets/yolo_v8s_mpk.tar.gz", opt);
Model は、.tar.gz を読み込み、渡された ModelOptions に基づいて、その MPK コントラクト の有効性を検証し、モデルの断片をインスタンス化します。まだ何も実行されていません。
4. 画像を Tensor としてラップします。
neat::Tensor input = neat::Tensor::from_cv_mat(
rgb,
neat::ImageSpec::PixelFormat::RGB);
Tensor は、Neat の型付きデータコンテナです。これには、形状、データ型、レイアウト、およびフレームワークがバイトを解釈するために必要なピクセル形式が含まれます。Neat がバイトだけでなくレイアウトも認識できるように、PixelFormat を渡す必要があります。
5. グラフを構成する
neat::Graph graph("hello_neat_app");
graph.add(neat::nodes::Input("image"));
graph.add(model);
graph.add(neat::nodes::Output("detections"));
Graph はアプリケーションのフローです。各 add(...) は次のステップを追加するため、これにより線形のフロー image → model → detections が構築されます。ステップ 3 のモデルの一部が、その中の 1 つのステップになります。
6. グラフを構築して実行する
neat::Run run = graph.build();
run.push("image", neat::TensorList{input});
run.close_input();
neat::TensorList outputs = run.pull_tensors("detections", /*timeout_ms=*/2000);
run.close();
build() は、公開されているグラフを、ノード名を保持したまま、実行可能なランタイムグラフに変換します。次に、入力データを名前付きの入力にpushし、それ以上の入力が不要になったらclose_input()を呼び、タイムアウトを設定して、名前付きの出力から結果をpullします。pull_tensorsは、TensorListを返します。これは、Model.runが生成するのと同じ形状のテンソルであり、ここでは、パックされたYOLOv8のBBOX出力です。
7. バウンディングボックスをデコード
neat::TensorList decoded = neat::decode_bbox(outputs);
decode_bbox は、1:1 の位置関係を持つ TensorList → TensorList 変換です。各デコードされた出力は、[num_detections, 6] の形状を持つ float32 テンソルであり、列は (x1, y1, x2, y2, score, class_id) です。
8. バウンディングボックスを読み取る
const neat::Tensor& boxes = decoded.front(); // [num_detections, 6] float32
auto m = boxes.map_read();
const float* d = static_cast<const float*>(m.data);
for (int64_t i = 0; i < boxes.shape[0]; ++i) {
const float* r = d + i * 6; // x1 y1 x2 y2 score class_id
const int cls = static_cast<int>(r[5]);
const char* name = (cls == 0) ? "person" : (cls == 27) ? "tie" : "?";
std::printf("%-8s %.2f [%4.0f %4.0f %4.0f %4.0f]\n", name, r[4], r[0], r[1], r[2], r[3]);
}
Pythonでは、デコードされたテンソルは次のように読み込まれます。 [N, 6] NumPy配列を介して to_numpy()C++では、テンソルをマッピングし、浮動小数点数を読み取ります。モデルはCOCOクラスIDを出力します。それらを表示名にマッピングするのはアプリケーション側で行います。
完全なプログラム
プロジェクトディレクトリにファイルを作成し、ビルドして実行します。
CMakeLists.txt と main.cpp を作成します。
cmake_minimum_required(VERSION 3.16)
project(sima_neat_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Supports both DevKit/native installs (system paths) and
# cross builds with SYSROOT exported (SDK sysroot paths).
if(DEFINED ENV{SYSROOT} AND NOT "$ENV{SYSROOT}" STREQUAL "")
list(APPEND CMAKE_PREFIX_PATH
"$ENV{SYSROOT}/usr"
"$ENV{SYSROOT}/usr/lib"
"$ENV{SYSROOT}/usr/lib/aarch64-linux-gnu"
)
endif()
find_package(SimaNeat REQUIRED CONFIG)
find_package(PkgConfig REQUIRED)
pkg_check_modules(OPENCV REQUIRED IMPORTED_TARGET opencv4)
add_executable(sima_neat_app main.cpp)
target_link_libraries(sima_neat_app
PRIVATE
SimaNeat::sima_neat
PkgConfig::OPENCV
)
強調表示されている2つの行は、アプリケーションとNeatをリンクさせる部分です。find_package(SimaNeat REQUIRED CONFIG)は、インストールされたNeatパッケージを(SimaNeatConfig.cmakeを介して)検索し、target_link_libraries(sima_neat_app PRIVATE SimaNeat::sima_neat ...)は、それとリンクします。インポートされたSimaNeat::sima_neatターゲットは、Neatのインクルードディレクトリと推移的な依存関係を自動的に伝播するため、手動でのインクルード/ライブラリパスの指定は不要です。(PkgConfig::OPENCVは、このアプリケーションがOpenCVを使用して画像をロードするため、必要です。)
#include "neat.h"
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
namespace neat = simaai::neat;
neat::Model::Options yolo_model_options() {
neat::Model::Options opt;
opt.preprocess.kind = neat::InputKind::Image;
opt.preprocess.preset = neat::NormalizePreset::COCO_YOLO;
opt.decode_type = neat::BoxDecodeType::YoloV8;
opt.score_threshold = 0.25f;
opt.nms_iou_threshold = 0.45f;
opt.top_k = 100;
return opt;
}
int main() {
cv::Mat bgr = cv::imread("assets/tutorial_sample_image.png");
if (bgr.empty())
throw std::runtime_error("failed to read assets/tutorial_sample_image.png");
cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
neat::Model model("assets/yolo_v8s_mpk.tar.gz", yolo_model_options());
neat::Tensor input = neat::Tensor::from_cv_mat(
rgb,
neat::ImageSpec::PixelFormat::RGB);
// Compose the model into a Graph application: image -> model -> detections.
neat::Graph graph("hello_neat_app");
graph.add(neat::nodes::Input("image"));
graph.add(model);
graph.add(neat::nodes::Output("detections"));
// Build the app, push the image into the named input, pull the named output.
neat::Run run = graph.build();
run.push("image", neat::TensorList{input});
run.close_input();
neat::TensorList outputs = run.pull_tensors("detections", /*timeout_ms=*/2000);
run.close();
neat::TensorList decoded = neat::decode_bbox(outputs);
const neat::Tensor& boxes = decoded.front(); // [num_detections, 6] float32
auto m = boxes.map_read();
const float* d = static_cast<const float*>(m.data);
for (int64_t i = 0; i < boxes.shape[0]; ++i) {
const float* r = d + i * 6; // x1 y1 x2 y2 score class_id
const int cls = static_cast<int>(r[5]);
const char* name = (cls == 0) ? "person" : (cls == 27) ? "tie" : "?";
std::printf("%-8s %.2f [%4.0f %4.0f %4.0f %4.0f]\n", name, r[4], r[0], r[1], r[2], r[3]);
}
std::printf("[OK] Graph app completed\n");
return 0;
}
構築:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
実行:
- DevKit 上で
./build/sima_neat_app
- Neat SDK がインストールされているホスト
dk build/sima_neat_app
検出ごとに1行表示されます。その後は:
[OK] Graph app completed
Neat がどのように組み立てられたか
API は、その構造に直接対応しています。
Graphはアプリケーションのフローを保持し、graph.add(...)は各ステップを順番に追加します。- 名前付きの入力と出力は、ランタイムのエンドポイントになります:
run.push("image", ...)とrun.pull_tensors("detections")。 Modelは、Model.runを使用して直接呼び出すのと同じフラグメントであり、ここではアプリケーション内の 1 つのノードとして実行されます。
次のステップ
より高度なグラフの構成については、グラフプログラミングモデル を参照してください。
そこから、より広範な SiMa.ai Neat の学習リソースに進みます。
- モデル、グラフ、実行などの主要な Neat の概念を説明する 基本的なプログラミングモデル を学習します。
- 特定の概念とワークフローを段階的に説明する チュートリアル を参照します。
- アプリポータル で厳選されたアプリケーションを探索し、GitHub上のアプリケーションリポジトリ でソースコードを確認します。