前処理 API - 詳細#
このセクションの目的は、前処理 API の機能や後処理などの詳細を説明することです。
前処理機能#
以下は、前処理 API 機能の完全なリストです:
特定の入出力への対処#
モデルに入力が 1 つしかない場合、単純な ov::preprocess::PrePostProcessor::input()
は、この入力 (テンソル、ステップ、モデル) の前処理ビルダーへの参照を取得します:
# モデルに入力が 1 つしかない場合は、インデックス/名前は必要ありません
ppp.input().preprocess().scale(50.)
# 出力も同様です
ppp.output() \
.postprocess().convert_element_type(Type.u8)
ppp.input() // モデルに入力が 1 つしかない場合は、インデックス/名前は必要ありません
.preprocess().scale(50.f);
ppp.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) # モデルの 2 番目の入力を取得
ppp.output(2) # モデル内のインデックス=2 (3 番目) の出力を取得
auto &input_1 = ppp.input(1); // モデルの 2 番目の入力を取得
auto &output_1 = ppp.output(2); // モデル内のインデックス=2 (3 番目) の出力を取得
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’ 次元のレイアウトを定義する必要があります。例:
# モデルの形状が {1, 3, 224, 224} であると想定
# N=1, C=3, H=224, W=224
ppp.input(input_name).model().set_layout(Layout('NCHW'))
# 平均/スケールには 3 つの値があり、C=3 と一致
ppp.input(input_name).preprocess() \
.mean([103.94, 116.78, 123.68]).scale([57.21, 57.45, 57.73])
// モデルの形状が {1, 3, 224, 224} であると想定
ppp.input("input").model().set_layout("NCHW"); // N=1, C=3, H=224, W=224
// 平均/スケールには 3 つの値があり、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 ビット整数値 (各色) の配列で表されますが、モデルは浮動小数点テンソルを受け入れます。
前処理ステップとして精度変換を実行グラフに統合します:
# 最初にテンソルのデータタイプを定義
ppp.input(input_name).tensor().set_element_type(Type.u8)
# 次に前処理ステップを定義
ppp.input(input_name).preprocess().convert_element_type(Type.f32)
# `model` の要素タイプへの変換が必要な場合は、'f32' を省略できます
ppp.input(input_name).preprocess().convert_element_type()
// 最初にテンソルのデータタイプを定義
ppp.input("input").tensor().set_element_type(ov::element::u8);
// 次に前処理ステップを定義
ppp.input("input").preprocess().convert_element_type(ov::element::f32);
// `model` の要素タイプへの変換が必要な場合は、'f32' を省略できます
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}
の入力を要求することがあります。
変換は、ユーザーのテンソルのレイアウトと元のモデルのレイアウトを使用して暗黙的に行われます。
# 最初にテンソルのレイアウトを定義
ppp.input(input_name).tensor().set_layout(Layout('NHWC'))
# 次にモデルのレイアウトを定義
ppp.input(input_name).model().set_layout(Layout('NCHW'))
print(ppp) # '暗黙的なレイアウト変換ステップ’ をプリント
// 最初にテンソルのレイアウトを定義
ppp.input("input").tensor().set_layout("NHWC");
// 次にモデルのレイアウトを定義
ppp.input("input").model().set_layout("NCHW");
std::cout << ppp; // '暗黙的なレイアウト変換ステップ’ をプリント
コードでレイアウトを使用せずに手動で軸を転置することもできます:
ppp.input(input_name).tensor().set_shape([1, 480, 640, 3])
# モデルは形状 {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});
// モデルは形状 {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);
元のモデルに既知の空間次元 (width``+``height
) がある場合、ターゲットの width
/height
は省略できます。
ppp.input(input_name).tensor().set_shape([1, 3, 960, 1280])
# モデルは {1, 3, 480, 640} 形状を受け入れるため、最後の次元は 'H' と 'W’ になります
ppp.input(input_name).model().set_layout(Layout('??HW'))
# モデルの次元に合わせてサイズを変更
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"); // モデルは {1, 3, 480, 640} 形状を受け入れ
// モデルの次元に合わせてサイズを変更
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 を使用する方法があります。このような場合は、元の input
を 2 つまたは 3 つの入力に分割する NV12_TWO_PLANES
および I420_THREE_PLANES
ソースカラー形式があります。
# 元の `input` が 2 つの別々の入力に分割されます: `input/y' と '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) # 何が起こるかを確認するために前処理手順をダンプ
// 元の `input` が 2 つの別々の入力に分割されます: `input/y' と '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; // 何が起こるかを確認するために前処理手順をダンプ
この例では、元の input
が input/y
入力と input/uv
入力に分割されます。あるソースから input/y
を入力し、別のソースから input/uv
を入力できます。これらのソースを使用して、RGB
への色変換が実行されます。NV12 バッファーの追加コピーないため、より効率的です。
C++ リファレンス:
ov::preprocess::ColorFormat
ov::preprocess::PreProcessSteps::convert_color
カスタム操作#
前処理 API を使用すると、カスタム前処理ステップを実行グラフに追加することもできます。カスタム関数は現在の入力ノードを受け入れ、定義された前処理操作を適用して、新しい input
を返します。
注
カスタム前処理関数は、入力後にのみノードを挿入する必要があります。これはモデルのコンパイル中に行われます。この関数は実行フェーズ中に呼び出されることはありません。これは複雑で、OpenVINO™ 操作に関する知識が必要です。
特定のトリミングやサイズ変更など、入力直後に実行グラフに追加の操作を挿入する場合、前処理 API が適しています。
# いくつかのカスタム操作を挿入することが可能
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):
# カスタムノードは前処理ステップとして挿入できます
return ops.abs(output)
ppp.input("input").preprocess() \
.custom(custom_abs)
ppp.input("input_image").preprocess()
.custom([](const ov::Output<ov::Node>& node) {
// カスタムノードは前処理ステップとして挿入できます
return std::make_shared<ov::opset8::Abs>(node);
});
C++ リファレンス:
ov::preprocess::PreProcessSteps::custom()
後処理#
後処理ステップをモデル出力に追加できます。前処理に関して、これらのステップもグラフに統合され、選択したデバイス上で実行されます。
前処理では次のフローが使用されます: ユーザーテンソル -> ステップ -> モデル入力。
後処理では逆の処理を使用します: モデル出力 -> ステップ -> ユーザーテンソル。
前処理と比べて、後処理ステージの操作はそれほど多くありません。現在、次の後処理操作のみがサポートされています:
レイアウトを変換します。
要素タイプを変換します。
操作をカスタマイズします。
これらの操作の使用法は前処理に似ています。次の例を参照してください:
# モデルの出力は 'NCHW' レイアウト
ppp.output('result').model().set_layout(Layout('NCHW'))
# 対象ユーザーのテンソルを U8 タイプ + 'NHWC' レイアウトに設定
# 精度とレイアウトの変換は暗黙的に行われます
ppp.output('result').tensor()\
.set_layout(Layout("NHWC"))\
.set_element_type(Type.u8)
# また、カスタム操作を挿入することも可能です
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):
# カスタムノードは後処理ステップとして挿入できます
return ops.abs(output)
ppp.output("result").postprocess()\
.custom(custom_abs)
// モデルの出力は 'NCHW' レイアウト
ppp.output("result_image").model().set_layout("NCHW");
// 対象ユーザーのテンソルを U8 タイプ + 'NHWC' レイアウトに設定
// 精度とレイアウトの変換は暗黙的に行われます
ppp.output("result_image").tensor()
.set_layout("NHWC")
.set_element_type(ov::element::u8);
// また、カスタム操作を挿入することも可能です
ppp.output("result_image").postprocess()
.custom([](const ov::Output<ov::Node>& node) {
// カスタムノードは後処理ステップとして挿入できます
return std::make_shared<ov::opset8::Abs>(node);
});
C++ リファレンス:
ov::preprocess::PostProcessSteps
ov::preprocess::OutputModelInfo
ov::preprocess::OutputTensorInfo