Перейти до основного вмісту

008. Інтегруйте модель у ваш конвеєр

ПолеЗначення
КатегоріяГрафи та конвеєри
СкладністьСередній
Орієнтовний час читання15 minutes
Міткиgraph, composition, patterns

У розділі 003 граф будується вузол за вузлом. Це найочевидніший спосіб, але, маючи Model, ви рідко захочете вручну налаштовувати його внутрішні компоненти. model.graph(...) надає вам конвеєр моделі у вигляді групи, яку ви додаєте до Graph за допомогою однієї операції add(...). Цікаве питання полягає в тому, яку частину меж ця група приносить із собою, і саме це контролюється ModelRouteOptions.

У цьому розділі порівнюються дві конфігурації маршрутів для однієї й тієї ж моделі: самостійний граф, який можна запустити і який містить власні публічні вхідні/вихідні межі, і граф, який додається, і в якому відсутній вхід, щоб його можна було підключити до попереднього джерела (наприклад, камери) під чітко визначеним ім’ям. Наприкінці ви створите обидва графи та виведете рядок 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.

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_name, name_suffix та buffer_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 — це параметр, який визначає, яку межу група приносить із собою.

Кожен варіант виводить describe_backend(), щоб ви могли порівняти два рядки бекенду; потім файл також створює та запускає вручну з’єднаний прямий граф Input -> Output, щоб підтвердити кінцевий шлях, виводячи direct_rank=.

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

Запуск

Запустіть команди Python і C++ (попередньо скомпільовані) з кореневої теки встановлення Neat (теки, яка містить share/ і lib/); запустіть команди збірка з вихідного коду з кореневої теки репозиторію.

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;
}
}

Джерело