From a6ef9b4584e816ae62bb4823ec2870d6c24ce27a Mon Sep 17 00:00:00 2001 From: Anatoliy Talamanov Date: Mon, 17 Feb 2020 23:29:55 +0300 Subject: [PATCH] Merge pull request #16213 from TolyaTalamanov:at/lambdas-for-kernels G-API: Using functors as kernel implementation * Implement ability to create kernel impls from functors * Clean up * Replace make_ocv_functor to ocv_kernel * Clean up * Replace GCPUFunctor -> GOCVFunctor * Move GOCVFunctor to cv::gapi::cpu namespace * Implement override for rvalue and lvalue cases * Fix comments to review * Remove GAPI_EXPORT for template functions * Fix indentation --- .../include/opencv2/gapi/cpu/gcpukernel.hpp | 72 +++++++++++- modules/gapi/include/opencv2/gapi/gkernel.hpp | 27 +++++ modules/gapi/test/gapi_sample_pipelines.cpp | 110 ++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) diff --git a/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp b/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp index e9c6e2618c..3ad970e0a1 100644 --- a/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp +++ b/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp @@ -72,6 +72,17 @@ namespace cpu */ GAPI_EXPORTS cv::gapi::GBackend backend(); /** @} */ + + class GOCVFunctor; + + //! @cond IGNORED + template + GOCVFunctor ocv_kernel(const Callable& c); + + template + GOCVFunctor ocv_kernel(Callable& c); + //! @endcond + } // namespace cpu } // namespace gapi @@ -279,6 +290,12 @@ struct OCVCallHelper, std::tuple > Impl::run(std::forward(ins)..., outs...); postprocess(outs...); } + + template + static void call(Impl& impl, Inputs&&... ins, Outputs&&... outs) + { + impl(std::forward(ins)..., outs...); + } }; template @@ -290,7 +307,17 @@ struct OCVCallHelper, std::tuple > //them to parameters of ad-hoc function //Convert own::Scalar to cv::Scalar before call kernel and run kernel //convert cv::Scalar to own::Scalar after call kernel and write back results - call_and_postprocess::get(ctx, IIs))...>::call(get_in::get(ctx, IIs)..., get_out::get(ctx, OIs)...); + call_and_postprocess::get(ctx, IIs))...> + ::call(get_in::get(ctx, IIs)..., + get_out::get(ctx, OIs)...); + } + + template + static void call_impl(cv::GCPUContext &ctx, Impl& impl, detail::Seq, detail::Seq) + { + call_and_postprocess::get(ctx, IIs))...> + ::call(impl, cv::detail::get_in::get(ctx, IIs)..., + cv::detail::get_out::get(ctx, OIs)...); } static void call(GCPUContext &ctx) @@ -299,6 +326,16 @@ struct OCVCallHelper, std::tuple > typename detail::MkSeq::type(), typename detail::MkSeq::type()); } + + // NB: Same as call but calling the object + // This necessary for kernel implementations that have a state + // and are represented as an object + static void callFunctor(cv::GCPUContext &ctx, Impl& impl) + { + call_impl(ctx, impl, + typename detail::MkSeq::type(), + typename detail::MkSeq::type()); + } }; } // namespace detail @@ -318,6 +355,39 @@ public: #define GAPI_OCV_KERNEL(Name, API) struct Name: public cv::GCPUKernelImpl +class gapi::cpu::GOCVFunctor : public gapi::GFunctor +{ +public: + using Impl = std::function; + + GOCVFunctor(const char* id, const Impl& impl) + : gapi::GFunctor(id), impl_{GCPUKernel(impl)} + { + } + + GKernelImpl impl() const override { return impl_; } + gapi::GBackend backend() const override { return gapi::cpu::backend(); } + +private: + GKernelImpl impl_; +}; + +//! @cond IGNORED +template +gapi::cpu::GOCVFunctor gapi::cpu::ocv_kernel(Callable& c) +{ + using P = detail::OCVCallHelper; + return GOCVFunctor(K::id(), std::bind(&P::callFunctor, std::placeholders::_1, std::ref(c))); +} + +template +gapi::cpu::GOCVFunctor gapi::cpu::ocv_kernel(const Callable& c) +{ + using P = detail::OCVCallHelper; + return GOCVFunctor(K::id(), std::bind(&P::callFunctor, std::placeholders::_1, c)); +} +//! @endcond + } // namespace cv #endif // OPENCV_GAPI_GCPUKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/gkernel.hpp b/modules/gapi/include/opencv2/gapi/gkernel.hpp index 32cae314d6..becd7796e2 100644 --- a/modules/gapi/include/opencv2/gapi/gkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/gkernel.hpp @@ -383,6 +383,20 @@ namespace std namespace cv { namespace gapi { + class GFunctor + { + public: + virtual cv::GKernelImpl impl() const = 0; + virtual cv::gapi::GBackend backend() const = 0; + const char* id() const { return m_id; } + + virtual ~GFunctor() = default; + protected: + GFunctor(const char* id) : m_id(id) { }; + private: + const char* m_id; + }; + /** \addtogroup gapi_compile_args * @{ */ @@ -460,6 +474,10 @@ namespace gapi { } public: + void include(const GFunctor& functor) + { + m_id_kernels[functor.id()] = std::make_pair(functor.backend(), functor.impl()); + } /** * @brief Returns total number of kernels * in the package (across all backends included) @@ -618,6 +636,15 @@ namespace gapi { return pkg; }; + template + GKernelPackage kernels(FF&... functors) + { + GKernelPackage pkg; + int unused[] = { 0, (pkg.include(functors), 0)... }; + cv::util::suppress_unused_warning(unused); + return pkg; + }; + /** @} */ // FYI - this function is already commented above diff --git a/modules/gapi/test/gapi_sample_pipelines.cpp b/modules/gapi/test/gapi_sample_pipelines.cpp index 9c329f97fb..58b92580d5 100644 --- a/modules/gapi/test/gapi_sample_pipelines.cpp +++ b/modules/gapi/test/gapi_sample_pipelines.cpp @@ -11,6 +11,8 @@ #include #include "logger.hpp" +#include + namespace opencv_test { @@ -42,6 +44,11 @@ namespace } }; + G_TYPED_KERNEL(GCustom, , "org.opencv.test.custom") + { + static GMatDesc outMeta(GMatDesc in) { return in; } + }; + // These definitons test the correct macro work if the kernel has multiple output values G_TYPED_KERNEL(GRetGArrayTupleOfGMat2Kernel, >(GMat, Scalar)>, "org.opencv.test.retarrayoftupleofgmat2kernel") {}; G_TYPED_KERNEL(GRetGArraTupleyOfGMat3Kernel, >(GMat)>, "org.opencv.test.retarrayoftupleofgmat3kernel") {}; @@ -335,4 +342,107 @@ TEST(GAPI_Pipeline, CanUseOwnMatAsOutput) EXPECT_NO_THROW(comp.apply({in_own_mat}, {out_own_mat})); } +TEST(GAPI_Pipeline, CreateKernelImplFromLambda) +{ + cv::Size size(300, 300); + int type = CV_8UC3; + cv::Mat in_mat(size, type); + cv::randu(in_mat, cv::Scalar::all(0), cv::Scalar::all(255)); + int value = 5; + + cv::GMat in; + cv::GMat out = GCustom::on(in); + cv::GComputation comp(in, out); + + // OpenCV ////////////////////////////////////////////////////////////////////////// + auto ref_mat = in_mat + value; + + // G-API ////////////////////////////////////////////////////////////////////////// + auto impl = cv::gapi::cpu::ocv_kernel([&value](const cv::Mat& src, cv::Mat& dst) + { + dst = src + value; + }); + + cv::Mat out_mat; + auto pkg = cv::gapi::kernels(impl); + comp.apply(in_mat, out_mat, cv::compile_args(pkg)); + + EXPECT_EQ(0, cv::norm(out_mat, ref_mat)); +} + +TEST(GAPI_Pipeline, ReplaceDefaultByLambda) +{ + cv::Size size(300, 300); + int type = CV_8UC3; + cv::Mat in_mat1(size, type); + cv::Mat in_mat2(size, type); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + + cv::GMat in1, in2; + cv::GMat out = cv::gapi::add(in1, in2); + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out)); + + // OpenCV ////////////////////////////////////////////////////////////////////////// + cv::Mat ref_mat = in_mat1 + in_mat2; + + + // G-API ////////////////////////////////////////////////////////////////////////// + bool is_called = false; + auto impl = cv::gapi::cpu::ocv_kernel([&is_called] + (const cv::Mat& src1, const cv::Mat& src2, int, cv::Mat& dst) + { + is_called = true; + dst = src1 + src2; + }); + + cv::Mat out_mat; + auto pkg = cv::gapi::kernels(impl); + comp.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat), cv::compile_args(pkg)); + + EXPECT_EQ(0, cv::norm(out_mat, ref_mat)); + EXPECT_TRUE(is_called); +} + +struct AddImpl +{ + void operator()(const cv::Mat& in1, const cv::Mat& in2, int, cv::Mat& out) + { + out = in1 + in2; + is_called = true; + } + + bool is_called = false; +}; + +TEST(GAPI_Pipeline, ReplaceDefaultByFunctor) +{ + cv::Size size(300, 300); + int type = CV_8UC3; + cv::Mat in_mat1(size, type); + cv::Mat in_mat2(size, type); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + + cv::GMat in1, in2; + cv::GMat out = cv::gapi::add(in1, in2); + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out)); + + // OpenCV ////////////////////////////////////////////////////////////////////////// + cv::Mat ref_mat = in_mat1 + in_mat2; + + + // G-API /////////////////////////////////////////////////////////////////////////// + AddImpl f; + EXPECT_FALSE(f.is_called); + auto impl = cv::gapi::cpu::ocv_kernel(f); + + cv::Mat out_mat; + auto pkg = cv::gapi::kernels(impl); + comp.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat), cv::compile_args(pkg)); + + EXPECT_EQ(0, cv::norm(out_mat, ref_mat)); + EXPECT_TRUE(f.is_called); +} + } // namespace opencv_test