Merge pull request #18419 from TolyaTalamanov:at/generic-inference
authorAnatoliy Talamanov <anatoliy.talamanov@intel.com>
Thu, 8 Oct 2020 22:12:25 +0000 (01:12 +0300)
committerGitHub <noreply@github.com>
Thu, 8 Oct 2020 22:12:25 +0000 (22:12 +0000)
[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

modules/gapi/include/opencv2/gapi/gcall.hpp
modules/gapi/include/opencv2/gapi/infer.hpp
modules/gapi/include/opencv2/gapi/infer/ie.hpp
modules/gapi/src/api/gcall.cpp
modules/gapi/src/api/gcall_priv.hpp
modules/gapi/src/api/ginfer.cpp
modules/gapi/src/backends/ie/giebackend.cpp
modules/gapi/src/compiler/gmodel.cpp
modules/gapi/src/compiler/gmodel.hpp
modules/gapi/src/compiler/gmodelbuilder.cpp
modules/gapi/test/infer/gapi_infer_ie_test.cpp

index ed5ba5f..511eca1 100644 (file)
@@ -56,11 +56,16 @@ public:
     Priv& priv();
     const Priv& priv() const;
 
-protected:
-    std::shared_ptr<Priv> m_priv;
+    // GKernel and params can be modified, it's needed for infer<Generic>,
+    // because information about output shapes doesn't exist in compile time
+    GKernel& kernel();
+    cv::util::any& params();
 
     void setArgs(std::vector<GArg> &&args);
 
+protected:
+    std::shared_ptr<Priv> 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);
index 50086dd..4fdd2df 100644 (file)
@@ -121,6 +121,45 @@ struct GInferBase {
     }
 };
 
+// Struct stores network input/output names.
+// Used by infer<Generic>
+struct InOutInfo
+{
+    std::vector<std::string> in_names;
+    std::vector<std::string> 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<std::string, cv::GMat>& getBlobs() const;
+
+private:
+    std::unordered_map<std::string, cv::GMat> in_blobs;
+};
+/** @} */
+
+/**
+ * @{
+ * @brief G-API object used to collect network outputs
+ */
+struct GAPI_EXPORTS GInferOutputs
+{
+public:
+    GInferOutputs(std::shared_ptr<cv::GCall> call);
+    cv::GMat at(const std::string& name);
+
+private:
+    std::shared_ptr<cv::GCall> m_call;
+    InOutInfo* m_info = nullptr;
+    std::unordered_map<std::string, cv::GMat> 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<Net>::on(std::forward<Args>(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<typename T = Generic> GInferOutputs
+infer(const std::string& tag, const GInferInputs& inputs)
+{
+    std::vector<GArg> input_args;
+    std::vector<std::string> 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<cv::GCall>(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
index c6d7f27..8421d9e 100644 (file)
@@ -17,6 +17,7 @@
 
 #include <opencv2/core/cvdef.h>     // GAPI_EXPORTS
 #include <opencv2/gapi/gkernel.hpp> // GKernelPackage
+#include <opencv2/gapi/infer.hpp>   // 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<typename Net::InArgs>::value  // num_in
               , std::tuple_size<typename Net::OutArgs>::value // num_out
-              } {
+              , false} {
     };
 
     Params<Net>& cfgInputLayers(const typename PortCfg<Net>::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<cv::gapi::Generic> {
+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
index 6f5f65b..6a2121b 100644 (file)
@@ -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;
+}
index edc2c22..b142432 100644 (file)
@@ -42,10 +42,11 @@ class GCall::Priv
 {
 public:
     std::vector<GArg> 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);
 };
index 98eeef5..31d851b 100644 (file)
@@ -25,3 +25,33 @@ std::vector<cv::gapi::GBackend> cv::gapi::GNetPackage::backends() const {
     for (const auto &nn : networks) unique_set.insert(nn.backend);
     return std::vector<cv::gapi::GBackend>(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<std::string, cv::GMat>& cv::GInferInputs::getBlobs() const {
+    return in_blobs;
+}
+
+cv::GInferOutputs::GInferOutputs(std::shared_ptr<cv::GCall> call)
+    : m_call(std::move(call)), m_info(cv::util::any_cast<InOutInfo>(&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<int>(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
index 1565d03..b7bda2f 100644 (file)
@@ -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<NetworkParams>();
-            const auto &pp = cv::util::any_cast<cv::gapi::ie::detail::ParamDesc>(np.opaque);
+            auto &np = gm.metadata(nh).get<NetworkParams>();
+            auto &pp = cv::util::any_cast<cv::gapi::ie::detail::ParamDesc>(np.opaque);
             const auto &ki = cv::util::any_cast<KImpl>(ii.opaque);
+
+            GModel::Graph model(gr);
+            auto& op = model.metadata(nh).get<Op>();
+
+            // 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<cv::InOutInfo>(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});
index 39dc1da..b5b76fd 100644 (file)
 
 namespace cv { namespace gimpl {
 
-ade::NodeHandle GModel::mkOpNode(GModel::Graph &g, const GKernel &k, const std::vector<GArg> &args, const std::string &island)
+ade::NodeHandle GModel::mkOpNode(GModel::Graph &g,
+                                 const GKernel &k,
+                                 const std::vector<GArg> &args,
+                                 const cv::util::any &params,
+                                 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;
index 8f78ba4..5f02e58 100644 (file)
@@ -61,6 +61,7 @@ struct Op
     std::vector<RcDesc> 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<GArg>& args, const std::string &island);
+    GAPI_EXPORTS ade::NodeHandle mkOpNode(Graph &g,
+                                          const GKernel &k,
+                                          const std::vector<GArg>& 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);
 
index 87e9ab5..80abadd 100644 (file)
@@ -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;
     }
index 74d8558..3125705 100644 (file)
@@ -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<cv::gapi::Generic>("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<cv::gapi::Generic> 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