モデルをパイプラインに組み込む
| 項目 | 値 |
|---|---|
| カテゴリ | グラフとパイプライン |
| 難易度 | 中級 |
| 推定所要時間 | 15 minutes |
| ラベル | graph, composition, patterns |
第3章では、グラフをノードごとに構築します。これは最も明確な方法ですが、一度Modelを作成すると、その内部をすべて手動で接続する必要はほとんどありません。model.graph(...)は、モデルのパイプラインをグループとして提供し、それをGraphに1つのadd(...)で追加します。重要な点は、そのグループがどの程度の境界線(インターフェース)を一緒に持ち込むかであり、それはModelRouteOptionsによって制御されます。
この章では、同じモデルに対して2つの異なるルーティング設定を比較します。1つは、独自のパブリックな入力/出力境界を含む、スタンドアロンで実行可能なグラフであり、もう1つは入力を省略して、上流のソース(たとえばカメラ)に明示的な名前で接続できるグラフです。この章の終わりまでに、両方を構成し、それぞれのバックエンドのGStreamer文字列を出力することで、配線の違いを正確に確認できます。
ウォークスルー
実行可能なモデルグラフを構成する
最初のパターンでは、モデルに完全に実行可能なグラフを要求します。ルーティングオプションでinclude_input = trueとinclude_output = trueを設定すると、model.graph(opts)は、モデルグループの周囲に明示的なパブリックな入力と出力の境界を挿入します。これにより、結果として得られるGraphは、他の要素を接続することなく、単独で構築および実行できます。graph.add(model.graph(opts))が全体の構成であり、この単一のaddが、すべてのパターンで最終的に行われる処理です。describe_backend()を出力すると、生成されたGStreamerパイプライン文字列が表示されます。
ルーティングオプションはModel::RouteOptionsであり、グラフはsimaai::neat::Graphです。
simaai::neat::Model::RouteOptions runnable_opt;
runnable_opt.include_input = true;
runnable_opt.include_output = true;
// CORE LOGIC
simaai::neat::Graph from_model;
from_model.add(model.graph(runnable_opt));
// END CORE LOGIC
接続時のルーティングオプションを構成する
2番目のパターンでは、モデルを独自の入力を持たずに、上流のソースに接続します。ここでは、include_input = falseによってパブリックな入力境界を削除します(フレームは他の場所から取得されます)、include_output = trueによって出力を保持し、upstream_name、name_suffix、およびbuffer_nameによって配線と要素の名前を明示的にします。このように一貫した命名を行うことで、バックエンドグラフが読みやすく、複数のカメラまたは複数のモデルを使用したデプロイメントで診断しやすくなります。
simaai::neat::Model::RouteOptions sopt;
sopt.include_input = false;
sopt.include_output = true;
sopt.upstream_name = "camera0";
sopt.name_suffix = "_camera0";
sopt.buffer_name = "camera0";
モデルグループを接続する
これらのオプションを設定すると、graph.add(model.graph(opts)) は同じモデルグループを注入し、今度は独自のソースを保持するのではなく、名前付きの上流に接続 するように設定します。これは、最初のパターンと同じ add 呼び出しであり、変更されたのはルートオプションだけです。つまり、合成は単一の操作であり、ModelRouteOptions は、グループがどの境界を伴って処理されるかを決定する設定項目です。
各バリアントは、describe_backend() を出力するため、2つのバックエンド文字列を比較できます。その後、ファイルは手動で作成した直接的な Input -> Output グラフを構築および実行して、エンドツーエンドのパスを確認し、direct_rank= を出力します。
simaai::neat::Graph attached;
attached.add(model.graph(sopt));
実行
Neat インストールルート(share/ と lib/ を含むディレクトリ)から、Python および C++(事前にビルドされたもの) コマンドを実行します。ソースからビルド コマンドは、リポジトリルートから実行します。
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_008_plug_model_into_pipeline \
--model /tmp/yolo_v8s.tar.gz
C++ (build from source):
./build.sh --target tutorial_008_plug_model_into_pipeline
./build/tutorials-standalone/tutorial_008_plug_model_into_pipeline \
--model /tmp/yolo_v8s.tar.gz
予想される出力(C++ ビルドは、各バックエンドグラフ文字列、次に直接グラフのランクを出力します)。
model_graph_backend=
...
attached_graph_backend=
...
direct_rank=3
[OK] 008_plug_model_into_pipeline
(Python ビルドは、direct_graph_backend= の後にバックエンド文字列、次に attached_graph_built=True を出力します。)この章の C++ ソースを、カスタムの CMakeLists.txt を使用して独自のプロジェクトに統合する方法(追加のフォルダーは不要)については、ランディングページにある チュートリアルの実行方法 を参照してください。
完全なソース
完全なソースプログラムを表示
// Three Graph composition patterns: direct nodes, model.graph(), attached Graph.
//
// Usage:
// tutorial_008_plug_model_into_pipeline [--model /path/to/model.tar.gz]
#include "neat.h"
#include <opencv2/core.hpp>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
namespace fs = std::filesystem;
namespace {
bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}
} // namespace
int main(int argc, char** argv) {
try {
const int width = 224;
const int height = 224;
cv::Mat rgb(height, width, CV_8UC3, cv::Scalar(80, 40, 160));
if (!rgb.isContinuous())
rgb = rgb.clone();
// Pattern 1: build a Graph by adding Input/Output nodes directly.
simaai::neat::InputOptions in;
in.format = "RGB";
in.width = width;
in.height = height;
in.depth = 3;
in.do_timestamp = true;
// CORE LOGIC
simaai::neat::Graph direct;
direct.add(simaai::neat::nodes::Input(in));
direct.add(simaai::neat::nodes::Output());
std::string model_path;
if (get_arg(argc, argv, "--model", model_path) && fs::exists(model_path)) {
simaai::neat::Model model(model_path);
// Pattern 2: ask model.graph() to include explicit public Input/Output boundaries.
simaai::neat::Model::RouteOptions runnable_opt;
runnable_opt.include_input = true;
runnable_opt.include_output = true;
// CORE LOGIC
simaai::neat::Graph from_model;
from_model.add(model.graph(runnable_opt));
std::cout << "model_graph_backend=\n" << from_model.describe_backend() << "\n";
// Pattern 3: attach the model under an upstream name with custom options.
simaai::neat::Model::RouteOptions sopt;
sopt.include_input = false;
sopt.include_output = true;
sopt.upstream_name = "camera0";
sopt.name_suffix = "_camera0";
sopt.buffer_name = "camera0";
// CORE LOGIC
simaai::neat::Graph attached;
attached.add(model.graph(sopt));
std::cout << "attached_graph_backend=\n" << attached.describe_backend() << "\n";
}
simaai::neat::TensorList out = direct.run(std::vector<cv::Mat>{rgb});
if (out.empty())
throw std::runtime_error("direct Graph output missing tensor");
std::cout << "direct_rank=" << out.front().shape.size() << "\n";
std::cout << "[OK] 008_plug_model_into_pipeline\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}