From: Anatoliy Talamanov Date: Thu, 8 Oct 2020 22:12:25 +0000 (+0300) Subject: Merge pull request #18419 from TolyaTalamanov:at/generic-inference X-Git-Tag: submit/tizen/20210224.033012~2^2~4 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=76be3529f491aa5835602021c65ef99975837cac;p=platform%2Fupstream%2Fopencv.git Merge pull request #18419 from TolyaTalamanov:at/generic-inference [G-API] Introduce generic version for cv::gapi::infer * Introduce generic infer * Move Generic to infer.hpp * Removew num_outs * Fix windows warnings * Fix comments to review * Fix doxygen * Add comment * Fix comments to review * standoalone ifdef in ginfer.cpp * Fix test --- diff --git a/modules/gapi/include/opencv2/gapi/gcall.hpp b/modules/gapi/include/opencv2/gapi/gcall.hpp index ed5ba5f..511eca1 100644 --- a/modules/gapi/include/opencv2/gapi/gcall.hpp +++ b/modules/gapi/include/opencv2/gapi/gcall.hpp @@ -56,11 +56,16 @@ public: Priv& priv(); const Priv& priv() const; -protected: - std::shared_ptr m_priv; + // GKernel and params can be modified, it's needed for infer, + // because information about output shapes doesn't exist in compile time + GKernel& kernel(); + cv::util::any& params(); void setArgs(std::vector &&args); +protected: + std::shared_ptr m_priv; + // Public versions return a typed array or opaque, those are implementation details detail::GArrayU yieldArray(int output = 0); detail::GOpaqueU yieldOpaque(int output = 0); diff --git a/modules/gapi/include/opencv2/gapi/infer.hpp b/modules/gapi/include/opencv2/gapi/infer.hpp index 50086dd..4fdd2df 100644 --- a/modules/gapi/include/opencv2/gapi/infer.hpp +++ b/modules/gapi/include/opencv2/gapi/infer.hpp @@ -121,6 +121,45 @@ struct GInferBase { } }; +// Struct stores network input/output names. +// Used by infer +struct InOutInfo +{ + std::vector in_names; + std::vector out_names; +}; + +/** + * @{ + * @brief G-API object used to collect network inputs + */ +class GAPI_EXPORTS GInferInputs +{ +public: + cv::GMat& operator[](const std::string& name); + const std::unordered_map& getBlobs() const; + +private: + std::unordered_map in_blobs; +}; +/** @} */ + +/** + * @{ + * @brief G-API object used to collect network outputs + */ +struct GAPI_EXPORTS GInferOutputs +{ +public: + GInferOutputs(std::shared_ptr call); + cv::GMat at(const std::string& name); + +private: + std::shared_ptr m_call; + InOutInfo* m_info = nullptr; + std::unordered_map out_blobs; +}; +/** @} */ // Base "Infer list" kernel. // All notes from "Infer" kernel apply here as well. @@ -254,6 +293,45 @@ typename Net::Result infer(Args&&... args) { return GInfer::on(std::forward(args)...); } +/** + * @brief Special network type + */ +struct Generic { }; + +/** + * @brief Calculates response for generic network + * + * @param tag a network tag + * @param inputs networks's inputs + * @return a GInferOutputs + */ +template GInferOutputs +infer(const std::string& tag, const GInferInputs& inputs) +{ + std::vector input_args; + std::vector input_names; + + const auto& blobs = inputs.getBlobs(); + for (auto&& p : blobs) + { + input_names.push_back(p.first); + input_args.emplace_back(p.second); + } + + GKinds kinds(blobs.size(), cv::detail::OpaqueKind::CV_MAT); + auto call = std::make_shared(GKernel{ + GInferBase::id(), + tag, + GInferBase::getOutMeta, + {}, // outShape will be filled later + std::move(kinds) + }); + + call->setArgs(std::move(input_args)); + call->params() = InOutInfo{input_names, {}}; + + return GInferOutputs{std::move(call)}; +} } // namespace gapi } // namespace cv diff --git a/modules/gapi/include/opencv2/gapi/infer/ie.hpp b/modules/gapi/include/opencv2/gapi/infer/ie.hpp index c6d7f27..8421d9e 100644 --- a/modules/gapi/include/opencv2/gapi/infer/ie.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/ie.hpp @@ -17,6 +17,7 @@ #include // GAPI_EXPORTS #include // GKernelPackage +#include // Generic namespace cv { namespace gapi { @@ -58,6 +59,8 @@ namespace detail { // (e.g. topology's partial execution) std::size_t num_in; // How many inputs are defined in the operation std::size_t num_out; // How many outputs are defined in the operation + + bool is_generic; }; } // namespace detail @@ -80,7 +83,7 @@ public: : desc{ model, weights, device, {}, {}, {} , std::tuple_size::value // num_in , std::tuple_size::value // num_out - } { + , false} { }; Params& cfgInputLayers(const typename PortCfg::In &ll) { @@ -107,13 +110,34 @@ public: } // BEGIN(G-API's network parametrization API) - GBackend backend() const { return cv::gapi::ie::backend(); } - std::string tag() const { return Net::tag(); } - cv::util::any params() const { return { desc }; } + GBackend backend() const { return cv::gapi::ie::backend(); } + std::string tag() const { return Net::tag(); } + cv::util::any params() const { return { desc }; } + // END(G-API's network parametrization API) + +protected: + detail::ParamDesc desc; +}; + +template<> +class Params { +public: + Params(const std::string& tag, + const std::string &model, + const std::string &weights, + const std::string &device) + : desc{ model, weights, device, {}, {}, {}, 0u, 0u, true}, m_tag(tag) { + }; + + // BEGIN(G-API's network parametrization API) + GBackend backend() const { return cv::gapi::ie::backend(); } + std::string tag() const { return m_tag; } + cv::util::any params() const { return { desc }; } // END(G-API's network parametrization API) protected: detail::ParamDesc desc; + std::string m_tag; }; } // namespace ie diff --git a/modules/gapi/src/api/gcall.cpp b/modules/gapi/src/api/gcall.cpp index 6f5f65b..6a2121b 100644 --- a/modules/gapi/src/api/gcall.cpp +++ b/modules/gapi/src/api/gcall.cpp @@ -78,3 +78,13 @@ const cv::GCall::Priv& cv::GCall::priv() const { return *m_priv; } + +cv::GKernel& cv::GCall::kernel() +{ + return m_priv->m_k; +} + +cv::util::any& cv::GCall::params() +{ + return m_priv->m_params; +} diff --git a/modules/gapi/src/api/gcall_priv.hpp b/modules/gapi/src/api/gcall_priv.hpp index edc2c22..b142432 100644 --- a/modules/gapi/src/api/gcall_priv.hpp +++ b/modules/gapi/src/api/gcall_priv.hpp @@ -42,10 +42,11 @@ class GCall::Priv { public: std::vector m_args; - const GKernel m_k; + GKernel m_k; // TODO: Rename to "constructionNode" or smt to reflect its lifetime GNode m_node; + cv::util::any m_params; explicit Priv(const GKernel &k); }; diff --git a/modules/gapi/src/api/ginfer.cpp b/modules/gapi/src/api/ginfer.cpp index 98eeef5..31d851b 100644 --- a/modules/gapi/src/api/ginfer.cpp +++ b/modules/gapi/src/api/ginfer.cpp @@ -25,3 +25,33 @@ std::vector cv::gapi::GNetPackage::backends() const { for (const auto &nn : networks) unique_set.insert(nn.backend); return std::vector(unique_set.begin(), unique_set.end()); } + +// FIXME: Inference API is currently only available in full mode +#if !defined(GAPI_STANDALONE) + +cv::GMat& cv::GInferInputs::operator[](const std::string& name) { + return in_blobs[name]; +} + +const std::unordered_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())) +{ +}; + +cv::GMat cv::GInferOutputs::at(const std::string& name) +{ + auto it = out_blobs.find(name); + if (it == 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); + } + return it->second; +}; +#endif // GAPI_STANDALONE diff --git a/modules/gapi/src/backends/ie/giebackend.cpp b/modules/gapi/src/backends/ie/giebackend.cpp index 1565d03..b7bda2f 100644 --- a/modules/gapi/src/backends/ie/giebackend.cpp +++ b/modules/gapi/src/backends/ie/giebackend.cpp @@ -721,9 +721,23 @@ namespace { // FIXME: Introduce a DNNBackend interface which'd specify // the framework for this??? GIEModel gm(gr); - const auto &np = gm.metadata(nh).get(); - const auto &pp = cv::util::any_cast(np.opaque); + 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(IEUnit{pp}); gm.metadata(nh).set(IECallable{ki.run}); gm.metadata(nh).set(CustomMetaFunction{ki.customMetaFunc}); diff --git a/modules/gapi/src/compiler/gmodel.cpp b/modules/gapi/src/compiler/gmodel.cpp index 39dc1da..b5b76fd 100644 --- a/modules/gapi/src/compiler/gmodel.cpp +++ b/modules/gapi/src/compiler/gmodel.cpp @@ -23,12 +23,16 @@ namespace cv { namespace gimpl { -ade::NodeHandle GModel::mkOpNode(GModel::Graph &g, const GKernel &k, const std::vector &args, const std::string &island) +ade::NodeHandle GModel::mkOpNode(GModel::Graph &g, + const GKernel &k, + const std::vector &args, + const cv::util::any ¶ms, + const std::string &island) { ade::NodeHandle op_h = g.createNode(); g.metadata(op_h).set(NodeType{NodeType::OP}); //These extra empty {} are to please GCC (-Wmissing-field-initializers) - g.metadata(op_h).set(Op{k, args, {}, {}}); + g.metadata(op_h).set(Op{k, args, {}, {}, params}); if (!island.empty()) g.metadata(op_h).set(Island{island}); return op_h; diff --git a/modules/gapi/src/compiler/gmodel.hpp b/modules/gapi/src/compiler/gmodel.hpp index 8f78ba4..5f02e58 100644 --- a/modules/gapi/src/compiler/gmodel.hpp +++ b/modules/gapi/src/compiler/gmodel.hpp @@ -61,6 +61,7 @@ struct Op std::vector outs; // TODO: Introduce a new type for resource references cv::gapi::GBackend backend; + cv::util::any params; // Operation specific information }; struct Data @@ -262,7 +263,11 @@ namespace GModel // GAPI_EXPORTS for tests GAPI_EXPORTS void init (Graph& g); - GAPI_EXPORTS ade::NodeHandle mkOpNode(Graph &g, const GKernel &k, const std::vector& args, const std::string &island); + GAPI_EXPORTS ade::NodeHandle mkOpNode(Graph &g, + const GKernel &k, + const std::vector& args, + const cv::util::any& params, + const std::string &island); // Isn't used by the framework or default backends, required for external backend development GAPI_EXPORTS ade::NodeHandle mkDataNode(Graph &g, const GShape shape); diff --git a/modules/gapi/src/compiler/gmodelbuilder.cpp b/modules/gapi/src/compiler/gmodelbuilder.cpp index 87e9ab5..80abadd 100644 --- a/modules/gapi/src/compiler/gmodelbuilder.cpp +++ b/modules/gapi/src/compiler/gmodelbuilder.cpp @@ -286,7 +286,7 @@ ade::NodeHandle cv::gimpl::GModelBuilder::put_OpNode(const cv::GNode &node) { GAPI_Assert(node.shape() == GNode::NodeShape::CALL); const auto &call_p = node.call().priv(); - auto nh = cv::gimpl::GModel::mkOpNode(m_gm, call_p.m_k, call_p.m_args, node_p.m_island); + auto nh = cv::gimpl::GModel::mkOpNode(m_gm, call_p.m_k, call_p.m_args, call_p.m_params, node_p.m_island); m_graph_ops[&node_p] = nh; return nh; } diff --git a/modules/gapi/test/infer/gapi_infer_ie_test.cpp b/modules/gapi/test/infer/gapi_infer_ie_test.cpp index 74d8558..3125705 100644 --- a/modules/gapi/test/infer/gapi_infer_ie_test.cpp +++ b/modules/gapi/test/infer/gapi_infer_ie_test.cpp @@ -350,6 +350,59 @@ TEST(DISABLED_TestTwoIENNPipeline, InferBasicImage) normAssert(cv::gapi::ie::util::to_ocv(ie_gender2), gapi_gender2, "Test gender output 2"); } +TEST(TestAgeGenderIE, GenericInfer) +{ + initDLDTDataPath(); + + cv::gapi::ie::detail::ParamDesc params; + params.model_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + params.weights_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + params.device_id = "CPU"; + + cv::Mat in_mat(cv::Size(320, 240), CV_8UC3); + cv::randu(in_mat, 0, 255); + + cv::Mat gapi_age, gapi_gender; + + // Load & run IE network + IE::Blob::Ptr ie_age, ie_gender; + { + auto plugin = cv::gimpl::ie::wrap::getPlugin(params); + auto net = cv::gimpl::ie::wrap::readNetwork(params); + setNetParameters(net); + auto this_network = cv::gimpl::ie::wrap::loadNetwork(plugin, net, params); + auto infer_request = this_network.CreateInferRequest(); + infer_request.SetBlob("data", cv::gapi::ie::util::to_ie(in_mat)); + infer_request.Infer(); + ie_age = infer_request.GetBlob("age_conv3"); + ie_gender = infer_request.GetBlob("prob"); + } + + // Configure & run G-API + cv::GMat in; + GInferInputs inputs; + inputs["data"] = in; + + auto outputs = cv::gapi::infer("age-gender-generic", inputs); + + auto age = outputs.at("age_conv3"); + auto gender = outputs.at("prob"); + + cv::GComputation comp(cv::GIn(in), cv::GOut(age, gender)); + + cv::gapi::ie::Params pp{"age-gender-generic", + params.model_path, + params.weights_path, + params.device_id}; + + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Validate with IE itself (avoid DNN module dependency here) + normAssert(cv::gapi::ie::util::to_ocv(ie_age), gapi_age, "Test age output" ); + normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output"); +} + } // namespace opencv_test #endif // HAVE_INF_ENGINE