メインコンテンツまでスキップ

推論前に画像を前処理する

項目
カテゴリモデルと推論
難易度中級
推定所要時間15-20 minutes
ラベルpreprocessing, normalization, image

コンパイルされたモデルは、特定の形状と値の範囲で入力されることを想定しています。つまり、固定された色の順序、固定された次元、および学習時に使用された正規化のレシピです。前処理は、生のデコードされた画像を目的のテンソルに変換する段階です。これを間違えると、モデルは実行されますが、信頼性の低い無意味な結果を返すだけです。そのため、デプロイされたモデルが「壊れている」ように見える場合は、まず前処理を確認する必要があります。

この章では、最も頻繁に使用する前処理の制御(色の形式、入力/出力の次元、リサイズ動作、およびチャネルごとのmean/stddev正規化)を設定し、次に、完全なモデルに単一の決定論的なテンソルを適用する前に、モデルの前処理グラフを調べます。この章の終わりまでに、完全な前処理契約を宣言し、それをモデルに添付し、設定されたルートが存在することを確認します。

ウォークスルー

前処理契約を設定する

これらのオプションは、前処理段階で適用される契約を宣言します。format(またはcolor_convert.input_format)は、入力時の色の順序を固定します。input_max_*フィールドは、ランタイムが受け入れる動的な入力を制限します。リサイズ/出力の次元は、推論のために生成されるテンソルのサイズを設定します。そして、normalizeとチャネルごとのmean/stddev定数は、値のスケーリングを適用します。正規化定数は、モデルの学習時のレシピと一致する必要があります。一致しない統計は、信頼性の低い出力の最も一般的な原因です。

フィールドはModel::Options::preprocessの下に存在します。color_convert.input_formatPreprocessColorFormat列挙型を受け取り、normalize.enableAutoFlagであり、normalize.mean / normalize.stddevstd::array<float, 3>です。

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

前処理を個別に検査する

この章では、完全なモデルを実行する前に、前処理フラグメントを検査します。これにより、後続のデバッグを行う前に、ルートが存在することを確認できます。

stages::Preproc(frames, model) は、前処理ステップを単独で実行し、前処理された Tensor を直接返します。pre.shape.size()(ランク)と pre.dtype を読み取り、コントラクトが有効になっていることを確認します。

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/ を含むディレクトリ)から、Python および C++(事前にビルドされたもの) のコマンドを実行します。ソースコードからビルドするコマンドは、リポジトリのルートから実行します。

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

ソース