Merge pull request #14648 from smirnov-alexey:as/gapi_transform
authorAlexey Smirnov <alexey.smirnov@intel.com>
Mon, 17 Jun 2019 13:26:28 +0000 (16:26 +0300)
committerAlexander Alekhin <alexander.a.alekhin@gmail.com>
Mon, 17 Jun 2019 13:26:28 +0000 (16:26 +0300)
* Introduce GAPI_TRANSFORM initial interface

Comes along with simple tests and kernel package changes

* Fix documentation and adjust combine() function

* Fix stuff after rebasing on master

* Remove redundant functionality

* Refactoring according to review feedback provided

* Fixes according to review feedback

* Reconsider transformations return and fix a warning

* Fixes from code review

* Add a new simple test

* Cleanup, added tests on GScalar, GMatP, GArray

modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp
modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp
modules/gapi/include/opencv2/gapi/gcommon.hpp
modules/gapi/include/opencv2/gapi/gcompoundkernel.hpp
modules/gapi/include/opencv2/gapi/gkernel.hpp
modules/gapi/include/opencv2/gapi/gtransform.hpp [new file with mode: 0644]
modules/gapi/include/opencv2/gapi/ocl/goclkernel.hpp
modules/gapi/include/opencv2/gapi/util/util.hpp
modules/gapi/src/api/gkernel.cpp
modules/gapi/src/compiler/passes/kernels.cpp
modules/gapi/test/gapi_transform_tests.cpp [new file with mode: 0644]

index 9597942..4205776 100644 (file)
@@ -2,7 +2,7 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #ifndef OPENCV_GAPI_GCPUKERNEL_HPP
@@ -19,6 +19,7 @@
 #include <opencv2/gapi/garg.hpp>
 #include <opencv2/gapi/own/convert.hpp> //to_ocv
 #include <opencv2/gapi/util/compiler_hints.hpp> //suppress_unused_warning
+#include <opencv2/gapi/util/util.hpp>
 
 // FIXME: namespace scheme for backends?
 namespace cv {
@@ -258,7 +259,8 @@ struct OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> >
 } // namespace detail
 
 template<class Impl, class K>
-class GCPUKernelImpl: public detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>
+class GCPUKernelImpl: public cv::detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>,
+                      public cv::detail::KernelTag
 {
     using P = detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
 
index b6adf9e..1d8bfd8 100644 (file)
@@ -2,7 +2,7 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #ifndef OPENCV_GAPI_FLUID_KERNEL_HPP
@@ -275,7 +275,7 @@ struct FluidCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>, UseScratch
 
 
 template<class Impl, class K, bool UseScratch>
-class GFluidKernelImpl
+class GFluidKernelImpl : public cv::detail::KernelTag
 {
     static const int LPI = 1;
     static const auto Kind = GFluidKernel::Kind::Filter;
index e97e10e..dac640a 100644 (file)
@@ -29,6 +29,12 @@ namespace detail
     {
         static const char* tag() { return ""; };
     };
+
+    // These structures are tags which separate kernels and transformations
+    struct KernelTag
+    {};
+    struct TransformTag
+    {};
 }
 
 // This definition is here because it is reused by both public(?) and internal
index f4c0234..7d960cd 100644 (file)
@@ -2,7 +2,7 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #ifndef OPENCV_GAPI_GCOMPOUNDKERNEL_HPP
@@ -65,22 +65,6 @@ template<typename U> struct get_compound_in<cv::GArray<U>>
     }
 };
 
-// Kernel may return one object(GMat, GScalar) or a tuple of objects.
-// This helper is needed to cast return value to the same form(tuple)
-template<typename>
-struct tuple_wrap_helper;
-
-template<typename T> struct tuple_wrap_helper
-{
-    static std::tuple<T> get(T&& obj) { return std::make_tuple(std::move(obj)); }
-};
-
-template<typename... Objs>
-struct tuple_wrap_helper<std::tuple<Objs...>>
-{
-    static std::tuple<Objs...> get(std::tuple<Objs...>&& objs) { return std::forward<std::tuple<Objs...>>(objs); }
-};
-
 template<typename, typename, typename>
 struct GCompoundCallHelper;
 
@@ -104,7 +88,8 @@ struct GCompoundCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> >
 };
 
 template<class Impl, class K>
