From 5330112f059918b04c6c3da798a1def59b67795c Mon Sep 17 00:00:00 2001 From: Anatoliy Talamanov Date: Fri, 2 Jun 2023 13:31:03 +0200 Subject: [PATCH] Merge pull request #23595 from TolyaTalamanov:at/implement-openvino-backend [G-API] Implement OpenVINO 2.0 backend #23595 ### Pull Request Readiness Checklist Implemented basic functionality for `OpenVINO` 2.0 G-API backend. #### Overview - [x] Implement `Infer` kernel with some of essential configurable parameters + IR/Blob models format support. - [ ] Implement the rest of kernels: `InferList`, `InferROI`, `Infer2` + other configurable params (e.g reshape) - [x] Asyncrhonous execution support - [ ] Remote context support See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/gapi/CMakeLists.txt | 5 + .../opencv2/gapi/infer/bindings_ov.hpp | 128 +++ .../gapi/include/opencv2/gapi/infer/ov.hpp | 685 +++++++++++ modules/gapi/misc/python/pyopencv_gapi.hpp | 6 + modules/gapi/misc/python/shadow_gapi.hpp | 1 + .../misc/python/test/test_gapi_infer_ov.py | 238 ++++ modules/gapi/src/api/gmat.cpp | 16 +- modules/gapi/src/backends/common/gbackend.hpp | 6 + modules/gapi/src/backends/ov/bindings_ov.cpp | 168 +++ modules/gapi/src/backends/ov/govbackend.cpp | 1001 +++++++++++++++++ modules/gapi/src/backends/ov/govbackend.hpp | 66 ++ modules/gapi/src/backends/ov/util.hpp | 35 + .../gapi/test/infer/gapi_infer_ov_tests.cpp | 540 +++++++++ modules/python/src2/cv2_convert.hpp | 34 + .../predefined_types.py | 8 + 15 files changed, 2933 insertions(+), 4 deletions(-) create mode 100644 modules/gapi/include/opencv2/gapi/infer/bindings_ov.hpp create mode 100644 modules/gapi/include/opencv2/gapi/infer/ov.hpp create mode 100644 modules/gapi/misc/python/test/test_gapi_infer_ov.py create mode 100644 modules/gapi/src/backends/ov/bindings_ov.cpp create mode 100644 modules/gapi/src/backends/ov/govbackend.cpp create mode 100644 modules/gapi/src/backends/ov/govbackend.hpp create mode 100644 modules/gapi/src/backends/ov/util.hpp create mode 100644 modules/gapi/test/infer/gapi_infer_ov_tests.cpp diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index 2553b3ce8d..b9f2c11a2f 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -156,6 +156,10 @@ set(gapi_srcs src/backends/ie/giebackend.cpp src/backends/ie/giebackend/giewrapper.cpp + # OV Backend. FIXME: should be included by CMake + # if and only if OV support is enabled + src/backends/ov/govbackend.cpp + # ONNX backend src/backends/onnx/gonnxbackend.cpp @@ -182,6 +186,7 @@ set(gapi_srcs # Python bridge src/backends/ie/bindings_ie.cpp src/backends/onnx/bindings_onnx.cpp + src/backends/ov/bindings_ov.cpp src/backends/python/gpythonbackend.cpp # OpenVPL Streaming source diff --git a/modules/gapi/include/opencv2/gapi/infer/bindings_ov.hpp b/modules/gapi/include/opencv2/gapi/infer/bindings_ov.hpp new file mode 100644 index 0000000000..08f5c83a3f --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer/bindings_ov.hpp @@ -0,0 +1,128 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2023 Intel Corporation + +#ifndef OPENCV_GAPI_INFER_BINDINGS_OV_HPP +#define OPENCV_GAPI_INFER_BINDINGS_OV_HPP + +#include +#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS +#include // GKernelPackage +#include // Params + +#include + +namespace cv { +namespace gapi { +namespace ov { + +// NB: Used by python wrapper +// This class can be marked as SIMPLE, because it's implemented as pimpl +class GAPI_EXPORTS_W_SIMPLE PyParams { +public: + GAPI_WRAP + PyParams() = default; + + GAPI_WRAP + PyParams(const std::string &tag, + const std::string &model_path, + const std::string &bin_path, + const std::string &device); + + GAPI_WRAP + PyParams(const std::string &tag, + const std::string &blob_path, + const std::string &device); + + GAPI_WRAP + PyParams& cfgPluginConfig( + const std::map &config); + + GAPI_WRAP + PyParams& cfgInputTensorLayout(std::string tensor_layout); + + GAPI_WRAP + PyParams& cfgInputTensorLayout( + std::map layout_map); + + GAPI_WRAP + PyParams& cfgInputModelLayout(std::string tensor_layout); + + GAPI_WRAP + PyParams& cfgInputModelLayout( + std::map layout_map); + + GAPI_WRAP + PyParams& cfgOutputTensorLayout(std::string tensor_layout); + + GAPI_WRAP + PyParams& cfgOutputTensorLayout( + std::map layout_map); + + GAPI_WRAP + PyParams& cfgOutputModelLayout(std::string tensor_layout); + + GAPI_WRAP + PyParams& cfgOutputModelLayout( + std::map layout_map); + + GAPI_WRAP + PyParams& cfgOutputTensorPrecision(int precision); + + GAPI_WRAP + PyParams& cfgOutputTensorPrecision( + std::map precision_map); + + GAPI_WRAP + PyParams& cfgReshape(std::vector new_shape); + + GAPI_WRAP + PyParams& cfgReshape( + std::map> new_shape_map); + + GAPI_WRAP + PyParams& cfgNumRequests(const size_t nireq); + + GAPI_WRAP + PyParams& cfgMean(std::vector mean_values); + + GAPI_WRAP + PyParams& cfgMean( + std::map> mean_map); + + GAPI_WRAP + PyParams& cfgScale(std::vector scale_values); + + GAPI_WRAP + PyParams& cfgScale( + std::map> scale_map); + + GAPI_WRAP + PyParams& cfgResize(int interpolation); + + GAPI_WRAP + PyParams& cfgResize(std::map interpolation); + + GBackend backend() const; + std::string tag() const; + cv::util::any params() const; + +private: + std::shared_ptr> m_priv; +}; + +GAPI_EXPORTS_W PyParams params(const std::string &tag, + const std::string &model_path, + const std::string &weights, + const std::string &device); + +GAPI_EXPORTS_W PyParams params(const std::string &tag, + const std::string &bin_path, + const std::string &device); +} // namespace ov +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_INFER_BINDINGS_OV_HPP diff --git a/modules/gapi/include/opencv2/gapi/infer/ov.hpp b/modules/gapi/include/opencv2/gapi/infer/ov.hpp new file mode 100644 index 0000000000..99d701f937 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer/ov.hpp @@ -0,0 +1,685 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2023 Intel Corporation + +#ifndef OPENCV_GAPI_INFER_OV_HPP +#define OPENCV_GAPI_INFER_OV_HPP + +#include + +#include +#include // GAPI_EXPORTS +#include // GKernelType[M], GBackend +#include // Generic + +#include + +namespace cv { +namespace gapi { + +/** + * @brief This namespace contains G-API OpenVINO 2.0 backend functions, + * structures, and symbols. + */ +namespace ov { + +GAPI_EXPORTS cv::gapi::GBackend backend(); + +namespace detail { + +template +using AttrMap = std::map; +// NB: This type is supposed to be used to hold in/out layers +// attributes such as precision, layout, shape etc. +// +// User can provide attributes either: +// 1. cv::util::monostate - No value specified explicitly. +// 2. Attr - value specified explicitly that should be broadcasted to all layers. +// 3. AttrMap[str->T] - map specifies value for particular layer. +template +using LayerVariantAttr = cv::util::variant< cv::util::monostate + , AttrMap + , Attr>; + +struct ParamDesc { + struct Model { + + Model(const std::string &model_path_, + const std::string &bin_path_) + : model_path(model_path_), bin_path(bin_path_) { + } + + std::string model_path; + std::string bin_path; + + LayerVariantAttr input_tensor_layout; + LayerVariantAttr input_model_layout; + LayerVariantAttr output_tensor_layout; + LayerVariantAttr output_model_layout; + LayerVariantAttr output_tensor_precision; + + LayerVariantAttr> new_shapes; + + LayerVariantAttr> mean_values; + LayerVariantAttr> scale_values; + + LayerVariantAttr interpolation; + }; + + struct CompiledModel { + std::string blob_path; + }; + + using Kind = cv::util::variant; + + ParamDesc(Kind &&kind_, + const std::string &device_, + const bool is_generic_, + const size_t num_in_, + const size_t num_out_) + : kind(std::move(kind_)), device(device_), + is_generic(is_generic_), + num_in(num_in_), num_out(num_out_) { + } + + Kind kind; + + std::string device; + bool is_generic; + + std::size_t num_in; + std::size_t num_out; + + std::vector input_names; + std::vector output_names; + + using PluginConfigT = std::map; + PluginConfigT config; + + size_t nireq = 1; +}; + +// NB: Just helper to avoid code duplication. +static detail::ParamDesc::Model& +getModelToSetAttrOrThrow(detail::ParamDesc::Kind &kind, + const std::string &attr_name) { + if (cv::util::holds_alternative(kind)) { + cv::util::throw_error( + std::logic_error("Specifying " + attr_name + " isn't" + " possible for compiled model.")); + } + GAPI_Assert(cv::util::holds_alternative(kind)); + return cv::util::get(kind); +} + +} // namespace detail + +/** + * @brief This structure provides functions + * that fill inference parameters for "OpenVINO Toolkit" model. + */ +template struct Params { +public: + /** @brief Class constructor. + + Constructs Params based on model information and specifies default values for other + inference description parameters. Model is loaded and compiled using "OpenVINO Toolkit". + + @param model_path Path to a model. + @param bin_path Path to a data file. + For IR format (*.bin): + If path is empty, will try to read a bin file with the same name as xml. + If the bin file with the same name is not found, will load IR without weights. + For PDPD (*.pdmodel) and ONNX (*.onnx) formats bin_path isn't used. + @param device target device to use. + */ + Params(const std::string &model_path, + const std::string &bin_path, + const std::string &device) + : m_desc( detail::ParamDesc::Kind{detail::ParamDesc::Model{model_path, bin_path}} + , device + , false /* is generic */ + , std::tuple_size::value + , std::tuple_size::value) { + } + + /** @overload + Use this constructor to work with pre-compiled network. + Model is imported from a pre-compiled blob. + + @param blob_path path to the compiled model (*.blob). + @param device target device to use. + */ + Params(const std::string &blob_path, + const std::string &device) + : m_desc( detail::ParamDesc::Kind{detail::ParamDesc::CompiledModel{blob_path}} + , device + , false /* is generic */ + , std::tuple_size::value + , std::tuple_size::value) { + } + + /** @brief Specifies sequence of network input layers names for inference. + + The function is used to associate cv::gapi::infer<> inputs with the model inputs. + Number of names has to match the number of network inputs as defined in G_API_NET(). + In case a network has only single input layer, there is no need to specify name manually. + + @param layer_names std::array where N is the number of inputs + as defined in the @ref G_API_NET. Contains names of input layers. + @return reference to this parameter structure. + */ + Params& cfgInputLayers(const std::vector &layer_names) { + m_desc.input_names = layer_names; + return *this; + } + + /** @brief Specifies sequence of network output layers names for inference. + + The function is used to associate cv::gapi::infer<> outputs with the model outputs. + Number of names has to match the number of network outputs as defined in G_API_NET(). + In case a network has only single output layer, there is no need to specify name manually. + + @param layer_names std::array where N is the number of outputs + as defined in the @ref G_API_NET. Contains names of output layers. + @return reference to this parameter structure. + */ + Params& cfgOutputLayers(const std::vector &layer_names) { + m_desc.output_names = layer_names; + return *this; + } + + /** @brief Specifies OpenVINO plugin configuration. + + The function is used to set configuration for OpenVINO plugin. Some parameters + can be different for each plugin. Please follow https://docs.openvinotoolkit.org/latest/index.html + to check information about specific plugin. + + @param config Map of pairs: (config parameter name, config parameter value). + @return reference to this parameter structure. + */ + Params& cfgPluginConfig(const detail::ParamDesc::PluginConfigT &config) { + m_desc.config = config; + return *this; + } + + /** @brief Specifies tensor layout for an input layer. + + The function is used to set tensor layout for an input layer. + + @param layout Tensor layout ("NCHW", "NWHC", etc) + will be applied to all input layers. + @return reference to this parameter structure. + */ + Params& cfgInputTensorLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") + .input_tensor_layout = std::move(layout); + return *this; + } + + /** @overload + @param layout_map Map of pairs: name of corresponding input layer + and its tensor layout represented in std::string ("NCHW", "NHWC", etc) + @return reference to this parameter structure. + */ + Params& + cfgInputTensorLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") + .input_tensor_layout = std::move(layout_map); + return *this; + } + + /** @brief Specifies model layout for an input layer. + + The function is used to set model layout for an input layer. + + @param layout Model layout ("NCHW", "NHWC", etc) + will be applied to all input layers. + @return reference to this parameter structure. + */ + Params& cfgInputModelLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") + .input_model_layout = std::move(layout); + return *this; + } + + /** @overload + @param layout_map Map of pairs: name of corresponding input layer + and its model layout ("NCHW", "NHWC", etc) + @return reference to this parameter structure. + */ + Params& + cfgInputModelLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") + .input_model_layout = std::move(layout_map); + return *this; + } + + /** @brief Specifies tensor layout for an output layer. + + The function is used to set tensor layout for an output layer. + + @param layout Tensor layout ("NCHW", "NWHC", etc) + will be applied to all output layers. + @return reference to this parameter structure. + */ + Params& cfgOutputTensorLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") + .output_tensor_layout = std::move(layout); + return *this; + } + + /** @overload + @param layout_map Map of pairs: name of corresponding output layer + and its tensor layout represented in std::string ("NCHW", "NHWC", etc) + @return reference to this parameter structure. + */ + Params& + cfgOutputTensorLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") + .output_tensor_layout = std::move(layout_map); + return *this; + } + + /** @brief Specifies model layout for an output layer. + + The function is used to set model layout for an output layer. + + @param layout Model layout ("NCHW", "NHWC", etc) + will be applied to all output layers. + @return reference to this parameter structure. + */ + Params& cfgOutputModelLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") + .output_model_layout = std::move(layout); + return *this; + } + + /** @overload + @param layout_map Map of pairs: name of corresponding output layer + and its model layout ("NCHW", "NHWC", etc) + @return reference to this parameter structure. + */ + Params& + cfgOutputModelLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") + .output_model_layout = std::move(layout_map); + return *this; + } + + /** @brief Specifies tensor precision for an output layer. + + The function is used to set tensor precision for an output layer.. + + @param precision Precision in OpenCV format (CV_8U, CV_32F, ...) + will be applied to all output layers. + @return reference to this parameter structure. + */ + Params& cfgOutputTensorPrecision(int precision) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") + .output_tensor_precision = precision; + return *this; + } + + /** @overload + + @param precision_map Map of pairs: name of corresponding output layer + and its precision in OpenCV format (CV_8U, CV_32F, ...) + @return reference to this parameter structure. + */ + Params& + cfgOutputTensorPrecision(detail::AttrMap precision_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") + .output_tensor_precision = std::move(precision_map); + return *this; + } + + /** @brief Specifies the new shape for input layers. + + The function is used to set new shape for input layers. + + @param new_shape New shape will be applied to all input layers. + @return reference to this parameter structure. + */ + Params& + cfgReshape(std::vector new_shape) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") + .new_shapes = std::move(new_shape); + return *this; + } + + /** @overload + + @param new_shape_map Map of pairs: name of corresponding output layer + and its new shape. + @return reference to this parameter structure. + */ + Params& + cfgReshape(detail::AttrMap> new_shape_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") + .new_shapes = std::move(new_shape_map); + return *this; + } + + /** @brief Specifies number of asynchronous inference requests. + + @param nireq Number of inference asynchronous requests. + @return reference to this parameter structure. + */ + Params& cfgNumRequests(const size_t nireq) { + if (nireq == 0) { + cv::util::throw_error( + std::logic_error("Number of inference requests" + " must be greater than zero.")); + } + m_desc.nireq = nireq; + return *this; + } + + /** @brief Specifies mean values for preprocessing. + * + The function is used to set mean values for input layer preprocessing. + + @param mean_values Float vector contains mean values + @return reference to this parameter structure. + */ + Params& cfgMean(std::vector mean_values) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") + .mean_values = std::move(mean_values); + return *this; + } + + /** @overload + + @param mean_map Map of pairs: name of corresponding input layer + and its mean values. + @return reference to this parameter structure. + */ + Params& cfgMean(detail::AttrMap> mean_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") + .mean_values = std::move(mean_map); + return *this; + } + + /** @brief Specifies scale values for preprocessing. + * + The function is used to set scale values for input layer preprocessing. + + @param scale_values Float vector contains scale values + @return reference to this parameter structure. + */ + Params& cfgScale(std::vector scale_values) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") + .scale_values = std::move(scale_values); + return *this; + } + + /** @overload + + @param scale_map Map of pairs: name of corresponding input layer + and its mean values. + @return reference to this parameter structure. + */ + Params& cfgScale(detail::AttrMap> scale_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") + .scale_values = std::move(scale_map); + return *this; + } + + /** @brief Specifies resize interpolation algorithm. + * + The function is used to configure resize preprocessing for input layer. + + @param interpolation Resize interpolation algorithm. + Supported algorithms: #INTER_NEAREST, #INTER_LINEAR, #INTER_CUBIC. + @return reference to this parameter structure. + */ + Params& cfgResize(int interpolation) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") + .interpolation = std::move(interpolation); + return *this; + } + + /** @overload + + @param interpolation Map of pairs: name of corresponding input layer + and its resize algorithm. + @return reference to this parameter structure. + */ + Params& cfgResize(detail::AttrMap interpolation) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") + .interpolation = std::move(interpolation); + return *this; + } + + // BEGIN(G-API's network parametrization API) + GBackend backend() const { return cv::gapi::ov::backend(); } + std::string tag() const { return Net::tag(); } + cv::util::any params() const { return { m_desc }; } + // END(G-API's network parametrization API) + +protected: + detail::ParamDesc m_desc; +}; + +/* +* @brief This structure provides functions for generic network type that +* fill inference parameters. +* @see struct Generic +*/ +template<> +class Params { +public: + /** @brief Class constructor. + + Constructs Params based on model information and specifies default values for other + inference description parameters. Model is loaded and compiled using "OpenVINO Toolkit". + + @param tag string tag of the network for which these parameters are intended. + @param model_path Path to a model. + @param bin_path Path to a data file. + For IR format (*.bin): + If path is empty, will try to read a bin file with the same name as xml. + If the bin file with the same name is not found, will load IR without weights. + For PDPD (*.pdmodel) and ONNX (*.onnx) formats bin_path isn't used. + @param device target device to use. + */ + Params(const std::string &tag, + const std::string &model_path, + const std::string &bin_path, + const std::string &device) + : m_tag(tag), + m_desc( detail::ParamDesc::Kind{detail::ParamDesc::Model{model_path, bin_path}} + , device + , true /* is generic */ + , 0u + , 0u) { + } + + /** @overload + + This constructor for pre-compiled networks. Model is imported from pre-compiled + blob. + + @param tag string tag of the network for which these parameters are intended. + @param blob_path path to the compiled model (*.blob). + @param device target device to use. + */ + Params(const std::string &tag, + const std::string &blob_path, + const std::string &device) + : m_tag(tag), + m_desc( detail::ParamDesc::Kind{detail::ParamDesc::CompiledModel{blob_path}} + , device + , true /* is generic */ + , 0u + , 0u) { + } + + /** @see ov::Params::cfgPluginConfig. */ + Params& cfgPluginConfig(const detail::ParamDesc::PluginConfigT &config) { + m_desc.config = config; + return *this; + } + + /** @see ov::Params::cfgInputTensorLayout. */ + Params& cfgInputTensorLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") + .input_tensor_layout = std::move(layout); + return *this; + } + + /** @overload */ + Params& + cfgInputTensorLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") + .input_tensor_layout = std::move(layout_map); + return *this; + } + + /** @see ov::Params::cfgInputModelLayout. */ + Params& cfgInputModelLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") + .input_model_layout = std::move(layout); + return *this; + } + + /** @overload */ + Params& + cfgInputModelLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") + .input_model_layout = std::move(layout_map); + return *this; + } + + /** @see ov::Params::cfgOutputTensorLayout. */ + Params& cfgOutputTensorLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") + .output_tensor_layout = std::move(layout); + return *this; + } + + /** @overload */ + Params& + cfgOutputTensorLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") + .output_tensor_layout = std::move(layout_map); + return *this; + } + + /** @see ov::Params::cfgOutputModelLayout. */ + Params& cfgOutputModelLayout(std::string layout) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") + .output_model_layout = std::move(layout); + return *this; + } + + /** @overload */ + Params& + cfgOutputModelLayout(detail::AttrMap layout_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") + .output_model_layout = std::move(layout_map); + return *this; + } + + /** @see ov::Params::cfgOutputTensorPrecision. */ + Params& cfgOutputTensorPrecision(int precision) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") + .output_tensor_precision = precision; + return *this; + } + + /** @overload */ + Params& + cfgOutputTensorPrecision(detail::AttrMap precision_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") + .output_tensor_precision = std::move(precision_map); + return *this; + } + + /** @see ov::Params::cfgReshape. */ + Params& cfgReshape(std::vector new_shape) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") + .new_shapes = std::move(new_shape); + return *this; + } + + /** @overload */ + Params& + cfgReshape(detail::AttrMap> new_shape_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") + .new_shapes = std::move(new_shape_map); + return *this; + } + + /** @see ov::Params::cfgNumRequests. */ + Params& cfgNumRequests(const size_t nireq) { + if (nireq == 0) { + cv::util::throw_error( + std::logic_error("Number of inference requests" + " must be greater than zero.")); + } + m_desc.nireq = nireq; + return *this; + } + + /** @see ov::Params::cfgMean. */ + Params& cfgMean(std::vector mean_values) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") + .mean_values = std::move(mean_values); + return *this; + } + + /** @overload */ + Params& cfgMean(detail::AttrMap> mean_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") + .mean_values = std::move(mean_map); + return *this; + } + + /** @see ov::Params::cfgScale. */ + Params& cfgScale(std::vector scale_values) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") + .scale_values = std::move(scale_values); + return *this; + } + + /** @overload */ + Params& cfgScale(detail::AttrMap> scale_map) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") + .scale_values = std::move(scale_map); + return *this; + } + + /** @see ov::Params::cfgResize. */ + Params& cfgResize(int interpolation) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") + .interpolation = std::move(interpolation); + return *this; + } + + /** @overload */ + Params& cfgResize(detail::AttrMap interpolation) { + detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") + .interpolation = std::move(interpolation); + return *this; + } + + // BEGIN(G-API's network parametrization API) + GBackend backend() const { return cv::gapi::ov::backend(); } + std::string tag() const { return m_tag; } + cv::util::any params() const { return { m_desc }; } + // END(G-API's network parametrization API) + +protected: + std::string m_tag; + detail::ParamDesc m_desc; +}; + +} // namespace ov +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_INFER_OV_HPP diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp index 86273da321..736d8cc86e 100644 --- a/modules/gapi/misc/python/pyopencv_gapi.hpp +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -15,6 +15,7 @@ using gapi_GKernelPackage = cv::GKernelPackage; using gapi_GNetPackage = cv::gapi::GNetPackage; using gapi_ie_PyParams = cv::gapi::ie::PyParams; using gapi_onnx_PyParams = cv::gapi::onnx::PyParams; +using gapi_ov_PyParams = cv::gapi::ov::PyParams; using gapi_wip_IStreamSource_Ptr = cv::Ptr; using detail_ExtractArgsCallback = cv::detail::ExtractArgsCallback; using detail_ExtractMetaCallback = cv::detail::ExtractMetaCallback; @@ -22,6 +23,11 @@ using vector_GNetParam = std::vector; using vector_GMat = std::vector; using gapi_streaming_queue_capacity = cv::gapi::streaming::queue_capacity; using GStreamerSource_OutputType = cv::gapi::wip::GStreamerSource::OutputType; +using map_string_and_int = std::map; +using map_string_and_string = std::map; +using map_string_and_string = std::map; +using map_string_and_vector_size_t = std::map>; +using map_string_and_vector_float = std::map>; // NB: Python wrapper generate T_U for T // This behavior is only observed for inputs diff --git a/modules/gapi/misc/python/shadow_gapi.hpp b/modules/gapi/misc/python/shadow_gapi.hpp index cf81335e0b..c0c1e38136 100644 --- a/modules/gapi/misc/python/shadow_gapi.hpp +++ b/modules/gapi/misc/python/shadow_gapi.hpp @@ -80,5 +80,6 @@ namespace detail { gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ie::PyParams params); gapi::GNetParam GAPI_EXPORTS_W strip(gapi::onnx::PyParams params); + gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ov::PyParams params); } // namespace detail } // namespace cv diff --git a/modules/gapi/misc/python/test/test_gapi_infer_ov.py b/modules/gapi/misc/python/test/test_gapi_infer_ov.py new file mode 100644 index 0000000000..b4022b6e2d --- /dev/null +++ b/modules/gapi/misc/python/test/test_gapi_infer_ov.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python + +import numpy as np +import cv2 as cv +import os +import sys +import unittest + +from tests_common import NewOpenCVTests + + +try: + + if sys.version_info[:2] < (3, 0): + raise unittest.SkipTest('Python 2.x is not supported') + + + openvino_is_available = True + try: + from openvino.runtime import Core, Type, Layout, PartialShape + from openvino.preprocess import ResizeAlgorithm, PrePostProcessor + except ImportError: + openvino_is_available = False + + + def skip_if_openvino_not_available(): + if not openvino_is_available: + raise unittest.SkipTest("OpenVINO isn't available from python.") + + + class AgeGenderOV: + def __init__(self, model_path, bin_path, device): + self.device = device + self.core = Core() + self.model = self.core.read_model(model_path, bin_path) + + + def reshape(self, new_shape): + self.model.reshape(new_shape) + + + def cfgPrePostProcessing(self, pp_callback): + ppp = PrePostProcessor(self.model) + pp_callback(ppp) + self.model = ppp.build() + + + def apply(self, in_data): + compiled_model = self.core.compile_model(self.model, self.device) + infer_request = compiled_model.create_infer_request() + results = infer_request.infer(in_data) + ov_age = results['age_conv3'].squeeze() + ov_gender = results['prob'].squeeze() + return ov_age, ov_gender + + + class AgeGenderGAPI: + tag = 'age-gender-net' + + def __init__(self, model_path, bin_path, device): + g_in = cv.GMat() + inputs = cv.GInferInputs() + inputs.setInput('data', g_in) + # TODO: It'd be nice to pass dict instead. + # E.g cv.gapi.infer("net", {'data': g_in}) + outputs = cv.gapi.infer(AgeGenderGAPI.tag, inputs) + age_g = outputs.at("age_conv3") + gender_g = outputs.at("prob") + + self.comp = cv.GComputation(cv.GIn(g_in), cv.GOut(age_g, gender_g)) + self.pp = cv.gapi.ov.params(AgeGenderGAPI.tag, \ + model_path, bin_path, device) + + + def apply(self, in_data): + compile_args = cv.gapi.compile_args(cv.gapi.networks(self.pp)) + gapi_age, gapi_gender = self.comp.apply(cv.gin(in_data), compile_args) + gapi_gender = gapi_gender.squeeze() + gapi_age = gapi_age.squeeze() + return gapi_age, gapi_gender + + + class test_gapi_infer_ov(NewOpenCVTests): + + def test_age_gender_infer_image(self): + skip_if_openvino_not_available() + + root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' + model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + device_id = 'CPU' + + img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + img = cv.imread(img_path) + + # OpenVINO + def preproc(ppp): + ppp.input().model().set_layout(Layout("NCHW")) + ppp.input().tensor().set_element_type(Type.u8) \ + .set_spatial_static_shape(img.shape[0], img.shape[1]) \ + .set_layout(Layout("NHWC")) + ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR) + + + ref = AgeGenderOV(model_path, bin_path, device_id) + ref.cfgPrePostProcessing(preproc) + ov_age, ov_gender = ref.apply(np.expand_dims(img, 0)) + + # OpenCV G-API (No preproc required) + comp = AgeGenderGAPI(model_path, bin_path, device_id) + gapi_age, gapi_gender = comp.apply(img) + + # Check + self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) + + + def test_age_gender_infer_tensor(self): + skip_if_openvino_not_available() + + root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' + model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + device_id = 'CPU' + + img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + img = cv.imread(img_path) + + # Prepare data manually + tensor = cv.resize(img, (62, 62)).astype(np.float32) + tensor = np.transpose(tensor, (2, 0, 1)) + tensor = np.expand_dims(tensor, 0) + + # OpenVINO (No preproce required) + ref = AgeGenderOV(model_path, bin_path, device_id) + ov_age, ov_gender = ref.apply(tensor) + + # OpenCV G-API (No preproc required) + comp = AgeGenderGAPI(model_path, bin_path, device_id) + gapi_age, gapi_gender = comp.apply(tensor) + + # Check + self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) + + + def test_age_gender_infer_batch(self): + skip_if_openvino_not_available() + + root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' + model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + device_id = 'CPU' + + img_path1 = self.find_file('cv/face/david1.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + img_path2 = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + img1 = cv.imread(img_path1) + img2 = cv.imread(img_path2) + # img1 and img2 have the same size + batch_img = np.array([img1, img2]) + + # OpenVINO + def preproc(ppp): + ppp.input().model().set_layout(Layout("NCHW")) + ppp.input().tensor().set_element_type(Type.u8) \ + .set_spatial_static_shape(img1.shape[0], img2.shape[1]) \ + .set_layout(Layout("NHWC")) + ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR) + + + ref = AgeGenderOV(model_path, bin_path, device_id) + ref.reshape(PartialShape([2, 3, 62, 62])) + ref.cfgPrePostProcessing(preproc) + ov_age, ov_gender = ref.apply(batch_img) + + # OpenCV G-API + comp = AgeGenderGAPI(model_path, bin_path, device_id) + comp.pp.cfgReshape([2, 3, 62, 62]) \ + .cfgInputModelLayout("NCHW") \ + .cfgInputTensorLayout("NHWC") \ + .cfgResize(cv.INTER_LINEAR) + gapi_age, gapi_gender = comp.apply(batch_img) + + # Check + self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) + + + def test_age_gender_infer_planar(self): + skip_if_openvino_not_available() + + root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' + model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + device_id = 'CPU' + + img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + img = cv.imread(img_path) + planar_img = np.transpose(img, (2, 0, 1)) + planar_img = np.expand_dims(planar_img, 0) + + # OpenVINO + def preproc(ppp): + ppp.input().tensor().set_element_type(Type.u8) \ + .set_spatial_static_shape(img.shape[0], img.shape[1]) + ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR) + + + ref = AgeGenderOV(model_path, bin_path, device_id) + ref.cfgPrePostProcessing(preproc) + ov_age, ov_gender = ref.apply(planar_img) + + # OpenCV G-API + comp = AgeGenderGAPI(model_path, bin_path, device_id) + comp.pp.cfgResize(cv.INTER_LINEAR) + gapi_age, gapi_gender = comp.apply(planar_img) + + # Check + self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) + + +except unittest.SkipTest as e: + + message = str(e) + + class TestSkip(unittest.TestCase): + def setUp(self): + self.skipTest('Skip tests: ' + message) + + def test_skip(): + pass + + pass + + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/gapi/src/api/gmat.cpp b/modules/gapi/src/api/gmat.cpp index 47a246c293..03f2e736be 100644 --- a/modules/gapi/src/api/gmat.cpp +++ b/modules/gapi/src/api/gmat.cpp @@ -153,10 +153,18 @@ std::ostream& operator<<(std::ostream& os, const cv::GMatDesc &desc) break; } - os << "C" << desc.chan; - if (desc.planar) os << "p"; - os << " "; - os << desc.size.width << "x" << desc.size.height; + if (desc.isND()) { + os << " ["; + for (size_t i = 0; i < desc.dims.size() - 1; ++i) { + os << desc.dims[i] << "x"; + } + os << desc.dims.back() << "]"; + } else { + os << "C" << desc.chan; + if (desc.planar) os << "p"; + os << " "; + os << desc.size.width << "x" << desc.size.height; + } return os; } diff --git a/modules/gapi/src/backends/common/gbackend.hpp b/modules/gapi/src/backends/common/gbackend.hpp index b05d8e2c36..794af94d3a 100644 --- a/modules/gapi/src/backends/common/gbackend.hpp +++ b/modules/gapi/src/backends/common/gbackend.hpp @@ -227,6 +227,12 @@ inline void convertInt64ToInt32(const int64_t* src, int* dst, size_t size) [](int64_t el) { return static_cast(el); }); } +inline void convertInt32ToInt64(const int* src, int64_t* dst, size_t size) +{ + std::transform(src, src + size, dst, + [](int el) { return static_cast(el); }); +} + }} // cv::gimpl #endif // OPENCV_GAPI_GBACKEND_HPP diff --git a/modules/gapi/src/backends/ov/bindings_ov.cpp b/modules/gapi/src/backends/ov/bindings_ov.cpp new file mode 100644 index 0000000000..8bcbc497af --- /dev/null +++ b/modules/gapi/src/backends/ov/bindings_ov.cpp @@ -0,0 +1,168 @@ +#include + +cv::gapi::ov::PyParams::PyParams(const std::string &tag, + const std::string &model_path, + const std::string &bin_path, + const std::string &device) + : m_priv(std::make_shared>(tag, model_path, bin_path, device)) { +} + +cv::gapi::ov::PyParams::PyParams(const std::string &tag, + const std::string &blob_path, + const std::string &device) + : m_priv(std::make_shared>(tag, blob_path, device)) { +} + +cv::gapi::GBackend cv::gapi::ov::PyParams::backend() const { + return m_priv->backend(); +} + +std::string cv::gapi::ov::PyParams::tag() const { + return m_priv->tag(); +} + +cv::util::any cv::gapi::ov::PyParams::params() const { + return m_priv->params(); +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgPluginConfig( + const std::map &config) { + m_priv->cfgPluginConfig(config); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgInputTensorLayout(std::string tensor_layout) { + m_priv->cfgInputTensorLayout(std::move(tensor_layout)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgInputTensorLayout( + std::map layout_map) { + m_priv->cfgInputTensorLayout(std::move(layout_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgInputModelLayout(std::string tensor_layout) { + m_priv->cfgInputModelLayout(std::move(tensor_layout)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgInputModelLayout( + std::map layout_map) { + m_priv->cfgInputModelLayout(std::move(layout_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgOutputTensorLayout(std::string tensor_layout) { + m_priv->cfgOutputTensorLayout(std::move(tensor_layout)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgOutputTensorLayout( + std::map layout_map) { + m_priv->cfgOutputTensorLayout(std::move(layout_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgOutputModelLayout(std::string tensor_layout) { + m_priv->cfgOutputModelLayout(std::move(tensor_layout)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgOutputModelLayout( + std::map layout_map) { + m_priv->cfgOutputModelLayout(std::move(layout_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgOutputTensorPrecision(int precision) { + m_priv->cfgOutputTensorPrecision(precision); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgOutputTensorPrecision( + std::map precision_map) { + m_priv->cfgOutputTensorPrecision(precision_map); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgReshape(std::vector new_shape) { + m_priv->cfgReshape(std::move(new_shape)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgReshape( + std::map> new_shape_map) { + m_priv->cfgReshape(std::move(new_shape_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgNumRequests(const size_t nireq) { + m_priv->cfgNumRequests(nireq); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgMean(std::vector mean_values) { + m_priv->cfgMean(std::move(mean_values)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgMean( + std::map> mean_map) { + m_priv->cfgMean(std::move(mean_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgScale(std::vector scale_values) { + m_priv->cfgScale(std::move(scale_values)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgScale( + std::map> scale_map) { + m_priv->cfgScale(std::move(scale_map)); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgResize(int interpolation) { + m_priv->cfgResize(interpolation); + return *this; +} + +cv::gapi::ov::PyParams& +cv::gapi::ov::PyParams::cfgResize(std::map interpolation) { + m_priv->cfgResize(std::move(interpolation)); + return *this; +} + +cv::gapi::ov::PyParams cv::gapi::ov::params(const std::string &tag, + const std::string &model_path, + const std::string &weights, + const std::string &device) { + return {tag, model_path, weights, device}; +} + +cv::gapi::ov::PyParams cv::gapi::ov::params(const std::string &tag, + const std::string &blob_path, + const std::string &device) { + return {tag, blob_path, device}; +} diff --git a/modules/gapi/src/backends/ov/govbackend.cpp b/modules/gapi/src/backends/ov/govbackend.cpp new file mode 100644 index 0000000000..65f76a9ca1 --- /dev/null +++ b/modules/gapi/src/backends/ov/govbackend.cpp @@ -0,0 +1,1001 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2023 Intel Corporation + +#include "precomp.hpp" + +// needs to be included regardless if IE is present or not +// (cv::gapi::ov::backend() is still there and is defined always) +#include "backends/ov/govbackend.hpp" + +#ifdef HAVE_INF_ENGINE + +#include "backends/ov/util.hpp" +#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK! +#include "logger.hpp" + +#include +#include + +#if defined(HAVE_TBB) +# include // FIXME: drop it from here! +template using QueueClass = tbb::concurrent_bounded_queue; +#else +# include "executor/conc_queue.hpp" +template using QueueClass = cv::gapi::own::concurrent_bounded_queue; +#endif // TBB + +#include "utils/itt.hpp" + +#include + +#include + +#include + +using ParamDesc = cv::gapi::ov::detail::ParamDesc; + +static ov::Core getCore() { + static ov::Core core; + return core; +} + +static ov::AnyMap toOV(const ParamDesc::PluginConfigT &config) { + return {config.begin(), config.end()}; +} + +static std::map +toOV(const std::map> &shapes) { + std::map ov_shapes; + for (const auto &it : shapes) { + ov_shapes.emplace(it.first, ::ov::Shape(it.second)); + } + return ov_shapes; +} + +static ov::element::Type toOV(int depth) { + switch (depth) { + case CV_8U: return ov::element::u8; + case CV_32S: return ov::element::i32; + case CV_32F: return ov::element::f32; + case CV_16F: return ov::element::f16; + default: GAPI_Error("OV Backend: Unsupported data type"); + } + return ov::element::undefined; +} + +static ov::preprocess::ResizeAlgorithm toOVInterp(int interpolation) { + namespace pp = ov::preprocess; + switch (interpolation) { + case cv::INTER_LINEAR: return pp::ResizeAlgorithm::RESIZE_LINEAR; + case cv::INTER_NEAREST: return pp::ResizeAlgorithm::RESIZE_NEAREST; + case cv::INTER_CUBIC: return pp::ResizeAlgorithm::RESIZE_CUBIC; + default: GAPI_Error("OV Backend: Unsupported resize algorithm"); + } + // Unreachable code + GAPI_Assert(false); +} + +static std::vector toCV(const ov::Shape &shape) { + std::vector result; + result.reserve(shape.size()); + for (auto dim : shape) { + result.push_back(ade::util::checked_cast(dim)); + } + return result; +} + +static int toCV(const ov::element::Type &type) { + switch (type) { + case ov::element::u8: return CV_8U; + case ov::element::f32: return CV_32F; + case ov::element::i32: return CV_32S; + case ov::element::i64: return CV_32S; + case ov::element::f16: return CV_16F; + default: GAPI_Error("OV Backend: Unsupported data type"); + } + return -1; +} + +static void copyFromOV(const ov::Tensor &tensor, cv::Mat &mat) { + const auto total = mat.total() * mat.channels(); + if (tensor.get_element_type() != toOV(mat.depth()) || + tensor.get_size() != total ) { + std::stringstream ss; + ss << "Failed to copy data from ov::Tensor to cv::Mat." + << " Data type or number of elements mismatch." + << " cv::Mat: " << cv::descr_of(mat) << " and" + << " ov::Tensor: " << tensor.get_element_type() << " " + << tensor.get_shape(); + cv::util::throw_error(std::logic_error(ss.str())); + } + + if (tensor.get_element_type() == ov::element::i64) { + GAPI_LOG_WARNING(NULL, "INT64 isn't supported for cv::Mat. Conversion to INT32 is used."); + cv::gimpl::convertInt64ToInt32(tensor.data(), + mat.ptr(), + total); + } else { + std::copy_n(reinterpret_cast(tensor.data()), + tensor.get_byte_size(), + mat.ptr()); + } +} + +static void copyToOV(const cv::Mat &mat, ov::Tensor &tensor) { + // TODO: Ideally there should be check that mat and tensor + // dimensions are compatible. + const auto total = mat.total() * mat.channels(); + if (tensor.get_element_type() != toOV(mat.depth()) || + tensor.get_size() != total) { + std::stringstream ss; + ss << "Failed to copy data from cv::Mat to ov::Tensor." + << " Data type or number of elements mismatch." + << " ov::Tensor: " << tensor.get_element_type() << " " + << tensor.get_shape() << " and" + << " cv::Mat: " << cv::descr_of(mat); + cv::util::throw_error(std::logic_error(ss.str())); + } + + if (tensor.get_element_type() == ov::element::i64) { + cv::gimpl::convertInt32ToInt64(mat.ptr(), + tensor.data(), + total); + } else { + std::copy_n(mat.ptr(), + tensor.get_byte_size(), + reinterpret_cast(tensor.data())); + } +} + +std::vector cv::gapi::ov::util::to_ocv(const ::ov::Shape &shape) { + return toCV(shape); +} + +int cv::gapi::ov::util::to_ocv(const ::ov::element::Type &type) { + return toCV(type); +} + +struct OVUnit { + static const char *name() { return "OVUnit"; } + + explicit OVUnit(const ParamDesc &pd) + : params(pd) { + + // FIXME: Can this logic be encapsulated to prevent checking every time? + if (cv::util::holds_alternative(params.kind)) { + const auto desc = cv::util::get(params.kind); + model = getCore().read_model(desc.model_path, desc.bin_path); + GAPI_Assert(model); + + if (params.num_in == 1u && params.input_names.empty()) { + params.input_names = { model->inputs().begin()->get_any_name() }; + } + if (params.num_out == 1u && params.output_names.empty()) { + params.output_names = { model->outputs().begin()->get_any_name() }; + } + + } else { + GAPI_Assert(cv::util::holds_alternative(params.kind)); + std::ifstream file(cv::util::get(params.kind).blob_path, + std::ios_base::in | std::ios_base::binary); + GAPI_Assert(file.is_open()); + compiled_model = getCore().import_model(file, + params.device, + toOV(params.config)); + + if (params.num_in == 1u && params.input_names.empty()) { + params.input_names = { compiled_model.inputs().begin()->get_any_name() }; + } + if (params.num_out == 1u && params.output_names.empty()) { + params.output_names = { compiled_model.outputs().begin()->get_any_name() }; + } + } + }; + + cv::gimpl::ov::OVCompiled compile() { + if (cv::util::holds_alternative(params.kind)) { + compiled_model = getCore().compile_model(model, + params.device, + toOV(params.config)); + } + return {compiled_model}; + } + + cv::gapi::ov::detail::ParamDesc params; + std::shared_ptr model; + ov::CompiledModel compiled_model; +}; + +class OVCallContext +{ +public: + OVCallContext(const OVUnit & unit, + cv::gimpl::GIslandExecutable::IOutput & output, + const cv::GArgs & args, + const std::vector & outs, + cv::GRunArg::Meta && meta, + std::vector && input_objs, + std::vector && output_objs); + + const cv::GArgs& inArgs() const; + + // Generic accessor API + template + const T& inArg(std::size_t input) const { + return m_args.at(input).get(); + } + + template + std::vector& outVecR(std::size_t output) { + return outVecRef(output).wref(); + } + + // Syntax sugar + cv::GShape inShape(std::size_t input) const; + const cv::Mat& inMat (std::size_t input) const; + + cv::GRunArgP output (std::size_t idx); + cv::Mat& outMatR(std::size_t idx); + + const OVUnit &uu; + cv::gimpl::GIslandExecutable::IOutput &out; + + // To store exception appeared in callback. + std::exception_ptr eptr; + + const cv::GRunArg::Meta& getMeta() { return m_meta; }; +private: + cv::detail::VectorRef& outVecRef(std::size_t idx); + + cv::GArg packArg(const cv::GArg &arg); + + // To propagate accumulated meta from all inputs to output. + cv::GRunArg::Meta m_meta; + + // To store input/output data from frames + std::vector m_input_objs; + std::vector m_output_objs; + + // To simplify access to cv::Mat inside cv::RMat + cv::gimpl::Mag m_res; + + std::unordered_map m_results; + + // Input parameters passed to an inference operation. + cv::GArgs m_args; + cv::GShapes m_in_shapes; +}; + +OVCallContext::OVCallContext(const OVUnit & unit, + cv::gimpl::GIslandExecutable::IOutput & output, + const cv::GArgs & args, + const std::vector & outs, + cv::GRunArg::Meta && meta, + std::vector && input_objs, + std::vector && output_objs) +: uu(unit), out(output), m_meta(std::move(meta)), + m_input_objs(std::move(input_objs)), m_output_objs(std::move(output_objs)) +{ + for (auto& it : m_input_objs) cv::gimpl::magazine::bindInArg (m_res, it.first, it.second); + for (auto& it : m_output_objs) cv::gimpl::magazine::bindOutArg(m_res, it.first, it.second); + + m_args.reserve(args.size()); + using namespace std::placeholders; + ade::util::transform(args, + std::back_inserter(m_args), + std::bind(&OVCallContext::packArg, this, _1)); + + ade::util::transform(args, std::back_inserter(m_in_shapes), + [](const cv::GArg& arg) { + return arg.get().shape; + }); + + for (const auto out_it : ade::util::indexed(outs)) { + // FIXME: Can the same GArg type resolution mechanism be reused here? + const auto port = ade::util::index(out_it); + const auto desc = ade::util::value(out_it); + m_results[port] = cv::gimpl::magazine::getObjPtr(m_res, desc); + } +} + +const cv::GArgs& OVCallContext::inArgs() const { + return m_args; +} + +cv::GShape OVCallContext::inShape(std::size_t i) const { + return m_in_shapes[i]; +} + +const cv::Mat& OVCallContext::inMat(std::size_t input) const { + return inArg(input); +} + +cv::Mat& OVCallContext::outMatR(std::size_t idx) { + return *cv::util::get(m_results.at(idx)); +} + +cv::GRunArgP OVCallContext::output(std::size_t idx) { + return m_output_objs[idx].second; +}; + +cv::detail::VectorRef& OVCallContext::outVecRef(std::size_t idx) { + return cv::util::get(m_results.at(idx)); +} + +cv::GArg OVCallContext::packArg(const cv::GArg &arg) { + // No API placeholders allowed at this point + // FIXME: this check has to be done somewhere in compilation stage. + GAPI_Assert( arg.kind != cv::detail::ArgKind::GMAT + && arg.kind != cv::detail::ArgKind::GSCALAR + && arg.kind != cv::detail::ArgKind::GARRAY); + + if (arg.kind != cv::detail::ArgKind::GOBJREF) { + cv::util::throw_error(std::logic_error("Inference supports G-types ONLY!")); + } + GAPI_Assert(arg.kind == cv::detail::ArgKind::GOBJREF); + + // Wrap associated CPU object (either host or an internal one) + // FIXME: object can be moved out!!! GExecutor faced that. + const cv::gimpl::RcDesc &ref = arg.get(); + switch (ref.shape) + { + case cv::GShape::GMAT: return cv::GArg(m_res.slot()[ref.id]); + default: + cv::util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +struct OVCallable { + static const char *name() { return "OVRequestCallable"; } + using Run = std::function, + cv::gimpl::ov::RequestPool&)>; + Run run; +}; + +struct KImpl { + cv::gimpl::CustomMetaFunction::CM customMetaFunc; + OVCallable::Run run; +}; + +using GOVModel = ade::TypedGraph + < cv::gimpl::Protocol + , cv::gimpl::Op + , cv::gimpl::NetworkParams + , cv::gimpl::CustomMetaFunction + , OVUnit + , OVCallable + >; + +// FIXME: Same issue with Typed and ConstTyped +using GConstGOVModel = ade::ConstTypedGraph + < cv::gimpl::Protocol + , cv::gimpl::Op + , cv::gimpl::NetworkParams + , cv::gimpl::CustomMetaFunction + , OVUnit + , OVCallable + >; + +namespace { +class IInferExecutor { +public: + using Ptr = std::shared_ptr; + using NotifyCallbackF = std::function; + using SetInputDataF = std::function; + using ReadOutputDataF = std::function; + + // NB: The task is represented by: + // SetInputDataF - function which set input data. + // ReadOutputDataF - function which read output data. + struct Task { + SetInputDataF set_input_data; + ReadOutputDataF read_output_data; + }; + + IInferExecutor(::ov::InferRequest request, NotifyCallbackF notify) + : m_request(std::move(request)), + m_notify(std::move(notify)) { + }; + + virtual void execute(const Task& task) = 0; + virtual ~IInferExecutor() = default; + +protected: + ::ov::InferRequest m_request; + NotifyCallbackF m_notify; +}; + +class SyncInferExecutor : public IInferExecutor { + using IInferExecutor::IInferExecutor; + virtual void execute(const IInferExecutor::Task &task) override; +}; + +void SyncInferExecutor::execute(const IInferExecutor::Task &task) { + try { + task.set_input_data(m_request); + m_request.infer(); + task.read_output_data(m_request, nullptr); + } catch (...) { + m_notify(); + throw; + } + // NB: Notify pool that executor has finished. + m_notify(); +} + +class AsyncInferExecutor : public IInferExecutor { +public: + using IInferExecutor::IInferExecutor; + virtual void execute(const IInferExecutor::Task& task) override; + +private: + void callback(Task task, + ::ov::InferRequest request, + std::exception_ptr eptr) noexcept; +}; + +void AsyncInferExecutor::execute(const IInferExecutor::Task& task) { + using namespace std::placeholders; + using callback_t = std::function; + m_request.set_callback( + static_cast( + std::bind(&AsyncInferExecutor::callback, this, task, m_request, _1))); + try { + task.set_input_data(m_request); + m_request.start_async(); + } catch (...) { + m_request.set_callback([](std::exception_ptr){}); + m_notify(); + throw; + } +} + +void AsyncInferExecutor::callback(IInferExecutor::Task task, + ::ov::InferRequest request, + std::exception_ptr eptr) noexcept { + task.read_output_data(request, eptr); + request.set_callback([](std::exception_ptr){}); + // NB: Notify pool that executor has finished. + m_notify(); +} + +} // anonymous namespace + +// TODO: Make it generic to reuse in IE and ONNX backends. +class cv::gimpl::ov::RequestPool { +public: + explicit RequestPool(std::vector<::ov::InferRequest>&& requests); + + IInferExecutor::Ptr getIdleRequest(); + void waitAll(); + +private: + void setup(); + void release(const size_t id); + + QueueClass m_idle_ids; + std::vector m_requests; +}; + +void cv::gimpl::ov::RequestPool::release(const size_t id) { + m_idle_ids.push(id); +} + +cv::gimpl::ov::RequestPool::RequestPool(std::vector<::ov::InferRequest>&& requests) { + GAPI_Assert(!requests.empty()); + if (requests.size() == 1u) { + m_requests.push_back( + std::make_shared( + requests.front(), std::bind(&RequestPool::release, this, 0u))); + } else { + for (size_t i = 0; i < requests.size(); ++i) { + m_requests.push_back( + std::make_shared( + requests[i], std::bind(&RequestPool::release, this, i))); + } + } + setup(); +} + +void cv::gimpl::ov::RequestPool::setup() { + for (size_t i = 0; i < m_requests.size(); ++i) { + m_idle_ids.push(i); + } +} + +IInferExecutor::Ptr cv::gimpl::ov::RequestPool::getIdleRequest() { + size_t id = 0u; + m_idle_ids.pop(id); + return m_requests[id]; +} + +// NB: Not thread-safe. +void cv::gimpl::ov::RequestPool::waitAll() { + // NB: It will be blocked if at least one request is busy. + for (size_t i = 0; i < m_requests.size(); ++i) { + size_t id = 0u; + m_idle_ids.pop(id); + } + setup(); +} + + +// NB: This is a callback used by async infer +// to post outputs blobs (cv::GMat's). +static void PostOutputs(::ov::InferRequest &infer_request, + std::exception_ptr eptr, + std::shared_ptr ctx) { + GAPI_ITT_STATIC_LOCAL_HANDLE(ov_cb_post_outputs_hndl, "OV_async_callback_PostOutputs"); + GAPI_ITT_AUTO_TRACE_GUARD(ov_cb_post_outputs_hndl); + + ctx->eptr = std::move(eptr); + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + // NB: Copy data back only if execution finished sucessfuly. + // Otherwise just post outputs to keep streaming executor contract. + if (!ctx->eptr) { + const auto& out_name = ctx->uu.params.output_names[i]; + copyFromOV(infer_request.get_tensor(out_name), + ctx->outMatR(i)); + } + auto output = ctx->output(i); + ctx->out.meta(output, ctx->getMeta()); + ctx->out.post(std::move(output), ctx->eptr); + } +} + +namespace cv { +namespace gimpl { +namespace ov { + +template +using AttrMap = cv::gapi::ov::detail::AttrMap; + +template +using LayerVariantAttr = cv::gapi::ov::detail::LayerVariantAttr; + +template AttrMap +broadcastLayerAttr(const LayerVariantAttr &layer_attr, + const std::vector &layer_names) { + AttrMap map; + if (cv::util::holds_alternative>(layer_attr)) { + map = cv::util::get>(layer_attr); + // NB: Validate map: + std::unordered_set existing_layers = + {layer_names.begin(), layer_names.end()}; + + for (const auto &p : map) { + const auto it = existing_layers.find(p.first); + if (it == existing_layers.end()) { + cv::util::throw_error( + std::logic_error("OV Backend: Failed to" + " find layer with name: " + p.first)); + } + } + } else if (cv::util::holds_alternative(layer_attr)) { + // NB: Broadcast value to all layers. + auto elem = cv::util::get(layer_attr); + for (auto &&layer_name : layer_names) { + map.emplace(layer_name, elem); + } + } + return map; +} + +template +cv::optional lookUp(const std::map &map, const K& key) { + const auto it = map.find(key); + if (it == map.end()) { + return {}; + } + return cv::util::make_optional(std::move(it->second)); +} + +static bool isImage(const cv::GMatDesc &desc, + const ::ov::Shape &model_shape) { + return (model_shape.size() == 4u) && + (!desc.isND()) /* dims == 2 */ && + (desc.chan == 1 || desc.chan == 3) && + (desc.size.height != 1 && desc.size.width != 1) && + (desc.depth == CV_8U); +} + +struct Infer: public cv::detail::KernelTag { + using API = cv::GInferBase; + static cv::gapi::GBackend backend() { return cv::gapi::ov::backend(); } + static KImpl kernel() { return KImpl{outMeta, run}; } + + static cv::GMetaArgs outMeta(const ade::Graph &gr, + const ade::NodeHandle &nh, + const cv::GMetaArgs &in_metas, + const cv::GArgs &/*in_args*/) { + cv::GMetaArgs result; + + GConstGOVModel gm(gr); + const auto &uu = gm.metadata(nh).get(); + // Initialize input information + // Note our input layers list order matches the API order and so + // meta order. + GAPI_Assert(uu.params.input_names.size() == in_metas.size() + && "Known input layers count doesn't match input meta count"); + + // NB: Pre/Post processing configuration avaiable only for read models. + if (cv::util::holds_alternative(uu.params.kind)) { + const auto &model_info = cv::util::get(uu.params.kind); + const auto new_shapes = + broadcastLayerAttr(model_info.new_shapes, + uu.params.input_names); + const_cast&>(uu.model)->reshape(toOV(new_shapes)); + + const auto input_tensor_layout = + broadcastLayerAttr(model_info.input_tensor_layout, + uu.params.input_names); + const auto input_model_layout = + broadcastLayerAttr(model_info.input_model_layout, + uu.params.input_names); + + const auto interpolation = broadcastLayerAttr(model_info.interpolation, + uu.params.input_names); + const auto mean_values = broadcastLayerAttr(model_info.mean_values, + uu.params.input_names); + const auto scale_values = broadcastLayerAttr(model_info.scale_values, + uu.params.input_names); + // FIXME: Pre/Post processing step shouldn't be configured in this method. + ::ov::preprocess::PrePostProcessor ppp(uu.model); + for (auto &&it : ade::util::zip(ade::util::toRange(uu.params.input_names), + ade::util::toRange(in_metas))) { + const auto &mm = std::get<1>(it); + GAPI_Assert(cv::util::holds_alternative(mm)); + const auto &matdesc = cv::util::get(mm); + + const auto &input_name = std::get<0>(it); + auto &input_info = ppp.input(input_name); + input_info.tensor().set_element_type(toOV(matdesc.depth)); + + const auto explicit_in_model_layout = lookUp(input_model_layout, input_name); + if (explicit_in_model_layout) { + input_info.model().set_layout(::ov::Layout(*explicit_in_model_layout)); + } + const auto explicit_in_tensor_layout = lookUp(input_tensor_layout, input_name); + if (explicit_in_tensor_layout) { + input_info.tensor().set_layout(::ov::Layout(*explicit_in_tensor_layout)); + } + const auto explicit_resize = lookUp(interpolation, input_name); + // NB: Note that model layout still can't be empty. + // e.g If model converted to IRv11 without any additional + // info about layout via Model Optimizer. + const auto model_layout = ::ov::layout::get_layout(uu.model->input(input_name)); + const auto &input_shape = uu.model->input(input_name).get_shape(); + if (isImage(matdesc, input_shape)) { + // NB: Image case - all necessary preprocessng is configured automatically. + GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is image."); + // NB: Layout is already set just double check that + // user provided the correct one. In fact, there is only one correct for image. + if (explicit_in_tensor_layout && + *explicit_in_tensor_layout != "NHWC") { + std::stringstream ss; + ss << "OV Backend: Provided tensor layout " << *explicit_in_tensor_layout + << " is not compatible with input data " << matdesc << " for layer \"" + << input_name << "\". Expecting NHWC"; + util::throw_error(std::logic_error(ss.str())); + } + input_info.tensor().set_layout(::ov::Layout("NHWC")); + input_info.tensor().set_spatial_static_shape(matdesc.size.height, + matdesc.size.width); + // NB: Even though resize is automatically configured + // user have an opportunity to specify the interpolation algorithm. + auto interp = explicit_resize + ? toOVInterp(*explicit_resize) + : ::ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR; + input_info.preprocess().resize(interp); + } else { + // NB: Tensor case - resize or layout conversions must be explicitly specified. + GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is tensor."); + if (explicit_resize) { + if (matdesc.isND()) { + // NB: ND case - need to obtain "H" and "W" positions + // in order to configure resize. + if (!explicit_in_tensor_layout && model_layout.empty()) { + std::stringstream ss; + ss << "Resize for input layer: " << input_name + << "can't be configured." + << " Failed to extract H and W positions from layout."; + util::throw_error(std::logic_error(ss.str())); + } else { + const auto layout = explicit_in_tensor_layout + ? ::ov::Layout(*explicit_in_tensor_layout) : model_layout; + auto H_idx = ::ov::layout::height_idx(layout); + auto W_idx = ::ov::layout::width_idx(layout); + // NB: If layout is "...HW", H position is -2. + if (H_idx < 0) H_idx = matdesc.dims.size() + H_idx; + if (W_idx < 0) W_idx = matdesc.dims.size() + W_idx; + GAPI_Assert(H_idx >= 0 && H_idx < static_cast(matdesc.dims.size())); + GAPI_Assert(W_idx >= 0 && W_idx < static_cast(matdesc.dims.size())); + input_info.tensor().set_spatial_static_shape(matdesc.dims[H_idx], + matdesc.dims[W_idx]); + input_info.preprocess().resize(toOVInterp(*explicit_resize)); + } + } else { + // NB: 2D case - We know exactly where H and W... + input_info.tensor().set_spatial_static_shape(matdesc.size.height, + matdesc.size.width); + input_info.preprocess().resize(toOVInterp(*explicit_resize)); + } + } + } + // NB: Apply mean/scale as the last step of the preprocessing. + // Note that this can be applied to any input data if the + // position of "C" dimension is known. + const auto mean_vec = lookUp(mean_values, input_name); + if (mean_vec) { + input_info.preprocess().mean(*mean_vec); + } + + const auto scale_vec = lookUp(scale_values, input_name); + if (scale_vec) { + input_info.preprocess().scale(*scale_vec); + } + } + + const auto output_tensor_layout = + broadcastLayerAttr(model_info.output_tensor_layout, + uu.params.output_names); + const auto output_model_layout = + broadcastLayerAttr(model_info.output_model_layout, + uu.params.output_names); + const auto output_tensor_precision = + broadcastLayerAttr(model_info.output_tensor_precision, + uu.params.output_names); + + for (const auto &output_name : uu.params.output_names) { + const auto explicit_out_tensor_layout = + lookUp(output_tensor_layout, output_name); + if (explicit_out_tensor_layout) { + ppp.output(output_name).tensor() + .set_layout(::ov::Layout(*explicit_out_tensor_layout)); + } + + const auto explicit_out_model_layout = + lookUp(output_model_layout, output_name); + if (explicit_out_model_layout) { + ppp.output(output_name).model() + .set_layout(::ov::Layout(*explicit_out_model_layout)); + } + + const auto explicit_out_tensor_prec = + lookUp(output_tensor_precision, output_name); + if (explicit_out_tensor_prec) { + ppp.output(output_name).tensor() + .set_element_type(toOV(*explicit_out_tensor_prec)); + } + } + + GAPI_LOG_DEBUG(NULL, "OV Backend: PrePostProcessor: " << ppp); + const_cast&>(uu.model) = ppp.build(); + } + + for (const auto &out_name : uu.params.output_names) { + cv::GMatDesc outm; + if (cv::util::holds_alternative(uu.params.kind)) { + const auto &out = uu.model->output(out_name); + outm = cv::GMatDesc(toCV(out.get_element_type()), + toCV(out.get_shape())); + } else { + GAPI_Assert(cv::util::holds_alternative(uu.params.kind)); + const auto &out = uu.compiled_model.output(out_name); + outm = cv::GMatDesc(toCV(out.get_element_type()), + toCV(out.get_shape())); + } + result.emplace_back(std::move(outm)); + } + + return result; + } + + static void run(std::shared_ptr ctx, + cv::gimpl::ov::RequestPool &reqPool) { + using namespace std::placeholders; + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { + [ctx](::ov::InferRequest &infer_request) { + for (auto i : ade::util::iota(ctx->uu.params.num_in)) { + const auto& input_name = ctx->uu.params.input_names[i]; + auto input_tensor = infer_request.get_tensor(input_name); + // TODO: In some cases wrapping existing data pointer + // might be faster than copy. Make it a strategy. + copyToOV(ctx->inMat(i), input_tensor); + } + }, + std::bind(PostOutputs, _1, _2, ctx) + } + ); + } +}; + +} // namespace ov +} // namespace gimpl +} // namespace cv + +// IE backend implementation of GBackend::Priv /////////////////////// +namespace { +class GOVBackendImpl final: public cv::gapi::GBackend::Priv { + virtual void unpackKernel(ade::Graph &gr, + const ade::NodeHandle &nh, + const cv::GKernelImpl &ii) override { + using namespace cv::gimpl; + // FIXME: Introduce a DNNBackend interface which'd specify + // the framework for this??? + GOVModel gm(gr); + auto &np = gm.metadata(nh).get(); + auto &pp = cv::util::any_cast(np.opaque); + const auto &ki = cv::util::any_cast(ii.opaque); + + GModel::Graph model(gr); + auto& op = model.metadata(nh).get(); + + // NB: In case generic infer, info about in/out names is stored in operation (op.params) + if (pp.is_generic) + { + auto& info = cv::util::any_cast(op.params); + pp.input_names = info.in_names; + pp.output_names = info.out_names; + pp.num_in = info.in_names.size(); + pp.num_out = info.out_names.size(); + } + + gm.metadata(nh).set(OVUnit{pp}); + gm.metadata(nh).set(OVCallable{ki.run}); + gm.metadata(nh).set(CustomMetaFunction{ki.customMetaFunc}); + } + + virtual EPtr compile(const ade::Graph &graph, + const cv::GCompileArgs &, + const std::vector &nodes) const override { + return EPtr{new cv::gimpl::ov::GOVExecutable(graph, nodes)}; + } + + virtual cv::GKernelPackage auxiliaryKernels() const override { + return cv::gapi::kernels< cv::gimpl::ov::Infer >(); + } + + virtual bool controlsMerge() const override { + return true; + } + + virtual bool allowsMerge(const cv::gimpl::GIslandModel::Graph &, + const ade::NodeHandle &, + const ade::NodeHandle &, + const ade::NodeHandle &) const override { + return false; + } +}; + +} // anonymous namespace + +cv::gapi::GBackend cv::gapi::ov::backend() { + static cv::gapi::GBackend this_backend(std::make_shared()); + return this_backend; +} + +static std::vector<::ov::InferRequest> +createInferRequests(::ov::CompiledModel &compiled_model, + size_t num_infer_requests) { + std::vector<::ov::InferRequest> infer_requests; + for (size_t i = 0; i < num_infer_requests; ++i) { + infer_requests.push_back(compiled_model.create_infer_request()); + } + return infer_requests; +} + +// GOVExecutable implementation ////////////////////////////////////////////// +cv::gimpl::ov::GOVExecutable::GOVExecutable(const ade::Graph &g, + const std::vector &nodes) + : m_g(g), m_gm(m_g) { + + // FIXME: Currently this backend is capable to run a single inference node only. + // Need to extend our island fusion with merge/not-to-merge decision making parametrization + GConstGOVModel ovm(g); + + for (auto &nh : nodes) { + switch (m_gm.metadata(nh).get().t) { + case NodeType::OP: + if (this_nh == nullptr) { + this_nh = nh; + compiled = const_cast(ovm.metadata(this_nh).get()).compile(); + m_reqPool.reset(new RequestPool(createInferRequests(compiled.compiled_model, 1))); + } + else + util::throw_error(std::logic_error("Multi-node inference is not supported!")); + break; + + case NodeType::DATA: { + m_dataNodes.push_back(nh); + const auto &desc = m_gm.metadata(nh).get(); + if (desc.storage == Data::Storage::CONST_VAL) { + util::throw_error(std::logic_error("No const data please!")); + } + if (desc.storage == Data::Storage::INTERNAL) { + util::throw_error(std::logic_error("No internal data please!")); + } + break; + } + default: util::throw_error(std::logic_error("Unsupported NodeType type")); + } + } +} + +void cv::gimpl::ov::GOVExecutable::run(cv::gimpl::GIslandExecutable::IInput &in, + cv::gimpl::GIslandExecutable::IOutput &out) { + std::vector input_objs; + std::vector output_objs; + + const auto &in_desc = in.desc(); + auto in_msg = in.get(); + + if (cv::util::holds_alternative(in_msg)) + { + out.post(cv::gimpl::EndOfStream{}); + return; + } + + GAPI_Assert(cv::util::holds_alternative(in_msg)); + const auto in_vector = cv::util::get(in_msg); + cv::GRunArg::Meta stub_meta; + for (auto &&in_arg : in_vector) + { + stub_meta.insert(in_arg.meta.begin(), in_arg.meta.end()); + } + + input_objs.reserve(in_desc.size()); + for (auto &&it: ade::util::zip(ade::util::toRange(in_desc), + ade::util::toRange(in_vector))) + { + input_objs.emplace_back(std::get<0>(it), std::get<1>(it)); + } + + const auto &out_desc = out.desc(); + output_objs.reserve(out_desc.size()); + for (auto &&it: ade::util::indexed(ade::util::toRange(out_desc))) + { + output_objs.emplace_back(ade::util::value(it), + out.get(ade::util::checked_cast(ade::util::index(it)))); + } + + GConstGOVModel giem(m_g); + const auto &uu = giem.metadata(this_nh).get(); + const auto &op = m_gm.metadata(this_nh).get(); + + auto ctx = std::make_shared(uu, out, op.args, op.outs, + std::move(stub_meta), std::move(input_objs), std::move(output_objs)); + + const auto &kk = giem.metadata(this_nh).get(); + + try { + kk.run(ctx, *m_reqPool); + } catch (...) { + auto eptr = std::current_exception(); + for (auto i : ade::util::iota(ctx->uu.params.num_out)) + { + auto output = ctx->output(i); + ctx->out.meta(output, ctx->getMeta()); + ctx->out.post(std::move(output), eptr); + } + return; + } + + if (!m_gm.metadata().contains()) { + m_reqPool->waitAll(); + } +} + +#else // HAVE_INF_ENGINE + +cv::gapi::GBackend cv::gapi::ov::backend() { + // Still provide this symbol to avoid linking issues + util::throw_error(std::runtime_error("G-API has been compiled without OpenVINO support")); +} + +#endif // HAVE_INF_ENGINE diff --git a/modules/gapi/src/backends/ov/govbackend.hpp b/modules/gapi/src/backends/ov/govbackend.hpp new file mode 100644 index 0000000000..85e584e7f3 --- /dev/null +++ b/modules/gapi/src/backends/ov/govbackend.hpp @@ -0,0 +1,66 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2023 Intel Corporation + +#ifndef OPENCV_GAPI_GOVBACKEND_HPP +#define OPENCV_GAPI_GOVBACKEND_HPP + +// Include anyway - cv::gapi::ov::backend() still needs to be defined +#include "opencv2/gapi/infer/ov.hpp" + +#ifdef HAVE_INF_ENGINE + +#include + +#include "backends/common/gbackend.hpp" + +namespace cv { +namespace gimpl { +namespace ov { + +struct OVCompiled { + ::ov::CompiledModel compiled_model; +}; + +class RequestPool; + +class GOVExecutable final: public GIslandExecutable +{ + const ade::Graph &m_g; + GModel::ConstGraph m_gm; + + // The only executable stuff in this graph + // (assuming it is always single-op) + ade::NodeHandle this_nh; + OVCompiled compiled; + + // List of all resources in graph (both internal and external) + std::vector m_dataNodes; + + // To manage multiple async requests + std::unique_ptr m_reqPool; + +public: + GOVExecutable(const ade::Graph &graph, + const std::vector &nodes); + + virtual inline bool canReshape() const override { return false; } + virtual inline void reshape(ade::Graph&, const GCompileArgs&) override { + GAPI_Error("InternalError"); // Not implemented yet + } + + virtual void run(std::vector &&, + std::vector &&) override { + GAPI_Error("Not implemented"); + } + + virtual void run(GIslandExecutable::IInput &in, + GIslandExecutable::IOutput &out) override; +}; + +}}} + +#endif // HAVE_INF_ENGINE +#endif // OPENCV_GAPI_GOVBACKEND_HPP diff --git a/modules/gapi/src/backends/ov/util.hpp b/modules/gapi/src/backends/ov/util.hpp new file mode 100644 index 0000000000..07e6c08f6b --- /dev/null +++ b/modules/gapi/src/backends/ov/util.hpp @@ -0,0 +1,35 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2023 Intel Corporation + +#ifndef OPENCV_GAPI_INFER_OV_UTIL_HPP +#define OPENCV_GAPI_INFER_OV_UTIL_HPP + +#ifdef HAVE_INF_ENGINE + +// NOTE: This file is not included by default in infer/ov.hpp +// and won't be. infer/ov.hpp doesn't depend on OV headers itself. +// This file does -- so needs to be included separately by those who care. + +#include + +#include // GAPI_EXPORTS +#include // GKernelPackage + +namespace cv { +namespace gapi { +namespace ov { +namespace util { + +// NB: These functions are EXPORTed to make them accessible by the +// test suite only. +GAPI_EXPORTS std::vector to_ocv(const ::ov::Shape &shape); +GAPI_EXPORTS int to_ocv(const ::ov::element::Type &type); + +}}}} + +#endif // HAVE_INF_ENGINE + +#endif // OPENCV_GAPI_INFER_OV_UTIL_HPP diff --git a/modules/gapi/test/infer/gapi_infer_ov_tests.cpp b/modules/gapi/test/infer/gapi_infer_ov_tests.cpp new file mode 100644 index 0000000000..ecf5ab1a8c --- /dev/null +++ b/modules/gapi/test/infer/gapi_infer_ov_tests.cpp @@ -0,0 +1,540 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2023 Intel Corporation + +#ifdef HAVE_INF_ENGINE + +#include "../test_precomp.hpp" + +#include "backends/ov/util.hpp" + +#include + +#include + +namespace opencv_test +{ + +namespace { +// FIXME: taken from DNN module +void initDLDTDataPath() +{ +#ifndef WINRT + static bool initialized = false; + if (!initialized) + { + const char* omzDataPath = getenv("OPENCV_OPEN_MODEL_ZOO_DATA_PATH"); + if (omzDataPath) + cvtest::addDataSearchPath(omzDataPath); + const char* dnnDataPath = getenv("OPENCV_DNN_TEST_DATA_PATH"); + if (dnnDataPath) { + // Add the dnnDataPath itself - G-API is using some images there directly + cvtest::addDataSearchPath(dnnDataPath); + cvtest::addDataSearchPath(dnnDataPath + std::string("/omz_intel_models")); + } + initialized = true; + } +#endif // WINRT +} + +static const std::string SUBDIR = "intel/age-gender-recognition-retail-0013/FP32/"; + +void copyFromOV(ov::Tensor &tensor, cv::Mat &mat) { + GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize()); + std::copy_n(reinterpret_cast(tensor.data()), + tensor.get_byte_size(), + mat.ptr()); +} + +void copyToOV(const cv::Mat &mat, ov::Tensor &tensor) { + GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize()); + std::copy_n(mat.ptr(), + tensor.get_byte_size(), + reinterpret_cast(tensor.data())); +} + +// FIXME: taken from the DNN module +void normAssert(cv::InputArray ref, cv::InputArray test, + const char *comment /*= ""*/, + double l1 = 0.00001, double lInf = 0.0001) { + double normL1 = cvtest::norm(ref, test, cv::NORM_L1) / ref.getMat().total(); + EXPECT_LE(normL1, l1) << comment; + + double normInf = cvtest::norm(ref, test, cv::NORM_INF); + EXPECT_LE(normInf, lInf) << comment; +} + +ov::Core getCore() { + static ov::Core core; + return core; +} + +// TODO: AGNetGenComp, AGNetTypedComp, AGNetOVComp, AGNetOVCompiled +// can be generalized to work with any model and used as parameters for tests. + +struct AGNetGenComp { + static constexpr const char* tag = "age-gender-generic"; + using Params = cv::gapi::ov::Params; + + static Params params(const std::string &xml, + const std::string &bin, + const std::string &device) { + return {tag, xml, bin, device}; + } + + static Params params(const std::string &blob_path, + const std::string &device) { + return {tag, blob_path, device}; + } + + static cv::GComputation create() { + cv::GMat in; + GInferInputs inputs; + inputs["data"] = in; + auto outputs = cv::gapi::infer(tag, inputs); + auto age = outputs.at("age_conv3"); + auto gender = outputs.at("prob"); + return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)}; + } +}; + +struct AGNetTypedComp { + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "typed-age-gender"); + using Params = cv::gapi::ov::Params; + + static Params params(const std::string &xml_path, + const std::string &bin_path, + const std::string &device) { + return Params { + xml_path, bin_path, device + }.cfgOutputLayers({ "age_conv3", "prob" }); + } + + static cv::GComputation create() { + cv::GMat in; + cv::GMat age, gender; + std::tie(age, gender) = cv::gapi::infer(in); + return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)}; + } +}; + +class AGNetOVCompiled { +public: + AGNetOVCompiled(ov::CompiledModel &&compiled_model) + : m_compiled_model(std::move(compiled_model)) { + } + + void operator()(const cv::Mat &in_mat, + cv::Mat &age_mat, + cv::Mat &gender_mat) { + auto infer_request = m_compiled_model.create_infer_request(); + auto input_tensor = infer_request.get_input_tensor(); + copyToOV(in_mat, input_tensor); + + infer_request.infer(); + + auto age_tensor = infer_request.get_tensor("age_conv3"); + age_mat.create(cv::gapi::ov::util::to_ocv(age_tensor.get_shape()), + cv::gapi::ov::util::to_ocv(age_tensor.get_element_type())); + copyFromOV(age_tensor, age_mat); + + auto gender_tensor = infer_request.get_tensor("prob"); + gender_mat.create(cv::gapi::ov::util::to_ocv(gender_tensor.get_shape()), + cv::gapi::ov::util::to_ocv(gender_tensor.get_element_type())); + copyFromOV(gender_tensor, gender_mat); + } + + void export_model(const std::string &outpath) { + std::ofstream file{outpath, std::ios::out | std::ios::binary}; + GAPI_Assert(file.is_open()); + m_compiled_model.export_model(file); + } + +private: + ov::CompiledModel m_compiled_model; +}; + +struct ImageInputPreproc { + void operator()(ov::preprocess::PrePostProcessor &ppp) { + ppp.input().tensor().set_layout(ov::Layout("NHWC")) + .set_element_type(ov::element::u8) + .set_shape({1, size.height, size.width, 3}); + ppp.input().model().set_layout(ov::Layout("NCHW")); + ppp.input().preprocess().resize(::ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR); + } + + cv::Size size; +}; + +class AGNetOVComp { +public: + AGNetOVComp(const std::string &xml_path, + const std::string &bin_path, + const std::string &device) + : m_device(device) { + m_model = getCore().read_model(xml_path, bin_path); + } + + using PrePostProcessF = std::function; + + void cfgPrePostProcessing(PrePostProcessF f) { + ov::preprocess::PrePostProcessor ppp(m_model); + f(ppp); + m_model = ppp.build(); + } + + AGNetOVCompiled compile() { + auto compiled_model = getCore().compile_model(m_model, m_device); + return {std::move(compiled_model)}; + } + + void apply(const cv::Mat &in_mat, + cv::Mat &age_mat, + cv::Mat &gender_mat) { + compile()(in_mat, age_mat, gender_mat); + } + +private: + std::string m_device; + std::shared_ptr m_model; +}; + +} // anonymous namespace + +// TODO: Make all of tests below parmetrized to avoid code duplication +TEST(TestAgeGenderOV, InferTypedTensor) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat({1, 3, 62, 62}, CV_32F); + cv::randu(in_mat, -1, 1); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetTypedComp::create(); + auto pp = AGNetTypedComp::params(xml_path, bin_path, device); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferTypedImage) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat(300, 300, CV_8UC3); + cv::randu(in_mat, 0, 255); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()}); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetTypedComp::create(); + auto pp = AGNetTypedComp::params(xml_path, bin_path, device); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferGenericTensor) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat({1, 3, 62, 62}, CV_32F); + cv::randu(in_mat, -1, 1); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(xml_path, bin_path, device); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferGenericImage) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat(300, 300, CV_8UC3); + cv::randu(in_mat, 0, 255); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()}); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(xml_path, bin_path, device); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferGenericImageBlob) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string blob_path = "age-gender-recognition-retail-0013.blob"; + const std::string device = "CPU"; + + cv::Mat in_mat(300, 300, CV_8UC3); + cv::randu(in_mat, 0, 255); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()}); + auto cc_ref = ref.compile(); + // NB: Output blob will contain preprocessing inside. + cc_ref.export_model(blob_path); + cc_ref(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(blob_path, device); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferGenericTensorBlob) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string blob_path = "age-gender-recognition-retail-0013.blob"; + const std::string device = "CPU"; + + cv::Mat in_mat({1, 3, 62, 62}, CV_32F); + cv::randu(in_mat, -1, 1); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + auto cc_ref = ref.compile(); + cc_ref.export_model(blob_path); + cc_ref(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(blob_path, device); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferBothOutputsFP16) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat({1, 3, 62, 62}, CV_32F); + cv::randu(in_mat, -1, 1); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp){ + ppp.output(0).tensor().set_element_type(ov::element::f16); + ppp.output(1).tensor().set_element_type(ov::element::f16); + }); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(xml_path, bin_path, device); + pp.cfgOutputTensorPrecision(CV_16F); + + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, InferOneOutputFP16) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat({1, 3, 62, 62}, CV_32F); + cv::randu(in_mat, -1, 1); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + const std::string fp16_output_name = "prob"; + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing([&](ov::preprocess::PrePostProcessor &ppp){ + ppp.output(fp16_output_name).tensor().set_element_type(ov::element::f16); + }); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(xml_path, bin_path, device); + pp.cfgOutputTensorPrecision({{fp16_output_name, CV_16F}}); + + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderOV, ThrowCfgOutputPrecForBlob) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string blob_path = "age-gender-recognition-retail-0013.blob"; + const std::string device = "CPU"; + + // OpenVINO (Just for blob compilation) + AGNetOVComp ref(xml_path, bin_path, device); + auto cc_ref = ref.compile(); + cc_ref.export_model(blob_path); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(blob_path, device); + + EXPECT_ANY_THROW(pp.cfgOutputTensorPrecision(CV_16F)); +} + +TEST(TestAgeGenderOV, ThrowInvalidConfigIR) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(xml_path, bin_path, device); + pp.cfgPluginConfig({{"some_key", "some_value"}}); + + EXPECT_ANY_THROW(comp.compile(cv::GMatDesc{CV_8U,3,cv::Size{320, 240}}, + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST(TestAgeGenderOV, ThrowInvalidConfigBlob) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string blob_path = "age-gender-recognition-retail-0013.blob"; + const std::string device = "CPU"; + + // OpenVINO (Just for blob compilation) + AGNetOVComp ref(xml_path, bin_path, device); + auto cc_ref = ref.compile(); + cc_ref.export_model(blob_path); + + // G-API + auto comp = AGNetGenComp::create(); + auto pp = AGNetGenComp::params(blob_path, device); + pp.cfgPluginConfig({{"some_key", "some_value"}}); + + EXPECT_ANY_THROW(comp.compile(cv::GMatDesc{CV_8U,3,cv::Size{320, 240}}, + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST(TestAgeGenderOV, ThrowInvalidImageLayout) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + // NB: This mat may only have "NHWC" layout. + cv::Mat in_mat(300, 300, CV_8UC3); + cv::randu(in_mat, 0, 255); + cv::Mat gender, gapi_age, gapi_gender; + auto comp = AGNetTypedComp::create(); + auto pp = AGNetTypedComp::params(xml_path, bin_path, device); + + pp.cfgInputTensorLayout("NCHW"); + + EXPECT_ANY_THROW(comp.compile(cv::descr_of(in_mat), + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST(TestAgeGenderOV, InferTensorWithPreproc) { + initDLDTDataPath(); + const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + const std::string device = "CPU"; + + cv::Mat in_mat({1, 240, 320, 3}, CV_32F); + cv::randu(in_mat, -1, 1); + cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp) { + auto& input = ppp.input(); + input.tensor().set_spatial_static_shape(240, 320) + .set_layout("NHWC"); + input.preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR); + }); + ref.apply(in_mat, ov_age, ov_gender); + + // G-API + auto comp = AGNetTypedComp::create(); + auto pp = AGNetTypedComp::params(xml_path, bin_path, device); + pp.cfgResize(cv::INTER_LINEAR) + .cfgInputTensorLayout("NHWC"); + + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); +} + +} // namespace opencv_test + +#endif // HAVE_INF_ENGINE diff --git a/modules/python/src2/cv2_convert.hpp b/modules/python/src2/cv2_convert.hpp index eae20b2c98..73ef10968b 100644 --- a/modules/python/src2/cv2_convert.hpp +++ b/modules/python/src2/cv2_convert.hpp @@ -6,6 +6,8 @@ #include "cv2_numpy.hpp" #include #include +#include +#include #include // std::enable_if extern PyTypeObject* pyopencv_Mat_TypePtr; @@ -263,6 +265,38 @@ PyObject* pyopencv_from(const std::vector& value) return pyopencvVecConverter::from(value); } +template +bool pyopencv_to(PyObject *obj, std::map &map, const ArgInfo& info) +{ + PyObject* py_key = nullptr; + PyObject* py_value = nullptr; + Py_ssize_t pos = 0; + + if (!PyDict_Check(obj)) { + failmsg("Can't parse '%s'. Input argument isn't dict or" + " an instance of subtype of the dict type", info.name); + return false; + } + + while(PyDict_Next(obj, &pos, &py_key, &py_value)) + { + K cpp_key; + if (!pyopencv_to(py_key, cpp_key, ArgInfo("key", false))) { + failmsg("Can't parse dict key. Key on position %lu has a wrong type", pos); + return false; + } + + V cpp_value; + if (!pyopencv_to(py_value, cpp_value, ArgInfo("value", false))) { + failmsg("Can't parse dict value. Value on position %lu has a wrong type", pos); + return false; + } + + map.emplace(cpp_key, cpp_value); + } + return true; +} + template static bool pyopencv_to_generic_vec(PyObject* obj, std::vector& value, const ArgInfo& info) { diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index 36f0eac9eb..39a641c021 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -190,6 +190,14 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) ), export_name="SearchParams"), + AliasTypeNode.dict_("map_string_and_string", PrimitiveTypeNode.str_("map_string_and_string::key"), + PrimitiveTypeNode.str_("map_string_and_string::key::value")), + AliasTypeNode.dict_("map_string_and_int", PrimitiveTypeNode.str_("map_string_and_int::key"), + PrimitiveTypeNode.int_("map_string_and_int::key::value")), + AliasTypeNode.dict_("map_string_and_vector_size_t", PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"), + SequenceTypeNode("map_string_and_vector_size_t::key::value", PrimitiveTypeNode.int_("size_t"))), + AliasTypeNode.dict_("map_string_and_vector_float", PrimitiveTypeNode.str_("map_string_and_vector_float::key"), + SequenceTypeNode("map_string_and_vector_float::key::value", PrimitiveTypeNode.float_())), ) PREDEFINED_TYPES = dict(zip((t.ctype_name for t in _PREDEFINED_TYPES), _PREDEFINED_TYPES))