diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index c801bee1b0..2c5a4cf1ab 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -15,6 +15,7 @@ if (NOT HAVE_CXX11 OR NOT TARGET ade) endif() set(the_description "OpenCV G-API Core Module") + ocv_add_module(gapi opencv_imgproc) file(GLOB gapi_ext_hdrs @@ -45,6 +46,7 @@ set(gapi_srcs src/api/kernels_core.cpp src/api/kernels_imgproc.cpp src/api/render.cpp + src/api/ginfer.cpp # Compiler part src/compiler/gmodel.cpp @@ -82,6 +84,10 @@ set(gapi_srcs src/backends/ocl/goclimgproc.cpp src/backends/ocl/goclcore.cpp + # IE Backend. FIXME: should be included by CMake + # if and only if IE support is enabled + src/backends/ie/giebackend.cpp + # Compound src/backends/common/gcompoundbackend.cpp src/backends/common/gcompoundkernel.cpp @@ -100,9 +106,8 @@ ocv_module_include_directories("${CMAKE_CURRENT_LIST_DIR}/src") # Note `ade` is not a module name but link dependency for ${the_module} # (which is opencv_gapi) -ocv_create_module(ade) - -ocv_add_accuracy_tests() +ocv_create_module(ade ${INF_ENGINE_TARGET}) +ocv_add_accuracy_tests(${INF_ENGINE_TARGET}) # FIXME: test binary is linked with ADE directly since ADE symbols # are not exported from libopencv_gapi.so in any form - thus # there're two copies of ADE code in memory when tests run (!) diff --git a/modules/gapi/include/opencv2/gapi/garray.hpp b/modules/gapi/include/opencv2/gapi/garray.hpp index b69fb5d65b..ec07929989 100644 --- a/modules/gapi/include/opencv2/gapi/garray.hpp +++ b/modules/gapi/include/opencv2/gapi/garray.hpp @@ -20,6 +20,9 @@ #include #include +#include // flatten_g only! +#include // flatten_g only! + namespace cv { // Forward declaration; GNode and GOrigin are an internal @@ -247,6 +250,24 @@ namespace detail return m_ref->m_desc; } }; + + // Helper (FIXME: work-around?) + // stripping G types to their host types + // like cv::GArray would still map to std::vector + // but not to std::vector +#if defined(GAPI_STANDALONE) +# define FLATTEN_NS cv::gapi::own +#else +# define FLATTEN_NS cv +#endif + template struct flatten_g; + template<> struct flatten_g { using type = FLATTEN_NS::Mat; }; + template<> struct flatten_g { using type = FLATTEN_NS::Scalar; }; + template struct flatten_g { using type = T; }; +#undef FLATTEN_NS + // FIXME: the above mainly duplicates "ProtoToParam" thing from gtyped.hpp + // but I decided not to include gtyped here - probably worth moving that stuff + // to some common place? (DM) } // namespace detail /** \addtogroup gapi_data_objects @@ -263,10 +284,16 @@ public: detail::GArrayU strip() const { return m_ref; } private: - static void VCTor(detail::VectorRef& vref) { vref.reset(); } + // Host type (or Flat type) - the type this GArray is actually + // specified to. + using HT = typename detail::flatten_g::type>::type; + + static void VCTor(detail::VectorRef& vref) { + vref.reset(); + } void putDetails() { m_ref.setConstructFcn(&VCTor); - m_ref.specifyType(); + m_ref.specifyType(); } detail::GArrayU m_ref; diff --git a/modules/gapi/include/opencv2/gapi/gkernel.hpp b/modules/gapi/include/opencv2/gapi/gkernel.hpp index b8d1dbbea4..e3f67938d5 100644 --- a/modules/gapi/include/opencv2/gapi/gkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/gkernel.hpp @@ -36,8 +36,9 @@ struct GAPI_EXPORTS GKernel using M = std::function; const std::string name; // kernel ID, defined by its API (signature) + const std::string tag; // some (implementation-specific) tag const M outMeta; // generic adaptor to API::outMeta(...) - const GShapes outShapes; // types (shapes) kernel's outputs + const GShapes outShapes; // types (shapes) kernel's outputs }; // GKernelImpl describes particular kernel implementation to the system @@ -166,6 +167,12 @@ namespace detail } }; + //////////////////////////////////////////////////////////////////////////// + // Helper class to introduce tags to calls. By default there's no tag + struct NoTag { + static constexpr const char *tag() { return ""; } + }; + } // namespace detail // GKernelType and GKernelTypeM are base classes which implement typed ::on() @@ -175,8 +182,9 @@ namespace detail // GKernelTypeM respectively. template -class GKernelTypeM(Args...)> >: - public detail::MetaHelper, std::tuple> +class GKernelTypeM(Args...)> > + : public detail::MetaHelper, std::tuple> + , public detail::NoTag { template static std::tuple yield(cv::GCall &call, detail::Seq) @@ -190,7 +198,7 @@ public: static std::tuple on(Args... args) { - cv::GCall call(GKernel{K::id(), &K::getOutMeta, {detail::GTypeTraits::shape...}}); + cv::GCall call(GKernel{K::id(), K::tag(), &K::getOutMeta, {detail::GTypeTraits::shape...}}); call.pass(args...); return yield(call, typename detail::MkSeq::type()); } @@ -199,8 +207,9 @@ public: template class GKernelType; template -class GKernelType >: - public detail::MetaHelper, R> +class GKernelType > + : public detail::MetaHelper, R> + , public detail::NoTag { public: using InArgs = std::tuple; @@ -208,7 +217,7 @@ public: static R on(Args... args) { - cv::GCall call(GKernel{K::id(), &K::getOutMeta, {detail::GTypeTraits::shape}}); + cv::GCall call(GKernel{K::id(), K::tag(), &K::getOutMeta, {detail::GTypeTraits::shape}}); call.pass(args...); return detail::Yield::yield(call, 0); } @@ -244,6 +253,9 @@ public: public detail::G_ID_HELPER_CLASS(Class) // {body} is to be defined by user +#define G_API_OP G_TYPED_KERNEL +#define G_API_OP_M G_TYPED_KERNEL_M + namespace cv { namespace gapi @@ -437,6 +449,7 @@ namespace gapi { return includesAPI(KAPI::id()); } + // FIXME: The below comment is wrong, and who needs this function? /** * @brief Find a kernel (by its API) * diff --git a/modules/gapi/include/opencv2/gapi/gmat.hpp b/modules/gapi/include/opencv2/gapi/gmat.hpp index d6b6e7261f..8416a3a6c5 100644 --- a/modules/gapi/include/opencv2/gapi/gmat.hpp +++ b/modules/gapi/include/opencv2/gapi/gmat.hpp @@ -69,15 +69,26 @@ struct GAPI_EXPORTS GMatDesc int chan; cv::gapi::own::Size size; // NB.: no multi-dimensional cases covered yet bool planar; + std::vector dims; // FIXME: Maybe it's real questionable to have it here GMatDesc(int d, int c, cv::gapi::own::Size s, bool p = false) : depth(d), chan(c), size(s), planar(p) {} + GMatDesc(int d, const std::vector &dd) + : depth(d), chan(-1), size{-1,-1}, planar(false), dims(dd) {} + + GMatDesc(int d, std::vector &&dd) + : depth(d), chan(-1), size{-1,-1}, planar(false), dims(std::move(dd)) {} + GMatDesc() : GMatDesc(-1, -1, {-1,-1}) {} inline bool operator== (const GMatDesc &rhs) const { - return depth == rhs.depth && chan == rhs.chan && size == rhs.size && planar == rhs.planar; + return depth == rhs.depth + && chan == rhs.chan + && size == rhs.size + && planar == rhs.planar + && dims == rhs.dims; } inline bool operator!= (const GMatDesc &rhs) const @@ -85,6 +96,8 @@ struct GAPI_EXPORTS GMatDesc return !(*this == rhs); } + bool isND() const { return !dims.empty(); } + // Checks if the passed mat can be described by this descriptor // (it handles the case when // 1-channel mat can be reinterpreted as is (1-channel mat) diff --git a/modules/gapi/include/opencv2/gapi/gmetaarg.hpp b/modules/gapi/include/opencv2/gapi/gmetaarg.hpp index 5b6e2adfd8..0bd6767b7e 100644 --- a/modules/gapi/include/opencv2/gapi/gmetaarg.hpp +++ b/modules/gapi/include/opencv2/gapi/gmetaarg.hpp @@ -61,13 +61,15 @@ namespace detail } // namespace detail +// Note: descr_of(std::vector<..>) returns a GArrayDesc, while +// descrs_of(std::vector<..>) returns an array of Meta args! class Mat; class UMat; -GAPI_EXPORTS cv::GMetaArgs descr_of(const std::vector &vec); -GAPI_EXPORTS cv::GMetaArgs descr_of(const std::vector &vec); +GAPI_EXPORTS cv::GMetaArgs descrs_of(const std::vector &vec); +GAPI_EXPORTS cv::GMetaArgs descrs_of(const std::vector &vec); namespace gapi { namespace own { class Mat; - GAPI_EXPORTS cv::GMetaArgs descr_of(const std::vector &vec); + GAPI_EXPORTS cv::GMetaArgs descrs_of(const std::vector &vec); }} // namespace gapi::own } // namespace cv diff --git a/modules/gapi/include/opencv2/gapi/infer.hpp b/modules/gapi/include/opencv2/gapi/infer.hpp new file mode 100644 index 0000000000..484a6656df --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer.hpp @@ -0,0 +1,231 @@ +// 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) 2019 Intel Corporation + + +#ifndef OPENCV_GAPI_INFER_HPP +#define OPENCV_GAPI_INFER_HPP + +// FIXME: Inference API is currently only available in full mode +#if !defined(GAPI_STANDALONE) + +#include +#include // string +#include // tuple + +#include // any<> +#include // GKernelType[M], GBackend +#include // GArg +#include // CompileArgTag +#include // GMetaArg + +namespace cv { + +namespace detail { + // This tiny class eliminates the semantic difference between + // GKernelType and GKernelTypeM. + // FIXME: Something similar can be reused for regular kernels + template + struct KernelTypeMedium; + + template + struct KernelTypeMedium(Args...)> >: + public GKernelTypeM(Args...)> > {}; + + template + struct KernelTypeMedium >: + public GKernelType > {}; + +} // namespace detail + +template class GNetworkType; + +// TODO: maybe tuple_wrap_helper from util.hpp may help with this. +// Multiple-return-value network definition (specialized base class) +template +class GNetworkType(Args...)> > +{ +public: + using InArgs = std::tuple; + using OutArgs = std::tuple; + + using Result = OutArgs; + using API = std::function; + + using ResultL = std::tuple< cv::GArray... >; + using APIList = std::function, Args...)>; +}; + +// Single-return-value network definition (specialized base class) +template +class GNetworkType > +{ +public: + using InArgs = std::tuple; + using OutArgs = std::tuple; + + using Result = R; + using API = std::function; + + using ResultL = cv::GArray; + using APIList = std::function, Args...)>; +}; + +// Base "Infer" kernel. Note - for whatever network, kernel ID +// is always the same. Different inference calls are distinguished by +// network _tag_ (an extra field in GCall) +// +// getOutMeta is a stub callback collected by G-API kernel subsystem +// automatically. This is a rare case when this callback is defined by +// a particular backend, not by a network itself. +struct GInferBase { + static constexpr const char * id() { + return "org.opencv.dnn.infer"; // Universal stub + } + static GMetaArgs getOutMeta(const GMetaArgs &, const GArgs &) { + return GMetaArgs{}; // One more universal stub + } +}; + + +// Base "Infer list" kernel. +// All notes from "Infer" kernel apply here as well. +struct GInferListBase { + static constexpr const char * id() { + return "org.opencv.dnn.infer-roi"; // Universal stub + } + static GMetaArgs getOutMeta(const GMetaArgs &, const GArgs &) { + return GMetaArgs{}; // One more universal stub + } +}; + +// A generic inference kernel. API (::on()) is fully defined by the Net +// template parameter. +// Acts as a regular kernel in graph (via KernelTypeMedium). +template +struct GInfer final + : public GInferBase + , public detail::KernelTypeMedium< GInfer + , typename Net::API > { + using GInferBase::getOutMeta; // FIXME: name lookup conflict workaround? + + static constexpr const char* tag() { return Net::tag(); } +}; + +// A generic roi-list inference kernel. API (::on()) is derived from +// the Net template parameter (see more in infer<> overload). +template +struct GInferList final + : public GInferListBase + , public detail::KernelTypeMedium< GInferList + , typename Net::APIList > { + using GInferListBase::getOutMeta; // FIXME: name lookup conflict workaround? + + static constexpr const char* tag() { return Net::tag(); } +}; + +} // namespace cv + +// FIXME: Probably the signature makes a function/tuple/function round-trip +#define G_API_NET(Class, API, Tag) \ + struct Class final: public cv::GNetworkType { \ + static constexpr const char * tag() { return Tag; } \ + } + +namespace cv { +namespace gapi { + + +/** @brief Calculates responses for the specified network (template + * parameter) for every region in the source image. + * + * @tparam A network type defined with G_API_NET() macro. + * @param roi a list of rectangles describing regions of interest + * in the source image. Usually an output of object detector or tracker. + * @param args network's input parameters as specified in G_API_NET() macro. + * NOTE: verified to work reliably with 1-input topologies only. + * @return a list of objects of return type as defined in G_API_NET(). + * If a network has multiple return values (defined with a tuple), a tuple of + * GArray<> objects is returned with the appropriate types inside. + * @sa G_API_NET() + */ +template +typename Net::ResultL infer(cv::GArray roi, Args&&... args) { + return GInferList::on(roi, std::forward(args)...); +} + +/** + * @brief Calculates response for the specified network (template + * parameter) given the input data. + * + * @tparam A network type defined with G_API_NET() macro. + * @param args network's input parameters as specified in G_API_NET() macro. + * @return an object of return type as defined in G_API_NET(). + * If a network has multiple return values (defined with a tuple), a tuple of + * objects of apprpriate type is returned. + * @sa G_API_NET() + */ +template +typename Net::Result infer(Args&&... args) { + return GInfer::on(std::forward(args)...); +} + + +} // namespace gapi +} // namespace cv + +#endif // GAPI_STANDALONE + +namespace cv { +namespace gapi { + +// Note: the below code _is_ part of STANDALONE build, +// just to make our compiler code compileable. + +// A type-erased form of network parameters. +// Similar to how a type-erased GKernel is represented and used. +struct GAPI_EXPORTS GNetParam { + std::string tag; // FIXME: const? + GBackend backend; // Specifies the execution model + util::any params; // Backend-interpreted parameter structure +}; + +/** + * @brief A container class for network configurations. Similar to + * GKernelPackage.Use cv::gapi::networks() to construct this object. + * + * @sa cv::gapi::networks + */ +struct GAPI_EXPORTS GNetPackage { + explicit GNetPackage(std::initializer_list &&ii = {}); + std::vector backends() const; + std::vector networks; +}; +} // namespace gapi + +namespace detail { +template +gapi::GNetParam strip(T&& t) { + return gapi::GNetParam { t.tag() + , t.backend() + , t.params() + }; +} + +template<> struct CompileArgTag { + static const char* tag() { return "gapi.net_package"; } +}; + +} // namespace cv::detail + +namespace gapi { +template +cv::gapi::GNetPackage networks(Args&&... args) { + return cv::gapi::GNetPackage({ cv::detail::strip(args)... }); +} +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_INFER_HPP diff --git a/modules/gapi/include/opencv2/gapi/infer/ie.hpp b/modules/gapi/include/opencv2/gapi/infer/ie.hpp new file mode 100644 index 0000000000..e1c4e74503 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer/ie.hpp @@ -0,0 +1,106 @@ +// 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) 2019 Intel Corporation + +#ifndef OPENCV_GAPI_INFER_IE_HPP +#define OPENCV_GAPI_INFER_IE_HPP + +#ifdef HAVE_INF_ENGINE + +#include +#include +#include +#include // tuple, tuple_size + +#include +#include + +namespace cv { +namespace gapi { +// FIXME: introduce a new sub-namespace for NN? +namespace ie { + +GAPI_EXPORTS cv::gapi::GBackend backend(); + +namespace detail { + struct ParamDesc { + std::string model_path; + std::string weights_path; + std::string device_id; + + // NB: Here order follows the `Net` API + std::vector input_names; + std::vector output_names; + + std::unordered_map const_inputs; + + // NB: nun_* may differ from topology's real input/output port numbers + // (e.g. topology's partial execution) + std::size_t num_in; // How many inputs are defined in the operation + std::size_t num_out; // How many outputs are defined in the operation + }; +} // namespace detail + +// FIXME: this is probably a shared (reusable) thing +template +struct PortCfg { + using In = std::array + < std::string + , std::tuple_size::value >; + using Out = std::array + < std::string + , std::tuple_size::value >; +}; + +template class Params { +public: + Params(const std::string &model, + const std::string &weights, + const std::string &device) + : desc{ model, weights, device, {}, {}, {} + , std::tuple_size::value + , std::tuple_size::value + } { + }; + + Params& cfgInputLayers(const typename PortCfg::In &ll) { + desc.input_names.clear(); + desc.input_names.reserve(ll.size()); + std::copy(ll.begin(), ll.end(), + std::back_inserter(desc.input_names)); + return *this; + } + + Params& cfgOutputLayers(const typename PortCfg::Out &ll) { + desc.output_names.clear(); + desc.output_names.reserve(ll.size()); + std::copy(ll.begin(), ll.end(), + std::back_inserter(desc.output_names)); + return *this; + } + + Params& constInput(const std::string &layer_name, + const cv::Mat &data) { + desc.const_inputs[layer_name] = data; + return *this; + } + + // BEGIN(G-API's network parametrization API) + GBackend backend() const { return cv::gapi::ie::backend(); } + std::string tag() const { return Net::tag(); } + cv::util::any params() const { return { desc }; } + // END(G-API's network parametrization API) + +protected: + detail::ParamDesc desc; +}; + +} // namespace ie +} // namespace gapi +} // namespace cv + +#endif // HAVE_INF_ENGINE + +#endif // OPENCV_GAPI_INFER_HPP diff --git a/modules/gapi/include/opencv2/gapi/infer/ie/util.hpp b/modules/gapi/include/opencv2/gapi/infer/ie/util.hpp new file mode 100644 index 0000000000..ce433a4f6c --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer/ie/util.hpp @@ -0,0 +1,31 @@ +// 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) 2019 Intel Corporation + +#ifndef OPENCV_GAPI_INFER_IE_UTIL_HPP +#define OPENCV_GAPI_INFER_IE_UTIL_HPP + +#ifdef HAVE_INF_ENGINE + +// NOTE: This file is not included by default in infer/ie.hpp +// and won't be. infer/ie.hpp doesn't depend on IE headers itself. +// This file does -- so needs to be included separately by those who care. + +#include "inference_engine.hpp" + +namespace cv { +namespace gapi { +namespace ie { +namespace util { + +GAPI_EXPORTS std::vector to_ocv(const InferenceEngine::SizeVector &dims); + +GAPI_EXPORTS cv::Mat to_ocv(InferenceEngine::Blob::Ptr blob); +GAPI_EXPORTS InferenceEngine::Blob::Ptr to_ie(cv::Mat &blob); + +}}}} + +#endif // HAVE_INF_ENGINE +#endif // OPENCV_GAPI_INFER_IE_UTIL_HPP diff --git a/modules/gapi/include/opencv2/gapi/own/convert.hpp b/modules/gapi/include/opencv2/gapi/own/convert.hpp index 4a7394a11d..576b5ee791 100644 --- a/modules/gapi/include/opencv2/gapi/own/convert.hpp +++ b/modules/gapi/include/opencv2/gapi/own/convert.hpp @@ -17,8 +17,23 @@ namespace cv { - inline cv::gapi::own::Mat to_own(Mat const& m) { return {m.rows, m.cols, m.type(), m.data, m.step};}; + template + std::vector to_own(const cv::MatSize &sz) { + std::vector result(sz.dims()); + for (int i = 0; i < sz.dims(); i++) { + // Note: cv::MatSize is not iterable + result[i] = static_cast(sz[i]); + } + return result; + } + cv::gapi::own::Mat to_own(Mat&&) = delete; + inline cv::gapi::own::Mat to_own(Mat const& m) { + return (m.dims == 2) + ? cv::gapi::own::Mat{m.rows, m.cols, m.type(), m.data, m.step} + : cv::gapi::own::Mat{to_own(m.size), m.type(), m.data}; + }; + inline cv::gapi::own::Scalar to_own(const cv::Scalar& s) { return {s[0], s[1], s[2], s[3]}; }; @@ -32,7 +47,11 @@ namespace gapi { namespace own { - inline cv::Mat to_ocv(Mat const& m) { return {m.rows, m.cols, m.type(), m.data, m.step};}; + inline cv::Mat to_ocv(Mat const& m) { + return m.dims.empty() + ? cv::Mat{m.rows, m.cols, m.type(), m.data, m.step} + : cv::Mat{m.dims, m.type(), m.data}; + } cv::Mat to_ocv(Mat&&) = delete; inline cv::Scalar to_ocv(const Scalar& s) { return {s[0], s[1], s[2], s[3]}; }; diff --git a/modules/gapi/include/opencv2/gapi/own/mat.hpp b/modules/gapi/include/opencv2/gapi/own/mat.hpp index 20f5b55d82..191eaa1562 100644 --- a/modules/gapi/include/opencv2/gapi/own/mat.hpp +++ b/modules/gapi/include/opencv2/gapi/own/mat.hpp @@ -16,6 +16,7 @@ #include //std::shared_ptr #include //std::memcpy +#include //std::accumulate #include namespace cv { namespace gapi { namespace own { @@ -49,6 +50,10 @@ namespace cv { namespace gapi { namespace own { : flags((type & TYPE_MASK)), rows(_rows), cols(_cols), data((uchar*)_data), step(_step == AUTO_STEP ? detail::default_step(type, _cols) : _step) {} + MatHeader(const std::vector &_dims, int type, void* _data) + : flags((type & TYPE_MASK)), data((uchar*)_data), step(0), dims(_dims) + {} + MatHeader(const MatHeader& ) = default; MatHeader(MatHeader&& src) : MatHeader(src) // reuse copy constructor here { @@ -74,8 +79,10 @@ namespace cv { namespace gapi { namespace own { //! pointer to the data uchar* data = nullptr; size_t step = 0; + //! dimensions (ND-case) + std::vector dims; }; - } + } // namespace detail //concise version of cv::Mat suitable for GAPI needs (used when no dependence on OpenCV is required) class Mat : public detail::MatHeader{ public: @@ -100,6 +107,14 @@ namespace cv { namespace gapi { namespace own { : MatHeader (_rows, _cols, _type, _data, _step) {} + Mat(const std::vector &_dims, int _type, void* _data) + : MatHeader (_dims, _type, _data) + {} + + Mat(std::vector &&_dims, int _type, void* _data) + : MatHeader (std::move(_dims), _type, _data) + {} + Mat(Mat const& src, const Rect& roi ) : Mat(src) { @@ -120,9 +135,6 @@ namespace cv { namespace gapi { namespace own { Mat& operator = (const Scalar& s) { constexpr unsigned max_channels = 4; //Scalar can't fit more than 4 - const auto channels = static_cast(this->channels()); - GAPI_Assert(channels <= max_channels); - using func_p_t = void (*)(void*, int, Scalar const&); using detail::assign_row; #define TABLE_ENTRY(type) {assign_row, assign_row, assign_row, assign_row} @@ -145,10 +157,22 @@ namespace cv { namespace gapi { namespace own { const auto depth = static_cast(this->depth()); GAPI_Assert(depth < sizeof(func_tbl)/sizeof(func_tbl[0])); - for (int r = 0; r < rows; ++r) + if (dims.empty()) { - auto* f = func_tbl[depth][channels -1]; - (*f)(static_cast(ptr(r)), cols, s ); + const auto channels = static_cast(this->channels()); + GAPI_Assert(channels <= max_channels); + + auto* f = func_tbl[depth][channels - 1]; + for (int r = 0; r < rows; ++r) + { + (*f)(static_cast(ptr(r)), cols, s ); + } + } + else + { + auto* f = func_tbl[depth][0]; + // FIXME: better to refactor assign_row to use std::size_t by default + (*f)(static_cast(data), static_cast(total()), s); } return *this; } @@ -187,8 +211,9 @@ namespace cv { namespace gapi { namespace own { /** @brief Returns the number of matrix channels. The method returns the number of matrix channels. + If matrix is N-dimensional, -1 is returned. */ - int channels() const {return CV_MAT_CN(flags);} + int channels() const {return dims.empty() ? CV_MAT_CN(flags) : -1;} /** @param _rows New number of rows. @@ -197,7 +222,7 @@ namespace cv { namespace gapi { namespace own { */ void create(int _rows, int _cols, int _type) { - create({_cols, _rows}, _type); + create(Size{_cols, _rows}, _type); } /** @overload @param _size Alternative new matrix size specification: Size(cols, rows) @@ -215,6 +240,18 @@ namespace cv { namespace gapi { namespace own { } } + void create(const std::vector &_dims, int _type) + { + // FIXME: make a proper reallocation-on-demands + // WARNING: no tensor views, so no strides + Mat tmp{_dims, _type, nullptr}; + // FIXME: this accumulate duplicates a lot + const auto sz = std::accumulate(_dims.begin(), _dims.end(), 1, std::multiplies()); + tmp.memory.reset(new uchar[CV_ELEM_SIZE(_type)*sz], [](uchar * p){delete[] p;}); + tmp.data = tmp.memory.get(); + *this = std::move(tmp); + } + /** @brief Copies the matrix to another one. The method copies the matrix data to another matrix. Before copying the data, the method invokes : @@ -227,10 +264,18 @@ namespace cv { namespace gapi { namespace own { */ void copyTo(Mat& dst) const { - dst.create(rows, cols, type()); - for (int r = 0; r < rows; ++r) + if (dims.empty()) { - std::copy_n(ptr(r), detail::default_step(type(),cols), dst.ptr(r)); + dst.create(rows, cols, type()); + for (int r = 0; r < rows; ++r) + { + std::copy_n(ptr(r), detail::default_step(type(),cols), dst.ptr(r)); + } + } + else + { + dst.create(dims, depth()); + std::copy_n(data, total()*elemSize(), data); } } @@ -248,10 +293,12 @@ namespace cv { namespace gapi { namespace own { */ size_t total() const { - return static_cast(rows * cols); + return static_cast + (dims.empty() + ? (rows * cols) + : std::accumulate(dims.begin(), dims.end(), 1, std::multiplies())); } - /** @overload @param roi Extracted submatrix specified as a rectangle. */ diff --git a/modules/gapi/src/api/gbackend.cpp b/modules/gapi/src/api/gbackend.cpp index 43227bf590..26a036ae91 100644 --- a/modules/gapi/src/api/gbackend.cpp +++ b/modules/gapi/src/api/gbackend.cpp @@ -7,6 +7,7 @@ #include "precomp.hpp" #include // unique_ptr +#include // multiplies #include #include @@ -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 diff --git a/modules/gapi/src/api/ginfer.cpp b/modules/gapi/src/api/ginfer.cpp new file mode 100644 index 0000000000..98eeef5ab6 --- /dev/null +++ b/modules/gapi/src/api/ginfer.cpp @@ -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 // hash +#include // accumulate +#include +#include + +#include + +#include + +cv::gapi::GNetPackage::GNetPackage(std::initializer_list &&ii) + : networks(std::move(ii)) { +} + +std::vector cv::gapi::GNetPackage::backends() const { + std::unordered_set unique_set; + for (const auto &nn : networks) unique_set.insert(nn.backend); + return std::vector(unique_set.begin(), unique_set.end()); +} diff --git a/modules/gapi/src/api/gmat.cpp b/modules/gapi/src/api/gmat.cpp index 5bc55ef2d9..006e99fab4 100644 --- a/modules/gapi/src/api/gmat.cpp +++ b/modules/gapi/src/api/gmat.cpp @@ -6,6 +6,10 @@ #include "precomp.hpp" + +#include +#include + #include #include //gapi::own::Mat #include @@ -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 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 &vec) +cv::GMetaArgs cv::descrs_of(const std::vector &vec) { return vec_descr_of(vec); } -cv::GMetaArgs cv::descr_of(const std::vector &vec) +cv::GMetaArgs cv::descrs_of(const std::vector &vec) { return vec_descr_of(vec); } @@ -70,10 +85,12 @@ cv::GMetaArgs cv::descr_of(const std::vector &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 &vec) +cv::GMetaArgs cv::gapi::own::descrs_of(const std::vector &vec) { return vec_descr_of(vec); } diff --git a/modules/gapi/src/backends/common/gbackend.hpp b/modules/gapi/src/backends/common/gbackend.hpp index 0b2a425c24..0c9c20e444 100644 --- a/modules/gapi/src/backends/common/gbackend.hpp +++ b/modules/gapi/src/backends/common/gbackend.hpp @@ -99,9 +99,9 @@ inline cv::util::optional getCompileArg(const cv::GCompileArgs &args) return cv::util::optional(); } -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 diff --git a/modules/gapi/src/backends/cpu/gcpubackend.cpp b/modules/gapi/src/backends/cpu/gcpubackend.cpp index cfdb7ae030..052b47f1d9 100644 --- a/modules/gapi/src/backends/cpu/gcpubackend.cpp +++ b/modules/gapi/src/backends/cpu/gcpubackend.cpp @@ -7,9 +7,6 @@ #include "precomp.hpp" -#include -#include - #include #include @@ -26,8 +23,6 @@ #include "compiler/gmodel.hpp" #include "backends/cpu/gcpubackend.hpp" -#include -#include #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 &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(); - 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().arg); diff --git a/modules/gapi/src/backends/cpu/gcpubackend.hpp b/modules/gapi/src/backends/cpu/gcpubackend.hpp index 923a05eb13..28cffa1ab8 100644 --- a/modules/gapi/src/backends/cpu/gcpubackend.hpp +++ b/modules/gapi/src/backends/cpu/gcpubackend.hpp @@ -68,4 +68,4 @@ public: }} -#endif // OPENCV_GAPI_GBACKEND_HPP +#endif // OPENCV_GAPI_GCPUBACKEND_HPP diff --git a/modules/gapi/src/backends/ie/giebackend.cpp b/modules/gapi/src/backends/ie/giebackend.cpp new file mode 100644 index 0000000000..2a5d97db87 --- /dev/null +++ b/modules/gapi/src/backends/ie/giebackend.cpp @@ -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 +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#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(rc.x) + , static_cast(rc.y) + , static_cast(rc.width) + , static_cast(rc.height) + }; +} + +inline IE::SizeVector toIE(const cv::MatSize &sz) { + return cv::to_own(sz); +} +inline std::vector toCV(const IE::SizeVector &vsz) { + std::vector result; + result.reserve(vsz.size()); + for (auto sz : vsz) { + result.push_back(ade::util::checked_cast(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(tDesc, const_cast(mat.ptr())) + HANDLE(8U, uint8_t); + HANDLE(32F, float); +#undef HANDLE + default: GAPI_Assert(false && "Unsupported data type"); + } + return IE::Blob::Ptr{}; +} + +template +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(), \ + mat.total(), \ + reinterpret_cast(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 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 results; + + // Generic accessor API + template + const T& inArg(std::size_t input) { return args.at(input).get(); } + + // Syntax sugar + const cv::gapi::own::Mat& inMat(std::size_t input) { + return inArg(input); + } + cv::gapi::own::Mat& outMatR(std::size_t output) { + return *cv::util::get(results.at(output)); + } + + template std::vector& outVecR(std::size_t output) { // FIXME: the same issue + return outVecRef(output).wref(); + } + cv::detail::VectorRef& outVecRef(std::size_t output) { + return cv::util::get(results.at(output)); + } +}; + +struct IECallable { + static const char *name() { return "IERequestCallable"; } + // FIXME: Make IECallContext manage them all? (3->1) + using Run = std::function; + 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 &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().t) { + case NodeType::OP: + if (this_nh == nullptr) { + this_nh = nh; + this_iec = iem.metadata(this_nh).get().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(); + 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(); + switch (ref.shape) + { + case GShape::GMAT: return GArg(m_res.slot()[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().at(ref.id)); + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +void cv::gimpl::ie::GIEExecutable::run(std::vector &&input_objs, + std::vector &&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(); + + // 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(); + const auto &kk = giem.metadata(this_nh).get(); + 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(); + + // 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(mm) + && "Non-GMat inputs are not supported"); + + const auto &meta = util::get(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 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(); + + // 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(mm) + && "Non-GMat inputs are not supported"); + + const auto &meta = util::get(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(0u).rref(); + 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 > 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(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 &out_vec = ctx.outVecR(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(); + const auto &pp = cv::util::any_cast(np.opaque); + const auto &ki = cv::util::any_cast(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 &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()); + 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()); +} + +std::vector 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 diff --git a/modules/gapi/src/backends/ie/giebackend.hpp b/modules/gapi/src/backends/ie/giebackend.hpp new file mode 100644 index 0000000000..d7ac175010 --- /dev/null +++ b/modules/gapi/src/backends/ie/giebackend.hpp @@ -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 // 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 + +#if defined(__GNUC__) +#pragma GCC visibility pop +#endif +// END }}} +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#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 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 &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 &&input_objs, + std::vector &&output_objs) override; +}; + +}}} + +#endif // HAVE_INF_ENGINE +#endif // OPENCV_GAPI_GIEBACKEND_HPP diff --git a/modules/gapi/src/backends/ocl/goclbackend.cpp b/modules/gapi/src/backends/ocl/goclbackend.cpp index ab2c420564..ea1e4d40e1 100644 --- a/modules/gapi/src/backends/ocl/goclbackend.cpp +++ b/modules/gapi/src/backends/ocl/goclbackend.cpp @@ -7,9 +7,6 @@ #include "precomp.hpp" -#include -#include - #include #include @@ -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(); - 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().arg); diff --git a/modules/gapi/src/compiler/gcompiler.cpp b/modules/gapi/src/compiler/gcompiler.cpp index 9925c196ad..438ee23f23 100644 --- a/modules/gapi/src/compiler/gcompiler.cpp +++ b/modules/gapi/src/compiler/gcompiler.cpp @@ -72,6 +72,12 @@ namespace return combine(ocv_pkg, user_pkg_with_aux); } + cv::gapi::GNetPackage getNetworkPackage(cv::GCompileArgs &args) + { + return cv::gimpl::getCompileArg(args) + .value_or(cv::gapi::GNetPackage{}); + } + cv::util::optional getGraphDumpDirectory(cv::GCompileArgs& args) { auto dump_info = cv::gimpl::getCompileArg(args); @@ -87,6 +93,16 @@ namespace return cv::util::make_optional(dump_info.value().m_dump_path); } } + + template + 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 all_backends; + const auto take = [&](std::vector &&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) diff --git a/modules/gapi/src/compiler/gcompiler.hpp b/modules/gapi/src/compiler/gcompiler.hpp index 3848434059..b4b8157dbf 100644 --- a/modules/gapi/src/compiler/gcompiler.hpp +++ b/modules/gapi/src/compiler/gcompiler.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -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(); diff --git a/modules/gapi/src/compiler/gmodel.cpp b/modules/gapi/src/compiler/gmodel.cpp index ce7134745a..8c6ce334c4 100644 --- a/modules/gapi/src/compiler/gmodel.cpp +++ b/modules/gapi/src/compiler/gmodel.cpp @@ -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}); diff --git a/modules/gapi/src/compiler/gmodel.hpp b/modules/gapi/src/compiler/gmodel.hpp index cf65c7382d..af6cfc78f8 100644 --- a/modules/gapi/src/compiler/gmodel.hpp +++ b/modules/gapi/src/compiler/gmodel.hpp @@ -22,6 +22,8 @@ // This part of the system is API-unaware by its design. // +#include + #include #include @@ -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 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: diff --git a/modules/gapi/src/compiler/passes/kernels.cpp b/modules/gapi/src/compiler/passes/kernels.cpp index f5f0098cae..c81f8d75fc 100644 --- a/modules/gapi/src/compiler/passes/kernels.cpp +++ b/modules/gapi/src/compiler/passes/kernels.cpp @@ -12,6 +12,8 @@ #include #include // compound::backend() +#include // GKernelPackage +#include // 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 pgr(ctx.graph); + + for (const auto &nh : gr.nodes()) + { + if (gr.metadata(nh).get().t == NodeType::OP) + { + auto &op = gr.metadata(nh).get(); + 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, diff --git a/modules/gapi/src/compiler/passes/meta.cpp b/modules/gapi/src/compiler/passes/meta.cpp index 1577a86175..4b52643466 100644 --- a/modules/gapi/src/compiler/passes/meta.cpp +++ b/modules/gapi/src/compiler/passes/meta.cpp @@ -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() + ? gr.metadata(nh).get().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 diff --git a/modules/gapi/src/compiler/passes/passes.hpp b/modules/gapi/src/compiler/passes/passes.hpp index 4daddab451..91436dcd6f 100644 --- a/modules/gapi/src/compiler/passes/passes.hpp +++ b/modules/gapi/src/compiler/passes/passes.hpp @@ -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); diff --git a/modules/gapi/src/executor/gexecutor.cpp b/modules/gapi/src/executor/gexecutor.cpp index aacc4d1222..8a3e24b834 100644 --- a/modules/gapi/src/executor/gexecutor.cpp +++ b/modules/gapi/src/executor/gexecutor.cpp @@ -88,7 +88,7 @@ void cv::gimpl::GExecutor::initResource(const ade::NodeHandle &orig_nh) const Data &d = m_gm.metadata(orig_nh).get(); 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().arg); diff --git a/modules/gapi/test/gapi_desc_tests.cpp b/modules/gapi/test/gapi_desc_tests.cpp index fa79230ff8..65d66d6f17 100644 --- a/modules/gapi/test/gapi_desc_tests.cpp +++ b/modules/gapi/test/gapi_desc_tests.cpp @@ -31,6 +31,7 @@ TEST(GAPI_MetaDesc, MatDesc) EXPECT_EQ(1, desc1.chan); EXPECT_EQ(320, desc1.size.width); EXPECT_EQ(240, desc1.size.height); + EXPECT_FALSE(desc1.isND()); cv::Mat m2(480, 640, CV_8UC3); const auto desc2 = cv::descr_of(m2); @@ -38,6 +39,21 @@ TEST(GAPI_MetaDesc, MatDesc) EXPECT_EQ(3, desc2.chan); EXPECT_EQ(640, desc2.size.width); EXPECT_EQ(480, desc2.size.height); + EXPECT_FALSE(desc2.isND()); +} + +TEST(GAPI_MetaDesc, MatDescND) +{ + std::vector dims = {1,3,299,299}; + cv::Mat m(dims, CV_32F); + const auto desc = cv::descr_of(m); + EXPECT_EQ(CV_32F, desc.depth); + EXPECT_EQ(-1, desc.chan); + EXPECT_EQ(1, desc.dims[0]); + EXPECT_EQ(3, desc.dims[1]); + EXPECT_EQ(299, desc.dims[2]); + EXPECT_EQ(299, desc.dims[3]); + EXPECT_TRUE(desc.isND()); } TEST(GAPI_MetaDesc, VecMatDesc) @@ -45,13 +61,13 @@ TEST(GAPI_MetaDesc, VecMatDesc) std::vector vec1 = { cv::Mat(240, 320, CV_8U)}; - const auto desc1 = cv::descr_of(vec1); + const auto desc1 = cv::descrs_of(vec1); EXPECT_EQ((GMatDesc{CV_8U, 1, {320, 240}}), get(desc1[0])); std::vector vec2 = { cv::UMat(480, 640, CV_8UC3)}; - const auto desc2 = cv::descr_of(vec2); + const auto desc2 = cv::descrs_of(vec2); EXPECT_EQ((GMatDesc{CV_8U, 3, {640, 480}}), get(desc2[0])); } @@ -61,7 +77,7 @@ TEST(GAPI_MetaDesc, VecOwnMatDesc) cv::gapi::own::Mat(240, 320, CV_8U, nullptr), cv::gapi::own::Mat(480, 640, CV_8UC3, nullptr)}; - const auto desc = cv::gapi::own::descr_of(vec); + const auto desc = cv::gapi::own::descrs_of(vec); EXPECT_EQ((GMatDesc{CV_8U, 1, {320, 240}}), get(desc[0])); EXPECT_EQ((GMatDesc{CV_8U, 3, {640, 480}}), get(desc[1])); } @@ -72,7 +88,7 @@ TEST(GAPI_MetaDesc, AdlVecOwnMatDesc) cv::gapi::own::Mat(240, 320, CV_8U, nullptr), cv::gapi::own::Mat(480, 640, CV_8UC3, nullptr)}; - const auto desc = descr_of(vec); + const auto desc = descrs_of(vec); EXPECT_EQ((GMatDesc{CV_8U, 1, {320, 240}}), get(desc[0])); EXPECT_EQ((GMatDesc{CV_8U, 3, {640, 480}}), get(desc[1])); } @@ -93,6 +109,38 @@ TEST(GAPI_MetaDesc, Compare_Not_Equal_MatDesc) EXPECT_TRUE(desc1 != desc2); } +TEST(GAPI_MetaDesc, Compare_Equal_MatDesc_ND) +{ + const auto desc1 = cv::GMatDesc{CV_8U, {1,3,224,224}}; + const auto desc2 = cv::GMatDesc{CV_8U, {1,3,224,224}}; + + EXPECT_TRUE(desc1 == desc2); +} + +TEST(GAPI_MetaDesc, Compare_Not_Equal_MatDesc_ND_1) +{ + const auto desc1 = cv::GMatDesc{CV_8U, {1,1000}}; + const auto desc2 = cv::GMatDesc{CV_32F, {1,1000}}; + + EXPECT_TRUE(desc1 != desc2); +} + +TEST(GAPI_MetaDesc, Compare_Not_Equal_MatDesc_ND_2) +{ + const auto desc1 = cv::GMatDesc{CV_8U, {1,1000}}; + const auto desc2 = cv::GMatDesc{CV_8U, {1,1400}}; + + EXPECT_TRUE(desc1 != desc2); +} + +TEST(GAPI_MetaDesc, Compare_Not_Equal_MatDesc_ND_3) +{ + const auto desc1 = cv::GMatDesc{CV_8U, {1,1000}}; + const auto desc2 = cv::GMatDesc{CV_8U, 1, {32,32}}; + + EXPECT_TRUE(desc1 != desc2); +} + TEST(GAPI_MetaDesc, Compile_MatchMetaNumber_1) { cv::GMat in; diff --git a/modules/gapi/test/infer/gapi_infer_ie_test.cpp b/modules/gapi/test/infer/gapi_infer_ie_test.cpp new file mode 100644 index 0000000000..5e1d650728 --- /dev/null +++ b/modules/gapi/test/infer/gapi_infer_ie_test.cpp @@ -0,0 +1,281 @@ +// 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) 2019 Intel Corporation + +#include "../test_precomp.hpp" + +#ifdef HAVE_INF_ENGINE + +#include + +//////////////////////////////////////////////////////////////////////////////// +// 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 + +#if defined(__GNUC__) +#pragma GCC visibility pop +#endif +// END }}} +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +namespace opencv_test +{ +namespace { + +// FIXME: taken from DNN module +static void initDLDTDataPath() +{ +#ifndef WINRT + static bool initialized = false; + if (!initialized) + { + const char* omzDataPath = getenv("OPENCV_OPEN_MODEL_ZOO_DATA_PATH"); + if (omzDataPath) + cvtest::addDataSearchPath(omzDataPath); + const char* dnnDataPath = getenv("OPENCV_DNN_TEST_DATA_PATH"); + if (dnnDataPath) { + // Add the dnnDataPath itself - G-API is using some images there directly + cvtest::addDataSearchPath(dnnDataPath); + cvtest::addDataSearchPath(dnnDataPath + std::string("/omz_intel_models")); + } + initialized = true; + } +#endif // WINRT +} + +// FIXME: taken from the DNN module +void normAssert(cv::InputArray ref, cv::InputArray test, + const char *comment /*= ""*/, + double l1 = 0.00001, double lInf = 0.0001) +{ + double normL1 = cvtest::norm(ref, test, cv::NORM_L1) / ref.getMat().total(); + EXPECT_LE(normL1, l1) << comment; + + double normInf = cvtest::norm(ref, test, cv::NORM_INF); + EXPECT_LE(normInf, lInf) << comment; +} + +} // anonymous namespace + +// TODO: Probably DNN/IE part can be further parametrized with a template +// NOTE: here ".." is used to leave the default "gapi/" search scope +TEST(TestAgeGenderIE, InferBasicTensor) +{ + initDLDTDataPath(); + + const std::string path = "Retail/object_attributes/age_gender/dldt/age-gender-recognition-retail-0013"; + const auto topology_path = findDataFile(path + ".xml", false); + const auto weights_path = findDataFile(path + ".bin", false); + + // Load IE network, initialize input data using that. + namespace IE = InferenceEngine; + cv::Mat in_mat; + cv::Mat gapi_age, gapi_gender; + + IE::Blob::Ptr ie_age, ie_gender; + { + IE::CNNNetReader reader; + reader.ReadNetwork(topology_path); + reader.ReadWeights(weights_path); + auto net = reader.getNetwork(); + + const auto &iedims = net.getInputsInfo().begin()->second->getDims(); + auto cvdims = cv::gapi::ie::util::to_ocv(iedims); + std::reverse(cvdims.begin(), cvdims.end()); + in_mat.create(cvdims, CV_32F); + cv::randu(in_mat, -1, 1); + + auto plugin = IE::PluginDispatcher().getPluginByDevice("CPU"); + auto plugin_net = plugin.LoadNetwork(net, {}); + auto infer_request = plugin_net.CreateInferRequest(); + + infer_request.SetBlob("data", cv::gapi::ie::util::to_ie(in_mat)); + infer_request.Infer(); + ie_age = infer_request.GetBlob("age_conv3"); + ie_gender = infer_request.GetBlob("prob"); + } + + // Configure & run G-API + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "test-age-gender"); + + cv::GMat in; + cv::GMat age, gender; + std::tie(age, gender) = cv::gapi::infer(in); + cv::GComputation comp(cv::GIn(in), cv::GOut(age, gender)); + + auto pp = cv::gapi::ie::Params { + topology_path, weights_path, "CPU" + }.cfgOutputLayers({ "age_conv3", "prob" }); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Validate with IE itself (avoid DNN module dependency here) + normAssert(cv::gapi::ie::util::to_ocv(ie_age), gapi_age, "Test age output" ); + normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderIE, InferBasicImage) +{ + initDLDTDataPath(); + + const std::string path = "Retail/object_attributes/age_gender/dldt/age-gender-recognition-retail-0013"; + const auto topology_path = findDataFile(path + ".xml", false); + const auto weights_path = findDataFile(path + ".bin", false); + + // FIXME: Ideally it should be an image from disk + // cv::Mat in_mat = cv::imread(findDataFile("grace_hopper_227.png")); + cv::Mat in_mat(cv::Size(320, 240), CV_8UC3); + cv::randu(in_mat, 0, 255); + + cv::Mat gapi_age, gapi_gender; + + // Load & run IE network + namespace IE = InferenceEngine; + IE::Blob::Ptr ie_age, ie_gender; + { + IE::CNNNetReader reader; + reader.ReadNetwork(topology_path); + reader.ReadWeights(weights_path); + auto net = reader.getNetwork(); + auto &ii = net.getInputsInfo().at("data"); + ii->setPrecision(IE::Precision::U8); + ii->setLayout(IE::Layout::NHWC); + ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR); + + auto plugin = IE::PluginDispatcher().getPluginByDevice("CPU"); + auto plugin_net = plugin.LoadNetwork(net, {}); + auto infer_request = plugin_net.CreateInferRequest(); + + infer_request.SetBlob("data", cv::gapi::ie::util::to_ie(in_mat)); + infer_request.Infer(); + ie_age = infer_request.GetBlob("age_conv3"); + ie_gender = infer_request.GetBlob("prob"); + } + + // Configure & run G-API + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "test-age-gender"); + + cv::GMat in; + cv::GMat age, gender; + std::tie(age, gender) = cv::gapi::infer(in); + cv::GComputation comp(cv::GIn(in), cv::GOut(age, gender)); + + auto pp = cv::gapi::ie::Params { + topology_path, weights_path, "CPU" + }.cfgOutputLayers({ "age_conv3", "prob" }); + comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Validate with IE itself (avoid DNN module dependency here) + normAssert(cv::gapi::ie::util::to_ocv(ie_age), gapi_age, "Test age output" ); + normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output"); +} + +TEST(TestAgeGenderIE, InferROIList) +{ + initDLDTDataPath(); + + const std::string path = "Retail/object_attributes/age_gender/dldt/age-gender-recognition-retail-0013"; + const auto topology_path = findDataFile(path + ".xml", false); + const auto weights_path = findDataFile(path + ".bin", false); + + // FIXME: Ideally it should be an image from disk + // cv::Mat in_mat = cv::imread(findDataFile("grace_hopper_227.png")); + cv::Mat in_mat(cv::Size(640, 480), CV_8UC3); + cv::randu(in_mat, 0, 255); + + std::vector rois = { + cv::Rect(cv::Point{ 0, 0}, cv::Size{80, 120}), + cv::Rect(cv::Point{50, 100}, cv::Size{96, 160}), + }; + + std::vector gapi_age, gapi_gender; + + // Load & run IE network + namespace IE = InferenceEngine; + std::vector ie_age, ie_gender; + { + IE::CNNNetReader reader; + reader.ReadNetwork(topology_path); + reader.ReadWeights(weights_path); + auto net = reader.getNetwork(); + auto &ii = net.getInputsInfo().at("data"); + ii->setPrecision(IE::Precision::U8); + ii->setLayout(IE::Layout::NHWC); + ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR); + + auto plugin = IE::PluginDispatcher().getPluginByDevice("CPU"); + auto plugin_net = plugin.LoadNetwork(net, {}); + auto infer_request = plugin_net.CreateInferRequest(); + auto frame_blob = cv::gapi::ie::util::to_ie(in_mat); + + for (auto &&rc : rois) { + const auto ie_rc = IE::ROI { + 0u + , static_cast(rc.x) + , static_cast(rc.y) + , static_cast(rc.width) + , static_cast(rc.height) + }; + infer_request.SetBlob("data", IE::make_shared_blob(frame_blob, ie_rc)); + infer_request.Infer(); + + using namespace cv::gapi::ie::util; + ie_age.push_back(to_ocv(infer_request.GetBlob("age_conv3")).clone()); + ie_gender.push_back(to_ocv(infer_request.GetBlob("prob")).clone()); + } + } + + // Configure & run G-API + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "test-age-gender"); + + cv::GArray rr; + cv::GMat in; + cv::GArray age, gender; + std::tie(age, gender) = cv::gapi::infer(rr, in); + cv::GComputation comp(cv::GIn(in, rr), cv::GOut(age, gender)); + + auto pp = cv::gapi::ie::Params { + topology_path, weights_path, "CPU" + }.cfgOutputLayers({ "age_conv3", "prob" }); + comp.apply(cv::gin(in_mat, rois), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Validate with IE itself (avoid DNN module dependency here) + ASSERT_EQ(2u, ie_age.size() ); + ASSERT_EQ(2u, ie_gender.size()); + ASSERT_EQ(2u, gapi_age.size() ); + ASSERT_EQ(2u, gapi_gender.size()); + + normAssert(ie_age [0], gapi_age [0], "0: Test age output"); + normAssert(ie_gender[0], gapi_gender[0], "0: Test gender output"); + normAssert(ie_age [1], gapi_age [1], "1: Test age output"); + normAssert(ie_gender[1], gapi_gender[1], "1: Test gender output"); +} + + +} // namespace opencv_test + +#endif // HAVE_INF_ENGINE diff --git a/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp b/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp index e166b87a7a..3a441e6ba1 100644 --- a/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp +++ b/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp @@ -23,12 +23,12 @@ namespace { cv::GMat unaryOp(cv::GMat m) { - return cv::GCall(cv::GKernel{"gapi.test.unaryop", nullptr, { GShape::GMAT } }).pass(m).yield(0); + return cv::GCall(cv::GKernel{"gapi.test.unaryop", "", nullptr, { GShape::GMAT } }).pass(m).yield(0); } cv::GMat binaryOp(cv::GMat m1, cv::GMat m2) { - return cv::GCall(cv::GKernel{"gapi.test.binaryOp", nullptr, { GShape::GMAT } }).pass(m1, m2).yield(0); + return cv::GCall(cv::GKernel{"gapi.test.binaryOp", "", nullptr, { GShape::GMAT } }).pass(m1, m2).yield(0); } std::vector collectOperations(const cv::gimpl::GModel::Graph& gr) diff --git a/modules/gapi/test/own/mat_tests.cpp b/modules/gapi/test/own/mat_tests.cpp index 42245a53f1..76501a301f 100644 --- a/modules/gapi/test/own/mat_tests.cpp +++ b/modules/gapi/test/own/mat_tests.cpp @@ -12,6 +12,7 @@ namespace opencv_test { using Mat = cv::gapi::own::Mat; +using Dims = std::vector; TEST(OwnMat, DefaultConstruction) { @@ -22,6 +23,7 @@ TEST(OwnMat, DefaultConstruction) ASSERT_EQ(m.cols, 0); ASSERT_EQ(m.type(), 0); ASSERT_EQ(m.depth(), 0); + ASSERT_TRUE(m.dims.empty()); } TEST(OwnMat, Create) @@ -39,6 +41,25 @@ TEST(OwnMat, Create) ASSERT_EQ(m.channels(), 1); ASSERT_EQ(m.elemSize(), sizeof(uint8_t)); ASSERT_EQ(m.step, sizeof(uint8_t) * m.cols); + ASSERT_TRUE(m.dims.empty()); +} + +TEST(OwnMat, CreateND) +{ + Dims dims = {1,1,32,32}; + Mat m; + m.create(dims, CV_32F); + + ASSERT_NE(nullptr , m.data ); + ASSERT_EQ((cv::gapi::own::Size{0,0}), (cv::gapi::own::Size{m.cols, m.rows})); + + ASSERT_EQ(static_cast(dims[0]*dims[1]*dims[2]*dims[3]), m.total()); + ASSERT_EQ(CV_32F , m.type() ); + ASSERT_EQ(CV_32F , m.depth() ); + ASSERT_EQ(-1 , m.channels()); + ASSERT_EQ(sizeof(float) , m.elemSize()); + ASSERT_EQ(0u , m.step ); + ASSERT_EQ(dims , m.dims ); } TEST(OwnMat, CreateOverload) @@ -56,7 +77,9 @@ TEST(OwnMat, CreateOverload) ASSERT_EQ(m.channels(), 1); ASSERT_EQ(m.elemSize(), sizeof(uint8_t)); ASSERT_EQ(m.step, sizeof(uint8_t) * m.cols); + ASSERT_TRUE(m.dims.empty()); } + TEST(OwnMat, Create3chan) { auto size = cv::Size{32,16}; @@ -71,6 +94,7 @@ TEST(OwnMat, Create3chan) ASSERT_EQ(m.channels(), 3); ASSERT_EQ(m.elemSize(), 3 * sizeof(uint8_t)); ASSERT_EQ(m.step, 3* sizeof(uint8_t) * m.cols); + ASSERT_TRUE(m.dims.empty()); } struct NonEmptyMat { @@ -91,7 +115,8 @@ namespace { cv::Size{mat.cols, mat.rows}, mat.type(), mat.depth(), - mat.channels() + mat.channels(), + mat.dims ); }; @@ -194,6 +219,23 @@ TEST(OwnMatConversion, WithStep) << (cvMat != cvMatFromOwn); } +TEST(OwnMatConversion, WithND) +{ + const Dims dims = {1,3,8,8}; + std::vector data(dims[0]*dims[1]*dims[2]*dims[3]); + for (size_t i = 0u; i < data.size(); i++) + { + data[i] = static_cast(i); + } + cv::Mat cvMat(dims, CV_32S, data.data()); + auto ownMat = to_own(cvMat); + auto cvMatFromOwn = cv::gapi::own::to_ocv(ownMat); + + EXPECT_EQ(0, cv::countNonZero(cvMat != cvMatFromOwn)) + << cvMat << std::endl + << (cvMat != cvMatFromOwn); +} + TEST(OwnMat, PtrWithStep) { constexpr int width = 8; @@ -242,6 +284,149 @@ TEST(OwnMat, CopyToWithStep) << (to_ocv(mat) != to_ocv(dst)); } +TEST(OwnMat, AssignNDtoRegular) +{ + const auto sz = cv::gapi::own::Size{32,32}; + const auto dims = Dims{1,3,224,224}; + + Mat a; + a.create(sz, CV_8U); + const auto *old_ptr = a.data; + + ASSERT_NE(nullptr , a.data); + ASSERT_EQ(sz , (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(sz.width*sz.height), a.total()); + ASSERT_EQ(CV_8U , a.type()); + ASSERT_EQ(CV_8U , a.depth()); + ASSERT_EQ(1 , a.channels()); + ASSERT_EQ(sizeof(uint8_t), a.elemSize()); + ASSERT_EQ(static_cast(sz.width), a.step); + ASSERT_TRUE(a.dims.empty()); + + Mat b; + b.create(dims, CV_32F); + a = b; + + ASSERT_NE(nullptr , a.data); + ASSERT_NE(old_ptr , a.data); + ASSERT_EQ((cv::gapi::own::Size{0,0}), (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(dims[0]*dims[1]*dims[2]*dims[3]), a.total()); + ASSERT_EQ(CV_32F , a.type()); + ASSERT_EQ(CV_32F , a.depth()); + ASSERT_EQ(-1 , a.channels()); + ASSERT_EQ(sizeof(float), a.elemSize()); + ASSERT_EQ(0u , a.step); + ASSERT_EQ(dims , a.dims); +} + +TEST(OwnMat, AssignRegularToND) +{ + const auto sz = cv::gapi::own::Size{32,32}; + const auto dims = Dims{1,3,224,224}; + + Mat a; + a.create(dims, CV_32F); + const auto *old_ptr = a.data; + + ASSERT_NE(nullptr , a.data); + ASSERT_EQ((cv::gapi::own::Size{0,0}), (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(dims[0]*dims[1]*dims[2]*dims[3]), a.total()); + ASSERT_EQ(CV_32F , a.type()); + ASSERT_EQ(CV_32F , a.depth()); + ASSERT_EQ(-1 , a.channels()); + ASSERT_EQ(sizeof(float), a.elemSize()); + ASSERT_EQ(0u , a.step); + ASSERT_EQ(dims , a.dims); + + Mat b; + b.create(sz, CV_8U); + a = b; + + ASSERT_NE(nullptr , a.data); + ASSERT_NE(old_ptr , a.data); + ASSERT_EQ(sz , (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(sz.width*sz.height), a.total()); + ASSERT_EQ(CV_8U , a.type()); + ASSERT_EQ(CV_8U , a.depth()); + ASSERT_EQ(1 , a.channels()); + ASSERT_EQ(sizeof(uint8_t), a.elemSize()); + ASSERT_EQ(static_cast(sz.width), a.step); + ASSERT_TRUE(a.dims.empty()); +} + +TEST(OwnMat, CopyNDtoRegular) +{ + const auto sz = cv::gapi::own::Size{32,32}; + const auto dims = Dims{1,3,224,224}; + + Mat a; + a.create(sz, CV_8U); + const auto *old_ptr = a.data; + + ASSERT_NE(nullptr , a.data); + ASSERT_EQ(sz , (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(sz.width*sz.height), a.total()); + ASSERT_EQ(CV_8U , a.type()); + ASSERT_EQ(CV_8U , a.depth()); + ASSERT_EQ(1 , a.channels()); + ASSERT_EQ(sizeof(uint8_t), a.elemSize()); + ASSERT_EQ(static_cast(sz.width), a.step); + ASSERT_TRUE(a.dims.empty()); + + Mat b; + b.create(dims, CV_32F); + b.copyTo(a); + + ASSERT_NE(nullptr , a.data); + ASSERT_NE(old_ptr , a.data); + ASSERT_NE(b.data , a.data); + ASSERT_EQ((cv::gapi::own::Size{0,0}), (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(dims[0]*dims[1]*dims[2]*dims[3]), a.total()); + ASSERT_EQ(CV_32F , a.type()); + ASSERT_EQ(CV_32F , a.depth()); + ASSERT_EQ(-1 , a.channels()); + ASSERT_EQ(sizeof(float), a.elemSize()); + ASSERT_EQ(0u , a.step); + ASSERT_EQ(dims , a.dims); +} + +TEST(OwnMat, CopyRegularToND) +{ + const auto sz = cv::gapi::own::Size{32,32}; + const auto dims = Dims{1,3,224,224}; + + Mat a; + a.create(dims, CV_32F); + const auto *old_ptr = a.data; + + + ASSERT_NE(nullptr , a.data); + ASSERT_EQ((cv::gapi::own::Size{0,0}), (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(dims[0]*dims[1]*dims[2]*dims[3]), a.total()); + ASSERT_EQ(CV_32F , a.type()); + ASSERT_EQ(CV_32F , a.depth()); + ASSERT_EQ(-1 , a.channels()); + ASSERT_EQ(sizeof(float), a.elemSize()); + ASSERT_EQ(0u , a.step); + ASSERT_EQ(dims , a.dims); + + Mat b; + b.create(sz, CV_8U); + b.copyTo(a); + + ASSERT_NE(nullptr , a.data); + ASSERT_NE(old_ptr , a.data); + ASSERT_NE(b.data , a.data); + ASSERT_EQ(sz , (cv::gapi::own::Size{a.cols, a.rows})); + ASSERT_EQ(static_cast(sz.width*sz.height), a.total()); + ASSERT_EQ(CV_8U , a.type()); + ASSERT_EQ(CV_8U , a.depth()); + ASSERT_EQ(1 , a.channels()); + ASSERT_EQ(sizeof(uint8_t), a.elemSize()); + ASSERT_EQ(static_cast(sz.width), a.step); + ASSERT_TRUE(a.dims.empty()); +} + TEST(OwnMat, ScalarAssign32SC1) { constexpr int width = 8; @@ -304,6 +489,19 @@ TEST(OwnMat, ScalarAssign8UC1) << cmp_result_mat << std::endl; } +TEST(OwnMat, ScalarAssignND) +{ + std::vector dims = {1,1000}; + Mat m; + m.create(dims, CV_32F); + m = cv::gapi::own::Scalar{-1}; + const float *ptr = reinterpret_cast(m.data); + + for (auto i = 0u; i < m.total(); i++) { + EXPECT_EQ(-1.f, ptr[i]); + } +} + TEST(OwnMat, ScalarAssign8UC3) { constexpr auto cv_type = CV_8SC3; diff --git a/modules/gapi/test/test_precomp.hpp b/modules/gapi/test/test_precomp.hpp index a2f8f3fc52..85373b8dd8 100644 --- a/modules/gapi/test/test_precomp.hpp +++ b/modules/gapi/test/test_precomp.hpp @@ -14,6 +14,7 @@ #include #include + #include #include #include @@ -25,5 +26,6 @@ #include #include #include +#include #endif // __OPENCV_GAPI_TEST_PRECOMP_HPP__