【C++】ONNX Runtime+Visual Studio 2022の環境構築

ONNX Runtime環境構築

今回はC++版のONNXライブラリをVisual Studio 2022で使用できるように設定していこうと思います。

1. ONNXの環境設定

1.1. プロジェクト作成

まずはVisual Studio 2022でプロジェクトを作成します。

Visual Studio 2022のインストールはこちら。

今回は「Onnx_Project」というプロジェクトを作成しました。

1.2. ONNX Runtimeのインストール

プロジェクトから「NuGetパッケージの管理」を開きます。

NuGetパッケージの管理画面を開いたら左のタブの「参照」を選択し、「onnx」と入力します。

すると、

  • Microsoft.ML.OnnxRuntime
  • Microsoft.ML.OnnxRuntime.Gpu

というパッケージが表示されます。
上がCPU版、下がGPU版のパッケージになります。
今回はCPU想定なので上の「Microsoft.ML.OnnxRuntime」を選択します。

パッケージを選択すると右側に詳細が表示されるので最新版か安定板を選択して「インストール」を押します。

途中でこのような画面が表示された場合はそのまま「適用」を押して進めます。

2. 動作確認

下記のGithubを参考にMnist推論用のプログラムを書いて動作確認してみます。

まず、MNISTの中から「mnist.onnx」をダウンロードします。

2.1. Mnistデータ読み込み関数

Mnist画像読み込み用の関数を作成します。

Mnistデータのダウンロードについてはこちらの記事に記載してます。

Mnistデータは学習用であれば6000枚、テスト用であれば10000枚入っていますが、とりあえず最初の1枚目を読み込んで確認します。

// 4Byte分のデータを数値データへ変換
int Convert4Byte(char* cData) {
    unsigned char ucData[4];
    int iData = 0;
    for (int i = 0; i < 4; i++) {
        ucData[i] = (unsigned char)cData[i];
        iData |= (int)ucData[i] << (24 - (i * 8));
    }
    return iData;
}

// 画像データ読み込み
int ReadFile_Image(std::string filename, std::vector<float>& vfImg) {
	char cData[16];
	int magic_number = 0;
	int number_of_images = 0;
	int rows = 0;
	int cols = 0;

	std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary);

	// ヘッダー読み込み
	ifs.read(cData, 16);
	// マジックナンバー
	magic_number = Convert4Byte(cData);
	// データ総数
	number_of_images = Convert4Byte(&cData[4]);
	// 各データの行数
	rows = Convert4Byte(&cData[8]);
	// 各データの列数
	cols = Convert4Byte(&cData[12]);

	// ヘッダー表示
	std::cout << "マジックナンバー" << " " << magic_number << std::endl;
	std::cout << "画像総数" << " " << number_of_images << std::endl;
	std::cout << "画像縦サイズ[Rows]" << " " << rows << std::endl;
	std::cout << "画像横サイズ[Cols]" << " " << cols << std::endl;

	unsigned char* temp = new unsigned char[rows * cols];
	ifs.read((char*)temp, rows * cols);

	int count = 0;
	for (int r = 0; r < rows; r++) {
		for (int c = 0; c < cols; c++) {
			vfImg.push_back((float)temp[count++]);
		}
	}

	delete[] temp;
	ifs.close();

	return 0;
}

2.2. Mnist構造体

上記のGithubからMnist構造体の部分を抜き出しました。

template <typename T>
static void softmax(T& input) {
    float rowmax = *std::max_element(input.begin(), input.end());
    std::vector<float> y(input.size());
    float sum = 0.0f;
    for (size_t i = 0; i != input.size(); ++i) {
        sum += y[i] = std::exp(input[i] - rowmax);
    }
    for (size_t i = 0; i != input.size(); ++i) {
        input[i] = y[i] / sum;
    }
}

// Mnist構造体
struct MNIST {
    MNIST() {
        auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
        input_tensor_ = Ort::Value::CreateTensor<float>(memory_info, input_image_.data(), input_image_.size(),
            input_shape_.data(), input_shape_.size());
        output_tensor_ = Ort::Value::CreateTensor<float>(memory_info, results_.data(), results_.size(),
            output_shape_.data(), output_shape_.size());
    }

    std::ptrdiff_t Run() {
        const char* input_names[] = { "Input3" };
        const char* output_names[] = { "Plus214_Output_0" };

        Ort::RunOptions run_options;
        session_.Run(run_options, input_names, &input_tensor_, 1, output_names, &output_tensor_, 1);
        softmax(results_);
        result_ = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end()));
        return result_;
    }

    static constexpr const int width_ = 28;
    static constexpr const int height_ = 28;

    std::array<float, width_* height_> input_image_{};
    std::array<float, 10> results_{};
    int64_t result_{ 0 };

private:
    Ort::Env env;
    Ort::Session session_{ env, L"mnist.onnx", Ort::SessionOptions{nullptr} };

    Ort::Value input_tensor_{ nullptr };
    std::array<int64_t, 4> input_shape_{ 1, 1, width_, height_ };

    Ort::Value output_tensor_{ nullptr };
    std::array<int64_t, 2> output_shape_{ 1, 10 };
};

static std::unique_ptr<MNIST> mnist_;

2.3. 画像読み込み→Mnist構造体へセット

次に画像を読み込み、表示してMnist構造体へ渡す関数を作成します。

