メインコンテンツまでスキップ

アプリケーションの実行

グラフの作成

元の画像における Neat による YOLOv8 の検出結果

以下のプログラムで生成された検出結果を、元の画像に重ねて表示します。

これは、小さなアプリケーションとして実行するのと同じYOLOv8推論です。Model.run(...)を直接呼び出す代わりに(モデルを実行するの場合と同様)、モデルをGraph(入力、モデル、および出力を持つ名前付きグラフフロー)に組み込み、それを構築してプッシュ/プルします。PythonとC++で同じプログラムを使用できます。各コードブロックで言語タブを選択してください。

この最初のアプリケーションでは、意図的に単純な構造にしています。

  • 名前付きの_入力_ (nodes.input("image"))は、データがアプリケーションにどこから入力されるかを示します。
  • モデル (graph.add(model))は、グラフ内の1つのステップとしてモデルを実行します。
  • 名前付きの_出力_ (nodes.output("detections"))は、アプリケーションが結果を読み取る場所を示します。

同じAPIは、後により複雑なアプリケーションにも拡張できます。ここでは、基本的な構成パターンを理解することが目的です。

言語を選択してください。

任意のコードブロックにある「Python / C++」タブを使用してください。選択はサイト全体の言語セレクターに従うため、すべてのコードスニペットとプログラム全体がまとめて切り替わります。

プロジェクトのセットアップ

すでにモデルを実行するを実行しましたか?

このセクションはスキップできます。ここでは、同じassets/ディレクトリ、モデルパッケージ、およびサンプル画像を使用します。直接、コードをステップごとに確認するに進んでください。

  1. モデルと入力画像用のアセットディレクトリを作成します。
    mkdir -p assets
  2. モデルをダウンロードします:
    sima-cli modelzoo -v 2.0.0 get yolo_v8s
    sima-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.txtmain.cpp を作成します。

CMakeLists.txt
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を使用して画像をロードするため、必要です。)

main.cpp
#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 がどのように組み立てられたか

こんにちは。Neat グラフアプリの操作の流れです。

API は、その構造に直接対応しています。

  • Graph はアプリケーションのフローを保持し、graph.add(...) は各ステップを順番に追加します。
  • 名前付きの入力と出力は、ランタイムのエンドポイントになります: run.push("image", ...)run.pull_tensors("detections")
  • Model は、Model.run を使用して直接呼び出すのと同じフラグメントであり、ここではアプリケーション内の 1 つのノードとして実行されます。

次のステップ

より高度なグラフの構成については、グラフプログラミングモデル を参照してください。

そこから、より広範な SiMa.ai Neat の学習リソースに進みます。