非同期画像分類のサンプル#

このサンプルでは、非同期推論要求 API を使用して画像分類モデルを推論する方法を示します。サンプルを使用する前に、次の要件を参照してください:

  • 入力と出力が 1 つだけのモデルがサポートされます。

  • このサンプルは、core.read_model でサポートされるすべてのファイル形式を受け入れます。

  • サンプルをビルドするには、 “サンプルの導入” ガイドのサンプル・アプリケーションのビルドセクションにある手順を参照してください。

どのように動作するか#

起動時に、サンプル・アプリケーションはコマンドライン・パラメーターを読み取り、入力データを準備し、指定されたモデルとイメージを OpenVINO™ ランタイムプラグインに読み取ります。モデルのバッチサイズは、読み込んだ画像の数に応じて設定されます。バッチモードは、非同期モードでは独立した属性です。非同期モードは、どのようなバッチサイズでも効率的に機能します。

サンプルは推論要求オブジェクトを作成して完了コールバックを割り当てます。完了コールバック処理のスコープ内で、推論要求が再度実行されます。

その後、アプリケーションは最初の推論要求の推論を開始し、10 番目の推論要求の実行が完了するまで待機します。非同期モードでは、画像のスループットが向上する可能性があります。

推論が完了すると、アプリケーションはデータを標準出力ストリームに出力します。ラベルをモデルの .labels ファイルに配置すると、きれいな出力が得られます。

#!/usr/bin/env python3 
# -*- coding: utf-8 -*- 
# Copyright (C) 2018-2024 Intel Corporation 
# SPDX-License-Identifier: Apache-2.0 

import argparse 
import logging as log 
import sys 

import cv2 
import numpy as np 
import openvino as ov 

def parse_args() -> argparse.Namespace: """Parse and return command line arguments.""" 
    parser = argparse.ArgumentParser(add_help=False) 
    args = parser.add_argument_group('Options') 
    # fmt: off 
    args.add_argument('-h', '--help', action='help', 
                     help='Show this help message and exit.') 
    args.add_argument('-m', '--model', type=str, required=True, 
                     help='Required.Path to an .xml or .onnx file with a trained model.') 
    args.add_argument('-i', '--input', type=str, required=True, nargs='+', 
                    help='Required.Path to an image file(s).') 
    args.add_argument('-d', '--device', type=str, default='CPU', 
                   help='Optional.Specify the target device to infer on; CPU, GPU, GNA or HETERO: ' 'is acceptable.The sample will look for a suitable plugin for device specified.' 'Default value is CPU.')# fmt: on 
    return parser.parse_args() 

def completion_callback(infer_request: ov.InferRequest, image_path: str) -> 
None: 
    predictions = next(iter(infer_request.results.values())) 

    # 結果を含む numpy.ndarray の形状を変更して、1 次元の別の形状を取得します 
    probs = predictions.reshape(-1) 

    # 確率の降順で 10 個のクラス ID の配列を取得します 
    top_10 = np.argsort(probs)[-10:][::-1] 

    header = 'class_id probability’ 
 
   log.info(f'Image path: {image_path}') 
    log.info('Top 10 results: ') 
    log.info(header) 
    log.info('-' * len(header)) 

    for class_id in top_10: 
        probability_indent = ' ' * (len('class_id') - len(str(class_id)) + 1) 
        log.info(f'{class_id}{probability_indent}{probs[class_id]:.7f}') 

    log.info('') 

def main() -> int: 
    log.basicConfig(format='[ %(levelname)s ] %(message)s', level=log.INFO, stream=sys.stdout) 
    args = parse_args() 

# --------------------------- ステップ 1. OpenVINO ランタイムコアを初期化 ------------------------------------------------ 
    log.info('Creating OpenVINO Runtime Core') 
    core = ov.Core() 

