From: Anatoliy Talamanov Date: Tue, 30 Mar 2021 20:59:02 +0000 (+0300) Subject: Merge pull request #19804 from TolyaTalamanov:at/python-custom-op X-Git-Tag: accepted/tizen/unified/20220125.121719~1^2~129 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=3f14cb073b60d3fd962ee1b31d59c87e6886be1d;p=platform%2Fupstream%2Fopencv.git Merge pull request #19804 from TolyaTalamanov:at/python-custom-op [G-API] Introduce custom python operator API * Introduce custom python operator API * Add wip namespace --- diff --git a/modules/gapi/include/opencv2/gapi/garray.hpp b/modules/gapi/include/opencv2/gapi/garray.hpp index 0798655..36e61de 100644 --- a/modules/gapi/include/opencv2/gapi/garray.hpp +++ b/modules/gapi/include/opencv2/gapi/garray.hpp @@ -35,14 +35,14 @@ template class GArray; * \addtogroup gapi_meta_args * @{ */ -struct GArrayDesc +struct GAPI_EXPORTS_W_SIMPLE GArrayDesc { // FIXME: Body // FIXME: Also implement proper operator== then bool operator== (const GArrayDesc&) const { return true; } }; template GArrayDesc descr_of(const std::vector &) { return {};} -static inline GArrayDesc empty_array_desc() {return {}; } +GAPI_EXPORTS_W inline GArrayDesc empty_array_desc() {return {}; } /** @} */ std::ostream& operator<<(std::ostream& os, const cv::GArrayDesc &desc); diff --git a/modules/gapi/include/opencv2/gapi/gmat.hpp b/modules/gapi/include/opencv2/gapi/gmat.hpp index 20a10db..5e567fb 100644 --- a/modules/gapi/include/opencv2/gapi/gmat.hpp +++ b/modules/gapi/include/opencv2/gapi/gmat.hpp @@ -73,25 +73,25 @@ class RMat; * \addtogroup gapi_meta_args * @{ */ -struct GAPI_EXPORTS GMatDesc +struct GAPI_EXPORTS_W_SIMPLE GMatDesc { // FIXME: Default initializers in C++14 - int depth; - int chan; - cv::Size size; // NB.: no multi-dimensional cases covered yet - bool planar; - std::vector dims; // FIXME: Maybe it's real questionable to have it here + GAPI_PROP int depth; + GAPI_PROP int chan; + GAPI_PROP cv::Size size; // NB.: no multi-dimensional cases covered yet + GAPI_PROP bool planar; + GAPI_PROP std::vector dims; // FIXME: Maybe it's real questionable to have it here - GMatDesc(int d, int c, cv::Size s, bool p = false) + GAPI_WRAP GMatDesc(int d, int c, cv::Size s, bool p = false) : depth(d), chan(c), size(s), planar(p) {} - GMatDesc(int d, const std::vector &dd) + GAPI_WRAP GMatDesc(int d, const std::vector &dd) : depth(d), chan(-1), size{-1,-1}, planar(false), dims(dd) {} - GMatDesc(int d, std::vector &&dd) + GAPI_WRAP 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}) {} + GAPI_WRAP GMatDesc() : GMatDesc(-1, -1, {-1,-1}) {} inline bool operator== (const GMatDesc &rhs) const { @@ -155,7 +155,7 @@ struct GAPI_EXPORTS GMatDesc // Meta combinator: return a new GMatDesc with specified data depth // and number of channels. // (all other fields are taken unchanged from this GMatDesc) - GMatDesc withType(int ddepth, int dchan) const + GAPI_WRAP GMatDesc withType(int ddepth, int dchan) const { GAPI_Assert(CV_MAT_CN(ddepth) == 1 || ddepth == -1); GMatDesc desc = withDepth(ddepth); diff --git a/modules/gapi/include/opencv2/gapi/gopaque.hpp b/modules/gapi/include/opencv2/gapi/gopaque.hpp index 6117971..9c56b14 100644 --- a/modules/gapi/include/opencv2/gapi/gopaque.hpp +++ b/modules/gapi/include/opencv2/gapi/gopaque.hpp @@ -33,14 +33,14 @@ template class GOpaque; * \addtogroup gapi_meta_args * @{ */ -struct GOpaqueDesc +struct GAPI_EXPORTS_W_SIMPLE GOpaqueDesc { // FIXME: Body // FIXME: Also implement proper operator== then bool operator== (const GOpaqueDesc&) const { return true; } }; template GOpaqueDesc descr_of(const U &) { return {};} -static inline GOpaqueDesc empty_gopaque_desc() {return {}; } +GAPI_EXPORTS_W inline GOpaqueDesc empty_gopaque_desc() {return {}; } /** @} */ std::ostream& operator<<(std::ostream& os, const cv::GOpaqueDesc &desc); diff --git a/modules/gapi/include/opencv2/gapi/gscalar.hpp b/modules/gapi/include/opencv2/gapi/gscalar.hpp index 00abdd1..d4af2ca 100644 --- a/modules/gapi/include/opencv2/gapi/gscalar.hpp +++ b/modules/gapi/include/opencv2/gapi/gscalar.hpp @@ -49,7 +49,7 @@ private: * \addtogroup gapi_meta_args * @{ */ -struct GScalarDesc +struct GAPI_EXPORTS_W_SIMPLE GScalarDesc { // NB.: right now it is empty @@ -64,9 +64,9 @@ struct GScalarDesc } }; -static inline GScalarDesc empty_scalar_desc() { return GScalarDesc(); } +GAPI_EXPORTS_W inline GScalarDesc empty_scalar_desc() { return GScalarDesc(); } -GAPI_EXPORTS GScalarDesc descr_of(const cv::Scalar &scalar); +GAPI_EXPORTS GScalarDesc descr_of(const cv::Scalar &scalar); std::ostream& operator<<(std::ostream& os, const cv::GScalarDesc &desc); diff --git a/modules/gapi/include/opencv2/gapi/own/exports.hpp b/modules/gapi/include/opencv2/gapi/own/exports.hpp index da42a32..1978991 100644 --- a/modules/gapi/include/opencv2/gapi/own/exports.hpp +++ b/modules/gapi/include/opencv2/gapi/own/exports.hpp @@ -12,10 +12,12 @@ # include # define GAPI_EXPORTS CV_EXPORTS /* special informative macros for wrapper generators */ +# define GAPI_PROP CV_PROP # define GAPI_WRAP CV_WRAP # define GAPI_EXPORTS_W_SIMPLE CV_EXPORTS_W_SIMPLE # define GAPI_EXPORTS_W CV_EXPORTS_W # else +# define GAPI_PROP # define GAPI_WRAP # define GAPI_EXPORTS # define GAPI_EXPORTS_W_SIMPLE diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp index 5731cc2..de6528b 100644 --- a/modules/gapi/misc/python/pyopencv_gapi.hpp +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -3,6 +3,11 @@ #ifdef HAVE_OPENCV_GAPI +#ifdef _MSC_VER +#pragma warning(disable: 4503) // "decorated name length exceeded" + // on empty_meta(const cv::GMetaArgs&, const cv::GArgs&) +#endif + #include #include @@ -507,10 +512,100 @@ static cv::GRunArgs run_py_kernel(PyObject* kernel, // FIXME: Now it's impossible to obtain meta function from operation, // because kernel connects to operation only by id (string). -static GMetaArgs empty_meta(const cv::GMetaArgs &, const cv::GArgs &) { +static cv::GMetaArgs empty_meta(const cv::GMetaArgs &, const cv::GArgs &) { return {}; } +static GMetaArg get_meta_arg(PyObject* obj) +{ + if (PyObject_TypeCheck(obj, + reinterpret_cast(pyopencv_GMatDesc_TypePtr))) + { + return cv::GMetaArg{reinterpret_cast(obj)->v}; + } + else if (PyObject_TypeCheck(obj, + reinterpret_cast(pyopencv_GScalarDesc_TypePtr))) + { + return cv::GMetaArg{reinterpret_cast(obj)->v}; + } + else if (PyObject_TypeCheck(obj, + reinterpret_cast(pyopencv_GArrayDesc_TypePtr))) + { + return cv::GMetaArg{reinterpret_cast(obj)->v}; + } + else if (PyObject_TypeCheck(obj, + reinterpret_cast(pyopencv_GOpaqueDesc_TypePtr))) + { + return cv::GMetaArg{reinterpret_cast(obj)->v}; + } + else + { + util::throw_error(std::logic_error("Unsupported output meta type")); + } +} + +static cv::GMetaArgs get_meta_args(PyObject* tuple) +{ + size_t size = PyTuple_Size(tuple); + + cv::GMetaArgs metas; + metas.reserve(size); + for (size_t i = 0; i < size; ++i) + { + metas.push_back(get_meta_arg(PyTuple_GetItem(tuple, i))); + } + + return metas; +} + +static GMetaArgs python_meta(PyObject* outMeta, const cv::GMetaArgs &meta, const cv::GArgs &gargs) { + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + cv::GMetaArgs out_metas; + try + { + PyObject* args = PyTuple_New(meta.size()); + size_t idx = 0; + for (auto&& m : meta) + { + switch (m.index()) + { + case cv::GMetaArg::index_of(): + PyTuple_SetItem(args, idx, pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(args, idx, pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(args, idx, pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(args, idx, pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(args, idx, gargs[idx].get()); + break; + case cv::GMetaArg::index_of(): + util::throw_error(std::logic_error("GFrame isn't supported for custom operation")); + break; + } + ++idx; + } + PyObject* result = PyObject_CallObject(outMeta, args); + out_metas = PyTuple_Check(result) ? get_meta_args(result) + : cv::GMetaArgs{get_meta_arg(result)}; + } + catch (...) + { + PyGILState_Release(gstate); + throw; + } + PyGILState_Release(gstate); + + return out_metas; +} + static PyObject* pyopencv_cv_gapi_kernels(PyObject* , PyObject* py_args, PyObject*) { using namespace cv; @@ -538,6 +633,69 @@ static PyObject* pyopencv_cv_gapi_kernels(PyObject* , PyObject* py_args, PyObjec return pyopencv_from(pkg); } +static PyObject* pyopencv_cv_gapi_op(PyObject* , PyObject* py_args, PyObject*) +{ + using namespace cv; + Py_ssize_t size = PyTuple_Size(py_args); + std::string id; + if (!pyopencv_to(PyTuple_GetItem(py_args, 0), id, ArgInfo("id", false))) + { + PyErr_SetString(PyExc_TypeError, "Failed to obtain: operation id must be a string"); + return NULL; + } + PyObject* outMeta = PyTuple_GetItem(py_args, 1); + Py_INCREF(outMeta); + + cv::GArgs args; + for (int i = 2; i < size; i++) + { + PyObject* item = PyTuple_GetItem(py_args, i); + if (PyObject_TypeCheck(item, + reinterpret_cast(pyopencv_GMat_TypePtr))) + { + args.emplace_back(reinterpret_cast(item)->v); + } + else if (PyObject_TypeCheck(item, + reinterpret_cast(pyopencv_GScalar_TypePtr))) + { + args.emplace_back(reinterpret_cast(item)->v); + } + else if (PyObject_TypeCheck(item, + reinterpret_cast(pyopencv_GOpaqueT_TypePtr))) + { + auto&& arg = reinterpret_cast(item)->v.arg(); +#define HC(T, K) case cv::GOpaqueT::Storage:: index_of>(): \ + args.emplace_back(cv::util::get>(arg)); \ + break; \ + + SWITCH(arg.index(), GOPAQUE_TYPE_LIST_G, HC) +#undef HC + } + else if (PyObject_TypeCheck(item, + reinterpret_cast(pyopencv_GArrayT_TypePtr))) + { + auto&& arg = reinterpret_cast(item)->v.arg(); +#define HC(T, K) case cv::GArrayT::Storage:: index_of>(): \ + args.emplace_back(cv::util::get>(arg)); \ + break; \ + + SWITCH(arg.index(), GARRAY_TYPE_LIST_G, HC) +#undef HC + } + else + { + Py_INCREF(item); + args.emplace_back(cv::GArg(item)); + } + } + + cv::GKernel::M outMetaWrapper = std::bind(python_meta, + outMeta, + std::placeholders::_1, + std::placeholders::_2); + return pyopencv_from(cv::gapi::wip::op(id, outMetaWrapper, std::move(args))); +} + static PyObject* pyopencv_cv_gin(PyObject*, PyObject* py_args, PyObject*) { Py_INCREF(py_args); diff --git a/modules/gapi/misc/python/python_bridge.hpp b/modules/gapi/misc/python/python_bridge.hpp index b92553b..51f0ca8 100644 --- a/modules/gapi/misc/python/python_bridge.hpp +++ b/modules/gapi/misc/python/python_bridge.hpp @@ -119,6 +119,7 @@ GARRAY_TYPE_LIST_G(DEFINE_TYPE_TRAITS, DEFINE_TYPE_TRAITS) class GAPI_EXPORTS_W_SIMPLE GOpaqueT { public: + GOpaqueT() = default; using Storage = cv::detail::MakeVariantType; template @@ -156,6 +157,7 @@ private: class GAPI_EXPORTS_W_SIMPLE GArrayT { public: + GArrayT() = default; using Storage = cv::detail::MakeVariantType; template @@ -190,6 +192,136 @@ private: Storage m_arg; }; +namespace gapi { +namespace wip { + +class GAPI_EXPORTS_W_SIMPLE GOutputs +{ +public: + GOutputs() = default; + GOutputs(const std::string& id, cv::GKernel::M outMeta, cv::GArgs &&ins); + + GAPI_WRAP cv::GMat getGMat(); + GAPI_WRAP cv::GScalar getGScalar(); + GAPI_WRAP cv::GArrayT getGArray(cv::gapi::ArgType type); + GAPI_WRAP cv::GOpaqueT getGOpaque(cv::gapi::ArgType type); + +private: + class Priv; + std::shared_ptr m_priv; +}; + +GOutputs op(const std::string& id, cv::GKernel::M outMeta, cv::GArgs&& args); + +template +GOutputs op(const std::string& id, cv::GKernel::M outMeta, T&&... args) +{ + return op(id, outMeta, cv::GArgs{cv::GArg(std::forward(args))... }); +} + +} // namespace wip +} // namespace gapi } // namespace cv +cv::gapi::wip::GOutputs cv::gapi::wip::op(const std::string& id, + cv::GKernel::M outMeta, + cv::GArgs&& args) +{ + cv::gapi::wip::GOutputs outputs{id, outMeta, std::move(args)}; + return outputs; +} + +class cv::gapi::wip::GOutputs::Priv +{ +public: + Priv(const std::string& id, cv::GKernel::M outMeta, cv::GArgs &&ins); + + cv::GMat getGMat(); + cv::GScalar getGScalar(); + cv::GArrayT getGArray(cv::gapi::ArgType); + cv::GOpaqueT getGOpaque(cv::gapi::ArgType); + +private: + int output = 0; + std::unique_ptr m_call; +}; + +cv::gapi::wip::GOutputs::Priv::Priv(const std::string& id, cv::GKernel::M outMeta, cv::GArgs &&args) +{ + cv::GKinds kinds; + kinds.reserve(args.size()); + std::transform(args.begin(), args.end(), std::back_inserter(kinds), + [](const cv::GArg& arg) { return arg.opaque_kind; }); + + m_call.reset(new cv::GCall{cv::GKernel{id, {}, outMeta, {}, std::move(kinds), {}}}); + m_call->setArgs(std::move(args)); +} + +cv::GMat cv::gapi::wip::GOutputs::Priv::getGMat() +{ + m_call->kernel().outShapes.push_back(cv::GShape::GMAT); + // ...so _empty_ constructor is passed here. + m_call->kernel().outCtors.emplace_back(cv::util::monostate{}); + return m_call->yield(output++); +} + +cv::GScalar cv::gapi::wip::GOutputs::Priv::getGScalar() +{ + m_call->kernel().outShapes.push_back(cv::GShape::GSCALAR); + // ...so _empty_ constructor is passed here. + m_call->kernel().outCtors.emplace_back(cv::util::monostate{}); + return m_call->yieldScalar(output++); +} + +cv::GArrayT cv::gapi::wip::GOutputs::Priv::getGArray(cv::gapi::ArgType type) +{ + m_call->kernel().outShapes.push_back(cv::GShape::GARRAY); +#define HC(T, K) \ + case K: \ + m_call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor>::get()); \ + return cv::GArrayT(m_call->yieldArray(output++)); \ + + SWITCH(type, GARRAY_TYPE_LIST_G, HC) +#undef HC +} + +cv::GOpaqueT cv::gapi::wip::GOutputs::Priv::getGOpaque(cv::gapi::ArgType type) +{ + m_call->kernel().outShapes.push_back(cv::GShape::GOPAQUE); +#define HC(T, K) \ + case K: \ + m_call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor>::get()); \ + return cv::GOpaqueT(m_call->yieldOpaque(output++)); \ + + SWITCH(type, GOPAQUE_TYPE_LIST_G, HC) +#undef HC +} + +cv::gapi::wip::GOutputs::GOutputs(const std::string& id, + cv::GKernel::M outMeta, + cv::GArgs &&ins) : + m_priv(new cv::gapi::wip::GOutputs::Priv(id, outMeta, std::move(ins))) +{ +} + +cv::GMat cv::gapi::wip::GOutputs::getGMat() +{ + return m_priv->getGMat(); +} + +cv::GScalar cv::gapi::wip::GOutputs::getGScalar() +{ + return m_priv->getGScalar(); +} + +cv::GArrayT cv::gapi::wip::GOutputs::getGArray(cv::gapi::ArgType type) +{ + return m_priv->getGArray(type); +} + +cv::GOpaqueT cv::gapi::wip::GOutputs::getGOpaque(cv::gapi::ArgType type) +{ + return m_priv->getGOpaque(type); +} + #endif // OPENCV_GAPI_PYTHON_BRIDGE_HPP diff --git a/modules/gapi/misc/python/shadow_gapi.hpp b/modules/gapi/misc/python/shadow_gapi.hpp index 0383919..bb82002 100644 --- a/modules/gapi/misc/python/shadow_gapi.hpp +++ b/modules/gapi/misc/python/shadow_gapi.hpp @@ -7,6 +7,7 @@ namespace cv GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GKernelPackage pkg); GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GNetPackage pkg); + GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GKernelPackage kernels, gapi::GNetPackage nets); // NB: This classes doesn't exist in *.so // HACK: Mark them as a class to force python wrapper generate code for this entities @@ -14,7 +15,7 @@ namespace cv class GAPI_EXPORTS_W_SIMPLE GProtoInputArgs { }; class GAPI_EXPORTS_W_SIMPLE GProtoOutputArgs { }; class GAPI_EXPORTS_W_SIMPLE GRunArg { }; - class GAPI_EXPORTS_W_SIMPLE GMetaArg { }; + class GAPI_EXPORTS_W_SIMPLE GMetaArg { GAPI_WRAP GMetaArg(); }; using GProtoInputArgs = GIOProtoArgs; using GProtoOutputArgs = GIOProtoArgs; diff --git a/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py b/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py index 05b747f..0e3eccd 100644 --- a/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py +++ b/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py @@ -68,6 +68,85 @@ def custom_boundingRect(array): # G-API - array of tuples (n_points). return cv.boundingRect(np.array(array)) +# Test input mat +def add(g_in1, g_in2, dtype): + def custom_add_meta(img_desc1, img_desc2, dtype): + return img_desc1 + + return cv.gapi_wip_op('custom.add', custom_add_meta, g_in1, g_in2, dtype).getGMat() + + +# Test multiple output mat +def split3(g_in): + def custom_split3_meta(img_desc): + out_desc = img_desc.withType(img_desc.depth, 1) + return out_desc, out_desc, out_desc + + op = cv.gapi_wip_op('custom.split3', custom_split3_meta, g_in) + + ch1 = op.getGMat() + ch2 = op.getGMat() + ch3 = op.getGMat() + + return ch1, ch2, ch3 + +# Test output scalar +def mean(g_in): + def custom_mean_meta(img_desc): + return cv.empty_scalar_desc() + + op = cv.gapi_wip_op('custom.mean', custom_mean_meta, g_in) + return op.getGScalar() + + +# Test input scalar +def addC(g_in, g_sc, dtype): + def custom_addC_meta(img_desc, sc_desc, dtype): + return img_desc + + op = cv.gapi_wip_op('custom.addC', custom_addC_meta, g_in, g_sc, dtype) + return op.getGMat() + + +# Test output opaque. +def size(g_in): + def custom_size_meta(img_desc): + return cv.empty_gopaque_desc() + + op = cv.gapi_wip_op('custom.size', custom_size_meta, g_in) + return op.getGOpaque(cv.gapi.CV_SIZE) + + +# Test input opaque. +def sizeR(g_rect): + def custom_sizeR_meta(opaque_desc): + return cv.empty_gopaque_desc() + + op = cv.gapi_wip_op('custom.sizeR', custom_sizeR_meta, g_rect) + return op.getGOpaque(cv.gapi.CV_SIZE) + + +# Test input array. +def boundingRect(g_array): + def custom_boundingRect_meta(array_desc): + return cv.empty_gopaque_desc() + + op = cv.gapi_wip_op('custom.boundingRect', custom_boundingRect_meta, g_array) + return op.getGOpaque(cv.gapi.CV_RECT) + + +# Test output GArray. +def goodFeaturesToTrack(g_in, max_corners, quality_lvl, + min_distance, mask, block_sz, + use_harris_detector, k): + def custom_goodFeaturesToTrack_meta(img_desc, max_corners, quality_lvl, + min_distance, mask, block_sz, use_harris_detector, k): + return cv.empty_array_desc() + + op = cv.gapi_wip_op('custom.goodFeaturesToTrack', custom_goodFeaturesToTrack_meta, g_in, + max_corners, quality_lvl, min_distance, mask, block_sz, use_harris_detector, k) + return op.getGArray(cv.gapi.CV_POINT2F) + class gapi_sample_pipelines(NewOpenCVTests): @@ -270,5 +349,178 @@ class gapi_sample_pipelines(NewOpenCVTests): self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + def test_custom_op_add(self): + sz = (3, 3) + in_mat1 = np.full(sz, 45, dtype=np.uint8) + in_mat2 = np.full(sz, 50, dtype=np.uint8) + + # OpenCV + expected = cv.add(in_mat1, in_mat2) + + # G-API + g_in1 = cv.GMat() + g_in2 = cv.GMat() + g_out = add(g_in1, g_in2, cv.CV_8UC1) + + comp = cv.GComputation(cv.GIn(g_in1, g_in2), cv.GOut(g_out)) + + pkg = cv.gapi_wip_kernels((custom_add, 'custom.add')) + actual = comp.apply(cv.gin(in_mat1, in_mat2), args=cv.compile_args(pkg)) + + self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + + + def test_custom_op_split3(self): + sz = (4, 4) + in_ch1 = np.full(sz, 1, dtype=np.uint8) + in_ch2 = np.full(sz, 2, dtype=np.uint8) + in_ch3 = np.full(sz, 3, dtype=np.uint8) + # H x W x C + in_mat = np.stack((in_ch1, in_ch2, in_ch3), axis=2) + + # G-API + g_in = cv.GMat() + g_ch1, g_ch2, g_ch3 = split3(g_in) + + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_ch1, g_ch2, g_ch3)) + + pkg = cv.gapi_wip_kernels((custom_split3, 'custom.split3')) + ch1, ch2, ch3 = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg)) + + self.assertEqual(0.0, cv.norm(in_ch1, ch1, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(in_ch2, ch2, cv.NORM_INF)) + self.assertEqual(0.0, cv.norm(in_ch3, ch3, cv.NORM_INF)) + + + def test_custom_op_mean(self): + img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + in_mat = cv.imread(img_path) + + # OpenCV + expected = cv.mean(in_mat) + + # G-API + g_in = cv.GMat() + g_out = mean(g_in) + + comp = cv.GComputation(g_in, g_out) + + pkg = cv.gapi_wip_kernels((custom_mean, 'custom.mean')) + actual = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg)) + + # Comparison + self.assertEqual(expected, actual) + + + def test_custom_op_addC(self): + sz = (3, 3, 3) + in_mat = np.full(sz, 45, dtype=np.uint8) + sc = (50, 10, 20) + + # Numpy reference, make array from sc to keep uint8 dtype. + expected = in_mat + np.array(sc, dtype=np.uint8) + + # G-API + g_in = cv.GMat() + g_sc = cv.GScalar() + g_out = addC(g_in, g_sc, cv.CV_8UC1) + comp = cv.GComputation(cv.GIn(g_in, g_sc), cv.GOut(g_out)) + + pkg = cv.gapi_wip_kernels((custom_addC, 'custom.addC')) + actual = comp.apply(cv.gin(in_mat, sc), args=cv.compile_args(pkg)) + + self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + + + def test_custom_op_size(self): + sz = (100, 150, 3) + in_mat = np.full(sz, 45, dtype=np.uint8) + + # Open_cV + expected = (100, 150) + + # G-API + g_in = cv.GMat() + g_sz = size(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_sz)) + + pkg = cv.gapi_wip_kernels((custom_size, 'custom.size')) + actual = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg)) + + self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + + + def test_custom_op_sizeR(self): + # x, y, h, w + roi = (10, 15, 100, 150) + + expected = (100, 150) + + # G-API + g_r = cv.GOpaqueT(cv.gapi.CV_RECT) + g_sz = sizeR(g_r) + comp = cv.GComputation(cv.GIn(g_r), cv.GOut(g_sz)) + + pkg = cv.gapi_wip_kernels((custom_sizeR, 'custom.sizeR')) + actual = comp.apply(cv.gin(roi), args=cv.compile_args(pkg)) + + # cv.norm works with tuples ? + self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + + + def test_custom_op_boundingRect(self): + points = [(0,0), (0,1), (1,0), (1,1)] + + # OpenCV + expected = cv.boundingRect(np.array(points)) + + # G-API + g_pts = cv.GArrayT(cv.gapi.CV_POINT) + g_br = boundingRect(g_pts) + comp = cv.GComputation(cv.GIn(g_pts), cv.GOut(g_br)) + + pkg = cv.gapi_wip_kernels((custom_boundingRect, 'custom.boundingRect')) + actual = comp.apply(cv.gin(points), args=cv.compile_args(pkg)) + + # cv.norm works with tuples ? + self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF)) + + + def test_custom_op_goodFeaturesToTrack(self): + # G-API + img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) + in_mat = cv.cvtColor(cv.imread(img_path), cv.COLOR_RGB2GRAY) + + # NB: goodFeaturesToTrack configuration + max_corners = 50 + quality_lvl = 0.01 + min_distance = 10 + block_sz = 3 + use_harris_detector = True + k = 0.04 + mask = None + + # OpenCV + expected = cv.goodFeaturesToTrack(in_mat, max_corners, quality_lvl, + min_distance, mask=mask, + blockSize=block_sz, useHarrisDetector=use_harris_detector, k=k) + + # G-API + g_in = cv.GMat() + g_out = goodFeaturesToTrack(g_in, max_corners, quality_lvl, + min_distance, mask, block_sz, use_harris_detector, k) + + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + pkg = cv.gapi_wip_kernels((custom_goodFeaturesToTrack, 'custom.goodFeaturesToTrack')) + actual = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg)) + + # NB: OpenCV & G-API have different output types. + # OpenCV - numpy array with shape (num_points, 1, 2) + # G-API - list of tuples with size - num_points + # Comparison + self.assertEqual(0.0, cv.norm(expected.flatten(), + np.array(actual, dtype=np.float32).flatten(), cv.NORM_INF)) + + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/gapi/src/backends/python/gpythonbackend.cpp b/modules/gapi/src/backends/python/gpythonbackend.cpp index 748c2f1..7f4a867 100644 --- a/modules/gapi/src/backends/python/gpythonbackend.cpp +++ b/modules/gapi/src/backends/python/gpythonbackend.cpp @@ -74,7 +74,7 @@ class GPythonExecutable final: public cv::gimpl::GIslandExecutable public: GPythonExecutable(const ade::Graph &, - const std::vector &); + const std::vector &); const ade::Graph& m_g; cv::gimpl::GModel::ConstGraph m_gm; diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index f2b7492..dca43c5 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -2185,7 +2185,6 @@ static int convert_to_char(PyObject *o, char *dst, const ArgInfo& info) #include "pyopencv_generated_types_content.h" #include "pyopencv_generated_funcs.h" - static PyMethodDef special_methods[] = { {"redirectError", CV_PY_FN_WITH_KW(pycvRedirectError), "redirectError(onError) -> None"}, #ifdef HAVE_OPENCV_HIGHGUI @@ -2200,6 +2199,7 @@ static PyMethodDef special_methods[] = { #ifdef HAVE_OPENCV_GAPI {"GIn", CV_PY_FN_WITH_KW(pyopencv_cv_GIn), "GIn(...) -> GInputProtoArgs"}, {"gapi_wip_kernels", CV_PY_FN_WITH_KW(pyopencv_cv_gapi_kernels), "kernels(...) -> GKernelPackage"}, + {"gapi_wip_op", CV_PY_FN_WITH_KW_(pyopencv_cv_gapi_op, 0), "kernels(...) -> retval\n"}, {"GOut", CV_PY_FN_WITH_KW(pyopencv_cv_GOut), "GOut(...) -> GOutputProtoArgs"}, {"gin", CV_PY_FN_WITH_KW(pyopencv_cv_gin), "gin(...) -> ExtractArgsCallback"}, {"descr_of", CV_PY_FN_WITH_KW(pyopencv_cv_descr_of), "descr_of(...) -> ExtractMetaCallback"}, diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index 8974650..de43a40 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -829,6 +829,7 @@ class CppHeaderParser(object): ("GAPI_EXPORTS_W", "CV_EXPORTS_W"), ("GAPI_EXPORTS_W_SIMPLE","CV_EXPORTS_W_SIMPLE"), ("GAPI_WRAP", "CV_WRAP"), + ("GAPI_PROP", "CV_PROP"), ('defined(GAPI_STANDALONE)', '0'), ])