跳至主要内容

在推論之前預處理圖像

欄位
類別模型與推論
難度中級
預估閱讀時間15-20 minutes
標籤preprocessing, normalization, image

編譯後的模型會預期其輸入具有精確的形狀和值範圍:固定的色彩順序、固定的尺寸,以及模型訓練時使用的正規化方法。預處理是將原始解碼後的圖像轉換為精確的張量的階段。如果預處理不正確,模型仍然會執行,但它會產生毫無意義的結果,這就是為什麼在部署的模型「看起來有問題」時,預處理應該是首先驗證的內容。

本章設定了您最常用的預處理控制項:色彩格式、輸入/輸出尺寸、調整大小行為以及每個通道的 mean/stddev 正規化。然後,它會在執行一個確定性張量通過整個模型之前,檢查模型的預處理圖。到本章結束時,您將會定義一個完整的預處理合約,將其附加到一個模型,並確認已設定的流程存在。

操作指南

設定預處理合約

這些選項定義了預處理階段強制執行的合約。format(或 color_convert.input_format)會在輸入時固定色彩順序;input_max_* 欄位會限制執行階段將接受的動態輸入;調整大小/輸出尺寸會設定用於推論的張量大小;normalize 加上每個通道的 mean/stddev 常數會應用值縮放。正規化常數必須與模型訓練時使用的配方相符——不匹配的統計資料是最常見的低信度輸出原因。

C++: 欄位位於 Model::Options::preprocess 下——color_convert.input_format 採用 PreprocessColorFormat 列舉,normalize.enable 是一個 AutoFlag,而 normalize.mean / normalize.stddevstd::array<float, 3>

Python: 欄位位於 ModelOptions.preprocess 下——color_convert.input_format 採用 PreprocessColorFormat 列舉,normalize.enable 是一個 AutoFlag,常數是分配給 normalize.mean / normalize.stddev 的列表。

tutorials/006_preprocess_images/preprocess_images.cpp
simaai::neat::Model::Options opt;
opt.preprocess.color_convert.input_format = simaai::neat::PreprocessColorFormat::BGR;
opt.preprocess.input_max_width = size;
opt.preprocess.input_max_height = size;
opt.preprocess.input_max_depth = 3;
opt.preprocess.resize.width = size;
opt.preprocess.resize.height = size;
opt.preprocess.resize.width = size;
opt.preprocess.resize.height = size;
opt.preprocess.normalize.enable = simaai::neat::AutoFlag::On;
opt.preprocess.normalize.mean = std::array<float, 3>{0.5f, 0.5f, 0.5f};
opt.preprocess.normalize.stddev = std::array<float, 3>{0.5f, 0.5f, 0.5f};

建立模型

從檔案路徑加上選項建構 Model,將您的預處理合約繫結到已載入的模型。從此時起,模型會攜帶預處理定義,因此從其衍生出的任何階段或執行都會重複使用相同的配方。

tutorials/006_preprocess_images/preprocess_images.cpp
simaai::neat::Model model(model_path, opt);

獨立檢查預處理

本章在執行整個模型之前檢查預處理片段,因此您可以確認流程存在,然後再對下游的任何內容進行除錯。

C++:stages::Preproc(frames, model) 會單獨執行預處理步驟,並直接傳回預處理後的 Tensor ——我們讀取 pre.shape.size()(階數)和 pre.dtype,以確認合約已生效。

Python:model.preprocess() 傳回預處理 Graph 片段,因此我們會列印 describe() 以檢查已設定的路由;後續的 model.run([tensor]) 會執行完整的路徑,並回報輸出計數。

tutorials/006_preprocess_images/preprocess_images.cpp
simaai::neat::TensorList pre_outputs =
simaai::neat::stages::Preproc(std::vector<cv::Mat>{bgr}, model);
if (pre_outputs.empty())
throw std::runtime_error("preprocess produced no outputs");
simaai::neat::Tensor pre = pre_outputs.front();
if (pre.shape.empty())
throw std::runtime_error("preprocess output shape is empty");

執行

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

C++ (prebuilt):

./lib/sima-neat/tutorials/tutorial_006_preprocess_images \
--model /tmp/resnet_50.tar.gz --size 224

C++ (build from source):

./build.sh --target tutorial_006_preprocess_images
./build/tutorials-standalone/tutorial_006_preprocess_images \
--model /tmp/resnet_50.tar.gz --size 224

預期的輸出(C++ 建置會列印預處理後的張量階數和 dtype 列舉):

preproc_rank=3
preproc_dtype=1
[OK] 006_preprocess_images

(Python 建置會列印 preproc_graph=ready、圖的描述以及 output_count=...。)若要將本章的 C++ 原始碼整合到您自己的專案中,並使用自訂的 CMakeLists.txt(無需額外的資料夾),請參閱登陸頁面上的 如何執行教學

完整原始碼

顯示完整原始碼程式
tutorials/006_preprocess_images/preprocess_images.cpp
// Run preprocessing standalone via stages::Preproc and inspect the resulting tensor.
//
// Usage:
// tutorial_006_preprocess_images --model /path/to/resnet_50.tar.gz [--size 224]

#include "neat.h"

#include "pipeline/StageRun.h"

#include <opencv2/core.hpp>

#include <array>
#include <iostream>
#include <stdexcept>
#include <string>

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

int parse_int_arg(int argc, char** argv, const std::string& key, int def) {
std::string value;
if (!get_arg(argc, argv, key, value))
return def;
return std::stoi(value);
}

} // namespace

int main(int argc, char** argv) {
try {
std::string model_path;
if (!get_arg(argc, argv, "--model", model_path)) {
std::cerr << "Usage: tutorial_006_preprocess_images --model <path> [--size <n>]\n";
return 1;
}
const int size = parse_int_arg(argc, argv, "--size", 224);

simaai::neat::Model::Options opt;
opt.preprocess.color_convert.input_format = simaai::neat::PreprocessColorFormat::BGR;
opt.preprocess.input_max_width = size;
opt.preprocess.input_max_height = size;
opt.preprocess.input_max_depth = 3;
opt.preprocess.resize.width = size;
opt.preprocess.resize.height = size;
opt.preprocess.resize.width = size;
opt.preprocess.resize.height = size;
opt.preprocess.normalize.enable = simaai::neat::AutoFlag::On;
opt.preprocess.normalize.mean = std::array<float, 3>{0.5f, 0.5f, 0.5f};
opt.preprocess.normalize.stddev = std::array<float, 3>{0.5f, 0.5f, 0.5f};

simaai::neat::Model model(model_path, opt);

cv::Mat bgr(size, size, CV_8UC3, cv::Scalar(40, 80, 120));
if (!bgr.isContinuous())
bgr = bgr.clone();

// CORE LOGIC
// stages::Preproc runs just the preprocessing step from the model's Options
// and returns the preprocessed Tensor.
simaai::neat::TensorList pre_outputs =
simaai::neat::stages::Preproc(std::vector<cv::Mat>{bgr}, model);
if (pre_outputs.empty())
throw std::runtime_error("preprocess produced no outputs");
simaai::neat::Tensor pre = pre_outputs.front();
if (pre.shape.empty())
throw std::runtime_error("preprocess output shape is empty");

std::cout << "preproc_rank=" << pre.shape.size() << "\n";
std::cout << "preproc_dtype=" << static_cast<int>(pre.dtype) << "\n";
std::cout << "[OK] 006_preprocess_images\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}

來源