# --------------------------- ステップ 2. モデルを読み込み -------------------------------------------------------------------- 
    log.info(f'Reading the model: {args.model}') 
    # (.xml と .bin ファイル) または (.onnx ファイル) 
    model = core.read_model(args.model)
    if len(model.inputs) != 1: 
        log.error('Sample supports only single input topologies') 
        return -1 

    if len(model.outputs) != 1: 
        log.error('Sample supports only single output topologies') 
        return -1 

# --------------------------- ステップ 3. 前処理を適用 ------------------------------------------------------------- 
    ppp = ov.preprocess.PrePostProcessor(model) 

    # 1) 入力テンソル情報を設定:
    # - input() は単一のモデル入力に関する情報を提供 
    # - テンソルの精度は 'u8’ 
    # - データのレイアウトは 'NHWC’ 
    ppp.input().tensor() \ 
        .set_element_type(ov.Type.u8) \ 
        .set_layout(ov.Layout('NHWC')) # noqa: N400 
    # 2) モデルの入力に 'NCHW' レイアウトがあると仮定 
    ppp.input().model().set_layout(ov.Layout('NCHW')) 
    # 3) 出力テンソル情報を設定:
    # - テンソルの精度は 'f32’ 
    ppp.output().tensor().set_element_type(ov.Type.f32)

    # 4) 元の 'model’ を変更する前処理を適用 
    model = ppp.build() 

    # --------------------------- ステップ 4. 入力をセットアップ -------------------------------------------------------------------- 
    # 入力画像を読み込み 
    images = [cv2.imread(image_path) for image_path in args.input] 

    # モデル入力次元に合わせて画像のサイズを変更 
    _, _, h, w = model.input().shape 
    resized_images = [cv2.resize(image, (w, h)) for image in images] 

    # N 次元を追加 
    input_tensors = [np.expand_dims(image, 0) for image in resized_images] 

# --------------------------- ステップ 5. モデルをデバイスにロード ----------------------------------------------------- 
    log.info('Loading the model to the plugin') 
    compiled_model = core.compile_model(model, args.device) 

# --------------------------- ステップ 6. 推論要求キューの作成 ------------------------------------------------------ 
    log.info('Starting inference in asynchronous mode') 
    # 最適な数の推論要求を使用して非同期キューを作成 
    infer_queue = ov.AsyncInferQueue(compiled_model) 
    infer_queue.set_callback(completion_callback) 

# --------------------------- ステップ 7. 推論を実行 -------------------------------------------------------------------- 
    for i, input_tensor in enumerate(input_tensors): 
        infer_queue.start_async({0: input_tensor}, args.input[i]) 

    infer_queue.wait_all() 
# ---------------------------------------------------------------------------------------------------------------------- 
    log.info('This sample is an API example, for any performance measurements please use the dedicated benchmark_app tool\n') 
    return 0 

if __name__ == '__main__': 
    sys.exit(main())
// Copyright (C) 2018-2024 Intel Corporation 
// SPDX-License-Identifier: Apache-2.0 
// 

/** 
* @brief The entry point the OpenVINO Runtime sample application 
* @file classification_sample_async/main.cpp 
* @example classification_sample_async/main.cpp 
*/ 

#include <sys/stat.h> 

#include <condition_variable> 
#include <fstream> 
#include <map> 
#include <memory> 
#include <mutex> 
#include <string> 
#include <vector> 

// clang-format off 
#include "openvino/openvino.hpp" 

#include "samples/args_helper.hpp" 
#include "samples/common.hpp" 
#include "samples/classification_results.h" 
#include "samples/slog.hpp" 
#include "format_reader_ptr.h" 

#include "classification_sample_async.h" 
// clang-format on 

using namespace ov::preprocess; 

