前処理 API - 詳細

このセクションの目的は、前処理 API の機能や後処理などの詳細を説明することです。

前処理機能

以下は、前処理 API 機能の完全なリストです。

特定の入出力への対処

モデルに入力が 1 つしかない場合、単純な ov::preprocess::PrePostProcessor::input() は、この入力 (テンソル、ステップ、モデル) の前処理ビルダーへの参照を取得します。

# no index/name is needed if model has one input
ppp.input().preprocess().scale(50.)

# same for output
ppp.output() \
    .postprocess().convert_element_type(Type.u8)
ppp.input() // no index/name is needed if model has one input
  .preprocess().scale(50.f);

ppp.output()   // same for output
  .postprocess().convert_element_type(ov::element::u8);

一般に、モデルに複数の入力/出力がある場合、それぞれをテンソル名でアドレス指定できます。

ppp.input(input_name)
ppp.output('result')
 auto &input_image = ppp.input("image");
 auto &output_result = ppp.output("result");

または、インデックスによって指定することもできます。

ppp.input(1) # Gets 2nd input in a model
ppp.output(2) # Gets output with index=2 (3rd one) in a model
 auto &input_1 = ppp.input(1); // Gets 2nd input in a model
 auto &output_1 = ppp.output(2); // Get output with index=2 (3rd one) in a model

C++ リファレンス:

  • ov::preprocess::InputTensorInfo

  • ov::preprocess::OutputTensorInfo

  • ov::preprocess::PrePostProcessor

サポートされる前処理操作

C++ リファレンス:

  • ov::preprocess::PreProcessSteps

平均/スケールの正規化

一般的なデータ正規化には、データ項目ごとに 2 つの操作 (平均値の減算と標準偏差での除算) が含まれます。これは次のコードで実行できます。

ppp.input(input_name).preprocess().mean(128).scale(127)
ppp.input("input").preprocess().mean(128).scale(127);

コンピューター・ビジョンでは、通常、領域の正規化は R、G、B 値に対して個別に行われます。これには、‘C’ 次元のレイアウトを定義する必要があります。
例:

# Suppose model's shape is {1, 3, 224, 224}
# N=1, C=3, H=224, W=224
ppp.input(input_name).model().set_layout(Layout('NCHW'))
# Mean/Scale has 3 values which matches with C=3
ppp.input(input_name).preprocess() \
                                                    .mean([103.94, 116.78, 123.68]).scale([57.21, 57.45, 57.73])
// Suppose model's shape is {1, 3, 224, 224}
ppp.input("input").model().set_layout("NCHW"); // N=1, C=3, H=224, W=224
// Mean/Scale has 3 values which matches with C=3
ppp.input("input").preprocess()
  .mean({103.94f, 116.78f, 123.68f}).scale({57.21f, 57.45f, 57.73f});

C++ リファレンス:

  • ov::preprocess::PreProcessSteps::mean()

  • ov::preprocess::PreProcessSteps::scale()

変換精度

コンピューター・ビジョンでは、イメージは符号なし 8 ビット整数値 (各色) の配列で表されますが、モデルは浮動小数点テンソルを受け入れます。

前処理ステップとして精度変換を実行グラフに統合します。

# First define data type for your tensor
ppp.input(input_name).tensor().set_element_type(Type.u8)

# Then define preprocessing step
ppp.input(input_name).preprocess().convert_element_type(Type.f32)

# If conversion is needed to `model's` element type, 'f32' can be omitted
ppp.input(input_name).preprocess().convert_element_type()
// First define data type for your tensor
ppp.input("input").tensor().set_element_type(ov::element::u8);

// Then define preprocessing step
ppp.input("input").preprocess().convert_element_type(ov::element::f32);

// If conversion is needed to `model's` element type, 'f32' can be omitted
ppp.input("input").preprocess().convert_element_type();

C++ リファレンス:

  • `ov::preprocess::InputTensorInfo::set_element_type()

  • `ov::preprocess::PreProcessSteps::convert_element_type()

レイアウトを変換 (転置)

行列/テンソルの転置はディープラーニングでは一般的な操作です。{480, 640, 3} 要素の配列である 640x480 の BMP 画像がある場合、ディープラーニング・モデルは形状 {1, 3, 480, 640} の入力を要求することがあります。

変換は、ユーザーのテンソルのレイアウトと元のモデルのレイアウトを使用して暗黙的に行われます。

