Merge pull request #17493 from TolyaTalamanov:at/python-bindings-gapi

* Implement G-API python bindings

* Fix hdr_parser

* Drop initlization with brackets using regexp

* Handle bracket initilization another way

* Add test for core operations

* Declaration and definition of View constructor now in different files

* Refactor tests

* Remove combine decorator from tests

* Fix comment to review

* Fix test

* Fix comments to review

* Remove GCompilerArgs implementation from python

Co-authored-by: Pinaev <danil.pinaev@intel.com>
This commit is contained in:
Anatoliy Talamanov 2020-07-29 16:18:52 +03:00 committed by GitHub
parent afe9993376
commit c708f506a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 128 additions and 24 deletions

View File

@ -24,6 +24,8 @@ ocv_add_module(gapi
opencv_imgproc opencv_imgproc
OPTIONAL OPTIONAL
opencv_video opencv_video
WRAP
python
) )
if(MSVC) if(MSVC)

View File

@ -528,7 +528,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref
@param ddepth optional depth of the output matrix. @param ddepth optional depth of the output matrix.
@sa sub, addWeighted @sa sub, addWeighted
*/ */
GAPI_EXPORTS GMat add(const GMat& src1, const GMat& src2, int ddepth = -1); GAPI_EXPORTS_W GMat add(const GMat& src1, const GMat& src2, int ddepth = -1);
/** @brief Calculates the per-element sum of matrix and given scalar. /** @brief Calculates the per-element sum of matrix and given scalar.

View File

@ -16,7 +16,7 @@ namespace gapi {
namespace core { namespace core {
namespace cpu { namespace cpu {
GAPI_EXPORTS GKernelPackage kernels(); GAPI_EXPORTS_W cv::gapi::GKernelPackage kernels();
} // namespace cpu } // namespace cpu
} // namespace core } // namespace core

View File

@ -13,7 +13,7 @@
namespace cv { namespace gapi { namespace core { namespace fluid { namespace cv { namespace gapi { namespace core { namespace fluid {
GAPI_EXPORTS GKernelPackage kernels(); GAPI_EXPORTS_W cv::gapi::GKernelPackage kernels();
}}}} }}}}

View File

@ -56,8 +56,6 @@ public:
} }
}; };
View() = default;
const inline uint8_t* InLineB(int index) const // -(w-1)/2...0...+(w-1)/2 for Filters const inline uint8_t* InLineB(int index) const // -(w-1)/2...0...+(w-1)/2 for Filters
{ {
return m_cache->linePtr(index); return m_cache->linePtr(index);
@ -80,6 +78,7 @@ public:
Priv& priv(); // internal use only Priv& priv(); // internal use only
const Priv& priv() const; // internal use only const Priv& priv() const; // internal use only
View();
View(std::unique_ptr<Priv>&& p); View(std::unique_ptr<Priv>&& p);
View(View&& v); View(View&& v);
View& operator=(View&& v); View& operator=(View&& v);

View File

@ -93,9 +93,12 @@ namespace detail {
* passed in (a variadic template parameter pack) into a vector of * passed in (a variadic template parameter pack) into a vector of
* cv::GCompileArg objects. * cv::GCompileArg objects.
*/ */
struct GAPI_EXPORTS GCompileArg struct GAPI_EXPORTS_W_SIMPLE GCompileArg
{ {
public: public:
// NB: Required for pythnon bindings
GCompileArg() = default;
std::string tag; std::string tag;
// FIXME: use decay in GArg/other trait-based wrapper before leg is shot! // FIXME: use decay in GArg/other trait-based wrapper before leg is shot!

View File

@ -118,7 +118,7 @@ namespace I {
* *
* @sa GCompiled * @sa GCompiled
*/ */
class GAPI_EXPORTS GComputation class GAPI_EXPORTS_W GComputation
{ {
public: public:
class Priv; class Priv;
@ -191,7 +191,7 @@ public:
* @param in2 second input GMat of the defined binary computation * @param in2 second input GMat of the defined binary computation
* @param out output GMat of the defined binary computation * @param out output GMat of the defined binary computation
*/ */
GComputation(GMat in1, GMat in2, GMat out); // Binary overload GAPI_WRAP GComputation(GMat in1, GMat in2, GMat out); // Binary overload
/** /**
* @brief Defines a binary (two inputs -- one output) computation * @brief Defines a binary (two inputs -- one output) computation
@ -275,7 +275,7 @@ public:
* @param args compilation arguments for underlying compilation * @param args compilation arguments for underlying compilation
* process. * process.
*/ */
void apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args = {}); // Unary overload void apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args = {}); // Unary overload
/** /**
* @brief Execute an unary computation (with compilation on the fly) * @brief Execute an unary computation (with compilation on the fly)
@ -286,7 +286,7 @@ public:
* @param args compilation arguments for underlying compilation * @param args compilation arguments for underlying compilation
* process. * process.
*/ */
void apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args = {}); // Unary overload (scalar) void apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args = {}); // Unary overload (scalar)
/** /**
* @brief Execute a binary computation (with compilation on the fly) * @brief Execute a binary computation (with compilation on the fly)
@ -298,7 +298,7 @@ public:
* @param args compilation arguments for underlying compilation * @param args compilation arguments for underlying compilation
* process. * process.
*/ */
void apply(cv::Mat in1, cv::Mat in2, cv::Mat &out, GCompileArgs &&args = {}); // Binary overload GAPI_WRAP void apply(cv::Mat in1, cv::Mat in2, CV_OUT cv::Mat &out, GCompileArgs &&args = {}); // Binary overload
/** /**
* @brief Execute an binary computation (with compilation on the fly) * @brief Execute an binary computation (with compilation on the fly)

View File

@ -445,7 +445,7 @@ namespace gapi {
* Finally, two kernel packages can be combined into a new one * Finally, two kernel packages can be combined into a new one
* with function cv::gapi::combine(). * with function cv::gapi::combine().
*/ */
class GAPI_EXPORTS GKernelPackage class GAPI_EXPORTS_W_SIMPLE GKernelPackage
{ {
/// @private /// @private
@ -712,6 +712,7 @@ namespace detail
static const char* tag() { return "gapi.use_only"; } static const char* tag() { return "gapi.use_only"; }
}; };
} // namespace detail } // namespace detail
} // namespace cv } // namespace cv
#endif // OPENCV_GAPI_GKERNEL_HPP #endif // OPENCV_GAPI_GKERNEL_HPP

View File

@ -46,10 +46,10 @@ struct GOrigin;
* `cv::GArray<T>` | std::vector<T> * `cv::GArray<T>` | std::vector<T>
* `cv::GOpaque<T>` | T * `cv::GOpaque<T>` | T
*/ */
class GAPI_EXPORTS GMat class GAPI_EXPORTS_W_SIMPLE GMat
{ {
public: public:
GMat(); // Empty constructor GAPI_WRAP GMat(); // Empty constructor
GMat(const GNode &n, std::size_t out); // Operation result constructor GMat(const GNode &n, std::size_t out); // Operation result constructor
GOrigin& priv(); // Internal use only GOrigin& priv(); // Internal use only

View File

@ -16,7 +16,7 @@ namespace gapi {
namespace core { namespace core {
namespace ocl { namespace ocl {
GAPI_EXPORTS GKernelPackage kernels(); GAPI_EXPORTS_W cv::gapi::GKernelPackage kernels();
} // namespace ocl } // namespace ocl
} // namespace core } // namespace core

View File

@ -11,8 +11,15 @@
# if defined(__OPENCV_BUILD) # if defined(__OPENCV_BUILD)
# include <opencv2/core/base.hpp> # include <opencv2/core/base.hpp>
# define GAPI_EXPORTS CV_EXPORTS # define GAPI_EXPORTS CV_EXPORTS
/* special informative macros for wrapper generators */
# define GAPI_WRAP CV_WRAP
# define GAPI_EXPORTS_W_SIMPLE CV_EXPORTS_W_SIMPLE
# define GAPI_EXPORTS_W CV_EXPORTS_W
# else # else
# define GAPI_WRAP
# define GAPI_EXPORTS # define GAPI_EXPORTS
# define GAPI_EXPORTS_W_SIMPLE
# define GAPI_EXPORTS_W
#if 0 // Note: the following version currently is not needed for non-OpenCV build #if 0 // Note: the following version currently is not needed for non-OpenCV build
# if defined _WIN32 # if defined _WIN32

View File

@ -13,7 +13,7 @@
namespace cv { namespace gapi { namespace core { namespace plaidml { namespace cv { namespace gapi { namespace core { namespace plaidml {
GAPI_EXPORTS GKernelPackage kernels(); GAPI_EXPORTS cv::gapi::GKernelPackage kernels();
}}}} }}}}

View File

@ -0,0 +1,13 @@
using gapi_GKernelPackage = cv::gapi::GKernelPackage;
template<>
bool pyopencv_to(PyObject* obj, std::vector<GCompileArg>& value, const ArgInfo& info)
{
return pyopencv_to_generic_vec(obj, value, info);
}
template<>
PyObject* pyopencv_from(const std::vector<GCompileArg>& value)
{
return pyopencv_from_generic_vec(value);
}

View File

@ -0,0 +1,7 @@
#error This is a shadow header file, which is not intended for processing by any compiler. \
Only bindings parser should handle this file.
namespace cv
{
GAPI_EXPORTS_W GCompileArgs compile_args(gapi::GKernelPackage pkg);
} // namespace cv

View File

@ -0,0 +1,42 @@
#!/usr/bin/env python
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests
# Plaidml is an optional backend
pkgs = [
cv.gapi.core.ocl.kernels(),
cv.gapi.core.cpu.kernels(),
cv.gapi.core.fluid.kernels()
# cv.gapi.core.plaidml.kernels()
]
class gapi_core_test(NewOpenCVTests):
def test_add(self):
# TODO: Extend to use any type and size here
sz = (1280, 720)
in1 = np.random.randint(0, 100, sz).astype(np.uint8)
in2 = np.random.randint(0, 100, sz).astype(np.uint8)
# OpenCV
expected = in1 + in2
# G-API
g_in1 = cv.GMat()
g_in2 = cv.GMat()
g_out = cv.gapi.add(g_in1, g_in2)
comp = cv.GComputation(g_in1, g_in2, g_out)
for pkg in pkgs:
actual = comp.apply(in1, in2, args=cv.compile_args(pkg))
# Comparison
self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF))
if __name__ == '__main__':
NewOpenCVTests.bootstrap()

View File

@ -694,6 +694,8 @@ fluid::View::View(std::unique_ptr<Priv>&& p)
: m_priv(std::move(p)), m_cache(&m_priv->cache()) : m_priv(std::move(p)), m_cache(&m_priv->cache())
{ /* nothing */ } { /* nothing */ }
fluid::View::View() = default;
fluid::View::View(View&&) = default; fluid::View::View(View&&) = default;
fluid::View& fluid::View::operator=(View&&) = default; fluid::View& fluid::View::operator=(View&&) = default;
fluid::View::~View() = default; fluid::View::~View() = default;

View File

@ -27,14 +27,14 @@ gen_template_check_self = Template("""
gen_template_call_constructor_prelude = Template("""new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new gen_template_call_constructor_prelude = Template("""new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new
if(self) """) if(self) """)
gen_template_call_constructor = Template("""self->v.reset(new ${cname}${args})""") gen_template_call_constructor = Template("""self->v.reset(new ${cname}${py_args})""")
gen_template_simple_call_constructor_prelude = Template("""if(self) """) gen_template_simple_call_constructor_prelude = Template("""if(self) """)
gen_template_simple_call_constructor = Template("""new (&(self->v)) ${cname}${args}""") gen_template_simple_call_constructor = Template("""new (&(self->v)) ${cname}${py_args}""")
gen_template_parse_args = Template("""const char* keywords[] = { $kw_list, NULL }; gen_template_parse_args = Template("""const char* keywords[] = { $kw_list, NULL };
if( PyArg_ParseTupleAndKeywords(args, kw, "$fmtspec", (char**)keywords, $parse_arglist)$code_cvt )""") if( PyArg_ParseTupleAndKeywords(py_args, kw, "$fmtspec", (char**)keywords, $parse_arglist)$code_cvt )""")
gen_template_func_body = Template("""$code_decl gen_template_func_body = Template("""$code_decl
$code_parse $code_parse
@ -362,6 +362,7 @@ class ArgInfo(object):
self.inputarg = True self.inputarg = True
self.outputarg = False self.outputarg = False
self.returnarg = False self.returnarg = False
self.isrvalueref = False
for m in arg_tuple[3]: for m in arg_tuple[3]:
if m == "/O": if m == "/O":
self.inputarg = False self.inputarg = False
@ -377,6 +378,8 @@ class ArgInfo(object):
elif m.startswith("/CA"): elif m.startswith("/CA"):
self.isarray = True self.isarray = True
self.arraycvt = m[2:].strip() self.arraycvt = m[2:].strip()
elif m == "/RRef":
self.isrvalueref = True
self.py_inputarg = False self.py_inputarg = False
self.py_outputarg = False self.py_outputarg = False
@ -529,14 +532,14 @@ class FuncInfo(object):
def get_wrapper_prototype(self, codegen): def get_wrapper_prototype(self, codegen):
full_fname = self.get_wrapper_name() full_fname = self.get_wrapper_name()
if self.isconstructor: if self.isconstructor:
return "static int {fn_name}(pyopencv_{type_name}_t* self, PyObject* args, PyObject* kw)".format( return "static int {fn_name}(pyopencv_{type_name}_t* self, PyObject* py_args, PyObject* kw)".format(
fn_name=full_fname, type_name=codegen.classes[self.classname].name) fn_name=full_fname, type_name=codegen.classes[self.classname].name)
if self.classname: if self.classname:
self_arg = "self" self_arg = "self"
else: else:
self_arg = "" self_arg = ""
return "static PyObject* %s(PyObject* %s, PyObject* args, PyObject* kw)" % (full_fname, self_arg) return "static PyObject* %s(PyObject* %s, PyObject* py_args, PyObject* kw)" % (full_fname, self_arg)
def get_tab_entry(self): def get_tab_entry(self):
prototype_list = [] prototype_list = []
@ -682,6 +685,10 @@ class FuncInfo(object):
if not code_args.endswith("("): if not code_args.endswith("("):
code_args += ", " code_args += ", "
if a.isrvalueref:
a.name = 'std::move(' + a.name + ')'
code_args += amp + a.name code_args += amp + a.name
code_args += ")" code_args += ")"
@ -695,7 +702,7 @@ class FuncInfo(object):
templ = gen_template_call_constructor templ = gen_template_call_constructor
code_prelude = templ_prelude.substitute(name=selfinfo.name, cname=selfinfo.cname) code_prelude = templ_prelude.substitute(name=selfinfo.name, cname=selfinfo.cname)
code_fcall = templ.substitute(name=selfinfo.name, cname=selfinfo.cname, args=code_args) code_fcall = templ.substitute(name=selfinfo.name, cname=selfinfo.cname, py_args=code_args)
if v.isphantom: if v.isphantom:
code_fcall = code_fcall.replace("new " + selfinfo.cname, self.cname.replace("::", "_")) code_fcall = code_fcall.replace("new " + selfinfo.cname, self.cname.replace("::", "_"))
else: else:
@ -744,7 +751,7 @@ class FuncInfo(object):
parse_arglist = ", ".join(["&" + all_cargs[argno][1] for aname, argno in v.py_arglist]), parse_arglist = ", ".join(["&" + all_cargs[argno][1] for aname, argno in v.py_arglist]),
code_cvt = " &&\n ".join(code_cvt_list)) code_cvt = " &&\n ".join(code_cvt_list))
else: else:
code_parse = "if(PyObject_Size(args) == 0 && (!kw || PyObject_Size(kw) == 0))" code_parse = "if(PyObject_Size(py_args) == 0 && (!kw || PyObject_Size(kw) == 0))"
if len(v.py_outlist) == 0: if len(v.py_outlist) == 0:
code_ret = "Py_RETURN_NONE" code_ret = "Py_RETURN_NONE"

View File

@ -111,6 +111,11 @@ class CppHeaderParser(object):
if npos >= 0: if npos >= 0:
modlist.append("/C") modlist.append("/C")
npos = arg_str.find("&&")
if npos >= 0:
arg_str = arg_str.replace("&&", '')
modlist.append("/RRef")
npos = arg_str.find("&") npos = arg_str.find("&")
if npos >= 0: if npos >= 0:
modlist.append("/Ref") modlist.append("/Ref")
@ -715,6 +720,8 @@ class CppHeaderParser(object):
return stmt_type, classname, True, decl return stmt_type, classname, True, decl
if stmt.startswith("enum") or stmt.startswith("namespace"): if stmt.startswith("enum") or stmt.startswith("namespace"):
# NB: Drop inheritance syntax for enum
stmt = stmt.split(':')[0]
stmt_list = stmt.rsplit(" ", 1) stmt_list = stmt.rsplit(" ", 1)
if len(stmt_list) < 2: if len(stmt_list) < 2:
stmt_list.append("<unnamed>") stmt_list.append("<unnamed>")
@ -812,6 +819,15 @@ class CppHeaderParser(object):
l = l0.strip() l = l0.strip()
# G-API specific aliases
l = self.batch_replace(l, [
("GAPI_EXPORTS", "CV_EXPORTS"),
("GAPI_EXPORTS_W", "CV_EXPORTS_W"),
("GAPI_EXPORTS_W_SIMPLE","CV_EXPORTS_W_SIMPLE"),
("GAPI_WRAP", "CV_WRAP"),
('defined(GAPI_STANDALONE)', '0'),
])
if state == SCAN and l.startswith("#"): if state == SCAN and l.startswith("#"):
state = DIRECTIVE state = DIRECTIVE
# fall through to the if state == DIRECTIVE check # fall through to the if state == DIRECTIVE check
@ -867,7 +883,12 @@ class CppHeaderParser(object):
sys.exit(-1) sys.exit(-1)
while 1: while 1:
token, pos = self.find_next_token(l, [";", "\"", "{", "}", "//", "/*"]) # NB: Avoid parsing '{' for case:
# foo(Obj&& = {});
if re.search(r'=\s*\{\s*\}', l):
token, pos = ';', len(l)
else:
token, pos = self.find_next_token(l, [";", "\"", "{", "}", "//", "/*"])
if not token: if not token:
block_head += " " + l block_head += " " + l