namespace { 
bool parse_and_check_command_line(int argc, char* argv[]) { 
    gflags::ParseCommandLineNonHelpFlags(&argc, &argv, true); 
    if (FLAGS_h) { 
        show_usage(); 
        showAvailableDevices(); 
        return false; 
    } 
    slog::info << "Parsing input parameters" << slog::endl; 

    if (FLAGS_m.empty()) { 
        show_usage(); 
        throw std::logic_error("Model is required but not set.Please set -m option."); } 

    if (FLAGS_i.empty()) { 
        show_usage(); 
        throw std::logic_error("Input is required but not set.Please set -i option."); 
    } 

    return true;
} 
} // namespace

int main(int argc, char* argv[]) { 
    try { 
        // -------- OpenVINO ランタイムのバージョンを取得 -------- 
        slog::info << ov::get_openvino_version() << slog::endl; 

        // -------- 入力引数の解析と検証 -------- 
        if (!parse_and_check_command_line(argc, argv)) { 
            return EXIT_SUCCESS; 
        } 

        // -------- 入力の読み込み -------- 
        // このベクトルには、処理された画像へのパスが格納されます。 
        std::vector<std::string> image_names; 
        parseInputFilesArguments(image_names); 
        if (image_names.empty()) 
            throw std::logic_error("No suitable images were found"); 

        // -------- ステップ 1. OpenVINO ランタイムコアの初期化 -------- 
        ov::Core core; 

        // -------- ステップ 2. モデルの読み取り -------- 
        slog::info << "Loading model files: << slog::endl << FLAGS_m << slog::endl; 
        std::shared_ptr<ov::Model> model = core.read_model(FLAGS_m); 
    printInputAndOutputsInfo(*model); 

        OPENVINO_ASSERT(model->inputs().size() == 1, "Sample supports models with 1 input only"); 
        OPENVINO_ASSERT(model->outputs().size() == 1, "Sample supports models with 1 output only"); 

        // -------- ステップ 3. 前処理の構成 -------- 
        const ov::Layout tensor_layout{"NHWC"}; 

        ov::preprocess::PrePostProcessor ppp(model); 
        // 1) 引数のない input() はモデルに単一の入力があると想定します 
        ov::preprocess::InputInfo& input_info = ppp.input(); 
        // 2) 入力テンソル情報を設定: 
        // - テンソルの精度は 'u8’ と仮定します 
        // - データのレイアウトは 'NHWC’ です 
        input_info.tensor().set_element_type(ov::element::u8).set_layout(tensor_layout); 
        // 3) モデルに入力用の 'NCHW’ レイアウトがあると仮定 
        input_info.model().set_layout("NCHW"); 
        // 4) 引数なしの output() はモデルが単一の結果を持つと仮定 
        // - 引数なしの output() はモデルが単一の結果を持つと仮定 
        // - テンソルの精度は 'f32' と仮定 
        ppp.output().tensor().set_element_type(ov::element::f32); 

        // 5) build() メソッドが呼び出されると、レイアウトと精度の変換のため 
        // 前 (後) 処理ステップが自動的に挿入されます。 
        model = ppp.build(); 

        // -------- ステップ 4. 入力画像をリード -------- 
        slog::info << "Read input images" << slog::endl; 

        ov::Shape input_shape = model->input().get_shape(); 
        const size_t width = input_shape[ov::layout::width_idx(tensor_layout)]; 
        const size_t height = input_shape[ov::layout::height_idx(tensor_layout)]; 

        std::vector<std::shared_ptr<unsigned char>> images_data; 
        std::vector<std::string> valid_image_names; 
        for (const auto& i : image_names) { 
            FormatReader::ReaderPtr reader(i.c_str()); 
            if (reader.get() == nullptr) { 
                slog::warn << "Image " + i + " cannot be read!" << slog::endl; 
                continue; 
            } 
            // 画像データを収集 
            std::shared_ptr<unsigned char> data(reader->getData(width, height)); 
            if (data != nullptr) { 
                images_data.push_back(data); 
                valid_image_names.push_back(i); 
            } 
        } 
        if (images_data.empty() || valid_image_names.empty()) 
            throw std::logic_error("Valid input images were not found!"); 

        // -------- ステップ 5. 画像数を使用したバッチサイズの設定 -------- 
        const size_t batchSize = images_data.size(); 
        slog::info << "Set batch size " << std::to_string(batchSize) << slog::endl; 
        ov::set_batch(model, batchSize); 
        printInputAndOutputsInfo(*model); 

        // -------- ステップ 6. モデルをデバイスにロード -------- 
        slog::info << "Loading model to the device " << FLAGS_d << slog::endl; 
        ov::CompiledModel compiled_model = core.compile_model(model, FLAGS_d); 

        // -------- ステップ 7. 推論要求の作成 -------- 
        slog::info << "Create infer request" << slog::endl; 
        ov::InferRequest infer_request = compiled_model.create_infer_request(); 

        // -------- ステップ 8. 複数の入力画像を一括で結合 -------- 
        ov::Tensor input_tensor = infer_request.get_input_tensor(); 

        for (size_t image_id = 0; image_id < images_data.size(); ++image_id) { 
            const size_t image_size = shape_size(model->input().get_shape()) / batchSize; 
            std::memcpy(input_tensor.data<std::uint8_t>() + image_id * image_size, images_data[image_id].get(), image_size); 
        } 

        // -------- ステップ 9. 非同期推論を行う -------- 
        size_t num_iterations = 10; 
        size_t cur_iteration = 0; 
        std::condition_variable condVar; 
        std::mutex mutex; std::exception_ptr exception_var; 
        // -------- ステップ 10. 非同期推論を行います -------- 
        infer_request.set_callback([&](
            std::exception_ptr ex) { std::lock_guard<std::mutex> l(mutex); 
            if (ex) { 
                exception_var = ex; 
                condVar.notify_all(); 
                return; 
            } 

            cur_iteration++; 
            slog::info << "Completed " << cur_iteration << " async request execution" << slog::endl; 
            if (cur_iteration < num_iterations) { 
                // ここでユーザーは推論結果を含む出力を読み取り、新しい入力で 
                //非同期リクエストを再度繰り返すことができます。 
                infer_request.start_async(); 
            } else { 
                // 最後の非同期推論要求の実行後にサンプル実行を 
                // 続行する 
                condVar.notify_one(); 
            } 
        }); 

        // 最初の非同期リクエストを開始 
        slog::info << "Start inference (asynchronous executions)" << slog::endl; 
        infer_request.start_async(); 

        // 非同期リクエストのすべての反復を待機 
        std::unique_lock<std::mutex> lock(mutex); 
        condVar.wait(lock, [&] { 
            if (exception_var) { 
                std::rethrow_exception(exception_var); 
            } 

            return cur_iteration == num_iterations; 
         }); 

        slog::info << "Completed async requests execution" << slog::endl; 

        // -------- ステップ 11. 出力を処理 -------- 
        ov::Tensor output = infer_request.get_output_tensor(); 

        // ファイルからラベルを読み取る (例 AlexNet.labels) 
        std::string labelFileName = fileNameNoExt(FLAGS_m) + ".labels"; 
        std::vector<std::string> labels; 

        std::ifstream inputFile; 
        inputFile.open(labelFileName, std::ios::in); 
        if (inputFile.is_open()) { 
            std::string strLine; 
            while (std::getline(inputFile, strLine)) { 
                trim(strLine); 
                labels.push_back(strLine); 
            } 
        } 

        // フォーマットされた分類結果をプリント 
        constexpr size_t N_TOP_RESULTS = 10; 
        ClassificationResult classificationResult(output, valid_image_names, batchSize, N_TOP_RESULTS, labels); 
        classificationResult.show(); 
    } catch (const std::exception& ex) { 
        slog::err << ex.what() << slog::endl; 
        return EXIT_FAILURE; 
    } catch (...) { 
            slog::err << "Unknown/internal exception happened." << slog::endl; 
            return EXIT_FAILURE; 
        } 

        return EXIT_SUCCESS; 
}

