Merge pull request #18762 from TolyaTalamanov:at/support-garray
authorAnatoliy Talamanov <anatoliy.talamanov@intel.com>
Fri, 27 Nov 2020 17:39:46 +0000 (20:39 +0300)
committerGitHub <noreply@github.com>
Fri, 27 Nov 2020 17:39:46 +0000 (17:39 +0000)
[G-API] Wrap GArray

* Wrap GArray for output

* Collect in/out info in graph

* Add imgproc tests

* Add cv::Point2f

* Update test_gapi_imgproc.py

* Fix comments to review

20 files changed:
modules/gapi/include/opencv2/gapi/garray.hpp
modules/gapi/include/opencv2/gapi/gcommon.hpp
modules/gapi/include/opencv2/gapi/gkernel.hpp
modules/gapi/include/opencv2/gapi/imgproc.hpp
modules/gapi/include/opencv2/gapi/opencv_includes.hpp
modules/gapi/include/opencv2/gapi/own/types.hpp
modules/gapi/include/opencv2/gapi/s11n.hpp
modules/gapi/misc/python/pyopencv_gapi.hpp
modules/gapi/misc/python/shadow_gapi.hpp
modules/gapi/misc/python/test/test_gapi_core.py
modules/gapi/misc/python/test/test_gapi_imgproc.py [new file with mode: 0644]
modules/gapi/misc/python/test/test_gapi_sample_pipelines.py
modules/gapi/misc/python/test/test_gapi_streaming.py
modules/gapi/src/api/gcomputation.cpp
modules/gapi/src/backends/common/serialization.cpp
modules/gapi/src/compiler/gcompiled_priv.hpp
modules/gapi/src/compiler/gcompiler.cpp
modules/gapi/src/compiler/gstreaming.cpp
modules/gapi/src/compiler/gstreaming_priv.hpp
modules/gapi/test/own/gapi_types_tests.cpp

index 0798655..5d4b3c5 100644 (file)
@@ -368,6 +368,8 @@ private:
     detail::GArrayU m_ref;
 };
 
+using GArrayP2f = GArray<cv::Point2f>;
+
 /** @} */
 
 } // namespace cv
index 0242020..a474140 100644 (file)
@@ -49,6 +49,7 @@ namespace detail
         CV_UINT64,     // uint64_t user G-API data
         CV_STRING,     // std::string user G-API data
         CV_POINT,      // cv::Point user G-API data
+        CV_POINT2F,    // cv::Point2f user G-API data
         CV_SIZE,       // cv::Size user G-API data
         CV_RECT,       // cv::Rect user G-API data
         CV_SCALAR,     // cv::Scalar user G-API data
@@ -68,15 +69,16 @@ namespace detail
     template<> struct GOpaqueTraits<cv::Size>    { static constexpr const OpaqueKind kind = OpaqueKind::CV_SIZE; };
     template<> struct GOpaqueTraits<cv::Scalar>  { static constexpr const OpaqueKind kind = OpaqueKind::CV_SCALAR; };
     template<> struct GOpaqueTraits<cv::Point>   { static constexpr const OpaqueKind kind = OpaqueKind::CV_POINT; };
+    template<> struct GOpaqueTraits<cv::Point2f> { static constexpr const OpaqueKind kind = OpaqueKind::CV_POINT2F; };
     template<> struct GOpaqueTraits<cv::Mat>     { static constexpr const OpaqueKind kind = OpaqueKind::CV_MAT; };
     template<> struct GOpaqueTraits<cv::Rect>    { static constexpr const OpaqueKind kind = OpaqueKind::CV_RECT; };
     template<> struct GOpaqueTraits<cv::GMat>    { static constexpr const OpaqueKind kind = OpaqueKind::CV_MAT; };
     template<> struct GOpaqueTraits<cv::gapi::wip::draw::Prim>
                                                  { static constexpr const OpaqueKind kind = OpaqueKind::CV_DRAW_PRIM; };
-    using GOpaqueTraitsArrayTypes = std::tuple<int, double, float, uint64_t, bool, std::string, cv::Size, cv::Scalar, cv::Point,
+    using GOpaqueTraitsArrayTypes = std::tuple<int, double, float, uint64_t, bool, std::string, cv::Size, cv::Scalar, cv::Point, cv::Point2f,
                                                cv::Mat, cv::Rect, cv::gapi::wip::draw::Prim>;
     // GOpaque is not supporting cv::Mat and cv::Scalar since there are GScalar and GMat types
-    using GOpaqueTraitsOpaqueTypes = std::tuple<int, double, float, uint64_t, bool, std::string, cv::Size, cv::Point, cv::Rect,
+    using GOpaqueTraitsOpaqueTypes = std::tuple<int, double, float, uint64_t, bool, std::string, cv::Size, cv::Point, cv::Point2f, cv::Rect,
                                                 cv::gapi::wip::draw::Prim>;
 } // namespace detail
 
