跳至主要内容

將模型整合到您的管線中

欄位
類別圖形與管線
難度中級
預估閱讀時間15 minutes
標籤graph, composition, patterns

第 003 章逐步建立圖。這是一種最明確的流程,但一旦您擁有一個 Model,您通常不希望手動設定其內部元件。model.graph(...) 將模型的管線作為一個群組提供,您可以將其放入一個 Graph 中,只需使用一個 add(...) 即可。有趣的問題是,這個群組會帶來多少邊界資訊——而這正是 ModelRouteOptions 控制的。

本章將針對同一個模型,對比兩種路由設定:一種是包含自身公共輸入/輸出邊界的獨立可執行圖,另一種是省略輸入的附加圖,以便它可以連接到上游來源(例如,一個攝影機),並使用明確的名稱。到本章結束時,您將會組合這兩種設定,並列印每個設定的後端 GStreamer 字串,以便您可以準確地看到接線方式的差異。

操作指南

組合一個可執行的模型圖

第一種模式要求模型提供一個完全可執行的圖。在路由選項中設定 include_input = trueinclude_output = true,會指示 model.graph(opts) 在模型群組周圍插入明確的公共輸入和輸出邊界,因此,生成的 Graph 可以獨立建立和執行,而無需附加任何其他元件。graph.add(model.graph(opts)) 是整個組合過程——這個單一的 add 是所有模式的基礎。列印 describe_backend() 會顯示生成的 GStreamer 管線字串。

**C++:**路由選項是 Model::RouteOptions;圖是 simaai::neat::Graph

**Python:**路由選項是 pyneat.ModelRouteOptions;圖是 pyneat.Graph

tutorials/008_plug_model_into_pipeline/plug_model_into_pipeline.cpp
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

設定附加時的路由選項

第二種模式將模型附加到上游來源,而不是為其提供自身的輸入。在這裡,include_input = false 會移除公共輸入邊界(幀將來自其他地方),include_output = true 會保留輸出,而 upstream_namename_suffixbuffer_name 會使接線和元件名稱明確化。像這樣一致的命名方式,有助於在多攝影機或多模型部署中,使後端圖更易於閱讀和診斷。

tutorials/008_plug_model_into_pipeline/plug_model_into_pipeline.cpp
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 則是決定該群組帶入哪些邊界的控制項。

**C++:**每個變體都會列印其 describe_backend(),以便您可以比較這兩個後端字串;然後,該檔案還會建立並執行一個手動連接的直接 Input -> Output 圖,以確認端到端的路徑,並列印 direct_rank=

**Python:**連接的變體會列印 attached_graph_built=True,以確認組合已成功。

tutorials/008_plug_model_into_pipeline/plug_model_into_pipeline.cpp
simaai::neat::Graph attached;
attached.add(model.graph(sopt));

執行

Neat 安裝根目錄(包含 share/lib/ 的目錄)執行 PythonC++(預先建置) 命令;從 儲存庫根目錄執行 從原始碼建置 命令。

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(無需額外的資料夾),請參閱登陸頁面上的 如何執行教學

完整原始碼

顯示完整原始碼程式
tutorials/008_plug_model_into_pipeline/plug_model_into_pipeline.cpp
// 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;
}
}

來源