Skip to main content

Preprocess Images Before Inference

Preprocess Images Before Inference — animated walkthrough overview

FieldValue
DifficultyIntermediate
Estimated Read Time15-20 minutes
Labelspreprocessing, normalization, image

A compiled model expects its input in one precise shape and value range: a fixed color order, fixed dimensions, and the normalization recipe it was trained with. Preprocessing is the stage that takes a raw decoded image and turns it into exactly that tensor. Get it wrong and the model still runs — it just returns confident nonsense, which is why preprocessing is the first thing to verify when a deployed model "looks broken."

This chapter configures the preprocessing controls you reach for most — color format, input/output dimensions, resize behavior, and per-channel mean/stddev normalization — and then inspects the model's preprocessing graph before running one deterministic tensor through the full model. By the end you will have declared a full preproc contract, attached it to a model, and confirmed the configured route is present.

Walkthrough

Configure the preprocessing contract

These options declare the contract the preprocessing stage enforces. format (or color_convert.input_format) fixes the color order at ingress; the input_max_* fields bound the dynamic input the runtime will accept; the resize/output dimensions set the tensor size produced for inference; and normalize plus the per-channel mean/stddev constants apply the value scaling. The normalization constants must match the model's training-time recipe — mismatched stats are the most common cause of low-confidence output.

Fields live under Model::Options::preprocesscolor_convert.input_format takes a PreprocessColorFormat enum, normalize.enable is an AutoFlag, and normalize.mean / normalize.stddev are std::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};

Build the model

Constructing the Model from the archive path plus the options binds your preprocessing contract to the loaded model. From here the model carries the preprocessing definition with it, so any stage or run derived from it reuses the same recipe.

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

Inspect preprocessing in isolation

This chapter inspects the preprocessing fragment before running the full model, so you can confirm the route exists before debugging anything downstream.

stages::Preproc(frames, model) runs the preprocessing step alone and returns the preprocessed Tensor directly — we read pre.shape.size() (rank) and pre.dtype to confirm the contract took effect.

tutorials/006_preprocess_images/preprocess_images.cpp
simaai::neat::Tensor pre =
simaai::neat::stages::Preproc(std::vector<cv::Mat>{bgr}, model).front();

Run

Run the Python and C++ (prebuilt) commands from the Neat install root (the directory that contains share/ and lib/); run the build from source commands from the repo root.

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

Expected output (the C++ build prints the preprocessed tensor's rank and dtype enum):

preproc_rank=3
preproc_dtype=1
[OK] 006_preprocess_images

(The Python build prints preproc_graph=ready, the graph description, and output_count=....) To integrate this chapter's C++ source into your own project with a custom CMakeLists.txt (no extras folder required), see How to Run Tutorials on the landing page.

Full source

Show the complete C++ and Python programs
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::Tensor pre =
simaai::neat::stages::Preproc(std::vector<cv::Mat>{bgr}, model).front();

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

Source