index d4c3e6c..0ec7dd0 100644 (file)
 
 namespace cv {
 
-using GShapes = std::vector<GShape>;
-using GKinds = std::vector<cv::detail::OpaqueKind>;
-using GCtors  = std::vector<detail::HostCtor>;
+struct GTypeInfo
+{
+    GShape                 shape;
+    cv::detail::OpaqueKind kind;
+};
+
+using GShapes    = std::vector<GShape>;
+using GKinds     = std::vector<cv::detail::OpaqueKind>;
+using GCtors     = std::vector<detail::HostCtor>;
+using GTypesInfo = std::vector<GTypeInfo>;
 
 // GKernel describes kernel API to the system
 // FIXME: add attributes of a kernel, (e.g. number and types
index e41c250..7435ec1 100644 (file)
@@ -1035,7 +1035,7 @@ or #cornerMinEigenVal.
 
 @return vector of detected corners.
  */
-GAPI_EXPORTS GArray<Point2f> goodFeaturesToTrack(const GMat  &image,
+GAPI_EXPORTS_W GArray<Point2f> goodFeaturesToTrack(const GMat  &image,
                                                        int    maxCorners,
                                                        double qualityLevel,
                                                        double minDistance,
@@ -1350,7 +1350,7 @@ Resulting gray color value computed as
 @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1.
 @sa RGB2YUV
  */
-GAPI_EXPORTS GMat RGB2Gray(const GMat& src);
+GAPI_EXPORTS_W GMat RGB2Gray(const GMat& src);
 
 /** @overload
 Resulting gray color value computed as
index 5f25fe4..08b2d6e 100644 (file)
 #  include <opencv2/gapi/own/mat.hpp>
 // replacement of cv's structures:
 namespace cv {
-    using Rect   = gapi::own::Rect;
-    using Size   = gapi::own::Size;
-    using Point  = gapi::own::Point;
-    using Scalar = gapi::own::Scalar;
-    using Mat    = gapi::own::Mat;
+    using Rect    = gapi::own::Rect;
+    using Size    = gapi::own::Size;
+    using Point   = gapi::own::Point;
+    using Point2f = gapi::own::Point2f;
+    using Scalar  = gapi::own::Scalar;
+    using Mat     = gapi::own::Mat;
 }  // namespace cv
 #endif // !defined(GAPI_STANDALONE)
 
index 20445ee..c77a62c 100644 (file)
@@ -28,6 +28,16 @@ public:
     int y = 0;
 };
 
+class Point2f
+{
+public:
+    Point2f() = default;
+    Point2f(float _x, float _y) : x(_x),  y(_y)  {};
+
+    float x = 0.f;
+    float y = 0.f;
+};
+
 class Rect
 {
 public:
index 0e3e382..0e2c4c2 100644 (file)
@@ -121,6 +121,9 @@ GAPI_EXPORTS std::unique_ptr<IIStream> getInStream(const std::vector<char> &p);
 GAPI_EXPORTS IOStream& operator<< (IOStream& os, const cv::Point &pt);
 GAPI_EXPORTS IIStream& operator>> (IIStream& is,       cv::Point &pt);
 
+GAPI_EXPORTS IOStream& operator<< (IOStream& os, const cv::Point2f &pt);
+GAPI_EXPORTS IIStream& operator>> (IIStream& is,       cv::Point2f &pt);
+
 GAPI_EXPORTS IOStream& operator<< (IOStream& os, const cv::Size &sz);
 GAPI_EXPORTS IIStream& operator>> (IIStream& is,       cv::Size &sz);
 
index 57c0b3d..e25328e 100644 (file)
@@ -47,8 +47,20 @@ static PyObject* from_grunarg(const GRunArg& v)
             const auto& s = util::get<cv::Scalar>(v);
             return pyopencv_from(s);
         }
-
+        case GRunArg::index_of<cv::detail::VectorRef>():
+        {
+            const auto& vref = util::get<cv::detail::VectorRef>(v);
+            switch (vref.getKind())
+            {
+                case cv::detail::OpaqueKind::CV_POINT2F:
+                    return pyopencv_from(vref.rref<cv::Point2f>());
+                default:
+                    PyErr_SetString(PyExc_TypeError, "Unsupported kind for GArray");
+                    return NULL;
+            }
+        }
         default:
+            PyErr_SetString(PyExc_TypeError, "Failed to unpack GRunArgs");
             return NULL;
     }
     GAPI_Assert(false);
@@ -65,7 +77,6 @@ PyObject* pyopencv_from(const GRunArgs& value)
         PyObject* item = from_grunarg(value[0]);
         if(!item)
         {
-            PyErr_SetString(PyExc_TypeError, "Failed to unpack GRunArgs");
             return NULL;
         }
         return item;
@@ -117,9 +128,13 @@ static PyObject* extract_proto_args(PyObject* py_args, PyObject* kw)
         {
             args.emplace_back(reinterpret_cast<pyopencv_GMat_t*>(item)->v);
         }
+        else if (PyObject_TypeCheck(item, reinterpret_cast<PyTypeObject*>(pyopencv_GArrayP2f_TypePtr)))
+        {
+            args.emplace_back(reinterpret_cast<pyopencv_GArrayP2f_t*>(item)->v.strip());
+        }
         else
         {
-            PyErr_SetString(PyExc_TypeError, "cv.GIn() supports only cv.GMat and cv.GScalar");
+            PyErr_SetString(PyExc_TypeError, "Unsupported type for cv.GIn()/cv.GOut()");
             return NULL;
         }
     }
index 0fac222..7923145 100644 (file)
@@ -16,6 +16,8 @@ namespace cv
    class GAPI_EXPORTS_W_SIMPLE GRunArg { };
    class GAPI_EXPORTS_W_SIMPLE GMetaArg { };
 
+   class GAPI_EXPORTS_W_SIMPLE GArrayP2f { };
+
    using GProtoInputArgs  = GIOProtoArgs<In_Tag>;
    using GProtoOutputArgs = GIOProtoArgs<Out_Tag>;
 
index cd85d9c..267037a 100644 (file)
@@ -2,26 +2,27 @@
 
 import numpy as np
 import cv2 as cv
+import os
 
 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()
-       ]
+          ('ocl'    , cv.gapi.core.ocl.kernels()),
+          ('cpu'    , cv.gapi.core.cpu.kernels()),
+          ('fluid'  , cv.gapi.core.fluid.kernels())
+          # ('plaidml', 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)
-        in2 = np.random.randint(0, 100, sz)
+        sz = (720, 1280)
+        in1 = np.full(sz, 100)
+        in2 = np.full(sz, 50)
 
         # OpenCV
         expected = cv.add(in1, in2)
@@ -32,17 +33,18 @@ class gapi_core_test(NewOpenCVTests):
         g_out = cv.gapi.add(g_in1, g_in2)
         comp = cv.GComputation(cv.GIn(g_in1, g_in2), cv.GOut(g_out))
 
-        for pkg in pkgs:
+        for pkg_name, pkg in pkgs:
             actual = comp.apply(cv.gin(in1, in2), args=cv.compile_args(pkg))
             # Comparison
-            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF))
-            self.assertEqual(expected.dtype, actual.dtype)
+            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
+            self.assertEqual(expected.dtype, actual.dtype, 'Failed on ' + pkg_name + ' backend')
 
 
     def test_add_uint8(self):
-        sz = (1280, 720)
-        in1 = np.random.randint(0, 100, sz).astype(np.uint8)
-        in2 = np.random.randint(0, 100, sz).astype(np.uint8)
+        sz = (720, 1280)
+        in1 = np.full(sz, 100, dtype=np.uint8)
+        in2 = np.full(sz, 50 , dtype=np.uint8)
 
         # OpenCV
         expected = cv.add(in1, in2)
@@ -53,16 +55,17 @@ class gapi_core_test(NewOpenCVTests):
         g_out = cv.gapi.add(g_in1, g_in2)
         comp = cv.GComputation(cv.GIn(g_in1, g_in2), cv.GOut(g_out))
 
-        for pkg in pkgs:
+        for pkg_name, pkg in pkgs:
             actual = comp.apply(cv.gin(in1, in2), args=cv.compile_args(pkg))
             # Comparison
-            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF))
-            self.assertEqual(expected.dtype, actual.dtype)
+            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
+            self.assertEqual(expected.dtype, actual.dtype, 'Failed on ' + pkg_name + ' backend')
 
 
     def test_mean(self):
-        sz = (1280, 720, 3)
-        in_mat = np.random.randint(0, 100, sz)
+        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)
@@ -72,15 +75,16 @@ class gapi_core_test(NewOpenCVTests):
         g_out = cv.gapi.mean(g_in)
         comp = cv.GComputation(g_in, g_out)
 
-        for pkg in pkgs:
+        for pkg_name, pkg in pkgs:
             actual = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg))
             # Comparison
-            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF))
+            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
 
 
     def test_split3(self):
-        sz = (1280, 720, 3)
-        in_mat = np.random.randint(0, 100, sz)
+        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.split(in_mat)
@@ -90,19 +94,19 @@ class gapi_core_test(NewOpenCVTests):
         b, g, r = cv.gapi.split3(g_in)
         comp = cv.GComputation(cv.GIn(g_in), cv.GOut(b, g, r))
 
-        for pkg in pkgs:
+        for pkg_name, pkg in pkgs:
             actual = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg))
             # Comparison
             for e, a in zip(expected, actual):
-                self.assertEqual(0.0, cv.norm(e, a, cv.NORM_INF))
-                self.assertEqual(e.dtype, a.dtype)
+                self.assertEqual(0.0, cv.norm(e, a, cv.NORM_INF),
+                                 'Failed on ' + pkg_name + ' backend')
+                self.assertEqual(e.dtype, a.dtype, 'Failed on ' + pkg_name + ' backend')
 
 
     def test_threshold(self):
-        sz = (1280, 720)
-        in_mat = np.random.randint(0, 100, sz).astype(np.uint8)
-        rand_int = np.random.randint(0, 50)
-        maxv = (rand_int, rand_int)
+        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)
+        maxv = (30, 30)
 
         # OpenCV
         expected_thresh, expected_mat = cv.threshold(in_mat, maxv[0], maxv[0], cv.THRESH_TRIANGLE)
@@ -113,12 +117,15 @@ class gapi_core_test(NewOpenCVTests):
         mat, threshold = cv.gapi.threshold(g_in, g_sc, cv.THRESH_TRIANGLE)
         comp = cv.GComputation(cv.GIn(g_in, g_sc), cv.GOut(mat, threshold))
 
-        for pkg in pkgs:
+        for pkg_name, pkg in pkgs:
             actual_mat, actual_thresh = comp.apply(cv.gin(in_mat, maxv), args=cv.compile_args(pkg))
             # Comparison
-            self.assertEqual(0.0, cv.norm(expected_mat, actual_mat, cv.NORM_INF))
-            self.assertEqual(expected_mat.dtype, actual_mat.dtype)
-            self.assertEqual(expected_thresh, actual_thresh[0])
+            self.assertEqual(0.0, cv.norm(expected_mat, actual_mat, cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
+            self.assertEqual(expected_mat.dtype, actual_mat.dtype,
+                             'Failed on ' + pkg_name + ' backend')
+            self.assertEqual(expected_thresh, actual_thresh[0],
+                             'Failed on ' + pkg_name + ' backend')
 
 
 if __name__ == '__main__':
diff --git a/modules/gapi/misc/python/test/test_gapi_imgproc.py b/modules/gapi/misc/python/test/test_gapi_imgproc.py
new file mode 100644 (file)
index 0000000..dd1e397
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+import numpy as np
+import cv2 as cv
+import os
+
+from tests_common import NewOpenCVTests
+
+
+# Plaidml is an optional backend
+pkgs = [
+           ('ocl'    , cv.gapi.core.ocl.kernels()),
+           ('cpu'    , cv.gapi.core.cpu.kernels()),
+           ('fluid'  , cv.gapi.core.fluid.kernels())
+           # ('plaidml', cv.gapi.core.plaidml.kernels())
+       ]
+
+
+class gapi_imgproc_test(NewOpenCVTests):
+
+    def test_good_features_to_track(self):
+        # TODO: Extend to use any type and size here
+        img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
+        in1 = 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(in1, 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 = cv.gapi.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))
+
+        for pkg_name, pkg in pkgs:
+            actual = comp.apply(cv.gin(in1), args=cv.compile_args(pkg))
+            # NB: OpenCV & G-API have different output shapes:
+            # OpenCV - (num_points, 1, 2)
+            # G-API  - (num_points, 2)
+            # Comparison
+            self.assertEqual(0.0, cv.norm(expected.flatten(), actual.flatten(), cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
+
+
+    def test_rgb2gray(self):
+        # TODO: Extend to use any type and size here
+        img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
+        in1 = cv.imread(img_path)
+
+        # OpenCV
+        expected = cv.cvtColor(in1, cv.COLOR_RGB2GRAY)
+
+        # G-API
+        g_in = cv.GMat()
+        g_out = cv.gapi.RGB2Gray(g_in)
+
+        comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out))
+
+        for pkg_name, pkg in pkgs:
+            actual = comp.apply(cv.gin(in1), args=cv.compile_args(pkg))
+            # Comparison
+            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
+
+
+if __name__ == '__main__':
+    NewOpenCVTests.bootstrap()
index 8000496..53304fc 100644 (file)
@@ -2,25 +2,26 @@
 
 import numpy as np
 import cv2 as cv
+import os
 
 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()
-       ]
+         ('ocl'    , cv.gapi.core.ocl.kernels()),
+         ('cpu'    , cv.gapi.core.cpu.kernels()),
+         ('fluid'  , cv.gapi.core.fluid.kernels())
+         # ('plaidml', cv.gapi.core.plaidml.kernels())
+     ]
 
 
 class gapi_sample_pipelines(NewOpenCVTests):
 
     # NB: This test check multiple outputs for operation
     def test_mean_over_r(self):
-        sz = (100, 100, 3)
-        in_mat = np.random.randint(0, 100, sz).astype(np.uint8)
+        img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
+        in_mat = cv.imread(img_path)
 
         # # OpenCV
         _, _, r_ch = cv.split(in_mat)
@@ -32,10 +33,11 @@ class gapi_sample_pipelines(NewOpenCVTests):
         g_out = cv.gapi.mean(r)
         comp = cv.GComputation(g_in, g_out)
 
-        for pkg in pkgs:
+        for pkg_name, pkg in pkgs:
             actual = comp.apply(cv.gin(in_mat), args=cv.compile_args(pkg))
             # Comparison
-            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF))
+            self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF),
+                             'Failed on ' + pkg_name + ' backend')
 
 
 if __name__ == '__main__':
index bf182d9..ae7ef5d 100644 (file)
@@ -47,6 +47,8 @@ class test_gapi_streaming(NewOpenCVTests):
         ccomp.start()
 
         # Assert
+        max_num_frames  = 10
+        proc_num_frames = 0
         while cap.isOpened():
             has_expected, expected = cap.read()
             has_actual,   actual   = ccomp.pull()
@@ -58,6 +60,10 @@ class test_gapi_streaming(NewOpenCVTests):
 
             self.assertEqual(0.0, cv.norm(cv.medianBlur(expected, ksize), actual, cv.NORM_INF))
 
+            proc_num_frames += 1
+            if proc_num_frames == max_num_frames:
+                break;
+
 
     def test_video_split3(self):
         path = self.find_file('cv/video/768x576.avi', [os.environ['OPENCV_TEST_DATA_PATH']])
@@ -76,6 +82,8 @@ class test_gapi_streaming(NewOpenCVTests):
         ccomp.start()
 
         # Assert
+        max_num_frames  = 10
+        proc_num_frames = 0
         while cap.isOpened():
             has_expected, frame = cap.read()
             has_actual,   actual   = ccomp.pull()
@@ -89,6 +97,10 @@ class test_gapi_streaming(NewOpenCVTests):
             for e, a in zip(expected, actual):
                 self.assertEqual(0.0, cv.norm(e, a, cv.NORM_INF))
 
+            proc_num_frames += 1
+            if proc_num_frames == max_num_frames:
+                break;
+
 
     def test_video_add(self):
         sz = (576, 768, 3)
@@ -111,6 +123,8 @@ class test_gapi_streaming(NewOpenCVTests):
         ccomp.start()
 
         # Assert
+        max_num_frames  = 10
+        proc_num_frames = 0
         while cap.isOpened():
             has_expected, frame  = cap.read()
             has_actual,   actual = ccomp.pull()
@@ -123,6 +137,65 @@ class test_gapi_streaming(NewOpenCVTests):
             expected = cv.add(frame, in_mat)
             self.assertEqual(0.0, cv.norm(expected, actual, cv.NORM_INF))
 
+            proc_num_frames += 1
+            if proc_num_frames == max_num_frames:
+                break;
+
+
+    def test_video_good_features_to_track(self):
+        path = self.find_file('cv/video/768x576.avi', [os.environ['OPENCV_TEST_DATA_PATH']])
+
+        # 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
+        cap = cv.VideoCapture(path)
+
+        # G-API
+        g_in = cv.GMat()
+        g_gray = cv.gapi.RGB2Gray(g_in)
+        g_out = cv.gapi.goodFeaturesToTrack(g_gray, max_corners, quality_lvl,
+                                            min_distance, mask, block_sz, use_harris_detector, k)
+
+        c = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out))
+
+        ccomp = c.compileStreaming()
+        source = cv.gapi.wip.make_capture_src(path)
+        ccomp.setSource(source)
+        ccomp.start()
+
+        # Assert
+        max_num_frames  = 10
+        proc_num_frames = 0
+        while cap.isOpened():
+            has_expected, frame  = cap.read()
+            has_actual,   actual = ccomp.pull()
+
+            self.assertEqual(has_expected, has_actual)
+
+            if not has_actual:
+                break
+
+            # OpenCV
+            frame = cv.cvtColor(frame, cv.COLOR_RGB2GRAY)
+            expected = cv.goodFeaturesToTrack(frame, max_corners, quality_lvl,
+                                              min_distance, mask=mask,
+                                              blockSize=block_sz, useHarrisDetector=use_harris_detector, k=k)
+            for e, a in zip(expected, actual):
+                # NB: OpenCV & G-API have different output shapes:
+                # OpenCV - (num_points, 1, 2)
+                # G-API  - (num_points, 2)
+                self.assertEqual(0.0, cv.norm(e.flatten(), a.flatten(), cv.NORM_INF))
+
+            proc_num_frames += 1
+            if proc_num_frames == max_num_frames:
+                break;
 
 
 if __name__ == '__main__':
index 9ff0273..5668cdd 100644 (file)
@@ -9,6 +9,7 @@
 #include <algorithm> // remove_if
 #include <cctype>    // isspace (non-locale version)
 #include <ade/util/algorithm.hpp>
+#include <ade/util/zip_range.hpp>   // util::indexed
 
 #include "logger.hpp" // GAPI_LOG
 
@@ -21,6 +22,7 @@
 
 #include "compiler/gmodelbuilder.hpp"
 #include "compiler/gcompiler.hpp"
+#include "compiler/gcompiled_priv.hpp"
 
 // cv::GComputation private implementation /////////////////////////////////////
 // <none>
@@ -174,28 +176,42 @@ cv::GRunArgs cv::GComputation::apply(GRunArgs &&ins, GCompileArgs &&args)
 {
     recompile(descr_of(ins), std::move(args));
 
-    const auto& out_metas = m_priv->m_lastCompiled.outMetas();
+    const auto& out_info = m_priv->m_lastCompiled.priv().outInfo();
+
     GRunArgs run_args;
     GRunArgsP outs;
-    run_args.reserve(out_metas.size());
-    outs.reserve(out_metas.size());
+    run_args.reserve(out_info.size());
+    outs.reserve(out_info.size());
 
-    for (auto&& meta : out_metas)
+    for (auto&& info : out_info)
     {
-        switch (meta.index())
+        switch (info.shape)
         {
-            case cv::GMetaArg::index_of<cv::GMatDesc>():
+            case cv::GShape::GMAT:
             {
                 run_args.emplace_back(cv::Mat{});
                 outs.emplace_back(&cv::util::get<cv::Mat>(run_args.back()));
                 break;
             }
-            case cv::GMetaArg::index_of<cv::GScalarDesc>():
+            case cv::GShape::GSCALAR:
             {
                 run_args.emplace_back(cv::Scalar{});
                 outs.emplace_back(&cv::util::get<cv::Scalar>(run_args.back()));
                 break;
             }
+            case cv::GShape::GARRAY:
+            {
+                switch (info.kind)
+                {
+                    case cv::detail::OpaqueKind::CV_POINT2F:
+                        run_args.emplace_back(cv::detail::VectorRef{std::vector<cv::Point2f>{}});
+                        outs.emplace_back(cv::util::get<cv::detail::VectorRef>(run_args.back()));
+                        break;
+                    default:
+                        util::throw_error(std::logic_error("Unsupported kind for GArray"));
+                }
+                break;
+            }
             default:
                 util::throw_error(std::logic_error("Only cv::GMat and cv::GScalar are supported for python output"));
         }
index 592c03c..8c2313b 100644 (file)
@@ -152,6 +152,13 @@ IIStream& operator>> (IIStream& is, cv::Point& pt) {
     return is >> pt.x >> pt.y;
 }
 
+IOStream& operator<< (IOStream& os, const cv::Point2f &pt) {
+    return os << pt.x << pt.y;
+}
+IIStream& operator>> (IIStream& is, cv::Point2f& pt) {
+    return is >> pt.x >> pt.y;
+}
+
 IOStream& operator<< (IOStream& os, const cv::Size &sz) {
     return os << sz.width << sz.height;
 }
@@ -516,17 +523,17 @@ IOStream& operator<< (IOStream& os, const cv::GArg &arg) {
         GAPI_Assert(arg.kind == cv::detail::ArgKind::OPAQUE_VAL);
         GAPI_Assert(arg.opaque_kind != cv::detail::OpaqueKind::CV_UNKNOWN);
         switch (arg.opaque_kind) {
-        case cv::detail::OpaqueKind::CV_BOOL:   os << arg.get<bool>();         break;
-        case cv::detail::OpaqueKind::CV_INT:    os << arg.get<int>();          break;
-        case cv::detail::OpaqueKind::CV_UINT64: os << arg.get<uint64_t>();     break;
-        case cv::detail::OpaqueKind::CV_DOUBLE: os << arg.get<double>();       break;
-        case cv::detail::OpaqueKind::CV_FLOAT:  os << arg.get<float>();        break;
-        case cv::detail::OpaqueKind::CV_STRING: os << arg.get<std::string>();  break;
-        case cv::detail::OpaqueKind::CV_POINT:  os << arg.get<cv::Point>();    break;
-        case cv::detail::OpaqueKind::CV_SIZE:   os << arg.get<cv::Size>();     break;
-        case cv::detail::OpaqueKind::CV_RECT:   os << arg.get<cv::Rect>();     break;
-        case cv::detail::OpaqueKind::CV_SCALAR: os << arg.get<cv::Scalar>();   break;
-        case cv::detail::OpaqueKind::CV_MAT:    os << arg.get<cv::Mat>();      break;
+        case cv::detail::OpaqueKind::CV_BOOL:    os << arg.get<bool>();         break;
+        case cv::detail::OpaqueKind::CV_INT:     os << arg.get<int>();          break;
+        case cv::detail::OpaqueKind::CV_UINT64:  os << arg.get<uint64_t>();     break;
+        case cv::detail::OpaqueKind::CV_DOUBLE:  os << arg.get<double>();       break;
+        case cv::detail::OpaqueKind::CV_FLOAT:   os << arg.get<float>();        break;
+        case cv::detail::OpaqueKind::CV_STRING:  os << arg.get<std::string>();  break;
+        case cv::detail::OpaqueKind::CV_POINT:   os << arg.get<cv::Point>();    break;
+        case cv::detail::OpaqueKind::CV_SIZE:    os << arg.get<cv::Size>();     break;
+        case cv::detail::OpaqueKind::CV_RECT:    os << arg.get<cv::Rect>();     break;
+        case cv::detail::OpaqueKind::CV_SCALAR:  os << arg.get<cv::Scalar>();   break;
+        case cv::detail::OpaqueKind::CV_MAT:     os << arg.get<cv::Mat>();      break;
         default: GAPI_Assert(false && "GArg: Unsupported (unknown?) opaque value type");
         }
     }
@@ -550,17 +557,18 @@ IIStream& operator>> (IIStream& is, cv::GArg &arg) {
         switch (arg.opaque_kind) {
 #define HANDLE_CASE(E,T) case cv::detail::OpaqueKind::CV_##E:           \
             { T t{}; is >> t; arg = (cv::GArg(t)); } break
-            HANDLE_CASE(BOOL   , bool);
-            HANDLE_CASE(INT    , int);
-            HANDLE_CASE(UINT64 , uint64_t);
-            HANDLE_CASE(DOUBLE , double);
-            HANDLE_CASE(FLOAT  , float);
-            HANDLE_CASE(STRING , std::string);
-            HANDLE_CASE(POINT  , cv::Point);
-            HANDLE_CASE(SIZE   , cv::Size);
-            HANDLE_CASE(RECT   , cv::Rect);
-            HANDLE_CASE(SCALAR , cv::Scalar);
-            HANDLE_CASE(MAT    , cv::Mat);
+            HANDLE_CASE(BOOL    , bool);
+            HANDLE_CASE(INT     , int);
+            HANDLE_CASE(UINT64  , uint64_t);
+            HANDLE_CASE(DOUBLE  , double);
+            HANDLE_CASE(FLOAT   , float);
+            HANDLE_CASE(STRING  , std::string);
+            HANDLE_CASE(POINT   , cv::Point);
+            HANDLE_CASE(POINT2F , cv::Point2f);
+            HANDLE_CASE(SIZE    , cv::Size);
+            HANDLE_CASE(RECT    , cv::Rect);
+            HANDLE_CASE(SCALAR  , cv::Scalar);
+            HANDLE_CASE(MAT     , cv::Mat);
 #undef HANDLE_CASE
         default: GAPI_Assert(false && "GArg: Unsupported (unknown?) opaque value type");
         }
index f21bfc8..b08b1f9 100644 (file)
@@ -38,6 +38,10 @@ class GAPI_EXPORTS GCompiled::Priv
     GMetaArgs  m_outMetas; // inferred by compiler
     std::unique_ptr<cv::gimpl::GExecutor> m_exec;
 
+    // NB: Used by python wrapper to clarify input/output types
+    GTypesInfo m_out_info;
+    GTypesInfo m_in_info;
+
     void checkArgs(const cv::gimpl::GRuntimeArgs &args) const;
 
 public:
@@ -55,6 +59,12 @@ public:
     const GMetaArgs& outMetas() const;
 
     const cv::gimpl::GModel::Graph& model() const;
+
+    void setOutInfo(const GTypesInfo& info) { m_out_info = std::move(info); }
+    const GTypesInfo& outInfo() const { return m_out_info; }
+
+    void setInInfo(const GTypesInfo& info) { m_in_info = std::move(info); }
+    const GTypesInfo& inInfo() const { return m_in_info; }
 };
 
 }
index f6fa398..4d050db 100644 (file)
@@ -417,6 +417,19 @@ void cv::gimpl::GCompiler::compileIslands(ade::Graph &g, const cv::GCompileArgs
     GIslandModel::compileIslands(gim, g, args);
 }
 
+static cv::GTypesInfo collectInfo(const cv::gimpl::GModel::ConstGraph& g,
+                                  const std::vector<ade::NodeHandle>& nhs) {
+    cv::GTypesInfo info;
+    info.reserve(nhs.size());
+
+    ade::util::transform(nhs, std::back_inserter(info), [&g](const ade::NodeHandle& nh) {
+        const auto& data = g.metadata(nh).get<cv::gimpl::Data>();
+        return cv::GTypeInfo{data.shape, data.kind};
+    });
+
+    return info;
+}
+
 cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg)
 {
     // This is the final compilation step. Here:
@@ -435,6 +448,8 @@ cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg)
     //     an execution plan for it (backend-specific execution)
     // ...before call to produceCompiled();
 
+    GModel::ConstGraph cgr(*pg);
+
     const auto &outMetas = GModel::ConstGraph(*pg).metadata()
         .get<OutputMeta>().outMeta;
     std::unique_ptr<GExecutor> pE(new GExecutor(std::move(pg)));
@@ -443,6 +458,14 @@ cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg)
 
     GCompiled compiled;
     compiled.priv().setup(m_metas, outMetas, std::move(pE));
+
+    // NB: Need to store input/output GTypeInfo to allocate output arrays for python bindings
+    auto out_meta = collectInfo(cgr, cgr.metadata().get<cv::gimpl::Protocol>().out_nhs);
+    auto in_meta  = collectInfo(cgr, cgr.metadata().get<cv::gimpl::Protocol>().in_nhs);
+
+    compiled.priv().setOutInfo(std::move(out_meta));
+    compiled.priv().setInInfo(std::move(in_meta));
+
     return compiled;
 }
 
@@ -458,13 +481,15 @@ cv::GStreamingCompiled cv::gimpl::GCompiler::produceStreamingCompiled(GPtr &&pg)
         outMetas = GModel::ConstGraph(*pg).metadata().get<OutputMeta>().outMeta;
     }
 
-    auto out_desc = GModel::ConstGraph(*pg).metadata().get<cv::gimpl::Protocol>().outputs;
-    GShapes out_shapes;
-    for (auto&& desc : out_desc)
-    {
-        out_shapes.push_back(desc.shape);
-    }
-    compiled.priv().setOutShapes(std::move(out_shapes));
+
+    GModel::ConstGraph cgr(*pg);
+
+    // NB: Need to store input/output GTypeInfo to allocate output arrays for python bindings
+    auto out_meta = collectInfo(cgr, cgr.metadata().get<cv::gimpl::Protocol>().out_nhs);
+    auto in_meta  = collectInfo(cgr, cgr.metadata().get<cv::gimpl::Protocol>().in_nhs);
+
+    compiled.priv().setOutInfo(std::move(out_meta));
+    compiled.priv().setInInfo(std::move(in_meta));
 
     std::unique_ptr<GStreamingExecutor> pE(new GStreamingExecutor(std::move(pg),
                                                                   m_args));
index eb06f3f..fa736d5 100644 (file)
@@ -8,6 +8,7 @@
 #include "precomp.hpp"
 
 #include <ade/graph.hpp>
+#include <ade/util/zip_range.hpp>   // util::indexed
 
 #include <opencv2/gapi/gproto.hpp> // can_describe
 #include <opencv2/gapi/gcompiled.hpp>
@@ -121,13 +122,13 @@ std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
     // FIXME: Why it is not @ priv??
     GRunArgs run_args;
     GRunArgsP outs;
-    const auto& out_shapes = m_priv->outShapes();
-    run_args.reserve(out_shapes.size());
-    outs.reserve(out_shapes.size());
+    const auto& out_info = m_priv->outInfo();
+    run_args.reserve(out_info.size());
+    outs.reserve(out_info.size());
 
-    for (auto&& shape : out_shapes)
+    for (auto&& info : out_info)
     {
-        switch (shape)
+        switch (info.shape)
         {
             case cv::GShape::GMAT:
             {
@@ -141,6 +142,19 @@ std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
                 outs.emplace_back(&cv::util::get<cv::Scalar>(run_args.back()));
                 break;
             }
+            case cv::GShape::GARRAY:
+            {
+                switch (info.kind)
+                {
+                    case cv::detail::OpaqueKind::CV_POINT2F:
+                        run_args.emplace_back(cv::detail::VectorRef{std::vector<cv::Point2f>{}});
+                        outs.emplace_back(cv::util::get<cv::detail::VectorRef>(run_args.back()));
+                        break;
+                    default:
+                        util::throw_error(std::logic_error("Unsupported kind for GArray"));
+                }
+                break;
+            }
             default:
                 util::throw_error(std::logic_error("Only cv::GMat and cv::GScalar are supported for python output"));
         }
index 2f195ca..be0869e 100644 (file)
@@ -27,7 +27,10 @@ class GAPI_EXPORTS GStreamingCompiled::Priv
     GMetaArgs  m_metas;    // passed by user
     GMetaArgs  m_outMetas; // inferred by compiler
     std::unique_ptr<cv::gimpl::GStreamingExecutor> m_exec;
-    GShapes m_out_shapes;
+
+    // NB: Used by python wrapper to clarify input/output types
+    GTypesInfo m_out_info;
+    GTypesInfo m_in_info;
 
 public:
     void setup(const GMetaArgs &metaArgs,
@@ -48,10 +51,11 @@ public:
 
     bool running() const;
 
-    // NB: std::tuple<bool, cv::GRunArgs> pull() creates GRunArgs for outputs,
-    // so need to know out shapes to create corresponding GRunArg
-    void setOutShapes(GShapes shapes) { m_out_shapes = std::move(shapes); }
-    const GShapes& outShapes() const { return m_out_shapes; }
+    void setOutInfo(const GTypesInfo& info) { m_out_info = std::move(info); }
+    const GTypesInfo& outInfo() const { return m_out_info; }
+
+    void setInInfo(const GTypesInfo& info) { m_in_info = std::move(info); }
+    const GTypesInfo& inInfo() const { return m_in_info; }
 };
 
 } // namespace cv
index b40bb1d..602a931 100644 (file)
@@ -27,6 +27,22 @@ TEST(Point, CreateWithParams)
     EXPECT_EQ(2, p.y);
 }
 
+TEST(Point2f, CreateEmpty)
+{
+    cv::gapi::own::Point2f p;
+
+    EXPECT_EQ(0.f, p.x);
+    EXPECT_EQ(0.f, p.y);
+}
+
+TEST(Point2f, CreateWithParams)
+{
+    cv::gapi::own::Point2f p = {3.14f, 2.71f};
+
+    EXPECT_EQ(3.14f, p.x);
+    EXPECT_EQ(2.71f, p.y);
+}
+
 TEST(Rect, CreateEmpty)
 {
     cv::gapi::own::Rect r;