[OV2.0] Preprocessing - add 'convert_layout' (#7772)
* Preprocessing - add 'convert_layout' * Fix comments, removed 'set_data_shape' for time being
This commit is contained in:
parent
dc1a1d70e8
commit
5b39e407d9
@ -419,6 +419,91 @@ static RefPreprocessParams resize_lvalues() {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RefPreprocessParams convert_layout_nhwc_to_nchw_lvalue() {
|
||||||
|
RefPreprocessParams res("convert_layout_nhwc_to_nchw_lvalue");
|
||||||
|
res.function = []() {
|
||||||
|
auto f = create_simple_function(element::u8, {1, 3, 2, 2});
|
||||||
|
f->get_parameters()[0]->set_layout("NCHW");
|
||||||
|
auto p = PreProcessSteps();
|
||||||
|
p.convert_layout("NCHW");
|
||||||
|
|
||||||
|
f = PrePostProcessor()
|
||||||
|
.input(InputInfo()
|
||||||
|
.tensor(InputTensorInfo().set_layout("NHWC"))
|
||||||
|
.preprocess(std::move(p)))
|
||||||
|
.build(f);
|
||||||
|
return f;
|
||||||
|
};
|
||||||
|
res.inputs.emplace_back(Shape{1, 2, 2, 3}, element::u8, std::vector<uint8_t>{1, 2, 3, // [H=0, W=0, RGB]
|
||||||
|
4, 5, 6, // [H=0, W=1]
|
||||||
|
7, 8, 9, // [H=1, W=0]
|
||||||
|
10, 11, 12}); // [H=1, W=1]
|
||||||
|
res.expected.emplace_back(Shape{1, 3, 2, 2}, element::u8, std::vector<uint8_t>{1, 4, 7, 10, // R
|
||||||
|
2, 5, 8, 11, // G
|
||||||
|
3, 6, 9, 12}); // B
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RefPreprocessParams convert_layout_nhwc_to_net_no_tensor_shape() {
|
||||||
|
RefPreprocessParams res("convert_layout_nhwc_to_net_no_tensor_shape");
|
||||||
|
res.function = []() {
|
||||||
|
auto f = create_simple_function(element::u8, {1, 3, 2, 2});
|
||||||
|
f->get_parameters()[0]->set_layout("NCHW");
|
||||||
|
auto p = PreProcessSteps();
|
||||||
|
p.convert_layout();
|
||||||
|
f = PrePostProcessor()
|
||||||
|
.input(InputInfo()
|
||||||
|
.tensor(InputTensorInfo().set_layout("NHWC"))
|
||||||
|
.preprocess(std::move(p)))
|
||||||
|
.build(f);
|
||||||
|
return f;
|
||||||
|
};
|
||||||
|
res.inputs.emplace_back(Shape{1, 2, 2, 3}, element::u8, std::vector<uint8_t>{1, 2, 3, // [H=0, W=0, RGB]
|
||||||
|
4, 5, 6, // [H=0, W=1]
|
||||||
|
7, 8, 9, // [H=1, W=0]
|
||||||
|
10, 11, 12}); // [H=1, W=1]
|
||||||
|
res.expected.emplace_back(Shape{1, 3, 2, 2}, element::u8, std::vector<uint8_t>{1, 4, 7, 10, // R
|
||||||
|
2, 5, 8, 11, // G
|
||||||
|
3, 6, 9, 12}); // B
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static RefPreprocessParams resize_and_convert_layout() {
|
||||||
|
RefPreprocessParams res("resize_and_convert_layout");
|
||||||
|
res.function = []() {
|
||||||
|
auto f = create_simple_function(element::f32, PartialShape{1, 2, 2, 2});
|
||||||
|
f = PrePostProcessor()
|
||||||
|
.input(InputInfo()
|
||||||
|
.tensor(InputTensorInfo()
|
||||||
|
.set_layout("NCHW")
|
||||||
|
.set_spatial_dynamic_shape())
|
||||||
|
.preprocess(PreProcessSteps()
|
||||||
|
.resize(ResizeAlgorithm::RESIZE_LINEAR)
|
||||||
|
.convert_layout())
|
||||||
|
.network(InputNetworkInfo().set_layout("NHWC")))
|
||||||
|
.build(f);
|
||||||
|
return f;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = std::make_shared<HostTensor>();
|
||||||
|
// clang-format off
|
||||||
|
std::vector<float> input = {
|
||||||
|
1., 1., 1., 1., // channel 1
|
||||||
|
1., 1., 1., 1.,
|
||||||
|
1., 1., 1., 1.,
|
||||||
|
1., 1., 1., 1.,
|
||||||
|
2., 2., 2., 2., // channel 2
|
||||||
|
2., 2., 2., 2.,
|
||||||
|
2., 2., 2., 2.,
|
||||||
|
2., 2., 2., 2.,
|
||||||
|
};
|
||||||
|
std::vector<float> expected = {1., 2., 1., 2., 1., 2., 1., 2.};
|
||||||
|
// clang-format on
|
||||||
|
res.inputs.emplace_back(element::f32, Shape{1, 2, 4, 4}, input);
|
||||||
|
res.expected.emplace_back(Shape{1, 2, 2, 2}, element::f32, expected);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<RefPreprocessParams> allPreprocessTests() {
|
std::vector<RefPreprocessParams> allPreprocessTests() {
|
||||||
return std::vector<RefPreprocessParams> {
|
return std::vector<RefPreprocessParams> {
|
||||||
@ -437,7 +522,10 @@ std::vector<RefPreprocessParams> allPreprocessTests() {
|
|||||||
resize_from_spatial_dims(),
|
resize_from_spatial_dims(),
|
||||||
resize_to_network_width_height(),
|
resize_to_network_width_height(),
|
||||||
resize_to_specified_width_height(),
|
resize_to_specified_width_height(),
|
||||||
resize_lvalues()
|
resize_lvalues(),
|
||||||
|
convert_layout_nhwc_to_nchw_lvalue(),
|
||||||
|
convert_layout_nhwc_to_net_no_tensor_shape(),
|
||||||
|
resize_and_convert_layout()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +254,22 @@ inline std::shared_ptr<Function> resize_cubic() {
|
|||||||
return function;
|
return function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::shared_ptr<Function> resize_and_convert_layout() {
|
||||||
|
using namespace ov::preprocess;
|
||||||
|
auto function = create_preprocess_1input(element::f32, PartialShape{1, 30, 20, 3});
|
||||||
|
function = PrePostProcessor()
|
||||||
|
.input(InputInfo()
|
||||||
|
.tensor(InputTensorInfo()
|
||||||
|
.set_layout("NHWC")
|
||||||
|
.set_spatial_static_shape(40, 30))
|
||||||
|
.preprocess(PreProcessSteps()
|
||||||
|
.convert_layout()
|
||||||
|
.resize(ResizeAlgorithm::RESIZE_LINEAR))
|
||||||
|
.network(InputNetworkInfo().set_layout("NCHW")))
|
||||||
|
.build(function);
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::vector<preprocess_func> generic_preprocess_functions() {
|
inline std::vector<preprocess_func> generic_preprocess_functions() {
|
||||||
return std::vector<preprocess_func> {
|
return std::vector<preprocess_func> {
|
||||||
preprocess_func(mean_only, "mean_only", 0.01f),
|
preprocess_func(mean_only, "mean_only", 0.01f),
|
||||||
@ -273,6 +289,7 @@ inline std::vector<preprocess_func> generic_preprocess_functions() {
|
|||||||
preprocess_func(resize_nearest, "resize_nearest", 0.01f),
|
preprocess_func(resize_nearest, "resize_nearest", 0.01f),
|
||||||
preprocess_func(resize_linear_nhwc, "resize_linear_nhwc", 0.01f),
|
preprocess_func(resize_linear_nhwc, "resize_linear_nhwc", 0.01f),
|
||||||
preprocess_func(resize_cubic, "resize_cubic", 0.01f),
|
preprocess_func(resize_cubic, "resize_cubic", 0.01f),
|
||||||
|
preprocess_func(resize_and_convert_layout, "resize_and_convert_layout", 0.01f),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,11 +9,20 @@
|
|||||||
|
|
||||||
#include "ngraph/attribute_adapter.hpp"
|
#include "ngraph/attribute_adapter.hpp"
|
||||||
#include "openvino/core/core_visibility.hpp"
|
#include "openvino/core/core_visibility.hpp"
|
||||||
|
#include "openvino/core/partial_shape.hpp"
|
||||||
#include "openvino/core/rank.hpp"
|
#include "openvino/core/rank.hpp"
|
||||||
#include "openvino/core/variant.hpp"
|
#include "openvino/core/variant.hpp"
|
||||||
|
|
||||||
namespace ov {
|
namespace ov {
|
||||||
|
|
||||||
|
class Layout;
|
||||||
|
|
||||||
|
namespace layout {
|
||||||
|
|
||||||
|
std::vector<int64_t> find_permutation(const Layout& src_layout, const Rank& src_shape_rank, const Layout& dst_layout);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class OPENVINO_API Layout {
|
class OPENVINO_API Layout {
|
||||||
public:
|
public:
|
||||||
/// \brief Constructs a dynamic Layout with no layout information.
|
/// \brief Constructs a dynamic Layout with no layout information.
|
||||||
@ -59,6 +68,10 @@ public:
|
|||||||
/// \brief String representation of Layout
|
/// \brief String representation of Layout
|
||||||
std::string to_string() const;
|
std::string to_string() const;
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return *this == Layout();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// stores dimension names map to index in a layout
|
/// stores dimension names map to index in a layout
|
||||||
std::unordered_map<std::string, std::int64_t> m_names;
|
std::unordered_map<std::string, std::int64_t> m_names;
|
||||||
@ -70,6 +83,10 @@ private:
|
|||||||
bool m_dynamic = false;
|
bool m_dynamic = false;
|
||||||
int64_t m_left_size = 0;
|
int64_t m_left_size = 0;
|
||||||
int64_t m_right_size = 0;
|
int64_t m_right_size = 0;
|
||||||
|
|
||||||
|
friend std::vector<int64_t> layout::find_permutation(const Layout& src_layout,
|
||||||
|
const Rank& src_shape_rank,
|
||||||
|
const Layout& dst_layout);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace layout {
|
namespace layout {
|
||||||
|
@ -176,6 +176,37 @@ public:
|
|||||||
///
|
///
|
||||||
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner.
|
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner.
|
||||||
PreProcessSteps&& resize(ResizeAlgorithm alg) &&;
|
PreProcessSteps&& resize(ResizeAlgorithm alg) &&;
|
||||||
|
|
||||||
|
/// \brief Add 'convert layout' operation to specified layout - Lvalue version.
|
||||||
|
///
|
||||||
|
/// \details Adds appropriate 'transpose' operation between user layout and target layout.
|
||||||
|
/// Current implementation requires source and destination layout to have same number of dimensions
|
||||||
|
///
|
||||||
|
/// \example Example: when user data has 'NHWC' layout (example is RGB image, [1, 224, 224, 3]) but network expects
|
||||||
|
/// planar input image ('NCHW', [1, 3, 224, 224]). Preprocessing may look like this:
|
||||||
|
///
|
||||||
|
/// \code{.cpp} auto proc =
|
||||||
|
/// PrePostProcessor()
|
||||||
|
/// .input(InputInfo()
|
||||||
|
/// .tensor(InputTensorInfo().set_layout("NHWC")) // User data is NHWC
|
||||||
|
/// .preprocess(PreProcessSteps()
|
||||||
|
/// .convert_layout("NCHW")) // Network expects input as NCHW
|
||||||
|
/// );
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// \param dst_layout New layout after conversion. If not specified - destination layout is obtained from
|
||||||
|
/// appropriate network input properties.
|
||||||
|
///
|
||||||
|
/// \return Reference to 'this' to allow chaining with other calls in a builder-like manner.
|
||||||
|
PreProcessSteps& convert_layout(const Layout& dst_layout = {}) &;
|
||||||
|
|
||||||
|
/// \brief Add resize operation to network dimensions - Rvalue version.
|
||||||
|
///
|
||||||
|
/// \param dst_layout New layout after conversion. If not specified - destination layout is obtained from
|
||||||
|
/// appropriate network input properties.
|
||||||
|
///
|
||||||
|
/// \return Rvalue reference to 'this' to allow chaining with other calls in a builder-like manner.
|
||||||
|
PreProcessSteps&& convert_layout(const Layout& dst_layout = {}) &&;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace preprocess
|
} // namespace preprocess
|
||||||
|
@ -233,6 +233,33 @@ std::string Layout::to_string() const {
|
|||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace layout {
|
||||||
|
|
||||||
|
std::vector<int64_t> find_permutation(const Layout& src_layout, const Rank& rank, const Layout& dst) {
|
||||||
|
// Basic implementation so far, can support partially-specified layouts later (shape rank will be needed for dynamic
|
||||||
|
// layouts)
|
||||||
|
OPENVINO_ASSERT(!src_layout.m_dynamic && !dst.m_dynamic, "Conversion is not supported for dynamic layouts");
|
||||||
|
OPENVINO_ASSERT(src_layout.m_left_size == src_layout.m_left_size,
|
||||||
|
"Conversion is not supported for layouts with different sizes");
|
||||||
|
std::vector<int64_t> res(src_layout.m_left_size);
|
||||||
|
for (int64_t i = 0; i < src_layout.m_left_size; i++) {
|
||||||
|
auto it = src_layout.m_index_map.find(i);
|
||||||
|
OPENVINO_ASSERT(it != src_layout.m_index_map.end(),
|
||||||
|
"Conversion is not supported for partially specified source layout: ",
|
||||||
|
src_layout.to_string());
|
||||||
|
auto name = it->second;
|
||||||
|
OPENVINO_ASSERT(dst.has_name(name),
|
||||||
|
"Source dimension name '",
|
||||||
|
name,
|
||||||
|
"' is not found in destination layout: ",
|
||||||
|
dst.to_string());
|
||||||
|
res[dst.get_index_by_name(name)] = i;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace layout
|
||||||
|
|
||||||
#define DEFINE_NAMED_DIMENSION(NAME, name) \
|
#define DEFINE_NAMED_DIMENSION(NAME, name) \
|
||||||
bool layout::has_##name(const Layout& layout) { \
|
bool layout::has_##name(const Layout& layout) { \
|
||||||
return layout.has_name(NAME); \
|
return layout.has_name(NAME); \
|
||||||
|
@ -197,7 +197,7 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
|
|||||||
param = function->get_parameters().front();
|
param = function->get_parameters().front();
|
||||||
}
|
}
|
||||||
// Set parameter layout from 'network' information
|
// Set parameter layout from 'network' information
|
||||||
if (input->m_network_data && input->m_network_data->is_layout_set() && param->get_layout() == Layout()) {
|
if (input->m_network_data && input->m_network_data->is_layout_set() && param->get_layout().empty()) {
|
||||||
param->set_layout(input->m_network_data->get_layout());
|
param->set_layout(input->m_network_data->get_layout());
|
||||||
}
|
}
|
||||||
auto consumers = param->output(0).get_target_inputs();
|
auto consumers = param->output(0).get_target_inputs();
|
||||||
@ -210,7 +210,19 @@ std::shared_ptr<Function> PrePostProcessor::build(const std::shared_ptr<Function
|
|||||||
if (!input->m_tensor_data->is_element_type_set()) {
|
if (!input->m_tensor_data->is_element_type_set()) {
|
||||||
input->m_tensor_data->set_element_type(param->get_element_type());
|
input->m_tensor_data->set_element_type(param->get_element_type());
|
||||||
}
|
}
|
||||||
auto new_param_shape = param->get_partial_shape();
|
auto net_shape = param->get_partial_shape();
|
||||||
|
auto new_param_shape = net_shape;
|
||||||
|
if (input->m_tensor_data->is_layout_set() && param->get_layout() != Layout() &&
|
||||||
|
param->get_layout() != input->m_tensor_data->get_layout()) {
|
||||||
|
// Find transpose between network and tensor layouts and update tensor shape
|
||||||
|
auto net_to_tensor =
|
||||||
|
layout::find_permutation(param->get_layout(), net_shape.rank(), input->m_tensor_data->get_layout());
|
||||||
|
std::vector<ov::Dimension> dims(new_param_shape.size());
|
||||||
|
std::transform(net_to_tensor.begin(), net_to_tensor.end(), dims.begin(), [&](int64_t v) {
|
||||||
|
return new_param_shape[v];
|
||||||
|
});
|
||||||
|
new_param_shape = PartialShape(dims);
|
||||||
|
}
|
||||||
if (input->m_tensor_data->is_spatial_shape_set()) {
|
if (input->m_tensor_data->is_spatial_shape_set()) {
|
||||||
auto height_idx = get_and_check_height_idx(input->m_tensor_data->get_layout(), new_param_shape);
|
auto height_idx = get_and_check_height_idx(input->m_tensor_data->get_layout(), new_param_shape);
|
||||||
auto width_idx = get_and_check_width_idx(input->m_tensor_data->get_layout(), new_param_shape);
|
auto width_idx = get_and_check_width_idx(input->m_tensor_data->get_layout(), new_param_shape);
|
||||||
@ -410,6 +422,16 @@ PreProcessSteps&& PreProcessSteps::resize(ResizeAlgorithm alg) && {
|
|||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreProcessSteps& PreProcessSteps::convert_layout(const Layout& dst_layout) & {
|
||||||
|
m_impl->add_convert_layout_impl(dst_layout);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreProcessSteps&& PreProcessSteps::convert_layout(const Layout& dst_layout) && {
|
||||||
|
m_impl->add_convert_layout_impl(dst_layout);
|
||||||
|
return std::move(*this);
|
||||||
|
}
|
||||||
|
|
||||||
PreProcessSteps& PreProcessSteps::custom(const CustomPreprocessOp& preprocess_cb) & {
|
PreProcessSteps& PreProcessSteps::custom(const CustomPreprocessOp& preprocess_cb) & {
|
||||||
// 'true' indicates that custom preprocessing step will trigger validate_and_infer_types
|
// 'true' indicates that custom preprocessing step will trigger validate_and_infer_types
|
||||||
m_impl->actions().emplace_back(std::make_tuple(
|
m_impl->actions().emplace_back(std::make_tuple(
|
||||||
|
@ -146,5 +146,25 @@ void PreProcessSteps::PreProcessStepsImpl::add_resize_impl(ResizeAlgorithm alg,
|
|||||||
true));
|
true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PreProcessSteps::PreProcessStepsImpl::add_convert_layout_impl(const Layout& layout) {
|
||||||
|
m_actions.emplace_back(std::make_tuple(
|
||||||
|
[layout](const std::vector<std::shared_ptr<Node>>& nodes, PreprocessingContext& context) {
|
||||||
|
OPENVINO_ASSERT(!nodes.empty(), "Internal error: Can't convert layout for empty input.");
|
||||||
|
OPENVINO_ASSERT(nodes.size() == 1,
|
||||||
|
"Can't convert layout for multi-plane input. Suggesting to convert current image to "
|
||||||
|
"RGB/BGR color format using 'convert_color'");
|
||||||
|
Layout dst_layout = layout.empty() ? context.network_layout() : layout;
|
||||||
|
auto permutation =
|
||||||
|
layout::find_permutation(context.layout(), nodes[0]->get_output_partial_shape(0).rank(), dst_layout);
|
||||||
|
auto perm_constant =
|
||||||
|
op::v0::Constant::create<int64_t>(element::i64, Shape{permutation.size()}, permutation);
|
||||||
|
auto transpose = std::make_shared<op::v1::Transpose>(nodes[0], perm_constant);
|
||||||
|
transpose->set_friendly_name(nodes[0]->get_friendly_name() + "/convert_layout");
|
||||||
|
context.layout() = dst_layout; // Update context's current layout
|
||||||
|
return transpose;
|
||||||
|
},
|
||||||
|
true));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace preprocess
|
} // namespace preprocess
|
||||||
} // namespace ov
|
} // namespace ov
|
||||||
|
@ -116,6 +116,7 @@ public:
|
|||||||
void add_mean_impl(const std::vector<float>& values);
|
void add_mean_impl(const std::vector<float>& values);
|
||||||
void add_convert_impl(const element::Type& type);
|
void add_convert_impl(const element::Type& type);
|
||||||
void add_resize_impl(ResizeAlgorithm alg, int dst_height, int dst_width);
|
void add_resize_impl(ResizeAlgorithm alg, int dst_height, int dst_width);
|
||||||
|
void add_convert_layout_impl(const Layout& layout);
|
||||||
|
|
||||||
const std::list<std::tuple<InternalPreprocessOp, bool>>& actions() const {
|
const std::list<std::tuple<InternalPreprocessOp, bool>>& actions() const {
|
||||||
return m_actions;
|
return m_actions;
|
||||||
|
Loading…
Reference in New Issue
Block a user