# First define layout for your tensor
ppp.input(input_name).tensor().set_layout(Layout('NHWC'))

# Then define layout of model
ppp.input(input_name).model().set_layout(Layout('NCHW'))

print(ppp)  # Will print 'implicit layout conversion step'
// First define layout for your tensor
ppp.input("input").tensor().set_layout("NHWC");

// Then define layout of model
ppp.input("input").model().set_layout("NCHW");

std::cout << ppp; // Will print 'implicit layout conversion step'

コードでレイアウトを使用せずに手動で軸を転置することもできます。

ppp.input(input_name).tensor().set_shape([1, 480, 640, 3])

# Model expects shape {1, 3, 480, 640}
ppp.input(input_name).preprocess()\
    .convert_layout([0, 3, 1, 2])
# 0 -> 0; 3 -> 1; 1 -> 2; 2 -> 3
ppp.input("input").tensor().set_shape({1, 480, 640, 3});
// Model expects shape {1, 3, 480, 640}
ppp.input("input").preprocess().convert_layout({0, 3, 1, 2});
// 0 -> 0; 3 -> 1; 1 -> 2; 2 -> 3

これは同じ転置を実行します。ただし、ソースとデスティネーションのレイアウトを採用するアプローチのほうが読みやすく、理解しやすい場合があります。

C++ リファレンス:

  • ov::preprocess::PreProcessSteps::convert_layout()

  • ov::preprocess::InputTensorInfo::set_layout()

  • ov::preprocess::InputModelInfo::set_layout()

  • ov::Layout

画像のサイズ変更

画像サイズ変更は、コンピューター・ビジョン・タスクの一般的な前処理ステップです。前処理 API を使用すると、このステップを実行グラフに統合してターゲットデバイス上で実行できます。

入力画像のサイズを変更するには、レイアウトH および W 次元を定義する必要があります。

ppp.input(input_name).tensor().set_shape([1, 3, 960, 1280])
ppp.input(input_name).model().set_layout(Layout('??HW'))
ppp.input(input_name).preprocess()\
    .resize(ResizeAlgorithm.RESIZE_LINEAR, 480, 640)
ppp.input("input").tensor().set_shape({1, 3, 960, 1280});
ppp.input("input").model().set_layout("??HW");
ppp.input("input").preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR, 480, 640);

元のモデルに既知の空間次元 (幅 + 高さ) がある場合、ターゲットの/高さは省略できます。

ppp.input(input_name).tensor().set_shape([1, 3, 960, 1280])
# Model accepts {1, 3, 480, 640} shape, thus last dimensions are 'H' and 'W'
ppp.input(input_name).model().set_layout(Layout('??HW'))
# Resize to model's dimension
ppp.input(input_name).preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR)
ppp.input("input").tensor().set_shape({1, 3, 960, 1280});
ppp.input("input").model().set_layout("??HW"); // Model accepts {1, 3, 480, 640} shape
// Resize to model's dimension
ppp.input("input").preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR);

C++ リファレンス:

  • ov::preprocess::PreProcessSteps::resize()
  • ov::preprocess::ResizeAlgorithm

カラー変換

代表的な使用例は、カラーチャネルを RGB から BGR に、またはその逆に変換することです。これを行うには、tensor セクションでソースカラー形式を指定し、convert_color 前処理操作を実行します。次の例では、モデル入力の必要に応じて、BGR イメージを RGB に変換する必要があります。

ppp.input(input_name).tensor().set_color_format(ColorFormat.BGR)

ppp.input(input_name).preprocess().convert_color(ColorFormat.RGB)
ppp.input("input").tensor().set_color_format(ov::preprocess::ColorFormat::BGR);
ppp.input("input").preprocess().convert_color(ov::preprocess::ColorFormat::RGB);

カラー変換 - NV12/I420

前処理では、YUV 形式のソースカラー (NV12 および I420) もサポートされます。高度なケースでは、このような YUV 画像を別々のプレーンに分割することができます。例えば、NV12 画像の Y コンポーネントが 1 つのソースから取得され、UV コンポーネントが別のソースから取得される場合です。ユーザーのアプリケーションでコンポーネントを手動で連結するのは、パフォーマンスとデバイス使用率の観点から完璧なソリューションであるとは言えません。ただし、前処理 API を使用する方法があります。このような場合は、元の入力を 2 つまたは 3 つの入力に分割する NV12_TWO_PLANES および I420_THREE_PLANES ソースカラー形式があります。