void SetMnist(void) {
	std::vector<float> vfImg;

	// Mnist画像読み込み
	ReadFile_Image("train-images.idx3-ubyte", vfImg);

	// 画像確認
	for (int r = 0; r < 28; r++) {
		for (int c = 0; c < 28; c++) {
			if (vfImg[r * 28 + c] > 0) {
				std::cout << "X ";
			}
			else {
				std::cout << "  ";
			}
		}
		std::cout << std::endl;
	}
	// vector -> array変換
	std::copy(vfImg.begin(), vfImg.end(), mnist_->input_image_.data());
}

2.4. 推論・結果表示

最後に推論して結果を表示します。

mnist_->Run();
std::cout << "推論結果:" << mnist_->result_ << std::endl;

2.5. テストプログラム全体と実行結果

プログラム全体は以下のようになりました。

#include <iostream>
#include <fstream>
#include <onnxruntime_cxx_api.h>

// 4Byte分のデータを数値データへ変換
int Convert4Byte(char* cData) {
    unsigned char ucData[4];
    int iData = 0;
    for (int i = 0; i < 4; i++) {
        ucData[i] = (unsigned char)cData[i];
        iData |= (int)ucData[i] << (24 - (i * 8));
    }
    return iData;
}

// 画像データ読み込み
int ReadFile_Image(std::string filename, std::vector<float>& vfImg) {
	char cData[16];
	int magic_number = 0;
	int number_of_images = 0;
	int rows = 0;
	int cols = 0;

	std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary);

	// ヘッダー読み込み
	ifs.read(cData, 16);
	// マジックナンバー
	magic_number = Convert4Byte(cData);
	// データ総数
	number_of_images = Convert4Byte(&cData[4]);
	// 各データの行数
	rows = Convert4Byte(&cData[8]);
	// 各データの列数
	cols = Convert4Byte(&cData[12]);

	// ヘッダー表示
	std::cout << "マジックナンバー" << " " << magic_number << std::endl;
	std::cout << "画像総数" << " " << number_of_images << std::endl;
	std::cout << "画像縦サイズ[Rows]" << " " << rows << std::endl;
	std::cout << "画像横サイズ[Cols]" << " " << cols << std::endl;

	unsigned char* temp = new unsigned char[rows * cols];
	ifs.read((char*)temp, rows * cols);

	int count = 0;
	for (int r = 0; r < rows; r++) {
		for (int c = 0; c < cols; c++) {
			vfImg.push_back((float)temp[count++]);
		}
	}

	delete[] temp;
	ifs.close();

	return 0;
}

template <typename T>
static void softmax(T& input) {
    float rowmax = *std::max_element(input.begin(), input.end());
    std::vector<float> y(input.size());
    float sum = 0.0f;
    for (size_t i = 0; i != input.size(); ++i) {
        sum += y[i] = std::exp(input[i] - rowmax);
    }
    for (size_t i = 0; i != input.size(); ++i) {
        input[i] = y[i] / sum;
    }
}

// Mnist構造体
struct MNIST {
    MNIST() {
        auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
        input_tensor_ = Ort::Value::CreateTensor<float>(memory_info, input_image_.data(), input_image_.size(),
            input_shape_.data(), input_shape_.size());
        output_tensor_ = Ort::Value::CreateTensor<float>(memory_info, results_.data(), results_.size(),
            output_shape_.data(), output_shape_.size());
    }

    std::ptrdiff_t Run() {
        const char* input_names[] = { "Input3" };
        const char* output_names[] = { "Plus214_Output_0" };

        Ort::RunOptions run_options;
        session_.Run(run_options, input_names, &input_tensor_, 1, output_names, &output_tensor_, 1);
        softmax(results_);
        result_ = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end()));
        return result_;
    }

    static constexpr const int width_ = 28;
    static constexpr const int height_ = 28;

    std::array<float, width_* height_> input_image_{};
    std::array<float, 10> results_{};
    int64_t result_{ 0 };

private:
    Ort::Env env;
    Ort::Session session_{ env, L"mnist.onnx", Ort::SessionOptions{nullptr} };

    Ort::Value input_tensor_{ nullptr };
    std::array<int64_t, 4> input_shape_{ 1, 1, width_, height_ };

    Ort::Value output_tensor_{ nullptr };
    std::array<int64_t, 2> output_shape_{ 1, 10 };
};

static std::unique_ptr<MNIST> mnist_;

void SetMnist(void) {
	std::vector<float> vfImg;

	// Mnist画像読み込み
	ReadFile_Image("train-images.idx3-ubyte", vfImg);

	// 画像確認
	for (int r = 0; r < 28; r++) {
		for (int c = 0; c < 28; c++) {
			if (vfImg[r * 28 + c] > 0) {
				std::cout << "X ";
			}
			else {
				std::cout << "  ";
			}
		}
		std::cout << std::endl;
	}
	// vector -> array変換
	std::copy(vfImg.begin(), vfImg.end(), mnist_->input_image_.data());
}

int main() {
	mnist_ = std::make_unique<MNIST>();
	SetMnist();
	mnist_->Run();
	std::cout << "推論結果:" << mnist_->result_ << std::endl;
	return 0;
}

実行結果は以下の通りです。

画像と推論結果が一致しており
正常に動作していることが確認できました。

今回は以上です。

3. 参考サイト

・ONNX Runtime リファレンス
https://onnxruntime.ai/docs/

コメント

タイトルとURLをコピーしました