1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4 //
5 // Copyright (C) 2018-2019, Intel Corporation, all rights reserved.
6 // Third party copyrights are property of their respective owners.
7 
8 #include "precomp.hpp"
9 
10 #include <fstream>
11 
12 #include "ie_ngraph.hpp"
13 
14 #include <opencv2/dnn/shape_utils.hpp>
15 
16 #ifdef HAVE_DNN_NGRAPH
17 #include <ie_extension.h>
18 #endif  // HAVE_DNN_NGRAPH
19 
20 #include <opencv2/core/utils/configuration.private.hpp>
21 #include <opencv2/core/utils/logger.hpp>
22 
23 #include "opencv2/core/utils/filesystem.hpp"
24 #include "opencv2/core/utils/filesystem.private.hpp"
25 
26 namespace cv { namespace dnn {
27 
28 #ifdef HAVE_DNN_NGRAPH
29 
30 static bool DNN_IE_SERIALIZE = utils::getConfigurationParameterBool("OPENCV_DNN_IE_SERIALIZE", false);
31 
32 // For networks with input layer which has an empty name, IE generates a name id[some_number].
33 // OpenCV lets users use an empty input name and to prevent unexpected naming,
34 // we can use some predefined name.
35 static std::string kDefaultInpLayerName = "opencv_ngraph_empty_inp_layer_name";
36 static constexpr const char* kOpenCVLayersType = "opencv_ngraph_layer";
37 
shapesToStr(const std::vector<Mat> & mats)38 static std::string shapesToStr(const std::vector<Mat>& mats)
39 {
40     std::ostringstream shapes;
41     shapes << mats.size() << " ";
42     for (const Mat& m : mats)
43     {
44         shapes << m.dims << " ";
45         for (int i = 0; i < m.dims; ++i)
46             shapes << m.size[i] << " ";
47     }
48     return shapes.str();
49 }
50 
strToShapes(const std::string & str,std::vector<std::vector<size_t>> & shapes)51 static void strToShapes(const std::string& str, std::vector<std::vector<size_t> >& shapes)
52 {
53     std::istringstream ss(str);
54     int num, dims;
55     ss >> num;
56     shapes.resize(num);
57     for (int i = 0; i < num; ++i)
58     {
59         ss >> dims;
60         shapes[i].resize(dims);
61         for (int j = 0; j < dims; ++j)
62             ss >> shapes[i][j];
63     }
64 }
65 
66 static std::vector<Ptr<NgraphBackendWrapper> >
ngraphWrappers(const std::vector<Ptr<BackendWrapper>> & ptrs)67 ngraphWrappers(const std::vector<Ptr<BackendWrapper> >& ptrs)
68 {
69     std::vector<Ptr<NgraphBackendWrapper> > wrappers(ptrs.size());
70     for (int i = 0; i < ptrs.size(); ++i)
71     {
72         CV_Assert(!ptrs[i].empty());
73         wrappers[i] = ptrs[i].dynamicCast<NgraphBackendWrapper>();
74         CV_Assert(!wrappers[i].empty());
75     }
76     return wrappers;
77 }
78 
79 class NgraphCustomOp: public ngraph::op::Op {
80 public:
get_type_info() const81     const ngraph::NodeTypeInfo& get_type_info() const override
82     {
83         static constexpr ngraph::NodeTypeInfo type_info{kOpenCVLayersType, 0};
84         return type_info;
85     }
86 
87 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_3)
NgraphCustomOp(const ngraph::OutputVector & inputs,const std::map<std::string,InferenceEngine::Parameter> & params={})88     NgraphCustomOp(const ngraph::OutputVector& inputs,
89 #else
90     NgraphCustomOp(const ngraph::NodeVector& inputs,
91 #endif
92                    const std::map<std::string, InferenceEngine::Parameter>& params = {}):
93         Op(inputs), params(params)
94     {
95         constructor_validate_and_infer_types();
96     }
97 
~NgraphCustomOp()98     ~NgraphCustomOp()
99     {
100         // nothing
101     }
102 
validate_and_infer_types()103     void validate_and_infer_types() override
104     {
105         std::vector<std::vector<size_t> > shapes;
106         strToShapes(params["outputs"], shapes);
107         set_output_size(shapes.size());
108         for (size_t i = 0; i < shapes.size(); ++i)
109         {
110             ngraph::Shape output_shape(shapes[i]);
111             set_output_type(i, get_input_element_type(0), output_shape);
112         }
113     }
114 
115 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_4)
clone_with_new_inputs(const ngraph::OutputVector & new_args) const116     std::shared_ptr<ngraph::Node> clone_with_new_inputs(const ngraph::OutputVector& new_args) const override
117     {
118         return std::make_shared<NgraphCustomOp>(new_args, params);
119     }
120 #else
copy_with_new_args(const ngraph::NodeVector & new_args) const121     std::shared_ptr<ngraph::Node> copy_with_new_args(const ngraph::NodeVector& new_args) const override
122     {
123 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_3)
124         return std::make_shared<NgraphCustomOp>(ngraph::as_output_vector(new_args), params);
125 #else
126         return std::make_shared<NgraphCustomOp>(new_args, params);
127 #endif
128     }
129 #endif
130 
visit_attributes(ngraph::AttributeVisitor & visitor)131     bool visit_attributes(ngraph::AttributeVisitor& visitor) override
132     {
133         for (auto& attr : params)
134         {
135             if (attr.second.is<std::string>())
136                 visitor.on_attribute(attr.first, attr.second.as<std::string>());
137         }
138         return true;
139     }
140 
141     std::map<std::string, InferenceEngine::Parameter> params;
142 };
143 
144 
145 class InfEngineNgraphCustomLayer : public InferenceEngine::ILayerExecImpl
146 {
147 public:
148 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_2)
InfEngineNgraphCustomLayer(const std::shared_ptr<ngraph::Node> & _node)149     explicit InfEngineNgraphCustomLayer(const std::shared_ptr<ngraph::Node>& _node)
150     {
151         node = std::dynamic_pointer_cast<NgraphCustomOp>(_node);
152         CV_Assert(node);
153         std::string implStr = node->params["impl"];
154         std::istringstream iss(implStr);
155 #else
156     explicit InfEngineNgraphCustomLayer(const InferenceEngine::CNNLayer& layer) : cnnLayer(layer)
157     {
158         std::istringstream iss(layer.GetParamAsString("impl"));
159 #endif
160         size_t ptr;
161         iss >> ptr;
162         cvLayer = (Layer*)ptr;
163 
164         std::vector<std::vector<size_t> > shapes;
165 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_2)
166         strToShapes(node->params["internals"], shapes);
167 #else
168         strToShapes(layer.GetParamAsString("internals"), shapes);
169 #endif
170         internals.resize(shapes.size());
171         for (int i = 0; i < shapes.size(); ++i)
172             internals[i].create(std::vector<int>(shapes[i].begin(), shapes[i].end()), CV_32F);
173     }
174 
175     ~InfEngineNgraphCustomLayer()
176     {
177         // nothing
178     }
179 
180     virtual InferenceEngine::StatusCode execute(std::vector<InferenceEngine::Blob::Ptr>& inputs,
181                                                 std::vector<InferenceEngine::Blob::Ptr>& outputs,
182                                                 InferenceEngine::ResponseDesc *resp) noexcept
183     {
184         std::vector<Mat> inpMats, outMats;
185         infEngineBlobsToMats(inputs, inpMats);
186         infEngineBlobsToMats(outputs, outMats);
187 
188         try
189         {
190             cvLayer->forward(inpMats, outMats, internals);
191             return InferenceEngine::StatusCode::OK;
192         }
193         catch (...)
194         {
195             return InferenceEngine::StatusCode::GENERAL_ERROR;
196         }
197     }
198 
199     virtual InferenceEngine::StatusCode
200     getSupportedConfigurations(std::vector<InferenceEngine::LayerConfig>& conf,
201                                InferenceEngine::ResponseDesc* resp) noexcept
202     {
203         std::vector<InferenceEngine::DataConfig> inDataConfig;
204         std::vector<InferenceEngine::DataConfig> outDataConfig;
205 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_2)
206         InferenceEngine::SizeVector order;
207         size_t offset = std::numeric_limits<size_t>::max();
208         for (int i = 0; i < node->get_input_size(); ++i)
209         {
210             InferenceEngine::DataConfig conf;
211             auto shape = node->input_value(i).get_shape();
212             order.resize(shape.size());
213             std::iota(order.begin(), order.end(), 0);
214             conf.desc = InferenceEngine::TensorDesc(InferenceEngine::Precision::FP32, shape, {shape, order, offset});
215             inDataConfig.push_back(conf);
216         }
217 
218         for (int i = 0; i < node->get_output_size(); ++i)
219         {
220             InferenceEngine::DataConfig conf;
221             auto shape = node->output(i).get_shape();
222             order.resize(shape.size());
223             std::iota(order.begin(), order.end(), 0);
224             conf.desc = InferenceEngine::TensorDesc(InferenceEngine::Precision::FP32, shape, {shape, order, offset});
225             outDataConfig.push_back(conf);
226         }
227 #else
228         for (auto& it : cnnLayer.insData)
229         {
230             InferenceEngine::DataConfig conf;
231             conf.desc = it.lock()->getTensorDesc();
232             inDataConfig.push_back(conf);
233         }
234 
235         for (auto& it : cnnLayer.outData)
236         {
237             InferenceEngine::DataConfig conf;
238             conf.desc = it->getTensorDesc();
239             outDataConfig.push_back(conf);
240         }
241 #endif
242 
243         InferenceEngine::LayerConfig layerConfig;
244         layerConfig.inConfs = inDataConfig;
245         layerConfig.outConfs = outDataConfig;
246 
247         conf.push_back(layerConfig);
248         return InferenceEngine::StatusCode::OK;
249     }
250 
251     InferenceEngine::StatusCode init(InferenceEngine::LayerConfig& config,
252                                      InferenceEngine::ResponseDesc *resp) noexcept
253     {
254         return InferenceEngine::StatusCode::OK;
255     }
256 
257 private:
258 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_2)
259     std::shared_ptr<NgraphCustomOp> node;
260 #else
261     InferenceEngine::CNNLayer cnnLayer;
262 #endif
263     dnn::Layer* cvLayer;
264     std::vector<Mat> internals;
265 };
266 
267 #if INF_ENGINE_VER_MAJOR_LT(INF_ENGINE_RELEASE_2020_2)
268 class InfEngineNgraphCustomLayerFactory : public InferenceEngine::ILayerImplFactory {
269 public:
InfEngineNgraphCustomLayerFactory(const InferenceEngine::CNNLayer * layer)270     explicit InfEngineNgraphCustomLayerFactory(const InferenceEngine::CNNLayer* layer) : cnnLayer(*layer)
271     {
272         // nothing
273     }
274 
275     InferenceEngine::StatusCode
getImplementations(std::vector<InferenceEngine::ILayerImpl::Ptr> & impls,InferenceEngine::ResponseDesc * resp)276     getImplementations(std::vector<InferenceEngine::ILayerImpl::Ptr>& impls,
277                        InferenceEngine::ResponseDesc* resp) noexcept override
278     {
279         impls.push_back(std::make_shared<InfEngineNgraphCustomLayer>(cnnLayer));
280         return InferenceEngine::StatusCode::OK;
281     }
282 
283 private:
284     InferenceEngine::CNNLayer cnnLayer;
285 };
286 #endif
287 
288 
289 class InfEngineNgraphExtension : public InferenceEngine::IExtension
290 {
291 public:
Unload()292     void Unload() noexcept override {}
Release()293     void Release() noexcept override { delete this; }
GetVersion(const InferenceEngine::Version * &) const294     void GetVersion(const InferenceEngine::Version*&) const noexcept override {}
295 
296 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_2)
getImplTypes(const std::shared_ptr<ngraph::Node> & node)297     std::vector<std::string> getImplTypes(const std::shared_ptr<ngraph::Node>& node) override {
298         return {"CPU"};
299     }
300 
getImplementation(const std::shared_ptr<ngraph::Node> & node,const std::string & implType)301     InferenceEngine::ILayerImpl::Ptr getImplementation(const std::shared_ptr<ngraph::Node>& node, const std::string& implType) override {
302         if (std::dynamic_pointer_cast<NgraphCustomOp>(node) && implType == "CPU") {
303             return std::make_shared<InfEngineNgraphCustomLayer>(node);
304         }
305         return nullptr;
306     }
307 #else
SetLogCallback(InferenceEngine::IErrorListener &)308     virtual void SetLogCallback(InferenceEngine::IErrorListener&) noexcept {}
309 
getPrimitiveTypes(char ** &,unsigned int &,InferenceEngine::ResponseDesc *)310     virtual InferenceEngine::StatusCode getPrimitiveTypes(char**&, unsigned int&,
311                                                           InferenceEngine::ResponseDesc*) noexcept
312     {
313         return InferenceEngine::StatusCode::OK;
314     }
315 
getFactoryFor(InferenceEngine::ILayerImplFactory * & factory,const InferenceEngine::CNNLayer * cnnLayer,InferenceEngine::ResponseDesc * resp)316     InferenceEngine::StatusCode getFactoryFor(InferenceEngine::ILayerImplFactory*& factory,
317                                               const InferenceEngine::CNNLayer* cnnLayer,
318                                               InferenceEngine::ResponseDesc* resp) noexcept
319     {
320         if (cnnLayer->type != kOpenCVLayersType)
321             return InferenceEngine::StatusCode::NOT_IMPLEMENTED;
322         factory = new InfEngineNgraphCustomLayerFactory(cnnLayer);
323         return InferenceEngine::StatusCode::OK;
324     }
325 #endif
326 };
327 
328 
329 
InfEngineNgraphNode(std::shared_ptr<ngraph::Node> && _node)330 InfEngineNgraphNode::InfEngineNgraphNode(std::shared_ptr<ngraph::Node>&& _node)
331     : BackendNode(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH), node(std::move(_node)) {}
332 
InfEngineNgraphNode(std::shared_ptr<ngraph::Node> & _node)333 InfEngineNgraphNode::InfEngineNgraphNode(std::shared_ptr<ngraph::Node>& _node)
334     : BackendNode(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH), node(_node) {}
335 
InfEngineNgraphNode(const std::vector<Ptr<BackendNode>> & nodes,Ptr<Layer> & cvLayer_,std::vector<Mat * > & inputs,std::vector<Mat> & outputs,std::vector<Mat> & internals)336 InfEngineNgraphNode::InfEngineNgraphNode(const std::vector<Ptr<BackendNode> >& nodes,
337                                          Ptr<Layer>& cvLayer_, std::vector<Mat*>& inputs,
338                                          std::vector<Mat>& outputs, std::vector<Mat>& internals)
339     : BackendNode(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH), cvLayer(cvLayer_)
340 {
341     std::ostringstream oss;
342     oss << (size_t)cvLayer.get();
343 
344     std::map<std::string, InferenceEngine::Parameter> params = {
345         {"impl", oss.str()},
346         {"outputs", shapesToStr(outputs)},
347         {"internals", shapesToStr(internals)}
348     };
349 
350 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_3)
351     ngraph::OutputVector inp_nodes;
352 #else
353     ngraph::NodeVector inp_nodes;
354 #endif
355     for (const auto& node : nodes)
356         inp_nodes.emplace_back(node.dynamicCast<InfEngineNgraphNode>()->node);
357     node = std::make_shared<NgraphCustomOp>(inp_nodes, params);
358 
359     CV_Assert(!cvLayer->name.empty());
360     setName(cvLayer->name);
361 }
362 
setName(const std::string & name)363 void InfEngineNgraphNode::setName(const std::string& name) {
364     node->set_friendly_name(name);
365 }
366 
InfEngineNgraphNet(detail::NetImplBase & netImpl)367 InfEngineNgraphNet::InfEngineNgraphNet(detail::NetImplBase& netImpl)
368     : netImpl_(netImpl)
369 {
370     hasNetOwner = false;
371     device_name = "CPU";
372 }
373 
InfEngineNgraphNet(detail::NetImplBase & netImpl,InferenceEngine::CNNNetwork & net)374 InfEngineNgraphNet::InfEngineNgraphNet(detail::NetImplBase& netImpl, InferenceEngine::CNNNetwork& net)
375     : netImpl_(netImpl)
376     , cnn(net)
377 {
378     hasNetOwner = true;
379     device_name = "CPU";
380 }
381 
addOutput(const std::string & name)382 void InfEngineNgraphNet::addOutput(const std::string& name)
383 {
384     requestedOutputs.push_back(name);
385 }
386 
setNodePtr(std::shared_ptr<ngraph::Node> * ptr)387 void InfEngineNgraphNet::setNodePtr(std::shared_ptr<ngraph::Node>* ptr) {
388     all_nodes.emplace((*ptr)->get_friendly_name(), ptr);
389 }
390 
release()391  void InfEngineNgraphNet::release() {
392      for (auto& node : components.back()) {
393 #if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2020_4)
394          if (!(ngraph::op::is_parameter(node) || ngraph::op::is_output(node) || ngraph::op::is_constant(node)) ) {
395 #else
396          if (!(node->is_parameter() || node->is_output() || node->is_constant()) ) {
397 #endif
398              auto it = all_nodes.find(node->get_friendly_name());
399              if (it != all_nodes.end()) {
400                  unconnectedNodes.erase(*(it->second));
401                  it->second->reset();
402                  all_nodes.erase(it);
403              }
404          }
405      }
406  }
407 
408 void InfEngineNgraphNet::dfs(std::shared_ptr<ngraph::Node>& node,
409                              std::vector<std::shared_ptr<ngraph::Node>>& comp,
410                              std::unordered_map<std::string, bool>& used) {
411     used[node->get_friendly_name()] = true;
412     comp.push_back(node);
413     auto inputs = node->get_users();
414     for (size_t i = 0; i < node->get_input_size(); ++i) {
415         inputs.push_back(node->input_value(i).get_node()->shared_from_this());
416     }
417 
418     for (auto& to : inputs) {
419         if (!used[to->get_friendly_name()]) {
420             dfs(to, comp, used);
421         }
422     }
423 }
424 
425 int InfEngineNgraphNet::getNumComponents() {
426     if (!components.empty()) {
427         return components.size();
428     }
429     std::unordered_map<std::string, bool> used;
430     auto inputs = ngraph_function->get_ordered_ops();
431     for (auto& node : inputs) {
432         used.emplace(node->get_friendly_name(), false);
433     }
434 
435     for (auto& node : inputs) {
436         if (!used[node->get_friendly_name()]) {
437             std::vector<std::shared_ptr<ngraph::Node>> current_comp;
438             dfs(node, current_comp, used);
439             components.push_back(current_comp);
440         }
441     }
442     return components.size();
443 }
444 
445 void InfEngineNgraphNet::createNet(Target targetId) {
446     if (!hasNetOwner)
447     {
448         CV_Assert(!unconnectedNodes.empty());
449         ngraph::ResultVector outs;
450         for (auto& node : unconnectedNodes)
451         {
452             auto out = std::make_shared<ngraph::op::Result>(node);
453             outs.push_back(out);
454         }
455         CV_Assert_N(!inputs_vec.empty(), !outs.empty());
456         ngraph_function = std::make_shared<ngraph::Function>(outs, inputs_vec);
457 
458         int num_comp = getNumComponents();
459         if (num_comp > 1) {
460             for (int i = num_comp - 1; i >= 0; --i) {
461                 ngraph::ResultVector outputs;
462                 ngraph::ParameterVector inps;
463                 for (auto& node : components.back()) {
464 #if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2020_4)
465                     if (ngraph::op::is_parameter(node)) {
466 #else
467                     if (node->is_parameter()) {
468 #endif
469                         auto parameter = std::dynamic_pointer_cast<ngraph::op::Parameter>(node);
470                         inps.push_back(parameter);
471                     }
472 #if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2020_4)
473                     else if (ngraph::op::is_output(node)) {
474 #else
475                     else if (node->is_output()) {
476 #endif
477                         auto result = std::dynamic_pointer_cast<ngraph::op::Result>(node);
478                         outputs.push_back(result);
479                     }
480                 }
481                 isInit = false;
482                 CV_Assert_N(!inps.empty(), !outputs.empty());
483                 ngraph_function = std::make_shared<ngraph::Function>(outputs, inps);
484                 release();
485                 components.pop_back();
486                 init(targetId);
487             }
488         } else {
489             release();
490             components.clear();
491             init(targetId);
492         }
493     }
494 }
495 
496 void InfEngineNgraphNet::init(Target targetId)
497 {
498     if (!hasNetOwner)
499     {
500         if (targetId == DNN_TARGET_OPENCL_FP16)
501         {
502             auto nodes = ngraph_function->get_ordered_ops();
503             for (auto& node : nodes)
504             {
505                 auto parameter = std::dynamic_pointer_cast<ngraph::op::Parameter>(node);
506                 if (parameter && parameter->get_element_type() == ngraph::element::f32)
507                 {
508                     parameter->set_element_type(ngraph::element::f16);
509                 }
510                 auto constant = std::dynamic_pointer_cast<ngraph::op::Constant>(node);
511                 if (constant && constant->get_element_type() == ngraph::element::f32)
512                 {
513                     const float* floatsData = constant->get_data_ptr<float>();
514                     size_t total = ngraph::shape_size(constant->get_shape());
515                     Mat floats(1, total, CV_32F, (void*)floatsData);
516                     Mat halfs;
517                     cv::convertFp16(floats, halfs);
518 
519                     auto new_const = std::make_shared<ngraph::op::Constant>(ngraph::element::f16, constant->get_shape(), halfs.data);
520                     new_const->set_friendly_name(constant->get_friendly_name());
521                     ngraph::replace_node(constant, new_const);
522                 }
523             }
524             ngraph_function->validate_nodes_and_infer_types();
525         }
526         cnn = InferenceEngine::CNNNetwork(ngraph_function);
527 
528         if (DNN_IE_SERIALIZE)
529         {
530 #ifndef OPENCV_DNN_DISABLE_NETWORK_AUTO_DUMP
531             std::string dumpFileNameBase = netImpl_.getDumpFileNameBase();
532             try
533             {
534                 cnn.serialize(dumpFileNameBase + "_ngraph.xml", dumpFileNameBase + "_ngraph.bin");
535             }
536             catch (const std::exception& e)
537             {
538                 std::ofstream out((dumpFileNameBase + "_ngraph.error").c_str(), std::ios::out);
539                 out << "Exception: " << e.what() << std::endl;
540             }
541             catch (...)
542             {
543                 std::ofstream out((dumpFileNameBase + "_ngraph.error").c_str(), std::ios::out);
544                 out << "Can't dump: unknown exception" << std::endl;
545             }
546 #endif
547         }
548     }
549 
550     switch (targetId)
551     {
552         case DNN_TARGET_CPU:
553             device_name = "CPU";
554             break;
555         case DNN_TARGET_OPENCL:
556         case DNN_TARGET_OPENCL_FP16:
557             device_name = "GPU";
558             break;
559         case DNN_TARGET_MYRIAD:
560             device_name = "MYRIAD";
561             break;
562         case DNN_TARGET_HDDL:
563             device_name = "HDDL";
564             break;
565         case DNN_TARGET_FPGA:
566             device_name = "FPGA";
567             break;
568         default:
569             CV_Error(Error::StsNotImplemented, "Unknown target");
570     };
571 
572     if (!hasNetOwner) {
573         for (size_t i = 0; i < ngraph_function->get_output_size(); ++i) {
574             auto node = ngraph_function->output(i).get_node();
575             for (size_t j = 0; j < node->get_input_size(); ++j) {
576                 std::string name = node->input_value(j).get_node()->get_friendly_name();
577                 auto iter = std::find(requestedOutputs.begin(), requestedOutputs.end(), name);
578                 if (iter != requestedOutputs.end()) {
579                     requestedOutputs.erase(iter);
580                     cnn.addOutput(name);
581                 }
582             }
583         }
584     }
585     for (const auto& name : requestedOutputs)
586     {
587         cnn.addOutput(name);
588     }
589 
590     for (const auto& it : cnn.getInputsInfo())
591     {
592         const std::string& name = it.first;
593         auto blobIt = allBlobs.find(name);
594         CV_Assert(blobIt != allBlobs.end());
595         it.second->setPrecision(blobIt->second->getTensorDesc().getPrecision());
596     }
597 
598     for (const auto& it : cnn.getOutputsInfo())
599     {
600         const std::string& name = it.first;
601         auto blobIt = allBlobs.find(name);
602         CV_Assert(blobIt != allBlobs.end());
603         it.second->setPrecision(blobIt->second->getTensorDesc().getPrecision());  // Should be always FP32
604     }
605 
606     initPlugin(cnn);
607 }
608 
609 ngraph::ParameterVector InfEngineNgraphNet::setInputs(const std::vector<cv::Mat>& inputs,
610                                    const std::vector<std::string>& names) {
611     CV_Assert_N(inputs.size() == names.size());
612     ngraph::ParameterVector current_inp;
613     for (size_t i = 0; i < inputs.size(); i++)
614     {
615         std::vector<size_t> shape = getShape<size_t>(inputs[i]);
616         auto inp = std::make_shared<ngraph::op::Parameter>(ngraph::element::f32, ngraph::Shape(shape));
617         inp->set_friendly_name(names[i]);
618 
619         auto it = std::find_if(inputs_vec.begin(), inputs_vec.end(),
620                                 [&inp](const std::shared_ptr<ngraph::op::Parameter>& a) {
621                                 return a->get_friendly_name() == inp->get_friendly_name();
622                   });
623         if (it == inputs_vec.end()) {
624             inputs_vec.push_back(inp);
625             current_inp.push_back(inp);
626         } else {
627             current_inp.push_back(*it);
628         }
629     }
630     return current_inp;
631 }
632 
633 void InfEngineNgraphNet::setUnconnectedNodes(Ptr<InfEngineNgraphNode>& node) {
634     unconnectedNodes.insert(node->node);
635 }
636 
637 void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net)
638 {
639     CV_Assert(!isInitialized());
640 
641     try
642     {
643         AutoLock lock(getInitializationMutex());
644         InferenceEngine::Core& ie = getCore(device_name);
645         {
646             isInit = true;
647             std::vector<std::string> candidates;
648             std::string param_pluginPath = utils::getConfigurationParameterString("OPENCV_DNN_IE_EXTRA_PLUGIN_PATH", "");
649             if (!param_pluginPath.empty())
650             {
651                 candidates.push_back(param_pluginPath);
652             }
653             bool found = false;
654             for (size_t i = 0; i != candidates.size(); ++i)
655             {
656                 const std::string& libName = candidates[i];
657                 try
658                 {
659                     InferenceEngine::IExtensionPtr extension =
660 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_4)
661                         std::make_shared<InferenceEngine::Extension>(libName);
662 #else
663                         InferenceEngine::make_so_pointer<InferenceEngine::IExtension>(libName);
664 #endif
665 
666                     ie.AddExtension(extension, "CPU");
667                     CV_LOG_INFO(NULL, "DNN-IE: Loaded extension plugin: " << libName);
668                     found = true;
669                     break;
670                 }
671                 catch(...) {}
672             }
673             if (!found && !candidates.empty())
674             {
675                 CV_LOG_WARNING(NULL, "DNN-IE: Can't load extension plugin (extra layers for some networks). Specify path via OPENCV_DNN_IE_EXTRA_PLUGIN_PATH parameter");
676             }
677             // Some of networks can work without a library of extra layers.
678             // OpenCV fallbacks as extensions.
679             try
680             {
681                 ie.AddExtension(std::make_shared<InfEngineNgraphExtension>(), "CPU");
682             }
683             catch(const std::exception& e)
684             {
685                 CV_LOG_INFO(NULL, "DNN-IE: Can't register OpenCV custom layers nGraph extension: " << e.what());
686             }
687 #ifndef _WIN32
688             // Limit the number of CPU threads.
689             if (device_name == "CPU")
690                 ie.SetConfig({{
691                     InferenceEngine::PluginConfigParams::KEY_CPU_THREADS_NUM, format("%d", getNumThreads()),
692                 }}, device_name);
693 #endif
694 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_2)
695             if (device_name.find("GPU") == 0)
696             {
697 #if OPENCV_HAVE_FILESYSTEM_SUPPORT
698                 std::string cache_path = utils::fs::getCacheDirectory((std::string("dnn_ie_cache_") + device_name).c_str(), "OPENCV_DNN_IE_GPU_CACHE_DIR");
699 #else
700                 std::string cache_path = utils::getConfigurationParameterString("OPENCV_DNN_IE_GPU_CACHE_DIR", "");
701 #endif
702                 if (!cache_path.empty() && cache_path != "disabled")
703                 {
704                     CV_LOG_INFO(NULL, "OpenCV/nGraph: using GPU kernels cache: " << cache_path);
705                     ie.SetConfig({{
706                         InferenceEngine::PluginConfigParams::KEY_CACHE_DIR, cache_path,
707                     }}, device_name);
708                 }
709             }
710 #endif
711         }
712         std::map<std::string, std::string> config;
713         if (device_name == "MYRIAD" || device_name == "HDDL") {
714 #if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2020_4)
715             config.emplace("MYRIAD_DETECT_NETWORK_BATCH", CONFIG_VALUE(NO));
716 #else
717             config.emplace("VPU_DETECT_NETWORK_BATCH", CONFIG_VALUE(NO));
718 #endif
719         }
720 
721         bool isHetero = device_name == "FPGA";
722         // It is actual only for non-CPU targets and networks built in runtime using nGraph.
723         // We do not check IR models because they can be with version less than IRv10
724         if (!isHetero && device_name != "CPU" && !hasNetOwner)
725         {
726             for (auto& node : net.getFunction()->get_ops())
727             {
728                 if (node->description() == kOpenCVLayersType)
729                 {
730                     isHetero = true;
731                     break;
732                 }
733             }
734         }
735         if (isHetero)
736             netExec = ie.LoadNetwork(net, "HETERO:" + device_name + ",CPU", config);
737         else
738             netExec = ie.LoadNetwork(net, device_name, config);
739     }
740     catch (const std::exception& ex)
741     {
742         CV_Error(Error::StsError, format("Failed to initialize Inference Engine backend (device = %s): %s", device_name.c_str(), ex.what()));
743     }
744 }
745 
746 bool InfEngineNgraphNet::isInitialized()
747 {
748     return isInit;
749 }
750 
751 bool NgraphBackendLayer::getMemoryShapes(const std::vector<MatShape> &inputs,
752                                             const int requiredOutputs,
753                                             std::vector<MatShape> &outputs,
754                                             std::vector<MatShape> &internals) const
755 {
756     InferenceEngine::ICNNNetwork::InputShapes inShapes = t_net.getInputShapes();
757     InferenceEngine::ICNNNetwork::InputShapes::iterator itr;
758     bool equal_flag = true;
759     size_t i = 0;
760     for (itr = inShapes.begin(); itr != inShapes.end(); ++itr)
761     {
762         InferenceEngine::SizeVector currentInShape(inputs[i].begin(), inputs[i].end());
763         if (itr->second != currentInShape)
764         {
765             itr->second = currentInShape;
766             equal_flag = false;
767         }
768         i++;
769     }
770 
771     if (!equal_flag)
772     {
773         InferenceEngine::CNNNetwork curr_t_net(t_net);
774         curr_t_net.reshape(inShapes);
775     }
776     std::vector<size_t> dims = t_net.getOutputsInfo()[name]->getDims();
777     outputs.push_back(MatShape(dims.begin(), dims.end()));
778     return false;
779 }
780 
781 bool NgraphBackendLayer::supportBackend(int backendId)
782 {
783     CV_LOG_DEBUG(NULL, "NgraphBackendLayer::supportBackend(" << backendId << ")");
784     return backendId == DNN_BACKEND_DEFAULT ||
785            (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH);
786 }
787 
788 void NgraphBackendLayer::forward(InputArrayOfArrays inputs, OutputArrayOfArrays outputs,
789                                     OutputArrayOfArrays internals)
790 {
791     CV_Error(Error::StsInternal, "Choose Inference Engine as a preferable backend.");
792 }
793 
794 
795 static InferenceEngine::Layout estimateLayout(const Mat& m)
796 {
797     if (m.dims == 4)
798         return InferenceEngine::Layout::NCHW;
799     else if (m.dims == 3)
800         return InferenceEngine::Layout::CHW;
801     else if (m.dims == 2)
802         return InferenceEngine::Layout::NC;
803     else if (m.dims == 1)
804         return InferenceEngine::Layout::C;
805     else if (m.dims == 5)
806         return InferenceEngine::Layout::NCDHW;
807     else
808         return InferenceEngine::Layout::ANY;
809 }
810 
811 static InferenceEngine::DataPtr wrapToInfEngineDataNode(const Mat& m, const std::string& name = "")
812 {
813     std::vector<size_t> shape = getShape<size_t>(m);
814     if (m.type() == CV_32F)
815         return InferenceEngine::DataPtr(new InferenceEngine::Data(name,
816                {InferenceEngine::Precision::FP32, shape, estimateLayout(m)}));
817     else if (m.type() == CV_8U)
818         return InferenceEngine::DataPtr(new InferenceEngine::Data(name,
819                {InferenceEngine::Precision::U8, shape, estimateLayout(m)}));
820     else
821         CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str()));
822 }
823 
824 InferenceEngine::Blob::Ptr wrapToNgraphBlob(const Mat& m, const std::vector<size_t>& shape,
825                                                InferenceEngine::Layout layout)
826 {
827     if (m.type() == CV_32F)
828         return InferenceEngine::make_shared_blob<float>(
829                {InferenceEngine::Precision::FP32, shape, layout}, (float*)m.data);
830     else if (m.type() == CV_8U)
831         return InferenceEngine::make_shared_blob<uint8_t>(
832                {InferenceEngine::Precision::U8, shape, layout}, (uint8_t*)m.data);
833     else
834         CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str()));
835 }
836 
837 InferenceEngine::Blob::Ptr wrapToNgraphBlob(const Mat& m, InferenceEngine::Layout layout)
838 {
839     std::vector<size_t> shape = getShape<size_t>(m);
840     return wrapToNgraphBlob(m, shape, layout);
841 }
842 
843 NgraphBackendWrapper::NgraphBackendWrapper(int targetId, const cv::Mat& m)
844     : BackendWrapper(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, targetId)
845 {
846     dataPtr = wrapToInfEngineDataNode(m);
847     blob = wrapToNgraphBlob(m, estimateLayout(m));
848 }
849 
850 NgraphBackendWrapper::NgraphBackendWrapper(Ptr<BackendWrapper> wrapper)
851     : BackendWrapper(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, wrapper->targetId)
852 {
853     Ptr<NgraphBackendWrapper> ieWrapper = wrapper.dynamicCast<NgraphBackendWrapper>();
854     CV_Assert(!ieWrapper.empty());
855     InferenceEngine::DataPtr srcData = ieWrapper->dataPtr;
856     dataPtr = InferenceEngine::DataPtr(new InferenceEngine::Data(srcData->getName(), srcData->getTensorDesc()));
857     blob = ieWrapper->blob;
858 }
859 
860 Ptr<BackendWrapper> NgraphBackendWrapper::create(Ptr<BackendWrapper> wrapper)
861 {
862     return Ptr<BackendWrapper>(new NgraphBackendWrapper(wrapper));
863 }
864 
865 NgraphBackendWrapper::~NgraphBackendWrapper()
866 {
867     // nothing
868 }
869 
870 void NgraphBackendWrapper::copyToHost()
871 {
872     CV_LOG_DEBUG(NULL, "NgraphBackendWrapper::copyToHost()");
873     //CV_Error(Error::StsNotImplemented, "");
874 }
875 
876 void NgraphBackendWrapper::setHostDirty()
877 {
878     CV_LOG_DEBUG(NULL, "NgraphBackendWrapper::setHostDirty()");
879     //CV_Error(Error::StsNotImplemented, "");
880 }
881 
882 InferenceEngine::Blob::Ptr copyBlob(const InferenceEngine::Blob::Ptr& blob)
883 {
884     InferenceEngine::Blob::Ptr copy;
885     auto description = blob->getTensorDesc();
886     InferenceEngine::Precision precision = description.getPrecision();
887     if (precision == InferenceEngine::Precision::FP32)
888     {
889         copy = InferenceEngine::make_shared_blob<float>(description);
890     }
891     else if (precision == InferenceEngine::Precision::U8)
892     {
893         copy = InferenceEngine::make_shared_blob<uint8_t>(description);
894     }
895     else
896         CV_Error(Error::StsNotImplemented, "Unsupported blob precision");
897     copy->allocate();
898     return copy;
899 }
900 
901 InferenceEngine::DataPtr ngraphDataNode(const Ptr<BackendWrapper>& ptr)
902 {
903     CV_Assert(!ptr.empty());
904     Ptr<NgraphBackendWrapper> p = ptr.dynamicCast<NgraphBackendWrapper>();
905     CV_Assert(!p.empty());
906     return p->dataPtr;
907 }
908 
909 
910 void forwardNgraph(const std::vector<Ptr<BackendWrapper> >& outBlobsWrappers,
911                       Ptr<BackendNode>& node, bool isAsync)
912 {
913     CV_Assert(!node.empty());
914     Ptr<InfEngineNgraphNode> ieNode = node.dynamicCast<InfEngineNgraphNode>();
915     CV_Assert(!ieNode.empty());
916     ieNode->net->forward(outBlobsWrappers, isAsync);
917 }
918 
919 void InfEngineNgraphNet::reset()
920 {
921     allBlobs.clear();
922     infRequests.clear();
923     isInit = false;
924 }
925 
926 void InfEngineNgraphNet::addBlobs(const std::vector<cv::Ptr<BackendWrapper> >& ptrs)
927 {
928     auto wrappers = ngraphWrappers(ptrs);
929     for (const auto& wrapper : wrappers)
930     {
931         std::string name = wrapper->dataPtr->getName();
932         name = name.empty() ? kDefaultInpLayerName : name;
933         allBlobs.insert({name, wrapper->blob});
934     }
935 }
936 
937 void InfEngineNgraphNet::NgraphReqWrapper::makePromises(const std::vector<Ptr<BackendWrapper> >& outsWrappers)
938 {
939     auto outs = ngraphWrappers(outsWrappers);
940     outProms.clear();
941     outProms.resize(outs.size());
942     outsNames.resize(outs.size());
943     for (int i = 0; i < outs.size(); ++i)
944     {
945         outs[i]->futureMat = outProms[i].getArrayResult();
946         outsNames[i] = outs[i]->dataPtr->getName();
947     }
948 }
949 
950 Mat ngraphBlobToMat(const InferenceEngine::Blob::Ptr& blob)
951 {
952     std::vector<size_t> dims = blob->getTensorDesc().getDims();
953     std::vector<int> size(dims.begin(), dims.end());
954     auto precision = blob->getTensorDesc().getPrecision();
955 
956     int type = -1;
957     switch (precision)
958     {
959         case InferenceEngine::Precision::FP32: type = CV_32F; break;
960         case InferenceEngine::Precision::U8: type = CV_8U; break;
961         default:
962             CV_Error(Error::StsNotImplemented, "Unsupported blob precision");
963     }
964     return Mat(size, type, (void*)blob->buffer());
965 }
966 
967 void InfEngineNgraphNet::forward(const std::vector<Ptr<BackendWrapper> >& outBlobsWrappers, bool isAsync)
968 {
969     CV_LOG_DEBUG(NULL, "InfEngineNgraphNet::forward(" << (isAsync ? "async" : "sync") << ")");
970 
971     // Look for finished requests.
972     Ptr<NgraphReqWrapper> reqWrapper;
973     for (auto& wrapper : infRequests)
974     {
975         if (wrapper->isReady)
976         {
977             reqWrapper = wrapper;
978             break;
979         }
980     }
981     if (reqWrapper.empty())
982     {
983         reqWrapper = Ptr<NgraphReqWrapper>(new NgraphReqWrapper());
984         try
985         {
986             reqWrapper->req = netExec.CreateInferRequest();
987         }
988         catch (const std::exception& ex)
989         {
990             CV_Error(Error::StsAssert, format("Failed to initialize Inference Engine backend: %s", ex.what()));
991         }
992         infRequests.push_back(reqWrapper);
993 
994         InferenceEngine::BlobMap inpBlobs, outBlobs;
995         for (const auto& it : cnn.getInputsInfo())
996         {
997             const std::string& name = it.first;
998             auto blobIt = allBlobs.find(name);
999             CV_Assert(blobIt != allBlobs.end());
1000             inpBlobs[name] = isAsync ? copyBlob(blobIt->second) : blobIt->second;
1001         }
1002         for (const auto& it : cnn.getOutputsInfo())
1003         {
1004             const std::string& name = it.first;
1005             auto blobIt = allBlobs.find(name);
1006             CV_Assert(blobIt != allBlobs.end());
1007             outBlobs[name] = isAsync ? copyBlob(blobIt->second) : blobIt->second;
1008         }
1009         reqWrapper->req.SetInput(inpBlobs);
1010         reqWrapper->req.SetOutput(outBlobs);
1011 
1012 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_4)
1013         InferenceEngine::InferRequest infRequest = reqWrapper->req;
1014         NgraphReqWrapper* wrapperPtr = reqWrapper.get();
1015         CV_Assert(wrapperPtr && "Internal error");
1016 #else
1017         InferenceEngine::IInferRequest::Ptr infRequestPtr = reqWrapper->req;
1018         CV_Assert(infRequestPtr);
1019         InferenceEngine::IInferRequest& infRequest = *infRequestPtr.get();
1020         infRequest.SetUserData(reqWrapper.get(), 0);
1021 #endif
1022 
1023 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_4)
1024         // do NOT capture 'reqWrapper' (smart ptr) in the lambda callback
1025         infRequest.SetCompletionCallback<std::function<void(InferenceEngine::InferRequest, InferenceEngine::StatusCode)>>(
1026             [wrapperPtr](InferenceEngine::InferRequest /*request*/, InferenceEngine::StatusCode status)
1027 #else
1028         infRequest.SetCompletionCallback(
1029             [](InferenceEngine::IInferRequest::Ptr requestPtr, InferenceEngine::StatusCode status)
1030 #endif
1031             {
1032                 CV_LOG_DEBUG(NULL, "DNN(nGraph): completionCallback(" << (int)status << ")");
1033 #if !INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_4)
1034                 CV_Assert(requestPtr);
1035                 InferenceEngine::IInferRequest& request = *requestPtr.get();
1036 
1037                 NgraphReqWrapper* wrapperPtr;
1038                 request.GetUserData((void**)&wrapperPtr, 0);
1039                 CV_Assert(wrapperPtr && "Internal error");
1040 #endif
1041                 NgraphReqWrapper& wrapper = *wrapperPtr;
1042 
1043                 size_t processedOutputs = 0;
1044                 try
1045                 {
1046                     for (; processedOutputs < wrapper.outProms.size(); ++processedOutputs)
1047                     {
1048                         const std::string& name = wrapper.outsNames[processedOutputs];
1049                         Mat m = ngraphBlobToMat(wrapper.req.GetBlob(name));
1050 
1051                         try
1052                         {
1053                             CV_Assert(status == InferenceEngine::StatusCode::OK);
1054                             wrapper.outProms[processedOutputs].setValue(m.clone());
1055                         }
1056                         catch (...)
1057                         {
1058                             try {
1059                                 wrapper.outProms[processedOutputs].setException(std::current_exception());
1060                             } catch(...) {
1061                                 CV_LOG_ERROR(NULL, "DNN: Exception occurred during async inference exception propagation");
1062                             }
1063                         }
1064                     }
1065                 }
1066                 catch (...)
1067                 {
1068                     std::exception_ptr e = std::current_exception();
1069                     for (; processedOutputs < wrapper.outProms.size(); ++processedOutputs)
1070                     {
1071                         try {
1072                             wrapper.outProms[processedOutputs].setException(e);
1073                         } catch(...) {
1074                             CV_LOG_ERROR(NULL, "DNN: Exception occurred during async inference exception propagation");
1075                         }
1076                     }
1077                 }
1078                 wrapper.isReady = true;
1079             }
1080         );
1081     }
1082 
1083     if (isAsync)
1084     {
1085         // Copy actual data to infer request's input blobs.
1086         for (const auto& it : cnn.getInputsInfo())
1087         {
1088             const std::string& name = it.first;
1089             auto blobIt = allBlobs.find(name);
1090             Mat srcMat = ngraphBlobToMat(blobIt->second);
1091             Mat dstMat = ngraphBlobToMat(reqWrapper->req.GetBlob(name));
1092             srcMat.copyTo(dstMat);
1093         }
1094 
1095         // Set promises to output blobs wrappers.
1096         reqWrapper->makePromises(outBlobsWrappers);
1097 
1098         reqWrapper->isReady = false;
1099         reqWrapper->req.StartAsync();
1100     }
1101     else
1102     {
1103         reqWrapper->req.Infer();
1104     }
1105 }
1106 
1107 #else
1108 void forwardNgraph(const std::vector<Ptr<BackendWrapper> >& outBlobsWrappers,
1109                    Ptr<BackendNode>& node, bool isAsync)
1110 {
1111     CV_Assert(false && "nGraph is not enabled in this OpenCV build");
1112 }
1113 #endif
1114 
1115 }}
1116