# This will split original `input` to 2 separate inputs: `input/y' and 'input/uv'
ppp.input(input_name).tensor()\
    .set_color_format(ColorFormat.NV12_TWO_PLANES)

ppp.input(input_name).preprocess()\
    .convert_color(ColorFormat.RGB)
print(ppp)  # Dump preprocessing steps to see what will happen
// This will split original `input` to 2 separate inputs: `input/y' and 'input/uv'
ppp.input("input").tensor().set_color_format(ov::preprocess::ColorFormat::NV12_TWO_PLANES);
ppp.input("input").preprocess().convert_color(ov::preprocess::ColorFormat::RGB);
std::cout << ppp;  // Dump preprocessing steps to see what will happen

この例では、元の入力input/y 入力と input/uv 入力に分割されます。あるソースから input/y を入力し、別のソースから input/uv を入力できます。これらのソースを使用して、RGB への色変換が実行されます。NV12 バッファーの追加コピーないため、より効率的です。

C++ リファレンス:

  • ov::preprocess::ColorFormat

  • ov::preprocess::PreProcessSteps::convert_color

カスタム操作

前処理 API を使用すると、カスタム前処理ステップを実行グラフに追加することもできます。カスタム関数は現在の入力ノードを受け入れ、定義された前処理操作を適用して、新しいノードを返します。

カスタム前処理関数は、入力後にのみノードを挿入する必要があります。これはモデルのコンパイル中に行われます。この関数は実行フェーズ中に呼び出されることはありません。これは複雑で、OpenVINO™ 操作に関する知識が必要です。

特定のトリミングやサイズ変更など、入力直後に実行グラフに追加の操作を挿入する場合、前処理 API が適しています。

# It is possible to insert some custom operations
import openvino.runtime.opset12 as ops
from openvino.runtime import Output
from openvino.runtime.utils.decorators import custom_preprocess_function

@custom_preprocess_function
def custom_abs(output: Output):
    # Custom nodes can be inserted as Preprocessing steps
    return ops.abs(output)

ppp.input("input").preprocess() \
    .custom(custom_abs)
 ppp.input("input_image").preprocess()
    .custom([](const ov::Output<ov::Node>& node) {
        // Custom nodes can be inserted as Pre-processing steps
        return std::make_shared<ov::opset8::Abs>(node);
    });

C++ リファレンス:

後処理

後処理ステップをモデル出力に追加できます。前処理に関して、これらのステップもグラフに統合され、選択したデバイス上で実行されます。

前処理では次のフローが使用されます: ユーザーテンソル -> ステップ -> モデル入力

後処理ではその逆を行います: モデル出力 -> ステップ -> ユーザーテンソル

前処理と比べて、後処理ステージの操作はそれほど多くありません。現在、次の後処理操作のみがサポートされています。

  • レイアウトを変換します。

  • 要素タイプを変換します。

  • 操作をカスタマイズします。

これらの操作の使用法は前処理に似ています。次の例を参照してください。

# Model's output has 'NCHW' layout
ppp.output('result').model().set_layout(Layout('NCHW'))

# Set target user's tensor to U8 type + 'NHWC' layout
# Precision & layout conversions will be done implicitly
ppp.output('result').tensor()\
    .set_layout(Layout("NHWC"))\
    .set_element_type(Type.u8)

# Also it is possible to insert some custom operations
import openvino.runtime.opset12 as ops
from openvino.runtime import Output
from openvino.runtime.utils.decorators import custom_preprocess_function

@custom_preprocess_function
def custom_abs(output: Output):
    # Custom nodes can be inserted as Post-processing steps
    return ops.abs(output)

ppp.output("result").postprocess()\
    .custom(custom_abs)
 // Model's output has 'NCHW' layout
 ppp.output("result_image").model().set_layout("NCHW");

 // Set target user's tensor to U8 type + 'NHWC' layout
 // Precision & layout conversions will be done implicitly
 ppp.output("result_image").tensor()
    .set_layout("NHWC")
    .set_element_type(ov::element::u8);

 // Also it is possible to insert some custom operations
 ppp.output("result_image").postprocess()
    .custom([](const ov::Output<ov::Node>& node) {
        // Custom nodes can be inserted as Post-processing steps
        return std::make_shared<ov::opset8::Abs>(node);
    });

C++ リファレンス:

  • ov::preprocess::PostProcessSteps

  • ov::preprocess::OutputModelInfo

  • ov::preprocess::OutputTensorInfo