各サンプルの明示的な説明は、“OpenVINO™ ランタイムとアプリケーションの統合” ガイドの統合ステップセクションで確認できます。

実行する#

-h オプションを指定してアプリケーションを実行すると、使用方法が表示されます。

python classification_sample_async.py -h

使用方法:


usage: classification_sample_async.py [-h] -m MODEL -i INPUT [INPUT ...]
                                      [-d DEVICE] 

Options: 
  -h, --help             Show this help message and exit.
  -m MODEL, --model MODEL 
                         Required.Path to an .xml or .onnx file with a trained 
                         model.
  -i INPUT [INPUT ...], --input INPUT [INPUT ...] 
                         Required. Path to an image file(s).
  -d DEVICE, --device DEVICE 
                         Optional.Specify the target device to infer on; CPU, 
                         GPU or HETERO: is acceptable.The sample 
                         will look for a suitable plugin for device specified.
                         Default value is CPU.
classification_sample_async -h

Usage instructions:


[ INFO ] OpenVINO Runtime version .........<version> 
[ INFO ] Build ...........<build> 

classification_sample_async [OPTION] 
Options: 

    -h                   Print usage instructions.
    -m "<path>"          Required.Path to an .xml file with a trained model.
    -i "<path>"          Required.Path to a folder with images or path to image files: a .ubyte file for LeNet and a .bmp file for other models.
    -d "<device>"        Optional. Specify the target device to infer on (the list of available devices is shown below).Default value is CPU.Use "-d HETERO:<comma_separated_devices_list>" format to specify the HETERO plugin.Sample will look for a suitable plugin for the device specified.

