跳至主要内容

執行應用程式

組裝您的圖。

從原始影像中的 Neat 取得的 YOLOv8 偵測結果。

以下程式碼所產生的檢測結果,會繪製在原始影像上。

這與小型「應用程式」中的 YOLOv8 推論相同:與直接呼叫 Model.run(...)(如 執行模型 中所示)不同,您將模型組合成一個 Graph,即一個帶有輸入、模型和輸出的命名圖流程,然後建立並推送/拉取。以下程式碼以 Python 和 C++ 兩種語言呈現,請在每個程式碼區塊中選擇對應的語言標籤。

對於這個第一個應用程式,其結構刻意設計得簡單:

  • 一個命名的「輸入」(nodes.input("image"))標示資料進入應用程式的位置。
  • 一個「模型」(graph.add(model))將模型作為圖中的一個步驟執行。
  • 一個命名的「輸出」(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

    開啟或下載範例圖片

逐步講解程式碼

該程式由八個簡短的程式碼片段組成。切換每個程式碼片段的語言標籤。

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 在單一物件中宣告模型路徑:說明 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 的類型化資料容器——包含形狀、資料類型、佈局,以及框架需要用來解讀位元組的像素格式。必須傳遞 PixelFormat,這樣 Neat 才能知道佈局,而不僅僅是位元組。

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 的模型片段會變成其中的一個步驟。

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 是一個 TensorList → TensorList 轉換,位置為 1:1。每個解碼後的輸出都是一個 float32 張量,其形狀為 [num_detections, 6],欄位為 (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
)

這兩行標記的程式碼是將您的應用程式與 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

您應該會看到每次檢測結果都顯示在一行上,然後:

[OK] Graph app completed

Neat 整合了哪些內容

您好 Neat 圖表應用流程

這些 API 直接對應到該結構:

  • Graph 包含應用程式流程;graph.add(...) 依序附加每個步驟。
  • 具命名的輸入和輸出會成為執行階段端點:run.push("image", ...)run.pull_tensors("detections")
  • Model 是與您直接使用 Model.run 呼叫的相同片段;在這裡,它作為應用程式內的一個節點執行。

後續步驟

若要深入了解圖的組裝方式,請繼續閱讀 Graph 程式設計模型

從那裡開始,繼續使用更廣泛的 SiMa.ai Neat 學習資源: