모델을 파이프라인에 연결하기
| 필드 | 값 |
|---|---|
| 범주 | 그래프 및 파이프라인 |
| 난이도 | 중급 |
| 예상 소요 시간 | 15 minutes |
| 레이블 | graph, composition, patterns |
3장에서는 그래프를 노드 단위로 구축했습니다. 이것이 가장 명확한 방법이지만, 일단 그래프를 구축하면 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.
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 배선 및 요소 이름을 명확하게 지정합니다. 이와 같이 일관성 있는 명명 규칙을 사용하면 다중 카메라 또는 다중 모델 배포 환경에서 백엔드 그래프의 가독성과 문제 진단이 용이해집니다.
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=.
simaai::neat::Graph attached;
attached.add(model.graph(sopt));
실행
Python 및 C++(미리 빌드된 버전) 명령을 실행합니다.Neat root 디렉터리(해당 디렉터리에 포함된)를 설치합니다. 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 (추가 폴더는 필요하지 않음), 랜딩 페이지의 튜토리얼 실행 방법을 참조하십시오.
전체 소스
전체 소스 프로그램 표시
// 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;
}
}