Available target devices: <devices>

サンプルを実行するには、モデルとイメージを指定する必要があります:

  • TensorFlow Zoo、HuggingFace、TensorFlow Hub などのモデル・リポジトリーから推論タスクに固有のモデルを取得できます。

  • ストレージで利用可能なメディア・ファイル・コレクションの画像を使用できます。

  • OpenVINO™ ツールキットのサンプルとデモは、デフォルトでは BGR チャンネル順序での入力を想定しています。RGB 順序で動作するようにモデルをトレーニングした場合は、サンプルまたはデモ・アプリケーションでデフォルトのチャンネル順序を手動で再配置するか、reverse_input_channels 引数を指定したモデル・トランスフォーメーション API を使用してモデルを再変換する必要があります。引数の詳細については、前処理計算の埋め込み入力チャンネルを反転するときセクションを参照してください。

  • トレーニングされたモデルでサンプルを実行する前に、モデル・トランスフォーメーション API を使用してモデルが中間表現 (IR) 形式 (*.xml + *.bin) に変換されていることを確認してください。

  • このサンプルは、前処理を必要としない ONNX 形式 (.onnx) のモデルを受け入れます。

  • サンプルは NCHW モデルレイアウトのみをサポートします。

  • 単一のオプションを複数回指定すると、最後の値のみが適用されます。例えば、-m フラグは次のようになります:

    python classification_sample_async.py -m model.xml -m model2.xml
    ./classification_sample_async -m model.xml -m model2.xml

#

  1. 事前トレーニングされたモデルをダウンロードします:

  2. 以下を使用して変換できます:

    import openvino as ov 
    
    ov_model = ov.convert_model('./models/alexnet') 
    # または、モデルが Python モデル・オブジェクトの場合 
    ov_model = ov.convert_model(alexnet)
    ovc ./models/alexnet
  1. GPU 上のモデルを使用して、画像ファイルの推論を実行します。例:

    python classification_sample_async.py -m ./models/alexnet.xml -i ./test_data/images/banana.jpg ./test_data/images/car.bmp -d GPU
    classification_sample_async -m ./models/googlenet-v1.xml -i ./images/dog.bmp -d GPU

サンプルの出力#

サンプル・アプリケーションは、各ステップを標準出力ストリームに記録し、上位 10 の推論結果を出力します。

