diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index 88ddeead16..137894cb8f 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -145,6 +145,9 @@ set(gapi_srcs # Serialization API and routines src/api/s11n.cpp src/backends/common/serialization.cpp + + # Python bridge + src/backends/ie/bindings_ie.cpp ) ocv_add_dispatched_file(backends/fluid/gfluidimgproc_func SSE4_1 AVX2) diff --git a/modules/gapi/include/opencv2/gapi/infer.hpp b/modules/gapi/include/opencv2/gapi/infer.hpp index 4fdd2df875..9b4580ec6b 100644 --- a/modules/gapi/include/opencv2/gapi/infer.hpp +++ b/modules/gapi/include/opencv2/gapi/infer.hpp @@ -133,14 +133,18 @@ struct InOutInfo * @{ * @brief G-API object used to collect network inputs */ -class GAPI_EXPORTS GInferInputs +class GAPI_EXPORTS_W_SIMPLE GInferInputs { +using Map = std::unordered_map; public: + GAPI_WRAP GInferInputs(); + GAPI_WRAP void setInput(const std::string& name, const cv::GMat& value); + cv::GMat& operator[](const std::string& name); - const std::unordered_map& getBlobs() const; + const Map& getBlobs() const; private: - std::unordered_map in_blobs; + std::shared_ptr in_blobs; }; /** @} */ @@ -148,16 +152,16 @@ private: * @{ * @brief G-API object used to collect network outputs */ -struct GAPI_EXPORTS GInferOutputs +struct GAPI_EXPORTS_W_SIMPLE GInferOutputs { public: + GAPI_WRAP GInferOutputs() = default; GInferOutputs(std::shared_ptr call); - cv::GMat at(const std::string& name); + GAPI_WRAP cv::GMat at(const std::string& name); private: - std::shared_ptr m_call; - InOutInfo* m_info = nullptr; - std::unordered_map out_blobs; + struct Priv; + std::shared_ptr m_priv; }; /** @} */ @@ -333,6 +337,11 @@ infer(const std::string& tag, const GInferInputs& inputs) return GInferOutputs{std::move(call)}; } +GAPI_EXPORTS_W inline GInferOutputs infer(const String& name, const GInferInputs& inputs) +{ + return infer(name, inputs); +} + } // namespace gapi } // namespace cv @@ -361,8 +370,8 @@ struct GAPI_EXPORTS GNetParam { * * @sa cv::gapi::networks */ -struct GAPI_EXPORTS GNetPackage { - GNetPackage() : GNetPackage({}) {} +struct GAPI_EXPORTS_W_SIMPLE GNetPackage { + GAPI_WRAP GNetPackage() : GNetPackage({}) {} explicit GNetPackage(std::initializer_list &&ii); std::vector backends() const; std::vector networks; diff --git a/modules/gapi/include/opencv2/gapi/infer/bindings_ie.hpp b/modules/gapi/include/opencv2/gapi/infer/bindings_ie.hpp new file mode 100644 index 0000000000..fdd4128b1a --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer/bindings_ie.hpp @@ -0,0 +1,56 @@ +// 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) 2020 Intel Corporation + +#ifndef OPENCV_GAPI_INFER_BINDINGS_IE_HPP +#define OPENCV_GAPI_INFER_BINDINGS_IE_HPP + +#include +#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS +#include // GKernelPackage +#include // Params + +#include + +namespace cv { +namespace gapi { +namespace ie { + +// 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: + PyParams() = default; + + PyParams(const std::string &tag, + const std::string &model, + const std::string &weights, + const std::string &device); + + PyParams(const std::string &tag, + const std::string &model, + const std::string &device); + + 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, + const std::string &weights, + const std::string &device); + +GAPI_EXPORTS_W PyParams params(const std::string &tag, + const std::string &model, + const std::string &device); +} // namespace ie +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_INFER_BINDINGS_IE_HPP diff --git a/modules/gapi/include/opencv2/gapi/infer/ie.hpp b/modules/gapi/include/opencv2/gapi/infer/ie.hpp index dd2459da08..a8bc0bb05d 100644 --- a/modules/gapi/include/opencv2/gapi/infer/ie.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/ie.hpp @@ -162,4 +162,4 @@ protected: } // namespace gapi } // namespace cv -#endif // OPENCV_GAPI_INFER_HPP +#endif // OPENCV_GAPI_INFER_IE_HPP diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp index 0e862a4010..57c0b3db4f 100644 --- a/modules/gapi/misc/python/pyopencv_gapi.hpp +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -5,6 +5,8 @@ // NB: Python wrapper replaces :: with _ for classes using gapi_GKernelPackage = cv::gapi::GKernelPackage; +using gapi_GNetPackage = cv::gapi::GNetPackage; +using gapi_ie_PyParams = cv::gapi::ie::PyParams; using gapi_wip_IStreamSource_Ptr = cv::Ptr; // FIXME: Python wrapper generate code without namespace std, diff --git a/modules/gapi/misc/python/shadow_gapi.hpp b/modules/gapi/misc/python/shadow_gapi.hpp index 72d7686eeb..0fac222212 100644 --- a/modules/gapi/misc/python/shadow_gapi.hpp +++ b/modules/gapi/misc/python/shadow_gapi.hpp @@ -6,23 +6,25 @@ namespace cv struct GAPI_EXPORTS_W_SIMPLE GCompileArg { }; GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GKernelPackage pkg); + GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GNetPackage pkg); // NB: This classes doesn't exist in *.so // HACK: Mark them as a class to force python wrapper generate code for this entities class GAPI_EXPORTS_W_SIMPLE GProtoArg { }; class GAPI_EXPORTS_W_SIMPLE GProtoInputArgs { }; class GAPI_EXPORTS_W_SIMPLE GProtoOutputArgs { }; - class GAPI_EXPORTS_W_SIMPLE GRunArg { }; - class GAPI_EXPORTS_W_SIMPLE GMetaArg { }; + class GAPI_EXPORTS_W_SIMPLE GRunArg { }; + class GAPI_EXPORTS_W_SIMPLE GMetaArg { }; using GProtoInputArgs = GIOProtoArgs; using GProtoOutputArgs = GIOProtoArgs; namespace gapi { + GAPI_EXPORTS_W gapi::GNetPackage networks(const cv::gapi::ie::PyParams& params); namespace wip { class GAPI_EXPORTS_W IStreamSource { }; - } - } + } // namespace wip + } // namespace gapi } // namespace cv diff --git a/modules/gapi/misc/python/test/test_gapi_infer.py b/modules/gapi/misc/python/test/test_gapi_infer.py new file mode 100644 index 0000000000..a6fabf7253 --- /dev/null +++ b/modules/gapi/misc/python/test/test_gapi_infer.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +import numpy as np +import cv2 as cv +import os + +from tests_common import NewOpenCVTests + + +class test_gapi_infer(NewOpenCVTests): + + def test_getAvailableTargets(self): + targets = cv.dnn.getAvailableTargets(cv.dnn.DNN_BACKEND_OPENCV) + self.assertTrue(cv.dnn.DNN_TARGET_CPU in targets) + + + def test_age_gender_infer(self): + + # NB: Check IE + if not cv.dnn.DNN_TARGET_CPU in cv.dnn.getAvailableTargets(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE): + return + + 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')]) + weights_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) + img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + device_id = 'CPU' + img = cv.resize(cv.imread(img_path), (62,62)) + + # OpenCV DNN + net = cv.dnn.readNetFromModelOptimizer(model_path, weights_path) + net.setPreferableBackend(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE) + net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU) + + blob = cv.dnn.blobFromImage(img) + + net.setInput(blob) + dnn_age, dnn_gender = net.forward(net.getUnconnectedOutLayersNames()) + + # OpenCV G-API + g_in = cv.GMat() + inputs = cv.GInferInputs() + inputs.setInput('data', g_in) + + outputs = cv.gapi.infer("net", inputs) + age_g = outputs.at("age_conv3") + gender_g = outputs.at("prob") + + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(age_g, gender_g)) + pp = cv.gapi.ie.params("net", model_path, weights_path, device_id) + + nets = cv.gapi.networks(pp) + args = cv.compile_args(nets) + gapi_age, gapi_gender = comp.apply(cv.gin(img), args=cv.compile_args(cv.gapi.networks(pp))) + + # Check + self.assertEqual(0.0, cv.norm(dnn_gender, gapi_gender, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(dnn_age, gapi_age, cv.NORM_INF)) + + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/gapi/src/api/ginfer.cpp b/modules/gapi/src/api/ginfer.cpp index 31d851b8e6..20511a4aaf 100644 --- a/modules/gapi/src/api/ginfer.cpp +++ b/modules/gapi/src/api/ginfer.cpp @@ -29,29 +29,52 @@ std::vector cv::gapi::GNetPackage::backends() const { // FIXME: Inference API is currently only available in full mode #if !defined(GAPI_STANDALONE) +cv::GInferInputs::GInferInputs() + : in_blobs(std::make_shared()) +{ +} + cv::GMat& cv::GInferInputs::operator[](const std::string& name) { - return in_blobs[name]; + return (*in_blobs)[name]; } -const std::unordered_map& cv::GInferInputs::getBlobs() const { - return in_blobs; +const cv::GInferInputs::Map& cv::GInferInputs::getBlobs() const { + return *in_blobs; } -cv::GInferOutputs::GInferOutputs(std::shared_ptr call) - : m_call(std::move(call)), m_info(cv::util::any_cast(&m_call->params())) +void cv::GInferInputs::setInput(const std::string& name, const cv::GMat& value) { + in_blobs->emplace(name, value); +} + +struct cv::GInferOutputs::Priv { + Priv(std::shared_ptr); + + std::shared_ptr call; + InOutInfo* info = nullptr; + std::unordered_map out_blobs; }; +cv::GInferOutputs::Priv::Priv(std::shared_ptr c) + : call(std::move(c)), info(cv::util::any_cast(&call->params())) +{ +} + +cv::GInferOutputs::GInferOutputs(std::shared_ptr call) + : m_priv(std::make_shared(std::move(call))) +{ +} + cv::GMat cv::GInferOutputs::at(const std::string& name) { - auto it = out_blobs.find(name); - if (it == out_blobs.end()) { + auto it = m_priv->out_blobs.find(name); + if (it == m_priv->out_blobs.end()) { // FIXME: Avoid modifying GKernel - m_call->kernel().outShapes.push_back(cv::GShape::GMAT); - int out_idx = static_cast(out_blobs.size()); - it = out_blobs.emplace(name, m_call->yield(out_idx)).first; - m_info->out_names.push_back(name); + m_priv->call->kernel().outShapes.push_back(cv::GShape::GMAT); + int out_idx = static_cast(m_priv->out_blobs.size()); + it = m_priv->out_blobs.emplace(name, m_priv->call->yield(out_idx)).first; + m_priv->info->out_names.push_back(name); } return it->second; -}; +} #endif // GAPI_STANDALONE diff --git a/modules/gapi/src/backends/ie/bindings_ie.cpp b/modules/gapi/src/backends/ie/bindings_ie.cpp new file mode 100644 index 0000000000..35191d7bcb --- /dev/null +++ b/modules/gapi/src/backends/ie/bindings_ie.cpp @@ -0,0 +1,39 @@ +#include + +cv::gapi::ie::PyParams::PyParams(const std::string &tag, + const std::string &model, + const std::string &weights, + const std::string &device) + : m_priv(std::make_shared>(tag, model, weights, device)) { +} + +cv::gapi::ie::PyParams::PyParams(const std::string &tag, + const std::string &model, + const std::string &device) + : m_priv(std::make_shared>(tag, model, device)) { +} + +cv::gapi::GBackend cv::gapi::ie::PyParams::backend() const { + return m_priv->backend(); +} + +std::string cv::gapi::ie::PyParams::tag() const { + return m_priv->tag(); +} + +cv::util::any cv::gapi::ie::PyParams::params() const { + return m_priv->params(); +} + +cv::gapi::ie::PyParams cv::gapi::ie::params(const std::string &tag, + const std::string &model, + const std::string &weights, + const std::string &device) { + return {tag, model, weights, device}; +} + +cv::gapi::ie::PyParams cv::gapi::ie::params(const std::string &tag, + const std::string &model, + const std::string &device) { + return {tag, model, device}; +}