C++で実行するOepnVinoによる手書き文字認識(MNIST)

# 追記
OpenVINOのバージョンが上がり、C++のソースも古くなりました。
そのため最新版のOpenVINOに対応したものに変更したいのですが時間が取れず...
2020年4月ぐらいには更新できるようにします。
遅れても、オリンピックまでには！

# 追記(2020.01.27)
まだ記事は書けていませんが、Githubにコードは置きました。
最新版のOpenVINO(2021.2)じゃないと困るよ！という人は先にコードを見ていてください。
記事は2021年の2月末までには書き上げるようにします。
[Githubのコード置き場](https://github.com/fan2tamo/OpenVINOApp)
内容は、以下となります。

- Kerasでmnistを学習し、onnx or pbで学習済みモデルの保存
- 学習済みモデルをmodel optimizerでIR形式に変換
- OpenVINOを扱うdllを作成(C++)
- 作ったdllを使い推論するアプリを作成(C++)
- Swigでdll(C++)をC#で使えるようにAPIを追加
- 作ったdllを使い推論するアプリを作成(C#)

# 追記(2020.02.23)
ようやく最新版のOpenVINO(2021.2)に対応したものに書き換えました。
また、C++だけではなくC#からも使えるようにしていますので、この記事よりは以下の記事を見るようにしてください。
[C#で実行するOpenVINOによる手書き文字認識(MNIST)](https://qiita.com/fan2tamo/items/18f418bd6d23686621ea)

オリンピックに間に合って良かった。

# はじめに
近年、ChainerやTensorFlowなどの登場により、手軽に個人でDeepLearningを実行できるようになりつつありますが、個人では以下の理由により、学習フェーズの実行はまだまだハードルが高いです。

- 十分な量・質の学習データを用意する必要がある
- 専門的な知識がないとネットワークの定義が難しい
- 高性能なGPUがないと学習に時間がかかる

逆に、学習済みモデルさえどこからか用意できれば、推論フェーズは個人で十分に可能のため、今回はIntel社のOpenVinoを使用してMNISTの推論にトライしてみました。
と書きつつ、MNISTの学習済みモデルを見つけられなかったので、今回は学習フェーズもやることとします。

# このエントリで身につくこと

- Model Optimaizerの使い方
- OpenVinoでの推論のやり方

# 環境
このエントリを書いている私の環境は以下です。

- OS : Windows10 Pro x64 RS5
- Visual Studio Pro: 2017 & 2019
- OpenVino : 2019 R1
- Python3 : 3.7.2

Visual Studio Pro 2019はまったく不要です。
たぶん2017だけの方がトラブルは少ないです。

# OpenVinoとは
Intel社が提供する推論エンジンです。
ChainerやTensorFlowは学習も推論もできますが、OpenVinoは推論しかできません。
そのため、学習済みモデルを他のフレームワークで作成し、Model OptimizerでOpenVinoが扱える形式に変換する必要があります。
なお、OpenVinoは実行するデバイスはIntel社製のCPU, Myriad, FPGAに限定されています。

# OpenVinoのインストール
これはいろんな人が既に書いていますので省略します。
なにも言及しないものアレなので、[おすすめのエントリ](https://qiita.com/hanapage/items/a2f76d97bed0204d3819)を紹介しておきます。

# MNISTの学習
ここは本題ではないのでサクッと行きたいと思います。
冒頭で記載したようにMNISTの学習済みモデルを見つけることができなかったので、自力で学習させていきましょう。
[ここ](https://qiita.com/sotanmochi/items/afa9c70343734c32bc56)にものすごくピッタリなエントリがありますので、ほぼそのまま使わせてもらうことにしましょう。
変更点はネットワークの定義と、入力画像をグレイスケールではなくカラー画像としたことです。
ソースは[ここ](https://github.com/fantamo/OpenVino_MNIST/blob/master/mnist.py)に置きました。
実行させると1時間前後ぐらいで学習が完了すると思います。
サクッと終わらせたい人はGoogle ColabでGPUを使うことをお勧めします。
一応GPUで学習させて、最後CPUに戻して、ONNX形式に出力するようにしていますので、Google Colab上でも問題なく動きます。

これでONNX形式の学習済みモデルがmnist.onnxというファイル名で作成できました。

# Model OptimaizerによるIR形式への変換
OpenVinoはONNX形式の学習済みモデルを動かすことはできず、IR形式と呼ばれる専用の形式で記述された学習済みモデルでしか動作させることができません。そのため、ONNX形式をIR形式に変換する必要があります。ここが皆さんが遭遇する最初の山かもしれません。

[公式ページ](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_Deep_Learning_Model_Optimizer_DevGuide.html)によると変換する前に準備が必要なようです。
[ここ](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_Config_Model_Optimizer.html)を確認すると"install_prerequisites_onnx.bat"を実行する必要があると記載されいるので、さっそく実行します。

```
cd C:\Program Files (x86)\IntelSWTools\openvino_2019.1.087\deployment_tools\model_optimizer\install_prerequisites
install_prerequisites_onnx.bat
```

次は変換です。今回はONNX形式からの変換なので、[このページ](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_Convert_Model_From_ONNX.html)を参照することになります。
上記ページをみると、以下のコマンドで変換ができると記載があります。

```
python3 mo.py --input_model mnist.onnx
```

上記を実行するとエラーが発生します。上記コマンドによりIR形式のモデルを現在コマンド実行している場所に保存しようとするため、管理者権限がないために保存に失敗してエラーとなっています。そのため、保存する場所を指定します。

```
python3 mo.py --input_model mnist.onnx --output_dir C:\Work\
```

出力する場所は適宜変えてください。
上記を実行するとC:\Work\に以下のファイルが作成されていることが確認できます。

- mnist.xml
- mnist.bin
- mnist.mapping

これでModel Optimaizerによる変換は完了です。
なお、今回はONNX形式からの変換でしたが、TensorFlowやMXNetなど複数の形式からIR形式に変換できますので、お使いのフレームワークに応じて変換してみてください。

# Inference Engineによる推論
ようやく学習済みモデルが作成できましたので、実際の推論(Inference)を実施します。
推論用のコードはC++かPythonで書くことができます。
手軽に試したいのでPythonで書こうかなと思ったのですが、Pythonで説明しているQiitaのエントリは我ら(勝手に"我らが"とか言ってすみません)が[PINTO](https://qiita.com/PINTO)さんを筆頭に良質なエントリがありますが、C++で書いてある記事は少ないのでC++で書くことにします。

なにはともあれ、[Inference Engineの説明ページ](https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_with_customer_application_new_API.html)を見てみます。
上記ページによると以下の流れで推論を実施することになります。

1. Load Plugin
2. Read Model IR
3. Configure Input & Output
4. Load Model
5. Create Infer Request
6. Prepare Input
7. Infer
8. Process Output

順にコードに落としていきます。
なお、不明な個所があったら、[Inference Engineの説明ページ](https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_with_customer_application_new_API.html)を参照してください。

## 1.Load Plugin
ここでは推論を実行するデバイス(CPU, GPU, Myriard, FPGAなど)に応じたプラグインを読み込む処理をします。

```C++
bool LoadPlugin(const std::string& device, InferencePlugin& plugin)
{
    bool ret = true;

    try
    {
        plugin = PluginDispatcher().getPluginByDevice(device);

        if (device == "CPU")
        {
            plugin.AddExtension(std::make_shared<Extensions::Cpu::CpuExtensions>());
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}
```


デバイスがCPUの場合はAddExtension()を実行しています。
[ここ](https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_your_kernels_into_IE.html)を参照するとCustom Layerを使用する場合に実行すればよさそうです。
ただ、このAddExtension()は私がよく分かっておらず、PINTOさんの[このエントリ](https://qiita.com/PINTO/items/628e45e32070777360ae)や、OpenVinoの公式サンプルで使っているため記載しています。
今回のMNISTを動かすだけであれば不要でした。
Custom Layerってなんですかね？

## 2.Read Model IR
ここではONNX形式から変換したIR形式の学習済みモデルを読み込みます。

```C++
bool ReadModel(const std::string &modelPath, CNNNetwork& network)
{
    bool ret = true;
    CNNNetReader network_reader;

    try
    {
        network_reader.ReadNetwork(modelPath);
        network_reader.ReadWeights(modelPath.substr(0, modelPath.size() - 4) + ".bin");
        network_reader.getNetwork().setBatchSize(1);
        network = network_reader.getNetwork();
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}
```

引数のmodelPathには.xmlファイルのパスを渡します。
そのため、学習済みモデルとして必要なものは.xmlファイルと.binファイルの2つです。
IR形式に変換する際に.mappingファイルが作成されていますが、実は不要です。

## 3. Configure Input & Output
ここでは入力層と出力の情報を設定します。

```c++
bool ConfigureInput(CNNNetwork& network, InputsDataMap& input_info, std::string& input_name, const Precision precision, const Layout layout)
{
    bool ret = true;

    try
    {
        input_info = InputsDataMap(network.getInputsInfo());

        for (auto&& input : input_info)
        {
            input_name = input.first;
            input.second->setPrecision(precision);
            input.second->setLayout(layout);
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool ConfigureOutput(CNNNetwork& network, OutputsDataMap& output_info, std::string& output_name, const Precision precision, const Layout layout)
{
    bool ret = true;

    try
    {
        output_info = OutputsDataMap(network.getOutputsInfo());

        for (auto&& output : output_info)
        {
            output_name = output.first;
            output.second->setPrecision(precision);
            output.second->setLayout(layout);
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}
```

~~入力層・出力層~~ 入力と出力のPresicionとLayout設定しています。
Layoutは.xmlファイルを見ればわかります。
例えば、今回の例ではmnist.xmlを開き、最初のlayer(type="Input"となっているlayer)を確認すると以下のようになっていることがわかります。

```xml
<layer id="0" name="Input_0" precision="FP32" type="Input">
    <output>
        <port id="0">
            <dim>1</dim>
            <dim>3</dim>
            <dim>28</dim>
            <dim>28</dim>
        </port>
    </output>
</layer>
```

dimタグを確認すると1,3,28,28となっています。
これはNCHWというLayoutになり、それぞれ以下を示します。

- N : Batch
- C : Channel
- H : Height
- W : Width

NはBatchと説明していますが、~~学習時にBatchSize=100などと設定していたはずなのに、なぜxmlだと1になっているのか？という疑問があるかもしれません。これはIR形式に変換する際にBatchSizeを指定するオプションがあり、指定しない場合はデフォルトの1が設定されるからです。~~ 学習時にBatchSize=100などと指定していた場合は、通常はNがそのまま100になっています。
そのため推論を実行する際に入力として画像100枚が必要となりますので、今回は100から1に変更することとします。
変更するためには--batchでバッチサイズだけ指定するか、--input_shapeで入力の形状を指定します。
ではなぜ今回は--batchも--input_shapeも付けていないかというと、mnist.pyの128行目で形状を指定しているからです。
ONNXの時点で既にバッチサイズが1となっているため、Model Optimizerではバッチサイズや入力の形状を指定していません。
Model Optimizerの詳細は[ここ](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_Converting_Model_General.html)を参照してください。

で、次にPrecisionなのですが、実はここはよくわかりません。
~~precision属性を確認するとFP32となっているのに、それを指定するとうまく動きません。コード上はU8を指定することで正常に動くようになるのですが、ここの謎が解けず。。。
すみません、どなたが教えていただけると助かります。~~
どうやら.xmlファイルのPrecisionは重みのPrecisionとのことです。
なので、ここがFP32だからと言って、InputのPrecisionがFP32であるというのは間違いということです。
単に画像を入力するだけなので、U8だ！！！
と思っていますが、すみません、本当かどうかは今のところわかりません。
とりあえずFP32だとうまく動作せず、U8でうまく動いたのでU8だとわかったというのが正直なところです。

次に出力層のLayoutを確認します。

```xml
<layer id="11" name="LinearFunction_1" precision="FP32" type="FullyConnected">
    <data alpha="0" beta="0" out-size="10"/>
        <input>
            <port id="0">
                <dim>1</dim>
                <dim>500</dim>
            </port>
        </input>
        <output>
            <port id="3">
                <dim>1</dim>
                <dim>10</dim>
            </port>
        </output>
        <blobs>
            <weights offset="1708288" size="20000"/>
            <biases offset="1728288" size="40"/>
        </blobs>
    </layer>
</layers>
```

dimタグを確認すると1,10となっています。これはNCというLayoutになります。

出力層のPrecisionは~~素直にxmlの記載通りFP32を指定します。~~ .xmlファイルのmeta_dataタグに着目します。

```xml
<meta_data>
    <MO_version value="2019.1.0-341-gc9b66a2"/>
    <cli_parameters>
        <data_type value="float"/>
        <disable_fusing value="False"/>
        <disable_gfusing value="False"/>
        <disable_nhwc_to_nchw value="False"/>
        <disable_omitting_optional value="False"/>
        <disable_resnet_optimization value="False"/>
        <enable_concat_optimization value="False"/>
        <enable_flattening_nested_params value="False"/>
        <extensions value="DIR"/>
        <framework value="onnx"/>
        <generate_deprecated_IR_V2 value="False"/>
        <input_model value="DIR\mnist.onnx"/>
        <input_model_is_text value="False"/>
        <k value="DIR\CustomLayersMapping.xml"/>
        <keep_shape_ops value="False"/>
        <legacy_mxnet_model value="False"/>
        <log_level value="ERROR"/>
        <mean_scale_values value="{}"/>
        <mean_values value="()"/>
        <move_to_preprocess value="False"/>
        <output_dir value="DIR"/>
        <remove_output_softmax value="False"/>
        <reverse_input_channels value="False"/>
        <save_params_from_nd value="False"/>
        <scale_values value="()"/>
        <silent value="False"/>
        <version value="False"/>
        <unset unset_cli_parameters="batch, counts, finegrain_fusing, freeze_placeholder_with_value, input, input_checkpoint, input_meta_graph, input_proto, input_shape, input_symbol, mean_file, mean_file_offsets, model_name, nd_prefix_name, output, placeholder_shapes, pretrained_model_name, saved_model_dir, saved_model_tags, scale, tensorboard_logdir, tensorflow_custom_layer_libraries, tensorflow_custom_operations_config_update, tensorflow_object_detection_api_pipeline_config, tensorflow_operation_patterns, tensorflow_subgraph_patterns, tensorflow_use_custom_operations_config"/>
    </cli_parameters>
</meta_data>
```

上記の中で、data_typeタグのvalue属性が出力のPresicionになります。
今回のmnist.xmlではvalue="float"となっていますが、これはFP32を指すようです。
根拠としては、Model Optimizerで--data_typeを付けないとデフォルトのFP32がセットされ、今回は--date_typeを付けていないため、float == FP32であると結論付けています。
そのため、今回はFP32を指定することになります。

## 4. Load Model
実行可能なネットワークを作成します。
特に何も考えずにこのまま実行しましょう。

```C++
bool LoadModel(CNNNetwork& network, InferencePlugin& plugin, ExecutableNetwork& executable_network)
{
    bool ret = true;

    try
    {
        executable_network = plugin.LoadNetwork(network, {});
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

```

## 5. Create Infer Request
ここでは推論を実行するオブジェクトを作成します。
ここも特に何も考えずにこのまま実行しましょう。

```C++
bool CreateInferRequest(ExecutableNetwork& executable_network, InferRequest::Ptr& async_infer_request)
{
    bool ret = true;

    try
    {
        async_infer_request = executable_network.CreateInferRequestPtr();
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}
```

## 6. Prepare Input
PrepareInput()で推論を実行するオブジェクトにデータを渡します。
入力画像を1,3,28,28の形式に合うようにmatU8ToBlob()内でリサイズして、推論を実行するオブジェクトに渡しています。
あれ、このmataU8ToBlob()のせいで、入力層のPrecisionがFP32なのにU8を指定しないといけないのかな？

```C++

template <typename T>
void matU8ToBlob(const cv::Mat& orig_image, InferenceEngine::Blob::Ptr& blob, int batchIndex = 0)
{
    InferenceEngine::SizeVector blobSize = blob->getTensorDesc().getDims();
    const size_t width = blobSize[3];
    const size_t height = blobSize[2];
    const size_t channels = blobSize[1];
    T* blob_data = blob->buffer().as<T*>();

    cv::Mat resized_image(orig_image);
    if (width != orig_image.size().width || height != orig_image.size().height) {
        cv::resize(orig_image, resized_image, cv::Size(width, height));
    }

    int batchOffset = batchIndex * width * height * channels;

    for (size_t c = 0; c < channels; c++) {
        for (size_t h = 0; h < height; h++) {
            for (size_t w = 0; w < width; w++) {
                blob_data[batchOffset + c * width * height + h * width + w] = resized_image.at<cv::Vec3b>(h, w)[c];
            }
        }
    }
}

bool PrepareInput(InferRequest::Ptr& async_infer_request, const std::string & input_name, const cv::Mat & image)
{
    bool ret = true;

    try
    {
        Blob::Ptr input = async_infer_request->GetBlob(input_name);
        matU8ToBlob<uint8_t>(image, input);
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}
```

## 7. Infer
推論を実行します。

```C++
bool Infer(InferRequest::Ptr& async_infer_request)
{
    bool ret = true;

    try
    {
        async_infer_request->StartAsync();
        async_infer_request->Wait(IInferRequest::WaitMode::RESULT_READY);
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}
```

## 8. Process Output
推論結果を取り出します。

```C++
int ProcessOutput(InferRequest::Ptr& async_infer_request, const std::string& output_name)
{

    int result = 0;
    float buf= 0;

    try
    {
        const float* oneHotVector = async_infer_request->GetBlob(output_name)->buffer().as<float*>();

        for (int i = 0; i < 10; i++)
        {
            if (oneHotVector[i] > buf)
            {
                buf = oneHotVector[i];
                result = i;
            }
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        result = -1;
    }

    return result;
}
```

出力層のdimタグを思い出してほしいのですが、以下でしたね。

```xml
<dim>1</dim>
<dim>10</dim>
```
またPrecisionはFP32でした。
従って、出力層の出力はfloatの1x10の配列ということになるため、一番値が大きいものを探してreturnしています。

## コード全体
ソースは[ここ](https://github.com/fantamo/OpenVino_MNIST/blob/master/main.cpp)に置きましたが、コード全体を載せておきます。

```C++
#include <iostream>
#include <inference_engine.hpp>
#include <opencv2/opencv.hpp>
#include <ie_iextension.h>
#include <ext_list.hpp>

using namespace InferenceEngine;

bool LoadPlugin(const std::string& device, InferencePlugin& plugin)
{
    bool ret = true;

    try
    {
        plugin = PluginDispatcher().getPluginByDevice(device);

        if (device == "CPU")
        {
            plugin.AddExtension(std::make_shared<Extensions::Cpu::CpuExtensions>());
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool ReadModel(const std::string &modelPath, CNNNetwork& network)
{
    bool ret = true;
    CNNNetReader network_reader;

    try
    {
        network_reader.ReadNetwork(modelPath);
        network_reader.ReadWeights(modelPath.substr(0, modelPath.size() - 4) + ".bin");
        network_reader.getNetwork().setBatchSize(1);
        network = network_reader.getNetwork();
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool ConfigureInput(CNNNetwork& network, InputsDataMap& input_info, std::string& input_name, const Precision precision, const Layout layout)
{
    bool ret = true;

    try
    {
        input_info = InputsDataMap(network.getInputsInfo());

        for (auto&& input : input_info)
        {
            input_name = input.first;
            input.second->setPrecision(precision);
            input.second->setLayout(layout);
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool ConfigureOutput(CNNNetwork& network, OutputsDataMap& output_info, std::string& output_name, const Precision precision, const Layout layout)
{
    bool ret = true;

    try
    {
        output_info = OutputsDataMap(network.getOutputsInfo());

        for (auto&& output : output_info)
        {
            output_name = output.first;
            output.second->setPrecision(precision);
            output.second->setLayout(layout);
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool LoadModel(CNNNetwork& network, InferencePlugin& plugin, ExecutableNetwork& executable_network)
{
    bool ret = true;

    try
    {
        executable_network = plugin.LoadNetwork(network, {});
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool CreateInferRequest(ExecutableNetwork& executable_network, InferRequest::Ptr& async_infer_request)
{
    bool ret = true;

    try
    {
        async_infer_request = executable_network.CreateInferRequestPtr();
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

template <typename T>
void matU8ToBlob(const cv::Mat& orig_image, InferenceEngine::Blob::Ptr& blob, int batchIndex = 0)
{
    InferenceEngine::SizeVector blobSize = blob->getTensorDesc().getDims();
    const size_t width = blobSize[3];
    const size_t height = blobSize[2];
    const size_t channels = blobSize[1];
    T* blob_data = blob->buffer().as<T*>();

    cv::Mat resized_image(orig_image);
    if (width != orig_image.size().width || height != orig_image.size().height) {
        cv::resize(orig_image, resized_image, cv::Size(width, height));
    }

    int batchOffset = batchIndex * width * height * channels;

    for (size_t c = 0; c < channels; c++) {
        for (size_t h = 0; h < height; h++) {
            for (size_t w = 0; w < width; w++) {
                blob_data[batchOffset + c * width * height + h * width + w] = resized_image.at<cv::Vec3b>(h, w)[c];
            }
        }
    }
}

bool PrepareInput(InferRequest::Ptr& async_infer_request, const std::string & input_name, const cv::Mat & image)
{
    bool ret = true;

    try
    {
        Blob::Ptr input = async_infer_request->GetBlob(input_name);
        matU8ToBlob<uint8_t>(image, input);
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

bool Infer(InferRequest::Ptr& async_infer_request)
{
    bool ret = true;

    try
    {
        async_infer_request->StartAsync();
        async_infer_request->Wait(IInferRequest::WaitMode::RESULT_READY);
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        ret = false;
    }

    return ret;
}

int ProcessOutput(InferRequest::Ptr& async_infer_request, const std::string& output_name)
{

    int result = 0;
    float buf= 0;

    try
    {
        const float* oneHotVector = async_infer_request->GetBlob(output_name)->buffer().as<float*>();

        for (int i = 0; i < 10; i++)
        {
            if (oneHotVector[i] > buf)
            {
                buf = oneHotVector[i];
                result = i;
            }
        }
    }
    catch (const std::exception & ex)
    {
        OutputDebugStringA(ex.what());
        result = -1;
    }

    return result;
}

int main(int argc, char** argv)
{
    InferencePlugin plugin;
    CNNNetwork network;
    InputsDataMap input_info;
    OutputsDataMap output_info;
    ExecutableNetwork executable_network;
    InferRequest::Ptr async_infer_request;
    std::string input_name;
    std::string output_name;
    std::string device(argv[1]);
    std::string modelPath(argv[2]);
    std::string imagePath(argv[3]);
    int result = 0;

    cv::Mat img = cv::imread(imagePath, 1);

    LoadPlugin(device, plugin);
    ReadModel(modelPath, network);
    ConfigureInput(network, input_info, input_name, Precision::U8, Layout::NCHW);
    ConfigureOutput(network, output_info, output_name, Precision::FP32, Layout::NC);
    LoadModel(network, plugin, executable_network);
    CreateInferRequest(executable_network, async_infer_request);
    PrepareInput(async_infer_request, input_name, img);
    Infer(async_infer_request);
    result = ProcessOutput(async_infer_request, output_name);

    printf("result = %d", result);
}

```

コードは上記で問題ないのですが、**x64**でビルドする必要があったり、includeファイルのパスやスタティックライブラリの指定などが必要です。
以下は自分の環境での設定ですので定義お使いの環境に合わせて変えてください。

- 追加するinclude path
    - C:\Program Files (x86)\IntelSWTools\openvino\deployment_tools\inference_engine\samples\common
    - C:\Program Files (x86)\IntelSWTools\openvino\deployment_tools\inference_engine\samples\common\format_reader
    - C:\Program Files (x86)\IntelSWTools\openvino\deployment_tools\inference_engine\external\tbb\include
    - C:\Program Files (x86)\IntelSWTools\openvino\deployment_tools\inference_engine\src\extension
    - C:\Program Files (x86)\IntelSWTools\openvino\deployment_tools\inference_engine\include
    - C:\Program Files (x86)\IntelSWTools\openvino\opencv\include;%(AdditionalIncludeDirectories)
- 追加するスタティックライブラリ
    - C:\Program Files (x86)\IntelSWTools\openvino\deployment_tools\inference_engine\lib\intel64\Debug\inference_engined.lib
    Releaseビルド時はファイル名が変わります。

    - C:\Program Files (x86)\IntelSWTools\openvino\opencv\lib\opencv_world410d.lib
    releaseビルド時はファイル名が変わります。

    -  \$(USERPROFILE)\Documents\Intel\OpenVINO\inference_engine_samples_build_2017\intel64\\$(Configuration)\cpu_extension.lib

- ビルド後のイベント
    - xcopy "C:\Program Files (x86)\IntelSWTools\openvino\inference_engine\bin\intel64\$(Configuration)" \$(SolutionDir)\$(Platform)\\$(Configuration)\ /D /S /R /Y /I /K
    - xcopy $(USERPROFILE)\Documents\Intel\OpenVINO\inference_engine_samples_build_2017\intel64\$(Configuration) \$(SolutionDir)\$(Platform)\\$(Configuration)\  /D /S /R /Y /I /K
    - xcopy "C:\Program Files (x86)\IntelSWTools\openvino\opencv\bin\opencv_world410d.dll" \$(SolutionDir)\$(Platform)\\$(Configuration)\ /D /S /R /Y /I /K
    releaseビルド時はファイル名が変わります。

恐らく不要なモジュールもいっぱいあると思いますが、どれが不要かよくわからなかったので、全部コピーしておきます。

入力データはペイント(mspaint.exe)で手軽に作れます。
画像サイズを正方形にさえしてあればmatU8ToBlob()でリサイズするため、必ずしも28x28で作る必要はありません。
今回は![one.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329392/5b9ff802-0886-77d2-2668-5e5357e25495.png)を使って予測してみます。

```
infer.exe CPU C:\Work\mnist.xml "C:\Work\one.png"
```

結果はどうでしょうか？
問題なく予測できているはずです。
使用したIRモデルと入力画像は[ここ](https://github.com/fantamo/OpenVino_MNIST)に置いておきました。

# 終わりに
OpenVinoを使って推論を実行してみました。
あまりC++のエントリはないので、みなさんの助けになれば幸いです。
誤記や認識間違いあればご指摘をお願いします。

# 参考リンク
- OpenVino公式
    - https://docs.openvinotoolkit.org/latest/_docs_MO_DG_Deep_Learning_Model_Optimizer_DevGuide.html
    - https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_Config_Model_Optimizer.html
    - https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_Convert_Model_From_ONNX.html
    - https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_with_customer_application_new_API.html
    - https://docs.openvinotoolkit.org/latest/_docs_IE_DG_Integrate_your_kernels_into_IE.html
    - https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_Converting_Model_General.html
- OpenVinoインストール
    - https://qiita.com/hanapage/items/a2f76d97bed0204d3819
- ONNX
    - https://qiita.com/sotanmochi/items/afa9c70343734c32bc56
- PINTOさん
    - https://qiita.com/PINTO
- ソース一式
    - https://github.com/fantamo/OpenVino_MNIST