// 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" #if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000 #include "backends/ov/util.hpp" #include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK! #include "logger.hpp" #include #include #include // getConfigurationParameterBool #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; // NB: Some of OV plugins fail during ov::Core destroying in specific cases. // Solution is allocate ov::Core in heap and doesn't destroy it, which cause // leak, but fixes tests on CI. This behaviour is configurable by using // OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND=0 static ov::Core create_OV_Core_pointer() { // NB: 'delete' is never called static ov::Core* core = new ov::Core(); return *core; } static ov::Core create_OV_Core_instance() { static ov::Core core; return core; } ov::Core cv::gapi::ov::wrap::getCore() { // NB: to make happy memory leak tools use: // - OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND=0 static bool param_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND = utils::getConfigurationParameterBool( "OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND", #if defined(_WIN32) || defined(__APPLE__) true #else false #endif ); return param_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND ? create_OV_Core_pointer() : create_OV_Core_instance(); } 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 (toCV(tensor.get_element_type()) != 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 cv::Mat wrapOV(const cv::MediaFrame::View& view, const cv::GFrameDesc& desc) { cv::Mat out; switch (desc.fmt) { case cv::MediaFormat::BGR: { out = cv::Mat(desc.size, CV_8UC3, view.ptr[0], view.stride[0]); return out; } case cv::MediaFormat::NV12: { auto y_plane = cv::Mat(desc.size, CV_8UC1, view.ptr[0], view.stride[0]); auto uv_plane = cv::Mat(desc.size / 2, CV_8UC2, view.ptr[1], view.stride[1]); cvtColorTwoPlane(y_plane, uv_plane, out, cv::COLOR_YUV2BGR_NV12); return out; } case cv::MediaFormat::GRAY: { out = cv::Mat(desc.size, CV_8UC1, view.ptr[0], view.stride[0]); return out; } default: GAPI_Error("OV Backend: Unsupported media format"); } return out; } 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 (toCV(tensor.get_element_type()) != 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())); } } static void copyToOV(const cv::MediaFrame &frame, ov::Tensor &tensor) { const auto view = cv::MediaFrame::View(frame.access(cv::MediaFrame::Access::R)); auto matFromFrame = wrapOV(view, frame.desc()); copyToOV(matFromFrame, tensor); } 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); } void cv::gapi::ov::util::to_ov(const cv::Mat &mat, ::ov::Tensor &tensor) { copyToOV(mat, tensor); } void cv::gapi::ov::util::to_ocv(const ::ov::Tensor &tensor, cv::Mat &mat) { copyFromOV(tensor, mat); } 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 = cv::gapi::ov::wrap::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 = cv::gapi::ov::wrap::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 = cv::gapi::ov::wrap::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::gimpl::ov::Options & options); 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; const cv::MediaFrame& inFrame (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; }; const cv::gimpl::ov::Options& getOptions() const { return m_options; }; 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; cv::gimpl::ov::Options m_options; }; 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, const cv::gimpl::ov::Options & options) : uu(unit), out(output), m_meta(std::move(meta)), m_input_objs(std::move(input_objs)), m_output_objs(std::move(output_objs)), m_options(options) { 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); } const cv::MediaFrame& OVCallContext::inFrame(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]); // Note: .at() is intentional for GArray as object MUST be already there // (and constructed by either bindIn/Out or resetInternal) case cv::GShape::GARRAY: return cv::GArg(m_res.slot().at(ref.id)); // Note: .at() is intentional for GOpaque as object MUST be already there // (and constructed by either bindIn/Out or resetInternal) case cv::GShape::GOPAQUE: return cv::GArg(m_res.slot().at(ref.id)); case cv::GShape::GFRAME: 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 successfully // and inference only mode is disabled. // Otherwise just post outputs to maintain streaming executor contract. if (!ctx->eptr && !ctx->getOptions().inference_only) { 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); } } class PostOutputsList { public: PostOutputsList(size_t size, std::shared_ptr ctx); void operator()(::ov::InferRequest &infer_request, std::exception_ptr eptr, size_t pos) const; private: struct Priv { std::atomic finished{0u}; size_t size; std::shared_ptr ctx; }; std::shared_ptr m_priv; }; PostOutputsList::PostOutputsList(size_t size, std::shared_ptr ctx) : m_priv(new Priv{}) { m_priv->size = size; m_priv->ctx = ctx; } void PostOutputsList::operator()(::ov::InferRequest &infer_request, std::exception_ptr eptr, size_t pos) const { auto&& ctx = m_priv->ctx; auto&& finished = m_priv->finished; auto&& size = m_priv->size; ctx->eptr = eptr; if (!ctx->eptr) { for (auto i : ade::util::iota(ctx->uu.params.num_out)) { std::vector &out_vec = ctx->outVecR(i); const auto &out_name = ctx->uu.params.output_names[i]; const auto &out_tensor = infer_request.get_tensor(out_name); out_vec[pos].create(toCV(out_tensor.get_shape()), toCV(out_tensor.get_element_type())); copyFromOV(out_tensor, out_vec[pos]); } } ++finished; if (finished == size) { 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), ctx->eptr); } } } static void copyToOV(std::shared_ptr ctx, uint32_t input_idx, ov::Tensor &tensor) { switch (ctx->inShape(input_idx)) { case cv::GShape::GMAT: copyToOV(ctx->inMat(input_idx), tensor); break; case cv::GShape::GFRAME: copyToOV(ctx->inFrame(input_idx), tensor); break; default: GAPI_Assert("Unsupported input shape for OV backend"); } } 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)); } // NB: This function is used to preprocess input image // for InferROI, InferList, InferList2 kernels. static cv::Mat preprocess(const cv::Mat &in_mat, const cv::Rect &roi, const ::ov::Shape &model_shape) { cv::Mat out; // FIXME: Since there is no information about H and W positions // among tensor dimmensions assume that model layout is "NHWC". // (In fact "NHWC" is the only right layout for preprocessing because // it works only with images. GAPI_Assert(model_shape.size() == 4u); const auto H = model_shape[1]; const auto W = model_shape[2]; const auto C = model_shape[3]; // NB: Soft check that at least number of channels matches. if (static_cast(C) != in_mat.channels()) { std::stringstream ss; ss << "OV Backend: Failed to preprocess input data " " (Number of channels mismatch)." " Provided data: " << cv::descr_of(in_mat) << " and Model shape: " << model_shape; util::throw_error(std::logic_error(ss.str())); } // NB: Crop roi and resize to model size. cv::resize(in_mat(roi), out, cv::Size(W, H)); return out; } // NB: This function is used to preprocess input image // for InferROI, InferList, InferList2 kernels. static cv::Mat preprocess(MediaFrame::View& view, const cv::GFrameDesc& desc, const cv::Rect& roi, const ::ov::Shape &model_shape) { return preprocess(wrapOV(view, desc), roi, model_shape); } static void preprocess_and_copy(std::shared_ptr ctx, uint32_t input_idx, const cv::Rect &roi, const ::ov::Shape &model_shape, ::ov::Tensor& tensor) { switch (ctx->inShape(input_idx)) { case cv::GShape::GMAT: { auto roi_mat = preprocess(ctx->inMat(input_idx), roi, model_shape); copyToOV(roi_mat, tensor); break; } case cv::GShape::GFRAME: { auto currentFrame = ctx->inFrame(input_idx); auto view = cv::MediaFrame::View(currentFrame.access(cv::MediaFrame::Access::R)); auto roi_mat = preprocess(view, currentFrame.desc(), roi, model_shape); copyToOV(roi_mat, tensor); break; } default: GAPI_Assert("Unsupported input shape for OV backend"); } } 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); } static bool isImage(const cv::GMetaArg &meta, const ::ov::Shape &shape) { if (cv::util::holds_alternative(meta)) { return true; } GAPI_Assert(cv::util::holds_alternative(meta)); auto matdesc = cv::util::get(meta); return isImage(matdesc, shape); } class PrePostProcWrapper { public: PrePostProcWrapper(std::shared_ptr<::ov::Model> &model, const ParamDesc::Model &model_info, const std::vector &input_names, const std::vector &output_names) : m_ppp(model), m_model(model), m_model_info(model_info), m_input_names(input_names), m_output_names(output_names) { // NB: Do Reshape right away since it must be the first step of model modification // and applicable for all infer kernels. const auto new_shapes = broadcastLayerAttr(model_info.new_shapes, input_names); m_model->reshape(toOV(new_shapes)); const auto &mi = m_model_info; m_input_tensor_layout = broadcastLayerAttr(mi.input_tensor_layout, m_input_names); m_input_model_layout = broadcastLayerAttr(mi.input_model_layout, m_input_names); m_interpolation = broadcastLayerAttr(mi.interpolation, m_input_names); m_mean_values = broadcastLayerAttr(mi.mean_values, m_input_names); m_scale_values = broadcastLayerAttr(mi.scale_values, m_input_names); m_interpolation = broadcastLayerAttr(mi.interpolation, m_input_names); m_output_tensor_layout = broadcastLayerAttr(mi.output_tensor_layout, m_output_names); m_output_model_layout = broadcastLayerAttr(mi.output_model_layout, m_output_names); m_output_tensor_precision = broadcastLayerAttr(mi.output_tensor_precision, m_output_names); }; void cfgLayouts(const std::string &input_name) { auto &input_info = m_ppp.input(input_name); const auto explicit_in_model_layout = lookUp(m_input_model_layout, input_name); if (explicit_in_model_layout) { input_info.model().set_layout(::ov::Layout(*explicit_in_model_layout)); } else if (m_model->input(input_name).get_shape().size() == 4u) { const auto& input_layout = ::ov::layout::get_layout(m_model->input(input_name)); if (!input_layout.empty()) { GAPI_LOG_INFO(NULL, "Model input layout " << input_name << " found: " << input_layout.to_string() << "."); } else { // NB: Back compatibility with IR's without any layout information. // Note that default is only applicable for 4D inputs in order to // support auto resize for image use cases. GAPI_LOG_WARNING(NULL, "Failed to find layout for input layer \"" << input_name << "\" - NCHW is set by default"); const std::string default_layout = "NCHW"; input_info.model().set_layout(::ov::Layout(default_layout)); m_input_model_layout.emplace(input_name, default_layout); } } const auto explicit_in_tensor_layout = lookUp(m_input_tensor_layout, input_name); if (explicit_in_tensor_layout) { input_info.tensor().set_layout(::ov::Layout(*explicit_in_tensor_layout)); } } void cfgScaleMean(const std::string &input_name, const GMetaArg &input_meta) { auto &input_info = m_ppp.input(input_name); const auto mean_vec = lookUp(m_mean_values, input_name); const auto scale_vec = lookUp(m_scale_values, input_name); if (mean_vec || scale_vec) { GAPI_Assert(cv::util::holds_alternative(input_meta)); const auto depth = cv::util::get(input_meta).depth; const bool depth_is_real = (depth == CV_32F) || (depth == CV_16F); if (!depth_is_real) { input_info.preprocess().convert_element_type(toOV(CV_32F)); } } if (mean_vec) { input_info.preprocess().mean(*mean_vec); } if (scale_vec) { input_info.preprocess().scale(*scale_vec); } } // FIXME: Decompose this... void cfgPreProcessing(const std::string &input_name, const cv::GMetaArg &input_meta, const bool disable_img_resize = false) { GAPI_Assert(cv::util::holds_alternative(input_meta) || cv::util::holds_alternative(input_meta)); const auto explicit_in_tensor_layout = lookUp(m_input_tensor_layout, input_name); const auto explicit_in_model_layout = lookUp(m_input_model_layout, input_name); const auto explicit_resize = lookUp(m_interpolation, input_name); if (disable_img_resize && explicit_resize.has_value()) { std::stringstream ss; util::throw_error(std::logic_error( "OV Backend: Resize for layer \"" + input_name + "\" will be performed" " on host via OpenCV so explicitly configured resize is prohibited.")); } const auto &input_shape = m_model->input(input_name).get_shape(); auto &input_info = m_ppp.input(input_name); auto isMat = cv::util::holds_alternative(input_meta); auto prec = isMat ? cv::util::get(input_meta).depth : CV_8U; m_ppp.input(input_name).tensor().set_element_type(toOV(prec)); const auto &matdesc = isMat ? cv::util::get(input_meta) : cv::GMatDesc(); const auto &framedesc = !isMat ? cv::util::get(input_meta) : cv::GFrameDesc(); if (isImage(input_meta, input_shape)) { // NB: Image case - all necessary preprocessng is configured automatically. GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is image."); if (explicit_in_tensor_layout && *explicit_in_tensor_layout != "NHWC") { std::stringstream desc_str; if (isMat) { desc_str << matdesc; } else { desc_str << framedesc; } std::stringstream ss; ss << "OV Backend: Provided tensor layout " << *explicit_in_tensor_layout << " is not compatible with input data " << desc_str.str() << " for layer \"" << input_name << "\". Expecting NHWC"; util::throw_error(std::logic_error(ss.str())); } else { input_info.tensor().set_layout(::ov::Layout("NHWC")); } if (!disable_img_resize) { const auto size = isMat ? cv::util::get(input_meta).size : cv::util::get(input_meta).size; input_info.tensor().set_spatial_static_shape(size.height, 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. const auto model_layout = explicit_in_model_layout ? ::ov::Layout(*explicit_in_model_layout) : ::ov::layout::get_layout(m_model->input(input_name)); 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)); } } } } void cfgPostProcessing() { for (const auto &output_name : m_output_names) { const auto explicit_out_tensor_layout = lookUp(m_output_tensor_layout, output_name); if (explicit_out_tensor_layout) { m_ppp.output(output_name).tensor() .set_layout(::ov::Layout(*explicit_out_tensor_layout)); } const auto explicit_out_model_layout = lookUp(m_output_model_layout, output_name); if (explicit_out_model_layout) { m_ppp.output(output_name).model() .set_layout(::ov::Layout(*explicit_out_model_layout)); } const auto explicit_out_tensor_prec = lookUp(m_output_tensor_precision, output_name); if (explicit_out_tensor_prec) { m_ppp.output(output_name).tensor() .set_element_type(toOV(*explicit_out_tensor_prec)); } } } void finalize() { GAPI_LOG_DEBUG(NULL, "OV Backend: PrePostProcessor: " << m_ppp); m_model = m_ppp.build(); } private: ::ov::preprocess::PrePostProcessor m_ppp; std::shared_ptr<::ov::Model> &m_model; const ParamDesc::Model &m_model_info; const std::vector &m_input_names; const std::vector &m_output_names; cv::gimpl::ov::AttrMap m_input_tensor_layout; cv::gimpl::ov::AttrMap m_input_model_layout; cv::gimpl::ov::AttrMap m_interpolation; cv::gimpl::ov::AttrMap> m_mean_values; cv::gimpl::ov::AttrMap> m_scale_values; cv::gimpl::ov::AttrMap m_output_tensor_layout; cv::gimpl::ov::AttrMap m_output_model_layout; cv::gimpl::ov::AttrMap m_output_tensor_precision; }; 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); auto& model = const_cast&>(uu.model); PrePostProcWrapper ppp {model, model_info, uu.params.input_names, uu.params.output_names}; for (auto &&it : ade::util::zip(ade::util::toRange(uu.params.input_names), ade::util::toRange(in_metas))) { const auto &input_name = std::get<0>(it); const auto &mm = std::get<1>(it); ppp.cfgLayouts(input_name); ppp.cfgPreProcessing(input_name, mm); ppp.cfgScaleMean(input_name, mm); } ppp.cfgPostProcessing(); ppp.finalize(); } 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) { // NB: No need to populate model inputs with data // if it's inference only mode. if (ctx->getOptions().inference_only) { return; } 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, i, input_tensor); } }, std::bind(PostOutputs, _1, _2, ctx) } ); } }; struct InferROI: public cv::detail::KernelTag { using API = cv::GInferROIBase; 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 // FIXME: So far it is pretty limited GAPI_Assert(1u == uu.params.input_names.size()); GAPI_Assert(2u == in_metas.size()); const auto &input_name = uu.params.input_names.at(0); const auto &mm = in_metas.at(1u); GAPI_Assert(cv::util::holds_alternative(mm) || cv::util::holds_alternative(mm)); const bool is_model = cv::util::holds_alternative(uu.params.kind); const auto &input_shape = is_model ? uu.model->input(input_name).get_shape() : uu.compiled_model.input(input_name).get_shape(); if (!isImage(mm, input_shape)) { util::throw_error(std::runtime_error( "OV Backend: InferROI supports only image as the 1th argument")); } if (is_model) { const auto &model_info = cv::util::get(uu.params.kind); auto& model = const_cast&>(uu.model); PrePostProcWrapper ppp {model, model_info, uu.params.input_names, uu.params.output_names}; ppp.cfgLayouts(input_name); ppp.cfgPreProcessing(input_name, mm, true /*disable_img_resize*/); ppp.cfgScaleMean(input_name, mm); ppp.cfgPostProcessing(); ppp.finalize(); } 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; if (ctx->getOptions().inference_only) { cv::util::throw_error( std::logic_error("OV Backend: Inference only mode is not supported for InferROI!")); } reqPool.getIdleRequest()->execute( IInferExecutor::Task { [ctx](::ov::InferRequest &infer_request) { GAPI_Assert(ctx->uu.params.num_in == 1); const auto &input_name = ctx->uu.params.input_names[0]; auto input_tensor = infer_request.get_tensor(input_name); const auto &shape = input_tensor.get_shape(); const auto &roi = ctx->inArg(0).rref(); preprocess_and_copy(ctx, 1, roi, shape, input_tensor); }, std::bind(PostOutputs, _1, _2, ctx) } ); } }; struct InferList: public cv::detail::KernelTag { using API = cv::GInferListBase; 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*/) { 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() - 1u) && "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); auto& model = const_cast&>(uu.model); PrePostProcWrapper ppp {model, model_info, uu.params.input_names, uu.params.output_names}; size_t idx = 1u; for (auto &&input_name : uu.params.input_names) { const auto &mm = in_metas[idx++]; GAPI_Assert(cv::util::holds_alternative(mm) || cv::util::holds_alternative(mm)); const auto &input_shape = uu.model->input(input_name).get_shape(); if (!isImage(mm, input_shape)) { util::throw_error(std::runtime_error( "OV Backend: Only image is supported" " as the " + std::to_string(idx) + "th argument for InferList")); } ppp.cfgLayouts(input_name); ppp.cfgPreProcessing(input_name, mm, true /*disable_img_resize*/); ppp.cfgScaleMean(input_name, mm); } ppp.cfgPostProcessing(); ppp.finalize(); } // roi-list version is much easier at the moment. // All our outputs are vectors which don't have // metadata at the moment - so just create a vector of // "empty" array metadatas of the required size. return cv::GMetaArgs(uu.params.output_names.size(), cv::GMetaArg{cv::empty_array_desc()}); } static void run(std::shared_ptr ctx, cv::gimpl::ov::RequestPool &reqPool) { if (ctx->getOptions().inference_only) { cv::util::throw_error( std::logic_error("OV Backend: Inference only mode is not supported for InferList!")); } const auto& in_roi_vec = ctx->inArg(0u).rref(); // NB: In case there is no input data need to post output anyway if (in_roi_vec.empty()) { 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)); } return; } for (auto i : ade::util::iota(ctx->uu.params.num_out)) { // FIXME: Isn't this should be done automatically // by some resetInternalData(), etc? (Probably at the GExecutor level) auto& out_vec = ctx->outVecR(i); out_vec.clear(); out_vec.resize(in_roi_vec.size()); } PostOutputsList callback(in_roi_vec.size(), ctx); for (auto&& it : ade::util::indexed(in_roi_vec)) { const auto pos = ade::util::index(it); const auto &rc = ade::util::value(it); reqPool.getIdleRequest()->execute( IInferExecutor::Task { [ctx, rc](::ov::InferRequest &infer_request) { const auto &input_name = ctx->uu.params.input_names[0]; auto input_tensor = infer_request.get_tensor(input_name); const auto &shape = input_tensor.get_shape(); preprocess_and_copy(ctx, 1, rc, shape, input_tensor); }, std::bind(callback, std::placeholders::_1, std::placeholders::_2, pos) } ); } } }; struct InferList2: public cv::detail::KernelTag { using API = cv::GInferList2Base; 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*/) { 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() - 1u) && "Known input layers count doesn't match input meta count"); const auto &op = gm.metadata(nh).get(); // In contrast to InferList, the InferList2 has only one // "full-frame" image argument, and all the rest are arrays of // ether ROI or blobs. So here we set the 0th arg image format // to all inputs which are ROI-based (skipping the // "blob"-based ones) // FIXME: this is filtering not done, actually! GArrayDesc has // no hint for its underlying type! const auto &input_name_0 = uu.params.input_names.front(); const auto &mm_0 = in_metas[0u]; if (!(cv::util::holds_alternative(mm_0) || cv::util::holds_alternative(mm_0))) { util::throw_error(std::runtime_error( "OV Backend: Unsupported input meta" " for 0th argument in OV backend")); } const bool is_model = cv::util::holds_alternative(uu.params.kind); const auto &input_shape = is_model ? uu.model->input(input_name_0).get_shape() : uu.compiled_model.input(input_name_0).get_shape(); if (!isImage(mm_0, input_shape)) { util::throw_error(std::runtime_error( "OV Backend: InferList2 supports only image as the 0th argument")); } if (is_model) { const auto &model_info = cv::util::get(uu.params.kind); auto& model = const_cast&>(uu.model); PrePostProcWrapper ppp {model, model_info, uu.params.input_names, uu.params.output_names}; size_t idx = 1u; for (auto &&input_name : uu.params.input_names) { GAPI_Assert(util::holds_alternative(in_metas[idx]) && "Non-array inputs are not supported"); ppp.cfgLayouts(input_name); if (op.k.inKinds[idx] == cv::detail::OpaqueKind::CV_RECT) { ppp.cfgPreProcessing(input_name, mm_0, true /*disable_img_resize*/); } else { // This is a cv::GMat (equals to: cv::Mat) // Just validate that it is really the type // (other types are prohibited here) GAPI_Assert(op.k.inKinds[idx] == cv::detail::OpaqueKind::CV_MAT); } ppp.cfgScaleMean(input_name, mm_0); idx++; // NB: Never forget to increment the counter } ppp.cfgPostProcessing(); ppp.finalize(); } // roi-list version is much easier at the moment. // All our outputs are vectors which don't have // metadata at the moment - so just create a vector of // "empty" array metadatas of the required size. return cv::GMetaArgs(uu.params.output_names.size(), cv::GMetaArg{cv::empty_array_desc()}); } static void run(std::shared_ptr ctx, cv::gimpl::ov::RequestPool &reqPool) { if (ctx->getOptions().inference_only) { cv::util::throw_error( std::logic_error("OV Backend: Inference only mode is not supported for InferList2!")); } GAPI_Assert(ctx->inArgs().size() > 1u && "This operation must have at least two arguments"); // NB: This blob will be used to make roi from its, so // it should be treated as image const auto list_size = ctx->inArg(1u).size(); if (list_size == 0u) { 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)); } return; } for (auto i : ade::util::iota(ctx->uu.params.num_out)) { // FIXME: Isn't this should be done automatically // by some resetInternalData(), etc? (Probably at the GExecutor level) auto& out_vec = ctx->outVecR(i); out_vec.clear(); out_vec.resize(list_size); } PostOutputsList callback(list_size, ctx); for (const auto &list_idx : ade::util::iota(list_size)) { reqPool.getIdleRequest()->execute( IInferExecutor::Task { [ctx, list_idx, list_size](::ov::InferRequest &infer_request) { for (auto in_idx : ade::util::iota(ctx->uu.params.num_in)) { const auto &this_vec = ctx->inArg(in_idx+1u); GAPI_Assert(this_vec.size() == list_size); const auto &input_name = ctx->uu.params.input_names[in_idx]; auto input_tensor = infer_request.get_tensor(input_name); const auto &shape = input_tensor.get_shape(); if (this_vec.getKind() == cv::detail::OpaqueKind::CV_RECT) { const auto &vec = this_vec.rref(); const auto roi_mat = preprocess(ctx->inMat(0), vec[list_idx], shape); copyToOV(roi_mat, input_tensor); } else if (this_vec.getKind() == cv::detail::OpaqueKind::CV_MAT) { const auto &vec = this_vec.rref(); const auto &mat = vec[list_idx]; copyToOV(mat, input_tensor); } else { GAPI_Assert(false && "OV Backend: Only Rect and Mat types are supported for InferList2"); } } }, std::bind(callback, std::placeholders::_1, std::placeholders::_2, list_idx) } // task ); } // for } }; } // 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 &compileArgs, const std::vector &nodes) const override { return EPtr{new cv::gimpl::ov::GOVExecutable(graph, compileArgs, nodes)}; } virtual cv::GKernelPackage auxiliaryKernels() const override { return cv::gapi::kernels< cv::gimpl::ov::Infer , cv::gimpl::ov::InferROI , cv::gimpl::ov::InferList , cv::gimpl::ov::InferList2 >(); } 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 cv::GCompileArgs &compileArgs, const std::vector &nodes) : m_g(g), m_gm(m_g) { m_options.inference_only = cv::gapi::getCompileArg(compileArgs).has_value(); // 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; const auto &unit = ovm.metadata(this_nh).get(); compiled = const_cast(unit).compile(); m_reqPool.reset(new RequestPool(createInferRequests( compiled.compiled_model, unit.params.nireq))); } 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)) { m_reqPool->waitAll(); 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), m_options); 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 && INF_ENGINE_RELEASE >= 2022010000 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 && INF_ENGINE_RELEASE >= 2022010000