[ INFO ] Creating OpenVINO Runtime Core 
[ INFO ] Reading the model: C:/test_data/models/alexnet.xml 
[ INFO ] Loading the model to the plugin 
[ INFO ] Starting inference in asynchronous mode 
[ INFO ] Image path: /test_data/images/banana.jpg 
[ INFO ] Top 10 results: [ INFO ] class_id probability 
[ INFO ] -------------------- 
[ INFO ] 954 0.9707602 
[ INFO ] 666 0.0216788 
[ INFO ] 659 0.0032558 
[ INFO ] 435 0.0008082 
[ INFO ] 809 0.0004359 
[ INFO ] 502 0.0003860 
[ INFO ] 618 0.0002867 
[ INFO ] 910 0.0002866 
[ INFO ] 951 0.0002410 
[ INFO ] 961 0.0002193 
[ INFO ] 
[ INFO ] Image path: /test_data/images/car.bmp 
[ INFO ] Top 10 results: [ INFO ] class_id probability 
[ INFO ] -------------------- 
[ INFO ] 656 0.5120340 
[ INFO ] 874 0.1142275 
[ INFO ] 654 0.0697167 
[ INFO ] 436 0.0615163 
[ INFO ] 581 0.0552262 
[ INFO ] 705 0.0304179 
[ INFO ] 675 0.0151660 
[ INFO ] 734 0.0151582 
[ INFO ] 627 0.0148493 
[ INFO ] 757 0.0120964 
[ INFO ] 
[ INFO ] This sample is an API example, for any performance measurements please use the dedicated benchmark_app tool

サンプル・アプリケーションは、各ステップを標準出力ストリームに記録し、上位 10 の推論結果を出力します。

[ INFO ] OpenVINO Runtime version .........<version> 
[ INFO ] Build ...........<build> 
[ INFO ] 
[ INFO ] Parsing input parameters 
[ INFO ] Files were added: 1 
[ INFO ]     /images/dog.bmp 
[ INFO ] Loading model files: 
[ INFO ] /models/googlenet-v1.xml 
[ INFO ] model name: GoogleNet 
[ INFO ]     inputs 
[ INFO ]         input name: data 
[ INFO ]         input type: f32 
[ INFO ]         input shape: {1, 3, 224, 224} 
[ INFO ]     outputs 
[ INFO ]         output name: prob 
[ INFO ]         output type: f32 
[ INFO ]         output shape: {1, 1000} 
[ INFO ] Read input images 
[ INFO ] Set batch size 1 
[ INFO ] model name: GoogleNet 
[ INFO ]     inputs 
[ INFO ]         input name: data 
[ INFO ]         input type: u8 
[ INFO ]         input shape: {1, 224, 224, 3} 
[ INFO ]     outputs 
[ INFO ]         output name: prob 
[ INFO ]         output type: f32 
[ INFO ]         output shape: {1, 1000} 
[ INFO ] Loading model to the device GPU 
[ INFO ] Create infer request 
[ INFO ] Start inference (asynchronous executions) 
[ INFO ] Completed 1 async request execution 
[ INFO ] Completed 2 async request execution 
[ INFO ] Completed 3 async request execution 
[ INFO ] Completed 4 async request execution 
[ INFO ] Completed 5 async request execution 
[ INFO ] Completed 6 async request execution 
[ INFO ] Completed 7 async request execution 
[ INFO ] Completed 8 async request execution 
[ INFO ] Completed 9 async request execution 
[ INFO ] Completed 10 async request execution 
[ INFO ] Completed async requests execution
 
Top 10 results: 

Image /images/dog.bmp

classid probability 
------- ----------- 
156 0.8935547 
218 0.0608215 
215 0.0217133 
219 0.0105667 
212 0.0018835 
217 0.0018730 
152 0.0018730 
157 0.0015745 
154 0.0012817 
220 0.0010099

関連情報#