Merge pull request #15090 from dmatveev:dm/ng-0001-g-api-inference-api
* G-API-NG/API: Introduced inference API and IE-based backend - Very quick-n-dirty implementation - OpenCV's own DNN module is not used - No tests so far * G-API-NG/IE: Refined IE backend, added more tests * G-API-NG/IE: Fixed various CI warnings & build issues + tests - Added tests on multi-dimensional own::Mat - Added tests on GMatDesc with dimensions - Documentation on infer.hpp - Fixed more warnings + added a ROI list test - Fix descr_of clash for vector<Mat> & standalone mode - Fix build issue with gcc-4.8x - Addressed review comments * G-API-NG/IE: Addressed review comments - Pass `false` to findDataFile() - Add deprecation warning suppression macros for IE
This commit is contained in:
committed by
Alexander Alekhin
parent
59b0314a0e
commit
0757a51e2b
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include <memory> // unique_ptr
|
||||
#include <functional> // multiplies
|
||||
|
||||
#include <opencv2/gapi/gkernel.hpp>
|
||||
#include <opencv2/gapi/own/convert.hpp>
|
||||
@@ -355,21 +356,39 @@ void writeBack(const Mag& mag, const RcDesc &rc, GRunArgP &g_arg, bool is_umat)
|
||||
|
||||
} // namespace magazine
|
||||
|
||||
void createMat(const cv::GMatDesc desc, cv::gapi::own::Mat& mat)
|
||||
void createMat(const cv::GMatDesc &desc, cv::gapi::own::Mat& mat)
|
||||
{
|
||||
const auto type = desc.planar ? desc.depth : CV_MAKETYPE(desc.depth, desc.chan);
|
||||
const auto size = desc.planar ? cv::gapi::own::Size{desc.size.width, desc.size.height*desc.chan}
|
||||
: desc.size;
|
||||
mat.create(size, type);
|
||||
// FIXME: Refactor (probably start supporting N-Dimensional blobs natively
|
||||
if (desc.dims.empty())
|
||||
{
|
||||
const auto type = desc.planar ? desc.depth : CV_MAKETYPE(desc.depth, desc.chan);
|
||||
const auto size = desc.planar ? cv::gapi::own::Size{desc.size.width, desc.size.height*desc.chan}
|
||||
: desc.size;
|
||||
mat.create(size, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
GAPI_Assert(!desc.planar);
|
||||
mat.create(desc.dims, desc.depth);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(GAPI_STANDALONE)
|
||||
void createMat(const cv::GMatDesc desc, cv::Mat& mat)
|
||||
void createMat(const cv::GMatDesc &desc, cv::Mat& mat)
|
||||
{
|
||||
const auto type = desc.planar ? desc.depth : CV_MAKETYPE(desc.depth, desc.chan);
|
||||
const auto size = desc.planar ? cv::Size{desc.size.width, desc.size.height*desc.chan}
|
||||
: cv::gapi::own::to_ocv(desc.size);
|
||||
mat.create(size, type);
|
||||
// FIXME: Refactor (probably start supporting N-Dimensional blobs natively
|
||||
if (desc.dims.empty())
|
||||
{
|
||||
const auto type = desc.planar ? desc.depth : CV_MAKETYPE(desc.depth, desc.chan);
|
||||
const auto size = desc.planar ? cv::Size{desc.size.width, desc.size.height*desc.chan}
|
||||
: cv::gapi::own::to_ocv(desc.size);
|
||||
mat.create(size, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
GAPI_Assert(!desc.planar);
|
||||
mat.create(desc.dims, desc.depth);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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) 2018-2019 Intel Corporation
|
||||
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <functional> // hash
|
||||
#include <numeric> // accumulate
|
||||
#include <unordered_set>
|
||||
#include <iterator>
|
||||
|
||||
#include <ade/util/algorithm.hpp>
|
||||
|
||||
#include <opencv2/gapi/infer.hpp>
|
||||
|
||||
cv::gapi::GNetPackage::GNetPackage(std::initializer_list<GNetParam> &&ii)
|
||||
: networks(std::move(ii)) {
|
||||
}
|
||||
|
||||
std::vector<cv::gapi::GBackend> cv::gapi::GNetPackage::backends() const {
|
||||
std::unordered_set<cv::gapi::GBackend> unique_set;
|
||||
for (const auto &nn : networks) unique_set.insert(nn.backend);
|
||||
return std::vector<cv::gapi::GBackend>(unique_set.begin(), unique_set.end());
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <ade/util/iota_range.hpp>
|
||||
#include <ade/util/algorithm.hpp>
|
||||
|
||||
#include <opencv2/gapi/opencv_includes.hpp>
|
||||
#include <opencv2/gapi/own/mat.hpp> //gapi::own::Mat
|
||||
#include <opencv2/gapi/gmat.hpp>
|
||||
@@ -49,20 +53,31 @@ namespace{
|
||||
#if !defined(GAPI_STANDALONE)
|
||||
cv::GMatDesc cv::descr_of(const cv::Mat &mat)
|
||||
{
|
||||
return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}};
|
||||
const auto mat_dims = mat.size.dims();
|
||||
|
||||
if (mat_dims == 2)
|
||||
return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}};
|
||||
|
||||
std::vector<int> dims(mat_dims);
|
||||
for (auto i : ade::util::iota(mat_dims)) {
|
||||
// Note: cv::MatSize is not iterable
|
||||
dims[i] = mat.size[i];
|
||||
}
|
||||
return GMatDesc{mat.depth(), std::move(dims)};
|
||||
}
|
||||
|
||||
cv::GMatDesc cv::descr_of(const cv::UMat &mat)
|
||||
{
|
||||
GAPI_Assert(mat.size.dims() == 2);
|
||||
return GMatDesc{ mat.depth(), mat.channels(),{ mat.cols, mat.rows } };
|
||||
}
|
||||
|
||||
cv::GMetaArgs cv::descr_of(const std::vector<cv::Mat> &vec)
|
||||
cv::GMetaArgs cv::descrs_of(const std::vector<cv::Mat> &vec)
|
||||
{
|
||||
return vec_descr_of(vec);
|
||||
}
|
||||
|
||||
cv::GMetaArgs cv::descr_of(const std::vector<cv::UMat> &vec)
|
||||
cv::GMetaArgs cv::descrs_of(const std::vector<cv::UMat> &vec)
|
||||
{
|
||||
return vec_descr_of(vec);
|
||||
}
|
||||
@@ -70,10 +85,12 @@ cv::GMetaArgs cv::descr_of(const std::vector<cv::UMat> &vec)
|
||||
|
||||
cv::GMatDesc cv::gapi::own::descr_of(const cv::gapi::own::Mat &mat)
|
||||
{
|
||||
return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}};
|
||||
return (mat.dims.empty())
|
||||
? GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}}
|
||||
: GMatDesc{mat.depth(), mat.dims};
|
||||
}
|
||||
|
||||
cv::GMetaArgs cv::gapi::own::descr_of(const std::vector<cv::gapi::own::Mat> &vec)
|
||||
cv::GMetaArgs cv::gapi::own::descrs_of(const std::vector<cv::gapi::own::Mat> &vec)
|
||||
{
|
||||
return vec_descr_of(vec);
|
||||
}
|
||||
|
||||
@@ -99,9 +99,9 @@ inline cv::util::optional<T> getCompileArg(const cv::GCompileArgs &args)
|
||||
return cv::util::optional<T>();
|
||||
}
|
||||
|
||||
void createMat(const cv::GMatDesc desc, cv::gapi::own::Mat& mat);
|
||||
void createMat(const cv::GMatDesc& desc, cv::gapi::own::Mat& mat);
|
||||
#if !defined(GAPI_STANDALONE)
|
||||
void createMat(const cv::GMatDesc desc, cv::Mat& mat);
|
||||
void createMat(const cv::GMatDesc& desc, cv::Mat& mat);
|
||||
#endif
|
||||
|
||||
}} // cv::gimpl
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <ade/util/algorithm.hpp>
|
||||
|
||||
#include <ade/util/range.hpp>
|
||||
@@ -26,8 +23,6 @@
|
||||
#include "compiler/gmodel.hpp"
|
||||
|
||||
#include "backends/cpu/gcpubackend.hpp"
|
||||
#include <opencv2/gapi/cpu/imgproc.hpp>
|
||||
#include <opencv2/gapi/cpu/core.hpp>
|
||||
|
||||
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
|
||||
|
||||
@@ -76,7 +71,7 @@ cv::gapi::GBackend cv::gapi::cpu::backend()
|
||||
return this_backend;
|
||||
}
|
||||
|
||||
// GCPUExcecutable implementation //////////////////////////////////////////////
|
||||
// GCPUExecutable implementation //////////////////////////////////////////////
|
||||
cv::gimpl::GCPUExecutable::GCPUExecutable(const ade::Graph &g,
|
||||
const std::vector<ade::NodeHandle> &nodes)
|
||||
: m_g(g), m_gm(m_g)
|
||||
@@ -92,7 +87,7 @@ cv::gimpl::GCPUExecutable::GCPUExecutable(const ade::Graph &g,
|
||||
{
|
||||
m_dataNodes.push_back(nh);
|
||||
const auto &desc = m_gm.metadata(nh).get<Data>();
|
||||
if (desc.storage == Data::Storage::CONST)
|
||||
if (desc.storage == Data::Storage::CONST_VAL)
|
||||
{
|
||||
auto rc = RcDesc{desc.rc, desc.shape, desc.ctor};
|
||||
magazine::bindInArg(m_res, rc, m_gm.metadata(nh).get<ConstValue>().arg);
|
||||
|
||||
@@ -68,4 +68,4 @@ public:
|
||||
|
||||
}}
|
||||
|
||||
#endif // OPENCV_GAPI_GBACKEND_HPP
|
||||
#endif // OPENCV_GAPI_GCPUBACKEND_HPP
|
||||
|
||||
@@ -0,0 +1,604 @@
|
||||
// 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) 2018 Intel Corporation
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#ifdef HAVE_INF_ENGINE
|
||||
|
||||
#if INF_ENGINE_RELEASE <= 2018050000
|
||||
# error G-API IE module supports only OpenVINO IE >= 2019 R1
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <ade/util/algorithm.hpp>
|
||||
|
||||
#include <ade/util/range.hpp>
|
||||
#include <ade/util/zip_range.hpp>
|
||||
#include <ade/util/chain_range.hpp>
|
||||
#include <ade/typed_graph.hpp>
|
||||
|
||||
#include <opencv2/gapi/gcommon.hpp>
|
||||
#include <opencv2/gapi/garray.hpp>
|
||||
#include <opencv2/gapi/util/any.hpp>
|
||||
#include <opencv2/gapi/gtype_traits.hpp>
|
||||
|
||||
#include <opencv2/gapi/infer.hpp>
|
||||
#include <opencv2/gapi/infer/ie/util.hpp>
|
||||
|
||||
#include "compiler/gobjref.hpp"
|
||||
#include "compiler/gmodel.hpp"
|
||||
|
||||
#include "backends/ie/giebackend.hpp"
|
||||
|
||||
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
|
||||
|
||||
namespace IE = InferenceEngine;
|
||||
|
||||
namespace {
|
||||
|
||||
inline IE::ROI toIE(const cv::Rect &rc) {
|
||||
return IE::ROI
|
||||
{ 0u
|
||||
, static_cast<std::size_t>(rc.x)
|
||||
, static_cast<std::size_t>(rc.y)
|
||||
, static_cast<std::size_t>(rc.width)
|
||||
, static_cast<std::size_t>(rc.height)
|
||||
};
|
||||
}
|
||||
|
||||
inline IE::SizeVector toIE(const cv::MatSize &sz) {
|
||||
return cv::to_own<IE::SizeVector::value_type>(sz);
|
||||
}
|
||||
inline std::vector<int> toCV(const IE::SizeVector &vsz) {
|
||||
std::vector<int> result;
|
||||
result.reserve(vsz.size());
|
||||
for (auto sz : vsz) {
|
||||
result.push_back(ade::util::checked_cast<int>(sz));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline IE::Precision toIE(int depth) {
|
||||
switch (depth) {
|
||||
case CV_8U: return IE::Precision::U8;
|
||||
case CV_32F: return IE::Precision::FP32;
|
||||
default: GAPI_Assert(false && "Unsupported data type");
|
||||
}
|
||||
return IE::Precision::UNSPECIFIED;
|
||||
}
|
||||
inline int toCV(IE::Precision prec) {
|
||||
switch (prec) {
|
||||
case IE::Precision::U8: return CV_8U;
|
||||
case IE::Precision::FP32: return CV_32F;
|
||||
default: GAPI_Assert(false && "Unsupported data type");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline IE::TensorDesc toIE(const cv::Mat &mat) {
|
||||
const auto &sz = mat.size;
|
||||
|
||||
// NB: For some reason RGB image is 2D image
|
||||
// (since channel component is not counted here).
|
||||
if (sz.dims() == 2) {
|
||||
// NB: This logic is mainly taken from IE samples
|
||||
const size_t channels = mat.channels();
|
||||
const size_t height = mat.size().height;
|
||||
const size_t width = mat.size().width;
|
||||
|
||||
const size_t strideH = mat.step.buf[0];
|
||||
const size_t strideW = mat.step.buf[1];
|
||||
|
||||
const bool is_dense =
|
||||
strideW == channels &&
|
||||
strideH == channels * width;
|
||||
|
||||
if (!is_dense)
|
||||
cv::util::throw_error(std::logic_error("Doesn't support conversion"
|
||||
" from non-dense cv::Mat"));
|
||||
|
||||
return IE::TensorDesc(toIE(mat.depth()),
|
||||
IE::SizeVector{1, channels, height, width},
|
||||
IE::Layout::NHWC);
|
||||
}
|
||||
|
||||
GAPI_Assert(sz.dims() == 4); // NB: Will relax when needed (to known use)
|
||||
return IE::TensorDesc(toIE(mat.depth()), toIE(sz), IE::Layout::NCHW);
|
||||
}
|
||||
|
||||
inline IE::Blob::Ptr wrapIE(const cv::Mat &mat) {
|
||||
const auto tDesc = toIE(mat);
|
||||
switch (mat.depth()) {
|
||||
// NB: Seems there's no way to create an untyped (T-less) Blob::Ptr
|
||||
// in IE given only precision via TensorDesc. So we have to do this:
|
||||
#define HANDLE(E,T) \
|
||||
case CV_##E: return IE::make_shared_blob<T>(tDesc, const_cast<T*>(mat.ptr<T>()))
|
||||
HANDLE(8U, uint8_t);
|
||||
HANDLE(32F, float);
|
||||
#undef HANDLE
|
||||
default: GAPI_Assert(false && "Unsupported data type");
|
||||
}
|
||||
return IE::Blob::Ptr{};
|
||||
}
|
||||
|
||||
template<class MatType>
|
||||
inline void copyFromIE(const IE::Blob::Ptr &blob, MatType &mat) {
|
||||
switch (blob->getTensorDesc().getPrecision()) {
|
||||
#define HANDLE(E,T) \
|
||||
case IE::Precision::E: std::copy_n(blob->buffer().as<T*>(), \
|
||||
mat.total(), \
|
||||
reinterpret_cast<T*>(mat.data)); \
|
||||
break;
|
||||
HANDLE(U8, uint8_t);
|
||||
HANDLE(FP32, float);
|
||||
#undef HANDLE
|
||||
default: GAPI_Assert(false && "Unsupported data type");
|
||||
}
|
||||
}
|
||||
|
||||
// IE-specific metadata, represents a network with its parameters
|
||||
struct IEUnit {
|
||||
static const char *name() { return "IEModelConfig"; }
|
||||
|
||||
cv::gapi::ie::detail::ParamDesc params;
|
||||
IE::CNNNetwork net;
|
||||
IE::InputsDataMap inputs;
|
||||
IE::OutputsDataMap outputs;
|
||||
|
||||
explicit IEUnit(const cv::gapi::ie::detail::ParamDesc &pp)
|
||||
: params(pp) {
|
||||
|
||||
IE::CNNNetReader reader;
|
||||
reader.ReadNetwork(params.model_path);
|
||||
reader.ReadWeights(params.weights_path);
|
||||
net = reader.getNetwork();
|
||||
inputs = net.getInputsInfo();
|
||||
outputs = net.getOutputsInfo();
|
||||
|
||||
// The practice shows that not all inputs and not all outputs
|
||||
// are mandatory to specify in IE model.
|
||||
// So what we're concerned here about is:
|
||||
// if opeation's (not topology's) input/output number is
|
||||
// greater than 1, then we do care about input/output layer
|
||||
// names. Otherwise, names are picked up automatically.
|
||||
// TODO: Probably this check could be done at the API entry point? (gnet)
|
||||
if (params.num_in > 1u && params.num_in != params.input_names.size()) {
|
||||
cv::util::throw_error(std::logic_error("Please specify input layer names for "
|
||||
+ params.model_path));
|
||||
}
|
||||
if (params.num_out > 1u && params.num_out != params.output_names.size()) {
|
||||
cv::util::throw_error(std::logic_error("Please specify output layer names for "
|
||||
+ params.model_path));
|
||||
}
|
||||
if (params.num_in == 1u && params.input_names.empty()) {
|
||||
params.input_names = { inputs.begin()->first };
|
||||
}
|
||||
if (params.num_out == 1u && params.output_names.empty()) {
|
||||
params.output_names = { outputs.begin()->first };
|
||||
}
|
||||
}
|
||||
|
||||
// This method is [supposed to be] called at Island compilation stage
|
||||
cv::gimpl::ie::IECompiled compile() const {
|
||||
auto this_plugin = IE::PluginDispatcher().getPluginByDevice(params.device_id);
|
||||
auto this_network = this_plugin.LoadNetwork(net, {}); // FIXME: 2nd parameter to be
|
||||
// configurable via the API
|
||||
auto this_request = this_network.CreateInferRequest();
|
||||
|
||||
// Bind const data to infer request
|
||||
for (auto &&p : params.const_inputs) {
|
||||
this_request.SetBlob(p.first, wrapIE(p.second));
|
||||
}
|
||||
|
||||
return {this_plugin, this_network, this_request};
|
||||
}
|
||||
};
|
||||
|
||||
struct IECallContext
|
||||
{
|
||||
// Input parameters passed to an inference operation.
|
||||
std::vector<cv::GArg> args;
|
||||
|
||||
//FIXME: avoid conversion of arguments from internal representaion to OpenCV one on each call
|
||||
//to OCV kernel. (This can be achieved by a two single time conversions in GCPUExecutable::run,
|
||||
//once on enter for input and output arguments, and once before return for output arguments only
|
||||
//FIXME: check if the above applies to this backend (taken from CPU)
|
||||
std::unordered_map<std::size_t, cv::GRunArgP> results;
|
||||
|
||||
// Generic accessor API
|
||||
template<typename T>
|
||||
const T& inArg(std::size_t input) { return args.at(input).get<T>(); }
|
||||
|
||||
// Syntax sugar
|
||||
const cv::gapi::own::Mat& inMat(std::size_t input) {
|
||||
return inArg<cv::gapi::own::Mat>(input);
|
||||
}
|
||||
cv::gapi::own::Mat& outMatR(std::size_t output) {
|
||||
return *cv::util::get<cv::gapi::own::Mat*>(results.at(output));
|
||||
}
|
||||
|
||||
template<typename T> std::vector<T>& outVecR(std::size_t output) { // FIXME: the same issue
|
||||
return outVecRef(output).wref<T>();
|
||||
}
|
||||
cv::detail::VectorRef& outVecRef(std::size_t output) {
|
||||
return cv::util::get<cv::detail::VectorRef>(results.at(output));
|
||||
}
|
||||
};
|
||||
|
||||
struct IECallable {
|
||||
static const char *name() { return "IERequestCallable"; }
|
||||
// FIXME: Make IECallContext manage them all? (3->1)
|
||||
using Run = std::function<void(cv::gimpl::ie::IECompiled &, const IEUnit &, IECallContext &)>;
|
||||
Run run;
|
||||
};
|
||||
|
||||
struct KImpl {
|
||||
cv::gimpl::CustomMetaFunction::CM customMetaFunc;
|
||||
IECallable::Run run;
|
||||
};
|
||||
|
||||
// FIXME: Is there a way to take a typed graph (our GModel),
|
||||
// and create a new typed graph _ATOP_ of that (by extending with a couple of
|
||||
// new types?).
|
||||
// Alternatively, is there a way to compose types graphs?
|
||||
//
|
||||
// If not, we need to introduce that!
|
||||
using GIEModel = ade::TypedGraph
|
||||
< cv::gimpl::Protocol
|
||||
, cv::gimpl::Op
|
||||
, cv::gimpl::NetworkParams
|
||||
, cv::gimpl::CustomMetaFunction
|
||||
, IEUnit
|
||||
, IECallable
|
||||
>;
|
||||
|
||||
// FIXME: Same issue with Typed and ConstTyped
|
||||
using GConstGIEModel = ade::ConstTypedGraph
|
||||
< cv::gimpl::Protocol
|
||||
, cv::gimpl::Op
|
||||
, cv::gimpl::NetworkParams
|
||||
, cv::gimpl::CustomMetaFunction
|
||||
, IEUnit
|
||||
, IECallable
|
||||
>;
|
||||
} // anonymous namespace
|
||||
|
||||
// GCPUExcecutable implementation //////////////////////////////////////////////
|
||||
cv::gimpl::ie::GIEExecutable::GIEExecutable(const ade::Graph &g,
|
||||
const std::vector<ade::NodeHandle> &nodes)
|
||||
: m_g(g), m_gm(m_g) {
|
||||
// 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
|
||||
GConstGIEModel iem(g);
|
||||
|
||||
for (auto &nh : nodes) {
|
||||
switch (m_gm.metadata(nh).get<NodeType>().t) {
|
||||
case NodeType::OP:
|
||||
if (this_nh == nullptr) {
|
||||
this_nh = nh;
|
||||
this_iec = iem.metadata(this_nh).get<IEUnit>().compile();
|
||||
}
|
||||
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<Data>();
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Document what it does
|
||||
cv::GArg cv::gimpl::ie::GIEExecutable::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) {
|
||||
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<cv::gimpl::RcDesc>();
|
||||
switch (ref.shape)
|
||||
{
|
||||
case GShape::GMAT: return GArg(m_res.slot<cv::gapi::own::Mat>()[ref.id]);
|
||||
|
||||
// Note: .at() is intentional for GArray as object MUST be already there
|
||||
// (and constructed by either bindIn/Out or resetInternal)
|
||||
case GShape::GARRAY: return GArg(m_res.slot<cv::detail::VectorRef>().at(ref.id));
|
||||
|
||||
default:
|
||||
util::throw_error(std::logic_error("Unsupported GShape type"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void cv::gimpl::ie::GIEExecutable::run(std::vector<InObj> &&input_objs,
|
||||
std::vector<OutObj> &&output_objs) {
|
||||
// Update resources with run-time information - what this Island
|
||||
// has received from user (or from another Island, or mix...)
|
||||
// FIXME: Check input/output objects against GIsland protocol
|
||||
|
||||
for (auto& it : input_objs) magazine::bindInArg (m_res, it.first, it.second);
|
||||
for (auto& it : output_objs) magazine::bindOutArg(m_res, it.first, it.second);
|
||||
|
||||
// FIXME: Running just a single node now.
|
||||
// Not sure if need to support many of them, though
|
||||
// FIXME: Make this island-unmergeable?
|
||||
const auto &op = m_gm.metadata(this_nh).get<Op>();
|
||||
|
||||
// Initialize kernel's execution context:
|
||||
// - Input parameters
|
||||
IECallContext context;
|
||||
context.args.reserve(op.args.size());
|
||||
using namespace std::placeholders;
|
||||
ade::util::transform(op.args,
|
||||
std::back_inserter(context.args),
|
||||
std::bind(&GIEExecutable::packArg, this, _1));
|
||||
|
||||
// - Output parameters.
|
||||
for (const auto &out_it : ade::util::indexed(op.outs)) {
|
||||
// FIXME: Can the same GArg type resolution mechanism be reused here?
|
||||
const auto out_port = ade::util::index(out_it);
|
||||
const auto out_desc = ade::util::value(out_it);
|
||||
context.results[out_port] = magazine::getObjPtr(m_res, out_desc);
|
||||
}
|
||||
|
||||
// And now trigger the execution
|
||||
GConstGIEModel giem(m_g);
|
||||
const auto &uu = giem.metadata(this_nh).get<IEUnit>();
|
||||
const auto &kk = giem.metadata(this_nh).get<IECallable>();
|
||||
kk.run(this_iec, uu, context);
|
||||
|
||||
for (auto &it : output_objs) magazine::writeBack(m_res, it.first, it.second);
|
||||
}
|
||||
|
||||
namespace cv {
|
||||
namespace gimpl {
|
||||
namespace ie {
|
||||
|
||||
struct Infer: public cv::detail::KernelTag {
|
||||
using API = cv::GInferBase;
|
||||
static cv::gapi::GBackend backend() { return cv::gapi::ie::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*/) {
|
||||
// Specify network's output layer metadata to the framework
|
||||
// Also specify the input information to the IE from the framework
|
||||
// NB: Have no clue if network's input [dimensions] may ever define
|
||||
// its output dimensions. It seems possible with OpenCV DNN APIs
|
||||
|
||||
cv::GMetaArgs result;
|
||||
|
||||
GConstGIEModel gm(gr);
|
||||
const auto &uu = gm.metadata(nh).get<IEUnit>();
|
||||
|
||||
// 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");
|
||||
|
||||
for (auto &&it : ade::util::zip(ade::util::toRange(uu.params.input_names),
|
||||
ade::util::toRange(in_metas))) {
|
||||
auto &&ii = uu.inputs.at(std::get<0>(it));
|
||||
const auto & mm = std::get<1>(it);
|
||||
|
||||
GAPI_Assert(util::holds_alternative<cv::GMatDesc>(mm)
|
||||
&& "Non-GMat inputs are not supported");
|
||||
|
||||
const auto &meta = util::get<cv::GMatDesc>(mm);
|
||||
ii->setPrecision(toIE(meta.depth));
|
||||
ii->setLayout(meta.isND() ? IE::Layout::NCHW : IE::Layout::NHWC);
|
||||
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
|
||||
}
|
||||
|
||||
// FIXME: It would be nice here to have an exact number of network's
|
||||
// input/output parameters. Probably GCall should store it here for us.
|
||||
// It doesn't, as far as I know..
|
||||
for (const auto &out_name : uu.params.output_names) {
|
||||
// NOTE: our output_names vector follows the API order
|
||||
// of this operation's outputs
|
||||
const IE::DataPtr& ie_out = uu.outputs.at(out_name);
|
||||
const IE::SizeVector dims = ie_out->getTensorDesc().getDims();
|
||||
|
||||
cv::GMatDesc outm(toCV(ie_out->getPrecision()),
|
||||
toCV(ie_out->getTensorDesc().getDims()));
|
||||
result.emplace_back(outm);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void run(IECompiled &iec, const IEUnit &uu, IECallContext &ctx) {
|
||||
// non-generic version for now:
|
||||
// - assumes all inputs/outputs are always Mats
|
||||
for (auto i : ade::util::iota(uu.params.num_in)) {
|
||||
// TODO: Ideally we shouldn't do SetBlob() but GetBlob() instead,
|
||||
// and redirect our data producers to this memory
|
||||
// (A memory dialog comes to the picture again)
|
||||
|
||||
const cv::Mat this_mat = to_ocv(ctx.inMat(i));
|
||||
IE::Blob::Ptr this_blob = wrapIE(this_mat);
|
||||
iec.this_request.SetBlob(uu.params.input_names[i], this_blob);
|
||||
}
|
||||
iec.this_request.Infer();
|
||||
for (auto i : ade::util::iota(uu.params.num_out)) {
|
||||
// TODO: Think on avoiding copying here.
|
||||
// Either we should ask IE to use our memory (what is not always the
|
||||
// best policy) or use IE-allocated buffer inside (and pass it to the graph).
|
||||
// Not a <very> big deal for classifiers and detectors,
|
||||
// but may be critical to segmentation.
|
||||
|
||||
cv::gapi::own::Mat& out_mat = ctx.outMatR(i);
|
||||
IE::Blob::Ptr this_blob = iec.this_request.GetBlob(uu.params.output_names[i]);
|
||||
copyFromIE(this_blob, out_mat);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InferList: public cv::detail::KernelTag {
|
||||
using API = cv::GInferListBase;
|
||||
static cv::gapi::GBackend backend() { return cv::gapi::ie::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*/) {
|
||||
// Specify the input information to the IE from the framework
|
||||
// NB: Have no clue if network's input [dimensions] may ever define
|
||||
// its output dimensions. It seems possible with OpenCV DNN APIs
|
||||
|
||||
GConstGIEModel gm(gr);
|
||||
const auto &uu = gm.metadata(nh).get<IEUnit>();
|
||||
|
||||
// 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");
|
||||
|
||||
std::size_t idx = 1u;
|
||||
for (auto &&input_name : uu.params.input_names) {
|
||||
auto &&ii = uu.inputs.at(input_name);
|
||||
const auto & mm = in_metas[idx++];
|
||||
|
||||
GAPI_Assert(util::holds_alternative<cv::GMatDesc>(mm)
|
||||
&& "Non-GMat inputs are not supported");
|
||||
|
||||
const auto &meta = util::get<cv::GMatDesc>(mm);
|
||||
ii->setPrecision(toIE(meta.depth));
|
||||
ii->setLayout(meta.isND() ? IE::Layout::NCHW : IE::Layout::NHWC);
|
||||
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
|
||||
}
|
||||
|
||||
// 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(IECompiled &iec, const IEUnit &uu, IECallContext &ctx) {
|
||||
// non-generic version for now:
|
||||
// - assumes zero input is always ROI list
|
||||
// - assumes all inputs/outputs are always Mats
|
||||
GAPI_Assert(uu.params.num_in == 1); // roi list is not counted in net's inputs
|
||||
|
||||
const auto& in_roi_vec = ctx.inArg<cv::detail::VectorRef>(0u).rref<cv::Rect>();
|
||||
const cv::Mat this_mat = to_ocv(ctx.inMat(1u));
|
||||
IE::Blob::Ptr this_blob = wrapIE(this_mat);
|
||||
|
||||
// FIXME: This could be done ONCE at graph compile stage!
|
||||
std::vector< std::vector<int> > cached_dims(uu.params.num_out);
|
||||
for (auto i : ade::util::iota(uu.params.num_out)) {
|
||||
const IE::DataPtr& ie_out = uu.outputs.at(uu.params.output_names[i]);
|
||||
cached_dims[i] = toCV(ie_out->getTensorDesc().getDims());
|
||||
ctx.outVecR<cv::Mat>(i).clear();
|
||||
// FIXME: Isn't this should be done automatically
|
||||
// by some resetInternalData(), etc? (Probably at the GExecutor level)
|
||||
}
|
||||
|
||||
for (const auto &rc : in_roi_vec) {
|
||||
// FIXME: Assumed only 1 input
|
||||
IE::Blob::Ptr roi_blob = IE::make_shared_blob(this_blob, toIE(rc));
|
||||
iec.this_request.SetBlob(uu.params.input_names[0u], roi_blob);
|
||||
iec.this_request.Infer();
|
||||
|
||||
// While input is fixed to be 1,
|
||||
// there may be still multiple outputs
|
||||
for (auto i : ade::util::iota(uu.params.num_out)) {
|
||||
std::vector<cv::Mat> &out_vec = ctx.outVecR<cv::Mat>(i);
|
||||
|
||||
IE::Blob::Ptr out_blob = iec.this_request.GetBlob(uu.params.output_names[i]);
|
||||
|
||||
cv::Mat out_mat(cached_dims[i], toCV(out_blob->getTensorDesc().getPrecision()));
|
||||
copyFromIE(out_blob, out_mat); // FIXME: Avoid data copy. Not sure if it is possible though
|
||||
out_vec.push_back(std::move(out_mat));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ie
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
||||
|
||||
|
||||
// IE backend implementation of GBackend::Priv ///////////////////////
|
||||
namespace {
|
||||
class GIEBackendImpl 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???
|
||||
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);
|
||||
const auto &ki = cv::util::any_cast<KImpl>(ii.opaque);
|
||||
gm.metadata(nh).set(IEUnit{pp});
|
||||
gm.metadata(nh).set(IECallable{ki.run});
|
||||
gm.metadata(nh).set(CustomMetaFunction{ki.customMetaFunc});
|
||||
}
|
||||
|
||||
virtual EPtr compile(const ade::Graph &graph,
|
||||
const cv::GCompileArgs &,
|
||||
const std::vector<ade::NodeHandle> &nodes) const override {
|
||||
return EPtr{new cv::gimpl::ie::GIEExecutable(graph, nodes)};
|
||||
}
|
||||
|
||||
virtual cv::gapi::GKernelPackage auxiliaryKernels() const override {
|
||||
return cv::gapi::kernels< cv::gimpl::ie::Infer
|
||||
, cv::gimpl::ie::InferList
|
||||
>();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cv::gapi::GBackend cv::gapi::ie::backend() {
|
||||
static cv::gapi::GBackend this_backend(std::make_shared<GIEBackendImpl>());
|
||||
return this_backend;
|
||||
}
|
||||
|
||||
cv::Mat cv::gapi::ie::util::to_ocv(InferenceEngine::Blob::Ptr blob) {
|
||||
const auto& tdesc = blob->getTensorDesc();
|
||||
return cv::Mat(toCV(tdesc.getDims()),
|
||||
toCV(tdesc.getPrecision()),
|
||||
blob->buffer().as<uint8_t*>());
|
||||
}
|
||||
|
||||
std::vector<int> cv::gapi::ie::util::to_ocv(const InferenceEngine::SizeVector &dims) {
|
||||
return toCV(dims);
|
||||
}
|
||||
|
||||
InferenceEngine::Blob::Ptr cv::gapi::ie::util::to_ie(cv::Mat &blob) {
|
||||
return wrapIE(blob);
|
||||
}
|
||||
|
||||
#endif // HAVE_INF_ENGINE
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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) 2018 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_GIEBACKEND_HPP
|
||||
#define OPENCV_GAPI_GIEBACKEND_HPP
|
||||
|
||||
#ifdef HAVE_INF_ENGINE
|
||||
|
||||
#include <ade/util/algorithm.hpp> // type_list_index
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FIXME: Suppress deprecation warnings for OpenVINO 2019R2+
|
||||
// BEGIN {{{
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable: 4996) // was declared deprecated
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC visibility push(default)
|
||||
#endif
|
||||
|
||||
#include <inference_engine.hpp>
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC visibility pop
|
||||
#endif
|
||||
// END }}}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <opencv2/gapi/garg.hpp>
|
||||
#include <opencv2/gapi/gproto.hpp>
|
||||
#include <opencv2/gapi/infer/ie.hpp>
|
||||
|
||||
#include "api/gorigin.hpp"
|
||||
#include "backends/common/gbackend.hpp"
|
||||
#include "compiler/gislandmodel.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace gimpl {
|
||||
namespace ie {
|
||||
|
||||
struct IECompiled {
|
||||
InferenceEngine::InferencePlugin this_plugin;
|
||||
InferenceEngine::ExecutableNetwork this_network;
|
||||
InferenceEngine::InferRequest this_request;
|
||||
};
|
||||
|
||||
class GIEExecutable final: public GIslandExecutable
|
||||
{
|
||||
const ade::Graph &m_g;
|
||||
GModel::ConstGraph m_gm;
|
||||
|
||||
// The only executable stuff in this graph
|
||||
// (assuming it is always single-op)
|
||||
ade::NodeHandle this_nh;
|
||||
IECompiled this_iec;
|
||||
|
||||
// List of all resources in graph (both internal and external)
|
||||
std::vector<ade::NodeHandle> m_dataNodes;
|
||||
|
||||
// Actual data of all resources in graph (both internal and external)
|
||||
Mag m_res;
|
||||
|
||||
// Execution helpers
|
||||
GArg packArg(const GArg &arg);
|
||||
|
||||
public:
|
||||
GIEExecutable(const ade::Graph &graph,
|
||||
const std::vector<ade::NodeHandle> &nodes);
|
||||
|
||||
virtual inline bool canReshape() const override { return false; }
|
||||
virtual inline void reshape(ade::Graph&, const GCompileArgs&) override {
|
||||
GAPI_Assert(false); // Not implemented yet
|
||||
}
|
||||
|
||||
virtual void run(std::vector<InObj> &&input_objs,
|
||||
std::vector<OutObj> &&output_objs) override;
|
||||
};
|
||||
|
||||
}}}
|
||||
|
||||
#endif // HAVE_INF_ENGINE
|
||||
#endif // OPENCV_GAPI_GIEBACKEND_HPP
|
||||
@@ -7,9 +7,6 @@
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <ade/util/algorithm.hpp>
|
||||
|
||||
#include <ade/util/range.hpp>
|
||||
@@ -26,8 +23,6 @@
|
||||
#include "compiler/gmodel.hpp"
|
||||
|
||||
#include "backends/ocl/goclbackend.hpp"
|
||||
#include "backends/ocl/goclimgproc.hpp"
|
||||
#include "backends/ocl/goclcore.hpp"
|
||||
|
||||
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
|
||||
|
||||
@@ -92,7 +87,7 @@ cv::gimpl::GOCLExecutable::GOCLExecutable(const ade::Graph &g,
|
||||
{
|
||||
m_dataNodes.push_back(nh);
|
||||
const auto &desc = m_gm.metadata(nh).get<Data>();
|
||||
if (desc.storage == Data::Storage::CONST)
|
||||
if (desc.storage == Data::Storage::CONST_VAL)
|
||||
{
|
||||
auto rc = RcDesc{desc.rc, desc.shape, desc.ctor};
|
||||
magazine::bindInArg(m_res, rc, m_gm.metadata(nh).get<ConstValue>().arg);
|
||||
|
||||
@@ -72,6 +72,12 @@ namespace
|
||||
return combine(ocv_pkg, user_pkg_with_aux);
|
||||
}
|
||||
|
||||
cv::gapi::GNetPackage getNetworkPackage(cv::GCompileArgs &args)
|
||||
{
|
||||
return cv::gimpl::getCompileArg<cv::gapi::GNetPackage>(args)
|
||||
.value_or(cv::gapi::GNetPackage{});
|
||||
}
|
||||
|
||||
cv::util::optional<std::string> getGraphDumpDirectory(cv::GCompileArgs& args)
|
||||
{
|
||||
auto dump_info = cv::gimpl::getCompileArg<cv::graph_dump_path>(args);
|
||||
@@ -87,6 +93,16 @@ namespace
|
||||
return cv::util::make_optional(dump_info.value().m_dump_path);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename C>
|
||||
cv::gapi::GKernelPackage auxKernelsFrom(const C& c) {
|
||||
cv::gapi::GKernelPackage result;
|
||||
for (const auto &b : c) {
|
||||
result = cv::gapi::combine(result, b.priv().auxiliaryKernels());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
@@ -98,13 +114,28 @@ cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
|
||||
: m_c(c), m_metas(std::move(metas)), m_args(std::move(args))
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
m_all_kernels = getKernelPackage(m_args);
|
||||
auto dump_path = getGraphDumpDirectory(m_args);
|
||||
|
||||
auto kernels_to_use = getKernelPackage(m_args);
|
||||
auto networks_to_use = getNetworkPackage(m_args);
|
||||
std::unordered_set<cv::gapi::GBackend> all_backends;
|
||||
const auto take = [&](std::vector<cv::gapi::GBackend> &&v) {
|
||||
all_backends.insert(v.begin(), v.end());
|
||||
};
|
||||
take(kernels_to_use.backends());
|
||||
take(networks_to_use.backends());
|
||||
m_all_kernels = cv::gapi::combine(kernels_to_use,
|
||||
auxKernelsFrom(all_backends));
|
||||
// NB: The expectation in the line above is that
|
||||
// NN backends (present here via network package) always add their
|
||||
// inference kernels via auxiliary...()
|
||||
|
||||
auto dump_path = getGraphDumpDirectory(m_args);
|
||||
|
||||
m_e.addPassStage("init");
|
||||
m_e.addPass("init", "check_cycles", ade::passes::CheckCycles());
|
||||
m_e.addPass("init", "expand_kernels", std::bind(passes::expandKernels, _1,
|
||||
m_all_kernels)); // NB: package is copied
|
||||
m_e.addPass("init", "expand_kernels",
|
||||
std::bind(passes::expandKernels, _1,
|
||||
m_all_kernels)); // NB: package is copied
|
||||
m_e.addPass("init", "topo_sort", ade::passes::TopologicalSort());
|
||||
m_e.addPass("init", "init_islands", passes::initIslands);
|
||||
m_e.addPass("init", "check_islands", passes::checkIslands);
|
||||
@@ -117,8 +148,13 @@ cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
|
||||
m_all_kernels.remove(cv::gapi::compound::backend());
|
||||
|
||||
m_e.addPassStage("kernels");
|
||||
m_e.addPass("kernels", "resolve_kernels", std::bind(passes::resolveKernels, _1,
|
||||
std::ref(m_all_kernels))); // NB: and not copied here
|
||||
m_e.addPass("kernels", "bind_net_params",
|
||||
std::bind(passes::bindNetParams, _1,
|
||||
networks_to_use));
|
||||
m_e.addPass("kernels", "resolve_kernels",
|
||||
std::bind(passes::resolveKernels, _1,
|
||||
std::ref(m_all_kernels))); // NB: and not copied here
|
||||
// (no compound backend present here)
|
||||
m_e.addPass("kernels", "check_islands_content", passes::checkIslandsContent);
|
||||
|
||||
m_e.addPassStage("meta");
|
||||
@@ -142,7 +178,9 @@ cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
|
||||
dump_path.value()));
|
||||
}
|
||||
|
||||
// Process backends at the last moment (after all G-API passes are added).
|
||||
// FIXME: This should be called for "ActiveBackends" only (see metadata).
|
||||
// However, ActiveBackends are known only after passes are actually executed.
|
||||
// At these stage, they are not executed yet.
|
||||
ade::ExecutionEngineSetupContext ectx(m_e);
|
||||
auto backends = m_all_kernels.backends();
|
||||
for (auto &b : backends)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <opencv2/gapi/gcommon.hpp>
|
||||
#include <opencv2/gapi/gkernel.hpp>
|
||||
#include <opencv2/gapi/infer.hpp>
|
||||
#include <opencv2/gapi/gcomputation.hpp>
|
||||
|
||||
#include <ade/execution_engine/execution_engine.hpp>
|
||||
@@ -26,6 +27,7 @@ class GAPI_EXPORTS GCompiler
|
||||
ade::ExecutionEngine m_e;
|
||||
|
||||
cv::gapi::GKernelPackage m_all_kernels;
|
||||
cv::gapi::GNetPackage m_all_networks;
|
||||
|
||||
void validateInputMeta();
|
||||
void validateOutProtoArgs();
|
||||
|
||||
@@ -47,7 +47,7 @@ ade::NodeHandle GModel::mkDataNode(GModel::Graph &g, const GOrigin& origin)
|
||||
{
|
||||
auto value = value_of(origin);
|
||||
meta = descr_of(value);
|
||||
storage = Data::Storage::CONST;
|
||||
storage = Data::Storage::CONST_VAL;
|
||||
g.metadata(data_h).set(ConstValue{value});
|
||||
}
|
||||
g.metadata(data_h).set(Data{origin.shape, id, meta, origin.ctor, storage});
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
// This part of the system is API-unaware by its design.
|
||||
//
|
||||
|
||||
#include <opencv2/gapi/util/any.hpp>
|
||||
|
||||
#include <opencv2/gapi/garg.hpp>
|
||||
#include <opencv2/gapi/gkernel.hpp>
|
||||
|
||||
@@ -76,7 +78,8 @@ struct Data
|
||||
INTERNAL, // data object is not listed in GComputation protocol
|
||||
INPUT, // data object is listed in GComputation protocol as Input
|
||||
OUTPUT, // data object is listed in GComputation protocol as Output
|
||||
CONST, // data object is constant
|
||||
CONST_VAL, // data object is constant.
|
||||
// Note: CONST is sometimes defined in Win sys headers
|
||||
};
|
||||
Storage storage;
|
||||
};
|
||||
@@ -142,6 +145,33 @@ struct ActiveBackends
|
||||
std::unordered_set<cv::gapi::GBackend> backends;
|
||||
};
|
||||
|
||||
// Backend-specific inference parameters for a neural network.
|
||||
// Since these parameters are set on compilation stage (not
|
||||
// on a construction stage), these parameters are bound lately
|
||||
// to the operation node.
|
||||
// NB: These parameters are not included into GModel by default
|
||||
// since it is not used regularly by all parties.
|
||||
struct NetworkParams
|
||||
{
|
||||
static const char *name() { return "NetworkParams"; }
|
||||
cv::util::any opaque;
|
||||
};
|
||||
|
||||
// This is a custom metadata handling operator.
|
||||
// Sometimes outMeta() can't be bound to input parameters only
|
||||
// so several backends (today -- mainly inference) may find this useful.
|
||||
// If provided, the meta inference pass uses this function instead of
|
||||
// OP.k.outMeta.
|
||||
struct CustomMetaFunction
|
||||
{
|
||||
static const char *name() { return "CustomMetaFunction"; }
|
||||
using CM = std::function< cv::GMetaArgs( const ade::Graph &,
|
||||
const ade::NodeHandle &,
|
||||
const cv::GMetaArgs &,
|
||||
const cv::GArgs &)>;
|
||||
CM customOutMeta;
|
||||
};
|
||||
|
||||
namespace GModel
|
||||
{
|
||||
using Graph = ade::TypedGraph
|
||||
@@ -159,6 +189,7 @@ namespace GModel
|
||||
, DataObjectCounter
|
||||
, IslandModel
|
||||
, ActiveBackends
|
||||
, CustomMetaFunction
|
||||
>;
|
||||
|
||||
// FIXME: How to define it based on GModel???
|
||||
@@ -177,6 +208,7 @@ namespace GModel
|
||||
, DataObjectCounter
|
||||
, IslandModel
|
||||
, ActiveBackends
|
||||
, CustomMetaFunction
|
||||
>;
|
||||
|
||||
// FIXME:
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <ade/passes/check_cycles.hpp>
|
||||
|
||||
#include <opencv2/gapi/gcompoundkernel.hpp> // compound::backend()
|
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage
|
||||
#include <opencv2/gapi/infer.hpp> // GNetPackage
|
||||
|
||||
#include "compiler/gmodel.hpp"
|
||||
#include "compiler/passes/passes.hpp"
|
||||
@@ -97,7 +99,37 @@ namespace
|
||||
gr.erase(subgr_out_nh);
|
||||
}
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// This pass, given the network package, associates every infer[list] node
|
||||
// with particular inference backend and its parameters.
|
||||
void cv::gimpl::passes::bindNetParams(ade::passes::PassContext &ctx,
|
||||
const gapi::GNetPackage &pkg)
|
||||
{
|
||||
GModel::Graph gr(ctx.graph);
|
||||
ade::TypedGraph<NetworkParams> pgr(ctx.graph);
|
||||
|
||||
for (const auto &nh : gr.nodes())
|
||||
{
|
||||
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
|
||||
{
|
||||
auto &op = gr.metadata(nh).get<Op>();
|
||||
if (op.k.tag.empty())
|
||||
continue;
|
||||
|
||||
// FIXME: What if there's more than one???
|
||||
const auto it = ade::util::find_if(pkg.networks,
|
||||
[&](const cv::gapi::GNetParam &p) {
|
||||
return p.tag == op.k.tag;
|
||||
});
|
||||
if (it == std::end(pkg.networks))
|
||||
continue;
|
||||
|
||||
pgr.metadata(nh).set(NetworkParams{it->params});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This pass, given the kernel package, selects a kernel implementation
|
||||
// for every operation in the graph
|
||||
void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx,
|
||||
|
||||
@@ -49,7 +49,8 @@ void cv::gimpl::passes::inferMeta(ade::passes::PassContext &ctx, bool meta_is_in
|
||||
|
||||
// Prepare operation's input metadata vector
|
||||
// Note that it's size is usually different from nh.inEdges.size(),
|
||||
// and its element count is equal to operation's arguments count.
|
||||
// and its element count is equal to operation's arguments count
|
||||
// (which may contain graph-construction-time parameters like integers, etc)
|
||||
GMetaArgs input_meta_args(op.args.size());
|
||||
|
||||
// Iterate through input edges, update input_meta_args's slots
|
||||
@@ -66,16 +67,22 @@ void cv::gimpl::passes::inferMeta(ade::passes::PassContext &ctx, bool meta_is_in
|
||||
{
|
||||
// No meta in an input argument - a fatal error
|
||||
// (note graph is traversed here in topoligcal order)
|
||||
util::throw_error(std::logic_error("Fatal: input object's metadata "
|
||||
"not found!"));
|
||||
util::throw_error(std::logic_error("Fatal: input object's metadata "
|
||||
"not found!"));
|
||||
// FIXME: Add more details!!!
|
||||
}
|
||||
input_meta_args.at(input_port) = input_meta;
|
||||
}
|
||||
|
||||
// Now ask kernel for it's output meta.
|
||||
// Resulting out_args may have a larger size than op.outs, since some
|
||||
// outputs could stay unused (unconnected)
|
||||
const auto out_metas = op.k.outMeta(input_meta_args, op.args);
|
||||
const auto out_metas = gr.metadata(nh).contains<CustomMetaFunction>()
|
||||
? gr.metadata(nh).get<CustomMetaFunction>().customOutMeta(ctx.graph,
|
||||
nh,
|
||||
input_meta_args,
|
||||
op.args)
|
||||
: op.k.outMeta(input_meta_args, op.args);
|
||||
|
||||
// Walk through operation's outputs, update meta of output objects
|
||||
// appropriately
|
||||
|
||||
@@ -25,6 +25,12 @@ namespace ade {
|
||||
|
||||
namespace cv {
|
||||
|
||||
// Forward declarations - internal
|
||||
namespace gapi {
|
||||
class GKernelPackage;
|
||||
struct GNetPackage;
|
||||
} // namespace gapi
|
||||
|
||||
namespace gimpl { namespace passes {
|
||||
|
||||
void dumpDot(const ade::Graph &g, std::ostream& os);
|
||||
@@ -44,6 +50,9 @@ void storeResultingMeta(ade::passes::PassContext &ctx);
|
||||
void expandKernels(ade::passes::PassContext &ctx,
|
||||
const gapi::GKernelPackage& kernels);
|
||||
|
||||
void bindNetParams(ade::passes::PassContext &ctx,
|
||||
const gapi::GNetPackage &networks);
|
||||
|
||||
void resolveKernels(ade::passes::PassContext &ctx,
|
||||
const gapi::GKernelPackage &kernels);
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ void cv::gimpl::GExecutor::initResource(const ade::NodeHandle &orig_nh)
|
||||
const Data &d = m_gm.metadata(orig_nh).get<Data>();
|
||||
|
||||
if ( d.storage != Data::Storage::INTERNAL
|
||||
&& d.storage != Data::Storage::CONST)
|
||||
&& d.storage != Data::Storage::CONST_VAL)
|
||||
return;
|
||||
|
||||
// INTERNALS+CONST only! no need to allocate/reset output objects
|
||||
@@ -105,7 +105,7 @@ void cv::gimpl::GExecutor::initResource(const ade::NodeHandle &orig_nh)
|
||||
break;
|
||||
|
||||
case GShape::GSCALAR:
|
||||
if (d.storage == Data::Storage::CONST)
|
||||
if (d.storage == Data::Storage::CONST_VAL)
|
||||
{
|
||||
auto rc = RcDesc{d.rc, d.shape, d.ctor};
|
||||
magazine::bindInArg(m_res, rc, m_gm.metadata(orig_nh).get<ConstValue>().arg);
|
||||
|
||||
Reference in New Issue
Block a user