C++でのMNISTによる数字認識
C++でのMNISTによる数字認識
Section titled “C++でのMNISTによる数字認識”このサンプルは、Model ZooのMNISTモデルを使用しています: https://github.com/onnx/models/tree/main/validated/vision/classification/mnist

コンパイル済みのOnnxruntime.dll / lib(dllのビルド方法へのリンク) Windows Visual Studioコンパイラ(cl.exe)
このディレクトリで ‘build.bat’ を実行してcl.exeを呼び出し、MNIST.exeを生成します。 その後、MNIST.exeを実行します。
左側のボックスで、左マウスボタン(またはタッチ)を使用して数字を描画します。マウスボタンを離すと、モデルが実行され、モデルの出力が表示されます。複数の描画ストロークを必要とする数字を描画する場合、各ストロークの終わりにモデルが実行され、おそらく誤った予測が表示されることに注意してください(しかし、それを見るのは面白く、「モデルを実行」ボタンを押す必要がなくなります)。
画像をクリアするには、どこでも右マウスボタンをクリックします。
ランタイムを初期化するために、単一のOrt::Envがグローバルに作成されます。
Ort::Env env{ORT_LOGGING_LEVEL_WARNING, "test"};MNIST構造体は、Onnx Runtimeとのすべての対話を抽象化し、テンソルを作成し、モデルを実行します。
WWinMainはWindowsのエントリポイントであり、メインウィンドウを作成します。
WndProcはウィンドウのウィンドウプロシージャであり、マウス入力を処理し、グラフィックを描画します。
データの事前処理
Section titled “データの事前処理”MNISTの入力は{1,1,28,28}形状のfloatテンソルであり、基本的には28x28の浮動小数点グレースケール画像です(0.0 = 背景、1.0 = 前景)。
このサンプルでは、画像をピクセルあたり32ビットのWindows DIBセクションに格納します。これは、Windowsで描画したり画面に描画したりするのが簡単だからです。DIBはここで作成されます:
{ BITMAPINFO bmi{}; bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); bmi.bmiHeader.biWidth = MNIST::width_; bmi.bmiHeader.biHeight = -MNIST::height_; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biCompression = BI_RGB;
void* bits; dib_ = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); }DIBデータを変換してモデルの入力テンソルに書き込む関数:
void ConvertDibToMnist() { DIBInfo info{dib_};
const DWORD* input = reinterpret_cast<const DWORD*>(info.Bits()); float* output = mnist_.input_image_.data();
std::fill(mnist_.input_image_.begin(), mnist_.input_image_.end(), 0.f);
for (unsigned y = 0; y < MNIST::height_; y++) { for (unsigned x = 0; x < MNIST::width_; x++) { output[x] += input[x] == 0 ? 1.0f : 0.0f; } input = reinterpret_cast<const DWORD*>(reinterpret_cast<const BYTE*>(input) + info.Pitch()); } output += MNIST::width_;}出力の後処理
Section titled “出力の後処理”MNISTの出力は、各数字の尤度重みを保持する単純な{1,10} floatテンソルです。値が最も高い数字が、モデルの最良の推測です。
MNIST構造体は、これを行うためにstd::max_elementを使用し、それをresult_に格納します:
result_ = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end()));さらに面白くするために、ウィンドウペイントハンドラーは確率をグラフ化し、ここで重みを表示します:
// 勝者をハイライト RECT rc{graphs_left, mnist_.result_ * 16, graphs_left + graph_width + 128, (mnist_.result_ + 1) * 16}; FillRect(hdc, &rc, brush_winner_);
// すべてのエントリについて、確率とグラフを描画します SetBkMode(hdc, TRANSPARENT); wchar_t value[80]; for (unsigned i = 0; i < 10; i++) { int y = 16 * i; float result = mnist_.results_[i];
auto length = wsprintf(value, L"%2d: %d.%02d", i, int(result), abs(int(result * 100) % 100)); TextOut(hdc, graphs_left + graph_width + 5, y, value, length);
Rectangle(hdc, graphs_zero, y + 1, graphs_zero + result * graph_width / range, y + 14); }
// ゼロ線を描画 MoveToEx(hdc, graphs_zero, 0, nullptr); LineTo(hdc, graphs_zero, 16 * 10);Ort::Session
Section titled “Ort::Session”-
作成:Ort::Sessionは、MNIST構造体内でここで作成されます:
Ort::Session session_{env, ORT_TSTR("model.onnx"), Ort::SessionOptions{nullptr}}; -
入力と出力の設定:入力と出力のテンソルはここで作成されます:
MNIST() {auto allocator_info = Ort::AllocatorInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());output_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, results_.data(), results_.size(), output_shape_.data(), output_shape_.size());}この使用法では、Ortにバッファを割り当てさせる代わりに、データのメモリ位置を提供しています。この場合、バッファは小さく、MNIST構造体の固定メンバーにすることができるため、この方が簡単です。
-
実行:セッションの実行はRun()メソッドで行われます:
int Run() {const char* input_names[] = {"Input3"};const char* output_names[] = {"Plus214_Output_0"};session_.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor_, 1, output_names, &output_tensor_, 1);result_ = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end()));return result_;}