본문으로 건너뛰기

추론 전에 이미지 전처리

필드
범주모델 및 추론
난이도중급
예상 소요 시간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/가 포함된 디렉터리)에서 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=...를 출력합니다.) 사용자 지정 CMakeLists.txt (추가 폴더 불필요)를 사용하여 이 장의 C++ 소스를 자체 프로젝트에 통합하는 방법은 랜딩 페이지의 튜토리얼 실행 방법을 참조하십시오.

전체 소스

전체 소스 프로그램 표시
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;
}
}

소스