-class GCompoundKernelImpl: public cv::detail::GCompoundCallHelper<Impl, typename K::InArgs, typename K::OutArgs>
+class GCompoundKernelImpl: public cv::detail::GCompoundCallHelper<Impl, typename K::InArgs, typename K::OutArgs>,
+                           public cv::detail::KernelTag
 {
     using P = cv::detail::GCompoundCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
 
index db9ac76..b8d1dbb 100644 (file)
@@ -2,7 +2,7 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #ifndef OPENCV_GAPI_GKERNEL_HPP
@@ -22,6 +22,7 @@
 #include <opencv2/gapi/gmetaarg.hpp>  // GMetaArg
 #include <opencv2/gapi/gtype_traits.hpp> // GTypeTraits
 #include <opencv2/gapi/util/compiler_hints.hpp> //suppress_unused_warning
+#include <opencv2/gapi/gtransform.hpp>
 
 namespace cv {
 
@@ -170,12 +171,12 @@ namespace detail
 // GKernelType and GKernelTypeM are base classes which implement typed ::on()
 // method based on kernel signature. GKernelTypeM stands for multiple-return-value kernels
 //
-// G_TYPED_KERNEL and G_TYPED_KERNEK_M macros inherit user classes from GKernelType and
+// G_TYPED_KERNEL and G_TYPED_KERNEL_M macros inherit user classes from GKernelType and
 // GKernelTypeM respectively.
 
 template<typename K, typename... R, typename... Args>
 class GKernelTypeM<K, std::function<std::tuple<R...>(Args...)> >:
-        public detail::MetaHelper<K, std::tuple<Args...>, std::tuple<R...> >
+        public detail::MetaHelper<K, std::tuple<Args...>, std::tuple<R...>>
 {
     template<int... IIs>
     static std::tuple<R...> yield(cv::GCall &call, detail::Seq<IIs...>)
@@ -199,7 +200,7 @@ template<typename, typename> class GKernelType;
 
 template<typename K, typename R, typename... Args>
 class GKernelType<K, std::function<R(Args...)> >:
-        public detail::MetaHelper<K, std::tuple<Args...>, R >
+        public detail::MetaHelper<K, std::tuple<Args...>, R>
 {
 public:
     using InArgs  = std::tuple<Args...>;
@@ -240,7 +241,7 @@ public:
 #define G_TYPED_KERNEL_M(Class, API, Id)                                    \
     G_ID_HELPER_BODY(Class, Id)                                             \
     struct Class final: public cv::GKernelTypeM<Class, std::function API >, \
-                        public detail::G_ID_HELPER_CLASS(Class)             \
+                        public detail::G_ID_HELPER_CLASS(Class)
 // {body} is to be defined by user
 
 namespace cv
@@ -296,12 +297,13 @@ namespace gapi {
     // FIXME: Hide implementation
     /**
      * @brief A container class for heterogeneous kernel
-     * implementation collections.
+     * implementation collections and graph transformations.
      *
      * GKernelPackage is a special container class which stores kernel
-     * _implementations_. Objects of this class are created and passed
-     * to cv::GComputation::compile() to specify which kernels to use
-     * in the compiled graph. GKernelPackage may contain kernels of
+     * _implementations_ and graph _transformations_. Objects of this class
+     * are created and passed to cv::GComputation::compile() to specify
+     * which kernels to use and which transformations to apply in the
+     * compiled graph. GKernelPackage may contain kernels of
      * different backends, e.g. be heterogeneous.
      *
      * The most easy way to create a kernel package is to use function
@@ -313,7 +315,8 @@ namespace gapi {
      * with an empty package (created with the default constructor)
      * and then by populating it with kernels via call to
      * GKernelPackage::include(). Note this method is also a template
-     * one since G-API kernel implementations are _types_, not objects.
+     * one since G-API kernel and transformation implementations are _types_,
+     * not objects.
      *
      * Finally, two kernel packages can be combined into a new one
      * with function cv::gapi::combine().
@@ -327,6 +330,9 @@ namespace gapi {
         /// @private
         M m_id_kernels;
 
+        /// @private
+        std::vector<GTransform> m_transformations;
+
     protected:
         /// @private
         // Check if package contains ANY implementation of a kernel API
@@ -337,26 +343,61 @@ namespace gapi {
         // Remove ALL implementations of the given API (identified by ID)
         void removeAPI(const std::string &id);
 
+        /// @private
+        // Partial include() specialization for kernels
+        template <typename KImpl>
+        typename std::enable_if<(std::is_base_of<detail::KernelTag, KImpl>::value), void>::type
+        includeHelper()
+        {
+            auto backend     = KImpl::backend();
+            auto kernel_id   = KImpl::API::id();
+            auto kernel_impl = GKernelImpl{KImpl::kernel()};
+            removeAPI(kernel_id);
+
+            m_id_kernels[kernel_id] = std::make_pair(backend, kernel_impl);
+        }
+
+        /// @private
+        // Partial include() specialization for transformations
+        template <typename TImpl>
+        typename std::enable_if<(std::is_base_of<detail::TransformTag, TImpl>::value), void>::type
+        includeHelper()
+        {
+            m_transformations.emplace_back(TImpl::transformation());
+        }
+
     public:
         /**
-         * @brief Returns total number of kernels in the package
-         * (across all backends included)
+         * @brief Returns total number of kernels
+         * in the package (across all backends included)
          *
          * @return a number of kernels in the package
          */
         std::size_t size() const;
 
         /**
+         * @brief Returns vector of transformations included in the package
+         *
+         * @return vector of transformations included in the package
+         */
+        const std::vector<GTransform>& get_transformations() const;
+
+        /**
          * @brief Test if a particular kernel _implementation_ KImpl is
          * included in this kernel package.
          *
          * @sa includesAPI()
          *
+         * @note cannot be applied to transformations
+         *
          * @return true if there is such kernel, false otherwise.
          */
         template<typename KImpl>
         bool includes() const
         {
+            static_assert(std::is_base_of<detail::KernelTag, KImpl>::value,
+                          "includes() can be applied to kernels only");
+
             auto kernel_it = m_id_kernels.find(KImpl::API::id());
             return kernel_it != m_id_kernels.end() &&
                    kernel_it->second.first == KImpl::backend();
@@ -417,17 +458,13 @@ namespace gapi {
 
         // FIXME: No overwrites allowed?
         /**
-         * @brief Put a new kernel implementation KImpl into package.
+         * @brief Put a new kernel implementation or a new transformation
+         * KImpl into the package.
          */
         template<typename KImpl>
         void include()
         {
-            auto backend     = KImpl::backend();
-            auto kernel_id   = KImpl::API::id();
-            auto kernel_impl = GKernelImpl{KImpl::kernel()};
-            removeAPI(kernel_id);
-
-            m_id_kernels[kernel_id] = std::make_pair(backend, kernel_impl);
+            includeHelper<KImpl>();
         }
 
         /**
@@ -452,15 +489,15 @@ namespace gapi {
 
     /**
      * @brief Create a kernel package object containing kernels
-     * specified in variadic template argument.
+     * and transformations specified in variadic template argument.
      *
-     * In G-API, kernel implementations are _types_. Every backend has
-     * its own kernel API (like GAPI_OCV_KERNEL() and
+     * In G-API, kernel implementations and transformations are _types_.
+     * Every backend has its own kernel API (like GAPI_OCV_KERNEL() and
      * GAPI_FLUID_KERNEL()) but all of that APIs define a new type for
      * each kernel implementation.
      *
      * Use this function to pass kernel implementations (defined in
-     * either way) to the system. Example:
+     * either way) and transformations to the system. Example:
      *
      * @snippet modules/gapi/samples/api_ref_snippets.cpp kernels_snippet
      *
@@ -470,6 +507,10 @@ namespace gapi {
      */
     template<typename... KK> GKernelPackage kernels()
     {
+        // FIXME: currently there is no check that transformations' signatures are unique
+        // and won't be any intersection in graph compilation stage
+        static_assert(detail::all_unique<typename KK::API...>::value, "Kernels API must be unique");
+
         GKernelPackage pkg;
 
         // For those who wonder - below is a trick to call a number of
@@ -478,8 +519,6 @@ namespace gapi {
         // Just note that `f(),a` always equals to `a` (with f() called!)
         // and parentheses are used to hide function call in the expanded sequence.
         // Leading 0 helps to handle case when KK is an empty list (kernels<>()).
-
-        static_assert(detail::all_unique<typename KK::API...>::value, "Kernels API must be unique");
         int unused[] = { 0, (pkg.include<KK>(), 0)... };
         cv::util::suppress_unused_warning(unused);
         return pkg;
diff --git a/modules/gapi/include/opencv2/gapi/gtransform.hpp b/modules/gapi/include/opencv2/gapi/gtransform.hpp
new file mode 100644 (file)
index 0000000..e8ce161
--- /dev/null
@@ -0,0 +1,98 @@
+// 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_GTRANSFORM_HPP
+#define OPENCV_GAPI_GTRANSFORM_HPP
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <opencv2/gapi/gcommon.hpp>
+#include <opencv2/gapi/util/util.hpp>
+#include <opencv2/gapi/garg.hpp>
+#include <opencv2/gapi/gtype_traits.hpp>
+#include <opencv2/gapi/util/compiler_hints.hpp>
+
+namespace cv
+{
+
+struct GAPI_EXPORTS GTransform
+{
+    using F = std::function<GArgs(const GArgs &)>;
+
+    std::string description;
+    F pattern;
+    F substitute;
+
+    GTransform(const std::string& d, const F &p, const F &s) : description(d), pattern(p), substitute(s){};
+};
+
+namespace detail
+{
+
+template <typename, typename, typename>
+struct TransHelper;
+
+template <typename K, typename... Ins, typename Out>
+struct TransHelper<K, std::tuple<Ins...>, Out>
+{
+    template <typename Callable, int... IIs, int... OIs>
+    static GArgs invoke(Callable f, const GArgs &in_args, Seq<IIs...>, Seq<OIs...>)
+    {
+        const auto r = tuple_wrap_helper<Out>::get(f(in_args.at(IIs).template get<Ins>()...));
+        return GArgs{GArg(std::get<OIs>(r))...};
+    }
+
+    static GArgs get_pattern(const GArgs &in_args)
+    {
+        return invoke(K::pattern, in_args, typename MkSeq<sizeof...(Ins)>::type(),
+                      typename MkSeq<std::tuple_size<typename tuple_wrap_helper<Out>::type>::value>::type());
+    }
+    static GArgs get_substitute(const GArgs &in_args)
+    {
+        return invoke(K::substitute, in_args, typename MkSeq<sizeof...(Ins)>::type(),
+                      typename MkSeq<std::tuple_size<typename tuple_wrap_helper<Out>::type>::value>::type());
+    }
+};
+} // namespace detail
+
+template <typename, typename>
+class GTransformImpl;
+
+template <typename K, typename R, typename... Args>
+class GTransformImpl<K, std::function<R(Args...)>> : public cv::detail::TransHelper<K, std::tuple<Args...>, R>,
+                                                     public cv::detail::TransformTag
+{
+public:
+    // FIXME: currently there is no check that transformations' signatures are unique
+    // and won't be any intersection in graph compilation stage
+    using API = K;
+
+    static GTransform transformation()
+    {
+        return GTransform(K::descr(), &K::get_pattern, &K::get_substitute);
+    }
+};
+} // namespace cv
+
+#define G_DESCR_HELPER_CLASS(Class) Class##DescrHelper
+
+#define G_DESCR_HELPER_BODY(Class, Descr)                       \
+    namespace detail                                            \
+    {                                                           \
+    struct G_DESCR_HELPER_CLASS(Class)                          \
+    {                                                           \
+        static constexpr const char *descr() { return Descr; }; \
+    };                                                          \
+    }
+
+#define GAPI_TRANSFORM(Class, API, Descr)                                     \
+    G_DESCR_HELPER_BODY(Class, Descr)                                         \
+    struct Class final : public cv::GTransformImpl<Class, std::function API>, \
+                         public detail::G_DESCR_HELPER_CLASS(Class)
+
+#endif // OPENCV_GAPI_GTRANSFORM_HPP
index ea2cda0..6927492 100644 (file)
@@ -2,7 +2,7 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #ifndef OPENCV_GAPI_GOCLKERNEL_HPP
@@ -226,7 +226,8 @@ struct OCLCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...> >
 } // namespace detail
 
 template<class Impl, class K>
-class GOCLKernelImpl: public detail::OCLCallHelper<Impl, typename K::InArgs, typename K::OutArgs>
+class GOCLKernelImpl: public cv::detail::OCLCallHelper<Impl, typename K::InArgs, typename K::OutArgs>,
+                      public cv::detail::KernelTag
 {
     using P = detail::OCLCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
 
index a62c0c5..f68b738 100644 (file)
@@ -2,13 +2,13 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #ifndef OPENCV_GAPI_UTIL_HPP
 #define OPENCV_GAPI_UTIL_HPP
 
-#include <utility> // std::tuple
+#include <tuple>
 
 // \cond HIDDEN_SYMBOLS
 // This header file contains some generic utility functions which are
@@ -97,6 +97,22 @@ namespace detail
     template <typename T1, typename... Ts>
     struct all_unique<T1, Ts...> : std::integral_constant<bool, !contains<T1, Ts...>::value &&
                                                                  all_unique<Ts...>::value> {};
+
+    template<typename>
+    struct tuple_wrap_helper;
+
+    template<typename T> struct tuple_wrap_helper
+    {
+        using type = std::tuple<T>;
+        static type get(T&& obj) { return std::make_tuple(std::move(obj)); }
+    };
+
+    template<typename... Objs>
+    struct tuple_wrap_helper<std::tuple<Objs...>>
+    {
+        using type = std::tuple<Objs...>;
+        static type get(std::tuple<Objs...>&& objs) { return std::forward<std::tuple<Objs...>>(objs); }
+    };
 } // namespace detail
 } // namespace cv
 
index ef682e8..6993e95 100644 (file)
@@ -2,7 +2,7 @@
 // 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
+// Copyright (C) 2018-2019 Intel Corporation
 
 
 #include "precomp.hpp"
@@ -50,6 +50,11 @@ std::size_t cv::gapi::GKernelPackage::size() const
     return m_id_kernels.size();
 }
 
+const std::vector<cv::GTransform> &cv::gapi::GKernelPackage::get_transformations() const
+{
+    return m_transformations;
+}
+
 cv::gapi::GKernelPackage cv::gapi::combine(const GKernelPackage  &lhs,
                                            const GKernelPackage  &rhs)
 {
@@ -66,6 +71,9 @@ cv::gapi::GKernelPackage cv::gapi::combine(const GKernelPackage  &lhs,
                 result.m_id_kernels.emplace(kernel.first, kernel.second);
             }
         }
+        for (const auto &transforms : lhs.m_transformations){
+            result.m_transformations.push_back(transforms);
+        }
         return result;
 }
 
index e00deaa..f5f0098 100644 (file)
@@ -35,7 +35,7 @@ namespace
     // 1. Get GCompoundKernel implementation
     // 2. Create GCompoundContext
     // 3. Run GCompoundKernel with GCompoundContext
-    // 4. Build subgraph from imputs/outputs GCompoundKernel
+    // 4. Build subgraph from inputs/outputs GCompoundKernel
     // 5. Replace compound node to subgraph
 
     void expand(ade::Graph& g, ade::NodeHandle nh, const ImplInfo& impl_info)
diff --git a/modules/gapi/test/gapi_transform_tests.cpp b/modules/gapi/test/gapi_transform_tests.cpp
new file mode 100644 (file)
index 0000000..f946499
--- /dev/null
@@ -0,0 +1,183 @@
+// 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 <tuple>
+
+#include "test_precomp.hpp"
+#include "opencv2/gapi/gtransform.hpp"
+
+namespace opencv_test
+{
+
+namespace
+{
+using GMat = cv::GMat;
+using GMat2 = std::tuple<GMat, GMat>;
+using GMat3 = std::tuple<GMat, GMat, GMat>;
+using GScalar = cv::GScalar;
+template <typename T> using GArray = cv::GArray<T>;
+
+GAPI_TRANSFORM(gmat_in_gmat_out, <GMat(GMat)>, "gmat_in_gmat_out")
+{
+    static GMat pattern(GMat) { return {}; }
+    static GMat substitute(GMat) { return {}; }
+};
+
+GAPI_TRANSFORM(gmat2_in_gmat_out, <GMat(GMat, GMat)>, "gmat2_in_gmat_out")
+{
+    static GMat pattern(GMat, GMat) { return {}; }
+    static GMat substitute(GMat, GMat) { return {}; }
+};
+
+GAPI_TRANSFORM(gmat2_in_gmat3_out, <GMat3(GMat, GMat)>, "gmat2_in_gmat3_out")
+{
+    static GMat3 pattern(GMat, GMat) { return {}; }
+    static GMat3 substitute(GMat, GMat) { return {}; }
+};
+
+GAPI_TRANSFORM(gmatp_in_gmatp_out, <GMatP(GMatP)>, "gmatp_in_gmatp_out")
+{
+    static GMatP pattern(GMatP) { return {}; }
+    static GMatP substitute(GMatP) { return {}; }
+};
+
+GAPI_TRANSFORM(gsc_in_gmat_out, <GMat(GScalar)>, "gsc_in_gmat_out")
+{
+    static GMat pattern(GScalar) { return {}; }
+    static GMat substitute(GScalar) { return {}; }
+};
+
+GAPI_TRANSFORM(gmat_in_gsc_out, <GScalar(GMat)>, "gmat_in_gsc_out")
+{
+    static GScalar pattern(GMat) { return {}; }
+    static GScalar substitute(GMat) { return {}; }
+};
+
+GAPI_TRANSFORM(garr_in_gmat_out, <GMat(GArray<int>)>, "garr_in_gmat_out")
+{
+    static GMat pattern(GArray<int>) { return {}; }
+    static GMat substitute(GArray<int>) { return {}; }
+};
+
+GAPI_TRANSFORM(gmat_in_garr_out, <GArray<int>(GMat)>, "gmat_in_garr_out")
+{
+    static GArray<int> pattern(GMat) { return {}; }
+    static GArray<int> substitute(GMat) { return {}; }
+};
+
+} // anonymous namespace
+
+TEST(KernelPackageTransform, CreatePackage)
+{
+    auto pkg = cv::gapi::kernels
+        < gmat_in_gmat_out
+        , gmat2_in_gmat_out
+        , gmat2_in_gmat3_out
+        , gsc_in_gmat_out
+        , gmat_in_gsc_out
+        >();
+
+    auto tr = pkg.get_transformations();
+    EXPECT_EQ(5u, tr.size());
+}
+
+TEST(KernelPackageTransform, Include)
+{
+    cv::gapi::GKernelPackage pkg;
+    pkg.include<gmat_in_gmat_out>();
+    pkg.include<gmat2_in_gmat_out>();
+    pkg.include<gmat2_in_gmat3_out>();
+    auto tr = pkg.get_transformations();
+    EXPECT_EQ(3u, tr.size());
+}
+
+TEST(KernelPackageTransform, Combine)
+{
+    auto pkg1 = cv::gapi::kernels<gmat_in_gmat_out>();
+    auto pkg2 = cv::gapi::kernels<gmat2_in_gmat_out>();
+    auto pkg_comb = cv::gapi::combine(pkg1, pkg2);
+    auto tr = pkg_comb.get_transformations();
+    EXPECT_EQ(2u, tr.size());
+}
+
+TEST(KernelPackageTransform, Pattern)
+{
+    auto tr = gmat2_in_gmat3_out::transformation();
+    GMat a, b;
+    auto pattern = tr.pattern({cv::GArg(a), cv::GArg(b)});
+
+    // return type of '2gmat_in_gmat3_out' is GMat3
+    EXPECT_EQ(3u, pattern.size());
+    for (const auto& p : pattern)
+    {
+        EXPECT_NO_THROW(p.get<GMat>());
+    }
+}
+
+TEST(KernelPackageTransform, Substitute)
+{
+    auto tr = gmat2_in_gmat3_out::transformation();
+    GMat a, b;
+    auto subst = tr.substitute({cv::GArg(a), cv::GArg(b)});
+
+    EXPECT_EQ(3u, subst.size());
+    for (const auto& s : subst)
+    {
+        EXPECT_NO_THROW(s.get<GMat>());
+    }
+}
+
+template <typename Transformation, typename InType, typename OutType>
+static void transformTest()
+{
+    auto tr = Transformation::transformation();
+    InType in;
+    auto pattern = tr.pattern({cv::GArg(in)});
+    auto subst = tr.substitute({cv::GArg(in)});
+
+    EXPECT_EQ(1u, pattern.size());
+    EXPECT_EQ(1u, subst.size());
+
+    auto checkOut = [](GArg& garg) {
+        EXPECT_TRUE(garg.kind == cv::detail::GTypeTraits<OutType>::kind);
+        EXPECT_NO_THROW(garg.get<OutType>());
+    };
+
+    checkOut(pattern[0]);
+    checkOut(subst[0]);
+}
+
+TEST(KernelPackageTransform, GMat)
+{
+    transformTest<gmat_in_gmat_out, GMat, GMat>();
+}
+
+TEST(KernelPackageTransform, GMatP)
+{
+    transformTest<gmatp_in_gmatp_out, GMatP, GMatP>();
+}
+
+TEST(KernelPackageTransform, GScalarIn)
+{
+    transformTest<gsc_in_gmat_out, GScalar, GMat>();
+}
+
+TEST(KernelPackageTransform, GScalarOut)
+{
+    transformTest<gmat_in_gsc_out, GMat, GScalar>();
+}
+
+TEST(KernelPackageTransform, DISABLED_GArrayIn)
+{
+    transformTest<garr_in_gmat_out, GArray<int>, GMat>();
+}
+
+TEST(KernelPackageTransform, DISABLED_GArrayOut)
+{
+    transformTest<gmat_in_garr_out, GMat, GArray<int>>();
+}
+
+} // namespace opencv_test