From 29e88e50ffaa8393eacd8343f09fbf2388535161 Mon Sep 17 00:00:00 2001 From: Dmitry Matveev Date: Wed, 26 Sep 2018 21:50:39 +0300 Subject: [PATCH] Merge pull request #12608 from dmatveev:gapi * G-API Initial code upload * Update G-API code base to Sep-24-2018 * The majority of OpenCV buildbot problems was addressed * Update G-API code base to 24-Sep-18 EOD * G-API code base update 25-Sep-2018 * Linux warnings should be resolved * Documentation build should become green * Number of Windows warnings should be reduced * Update G-API code base to 25-Sep-18 EOD * ARMv7 build issue should be resolved * ADE is bumped to latest version and should fix Clang builds for macOS/iOS * Remaining Windows warnings should be resolved * New Linux32 / ARMv7 warnings should be resolved * G-API code base update 25-Sep-2018-EOD2 * Final Windows warnings should be resolved now * G-API code base update 26-Sep-2018 * Fixed issues with precompiled headers in module and its tests --- modules/gapi/CMakeLists.txt | 94 + modules/gapi/cmake/DownloadADE.cmake | 36 + modules/gapi/cmake/init.cmake | 11 + modules/gapi/doc/intro.markdown | 3 + modules/gapi/include/opencv2/gapi.hpp | 21 + modules/gapi/include/opencv2/gapi/core.hpp | 1559 ++++++++++++ .../gapi/include/opencv2/gapi/cpu/core.hpp | 27 + .../include/opencv2/gapi/cpu/gcpukernel.hpp | 236 ++ .../gapi/include/opencv2/gapi/cpu/imgproc.hpp | 27 + .../opencv2/gapi/fluid/gfluidbuffer.hpp | 120 + .../opencv2/gapi/fluid/gfluidkernel.hpp | 283 +++ modules/gapi/include/opencv2/gapi/garg.hpp | 98 + modules/gapi/include/opencv2/gapi/garray.hpp | 239 ++ modules/gapi/include/opencv2/gapi/gcall.hpp | 63 + modules/gapi/include/opencv2/gapi/gcommon.hpp | 118 + .../gapi/include/opencv2/gapi/gcompiled.hpp | 59 + .../include/opencv2/gapi/gcompoundkernel.hpp | 123 + .../include/opencv2/gapi/gcomputation.hpp | 135 ++ modules/gapi/include/opencv2/gapi/gkernel.hpp | 409 ++++ modules/gapi/include/opencv2/gapi/gmat.hpp | 131 + .../gapi/include/opencv2/gapi/gmetaarg.hpp | 66 + modules/gapi/include/opencv2/gapi/gproto.hpp | 96 + modules/gapi/include/opencv2/gapi/gscalar.hpp | 68 + .../include/opencv2/gapi/gtype_traits.hpp | 150 ++ modules/gapi/include/opencv2/gapi/gtyped.hpp | 186 ++ modules/gapi/include/opencv2/gapi/imgproc.hpp | 677 ++++++ .../include/opencv2/gapi/opencv_includes.hpp | 16 + .../gapi/include/opencv2/gapi/operators.hpp | 69 + .../gapi/include/opencv2/gapi/own/assert.hpp | 41 + .../gapi/include/opencv2/gapi/own/convert.hpp | 47 + .../gapi/include/opencv2/gapi/own/exports.hpp | 28 + modules/gapi/include/opencv2/gapi/own/mat.hpp | 142 ++ .../gapi/include/opencv2/gapi/own/scalar.hpp | 45 + .../gapi/include/opencv2/gapi/own/types.hpp | 137 ++ .../gapi/include/opencv2/gapi/util/any.hpp | 159 ++ .../opencv2/gapi/util/compiler_hints.hpp | 21 + .../include/opencv2/gapi/util/optional.hpp | 178 ++ .../gapi/include/opencv2/gapi/util/throw.hpp | 36 + .../gapi/include/opencv2/gapi/util/util.hpp | 92 + .../include/opencv2/gapi/util/variant.hpp | 377 +++ .../gapi/perf/common/gapi_core_perf_tests.hpp | 1782 ++++++++++++++ .../perf/common/gapi_imgproc_perf_tests.hpp | 882 +++++++ .../perf/cpu/gapi_core_perf_tests_cpu.cpp | 219 ++ .../perf/cpu/gapi_imgproc_perf_tests_cpu.cpp | 119 + .../internal/gapi_compiler_perf_tests.cpp | 46 + modules/gapi/perf/perf_main.cpp | 11 + modules/gapi/perf/perf_precomp.hpp | 21 + modules/gapi/src/api/README.md | 1 + modules/gapi/src/api/gapi_priv.cpp | 43 + modules/gapi/src/api/gapi_priv.hpp | 77 + modules/gapi/src/api/garray.cpp | 44 + modules/gapi/src/api/gbackend.cpp | 269 +++ modules/gapi/src/api/gbackend_priv.hpp | 53 + modules/gapi/src/api/gcall.cpp | 64 + modules/gapi/src/api/gcall_priv.hpp | 37 + modules/gapi/src/api/gcomputation.cpp | 191 ++ modules/gapi/src/api/gcomputation_priv.hpp | 29 + modules/gapi/src/api/gkernel.cpp | 141 ++ modules/gapi/src/api/gmat.cpp | 71 + modules/gapi/src/api/gnode.cpp | 88 + modules/gapi/src/api/gnode.hpp | 58 + modules/gapi/src/api/gnode_priv.hpp | 52 + modules/gapi/src/api/gproto.cpp | 152 ++ modules/gapi/src/api/gproto_priv.hpp | 35 + modules/gapi/src/api/gscalar.cpp | 69 + modules/gapi/src/api/kernels_core.cpp | 347 +++ modules/gapi/src/api/kernels_imgproc.cpp | 142 ++ modules/gapi/src/api/operators.cpp | 211 ++ modules/gapi/src/backends/README.md | 2 + modules/gapi/src/backends/common/gbackend.hpp | 103 + .../src/backends/common/gcompoundbackend.cpp | 18 + .../src/backends/common/gcompoundkernel.cpp | 45 + modules/gapi/src/backends/cpu/gcpubackend.cpp | 227 ++ modules/gapi/src/backends/cpu/gcpubackend.hpp | 63 + modules/gapi/src/backends/cpu/gcpucore.cpp | 577 +++++ modules/gapi/src/backends/cpu/gcpucore.hpp | 24 + modules/gapi/src/backends/cpu/gcpuimgproc.cpp | 273 +++ modules/gapi/src/backends/cpu/gcpuimgproc.hpp | 23 + modules/gapi/src/backends/cpu/gcpukernel.cpp | 50 + .../gapi/src/backends/fluid/gfluidbackend.cpp | 1185 +++++++++ .../gapi/src/backends/fluid/gfluidbackend.hpp | 128 + .../gapi/src/backends/fluid/gfluidbuffer.cpp | 746 ++++++ .../src/backends/fluid/gfluidbuffer_priv.hpp | 298 +++ .../gapi/src/backends/fluid/gfluidcore.cpp | 2121 +++++++++++++++++ .../gapi/src/backends/fluid/gfluidcore.hpp | 19 + .../gapi/src/backends/fluid/gfluidimgproc.cpp | 1325 ++++++++++ .../gapi/src/backends/fluid/gfluidimgproc.hpp | 19 + .../gapi/src/backends/fluid/gfluidutils.hpp | 154 ++ modules/gapi/src/compiler/README.md | 1 + modules/gapi/src/compiler/gcompiled.cpp | 131 + modules/gapi/src/compiler/gcompiled_priv.hpp | 58 + modules/gapi/src/compiler/gcompiler.cpp | 273 +++ modules/gapi/src/compiler/gcompiler.hpp | 51 + modules/gapi/src/compiler/gislandmodel.cpp | 287 +++ modules/gapi/src/compiler/gislandmodel.hpp | 184 ++ modules/gapi/src/compiler/gmodel.cpp | 245 ++ modules/gapi/src/compiler/gmodel.hpp | 251 ++ modules/gapi/src/compiler/gmodelbuilder.cpp | 303 +++ modules/gapi/src/compiler/gmodelbuilder.hpp | 77 + modules/gapi/src/compiler/gobjref.hpp | 50 + modules/gapi/src/compiler/passes/dump_dot.cpp | 221 ++ modules/gapi/src/compiler/passes/exec.cpp | 640 +++++ modules/gapi/src/compiler/passes/helpers.cpp | 120 + modules/gapi/src/compiler/passes/helpers.hpp | 31 + modules/gapi/src/compiler/passes/islands.cpp | 231 ++ modules/gapi/src/compiler/passes/kernels.cpp | 155 ++ modules/gapi/src/compiler/passes/meta.cpp | 123 + modules/gapi/src/compiler/passes/passes.hpp | 58 + modules/gapi/src/compiler/transactions.hpp | 147 ++ modules/gapi/src/executor/gexecutor.cpp | 211 ++ modules/gapi/src/executor/gexecutor.hpp | 94 + modules/gapi/src/logger.hpp | 24 + modules/gapi/src/precomp.hpp | 19 + .../test/common/gapi_compoundkernel_tests.cpp | 500 ++++ modules/gapi/test/common/gapi_core_tests.cpp | 8 + modules/gapi/test/common/gapi_core_tests.hpp | 151 ++ .../gapi/test/common/gapi_core_tests_inl.hpp | 1420 +++++++++++ .../gapi/test/common/gapi_imgproc_tests.cpp | 8 + .../gapi/test/common/gapi_imgproc_tests.hpp | 41 + .../test/common/gapi_imgproc_tests_inl.hpp | 751 ++++++ .../gapi/test/common/gapi_operators_tests.cpp | 8 + .../gapi/test/common/gapi_operators_tests.hpp | 192 ++ .../test/common/gapi_operators_tests_inl.hpp | 102 + .../gapi/test/common/gapi_tests_common.hpp | 108 + modules/gapi/test/cpu/gapi_core_tests_cpu.cpp | 386 +++ .../gapi/test/cpu/gapi_core_tests_fluid.cpp | 486 ++++ .../gapi/test/cpu/gapi_imgproc_tests_cpu.cpp | 204 ++ .../test/cpu/gapi_imgproc_tests_fluid.cpp | 145 ++ .../test/cpu/gapi_operators_tests_cpu.cpp | 67 + .../test/cpu/gapi_operators_tests_fluid.cpp | 67 + modules/gapi/test/gapi_array_tests.cpp | 166 ++ modules/gapi/test/gapi_basic_hetero_tests.cpp | 124 + modules/gapi/test/gapi_desc_tests.cpp | 202 ++ modules/gapi/test/gapi_fluid_resize_test.cpp | 649 +++++ modules/gapi/test/gapi_fluid_roi_test.cpp | 197 ++ modules/gapi/test/gapi_fluid_test.cpp | 710 ++++++ modules/gapi/test/gapi_fluid_test_kernels.cpp | 434 ++++ modules/gapi/test/gapi_fluid_test_kernels.hpp | 105 + modules/gapi/test/gapi_gcompiled_tests.cpp | 173 ++ modules/gapi/test/gapi_gcomputation_tests.cpp | 68 + modules/gapi/test/gapi_kernel_tests.cpp | 202 ++ modules/gapi/test/gapi_mock_kernels.hpp | 123 + modules/gapi/test/gapi_sample_pipelines.cpp | 301 +++ modules/gapi/test/gapi_scalar_tests.cpp | 116 + modules/gapi/test/gapi_smoke_test.cpp | 97 + modules/gapi/test/gapi_typed_tests.cpp | 185 ++ modules/gapi/test/gapi_util_tests.cpp | 43 + .../test/internal/gapi_int_backend_tests.cpp | 86 + .../test/internal/gapi_int_executor_tests.cpp | 83 + .../gapi/test/internal/gapi_int_garg_test.cpp | 100 + .../test/internal/gapi_int_gmetaarg_test.cpp | 136 ++ .../internal/gapi_int_gmodel_builder_test.cpp | 364 +++ .../internal/gapi_int_island_fusion_tests.cpp | 527 ++++ .../test/internal/gapi_int_island_tests.cpp | 653 +++++ .../internal/gapi_int_recompilation_test.cpp | 71 + .../internal/gapi_int_resolve_kernel_test.cpp | 119 + .../test/internal/gapi_int_vectorref_test.cpp | 207 ++ .../test/internal/gapi_transactions_test.cpp | 222 ++ modules/gapi/test/own/gapi_types_tests.cpp | 159 ++ modules/gapi/test/own/mat_tests.cpp | 164 ++ modules/gapi/test/own/scalar_tests.cpp | 44 + modules/gapi/test/test_main.cpp | 12 + modules/gapi/test/test_precomp.hpp | 24 + modules/gapi/test/util/any_tests.cpp | 121 + modules/gapi/test/util/optional_tests.cpp | 175 ++ modules/gapi/test/util/variant_tests.cpp | 386 +++ 166 files changed, 35254 insertions(+) create mode 100644 modules/gapi/CMakeLists.txt create mode 100644 modules/gapi/cmake/DownloadADE.cmake create mode 100644 modules/gapi/cmake/init.cmake create mode 100644 modules/gapi/doc/intro.markdown create mode 100644 modules/gapi/include/opencv2/gapi.hpp create mode 100644 modules/gapi/include/opencv2/gapi/core.hpp create mode 100644 modules/gapi/include/opencv2/gapi/cpu/core.hpp create mode 100644 modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp create mode 100644 modules/gapi/include/opencv2/gapi/cpu/imgproc.hpp create mode 100644 modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp create mode 100644 modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp create mode 100644 modules/gapi/include/opencv2/gapi/garg.hpp create mode 100644 modules/gapi/include/opencv2/gapi/garray.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gcall.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gcommon.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gcompiled.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gcompoundkernel.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gcomputation.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gkernel.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gmat.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gmetaarg.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gproto.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gscalar.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gtype_traits.hpp create mode 100644 modules/gapi/include/opencv2/gapi/gtyped.hpp create mode 100644 modules/gapi/include/opencv2/gapi/imgproc.hpp create mode 100644 modules/gapi/include/opencv2/gapi/opencv_includes.hpp create mode 100644 modules/gapi/include/opencv2/gapi/operators.hpp create mode 100644 modules/gapi/include/opencv2/gapi/own/assert.hpp create mode 100644 modules/gapi/include/opencv2/gapi/own/convert.hpp create mode 100644 modules/gapi/include/opencv2/gapi/own/exports.hpp create mode 100644 modules/gapi/include/opencv2/gapi/own/mat.hpp create mode 100644 modules/gapi/include/opencv2/gapi/own/scalar.hpp create mode 100644 modules/gapi/include/opencv2/gapi/own/types.hpp create mode 100644 modules/gapi/include/opencv2/gapi/util/any.hpp create mode 100644 modules/gapi/include/opencv2/gapi/util/compiler_hints.hpp create mode 100644 modules/gapi/include/opencv2/gapi/util/optional.hpp create mode 100644 modules/gapi/include/opencv2/gapi/util/throw.hpp create mode 100644 modules/gapi/include/opencv2/gapi/util/util.hpp create mode 100644 modules/gapi/include/opencv2/gapi/util/variant.hpp create mode 100644 modules/gapi/perf/common/gapi_core_perf_tests.hpp create mode 100644 modules/gapi/perf/common/gapi_imgproc_perf_tests.hpp create mode 100644 modules/gapi/perf/cpu/gapi_core_perf_tests_cpu.cpp create mode 100644 modules/gapi/perf/cpu/gapi_imgproc_perf_tests_cpu.cpp create mode 100644 modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp create mode 100644 modules/gapi/perf/perf_main.cpp create mode 100644 modules/gapi/perf/perf_precomp.hpp create mode 100644 modules/gapi/src/api/README.md create mode 100644 modules/gapi/src/api/gapi_priv.cpp create mode 100644 modules/gapi/src/api/gapi_priv.hpp create mode 100644 modules/gapi/src/api/garray.cpp create mode 100644 modules/gapi/src/api/gbackend.cpp create mode 100644 modules/gapi/src/api/gbackend_priv.hpp create mode 100644 modules/gapi/src/api/gcall.cpp create mode 100644 modules/gapi/src/api/gcall_priv.hpp create mode 100644 modules/gapi/src/api/gcomputation.cpp create mode 100644 modules/gapi/src/api/gcomputation_priv.hpp create mode 100644 modules/gapi/src/api/gkernel.cpp create mode 100644 modules/gapi/src/api/gmat.cpp create mode 100644 modules/gapi/src/api/gnode.cpp create mode 100644 modules/gapi/src/api/gnode.hpp create mode 100644 modules/gapi/src/api/gnode_priv.hpp create mode 100644 modules/gapi/src/api/gproto.cpp create mode 100644 modules/gapi/src/api/gproto_priv.hpp create mode 100644 modules/gapi/src/api/gscalar.cpp create mode 100644 modules/gapi/src/api/kernels_core.cpp create mode 100644 modules/gapi/src/api/kernels_imgproc.cpp create mode 100644 modules/gapi/src/api/operators.cpp create mode 100644 modules/gapi/src/backends/README.md create mode 100644 modules/gapi/src/backends/common/gbackend.hpp create mode 100644 modules/gapi/src/backends/common/gcompoundbackend.cpp create mode 100644 modules/gapi/src/backends/common/gcompoundkernel.cpp create mode 100644 modules/gapi/src/backends/cpu/gcpubackend.cpp create mode 100644 modules/gapi/src/backends/cpu/gcpubackend.hpp create mode 100644 modules/gapi/src/backends/cpu/gcpucore.cpp create mode 100644 modules/gapi/src/backends/cpu/gcpucore.hpp create mode 100644 modules/gapi/src/backends/cpu/gcpuimgproc.cpp create mode 100644 modules/gapi/src/backends/cpu/gcpuimgproc.hpp create mode 100644 modules/gapi/src/backends/cpu/gcpukernel.cpp create mode 100644 modules/gapi/src/backends/fluid/gfluidbackend.cpp create mode 100644 modules/gapi/src/backends/fluid/gfluidbackend.hpp create mode 100644 modules/gapi/src/backends/fluid/gfluidbuffer.cpp create mode 100644 modules/gapi/src/backends/fluid/gfluidbuffer_priv.hpp create mode 100644 modules/gapi/src/backends/fluid/gfluidcore.cpp create mode 100644 modules/gapi/src/backends/fluid/gfluidcore.hpp create mode 100644 modules/gapi/src/backends/fluid/gfluidimgproc.cpp create mode 100644 modules/gapi/src/backends/fluid/gfluidimgproc.hpp create mode 100644 modules/gapi/src/backends/fluid/gfluidutils.hpp create mode 100644 modules/gapi/src/compiler/README.md create mode 100644 modules/gapi/src/compiler/gcompiled.cpp create mode 100644 modules/gapi/src/compiler/gcompiled_priv.hpp create mode 100644 modules/gapi/src/compiler/gcompiler.cpp create mode 100644 modules/gapi/src/compiler/gcompiler.hpp create mode 100644 modules/gapi/src/compiler/gislandmodel.cpp create mode 100644 modules/gapi/src/compiler/gislandmodel.hpp create mode 100644 modules/gapi/src/compiler/gmodel.cpp create mode 100644 modules/gapi/src/compiler/gmodel.hpp create mode 100644 modules/gapi/src/compiler/gmodelbuilder.cpp create mode 100644 modules/gapi/src/compiler/gmodelbuilder.hpp create mode 100644 modules/gapi/src/compiler/gobjref.hpp create mode 100644 modules/gapi/src/compiler/passes/dump_dot.cpp create mode 100644 modules/gapi/src/compiler/passes/exec.cpp create mode 100644 modules/gapi/src/compiler/passes/helpers.cpp create mode 100644 modules/gapi/src/compiler/passes/helpers.hpp create mode 100644 modules/gapi/src/compiler/passes/islands.cpp create mode 100644 modules/gapi/src/compiler/passes/kernels.cpp create mode 100644 modules/gapi/src/compiler/passes/meta.cpp create mode 100644 modules/gapi/src/compiler/passes/passes.hpp create mode 100644 modules/gapi/src/compiler/transactions.hpp create mode 100644 modules/gapi/src/executor/gexecutor.cpp create mode 100644 modules/gapi/src/executor/gexecutor.hpp create mode 100644 modules/gapi/src/logger.hpp create mode 100644 modules/gapi/src/precomp.hpp create mode 100644 modules/gapi/test/common/gapi_compoundkernel_tests.cpp create mode 100644 modules/gapi/test/common/gapi_core_tests.cpp create mode 100644 modules/gapi/test/common/gapi_core_tests.hpp create mode 100644 modules/gapi/test/common/gapi_core_tests_inl.hpp create mode 100644 modules/gapi/test/common/gapi_imgproc_tests.cpp create mode 100644 modules/gapi/test/common/gapi_imgproc_tests.hpp create mode 100644 modules/gapi/test/common/gapi_imgproc_tests_inl.hpp create mode 100644 modules/gapi/test/common/gapi_operators_tests.cpp create mode 100644 modules/gapi/test/common/gapi_operators_tests.hpp create mode 100644 modules/gapi/test/common/gapi_operators_tests_inl.hpp create mode 100644 modules/gapi/test/common/gapi_tests_common.hpp create mode 100644 modules/gapi/test/cpu/gapi_core_tests_cpu.cpp create mode 100644 modules/gapi/test/cpu/gapi_core_tests_fluid.cpp create mode 100644 modules/gapi/test/cpu/gapi_imgproc_tests_cpu.cpp create mode 100644 modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp create mode 100644 modules/gapi/test/cpu/gapi_operators_tests_cpu.cpp create mode 100644 modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp create mode 100644 modules/gapi/test/gapi_array_tests.cpp create mode 100644 modules/gapi/test/gapi_basic_hetero_tests.cpp create mode 100644 modules/gapi/test/gapi_desc_tests.cpp create mode 100644 modules/gapi/test/gapi_fluid_resize_test.cpp create mode 100644 modules/gapi/test/gapi_fluid_roi_test.cpp create mode 100644 modules/gapi/test/gapi_fluid_test.cpp create mode 100644 modules/gapi/test/gapi_fluid_test_kernels.cpp create mode 100644 modules/gapi/test/gapi_fluid_test_kernels.hpp create mode 100644 modules/gapi/test/gapi_gcompiled_tests.cpp create mode 100644 modules/gapi/test/gapi_gcomputation_tests.cpp create mode 100644 modules/gapi/test/gapi_kernel_tests.cpp create mode 100644 modules/gapi/test/gapi_mock_kernels.hpp create mode 100644 modules/gapi/test/gapi_sample_pipelines.cpp create mode 100644 modules/gapi/test/gapi_scalar_tests.cpp create mode 100644 modules/gapi/test/gapi_smoke_test.cpp create mode 100644 modules/gapi/test/gapi_typed_tests.cpp create mode 100644 modules/gapi/test/gapi_util_tests.cpp create mode 100644 modules/gapi/test/internal/gapi_int_backend_tests.cpp create mode 100644 modules/gapi/test/internal/gapi_int_executor_tests.cpp create mode 100644 modules/gapi/test/internal/gapi_int_garg_test.cpp create mode 100644 modules/gapi/test/internal/gapi_int_gmetaarg_test.cpp create mode 100644 modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp create mode 100644 modules/gapi/test/internal/gapi_int_island_fusion_tests.cpp create mode 100644 modules/gapi/test/internal/gapi_int_island_tests.cpp create mode 100644 modules/gapi/test/internal/gapi_int_recompilation_test.cpp create mode 100644 modules/gapi/test/internal/gapi_int_resolve_kernel_test.cpp create mode 100644 modules/gapi/test/internal/gapi_int_vectorref_test.cpp create mode 100644 modules/gapi/test/internal/gapi_transactions_test.cpp create mode 100644 modules/gapi/test/own/gapi_types_tests.cpp create mode 100644 modules/gapi/test/own/mat_tests.cpp create mode 100644 modules/gapi/test/own/scalar_tests.cpp create mode 100644 modules/gapi/test/test_main.cpp create mode 100644 modules/gapi/test/test_precomp.hpp create mode 100644 modules/gapi/test/util/any_tests.cpp create mode 100644 modules/gapi/test/util/optional_tests.cpp create mode 100644 modules/gapi/test/util/variant_tests.cpp diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt new file mode 100644 index 0000000000..de0ced4b5b --- /dev/null +++ b/modules/gapi/CMakeLists.txt @@ -0,0 +1,94 @@ +# FIXME: Remove CXX11 check after complete switch to OpenCV 4 branch +# (CI, bundle, workloads, etc) +if (NOT HAVE_CXX11 OR NOT TARGET ade) + # can't build G-API because of the above reasons + ocv_module_disable(gapi) + return() +endif() + +set(the_description "OpenCV G-API Core Module") +ocv_add_module(gapi opencv_imgproc) + +file(GLOB gapi_ext_hdrs + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.h" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/util/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/cpu/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/fluid/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/own/*.hpp" + ) + +set(gapi_srcs + # Front-end part + src/api/gapi_priv.cpp + src/api/gmat.cpp + src/api/garray.cpp + src/api/gscalar.cpp + src/api/gkernel.cpp + src/api/gbackend.cpp + src/api/gproto.cpp + src/api/gnode.cpp + src/api/gcall.cpp + src/api/gcomputation.cpp + src/api/operators.cpp + src/api/kernels_core.cpp + src/api/kernels_imgproc.cpp + + # Compiler part + src/compiler/gmodel.cpp + src/compiler/gmodelbuilder.cpp + src/compiler/gislandmodel.cpp + src/compiler/gcompiler.cpp + src/compiler/gcompiled.cpp + src/compiler/passes/helpers.cpp + src/compiler/passes/dump_dot.cpp + src/compiler/passes/islands.cpp + src/compiler/passes/meta.cpp + src/compiler/passes/kernels.cpp + src/compiler/passes/exec.cpp + + # Executor + src/executor/gexecutor.cpp + + # CPU Backend (currently built-in) + src/backends/cpu/gcpubackend.cpp + src/backends/cpu/gcpukernel.cpp + src/backends/cpu/gcpuimgproc.cpp + src/backends/cpu/gcpucore.cpp + + # Fluid Backend (also built-in, FIXME:move away) + src/backends/fluid/gfluidbuffer.cpp + src/backends/fluid/gfluidbackend.cpp + src/backends/fluid/gfluidimgproc.cpp + src/backends/fluid/gfluidcore.cpp + + # Compound + src/backends/common/gcompoundbackend.cpp + src/backends/common/gcompoundkernel.cpp + ) + +ocv_list_add_prefix(gapi_srcs "${CMAKE_CURRENT_LIST_DIR}/") + +# For IDE users +ocv_source_group("Src" FILES ${gapi_srcs}) +ocv_source_group("Include" FILES ${gapi_ext_hdrs}) + +ocv_set_module_sources(HEADERS ${gapi_ext_hdrs} SOURCES ${gapi_srcs}) +ocv_module_include_directories("${CMAKE_CURRENT_LIST_DIR}/src") + +# Note `ade` is not a module name but link dependency for ${the_module} +# (which is opencv_gapi) +ocv_create_module(ade) + +ocv_add_accuracy_tests() +# FIXME: test binary is linked with ADE directly since ADE symbols +# are not exported from libopencv_gapi.so in any form - thus +# there're two copies of ADE code in memory when tests run (!) +# src/ is specified to include dirs for INTERNAL tests only. +if(TARGET opencv_test_gapi) + target_include_directories(opencv_test_gapi PRIVATE "${CMAKE_CURRENT_LIST_DIR}/src") + target_link_libraries(opencv_test_gapi PRIVATE ade) +endif() + +ocv_add_perf_tests() diff --git a/modules/gapi/cmake/DownloadADE.cmake b/modules/gapi/cmake/DownloadADE.cmake new file mode 100644 index 0000000000..0ffd59eb7d --- /dev/null +++ b/modules/gapi/cmake/DownloadADE.cmake @@ -0,0 +1,36 @@ +if(ANDROID) + # FIXME: Android build will be enabled separately + return() +endif() + +set(ade_src_dir "${OpenCV_BINARY_DIR}/3rdparty/ade") +set(ade_filename "v0.1.1c.zip") +set(ade_subdir "ade-0.1.1c") +set(ade_md5 "db7e6a260229ee562a1b2857df473af8") +ocv_download(FILENAME ${ade_filename} + HASH ${ade_md5} + URL + "${OPENCV_ADE_URL}" + "$ENV{OPENCV_ADE_URL}" + "https://github.com/opencv/ade/archive/" + DESTINATION_DIR ${ade_src_dir} + ID ADE + STATUS res + UNPACK RELATIVE_URL) + +if (NOT res) + return() +endif() + +set(ADE_root "${ade_src_dir}/${ade_subdir}/sources/ade") +file(GLOB_RECURSE ADE_sources "${ADE_root}/source/*.cpp") +file(GLOB_RECURSE ADE_include "${ADE_root}/include/ade/*.hpp") +add_library(ade STATIC ${ADE_include} ${ADE_sources}) +target_include_directories(ade PUBLIC $) +set_target_properties(ade PROPERTIES POSITION_INDEPENDENT_CODE True) + +if(NOT BUILD_SHARED_LIBS) + ocv_install_target(ade EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) +endif() + +ocv_install_3rdparty_licenses(ade "${ade_src_dir}/${ade_subdir}/LICENSE") diff --git a/modules/gapi/cmake/init.cmake b/modules/gapi/cmake/init.cmake new file mode 100644 index 0000000000..9f6ebeff41 --- /dev/null +++ b/modules/gapi/cmake/init.cmake @@ -0,0 +1,11 @@ +if (ade_DIR) + # if ade_DIR is set, use ADE-supplied CMake script + # to set up variables to the prebuilt ADE + find_package(ade 0.1.0) +endif() + +if(NOT TARGET ade) + # if ade_DIR is not set, try to use automatically + # downloaded one (if there any) + include("${CMAKE_CURRENT_LIST_DIR}/DownloadADE.cmake") +endif() diff --git a/modules/gapi/doc/intro.markdown b/modules/gapi/doc/intro.markdown new file mode 100644 index 0000000000..ae06174324 --- /dev/null +++ b/modules/gapi/doc/intro.markdown @@ -0,0 +1,3 @@ +# Graph API {#gapi} + +Introduction to G-API (WIP). \ No newline at end of file diff --git a/modules/gapi/include/opencv2/gapi.hpp b/modules/gapi/include/opencv2/gapi.hpp new file mode 100644 index 0000000000..8e5bb068c8 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi.hpp @@ -0,0 +1,21 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_HPP +#define OPENCV_GAPI_HPP + +#include + +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/garray.hpp" +#include "opencv2/gapi/gcomputation.hpp" +#include "opencv2/gapi/gcompiled.hpp" +#include "opencv2/gapi/gtyped.hpp" +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/operators.hpp" + +#endif // OPENCV_GAPI_HPP diff --git a/modules/gapi/include/opencv2/gapi/core.hpp b/modules/gapi/include/opencv2/gapi/core.hpp new file mode 100644 index 0000000000..9ee45deb1d --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/core.hpp @@ -0,0 +1,1559 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_CORE_HPP +#define OPENCV_GAPI_CORE_HPP + +#include // std::tuple + +#include + +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/gkernel.hpp" + +/** \defgroup gapi_core G-API core (basic) functionality +@{ + @defgroup gapi_math Graph API: Math operations + @defgroup gapi_pixelwise Graph API: Pixelwise operations + @defgroup gapi_matrixop Graph API: Operations on matrices + @defgroup gapi_transform Graph API: Geometric, depth and LUT-like image transformations +@} + */ +namespace cv { namespace gapi { +namespace core { + using GMat2 = std::tuple; + using GMat3 = std::tuple; // FIXME: how to avoid this? + using GMat4 = std::tuple; + using GMatScalar = std::tuple; + + G_TYPED_KERNEL(GAdd, , "org.opencv.core.math.add") { + static GMatDesc outMeta(GMatDesc a, GMatDesc b, int ddepth) { + if (ddepth == -1) + { + // OpenCV: When the input arrays in add/subtract/multiply/divide + // functions have different depths, the output array depth must be + // explicitly specified! + // See artim_op() @ arithm.cpp + GAPI_Assert(a.chan == b.chan); + GAPI_Assert(a.depth == b.depth); + return a; + } + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GAddC, , "org.opencv.core.math.addC") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc, int ddepth) { + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GSub, , "org.opencv.core.math.sub") { + static GMatDesc outMeta(GMatDesc a, GMatDesc b, int ddepth) { + if (ddepth == -1) + { + // This macro should select a larger data depth from a and b + // considering the number of channels in the same + // FIXME!!! Clarify if it is valid for sub() + GAPI_Assert(a.chan == b.chan); + ddepth = std::max(a.depth, b.depth); + } + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GSubC, , "org.opencv.core.math.subC") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc, int ddepth) { + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GSubRC,, "org.opencv.core.math.subRC") { + static GMatDesc outMeta(GScalarDesc, GMatDesc b, int ddepth) { + return b.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GMul, , "org.opencv.core.math.mul") { + static GMatDesc outMeta(GMatDesc a, GMatDesc, double, int ddepth) { + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GMulCOld, , "org.opencv.core.math.mulCOld") { + static GMatDesc outMeta(GMatDesc a, double, int ddepth) { + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GMulC, , "org.opencv.core.math.mulC"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc, int ddepth) { + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GMulS, , "org.opencv.core.math.muls") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a; + } + }; // FIXME: Merge with MulC + + G_TYPED_KERNEL(GDiv, , "org.opencv.core.math.div") { + static GMatDesc outMeta(GMatDesc a, GMatDesc b, double, int ddepth) { + if (ddepth == -1) + { + GAPI_Assert(a.depth == b.depth); + return b; + } + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GDivC, , "org.opencv.core.math.divC") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc, double, int ddepth) { + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GDivRC, , "org.opencv.core.math.divRC") { + static GMatDesc outMeta(GScalarDesc, GMatDesc b, double, int ddepth) { + return b.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GMean, , "org.opencv.core.math.mean") { + static GScalarDesc outMeta(GMatDesc) { + return empty_scalar_desc(); + } + }; + + G_TYPED_KERNEL_M(GPolarToCart, , "org.opencv.core.math.polarToCart") { + static std::tuple outMeta(GMatDesc, GMatDesc a, bool) { + return std::make_tuple(a, a); + } + }; + + G_TYPED_KERNEL_M(GCartToPolar, , "org.opencv.core.math.cartToPolar") { + static std::tuple outMeta(GMatDesc x, GMatDesc, bool) { + return std::make_tuple(x, x); + } + }; + + G_TYPED_KERNEL(GMask, , "org.opencv.core.pixelwise.mask") { + static GMatDesc outMeta(GMatDesc in, GMatDesc) { + return in; + } + }; + + G_TYPED_KERNEL(GCmpGT, , "org.opencv.core.pixelwise.compare.cmpGT") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpGE, , "org.opencv.core.pixelwise.compare.cmpGE") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpLE, , "org.opencv.core.pixelwise.compare.cmpLE") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpLT, , "org.opencv.core.pixelwise.compare.cmpLT") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpEQ, , "org.opencv.core.pixelwise.compare.cmpEQ") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpNE, , "org.opencv.core.pixelwise.compare.cmpNE") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpGTScalar, , "org.opencv.core.pixelwise.compare.cmpGTScalar"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpGEScalar, , "org.opencv.core.pixelwise.compare.cmpGEScalar"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpLEScalar, , "org.opencv.core.pixelwise.compare.cmpLEScalar"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpLTScalar, , "org.opencv.core.pixelwise.compare.cmpLTScalar"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpEQScalar, , "org.opencv.core.pixelwise.compare.cmpEQScalar"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GCmpNEScalar, , "org.opencv.core.pixelwise.compare.cmpNEScalar"){ + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a.withDepth(CV_8U); + } + }; + + G_TYPED_KERNEL(GAnd, , "org.opencv.core.pixelwise.bitwise_and") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GAndS, , "org.opencv.core.pixelwise.bitwise_andS") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GOr, , "org.opencv.core.pixelwise.bitwise_or") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GOrS, , "org.opencv.core.pixelwise.bitwise_orS") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GXor, , "org.opencv.core.pixelwise.bitwise_xor") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GXorS, , "org.opencv.core.pixelwise.bitwise_xorS") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GNot, , "org.opencv.core.pixelwise.bitwise_not") { + static GMatDesc outMeta(GMatDesc a) { + return a; + } + }; + + G_TYPED_KERNEL(GSelect, , "org.opencv.core.pixelwise.select") { + static GMatDesc outMeta(GMatDesc a, GMatDesc, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GMin, , "org.opencv.core.matrixop.min") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GMax, , "org.opencv.core.matrixop.max") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GAbsDiff, , "org.opencv.core.matrixop.absdiff") { + static GMatDesc outMeta(GMatDesc a, GMatDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GAbsDiffC, , "org.opencv.core.matrixop.absdiffC") { + static GMatDesc outMeta(GMatDesc a, GScalarDesc) { + return a; + } + }; + + G_TYPED_KERNEL(GSum, , "org.opencv.core.matrixop.sum") { + static GScalarDesc outMeta(GMatDesc) { + return empty_scalar_desc(); + } + }; + + G_TYPED_KERNEL(GAddW, , "org.opencv.core.matrixop.addweighted") { + static GMatDesc outMeta(GMatDesc a, double, GMatDesc b, double, double, int ddepth) { + if (ddepth == -1) + { + // OpenCV: When the input arrays in add/subtract/multiply/divide + // functions have different depths, the output array depth must be + // explicitly specified! + // See artim_op() @ arithm.cpp + GAPI_Assert(a.chan == b.chan); + GAPI_Assert(a.depth == b.depth); + return a; + } + return a.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GNormL1, , "org.opencv.core.matrixop.norml1") { + static GScalarDesc outMeta(GMatDesc) { + return empty_scalar_desc(); + } + }; + + G_TYPED_KERNEL(GNormL2, , "org.opencv.core.matrixop.norml2") { + static GScalarDesc outMeta(GMatDesc) { + return empty_scalar_desc(); + } + }; + + G_TYPED_KERNEL(GNormInf, , "org.opencv.core.matrixop.norminf") { + static GScalarDesc outMeta(GMatDesc) { + return empty_scalar_desc(); + } + }; + + G_TYPED_KERNEL_M(GIntegral, , "org.opencv.core.matrixop.integral") { + static std::tuple outMeta(GMatDesc in, int sd, int sqd) { + return std::make_tuple(in.withSizeDelta(1,1).withDepth(sd), + in.withSizeDelta(1,1).withDepth(sqd)); + } + }; + + G_TYPED_KERNEL(GThreshold, , "org.opencv.core.matrixop.threshold") { + static GMatDesc outMeta(GMatDesc in, GScalarDesc, GScalarDesc, int) { + return in; + } + }; + + + G_TYPED_KERNEL_M(GThresholdOT, , "org.opencv.core.matrixop.thresholdOT") { + static std::tuple outMeta(GMatDesc in, GScalarDesc, int) { + return std::make_tuple(in, empty_scalar_desc()); + } + }; + + G_TYPED_KERNEL(GInRange, , "org.opencv.core.matrixop.inrange") { + static GMatDesc outMeta(GMatDesc in, GScalarDesc, GScalarDesc) { + return in.withType(CV_8U, 1); + } + }; + + G_TYPED_KERNEL_M(GSplit3, , "org.opencv.core.transform.split3") { + static std::tuple outMeta(GMatDesc in) { + const auto out_depth = in.depth; + const auto out_desc = in.withType(out_depth, 1); + return std::make_tuple(out_desc, out_desc, out_desc); + } + }; + + G_TYPED_KERNEL_M(GSplit4, ,"org.opencv.core.transform.split4") { + static std::tuple outMeta(GMatDesc in) { + const auto out_depth = in.depth; + const auto out_desc = in.withType(out_depth, 1); + return std::make_tuple(out_desc, out_desc, out_desc, out_desc); + } + }; + + G_TYPED_KERNEL(GResize, , "org.opencv.core.transform.resize") { + static GMatDesc outMeta(GMatDesc in, Size sz, double fx, double fy, int) { + if (sz.width != 0 && sz.height != 0) + { + return in.withSize(sz); + } + else + { + GAPI_Assert(fx != 0. && fy != 0.); + return in.withSize + (Size(static_cast(std::round(in.size.width * fx)), + static_cast(std::round(in.size.height * fy)))); + } + } + }; + + G_TYPED_KERNEL(GMerge3, , "org.opencv.core.transform.merge3") { + static GMatDesc outMeta(GMatDesc in, GMatDesc, GMatDesc) { + // Preserve depth and add channel component + return in.withType(in.depth, 3); + } + }; + + G_TYPED_KERNEL(GMerge4, , "org.opencv.core.transform.merge4") { + static GMatDesc outMeta(GMatDesc in, GMatDesc, GMatDesc, GMatDesc) { + // Preserve depth and add channel component + return in.withType(in.depth, 4); + } + }; + + G_TYPED_KERNEL(GRemap, , "org.opencv.core.transform.remap") { + static GMatDesc outMeta(GMatDesc in, Mat m1, Mat, int, int, Scalar) { + return in.withSize(m1.size()); + } + }; + + G_TYPED_KERNEL(GFlip, , "org.opencv.core.transform.flip") { + static GMatDesc outMeta(GMatDesc in, int) { + return in; + } + }; + + G_TYPED_KERNEL(GCrop, , "org.opencv.core.transform.crop") { + static GMatDesc outMeta(GMatDesc in, Rect rc) { + return in.withSize(Size(rc.width, rc.height)); + } + }; + + G_TYPED_KERNEL(GConcatHor, , "org.opencv.imgproc.transform.concatHor") { + static GMatDesc outMeta(GMatDesc l, GMatDesc r) { + return l.withSizeDelta(+r.size.width, 0); + } + }; + + G_TYPED_KERNEL(GConcatVert, , "org.opencv.imgproc.transform.concatVert") { + static GMatDesc outMeta(GMatDesc t, GMatDesc b) { + return t.withSizeDelta(0, +b.size.height); + } + }; + + G_TYPED_KERNEL(GLUT, , "org.opencv.core.transform.LUT") { + static GMatDesc outMeta(GMatDesc in, Mat) { + return in; + } + }; + + G_TYPED_KERNEL(GConvertTo, , "org.opencv.core.transform.convertTo") { + static GMatDesc outMeta(GMatDesc in, int rdepth, double, double) { + return rdepth < 0 ? in : in.withDepth(rdepth); + } + }; +} + +//! @addtogroup gapi_math +//! @{ + +/** @brief Calculates the per-element sum of two matrices. + +The function add calculates sum of two matrices of the same size and the same number of channels: +\f[\texttt{dst}(I) = \texttt{saturate} ( \texttt{src1}(I) + \texttt{src2}(I)) \quad \texttt{if mask}(I) \ne0\f] + +The function can be replaced with matrix expressions: + \f[\texttt{dst} = \texttt{src1} + \texttt{src2}\f] + +The input matrices and the output matrix can all have the same or different depths. For example, you +can add a 16-bit unsigned matrix to a 8-bit signed matrix and store the sum as a 32-bit +floating-point matrix. Depth of the output matrix is determined by the ddepth parameter. +If src1.depth() == src2.depth(), ddepth can be set to the default -1. In this case, the output matrix will have +the same depth as the input matrices. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.add" +@param src1 first input matrix. +@param src2 second input matrix. +@param ddepth optional depth of the output matrix. +@sa sub, addWeighted +*/ +GAPI_EXPORTS GMat add(const GMat& src1, const GMat& src2, int ddepth = -1); + +/** @brief Calculates the per-element sum of matrix and given scalar. + +The function addC adds a given scalar value to each element of given matrix. +The function can be replaced with matrix expressions: + + \f[\texttt{dst} = \texttt{src1} + \texttt{c}\f] + +Depth of the output matrix is determined by the ddepth parameter. +If ddepth is set to default -1, the depth of output matrix will be the same as the depth of input matrix. +The matrices can be single or multi channel. Output matrix must have the same size and number of channels as the input matrix. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.addC" +@param src1 first input matrix. +@param c scalar value to be added. +@param ddepth optional depth of the output matrix. +@sa sub, addWeighted +*/ +GAPI_EXPORTS GMat addC(const GMat& src1, const GScalar& c, int ddepth = -1); +//! @overload +GAPI_EXPORTS GMat addC(const GScalar& c, const GMat& src1, int ddepth = -1); + +/** @brief Calculates the per-element difference between two matrices. + +The function sub calculates difference between two matrices, when both matrices have the same size and the same number of +channels: + \f[\texttt{dst}(I) = \texttt{src1}(I) - \texttt{src2}(I)\f] + +The function can be replaced with matrix expressions: +\f[\texttt{dst} = \texttt{src1} - \texttt{src2}\f] + +The input matrices and the output matrix can all have the same or different depths. For example, you +can subtract two 8-bit unsigned matrices store the result as a 16-bit signed matrix. +Depth of the output matrix is determined by the ddepth parameter. +If src1.depth() == src2.depth(), ddepth can be set to the default -1. In this case, the output matrix will have +the same depth as the input matrices. The matrices can be single or multi channel. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.sub" +@param src1 first input matrix. +@param src2 second input matrix. +@param ddepth optional depth of the output matrix. +@sa add, addC + */ +GAPI_EXPORTS GMat sub(const GMat& src1, const GMat& src2, int ddepth = -1); + +/** @brief Calculates the per-element difference between matrix and given scalar. + +The function can be replaced with matrix expressions: + \f[\texttt{dst} = \texttt{src} - \texttt{c}\f] + +Depth of the output matrix is determined by the ddepth parameter. +If ddepth is set to default -1, the depth of output matrix will be the same as the depth of input matrix. +The matrices can be single or multi channel. Output matrix must have the same size as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.subC" +@param src first input matrix. +@param c scalar value to subtracted. +@param ddepth optional depth of the output matrix. +@sa add, addC, subRC + */ +GAPI_EXPORTS GMat subC(const GMat& src, const GScalar& c, int ddepth = -1); + +/** @brief Calculates the per-element difference between given scalar and the matrix. + +The function can be replaced with matrix expressions: + \f[\texttt{dst} = \texttt{val} - \texttt{src}\f] + +Depth of the output matrix is determined by the ddepth parameter. +If ddepth is set to default -1, the depth of output matrix will be the same as the depth of input matrix. +The matrices can be single or multi channel. Output matrix must have the same size as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.subRC" +@param c scalar value to subtract from. +@param src input matrix to be subtracted. +@param ddepth optional depth of the output matrix. +@sa add, addC, subC + */ +GAPI_EXPORTS GMat subRC(const GScalar& c, const GMat& src, int ddepth = -1); + +/** @brief Calculates the per-element scaled product of two matrices. + +The function mul calculates the per-element product of two matrices: + +\f[\texttt{dst} (I)= \texttt{saturate} ( \texttt{scale} \cdot \texttt{src1} (I) \cdot \texttt{src2} (I))\f] + +If src1.depth() == src2.depth(), ddepth can be set to the default -1. In this case, the output matrix will have +the same depth as the input matrices. The matrices can be single or multi channel. +Output matrix must have the same size as input matrices. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.mul" +@param src1 first input matrix. +@param src2 second input matrix of the same size and the same depth as src1. +@param scale optional scale factor. +@param ddepth optional depth of the output matrix. +@sa add, sub, div, addWeighted +*/ +GAPI_EXPORTS GMat mul(const GMat& src1, const GMat& src2, double scale, int ddepth = -1); + +/** @brief Multiplies matrix by scalar. + +The function mulC multiplies each element of matrix src by given scalar value: + +\f[\texttt{dst} (I)= \texttt{saturate} ( \texttt{src1} (I) \cdot \texttt{multiplier} )\f] + +The matrices can be single or multi channel. Output matrix must have the same size as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.mulC" +@param src input matrix. +@param multiplier factor to be multiplied. +@param ddepth optional depth of the output matrix. If -1, the depth of output matrix will be the same as input matrix depth. +@sa add, sub, div, addWeighted +*/ +GAPI_EXPORTS GMat mulC(const GMat& src, double multiplier, int ddepth = -1); +//! @overload +GAPI_EXPORTS GMat mulC(const GMat& src, const GScalar& multiplier, int ddepth = -1); // FIXME: merge with mulc +//! @overload +GAPI_EXPORTS GMat mulC(const GScalar& multiplier, const GMat& src, int ddepth = -1); // FIXME: merge with mulc + +/** @brief Performs per-element division of two matrices. + +The function divides one matrix by another: +\f[\texttt{dst(I) = saturate(src1(I)*scale/src2(I))}\f] + +When src2(I) is zero, dst(I) will also be zero. Different channels of +multi-channel matrices are processed independently. +The matrices can be single or multi channel. Output matrix must have the same size and depth as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.div" +@param src1 first input matrix. +@param src2 second input matrix of the same size and depth as src1. +@param scale scalar factor. +@param ddepth optional depth of the output matrix; you can only pass -1 when src1.depth() == src2.depth(). +@sa mul, add, sub +*/ +GAPI_EXPORTS GMat div(const GMat& src1, const GMat& src2, double scale, int ddepth = -1); + +/** @brief Divides matrix by scalar. + +The function divC divides each element of matrix src by given scalar value: + +\f[\texttt{dst(I) = saturate(src(I)*scale/divisor)}\f] + +When divisor is zero, dst(I) will also be zero. Different channels of +multi-channel matrices are processed independently. +The matrices can be single or multi channel. Output matrix must have the same size and depth as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.divC" +@param src input matrix. +@param divisor number to be divided by. +@param ddepth optional depth of the output matrix. If -1, the depth of output matrix will be the same as input matrix depth. +@param scale scale factor. +@sa add, sub, div, addWeighted +*/ +GAPI_EXPORTS GMat divC(const GMat& src, const GScalar& divisor, double scale, int ddepth = -1); + +/** @brief Divides scalar by matrix. + +The function divRC divides given scalar by each element of matrix src and keep the division result in new matrix of the same size and type as src: + +\f[\texttt{dst(I) = saturate(divident*scale/src(I))}\f] + +When src(I) is zero, dst(I) will also be zero. Different channels of +multi-channel matrices are processed independently. +The matrices can be single or multi channel. Output matrix must have the same size and depth as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.divRC" +@param src input matrix. +@param divident number to be divided. +@param ddepth optional depth of the output matrix. If -1, the depth of output matrix will be the same as input matrix depth. +@param scale scale factor +@sa add, sub, div, addWeighted +*/ +GAPI_EXPORTS GMat divRC(const GScalar& divident, const GMat& src, double scale, int ddepth = -1); + +/** @brief Applies a mask to a matrix. + +The function mask set value from given matrix if the corresponding pixel value in mask matrix set to true, +and set the matrix value to 0 overwise. + +Supported src matrix data types are @ref CV_8UC1, @ref CV_16SC1, @ref CV_16UC1. Supported mask data type is @ref CV_8UC1. + +@note Function textual ID is "org.opencv.core.math.mask" +@param src input matrix. +@param mask input mask matrix. +*/ +GAPI_EXPORTS GMat mask(const GMat& src, const GMat& mask); + +/** @brief Calculates an average (mean) of matrix elements. + +The function mean calculates the mean value M of matrix elements, +independently for each channel, and return it. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.math.mean" +@param src input matrix. +*/ +GAPI_EXPORTS GScalar mean(const GMat& src); + +/** @brief Calculates x and y coordinates of 2D vectors from their magnitude and angle. + +The function polarToCart calculates the Cartesian coordinates of each 2D +vector represented by the corresponding elements of magnitude and angle: +\f[\begin{array}{l} \texttt{x} (I) = \texttt{magnitude} (I) \cos ( \texttt{angle} (I)) \\ \texttt{y} (I) = \texttt{magnitude} (I) \sin ( \texttt{angle} (I)) \\ \end{array}\f] + +The relative accuracy of the estimated coordinates is about 1e-6. + +First output is a matrix of x-coordinates of 2D vectors. +Second output is a matrix of y-coordinates of 2D vectors. +Both output must have the same size and depth as input matrices. + +@note Function textual ID is "org.opencv.core.math.polarToCart" + +@param magnitude input floating-point @ref CV_32FC1 matrix (1xN) of magnitudes of 2D vectors; +@param angle input floating-point @ref CV_32FC1 matrix (1xN) of angles of 2D vectors. +@param angleInDegrees when true, the input angles are measured in +degrees, otherwise, they are measured in radians. +@sa cartToPolar, exp, log, pow, sqrt +*/ +GAPI_EXPORTS std::tuple polarToCart(const GMat& magnitude, const GMat& angle, + bool angleInDegrees = false); + +/** @brief Calculates the magnitude and angle of 2D vectors. + +The function cartToPolar calculates either the magnitude, angle, or both +for every 2D vector (x(I),y(I)): +\f[\begin{array}{l} \texttt{magnitude} (I)= \sqrt{\texttt{x}(I)^2+\texttt{y}(I)^2} , \\ \texttt{angle} (I)= \texttt{atan2} ( \texttt{y} (I), \texttt{x} (I))[ \cdot180 / \pi ] \end{array}\f] + +The angles are calculated with accuracy about 0.3 degrees. For the point +(0,0), the angle is set to 0. + +First output is a matrix of magnitudes of the same size and depth as input x. +Second output is a matrix of angles that has the same size and depth as +x; the angles are measured in radians (from 0 to 2\*Pi) or in degrees (0 to 360 degrees). + +@note Function textual ID is "org.opencv.core.math.cartToPolar" + +@param x matrix of @ref CV_32FC1 x-coordinates. +@param y array of @ref CV_32FC1 y-coordinates. +@param angleInDegrees a flag, indicating whether the angles are measured +in radians (which is by default), or in degrees. +@sa polarToCart +*/ +GAPI_EXPORTS std::tuple cartToPolar(const GMat& x, const GMat& y, + bool angleInDegrees = false); +//! @} gapi_math +//! +//! @addtogroup gapi_pixelwise +//! @{ + +/** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are greater compare to elements in second. + +The function compares elements of two matrices src1 and src2 of the same size: + \f[\texttt{dst} (I) = \texttt{src1} (I) > \texttt{src2} (I)\f] + +When the comparison result is true, the corresponding element of output +array is set to 255. The comparison operations can be replaced with the +equivalent matrix expressions: +\f[\texttt{dst} = \texttt{src1} > \texttt{src2}\f] + +Output matrix of depth @ref CV_8U must have the same size and the same number of channels as + the input matrices/matrix. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpGT" +@param src1 first input matrix. +@param src2 second input matrix/scalar of the same depth as first input matrix. +@sa min, max, threshold, cmpLE, cmpGE, cmpLS +*/ +GAPI_EXPORTS GMat cmpGT(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpGTScalar" +*/ +GAPI_EXPORTS GMat cmpGT(const GMat& src1, const GScalar& src2); + +/** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are less than elements in second. + +The function compares elements of two matrices src1 and src2 of the same size: + \f[\texttt{dst} (I) = \texttt{src1} (I) < \texttt{src2} (I)\f] + +When the comparison result is true, the corresponding element of output +array is set to 255. The comparison operations can be replaced with the +equivalent matrix expressions: + \f[\texttt{dst} = \texttt{src1} < \texttt{src2}\f] + +Output matrix of depth @ref CV_8U must have the same size and the same number of channels as + the input matrices/matrix. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLT" +@param src1 first input matrix. +@param src2 second input matrix/scalar of the same depth as first input matrix. +@sa min, max, threshold, cmpLE, cmpGE, cmpGT +*/ +GAPI_EXPORTS GMat cmpLT(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLTScalar" +*/ +GAPI_EXPORTS GMat cmpLT(const GMat& src1, const GScalar& src2); + +/** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are greater or equal compare to elements in second. + +The function compares elements of two matrices src1 and src2 of the same size: + \f[\texttt{dst} (I) = \texttt{src1} (I) >= \texttt{src2} (I)\f] + +When the comparison result is true, the corresponding element of output +array is set to 255. The comparison operations can be replaced with the +equivalent matrix expressions: + \f[\texttt{dst} = \texttt{src1} >= \texttt{src2}\f] + +Output matrix of depth @ref CV_8U must have the same size and the same number of channels as + the input matrices. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpGE" +@param src1 first input matrix. +@param src2 second input matrix/scalar of the same depth as first input matrix. +@sa min, max, threshold, cmpLE, cmpGT, cmpLS +*/ +GAPI_EXPORTS GMat cmpGE(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLGEcalar" +*/ +GAPI_EXPORTS GMat cmpGE(const GMat& src1, const GScalar& src2); + +/** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are less or equal compare to elements in second. + +The function compares elements of two matrices src1 and src2 of the same size: + \f[\texttt{dst} (I) = \texttt{src1} (I) <= \texttt{src2} (I)\f] + +When the comparison result is true, the corresponding element of output +array is set to 255. The comparison operations can be replaced with the +equivalent matrix expressions: + \f[\texttt{dst} = \texttt{src1} <= \texttt{src2}\f] + +Output matrix of depth @ref CV_8U must have the same size and the same number of channels as + the input matrices. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLE" +@param src1 first input matrix. +@param src2 second input matrix/scalar of the same depth as first input matrix. +@sa min, max, threshold, cmpGT, cmpGE, cmpLS +*/ +GAPI_EXPORTS GMat cmpLE(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLEScalar" +*/ +GAPI_EXPORTS GMat cmpLE(const GMat& src1, const GScalar& src2); + +/** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are equal to elements in second. + +The function compares elements of two matrices src1 and src2 of the same size: + \f[\texttt{dst} (I) = \texttt{src1} (I) == \texttt{src2} (I)\f] + +When the comparison result is true, the corresponding element of output +array is set to 255. The comparison operations can be replaced with the +equivalent matrix expressions: + \f[\texttt{dst} = \texttt{src1} == \texttt{src2}\f] + +Output matrix of depth @ref CV_8U must have the same size and the same number of channels as + the input matrices. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpEQ" +@param src1 first input matrix. +@param src2 second input matrix/scalar of the same depth as first input matrix. +@sa min, max, threshold, cmpNE +*/ +GAPI_EXPORTS GMat cmpEQ(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpEQScalar" +*/ +GAPI_EXPORTS GMat cmpEQ(const GMat& src1, const GScalar& src2); + +/** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are not equal to elements in second. + +The function compares elements of two matrices src1 and src2 of the same size: + \f[\texttt{dst} (I) = \texttt{src1} (I) != \texttt{src2} (I)\f] + +When the comparison result is true, the corresponding element of output +array is set to 255. The comparison operations can be replaced with the +equivalent matrix expressions: + \f[\texttt{dst} = \texttt{src1} != \texttt{src2}\f] + +Output matrix of depth @ref CV_8U must have the same size and the same number of channels as + the input matrices. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpNE" +@param src1 first input matrix. +@param src2 second input matrix/scalar of the same depth as first input matrix. +@sa min, max, threshold, cmpEQ +*/ +GAPI_EXPORTS GMat cmpNE(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.cmpNEScalar" +*/ +GAPI_EXPORTS GMat cmpNE(const GMat& src1, const GScalar& src2); + +/** @brief computes bitwise conjunction of the two matrixes (src1 & src2) +Calculates the per-element bit-wise logical conjunction of two matrices of the same size. + +In case of floating-point matrices, their machine-specific bit +representations (usually IEEE754-compliant) are used for the operation. +In case of multi-channel matrices, each channel is processed +independently. Output matrix must have the same size and depth as the input +matrices. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.bitwise_and" + +@param src1 first input matrix. +@param src2 second input matrix. +*/ +GAPI_EXPORTS GMat bitwise_and(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.bitwise_andS" +@param src1 first input matrix. +@param src2 scalar, which will be per-lemenetly conjuncted with elements of src1. +*/ +GAPI_EXPORTS GMat bitwise_and(const GMat& src1, const GScalar& src2); + +/** @brief computes bitwise disjunction of the two matrixes (src1 | src2) +Calculates the per-element bit-wise logical disjunction of two matrices of the same size. + +In case of floating-point matrices, their machine-specific bit +representations (usually IEEE754-compliant) are used for the operation. +In case of multi-channel matrices, each channel is processed +independently. Output matrix must have the same size and depth as the input +matrices. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.bitwise_or" + +@param src1 first input matrix. +@param src2 second input matrix. +*/ +GAPI_EXPORTS GMat bitwise_or(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.bitwise_orS" +@param src1 first input matrix. +@param src2 scalar, which will be per-lemenetly disjuncted with elements of src1. +*/ +GAPI_EXPORTS GMat bitwise_or(const GMat& src1, const GScalar& src2); + + +/** @brief computes bitwise logical "exclusive or" of the two matrixes (src1 ^ src2) +Calculates the per-element bit-wise logical "exclusive or" of two matrices of the same size. + +In case of floating-point matrices, their machine-specific bit +representations (usually IEEE754-compliant) are used for the operation. +In case of multi-channel matrices, each channel is processed +independently. Output matrix must have the same size and depth as the input +matrices. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.bitwise_xor" + +@param src1 first input matrix. +@param src2 second input matrix. +*/ +GAPI_EXPORTS GMat bitwise_xor(const GMat& src1, const GMat& src2); +/** @overload +@note Function textual ID is "org.opencv.core.pixelwise.compare.bitwise_xorS" +@param src1 first input matrix. +@param src2 scalar, for which per-lemenet "logical or" operation on elements of src1 will be performed. +*/ +GAPI_EXPORTS GMat bitwise_xor(const GMat& src1, const GScalar& src2); + + +/** @brief Inverts every bit of an array. +The function bitwise_not calculates per-element bit-wise inversion of the input +matrix: +\f[\texttt{dst} (I) = \neg \texttt{src} (I)\f] + +In case of floating-point matrices, their machine-specific bit +representations (usually IEEE754-compliant) are used for the operation. +In case of multi-channel matrices, each channel is processed +independently. Output matrix must have the same size and depth as the input +matrix. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.bitwise_not" + +@param src input matrix. +*/ +GAPI_EXPORTS GMat bitwise_not(const GMat& src); + +/** @brief Select values from either first or second of input matrices by given mask. +The function set to the output matrix either the value from the first input matrix if corresponding value of mask matrix is 255, + or value from the second input matrix (if value of mask matrix set to 0). + +Input mask matrix must be of @ref CV_8UC1 type, two other inout matrices and output matrix should be of the same type. The size should +be the same for all input and output matrices. +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.pixelwise.select" + +@param src1 first input matrix. +@param src2 second input matrix. +@param mask mask input matrix. +*/ +GAPI_EXPORTS GMat select(const GMat& src1, const GMat& src2, const GMat& mask); + +//! @} gapi_pixelwise + + +//! @addtogroup gapi_matrixop +//! @{ +/** @brief Calculates per-element minimum of two matrices. + +The function min calculates the per-element minimum of two matrices of the same size, number of channels and depth: +\f[\texttt{dst} (I)= \min ( \texttt{src1} (I), \texttt{src2} (I))\f] + where I is a multi-dimensional index of matrix elements. In case of + multi-channel matrices, each channel is processed independently. +Output matrix must be of the same size and depth as src1. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.min" +@param src1 first input matrix. +@param src2 second input matrix of the same size and depth as src1. +@sa max, compareEqual, compareLess, compareLessEqual +*/ +GAPI_EXPORTS GMat min(const GMat& src1, const GMat& src2); + +/** @brief Calculates per-element maximum of two matrices. + +The function max calculates the per-element maximum of two matrices of the same size, number of channels and depth: +\f[\texttt{dst} (I)= \max ( \texttt{src1} (I), \texttt{src2} (I))\f] + where I is a multi-dimensional index of matrix elements. In case of + multi-channel matrices, each channel is processed independently. +Output matrix must be of the same size and depth as src1. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.max" +@param src1 first input matrix. +@param src2 second input matrix of the same size and depth as src1. +@sa min, compare, compareEqual, compareGreater, compareGreaterEqual +*/ +GAPI_EXPORTS GMat max(const GMat& src1, const GMat& src2); + +/** @brief Calculates the per-element absolute difference between two matrices. + +The function absDiff calculates absolute difference between two matrices of the same size and depth: + \f[\texttt{dst}(I) = \texttt{saturate} (| \texttt{src1}(I) - \texttt{src2}(I)|)\f] + where I is a multi-dimensional index of matrix elements. In case of + multi-channel matrices, each channel is processed independently. +Output matrix must have the same size and depth as input matrices. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.absdiff" +@param src1 first input matrix. +@param src2 second input matrix. +@sa abs +*/ +GAPI_EXPORTS GMat absDiff(const GMat& src1, const GMat& src2); + +/** @brief Calculates absolute value of matrix elements. + +The function abs calculates absolute difference between matrix elements and given scalar value: + \f[\texttt{dst}(I) = \texttt{saturate} (| \texttt{src1}(I) - \texttt{matC}(I)|)\f] + where matC is constructed from given scalar c and has the same sizes and depth as input matrix src. + +Output matrix must be of the same size and depth as src. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.absdiffC" +@param src input matrix. +@param c scalar to be subtracted. +@sa min, max +*/ +GAPI_EXPORTS GMat absDiffC(const GMat& src, const GScalar& c); + +/** @brief Calculates sum of all matrix elements. + +The function sum calculates sum of all matrix elements, independently for each channel. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.sum" +@param src input matrix. +@sa min, max +*/ +GAPI_EXPORTS GScalar sum(const GMat& src); + +/** @brief Calculates the weighted sum of two matrices. + +The function addWeighted calculates the weighted sum of two matrices as follows: +\f[\texttt{dst} (I)= \texttt{saturate} ( \texttt{src1} (I)* \texttt{alpha} + \texttt{src2} (I)* \texttt{beta} + \texttt{gamma} )\f] +where I is a multi-dimensional index of array elements. In case of multi-channel matrices, each +channel is processed independently. + +The function can be replaced with a matrix expression: + \f[\texttt{dst}(I) = \texttt{alpha} * \texttt{src1}(I) - \texttt{beta} * \texttt{src2}(I) + \texttt{gamma} \f] + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.addweighted" +@param src1 first input matrix. +@param alpha weight of the first matrix elements. +@param src2 second input matrix of the same size and channel number as src1. +@param beta weight of the second matrix elements. +@param gamma scalar added to each sum. +@param ddepth optional depth of the output matrix. +@sa add, sub +*/ +GAPI_EXPORTS GMat addWeighted(const GMat& src1, double alpha, const GMat& src2, double beta, double gamma, int ddepth = -1); + +/** @brief Calculates the absolute L1 norm of a matrix. + +This version of normL1 calculates the absolute L1 norm of src. + +As example for one array consider the function \f$r(x)= \begin{pmatrix} x \\ 1-x \end{pmatrix}, x \in [-1;1]\f$. +The \f$ L_{1} \f$ norm for the sample value \f$r(-1) = \begin{pmatrix} -1 \\ 2 \end{pmatrix}\f$ +is calculated as follows +\f{align*} + \| r(-1) \|_{L_1} &= |-1| + |2| = 3 \\ +\f} +and for \f$r(0.5) = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix}\f$ the calculation is +\f{align*} + \| r(0.5) \|_{L_1} &= |0.5| + |0.5| = 1 \\ +\f} + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.norml1" +@param src input matrix. +@sa normL2, normInf +*/ +GAPI_EXPORTS GScalar normL1(const GMat& src); + +/** @brief Calculates the absolute L2 norm of a matrix. + +This version of normL2 calculates the absolute L2 norm of src. + +As example for one array consider the function \f$r(x)= \begin{pmatrix} x \\ 1-x \end{pmatrix}, x \in [-1;1]\f$. +The \f$ L_{2} \f$ norm for the sample value \f$r(-1) = \begin{pmatrix} -1 \\ 2 \end{pmatrix}\f$ +is calculated as follows +\f{align*} + \| r(-1) \|_{L_2} &= \sqrt{(-1)^{2} + (2)^{2}} = \sqrt{5} \\ +\f} +and for \f$r(0.5) = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix}\f$ the calculation is +\f{align*} + \| r(0.5) \|_{L_2} &= \sqrt{(0.5)^{2} + (0.5)^{2}} = \sqrt{0.5} \\ +\f} + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. +@note Function textual ID is "org.opencv.core.matrixop.norml2" +@param src input matrix. +@sa normL1, normInf +*/ +GAPI_EXPORTS GScalar normL2(const GMat& src); + +/** @brief Calculates the absolute infinite norm of a matrix. + +This version of normInf calculates the absolute infinite norm of src. + +As example for one array consider the function \f$r(x)= \begin{pmatrix} x \\ 1-x \end{pmatrix}, x \in [-1;1]\f$. +The \f$ L_{\infty} \f$ norm for the sample value \f$r(-1) = \begin{pmatrix} -1 \\ 2 \end{pmatrix}\f$ +is calculated as follows +\f{align*} + \| r(-1) \|_{L_\infty} &= \max(|-1|,|2|) = 2 +\f} +and for \f$r(0.5) = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix}\f$ the calculation is +\f{align*} + \| r(0.5) \|_{L_\infty} &= \max(|0.5|,|0.5|) = 0.5. +\f} + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.core.matrixop.norminf" +@param src input matrix. +@sa normL1, normL2 +*/ +GAPI_EXPORTS GScalar normInf(const GMat& src); + +/** @brief Calculates the integral of an image. + +The function calculates one or more integral images for the source image as follows: + +\f[\texttt{sum} (X,Y) = \sum _{x integral(const GMat& src, int sdepth = -1, int sqdepth = -1); + +/** @brief Applies a fixed-level threshold to each matrix element. + +The function applies fixed-level thresholding to a single- or multiple-channel matrix. +The function is typically used to get a bi-level (binary) image out of a grayscale image ( cmp funtions could be also used for +this purpose) or for removing a noise, that is, filtering out pixels with too small or too large +values. There are several depths of thresholding supported by the function. They are determined by +depth parameter. + +Also, the special values cv::THRESH_OTSU or cv::THRESH_TRIANGLE may be combined with one of the +above values. In these cases, the function determines the optimal threshold value using the Otsu's +or Triangle algorithm and uses it instead of the specified thresh . The function returns the +computed threshold value in addititon to thresholded matrix. +The Otsu's and Triangle methods are implemented only for 8-bit matrices. + +Input image should be single channel only in case of cv::THRESH_OTSU or cv::THRESH_TRIANGLE flags. +Output matrix must be of the same size and depth as src. + +@note Function textual ID is "org.opencv.core.matrixop.threshold" + +@param src input matrix (@ref CV_8UC1, @ref CV_8UC3, or @ref CV_32FC1). +@param thresh threshold value. +@param maxval maximum value to use with the cv::THRESH_BINARY and cv::THRESH_BINARY_INV thresholding +depths. +@param depth thresholding depth (see the cv::ThresholdTypes). + +@sa min, max, cmpGT, cmpLE, cmpGE, cmpLS + */ +GAPI_EXPORTS GMat threshold(const GMat& src, const GScalar& thresh, const GScalar& maxval, int depth); +/** @overload +This function appicable for all threshold depths except CV_THRESH_OTSU and CV_THRESH_TRIANGLE +@note Function textual ID is "org.opencv.core.matrixop.thresholdOT" +*/ +GAPI_EXPORTS std::tuple threshold(const GMat& src, const GScalar& maxval, int depth); + +/** @brief Applies a range-level threshold to each matrix element. + +The function applies range-level thresholding to a single- or multiple-channel matrix. +It sets output pixel value to OxFF if the corresponding pixel value of input matrix is in specified range,or 0 otherwise. + +Input and output matrices must be CV_8UC1. + +@note Function textual ID is "org.opencv.core.matrixop.inRange" + +@param src input matrix (CV_8UC1). +@param threshLow lower boundary value. +@param threshUp upper boundary value. + +@sa threshold + */ +GAPI_EXPORTS GMat inRange(const GMat& src, const GScalar& threshLow, const GScalar& threshUp); + +//! @} gapi_matrixop + +//! @addtogroup gapi_transform +//! @{ +/** @brief Resizes an image. + +The function resizes the image src down to or up to the specified size. + +Output image size will have the size dsize (when dsize is non-zero) or the size computed from +src.size(), fx, and fy; the depth of output is the same as of src. + +If you want to resize src so that it fits the pre-created dst, +you may call the function as follows: +@code + // explicitly specify dsize=dst.size(); fx and fy will be computed from that. + resize(src, dst, dst.size(), 0, 0, interpolation); +@endcode +If you want to decimate the image by factor of 2 in each direction, you can call the function this +way: +@code + // specify fx and fy and let the function compute the destination image size. + resize(src, dst, Size(), 0.5, 0.5, interpolation); +@endcode +To shrink an image, it will generally look best with cv::INTER_AREA interpolation, whereas to +enlarge an image, it will generally look best with cv::INTER_CUBIC (slow) or cv::INTER_LINEAR +(faster but still looks OK). + +@note Function textual ID is "org.opencv.core.transform.resize" + +@param src input image. +@param dsize output image size; if it equals zero, it is computed as: + \f[\texttt{dsize = Size(round(fx*src.cols), round(fy*src.rows))}\f] + Either dsize or both fx and fy must be non-zero. +@param fx scale factor along the horizontal axis; when it equals 0, it is computed as +\f[\texttt{(double)dsize.width/src.cols}\f] +@param fy scale factor along the vertical axis; when it equals 0, it is computed as +\f[\texttt{(double)dsize.height/src.rows}\f] +@param interpolation interpolation method, see cv::InterpolationFlags + +@sa warpAffine, warpPerspective, remap + */ +GAPI_EXPORTS GMat resize(const GMat& src, const Size& dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR); + +/** @brief Creates one 3-channel (4-channel) matrix out of 3(4) single-channel ones. + +The function merges several matrices to make a single multi-channel matrix. That is, each +element of the output matrix will be a concatenation of the elements of the input matrices, where +elements of i-th input matrix are treated as mv[i].channels()-element vectors. +Input matrix must be of @ref CV_8UC3 (@ref CV_8UC4) type. + +The function split3/split4 does the reverse operation. + +@note Function textual ID for merge3 is "org.opencv.core.transform.merge3" +@note Function textual ID for merge4 is "org.opencv.core.transform.merge4" + +@param src1 first input matrix to be merged +@param src2 second input matrix to be merged +@param src3 third input matrix to be merged +@param src4 fourth input matrix to be merged +@sa split4, split3 +*/ +GAPI_EXPORTS GMat merge4(const GMat& src1, const GMat& src2, const GMat& src3, const GMat& src4); +GAPI_EXPORTS GMat merge3(const GMat& src1, const GMat& src2, const GMat& src3); + +/** @brief Divides a 3-channel (4-channel) matrix into 3(4) single-channel matrices. + +The function splits a 3-channel (4-channel) matrix into 3(4) single-channel matrices: +\f[\texttt{mv} [c](I) = \texttt{src} (I)_c\f] + +All output matrices must be in @ref CV_8UC1. + +@note Function textual for split3 ID is "org.opencv.core.transform.split3" +@note Function textual for split4 ID is "org.opencv.core.transform.split4" + +@param src input @ref CV_8UC4 (@ref CV_8UC3) matrix. +@sa merge3, merge4 +*/ +GAPI_EXPORTS std::tuple split4(const GMat& src); +GAPI_EXPORTS std::tuple split3(const GMat& src); + +/** @brief Applies a generic geometrical transformation to an image. + +The function remap transforms the source image using the specified map: + +\f[\texttt{dst} (x,y) = \texttt{src} (map_x(x,y),map_y(x,y))\f] + +where values of pixels with non-integer coordinates are computed using one of available +interpolation methods. \f$map_x\f$ and \f$map_y\f$ can be encoded as separate floating-point maps +in \f$map_1\f$ and \f$map_2\f$ respectively, or interleaved floating-point maps of \f$(x,y)\f$ in +\f$map_1\f$, or fixed-point maps created by using convertMaps. The reason you might want to +convert from floating to fixed-point representations of a map is that they can yield much faster +(\~2x) remapping operations. In the converted case, \f$map_1\f$ contains pairs (cvFloor(x), +cvFloor(y)) and \f$map_2\f$ contains indices in a table of interpolation coefficients. +Output image must be of the same size and depth as input one. + +@note Function textual ID is "org.opencv.core.transform.remap" + +@param src Source image. +@param map1 The first map of either (x,y) points or just x values having the type CV_16SC2, +CV_32FC1, or CV_32FC2. +@param map2 The second map of y values having the type CV_16UC1, CV_32FC1, or none (empty map +if map1 is (x,y) points), respectively. +@param interpolation Interpolation method (see cv::InterpolationFlags). The method INTER_AREA is +not supported by this function. +@param borderMode Pixel extrapolation method (see cv::BorderTypes). When +borderMode=BORDER_TRANSPARENT, it means that the pixels in the destination image that +corresponds to the "outliers" in the source image are not modified by the function. +@param borderValue Value used in case of a constant border. By default, it is 0. +@note +Due to current implementation limitations the size of an input and output images should be less than 32767x32767. + */ +GAPI_EXPORTS GMat remap(const GMat& src, const Mat& map1, const Mat& map2, + int interpolation, int borderMode = BORDER_CONSTANT, + const Scalar& borderValue = Scalar()); + +/** @brief Flips a 2D matrix around vertical, horizontal, or both axes. + +The function flips the matrix in one of three different ways (row +and column indices are 0-based): +\f[\texttt{dst} _{ij} = +\left\{ +\begin{array}{l l} +\texttt{src} _{\texttt{src.rows}-i-1,j} & if\; \texttt{flipCode} = 0 \\ +\texttt{src} _{i, \texttt{src.cols} -j-1} & if\; \texttt{flipCode} > 0 \\ +\texttt{src} _{ \texttt{src.rows} -i-1, \texttt{src.cols} -j-1} & if\; \texttt{flipCode} < 0 \\ +\end{array} +\right.\f] +The example scenarios of using the function are the following: +* Vertical flipping of the image (flipCode == 0) to switch between + top-left and bottom-left image origin. This is a typical operation + in video processing on Microsoft Windows\* OS. +* Horizontal flipping of the image with the subsequent horizontal + shift and absolute difference calculation to check for a + vertical-axis symmetry (flipCode \> 0). +* Simultaneous horizontal and vertical flipping of the image with + the subsequent shift and absolute difference calculation to check + for a central symmetry (flipCode \< 0). +* Reversing the order of point arrays (flipCode \> 0 or + flipCode == 0). +Output image must be of the same depth as input one, size should be correct for given flipCode. + +@note Function textual ID is "org.opencv.core.transform.flip" + +@param src input matrix. +@param flipCode a flag to specify how to flip the array; 0 means +flipping around the x-axis and positive value (for example, 1) means +flipping around y-axis. Negative value (for example, -1) means flipping +around both axes. +@sa remap +*/ +GAPI_EXPORTS GMat flip(const GMat& src, int flipCode); + +/** @brief Crops a 2D matrix. + +The function crops the matrix by given cv::Rect. + +Output matrix must be of the same depth as input one, size is specified by given rect size. + +@note Function textual ID is "org.opencv.core.transform.crop" + +@param src input matrix. +@param rect a rect to crop a matrix to +@sa resize +*/ +GAPI_EXPORTS GMat crop(const GMat& src, const Rect& rect); + +/** @brief Applies horizontal concatenation to given matrices. + +The function horizontally concatenates two GMat matrices (with the same number of rows). +@code{.cpp} + GMat A = { 1, 4, + 2, 5, + 3, 6 }; + GMat B = { 7, 10, + 8, 11, + 9, 12 }; + + GMat C = gapi::concatHor(A, B); + //C: + //[1, 4, 7, 10; + // 2, 5, 8, 11; + // 3, 6, 9, 12] +@endcode +Output matrix must the same number of rows and depth as the src1 and src2, and the sum of cols of the src1 and src2. +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.imgproc.transform.concatHor" + +@param src1 first input matrix to be considered for horizontal concatenation. +@param src2 second input matrix to be considered for horizontal concatenation. +@sa concatVert +*/ +GAPI_EXPORTS GMat concatHor(const GMat& src1, const GMat& src2); + +/** @overload +The function horizontally concatenates given number of GMat matrices (with the same number of columns). +Output matrix must the same number of columns and depth as the input matrices, and the sum of rows of input matrices. + +@param v vector of input matrices to be concatenated horizontally. +*/ +GAPI_EXPORTS GMat concatHor(const std::vector &v); + +/** @brief Applies vertical concatenation to given matrices. + +The function vertically concatenates two GMat matrices (with the same number of cols). + @code{.cpp} + GMat A = { 1, 7, + 2, 8, + 3, 9 }; + GMat B = { 4, 10, + 5, 11, + 6, 12 }; + + GMat C = gapi::concatVert(A, B); + //C: + //[1, 7; + // 2, 8; + // 3, 9; + // 4, 10; + // 5, 11; + // 6, 12] + @endcode + +Output matrix must the same number of cols and depth as the src1 and src2, and the sum of rows of the src1 and src2. +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. + +@note Function textual ID is "org.opencv.imgproc.transform.concatVert" + +@param src1 first input matrix to be considered for vertical concatenation. +@param src2 second input matrix to be considered for vertical concatenation. +@sa concatHor +*/ +GAPI_EXPORTS GMat concatVert(const GMat& src1, const GMat& src2); + +/** @overload +The function vertically concatenates given number of GMat matrices (with the same number of columns). +Output matrix must the same number of columns and depth as the input matrices, and the sum of rows of input matrices. + +@param v vector of input matrices to be concatenated vertically. +*/ +GAPI_EXPORTS GMat concatVert(const std::vector &v); + + +/** @brief Performs a look-up table transform of a matrix. + +The function LUT fills the output matrix with values from the look-up table. Indices of the entries +are taken from the input matrix. That is, the function processes each element of src as follows: +\f[\texttt{dst} (I) \leftarrow \texttt{lut(src(I))}\f] + +Supported matrix data types are @ref CV_8UC1. +Output is a matrix of the same size and number of channels as src, and the same depth as lut. + +@note Function textual ID is "org.opencv.core.transform.LUT" + +@param src input matrix of 8-bit elements. +@param lut look-up table of 256 elements; in case of multi-channel input array, the table should +either have a single channel (in this case the same table is used for all channels) or the same +number of channels as in the input matrix. +*/ +GAPI_EXPORTS GMat LUT(const GMat& src, const Mat& lut); + +/** @brief Performs a 3D look-up table transform of a multi-channel matrix. + +The function LUT3D fills the output matrix with values from the look-up table. Indices of the entries +are taken from the input matrix. Interpolation is applied for mapping 0-255 range values to 0-16 range of 3DLUT table. +The function processes each element of src as follows: +@code{.cpp} + dst[i][j][k] = lut3D[~src_r][~src_g][~src_b]; +@endcode +where ~ means approximation. +Output is a matrix of of @ref CV_8UC3. + +@note Function textual ID is "org.opencv.core.transform.LUT3D" + +@param src input matrix of @ref CV_8UC3. +@param lut3D look-up table 17x17x17 3-channel elements. +@param interpolation The depth of interpoolation to be used. +*/ +GAPI_EXPORTS GMat LUT3D(const GMat& src, const GMat& lut3D, int interpolation = INTER_NEAREST); + +/** @brief Converts a matrix to another data depth with optional scaling. + +The method converts source pixel values to the target data depth. saturate_cast\<\> is applied at +the end to avoid possible overflows: + +\f[m(x,y) = saturate \_ cast( \alpha (*this)(x,y) + \beta )\f] +Output matrix must be of the same size as input one. + +@note Function textual ID is "org.opencv.core.transform.convertTo" +@param src input matrix to be converted from. +@param rdepth desired output matrix depth or, rather, the depth since the number of channels are the +same as the input has; if rdepth is negative, the output matrix will have the same depth as the input. +@param alpha optional scale factor. +@param beta optional delta added to the scaled values. + */ +GAPI_EXPORTS GMat convertTo(const GMat& src, int rdepth, double alpha=1, double beta=0); +//! @} gapi_transform + +} //namespace gapi +} //namespace cv + +#endif //OPENCV_GAPI_CORE_HPP diff --git a/modules/gapi/include/opencv2/gapi/cpu/core.hpp b/modules/gapi/include/opencv2/gapi/cpu/core.hpp new file mode 100644 index 0000000000..ec76fe5d5a --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/cpu/core.hpp @@ -0,0 +1,27 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_CPU_CORE_API_HPP +#define OPENCV_GAPI_CPU_CORE_API_HPP + +#include // GKernelPackage +#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS + +namespace cv { +namespace gapi { +namespace core { +namespace cpu { + +GAPI_EXPORTS GKernelPackage kernels(); + +} // namespace cpu +} // namespace core +} // namespace gapi +} // namespace cv + + +#endif // OPENCV_GAPI_CPU_CORE_API_HPP diff --git a/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp b/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp new file mode 100644 index 0000000000..daaca263db --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp @@ -0,0 +1,236 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCPUKERNEL_HPP +#define OPENCV_GAPI_GCPUKERNEL_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include //to_ocv +#include //suppress_unused_warning + +// FIXME: namespace scheme for backends? +namespace cv { + +namespace gimpl +{ + // Forward-declare an internal class + class GCPUExecutable; +} // namespace gimpl + +namespace gapi +{ +namespace cpu +{ + GAPI_EXPORTS cv::gapi::GBackend backend(); +} // namespace cpu +} // namespace gapi + +// Represents arguments which are passed to a wrapped CPU function +// FIXME: put into detail? +class GAPI_EXPORTS GCPUContext +{ +public: + // Generic accessor API + template + const T& inArg(int input) { return m_args.at(input).get(); } + + // Syntax sugar + const cv::gapi::own::Mat& inMat(int input); + cv::gapi::own::Mat& outMatR(int output); // FIXME: Avoid cv::gapi::own::Mat m = ctx.outMatR() + + const cv::gapi::own::Scalar& inVal(int input); + cv::gapi::own::Scalar& outValR(int output); // FIXME: Avoid cv::gapi::own::Scalar s = ctx.outValR() + template std::vector& outVecR(int output) // FIXME: the same issue + { + return outVecRef(output).wref(); + } + +protected: + detail::VectorRef& outVecRef(int output); + + std::vector m_args; + + //FIXME: avoid conversion of arguments from internal representaion to OpenCV one on each call + //to OCV kernel. (This can be achieved by a two single time conversions in GCPUExecutable::run, + //once on enter for input and output arguments, and once before return for output arguments only + std::unordered_map m_results; + + friend class gimpl::GCPUExecutable; +}; + +class GAPI_EXPORTS GCPUKernel +{ +public: + // This function is kernel's execution entry point (does the processing work) + using F = std::function; + + GCPUKernel(); + explicit GCPUKernel(const F& f); + + void apply(GCPUContext &ctx); + +protected: + F m_f; +}; + +// FIXME: This is an ugly ad-hoc imlpementation. TODO: refactor + +namespace detail +{ +template struct get_in; +template<> struct get_in +{ + static cv::Mat get(GCPUContext &ctx, int idx) { return to_ocv(ctx.inMat(idx)); } +}; +template<> struct get_in +{ + static cv::Scalar get(GCPUContext &ctx, int idx) { return to_ocv(ctx.inVal(idx)); } +}; +template struct get_in > +{ + static const std::vector& get(GCPUContext &ctx, int idx) { return ctx.inArg(idx).rref(); } +}; +template struct get_in +{ + static T get(GCPUContext &ctx, int idx) { return ctx.inArg(idx); } +}; + +struct tracked_cv_mat{ + tracked_cv_mat(cv::gapi::own::Mat& m) : r{to_ocv(m)}, original_data{m.data} {} + cv::Mat r; + uchar* original_data; + + operator cv::Mat& (){ return r;} + void validate() const{ + if (r.data != original_data) + { + util::throw_error + (std::logic_error + ("OpenCV kernel output parameter was reallocated. \n" + "Incorrect meta data was provided ?")); + } + } +}; + +struct scalar_wrapper +{ + scalar_wrapper(cv::gapi::own::Scalar& s) : m_s{cv::gapi::own::to_ocv(s)}, m_org_s(s) {}; + operator cv::Scalar& () { return m_s; } + void writeBack() const { m_org_s = to_own(m_s); } + + cv::Scalar m_s; + cv::gapi::own::Scalar& m_org_s; +}; + +template +void postprocess(Outputs&... outs) +{ + struct + { + void operator()(tracked_cv_mat* bm) { bm->validate(); } + void operator()(scalar_wrapper* sw) { sw->writeBack(); } + void operator()(...) { } + + } validate; + //dummy array to unfold parameter pack + int dummy[] = { 0, (validate(&outs), 0)... }; + cv::util::suppress_unused_warning(dummy); +} + +template struct get_out; +template<> struct get_out +{ + static tracked_cv_mat get(GCPUContext &ctx, int idx) + { + auto& r = ctx.outMatR(idx); + return {r}; + } +}; +template<> struct get_out +{ + static scalar_wrapper get(GCPUContext &ctx, int idx) + { + auto& s = ctx.outValR(idx); + return {s}; + } +}; +template struct get_out> +{ + static std::vector& get(GCPUContext &ctx, int idx) + { + return ctx.outVecR(idx); + } +}; + +template +struct OCVCallHelper; + +// FIXME: probably can be simplified with std::apply or analogue. +template +struct OCVCallHelper, std::tuple > +{ + template + struct call_and_postprocess + { + template + static void call(Inputs&&... ins, Outputs&&... outs) + { + //not using a std::forward on outs is deliberate in order to + //cause compilation error, by tring to bind rvalue references to lvalue references + Impl::run(std::forward(ins)..., outs...); + + postprocess(outs...); + } + }; + + template + static void call_impl(GCPUContext &ctx, detail::Seq, detail::Seq) + { + //Make sure that OpenCV kernels do not reallocate memory for output parameters + //by comparing it's state (data ptr) before and after the call. + //This is done by converting each output Mat into tracked_cv_mat object, and binding + //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)...); + } + + static void call(GCPUContext &ctx) + { + call_impl(ctx, + typename detail::MkSeq::type(), + typename detail::MkSeq::type()); + } +}; + +} // namespace detail + +template +class GCPUKernelImpl: public detail::OCVCallHelper +{ + using P = detail::OCVCallHelper; + +public: + using API = K; + + static cv::gapi::GBackend backend() { return cv::gapi::cpu::backend(); } + static cv::GCPUKernel kernel() { return GCPUKernel(&P::call); } +}; + +#define GAPI_OCV_KERNEL(Name, API) struct Name: public cv::GCPUKernelImpl + +} // namespace cv + +#endif // OPENCV_GAPI_GCPUKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/cpu/imgproc.hpp b/modules/gapi/include/opencv2/gapi/cpu/imgproc.hpp new file mode 100644 index 0000000000..0b96db08ae --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/cpu/imgproc.hpp @@ -0,0 +1,27 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_CPU_IMGPROC_API_HPP +#define OPENCV_GAPI_CPU_IMGPROC_API_HPP + +#include // GAPI_EXPORTS +#include // GKernelPackage + +namespace cv { +namespace gapi { +namespace imgproc { +namespace cpu { + +GAPI_EXPORTS GKernelPackage kernels(); + +} // namespace cpu +} // namespace imgproc +} // namespace gapi +} // namespace cv + + +#endif // OPENCV_GAPI_CPU_IMGPROC_API_HPP diff --git a/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp b/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp new file mode 100644 index 0000000000..dbed85ac9e --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/fluid/gfluidbuffer.hpp @@ -0,0 +1,120 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_FLUID_BUFFER_HPP +#define OPENCV_GAPI_FLUID_BUFFER_HPP + +#include +#include // accumulate +#include // ostream +#include // uint8_t + +#include +#include + +#include "opencv2/gapi/util/optional.hpp" +#include "opencv2/gapi/own/scalar.hpp" + +namespace cv { +namespace gapi { +namespace fluid { + +struct Border +{ +#if 1 + Border(int _type, cv::Scalar _val) : type(_type), value(to_own(_val)) {}; +#endif + Border(int _type, cv::gapi::own::Scalar _val) : type(_type), value(_val) {}; + int type; + cv::gapi::own::Scalar value; +}; + +using BorderOpt = util::optional; + +bool operator == (const Border& b1, const Border& b2); + +class GAPI_EXPORTS Buffer; + +class GAPI_EXPORTS View +{ +public: + View() = default; + + const uint8_t* InLineB(int index) const; // -(w-1)/2...0...+(w-1)/2 for Filters + template const T* InLine(int i) const + { + const uint8_t* ptr = this->InLineB(i); + return reinterpret_cast(ptr); + } + + operator bool() const; + bool ready() const; + int length() const; + int y() const; + + GMatDesc meta() const; + + class GAPI_EXPORTS Priv; // internal use only + Priv& priv(); // internal use only + const Priv& priv() const; // internal use only + + View(Priv* p); + +private: + std::shared_ptr m_priv; +}; + +class GAPI_EXPORTS Buffer +{ +public: + // Default constructor (executable creation stage, + // all following initialization performed in Priv::init()) + Buffer(); + // Scratch constructor (user kernels) + Buffer(const cv::GMatDesc &desc); + + // Constructor for intermediate buffers (for tests) + Buffer(const cv::GMatDesc &desc, + int max_line_consumption, int border_size, + int skew, + int wlpi, + BorderOpt border); + // Constructor for in/out buffers (for tests) + Buffer(const cv::Mat &data, bool is_input); + + uint8_t* OutLineB(int index = 0); + template T* OutLine(int index = 0) + { + uint8_t* ptr = this->OutLineB(index); + return reinterpret_cast(ptr); + } + + int y() const; + + int linesReady() const; + void debug(std::ostream &os) const; + int length() const; + int lpi() const; // LPI for WRITER + + GMatDesc meta() const; + + View mkView(int lineConsumption, int borderSize, BorderOpt border, bool ownStorage); + + class GAPI_EXPORTS Priv; // internal use only + Priv& priv(); // internal use only + const Priv& priv() const; // internal use only + +private: + std::shared_ptr m_priv; + +}; + +} // namespace cv::gapi::fluid +} // namespace cv::gapi +} // namespace cv + +#endif // OPENCV_GAPI_FLUID_BUFFER_HPP diff --git a/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp b/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp new file mode 100644 index 0000000000..220b64187f --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp @@ -0,0 +1,283 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_FLUID_KERNEL_HPP +#define OPENCV_GAPI_FLUID_KERNEL_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +// FIXME: namespace scheme for backends? +namespace cv { + +namespace gapi +{ +namespace fluid +{ + GAPI_EXPORTS cv::gapi::GBackend backend(); +} // namespace flud +} // namespace gapi + +class GAPI_EXPORTS GFluidKernel +{ +public: + enum class Kind + { + Filter, + Resize + }; + + // This function is a generic "doWork" callback + using F = std::function &)>; + + // This function is a generic "initScratch" callback + using IS = std::function; + + // This function is a generic "resetScratch" callback + using RS = std::function; + + // This function describes kernel metadata inference rule. + using M = std::function; + + // This function is a generic "getBorder" callback (extracts border-related data from kernel's input parameters) + using B = std::function; + + // FIXME: move implementations out of header file + GFluidKernel() {} + GFluidKernel(int w, Kind k, int l, bool scratch, const F& f, const IS &is, const RS &rs, const B& b) + : m_window(w) + , m_kind(k) + , m_lpi(l) + , m_scratch(scratch) + , m_f(f) + , m_is(is) + , m_rs(rs) + , m_b(b) {} + + int m_window = -1; + Kind m_kind; + const int m_lpi = -1; + const bool m_scratch = false; + + const F m_f; + const IS m_is; + const RS m_rs; + const B m_b; +}; + +// FIXME!!! +// This is the temporary and experimental API +// which should be replaced by runtime roi-based scheduling +struct GFluidOutputRois +{ + std::vector rois; +}; + +namespace detail +{ +template<> struct CompileArgTag +{ + static const char* tag() { return "gapi.fluid.outputRois"; } +}; +} // namespace detail + +namespace detail +{ +template struct fluid_get_in; +template<> struct fluid_get_in +{ + static const cv::gapi::fluid::View& get(const cv::GArgs &in_args, int idx) + { + return in_args[idx].get(); + } +}; + +template<> struct fluid_get_in +{ + static const cv::Scalar get(const cv::GArgs &in_args, int idx) + { + return cv::gapi::own::to_ocv(in_args[idx].get()); + } +}; +template struct fluid_get_in +{ + static T get(const cv::GArgs &in_args, int idx) + { + return in_args[idx].get(); + } +}; + +template +struct scratch_helper; + +template +struct scratch_helper +{ + // Init + template + static void help_init_impl(const cv::GMetaArgs &metas, + const cv::GArgs &in_args, + gapi::fluid::Buffer &scratch_buf, + detail::Seq) + { + Impl::initScratch(get_in_meta(metas, in_args, IIs)..., scratch_buf); + } + + static void help_init(const cv::GMetaArgs &metas, + const cv::GArgs &in_args, + gapi::fluid::Buffer &b) + { + help_init_impl(metas, in_args, b, typename detail::MkSeq::type()); + } + + // Reset + static void help_reset(gapi::fluid::Buffer &b) + { + Impl::resetScratch(b); + } +}; + +template +struct scratch_helper +{ + static void help_init(const cv::GMetaArgs &, + const cv::GArgs &, + gapi::fluid::Buffer &) + { + GAPI_Assert(false); + } + static void help_reset(gapi::fluid::Buffer &) + { + GAPI_Assert(false); + } +}; + +template struct is_gmat_type +{ + static const constexpr bool value = std::is_same::value; +}; + +template +struct get_border_helper; + +template +struct get_border_helper +{ + template + static gapi::fluid::BorderOpt get_border_impl(const GMetaArgs &metas, + const cv::GArgs &in_args, + cv::detail::Seq) + { + return util::make_optional(Impl::getBorder(cv::detail::get_in_meta(metas, in_args, IIs)...)); + } + + static gapi::fluid::BorderOpt help(const GMetaArgs &metas, + const cv::GArgs &in_args) + { + return get_border_impl(metas, in_args, typename detail::MkSeq::type()); + } +}; + +template +struct get_border_helper +{ + static gapi::fluid::BorderOpt help(const cv::GMetaArgs &, + const cv::GArgs &) + { + return {}; + } +}; + +template +struct FluidCallHelper; + +template +struct FluidCallHelper, std::tuple, UseScratch> +{ + static_assert(all_satisfy::value, "return type must be GMat"); + + // Execution dispatcher //////////////////////////////////////////////////// + template + static void call_impl(const cv::GArgs &in_args, + const std::vector &out_bufs, + detail::Seq, + detail::Seq) + { + Impl::run(fluid_get_in::get(in_args, IIs)..., *out_bufs[OIs]...); + } + + static void call(const cv::GArgs &in_args, + const std::vector &out_bufs) + { + constexpr int numOuts = (sizeof...(Outs)) + (UseScratch ? 1 : 0); + call_impl(in_args, out_bufs, + typename detail::MkSeq::type(), + typename detail::MkSeq::type()); + } + + // Scratch buffer initialization dispatcher //////////////////////////////// + static void init_scratch(const GMetaArgs &metas, + const cv::GArgs &in_args, + gapi::fluid::Buffer &b) + { + scratch_helper::help_init(metas, in_args, b); + } + + // Scratch buffer reset dispatcher ///////////////////////////////////////// + static void reset_scratch(gapi::fluid::Buffer &scratch_buf) + { + scratch_helper::help_reset(scratch_buf); + } + + static gapi::fluid::BorderOpt getBorder(const GMetaArgs &metas, const cv::GArgs &in_args) + { + // User must provide "init" callback if Window != 1 + // TODO: move to constexpr if when we enable C++17 + constexpr bool callCustomGetBorder = (Impl::Window != 1); + return get_border_helper::help(metas, in_args); + } +}; +} // namespace detail + + +template +class GFluidKernelImpl +{ + static const int LPI = 1; + static const auto Kind = GFluidKernel::Kind::Filter; + using P = detail::FluidCallHelper; + +public: + using API = K; + + static GFluidKernel kernel() + { + // FIXME: call() and getOutMeta() needs to be renamed so it is clear these + // functions are internal wrappers, not user API + return GFluidKernel(Impl::Window, Impl::Kind, Impl::LPI, + UseScratch, + &P::call, &P::init_scratch, &P::reset_scratch, &P::getBorder); + } + + static cv::gapi::GBackend backend() { return cv::gapi::fluid::backend(); } +}; + +#define GAPI_FLUID_KERNEL(Name, API, Scratch) struct Name: public cv::GFluidKernelImpl + +} // namespace cv + +#endif // OPENCV_GAPI_GCPUKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/garg.hpp b/modules/gapi/include/opencv2/gapi/garg.hpp new file mode 100644 index 0000000000..26c0d0a069 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/garg.hpp @@ -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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GARG_HPP +#define OPENCV_GAPI_GARG_HPP + +#include +#include + +#include +#include "opencv2/gapi/own/mat.hpp" + +#include "opencv2/gapi/util/any.hpp" +#include "opencv2/gapi/util/variant.hpp" + +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/garray.hpp" +#include "opencv2/gapi/gtype_traits.hpp" +#include "opencv2/gapi/gmetaarg.hpp" +#include "opencv2/gapi/own/scalar.hpp" + +namespace cv { + +class GArg; + +namespace detail { + template + using is_garg = std::is_same::type>; +} + +// Parameter holder class for a node +// Depending on platform capabilities, can either support arbitrary types +// (as `boost::any`) or a limited number of types (as `boot::variant`). +// FIXME: put into "details" as a user shouldn't use it in his code +class GAPI_EXPORTS GArg +{ +public: + GArg() {} + + template::value, int>::type = 0> + explicit GArg(const T &t) + : kind(detail::GTypeTraits::kind) + , value(detail::wrap_gapi_helper::wrap(t)) + { + } + + template::value, int>::type = 0> + explicit GArg(T &&t) + : kind(detail::GTypeTraits::type>::kind) + , value(detail::wrap_gapi_helper::wrap(t)) + { + } + + template T& get() + { + return util::any_cast::type>(value); + } + + template const T& get() const + { + return util::any_cast::type>(value); + } + + detail::ArgKind kind = detail::ArgKind::OPAQUE; + +protected: + util::any value; +}; + +using GArgs = std::vector; + +// FIXME: Express as M::type +// FIXME: Move to a separate file! +using GRunArg = util::variant; +using GRunArgs = std::vector; + +using GRunArgP = util::variant; +using GRunArgsP = std::vector; + + +template inline GRunArgs gin(const Ts&... args) +{ + return GRunArgs{ GRunArg(detail::wrap_host_helper::wrap_in(args))... }; +} + +template inline GRunArgsP gout(Ts&... args) +{ + return GRunArgsP{ GRunArgP(detail::wrap_host_helper::wrap_out(args))... }; +} + +} // namespace cv + +#endif // OPENCV_GAPI_GARG_HPP diff --git a/modules/gapi/include/opencv2/gapi/garray.hpp b/modules/gapi/include/opencv2/gapi/garray.hpp new file mode 100644 index 0000000000..33d9b582e6 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/garray.hpp @@ -0,0 +1,239 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GARRAY_HPP +#define OPENCV_GAPI_GARRAY_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include "opencv2/gapi/own/assert.hpp" + +namespace cv +{ +// Forward declaration; GNode and GOrigin are an internal +// (user-inaccessible) classes. +class GNode; +struct GOrigin; + +template class GArray; + +struct GArrayDesc +{ + // FIXME: Body + // FIXME: Also implement proper operator== then + bool operator== (const GArrayDesc&) const { return true; } +}; +template GArrayDesc descr_of(const std::vector &) { return {};} +inline GArrayDesc empty_array_desc() {return {}; } +std::ostream& operator<<(std::ostream& os, const cv::GArrayDesc &desc); + +namespace detail +{ + // ConstructVec is a callback which stores information about T and is used by + // G-API runtime to construct arrays in host memory (T remains opaque for G-API). + // ConstructVec is carried into G-API internals by GArrayU. + // Currently it is suitable for Host (CPU) plugins only, real offload may require + // more information for manual memory allocation on-device. + class VectorRef; + using ConstructVec = std::function; + + + // This class strips type information from GArray and makes it usable + // in the G-API graph compiler (expression unrolling, graph generation, etc). + // Part of GProtoArg. + class GAPI_EXPORTS GArrayU + { + public: + GArrayU(const GNode &n, std::size_t out); // Operation result constructor + + GOrigin& priv(); // Internal use only + const GOrigin& priv() const; // Internal use only + + protected: + GArrayU(); // Default constructor + template friend class cv::GArray; // (avialable to GArray only) + + void setConstructFcn(ConstructVec &&cv); // Store T-aware constructor + + std::shared_ptr m_priv; + }; + + // This class represents a typed STL vector reference. + // Depending on origins, this reference may be either "just a" reference to + // an object created externally, OR actually own the underlying object + // (be value holder). + class BasicVectorRef + { + public: + std::size_t m_elemSize = 0ul; + cv::GArrayDesc m_desc; + virtual ~BasicVectorRef() {} + }; + + template class VectorRefT: public BasicVectorRef + { + using empty_t = util::monostate; + using ro_ext_t = const std::vector *; + using rw_ext_t = std::vector *; + using rw_own_t = std::vector ; + util::variant m_ref; + + inline bool isEmpty() const { return util::holds_alternative(m_ref); } + inline bool isROExt() const { return util::holds_alternative(m_ref); } + inline bool isRWExt() const { return util::holds_alternative(m_ref); } + inline bool isRWOwn() const { return util::holds_alternative(m_ref); } + + void init(const std::vector* vec = nullptr) + { + m_elemSize = sizeof(T); + if (vec) m_desc = cv::descr_of(*vec); + } + + public: + VectorRefT() { init(); } + virtual ~VectorRefT() {} + + explicit VectorRefT(const std::vector& vec) : m_ref(&vec) { init(&vec); } + explicit VectorRefT(std::vector& vec) : m_ref(&vec) { init(&vec); } + explicit VectorRefT(std::vector&& vec) : m_ref(std::move(vec)) { init(&vec); } + + // Reset a VectorRefT. Called only for objects instantiated + // internally in G-API (e.g. temporary GArray's within a + // computation). Reset here means both initialization + // (creating an object) and reset (discarding its existing + // content before the next execution). Must never be called + // for external VectorRefTs. + void reset() + { + if (isEmpty()) + { + std::vector empty_vector; + m_desc = cv::descr_of(empty_vector); + m_ref = std::move(empty_vector); + GAPI_Assert(isRWOwn()); + } + else if (isRWOwn()) + { + util::get(m_ref).clear(); + } + else GAPI_Assert(false); // shouldn't be called in *EXT modes + } + + // Obtain a WRITE reference to underlying object + // Used by CPU kernel API wrappers when a kernel execution frame + // is created + std::vector& wref() + { + GAPI_Assert(isRWExt() || isRWOwn()); + if (isRWExt()) return *util::get(m_ref); + if (isRWOwn()) return util::get(m_ref); + util::throw_error(std::logic_error("Impossible happened")); + } + + // Obtain a READ reference to underlying object + // Used by CPU kernel API wrappers when a kernel execution frame + // is created + const std::vector& rref() const + { + // ANY vector can be accessed for reading, even if it declared for + // output. Example -- a GComputation from [in] to [out1,out2] + // where [out2] is a result of operation applied to [out1]: + // + // GComputation boundary + // . . . . . . . + // . . + // [in] ----> foo() ----> [out1] + // . . : + // . . . .:. . . + // . V . + // . bar() ---> [out2] + // . . . . . . . . . . . . + // + if (isROExt()) return *util::get(m_ref); + if (isRWExt()) return *util::get(m_ref); + if (isRWOwn()) return util::get(m_ref); + util::throw_error(std::logic_error("Impossible happened")); + } + }; + + // This class strips type information from VectorRefT<> and makes it usable + // in the G-API executables (carrying run-time data/information to kernels). + // Part of GRunArg. + // Its methods are typed proxies to VectorRefT. + // VectorRef maintains "reference" semantics so two copies of VectoRef refer + // to the same underlying object. + // FIXME: Put a good explanation on why cv::OutputArray doesn't fit this role + class VectorRef + { + std::shared_ptr m_ref; + + template inline void check() const + { + GAPI_DbgAssert(dynamic_cast*>(m_ref.get()) != nullptr); + GAPI_Assert(sizeof(T) == m_ref->m_elemSize); + } + + public: + VectorRef() = default; + template explicit VectorRef(const std::vector& vec) : m_ref(new VectorRefT(vec)) {} + template explicit VectorRef(std::vector& vec) : m_ref(new VectorRefT(vec)) {} + template explicit VectorRef(std::vector&& vec) : m_ref(new VectorRefT(vec)) {} + + template void reset() + { + if (!m_ref) m_ref.reset(new VectorRefT()); + + check(); + static_cast&>(*m_ref).reset(); + } + + template std::vector& wref() + { + check(); + return static_cast&>(*m_ref).wref(); + } + + template const std::vector& rref() const + { + check(); + return static_cast&>(*m_ref).rref(); + } + + cv::GArrayDesc descr_of() const + { + return m_ref->m_desc; + } + }; +} // namespace detail + +template class GArray +{ +public: + GArray() { putDetails(); } // Empty constructor + explicit GArray(detail::GArrayU &&ref) // GArrayU-based constructor + : m_ref(ref) { putDetails(); } // (used by GCall, not for users) + + detail::GArrayU strip() const { return m_ref; } + +private: + static void VCTor(detail::VectorRef& vref) { vref.reset(); } + void putDetails() {m_ref.setConstructFcn(&VCTor); } + + detail::GArrayU m_ref; +}; + +} // namespace cv + +#endif // OPENCV_GAPI_GARRAY_HPP diff --git a/modules/gapi/include/opencv2/gapi/gcall.hpp b/modules/gapi/include/opencv2/gapi/gcall.hpp new file mode 100644 index 0000000000..baf4f44e26 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gcall.hpp @@ -0,0 +1,63 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCALL_HPP +#define OPENCV_GAPI_GCALL_HPP + +#include "opencv2/gapi/garg.hpp" // GArg +#include "opencv2/gapi/gmat.hpp" // GMat +#include "opencv2/gapi/gscalar.hpp" // GScalar +#include "opencv2/gapi/garray.hpp" // GArray + +namespace cv { + +struct GKernel; + +// The whole idea of this class is to represent an operation +// which is applied to arguments. This is part of public API, +// since it is what users should use to define kernel interfaces. + +class GAPI_EXPORTS GCall final +{ +public: + class Priv; + + explicit GCall(const GKernel &k); + ~GCall(); + + template + GCall& pass(Ts&&... args) + { + setArgs({cv::GArg(std::move(args))...}); + return *this; + } + + // A generic yield method - obtain a link to operator's particular GMat output + GMat yield (int output = 0); + GScalar yieldScalar(int output = 0); + + template GArray yieldArray(int output = 0) + { + return GArray(yieldArray(output)); + } + + // Internal use only + Priv& priv(); + const Priv& priv() const; + +protected: + std::shared_ptr m_priv; + + void setArgs(std::vector &&args); + + // Public version returns a typed array, this one is implementation detail + detail::GArrayU yieldArray(int output = 0); +}; + +} // namespace cv + +#endif // OPENCV_GAPI_GCALL_HPP diff --git a/modules/gapi/include/opencv2/gapi/gcommon.hpp b/modules/gapi/include/opencv2/gapi/gcommon.hpp new file mode 100644 index 0000000000..e84c82c9c8 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gcommon.hpp @@ -0,0 +1,118 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMMON_HPP +#define OPENCV_GAPI_GCOMMON_HPP + +#include // std::hash +#include // std::vector +#include // decay + +#include + +#include "opencv2/gapi/util/any.hpp" +#include "opencv2/gapi/own/exports.hpp" +#include "opencv2/gapi/own/assert.hpp" + +namespace cv { + +namespace detail +{ + // This is a trait-like structure to mark backend-specific compile arguments + // with tags + template struct CompileArgTag; + template struct CompileArgTag + { + static const char* tag() { return ""; }; + }; +} + +// This definition is here because it is reused by both public(?) and internal +// modules. Keeping it here wouldn't expose public details (e.g., API-level) +// to components which are internal and operate on a lower-level entities +// (e.g., compiler, backends). +// FIXME: merge with ArgKind? +// FIXME: replace with variant[format desc]? +enum class GShape: int +{ + GMAT, + GSCALAR, + GARRAY, +}; + +struct GCompileArg; + +namespace detail { + template + using is_compile_arg = std::is_same::type>; +} +// CompileArg is an unified interface over backend-specific compilation +// information +// FIXME: Move to a separate file? +struct GAPI_EXPORTS GCompileArg +{ +public: + std::string tag; + + // FIXME: use decay in GArg/other trait-based wrapper before leg is shot! + template::value, int>::type = 0> + explicit GCompileArg(T &&t) + : tag(detail::CompileArgTag::type>::tag()) + , arg(t) + { + } + + template T& get() + { + return util::any_cast(arg); + } + + template const T& get() const + { + return util::any_cast(arg); + } + +private: + util::any arg; +}; + +using GCompileArgs = std::vector; + +template GCompileArgs compile_args(Ts&&... args) +{ + return GCompileArgs{ GCompileArg(args)... }; +} + +struct graph_dump_path +{ + std::string m_dump_path; +}; + +namespace detail +{ + template<> struct CompileArgTag + { + static const char* tag() { return "gapi.graph_dump_path"; } + }; +} + +} // namespace cv + +// std::hash overload for GShape +namespace std +{ +template<> struct hash +{ + size_t operator() (cv::GShape sh) const + { + return std::hash()(static_cast(sh)); + } +}; +} // namespace std + + +#endif // OPENCV_GAPI_GCOMMON_HPP diff --git a/modules/gapi/include/opencv2/gapi/gcompiled.hpp b/modules/gapi/include/opencv2/gapi/gcompiled.hpp new file mode 100644 index 0000000000..05e32b12a8 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gcompiled.hpp @@ -0,0 +1,59 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMPILED_HPP +#define OPENCV_GAPI_GCOMPILED_HPP + +#include + +#include "opencv2/gapi/own/assert.hpp" +#include "opencv2/core/mat.hpp" + +#include "opencv2/gapi/garg.hpp" + +namespace cv { + +// This class represents a compiled computation. +// In theory (and ideally), it can be used w/o the rest of APIs. +// In theory (and ideally), it can be serialized/deserialized. +// It can enable scenarious like deployment to an autonomous devince, FuSa, etc. +// +// Currently GCompiled assumes all GMats you used to pass data to G-API +// are valid and not destroyed while you use a GCompiled object. +// +// FIXME: In future, there should be a way to name I/O objects and specify it +// to GCompiled externally (for example, when it is loaded on the target system). + +class GAPI_EXPORTS GCompiled +{ +public: + class GAPI_EXPORTS Priv; + + GCompiled(); + + void operator() (GRunArgs &&ins, GRunArgsP &&outs); // Generic arg-to-arg + void operator() (cv::Mat in, cv::Mat &out); // Unary overload + void operator() (cv::Mat in, cv::Scalar &out); // Unary overload (scalar) + void operator() (cv::Mat in1, cv::Mat in2, cv::Mat &out); // Binary overload + void operator() (cv::Mat in1, cv::Mat in2, cv::Scalar &out); // Binary overload (scalar) + void operator() (const std::vector &ins, // Compatibility overload + const std::vector &outs); + + Priv& priv(); + + explicit operator bool () const; // Check if GCompiled is runnable or empty + + const GMetaArgs& metas() const; // Meta passed to compile() + const GMetaArgs& outMetas() const; // Inferred output metadata + +protected: + std::shared_ptr m_priv; +}; + +} + +#endif // OPENCV_GAPI_GCOMPILED_HPP diff --git a/modules/gapi/include/opencv2/gapi/gcompoundkernel.hpp b/modules/gapi/include/opencv2/gapi/gcompoundkernel.hpp new file mode 100644 index 0000000000..c5ac8a7d24 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gcompoundkernel.hpp @@ -0,0 +1,123 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMPOUNDKERNEL_HPP +#define OPENCV_GAPI_GCOMPOUNDKERNEL_HPP + +#include +#include +#include +#include + +namespace cv { +namespace gapi +{ +namespace compound +{ + // FIXME User does not need to know about this function + // Needs that user may define compound kernels(as cpu kernels) + GAPI_EXPORTS cv::gapi::GBackend backend(); +} // namespace compound +} // namespace gapi + +namespace detail +{ + +struct GCompoundContext +{ + explicit GCompoundContext(const GArgs& in_args); + template + const T& inArg(int input) { return m_args.at(input).get(); } + + GArgs m_args; + GArgs m_results; +}; + +class GAPI_EXPORTS GCompoundKernel +{ +// Compound kernel must use all of it's inputs +public: + using F = std::function; + + explicit GCompoundKernel(const F& f); + void apply(GCompoundContext& ctx); + +protected: + F m_f; +}; + +template struct get_compound_in +{ + static T get(GCompoundContext &ctx, int idx) { return ctx.inArg(idx); } +}; + +template struct get_compound_in> +{ + static cv::GArray get(GCompoundContext &ctx, int idx) + { + auto array = cv::GArray(); + ctx.m_args[idx] = GArg(array); + return array; + } +}; + +// 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 +struct tuple_wrap_helper; + +template struct tuple_wrap_helper +{ + static std::tuple get(T&& obj) { return std::make_tuple(std::move(obj)); } +}; + +template +struct tuple_wrap_helper> +{ + static std::tuple get(std::tuple&& objs) { return objs; } +}; + +template +struct GCompoundCallHelper; + +template +struct GCompoundCallHelper, std::tuple > +{ + template + static void expand_impl(GCompoundContext &ctx, detail::Seq, detail::Seq) + { + auto result = Impl::expand(get_compound_in::get(ctx, IIs)...); + auto tuple_return = tuple_wrap_helper::get(std::move(result)); + ctx.m_results = { cv::GArg(std::get(tuple_return))... }; + } + + static void expand(GCompoundContext &ctx) + { + expand_impl(ctx, + typename detail::MkSeq::type(), + typename detail::MkSeq::type()); + } +}; + +template +class GCompoundKernelImpl: public cv::detail::GCompoundCallHelper +{ + using P = cv::detail::GCompoundCallHelper; + +public: + using API = K; + + static cv::gapi::GBackend backend() { return cv::gapi::compound::backend(); } + static GCompoundKernel kernel() { return GCompoundKernel(&P::expand); } +}; + +} // namespace detail +#define GAPI_COMPOUND_KERNEL(Name, API) struct Name: public cv::detail::GCompoundKernelImpl + +} // namespace cv + +#endif // OPENCV_GAPI_GCOMPOUNDKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/gcomputation.hpp b/modules/gapi/include/opencv2/gapi/gcomputation.hpp new file mode 100644 index 0000000000..d5d5fd678e --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gcomputation.hpp @@ -0,0 +1,135 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMPUTATION_HPP +#define OPENCV_GAPI_GCOMPUTATION_HPP + +#include + +#include "opencv2/gapi/util/util.hpp" +#include "opencv2/gapi/gcommon.hpp" +#include "opencv2/gapi/gproto.hpp" +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gcompiled.hpp" + +namespace cv { + +namespace detail +{ + // FIXME: move to algorithm, cover with separate tests + // FIXME: replace with O(1) version (both memory and compilation time) + template + struct last_type; + + template + struct last_type { using type = T;}; + + template + struct last_type { using type = typename last_type::type; }; + + template + using last_type_t = typename last_type::type; +} + +class GAPI_EXPORTS GComputation +{ +public: + class Priv; + typedef std::function Generator; + + // Various constructors enable different ways to define a computation: ///// + // 1. Generic constructors + GComputation(const Generator& gen); // Generator overload + GComputation(GProtoInputArgs &&ins, + GProtoOutputArgs &&outs); // Arg-to-arg overload + + // 2. Syntax sugar and compatibility overloads + GComputation(GMat in, GMat out); // Unary overload + GComputation(GMat in, GScalar out); // Unary overload (scalar) + GComputation(GMat in1, GMat in2, GMat out); // Binary overload + GComputation(GMat in1, GMat in2, GScalar out); // Binary overload (scalar) + GComputation(const std::vector &ins, // Compatibility overload + const std::vector &outs); + + // Various versions of apply(): //////////////////////////////////////////// + // 1. Generic apply() + void apply(GRunArgs &&ins, GRunArgsP &&outs, GCompileArgs &&args = {}); // Arg-to-arg overload + + // 2. Syntax sugar and compatibility overloads + void apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args = {}); // Unary overload + void apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args = {}); // Unary overload (scalar) + void apply(cv::Mat in1, cv::Mat in2, cv::Mat &out, GCompileArgs &&args = {}); // Binary overload + void apply(cv::Mat in1, cv::Mat in2, cv::Scalar &out, GCompileArgs &&args = {}); // Binary overload (scalar) + void apply(const std::vector& ins, // Compatibility overload + const std::vector& outs, + GCompileArgs &&args = {}); + + // Various versions of compile(): ////////////////////////////////////////// + // 1. Generic compile() - requires metas to be passed as vector + GCompiled compile(GMetaArgs &&in_metas, GCompileArgs &&args = {}); + + // 2. Syntax sugar - variadic list of metas, no extra compile args + template + auto compile(const Ts&... metas) -> + typename std::enable_if::value, GCompiled>::type + { + return compile(GMetaArgs{GMetaArg(metas)...}, GCompileArgs()); + } + + // 3. Syntax sugar - variadic list of metas, extra compile args + // (seems optional parameters don't work well when there's an variadic template + // comes first) + // + // Ideally it should look like: + // + // template + // GCompiled compile(const Ts&... metas, GCompileArgs &&args) + // + // But not all compilers can hande this (and seems they shouldn't be able to). + template + auto compile(const Ts&... meta_and_compile_args) -> + typename std::enable_if::value + && std::is_same >::value, + GCompiled>::type + { + //FIXME: wrapping meta_and_compile_args into a tuple to unwrap them inside a helper function is the overkill + return compile(std::make_tuple(meta_and_compile_args...), + typename detail::MkSeq::type()); + } + + // Internal use only + Priv& priv(); + const Priv& priv() const; + +protected: + + // 4. Helper method for (3) + template + GCompiled compile(const std::tuple &meta_and_compile_args, detail::Seq) + { + GMetaArgs meta_args = {GMetaArg(std::get(meta_and_compile_args))...}; + GCompileArgs comp_args = std::get(meta_and_compile_args); + return compile(std::move(meta_args), std::move(comp_args)); + } + + std::shared_ptr m_priv; +}; + +namespace gapi +{ + // Declare an Island tagged with `name` and defined from `ins` to `outs` + // (exclusively, as ins/outs are data objects, and regioning is done on + // operations level). + // Throws if any operation between `ins` and `outs` are already assigned + // to another island. + void GAPI_EXPORTS island(const std::string &name, + GProtoInputArgs &&ins, + GProtoOutputArgs &&outs); +} // namespace gapi + +} // namespace cv +#endif // OPENCV_GAPI_GCOMPUTATION_HPP diff --git a/modules/gapi/include/opencv2/gapi/gkernel.hpp b/modules/gapi/include/opencv2/gapi/gkernel.hpp new file mode 100644 index 0000000000..36030f5c02 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gkernel.hpp @@ -0,0 +1,409 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GKERNEL_HPP +#define OPENCV_GAPI_GKERNEL_HPP + +#include +#include +#include // map (for GKernelPackage) +#include // lookup order +#include // string +#include // tuple +#include // false_type, true_type + +#include // CompileArgTag +#include // Seq +#include +#include // GArg +#include // GMetaArg +#include // GTypeTraits +#include //suppress_unused_warning + + +namespace cv { + +using GShapes = std::vector; + +// GKernel describes kernel API to the system +// FIXME: add attributes of a kernel, (e.g. number and types +// of inputs, etc) +struct GAPI_EXPORTS GKernel +{ + using M = std::function; + + const std::string name; // kernel ID, defined by its API (signature) + const M outMeta; // generic adaptor to API::outMeta(...) + const GShapes outShapes; // types (shapes) kernel's outputs +}; + +// GKernelImpl describes particular kernel implementation to the system +struct GAPI_EXPORTS GKernelImpl +{ + util::any opaque; // backend-specific opaque info +}; + +template class GKernelTypeM; + +namespace detail +{ + //////////////////////////////////////////////////////////////////////////// + // yield() is used in graph construction time as a generic method to obtain + // lazy "return value" of G-API operations + // + namespace + { + + template struct Yield; + template<> struct Yield + { + static inline cv::GMat yield(cv::GCall &call, int i) { return call.yield(i); } + }; + template<> struct Yield + { + static inline cv::GScalar yield(cv::GCall &call, int i) { return call.yieldScalar(i); } + }; + template struct Yield > + { + static inline cv::GArray yield(cv::GCall &call, int i) { return call.yieldArray(i); } + }; + } // anonymous namespace + + //////////////////////////////////////////////////////////////////////////// + // Helper classes which brings outputMeta() marshalling to kernel + // implementations + // + // 1. MetaType establishes G#Type -> G#Meta mapping between G-API dynamic + // types and its metadata descriptor types. + // This mapping is used to transform types to call outMeta() callback. + template struct MetaType; + template<> struct MetaType { using type = GMatDesc; }; + template<> struct MetaType { using type = GScalarDesc; }; + template struct MetaType > { using type = GArrayDesc; }; + template struct MetaType { using type = T; }; // opaque args passed as-is + + // 2. Hacky test based on MetaType to check if we operate on G-* type or not + template using is_nongapi_type = std::is_same::type>; + + // 3. Two ways to transform input arguments to its meta - for G-* and non-G* types: + template + typename std::enable_if::value, typename MetaType::type> + ::type get_in_meta(const GMetaArgs &in_meta, const GArgs &, int idx) + { + return util::get::type>(in_meta.at(idx)); + } + + template + typename std::enable_if::value, T> + ::type get_in_meta(const GMetaArgs &, const GArgs &in_args, int idx) + { + return in_args.at(idx).template get(); + } + + // 4. The MetaHelper itself: an entity which generates outMeta() call + // based on kernel signature, with arguments properly substituted. + // 4.1 - case for multiple return values + // FIXME: probably can be simplified with std::apply or analogue. + template + struct MetaHelper; + + template + struct MetaHelper, std::tuple > + { + template + static GMetaArgs getOutMeta_impl(const GMetaArgs &in_meta, + const GArgs &in_args, + detail::Seq, + detail::Seq) + { + // FIXME: decay? + using R = std::tuple::type...>; + const R r = K::outMeta( get_in_meta(in_meta, in_args, IIs)... ); + return GMetaArgs{ GMetaArg(std::get(r))... }; + } + // FIXME: help users identify how outMeta must look like (via default impl w/static_assert?) + + static GMetaArgs getOutMeta(const GMetaArgs &in_meta, + const GArgs &in_args) + { + return getOutMeta_impl(in_meta, + in_args, + typename detail::MkSeq::type(), + typename detail::MkSeq::type()); + } + }; + + // 4.1 - case for a single return value + // FIXME: How to avoid duplication here? + template + struct MetaHelper, Out > + { + template + static GMetaArgs getOutMeta_impl(const GMetaArgs &in_meta, + const GArgs &in_args, + detail::Seq) + { + // FIXME: decay? + using R = typename MetaType::type; + const R r = K::outMeta( get_in_meta(in_meta, in_args, IIs)... ); + return GMetaArgs{ GMetaArg(r) }; + } + // FIXME: help users identify how outMeta must look like (via default impl w/static_assert?) + + static GMetaArgs getOutMeta(const GMetaArgs &in_meta, + const GArgs &in_args) + { + return getOutMeta_impl(in_meta, + in_args, + typename detail::MkSeq::type()); + } + }; + +} // 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 +// GKernelTypeM respectively. + +template +class GKernelTypeM(Args...)> >: + public detail::MetaHelper, std::tuple > +{ + template + static std::tuple yield(cv::GCall &call, detail::Seq) + { + return std::make_tuple(detail::Yield::yield(call, IIs)...); + } + +public: + using InArgs = std::tuple; + using OutArgs = std::tuple; + + static std::tuple on(Args... args) + { + cv::GCall call(GKernel{K::id(), &K::getOutMeta, {detail::GTypeTraits::shape...}}); + call.pass(args...); + return yield(call, typename detail::MkSeq::type()); + } +}; + +template class GKernelType; + +template +class GKernelType >: + public detail::MetaHelper, R > +{ +public: + using InArgs = std::tuple; + using OutArgs = std::tuple; + + static R on(Args... args) + { + cv::GCall call(GKernel{K::id(), &K::getOutMeta, {detail::GTypeTraits::shape}}); + call.pass(args...); + return detail::Yield::yield(call, 0); + } +}; + +} // namespace cv + + +// FIXME: I don't know a better way so far. Feel free to suggest one +// The problem is that every typed kernel should have ::id() but body +// of the class is defined by user (with outMeta, other stuff) + +#define G_ID_HELPER_CLASS(Class) Class##IdHelper + +#define G_ID_HELPER_BODY(Class, Id) \ + namespace detail \ + { \ + struct G_ID_HELPER_CLASS(Class) \ + { \ + static constexpr const char * id() {return Id;}; \ + }; \ + } + +#define G_TYPED_KERNEL(Class, API, Id) \ + G_ID_HELPER_BODY(Class, Id) \ + struct Class final: public cv::GKernelType, \ + public detail::G_ID_HELPER_CLASS(Class) +// {body} is to be defined by user + +#define G_TYPED_KERNEL_M(Class, API, Id) \ + G_ID_HELPER_BODY(Class, Id) \ + struct Class final: public cv::GKernelTypeM, \ + public detail::G_ID_HELPER_CLASS(Class) \ +// {body} is to be defined by user + +namespace cv +{ +// Declare in cv:: namespace +enum class unite_policy +{ + REPLACE, + KEEP +}; + +namespace gapi +{ + // Prework: model "Device" API before it gets to G-API headers. + // FIXME: Don't mix with internal Backends class! + class GAPI_EXPORTS GBackend + { + public: + class Priv; + + // TODO: make it template (call `new` within??) + GBackend(); + explicit GBackend(std::shared_ptr &&p); + + Priv& priv(); + const Priv& priv() const; + std::size_t hash() const; + + bool operator== (const GBackend &rhs) const; + + private: + std::shared_ptr m_priv; + }; + + inline bool operator != (const GBackend &lhs, const GBackend &rhs) + { + return !(lhs == rhs); + } +} // namespace gapi +} // namespace cv + +namespace std +{ + template<> struct hash + { + std::size_t operator() (const cv::gapi::GBackend &b) const + { + return b.hash(); + } + }; +} // namespace std + + +namespace cv { +namespace gapi { + // Lookup order is in fact a vector of Backends to traverse during look-up + using GLookupOrder = std::vector; + inline GLookupOrder lookup_order(std::initializer_list &&list) + { + return GLookupOrder(std::move(list)); + } + + // FIXME: Hide implementation + class GAPI_EXPORTS GKernelPackage + { + using S = std::unordered_map; + using M = std::unordered_map; + M m_backend_kernels; + + protected: + // Check if package contains ANY implementation of a kernel API + // by API textual id. + bool includesAPI(const std::string &id) const; + + public: + // Return total number of kernels (accross all backends) + std::size_t size() const; + + // Check if particular kernel implementation exist in the package. + // The key word here is _particular_ - i.e., from the specific backend. + template + bool includes() const + { + const auto set_iter = m_backend_kernels.find(KImpl::backend()); + return (set_iter != m_backend_kernels.end()) + ? (set_iter->second.count(KImpl::API::id()) > 0) + : false; + } + + // Removes all the kernels related to the given backend + void remove(const GBackend& backend); + + // Check if package contains ANY implementation of a kernel API + // by API type. + template + bool includesAPI() const + { + return includesAPI(KAPI::id()); + } + + // Lookup a kernel, given the look-up order. Returns Backend which + // hosts kernel implementation. Throws if nothing found. + // + // If order is empty(), returns first suitable implementation. + template + GBackend lookup(const GLookupOrder &order = {}) const + { + return lookup(KAPI::id(), order).first; + } + + std::pair + lookup(const std::string &id, const GLookupOrder &order = {}) const; + + // Put a new kernel implementation into package + // FIXME: No overwrites allowed? + template void include() + { + auto backend = KImpl::backend(); + auto kernel_id = KImpl::API::id(); + auto kernel_impl = GKernelImpl{KImpl::kernel()}; + m_backend_kernels[backend][kernel_id] = std::move(kernel_impl); + } + + // Lists all backends which are included into package + std::vector backends() const; + + friend GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &, + const GKernelPackage &, + const cv::unite_policy); + }; + + template GKernelPackage kernels() + { + GKernelPackage pkg; + + // For those who wonder - below is a trick to call a number of + // methods based on parameter pack (zeroes just help hiding these + // calls into a sequence which helps to expand this parameter pack). + // 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<>()). + + int unused[] = { 0, (pkg.include(), 0)... }; + cv::util::suppress_unused_warning(unused); + return pkg; + }; + + // Return a new package based on `lhs` and `rhs`, + // with unity policy defined by `policy`. + GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs, + const GKernelPackage &rhs, + const cv::unite_policy policy); +} // namespace gapi + +namespace detail +{ + template<> struct CompileArgTag + { + static const char* tag() { return "gapi.kernel_package"; } + }; + template<> struct CompileArgTag + { + static const char* tag() { return "gapi.lookup_order"; } + }; +} // namespace detail +} // namespace cv + +#endif // OPENCV_GAPI_GKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/gmat.hpp b/modules/gapi/include/opencv2/gapi/gmat.hpp new file mode 100644 index 0000000000..cfe4e51e56 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gmat.hpp @@ -0,0 +1,131 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GMAT_HPP +#define OPENCV_GAPI_GMAT_HPP + +#include +#include // std::shared_ptr + +#include +#include // GShape + +#include "opencv2/gapi/own/types.hpp" // cv::gapi::own::Size +#include "opencv2/gapi/own/convert.hpp" // to_own +#include "opencv2/gapi/own/assert.hpp" + +// TODO GAPI_EXPORTS or so +namespace cv +{ +// Forward declaration; GNode and GOrigin are an internal +// (user-inaccessible) classes. +class GNode; +struct GOrigin; + +class GAPI_EXPORTS GMat +{ +public: + GMat(); // Empty constructor + GMat(const GNode &n, std::size_t out); // Operation result constructor + + GOrigin& priv(); // Internal use only + const GOrigin& priv() const; // Internal use only + +private: + std::shared_ptr m_priv; +}; + +struct GAPI_EXPORTS GMatDesc +{ + // FIXME: Default initializers in C++14 + int depth; + int chan; + cv::gapi::own::Size size; // NB.: no multi-dimensional cases covered yet + + inline bool operator== (const GMatDesc &rhs) const + { + return depth == rhs.depth && chan == rhs.chan && size == rhs.size; + } + + inline bool operator!= (const GMatDesc &rhs) const + { + return !(*this == rhs); + } + + // Meta combinator: return a new GMatDesc which differs in size by delta + // (all other fields are taken unchanged from this GMatDesc) + // FIXME: a better name? + GMatDesc withSizeDelta(cv::gapi::own::Size delta) const + { + GMatDesc desc(*this); + desc.size += delta; + return desc; + } + + GMatDesc withSizeDelta(cv::Size delta) const + { + return withSizeDelta(to_own(delta)); + } + + // Meta combinator: return a new GMatDesc which differs in size by delta + // (all other fields are taken unchanged from this GMatDesc) + // + // This is an overload. + GMatDesc withSizeDelta(int dx, int dy) const + { + return withSizeDelta(cv::gapi::own::Size{dx,dy}); + } + + GMatDesc withSize(cv::gapi::own::Size sz) const + { + GMatDesc desc(*this); + desc.size = sz; + return desc; + } + + GMatDesc withSize(cv::Size sz) const + { + return withSize(to_own(sz)); + } + + // Meta combinator: return a new GMatDesc with specified data depth. + // (all other fields are taken unchanged from this GMatDesc) + GMatDesc withDepth(int ddepth) const + { + GAPI_Assert(CV_MAT_CN(ddepth) == 1 || ddepth == -1); + GMatDesc desc(*this); + if (ddepth != -1) desc.depth = ddepth; + return desc; + } + + // 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_Assert(CV_MAT_CN(ddepth) == 1 || ddepth == -1); + GMatDesc desc = withDepth(ddepth); + desc.chan = dchan; + return desc; + } +}; + +static inline GMatDesc empty_gmat_desc() { return GMatDesc{-1,-1,{-1,-1}}; } + +class Mat; +GAPI_EXPORTS GMatDesc descr_of(const cv::Mat &mat); + +namespace gapi { namespace own { + class Mat; + CV_EXPORTS GMatDesc descr_of(const Mat &mat); +}}//gapi::own + +std::ostream& operator<<(std::ostream& os, const cv::GMatDesc &desc); + +} // namespace cv + +#endif // OPENCV_GAPI_GMAT_HPP diff --git a/modules/gapi/include/opencv2/gapi/gmetaarg.hpp b/modules/gapi/include/opencv2/gapi/gmetaarg.hpp new file mode 100644 index 0000000000..473be342ed --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gmetaarg.hpp @@ -0,0 +1,66 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GMETAARG_HPP +#define OPENCV_GAPI_GMETAARG_HPP + +#include +#include + +#include "opencv2/gapi/util/util.hpp" +#include "opencv2/gapi/util/variant.hpp" + +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/garray.hpp" + +namespace cv +{ +// FIXME: Rename to GMeta? +// FIXME: user shouldn't deal with it - put to detail? +// GMetaArg is an union type over descriptions of G-types which can serve as +// GComputation's in/output slots. +// +// GMetaArg objects are passed as arguments to GComputation::compile() +// to specify which data a compiled computation should be specialized on. +// For manual compile(), user must supply this metadata, in case of apply() +// this metadata is taken from arguments computation should operate on. +// +// The first type (monostate) is equal to "uninitialized"/"unresolved" meta. +using GMetaArg = util::variant + < util::monostate + , GMatDesc + , GScalarDesc + , GArrayDesc + >; +std::ostream& operator<<(std::ostream& os, const GMetaArg &); + +using GMetaArgs = std::vector; + +namespace detail +{ + // These traits are used by GComputation::compile() + + // FIXME: is_constructible doesn't work as variant doesn't do any SFINAE + // in its current template constructor + + template struct is_meta_descr : std::false_type {}; + template<> struct is_meta_descr : std::true_type {}; + template<> struct is_meta_descr : std::true_type {}; + template<> struct is_meta_descr : std::true_type {}; + + template + using are_meta_descrs = all_satisfy; + + template + using are_meta_descrs_but_last = all_satisfy::type>; + +} // namespace detail + +} // namespace cv + +#endif // OPENCV_GAPI_GMETAARG_HPP diff --git a/modules/gapi/include/opencv2/gapi/gproto.hpp b/modules/gapi/include/opencv2/gapi/gproto.hpp new file mode 100644 index 0000000000..8b53d9b642 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gproto.hpp @@ -0,0 +1,96 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GPROTO_HPP +#define OPENCV_GAPI_GPROTO_HPP + +#include +#include +#include + +#include "opencv2/gapi/util/variant.hpp" + +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/garray.hpp" +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gmetaarg.hpp" + +namespace cv { + +// FIXME: user shouldn't deal with it - put to detail? +// GProtoArg is an union type over G-types which can serve as +// GComputation's in/output slots. In other words, GProtoArg +// wraps any type which can serve as G-API exchange type. +// +// In Runtime, GProtoArgs are substituted with appropriate GRunArgs. +// +// GProtoArg objects are constructed in-place when user describes +// (captures) computations, user doesn't interact with these types +// directly. +using GProtoArg = util::variant + < GMat + , GScalar + , detail::GArrayU // instead of GArray + >; + +using GProtoArgs = std::vector; + +namespace detail +{ +template inline GProtoArgs packArgs(Ts... args) +{ + return GProtoArgs{ GProtoArg(wrap_gapi_helper::wrap(args))... }; +} + +} + +template +struct GIOProtoArgs +{ +public: + explicit GIOProtoArgs(const GProtoArgs& args) : m_args(args) {} + explicit GIOProtoArgs(GProtoArgs &&args) : m_args(std::move(args)) {} + + GProtoArgs m_args; +}; + +struct In_Tag{}; +struct Out_Tag{}; + +using GProtoInputArgs = GIOProtoArgs; +using GProtoOutputArgs = GIOProtoArgs; + +// Perfect forwarding +template inline GProtoInputArgs GIn(Ts&&... ts) +{ + return GProtoInputArgs(detail::packArgs(std::forward(ts)...)); +} + +template inline GProtoOutputArgs GOut(Ts&&... ts) +{ + return GProtoOutputArgs(detail::packArgs(std::forward(ts)...)); +} + +// Extract run-time arguments from node origin +// Can be used to extract constant values associated with G-objects +// (like GScalar) at graph construction time +GRunArg value_of(const GOrigin &origin); + +// Transform run-time computation arguments into a collection of metadata +// extracted from that arguments +GMetaArg GAPI_EXPORTS descr_of(const GRunArg &arg ); +GMetaArgs GAPI_EXPORTS descr_of(const GRunArgs &args); + +// Transform run-time operation result argument into metadata extracted from that argument +// Used to compare the metadata, which generated at compile time with the metadata result operation in run time +GMetaArg GAPI_EXPORTS descr_of(const GRunArgP& argp); + + +} // namespace cv + +#endif // OPENCV_GAPI_GPROTO_HPP diff --git a/modules/gapi/include/opencv2/gapi/gscalar.hpp b/modules/gapi/include/opencv2/gapi/gscalar.hpp new file mode 100644 index 0000000000..030b2e116a --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gscalar.hpp @@ -0,0 +1,68 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GSCALAR_HPP +#define OPENCV_GAPI_GSCALAR_HPP + +#include + +#include +#include // GShape +#include +#include "opencv2/gapi/own/scalar.hpp" +#include + +// TODO GAPI_EXPORTS or so +namespace cv +{ +// Forward declaration; GNode and GOrigin are an internal +// (user-inaccessible) classes. +class GNode; +struct GOrigin; + +class GAPI_EXPORTS GScalar +{ +public: + GScalar(); // Empty constructor + explicit GScalar(const cv::gapi::own::Scalar& s); // Constant value constructor from cv::gapi::own::Scalar + explicit GScalar(cv::gapi::own::Scalar&& s); // Constant value move-constructor from cv::gapi::own::Scalar + explicit GScalar(const cv::Scalar& s); // Constant value constructor from cv::Scalar + GScalar(double v0); // Constant value constructor from double + GScalar(const GNode &n, std::size_t out); // Operation result constructor + + GOrigin& priv(); // Internal use only + const GOrigin& priv() const; // Internal use only + +private: + std::shared_ptr m_priv; +}; + +struct GScalarDesc +{ + // NB.: right now it is empty + + inline bool operator== (const GScalarDesc &) const + { + return true; // NB: implement this method if GScalar meta appears + } + + inline bool operator!= (const GScalarDesc &rhs) const + { + return !(*this == rhs); + } +}; + +static inline GScalarDesc empty_scalar_desc() { return GScalarDesc(); } + +GAPI_EXPORTS GScalarDesc descr_of(const cv::gapi::own::Scalar &scalar); +GAPI_EXPORTS GScalarDesc descr_of(const cv::Scalar &scalar); + +std::ostream& operator<<(std::ostream& os, const cv::GScalarDesc &desc); + +} // namespace cv + +#endif // OPENCV_GAPI_GSCALAR_HPP diff --git a/modules/gapi/include/opencv2/gapi/gtype_traits.hpp b/modules/gapi/include/opencv2/gapi/gtype_traits.hpp new file mode 100644 index 0000000000..fcedefc718 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gtype_traits.hpp @@ -0,0 +1,150 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GTYPE_TRAITS_HPP +#define OPENCV_GAPI_GTYPE_TRAITS_HPP + +#include +#include + +#include +#include +#include +#include +#include + +namespace cv +{ +namespace detail +{ + // FIXME: These traits and enum and possible numerous switch(kind) + // block may be replaced with a special Handler object or with + // a double dispatch + enum class ArgKind: int + { + OPAQUE, // Unknown, generic, opaque-to-GAPI data type - STATIC + GOBJREF, // reference to object + GMAT, // a cv::GMat + GSCALAR, // a cv::GScalar + GARRAY, // a cv::GArrayU (note - exactly GArrayU, not GArray!) + }; + + // Describe G-API types (G-types) with traits. Mostly used by + // cv::GArg to store meta information about types passed into + // operation arguments. Please note that cv::GComputation is + // defined on GProtoArgs, not GArgs! + template struct GTypeTraits; + template struct GTypeTraits + { + static constexpr const ArgKind kind = ArgKind::OPAQUE; + }; + template<> struct GTypeTraits + { + static constexpr const ArgKind kind = ArgKind::GMAT; + static constexpr const GShape shape = GShape::GMAT; + }; + template<> struct GTypeTraits + { + static constexpr const ArgKind kind = ArgKind::GSCALAR; + static constexpr const GShape shape = GShape::GSCALAR; + }; + template struct GTypeTraits > + { + static constexpr const ArgKind kind = ArgKind::GARRAY; + static constexpr const GShape shape = GShape::GARRAY; + using host_type = std::vector; + using strip_type = cv::detail::VectorRef; + static cv::detail::GArrayU wrap_value(const cv::GArray &t) { return t.strip();} + static cv::detail::VectorRef wrap_in (const std::vector &t) { return detail::VectorRef(t); } + static cv::detail::VectorRef wrap_out ( std::vector &t) { return detail::VectorRef(t); } + }; + + // Tests if Trait for type T requires extra marshalling ("custom wrap") or not. + // If Traits has wrap_value() defined, it does. + template struct has_custom_wrap + { + template class check; + template static std::true_type test(check::wrap_value)> *); + template static std::false_type test(...); + using type = decltype(test(nullptr)); + static const constexpr bool value = std::is_same(nullptr))>::value; + }; + + // Resolve a Host type back to its associated G-Type. + // FIXME: Probably it can be avoided + template struct GTypeOf; + template<> struct GTypeOf { using type = cv::GMat; }; + template<> struct GTypeOf { using type = cv::GMat; }; + template<> struct GTypeOf { using type = cv::GScalar; }; + template<> struct GTypeOf { using type = cv::GScalar; }; + template struct GTypeOf > { using type = cv::GArray; }; + template using g_type_of_t = typename GTypeOf::type; + + // Marshalling helper for G-types and its Host types. Helps G-API + // to store G types in internal generic containers for further + // processing. Implements the following callbacks: + // + // * wrap() - converts user-facing G-type into an internal one + // for internal storage. + // Used when G-API operation is instantiated (G::on(), + // etc) during expressing a pipeline. Mostly returns input + // value "as is" except the case when G-type is a template. For + // template G-classes, calls custom wrap() from Traits. + // The value returned by wrap() is then wrapped into GArg() and + // stored in G-API metadata. + // + // Example: + // - cv::GMat arguments are passed as-is. + // - integers, pointers, STL containers, user types are passed as-is. + // - cv::GArray is converted to cv::GArrayU. + // + // * wrap_in() / wrap_out() - convert Host type associated with + // G-type to internal representation type. + // + // - For "simple" (non-template) G-types, returns value as-is. + // Example: cv::GMat has host type cv::Mat, when user passes a + // cv::Mat, system stores it internally as cv::Mat. + // + // - For "complex" (template) G-types, utilizes custom + // wrap_in()/wrap_out() as described in Traits. + // Example: cv::GArray has host type std::vector, when + // user passes a std::vector, system stores it + // internally as VectorRef (with stripped away). + template struct WrapValue + { + static auto wrap(const T& t) -> + typename std::remove_reference::type + { + return static_cast::type>(t); + } + + template static U wrap_in (const U &u) { return u; } + template static U* wrap_out(U &u) { return &u; } + }; + template struct WrapValue::value>::type> + { + static auto wrap(const T& t) -> decltype(GTypeTraits::wrap_value(t)) + { + return GTypeTraits::wrap_value(t); + } + template static auto wrap_in (const U &u) -> typename GTypeTraits::strip_type + { + return GTypeTraits::wrap_in(u); + } + template static auto wrap_out(U &u) -> typename GTypeTraits::strip_type + { + return GTypeTraits::wrap_out(u); + } + }; + + template using wrap_gapi_helper = WrapValue::type>; + template using wrap_host_helper = WrapValue >::type>; + +} // namespace detail +} // namespace cv + +#endif // OPENCV_GAPI_GTYPE_TRAITS_HPP diff --git a/modules/gapi/include/opencv2/gapi/gtyped.hpp b/modules/gapi/include/opencv2/gapi/gtyped.hpp new file mode 100644 index 0000000000..d691aeec09 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/gtyped.hpp @@ -0,0 +1,186 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GTYPED_HPP +#define OPENCV_GAPI_GTYPED_HPP + +#include + +#include "opencv2/gapi/gcomputation.hpp" +#include "opencv2/gapi/gcompiled.hpp" +#include "opencv2/gapi/gproto.hpp" +#include "opencv2/gapi/gcommon.hpp" + +namespace cv { + +namespace detail +{ + // FIXME: How to prevent coolhackers from extending it by their own types? + // FIXME: ...Should we care? + template struct ProtoToParam; + template<> struct ProtoToParam { using type = cv::Mat; }; + template<> struct ProtoToParam { using type = cv::Scalar; }; + template struct ProtoToParam > { using type = std::vector; }; + template using ProtoToParamT = typename ProtoToParam::type; + + template struct ProtoToMeta; + template<> struct ProtoToMeta { using type = cv::GMatDesc; }; + template<> struct ProtoToMeta { using type = cv::GScalarDesc; }; + template struct ProtoToMeta > { using type = cv::GArrayDesc; }; + template using ProtoToMetaT = typename ProtoToMeta::type; + + //workaround for MSVC 19.0 bug + template + auto make_default()->decltype(T{}) {return {};} +}; // detail + +template class GComputationT; + +// Single return value implementation +template class GComputationT +{ +public: + typedef std::function Gen; + + class GCompiledT + { + private: + friend class GComputationT; + + cv::GCompiled m_comp; + + explicit GCompiledT(const cv::GCompiled &comp) : m_comp(comp) {} + + public: + GCompiledT() {} + + void operator()(detail::ProtoToParamT... inArgs, + detail::ProtoToParamT &outArg) + { + m_comp(cv::gin(inArgs...), cv::gout(outArg)); + } + + explicit operator bool() const + { + return static_cast(m_comp); + } + }; + +private: + typedef std::pair Captured; + + Captured capture(const Gen& g, Args... args) + { + return Captured(g(args...), cv::GIn(args...)); + } + + Captured m_capture; + cv::GComputation m_comp; + +public: + GComputationT(const Gen &generator) + : m_capture(capture(generator, detail::make_default()...)) + , m_comp(cv::GProtoInputArgs(std::move(m_capture.second)), + cv::GOut(m_capture.first)) + { + } + + void apply(detail::ProtoToParamT... inArgs, + detail::ProtoToParamT &outArg) + { + m_comp.apply(cv::gin(inArgs...), cv::gout(outArg)); + } + + GCompiledT compile(detail::ProtoToMetaT... inDescs) + { + GMetaArgs inMetas = { GMetaArg(inDescs)... }; + return GCompiledT(m_comp.compile(std::move(inMetas), GCompileArgs())); + } + + GCompiledT compile(detail::ProtoToMetaT... inDescs, GCompileArgs &&args) + { + GMetaArgs inMetas = { GMetaArg(inDescs)... }; + return GCompiledT(m_comp.compile(std::move(inMetas), std::move(args))); + } +}; + +// Multiple (fixed) return value implementation. FIXME: How to avoid copy-paste? +template class GComputationT(Args...)> +{ +public: + typedef std::function(Args...)> Gen; + + class GCompiledT + { + private: + friend class GComputationT(Args...)>; + + cv::GCompiled m_comp; + explicit GCompiledT(const cv::GCompiled &comp) : m_comp(comp) {} + + public: + GCompiledT() {} + + void operator()(detail::ProtoToParamT... inArgs, + detail::ProtoToParamT&... outArgs) + { + m_comp(cv::gin(inArgs...), cv::gout(outArgs...)); + } + + explicit operator bool() const + { + return static_cast(m_comp); + } + }; + +private: + typedef std::pair Captured; + + template + Captured capture(GProtoArgs &&args, const std::tuple &rr, detail::Seq) + { + return Captured(cv::GOut(std::get(rr)...).m_args, args); + } + + Captured capture(const Gen& g, Args... args) + { + return capture(cv::GIn(args...).m_args, g(args...), typename detail::MkSeq::type()); + } + + Captured m_capture; + cv::GComputation m_comp; + +public: + GComputationT(const Gen &generator) + : m_capture(capture(generator, detail::make_default()...)) + , m_comp(cv::GProtoInputArgs(std::move(m_capture.second)), + cv::GProtoOutputArgs(std::move(m_capture.first))) + { + } + + void apply(detail::ProtoToParamT... inArgs, + detail::ProtoToParamT&... outArgs) + { + m_comp.apply(cv::gin(inArgs...), cv::gout(outArgs...)); + } + + GCompiledT compile(detail::ProtoToMetaT... inDescs) + { + GMetaArgs inMetas = { GMetaArg(inDescs)... }; + return GCompiledT(m_comp.compile(std::move(inMetas), GCompileArgs())); + } + + GCompiledT compile(detail::ProtoToMetaT... inDescs, GCompileArgs &&args) + { + GMetaArgs inMetas = { GMetaArg(inDescs)... }; + return GCompiledT(m_comp.compile(std::move(inMetas), std::move(args))); + } +}; + +} // namespace cv + +#endif // OPENCV_GAPI_GTYPED_HPP diff --git a/modules/gapi/include/opencv2/gapi/imgproc.hpp b/modules/gapi/include/opencv2/gapi/imgproc.hpp new file mode 100644 index 0000000000..6a3b1d9b79 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/imgproc.hpp @@ -0,0 +1,677 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_IMGPROC_HPP +#define OPENCV_GAPI_IMGPROC_HPP + +#include "opencv2/imgproc.hpp" + +#include // std::tuple + +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/gscalar.hpp" + + +/** \defgroup gapi_imgproc G-API image processing functionality +@{ + @defgroup gapi_filters Graph API: Image filters + @defgroup gapi_colorconvert Graph API: Converting image from one color space to another +@} + */ + +namespace cv { namespace gapi { + +namespace imgproc { + using GMat3 = std::tuple; // FIXME: how to avoid this? + + G_TYPED_KERNEL(GFilter2D, ,"org.opencv.imgproc.filters.filter2D") { + static GMatDesc outMeta(GMatDesc in, int ddepth, Mat, Point, Scalar, int, Scalar) { + return in.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GSepFilter, , "org.opencv.imgproc.filters.sepfilter") { + static GMatDesc outMeta(GMatDesc in, int ddepth, Mat, Mat, Point, Scalar, int, Scalar) { + return in.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GBoxFilter, , "org.opencv.imgproc.filters.boxfilter") { + static GMatDesc outMeta(GMatDesc in, int ddepth, Size, Point, bool, int, Scalar) { + return in.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GBlur, , "org.opencv.imgproc.filters.blur"){ + static GMatDesc outMeta(GMatDesc in, Size, Point, int, Scalar) { + return in; + } + }; + + G_TYPED_KERNEL(GGaussBlur, , "org.opencv.imgproc.filters.gaussianBlur") { + static GMatDesc outMeta(GMatDesc in, Size, double, double, int, Scalar) { + return in; + } + }; + + G_TYPED_KERNEL(GMedianBlur, , "org.opencv.imgproc.filters.medianBlur") { + static GMatDesc outMeta(GMatDesc in, int) { + return in; + } + }; + + G_TYPED_KERNEL(GErode, , "org.opencv.imgproc.filters.erode") { + static GMatDesc outMeta(GMatDesc in, Mat, Point, int, int, Scalar) { + return in; + } + }; + + G_TYPED_KERNEL(GDilate, , "org.opencv.imgproc.filters.dilate") { + static GMatDesc outMeta(GMatDesc in, Mat, Point, int, int, Scalar) { + return in; + } + }; + + G_TYPED_KERNEL(GSobel, , "org.opencv.imgproc.filters.sobel") { + static GMatDesc outMeta(GMatDesc in, int ddepth, int, int, int, double, double, int, Scalar) { + return in.withDepth(ddepth); + } + }; + + G_TYPED_KERNEL(GEqHist, , "org.opencv.imgproc.equalizeHist"){ + static GMatDesc outMeta(GMatDesc in) { + return in.withType(CV_8U, 1); + } + }; + + G_TYPED_KERNEL(GCanny, , "org.opencv.imgproc.canny"){ + static GMatDesc outMeta(GMatDesc in, double, double, int, bool) { + return in.withType(CV_8U, 1); + } + }; + + G_TYPED_KERNEL(GRGB2YUV, , "org.opencv.imgproc.colorconvert.rgb2yuv") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GYUV2RGB, , "org.opencv.imgproc.colorconvert.yuv2rgb") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GRGB2Lab, , "org.opencv.imgproc.colorconvert.rgb2lab") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GBGR2LUV, , "org.opencv.imgproc.colorconvert.bgr2luv") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GLUV2BGR, , "org.opencv.imgproc.colorconvert.luv2bgr") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GYUV2BGR, , "org.opencv.imgproc.colorconvert.yuv2bgr") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GBGR2YUV, , "org.opencv.imgproc.colorconvert.bgr2yuv") { + static GMatDesc outMeta(GMatDesc in) { + return in; // type still remains CV_8UC3; + } + }; + + G_TYPED_KERNEL(GRGB2Gray, , "org.opencv.imgproc.colorconvert.rgb2gray") { + static GMatDesc outMeta(GMatDesc in) { + return in.withType(CV_8U, 1); + } + }; + + G_TYPED_KERNEL(GRGB2GrayCustom, , "org.opencv.imgproc.colorconvert.rgb2graycustom") { + static GMatDesc outMeta(GMatDesc in, float, float, float) { + return in.withType(CV_8U, 1); + } + }; + + G_TYPED_KERNEL(GBGR2Gray, , "org.opencv.imgproc.colorconvert.bgr2gray") { + static GMatDesc outMeta(GMatDesc in) { + return in.withType(CV_8U, 1); + } + }; +} + + +//! @addtogroup gapi_filters +//! @{ +/** @brief Applies a separable linear filter to a matrix(image). + +The function applies a separable linear filter to the matrix. That is, first, every row of src is +filtered with the 1D kernel kernelX. Then, every column of the result is filtered with the 1D +kernel kernelY. The final result is returned. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note In case of floating-point computation, rounding to nearest even is procedeed +if hardware supports it (if not - to nearest value). + +@note Function textual ID is "org.opencv.imgproc.filters.sepfilter" +@param src Source image. +@param ddepth desired depth of the destination image (the following combinations of src.depth() and ddepth are supported: + + src.depth() = CV_8U, ddepth = -1/CV_16S/CV_32F/CV_64F + src.depth() = CV_16U/CV_16S, ddepth = -1/CV_32F/CV_64F + src.depth() = CV_32F, ddepth = -1/CV_32F/CV_64F + src.depth() = CV_64F, ddepth = -1/CV_64F + +when ddepth=-1, the output image will have the same depth as the source) +@param kernelX Coefficients for filtering each row. +@param kernelY Coefficients for filtering each column. +@param anchor Anchor position within the kernel. The default value \f$(-1,-1)\f$ means that the anchor +is at the kernel center. +@param delta Value added to the filtered results before storing them. +@param borderType Pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of constant border type +@sa boxFilter, gaussianBlur, medianBlur + */ +GAPI_EXPORTS GMat sepFilter(const GMat& src, int ddepth, const Mat& kernelX, const Mat& kernelY, const Point& anchor /*FIXME: = Point(-1,-1)*/, + const Scalar& delta /*FIXME = GScalar(0)*/, int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); + +/** @brief Convolves an image with the kernel. + +The function applies an arbitrary linear filter to an image. When +the aperture is partially outside the image, the function interpolates outlier pixel values +according to the specified border mode. + +The function does actually compute correlation, not the convolution: + +\f[\texttt{dst} (x,y) = \sum _{ \stackrel{0\leq x' < \texttt{kernel.cols},}{0\leq y' < \texttt{kernel.rows}} } \texttt{kernel} (x',y')* \texttt{src} (x+x'- \texttt{anchor.x} ,y+y'- \texttt{anchor.y} )\f] + +That is, the kernel is not mirrored around the anchor point. If you need a real convolution, flip +the kernel using flip and set the new anchor to `(kernel.cols - anchor.x - 1, kernel.rows - +anchor.y - 1)`. + +Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. +Output image must have the same size and number of channels an input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.filter2D" + +@param src input image. +@param ddepth desired depth of the destination image +@param kernel convolution kernel (or rather a correlation kernel), a single-channel floating point +matrix; if you want to apply different kernels to different channels, split the image into +separate color planes using split and process them individually. +@param anchor anchor of the kernel that indicates the relative position of a filtered point within +the kernel; the anchor should lie within the kernel; default value (-1,-1) means that the anchor +is at the kernel center. +@param delta optional value added to the filtered pixels before storing them in dst. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of constant border type +@sa sepFilter + */ +GAPI_EXPORTS GMat filter2D(const GMat& src, int ddepth, const Mat& kernel, const Point& anchor = Point(-1,-1), const Scalar& delta = Scalar(0), + int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); + + +/** @brief Blurs an image using the box filter. + +The function smooths an image using the kernel: + +\f[\texttt{K} = \alpha \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \hdotsfor{6} \\ 1 & 1 & 1 & \cdots & 1 & 1 \end{bmatrix}\f] + +where + +\f[\alpha = \fork{\frac{1}{\texttt{ksize.width*ksize.height}}}{when \texttt{normalize=true}}{1}{otherwise}\f] + +Unnormalized box filter is useful for computing various integral characteristics over each pixel +neighborhood, such as covariance matrices of image derivatives (used in dense optical flow +algorithms, and so on). If you need to compute pixel sums over variable-size windows, use cv::integral. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.boxfilter" + +@param src Source image. +@param dtype the output image depth (-1 to set the input image data type). +@param ksize blurring kernel size. +@param anchor Anchor position within the kernel. The default value \f$(-1,-1)\f$ means that the anchor +is at the kernel center. +@param normalize flag, specifying whether the kernel is normalized by its area or not. +@param borderType Pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of constant border type +@sa sepFilter, gaussianBlur, medianBlur, integral + */ +GAPI_EXPORTS GMat boxFilter(const GMat& src, int dtype, const Size& ksize, const Point& anchor = Point(-1,-1), + bool normalize = true, int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); + +/** @brief Blurs an image using the normalized box filter. + +The function smooths an image using the kernel: + +\f[\texttt{K} = \frac{1}{\texttt{ksize.width*ksize.height}} \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 & 1 \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \hdotsfor{6} \\ 1 & 1 & 1 & \cdots & 1 & 1 \\ \end{bmatrix}\f] + +The call `blur(src, dst, ksize, anchor, borderType)` is equivalent to `boxFilter(src, dst, src.type(), +anchor, true, borderType)`. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.blur" + +@param src Source image. +@param ksize blurring kernel size. +@param anchor anchor point; default value Point(-1,-1) means that the anchor is at the kernel +center. +@param borderType border mode used to extrapolate pixels outside of the image, see cv::BorderTypes +@param borderValue border value in case of constant border type +@sa boxFilter, bilateralFilter, GaussianBlur, medianBlur + */ +GAPI_EXPORTS GMat blur(const GMat& src, const Size& ksize, const Point& anchor = Point(-1,-1), + int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); + + +//GAPI_EXPORTS_W void blur( InputArray src, OutputArray dst, + // Size ksize, Point anchor = Point(-1,-1), + // int borderType = BORDER_DEFAULT ); + + +/** @brief Blurs an image using a Gaussian filter. + +The function filter2Ds the source image with the specified Gaussian kernel. +Output image must have the same type and number of channels an input image. + +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.gaussianBlur" + +@param src input image; +@param ksize Gaussian kernel size. ksize.width and ksize.height can differ but they both must be +positive and odd. Or, they can be zero's and then they are computed from sigma. +@param sigmaX Gaussian kernel standard deviation in X direction. +@param sigmaY Gaussian kernel standard deviation in Y direction; if sigmaY is zero, it is set to be +equal to sigmaX, if both sigmas are zeros, they are computed from ksize.width and ksize.height, +respectively (see cv::getGaussianKernel for details); to fully control the result regardless of +possible future modifications of all this semantics, it is recommended to specify all of ksize, +sigmaX, and sigmaY. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of constant border type +@sa sepFilter, boxFilter, medianBlur + */ +GAPI_EXPORTS GMat gaussianBlur(const GMat& src, const Size& ksize, double sigmaX, double sigmaY = 0, + int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); + +/** @brief Blurs an image using the median filter. + +The function smoothes an image using the median filter with the \f$\texttt{ksize} \times +\texttt{ksize}\f$ aperture. Each channel of a multi-channel image is processed independently. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. +The median filter uses cv::BORDER_REPLICATE internally to cope with border pixels, see cv::BorderTypes + +@note Function textual ID is "org.opencv.imgproc.filters.medianBlur" + +@param src input matrix (image) +@param ksize aperture linear size; it must be odd and greater than 1, for example: 3, 5, 7 ... +@sa boxFilter, gaussianBlur + */ +GAPI_EXPORTS GMat medianBlur(const GMat& src, int ksize); + +/** @brief Erodes an image by using a specific structuring element. + +The function erodes the source image using the specified structuring element that determines the +shape of a pixel neighborhood over which the minimum is taken: + +\f[\texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] + +Erosion can be applied several (iterations) times. In case of multi-channel images, each channel is processed independently. +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, and @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.erode" + +@param src input image +@param kernel structuring element used for erosion; if `element=Mat()`, a `3 x 3` rectangular +structuring element is used. Kernel can be created using getStructuringElement. +@param anchor position of the anchor within the element; default value (-1, -1) means that the +anchor is at the element center. +@param iterations number of times erosion is applied. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of a constant border +@sa dilate + */ +GAPI_EXPORTS GMat erode(const GMat& src, const Mat& kernel, const Point& anchor = Point(-1,-1), int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); + +/** @brief Erodes an image by using 3 by 3 rectangular structuring element. + +The function erodes the source image using the rectangular structuring element with rectangle center as an anchor. +Erosion can be applied several (iterations) times. In case of multi-channel images, each channel is processed independently. +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, and @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@param src input image +@param iterations number of times erosion is applied. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of a constant border +@sa erode, dilate3x3 + */ +GAPI_EXPORTS GMat erode3x3(const GMat& src, int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); + +/** @brief Dilates an image by using a specific structuring element. + +The function dilates the source image using the specified structuring element that determines the +shape of a pixel neighborhood over which the maximum is taken: +\f[\texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] + +Dilation can be applied several (iterations) times. In case of multi-channel images, each channel is processed independently. +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, and @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.dilate" + +@param src input image. +@param kernel structuring element used for dilation; if elemenat=Mat(), a 3 x 3 rectangular +structuring element is used. Kernel can be created using getStructuringElement +@param anchor position of the anchor within the element; default value (-1, -1) means that the +anchor is at the element center. +@param iterations number of times dilation is applied. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of a constant border +@sa erode, morphologyEx, getStructuringElement + */ +GAPI_EXPORTS GMat dilate(const GMat& src, const Mat& kernel, const Point& anchor = Point(-1,-1), int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); + +/** @brief Dilates an image by using 3 by 3 rectangular structuring element. + +The function dilates the source image using the specified structuring element that determines the +shape of a pixel neighborhood over which the maximum is taken: +\f[\texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] + +Dilation can be applied several (iterations) times. In case of multi-channel images, each channel is processed independently. +Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref CV_16SC1, and @ref CV_32FC1. +Output image must have the same type, size, and number of channels as the input image. +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.dilate" + +@param src input image. +@param iterations number of times dilation is applied. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of a constant border +@sa dilate, erode3x3 + */ + +GAPI_EXPORTS GMat dilate3x3(const GMat& src, int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); + +/** @brief Calculates the first, second, third, or mixed image derivatives using an extended Sobel operator. + +In all cases except one, the \f$\texttt{ksize} \times \texttt{ksize}\f$ separable kernel is used to +calculate the derivative. When \f$\texttt{ksize = 1}\f$, the \f$3 \times 1\f$ or \f$1 \times 3\f$ +kernel is used (that is, no Gaussian smoothing is done). `ksize = 1` can only be used for the first +or the second x- or y- derivatives. + +There is also the special value `ksize = CV_SCHARR (-1)` that corresponds to the \f$3\times3\f$ Scharr +filter that may give more accurate results than the \f$3\times3\f$ Sobel. The Scharr aperture is + +\f[\vecthreethree{-3}{0}{3}{-10}{0}{10}{-3}{0}{3}\f] + +for the x-derivative, or transposed for the y-derivative. + +The function calculates an image derivative by convolving the image with the appropriate kernel: + +\f[\texttt{dst} = \frac{\partial^{xorder+yorder} \texttt{src}}{\partial x^{xorder} \partial y^{yorder}}\f] + +The Sobel operators combine Gaussian smoothing and differentiation, so the result is more or less +resistant to the noise. Most often, the function is called with ( xorder = 1, yorder = 0, ksize = 3) +or ( xorder = 0, yorder = 1, ksize = 3) to calculate the first x- or y- image derivative. The first +case corresponds to a kernel of: + +\f[\vecthreethree{-1}{0}{1}{-2}{0}{2}{-1}{0}{1}\f] + +The second case corresponds to a kernel of: + +\f[\vecthreethree{-1}{-2}{-1}{0}{0}{0}{1}{2}{1}\f] + +@note Rounding to nearest even is procedeed if hardware supports it, if not - to nearest. + +@note Function textual ID is "org.opencv.imgproc.filters.sobel" + +@param src input image. +@param ddepth output image depth, see @ref filter_depths "combinations"; in the case of + 8-bit input images it will result in truncated derivatives. +@param dx order of the derivative x. +@param dy order of the derivative y. +@param ksize size of the extended Sobel kernel; it must be odd. +@param scale optional scale factor for the computed derivative values; by default, no scaling is +applied (see cv::getDerivKernels for details). +@param delta optional delta value that is added to the results prior to storing them in dst. +@param borderType pixel extrapolation method, see cv::BorderTypes +@param borderValue border value in case of constant border type +@sa filter2D, gaussianBlur, cartToPolar + */ +GAPI_EXPORTS GMat sobel(const GMat& src, int ddepth, int dx, int dy, int ksize = 3, + double scale = 1, double delta = 0, + int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); + +/** @brief Finds edges in an image using the Canny algorithm. + +The function finds edges in the input image and marks them in the output map edges using the +Canny algorithm. The smallest value between threshold1 and threshold2 is used for edge linking. The +largest value is used to find initial segments of strong edges. See + + +@note Function textual ID is "org.opencv.imgproc.filters.canny" + +@param image 8-bit input image. +@param threshold1 first threshold for the hysteresis procedure. +@param threshold2 second threshold for the hysteresis procedure. +@param apertureSize aperture size for the Sobel operator. +@param L2gradient a flag, indicating whether a more accurate \f$L_2\f$ norm +\f$=\sqrt{(dI/dx)^2 + (dI/dy)^2}\f$ should be used to calculate the image gradient magnitude ( +L2gradient=true ), or whether the default \f$L_1\f$ norm \f$=|dI/dx|+|dI/dy|\f$ is enough ( +L2gradient=false ). + */ +GAPI_EXPORTS GMat Canny(const GMat& image, double threshold1, double threshold2, + int apertureSize = 3, bool L2gradient = false); + +/** @brief Equalizes the histogram of a grayscale image. + +The function equalizes the histogram of the input image using the following algorithm: + +- Calculate the histogram \f$H\f$ for src . +- Normalize the histogram so that the sum of histogram bins is 255. +- Compute the integral of the histogram: +\f[H'_i = \sum _{0 \le j < i} H(j)\f] +- Transform the image using \f$H'\f$ as a look-up table: \f$\texttt{dst}(x,y) = H'(\texttt{src}(x,y))\f$ + +The algorithm normalizes the brightness and increases the contrast of the image. +@note The returned image is of the same size and type as input. + +@note Function textual ID is "org.opencv.imgproc.equalizeHist" + +@param src Source 8-bit single channel image. + */ +GAPI_EXPORTS GMat equalizeHist(const GMat& src); + +//! @} gapi_filters + +//! @addtogroup gapi_colorconvert +//! @{ +/** @brief Converts an image from RGB color space to gray-scaled. +The conventional ranges for R, G, and B channel values are 0 to 255. +Resulting gray color value computed as +\f[\texttt{dst} (I)= \texttt{0.299} * \texttt{src}(I).R + \texttt{0.587} * \texttt{src}(I).G + \texttt{0.114} * \texttt{src}(I).B \f] + +@note Function textual ID is "org.opencv.imgproc.colorconvert.rgb2gray" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1. +@sa RGB2YUV + */ +GAPI_EXPORTS GMat RGB2Gray(const GMat& src); + +/** @overload +Resulting gray color value computed as +\f[\texttt{dst} (I)= \texttt{rY} * \texttt{src}(I).R + \texttt{gY} * \texttt{src}(I).G + \texttt{bY} * \texttt{src}(I).B \f] + +@note Function textual ID is "org.opencv.imgproc.colorconvert.rgb2graycustom" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1. +@param rY float multiplier for R channel. +@param gY float multiplier for G channel. +@param bY float multiplier for B channel. +@sa RGB2YUV + */ +GAPI_EXPORTS GMat RGB2Gray(const GMat& src, float rY, float gY, float bY); + +/** @brief Converts an image from BGR color space to gray-scaled. +The conventional ranges for B, G, and R channel values are 0 to 255. +Resulting gray color value computed as +\f[\texttt{dst} (I)= \texttt{0.114} * \texttt{src}(I).B + \texttt{0.587} * \texttt{src}(I).G + \texttt{0.299} * \texttt{src}(I).R \f] + +@note Function textual ID is "org.opencv.imgproc.colorconvert.bgr2gray" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1. +@sa BGR2LUV + */ +GAPI_EXPORTS GMat BGR2Gray(const GMat& src); + +/** @brief Converts an image from RGB color space to YUV color space. + +The function converts an input image from RGB color space to YUV. +The conventional ranges for R, G, and B channel values are 0 to 255. + +In case of linear transformations, the range does not matter. But in case of a non-linear +transformation, an input RGB image should be normalized to the proper value range to get the correct +results, like here, at RGB \f$\rightarrow\f$ Y\*u\*v\* transformation. +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.rgb2yuv" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. +@sa YUV2RGB, RGB2Lab +*/ +GAPI_EXPORTS GMat RGB2YUV(const GMat& src); + +/** @brief Converts an image from BGR color space to LUV color space. + +The function converts an input image from BGR color space to LUV. +The conventional ranges for B, G, and R channel values are 0 to 255. + +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.bgr2luv" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. +@sa RGB2Lab, RGB2LUV +*/ +GAPI_EXPORTS GMat BGR2LUV(const GMat& src); + +/** @brief Converts an image from LUV color space to BGR color space. + +The function converts an input image from LUV color space to BGR. +The conventional ranges for B, G, and R channel values are 0 to 255. + +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.luv2bgr" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. +@sa BGR2LUV +*/ +GAPI_EXPORTS GMat LUV2BGR(const GMat& src); + +/** @brief Converts an image from YUV color space to BGR color space. + +The function converts an input image from YUV color space to BGR. +The conventional ranges for B, G, and R channel values are 0 to 255. + +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.yuv2bgr" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. +@sa BGR2YUV +*/ +GAPI_EXPORTS GMat YUV2BGR(const GMat& src); + +/** @brief Converts an image from BGR color space to YUV color space. + +The function converts an input image from BGR color space to YUV. +The conventional ranges for B, G, and R channel values are 0 to 255. + +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.bgr2yuv" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. +@sa YUV2BGR +*/ +GAPI_EXPORTS GMat BGR2YUV(const GMat& src); + +/** @brief Converts an image from RGB color space to Lab color space. + +The function converts an input image from BGR color space to Lab. +The conventional ranges for R, G, and B channel values are 0 to 255. + +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC1. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.rgb2lab" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1. +@sa RGB2YUV, RGB2LUV +*/ +GAPI_EXPORTS GMat RGB2Lab(const GMat& src); + +/** @brief Converts an image from YUV color space to RGB. +The function converts an input image from YUV color space to RGB. +The conventional ranges for Y, U, and V channel values are 0 to 255. + +Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. + +@note Function textual ID is "org.opencv.imgproc.colorconvert.yuv2rgb" + +@param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. + +@sa RGB2Lab, RGB2YUV +*/ +GAPI_EXPORTS GMat YUV2RGB(const GMat& src); + +//! @} gapi_colorconvert +} //namespace gapi +} //namespace cv + +#endif // OPENCV_GAPI_IMGPROC_HPP diff --git a/modules/gapi/include/opencv2/gapi/opencv_includes.hpp b/modules/gapi/include/opencv2/gapi/opencv_includes.hpp new file mode 100644 index 0000000000..9d45a7fb2e --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/opencv_includes.hpp @@ -0,0 +1,16 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OPENCV_INCLUDES_HPP +#define OPENCV_GAPI_OPENCV_INCLUDES_HPP + +#include +#include +#include +#include + +#endif // OPENCV_GAPI_OPENCV_INCLUDES_HPP diff --git a/modules/gapi/include/opencv2/gapi/operators.hpp b/modules/gapi/include/opencv2/gapi/operators.hpp new file mode 100644 index 0000000000..27a1d8012a --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/operators.hpp @@ -0,0 +1,69 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OPERATORS_HPP +#define OPENCV_GAPI_OPERATORS_HPP + +#include "opencv2/gapi/gmat.hpp" +#include "opencv2/gapi/gscalar.hpp" + +GAPI_EXPORTS cv::GMat operator+(const cv::GMat& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator+(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator+(const cv::GScalar& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator-(const cv::GMat& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator-(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator-(const cv::GScalar& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator*(const cv::GMat& lhs, float rhs); +GAPI_EXPORTS cv::GMat operator*(float lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator*(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator*(const cv::GScalar& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator/(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator/(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator/(const cv::GMat& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator&(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator|(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator^(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator~(const cv::GMat& lhs); + +GAPI_EXPORTS cv::GMat operator&(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator|(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator^(const cv::GScalar& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator&(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator|(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator^(const cv::GMat& lhs, const cv::GScalar& rhs); + +GAPI_EXPORTS cv::GMat operator>(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator>=(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator<(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator<=(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator==(const cv::GMat& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator!=(const cv::GMat& lhs, const cv::GMat& rhs); + +GAPI_EXPORTS cv::GMat operator>(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator>=(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator<(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator<=(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator==(const cv::GMat& lhs, const cv::GScalar& rhs); +GAPI_EXPORTS cv::GMat operator!=(const cv::GMat& lhs, const cv::GScalar& rhs); + +GAPI_EXPORTS cv::GMat operator>(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator>=(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator<(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator<=(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator==(const cv::GScalar& lhs, const cv::GMat& rhs); +GAPI_EXPORTS cv::GMat operator!=(const cv::GScalar& lhs, const cv::GMat& rhs); + + + +#endif // OPENCV_GAPI_OPERATORS_HPP diff --git a/modules/gapi/include/opencv2/gapi/own/assert.hpp b/modules/gapi/include/opencv2/gapi/own/assert.hpp new file mode 100644 index 0000000000..39ad735eaf --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/own/assert.hpp @@ -0,0 +1,41 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OWN_ASSERT_HPP +#define OPENCV_GAPI_OWN_ASSERT_HPP + +#if 0 +#include +#define GAPI_Assert(expr) CV_Assert(expr) + +#else +#include +#include +#include "opencv2/gapi/util/throw.hpp" + +namespace detail +{ + inline void assert_abort(const char* str, int line, const char* file, const char* func) + { + std::stringstream ss; + ss << file << ":" << line << ": Assertion " << str << " in function " << func << " failed\n"; + cv::util::throw_error(std::logic_error(ss.str())); + } +} + +#define GAPI_Assert(expr) \ +{ if (!(expr)) ::detail::assert_abort(#expr, __LINE__, __FILE__, __func__); } + +#endif + +#ifdef _DEBUG +# define GAPI_DbgAssert(expr) GAPI_Assert(expr) +#else +# define GAPI_DbgAssert(expr) +#endif + +#endif // OPENCV_GAPI_OWN_ASSERT_HPP diff --git a/modules/gapi/include/opencv2/gapi/own/convert.hpp b/modules/gapi/include/opencv2/gapi/own/convert.hpp new file mode 100644 index 0000000000..fc32e19fd4 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/own/convert.hpp @@ -0,0 +1,47 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OWN_CONVERT_HPP +#define OPENCV_GAPI_OWN_CONVERT_HPP + +#include +#include +#include +#include +#include "opencv2/gapi/own/scalar.hpp" + +namespace cv +{ + inline cv::gapi::own::Mat to_own(Mat const& m) { return {m.rows, m.cols, m.type(), m.data, m.step};}; + cv::gapi::own::Mat to_own(Mat&&) = delete; + + inline cv::gapi::own::Scalar to_own(const cv::Scalar& s) { return {s[0], s[1], s[2], s[3]}; }; + + inline cv::gapi::own::Size to_own (const Size& s) { return {s.width, s.height}; }; + + inline cv::gapi::own::Rect to_own (const Rect& r) { return {r.x, r.y, r.width, r.height}; }; + + + +namespace gapi +{ +namespace own +{ + inline cv::Mat to_ocv(Mat const& m) { return {m.rows, m.cols, m.type(), m.data, m.step};}; + cv::Mat to_ocv(Mat&&) = delete; + + inline cv::Scalar to_ocv(const Scalar& s) { return {s[0], s[1], s[2], s[3]}; }; + + inline cv::Size to_ocv (const Size& s) { return cv::Size(s.width, s.height); }; + + inline cv::Rect to_ocv (const Rect& r) { return cv::Rect(r.x, r.y, r.width, r.height); }; + +} // namespace own +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_OWN_CONVERT_HPP diff --git a/modules/gapi/include/opencv2/gapi/own/exports.hpp b/modules/gapi/include/opencv2/gapi/own/exports.hpp new file mode 100644 index 0000000000..0d955d0e4c --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/own/exports.hpp @@ -0,0 +1,28 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OWN_TYPES_HPP +#define OPENCV_GAPI_OWN_TYPES_HPP + +# if 0 +# include +# define GAPI_EXPORTS CV_EXPORTS + +# else +# if defined _WIN32 +# define GAPI_EXPORTS __declspec(dllexport) +# elif defined __GNUC__ && __GNUC__ >= 4 +# define GAPI_EXPORTS __attribute__ ((visibility ("default"))) +# endif + +# ifndef GAPI_EXPORTS +# define GAPI_EXPORTS +# endif + +# endif + +#endif // OPENCV_GAPI_OWN_TYPES_HPP diff --git a/modules/gapi/include/opencv2/gapi/own/mat.hpp b/modules/gapi/include/opencv2/gapi/own/mat.hpp new file mode 100644 index 0000000000..8cccc7ca8b --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/own/mat.hpp @@ -0,0 +1,142 @@ +// 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) 2018 Intel Corporation + + +#ifndef INCLUDE_OPENCV2_GAPI_OWN_MAT_HPP +#define INCLUDE_OPENCV2_GAPI_OWN_MAT_HPP + +#include "opencv2/core/cvdef.h" +#include "opencv2/gapi/own/types.hpp" +#include //std::shared_ptr + +namespace cv { namespace gapi { namespace own { + namespace detail { + inline size_t default_step(int type, int cols) + { + return CV_ELEM_SIZE(type) * cols; + } + //Matrix header, i.e. fields that are unique to each Mat object. + //Devoted class is needed to implement custom behavior on move (erasing state of moved from object) + struct MatHeader{ + enum { AUTO_STEP = 0}; + enum { TYPE_MASK = 0x00000FFF }; + + MatHeader() = default; + + MatHeader(int _rows, int _cols, int type, void* _data, size_t _step) + : flags((type & TYPE_MASK)), rows(_rows), cols(_cols), data((uchar*)_data), step(_step == AUTO_STEP ? detail::default_step(type, _cols) : _step) + {} + + MatHeader(const MatHeader& ) = default; + MatHeader(MatHeader&& src) : MatHeader(src) // reuse copy constructor here + { + MatHeader empty; //give it a name to call copy(not move) assignment below + src = empty; + } + MatHeader& operator=(const MatHeader& ) = default; + MatHeader& operator=(MatHeader&& src) + { + *this = src; //calling a copy assignment here, not move one + MatHeader empty; //give it a name to call copy(not move) assignment below + src = empty; + return *this; + } + /*! includes several bit-fields: + - depth + - number of channels + */ + int flags = 0; + + //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions + int rows = 0, cols = 0; + //! pointer to the data + uchar* data = nullptr; + size_t step = 0; + }; + } + //concise version of cv::Mat suitable for GAPI needs (used when no dependence on OpenCV is required) + class Mat : public detail::MatHeader{ + public: + + Mat() = default; + + /** @overload + @param _rows Number of rows in a 2D array. + @param _cols Number of columns in a 2D array. + @param _type Array type. Use CV_8UC1, ..., CV_64FC4 to create 1-4 channel matrices, or + CV_8UC(n), ..., CV_64FC(n) to create multi-channel (up to CV_CN_MAX channels) matrices. + @param _data Pointer to the user data. Matrix constructors that take data and step parameters do not + allocate matrix data. Instead, they just initialize the matrix header that points to the specified + data, which means that no data is copied. This operation is very efficient and can be used to + process external data using OpenCV functions. The external data is not automatically deallocated, so + you should take care of it. + @param _step Number of bytes each matrix row occupies. The value should include the padding bytes at + the end of each row, if any. If the parameter is missing (set to AUTO_STEP ), no padding is assumed + and the actual step is calculated as cols*elemSize(). See Mat::elemSize. + */ + Mat(int _rows, int _cols, int _type, void* _data, size_t _step = AUTO_STEP) + : MatHeader (_rows, _cols, _type, _data, _step) + {} + + Mat(Mat const& src) = default; + Mat(Mat&& src) = default; + + Mat& operator=(Mat const& src) = default; + Mat& operator=(Mat&& src) = default; + + /** @brief Returns the type of a matrix element. + + The method returns a matrix element type. This is an identifier compatible with the CvMat type + system, like CV_16SC3 or 16-bit signed 3-channel array, and so on. + */ + int type() const {return CV_MAT_TYPE(flags);} + + /** @brief Returns the depth of a matrix element. + + The method returns the identifier of the matrix element depth (the type of each individual channel). + For example, for a 16-bit signed element array, the method returns CV_16S . A complete list of + matrix types contains the following values: + - CV_8U - 8-bit unsigned integers ( 0..255 ) + - CV_8S - 8-bit signed integers ( -128..127 ) + - CV_16U - 16-bit unsigned integers ( 0..65535 ) + - CV_16S - 16-bit signed integers ( -32768..32767 ) + - CV_32S - 32-bit signed integers ( -2147483648..2147483647 ) + - CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN ) + - CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN ) + */ + int depth() const {return CV_MAT_DEPTH(flags);} + + /** @brief Returns the number of matrix channels. + + The method returns the number of matrix channels. + */ + int channels() const {return CV_MAT_CN(flags);} + + /** @overload + @param _size Alternative new matrix size specification: Size(cols, rows) + @param _type New matrix type. + */ + void create(cv::gapi::own::Size _size, int _type) + { + if (_size != cv::gapi::own::Size{cols, rows} ) + { + Mat tmp{_size.height, _size.width, _type, nullptr}; + tmp.memory.reset(new uchar[ tmp.step * tmp.rows], [](uchar * p){delete[] p;}); + tmp.data = tmp.memory.get(); + + *this = std::move(tmp); + } + } + private: + //actual memory allocated for storage, or nullptr if object is non owning view to over memory + std::shared_ptr memory; + }; + +} //namespace own +} //namespace gapi +} //namespace cv + +#endif /* INCLUDE_OPENCV2_GAPI_OWN_MAT_HPP */ diff --git a/modules/gapi/include/opencv2/gapi/own/scalar.hpp b/modules/gapi/include/opencv2/gapi/own/scalar.hpp new file mode 100644 index 0000000000..52a45af332 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/own/scalar.hpp @@ -0,0 +1,45 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GAPI_OWN_SCALAR_HPP +#define OPENCV_GAPI_GAPI_OWN_SCALAR_HPP + +namespace cv +{ +namespace gapi +{ +namespace own +{ + +class CV_EXPORTS Scalar +{ +public: + Scalar() = default; + explicit Scalar(double v0) { val[0] = v0; }; + Scalar(double v0, double v1, double v2 = 0, double v3 = 0) + : val{v0, v1, v2, v3} + { + } + + const double& operator[](int i) const { return val[i]; } + double& operator[](int i) { return val[i]; } + + static Scalar all(double v0) { return Scalar(v0, v0, v0, v0); } + + double val[4] = {0}; +}; + +inline bool operator==(const Scalar& lhs, const Scalar& rhs) +{ + return std::equal(std::begin(lhs.val), std::end(lhs.val), std::begin(rhs.val)); +} + +} // namespace own +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_GAPI_OWN_SCALAR_HPP diff --git a/modules/gapi/include/opencv2/gapi/own/types.hpp b/modules/gapi/include/opencv2/gapi/own/types.hpp new file mode 100644 index 0000000000..2f909a4982 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/own/types.hpp @@ -0,0 +1,137 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_TYPES_HPP +#define OPENCV_GAPI_TYPES_HPP + +#include // std::max, std::min + +#include "opencv2/core/base.hpp" //for CV_DbgAssert +#include "opencv2/gapi/own/assert.hpp" + +namespace cv +{ +namespace gapi +{ +namespace own +{ + +class Point +{ +public: + Point() = default; + Point(int _x, int _y) : x(_x), y(_y) {}; + + int x = 0; + int y = 0; +}; + +class Rect +{ +public: + Rect() = default; + Rect(int _x, int _y, int _width, int _height) : x(_x), y(_y), width(_width), height(_height) {}; +#if 1 + Rect(const cv::Rect& other) : x(other.x), y(other.y), width(other.width), height(other.height) {}; + inline Rect& operator=(const cv::Rect& other) + { + x = other.x; + y = other.x; + width = other.width; + height = other.height; + return *this; + } +#endif + + int x = 0; //!< x coordinate of the top-left corner + int y = 0; //!< y coordinate of the top-left corner + int width = 0; //!< width of the rectangle + int height = 0; //!< height of the rectangle +}; + +inline bool operator==(const Rect& lhs, const Rect& rhs) +{ + return lhs.x == rhs.x && lhs.y == rhs.y && lhs.width == rhs.width && lhs.height == rhs.height; +} + +inline bool operator!=(const Rect& lhs, const Rect& rhs) +{ + return !(lhs == rhs); +} + +inline Rect& operator&=(Rect& lhs, const Rect& rhs) +{ + int x1 = std::max(lhs.x, rhs.x); + int y1 = std::max(lhs.y, rhs.y); + lhs.width = std::min(lhs.x + lhs.width, rhs.x + rhs.width) - x1; + lhs.height = std::min(lhs.y + lhs.height, rhs.y + rhs.height) - y1; + lhs.x = x1; + lhs.y = y1; + if( lhs.width <= 0 || lhs.height <= 0 ) + lhs = Rect(); + return lhs; +} + +inline const Rect operator&(const Rect& lhs, const Rect& rhs) +{ + Rect result = lhs; + return result &= rhs; +} + +inline std::ostream& operator<<(std::ostream& o, const Rect& rect) +{ + return o << "[" << rect.width << " x " << rect.height << " from (" << rect.x << ", " << rect.y << ")]"; +} + +class Size +{ +public: + Size() = default; + Size(int _width, int _height) : width(_width), height(_height) {}; +#if 1 + Size(const cv::Size& other) : width(other.width), height(other.height) {}; + inline Size& operator=(const cv::Size& rhs) + { + width = rhs.width; + height = rhs.height; + return *this; + } +#endif + + int width = 0; + int height = 0; +}; + +inline Size& operator+=(Size& lhs, const Size& rhs) +{ + lhs.width += rhs.width; + lhs.height += rhs.height; + return lhs; +} + +inline bool operator==(const Size& lhs, const Size& rhs) +{ + return lhs.width == rhs.width && lhs.height == rhs.height; +} + +inline bool operator!=(const Size& lhs, const Size& rhs) +{ + return !(lhs == rhs); +} + + +inline std::ostream& operator<<(std::ostream& o, const Size& s) +{ + o << "[" << s.width << " x " << s.height << "]"; + return o; +} + +} // namespace own +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_TYPES_HPP diff --git a/modules/gapi/include/opencv2/gapi/util/any.hpp b/modules/gapi/include/opencv2/gapi/util/any.hpp new file mode 100644 index 0000000000..31e401181c --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/util/any.hpp @@ -0,0 +1,159 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_UTIL_ANY_HPP +#define OPENCV_GAPI_UTIL_ANY_HPP + +#include +#include +#include +#include + +#include "opencv2/gapi/util/throw.hpp" + +#if defined(_MSC_VER) + // disable MSVC warning on "multiple copy constructors specified" +# pragma warning(disable: 4521) +#endif + +namespace cv +{ + +namespace internal +{ + template + T down_cast(Source operand) + { +#if defined(__GXX_RTTI) || defined(_CPPRTTI) + return dynamic_cast(operand); +#else + #warning used static cast instead of dynamic because RTTI is disabled + return static_cast(operand); +#endif + } +} + +namespace util +{ + class bad_any_cast : public std::bad_cast + { + public: + virtual const char* what() const noexcept override + { + return "Bad any cast"; + } + }; + + //modeled against C++17 std::any + + class any + { + private: + struct holder; + using holder_ptr = std::unique_ptr; + struct holder + { + virtual holder_ptr clone() = 0; + virtual ~holder() = default; + }; + + template + struct holder_impl : holder + { + value_t v; + template + holder_impl(arg_t&& a) : v(std::forward(a)) {} + holder_ptr clone() override { return holder_ptr(new holder_impl (v));} + }; + + holder_ptr hldr; + public: + template + any(value_t&& arg) : hldr(new holder_impl::type>( std::forward(arg))) {} + + any(any const& src) : hldr( src.hldr ? src.hldr->clone() : nullptr) {} + //simple hack in order not to write enable_if for the template constructor + any(any & src) : any (const_cast(src)) {} + + any() = default; + any(any&& ) = default; + + any& operator=(any&&) = default; + + any& operator=(any const& src) + { + any copy(src); + swap(*this, copy); + return *this; + } + + template + friend value_t* any_cast(any* operand); + + template + friend const value_t* any_cast(const any* operand); + + friend void swap(any & lhs, any& rhs) + { + swap(lhs.hldr, rhs.hldr); + } + + }; + + template + value_t* any_cast(any* operand) + { + auto casted = internal::down_cast::type> *>(operand->hldr.get()); + if (casted){ + return & (casted->v); + } + return nullptr; + } + + template + const value_t* any_cast(const any* operand) + { + auto casted = internal::down_cast::type> *>(operand->hldr.get()); + if (casted){ + return & (casted->v); + } + return nullptr; + } + + template + value_t& any_cast(any& operand) + { + auto ptr = any_cast(&operand); + if (ptr) + { + return *ptr; + } + + throw_error(bad_any_cast()); + } + + + template + const value_t& any_cast(const any& operand) + { + auto ptr = any_cast(&operand); + if (ptr) + { + return *ptr; + } + + throw_error(bad_any_cast()); + } +} // namespace util +} // namespace cv + +#if defined(_MSC_VER) + // Enable "multiple copy constructors specified" back +# pragma warning(default: 4521) +#endif + +#endif // OPENCV_GAPI_UTIL_ANY_HPP diff --git a/modules/gapi/include/opencv2/gapi/util/compiler_hints.hpp b/modules/gapi/include/opencv2/gapi/util/compiler_hints.hpp new file mode 100644 index 0000000000..575655e8fb --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/util/compiler_hints.hpp @@ -0,0 +1,21 @@ +// 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) 2018 Intel Corporation + +#ifndef OPENCV_GAPI_UTIL_COMPILER_HINTS_HPP +#define OPENCV_GAPI_UTIL_COMPILER_HINTS_HPP + +namespace cv +{ +namespace util +{ + //! Utility template function to prevent "unused" warnings by various compilers. + template void suppress_unused_warning( const T& ) {} +} // namespace util +} // namespace cv + +#define UNUSED(x) cv::util::suppress_unused_warning(x) + +#endif /* OPENCV_GAPI_UTIL_COMPILER_HINTS_HPP */ diff --git a/modules/gapi/include/opencv2/gapi/util/optional.hpp b/modules/gapi/include/opencv2/gapi/util/optional.hpp new file mode 100644 index 0000000000..54126d6271 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/util/optional.hpp @@ -0,0 +1,178 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_UTIL_OPTIONAL_HPP +#define OPENCV_GAPI_UTIL_OPTIONAL_HPP + +#include "opencv2/gapi/util/variant.hpp" + +// A poor man's `optional` implementation, incompletely modeled against C++17 spec. +namespace cv +{ +namespace util +{ + class bad_optional_access: public std::exception + { + public: + virtual const char *what() const noexcept override + { + return "Bad optional access"; + } + }; + + // TODO: nullopt_t + + // Interface /////////////////////////////////////////////////////////////// + template class optional + { + public: + // Constructors + // NB.: there were issues with Clang 3.8 when =default() was used + // instead {} + optional() {}; + optional(const optional&) = default; + explicit optional(T &&value) noexcept; + explicit optional(const T &value) noexcept; + optional(optional &&) noexcept; + // TODO: optional(nullopt_t) noexcept; + // TODO: optional(const optional &) + // TODO: optional(optional &&) + // TODO: optional(Args&&...) + // TODO: optional(initializer_list) + // TODO: optional(U&& value); + + // Assignment + optional& operator=(const optional& rhs) = default; + optional& operator=(optional&& rhs); + + // Observers + T* operator-> (); + const T* operator-> () const; + T& operator* (); + const T& operator* () const; + // TODO: && versions + + operator bool() const noexcept; + bool has_value() const noexcept; + + T& value(); + const T& value() const; + // TODO: && versions + + template + T value_or(U &&default_value) const; + + void swap(optional &other) noexcept; + void reset() noexcept; + // TODO: emplace + + // TODO: operator==, !=, <, <=, >, >= + + private: + struct nothing {}; + util::variant m_holder; + }; + + template + optional::type> make_optional(T&& value); + + // TODO: Args... and initializer_list versions + + // Implementation ////////////////////////////////////////////////////////// + template optional::optional(T &&v) noexcept + : m_holder(v) + { + } + + template optional::optional(const T &v) noexcept + : m_holder(v) + { + } + + template optional::optional(optional&& rhs) noexcept + : m_holder(std::move(rhs.m_holder)) + { + rhs.reset(); + } + + template optional& optional::operator=(optional&& rhs) + { + m_holder = std::move(rhs.m_holder); + rhs.reset(); + return *this; + } + + template T* optional::operator-> () + { + return & *(*this); + } + + template const T* optional::operator-> () const + { + return & *(*this); + } + + template T& optional::operator* () + { + return this->value(); + } + + template const T& optional::operator* () const + { + return this->value(); + } + + template optional::operator bool() const noexcept + { + return this->has_value(); + } + + template bool optional::has_value() const noexcept + { + return util::holds_alternative(m_holder); + } + + template T& optional::value() + { + if (!this->has_value()) + throw_error(bad_optional_access()); + return util::get(m_holder); + } + + template const T& optional::value() const + { + if (!this->has_value()) + throw_error(bad_optional_access()); + return util::get(m_holder); + } + + template + template T optional::value_or(U &&default_value) const + { + return (this->has_value() ? this->value() : T(default_value)); + } + + template void optional::swap(optional &other) noexcept + { + m_holder.swap(other.m_holder); + } + + template void optional::reset() noexcept + { + if (this->has_value()) + m_holder = nothing{}; + } + + template + optional::type> make_optional(T&& value) + { + return optional::type>(std::forward(value)); + } +} // namespace util +} // namespace cv + +#endif // OPENCV_GAPI_UTIL_OPTIONAL_HPP diff --git a/modules/gapi/include/opencv2/gapi/util/throw.hpp b/modules/gapi/include/opencv2/gapi/util/throw.hpp new file mode 100644 index 0000000000..689bf583cf --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/util/throw.hpp @@ -0,0 +1,36 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_UTIL_THROW_HPP +#define OPENCV_GAPI_UTIL_THROW_HPP + +#include // std::forward + +#if !defined(__EXCEPTIONS) +#include +#include +#endif + +namespace cv +{ +namespace util +{ +template +[[noreturn]] void throw_error(ExceptionType &&e) +{ +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) + throw std::forward(e); +#else + fprintf(stderr, "An exception thrown! %s\n" , e.what()); + fflush(stderr); + abort(); +#endif +} +} // namespace util +} // namespace cv + +#endif // OPENCV_GAPI_UTIL_THROW_HPP diff --git a/modules/gapi/include/opencv2/gapi/util/util.hpp b/modules/gapi/include/opencv2/gapi/util/util.hpp new file mode 100644 index 0000000000..d0378e0e52 --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/util/util.hpp @@ -0,0 +1,92 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_UTIL_HPP +#define OPENCV_GAPI_UTIL_HPP + +#include // std::tuple + +// \cond HIDDEN_SYMBOLS +// This header file contains some generic utility functions which are +// used in other G-API Public API headers. +// +// PLEASE don't put any stuff here if it is NOT used in public API headers! + +namespace cv +{ +namespace detail +{ + // Recursive integer sequence type, useful for enumerating elements of + // template parameter packs. + template struct Seq { using next = Seq; }; + template struct MkSeq { using type = typename MkSeq::type::next; }; + template<> struct MkSeq<0>{ using type = Seq<>; }; + + // Checks if elements of variadic template satisfy the given Predicate. + // Implemented via tuple, with an interface to accept plain type lists + template class, typename, typename...> struct all_satisfy; + + template class F, typename T, typename... Ts> + struct all_satisfy > + { + static const constexpr bool value = F::value + && all_satisfy >::value; + }; + template class F, typename T> + struct all_satisfy > + { + static const constexpr bool value = F::value; + }; + + template class F, typename T, typename... Ts> + struct all_satisfy: public all_satisfy > {}; + + // Permute given tuple type C with given integer sequence II + // Sequence may be less than tuple C size. + template struct permute_tuple; + + template + struct permute_tuple > + { + using type = std::tuple< typename std::tuple_element::type... >; + }; + + // Given T..., generates a type sequence of sizeof...(T)-1 elements + // which is T... without its last element + // Implemented via tuple, with an interface to accept plain type lists + template struct all_but_last; + + template + struct all_but_last > + { + using C = std::tuple; + using S = typename MkSeq::value - 1>::type; + using type = typename permute_tuple::type; + }; + + template + struct all_but_last: public all_but_last > {}; + + template + using all_but_last_t = typename all_but_last::type; + + // NB.: This is here because there's no constexpr std::max in C++11 + template struct max_of_t + { + static constexpr const std::size_t rest = max_of_t::value; + static constexpr const std::size_t value = rest > S0 ? rest : S0; + }; + template struct max_of_t + { + static constexpr const std::size_t value = S; + }; +} // namespace detail +} // namespace cv + +// \endcond + +#endif // OPENCV_GAPI_UTIL_HPP diff --git a/modules/gapi/include/opencv2/gapi/util/variant.hpp b/modules/gapi/include/opencv2/gapi/util/variant.hpp new file mode 100644 index 0000000000..cb0270a73d --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/util/variant.hpp @@ -0,0 +1,377 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_UTIL_VARIANT_HPP +#define OPENCV_GAPI_UTIL_VARIANT_HPP + +#include +#include + +#include "opencv2/gapi/util/throw.hpp" +#include "opencv2/gapi/util/util.hpp" // max_of_t + +// A poor man's `variant` implementation, incompletely modeled against C++17 spec. +namespace cv +{ +namespace util +{ + namespace detail + { + template + struct type_list_index_helper + { + static const constexpr bool is_same = std::is_same::value; + static const constexpr std::size_t value = + std::conditional, type_list_index_helper>::type::value; + }; + + template + struct type_list_index_helper + { + static_assert(std::is_same::value, "Type not found"); + static const constexpr std::size_t value = I; + }; + + + template using are_different = + std::enable_if::type, + typename std::decay::type>::value, + V>; + } + + template + struct type_list_index + { + static const constexpr std::size_t value = detail::type_list_index_helper<0, Target, Types...>::value; + }; + + class bad_variant_access: public std::exception + { + public: + virtual const char *what() const noexcept override + { + return "Bad variant access"; + } + }; + + // Interface /////////////////////////////////////////////////////////////// + struct monostate {}; + inline bool operator==(const util::monostate&, const util::monostate&) + { + return true; + } + + template // FIXME: no references, arrays, and void + class variant + { + // FIXME: Replace with std::aligned_union after gcc4.8 support is dropped + static constexpr const std::size_t S = cv::detail::max_of_t::value; + static constexpr const std::size_t A = cv::detail::max_of_t::value; + using Memory = typename std::aligned_storage::type[1]; + + template struct cctr_h { + static void help(Memory memory, const Memory from) { + new (memory) T(*reinterpret_cast(from)); + } + }; + + template struct vctr_h { + static void help(Memory memory, const void* pval) { + new (memory) T(*reinterpret_cast(pval)); + } + }; + + template struct mctr_h { + static void help(Memory memory, void *pval) { + new (memory) T(std::move(*reinterpret_cast(pval))); + } + }; + + template struct copy_h { + static void help(Memory to, const Memory from) { + *reinterpret_cast(to) = *reinterpret_cast(from); + } + }; + + template struct move_h { + static void help(Memory to, const Memory from) { + *reinterpret_cast(to) = std::move(*reinterpret_cast(from)); + } + }; + + template struct swap_h { + static void help(Memory to, Memory from) { + std::swap(*reinterpret_cast(to), *reinterpret_cast(from)); + } + }; + + template struct dtor_h { + static void help(Memory memory) { + (void) memory; // MSCV warning + reinterpret_cast(memory)->~T(); + } + }; + + template struct equal_h { + static bool help(const Memory lhs, const Memory rhs) { + const T& t_lhs = *reinterpret_cast(lhs); + const T& t_rhs = *reinterpret_cast(rhs); + return t_lhs == t_rhs; + } + }; + + typedef void (*CCtr) (Memory, const Memory); // Copy c-tor (variant) + typedef void (*VCtr) (Memory, const void*); // Copy c-tor (value) + typedef void (*MCtr) (Memory, void*); // Generic move c-tor + typedef void (*Copy) (Memory, const Memory); // Copy assignment + typedef void (*Move) (Memory, const Memory); // Move assignment + typedef void (*Swap) (Memory, Memory); // Swap + typedef void (*Dtor) (Memory); // Destructor + + typedef bool (*Equal)(const Memory, const Memory); // Equality test (external) + + static constexpr std::array cctrs(){ return {{(&cctr_h::help)...}};} + static constexpr std::array vctrs(){ return {{(&vctr_h::help)...}};} + static constexpr std::array mctrs(){ return {{(&mctr_h::help)...}};} + static constexpr std::array cpyrs(){ return {{(©_h::help)...}};} + static constexpr std::array mvers(){ return {{(&move_h::help)...}};} + static constexpr std::array swprs(){ return {{(&swap_h::help)...}};} + static constexpr std::array dtors(){ return {{(&dtor_h::help)...}};} + + std::size_t m_index = 0; + + protected: + template friend T& get(variant &v); + template friend const T& get(const variant &v); + template friend bool operator==(const variant &lhs, + const variant &rhs); + Memory memory; + + public: + // Constructors + variant() noexcept; + variant(const variant& other); + variant(variant&& other) noexcept; + template explicit variant(const T& t); + // are_different is a SFINAE trick to avoid variant(T &&t) with T=variant + // for some reason, this version is called instead of variant(variant&& o) when + // variant is used in STL containers (examples: vector assignment) + template explicit variant(T&& t, typename detail::are_different::type = 0); + // template explicit variant(Args&&... args); + // FIXME: other constructors + + // Destructor + ~variant(); + + // Assignment + variant& operator=(const variant& rhs); + variant& operator=(variant &&rhs) noexcept; + + // SFINAE trick to avoid operator=(T&&) with T=variant<>, see comment above + template + typename detail::are_different + ::type operator=(T&& t) noexcept; + + // Observers + std::size_t index() const noexcept; + // FIXME: valueless_by_exception() + + // Modifiers + // FIXME: emplace() + void swap(variant &rhs) noexcept; + + // Non-C++17x! + template static constexpr std::size_t index_of(); + }; + + // FIMXE: visit + + template + T& get(util::variant &v); + + template + const T& get(const util::variant &v); + + template + bool holds_alternative(const util::variant &v) noexcept; + + // FIXME: T&&, const TT&& versions. + + // Implementation ////////////////////////////////////////////////////////// + template + variant::variant() noexcept + { + typedef typename std::tuple_element<0, std::tuple >::type TFirst; + new (memory) TFirst(); + } + + template + variant::variant(const variant &other) + : m_index(other.m_index) + { + (cctrs()[m_index])(memory, other.memory); + } + + template + variant::variant(variant &&other) noexcept + : m_index(other.m_index) + { + (mctrs()[m_index])(memory, other.memory); + } + + template + template + variant::variant(const T& t) + : m_index(util::type_list_index::value) + { + (vctrs()[m_index])(memory, &t); + } + + template + template + variant::variant(T&& t, typename detail::are_different::type) + : m_index(util::type_list_index::type, Ts...>::value) + { + (mctrs()[m_index])(memory, &t); + } + + template + variant::~variant() + { + (dtors()[m_index])(memory); + } + + template + variant& variant::operator=(const variant &rhs) + { + if (m_index != rhs.m_index) + { + (dtors()[ m_index])(memory); + (cctrs()[rhs.m_index])(memory, rhs.memory); + m_index = rhs.m_index; + } + else + { + (cpyrs()[rhs.m_index])(memory, rhs.memory); + } + return *this; + } + + template + variant& variant::operator=(variant &&rhs) noexcept + { + if (m_index != rhs.m_index) + { + (dtors()[ m_index])(memory); + (mctrs()[rhs.m_index])(memory, rhs.memory); + m_index = rhs.m_index; + } + else + { + (mvers()[rhs.m_index])(memory, rhs.memory); + } + return *this; + } + + template + template typename detail::are_different, T, variant&> + ::type variant::operator=(T&& t) noexcept + { + // FIXME: No version with implicit type conversion available! + static const constexpr std::size_t t_index = + util::type_list_index::value; + + if (t_index == m_index) + { + util::get(*this) = std::move(t); + return *this; + } + else return (*this = variant(std::move(t))); + } + + template + std::size_t util::variant::index() const noexcept + { + return m_index; + } + + template + void variant::swap(variant &rhs) noexcept + { + if (m_index == rhs.index()) + { + (swprs()[m_index](memory, rhs.memory)); + } + else + { + variant tmp(std::move(*this)); + *this = std::move(rhs); + rhs = std::move(tmp); + } + } + + template + template + constexpr std::size_t variant::index_of() + { + return util::type_list_index::value; // FIXME: tests! + } + + template + T& get(util::variant &v) + { + const constexpr std::size_t t_index = + util::type_list_index::value; + + if (v.index() == t_index) + return reinterpret_cast(v.memory); + else + throw_error(bad_variant_access()); + } + + template + const T& get(const util::variant &v) + { + const constexpr std::size_t t_index = + util::type_list_index::value; + + if (v.index() == t_index) + return reinterpret_cast(v.memory); + else + throw_error(bad_variant_access()); + } + + template + bool holds_alternative(const util::variant &v) noexcept + { + return v.index() == util::variant::template index_of(); + } + + template bool operator==(const variant &lhs, + const variant &rhs) + { + using V = variant; + + // Instantiate table only here since it requires operator== for + // should have operator== only if this one is used, not in general + static const std::array eqs = { + {(&V::template equal_h::help)...} + }; + if (lhs.index() != rhs.index()) + return false; + return (eqs[lhs.index()])(lhs.memory, rhs.memory); + } + + template bool operator!=(const variant &lhs, + const variant &rhs) + { + return !(lhs == rhs); + } +} // namespace cv +} // namespace util + +#endif // OPENCV_GAPI_UTIL_VARIANT_HPP diff --git a/modules/gapi/perf/common/gapi_core_perf_tests.hpp b/modules/gapi/perf/common/gapi_core_perf_tests.hpp new file mode 100644 index 0000000000..49d7791f75 --- /dev/null +++ b/modules/gapi/perf/common/gapi_core_perf_tests.hpp @@ -0,0 +1,1782 @@ +// 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) 2018 Intel Corporation + + +#include "../../test/common/gapi_tests_common.hpp" +#include "opencv2/gapi/core.hpp" + +namespace opencv_test +{ + using namespace perf; + + enum bitwiseOp + { + AND = 0, + OR = 1, + XOR = 2, + NOT = 3 + }; + +//------------------------------------------------------------------------------ + + class AddPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(AddPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::add(in_mat1, in_mat2, out_mat_ocv, cv::noArray(), dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::add(in1, in2, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class AddCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(AddCPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::add(in_mat1, sc, out_mat_ocv, cv::noArray(), dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::addC(in1, sc1, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class SubPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(SubPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::subtract(in_mat1, in_mat2, out_mat_ocv, cv::noArray(), dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::sub(in1, in2, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class SubCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(SubCPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::subtract(in_mat1, sc, out_mat_ocv, cv::noArray(), dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::subC(in1, sc1, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class SubRCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(SubRCPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::subtract(sc, in_mat1, out_mat_ocv, cv::noArray(), dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::subRC(sc1, in1, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MulPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MulPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::multiply(in_mat1, in_mat2, out_mat_ocv, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::mul(in1, in2, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MulDoublePerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MulDoublePerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + auto& rng = cv::theRNG(); + double d = rng.uniform(0.0, 10.0); + initMatrixRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::multiply(in_mat1, d, out_mat_ocv, 1, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + out = cv::gapi::mulC(in1, d, dtype); + cv::GComputation c(in1, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MulCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MulCPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::multiply(in_mat1, sc, out_mat_ocv, 1, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::mulC(in1, sc1, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class DivPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(DivPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::divide(in_mat1, in_mat2, out_mat_ocv, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::div(in1, in2, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class DivCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(DivCPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::divide(in_mat1, sc, out_mat_ocv, 1.0, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::divC(in1, sc1, 1.0, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class DivRCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(DivRCPerfTest, TestPerformance) + { + Size sz = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + initMatsRandU(type, sz, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::divide(sc, in_mat1, out_mat_ocv, 1.0, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::divRC(sc1, in1, 1.0, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MaskPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MaskPerfTest, TestPerformance) + { + Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatrixRandU(type, sz_in, type, false); + in_mat2 = cv::Mat(sz_in, CV_8UC1); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + in_mat2 = in_mat2 > 128; + + // OpenCV code /////////////////////////////////////////////////////////// + out_mat_ocv = cv::Mat::zeros(in_mat1.size(), in_mat1.type()); + in_mat1.copyTo(out_mat_ocv, in_mat2); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in, m; + auto out = cv::gapi::mask(in, m); + cv::GComputation c(cv::GIn(in, m), cv::GOut(out)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MeanPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MeanPerfTest, TestPerformance) + { + Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatrixRandU(type, sz_in, false); + cv::Scalar out_norm; + cv::Scalar out_norm_ocv; + + // OpenCV code /////////////////////////////////////////////////////////// + out_norm_ocv = cv::mean(in_mat1); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::mean(in); + cv::GComputation c(cv::GIn(in), cv::GOut(out)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1), cv::gout(out_norm)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1), cv::gout(out_norm)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(out_norm[0], out_norm_ocv[0]); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class Polar2CartPerfTest : public TestPerfParams {}; + PERF_TEST_P_(Polar2CartPerfTest, TestPerformance) + { + Size sz_in = GetParam(); + + initMatsRandU(CV_32FC1, sz_in, CV_32FC1, false); + cv::Mat out_mat2; + cv::Mat out_mat_ocv2; + + // OpenCV code /////////////////////////////////////////////////////////// + cv::polarToCart(in_mat1, in_mat2, out_mat_ocv, out_mat_ocv2); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out1, out2; + std::tie(out1, out2) = cv::gapi::polarToCart(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out1, out2)); + + // Warm-up graph engine: + c.apply(gin(in_mat1,in_mat2), gout(out_mat_gapi, out_mat2)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1,in_mat2), gout(out_mat_gapi, out_mat2)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class Cart2PolarPerfTest : public TestPerfParams {}; + PERF_TEST_P_(Cart2PolarPerfTest, TestPerformance) + { + Size sz_in = GetParam(); + + initMatsRandU(CV_32FC1, sz_in, CV_32FC1, false); + cv::Mat out_mat2(sz_in, CV_32FC1); + cv::Mat out_mat_ocv2(sz_in, CV_32FC1); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::cartToPolar(in_mat1, in_mat2, out_mat_ocv, out_mat_ocv2); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out1, out2; + std::tie(out1, out2) = cv::gapi::cartToPolar(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out1, out2)); + + // Warm-up graph engine: + c.apply(gin(in_mat1,in_mat2), gout(out_mat_gapi, out_mat2)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1,in_mat2), gout(out_mat_gapi, out_mat2)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class CmpPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(CmpPerfTest, TestPerformance) + { + CmpTypes opType = get<0>(GetParam()); + cv::Size sz = get<1>(GetParam()); + MatType type = get<2>(GetParam()); + + initMatsRandU(type, sz, CV_8U, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::compare(in_mat1, in_mat2, out_mat_ocv, opType); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + switch(opType) + { + case CMP_EQ: out = cv::gapi::cmpEQ(in1, in2); break; + case CMP_GT: out = cv::gapi::cmpGT(in1, in2); break; + case CMP_GE: out = cv::gapi::cmpGE(in1, in2); break; + case CMP_LT: out = cv::gapi::cmpLT(in1, in2); break; + case CMP_LE: out = cv::gapi::cmpLE(in1, in2); break; + case CMP_NE: out = cv::gapi::cmpNE(in1, in2); break; + default: FAIL() << "no such compare operation type for two matrices!"; + } + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class CmpWithScalarPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(CmpWithScalarPerfTest, TestPerformance) + { + CmpTypes opType = get<0>(GetParam()); + cv::Size sz = get<1>(GetParam()); + MatType type = get<2>(GetParam()); + + initMatsRandU(type, sz, CV_8U, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::compare(in_mat1, sc, out_mat_ocv, opType); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar in2; + switch(opType) + { + case CMP_EQ: out = cv::gapi::cmpEQ(in1, in2); break; + case CMP_GT: out = cv::gapi::cmpGT(in1, in2); break; + case CMP_GE: out = cv::gapi::cmpGE(in1, in2); break; + case CMP_LT: out = cv::gapi::cmpLT(in1, in2); break; + case CMP_LE: out = cv::gapi::cmpLE(in1, in2); break; + case CMP_NE: out = cv::gapi::cmpNE(in1, in2); break; + default: FAIL() << "no such compare operation type for matrix and scalar!"; + } + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class BitwisePerfTest : public TestPerfParams> {}; + PERF_TEST_P_(BitwisePerfTest, TestPerformance) + { + bitwiseOp opType = get<0>(GetParam()); + cv::Size sz = get<1>(GetParam()); + MatType type = get<2>(GetParam()); + + initMatsRandU(type, sz, type, false); + + // G-API code & corresponding OpenCV code //////////////////////////////// + cv::GMat in1, in2, out; + switch(opType) + { + case AND: + { + out = cv::gapi::bitwise_and(in1, in2); + cv::bitwise_and(in_mat1, in_mat2, out_mat_ocv); + break; + } + case OR: + { + out = cv::gapi::bitwise_or(in1, in2); + cv::bitwise_or(in_mat1, in_mat2, out_mat_ocv); + break; + } + case XOR: + { + out = cv::gapi::bitwise_xor(in1, in2); + cv::bitwise_xor(in_mat1, in_mat2, out_mat_ocv); + break; + } + default: + { + FAIL() << "no such bitwise operation type!"; + } + } + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class BitwiseNotPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(BitwiseNotPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatrixRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::bitwise_not(in_mat1, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in, out; + out = cv::gapi::bitwise_not(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class SelectPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(SelectPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatsRandU(type, sz_in, type, false); + cv::Mat in_mask(sz_in, CV_8UC1); + cv::randu(in_mask, cv::Scalar::all(0), cv::Scalar::all(255)); + + // OpenCV code /////////////////////////////////////////////////////////// + in_mat2.copyTo(out_mat_ocv); + in_mat1.copyTo(out_mat_ocv, in_mask); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, in3, out; + out = cv::gapi::select(in1, in2, in3); + cv::GComputation c(GIn(in1, in2, in3), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2, in_mask), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2, in_mask), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MinPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MinPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatsRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::min(in_mat1, in_mat2, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::min(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class MaxPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(MaxPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatsRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::max(in_mat1, in_mat2, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::max(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class AbsDiffPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(AbsDiffPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatsRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::absdiff(in_mat1, in_mat2, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, out; + out = cv::gapi::absDiff(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class AbsDiffCPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(AbsDiffCPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatsRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::absdiff(in_mat1, sc, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar sc1; + out = cv::gapi::absDiffC(in1, sc1); + cv::GComputation c(cv::GIn(in1, sc1), cv::GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, sc), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class SumPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(SumPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatrixRandU(type, sz_in, false); + cv::Scalar out_sum; + cv::Scalar out_sum_ocv; + + // OpenCV code /////////////////////////////////////////////////////////// + out_sum_ocv = cv::sum(in_mat1); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sum(in); + cv::GComputation c(cv::GIn(in), cv::GOut(out)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1), cv::gout(out_sum)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1), cv::gout(out_sum)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(out_sum[0], out_sum_ocv[0]); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class AddWeightedPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(AddWeightedPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int dtype = get<2>(GetParam()); + + auto& rng = cv::theRNG(); + double alpha = rng.uniform(0.0, 1.0); + double beta = rng.uniform(0.0, 1.0); + double gamma = rng.uniform(0.0, 1.0); + initMatsRandU(type, sz_in, dtype, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::addWeighted(in_mat1, alpha, in_mat2, beta, gamma, out_mat_ocv, dtype); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::addWeighted(in1, alpha, in2, beta, gamma, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class NormPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(NormPerfTest, TestPerformance) + { + NormTypes opType = get<0>(GetParam()); + cv::Size sz = get<1>(GetParam()); + MatType type = get<2>(GetParam()); + + initMatrixRandU(type, sz, type, false); + cv::Scalar out_norm; + cv::Scalar out_norm_ocv; + + // OpenCV code /////////////////////////////////////////////////////////// + out_norm_ocv = cv::norm(in_mat1, opType); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1; + cv::GScalar out; + switch(opType) + { + case NORM_L1: out = cv::gapi::normL1(in1); break; + case NORM_L2: out = cv::gapi::normL2(in1); break; + case NORM_INF: out = cv::gapi::normInf(in1); break; + default: FAIL() << "no such norm operation type!"; + } + cv::GComputation c(GIn(in1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1), gout(out_norm)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1), gout(out_norm)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(out_norm[0], out_norm_ocv[0]); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class IntegralPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(IntegralPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + MatType type_out = (type == CV_8U) ? CV_32SC1 : CV_64FC1; + + + in_mat1 = cv::Mat(sz_in, type); + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + + cv::Size sz_out = cv::Size(sz_in.width + 1, sz_in.height + 1); + cv::Mat out_mat1(sz_out, type_out); + cv::Mat out_mat_ocv1(sz_out, type_out); + + cv::Mat out_mat2(sz_out, CV_64FC1); + cv::Mat out_mat_ocv2(sz_out, CV_64FC1); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::integral(in_mat1, out_mat_ocv1, out_mat_ocv2); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out1, out2; + std::tie(out1, out2) = cv::gapi::integral(in1, type_out, CV_64FC1); + cv::GComputation c(cv::GIn(in1), cv::GOut(out1, out2)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1), cv::gout(out_mat1, out_mat2)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1), cv::gout(out_mat1, out_mat2)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv1 != out_mat1)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ThresholdPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ThresholdPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int tt = get<2>(GetParam()); + + auto& rng = cv::theRNG(); + cv::Scalar thr = cv::Scalar(rng(50),rng(50),rng(50),rng(50)); + cv::Scalar maxval = cv::Scalar(50 + rng(50),50 + rng(50),50 + rng(50),50 + rng(50)); + initMatrixRandU(type, sz_in, type, false); + cv::Scalar out_scalar; + + // OpenCV code /////////////////////////////////////////////////////////// + cv::threshold(in_mat1, out_mat_ocv, thr.val[0], maxval.val[0], tt); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar th1, mv1; + out = cv::gapi::threshold(in1, th1, mv1, tt); + cv::GComputation c(GIn(in1, th1, mv1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, thr, maxval), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, thr, maxval), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ThresholdOTPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ThresholdOTPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int tt = get<2>(GetParam()); + + auto& rng = cv::theRNG(); + cv::Scalar maxval = cv::Scalar(50 + rng(50),50 + rng(50),50 + rng(50),50 + rng(50)); + initMatrixRandU(type, sz_in, type, false); + cv::Scalar out_gapi_scalar; + double ocv_res; + + // OpenCV code /////////////////////////////////////////////////////////// + ocv_res = cv::threshold(in_mat1, out_mat_ocv, maxval.val[0], maxval.val[0], tt); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar mv1, scout; + std::tie(out, scout) = cv::gapi::threshold(in1, mv1, tt); + cv::GComputation c(cv::GIn(in1, mv1), cv::GOut(out, scout)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, maxval), gout(out_mat_gapi, out_gapi_scalar)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, maxval), gout(out_mat_gapi, out_gapi_scalar)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + EXPECT_EQ(ocv_res, out_gapi_scalar.val[0]); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class InRangePerfTest : public TestPerfParams> {}; + PERF_TEST_P_(InRangePerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + auto& rng = cv::theRNG(); + cv::Scalar thrLow = cv::Scalar(rng(100),rng(100),rng(100),rng(100)); + cv::Scalar thrUp = cv::Scalar(100 + rng(100),100 + rng(100),100 + rng(100),100 + rng(100)); + initMatrixRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::inRange(in_mat1, thrLow, thrUp, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1; + cv::GScalar th1, mv1; + auto out = cv::gapi::inRange(in1, th1, mv1); + cv::GComputation c(GIn(in1, th1, mv1), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, thrLow, thrUp), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, thrLow, thrUp), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class Split3PerfTest : public TestPerfParams {}; + PERF_TEST_P_(Split3PerfTest, TestPerformance) + { + cv::Size sz_in = GetParam(); + + initMatrixRandU(CV_8UC3, sz_in, CV_8UC1); + cv::Mat out_mat2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat3 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv3 = cv::Mat(sz_in, CV_8UC1); + + // OpenCV code /////////////////////////////////////////////////////////// + std::vector out_mats_ocv = {out_mat_ocv, out_mat_ocv2, out_mat_ocv3}; + cv::split(in_mat1, out_mats_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out1, out2, out3; + std::tie(out1, out2, out3) = cv::gapi::split3(in1); + cv::GComputation c(cv::GIn(in1), cv::GOut(out1, out2, out3)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1), cv::gout(out_mat_gapi, out_mat2, out_mat3)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1), cv::gout(out_mat_gapi, out_mat2, out_mat3)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv3 != out_mat3)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class Split4PerfTest : public TestPerfParams {}; + PERF_TEST_P_(Split4PerfTest, TestPerformance) + { + cv::Size sz_in = GetParam(); + + initMatrixRandU(CV_8UC4, sz_in, CV_8UC1); + cv::Mat out_mat2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat3 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat4 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv3 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv4 = cv::Mat(sz_in, CV_8UC1); + + // OpenCV code /////////////////////////////////////////////////////////// + std::vector out_mats_ocv = {out_mat_ocv, out_mat_ocv2, out_mat_ocv3, out_mat_ocv4}; + cv::split(in_mat1, out_mats_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out1, out2, out3, out4; + std::tie(out1, out2, out3, out4) = cv::gapi::split4(in1); + cv::GComputation c(cv::GIn(in1), cv::GOut(out1, out2, out3, out4)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1), cv::gout(out_mat_gapi, out_mat2, out_mat3, out_mat4)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1), cv::gout(out_mat_gapi, out_mat2, out_mat3, out_mat4)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv3 != out_mat3)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv4 != out_mat4)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class Merge3PerfTest : public TestPerfParams {}; + PERF_TEST_P_(Merge3PerfTest, TestPerformance) + { + cv::Size sz_in = GetParam(); + + initMatsRandU(CV_8UC1, sz_in, CV_8UC3); + cv::Mat in_mat3(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + cv::randn(in_mat3, mean, stddev); + + // OpenCV code /////////////////////////////////////////////////////////// + std::vector in_mats_ocv = {in_mat1, in_mat2, in_mat3}; + cv::merge(in_mats_ocv, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, in3; + auto out = cv::gapi::merge3(in1, in2, in3); + cv::GComputation c(cv::GIn(in1, in2, in3), cv::GOut(out)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1, in_mat2, in_mat3), cv::gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1, in_mat2, in_mat3), cv::gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class Merge4PerfTest : public TestPerfParams {}; + PERF_TEST_P_(Merge4PerfTest, TestPerformance) + { + cv::Size sz_in = GetParam(); + + initMatsRandU(CV_8UC1, sz_in, CV_8UC3); + cv::Mat in_mat3(sz_in, CV_8UC1); + cv::Mat in_mat4(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + cv::randn(in_mat3, mean, stddev); + cv::randn(in_mat4, mean, stddev); + + // OpenCV code /////////////////////////////////////////////////////////// + std::vector in_mats_ocv = {in_mat1, in_mat2, in_mat3, in_mat4}; + cv::merge(in_mats_ocv, out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2, in3, in4; + auto out = cv::gapi::merge4(in1, in2, in3, in4); + cv::GComputation c(cv::GIn(in1, in2, in3, in4), cv::GOut(out)); + + // Warm-up graph engine: + c.apply(cv::gin(in_mat1, in_mat2, in_mat3, in_mat4), cv::gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(cv::gin(in_mat1, in_mat2, in_mat3, in_mat4), cv::gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class RemapPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(RemapPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + initMatrixRandU(type, sz_in, type, false); + cv::Mat in_map1(sz_in, CV_16SC2); + cv::Mat in_map2 = cv::Mat(); + cv::randu(in_map1, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::Scalar bv = cv::Scalar(); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::remap(in_mat1, out_mat_ocv, in_map1, in_map2, cv::INTER_NEAREST, cv::BORDER_REPLICATE, bv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1; + auto out = cv::gapi::remap(in1, in_map1, in_map2, cv::INTER_NEAREST, cv::BORDER_REPLICATE, bv); + cv::GComputation c(in1, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class FlipPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(FlipPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + int flipCode = get<2>(GetParam()); + + initMatrixRandU(type, sz_in, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::flip(in_mat1, out_mat_ocv, flipCode); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::flip(in, flipCode); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class CropPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(CropPerfTest, TestPerformance) + { + cv::Size sz_in = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + cv::Rect rect_to = get<2>(GetParam()); + + initMatrixRandU(type, sz_in, type, false); + cv::Size sz_out = cv::Size(rect_to.width, rect_to.height); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::Mat(in_mat1, rect_to).copyTo(out_mat_ocv); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::crop(in, rect_to); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_out); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ConcatHorPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ConcatHorPerfTest, TestPerformance) + { + cv::Size sz_out = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + int wpart = sz_out.width / 4; + + cv::Size sz_in1 = cv::Size(wpart, sz_out.height); + cv::Size sz_in2 = cv::Size(sz_out.width - wpart, sz_out.height); + + in_mat1 = cv::Mat(sz_in1, type); + in_mat2 = cv::Mat(sz_in2, type); + + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::hconcat(in_mat1, in_mat2, out_mat_ocv ); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::concatHor(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ConcatHorVecPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ConcatHorVecPerfTest, TestPerformance) + { + cv::Size sz_out = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + int wpart1 = sz_out.width / 3; + int wpart2 = sz_out.width / 2; + + cv::Size sz_in1 = cv::Size(wpart1, sz_out.height); + cv::Size sz_in2 = cv::Size(wpart2, sz_out.height); + cv::Size sz_in3 = cv::Size(sz_out.width - wpart1 - wpart2, sz_out.height); + + in_mat1 = cv::Mat(sz_in1, type); + in_mat2 = cv::Mat(sz_in2, type); + cv::Mat in_mat3(sz_in3, type); + + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + cv::randn(in_mat3, mean, stddev); + + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + + std::vector cvmats = {in_mat1, in_mat2, in_mat3}; + + // OpenCV code /////////////////////////////////////////////////////////// + cv::hconcat(cvmats, out_mat_ocv ); + + // G-API code ////////////////////////////////////////////////////////////// + std::vector mats(3); + auto out = cv::gapi::concatHor(mats); + cv::GComputation c({mats[0], mats[1], mats[2]}, {out}); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2, in_mat3), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2, in_mat3), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ConcatVertPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ConcatVertPerfTest, TestPerformance) + { + cv::Size sz_out = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + int hpart = sz_out.height * 2/3; + + cv::Size sz_in1 = cv::Size(sz_out.width, hpart); + cv::Size sz_in2 = cv::Size(sz_out.width, sz_out.height - hpart); + + in_mat1 = cv::Mat(sz_in1, type); + in_mat2 = cv::Mat(sz_in2, type); + + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::vconcat(in_mat1, in_mat2, out_mat_ocv ); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::concatVert(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ConcatVertVecPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ConcatVertVecPerfTest, TestPerformance) + { + cv::Size sz_out = get<0>(GetParam()); + MatType type = get<1>(GetParam()); + + int hpart1 = sz_out.height * 2/5; + int hpart2 = sz_out.height / 5; + + cv::Size sz_in1 = cv::Size(sz_out.width, hpart1); + cv::Size sz_in2 = cv::Size(sz_out.width, hpart2); + cv::Size sz_in3 = cv::Size(sz_out.width, sz_out.height - hpart1 - hpart2); + + in_mat1 = cv::Mat(sz_in1, type); + in_mat2 = cv::Mat(sz_in2, type); + cv::Mat in_mat3 (sz_in3, type); + + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + cv::randn(in_mat3, mean, stddev); + + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + + std::vector cvmats = {in_mat1, in_mat2, in_mat3}; + + // OpenCV code /////////////////////////////////////////////////////////// + cv::vconcat(cvmats, out_mat_ocv ); + + // G-API code ////////////////////////////////////////////////////////////// + std::vector mats(3); + auto out = cv::gapi::concatVert(mats); + cv::GComputation c({mats[0], mats[1], mats[2]}, {out}); + + // Warm-up graph engine: + c.apply(gin(in_mat1, in_mat2, in_mat3), gout(out_mat_gapi)); + + TEST_CYCLE() + { + c.apply(gin(in_mat1, in_mat2, in_mat3), gout(out_mat_gapi)); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class LUTPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(LUTPerfTest, TestPerformance) + { + MatType type_mat = get<0>(GetParam()); + MatType type_lut = get<1>(GetParam()); + MatType type_out = CV_MAKETYPE(CV_MAT_DEPTH(type_lut), CV_MAT_CN(type_mat)); + cv::Size sz_in = get<2>(GetParam()); + + initMatrixRandU(type_mat, sz_in, type_out); + cv::Size sz_lut = cv::Size(1, 256); + cv::Mat in_lut (sz_lut, type_lut); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::LUT(in_mat1, in_lut, out_mat_ocv); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::LUT(in, in_lut); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ConvertToPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ConvertToPerfTest, TestPerformance) + { + MatType type_mat = get<0>(GetParam()); + int depth_to = get<1>(GetParam()); + cv::Size sz_in = get<2>(GetParam()); + MatType type_out = CV_MAKETYPE(depth_to, CV_MAT_CN(type_mat)); + + initMatrixRandU(type_mat, sz_in, type_out); + + // OpenCV code /////////////////////////////////////////////////////////// + in_mat1.convertTo(out_mat_ocv, depth_to); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::convertTo(in, depth_to); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ResizePerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ResizePerfTest, TestPerformance) + { + MatType type = get<0>(GetParam()); + int interp = get<1>(GetParam()); + cv::Size sz_in = get<2>(GetParam()); + cv::Size sz_out = get<3>(GetParam()); + double tolerance = get<4>(GetParam()); + + in_mat1 = cv::Mat(sz_in, type ); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + cv::randn(in_mat1, mean, stddev); + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::resize(in_mat1, out_mat_ocv, sz_out, 0.0, 0.0, interp); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::resize(in, sz_out, 0.0, 0.0, interp); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + cv::Mat absDiff; + cv::absdiff(out_mat_gapi, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + + class ResizeFxFyPerfTest : public TestPerfParams> {}; + PERF_TEST_P_(ResizeFxFyPerfTest, TestPerformance) + { + MatType type = get<0>(GetParam()); + int interp = get<1>(GetParam()); + cv::Size sz_in = get<2>(GetParam()); + double fx = get<3>(GetParam()); + double fy = get<4>(GetParam()); + double tolerance = get<5>(GetParam()); + + in_mat1 = cv::Mat(sz_in, type ); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + cv::randn(in_mat1, mean, stddev); + cv::Size sz_out = cv::Size(saturate_cast(sz_in.width *fx),saturate_cast(sz_in.height*fy)); + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::resize(in_mat1, out_mat_ocv, sz_out, fx, fy, interp); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::resize(in, sz_out, fx, fy, interp); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + cv::Mat absDiff; + cv::absdiff(out_mat_gapi, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + + SANITY_CHECK_NOTHING(); + } + +//------------------------------------------------------------------------------ + +} diff --git a/modules/gapi/perf/common/gapi_imgproc_perf_tests.hpp b/modules/gapi/perf/common/gapi_imgproc_perf_tests.hpp new file mode 100644 index 0000000000..8ba25edabc --- /dev/null +++ b/modules/gapi/perf/common/gapi_imgproc_perf_tests.hpp @@ -0,0 +1,882 @@ +// 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) 2018 Intel Corporation + + +#include "../../test/common/gapi_tests_common.hpp" +#include "opencv2/gapi/imgproc.hpp" + +namespace opencv_test +{ + + using namespace perf; + +//------------------------------------------------------------------------------ + +class SepFilterPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(SepFilterPerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0, dtype = 0; + cv::Size sz; + std::tie(type, kernSize, sz, dtype) = GetParam(); + + cv::Mat kernelX(kernSize, 1, CV_32F); + cv::Mat kernelY(kernSize, 1, CV_32F); + randu(kernelX, -1, 1); + randu(kernelY, -1, 1); + initMatsRandN(type, sz, dtype, false); + + cv::Point anchor = cv::Point(-1, -1); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::sepFilter2D(in_mat1, out_mat_ocv, dtype, kernelX, kernelY ); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sepFilter(in, dtype, kernelX, kernelY, anchor, cv::Scalar() ); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); +} + +//------------------------------------------------------------------------------ + +class Filter2DPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(Filter2DPerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0, borderType = 0, dtype = 0; + cv::Size sz; + std::tie(type, kernSize, sz, borderType, dtype) = GetParam(); + + initMatsRandN(type, sz, dtype, false); + + cv::Point anchor = {-1, -1}; + double delta = 0; + + cv::Mat kernel = cv::Mat(kernSize, kernSize, CV_32FC1 ); + cv::Scalar kernMean = cv::Scalar::all(1.0); + cv::Scalar kernStddev = cv::Scalar::all(2.0/3); + randn(kernel, kernMean, kernStddev); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::filter2D(in_mat1, out_mat_ocv, dtype, kernel, anchor, delta, borderType); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::filter2D(in, dtype, kernel, anchor, delta, borderType); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class BoxFilterPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(BoxFilterPerfTest, TestPerformance) +{ + MatType type = 0; + int filterSize = 0, borderType = 0, dtype = 0; + cv::Size sz; + double tolerance = 0.0; + std::tie(type, filterSize, sz, borderType, dtype, tolerance) = GetParam(); + + initMatsRandN(type, sz, dtype, false); + + cv::Point anchor = {-1, -1}; + bool normalize = true; + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::boxFilter(in_mat1, out_mat_ocv, dtype, cv::Size(filterSize, filterSize), anchor, normalize, borderType); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::boxFilter(in, dtype, cv::Size(filterSize, filterSize), anchor, normalize, borderType); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + cv::Mat absDiff; + cv::absdiff(out_mat_gapi, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class BlurPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(BlurPerfTest, TestPerformance) +{ + MatType type = 0; + int filterSize = 0, borderType = 0; + cv::Size sz; + double tolerance = 0.0; + std::tie(type, filterSize, sz, borderType, tolerance) = GetParam(); + + initMatsRandN(type, sz, type, false); + + cv::Point anchor = {-1, -1}; + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::blur(in_mat1, out_mat_ocv, cv::Size(filterSize, filterSize), anchor, borderType); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::blur(in, cv::Size(filterSize, filterSize), anchor, borderType); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + cv::Mat absDiff; + cv::absdiff(out_mat_gapi, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class GaussianBlurPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(GaussianBlurPerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0; + cv::Size sz; + std::tie(type, kernSize, sz) = GetParam(); + + cv::Size kSize = cv::Size(kernSize, kernSize); + auto& rng = cv::theRNG(); + double sigmaX = rng(); + initMatsRandN(type, sz, type, false); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::GaussianBlur(in_mat1, out_mat_ocv, kSize, sigmaX); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::gaussianBlur(in, kSize, sigmaX); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison //////////////////////////////////////////////////////////// + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); +} + +//------------------------------------------------------------------------------ + +class MedianBlurPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(MedianBlurPerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0; + cv::Size sz; + std::tie(type, kernSize, sz) = GetParam(); + + initMatsRandN(type, sz, type, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::medianBlur(in_mat1, out_mat_ocv, kernSize); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::medianBlur(in, kernSize); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class ErodePerfTest : public TestPerfParams> {}; +PERF_TEST_P_(ErodePerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0, kernType = 0; + cv::Size sz; + std::tie(type, kernSize, sz, kernType) = GetParam(); + + initMatsRandN(type, sz, type, false); + + cv::Mat kernel = cv::getStructuringElement(kernType, cv::Size(kernSize, kernSize)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::erode(in_mat1, out_mat_ocv, kernel); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::erode(in, kernel); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class Erode3x3PerfTest : public TestPerfParams> {}; +PERF_TEST_P_(Erode3x3PerfTest, TestPerformance) +{ + MatType type = 0; + int numIters = 0; + cv::Size sz; + std::tie(type, sz, numIters) = GetParam(); + + initMatsRandN(type, sz, type, false); + + cv::Mat kernel = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT, cv::Size(3, 3)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::erode(in_mat1, out_mat_ocv, kernel, cv::Point(-1, -1), numIters); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::erode3x3(in, numIters); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class DilatePerfTest : public TestPerfParams> {}; +PERF_TEST_P_(DilatePerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0, kernType = 0; + cv::Size sz; + std::tie(type, kernSize, sz, kernType) = GetParam(); + + initMatsRandN(type, sz, type, false); + + cv::Mat kernel = cv::getStructuringElement(kernType, cv::Size(kernSize, kernSize)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::dilate(in_mat1, out_mat_ocv, kernel); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::dilate(in, kernel); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class Dilate3x3PerfTest : public TestPerfParams> {}; +PERF_TEST_P_(Dilate3x3PerfTest, TestPerformance) +{ + MatType type = 0; + int numIters = 0; + cv::Size sz; + std::tie(type, sz, numIters) = GetParam(); + + initMatsRandN(type, sz, type, false); + + cv::Mat kernel = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT, cv::Size(3, 3)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::dilate(in_mat1, out_mat_ocv, kernel, cv::Point(-1,-1), numIters); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::dilate3x3(in, numIters); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class SobelPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(SobelPerfTest, TestPerformance) +{ + MatType type = 0; + int kernSize = 0, dtype = 0, dx = 0, dy = 0; + cv::Size sz; + std::tie(type, kernSize, sz, dtype, dx, dy) = GetParam(); + + initMatsRandN(type, sz, dtype, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Sobel(in_mat1, out_mat_ocv, dtype, dx, dy, kernSize); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sobel(in, dtype, dx, dy, kernSize ); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class CannyPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(CannyPerfTest, TestPerformance) +{ + MatType type; + int apSize = 0; + double thrLow = 0.0, thrUp = 0.0; + cv::Size sz; + bool l2gr = false; + std::tie(type, sz, thrLow, thrUp, apSize, l2gr) = GetParam(); + + initMatsRandN(type, sz, CV_8UC1, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Canny(in_mat1, out_mat_ocv, thrLow, thrUp, apSize, l2gr); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::Canny(in, thrLow, thrUp, apSize, l2gr); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class EqHistPerfTest : public TestPerfParams {}; +PERF_TEST_P_(EqHistPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC1, sz, CV_8UC1, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::equalizeHist(in_mat1, out_mat_ocv); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::equalizeHist(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class RGB2GrayPerfTest : public TestPerfParams {}; +PERF_TEST_P_(RGB2GrayPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC1, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_RGB2GRAY); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::RGB2Gray(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class BGR2GrayPerfTest : public TestPerfParams {}; +PERF_TEST_P_(BGR2GrayPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC1, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_BGR2GRAY); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::BGR2Gray(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class RGB2YUVPerfTest : public TestPerfParams {}; +PERF_TEST_P_(RGB2YUVPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_RGB2YUV); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::RGB2YUV(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class YUV2RGBPerfTest : public TestPerfParams {}; +PERF_TEST_P_(YUV2RGBPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_YUV2RGB); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::YUV2RGB(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class RGB2LabPerfTest : public TestPerfParams {}; +PERF_TEST_P_(RGB2LabPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_RGB2Lab); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::RGB2Lab(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class BGR2LUVPerfTest : public TestPerfParams {}; +PERF_TEST_P_(BGR2LUVPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_BGR2Luv); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::BGR2LUV(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class LUV2BGRPerfTest : public TestPerfParams {}; +PERF_TEST_P_(LUV2BGRPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_Luv2BGR); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::LUV2BGR(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } + + SANITY_CHECK_NOTHING(); + +} + +//------------------------------------------------------------------------------ + +class BGR2YUVPerfTest : public TestPerfParams {}; +PERF_TEST_P_(BGR2YUVPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_BGR2YUV); + + cv::GMat in; + auto out = cv::gapi::BGR2YUV(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); +} + +//------------------------------------------------------------------------------ + +class YUV2BGRPerfTest : public TestPerfParams {}; +PERF_TEST_P_(YUV2BGRPerfTest, TestPerformance) +{ + cv::Size sz = GetParam(); + initMatsRandN(CV_8UC3, sz, CV_8UC3, false); + + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_YUV2BGR); + + cv::GMat in; + auto out = cv::gapi::YUV2BGR(in); + cv::GComputation c(in, out); + + // Warm-up graph engine: + c.apply(in_mat1, out_mat_gapi); + + TEST_CYCLE() + { + c.apply(in_mat1, out_mat_gapi); + } + + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + + SANITY_CHECK_NOTHING(); +} + +//------------------------------------------------------------------------------ + +} diff --git a/modules/gapi/perf/cpu/gapi_core_perf_tests_cpu.cpp b/modules/gapi/perf/cpu/gapi_core_perf_tests_cpu.cpp new file mode 100644 index 0000000000..405d7f305d --- /dev/null +++ b/modules/gapi/perf/cpu/gapi_core_perf_tests_cpu.cpp @@ -0,0 +1,219 @@ +// 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) 2018 Intel Corporation + + +#include "../common/gapi_core_perf_tests.hpp" + +namespace opencv_test +{ + + INSTANTIATE_TEST_CASE_P(AddPerfTestCPU, AddPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(AddCPerfTestCPU, AddCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(SubPerfTestCPU, SubPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(SubCPerfTestCPU, SubCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(SubRCPerfTestCPU, SubRCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(MulPerfTestCPU, MulPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(MulDoublePerfTestCPU, MulDoublePerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(MulCPerfTestCPU, MulCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(DivPerfTestCPU, DivPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(DivCPerfTestCPU, DivCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(DivRCPerfTestCPU, DivRCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(MaskPerfTestCPU, MaskPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_16UC1, CV_16SC1))); + + INSTANTIATE_TEST_CASE_P(MeanPerfTestCPU, MeanPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(Polar2CartPerfTestCPU, Polar2CartPerfTest, Values( szSmall128, szVGA, sz720p, sz1080p )); + + INSTANTIATE_TEST_CASE_P(Cart2PolarPerfTestCPU, Cart2PolarPerfTest, Values( szSmall128, szVGA, sz720p, sz1080p )); + + INSTANTIATE_TEST_CASE_P(CmpPerfTestCPU, CmpPerfTest, + Combine(Values(CMP_EQ, CMP_GE, CMP_NE, CMP_GT, CMP_LT, CMP_LE), + Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(CmpWithScalarPerfTestCPU, CmpWithScalarPerfTest, + Combine(Values(CMP_EQ, CMP_GE, CMP_NE, CMP_GT, CMP_LT, CMP_LE), + Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(BitwisePerfTestCPU, BitwisePerfTest, + Combine(Values(AND, OR, XOR), + Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1))); + + INSTANTIATE_TEST_CASE_P(BitwiseNotPerfTestCPU, BitwiseNotPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(SelectPerfTestCPU, SelectPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(MinPerfTestCPU, MinPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(MaxPerfTestCPU, MaxPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(AbsDiffPerfTestCPU, AbsDiffPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(AbsDiffCPerfTestCPU, AbsDiffCPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(SumPerfTestCPU, SumPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(AddWeightedPerfTestCPU, AddWeightedPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values( -1, CV_8U, CV_16U, CV_32F ))); + + INSTANTIATE_TEST_CASE_P(NormPerfTestCPU, NormPerfTest, + Combine(Values(NORM_INF, NORM_L1, NORM_L2), + Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(IntegralPerfTestCPU, IntegralPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(ThresholdPerfTestCPU, ThresholdPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::THRESH_BINARY, cv::THRESH_BINARY_INV, cv::THRESH_TRUNC, cv::THRESH_TOZERO, cv::THRESH_TOZERO_INV))); + + INSTANTIATE_TEST_CASE_P(ThresholdPerfTestCPU, ThresholdOTPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1 ), + Values(cv::THRESH_OTSU, cv::THRESH_TRIANGLE))); + + INSTANTIATE_TEST_CASE_P(InRangePerfTestCPU, InRangePerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1 ))); + + INSTANTIATE_TEST_CASE_P(Split3PerfTestCPU, Split3PerfTest, Values( szSmall128, szVGA, sz720p, sz1080p )); + + INSTANTIATE_TEST_CASE_P(Split4PerfTestCPU, Split4PerfTest, Values( szSmall128, szVGA, sz720p, sz1080p )); + + INSTANTIATE_TEST_CASE_P(Merge3PerfTestCPU, Merge3PerfTest, Values( szSmall128, szVGA, sz720p, sz1080p )); + + INSTANTIATE_TEST_CASE_P(Merge4PerfTestCPU, Merge4PerfTest, Values( szSmall128, szVGA, sz720p, sz1080p )); + + INSTANTIATE_TEST_CASE_P(RemapPerfTestCPU, RemapPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(FlipPerfTestCPU, FlipPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(0,1,-1))); + + INSTANTIATE_TEST_CASE_P(CropPerfTestCPU, CropPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Rect(10, 8, 20, 35), cv::Rect(4, 10, 37, 50)))); + + INSTANTIATE_TEST_CASE_P(ConcatHorPerfTestCPU, ConcatHorPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(ConcatHorVecPerfTestCPU, ConcatHorVecPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(ConcatVertPerfTestCPU, ConcatVertPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(ConcatVertVecPerfTestCPU, ConcatVertVecPerfTest, + Combine(Values( szSmall128, szVGA, sz720p, sz1080p ), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ))); + + INSTANTIATE_TEST_CASE_P(LUTPerfTestCPU, LUTPerfTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(CV_8UC1), + Values( szSmall128, szVGA, sz720p, sz1080p ))); + + INSTANTIATE_TEST_CASE_P(LUTPerfTestCustomCPU, LUTPerfTest, + Combine(Values(CV_8UC3), + Values(CV_8UC3), + Values( szSmall128, szVGA, sz720p, sz1080p ))); + + INSTANTIATE_TEST_CASE_P(ConvertToPerfTestCPU, ConvertToPerfTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_32FC1), + Values(CV_8U, CV_16U, CV_16S, CV_32F), + Values( szSmall128, szVGA, sz720p, sz1080p ))); + + INSTANTIATE_TEST_CASE_P(ResizePerfTestCPU, ResizePerfTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values( szSmall128, szVGA, sz720p, sz1080p ), + Values(cv::Size(64,64), + cv::Size(30,30)), + Values(0.0))); + + INSTANTIATE_TEST_CASE_P(ResizeFxFyPerfTestCPU, ResizeFxFyPerfTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values( szSmall128, szVGA, sz720p, sz1080p ), + Values(0.5, 0.1), + Values(0.5, 0.1), + Values(0.0))); +} diff --git a/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_cpu.cpp b/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_cpu.cpp new file mode 100644 index 0000000000..ba61af5907 --- /dev/null +++ b/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_cpu.cpp @@ -0,0 +1,119 @@ +// 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) 2018 Intel Corporation + + +#include "../common/gapi_imgproc_perf_tests.hpp" + +namespace opencv_test +{ + + INSTANTIATE_TEST_CASE_P(SepFilterPerfTestCPU_8U, SepFilterPerfTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(3), + Values(szVGA, sz720p, sz1080p), + Values(-1, CV_16S, CV_32F))); + + INSTANTIATE_TEST_CASE_P(SepFilterPerfTestCPU_other, SepFilterPerfTest, + Combine(Values(CV_16UC1, CV_16SC1, CV_32FC1), + Values(3), + Values(szVGA, sz720p, sz1080p), + Values(-1, CV_32F))); + + INSTANTIATE_TEST_CASE_P(Filter2DPerfTestCPU, Filter2DPerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 4, 5, 7), + Values(szVGA, sz720p, sz1080p), + Values(cv::BORDER_DEFAULT), + Values(-1, CV_32F))); + + INSTANTIATE_TEST_CASE_P(BoxFilterPerfTestCPU, BoxFilterPerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3,5), + Values(szVGA, sz720p, sz1080p), + Values(cv::BORDER_DEFAULT), + Values(-1, CV_32F), + Values(0.0))); + + INSTANTIATE_TEST_CASE_P(BlurPerfTestCPU, BlurPerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(szVGA, sz720p, sz1080p), + Values(cv::BORDER_DEFAULT), + Values(0.0))); + + INSTANTIATE_TEST_CASE_P(GaussianBlurPerfTestCPU, GaussianBlurPerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(szVGA, sz720p, sz1080p))); + + INSTANTIATE_TEST_CASE_P(MedianBlurPerfTestCPU, MedianBlurPerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(szVGA, sz720p, sz1080p))); + + INSTANTIATE_TEST_CASE_P(ErodePerfTestCPU, ErodePerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(szVGA, sz720p, sz1080p), + Values(cv::MorphShapes::MORPH_RECT, + cv::MorphShapes::MORPH_CROSS, + cv::MorphShapes::MORPH_ELLIPSE))); + + INSTANTIATE_TEST_CASE_P(Erode3x3PerfTestCPU, Erode3x3PerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(szVGA, sz720p, sz1080p), + Values(1,2,4))); + + INSTANTIATE_TEST_CASE_P(DilatePerfTestCPU, DilatePerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(szVGA, sz720p, sz1080p), + Values(cv::MorphShapes::MORPH_RECT, + cv::MorphShapes::MORPH_CROSS, + cv::MorphShapes::MORPH_ELLIPSE))); + + INSTANTIATE_TEST_CASE_P(Dilate3x3PerfTestCPU, Dilate3x3PerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(szVGA, sz720p, sz1080p), + Values(1,2,4))); + + INSTANTIATE_TEST_CASE_P(SobelPerfTestCPU, SobelPerfTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(szVGA, sz720p, sz1080p), + Values(-1, CV_32F), + Values(0, 1), + Values(1, 2))); + + INSTANTIATE_TEST_CASE_P(CannyPerfTestCPU, CannyPerfTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(szVGA, sz720p, sz1080p), + Values(3.0, 120.0), + Values(125.0, 240.0), + Values(3, 5), + Values(true, false))); + + INSTANTIATE_TEST_CASE_P(EqHistPerfTestCPU, EqHistPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(RGB2GrayPerfTestCPU, RGB2GrayPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(BGR2GrayPerfTestCPU, BGR2GrayPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(RGB2YUVPerfTestCPU, RGB2YUVPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(YUV2RGBPerfTestCPU, YUV2RGBPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(RGB2LabPerfTestCPU, RGB2LabPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(BGR2LUVPerfTestCPU, BGR2LUVPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(LUV2BGRPerfTestCPU, LUV2BGRPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(BGR2YUVPerfTestCPU, BGR2YUVPerfTest, Values(szVGA, sz720p, sz1080p)); + + INSTANTIATE_TEST_CASE_P(YUV2BGRPerfTestCPU, YUV2BGRPerfTest, Values(szVGA, sz720p, sz1080p)); + +} diff --git a/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp b/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp new file mode 100644 index 0000000000..ef532ab9f8 --- /dev/null +++ b/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp @@ -0,0 +1,46 @@ +// 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) 2018 Intel Corporation + + +#include "perf_precomp.hpp" +#include "../../test/common/gapi_tests_common.hpp" +#include "../../src/backends/fluid/gfluidcore.hpp" + +namespace opencv_test +{ +using namespace perf; + +class CompilerPerfTest : public TestPerfParams> {}; +PERF_TEST_P_(CompilerPerfTest, TestPerformance) +{ + const auto params = GetParam(); + Size sz = get<0>(params); + MatType type = get<1>(params); + + initMatsRandU(type, sz, type, false); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in; + auto splitted = cv::gapi::split3(in); + auto add1 = cv::gapi::addC({1}, std::get<0>(splitted)); + auto add2 = cv::gapi::addC({2}, std::get<1>(splitted)); + auto add3 = cv::gapi::addC({3}, std::get<2>(splitted)); + auto out = cv::gapi::merge3(add1, add2, add3); + + TEST_CYCLE() + { + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, cv::compile_args(cv::gapi::core::fluid::kernels())); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(CompilerPerfTest, CompilerPerfTest, + Combine(Values(szSmall128, szVGA, sz720p, sz1080p), + Values(CV_8UC3))); + +} // namespace opencv_test diff --git a/modules/gapi/perf/perf_main.cpp b/modules/gapi/perf/perf_main.cpp new file mode 100644 index 0000000000..8d6d77edce --- /dev/null +++ b/modules/gapi/perf/perf_main.cpp @@ -0,0 +1,11 @@ +// 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) 2018 Intel Corporation + + +#include "perf_precomp.hpp" +//#include "../test/test_precomp.hpp" + +CV_PERF_TEST_MAIN(gapi) diff --git a/modules/gapi/perf/perf_precomp.hpp b/modules/gapi/perf/perf_precomp.hpp new file mode 100644 index 0000000000..c7e79ba2f4 --- /dev/null +++ b/modules/gapi/perf/perf_precomp.hpp @@ -0,0 +1,21 @@ +// 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) 2018 Intel Corporation + + +#ifndef __OPENCV_GAPI_PERF_PRECOMP_HPP__ +#define __OPENCV_GAPI_PERF_PRECOMP_HPP__ + +#include +#include + +#include "opencv2/ts.hpp" +#include "opencv2/gapi.hpp" +#include "opencv2/gapi/imgproc.hpp" +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/cpu/gcpukernel.hpp" +#include "opencv2/gapi/operators.hpp" + +#endif diff --git a/modules/gapi/src/api/README.md b/modules/gapi/src/api/README.md new file mode 100644 index 0000000000..970f730ecd --- /dev/null +++ b/modules/gapi/src/api/README.md @@ -0,0 +1 @@ +This directory contains implementation of G-API frontend (public API classes). \ No newline at end of file diff --git a/modules/gapi/src/api/gapi_priv.cpp b/modules/gapi/src/api/gapi_priv.cpp new file mode 100644 index 0000000000..38f17e9f53 --- /dev/null +++ b/modules/gapi/src/api/gapi_priv.cpp @@ -0,0 +1,43 @@ +// 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) 2018 Intel Corporation + + +#include + +#include "api/gapi_priv.hpp" +#include "api/gnode_priv.hpp" + +cv::GOrigin::GOrigin(GShape s, + const cv::GNode& n, + std::size_t p, + const cv::gimpl::HostCtor c) + : shape(s), node(n), port(p), ctor(c) +{ +} + +cv::GOrigin::GOrigin(GShape s, cv::gimpl::ConstVal v) + : shape(s), node(cv::GNode::Const()), value(v), port(INVALID_PORT) +{ +} + +bool cv::detail::GOriginCmp::operator() (const cv::GOrigin &lhs, + const cv::GOrigin &rhs) const +{ + const GNode::Priv* lhs_p = &lhs.node.priv(); + const GNode::Priv* rhs_p = &rhs.node.priv(); + if (lhs_p == rhs_p) + { + if (lhs.port == rhs.port) + { + // A data Origin is uniquely identified by {node/port} pair. + // The situation when there're two Origins with same {node/port}s + // but with different shapes (data formats) is illegal! + GAPI_Assert(lhs.shape == rhs.shape); + } + return lhs.port < rhs.port; + } + else return lhs_p < rhs_p; +} diff --git a/modules/gapi/src/api/gapi_priv.hpp b/modules/gapi/src/api/gapi_priv.hpp new file mode 100644 index 0000000000..edab0a08b6 --- /dev/null +++ b/modules/gapi/src/api/gapi_priv.hpp @@ -0,0 +1,77 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_PRIV_HPP +#define OPENCV_GAPI_PRIV_HPP + +#include // set +#include // map +#include + +#include "opencv2/gapi/util/variant.hpp" // variant +#include "opencv2/gapi/garray.hpp" // ConstructVec +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/gcommon.hpp" + +#include "opencv2/gapi/opencv_includes.hpp" + +#include "api/gnode.hpp" + +namespace cv +{ + +namespace gimpl +{ + // Union type for various user-defined type constructors (GArray, etc) + // FIXME: Replace construct-only API with a more generic one + // (probably with bits of introspection) + // Not required for non-user-defined types (GMat, GScalar, etc) + using HostCtor = util::variant + < util::monostate + , detail::ConstructVec + >; + + using ConstVal = util::variant + < util::monostate + , cv::gapi::own::Scalar + >; +} + +// TODO namespace gimpl? + +struct GOrigin +{ + static constexpr const std::size_t INVALID_PORT = std::numeric_limits::max(); + + GOrigin(GShape s, + const GNode& n, + std::size_t p = INVALID_PORT, + const gimpl::HostCtor h = {}); + GOrigin(GShape s, gimpl::ConstVal value); + + const GShape shape; // Shape of a produced object + const GNode node; // a GNode which produces an object + const gimpl::ConstVal value; // Node can have initial constant value, now only scalar is supported + const std::size_t port; // GNode's output number; FIXME: "= max_size" in C++14 + gimpl::HostCtor ctor; // FIXME: replace with an interface? +}; + +namespace detail +{ + struct GOriginCmp + { + bool operator() (const GOrigin &lhs, const GOrigin &rhs) const; + }; +} // namespace cv::details + +// TODO introduce a hash on GOrigin and define this via unordered_ ? +using GOriginSet = std::set; +template using GOriginMap = std::map; + +} // namespace cv + +#endif // OPENCV_GAPI_PRIV_HPP diff --git a/modules/gapi/src/api/garray.cpp b/modules/gapi/src/api/garray.cpp new file mode 100644 index 0000000000..e2a346c0bd --- /dev/null +++ b/modules/gapi/src/api/garray.cpp @@ -0,0 +1,44 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/garray.hpp" +#include "api/gapi_priv.hpp" // GOrigin + +// cv::detail::GArrayU public implementation /////////////////////////////////// +cv::detail::GArrayU::GArrayU() + : m_priv(new GOrigin(GShape::GARRAY, cv::GNode::Param())) +{ +} + +cv::detail::GArrayU::GArrayU(const GNode &n, std::size_t out) + : m_priv(new GOrigin(GShape::GARRAY, n, out)) +{ +} + +cv::GOrigin& cv::detail::GArrayU::priv() +{ + return *m_priv; +} + +const cv::GOrigin& cv::detail::GArrayU::priv() const +{ + return *m_priv; +} + +void cv::detail::GArrayU::setConstructFcn(ConstructVec &&cv) +{ + m_priv->ctor = std::move(cv); +} + +namespace cv { +std::ostream& operator<<(std::ostream& os, const cv::GArrayDesc &) +{ + // FIXME: add type information here + os << "(array)"; + return os; +} +} diff --git a/modules/gapi/src/api/gbackend.cpp b/modules/gapi/src/api/gbackend.cpp new file mode 100644 index 0000000000..72cdcad728 --- /dev/null +++ b/modules/gapi/src/api/gbackend.cpp @@ -0,0 +1,269 @@ +// 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) 2018 Intel Corporation + + +#include // unique_ptr + +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/own/convert.hpp" + +#include "api/gbackend_priv.hpp" +#include "backends/common/gbackend.hpp" +#include "compiler/gobjref.hpp" +#include "compiler/gislandmodel.hpp" + + + +// GBackend private implementation ///////////////////////////////////////////// +void cv::gapi::GBackend::Priv::unpackKernel(ade::Graph & /*graph */ , + const ade::NodeHandle & /*op_node*/ , + const GKernelImpl & /*impl */ ) +{ + // Default implementation is still there as Priv + // is instantiated by some tests. + // Priv is even instantiated as a mock object in a number of tests + // as a backend and this method is called for mock objects (doing nothing). + // FIXME: add a warning message here + // FIXME: Do something with this! Ideally this function should be "=0"; +} + +std::unique_ptr +cv::gapi::GBackend::Priv::compile(const ade::Graph&, + const GCompileArgs&, + const std::vector &) const +{ + // ...and this method is here for the same reason! + GAPI_Assert(false); + return {}; +} + +void cv::gapi::GBackend::Priv::addBackendPasses(ade::ExecutionEngineSetupContext &) +{ + // Do nothing by default, plugins may override this to + // add custom (backend-specific) graph transformations +} + +// GBackend public implementation ////////////////////////////////////////////// +cv::gapi::GBackend::GBackend() +{ +} + +cv::gapi::GBackend::GBackend(std::shared_ptr &&p) + : m_priv(std::move(p)) +{ +} + +cv::gapi::GBackend::Priv& cv::gapi::GBackend::priv() +{ + return *m_priv; +} + +const cv::gapi::GBackend::Priv& cv::gapi::GBackend::priv() const +{ + return *m_priv; +} + +std::size_t cv::gapi::GBackend::hash() const +{ + return std::hash{}(m_priv.get()); +} + +bool cv::gapi::GBackend::operator== (const cv::gapi::GBackend &rhs) const +{ + return m_priv == rhs.m_priv; +} + +// Abstract Host-side data manipulation //////////////////////////////////////// +// Reused between CPU backend and more generic GExecutor +namespace cv { +namespace gimpl { +namespace magazine { + +// FIXME implement the below functions with visit()? + +void bindInArg(Mag& mag, const RcDesc &rc, const GRunArg &arg) +{ + switch (rc.shape) + { + case GShape::GMAT: + { + auto& mag_mat = mag.template slot()[rc.id]; + switch (arg.index()) + { + case GRunArg::index_of() : mag_mat = util::get(arg); break; + case GRunArg::index_of() : mag_mat = to_own(util::get(arg)); break; + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + break; + } + + case GShape::GSCALAR: + { + auto& mag_scalar = mag.template slot()[rc.id]; + switch (arg.index()) + { + case GRunArg::index_of() : mag_scalar = util::get(arg); break; + case GRunArg::index_of() : mag_scalar = to_own(util::get(arg)); break; + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + break; + } + + case GShape::GARRAY: + mag.template slot()[rc.id] = util::get(arg); + break; + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + } +} + +void bindOutArg(Mag& mag, const RcDesc &rc, const GRunArgP &arg) +{ + switch (rc.shape) + { + case GShape::GMAT: + { + auto& mag_mat = mag.template slot()[rc.id]; + switch (arg.index()) + { + case GRunArgP::index_of() : mag_mat = * util::get(arg); break; + case GRunArgP::index_of() : mag_mat = to_own(* util::get(arg)); break; + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + break; + } + + case GShape::GSCALAR: + { + auto& mag_scalar = mag.template slot()[rc.id]; + switch (arg.index()) + { + case GRunArgP::index_of() : mag_scalar = *util::get(arg); break; + case GRunArgP::index_of() : mag_scalar = to_own(*util::get(arg)); break; + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + break; + } + case GShape::GARRAY: + mag.template slot()[rc.id] = util::get(arg); + break; + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +void resetInternalData(Mag& mag, const Data &d) +{ + if (d.storage != Data::Storage::INTERNAL) + return; + + switch (d.shape) + { + case GShape::GARRAY: + util::get(d.ctor) + (mag.template slot()[d.rc]); + break; + + case GShape::GSCALAR: + mag.template slot()[d.rc] = cv::gapi::own::Scalar(); + break; + + case GShape::GMAT: + // Do nothign here - FIXME unify with initInternalData? + break; + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +cv::GRunArg getArg(const Mag& mag, const RcDesc &ref) +{ + // Wrap associated CPU object (either host or an internal one) + switch (ref.shape) + { + case GShape::GMAT: return GRunArg(mag.template slot().at(ref.id)); + case GShape::GSCALAR: return GRunArg(mag.template slot().at(ref.id)); + // Note: .at() is intentional for GArray as object MUST be already there + // (and constructed by either bindIn/Out or resetInternal) + case GShape::GARRAY: return GRunArg(mag.template slot().at(ref.id)); + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +cv::GRunArgP getObjPtr(Mag& mag, const RcDesc &rc) +{ + switch (rc.shape) + { + case GShape::GMAT: return GRunArgP(&mag.template slot() [rc.id]); + case GShape::GSCALAR: return GRunArgP(&mag.template slot()[rc.id]); + // Note: .at() is intentional for GArray as object MUST be already there + // (and constructer by either bindIn/Out or resetInternal) + case GShape::GARRAY: + // FIXME(DM): For some absolutely unknown to me reason, move + // semantics is involved here without const_cast to const (and + // value from map is moved into return value GRunArgP, leaving + // map with broken value I've spent few late Friday hours + // debugging this!!!1 + return GRunArgP(const_cast(mag) + .template slot().at(rc.id)); + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +void writeBack(const Mag& mag, const RcDesc &rc, GRunArgP &g_arg) +{ + switch (rc.shape) + { + case GShape::GARRAY: + // Do nothing - should we really do anything here? + break; + + case GShape::GMAT: + { + //simply check that memory was not reallocated, i.e. + //both instances of Mat pointing to the same memory + uchar* out_arg_data = nullptr; + switch (g_arg.index()) + { + case GRunArgP::index_of() : out_arg_data = util::get(g_arg)->data; break; + case GRunArgP::index_of() : out_arg_data = util::get(g_arg)->data; break; + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + + auto& in_mag = mag.template slot().at(rc.id); + GAPI_Assert((out_arg_data == in_mag.data) && " data for output parameters was reallocated ?"); + break; + } + + case GShape::GSCALAR: + { + switch (g_arg.index()) + { + case GRunArgP::index_of() : *util::get(g_arg) = mag.template slot().at(rc.id); break; + case GRunArgP::index_of() : *util::get(g_arg) = cv::gapi::own::to_ocv(mag.template slot().at(rc.id)); break; + default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?")); + } + break; + } + + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +} // namespace magazine +} // namespace gimpl +} // namespace cv diff --git a/modules/gapi/src/api/gbackend_priv.hpp b/modules/gapi/src/api/gbackend_priv.hpp new file mode 100644 index 0000000000..1c6e297157 --- /dev/null +++ b/modules/gapi/src/api/gbackend_priv.hpp @@ -0,0 +1,53 @@ +// 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) 2018 Intel Corporation + + +#ifndef GAPI_API_GBACKEND_PRIV_HPP +#define GAPI_API_GBACKEND_PRIV_HPP + +#include +#include + +#include +#include // passes::PassContext +#include // ..SetupContext + +#include "opencv2/gapi/gcommon.hpp" +#include "opencv2/gapi/gkernel.hpp" + +namespace cv +{ +namespace gimpl +{ + class GBackend; + class GIslandExecutable; +} // namespace gimpl +} // namespace cv + +// GAPI_EXPORTS is here to make tests build on Windows +class GAPI_EXPORTS cv::gapi::GBackend::Priv +{ +public: + using EPtr = std::unique_ptr; + + virtual void unpackKernel(ade::Graph &graph, + const ade::NodeHandle &op_node, + const GKernelImpl &impl); + + // FIXME: since backends are not passed to ADE anymore, + // there's no need in having both cv::gimpl::GBackend + // and cv::gapi::GBackend - these two things can be unified + // NOTE - nodes are guaranteed to be topologically sorted. + virtual EPtr compile(const ade::Graph &graph, + const GCompileArgs &args, + const std::vector &nodes) const; + + virtual void addBackendPasses(ade::ExecutionEngineSetupContext &); + + virtual ~Priv() = default; +}; + +#endif // GAPI_API_GBACKEND_PRIV_HPP diff --git a/modules/gapi/src/api/gcall.cpp b/modules/gapi/src/api/gcall.cpp new file mode 100644 index 0000000000..bc23ec8854 --- /dev/null +++ b/modules/gapi/src/api/gcall.cpp @@ -0,0 +1,64 @@ +// 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) 2018 Intel Corporation + + +#include +#include "opencv2/gapi/gcall.hpp" +#include "api/gcall_priv.hpp" + +// GCall private implementation //////////////////////////////////////////////// +cv::GCall::Priv::Priv(const cv::GKernel &k) + : m_k(k) +{ +} + +// GCall public implementation ///////////////////////////////////////////////// + +cv::GCall::GCall(const cv::GKernel &k) + : m_priv(new Priv(k)) +{ + // Here we have a reference to GNode, + // and GNode has a reference to us. Cycle! Now see destructor. + m_priv->m_node = GNode::Call(*this); +} + +cv::GCall::~GCall() +{ + // When a GCall object is destroyed (and GCall::Priv is likely still alive, + // as there might be other references), reset m_node to break cycle. + m_priv->m_node = GNode(); +} + +void cv::GCall::setArgs(std::vector &&args) +{ + // FIXME: Check if argument number is matching kernel prototype + m_priv->m_args = std::move(args); +} + +cv::GMat cv::GCall::yield(int output) +{ + return cv::GMat(m_priv->m_node, output); +} + +cv::GScalar cv::GCall::yieldScalar(int output) +{ + return cv::GScalar(m_priv->m_node, output); +} + +cv::detail::GArrayU cv::GCall::yieldArray(int output) +{ + return cv::detail::GArrayU(m_priv->m_node, output); +} + +cv::GCall::Priv& cv::GCall::priv() +{ + return *m_priv; +} + +const cv::GCall::Priv& cv::GCall::priv() const +{ + return *m_priv; +} diff --git a/modules/gapi/src/api/gcall_priv.hpp b/modules/gapi/src/api/gcall_priv.hpp new file mode 100644 index 0000000000..ffb122ec85 --- /dev/null +++ b/modules/gapi/src/api/gcall_priv.hpp @@ -0,0 +1,37 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GCALL_PRIV_HPP +#define OPENCV_GCALL_PRIV_HPP + +#include +#include + +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gcall.hpp" +#include "opencv2/gapi/gkernel.hpp" + +#include "api/gnode.hpp" + +namespace cv { + +class GCall::Priv +{ +public: + std::vector m_args; + const GKernel m_k; + + // FIXME: Document that there's no recursion here. + // TODO: Rename to "constructionNode" or smt to reflect its lifetime + GNode m_node; + + explicit Priv(const GKernel &k); +}; + +} + +#endif // OPENCV_GCALL_PRIV_HPP diff --git a/modules/gapi/src/api/gcomputation.cpp b/modules/gapi/src/api/gcomputation.cpp new file mode 100644 index 0000000000..817da8fd29 --- /dev/null +++ b/modules/gapi/src/api/gcomputation.cpp @@ -0,0 +1,191 @@ +// 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) 2018 Intel Corporation + + +#include // remove_if +#include // isspace (non-locale version) +#include + +#include "opencv2/core/cvdef.h" +#include "logger.hpp" // GAPI_LOG + +#include "opencv2/gapi/gcomputation.hpp" +#include "opencv2/gapi/gkernel.hpp" + +#include "api/gcomputation_priv.hpp" +#include "api/gcall_priv.hpp" +#include "api/gnode_priv.hpp" + +#include "compiler/gmodelbuilder.hpp" +#include "compiler/gcompiler.hpp" + +// cv::GComputation private implementation ///////////////////////////////////// +// + +// cv::GComputation public implementation ////////////////////////////////////// +cv::GComputation::GComputation(const Generator& gen) + : m_priv(gen().m_priv) +{ +} + +cv::GComputation::GComputation(GMat in, GMat out) + : cv::GComputation(cv::GIn(in), cv::GOut(out)) +{ +} + + +cv::GComputation::GComputation(GMat in, GScalar out) + : cv::GComputation(cv::GIn(in), cv::GOut(out)) +{ +} + +cv::GComputation::GComputation(GMat in1, GMat in2, GMat out) + : cv::GComputation(cv::GIn(in1, in2), cv::GOut(out)) +{ +} + +cv::GComputation::GComputation(GMat in1, GMat in2, GScalar out) + : cv::GComputation(cv::GIn(in1, in2), cv::GOut(out)) +{ +} + +cv::GComputation::GComputation(const std::vector &ins, + const std::vector &outs) + : m_priv(new Priv()) +{ + const auto wrap = [](cv::GMat m) { return GProtoArg(m); }; + ade::util::transform(ins, std::back_inserter(m_priv->m_ins), wrap); + ade::util::transform(outs, std::back_inserter(m_priv->m_outs), wrap); +} + +cv::GComputation::GComputation(cv::GProtoInputArgs &&ins, + cv::GProtoOutputArgs &&outs) + : m_priv(new Priv()) +{ + m_priv->m_ins = std::move(ins.m_args); + m_priv->m_outs = std::move(outs.m_args); +} + +cv::GCompiled cv::GComputation::compile(GMetaArgs &&metas, GCompileArgs &&args) +{ + // FIXME: Cache gcompiled per parameters here? + cv::gimpl::GCompiler comp(*this, std::move(metas), std::move(args)); + return comp.compile(); +} + +void cv::GComputation::apply(GRunArgs &&ins, GRunArgsP &&outs, GCompileArgs &&args) +{ + const auto in_metas = descr_of(ins); + // FIXME Graph should be recompiled when GCompileArgs have changed + if (m_priv->m_lastMetas != in_metas) + { + // FIXME: Had to construct temporary object as compile() takes && (r-value) + m_priv->m_lastCompiled = compile(GMetaArgs(in_metas), std::move(args)); + m_priv->m_lastMetas = in_metas; // Update only here, if compile() was ok + } + m_priv->m_lastCompiled(std::move(ins), std::move(outs)); +} + +void cv::GComputation::apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args) +{ + apply(cv::gin(in), cv::gout(out), std::move(args)); + // FIXME: The following doesn't work! + // Operation result is not replicated into user's object + // apply({GRunArg(in)}, {GRunArg(out)}); +} + +void cv::GComputation::apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args) +{ + apply(cv::gin(in), cv::gout(out), std::move(args)); +} + +void cv::GComputation::apply(cv::Mat in1, cv::Mat in2, cv::Mat &out, GCompileArgs &&args) +{ + apply(cv::gin(in1, in2), cv::gout(out), std::move(args)); +} + +void cv::GComputation::apply(cv::Mat in1, cv::Mat in2, cv::Scalar &out, GCompileArgs &&args) +{ + apply(cv::gin(in1, in2), cv::gout(out), std::move(args)); +} + +void cv::GComputation::apply(const std::vector &ins, + const std::vector &outs, + GCompileArgs &&args) +{ + GRunArgs call_ins; + GRunArgsP call_outs; + + // Make a temporary copy of vector outs - cv::Mats are copies anyway + auto tmp = outs; + for (const cv::Mat &m : ins) { call_ins.emplace_back(m); } + for ( cv::Mat &m : tmp) { call_outs.emplace_back(&m); } + + apply(std::move(call_ins), std::move(call_outs), std::move(args)); +} + +cv::GComputation::Priv& cv::GComputation::priv() +{ + return *m_priv; +} + +const cv::GComputation::Priv& cv::GComputation::priv() const +{ + return *m_priv; +} + +// Islands ///////////////////////////////////////////////////////////////////// + +void cv::gapi::island(const std::string &name, + GProtoInputArgs &&ins, + GProtoOutputArgs &&outs) +{ + { + // Island must have a printable name. + // Forbid names which contain only spaces. + GAPI_Assert(!name.empty()); + const auto first_printable_it = std::find_if_not(name.begin(), name.end(), isspace); + const bool likely_printable = first_printable_it != name.end(); + GAPI_Assert(likely_printable); + } + // Even if the name contains spaces, keep it unmodified as user will + // then use this string to assign affinity, etc. + + // First, set island tags on all operations from `ins` to `outs` + auto island = cv::gimpl::unrollExpr(ins.m_args, outs.m_args); + if (island.all_ops.empty()) + { + util::throw_error(std::logic_error("Operation range is empty")); + } + for (auto &op_expr_node : island.all_ops) + { + auto &op_expr_node_p = op_expr_node.priv(); + + GAPI_Assert(op_expr_node.shape() == GNode::NodeShape::CALL); + const GCall& call = op_expr_node.call(); + const GCall::Priv& call_p = call.priv(); + + if (!op_expr_node_p.m_island.empty()) + { + util::throw_error(std::logic_error + ( "Operation " + call_p.m_k.name + + " is already assigned to island \"" + + op_expr_node_p.m_island + "\"")); + } + else + { + op_expr_node_p.m_island = name; + GAPI_LOG_INFO(NULL, + "Assigned " << call_p.m_k.name << "_" << &call_p << + " to island \"" << name << "\""); + } + } + + // Note - this function only sets islands to all operations in + // expression tree, it is just a first step. + // The second step is assigning intermediate data objects to Islands, + // see passes::initIslands for details. +} diff --git a/modules/gapi/src/api/gcomputation_priv.hpp b/modules/gapi/src/api/gcomputation_priv.hpp new file mode 100644 index 0000000000..13d1b9afa2 --- /dev/null +++ b/modules/gapi/src/api/gcomputation_priv.hpp @@ -0,0 +1,29 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMPUTATION_PRIV_HPP +#define OPENCV_GAPI_GCOMPUTATION_PRIV_HPP + +#include "opencv2/gapi.hpp" +#include "opencv2/gapi/gcall.hpp" + +#include "opencv2/gapi/util/variant.hpp" + +namespace cv { + +class GComputation::Priv +{ +public: + GCompiled m_lastCompiled; + GMetaArgs m_lastMetas; // TODO: make GCompiled remember its metas? + GProtoArgs m_ins; + GProtoArgs m_outs; +}; + +} + +#endif // OPENCV_GAPI_GCOMPUTATION_PRIV_HPP diff --git a/modules/gapi/src/api/gkernel.cpp b/modules/gapi/src/api/gkernel.cpp new file mode 100644 index 0000000000..812877b1fe --- /dev/null +++ b/modules/gapi/src/api/gkernel.cpp @@ -0,0 +1,141 @@ +// 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) 2018 Intel Corporation + + +#include // cerr +#include // hash +#include // accumulate + +#include + +#include "opencv2/core/cvdef.h" +#include "logger.hpp" +#include "opencv2/gapi/gkernel.hpp" + +#include "api/gbackend_priv.hpp" + +// GKernelPackage public implementation //////////////////////////////////////// +void cv::gapi::GKernelPackage::remove(const cv::gapi::GBackend& backend) +{ + m_backend_kernels.erase(backend); +} + +bool cv::gapi::GKernelPackage::includesAPI(const std::string &id) const +{ + // In current form not very efficient (n * log n) + auto it = std::find_if(m_backend_kernels.begin(), + m_backend_kernels.end(), + [&id](const M::value_type &p) { + return ade::util::contains(p.second, id); + }); + return (it != m_backend_kernels.end()); +} + +std::size_t cv::gapi::GKernelPackage::size() const +{ + return std::accumulate(m_backend_kernels.begin(), + m_backend_kernels.end(), + static_cast(0u), + [](std::size_t acc, const M::value_type& v) { + return acc + v.second.size(); + }); +} + +cv::gapi::GKernelPackage cv::gapi::combine(const GKernelPackage &lhs, + const GKernelPackage &rhs, + const cv::unite_policy policy) +{ + + if (policy == cv::unite_policy::REPLACE) + { + // REPLACE policy: if there is a collision, prefer RHS + // to LHS + // since OTHER package has a prefernece, start with its copy + GKernelPackage result(rhs); + // now iterate over LHS package and put kernel if and only + // if there's no such one + for (const auto &backend : lhs.m_backend_kernels) + { + for (const auto &kimpl : backend.second) + { + if (!result.includesAPI(kimpl.first)) + result.m_backend_kernels[backend.first].insert(kimpl); + } + } + return result; + } + else if (policy == cv::unite_policy::KEEP) + { + // KEEP policy: if there is a collision, just keep two versions + // of a kernel + GKernelPackage result(lhs); + for (const auto &p : rhs.m_backend_kernels) + { + result.m_backend_kernels[p.first].insert(p.second.begin(), + p.second.end()); + } + return result; + } + else GAPI_Assert(false); + return GKernelPackage(); +} + +std::pair +cv::gapi::GKernelPackage::lookup(const std::string &id, + const GLookupOrder &order) const +{ + if (order.empty()) + { + // If order is empty, return what comes first + auto it = std::find_if(m_backend_kernels.begin(), + m_backend_kernels.end(), + [&id](const M::value_type &p) { + return ade::util::contains(p.second, id); + }); + if (it != m_backend_kernels.end()) + { + // FIXME: Two lookups! + return std::make_pair(it->first, it->second.find(id)->second); + } + } + else + { + // There is order, so: + // 1. Limit search scope only to specified backends + // FIXME: Currently it is not configurable if search can fall-back + // to other backends (not listed in order) if kernel hasn't been found + // in the look-up list + // 2. Query backends in the specified order + for (const auto &selected_backend : order) + { + const auto kernels_it = m_backend_kernels.find(selected_backend); + if (kernels_it == m_backend_kernels.end()) + { + GAPI_LOG_WARNING(NULL, + "Backend " + << &selected_backend.priv() // FIXME: name instead + << " was listed in lookup list but was not found " + "in the package"); + continue; + } + if (ade::util::contains(kernels_it->second, id)) + { + // FIXME: two lookups! + return std::make_pair(selected_backend, kernels_it->second.find(id)->second); + } + } + } + + // If reached here, kernel was not found among selected backends. + util::throw_error(std::logic_error("Kernel " + id + " was not found")); +} + +std::vector cv::gapi::GKernelPackage::backends() const +{ + std::vector result; + for (const auto &p : m_backend_kernels) result.emplace_back(p.first); + return result; +} diff --git a/modules/gapi/src/api/gmat.cpp b/modules/gapi/src/api/gmat.cpp new file mode 100644 index 0000000000..5b3aa0758d --- /dev/null +++ b/modules/gapi/src/api/gmat.cpp @@ -0,0 +1,71 @@ +// 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) 2018 Intel Corporation + + +#include +#include //gapi::own::Mat + +#include "opencv2/gapi/gmat.hpp" +#include "api/gapi_priv.hpp" // GOrigin + +// cv::GMat public implementation ////////////////////////////////////////////// +cv::GMat::GMat() + : m_priv(new GOrigin(GShape::GMAT, GNode::Param())) +{ +} + +cv::GMat::GMat(const GNode &n, std::size_t out) + : m_priv(new GOrigin(GShape::GMAT, n, out)) +{ +} + +cv::GOrigin& cv::GMat::priv() +{ + return *m_priv; +} + +const cv::GOrigin& cv::GMat::priv() const +{ + return *m_priv; +} + +cv::GMatDesc cv::descr_of(const cv::Mat &mat) +{ + return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}}; +} + +cv::GMatDesc cv::gapi::own::descr_of(const cv::gapi::own::Mat &mat) +{ + return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}}; +} + +namespace cv { +std::ostream& operator<<(std::ostream& os, const cv::GMatDesc &desc) +{ + switch (desc.depth) + { +#define TT(X) case CV_##X: os << #X; break; + TT(8U); + TT(8S); + TT(16U); + TT(16S); + TT(32S); + TT(32F); + TT(64F); +#undef TT + default: + os << "(user type " + << std::hex << desc.depth << std::dec + << ")"; + break; + } + + os << "C" << desc.chan << " "; + os << desc.size.width << "x" << desc.size.height; + + return os; +} +} diff --git a/modules/gapi/src/api/gnode.cpp b/modules/gapi/src/api/gnode.cpp new file mode 100644 index 0000000000..4255ea6c7e --- /dev/null +++ b/modules/gapi/src/api/gnode.cpp @@ -0,0 +1,88 @@ +// 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) 2018 Intel Corporation + + +#include + +#include "api/gnode.hpp" +#include "api/gnode_priv.hpp" + +// GNode private implementation +cv::GNode::Priv::Priv() + : m_shape(NodeShape::EMPTY) +{ +} + +cv::GNode::Priv::Priv(GCall c) + : m_shape(NodeShape::CALL), m_spec(c) +{ +} + +cv::GNode::Priv::Priv(ParamTag) + : m_shape(NodeShape::PARAM) +{ +} + +cv::GNode::Priv::Priv(ConstTag) + : m_shape(NodeShape::CONST_BOUNDED) +{ +} + +// GNode public implementation +cv::GNode::GNode() + : m_priv(new Priv()) +{ +} + +cv::GNode::GNode(const GCall &c) + : m_priv(new Priv(c)) +{ +} + +cv::GNode::GNode(ParamTag) + : m_priv(new Priv(Priv::ParamTag())) +{ +} + +cv::GNode::GNode(ConstTag) + : m_priv(new Priv(Priv::ConstTag())) +{ +} + +cv::GNode cv::GNode::Call(const GCall &c) +{ + return GNode(c); +} + +cv::GNode cv::GNode::Param() +{ + return GNode(ParamTag()); +} + +cv::GNode cv::GNode::Const() +{ + return GNode(ConstTag()); +} + +cv::GNode::Priv& cv::GNode::priv() +{ + return *m_priv; +} + +const cv::GNode::Priv& cv::GNode::priv() const +{ + return *m_priv; +} + +const cv::GNode::NodeShape& cv::GNode::shape() const +{ + return m_priv->m_shape; +} + +const cv::GCall& cv::GNode::call() const +{ + return util::get(m_priv->m_spec); +} diff --git a/modules/gapi/src/api/gnode.hpp b/modules/gapi/src/api/gnode.hpp new file mode 100644 index 0000000000..bd6c7901e3 --- /dev/null +++ b/modules/gapi/src/api/gnode.hpp @@ -0,0 +1,58 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GNODE_HPP +#define OPENCV_GAPI_GNODE_HPP + +#include // std::shared_ptr + +namespace cv { + +class GCall; + +// TODO Move "internal" namespace +// TODO Expose details? + +// This class won't be public + +// data GNode = Call Operation [GNode] +// | Const +// | Param + +class GNode +{ +public: + class Priv; + + // Constructors + GNode(); // Empty (invalid) constructor + static GNode Call (const GCall &c); // Call constructor + static GNode Param(); // Param constructor + static GNode Const(); + + // Internal use only + Priv& priv(); + const Priv& priv() const; + enum class NodeShape: unsigned int; + + const NodeShape& shape() const; + const GCall& call() const; + +protected: + struct ParamTag {}; + struct ConstTag {}; + + explicit GNode(const GCall &c); + explicit GNode(ParamTag unused); + explicit GNode(ConstTag unused); + + std::shared_ptr m_priv; +}; + +} + +#endif // OPENCV_GAPI_GNODE_HPP diff --git a/modules/gapi/src/api/gnode_priv.hpp b/modules/gapi/src/api/gnode_priv.hpp new file mode 100644 index 0000000000..5425471f81 --- /dev/null +++ b/modules/gapi/src/api/gnode_priv.hpp @@ -0,0 +1,52 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GNODE_PRIV_HPP +#define OPENCV_GNODE_PRIV_HPP + +#include +#include +#include + +#include "opencv2/gapi/util/variant.hpp" + +#include "opencv2/gapi/gcall.hpp" +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gkernel.hpp" + +#include "api/gnode.hpp" + +namespace cv { + +enum class GNode::NodeShape: unsigned int +{ + EMPTY, + CALL, + PARAM, + CONST_BOUNDED +}; + +class GNode::Priv +{ +public: + // TODO: replace with optional? + typedef util::variant NodeSpec; + const NodeShape m_shape; + const NodeSpec m_spec; + std::string m_island; // user-modifiable attribute + struct ParamTag {}; + struct ConstTag {}; + + Priv(); // Empty (invalid) constructor + explicit Priv(GCall c); // Call conctrustor + explicit Priv(ParamTag u); // Param constructor + explicit Priv(ConstTag u); // Param constructor +}; + +} + +#endif // OPENCV_GNODE_PRIV_HPP diff --git a/modules/gapi/src/api/gproto.cpp b/modules/gapi/src/api/gproto.cpp new file mode 100644 index 0000000000..13da40e5d1 --- /dev/null +++ b/modules/gapi/src/api/gproto.cpp @@ -0,0 +1,152 @@ +// 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) 2018 Intel Corporation + + +#include +#include "opencv2/gapi/util/throw.hpp" +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gproto.hpp" + +#include "api/gapi_priv.hpp" +#include "api/gproto_priv.hpp" + +// FIXME: it should be a visitor! +// FIXME: Reimplement with traits? + +const cv::GOrigin& cv::gimpl::proto::origin_of(const cv::GProtoArg &arg) +{ + switch (arg.index()) + { + case cv::GProtoArg::index_of(): + return util::get(arg).priv(); + + case cv::GProtoArg::index_of(): + return util::get(arg).priv(); + + case cv::GProtoArg::index_of(): + return util::get(arg).priv(); + + default: + util::throw_error(std::logic_error("Unsupported GProtoArg type")); + } +} + +const cv::GOrigin& cv::gimpl::proto::origin_of(const cv::GArg &arg) +{ + // Generic, but not very efficient implementation + // FIXME: Walking a thin line here!!! Here we rely that GArg and + // GProtoArg share the same object and this is true while objects + // are reference-counted, so return value is not a reference to a tmp. + return origin_of(rewrap(arg)); +} + +bool cv::gimpl::proto::is_dynamic(const cv::GArg& arg) +{ + // FIXME: refactor this method to be auto-generated from + // - GProtoArg variant parameter pack, and + // - traits over every type + switch (arg.kind) + { + case detail::ArgKind::GMAT: + case detail::ArgKind::GSCALAR: + case detail::ArgKind::GARRAY: + return true; + + default: + return false; + } +} + +cv::GRunArg cv::value_of(const cv::GOrigin &origin) +{ + switch (origin.shape) + { + case GShape::GSCALAR: return GRunArg(util::get(origin.value)); + default: util::throw_error(std::logic_error("Unsupported shape for constant")); + } +} + +cv::GProtoArg cv::gimpl::proto::rewrap(const cv::GArg &arg) +{ + // FIXME: replace with a more generic any->variant + // (or variant -> variant) conversion? + switch (arg.kind) + { + case detail::ArgKind::GMAT: return GProtoArg(arg.get()); + case detail::ArgKind::GSCALAR: return GProtoArg(arg.get()); + case detail::ArgKind::GARRAY: return GProtoArg(arg.get()); + default: util::throw_error(std::logic_error("Unsupported GArg type")); + } +} + +cv::GMetaArg cv::descr_of(const cv::GRunArg &arg) +{ + switch (arg.index()) + { + case GRunArg::index_of(): + return cv::GMetaArg(descr_of(util::get(arg))); + + case GRunArg::index_of(): + return cv::GMetaArg(descr_of(util::get(arg))); + + case GRunArg::index_of(): + return cv::GMetaArg(descr_of(util::get(arg))); + + case GRunArg::index_of(): + return cv::GMetaArg(util::get(arg).descr_of()); + + default: util::throw_error(std::logic_error("Unsupported GRunArg type")); + } +} + +cv::GMetaArgs cv::descr_of(const cv::GRunArgs &args) +{ + cv::GMetaArgs metas; + ade::util::transform(args, std::back_inserter(metas), [](const cv::GRunArg &arg){ return descr_of(arg); }); + return metas; +} + +cv::GMetaArg cv::descr_of(const cv::GRunArgP &argp) +{ + switch (argp.index()) + { + case GRunArgP::index_of(): return GMetaArg(descr_of(*util::get(argp))); + case GRunArgP::index_of(): return GMetaArg(descr_of(*util::get(argp))); + case GRunArgP::index_of(): return GMetaArg(descr_of(*util::get(argp))); + case GRunArgP::index_of(): return GMetaArg(descr_of(*util::get(argp))); + case GRunArgP::index_of(): return GMetaArg(util::get(argp).descr_of()); + default: util::throw_error(std::logic_error("Unsupported GRunArgP type")); + } +} + +namespace cv { +std::ostream& operator<<(std::ostream& os, const cv::GMetaArg &arg) +{ + // FIXME: Implement via variant visitor + switch (arg.index()) + { + case cv::GMetaArg::index_of(): + os << "(unresolved)"; + break; + + case cv::GMetaArg::index_of(): + os << util::get(arg); + break; + + case cv::GMetaArg::index_of(): + os << util::get(arg); + break; + + case cv::GMetaArg::index_of(): + os << util::get(arg); + break; + default: + GAPI_Assert(false); + } + + return os; +} +} diff --git a/modules/gapi/src/api/gproto_priv.hpp b/modules/gapi/src/api/gproto_priv.hpp new file mode 100644 index 0000000000..2684924c75 --- /dev/null +++ b/modules/gapi/src/api/gproto_priv.hpp @@ -0,0 +1,35 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GPROTO_PRIV_HPP +#define OPENCV_GAPI_GPROTO_PRIV_HPP + +#include "opencv2/gapi/gproto.hpp" +#include "opencv2/gapi/garg.hpp" + +#include "api/gapi_priv.hpp" + +namespace cv { +namespace gimpl { +namespace proto { + +// These methods are used by GModelBuilder only +// FIXME: Document semantics + +// FIXME: GAPI_EXPORTS because of tests only! +// FIXME: Possible dangling reference alert!!! +GAPI_EXPORTS const GOrigin& origin_of (const GProtoArg &arg); +GAPI_EXPORTS const GOrigin& origin_of (const GArg &arg); + +bool is_dynamic(const GArg &arg); +GProtoArg rewrap (const GArg &arg); + +} // proto +} // gimpl +} // cv + +#endif // OPENCV_GAPI_GPROTO_PRIV_HPP diff --git a/modules/gapi/src/api/gscalar.cpp b/modules/gapi/src/api/gscalar.cpp new file mode 100644 index 0000000000..d8d73cd732 --- /dev/null +++ b/modules/gapi/src/api/gscalar.cpp @@ -0,0 +1,69 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/own/convert.hpp" +#include "api/gapi_priv.hpp" // GOrigin + +// cv::GScalar public implementation /////////////////////////////////////////// +cv::GScalar::GScalar() + : m_priv(new GOrigin(GShape::GSCALAR, cv::GNode::Param())) +{ +} + +cv::GScalar::GScalar(const GNode &n, std::size_t out) + : m_priv(new GOrigin(GShape::GSCALAR, n, out)) +{ +} + +cv::GScalar::GScalar(const cv::gapi::own::Scalar& s) + : m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(s))) +{ +} + +cv::GScalar::GScalar(cv::gapi::own::Scalar&& s) + : m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(std::move(s)))) +{ +} + +cv::GScalar::GScalar(const cv::Scalar& s) + : m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(to_own(s)))) +{ +} + +cv::GScalar::GScalar(double v0) + : m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(cv::gapi::own::Scalar(v0)))) +{ +} + +cv::GOrigin& cv::GScalar::priv() +{ + return *m_priv; +} + +const cv::GOrigin& cv::GScalar::priv() const +{ + return *m_priv; +} + +cv::GScalarDesc cv::descr_of(const cv::gapi::own::Scalar &) +{ + return empty_scalar_desc(); +} + +cv::GScalarDesc cv::descr_of(const cv::Scalar& s) +{ + return cv::descr_of(to_own(s)); +} + +namespace cv { +std::ostream& operator<<(std::ostream& os, const cv::GScalarDesc &) +{ + os << "(scalar)"; + return os; +} +} diff --git a/modules/gapi/src/api/kernels_core.cpp b/modules/gapi/src/api/kernels_core.cpp new file mode 100644 index 0000000000..9a58706e0a --- /dev/null +++ b/modules/gapi/src/api/kernels_core.cpp @@ -0,0 +1,347 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/gcall.hpp" +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/core.hpp" + +#include +#include + +namespace cv { namespace gapi { + +GMat add(const GMat& src1, const GMat& src2, int dtype) +{ + return core::GAdd::on(src1, src2, dtype); +} + +GMat addC(const GMat& src1, const GScalar& c, int dtype) +{ + return core::GAddC::on(src1, c, dtype); +} + +GMat addC(const GScalar& c, const GMat& src1, int dtype) +{ + return core::GAddC::on(src1, c, dtype); +} + +GMat sub(const GMat& src1, const GMat& src2, int dtype) +{ + return core::GSub::on(src1, src2, dtype); +} + +GMat subC(const GMat& src1, const GScalar& c, int dtype) +{ + return core::GSubC::on(src1, c, dtype); +} + +GMat subRC(const GScalar& c, const GMat& src, int dtype) +{ + return core::GSubRC::on(c, src, dtype); +} + +GMat mul(const GMat& src1, const GMat& src2, double scale, int dtype) +{ + return core::GMul::on(src1, src2, scale, dtype); +} + +GMat mulC(const GMat& src, double scale, int dtype) +{ + return core::GMulCOld::on(src, scale, dtype); +} + +GMat mulC(const GMat& src, const GScalar& multiplier, int dtype) +{ + return core::GMulC::on(src, multiplier, dtype); +} + +GMat mulC(const GScalar& multiplier, const GMat& src, int dtype) +{ + return core::GMulC::on(src, multiplier, dtype); +} + +GMat div(const GMat& src1, const GMat& src2, double scale, int dtype) +{ + return core::GDiv::on(src1, src2, scale, dtype); +} + +GMat divC(const GMat& src, const GScalar& divisor, double scale, int dtype) +{ + return core::GDivC::on(src, divisor, scale, dtype); +} + +GMat divRC(const GScalar& divident, const GMat& src, double scale, int dtype) +{ + return core::GDivRC::on(divident, src, scale, dtype); +} + +GScalar mean(const GMat& src) +{ + return core::GMean::on(src); +} + +GMat mask(const GMat& src, const GMat& mask) +{ + return core::GMask::on(src, mask); +} + +std::tuple polarToCart(const GMat& magnitude, const GMat& angle, + bool angleInDegrees) +{ + return core::GPolarToCart::on(magnitude, angle, angleInDegrees); +} + +std::tuple cartToPolar(const GMat& x, const GMat& y, + bool angleInDegrees) +{ + return core::GCartToPolar::on(x, y, angleInDegrees); +} + +GMat cmpGT(const GMat& src1, const GMat& src2) +{ + return core::GCmpGT::on(src1, src2); +} + +GMat cmpLT(const GMat& src1, const GMat& src2) +{ + return core::GCmpLT::on(src1, src2); +} + +GMat cmpGE(const GMat& src1, const GMat& src2) +{ + return core::GCmpGE::on(src1, src2); +} + +GMat cmpLE(const GMat& src1, const GMat& src2) +{ + return core::GCmpLE::on(src1, src2); +} + +GMat cmpEQ(const GMat& src1, const GMat& src2) +{ + return core::GCmpEQ::on(src1, src2); +} + +GMat cmpNE(const GMat& src1, const GMat& src2) +{ + return core::GCmpNE::on(src1, src2); +} + +GMat cmpGT(const GMat& src1, const GScalar& src2) +{ + return core::GCmpGTScalar::on(src1, src2); +} + +GMat cmpLT(const GMat& src1, const GScalar& src2) +{ + return core::GCmpLTScalar::on(src1, src2); +} + +GMat cmpGE(const GMat& src1, const GScalar& src2) +{ + return core::GCmpGEScalar::on(src1, src2); +} + +GMat cmpLE(const GMat& src1, const GScalar& src2) +{ + return core::GCmpLEScalar::on(src1, src2); +} + +GMat cmpEQ(const GMat& src1, const GScalar& src2) +{ + return core::GCmpEQScalar::on(src1, src2); +} + +GMat cmpNE(const GMat& src1, const GScalar& src2) +{ + return core::GCmpNEScalar::on(src1, src2); +} + +GMat min(const GMat& src1, const GMat& src2) +{ + return core::GMin::on(src1, src2); +} + +GMat max(const GMat& src1, const GMat& src2) +{ + return core::GMax::on(src1, src2); +} + +GMat absDiff(const GMat& src1, const GMat& src2) +{ + return core::GAbsDiff::on(src1, src2); +} + +GMat absDiffC(const GMat& src, const GScalar& c) +{ + return core::GAbsDiffC::on(src, c); +} + +GMat bitwise_and(const GMat& src1, const GMat& src2) +{ + return core::GAnd::on(src1, src2); +} + +GMat bitwise_and(const GMat& src1, const GScalar& src2) +{ + return core::GAndS::on(src1, src2); +} + +GMat bitwise_or(const GMat& src1, const GMat& src2) +{ + return core::GOr::on(src1, src2); +} + +GMat bitwise_or(const GMat& src1, const GScalar& src2) +{ + return core::GOrS::on(src1, src2); +} + +GMat bitwise_xor(const GMat& src1, const GMat& src2) +{ + return core::GXor::on(src1, src2); +} + +GMat bitwise_xor(const GMat& src1, const GScalar& src2) +{ + return core::GXorS::on(src1, src2); +} + +GMat bitwise_not(const GMat& src1) +{ + return core::GNot::on(src1); +} + +GMat select(const GMat& src1, const GMat& src2, const GMat& mask) +{ + return core::GSelect::on(src1, src2, mask); +} + +GScalar sum(const GMat& src) +{ + return core::GSum::on(src); +} + +GMat addWeighted(const GMat& src1, double alpha, const GMat& src2, double beta, double gamma, int dtype) +{ + return core::GAddW::on(src1, alpha, src2, beta, gamma, dtype); +} + +GScalar normL1(const GMat& src) +{ + return core::GNormL1::on(src); +} + +GScalar normL2(const GMat& src) +{ + return core::GNormL2::on(src); +} + +GScalar normInf(const GMat& src) +{ + return core::GNormInf::on(src); +} + +std::tuple integral(const GMat& src, int sdepth, int sqdepth) +{ + return core::GIntegral::on(src, sdepth, sqdepth); +} + +GMat threshold(const GMat& src, const GScalar& thresh, const GScalar& maxval, int type) +{ + GAPI_Assert(type != cv::THRESH_TRIANGLE && type != cv::THRESH_OTSU); + return core::GThreshold::on(src, thresh, maxval, type); +} + +std::tuple threshold(const GMat& src, const GScalar& maxval, int type) +{ + GAPI_Assert(type == cv::THRESH_TRIANGLE || type == cv::THRESH_OTSU); + return core::GThresholdOT::on(src, maxval, type); +} + +GMat inRange(const GMat& src, const GScalar& threshLow, const GScalar& threshUp) +{ + return core::GInRange::on(src, threshLow, threshUp); +} + +std::tuple split3(const GMat& src) +{ + return core::GSplit3::on(src); +} + +std::tuple split4(const GMat& src) +{ + return core::GSplit4::on(src); +} + +GMat merge3(const GMat& src1, const GMat& src2, const GMat& src3) +{ + return core::GMerge3::on(src1, src2, src3); +} + +GMat merge4(const GMat& src1, const GMat& src2, const GMat& src3, const GMat& src4) +{ + return core::GMerge4::on(src1, src2, src3, src4); +} + +GMat resize(const GMat& src, const Size& dsize, double fx, double fy, int interpolation) +{ + return core::GResize::on(src, dsize, fx, fy, interpolation); +} + +GMat remap(const GMat& src, const Mat& map1, const Mat& map2, + int interpolation, int borderMode, + const Scalar& borderValue) +{ + return core::GRemap::on(src, map1, map2, interpolation, borderMode, borderValue); +} + +GMat flip(const GMat& src, int flipCode) +{ + return core::GFlip::on(src, flipCode); +} + +GMat crop(const GMat& src, const Rect& rect) +{ + return core::GCrop::on(src, rect); +} + +GMat concatHor(const GMat& src1, const GMat& src2) +{ + return core::GConcatHor::on(src1, src2); +} + +GMat concatHor(const std::vector& v) +{ + GAPI_Assert(v.size() >= 2); + return std::accumulate(v.begin()+1, v.end(), v[0], core::GConcatHor::on); +} + +GMat concatVert(const GMat& src1, const GMat& src2) +{ + return core::GConcatVert::on(src1, src2); +} + +GMat concatVert(const std::vector& v) +{ + GAPI_Assert(v.size() >= 2); + return std::accumulate(v.begin()+1, v.end(), v[0], core::GConcatVert::on); +} + +GMat LUT(const GMat& src, const Mat& lut) +{ + return core::GLUT::on(src, lut); +} + +GMat convertTo(const GMat& m, int rtype, double alpha, double beta) +{ + return core::GConvertTo::on(m, rtype, alpha, beta); +} + +} //namespace gapi +} //namespace cv diff --git a/modules/gapi/src/api/kernels_imgproc.cpp b/modules/gapi/src/api/kernels_imgproc.cpp new file mode 100644 index 0000000000..0cd2b5a865 --- /dev/null +++ b/modules/gapi/src/api/kernels_imgproc.cpp @@ -0,0 +1,142 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/gcall.hpp" +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/imgproc.hpp" + +namespace cv { namespace gapi { + +GMat sepFilter(const GMat& src, int ddepth, const Mat& kernelX, const Mat& kernelY, const Point& anchor, + const Scalar& delta, int borderType, const Scalar& borderVal) +{ + return imgproc::GSepFilter::on(src, ddepth, kernelX, kernelY, anchor, delta, borderType, borderVal); +} + +GMat filter2D(const GMat& src, int ddepth, const Mat& kernel, const Point& anchor, const Scalar& delta, int borderType, + const Scalar& bordVal) +{ + return imgproc::GFilter2D::on(src, ddepth, kernel, anchor, delta, borderType, bordVal); +} + +GMat boxFilter(const GMat& src, int dtype, const Size& ksize, const Point& anchor, + bool normalize, int borderType, const Scalar& bordVal) +{ + return imgproc::GBoxFilter::on(src, dtype, ksize, anchor, normalize, borderType, bordVal); +} + +GMat blur(const GMat& src, const Size& ksize, const Point& anchor, + int borderType, const Scalar& bordVal) +{ + return imgproc::GBlur::on(src, ksize, anchor, borderType, bordVal); +} + +GMat gaussianBlur(const GMat& src, const Size& ksize, double sigmaX, double sigmaY, + int borderType, const Scalar& bordVal) +{ + return imgproc::GGaussBlur::on(src, ksize, sigmaX, sigmaY, borderType, bordVal); +} + +GMat medianBlur(const GMat& src, int ksize) +{ + return imgproc::GMedianBlur::on(src, ksize); +} + +GMat erode(const GMat& src, const Mat& kernel, const Point& anchor, int iterations, + int borderType, const Scalar& borderValue ) +{ + return imgproc::GErode::on(src, kernel, anchor, iterations, borderType, borderValue); +} + +GMat erode3x3(const GMat& src, int iterations, + int borderType, const Scalar& borderValue ) +{ + return erode(src, cv::Mat(), cv::Point(-1, -1), iterations, borderType, borderValue); +} + +GMat dilate(const GMat& src, const Mat& kernel, const Point& anchor, int iterations, + int borderType, const Scalar& borderValue) +{ + return imgproc::GDilate::on(src, kernel, anchor, iterations, borderType, borderValue); +} + +GMat dilate3x3(const GMat& src, int iterations, + int borderType, const Scalar& borderValue) +{ + return dilate(src, cv::Mat(), cv::Point(-1,-1), iterations, borderType, borderValue); +} + +GMat sobel(const GMat& src, int ddepth, int dx, int dy, int ksize, + double scale, double delta, + int borderType, const Scalar& bordVal) +{ + return imgproc::GSobel::on(src, ddepth, dx, dy, ksize, scale, delta, borderType, bordVal); +} + +GMat equalizeHist(const GMat& src) +{ + return imgproc::GEqHist::on(src); +} + +GMat Canny(const GMat& src, double thr1, double thr2, int apertureSize, bool l2gradient) +{ + return imgproc::GCanny::on(src, thr1, thr2, apertureSize, l2gradient); +} + +GMat RGB2Gray(const GMat& src) +{ + return imgproc::GRGB2Gray::on(src); +} + +GMat RGB2Gray(const GMat& src, float rY, float gY, float bY) +{ + return imgproc::GRGB2GrayCustom::on(src, rY, gY, bY); +} + +GMat BGR2Gray(const GMat& src) +{ + return imgproc::GBGR2Gray::on(src); +} + +GMat RGB2YUV(const GMat& src) +{ + return imgproc::GRGB2YUV::on(src); +} + +GMat BGR2LUV(const GMat& src) +{ + return imgproc::GBGR2LUV::on(src); +} + +GMat LUV2BGR(const GMat& src) +{ + return imgproc::GLUV2BGR::on(src); +} + +GMat BGR2YUV(const GMat& src) +{ + return imgproc::GBGR2YUV::on(src); +} + +GMat YUV2BGR(const GMat& src) +{ + return imgproc::GYUV2BGR::on(src); +} + +GMat YUV2RGB(const GMat& src) +{ + return imgproc::GYUV2RGB::on(src); +} + +GMat RGB2Lab(const GMat& src) +{ + return imgproc::GRGB2Lab::on(src); +} + +} //namespace gapi +} //namespace cv diff --git a/modules/gapi/src/api/operators.cpp b/modules/gapi/src/api/operators.cpp new file mode 100644 index 0000000000..78a883e771 --- /dev/null +++ b/modules/gapi/src/api/operators.cpp @@ -0,0 +1,211 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/imgproc.hpp" +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/gscalar.hpp" +#include "opencv2/gapi/operators.hpp" + +cv::GMat operator+(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::add(lhs, rhs); +} + +cv::GMat operator+(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::addC(lhs, rhs); +} + +cv::GMat operator+(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::addC(rhs, lhs); +} + +cv::GMat operator-(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::sub(lhs, rhs); +} + +cv::GMat operator-(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::subC(lhs, rhs); +} + +cv::GMat operator-(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::subRC(lhs, rhs); +} + +cv::GMat operator*(const cv::GMat& lhs, float rhs) +{ + return cv::gapi::mulC(lhs, static_cast(rhs)); +} + +cv::GMat operator*(float lhs, const cv::GMat& rhs) +{ + return cv::gapi::mulC(rhs, static_cast(lhs)); +} + +cv::GMat operator*(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::mulC(lhs, rhs); +} + +cv::GMat operator*(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::mulC(rhs, lhs); +} + +cv::GMat operator/(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::divC(lhs, rhs, 1.0); +} + +cv::GMat operator/(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::div(lhs, rhs, 1.0); +} + +cv::GMat operator/(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::divRC(lhs, rhs, 1.0); +} + +cv::GMat operator&(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::bitwise_and(lhs, rhs); +} + +cv::GMat operator&(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::bitwise_and(lhs, rhs); +} + +cv::GMat operator&(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::bitwise_and(rhs, lhs); +} + +cv::GMat operator|(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::bitwise_or(lhs, rhs); +} + +cv::GMat operator|(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::bitwise_or(lhs, rhs); +} + +cv::GMat operator|(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::bitwise_or(rhs, lhs); +} + +cv::GMat operator^(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::bitwise_xor(lhs, rhs); +} + +cv::GMat operator^(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::bitwise_xor(lhs, rhs); +} + +cv::GMat operator^(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::bitwise_xor(rhs, lhs); +} + +cv::GMat operator~(const cv::GMat& lhs) +{ + return cv::gapi::bitwise_not(lhs); +} + +cv::GMat operator>(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpGT(lhs, rhs); +} + +cv::GMat operator>=(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpGE(lhs, rhs); +} + +cv::GMat operator<(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpLT(lhs, rhs); +} + +cv::GMat operator<=(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpLE(lhs, rhs); +} + +cv::GMat operator==(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpEQ(lhs, rhs); +} + +cv::GMat operator!=(const cv::GMat& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpNE(lhs, rhs); +} + +cv::GMat operator>(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::cmpGT(lhs, rhs); +} + +cv::GMat operator>=(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::cmpGE(lhs, rhs); +} + +cv::GMat operator<(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::cmpLT(lhs, rhs); +} + +cv::GMat operator<=(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::cmpLE(lhs, rhs); +} + +cv::GMat operator==(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::cmpEQ(lhs, rhs); +} + +cv::GMat operator!=(const cv::GMat& lhs, const cv::GScalar& rhs) +{ + return cv::gapi::cmpNE(lhs, rhs); +} + +cv::GMat operator>(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpLT(rhs, lhs); +} +cv::GMat operator>=(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpLE(rhs, lhs); +} +cv::GMat operator<(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpGT(rhs, lhs); +} +cv::GMat operator<=(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpGE(rhs, lhs); +} +cv::GMat operator==(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpEQ(rhs, lhs); +} +cv::GMat operator!=(const cv::GScalar& lhs, const cv::GMat& rhs) +{ + return cv::gapi::cmpNE(rhs, lhs); +} diff --git a/modules/gapi/src/backends/README.md b/modules/gapi/src/backends/README.md new file mode 100644 index 0000000000..3aeeb1ecee --- /dev/null +++ b/modules/gapi/src/backends/README.md @@ -0,0 +1,2 @@ +This directory contains various G-API backends, which provide scheduling +logic and kernel implementations for specific targets. \ No newline at end of file diff --git a/modules/gapi/src/backends/common/gbackend.hpp b/modules/gapi/src/backends/common/gbackend.hpp new file mode 100644 index 0000000000..82dcf344b2 --- /dev/null +++ b/modules/gapi/src/backends/common/gbackend.hpp @@ -0,0 +1,103 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GBACKEND_HPP +#define OPENCV_GAPI_GBACKEND_HPP + +#include +#include + +#include + +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/own/mat.hpp" + +#include "opencv2/gapi/util/optional.hpp" +#include "opencv2/gapi/own/scalar.hpp" + +#include "compiler/gmodel.hpp" + +namespace cv { +namespace gimpl { + + // Forward declarations + struct Data; + struct RcDesc; + +namespace magazine { + template struct Class + { + template using MapT = std::unordered_map; + template MapT& slot() + { + return std::get::value>(slots); + } + template const MapT& slot() const + { + return std::get::value>(slots); + } + private: + std::tuple...> slots; + }; + +} // namespace magazine + +using Mag = magazine::Class; + +namespace magazine +{ + void bindInArg (Mag& mag, const RcDesc &rc, const GRunArg &arg); + void bindOutArg(Mag& mag, const RcDesc &rc, const GRunArgP &arg); + + void resetInternalData(Mag& mag, const Data &d); + cv::GRunArg getArg (const Mag& mag, const RcDesc &ref); + cv::GRunArgP getObjPtr ( Mag& mag, const RcDesc &rc); + void writeBack (const Mag& mag, const RcDesc &rc, GRunArgP &g_arg); +} // namespace magazine + +namespace detail +{ +template struct magazine +{ + template using MapT = std::unordered_map; + template MapT& slot() + { + return std::get::value>(slots); + } + template const MapT& slot() const + { + return std::get::value>(slots); + } +private: + std::tuple...> slots; +}; +} // namespace detail + +struct GRuntimeArgs +{ + GRunArgs inObjs; + GRunArgsP outObjs; +}; + +template +inline cv::util::optional getCompileArg(const cv::GCompileArgs &args) +{ + for (auto &compile_arg : args) + { + if (compile_arg.tag == cv::detail::CompileArgTag::tag()) + { + return cv::util::optional(compile_arg.get()); + } + } + return cv::util::optional(); +} + + + +}} // cv::gimpl + +#endif // OPENCV_GAPI_GBACKEND_HPP diff --git a/modules/gapi/src/backends/common/gcompoundbackend.cpp b/modules/gapi/src/backends/common/gcompoundbackend.cpp new file mode 100644 index 0000000000..25fa8a2ace --- /dev/null +++ b/modules/gapi/src/backends/common/gcompoundbackend.cpp @@ -0,0 +1,18 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend() + +#include "api/gbackend_priv.hpp" +#include "compiler/gislandmodel.hpp" // GIslandExecutable + +cv::gapi::GBackend cv::gapi::compound::backend() +{ + // A pointer to dummy Priv is used to uniquely identify backends + static cv::gapi::GBackend this_backend(std::make_shared()); + return this_backend; +} diff --git a/modules/gapi/src/backends/common/gcompoundkernel.cpp b/modules/gapi/src/backends/common/gcompoundkernel.cpp new file mode 100644 index 0000000000..fd9609e4db --- /dev/null +++ b/modules/gapi/src/backends/common/gcompoundkernel.cpp @@ -0,0 +1,45 @@ +// 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) 2018 Intel Corporation + + +#include // util::indexed +#include "opencv2/gapi/gcompoundkernel.hpp" +#include "compiler/gobjref.hpp" + +// FIXME move to backends + +cv::detail::GCompoundContext::GCompoundContext(const cv::GArgs& in_args) +{ + m_args.resize(in_args.size()); + for (const auto& it : ade::util::indexed(in_args)) + { + const auto& i = ade::util::index(it); + const auto& in_arg = ade::util::value(it); + + if (in_arg.kind != cv::detail::ArgKind::GOBJREF) + { + m_args[i] = in_arg; + } + else + { + const cv::gimpl::RcDesc &ref = in_arg.get(); + switch (ref.shape) + { + case GShape::GMAT : m_args[i] = GArg(GMat()); break; + case GShape::GSCALAR: m_args[i] = GArg(GScalar()); break; + case GShape::GARRAY :/* do nothing - as handled in a special way, see gcompoundkernel.hpp for details */; break; + default: GAPI_Assert(false); + } + } + } + GAPI_Assert(m_args.size() == in_args.size()); +} + +cv::detail::GCompoundKernel::GCompoundKernel(const F& f) : m_f(f) +{ +} + +void cv::detail::GCompoundKernel::apply(cv::detail::GCompoundContext& ctx) { m_f(ctx); } diff --git a/modules/gapi/src/backends/cpu/gcpubackend.cpp b/modules/gapi/src/backends/cpu/gcpubackend.cpp new file mode 100644 index 0000000000..42e580b950 --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpubackend.cpp @@ -0,0 +1,227 @@ +// 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) 2018 Intel Corporation + + +#include +#include + +#include + +#include +#include +#include + +#include + +#include "opencv2/gapi/gcommon.hpp" +#include "opencv2/gapi/util/any.hpp" +#include "opencv2/gapi/gtype_traits.hpp" + +#include "compiler/gobjref.hpp" +#include "compiler/gmodel.hpp" + +#include "backends/cpu/gcpubackend.hpp" +#include "backends/cpu/gcpuimgproc.hpp" +#include "backends/cpu/gcpucore.hpp" + +#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK! + +// FIXME: Is there a way to take a typed graph (our GModel), +// and create a new typed graph _ATOP_ of that (by extending with a couple of +// new types?). +// Alternatively, is there a way to compose types graphs? +// +// If not, we need to introduce that! +using GCPUModel = ade::TypedGraph + < cv::gimpl::Unit + , cv::gimpl::Protocol + >; + +// FIXME: Same issue with Typed and ConstTyped +using GConstGCPUModel = ade::ConstTypedGraph + < cv::gimpl::Unit + , cv::gimpl::Protocol + >; + +namespace +{ + class GCPUBackendImpl final: public cv::gapi::GBackend::Priv + { + virtual void unpackKernel(ade::Graph &graph, + const ade::NodeHandle &op_node, + const cv::GKernelImpl &impl) override + { + GCPUModel gm(graph); + auto cpu_impl = cv::util::any_cast(impl.opaque); + gm.metadata(op_node).set(cv::gimpl::Unit{cpu_impl}); + } + + virtual EPtr compile(const ade::Graph &graph, + const cv::GCompileArgs &, + const std::vector &nodes) const override + { + return EPtr{new cv::gimpl::GCPUExecutable(graph, nodes)}; + } + }; +} + +cv::gapi::GBackend cv::gapi::cpu::backend() +{ + static cv::gapi::GBackend this_backend(std::make_shared()); + return this_backend; +} + +// GCPUExcecutable implementation ////////////////////////////////////////////// +cv::gimpl::GCPUExecutable::GCPUExecutable(const ade::Graph &g, + const std::vector &nodes) + : m_g(g), m_gm(m_g) +{ + // Convert list of operations (which is topologically sorted already) + // into an execution script. + for (auto &nh : nodes) + { + switch (m_gm.metadata(nh).get().t) + { + case NodeType::OP: m_script.push_back({nh, GModel::collectOutputMeta(m_gm, nh)}); break; + case NodeType::DATA: + { + m_dataNodes.push_back(nh); + const auto &desc = m_gm.metadata(nh).get(); + if (desc.storage == Data::Storage::CONST) + { + auto rc = RcDesc{desc.rc, desc.shape, desc.ctor}; + magazine::bindInArg(m_res, rc, m_gm.metadata(nh).get().arg); + } + //preallocate internal Mats in advance + if (desc.storage == Data::Storage::INTERNAL && desc.shape == GShape::GMAT) + { + const auto mat_desc = util::get(desc.meta); + const auto type = CV_MAKETYPE(mat_desc.depth, mat_desc.chan); + m_res.slot()[desc.rc].create(mat_desc.size, type); + } + break; + } + default: util::throw_error(std::logic_error("Unsupported NodeType type")); + } + } +} + +// FIXME: Document what it does +cv::GArg cv::gimpl::GCPUExecutable::packArg(const GArg &arg) +{ + // No API placeholders allowed at this point + // FIXME: this check has to be done somewhere in compilation stage. + GAPI_Assert( arg.kind != cv::detail::ArgKind::GMAT + && arg.kind != cv::detail::ArgKind::GSCALAR + && arg.kind != cv::detail::ArgKind::GARRAY); + + if (arg.kind != cv::detail::ArgKind::GOBJREF) + { + // All other cases - pass as-is, with no transformations to GArg contents. + return arg; + } + GAPI_Assert(arg.kind == cv::detail::ArgKind::GOBJREF); + + // Wrap associated CPU object (either host or an internal one) + // FIXME: object can be moved out!!! GExecutor faced that. + const cv::gimpl::RcDesc &ref = arg.get(); + switch (ref.shape) + { + case GShape::GMAT: return GArg(m_res.slot() [ref.id]); + case GShape::GSCALAR: return GArg(m_res.slot()[ref.id]); + // Note: .at() is intentional for GArray as object MUST be already there + // (and constructed by either bindIn/Out or resetInternal) + case GShape::GARRAY: return GArg(m_res.slot().at(ref.id)); + default: + util::throw_error(std::logic_error("Unsupported GShape type")); + break; + } +} + +void cv::gimpl::GCPUExecutable::run(std::vector &&input_objs, + std::vector &&output_objs) +{ + // Update resources with run-time information - what this Island + // has received from user (or from another Island, or mix...) + // FIXME: Check input/output objects against GIsland protocol + + for (auto& it : input_objs) magazine::bindInArg (m_res, it.first, it.second); + for (auto& it : output_objs) magazine::bindOutArg(m_res, it.first, it.second); + + // Initialize (reset) internal data nodes with user structures + // before processing a frame (no need to do it for external data structures) + GModel::ConstGraph gm(m_g); + for (auto nh : m_dataNodes) + { + const auto &desc = gm.metadata(nh).get(); + + if ( desc.storage == Data::Storage::INTERNAL + && !util::holds_alternative(desc.ctor)) + { + // FIXME: Note that compile-time constant data objects (like + // a value-initialized GArray) also satisfy this condition + // and should be excluded, but now we just don't support it + magazine::resetInternalData(m_res, desc); + } + } + + // OpenCV backend execution is not a rocket science at all. + // Simply invoke our kernels in the proper order. + GConstGCPUModel gcm(m_g); + for (auto &op_info : m_script) + { + const auto &op = m_gm.metadata(op_info.nh).get(); + + // Obtain our real execution unit + // TODO: Should kernels be copyable? + GCPUKernel k = gcm.metadata(op_info.nh).get().k; + + // Initialize kernel's execution context: + // - Input parameters + GCPUContext context; + context.m_args.reserve(op.args.size()); + + using namespace std::placeholders; + ade::util::transform(op.args, + std::back_inserter(context.m_args), + std::bind(&GCPUExecutable::packArg, this, _1)); + + // - Output parameters. + // FIXME: pre-allocate internal Mats, etc, according to the known meta + for (const auto &out_it : ade::util::indexed(op.outs)) + { + // FIXME: Can the same GArg type resolution mechanism be reused here? + const auto out_port = ade::util::index(out_it); + const auto out_desc = ade::util::value(out_it); + context.m_results[out_port] = magazine::getObjPtr(m_res, out_desc); + } + + // Now trigger the executable unit + k.apply(context); + + //As Kernels are forbidden to allocate memory for (Mat) outputs, + //this code seems redundant, at least for Mats + //FIXME: unify with cv::detail::ensure_out_mats_not_reallocated + for (const auto &out_it : ade::util::indexed(op_info.expected_out_metas)) + { + const auto out_index = ade::util::index(out_it); + const auto expected_meta = ade::util::value(out_it); + const auto out_meta = descr_of(context.m_results[out_index]); + + if (expected_meta != out_meta) + { + util::throw_error + (std::logic_error + ("Output meta doesn't " + "coincide with the generated meta\n" + "Expected: " + ade::util::to_string(expected_meta) + "\n" + "Actual : " + ade::util::to_string(out_meta))); + } + } + } // for(m_script) + + for (auto &it : output_objs) magazine::writeBack(m_res, it.first, it.second); +} diff --git a/modules/gapi/src/backends/cpu/gcpubackend.hpp b/modules/gapi/src/backends/cpu/gcpubackend.hpp new file mode 100644 index 0000000000..c5ba79e09e --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpubackend.hpp @@ -0,0 +1,63 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCPUBACKEND_HPP +#define OPENCV_GAPI_GCPUBACKEND_HPP + +#include // map +#include // unordered_map +#include // tuple +#include // type_list_index + +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gproto.hpp" +#include "opencv2/gapi/cpu/gcpukernel.hpp" + + +#include "api/gapi_priv.hpp" +#include "backends/common/gbackend.hpp" +#include "compiler/gislandmodel.hpp" + +namespace cv { namespace gimpl { + +struct Unit +{ + static const char *name() { return "HostKernel"; } + GCPUKernel k; +}; + +class GCPUExecutable final: public GIslandExecutable +{ + const ade::Graph &m_g; + GModel::ConstGraph m_gm; + + struct OperationInfo + { + ade::NodeHandle nh; + GMetaArgs expected_out_metas; + }; + + // Execution script, currently absolutely naive + std::vector m_script; + // List of all resources in graph (both internal and external) + std::vector m_dataNodes; + + // Actual data of all resources in graph (both internal and external) + Mag m_res; + GArg packArg(const GArg &arg); + +public: + GCPUExecutable(const ade::Graph &graph, + const std::vector &nodes); + + virtual void run(std::vector &&input_objs, + std::vector &&output_objs) override; +}; + +}} + +#endif // OPENCV_GAPI_GBACKEND_HPP diff --git a/modules/gapi/src/backends/cpu/gcpucore.cpp b/modules/gapi/src/backends/cpu/gcpucore.cpp new file mode 100644 index 0000000000..39cde424b1 --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpucore.cpp @@ -0,0 +1,577 @@ +// 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) 2018 Intel Corporation + + +#include "precomp.hpp" + +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/cpu/core.hpp" +#include "backends/cpu/gcpucore.hpp" + +GAPI_OCV_KERNEL(GCPUAdd, cv::gapi::core::GAdd) +{ + static void run(const cv::Mat& a, const cv::Mat& b, int dtype, cv::Mat& out) + { + cv::add(a, b, out, cv::noArray(), dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUAddC, cv::gapi::core::GAddC) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, int dtype, cv::Mat& out) + { + cv::add(a, b, out, cv::noArray(), dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUSub, cv::gapi::core::GSub) +{ + static void run(const cv::Mat& a, const cv::Mat& b, int dtype, cv::Mat& out) + { + cv::subtract(a, b, out, cv::noArray(), dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUSubC, cv::gapi::core::GSubC) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, int dtype, cv::Mat& out) + { + cv::subtract(a, b, out, cv::noArray(), dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUSubRC, cv::gapi::core::GSubRC) +{ + static void run(const cv::Scalar& a, const cv::Mat& b, int dtype, cv::Mat& out) + { + cv::subtract(a, b, out, cv::noArray(), dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUMul, cv::gapi::core::GMul) +{ + static void run(const cv::Mat& a, const cv::Mat& b, double scale, int dtype, cv::Mat& out) + { + cv::multiply(a, b, out, scale, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUMulCOld, cv::gapi::core::GMulCOld) +{ + static void run(const cv::Mat& a, double b, int dtype, cv::Mat& out) + { + cv::multiply(a, b, out, 1, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUMulC, cv::gapi::core::GMulC) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, int dtype, cv::Mat& out) + { + cv::multiply(a, b, out, 1, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUDiv, cv::gapi::core::GDiv) +{ + static void run(const cv::Mat& a, const cv::Mat& b, double scale, int dtype, cv::Mat& out) + { + cv::divide(a, b, out, scale, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUDivC, cv::gapi::core::GDivC) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, double scale, int dtype, cv::Mat& out) + { + cv::divide(a, b, out, scale, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUDivRC, cv::gapi::core::GDivRC) +{ + static void run(const cv::Scalar& a, const cv::Mat& b, double scale, int dtype, cv::Mat& out) + { + cv::divide(a, b, out, scale, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUMask, cv::gapi::core::GMask) +{ + static void run(const cv::Mat& in, const cv::Mat& mask, cv::Mat& out) + { + out = cv::Mat::zeros(in.size(), in.type()); + in.copyTo(out, mask); + } +}; + +GAPI_OCV_KERNEL(GCPUMean, cv::gapi::core::GMean) +{ + static void run(const cv::Mat& in, cv::Scalar& out) + { + out = cv::mean(in); + } +}; + +GAPI_OCV_KERNEL(GCPUPolarToCart, cv::gapi::core::GPolarToCart) +{ + static void run(const cv::Mat& magn, const cv::Mat& angle, bool angleInDegrees, cv::Mat& outx, cv::Mat& outy) + { + cv::polarToCart(magn, angle, outx, outy, angleInDegrees); + } +}; + +GAPI_OCV_KERNEL(GCPUCartToPolar, cv::gapi::core::GCartToPolar) +{ + static void run(const cv::Mat& x, const cv::Mat& y, bool angleInDegrees, cv::Mat& outmagn, cv::Mat& outangle) + { + cv::cartToPolar(x, y, outmagn, outangle, angleInDegrees); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpGT, cv::gapi::core::GCmpGT) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_GT); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpGE, cv::gapi::core::GCmpGE) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_GE); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpLE, cv::gapi::core::GCmpLE) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_LE); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpLT, cv::gapi::core::GCmpLT) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_LT); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpEQ, cv::gapi::core::GCmpEQ) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_EQ); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpNE, cv::gapi::core::GCmpNE) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_NE); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpGTScalar, cv::gapi::core::GCmpGTScalar) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_GT); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpGEScalar, cv::gapi::core::GCmpGEScalar) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_GE); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpLEScalar, cv::gapi::core::GCmpLEScalar) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_LE); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpLTScalar, cv::gapi::core::GCmpLTScalar) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_LT); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpEQScalar, cv::gapi::core::GCmpEQScalar) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_EQ); + } +}; + +GAPI_OCV_KERNEL(GCPUCmpNEScalar, cv::gapi::core::GCmpNEScalar) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::compare(a, b, out, cv::CMP_NE); + } +}; + +GAPI_OCV_KERNEL(GCPUAnd, cv::gapi::core::GAnd) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::bitwise_and(a, b, out); + } +}; + +GAPI_OCV_KERNEL(GCPUAndS, cv::gapi::core::GAndS) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::bitwise_and(a, b, out); + } +}; + +GAPI_OCV_KERNEL(GCPUOr, cv::gapi::core::GOr) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::bitwise_or(a, b, out); + } +}; + +GAPI_OCV_KERNEL(GCPUOrS, cv::gapi::core::GOrS) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::bitwise_or(a, b, out); + } +}; + +GAPI_OCV_KERNEL(GCPUXor, cv::gapi::core::GXor) +{ + static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) + { + cv::bitwise_xor(a, b, out); + } +}; + +GAPI_OCV_KERNEL(GCPUXorS, cv::gapi::core::GXorS) +{ + static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out) + { + cv::bitwise_xor(a, b, out); + } +}; + +GAPI_OCV_KERNEL(GCPUNot, cv::gapi::core::GNot) +{ + static void run(const cv::Mat& a, cv::Mat& out) + { + cv::bitwise_not(a, out); + } +}; + +GAPI_OCV_KERNEL(GCPUSelect, cv::gapi::core::GSelect) +{ + static void run(const cv::Mat& src1, const cv::Mat& src2, const cv::Mat& mask, cv::Mat& out) + { + src2.copyTo(out); + src1.copyTo(out, mask); + } +}; + +GAPI_OCV_KERNEL(GCPUMin, cv::gapi::core::GMin) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out) + { + out = cv::min(in1, in2); + } +}; + +GAPI_OCV_KERNEL(GCPUMax, cv::gapi::core::GMax) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out) + { + out = cv::max(in1, in2); + } +}; + +GAPI_OCV_KERNEL(GCPUAbsDiff, cv::gapi::core::GAbsDiff) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out) + { + cv::absdiff(in1, in2, out); + } +}; + +GAPI_OCV_KERNEL(GCPUAbsDiffC, cv::gapi::core::GAbsDiffC) +{ + static void run(const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out) + { + cv::absdiff(in1, in2, out); + } +}; + +GAPI_OCV_KERNEL(GCPUSum, cv::gapi::core::GSum) +{ + static void run(const cv::Mat& in, cv::Scalar& out) + { + out = cv::sum(in); + } +}; + +GAPI_OCV_KERNEL(GCPUAddW, cv::gapi::core::GAddW) +{ + static void run(const cv::Mat& in1, double alpha, const cv::Mat& in2, double beta, double gamma, int dtype, cv::Mat& out) + { + cv::addWeighted(in1, alpha, in2, beta, gamma, out, dtype); + } +}; + +GAPI_OCV_KERNEL(GCPUNormL1, cv::gapi::core::GNormL1) +{ + static void run(const cv::Mat& in, cv::Scalar& out) + { + out = cv::norm(in, cv::NORM_L1); + } +}; + +GAPI_OCV_KERNEL(GCPUNormL2, cv::gapi::core::GNormL2) +{ + static void run(const cv::Mat& in, cv::Scalar& out) + { + out = cv::norm(in, cv::NORM_L2); + } +}; + +GAPI_OCV_KERNEL(GCPUNormInf, cv::gapi::core::GNormInf) +{ + static void run(const cv::Mat& in, cv::Scalar& out) + { + out = cv::norm(in, cv::NORM_INF); + } +}; + +GAPI_OCV_KERNEL(GCPUIntegral, cv::gapi::core::GIntegral) +{ + static void run(const cv::Mat& in, int sdepth, int sqdepth, cv::Mat& out, cv::Mat& outSq) + { + cv::integral(in, out, outSq, sdepth, sqdepth); + } +}; + +GAPI_OCV_KERNEL(GCPUThreshold, cv::gapi::core::GThreshold) +{ + static void run(const cv::Mat& in, const cv::Scalar& a, const cv::Scalar& b, int type, cv::Mat& out) + { + cv::threshold(in, out, a.val[0], b.val[0], type); + } +}; + +GAPI_OCV_KERNEL(GCPUThresholdOT, cv::gapi::core::GThresholdOT) +{ + static void run(const cv::Mat& in, const cv::Scalar& b, int type, cv::Mat& out, cv::Scalar& outScalar) + { + outScalar = cv::threshold(in, out, b.val[0], b.val[0], type); + } +}; + + +GAPI_OCV_KERNEL(GCPUInRange, cv::gapi::core::GInRange) +{ + static void run(const cv::Mat& in, const cv::Scalar& low, const cv::Scalar& up, cv::Mat& out) + { + cv::inRange(in, low, up, out); + } +}; + +GAPI_OCV_KERNEL(GCPUSplit3, cv::gapi::core::GSplit3) +{ + static void run(const cv::Mat& in, cv::Mat &m1, cv::Mat &m2, cv::Mat &m3) + { + std::vector outMats = {m1, m2, m3}; + cv::split(in, outMats); + + // Write back FIXME: Write a helper or avoid this nonsence completely! + m1 = outMats[0]; + m2 = outMats[1]; + m3 = outMats[2]; + } +}; + +GAPI_OCV_KERNEL(GCPUSplit4, cv::gapi::core::GSplit4) +{ + static void run(const cv::Mat& in, cv::Mat &m1, cv::Mat &m2, cv::Mat &m3, cv::Mat &m4) + { + std::vector outMats = {m1, m2, m3, m4}; + cv::split(in, outMats); + + // Write back FIXME: Write a helper or avoid this nonsence completely! + m1 = outMats[0]; + m2 = outMats[1]; + m3 = outMats[2]; + m4 = outMats[3]; + } +}; + +GAPI_OCV_KERNEL(GCPUMerge3, cv::gapi::core::GMerge3) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, const cv::Mat& in3, cv::Mat &out) + { + std::vector inMats = {in1, in2, in3}; + cv::merge(inMats, out); + } +}; + +GAPI_OCV_KERNEL(GCPUMerge4, cv::gapi::core::GMerge4) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, const cv::Mat& in3, const cv::Mat& in4, cv::Mat &out) + { + std::vector inMats = {in1, in2, in3, in4}; + cv::merge(inMats, out); + } +}; + +GAPI_OCV_KERNEL(GCPUResize, cv::gapi::core::GResize) +{ + static void run(const cv::Mat& in, cv::Size sz, double fx, double fy, int interp, cv::Mat &out) + { + cv::resize(in, out, sz, fx, fy, interp); + } +}; + +GAPI_OCV_KERNEL(GCPURemap, cv::gapi::core::GRemap) +{ + static void run(const cv::Mat& in, const cv::Mat& x, const cv::Mat& y, int a, int b, cv::Scalar s, cv::Mat& out) + { + cv::remap(in, out, x, y, a, b, s); + } +}; + +GAPI_OCV_KERNEL(GCPUFlip, cv::gapi::core::GFlip) +{ + static void run(const cv::Mat& in, int code, cv::Mat& out) + { + cv::flip(in, out, code); + } +}; + +GAPI_OCV_KERNEL(GCPUCrop, cv::gapi::core::GCrop) +{ + static void run(const cv::Mat& in, cv::Rect rect, cv::Mat& out) + { + cv::Mat(in, rect).copyTo(out); + } +}; + +GAPI_OCV_KERNEL(GCPUConcatHor, cv::gapi::core::GConcatHor) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out) + { + cv::hconcat(in1, in2, out); + } +}; + +GAPI_OCV_KERNEL(GCPUConcatVert, cv::gapi::core::GConcatVert) +{ + static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out) + { + cv::vconcat(in1, in2, out); + } +}; + +GAPI_OCV_KERNEL(GCPULUT, cv::gapi::core::GLUT) +{ + static void run(const cv::Mat& in, const cv::Mat& lut, cv::Mat& out) + { + cv::LUT(in, lut, out); + } +}; + +GAPI_OCV_KERNEL(GCPUConvertTo, cv::gapi::core::GConvertTo) +{ + static void run(const cv::Mat& in, int rtype, double alpha, double beta, cv::Mat& out) + { + in.convertTo(out, rtype, alpha, beta); + } +}; + +cv::gapi::GKernelPackage cv::gapi::core::cpu::kernels() +{ + static auto pkg = cv::gapi::kernels + < GCPUAdd + , GCPUAddC + , GCPUSub + , GCPUSubC + , GCPUSubRC + , GCPUMul + , GCPUMulC + , GCPUMulCOld + , GCPUDiv + , GCPUDivC + , GCPUDivRC + , GCPUMean + , GCPUMask + , GCPUPolarToCart + , GCPUCartToPolar + , GCPUCmpGT + , GCPUCmpGE + , GCPUCmpLE + , GCPUCmpLT + , GCPUCmpEQ + , GCPUCmpNE + , GCPUCmpGTScalar + , GCPUCmpGEScalar + , GCPUCmpLEScalar + , GCPUCmpLTScalar + , GCPUCmpEQScalar + , GCPUCmpNEScalar + , GCPUAnd + , GCPUAndS + , GCPUOr + , GCPUOrS + , GCPUXor + , GCPUXorS + , GCPUNot + , GCPUSelect + , GCPUMin + , GCPUMax + , GCPUAbsDiff + , GCPUAbsDiffC + , GCPUSum + , GCPUAddW + , GCPUNormL1 + , GCPUNormL2 + , GCPUNormInf + , GCPUIntegral + , GCPUThreshold + , GCPUThresholdOT + , GCPUInRange + , GCPUSplit3 + , GCPUSplit4 + , GCPUResize + , GCPUMerge3 + , GCPUMerge4 + , GCPURemap + , GCPUFlip + , GCPUCrop + , GCPUConcatHor + , GCPUConcatVert + , GCPULUT + , GCPUConvertTo + >(); + return pkg; +} diff --git a/modules/gapi/src/backends/cpu/gcpucore.hpp b/modules/gapi/src/backends/cpu/gcpucore.hpp new file mode 100644 index 0000000000..77e9e82a00 --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpucore.hpp @@ -0,0 +1,24 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCPUCORE_HPP +#define OPENCV_GAPI_GCPUCORE_HPP + +#include +#include + +#include "opencv2/gapi/cpu/gcpukernel.hpp" + +namespace cv { namespace gimpl { + +// NB: This is what a "Kernel Package" from the original Wiki doc should be. +void loadCPUCore(std::map &kmap); + +} +} + +#endif // OPENCV_GAPI_GCPUCORE_HPP diff --git a/modules/gapi/src/backends/cpu/gcpuimgproc.cpp b/modules/gapi/src/backends/cpu/gcpuimgproc.cpp new file mode 100644 index 0000000000..d14584bfac --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpuimgproc.cpp @@ -0,0 +1,273 @@ +// 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) 2018 Intel Corporation + + +#include "precomp.hpp" + +#include "opencv2/gapi/imgproc.hpp" +#include "opencv2/gapi/cpu/imgproc.hpp" +#include "backends/cpu/gcpuimgproc.hpp" + +GAPI_OCV_KERNEL(GCPUSepFilter, cv::gapi::imgproc::GSepFilter) +{ + static void run(const cv::Mat& in, int ddepth, const cv::Mat& kernX, const cv::Mat& kernY, const cv::Point& anchor, const cv::Scalar& delta, + int border, const cv::Scalar& bordVal, cv::Mat &out) + { + if( border == cv::BORDER_CONSTANT ) + { + cv::Mat temp_in; + int width_add = (kernY.cols - 1) / 2; + int height_add = (kernX.rows - 1) / 2; + cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, border, bordVal); + cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows); + cv::sepFilter2D(temp_in(rect), out, ddepth, kernX, kernY, anchor, delta.val[0], border); + } + else + cv::sepFilter2D(in, out, ddepth, kernX, kernY, anchor, delta.val[0], border); + } +}; + +GAPI_OCV_KERNEL(GCPUBoxFilter, cv::gapi::imgproc::GBoxFilter) +{ + static void run(const cv::Mat& in, int ddepth, const cv::Size& ksize, const cv::Point& anchor, bool normalize, int borderType, const cv::Scalar& bordVal, cv::Mat &out) + { + if( borderType == cv::BORDER_CONSTANT ) + { + cv::Mat temp_in; + int width_add = (ksize.width - 1) / 2; + int height_add = (ksize.height - 1) / 2; + cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, borderType, bordVal); + cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows); + cv::boxFilter(temp_in(rect), out, ddepth, ksize, anchor, normalize, borderType); + } + else + cv::boxFilter(in, out, ddepth, ksize, anchor, normalize, borderType); + } +}; + +GAPI_OCV_KERNEL(GCPUBlur, cv::gapi::imgproc::GBlur) +{ + static void run(const cv::Mat& in, const cv::Size& ksize, const cv::Point& anchor, int borderType, const cv::Scalar& bordVal, cv::Mat &out) + { + if( borderType == cv::BORDER_CONSTANT ) + { + cv::Mat temp_in; + int width_add = (ksize.width - 1) / 2; + int height_add = (ksize.height - 1) / 2; + cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, borderType, bordVal); + cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows); + cv::blur(temp_in(rect), out, ksize, anchor, borderType); + } + else + cv::blur(in, out, ksize, anchor, borderType); + } +}; + + +GAPI_OCV_KERNEL(GCPUFilter2D, cv::gapi::imgproc::GFilter2D) +{ + static void run(const cv::Mat& in, int ddepth, const cv::Mat& k, const cv::Point& anchor, const cv::Scalar& delta, int border, + const cv::Scalar& bordVal, cv::Mat &out) + { + if( border == cv::BORDER_CONSTANT ) + { + cv::Mat temp_in; + int width_add = (k.cols - 1) / 2; + int height_add = (k.rows - 1) / 2; + cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, border, bordVal ); + cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows); + cv::filter2D(temp_in(rect), out, ddepth, k, anchor, delta.val[0], border); + } + else + cv::filter2D(in, out, ddepth, k, anchor, delta.val[0], border); + } +}; + +GAPI_OCV_KERNEL(GCPUGaussBlur, cv::gapi::imgproc::GGaussBlur) +{ + static void run(const cv::Mat& in, const cv::Size& ksize, double sigmaX, double sigmaY, int borderType, const cv::Scalar& bordVal, cv::Mat &out) + { + if( borderType == cv::BORDER_CONSTANT ) + { + cv::Mat temp_in; + int width_add = (ksize.width - 1) / 2; + int height_add = (ksize.height - 1) / 2; + cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, borderType, bordVal ); + cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows); + cv::GaussianBlur(temp_in(rect), out, ksize, sigmaX, sigmaY, borderType); + } + else + cv::GaussianBlur(in, out, ksize, sigmaX, sigmaY, borderType); + } +}; + +GAPI_OCV_KERNEL(GCPUMedianBlur, cv::gapi::imgproc::GMedianBlur) +{ + static void run(const cv::Mat& in, int ksize, cv::Mat &out) + { + cv::medianBlur(in, out, ksize); + } +}; + +GAPI_OCV_KERNEL(GCPUErode, cv::gapi::imgproc::GErode) +{ + static void run(const cv::Mat& in, const cv::Mat& kernel, const cv::Point& anchor, int iterations, int borderType, const cv::Scalar& borderValue, cv::Mat &out) + { + cv::erode(in, out, kernel, anchor, iterations, borderType, borderValue); + } +}; + +GAPI_OCV_KERNEL(GCPUDilate, cv::gapi::imgproc::GDilate) +{ + static void run(const cv::Mat& in, const cv::Mat& kernel, const cv::Point& anchor, int iterations, int borderType, const cv::Scalar& borderValue, cv::Mat &out) + { + cv::dilate(in, out, kernel, anchor, iterations, borderType, borderValue); + } +}; + +GAPI_OCV_KERNEL(GCPUSobel, cv::gapi::imgproc::GSobel) +{ + static void run(const cv::Mat& in, int ddepth, int dx, int dy, int ksize, double scale, double delta, int borderType, + const cv::Scalar& bordVal, cv::Mat &out) + { + if( borderType == cv::BORDER_CONSTANT ) + { + cv::Mat temp_in; + int add = (ksize - 1) / 2; + cv::copyMakeBorder(in, temp_in, add, add, add, add, borderType, bordVal ); + cv::Rect rect = cv::Rect(add, add, in.cols, in.rows); + cv::Sobel(temp_in(rect), out, ddepth, dx, dy, ksize, scale, delta, borderType); + } + else + cv::Sobel(in, out, ddepth, dx, dy, ksize, scale, delta, borderType); + } +}; + +GAPI_OCV_KERNEL(GCPUEqualizeHist, cv::gapi::imgproc::GEqHist) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::equalizeHist(in, out); + } +}; + +GAPI_OCV_KERNEL(GCPUCanny, cv::gapi::imgproc::GCanny) +{ + static void run(const cv::Mat& in, double thr1, double thr2, int apSize, bool l2gradient, cv::Mat &out) + { + cv::Canny(in, out, thr1, thr2, apSize, l2gradient); + } +}; + +GAPI_OCV_KERNEL(GCPURGB2YUV, cv::gapi::imgproc::GRGB2YUV) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_RGB2YUV); + } +}; + +GAPI_OCV_KERNEL(GCPUYUV2RGB, cv::gapi::imgproc::GYUV2RGB) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_YUV2RGB); + } +}; + +GAPI_OCV_KERNEL(GCPURGB2Lab, cv::gapi::imgproc::GRGB2Lab) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_RGB2Lab); + } +}; + +GAPI_OCV_KERNEL(GCPUBGR2LUV, cv::gapi::imgproc::GBGR2LUV) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_BGR2Luv); + } +}; + +GAPI_OCV_KERNEL(GCPUBGR2YUV, cv::gapi::imgproc::GBGR2YUV) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_BGR2YUV); + } +}; + +GAPI_OCV_KERNEL(GCPULUV2BGR, cv::gapi::imgproc::GLUV2BGR) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_Luv2BGR); + } +}; + +GAPI_OCV_KERNEL(GCPUYUV2BGR, cv::gapi::imgproc::GYUV2BGR) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_YUV2BGR); + } +}; + +GAPI_OCV_KERNEL(GCPURGB2Gray, cv::gapi::imgproc::GRGB2Gray) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_RGB2GRAY); + } +}; + +GAPI_OCV_KERNEL(GCPUBGR2Gray, cv::gapi::imgproc::GBGR2Gray) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::cvtColor(in, out, cv::COLOR_BGR2GRAY); + } +}; + +GAPI_OCV_KERNEL(GCPURGB2GrayCustom, cv::gapi::imgproc::GRGB2GrayCustom) +{ + static void run(const cv::Mat& in, float rY, float bY, float gY, cv::Mat &out) + { + cv::Mat planes[3]; + cv::split(in, planes); + out = planes[0]*rY + planes[1]*bY + planes[2]*gY; + } +}; + +cv::gapi::GKernelPackage cv::gapi::imgproc::cpu::kernels() +{ + static auto pkg = cv::gapi::kernels + < GCPUFilter2D + , GCPUSepFilter + , GCPUBoxFilter + , GCPUBlur + , GCPUGaussBlur + , GCPUMedianBlur + , GCPUErode + , GCPUDilate + , GCPUSobel + , GCPUCanny + , GCPUEqualizeHist + , GCPURGB2YUV + , GCPUYUV2RGB + , GCPURGB2Lab + , GCPUBGR2LUV + , GCPUBGR2YUV + , GCPUYUV2BGR + , GCPULUV2BGR + , GCPUBGR2Gray + , GCPURGB2Gray + , GCPURGB2GrayCustom + >(); + return pkg; +} diff --git a/modules/gapi/src/backends/cpu/gcpuimgproc.hpp b/modules/gapi/src/backends/cpu/gcpuimgproc.hpp new file mode 100644 index 0000000000..172871a777 --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpuimgproc.hpp @@ -0,0 +1,23 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCPUIMGPROC_HPP +#define OPENCV_GAPI_GCPUIMGPROC_HPP + +#include +#include + +#include "opencv2/gapi/cpu/gcpukernel.hpp" + +namespace cv { namespace gimpl { + +// NB: This is what a "Kernel Package" from the origianl Wiki doc should be. +void loadCPUImgProc(std::map &kmap); + +}} + +#endif // OPENCV_GAPI_GCPUIMGPROC_HPP diff --git a/modules/gapi/src/backends/cpu/gcpukernel.cpp b/modules/gapi/src/backends/cpu/gcpukernel.cpp new file mode 100644 index 0000000000..a672892953 --- /dev/null +++ b/modules/gapi/src/backends/cpu/gcpukernel.cpp @@ -0,0 +1,50 @@ +// 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) 2018 Intel Corporation + + +#include + +#include "opencv2/gapi/cpu/gcpukernel.hpp" + +const cv::gapi::own::Mat& cv::GCPUContext::inMat(int input) +{ + return inArg(input); +} + +cv::gapi::own::Mat& cv::GCPUContext::outMatR(int output) +{ + return *util::get(m_results.at(output)); +} + +const cv::gapi::own::Scalar& cv::GCPUContext::inVal(int input) +{ + return inArg(input); +} + +cv::gapi::own::Scalar& cv::GCPUContext::outValR(int output) +{ + return *util::get(m_results.at(output)); +} + +cv::detail::VectorRef& cv::GCPUContext::outVecRef(int output) +{ + return util::get(m_results.at(output)); +} + +cv::GCPUKernel::GCPUKernel() +{ +} + +cv::GCPUKernel::GCPUKernel(const GCPUKernel::F &f) + : m_f(f) +{ +} + +void cv::GCPUKernel::apply(GCPUContext &ctx) +{ + GAPI_Assert(m_f); + m_f(ctx); +} diff --git a/modules/gapi/src/backends/fluid/gfluidbackend.cpp b/modules/gapi/src/backends/fluid/gfluidbackend.cpp new file mode 100644 index 0000000000..758f84e920 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidbackend.cpp @@ -0,0 +1,1185 @@ +// 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) 2018 Intel Corporation + + +#include +#include +#include // std::fixed, std::setprecision +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "opencv2/gapi/gcommon.hpp" +#include "logger.hpp" + +#include "opencv2/gapi/own/convert.hpp" +#include "opencv2/gapi/gmat.hpp" //for version of descr_of +// PRIVATE STUFF! +#include "compiler/gobjref.hpp" +#include "compiler/gmodel.hpp" + +#include "backends/fluid/gfluidbuffer_priv.hpp" +#include "backends/fluid/gfluidbackend.hpp" +#include "backends/fluid/gfluidimgproc.hpp" +#include "backends/fluid/gfluidcore.hpp" + +#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK! + +// FIXME: Is there a way to take a typed graph (our GModel), +// and create a new typed graph _ATOP_ of that (by extending with a couple of +// new types?). +// Alternatively, is there a way to compose types graphs? +// +// If not, we need to introduce that! +using GFluidModel = ade::TypedGraph + < cv::gimpl::FluidUnit + , cv::gimpl::FluidData + , cv::gimpl::Protocol + , cv::gimpl::FluidUseOwnBorderBuffer + >; + +// FIXME: Same issue with Typed and ConstTyped +using GConstFluidModel = ade::ConstTypedGraph + < cv::gimpl::FluidUnit + , cv::gimpl::FluidData + , cv::gimpl::Protocol + , cv::gimpl::FluidUseOwnBorderBuffer + >; + +// FluidBackend middle-layer implementation //////////////////////////////////// +namespace +{ + class GFluidBackendImpl final: public cv::gapi::GBackend::Priv + { + virtual void unpackKernel(ade::Graph &graph, + const ade::NodeHandle &op_node, + const cv::GKernelImpl &impl) override + { + GFluidModel fm(graph); + auto fluid_impl = cv::util::any_cast(impl.opaque); + fm.metadata(op_node).set(cv::gimpl::FluidUnit{fluid_impl, {}, 0, 0, 0.0}); + } + + virtual EPtr compile(const ade::Graph &graph, + const cv::GCompileArgs &args, + const std::vector &nodes) const override + { + const auto out_rois = cv::gimpl::getCompileArg(args).value_or(cv::GFluidOutputRois()); + return EPtr{new cv::gimpl::GFluidExecutable(graph, nodes, out_rois.rois)}; + } + + virtual void addBackendPasses(ade::ExecutionEngineSetupContext &ectx) override; + + }; +} + +cv::gapi::GBackend cv::gapi::fluid::backend() +{ + static cv::gapi::GBackend this_backend(std::make_shared()); + return this_backend; +} + +// FluidAgent implementation /////////////////////////////////////////////////// + +namespace cv { namespace gimpl { +struct FluidFilterAgent : public FluidAgent +{ +private: + virtual int firstWindow() const override; + virtual int nextWindow() const override; + virtual int linesRead() const override; +public: + using FluidAgent::FluidAgent; +}; + +struct FluidResizeAgent : public FluidAgent +{ +private: + virtual int firstWindow() const override; + virtual int nextWindow() const override; + virtual int linesRead() const override; +public: + using FluidAgent::FluidAgent; +}; + +struct FluidUpscaleAgent : public FluidAgent +{ +private: + virtual int firstWindow() const override; + virtual int nextWindow() const override; + virtual int linesRead() const override; +public: + using FluidAgent::FluidAgent; +}; +}} // namespace cv::gimpl + +cv::gimpl::FluidAgent::FluidAgent(const ade::Graph &g, ade::NodeHandle nh) + : k(GConstFluidModel(g).metadata(nh).get().k) // init(0) + , op_handle(nh) // init(1) + , op_name(GModel::ConstGraph(g).metadata(nh).get().k.name) // init(2) +{ + std::set out_w; + std::set out_h; + GModel::ConstGraph cm(g); + for (auto out_data : nh->outNodes()) + { + const auto &d = cm.metadata(out_data).get(); + cv::GMatDesc d_meta = cv::util::get(d.meta); + out_w.insert(d_meta.size.width); + out_h.insert(d_meta.size.height); + } + + // Different output sizes are not supported + GAPI_Assert(out_w.size() == 1 && out_h.size() == 1); +} + +void cv::gimpl::FluidAgent::reset() +{ + m_producedLines = 0; + + auto lines = firstWindow(); + for (auto &v : in_views) + { + if (v) + { + v.priv().reset(lines); + } + } +} + +namespace { +static int calcGcd (int n1, int n2) +{ + return (n2 == 0) ? n1 : calcGcd (n2, n1 % n2); +} + +// This is an empiric formula and this is not 100% guaranteed +// that it produces correct results in all possible cases +// FIXME: +// prove correctness or switch to some trusted method +// +// When performing resize input/output pixels form a cyclic +// pattern where inH/gcd input pixels are mapped to outH/gcd +// output pixels (pattern repeats gcd times). +// +// Output pixel can partually cover some of the input pixels. +// There are 3 possible cases: +// +// :___ ___: :___ _:_ ___: :___ __: ___ :__ ___: +// |___|___| |___|_:_|___| |___|__:|___|:__|___| +// : : : : : : : : : +// +// 1) No partial coverage, max window = scaleFactor; +// 2) Partial coverage occurs on the one side of the output pixel, +// max window = scaleFactor + 1; +// 3) Partial coverage occurs at both sides of the output pixel, +// max window = scaleFactor + 2; +// +// Type of the coverage is determined by remainder of +// inPeriodH/outPeriodH division, but it's an heuristic +// (howbeit didn't found the proof of the opposite so far). + +static int calcResizeWindow(int inH, int outH) +{ + CV_Assert(inH >= outH); + auto gcd = calcGcd(inH, outH); + int inPeriodH = inH/gcd; + int outPeriodH = outH/gcd; + int scaleFactor = inPeriodH / outPeriodH; + + switch ((inPeriodH) % (outPeriodH)) + { + case 0: return scaleFactor; break; + case 1: return scaleFactor + 1; break; + default: return scaleFactor + 2; + } +} + +static int maxReadWindow(const cv::GFluidKernel& k, int inH, int outH) +{ + switch (k.m_kind) + { + case cv::GFluidKernel::Kind::Filter: return k.m_window; break; + case cv::GFluidKernel::Kind::Resize: + { + if (inH >= outH) + { + return calcResizeWindow(inH, outH); + } + else + { + // Upscale always has window of 2 + return (inH == 1) ? 1 : 2; + } + } break; + default: CV_Assert(false); return 0; + } +} + +static int borderSize(const cv::GFluidKernel& k) +{ + switch (k.m_kind) + { + case cv::GFluidKernel::Kind::Filter: return (k.m_window - 1) / 2; break; + // Resize never reads from border pixels + case cv::GFluidKernel::Kind::Resize: return 0; break; + default: CV_Assert(false); return 0; + } +} + +double inCoord(int outIdx, double ratio) +{ + return outIdx * ratio; +} + +int windowStart(int outIdx, double ratio) +{ + return static_cast(inCoord(outIdx, ratio) + 1e-3); +} + +int windowEnd(int outIdx, double ratio) +{ + return static_cast(std::ceil(inCoord(outIdx + 1, ratio) - 1e-3)); +} + +double inCoordUpscale(int outCoord, double ratio) +{ + // Calculate the projection of output pixel's center + return (outCoord + 0.5) * ratio - 0.5; +} + +int upscaleWindowStart(int outCoord, double ratio) +{ + int start = static_cast(inCoordUpscale(outCoord, ratio)); + CV_Assert(start >= 0); + return start; +} + +int upscaleWindowEnd(int outCoord, double ratio, int inSz) +{ + int end = static_cast(std::ceil(inCoordUpscale(outCoord, ratio)) + 1); + if (end > inSz) + { + end = inSz; + } + return end; +} +} // anonymous namespace + +int cv::gimpl::FluidFilterAgent::firstWindow() const +{ + return k.m_window + k.m_lpi - 1; +} + +int cv::gimpl::FluidFilterAgent::nextWindow() const +{ + int lpi = std::min(k.m_lpi, m_outputLines - m_producedLines - k.m_lpi); + return k.m_window - 1 + lpi; +} + +int cv::gimpl::FluidFilterAgent::linesRead() const +{ + return k.m_lpi; +} + +int cv::gimpl::FluidResizeAgent::firstWindow() const +{ + auto outIdx = out_buffers[0]->priv().y(); + return windowEnd(outIdx, m_ratio) - windowStart(outIdx, m_ratio); +} + +int cv::gimpl::FluidResizeAgent::nextWindow() const +{ + auto outIdx = out_buffers[0]->priv().y(); + return windowEnd(outIdx + 1, m_ratio) - windowStart(outIdx + 1, m_ratio); +} + +int cv::gimpl::FluidResizeAgent::linesRead() const +{ + auto outIdx = out_buffers[0]->priv().y(); + return windowStart(outIdx + 1, m_ratio) - windowStart(outIdx, m_ratio); +} + +int cv::gimpl::FluidUpscaleAgent::firstWindow() const +{ + auto outIdx = out_buffers[0]->priv().y(); + return upscaleWindowEnd(outIdx, m_ratio, in_views[0].meta().size.height) - upscaleWindowStart(outIdx, m_ratio); +} + +int cv::gimpl::FluidUpscaleAgent::nextWindow() const +{ + auto outIdx = out_buffers[0]->priv().y(); + return upscaleWindowEnd(outIdx + 1, m_ratio, in_views[0].meta().size.height) - upscaleWindowStart(outIdx + 1, m_ratio); +} + +int cv::gimpl::FluidUpscaleAgent::linesRead() const +{ + auto outIdx = out_buffers[0]->priv().y(); + return upscaleWindowStart(outIdx + 1, m_ratio) - upscaleWindowStart(outIdx, m_ratio); +} + +bool cv::gimpl::FluidAgent::canRead() const +{ + // An agent can work if every input buffer have enough data to start + for (auto in_view : in_views) + { + if (in_view) + { + if (!in_view.ready()) + return false; + } + } + return true; +} + +bool cv::gimpl::FluidAgent::canWrite() const +{ + // An agent can work if there is space to write in its output + // allocated buffers + CV_Assert(!out_buffers.empty()); + auto out_begin = out_buffers.begin(); + auto out_end = out_buffers.end(); + if (k.m_scratch) out_end--; + for (auto it = out_begin; it != out_end; ++it) + { + if ((*it)->priv().full()) + { + return false; + } + } + return true; +} + +bool cv::gimpl::FluidAgent::canWork() const +{ + return canRead() && canWrite(); +} + +void cv::gimpl::FluidAgent::doWork() +{ + GAPI_Assert(m_outputLines > m_producedLines); + for (auto in_view : in_views) + { + if (in_view) in_view.priv().prepareToRead(); + } + + k.m_f(in_args, out_buffers); + + for (auto in_view : in_views) + { + if (in_view) in_view.priv().readDone(linesRead(), nextWindow()); + } + + for (auto out_buf : out_buffers) + { + out_buf->priv().writeDone(); + // FIXME WARNING: Scratch buffers rotated here too! + } + + m_producedLines += k.m_lpi; +} + +bool cv::gimpl::FluidAgent::done() const +{ + // m_producedLines is a multiple of LPI, while original + // height may be not. + return m_producedLines >= m_outputLines; +} + +void cv::gimpl::FluidAgent::debug(std::ostream &os) +{ + os << "Fluid Agent " << std::hex << this + << " (" << op_name << ") --" + << " canWork=" << std::boolalpha << canWork() + << " canRead=" << std::boolalpha << canRead() + << " canWrite=" << std::boolalpha << canWrite() + << " done=" << done() + << " lines=" << std::dec << m_producedLines << "/" << m_outputLines + << " {{\n"; + for (auto out_buf : out_buffers) + { + out_buf->debug(os); + } + std::cout << "}}" << std::endl; +} + +// GCPUExcecutable implementation ////////////////////////////////////////////// +cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g, + const std::vector &nodes, + const std::vector &outputRois) + : m_g(g), m_gm(m_g), m_outputRois(outputRois) +{ + GConstFluidModel fg(m_g); + + // Initialize vector of data buffers, build list of operations + // FIXME: There _must_ be a better way to [query] count number of DATA nodes + std::size_t mat_count = 0; + std::size_t last_agent = 0; + std::map all_gmat_ids; + + auto grab_mat_nh = [&](ade::NodeHandle nh) { + auto rc = m_gm.metadata(nh).get().rc; + if (m_id_map.count(rc) == 0) + { + all_gmat_ids[mat_count] = nh; + m_id_map[rc] = mat_count++; + } + }; + + for (const auto &nh : nodes) + { + switch (m_gm.metadata(nh).get().t) + { + case NodeType::DATA: + if (m_gm.metadata(nh).get().shape == GShape::GMAT) + grab_mat_nh(nh); + break; + + case NodeType::OP: + { + const auto& fu = fg.metadata(nh).get(); + switch (fu.k.m_kind) + { + case GFluidKernel::Kind::Filter: m_agents.emplace_back(new FluidFilterAgent(m_g, nh)); break; + case GFluidKernel::Kind::Resize: + { + if (fu.ratio >= 1.0) + { + m_agents.emplace_back(new FluidResizeAgent(m_g, nh)); + } + else + { + m_agents.emplace_back(new FluidUpscaleAgent(m_g, nh)); + } + } break; + default: CV_Assert(false); + } + // NB.: in_buffer_ids size is equal to Arguments size, not Edges size!!! + m_agents.back()->in_buffer_ids.resize(m_gm.metadata(nh).get().args.size(), -1); + for (auto eh : nh->inEdges()) + { + // FIXME Only GMats are currently supported (which can be represented + // as fluid buffers + if (m_gm.metadata(eh->srcNode()).get().shape == GShape::GMAT) + { + const auto in_port = m_gm.metadata(eh).get().port; + const auto in_buf = m_gm.metadata(eh->srcNode()).get().rc; + + m_agents.back()->in_buffer_ids[in_port] = in_buf; + grab_mat_nh(eh->srcNode()); + } + } + // FIXME: Assumption that all operation outputs MUST be connected + m_agents.back()->out_buffer_ids.resize(nh->outEdges().size(), -1); + for (auto eh : nh->outEdges()) + { + const auto& data = m_gm.metadata(eh->dstNode()).get(); + const auto out_port = m_gm.metadata(eh).get().port; + const auto out_buf = data.rc; + + m_agents.back()->out_buffer_ids[out_port] = out_buf; + if (data.shape == GShape::GMAT) grab_mat_nh(eh->dstNode()); + } + if (fu.k.m_scratch) + m_scratch_users.push_back(last_agent); + last_agent++; + break; + } + default: GAPI_Assert(false); + } + } + + // Check that IDs form a continiuos set (important for further indexing) + GAPI_Assert(m_id_map.size() > 0u); + GAPI_Assert(m_id_map.size() == mat_count); + + // Actually initialize Fluid buffers + GAPI_LOG_INFO(NULL, "Initializing " << mat_count << " fluid buffer(s)" << std::endl); + m_num_int_buffers = mat_count; + const std::size_t num_scratch = m_scratch_users.size(); + + // Calculate rois for each fluid buffer + + auto proto = m_gm.metadata().get(); + std::vector readStarts(mat_count); + std::vector rois(mat_count); + std::stack nodesToVisit; + + if (proto.outputs.size() != m_outputRois.size()) + { + CV_Assert(m_outputRois.size() == 0); + m_outputRois.resize(proto.outputs.size()); + } + + // First, initialize rois for output nodes, add them to traversal stack + for (const auto& it : ade::util::indexed(proto.out_nhs)) + { + const auto idx = ade::util::index(it); + const auto nh = ade::util::value(it); + + const auto &d = m_gm.metadata(nh).get(); + + // This is not our output + if (m_id_map.count(d.rc) == 0) + { + continue; + } + + if (d.shape == GShape::GMAT) + { + auto desc = util::get(d.meta); + if (m_outputRois[idx] == cv::gapi::own::Rect{}) + { + m_outputRois[idx] = cv::gapi::own::Rect{0, 0, desc.size.width, desc.size.height}; + } + + // Only slices are supported at the moment + GAPI_Assert(m_outputRois[idx].x == 0); + GAPI_Assert(m_outputRois[idx].width == desc.size.width); + + auto id = m_id_map.at(d.rc); + readStarts[id] = 0; + rois[id] = m_outputRois[idx]; + nodesToVisit.push(nh); + } + } + + // Perform a wide search from each of the output nodes + // And extend roi of buffers by border_size + // Each node can be visited multiple times + // (if node has been already visited, the check that inferred rois are the same is performed) + while (!nodesToVisit.empty()) + { + const auto startNode = nodesToVisit.top(); + nodesToVisit.pop(); + + if (!startNode->inNodes().empty()) + { + GAPI_Assert(startNode->inNodes().size() == 1); + const auto& oh = startNode->inNodes().front(); + const auto& data = m_gm.metadata(startNode).get(); + // only GMats participate in the process so it's valid to obtain GMatDesc + const auto& meta = util::get(data.meta); + + for (const auto& inNode : oh->inNodes()) + { + const auto& in_data = m_gm.metadata(inNode).get(); + + if (in_data.shape == GShape::GMAT) + { + const auto& in_meta = util::get(in_data.meta); + const auto& fd = fg.metadata(inNode).get(); + + auto adjFilterRoi = [](cv::gapi::own::Rect produced, int b, int max_height) { + // Extend with border roi which should be produced, crop to logical image size + cv::gapi::own::Rect roi = {produced.x, produced.y - b, produced.width, produced.height + 2*b}; + cv::gapi::own::Rect fullImg{ 0, 0, produced.width, max_height }; + return roi & fullImg; + }; + + auto adjResizeRoi = [](cv::gapi::own::Rect produced, cv::gapi::own::Size inSz, cv::gapi::own::Size outSz) { + auto map = [](int outCoord, int producedSz, int inSize, int outSize) { + double ratio = (double)inSize / outSize; + int w0 = 0, w1 = 0; + if (ratio >= 1.0) + { + w0 = windowStart(outCoord, ratio); + w1 = windowEnd (outCoord + producedSz - 1, ratio); + } + else + { + w0 = upscaleWindowStart(outCoord, ratio); + w1 = upscaleWindowEnd(outCoord + producedSz - 1, ratio, inSize); + } + return std::make_pair(w0, w1); + }; + + auto mapY = map(produced.y, produced.height, inSz.height, outSz.height); + auto y0 = mapY.first; + auto y1 = mapY.second; + + auto mapX = map(produced.x, produced.width, inSz.width, outSz.width); + auto x0 = mapX.first; + auto x1 = mapX.second; + + cv::gapi::own::Rect roi = {x0, y0, x1 - x0, y1 - y0}; + return roi; + }; + + cv::gapi::own::Rect produced = rois[m_id_map.at(data.rc)]; + + cv::gapi::own::Rect resized; + switch (fg.metadata(oh).get().k.m_kind) + { + case GFluidKernel::Kind::Filter: resized = produced; break; + case GFluidKernel::Kind::Resize: resized = adjResizeRoi(produced, in_meta.size, meta.size); break; + default: CV_Assert(false); + } + + int readStart = resized.y; + cv::gapi::own::Rect roi = adjFilterRoi(resized, fd.border_size, in_meta.size.height); + + auto in_id = m_id_map.at(in_data.rc); + if (rois[in_id] == cv::gapi::own::Rect{}) + { + readStarts[in_id] = readStart; + rois[in_id] = roi; + nodesToVisit.push(inNode); + } + else + { + GAPI_Assert(readStarts[in_id] == readStart); + GAPI_Assert(rois[in_id] == roi); + } + } // if (in_data.shape == GShape::GMAT) + } // for (const auto& inNode : oh->inNodes()) + } // if (!startNode->inNodes().empty()) + } // while (!nodesToVisit.empty()) + + // NB: Allocate ALL buffer object at once, and avoid any further reallocations + // (since raw pointers-to-elements are taken) + m_buffers.resize(m_num_int_buffers + num_scratch); + for (const auto &it : all_gmat_ids) + { + auto id = it.first; + auto nh = it.second; + const auto & d = m_gm.metadata(nh).get(); + const auto &fd = fg.metadata(nh).get(); + const auto meta = cv::util::get(d.meta); + + // FIXME: Only continuous set... + m_buffers[id].priv().init(meta, fd.max_consumption, fd.border_size, fd.skew, fd.lpi_write, readStarts[id], rois[id]); + + if (d.storage == Data::Storage::INTERNAL) + { + m_buffers[id].priv().allocate(fd.border); + std::stringstream stream; + m_buffers[id].debug(stream); + GAPI_LOG_INFO(NULL, stream.str()); + } + } + + // After buffers are allocated, repack: ... + for (auto &agent : m_agents) + { + // a. Agent input parameters with View pointers (creating Views btw) + const auto &op = m_gm.metadata(agent->op_handle).get(); + const auto &fu = fg.metadata(agent->op_handle).get(); + agent->in_args.resize(op.args.size()); + agent->in_views.resize(op.args.size()); + for (auto it : ade::util::zip(ade::util::iota(op.args.size()), + ade::util::toRange(agent->in_buffer_ids))) + { + auto in_idx = std::get<0>(it); + auto buf_idx = std::get<1>(it); + + if (buf_idx >= 0) + { + // IF there is input buffer, register a view (every unique + // reader has its own), and store it in agent Args + gapi::fluid::Buffer &buffer = m_buffers.at(m_id_map.at(buf_idx)); + + auto inEdge = GModel::getInEdgeByPort(m_g, agent->op_handle, in_idx); + auto ownStorage = fg.metadata(inEdge).get().use; + + gapi::fluid::View view = buffer.mkView(fu.line_consumption, fu.border_size, fu.border, ownStorage); + // NB: It is safe to keep ptr as view lifetime is buffer lifetime + agent->in_views[in_idx] = view; + agent->in_args[in_idx] = GArg(view); + agent->m_ratio = fu.ratio; + } + else + { + // Copy(FIXME!) original args as is + agent->in_args[in_idx] = op.args[in_idx]; + } + } + + // b. Agent output parameters with Buffer pointers. + agent->out_buffers.resize(agent->op_handle->outEdges().size(), nullptr); + for (auto it : ade::util::zip(ade::util::iota(agent->out_buffers.size()), + ade::util::toRange(agent->out_buffer_ids))) + { + auto out_idx = std::get<0>(it); + auto buf_idx = m_id_map.at(std::get<1>(it)); + agent->out_buffers.at(out_idx) = &m_buffers.at(buf_idx); + agent->m_outputLines = m_buffers.at(buf_idx).priv().outputLines(); + } + } + + // After parameters are there, initialize scratch buffers + if (num_scratch) + { + GAPI_LOG_INFO(NULL, "Initializing " << num_scratch << " scratch buffer(s)" << std::endl); + unsigned last_scratch_id = 0; + + for (auto i : m_scratch_users) + { + auto &agent = m_agents.at(i); + GAPI_Assert(agent->k.m_scratch); + + // Collect input metas to trigger scratch buffer initialization + // Array is sparse (num of elements == num of GArgs, not edges) + GMetaArgs in_metas(agent->in_args.size()); + for (auto eh : agent->op_handle->inEdges()) + { + const auto& in_data = m_gm.metadata(eh->srcNode()).get(); + in_metas[m_gm.metadata(eh).get().port] = in_data.meta; + } + + // Trigger Scratch buffer initialization method + const std::size_t new_scratch_idx = m_num_int_buffers + last_scratch_id; + + agent->k.m_is(in_metas, agent->in_args, m_buffers.at(new_scratch_idx)); + std::stringstream stream; + m_buffers[new_scratch_idx].debug(stream); + GAPI_LOG_INFO(NULL, stream.str()); + agent->out_buffers.emplace_back(&m_buffers[new_scratch_idx]); + last_scratch_id++; + } + } + + int total_size = 0; + for (const auto &i : ade::util::indexed(m_buffers)) + { + // Check that all internal and scratch buffers are allocated + auto idx = ade::util::index(i); + auto b = ade::util::value(i); + if (idx >= m_num_int_buffers || + m_gm.metadata(all_gmat_ids[idx]).get().storage == Data::Storage::INTERNAL) + { + GAPI_Assert(b.priv().size() > 0); + } + + // Buffers which will be bound to real images may have size of 0 at this moment + // (There can be non-zero sized const border buffer allocated in such buffers) + total_size += b.priv().size(); + } + GAPI_LOG_INFO(NULL, "Internal buffers: " << std::fixed << std::setprecision(2) << static_cast(total_size)/1024 << " KB\n"); +} + +// FIXME: Document what it does +void cv::gimpl::GFluidExecutable::bindInArg(const cv::gimpl::RcDesc &rc, const GRunArg &arg) +{ + switch (rc.shape) + { + case GShape::GMAT: m_buffers[m_id_map.at(rc.id)].priv().bindTo(to_ocv(util::get(arg)), true); break; + case GShape::GSCALAR: m_res.slot()[rc.id] = util::get(arg); break; + default: util::throw_error(std::logic_error("Unsupported GShape type")); + } +} + +void cv::gimpl::GFluidExecutable::bindOutArg(const cv::gimpl::RcDesc &rc, const GRunArgP &arg) +{ + // Only GMat is supported as return type + switch (rc.shape) + { + case GShape::GMAT: + { + cv::GMatDesc desc = m_buffers[m_id_map.at(rc.id)].meta(); + auto &outMat = *util::get(arg); + GAPI_Assert(outMat.data != nullptr); + GAPI_Assert(descr_of(outMat) == desc && "Output argument was not preallocated as it should be ?"); + m_buffers[m_id_map.at(rc.id)].priv().bindTo(to_ocv(outMat), false); + break; + } + default: util::throw_error(std::logic_error("Unsupported return GShape type")); + } +} + +void cv::gimpl::GFluidExecutable::packArg(cv::GArg &in_arg, const cv::GArg &op_arg) +{ + GAPI_Assert(op_arg.kind != cv::detail::ArgKind::GMAT + && op_arg.kind != cv::detail::ArgKind::GSCALAR); + + if (op_arg.kind == cv::detail::ArgKind::GOBJREF) + { + const cv::gimpl::RcDesc &ref = op_arg.get(); + if (ref.shape == GShape::GSCALAR) + { + in_arg = GArg(m_res.slot()[ref.id]); + } + } +} + +void cv::gimpl::GFluidExecutable::run(std::vector &&input_objs, + std::vector &&output_objs) +{ + // Bind input buffers from parameters + for (auto& it : input_objs) bindInArg(it.first, it.second); + for (auto& it : output_objs) bindOutArg(it.first, it.second); + + // Reset Buffers and Agents state before we go + for (auto &buffer : m_buffers) + buffer.priv().reset(); + + for (auto &agent : m_agents) + { + agent->reset(); + // Pass input cv::Scalar's to agent argument + const auto& op = m_gm.metadata(agent->op_handle).get(); + for (const auto& it : ade::util::indexed(op.args)) + { + const auto& arg = ade::util::value(it); + packArg(agent->in_args[ade::util::index(it)], arg); + } + } + + // Explicitly reset Scratch buffers, if any + for (auto scratch_i : m_scratch_users) + { + auto &agent = m_agents[scratch_i]; + GAPI_Assert(agent->k.m_scratch); + agent->k.m_rs(*agent->out_buffers.back()); + } + + // Now start executing our stuff! + // Fluid execution is: + // - run through list of Agents from Left to Right + // - for every Agent: + // - if all input Buffers have enough data to fulfill + // Agent's window - trigger Agent + // - on trigger, Agent takes all input lines from input buffers + // and produces a single output line + // - once Agent finishes, input buffers get "readDone()", + // and output buffers get "writeDone()" + // - if there's not enough data, Agent is skipped + // Yes, THAT easy! + bool complete = true; + do { + complete = true; + bool work_done=false; + for (auto &agent : m_agents) + { + // agent->debug(std::cout); + if (!agent->done()) + { + if (agent->canWork()) + { + agent->doWork(); work_done=true; + } + if (!agent->done()) complete = false; + } + } + GAPI_Assert(work_done || complete); + } while (!complete); // FIXME: number of iterations can be calculated statically +} + +// FIXME: these passes operate on graph global level!!! +// Need to fix this for heterogeneous (island-based) processing +void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx) +{ + using namespace cv::gimpl; + + // FIXME: all passes were moved to "exec" stage since Fluid + // should check Islands configuration first (which is now quite + // limited), and only then continue with all other passes. + // + // The passes/stages API must be streamlined! + ectx.addPass("exec", "fluid_sanity_check", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + auto isl_graph = g.metadata().get().model; + GIslandModel::Graph gim(*isl_graph); + + const auto num_non_fluid_islands = std::count_if + (gim.nodes().begin(), + gim.nodes().end(), + [&](const ade::NodeHandle &nh) { + return gim.metadata(nh).get().k == NodeKind::ISLAND && + gim.metadata(nh).get().object->backend() != cv::gapi::fluid::backend(); + }); + + // FIXME: Break this limitation! + if (num_non_fluid_islands > 0) + cv::util::throw_error(std::logic_error("Fluid doesn't support heterogeneous execution")); + }); + ectx.addPass("exec", "init_fluid_data", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + for (const auto node : g.nodes()) + { + if (g.metadata(node).get().t == NodeType::DATA) + { + fg.metadata(node).set(FluidData()); + } + } + }); + // FIXME: + // move to unpackKernel method + // when https://gitlab-icv.inn.intel.com/G-API/g-api/merge_requests/66 is merged + ectx.addPass("exec", "init_fluid_unit_borders", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + + auto sorted = g.metadata().get().nodes(); + for (auto node : sorted) + { + if (g.metadata(node).get().t == NodeType::OP) + { + // FIXME: check that op has only one data node on input + auto &fu = fg.metadata(node).get(); + const auto &op = g.metadata(node).get(); + + // Trigger user-defined "getBorder" callback + fu.border = fu.k.m_b(GModel::collectInputMeta(fg, node), op.args); + } + } + }); + ectx.addPass("exec", "init_fluid_units", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + + auto sorted = g.metadata().get().nodes(); + for (auto node : sorted) + { + if (g.metadata(node).get().t == NodeType::OP) + { + std::set in_hs, out_ws, out_hs; + + for (const auto& in : node->inNodes()) + { + const auto& d = g.metadata(in).get(); + if (d.shape == cv::GShape::GMAT) + { + const auto& meta = cv::util::get(d.meta); + in_hs.insert(meta.size.height); + } + } + + for (const auto& out : node->outNodes()) + { + const auto& d = g.metadata(out).get(); + if (d.shape == cv::GShape::GMAT) + { + const auto& meta = cv::util::get(d.meta); + out_ws.insert(meta.size.width); + out_hs.insert(meta.size.height); + } + } + + CV_Assert(in_hs.size() == 1 && out_ws.size() == 1 && out_hs.size() == 1); + + auto in_h = *in_hs .cbegin(); + auto out_h = *out_hs.cbegin(); + + auto &fu = fg.metadata(node).get(); + fu.ratio = (double)in_h / out_h; + + int w = maxReadWindow(fu.k, in_h, out_h); + int line_consumption = fu.k.m_lpi + w - 1; + int border_size = borderSize(fu.k); + + fu.border_size = border_size; + fu.line_consumption = line_consumption; + + GModel::log(g, node, "Line consumption: " + std::to_string(fu.line_consumption)); + GModel::log(g, node, "Border size: " + std::to_string(fu.border_size)); + } + } + }); + ectx.addPass("exec", "init_line_consumption", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + for (const auto node : g.nodes()) + { + if (g.metadata(node).get().t == NodeType::OP) + { + const auto &fu = fg.metadata(node).get(); + + for (auto in_data_node : node->inNodes()) + { + auto &fd = fg.metadata(in_data_node).get(); + + // Update (not Set) fields here since a single data node may be + // accessed by multiple consumers + fd.max_consumption = std::max(fu.line_consumption, fd.max_consumption); + fd.border_size = std::max(fu.border_size, fd.border_size); + + GModel::log(g, in_data_node, "Line consumption: " + std::to_string(fd.max_consumption) + + " (upd by " + std::to_string(fu.line_consumption) + ")", node); + GModel::log(g, in_data_node, "Border size: " + std::to_string(fd.border_size), node); + } + } + } + }); + ectx.addPass("exec", "calc_latency", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + + auto sorted = g.metadata().get().nodes(); + for (auto node : sorted) + { + if (g.metadata(node).get().t == NodeType::OP) + { + const auto &fu = fg.metadata(node).get(); + + const int own_latency = fu.line_consumption - fu.border_size; + GModel::log(g, node, "LPI: " + std::to_string(fu.k.m_lpi)); + + // Output latency is max(input_latency) + own_latency + int in_latency = 0; + for (auto in_data_node : node->inNodes()) + { + // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA) + in_latency = std::max(in_latency, fg.metadata(in_data_node).get().latency); + } + const int out_latency = in_latency + own_latency; + + for (auto out_data_node : node->outNodes()) + { + // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA) + auto &fd = fg.metadata(out_data_node).get(); + fd.latency = out_latency; + fd.lpi_write = fu.k.m_lpi; + GModel::log(g, out_data_node, "Latency: " + std::to_string(out_latency)); + } + } + } + }); + ectx.addPass("exec", "calc_skew", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + + auto sorted = g.metadata().get().nodes(); + for (auto node : sorted) + { + if (g.metadata(node).get().t == NodeType::OP) + { + int max_latency = 0; + for (auto in_data_node : node->inNodes()) + { + // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA) + max_latency = std::max(max_latency, fg.metadata(in_data_node).get().latency); + } + for (auto in_data_node : node->inNodes()) + { + // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA) + auto &fd = fg.metadata(in_data_node).get(); + + // Update (not Set) fields here since a single data node may be + // accessed by multiple consumers + fd.skew = std::max(fd.skew, max_latency - fd.latency); + + GModel::log(g, in_data_node, "Skew: " + std::to_string(fd.skew), node); + } + } + } + }); + ectx.addPass("exec", "init_buffer_borders", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + auto sorted = g.metadata().get().nodes(); + for (auto node : sorted) + { + if (g.metadata(node).get().t == NodeType::DATA) + { + auto &fd = fg.metadata(node).get(); + + // Assign border stuff to FluidData + + // In/out data nodes are bound to user data directly, + // so cannot be extended with a border + if (g.metadata(node).get().storage == Data::Storage::INTERNAL) + { + // For now border of the buffer's storage is the border + // of the first reader whose border size is the same. + // FIXME: find more clever strategy of border picking + // (it can be a border which is common for majority of the + // readers, also we can calculate the number of lines which + // will be copied by views on each iteration and base our choice + // on this criteria) + auto readers = node->outNodes(); + const auto &candidate = ade::util::find_if(readers, [&](ade::NodeHandle nh) { + const auto &fu = fg.metadata(nh).get(); + return fu.border_size == fd.border_size; + }); + GAPI_Assert(candidate != readers.end()); + + const auto &fu = fg.metadata(*candidate).get(); + fd.border = fu.border; + } + + if (fd.border) + { + GModel::log(g, node, "Border type: " + std::to_string(fd.border->type), node); + } + } + } + }); + ectx.addPass("exec", "init_view_borders", [](ade::passes::PassContext &ctx) + { + GModel::Graph g(ctx.graph); + if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this! + return; + + GFluidModel fg(ctx.graph); + for (auto node : g.nodes()) + { + if (g.metadata(node).get().t == NodeType::DATA) + { + auto &fd = fg.metadata(node).get(); + for (auto out_edge : node->outEdges()) + { + const auto &fu = fg.metadata(out_edge->dstNode()).get(); + + // There is no need in own storage for view if it's border is + // the same as the buffer's (view can have equal or smaller border + // size in this case) + if (fu.border_size == 0 || + (fu.border && fd.border && (*fu.border == *fd.border))) + { + GAPI_Assert(fu.border_size <= fd.border_size); + fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{false}); + } + else + { + fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{true}); + GModel::log(g, out_edge, "OwnBufferStorage: true"); + } + } + } + } + }); +} diff --git a/modules/gapi/src/backends/fluid/gfluidbackend.hpp b/modules/gapi/src/backends/fluid/gfluidbackend.hpp new file mode 100644 index 0000000000..f4446408f9 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidbackend.hpp @@ -0,0 +1,128 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_FLUID_BACKEND_HPP +#define OPENCV_GAPI_FLUID_BACKEND_HPP + +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gproto.hpp" +#include "opencv2/gapi/fluid/gfluidkernel.hpp" +#include "opencv2/gapi/fluid/gfluidbuffer.hpp" + +// PRIVATE STUFF! +#include "backends/common/gbackend.hpp" +#include "compiler/gislandmodel.hpp" + +namespace cv { namespace gimpl { + +struct FluidUnit +{ + static const char *name() { return "FluidKernel"; } + GFluidKernel k; + gapi::fluid::BorderOpt border; + int border_size; + int line_consumption; + double ratio; +}; + +struct FluidUseOwnBorderBuffer +{ + static const char *name() { return "FluidUseOwnBorderBuffer"; } + bool use; +}; + +struct FluidData +{ + static const char *name() { return "FluidData"; } + + // FIXME: This structure starts looking like "FluidBuffer" meta + int latency = 0; + int skew = 0; + int max_consumption = 1; + int border_size = 0; + int lpi_write = 1; + gapi::fluid::BorderOpt border; +}; + +struct FluidAgent +{ +public: + virtual ~FluidAgent() = default; + FluidAgent(const ade::Graph &g, ade::NodeHandle nh); + + GFluidKernel k; + ade::NodeHandle op_handle; // FIXME: why it is here??// + std::string op_name; + + // < 0 - not a buffer + // >= 0 - a buffer with RcID + std::vector in_buffer_ids; + std::vector out_buffer_ids; + + cv::GArgs in_args; + std::vector in_views; // sparce list of IN views + std::vector out_buffers; + + // FIXME Current assumption is that outputs have EQUAL SIZES + int m_outputLines = 0; + int m_producedLines = 0; + + double m_ratio = 0.0f; + + // Execution methods + void reset(); + bool canWork() const; + bool canRead() const; + bool canWrite() const; + void doWork(); + bool done() const; + + void debug(std::ostream& os); + +private: + // FIXME!!! + // move to another class + virtual int firstWindow() const = 0; + virtual int nextWindow() const = 0; + virtual int linesRead() const = 0; +}; + +class GFluidExecutable final: public GIslandExecutable +{ + const ade::Graph &m_g; + GModel::ConstGraph m_gm; + + std::vector> m_agents; + std::vector m_buffers; + + using Magazine = detail::magazine; + Magazine m_res; + + std::size_t m_num_int_buffers; // internal buffers counter (m_buffers - num_scratch) + std::vector m_scratch_users; + std::vector m_views; + + std::vector m_outputRois; + + std::unordered_map m_id_map; // GMat id -> buffer idx map + + void bindInArg (const RcDesc &rc, const GRunArg &arg); + void bindOutArg(const RcDesc &rc, const GRunArgP &arg); + void packArg (GArg &in_arg, const GArg &op_arg); + +public: + GFluidExecutable(const ade::Graph &g, + const std::vector &nodes, + const std::vector &outputRois); + + virtual void run(std::vector &&input_objs, + std::vector &&output_objs) override; +}; +}} // cv::gimpl + + +#endif // OPENCV_GAPI_FLUID_BACKEND_HPP diff --git a/modules/gapi/src/backends/fluid/gfluidbuffer.cpp b/modules/gapi/src/backends/fluid/gfluidbuffer.cpp new file mode 100644 index 0000000000..6614091587 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidbuffer.cpp @@ -0,0 +1,746 @@ +// 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) 2018 Intel Corporation + + +#include // hex, dec (debug) + +#include "opencv2/gapi/own/convert.hpp" + +#include "opencv2/gapi/fluid/gfluidbuffer.hpp" +#include "backends/fluid/gfluidbuffer_priv.hpp" +#include "opencv2/gapi/opencv_includes.hpp" + +#include "backends/fluid/gfluidutils.hpp" // saturate + +namespace cv { +namespace gapi { + +namespace fluid { +bool operator == (const fluid::Border& b1, const fluid::Border& b2) +{ + return b1.type == b2.type && b1.value == b2.value; +} +} // namespace fluid + +// Fluid BorderHandler implementation ///////////////////////////////////////////////// + +namespace { +template +// Expected inputs: +// row - row buffer allocated with border in mind (have memory for both image and border pixels) +// length - size of the buffer with left and right borders included +void fillBorderReplicateRow(uint8_t* row, int length, int chan, int borderSize) +{ + auto leftBorder = reinterpret_cast(row); + auto rightBorder = leftBorder + (length - borderSize) * chan; + for (int b = 0; b < borderSize; b++) + { + for (int c = 0; c < chan; c++) + { + leftBorder [b*chan + c] = leftBorder [borderSize*chan + c]; + rightBorder[b*chan + c] = rightBorder[-chan + c]; + } + } +} + +template +void fillBorderReflectRow(uint8_t* row, int length, int chan, int borderSize) +{ + auto leftBorder = reinterpret_cast(row); + auto rightBorder = leftBorder + (length - borderSize) * chan; + for (int b = 0; b < borderSize; b++) + { + for (int c = 0; c < chan; c++) + { + leftBorder [b*chan + c] = leftBorder [(2*borderSize - b)*chan + c]; + rightBorder[b*chan + c] = rightBorder[(-b - 2)*chan + c]; + } + } +} + +template +void fillConstBorderRow(uint8_t* row, int length, int chan, int borderSize, cv::gapi::own::Scalar borderValue) +{ + GAPI_DbgAssert(chan > 0 && chan <= 4); + + auto leftBorder = reinterpret_cast(row); + auto rightBorder = leftBorder + (length - borderSize) * chan; + for (int b = 0; b < borderSize; b++) + { + for (int c = 0; c < chan; c++) + { + leftBorder [b*chan + c] = fluid::saturate(borderValue[c], fluid::roundd); + rightBorder[b*chan + c] = fluid::saturate(borderValue[c], fluid::roundd); + } + } +} + +// Fills const border pixels in the whole mat +void fillBorderConstant(int borderSize, cv::gapi::own::Scalar borderValue, cv::Mat& mat) +{ + // cv::Scalar can contain maximum 4 chan + GAPI_Assert(mat.channels() > 0 && mat.channels() <= 4); + + auto getFillBorderRowFunc = [&](int type) { + switch(type) + { + case CV_8U: return &fillConstBorderRow< uint8_t>; break; + case CV_16S: return &fillConstBorderRow< int16_t>; break; + case CV_16U: return &fillConstBorderRow; break; + case CV_32F: return &fillConstBorderRow< float >; break; + default: CV_Assert(false); return &fillConstBorderRow; + } + }; + + auto fillBorderRow = getFillBorderRowFunc(mat.depth()); + for (int y = 0; y < mat.rows; y++) + { + fillBorderRow(mat.ptr(y), mat.cols, mat.channels(), borderSize, borderValue); + } +} +} // anonymous namespace + +fluid::BorderHandler::BorderHandler(int border_size) +{ + CV_Assert(border_size > 0); + m_border_size = border_size; +} + +template +fluid::BorderHandlerT::BorderHandlerT(int border_size, int data_type) + : BorderHandler(border_size) +{ + auto getFillBorderRowFunc = [&](int border, int dataType) { + if (border == cv::BORDER_REPLICATE) + { + switch(dataType) + { + case CV_8U: return &fillBorderReplicateRow< uint8_t>; break; + case CV_16S: return &fillBorderReplicateRow< int16_t>; break; + case CV_16U: return &fillBorderReplicateRow; break; + case CV_32F: return &fillBorderReplicateRow< float >; break; + default: CV_Assert(!"Unsupported data type"); return &fillBorderReplicateRow; + } + } + else if (border == cv::BORDER_REFLECT_101) + { + switch(dataType) + { + case CV_8U: return &fillBorderReflectRow< uint8_t>; break; + case CV_16S: return &fillBorderReflectRow< int16_t>; break; + case CV_16U: return &fillBorderReflectRow; break; + case CV_32F: return &fillBorderReflectRow< float >; break; + default: CV_Assert(!"Unsupported data type"); return &fillBorderReflectRow; + } + } + else + { + CV_Assert(!"Unsupported border type"); + return &fillBorderReflectRow; + } + }; + + m_fill_border_row = getFillBorderRowFunc(BorderType, data_type); +} + +namespace { +template int getBorderIdx(int log_idx, int desc_height); + +template<> int getBorderIdx(int log_idx, int desc_height) +{ + return log_idx < 0 ? 0 : desc_height - 1; +} + +template<> int getBorderIdx(int log_idx, int desc_height) +{ + return log_idx < 0 ? -log_idx : 2*(desc_height - 1) - log_idx; +} +} // namespace + +template +const uint8_t* fluid::BorderHandlerT::inLineB(int log_idx, const BufferStorageWithBorder& data, int desc_height) const +{ + auto idx = getBorderIdx(log_idx, desc_height); + return data.ptr(idx); +} + +fluid::BorderHandlerT::BorderHandlerT(int border_size, cv::gapi::own::Scalar border_value, int data_type, int desc_width) + : BorderHandler(border_size), m_border_value(border_value) +{ + m_const_border.create(1, desc_width + 2*m_border_size, data_type); + m_const_border = cv::gapi::own::to_ocv(border_value); +} + +const uint8_t* fluid::BorderHandlerT::inLineB(int /*log_idx*/, const BufferStorageWithBorder& /*data*/, int /*desc_height*/) const +{ + return m_const_border.ptr(0, m_border_size); +} + +void fluid::BorderHandlerT::fillCompileTimeBorder(BufferStorageWithBorder& data) const +{ + cv::gapi::fillBorderConstant(m_border_size, m_border_value, data.data()); +} + +template +void fluid::BorderHandlerT::updateBorderPixels(BufferStorageWithBorder &data, int startLine, int nLines) const +{ + auto& mat = data.data(); + auto length = mat.cols; + auto chan = mat.channels(); + + for (int l = startLine; l < startLine + nLines; l++) + { + auto row = mat.ptr(data.physIdx(l)); + m_fill_border_row(row, length, chan, m_border_size); + } +} + +std::size_t fluid::BorderHandlerT::size() const +{ + return m_const_border.total() * m_const_border.elemSize(); +} + +// Fluid BufferStorage implementation ////////////////////////////////////////// +void fluid::BufferStorageWithBorder::create(int capacity, int desc_width, int dtype, int border_size, Border border) +{ + auto width = (desc_width + 2*border_size); + m_data.create(capacity, width, dtype); + + switch(border.type) + { + case cv::BORDER_CONSTANT: + m_borderHandler.reset(new BorderHandlerT(border_size, border.value, dtype, desc_width)); break; + case cv::BORDER_REPLICATE: + m_borderHandler.reset(new BorderHandlerT(border_size, dtype)); break; + case cv::BORDER_REFLECT_101: + m_borderHandler.reset(new BorderHandlerT(border_size, dtype)); break; + default: + CV_Assert(false); + } + + m_borderHandler->fillCompileTimeBorder(*this); +} + +void fluid::BufferStorageWithoutBorder::create(int capacity, int desc_width, int dtype) +{ + auto width = desc_width; + m_data.create(capacity, width, dtype); + + m_is_virtual = true; +} + +const uint8_t* fluid::BufferStorageWithBorder::inLineB(int log_idx, int desc_height) const +{ + if (log_idx < 0 || log_idx >= desc_height) + { + return m_borderHandler->inLineB(log_idx, *this, desc_height); + } + else + { + return ptr(log_idx); + } +} + +const uint8_t* fluid::BufferStorageWithoutBorder::inLineB(int log_idx, int /*desc_height*/) const +{ + return ptr(log_idx); +} + +static void copyWithoutBorder(const cv::Mat& src, int src_border_size, cv::Mat& dst, int dst_border_size, int startSrcLine, int startDstLine, int lpi) +{ + // FIXME use cv::gapi::own::Rect when implement cv::gapi::own::Mat + auto subSrc = src(cv::Rect{src_border_size, startSrcLine, src.cols - 2*src_border_size, lpi}); + auto subDst = dst(cv::Rect{dst_border_size, startDstLine, dst.cols - 2*dst_border_size, lpi}); + + subSrc.copyTo(subDst); +} + +void fluid::BufferStorageWithoutBorder::copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const +{ + for (int l = startLine; l < startLine + nLines; l++) + { + copyWithoutBorder(m_data, 0, dst.data(), dst.borderSize(), physIdx(l), dst.physIdx(l), 1); + } +} + +void fluid::BufferStorageWithBorder::copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const +{ + // Copy required lpi lines line by line (to avoid wrap if invoked for multiple lines) + for (int l = startLine; l < startLine + nLines; l++) + { + copyWithoutBorder(m_data, borderSize(), dst.data(), dst.borderSize(), physIdx(l), dst.physIdx(l), 1); + } +} + +// FIXME? remember parent and remove src parameter? +void fluid::BufferStorageWithBorder::updateBeforeRead(int startLine, int nLines, const BufferStorage& src) +{ + // TODO: + // Cover with tests!! + // (Ensure that there are no redundant copies done + // and only required (not fetched before) lines are copied) + + GAPI_DbgAssert(startLine >= 0); + + src.copyTo(*this, startLine, nLines); + m_borderHandler->updateBorderPixels(*this, startLine, nLines); +} + +void fluid::BufferStorageWithoutBorder::updateBeforeRead(int /*startLine*/, int /*lpi*/, const BufferStorage& /*src*/) +{ + /* nothing */ +} + +void fluid::BufferStorageWithBorder::updateAfterWrite(int startLine, int nLines) +{ + // FIXME? + // Actually startLine + nLines can be > logical height so + // redundant end lines which will never be read + // can be filled in the ring buffer + m_borderHandler->updateBorderPixels(*this, startLine, nLines); +} + +void fluid::BufferStorageWithoutBorder::updateAfterWrite(int /*startLine*/, int /*lpi*/) +{ + /* nothing */ +} + +size_t fluid::BufferStorageWithBorder::size() const +{ + return m_data.total()*m_data.elemSize() + m_borderHandler->size(); +} + +size_t fluid::BufferStorageWithoutBorder::size() const +{ + return m_data.total()*m_data.elemSize(); +} + +namespace fluid { +namespace { +std::unique_ptr createStorage(int capacity, int desc_width, int type, + int border_size, fluid::BorderOpt border); +std::unique_ptr createStorage(int capacity, int desc_width, int type, + int border_size, fluid::BorderOpt border) +{ + if (border) + { + std::unique_ptr storage(new BufferStorageWithBorder); + storage->create(capacity, desc_width, type, border_size, border.value()); + return std::move(storage); + } + + std::unique_ptr storage(new BufferStorageWithoutBorder); + storage->create(capacity, desc_width, type); + return std::move(storage); +} + +std::unique_ptr createStorage(const cv::Mat& data, cv::gapi::own::Rect roi); +std::unique_ptr createStorage(const cv::Mat& data, cv::gapi::own::Rect roi) +{ + std::unique_ptr storage(new BufferStorageWithoutBorder); + storage->attach(data, roi); + return std::move(storage); +} +} // namespace +} // namespace fluid + +// Fluid View implementation /////////////////////////////////////////////////// + +void fluid::View::Priv::reset(int linesForFirstIteration) +{ + GAPI_DbgAssert(m_p); + + m_lines_next_iter = linesForFirstIteration; + m_read_caret = m_p->priv().readStart(); +} + +void fluid::View::Priv::readDone(int linesRead, int linesForNextIteration) +{ + CV_DbgAssert(m_p); + m_read_caret += linesRead; + m_read_caret %= m_p->meta().size.height; + m_lines_next_iter = linesForNextIteration; +} + +bool fluid::View::Priv::ready() const +{ + auto lastWrittenLine = m_p->priv().writeStart() + m_p->linesReady(); + // + bottom border + if (lastWrittenLine == m_p->meta().size.height) lastWrittenLine += m_border_size; + // + top border + lastWrittenLine += m_border_size; + + auto lastRequiredLine = m_read_caret + m_lines_next_iter; + + return lastWrittenLine >= lastRequiredLine; +} + +fluid::ViewPrivWithoutOwnBorder::ViewPrivWithoutOwnBorder(const Buffer *parent, int borderSize) +{ + CV_Assert(parent); + m_p = parent; + m_border_size = borderSize; +} + +const uint8_t* fluid::ViewPrivWithoutOwnBorder::InLineB(int index) const +{ + GAPI_DbgAssert(m_p); + + const auto &p_priv = m_p->priv(); + + CV_Assert( index >= -m_border_size + && index < -m_border_size + m_lines_next_iter); + + const int log_idx = m_read_caret + index; + + return p_priv.storage().inLineB(log_idx, m_p->meta().size.height); +} + +fluid::ViewPrivWithOwnBorder::ViewPrivWithOwnBorder(const Buffer *parent, int lineConsumption, int borderSize, Border border) +{ + GAPI_Assert(parent); + m_p = parent; + m_border_size = borderSize; + + auto desc = m_p->meta(); + int type = CV_MAKETYPE(desc.depth, desc.chan); + m_own_storage.create(lineConsumption, desc.size.width, type, borderSize, border); +} + +void fluid::ViewPrivWithOwnBorder::prepareToRead() +{ + int startLine = 0; + int nLines = 0; + + if (m_read_caret == m_p->priv().readStart()) + { + // Need to fetch full window on the first iteration + startLine = (m_read_caret > m_border_size) ? m_read_caret - m_border_size : 0; + nLines = m_lines_next_iter; + } + else + { + startLine = m_read_caret + m_border_size; + nLines = m_lines_next_iter - 2*m_border_size; + } + + m_own_storage.updateBeforeRead(startLine, nLines, m_p->priv().storage()); +} + +std::size_t fluid::ViewPrivWithOwnBorder::size() const +{ + GAPI_DbgAssert(m_p); + return m_own_storage.size(); +} + +const uint8_t* fluid::ViewPrivWithOwnBorder::InLineB(int index) const +{ + GAPI_DbgAssert(m_p); + + GAPI_Assert( index >= -m_border_size + && index < -m_border_size + m_lines_next_iter); + + const int log_idx = m_read_caret + index; + + return m_own_storage.inLineB(log_idx, m_p->meta().size.height); +} + +const uint8_t* fluid::View::InLineB(int index) const +{ + return m_priv->InLineB(index); +} + +fluid::View::operator bool() const +{ + return m_priv != nullptr && m_priv->m_p != nullptr; +} + +int fluid::View::length() const +{ + return m_priv->m_p->length(); +} + +bool fluid::View::ready() const +{ + return m_priv->ready(); +} + +int fluid::View::y() const +{ + return m_priv->m_read_caret - m_priv->m_border_size; +} + +cv::GMatDesc fluid::View::meta() const +{ + // FIXME: cover with test! + return m_priv->m_p->meta(); +} + +fluid::View::Priv& fluid::View::priv() +{ + return *m_priv; +} + +const fluid::View::Priv& fluid::View::priv() const +{ + return *m_priv; +} + +// Fluid Buffer implementation ///////////////////////////////////////////////// + +fluid::Buffer::Priv::Priv(int read_start, cv::gapi::own::Rect roi) + : m_readStart(read_start) + , m_roi(roi) +{} + +void fluid::Buffer::Priv::init(const cv::GMatDesc &desc, + int line_consumption, + int border_size, + int skew, + int wlpi, + int readStartPos, + cv::gapi::own::Rect roi) +{ + GAPI_Assert(m_line_consumption == -1); + GAPI_Assert(line_consumption > 0); + + m_line_consumption = line_consumption; + m_border_size = border_size; + m_skew = skew; + m_writer_lpi = wlpi; + m_desc = desc; + m_readStart = readStartPos; + m_roi = roi; +} + +void fluid::Buffer::Priv::allocate(BorderOpt border) +{ + GAPI_Assert(!m_storage); + + // Init physical buffer + + // FIXME? combine with skew? + auto maxRead = m_line_consumption + m_skew; + auto maxWritten = m_writer_lpi; + + auto max = std::max(maxRead, maxWritten); + auto min = std::min(maxRead, maxWritten); + + // FIXME: + // Fix the deadlock (completely)!!! + auto data_height = static_cast(std::ceil((double)max / min) * min); + + m_storage = createStorage(data_height, + m_desc.size.width, + CV_MAKETYPE(m_desc.depth, m_desc.chan), + m_border_size, + border); + + // Finally, initialize carets + m_write_caret = 0; +} + +void fluid::Buffer::Priv::bindTo(const cv::Mat &data, bool is_input) +{ + // FIXME: move all these fields into a separate structure + GAPI_Assert(m_skew == 0); + GAPI_Assert(m_desc == cv::descr_of(data)); + if ( is_input) CV_Assert(m_writer_lpi == 1); + + m_storage = createStorage(data, m_roi); + + m_is_input = is_input; + m_write_caret = is_input ? writeEnd(): writeStart(); + // NB: views remain the same! +} + +bool fluid::Buffer::Priv::full() const +{ + int slowest_y = writeEnd(); + if (!m_views.empty()) + { + // reset with maximum possible value and then find minimum + slowest_y = m_desc.size.height; + for (const auto &v : m_views) slowest_y = std::min(slowest_y, v.y()); + } + + return m_write_caret + lpi() - slowest_y > m_storage->rows(); +} + +void fluid::Buffer::Priv::writeDone() +{ + // There are possible optimizations which can be done to fill a border values + // in compile time of the graph (for example border is const), + // so there is no need to update border values after each write. + // If such optimizations weren't applied, fill border for lines + // which have been just written + m_storage->updateAfterWrite(m_write_caret, m_writer_lpi); + + // Final write may produce less LPI, so + // write caret may exceed logical buffer size + m_write_caret += m_writer_lpi; + // FIXME: add consistency check! +} + +void fluid::Buffer::Priv::reset() +{ + m_write_caret = m_is_input ? writeEnd() : writeStart(); +} + +int fluid::Buffer::Priv::size() const +{ + std::size_t view_sz = 0; + for (const auto &v : m_views) view_sz += v.priv().size(); + + auto total = view_sz; + if (m_storage) total += m_storage->size(); + + // FIXME: Change API to return size_t!!! + return static_cast(total); +} + +int fluid::Buffer::Priv::linesReady() const +{ + if (m_is_input) + { + return m_storage->rows(); + } + else + { + const int writes = std::min(m_write_caret - writeStart(), outputLines()); + return writes; + } +} + +uint8_t* fluid::Buffer::Priv::OutLineB(int index) +{ + GAPI_Assert(index >= 0 && index < m_writer_lpi); + + return m_storage->ptr(m_write_caret + index); +} + +int fluid::Buffer::Priv::lpi() const +{ + // FIXME: + // m_write_caret can be greater than m_writeRoi.y + m_writeRoi.height, so return value can be negative !!! + return std::min(writeEnd() - m_write_caret, m_writer_lpi); +} + +fluid::Buffer::Buffer() + : m_priv(new Priv()) +{ +} + +fluid::Buffer::Buffer(const cv::GMatDesc &desc) + : m_priv(new Priv()) +{ + int lineConsumption = 1; + int border = 0, skew = 0, wlpi = 1, readStart = 0; + cv::gapi::own::Rect roi = {0, 0, desc.size.width, desc.size.height}; + m_priv->init(desc, lineConsumption, border, skew, wlpi, readStart, roi); + m_priv->allocate({}); +} + +fluid::Buffer::Buffer(const cv::GMatDesc &desc, + int max_line_consumption, + int border_size, + int skew, + int wlpi, + BorderOpt border) + : m_priv(new Priv()) +{ + int readStart = 0; + cv::gapi::own::Rect roi = {0, 0, desc.size.width, desc.size.height}; + m_priv->init(desc, max_line_consumption, border_size, skew, wlpi, readStart, roi); + m_priv->allocate(border); +} + +fluid::Buffer::Buffer(const cv::Mat &data, bool is_input) + : m_priv(new Priv()) +{ + int lineConsumption = 1; + int border = 0, skew = 0, wlpi = 1, readStart = 0; + cv::gapi::own::Rect roi{0, 0, data.cols, data.rows}; + m_priv->init(descr_of(data), lineConsumption, border, skew, wlpi, readStart, roi); + m_priv->bindTo(data, is_input); +} + +uint8_t* fluid::Buffer::Buffer::OutLineB(int index) +{ + return m_priv->OutLineB(index); +} + +int fluid::Buffer::linesReady() const +{ + return m_priv->linesReady(); +} + +int fluid::Buffer::length() const +{ + return meta().size.width; +} + +int fluid::Buffer::lpi() const +{ + return m_priv->lpi(); +} + +cv::GMatDesc fluid::Buffer::meta() const +{ + return m_priv->meta(); +} + +fluid::View::View(Priv* p) + : m_priv(p) +{ /* nothing */ } + +fluid::View fluid::Buffer::mkView(int lineConsumption, int borderSize, BorderOpt border, bool ownStorage) +{ + // FIXME: logic outside of Priv (because View takes pointer to Buffer) + auto view = ownStorage ? View(new ViewPrivWithOwnBorder(this, lineConsumption, borderSize, border.value())) + : View(new ViewPrivWithoutOwnBorder(this, borderSize)); + m_priv->addView(view); + return view; +} + +void fluid::debugBufferPriv(const fluid::Buffer& buffer, std::ostream &os) +{ + // FIXME Use cv::gapi::own Size and Rect with operator<<, when merged ADE-285 + const auto& p = buffer.priv(); + os << "Fluid buffer " << std::hex << &buffer << std::dec + << " " << p.m_desc.size.width << " x " << p.m_desc.size.height << "]" + << " readStart:" << p.m_readStart + << " roi:" << "[" << p.m_roi.width << " x " << p.m_roi.height << " from (" << p.m_roi.x << ", " << p.m_roi.y << ")]" + <<" (phys " << "[" << p.storage().cols() << " x " << p.storage().rows() << "]" << ") :" + << " w: " << p.m_write_caret + << ", r: ["; + for (const auto &v : p.m_views) { os << &v.priv() << ":" << v.y() << " "; } + os << "], avail: " << buffer.linesReady() + << std::endl; +} + +void fluid::Buffer::debug(std::ostream &os) const +{ + debugBufferPriv(*this, os); +} + +fluid::Buffer::Priv& fluid::Buffer::priv() +{ + return *m_priv; +} + +const fluid::Buffer::Priv& fluid::Buffer::priv() const +{ + return *m_priv; +} + +int fluid::Buffer::y() const +{ + return m_priv->y(); +} + +} // namespace cv::gapi +} // namespace cv diff --git a/modules/gapi/src/backends/fluid/gfluidbuffer_priv.hpp b/modules/gapi/src/backends/fluid/gfluidbuffer_priv.hpp new file mode 100644 index 0000000000..f8b03d10d9 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidbuffer_priv.hpp @@ -0,0 +1,298 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_FLUID_BUFFER_PRIV_HPP +#define OPENCV_GAPI_FLUID_BUFFER_PRIV_HPP + +#include + +#include "opencv2/gapi/fluid/gfluidbuffer.hpp" +#include "opencv2/gapi/own/convert.hpp" // cv::gapi::own::to_ocv + +namespace cv { +namespace gapi { +namespace fluid { + +class BufferStorageWithBorder; + +class BorderHandler +{ +protected: + int m_border_size; + +public: + BorderHandler(int border_size); + virtual ~BorderHandler() = default; + virtual const uint8_t* inLineB(int log_idx, const BufferStorageWithBorder &data, int desc_height) const = 0; + + // Fills border pixels after buffer allocation (if possible (for const border)) + virtual void fillCompileTimeBorder(BufferStorageWithBorder &) const { /* nothing */ } + + // Fills required border lines + virtual void updateBorderPixels(BufferStorageWithBorder& /*data*/, int /*startLine*/, int /*lpi*/) const { /* nothing */ } + + inline int borderSize() const { return m_border_size; } + virtual std::size_t size() const { return 0; } +}; + +template +class BorderHandlerT : public BorderHandler +{ + std::function m_fill_border_row; +public: + BorderHandlerT(int border_size, int data_type); + virtual void updateBorderPixels(BufferStorageWithBorder& data, int startLine, int lpi) const override; + virtual const uint8_t* inLineB(int log_idx, const BufferStorageWithBorder &data, int desc_height) const override; +}; + +template<> +class BorderHandlerT : public BorderHandler +{ + cv::gapi::own::Scalar m_border_value; + cv::Mat m_const_border; + +public: + BorderHandlerT(int border_size, cv::gapi::own::Scalar border_value, int data_type, int desc_width); + virtual const uint8_t* inLineB(int log_idx, const BufferStorageWithBorder &data, int desc_height) const override; + virtual void fillCompileTimeBorder(BufferStorageWithBorder &) const override; + virtual std::size_t size() const override; +}; + +class BufferStorage +{ +protected: + cv::Mat m_data; + +public: + virtual void copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const = 0; + + virtual ~BufferStorage() = default; + + virtual const uint8_t* ptr(int idx) const = 0; + virtual uint8_t* ptr(int idx) = 0; + + inline bool empty() const { return m_data.empty(); } + + inline const cv::Mat& data() const { return m_data; } + inline cv::Mat& data() { return m_data; } + + inline int rows() const { return m_data.rows; } + inline int cols() const { return m_data.cols; } + inline int type() const { return m_data.type(); } + + virtual const uint8_t* inLineB(int log_idx, int desc_height) const = 0; + + // FIXME? remember parent and remove src parameter? + virtual void updateBeforeRead(int startLine, int nLines, const BufferStorage& src) = 0; + virtual void updateAfterWrite(int startLine, int nLines) = 0; + + virtual int physIdx(int logIdx) const = 0; + + virtual size_t size() const = 0; +}; + +class BufferStorageWithoutBorder final : public BufferStorage +{ + bool m_is_virtual = true; + cv::gapi::own::Rect m_roi; + +public: + virtual void copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const override; + + inline virtual const uint8_t* ptr(int idx) const override + { + GAPI_DbgAssert((m_is_virtual && m_roi == cv::gapi::own::Rect{}) || (!m_is_virtual && m_roi != cv::gapi::own::Rect{})); + return m_data.ptr(physIdx(idx), 0); + } + inline virtual uint8_t* ptr(int idx) override + { + GAPI_DbgAssert((m_is_virtual && m_roi == cv::gapi::own::Rect{}) || (!m_is_virtual && m_roi != cv::gapi::own::Rect{})); + return m_data.ptr(physIdx(idx), 0); + } + + inline void attach(const cv::Mat& _data, const cv::gapi::own::Rect& _roi) + { + m_data = _data(cv::gapi::own::to_ocv(_roi)); + m_roi = _roi; + m_is_virtual = false; + } + + void create(int capacity, int desc_width, int type); + + inline virtual const uint8_t* inLineB(int log_idx, int desc_height) const override; + + virtual void updateBeforeRead(int startLine, int nLines, const BufferStorage& src) override; + virtual void updateAfterWrite(int startLine, int nLines) override; + + inline virtual int physIdx(int logIdx) const override { return (logIdx - m_roi.y) % m_data.rows; } + + virtual size_t size() const override; +}; + +class BufferStorageWithBorder final: public BufferStorage +{ + std::unique_ptr m_borderHandler; + +public: + inline int borderSize() const { return m_borderHandler->borderSize(); } + + virtual void copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const override; + + inline virtual const uint8_t* ptr(int idx) const override + { + return m_data.ptr(physIdx(idx), borderSize()); + } + inline virtual uint8_t* ptr(int idx) override + { + return m_data.ptr(physIdx(idx), borderSize()); + } + + void create(int capacity, int desc_width, int type, int border_size, Border border); + + virtual const uint8_t* inLineB(int log_idx, int desc_height) const override; + + virtual void updateBeforeRead(int startLine, int nLines, const BufferStorage &src) override; + virtual void updateAfterWrite(int startLine, int nLines) override; + + inline virtual int physIdx(int logIdx) const override { return logIdx % m_data.rows; } + + virtual size_t size() const override; +}; + +// FIXME: GAPI_EXPORTS is used here only to access internal methods +// like readDone/writeDone in low-level tests +class GAPI_EXPORTS View::Priv +{ + friend class View; +protected: + const Buffer *m_p = nullptr; // FIXME replace with weak_ptr + int m_read_caret = -1; + int m_lines_next_iter = -1; + int m_border_size = -1; + +public: + virtual ~Priv() = default; + // API used by actors/backend + + virtual void prepareToRead() = 0; + + void readDone(int linesRead, int linesForNextIteration); + void reset(int linesForFirstIteration); + + virtual std::size_t size() const = 0; + + // Does the view have enough unread lines for next iteration + bool ready() const; + + // API used (indirectly) by user code + virtual const uint8_t* InLineB(int index) const = 0; +}; + +class ViewPrivWithoutOwnBorder final : public View::Priv +{ +public: + // API used by actors/backend + ViewPrivWithoutOwnBorder(const Buffer *p, int borderSize); + + virtual void prepareToRead() override { /* nothing */ } + + virtual std::size_t size() const override { return 0; } + + // API used (indirectly) by user code + virtual const uint8_t* InLineB(int index) const override; +}; + +class ViewPrivWithOwnBorder final : public View::Priv +{ + BufferStorageWithBorder m_own_storage; + +public: + // API used by actors/backend + ViewPrivWithOwnBorder(const Buffer *p, int lineCapacity, int borderSize, Border border); + + virtual void prepareToRead() override; + virtual std::size_t size() const override; + + // API used (indirectly) by user code + virtual const uint8_t* InLineB(int index) const override; +}; + +void debugBufferPriv(const Buffer& buffer, std::ostream &os); + +// FIXME: GAPI_EXPORTS is used here only to access internal methods +// like readDone/writeDone in low-level tests +class GAPI_EXPORTS Buffer::Priv +{ + int m_line_consumption = -1; + int m_border_size = -1; + int m_skew = -1; + int m_writer_lpi = 1; + + cv::GMatDesc m_desc = cv::GMatDesc{-1,-1,{-1,-1}}; + bool m_is_input = false; + + int m_write_caret = -1; + + std::vector m_views; + + std::unique_ptr m_storage; + + // Coordinate starting from which this buffer is assumed + // to be read (with border not being taken into account) + int m_readStart; + cv::gapi::own::Rect m_roi; + + friend void debugBufferPriv(const Buffer& p, std::ostream &os); + +public: + Priv() = default; + Priv(int read_start, cv::gapi::own::Rect roi); + + inline const BufferStorage& storage() const { return *m_storage.get(); } + + // API used by actors/backend + void init(const cv::GMatDesc &desc, + int line_consumption, + int border_size, + int skew, + int wlpi, + int readStart, + cv::gapi::own::Rect roi); + + void allocate(BorderOpt border); + void bindTo(const cv::Mat &data, bool is_input); + + void addView(const View& view) { m_views.push_back(view); } + + const GMatDesc meta() const { return m_desc; } + + bool full() const; + void writeDone(); + void reset(); + int size() const; + + int linesReady() const; + + inline int y() const { return m_write_caret; } + + inline int writer_lpi() const { return m_writer_lpi; } + + // API used (indirectly) by user code + uint8_t* OutLineB(int index = 0); + int lpi() const; + + inline int readStart() const { return m_readStart; } + inline int writeStart() const { return m_roi.y; } + inline int writeEnd() const { return m_roi.y + m_roi.height; } + inline int outputLines() const { return m_roi.height; } +}; + +} // namespace cv::gapi::fluid +} // namespace cv::gapi +} // namespace cv + +#endif // OPENCV_GAPI_FLUID_BUFFER_PRIV_HPP diff --git a/modules/gapi/src/backends/fluid/gfluidcore.cpp b/modules/gapi/src/backends/fluid/gfluidcore.cpp new file mode 100644 index 0000000000..f6d1e5b7d1 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidcore.cpp @@ -0,0 +1,2121 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/own/assert.hpp" +#include "opencv2/core/traits.hpp" +#include "opencv2/core/hal/intrin.hpp" + +#include "opencv2/gapi/core.hpp" + +#include "opencv2/gapi/fluid/gfluidbuffer.hpp" +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + +#include "gfluidbuffer_priv.hpp" +#include "gfluidbackend.hpp" +#include "gfluidutils.hpp" +#include "gfluidcore.hpp" + +#include +#include +#include + +namespace cv { +namespace gapi { +namespace fluid { + +//--------------------- +// +// Arithmetic functions +// +//--------------------- + +template +static inline DST absdiff(SRC1 x, SRC2 y) +{ + auto result = x > y? x - y: y - x; + return saturate(result, roundf); +} + +template +static inline DST addWeighted(SRC1 src1, SRC2 src2, float alpha, float beta, float gamma) +{ + float dst = src1*alpha + src2*beta + gamma; + return saturate(dst, roundf); +} + +template +static inline DST add(SRC1 x, SRC2 y) +{ + return saturate(x + y, roundf); +} + +template +static inline DST sub(SRC1 x, SRC2 y) +{ + return saturate(x - y, roundf); +} + +template +static inline DST subr(SRC1 x, SRC2 y) +{ + return saturate(y - x, roundf); // reverse: y - x +} + +template +static inline DST mul(SRC1 x, SRC2 y, float scale=1) +{ + auto result = scale * x * y; + return saturate(result, rintf); +} + +template +static inline DST div(SRC1 x, SRC2 y, float scale=1) +{ + // like OpenCV: returns 0, if y=0 + auto result = y? scale * x / y: 0; + return saturate(result, rintf); +} + +template +static inline DST divr(SRC1 x, SRC2 y, float scale=1) +{ + auto result = x? scale * y / x: 0; // reverse: y / x + return saturate(result, rintf); +} + +//--------------------------- +// +// Fluid kernels: addWeighted +// +//--------------------------- + +template +static void run_addweighted(Buffer &dst, const View &src1, const View &src2, + double alpha, double beta, double gamma) +{ + static_assert(std::is_same::value, "wrong types"); + + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + int length = width * chan; + + // NB: assume in/out types are not 64-bits + auto _alpha = static_cast( alpha ); + auto _beta = static_cast( beta ); + auto _gamma = static_cast( gamma ); + + for (int l=0; l < length; l++) + out[l] = addWeighted(in1[l], in2[l], _alpha, _beta, _gamma); +} + +GAPI_FLUID_KERNEL(GFluidAddW, cv::gapi::core::GAddW, false) +{ + static const int Window = 1; + + static void run(const View &src1, double alpha, const View &src2, + double beta, double gamma, int /*dtype*/, + Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_(uchar , ushort, ushort, run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_(uchar , short, short, run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_( short, short, short, run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_(ushort, ushort, ushort, run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_( float, uchar , uchar , run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_( float, ushort, ushort, run_addweighted, dst, src1, src2, alpha, beta, gamma); + BINARY_( float, short, short, run_addweighted, dst, src1, src2, alpha, beta, gamma); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//-------------------------- +// +// Fluid kernels: +, -, *, / +// +//-------------------------- + +enum Arithm { ARITHM_ABSDIFF, ARITHM_ADD, ARITHM_SUBTRACT, ARITHM_MULTIPLY, ARITHM_DIVIDE }; + +template +static void run_arithm(Buffer &dst, const View &src1, const View &src2, Arithm arithm, + double scale=1) +{ + static_assert(std::is_same::value, "wrong types"); + + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + int length = width * chan; + + // NB: assume in/out types are not 64-bits + float _scale = static_cast( scale ); + + switch (arithm) + { + case ARITHM_ABSDIFF: + for (int l=0; l < length; l++) + out[l] = absdiff(in1[l], in2[l]); + break; + case ARITHM_ADD: + for (int l=0; l < length; l++) + out[l] = add(in1[l], in2[l]); + break; + case ARITHM_SUBTRACT: + for (int l=0; l < length; l++) + out[l] = sub(in1[l], in2[l]); + break; + case ARITHM_MULTIPLY: + for (int l=0; l < length; l++) + out[l] = mul(in1[l], in2[l], _scale); + break; + case ARITHM_DIVIDE: + for (int l=0; l < length; l++) + out[l] = div(in1[l], in2[l], _scale); + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported arithmetic operation"); + } +} + +GAPI_FLUID_KERNEL(GFluidAdd, cv::gapi::core::GAdd, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, int /*dtype*/, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_arithm, dst, src1, src2, ARITHM_ADD); + BINARY_(uchar , short, short, run_arithm, dst, src1, src2, ARITHM_ADD); + BINARY_(uchar , float, float, run_arithm, dst, src1, src2, ARITHM_ADD); + BINARY_( short, short, short, run_arithm, dst, src1, src2, ARITHM_ADD); + BINARY_( float, uchar , uchar , run_arithm, dst, src1, src2, ARITHM_ADD); + BINARY_( float, short, short, run_arithm, dst, src1, src2, ARITHM_ADD); + BINARY_( float, float, float, run_arithm, dst, src1, src2, ARITHM_ADD); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidSub, cv::gapi::core::GSub, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, int /*dtype*/, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + BINARY_(uchar , short, short, run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + BINARY_(uchar , float, float, run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + BINARY_( short, short, short, run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + BINARY_( float, uchar , uchar , run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + BINARY_( float, short, short, run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + BINARY_( float, float, float, run_arithm, dst, src1, src2, ARITHM_SUBTRACT); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidMul, cv::gapi::core::GMul, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, double scale, int /*dtype*/, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + BINARY_(uchar , short, short, run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + BINARY_(uchar , float, float, run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + BINARY_( short, short, short, run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + BINARY_( float, uchar , uchar , run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + BINARY_( float, short, short, run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + BINARY_( float, float, float, run_arithm, dst, src1, src2, ARITHM_MULTIPLY, scale); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidDiv, cv::gapi::core::GDiv, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, double scale, int /*dtype*/, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + BINARY_(uchar , short, short, run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + BINARY_(uchar , float, float, run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + BINARY_( short, short, short, run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + BINARY_( float, uchar , uchar , run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + BINARY_( float, short, short, run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + BINARY_( float, float, float, run_arithm, dst, src1, src2, ARITHM_DIVIDE, scale); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidAbsDiff, cv::gapi::core::GAbsDiff, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_arithm, dst, src1, src2, ARITHM_ABSDIFF); + BINARY_(ushort, ushort, ushort, run_arithm, dst, src1, src2, ARITHM_ABSDIFF); + BINARY_( short, short, short, run_arithm, dst, src1, src2, ARITHM_ABSDIFF); + BINARY_( float, float, float, run_arithm, dst, src1, src2, ARITHM_ABSDIFF); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//-------------------------------------- +// +// Fluid kernels: +, -, *, / with Scalar +// +//-------------------------------------- + +static inline v_uint16x8 v_add_16u(const v_uint16x8 &x, const v_uint16x8 &y) { return x + y; } +static inline v_uint16x8 v_sub_16u(const v_uint16x8 &x, const v_uint16x8 &y) { return x - y; } +static inline v_uint16x8 v_subr_16u(const v_uint16x8 &x, const v_uint16x8 &y) { return y - x; } + +static inline v_float32x4 v_add_32f(const v_float32x4 &x, const v_float32x4 &y) { return x + y; } +static inline v_float32x4 v_sub_32f(const v_float32x4 &x, const v_float32x4 &y) { return x - y; } +static inline v_float32x4 v_subr_32f(const v_float32x4 &x, const v_float32x4 &y) { return y - x; } + +static inline int s_add_8u(uchar x, uchar y) { return x + y; } +static inline int s_sub_8u(uchar x, uchar y) { return x - y; } +static inline int s_subr_8u(uchar x, uchar y) { return y - x; } + +static inline float s_add_32f(float x, float y) { return x + y; } +static inline float s_sub_32f(float x, float y) { return x - y; } +static inline float s_subr_32f(float x, float y) { return y - x; } + +// manual SIMD if important case 8UC3 +static void run_arithm_s3(uchar out[], const uchar in[], int width, const uchar scalar[], + v_uint16x8 (*v_op)(const v_uint16x8&, const v_uint16x8&), + int (*s_op)(uchar, uchar)) +{ + int w = 0; + +#if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 x, y, z; + v_load_deinterleave(&in[3*w], x, y, z); + + v_uint16x8 r0, r1; + + v_expand(x, r0, r1); + r0 = v_op(r0, v_setall_u16(scalar[0])); // x + scalar[0] + r1 = v_op(r1, v_setall_u16(scalar[0])); + x = v_pack(r0, r1); + + v_expand(y, r0, r1); + r0 = v_op(r0, v_setall_u16(scalar[1])); // y + scalar[1] + r1 = v_op(r1, v_setall_u16(scalar[1])); + y = v_pack(r0, r1); + + v_expand(z, r0, r1); + r0 = v_op(r0, v_setall_u16(scalar[2])); // z + scalar[2] + r1 = v_op(r1, v_setall_u16(scalar[2])); + z = v_pack(r0, r1); + + v_store_interleave(&out[3*w], x, y, z); + } +#endif + UNUSED(v_op); + for (; w < width; w++) + { + out[3*w ] = saturate( s_op(in[3*w ], scalar[0]) ); + out[3*w + 1] = saturate( s_op(in[3*w + 1], scalar[1]) ); + out[3*w + 2] = saturate( s_op(in[3*w + 2], scalar[2]) ); + } +} + +// manually SIMD if rounding 32F into 8U, single channel +static void run_arithm_s1(uchar out[], const float in[], int width, const float scalar[], + v_float32x4 (*v_op)(const v_float32x4&, const v_float32x4&), + float (*s_op)(float, float)) +{ + int w = 0; + +#if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_float32x4 r0, r1, r2, r3; + r0 = v_load(&in[w ]); + r1 = v_load(&in[w + 4]); + r2 = v_load(&in[w + 8]); + r3 = v_load(&in[w + 12]); + + r0 = v_op(r0, v_setall_f32(scalar[0])); // r + scalar[0] + r1 = v_op(r1, v_setall_f32(scalar[0])); + r2 = v_op(r2, v_setall_f32(scalar[0])); + r3 = v_op(r3, v_setall_f32(scalar[0])); + + v_int32x4 i0, i1, i2, i3; + i0 = v_round(r0); + i1 = v_round(r1); + i2 = v_round(r2); + i3 = v_round(r3); + + v_uint16x8 us0, us1; + us0 = v_pack_u(i0, i1); + us1 = v_pack_u(i2, i3); + + v_uint8x16 uc; + uc = v_pack(us0, us1); + + v_store(&out[w], uc); + } +#endif + UNUSED(v_op); + for (; w < width; w++) + { + out[w] = saturate(s_op(in[w], scalar[0]), std::roundf); + } +} + +static void run_arithm_s_add3(uchar out[], const uchar in[], int width, const uchar scalar[]) +{ + run_arithm_s3(out, in, width, scalar, v_add_16u, s_add_8u); +} + +static void run_arithm_s_sub3(uchar out[], const uchar in[], int width, const uchar scalar[]) +{ + run_arithm_s3(out, in, width, scalar, v_sub_16u, s_sub_8u); +} + +static void run_arithm_s_subr3(uchar out[], const uchar in[], int width, const uchar scalar[]) +{ + run_arithm_s3(out, in, width, scalar, v_subr_16u, s_subr_8u); // reverse: subr +} + +static void run_arithm_s_add1(uchar out[], const float in[], int width, const float scalar[]) +{ + run_arithm_s1(out, in, width, scalar, v_add_32f, s_add_32f); +} + +static void run_arithm_s_sub1(uchar out[], const float in[], int width, const float scalar[]) +{ + run_arithm_s1(out, in, width, scalar, v_sub_32f, s_sub_32f); +} + +static void run_arithm_s_subr1(uchar out[], const float in[], int width, const float scalar[]) +{ + run_arithm_s1(out, in, width, scalar, v_subr_32f, s_subr_32f); // reverse: subr +} + +// manually unroll the inner cycle by channels +template +static void run_arithm_s(DST out[], const SRC in[], int width, int chan, + const SCALAR scalar[4], FUNC func) +{ + if (chan == 4) + { + for (int w=0; w < width; w++) + { + out[4*w + 0] = func(in[4*w + 0], scalar[0]); + out[4*w + 1] = func(in[4*w + 1], scalar[1]); + out[4*w + 2] = func(in[4*w + 2], scalar[2]); + out[4*w + 3] = func(in[4*w + 3], scalar[3]); + } + } + else + if (chan == 3) + { + for (int w=0; w < width; w++) + { + out[3*w + 0] = func(in[3*w + 0], scalar[0]); + out[3*w + 1] = func(in[3*w + 1], scalar[1]); + out[3*w + 2] = func(in[3*w + 2], scalar[2]); + } + } + else + if (chan == 2) + { + for (int w=0; w < width; w++) + { + out[2*w + 0] = func(in[2*w + 0], scalar[0]); + out[2*w + 1] = func(in[2*w + 1], scalar[1]); + } + } + else + if (chan == 1) + { + for (int w=0; w < width; w++) + { + out[w] = func(in[w], scalar[0]); + } + } + else + CV_Error(cv::Error::StsBadArg, "unsupported number of channels"); +} + +template +static void run_arithm_s(Buffer &dst, const View &src, const float scalar[4], Arithm arithm, + float scale=1) +{ + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + // What if we cast the scalar into the SRC type? + const SRC myscal[4] = { static_cast(scalar[0]), static_cast(scalar[1]), + static_cast(scalar[2]), static_cast(scalar[3]) }; + bool usemyscal = (myscal[0] == scalar[0]) && (myscal[1] == scalar[1]) && + (myscal[2] == scalar[2]) && (myscal[3] == scalar[3]); + + switch (arithm) + { + case ARITHM_ABSDIFF: + for (int w=0; w < width; w++) + for (int c=0; c < chan; c++) + out[chan*w + c] = absdiff(in[chan*w + c], scalar[c]); + break; + case ARITHM_ADD: + if (usemyscal) + { + if (std::is_same::value && + std::is_same::value && + chan == 3) + run_arithm_s_add3((uchar*)out, (const uchar*)in, width, (const uchar*)myscal); + else if (std::is_same::value && + std::is_same::value && + chan == 1) + run_arithm_s_add1((uchar*)out, (const float*)in, width, (const float*)myscal); + else + run_arithm_s(out, in, width, chan, myscal, add); + } + else + run_arithm_s(out, in, width, chan, scalar, add); + break; + case ARITHM_SUBTRACT: + if (usemyscal) + { + if (std::is_same::value && + std::is_same::value && + chan == 3) + run_arithm_s_sub3((uchar*)out, (const uchar*)in, width, (const uchar*)myscal); + else if (std::is_same::value && + std::is_same::value && + chan == 1) + run_arithm_s_sub1((uchar*)out, (const float*)in, width, (const float*)myscal); + else + run_arithm_s(out, in, width, chan, myscal, sub); + } + else + run_arithm_s(out, in, width, chan, scalar, sub); + break; + // TODO: optimize miltiplication and division + case ARITHM_MULTIPLY: + for (int w=0; w < width; w++) + for (int c=0; c < chan; c++) + out[chan*w + c] = mul(in[chan*w + c], scalar[c], scale); + break; + case ARITHM_DIVIDE: + for (int w=0; w < width; w++) + for (int c=0; c < chan; c++) + out[chan*w + c] = div(in[chan*w + c], scalar[c], scale); + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported arithmetic operation"); + } +} + +template +static void run_arithm_rs(Buffer &dst, const View &src, const float scalar[4], Arithm arithm, + float scale=1) +{ + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + // What if we cast the scalar into the SRC type? + const SRC myscal[4] = { static_cast(scalar[0]), static_cast(scalar[1]), + static_cast(scalar[2]), static_cast(scalar[3]) }; + bool usemyscal = (myscal[0] == scalar[0]) && (myscal[1] == scalar[1]) && + (myscal[2] == scalar[2]) && (myscal[3] == scalar[3]); + + switch (arithm) + { + case ARITHM_SUBTRACT: + if (usemyscal) + { + if (std::is_same::value && + std::is_same::value && + chan == 3) + run_arithm_s_subr3((uchar*)out, (const uchar*)in, width, (const uchar*)myscal); + else if (std::is_same::value && + std::is_same::value && + chan == 1) + run_arithm_s_subr1((uchar*)out, (const float*)in, width, (const float*)myscal); + else + run_arithm_s(out, in, width, chan, myscal, subr); + } + else + run_arithm_s(out, in, width, chan, scalar, subr); + break; + // TODO: optimize division + case ARITHM_DIVIDE: + for (int w=0; w < width; w++) + for (int c=0; c < chan; c++) + out[chan*w + c] = div(scalar[c], in[chan*w + c], scale); + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported arithmetic operation"); + } +} + +GAPI_FLUID_KERNEL(GFluidAbsDiffC, cv::gapi::core::GAbsDiffC, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &_scalar, Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_s, dst, src, scalar, ARITHM_ABSDIFF); + UNARY_(ushort, ushort, run_arithm_s, dst, src, scalar, ARITHM_ABSDIFF); + UNARY_( short, short, run_arithm_s, dst, src, scalar, ARITHM_ABSDIFF); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidAddC, cv::gapi::core::GAddC, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &_scalar, int /*dtype*/, Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_s, dst, src, scalar, ARITHM_ADD); + UNARY_(uchar , short, run_arithm_s, dst, src, scalar, ARITHM_ADD); + UNARY_(uchar , float, run_arithm_s, dst, src, scalar, ARITHM_ADD); + UNARY_( short, short, run_arithm_s, dst, src, scalar, ARITHM_ADD); + UNARY_( float, uchar , run_arithm_s, dst, src, scalar, ARITHM_ADD); + UNARY_( float, short, run_arithm_s, dst, src, scalar, ARITHM_ADD); + UNARY_( float, float, run_arithm_s, dst, src, scalar, ARITHM_ADD); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidSubC, cv::gapi::core::GSubC, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &_scalar, int /*dtype*/, Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_(uchar , short, run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_(uchar , float, run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( short, short, run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( float, uchar , run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( float, short, run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( float, float, run_arithm_s, dst, src, scalar, ARITHM_SUBTRACT); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidSubRC, cv::gapi::core::GSubRC, false) +{ + static const int Window = 1; + + static void run(const cv::Scalar &_scalar, const View &src, int /*dtype*/, Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_(uchar , short, run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_(uchar , float, run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( short, short, run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( float, uchar , run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( float, short, run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + UNARY_( float, float, run_arithm_rs, dst, src, scalar, ARITHM_SUBTRACT); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidMulC, cv::gapi::core::GMulC, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &_scalar, int /*dtype*/, Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + const float scale = 1.f; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_(uchar , short, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_(uchar , float, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( short, short, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( float, uchar , run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( float, short, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( float, float, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidMulCOld, cv::gapi::core::GMulCOld, false) +{ + static const int Window = 1; + + static void run(const View &src, double _scalar, int /*dtype*/, Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar), + static_cast(_scalar), + static_cast(_scalar), + static_cast(_scalar) + }; + const float scale = 1.f; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_(uchar , short, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_(uchar , float, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( short, short, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( float, uchar , run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( float, short, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + UNARY_( float, float, run_arithm_s, dst, src, scalar, ARITHM_MULTIPLY, scale); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidDivC, cv::gapi::core::GDivC, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &_scalar, double _scale, int /*dtype*/, + Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + const float scale = static_cast(_scale); + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_(uchar , short, run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_(uchar , float, run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( short, short, run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( float, uchar , run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( float, short, run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( float, float, run_arithm_s, dst, src, scalar, ARITHM_DIVIDE, scale); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidDivRC, cv::gapi::core::GDivRC, false) +{ + static const int Window = 1; + + static void run(const cv::Scalar &_scalar, const View &src, double _scale, int /*dtype*/, + Buffer &dst) + { + const float scalar[4] = { + static_cast(_scalar[0]), + static_cast(_scalar[1]), + static_cast(_scalar[2]), + static_cast(_scalar[3]) + }; + const float scale = static_cast(_scale); + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_(uchar , short, run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_(uchar , float, run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( short, short, run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( float, uchar , run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( float, short, run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + UNARY_( float, float, run_arithm_rs, dst, src, scalar, ARITHM_DIVIDE, scale); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//---------------------------- +// +// Fluid math kernels: bitwise +// +//---------------------------- + +enum Bitwise { BW_AND, BW_OR, BW_XOR, BW_NOT }; + +template +static void run_bitwise2(Buffer &dst, const View &src1, const View &src2, Bitwise bitwise) +{ + static_assert(std::is_same::value, "wrong types"); + static_assert(std::is_same::value, "wrong types"); + + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + int length = width * chan; + + switch (bitwise) + { + case BW_AND: + for (int l=0; l < length; l++) + out[l] = in1[l] & in2[l]; + break; + case BW_OR: + for (int l=0; l < length; l++) + out[l] = in1[l] | in2[l]; + break; + case BW_XOR: + for (int l=0; l < length; l++) + out[l] = in1[l] ^ in2[l]; + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported bitwise operation"); + } +} + +template +static void run_bitwise1(Buffer &dst, const View &src, Bitwise bitwise) +{ + static_assert(std::is_same::value, "wrong types"); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + int length = width * chan; + + switch (bitwise) + { + case BW_NOT: + for (int l=0; l < length; l++) + out[l] = ~in[l]; + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported bitwise operation"); + } +} + +GAPI_FLUID_KERNEL(GFluidAnd, cv::gapi::core::GAnd, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_bitwise2, dst, src1, src2, BW_AND); + BINARY_(ushort, ushort, ushort, run_bitwise2, dst, src1, src2, BW_AND); + BINARY_( short, short, short, run_bitwise2, dst, src1, src2, BW_AND); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidOr, cv::gapi::core::GOr, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_bitwise2, dst, src1, src2, BW_OR); + BINARY_(ushort, ushort, ushort, run_bitwise2, dst, src1, src2, BW_OR); + BINARY_( short, short, short, run_bitwise2, dst, src1, src2, BW_OR); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidXor, cv::gapi::core::GXor, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_bitwise2, dst, src1, src2, BW_XOR); + BINARY_(ushort, ushort, ushort, run_bitwise2, dst, src1, src2, BW_XOR); + BINARY_( short, short, short, run_bitwise2, dst, src1, src2, BW_XOR); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidNot, cv::gapi::core::GNot, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_bitwise1, dst, src, BW_NOT); + UNARY_(ushort, ushort, run_bitwise1, dst, src, BW_NOT); + UNARY_( short, short, run_bitwise1, dst, src, BW_NOT); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//------------------- +// +// Fluid kernels: LUT +// +//------------------- + +GAPI_FLUID_KERNEL(GFluidLUT, cv::gapi::core::GLUT, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Mat& lut, Buffer &dst) + { + GAPI_Assert(CV_8U == dst.meta().depth); + GAPI_Assert(CV_8U == src.meta().depth); + + GAPI_DbgAssert(CV_8U == lut.type()); + GAPI_DbgAssert(256 == lut.cols * lut.rows); + GAPI_DbgAssert(dst.length() == src.length()); + GAPI_DbgAssert(dst.meta().chan == src.meta().chan); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + int length = width * chan; + + for (int l=0; l < length; l++) + out[l] = lut.data[ in[l] ]; + } +}; + +//------------------------- +// +// Fluid kernels: convertTo +// +//------------------------- + +template +static void run_convertto(Buffer &dst, const View &src, double _alpha, double _beta) +{ + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + int length = width * chan; + + // NB: don't do this if SRC or DST is 64-bit + auto alpha = static_cast( _alpha ); + auto beta = static_cast( _beta ); + + // compute faster if no alpha no beta + if (alpha == 1 && beta == 0) + { + // manual SIMD if need rounding + if (std::is_integral::value && std::is_floating_point::value) + { + GAPI_Assert(( std::is_same::value )); + + int l = 0; // cycle index + + #if CV_SIMD128 + if (std::is_same::value) + { + for (; l <= length-16; l+=16) + { + v_int32x4 i0, i1, i2, i3; + i0 = v_round( v_load( (float*)& in[l ] ) ); + i1 = v_round( v_load( (float*)& in[l + 4] ) ); + i2 = v_round( v_load( (float*)& in[l + 8] ) ); + i3 = v_round( v_load( (float*)& in[l + 12] ) ); + + v_uint16x8 us0, us1; + us0 = v_pack_u(i0, i1); + us1 = v_pack_u(i2, i3); + + v_uint8x16 uc; + uc = v_pack(us0, us1); + v_store((uchar*)& out[l], uc); + } + } + if (std::is_same::value) + { + for (; l <= length-8; l+=8) + { + v_int32x4 i0, i1; + i0 = v_round( v_load( (float*)& in[l ] ) ); + i1 = v_round( v_load( (float*)& in[l + 4] ) ); + + v_uint16x8 us; + us = v_pack_u(i0, i1); + v_store((ushort*)& out[l], us); + } + } + #endif + + // tail of SIMD cycle + for (; l < length; l++) + { + out[l] = saturate(in[l], rintf); + } + } + else if (std::is_integral::value) // here SRC is integral + { + for (int l=0; l < length; l++) + { + out[l] = saturate(in[l]); + } + } + else // DST is floating-point, SRC is any + { + for (int l=0; l < length; l++) + { + out[l] = static_cast(in[l]); + } + } + } + else // if alpha or beta is non-trivial + { + // TODO: optimize if alpha and beta and data are integral + for (int l=0; l < length; l++) + { + out[l] = saturate(in[l]*alpha + beta, rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidConvertTo, cv::gapi::core::GConvertTo, false) +{ + static const int Window = 1; + + static void run(const View &src, int /*rtype*/, double alpha, double beta, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_convertto, dst, src, alpha, beta); + UNARY_(uchar , ushort, run_convertto, dst, src, alpha, beta); + UNARY_(uchar , float, run_convertto, dst, src, alpha, beta); + UNARY_(ushort, uchar , run_convertto, dst, src, alpha, beta); + UNARY_(ushort, ushort, run_convertto, dst, src, alpha, beta); + UNARY_(ushort, float, run_convertto, dst, src, alpha, beta); + UNARY_( float, uchar , run_convertto, dst, src, alpha, beta); + UNARY_( float, ushort, run_convertto, dst, src, alpha, beta); + UNARY_( float, float, run_convertto, dst, src, alpha, beta); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//----------------------------- +// +// Fluid math kernels: min, max +// +//----------------------------- + +enum Minmax { MM_MIN, MM_MAX }; + +template +static void run_minmax(Buffer &dst, const View &src1, const View &src2, Minmax minmax) +{ + static_assert(std::is_same::value, "wrong types"); + static_assert(std::is_same::value, "wrong types"); + + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + int length = width * chan; + + switch (minmax) + { + case MM_MIN: + for (int l=0; l < length; l++) + out[l] = in1[l] < in2[l]? in1[l]: in2[l]; + break; + case MM_MAX: + for (int l=0; l < length; l++) + out[l] = in1[l] > in2[l]? in1[l]: in2[l]; + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported min/max operation"); + } +} + +GAPI_FLUID_KERNEL(GFluidMin, cv::gapi::core::GMin, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_minmax, dst, src1, src2, MM_MIN); + BINARY_(ushort, ushort, ushort, run_minmax, dst, src1, src2, MM_MIN); + BINARY_( short, short, short, run_minmax, dst, src1, src2, MM_MIN); + BINARY_( float, float, float, run_minmax, dst, src1, src2, MM_MIN); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidMax, cv::gapi::core::GMax, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar , uchar , uchar , run_minmax, dst, src1, src2, MM_MAX); + BINARY_(ushort, ushort, ushort, run_minmax, dst, src1, src2, MM_MAX); + BINARY_( short, short, short, run_minmax, dst, src1, src2, MM_MAX); + BINARY_( float, float, float, run_minmax, dst, src1, src2, MM_MAX); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//----------------------- +// +// Fluid kernels: compare +// +//----------------------- + +enum Compare { CMP_EQ, CMP_NE, CMP_GE, CMP_GT, CMP_LE, CMP_LT }; + +template +static void run_cmp(Buffer &dst, const View &src1, const View &src2, Compare compare) +{ + static_assert(std::is_same::value, "wrong types"); + static_assert(std::is_same::value, "wrong types"); + + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + int length = width * chan; + + switch (compare) + { + case CMP_EQ: + for (int l=0; l < length; l++) + out[l] = in1[l] == in2[l]? 255: 0; + break; + case CMP_NE: + for (int l=0; l < length; l++) + out[l] = in1[l] != in2[l]? 255: 0; + break; + case CMP_GE: + for (int l=0; l < length; l++) + out[l] = in1[l] >= in2[l]? 255: 0; + break; + case CMP_LE: + for (int l=0; l < length; l++) + out[l] = in1[l] <= in2[l]? 255: 0; + break; + case CMP_GT: + for (int l=0; l < length; l++) + out[l] = in1[l] > in2[l]? 255: 0; + break; + case CMP_LT: + for (int l=0; l < length; l++) + out[l] = in1[l] < in2[l]? 255: 0; + break; + default: + CV_Error(cv::Error::StsBadArg, "unsupported compare operation"); + } +} + +GAPI_FLUID_KERNEL(GFluidCmpEQ, cv::gapi::core::GCmpEQ, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar, uchar , uchar , run_cmp, dst, src1, src2, CMP_EQ); + BINARY_(uchar, short, short, run_cmp, dst, src1, src2, CMP_EQ); + BINARY_(uchar, float, float, run_cmp, dst, src1, src2, CMP_EQ); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpNE, cv::gapi::core::GCmpNE, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar, uchar , uchar , run_cmp, dst, src1, src2, CMP_NE); + BINARY_(uchar, short, short, run_cmp, dst, src1, src2, CMP_NE); + BINARY_(uchar, float, float, run_cmp, dst, src1, src2, CMP_NE); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpGE, cv::gapi::core::GCmpGE, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar, uchar , uchar , run_cmp, dst, src1, src2, CMP_GE); + BINARY_(uchar, short, short, run_cmp, dst, src1, src2, CMP_GE); + BINARY_(uchar, float, float, run_cmp, dst, src1, src2, CMP_GE); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpGT, cv::gapi::core::GCmpGT, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar, uchar , uchar , run_cmp, dst, src1, src2, CMP_GT); + BINARY_(uchar, short, short, run_cmp, dst, src1, src2, CMP_GT); + BINARY_(uchar, float, float, run_cmp, dst, src1, src2, CMP_GT); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpLE, cv::gapi::core::GCmpLE, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar, uchar , uchar , run_cmp, dst, src1, src2, CMP_LE); + BINARY_(uchar, short, short, run_cmp, dst, src1, src2, CMP_LE); + BINARY_(uchar, float, float, run_cmp, dst, src1, src2, CMP_LE); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpLT, cv::gapi::core::GCmpLT, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, Buffer &dst) + { + // DST SRC1 SRC2 OP __VA_ARGS__ + BINARY_(uchar, uchar , uchar , run_cmp, dst, src1, src2, CMP_LT); + BINARY_(uchar, short, short, run_cmp, dst, src1, src2, CMP_LT); + BINARY_(uchar, float, float, run_cmp, dst, src1, src2, CMP_LT); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//--------------------- +// +// Compare with GScalar +// +//--------------------- + +template +static void run_cmp(DST out[], const SRC in[], int length, Compare compare, SCALAR s) +{ + switch (compare) + { + case CMP_EQ: + for (int l=0; l < length; l++) + out[l] = in[l] == s? 255: 0; + break; + case CMP_NE: + for (int l=0; l < length; l++) + out[l] = in[l] != s? 255: 0; + break; + case CMP_GE: + for (int l=0; l < length; l++) + out[l] = in[l] >= s? 255: 0; + break; + case CMP_LE: + for (int l=0; l < length; l++) + out[l] = in[l] <= s? 255: 0; + break; + case CMP_GT: + for (int l=0; l < length; l++) + out[l] = in[l] > s? 255: 0; + break; + case CMP_LT: + for (int l=0; l < length; l++) + out[l] = in[l] < s? 255: 0; + break; + default: + CV_Error(cv::Error::StsBadArg, "unsupported compare operation"); + } +} + +template +static void run_cmp(Buffer &dst, const View &src, Compare compare, const cv::Scalar &scalar) +{ + static_assert(std::is_same::value, "wrong types"); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + int length = width * chan; + + // compute faster if scalar rounds to SRC + double d = scalar[0] ; + SRC s = static_cast( scalar[0] ); + + if (s == d) + run_cmp(out, in, length, compare, s); + else + run_cmp(out, in, length, compare, d); +} + +GAPI_FLUID_KERNEL(GFluidCmpEQScalar, cv::gapi::core::GCmpEQScalar, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &scalar, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar, uchar , run_cmp, dst, src, CMP_EQ, scalar); + UNARY_(uchar, short, run_cmp, dst, src, CMP_EQ, scalar); + UNARY_(uchar, float, run_cmp, dst, src, CMP_EQ, scalar); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpNEScalar, cv::gapi::core::GCmpNEScalar, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &scalar, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar, uchar , run_cmp, dst, src, CMP_NE, scalar); + UNARY_(uchar, short, run_cmp, dst, src, CMP_NE, scalar); + UNARY_(uchar, float, run_cmp, dst, src, CMP_NE, scalar); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpGEScalar, cv::gapi::core::GCmpGEScalar, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &scalar, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar, uchar , run_cmp, dst, src, CMP_GE, scalar); + UNARY_(uchar, short, run_cmp, dst, src, CMP_GE, scalar); + UNARY_(uchar, float, run_cmp, dst, src, CMP_GE, scalar); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpGTScalar, cv::gapi::core::GCmpGTScalar, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &scalar, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar, uchar , run_cmp, dst, src, CMP_GT, scalar); + UNARY_(uchar, short, run_cmp, dst, src, CMP_GT, scalar); + UNARY_(uchar, float, run_cmp, dst, src, CMP_GT, scalar); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpLEScalar, cv::gapi::core::GCmpLEScalar, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &scalar, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar, uchar , run_cmp, dst, src, CMP_LE, scalar); + UNARY_(uchar, short, run_cmp, dst, src, CMP_LE, scalar); + UNARY_(uchar, float, run_cmp, dst, src, CMP_LE, scalar); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +GAPI_FLUID_KERNEL(GFluidCmpLTScalar, cv::gapi::core::GCmpLTScalar, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &scalar, Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar, uchar , run_cmp, dst, src, CMP_LT, scalar); + UNARY_(uchar, short, run_cmp, dst, src, CMP_LT, scalar); + UNARY_(uchar, float, run_cmp, dst, src, CMP_LT, scalar); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//------------------------- +// +// Fluid kernels: threshold +// +//------------------------- + +template +static void run_threshold(Buffer &dst, const View &src, const cv::Scalar &thresh, + const cv::Scalar &maxval, + int type) +{ + static_assert(std::is_same::value, "wrong types"); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + int length = width * chan; + + DST thresh_ = saturate(thresh[0], floord); + DST threshd = saturate(thresh[0], roundd); + DST maxvald = saturate(maxval[0], roundd); + + switch (type) + { + case cv::THRESH_BINARY: + for (int l=0; l < length; l++) + out[l] = in[l] > thresh_? maxvald: 0; + break; + case cv::THRESH_BINARY_INV: + for (int l=0; l < length; l++) + out[l] = in[l] > thresh_? 0: maxvald; + break; + case cv::THRESH_TRUNC: + for (int l=0; l < length; l++) + out[l] = in[l] > thresh_? threshd: in[l]; + break; + case cv::THRESH_TOZERO: + for (int l=0; l < length; l++) + out[l] = in[l] > thresh_? in[l]: 0; + break; + case cv::THRESH_TOZERO_INV: + for (int l=0; l < length; l++) + out[l] = in[l] > thresh_? 0: in[l]; + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported threshold type"); + } +} + +GAPI_FLUID_KERNEL(GFluidThreshold, cv::gapi::core::GThreshold, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &thresh, + const cv::Scalar &maxval, + int type, + Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_threshold, dst, src, thresh, maxval, type); + UNARY_(ushort, ushort, run_threshold, dst, src, thresh, maxval, type); + UNARY_( short, short, run_threshold, dst, src, thresh, maxval, type); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//------------------------ +// +// Fluid kernels: in-range +// +//------------------------ + +static void run_inrange3(uchar out[], const uchar in[], int width, + const uchar lower[], const uchar upper[]) +{ + int w = 0; // cycle index + +#if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 i0, i1, i2; + v_load_deinterleave(&in[3*w], i0, i1, i2); + + v_uint8x16 o; + o = (i0 >= v_setall_u8(lower[0])) & (i0 <= v_setall_u8(upper[0])) & + (i1 >= v_setall_u8(lower[1])) & (i1 <= v_setall_u8(upper[1])) & + (i2 >= v_setall_u8(lower[2])) & (i2 <= v_setall_u8(upper[2])); + + v_store(&out[w], o); + } +#endif + + for (; w < width; w++) + { + out[w] = in[3*w ] >= lower[0] && in[3*w ] <= upper[0] && + in[3*w+1] >= lower[1] && in[3*w+1] <= upper[1] && + in[3*w+2] >= lower[2] && in[3*w+2] <= upper[2] ? 255: 0; + } +} + +template +static void run_inrange(Buffer &dst, const View &src, const cv::Scalar &upperb, + const cv::Scalar &lowerb) +{ + static_assert(std::is_same::value, "wrong types"); + static_assert(std::is_integral::value, "wrong types"); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = src.length(); + int chan = src.meta().chan; + GAPI_Assert(dst.meta().chan == 1); + + // for integral input, in[i] >= lower equals in[i] >= ceil(lower) + // so we can optimize compare operations by rounding lower/upper + SRC lower[4], upper[4]; + for (int c=0; c < chan; c++) + { + lower[c] = saturate(lowerb[c], ceild); + upper[c] = saturate(upperb[c], floord); + } + + // manually SIMD for important case if RGB/BGR + if (std::is_same::value && chan==3) + { + run_inrange3((uchar*)out, (const uchar*)in, width, + (const uchar*)lower, (const uchar*)upper); + return; + } + + // TODO: please manually SIMD if multiple channels: + // modern compilers would perfectly vectorize this code if one channel, + // but may need help with de-interleaving channels if RGB/BGR image etc + switch (chan) + { + case 1: + for (int w=0; w < width; w++) + out[w] = in[w] >= lower[0] && in[w] <= upper[0]? 255: 0; + break; + case 2: + for (int w=0; w < width; w++) + out[w] = in[2*w ] >= lower[0] && in[2*w ] <= upper[0] && + in[2*w+1] >= lower[1] && in[2*w+1] <= upper[1] ? 255: 0; + break; + case 3: + for (int w=0; w < width; w++) + out[w] = in[3*w ] >= lower[0] && in[3*w ] <= upper[0] && + in[3*w+1] >= lower[1] && in[3*w+1] <= upper[1] && + in[3*w+2] >= lower[2] && in[3*w+2] <= upper[2] ? 255: 0; + break; + case 4: + for (int w=0; w < width; w++) + out[w] = in[4*w ] >= lower[0] && in[4*w ] <= upper[0] && + in[4*w+1] >= lower[1] && in[4*w+1] <= upper[1] && + in[4*w+2] >= lower[2] && in[4*w+2] <= upper[2] && + in[4*w+3] >= lower[3] && in[4*w+3] <= upper[3] ? 255: 0; + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported number of channels"); + } +} + +GAPI_FLUID_KERNEL(GFluidInRange, cv::gapi::core::GInRange, false) +{ + static const int Window = 1; + + static void run(const View &src, const cv::Scalar &lowerb, const cv::Scalar& upperb, + Buffer &dst) + { + // DST SRC OP __VA_ARGS__ + INRANGE_(uchar, uchar , run_inrange, dst, src, upperb, lowerb); + INRANGE_(uchar, ushort, run_inrange, dst, src, upperb, lowerb); + INRANGE_(uchar, short, run_inrange, dst, src, upperb, lowerb); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//---------------------- +// +// Fluid kernels: select +// +//---------------------- + +// manually vectored function for important case if RGB/BGR image +static void run_select_row3(int width, uchar out[], uchar in1[], uchar in2[], uchar in3[]) +{ + int w = 0; // cycle index + +#if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 a1, b1, c1; + v_uint8x16 a2, b2, c2; + v_uint8x16 mask; + v_uint8x16 a, b, c; + + v_load_deinterleave(&in1[3*w], a1, b1, c1); + v_load_deinterleave(&in2[3*w], a2, b2, c2); + + mask = v_load(&in3[w]); + mask = mask != v_setzero_u8(); + + a = v_select(mask, a1, a2); + b = v_select(mask, b1, b2); + c = v_select(mask, c1, c2); + + v_store_interleave(&out[3*w], a, b, c); + } +#endif + + for (; w < width; w++) + { + out[3*w ] = in3[w]? in1[3*w ]: in2[3*w ]; + out[3*w + 1] = in3[w]? in1[3*w + 1]: in2[3*w + 1]; + out[3*w + 2] = in3[w]? in1[3*w + 2]: in2[3*w + 2]; + } +} + +// parameter chan is compile-time known constant, normally chan=1..4 +template +static void run_select_row(int width, DST out[], SRC1 in1[], SRC2 in2[], SRC3 in3[]) +{ + if (std::is_same::value && chan==3) + { + // manually vectored function for important case if RGB/BGR image + run_select_row3(width, (uchar*)out, (uchar*)in1, (uchar*)in2, (uchar*)in3); + return; + } + + // because `chan` is template parameter, its value is known at compilation time, + // so that modern compilers would efficiently vectorize this cycle if chan==1 + // (if chan>1, compilers may need help with de-interleaving of the channels) + for (int w=0; w < width; w++) + { + for (int c=0; c < chan; c++) + { + out[w*chan + c] = in3[w]? in1[w*chan + c]: in2[w*chan + c]; + } + } +} + +template +static void run_select(Buffer &dst, const View &src1, const View &src2, const View &src3) +{ + static_assert(std::is_same::value, "wrong types"); + static_assert(std::is_same::value, "wrong types"); + static_assert(std::is_same::value, "wrong types"); + + auto *out = dst.OutLine(); + + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + const auto *in3 = src3.InLine(0); + + int width = dst.length(); + int chan = dst.meta().chan; + + switch (chan) + { + case 1: run_select_row<1>(width, out, in1, in2, in3); break; + case 2: run_select_row<2>(width, out, in1, in2, in3); break; + case 3: run_select_row<3>(width, out, in1, in2, in3); break; + case 4: run_select_row<4>(width, out, in1, in2, in3); break; + default: CV_Error(cv::Error::StsBadArg, "unsupported number of channels"); + } +} + +GAPI_FLUID_KERNEL(GFluidSelect, cv::gapi::core::GSelect, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, const View &src3, Buffer &dst) + { + // DST SRC1 SRC2 SRC3 OP __VA_ARGS__ + SELECT_(uchar , uchar , uchar , uchar, run_select, dst, src1, src2, src3); + SELECT_(ushort, ushort, ushort, uchar, run_select, dst, src1, src2, src3); + SELECT_( short, short, short, uchar, run_select, dst, src1, src2, src3); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } +}; + +//---------------------------------------------------- +// +// Fluid kernels: split, merge, polat2cart, cart2polar +// +//---------------------------------------------------- + +GAPI_FLUID_KERNEL(GFluidSplit3, cv::gapi::core::GSplit3, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst1, Buffer &dst2, Buffer &dst3) + { + const auto *in = src.InLine(0); + auto *out1 = dst1.OutLine(); + auto *out2 = dst2.OutLine(); + auto *out3 = dst3.OutLine(); + + GAPI_Assert(3 == src.meta().chan); + int width = src.length(); + + int w = 0; // cycle counter + + #if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 a, b, c; + v_load_deinterleave(&in[3*w], a, b, c); + v_store(&out1[w], a); + v_store(&out2[w], b); + v_store(&out3[w], c); + } + #endif + + for (; w < width; w++) + { + out1[w] = in[3*w ]; + out2[w] = in[3*w + 1]; + out3[w] = in[3*w + 2]; + } + } +}; + +GAPI_FLUID_KERNEL(GFluidSplit4, cv::gapi::core::GSplit4, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst1, Buffer &dst2, Buffer &dst3, Buffer &dst4) + { + const auto *in = src.InLine(0); + auto *out1 = dst1.OutLine(); + auto *out2 = dst2.OutLine(); + auto *out3 = dst3.OutLine(); + auto *out4 = dst4.OutLine(); + + GAPI_Assert(4 == src.meta().chan); + int width = src.length(); + + int w = 0; // cycle counter + + #if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 a, b, c, d; + v_load_deinterleave(&in[4*w], a, b, c, d); + v_store(&out1[w], a); + v_store(&out2[w], b); + v_store(&out3[w], c); + v_store(&out4[w], d); + } + #endif + + for (; w < width; w++) + { + out1[w] = in[4*w ]; + out2[w] = in[4*w + 1]; + out3[w] = in[4*w + 2]; + out4[w] = in[4*w + 3]; + } + } +}; + +GAPI_FLUID_KERNEL(GFluidMerge3, cv::gapi::core::GMerge3, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, const View &src3, Buffer &dst) + { + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + const auto *in3 = src3.InLine(0); + auto *out = dst.OutLine(); + + GAPI_Assert(3 == dst.meta().chan); + int width = dst.length(); + + int w = 0; // cycle counter + + #if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 a, b, c; + a = v_load(&in1[w]); + b = v_load(&in2[w]); + c = v_load(&in3[w]); + v_store_interleave(&out[3*w], a, b, c); + } + #endif + + for (; w < width; w++) + { + out[3*w ] = in1[w]; + out[3*w + 1] = in2[w]; + out[3*w + 2] = in3[w]; + } + } +}; + +GAPI_FLUID_KERNEL(GFluidMerge4, cv::gapi::core::GMerge4, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, const View &src3, const View &src4, + Buffer &dst) + { + const auto *in1 = src1.InLine(0); + const auto *in2 = src2.InLine(0); + const auto *in3 = src3.InLine(0); + const auto *in4 = src4.InLine(0); + auto *out = dst.OutLine(); + + GAPI_Assert(4 == dst.meta().chan); + int width = dst.length(); + + int w = 0; // cycle counter + + #if CV_SIMD128 + for (; w <= width-16; w+=16) + { + v_uint8x16 a, b, c, d; + a = v_load(&in1[w]); + b = v_load(&in2[w]); + c = v_load(&in3[w]); + d = v_load(&in4[w]); + v_store_interleave(&out[4*w], a, b, c, d); + } + #endif + + for (; w < width; w++) + { + out[4*w ] = in1[w]; + out[4*w + 1] = in2[w]; + out[4*w + 2] = in3[w]; + out[4*w + 3] = in4[w]; + } + } +}; + +GAPI_FLUID_KERNEL(GFluidPolarToCart, cv::gapi::core::GPolarToCart, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, bool angleInDegrees, + Buffer &dst1, Buffer &dst2) + { + GAPI_Assert(src1.meta().depth == CV_32F); + GAPI_Assert(src2.meta().depth == CV_32F); + GAPI_Assert(dst1.meta().depth == CV_32F); + GAPI_Assert(dst2.meta().depth == CV_32F); + + const auto * in1 = src1.InLine(0); + const auto * in2 = src2.InLine(0); + auto *out1 = dst1.OutLine(); + auto *out2 = dst2.OutLine(); + + int width = src1.length(); + int chan = src2.meta().chan; + int length = width * chan; + + // SIMD: compiler vectoring! + for (int l=0; l < length; l++) + { + float angle = angleInDegrees? + in2[l] * static_cast(CV_PI / 180): + in2[l]; + float magnitude = in1[l]; + float x = magnitude * std::cos(angle); + float y = magnitude * std::sin(angle); + out1[l] = x; + out2[l] = y; + } + } +}; + +GAPI_FLUID_KERNEL(GFluidCartToPolar, cv::gapi::core::GCartToPolar, false) +{ + static const int Window = 1; + + static void run(const View &src1, const View &src2, bool angleInDegrees, + Buffer &dst1, Buffer &dst2) + { + GAPI_Assert(src1.meta().depth == CV_32F); + GAPI_Assert(src2.meta().depth == CV_32F); + GAPI_Assert(dst1.meta().depth == CV_32F); + GAPI_Assert(dst2.meta().depth == CV_32F); + + const auto * in1 = src1.InLine(0); + const auto * in2 = src2.InLine(0); + auto *out1 = dst1.OutLine(); + auto *out2 = dst2.OutLine(); + + int width = src1.length(); + int chan = src2.meta().chan; + int length = width * chan; + + // SIMD: compiler vectoring! + for (int l=0; l < length; l++) + { + float x = in1[l]; + float y = in2[l]; + float magnitude = std::hypot(y, x); + float angle_rad = std::atan2(y, x); + float angle = angleInDegrees? + angle_rad * static_cast(180 / CV_PI): + angle_rad; + out1[l] = magnitude; + out2[l] = angle; + } + } +}; + +GAPI_FLUID_KERNEL(GFluidResize, cv::gapi::core::GResize, true) +{ + static const int Window = 1; + static const auto Kind = GFluidKernel::Kind::Resize; + + constexpr static const int INTER_RESIZE_COEF_BITS = 11; + constexpr static const int INTER_RESIZE_COEF_SCALE = 1 << INTER_RESIZE_COEF_BITS; + constexpr static const short ONE = INTER_RESIZE_COEF_SCALE; + + struct ResizeUnit + { + short alpha0; + short alpha1; + int s0; + int s1; + }; + + static ResizeUnit map(double ratio, int start, int max, int outCoord) + { + float f = static_cast((outCoord + 0.5f) * ratio - 0.5f); + int s = cvFloor(f); + f -= s; + + ResizeUnit ru; + + ru.s0 = std::max(s - start, 0); + ru.s1 = ((f == 0.0) || s + 1 >= max) ? s - start : s - start + 1; + + ru.alpha0 = saturate_cast((1.0f - f) * INTER_RESIZE_COEF_SCALE); + ru.alpha1 = saturate_cast((f) * INTER_RESIZE_COEF_SCALE); + + return ru; + } + + static void initScratch(const cv::GMatDesc& in, + cv::Size outSz, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer &scratch) + { + CV_Assert(in.depth == CV_8U && in.chan == 3); + + cv::Size scratch_size{static_cast(outSz.width * sizeof(ResizeUnit)), 1}; + + cv::GMatDesc desc; + desc.chan = 1; + desc.depth = CV_8UC1; + desc.size = to_own(scratch_size); + + cv::gapi::fluid::Buffer buffer(desc); + scratch = std::move(buffer); + + ResizeUnit* mapX = scratch.OutLine(); + double hRatio = (double)in.size.width / outSz.width; + + for (int x = 0, w = outSz.width; x < w; x++) + { + mapX[x] = map(hRatio, 0, in.size.width, x); + } + } + + static void resetScratch(cv::gapi::fluid::Buffer& /*scratch*/) + {} + + static void run(const cv::gapi::fluid::View& in, cv::Size /*sz*/, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer& out, cv::gapi::fluid::Buffer &scratch) + { + double vRatio = (double)in.meta().size.height / out.meta().size.height; + auto mapY = map(vRatio, in.y(), in.meta().size.height, out.y()); + + auto beta0 = mapY.alpha0; + auto beta1 = mapY.alpha1; + + const auto src0 = in.InLine (mapY.s0); + const auto src1 = in.InLine (mapY.s1); + + auto dst = out.OutLine(); + + ResizeUnit* mapX = scratch.OutLine(); + + for (int x = 0; x < out.length(); x++) + { + short alpha0 = mapX[x].alpha0; + short alpha1 = mapX[x].alpha1; + int sx0 = mapX[x].s0; + int sx1 = mapX[x].s1; + + int res00 = src0[3*sx0 ]*alpha0 + src0[3*(sx1) ]*alpha1; + int res10 = src1[3*sx0 ]*alpha0 + src1[3*(sx1) ]*alpha1; + + int res01 = src0[3*sx0 + 1]*alpha0 + src0[3*(sx1) + 1]*alpha1; + int res11 = src1[3*sx0 + 1]*alpha0 + src1[3*(sx1) + 1]*alpha1; + + int res02 = src0[3*sx0 + 2]*alpha0 + src0[3*(sx1) + 2]*alpha1; + int res12 = src1[3*sx0 + 2]*alpha0 + src1[3*(sx1) + 2]*alpha1; + + dst[3*x ] = uchar(( ((beta0 * (res00 >> 4)) >> 16) + ((beta1 * (res10 >> 4)) >> 16) + 2)>>2); + dst[3*x + 1] = uchar(( ((beta0 * (res01 >> 4)) >> 16) + ((beta1 * (res11 >> 4)) >> 16) + 2)>>2); + dst[3*x + 2] = uchar(( ((beta0 * (res02 >> 4)) >> 16) + ((beta1 * (res12 >> 4)) >> 16) + 2)>>2); + } + } +}; + +} // namespace fliud +} // namespace gapi +} // namespace cv + +cv::gapi::GKernelPackage cv::gapi::core::fluid::kernels() +{ + using namespace cv::gapi::fluid; + + return cv::gapi::kernels + < GFluidAdd + ,GFluidSub + ,GFluidMul + ,GFluidDiv + ,GFluidAbsDiff + ,GFluidAnd + ,GFluidOr + ,GFluidXor + ,GFluidMin + ,GFluidMax + ,GFluidCmpGT + ,GFluidCmpGE + ,GFluidCmpLE + ,GFluidCmpLT + ,GFluidCmpEQ + ,GFluidCmpNE + ,GFluidAddW + ,GFluidNot + ,GFluidLUT + ,GFluidConvertTo + ,GFluidSplit3 + ,GFluidSplit4 + ,GFluidMerge3 + ,GFluidMerge4 + ,GFluidSelect + ,GFluidPolarToCart + ,GFluidCartToPolar + ,GFluidAddC + ,GFluidSubC + ,GFluidSubRC + ,GFluidMulC + ,GFluidMulCOld + ,GFluidDivC + ,GFluidDivRC + ,GFluidAbsDiffC + ,GFluidCmpGTScalar + ,GFluidCmpGEScalar + ,GFluidCmpLEScalar + ,GFluidCmpLTScalar + ,GFluidCmpEQScalar + ,GFluidCmpNEScalar + ,GFluidThreshold + ,GFluidInRange + ,GFluidResize + #if 0 + ,GFluidMean -- not fluid + ,GFluidSum -- not fluid + ,GFluidNormL1 -- not fluid + ,GFluidNormL2 -- not fluid + ,GFluidNormInf -- not fluid + ,GFluidIntegral -- not fluid + ,GFluidThresholdOT -- not fluid + ,GFluidResize -- not fluid (?) + ,GFluidRemap -- not fluid + ,GFluidFlip -- not fluid + ,GFluidCrop -- not fluid + ,GFluidConcatHor + ,GFluidConcatVert -- not fluid + #endif + >(); +} diff --git a/modules/gapi/src/backends/fluid/gfluidcore.hpp b/modules/gapi/src/backends/fluid/gfluidcore.hpp new file mode 100644 index 0000000000..eeae8d3742 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidcore.hpp @@ -0,0 +1,19 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GFLUIDCORE_HPP +#define OPENCV_GAPI_GFLUIDCORE_HPP + +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + +namespace cv { namespace gapi { namespace core { namespace fluid { + +GAPI_EXPORTS GKernelPackage kernels(); + +}}}} + +#endif // OPENCV_GAPI_GFLUIDCORE_HPP diff --git a/modules/gapi/src/backends/fluid/gfluidimgproc.cpp b/modules/gapi/src/backends/fluid/gfluidimgproc.cpp new file mode 100644 index 0000000000..1dd291ad51 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidimgproc.cpp @@ -0,0 +1,1325 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/own/assert.hpp" +#include "opencv2/core/traits.hpp" +#include "opencv2/imgproc/types_c.h" + +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/imgproc.hpp" + +#include "opencv2/gapi/own/types.hpp" + +#include "opencv2/gapi/fluid/gfluidbuffer.hpp" +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + +#include "gfluidbuffer_priv.hpp" +#include "gfluidbackend.hpp" +#include "gfluidimgproc.hpp" +#include "gfluidutils.hpp" + +#include +#include + +namespace cv { +namespace gapi { +namespace fluid { + +//---------------------------------- +// +// Fluid kernels: RGB2Gray, BGR2Gray +// +//---------------------------------- + +// Y' = 0.299*R' + 0.587*G' + 0.114*B' +// U' = (B' - Y')*0.492 +// V' = (R' - Y')*0.877 +static const float coef_rgb2yuv_bt601[5] = {0.299f, 0.587f, 0.114f, 0.492f, 0.877f}; + +// R' = Y' + 1.140*V' +// G' = Y' - 0.394*U' - 0.581*V' +// B' = Y' + 2.032*U' +static const float coef_yuv2rgb_bt601[4] = {1.140f, -0.394f, -0.581f, 2.032f}; + +static void run_rgb2gray(Buffer &dst, const View &src, float coef_r, float coef_g, float coef_b) +{ + GAPI_Assert(src.meta().depth == CV_8U); + GAPI_Assert(dst.meta().depth == CV_8U); + GAPI_Assert(src.meta().chan == 3); + GAPI_Assert(dst.meta().chan == 1); + GAPI_Assert(src.length() == dst.length()); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + + // TODO: Vectorize for SIMD + for (int w=0; w < width; w++) + { + uchar r = in[3*w ]; + uchar g = in[3*w + 1]; + uchar b = in[3*w + 2]; + float result = coef_r*r + coef_g*g + coef_b*b; + out[w] = saturate(result, roundf); + } +} + +GAPI_FLUID_KERNEL(GFluidRGB2GrayCustom, cv::gapi::imgproc::GRGB2GrayCustom, false) +{ + static const int Window = 1; + + static void run(const View &src, float coef_r, float coef_g, float coef_b, Buffer &dst) + { + run_rgb2gray(dst, src, coef_r, coef_g, coef_b); + } +}; + +GAPI_FLUID_KERNEL(GFluidRGB2Gray, cv::gapi::imgproc::GRGB2Gray, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + float coef_r = coef_rgb2yuv_bt601[0]; + float coef_g = coef_rgb2yuv_bt601[1]; + float coef_b = coef_rgb2yuv_bt601[2]; + run_rgb2gray(dst, src, coef_r, coef_g, coef_b); + } +}; + +GAPI_FLUID_KERNEL(GFluidBGR2Gray, cv::gapi::imgproc::GBGR2Gray, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + float coef_r = coef_rgb2yuv_bt601[0]; + float coef_g = coef_rgb2yuv_bt601[1]; + float coef_b = coef_rgb2yuv_bt601[2]; + run_rgb2gray(dst, src, coef_b, coef_g, coef_r); + } +}; + +//-------------------------------------- +// +// Fluid kernels: RGB-to-YUV, YUV-to-RGB +// +//-------------------------------------- + +static void run_rgb2yuv(Buffer &dst, const View &src, const float coef[5]) +{ + GAPI_Assert(src.meta().depth == CV_8U); + GAPI_Assert(dst.meta().depth == CV_8U); + GAPI_Assert(src.meta().chan == 3); + GAPI_Assert(dst.meta().chan == 3); + GAPI_Assert(src.length() == dst.length()); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + + // TODO: Vectorize for SIMD + for (int w=0; w < width; w++) + { + uchar r = in[3*w ]; + uchar g = in[3*w + 1]; + uchar b = in[3*w + 2]; + float y = coef[0]*r + coef[1]*g + coef[2]*b; + float u = coef[3]*(b - y) + 128; + float v = coef[4]*(r - y) + 128; + out[3*w ] = saturate(y, roundf); + out[3*w + 1] = saturate(u, roundf); + out[3*w + 2] = saturate(v, roundf); + } +} + +static void run_yuv2rgb(Buffer &dst, const View &src, const float coef[4]) +{ + GAPI_Assert(src.meta().depth == CV_8U); + GAPI_Assert(dst.meta().depth == CV_8U); + GAPI_Assert(src.meta().chan == 3); + GAPI_Assert(dst.meta().chan == 3); + GAPI_Assert(src.length() == dst.length()); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + + // TODO: Vectorize for SIMD + for (int w=0; w < width; w++) + { + uchar y = in[3*w ]; + int u = in[3*w + 1] - 128; + int v = in[3*w + 2] - 128; + float r = y + coef[0]*v; + float g = y + coef[1]*u + coef[2]*v; + float b = y + coef[3]*u; + out[3*w ] = saturate(r, roundf); + out[3*w + 1] = saturate(g, roundf); + out[3*w + 2] = saturate(b, roundf); + } +} + +GAPI_FLUID_KERNEL(GFluidRGB2YUV, cv::gapi::imgproc::GRGB2YUV, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + run_rgb2yuv(dst, src, coef_rgb2yuv_bt601); + } +}; + +GAPI_FLUID_KERNEL(GFluidYUV2RGB, cv::gapi::imgproc::GYUV2RGB, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + run_yuv2rgb(dst, src, coef_yuv2rgb_bt601); + } +}; + +//-------------------------------------- +// +// Fluid kernels: RGB-to-Lab, BGR-to-LUV +// +//-------------------------------------- + +enum LabLUV { LL_Lab, LL_LUV }; + +// gamma-correction (inverse) for sRGB, 1/gamma=2.4 for inverse, like for Mac OS (?) +static inline float f_gamma(float x) +{ + return x <= 0.04045f ? x*(1.f/12.92f) : std::pow((x + 0.055f)*(1/1.055f), 2.4f); +} + +// saturate into interval [0, 1] +static inline float clip01(float value) +{ + return value < 0? 0: + value > 1? 1: + value; +} + +static inline void f_rgb2xyz(float R, float G, float B, + float& X, float& Y, float& Z) +{ + X = clip01(0.412453f*R + 0.357580f*G + 0.180423f*B); + Y = clip01(0.212671f*R + 0.715160f*G + 0.072169f*B); + Z = clip01(0.019334f*R + 0.119193f*G + 0.950227f*B); +} + +static inline void f_xyz2lab(float X, float Y, float Z, + float& L, float& a, float& b) +{ + // CIE XYZ values of reference white point for D65 illuminant + static const float Xn = 0.950456f, Yn = 1.f, Zn = 1.088754f; + + // Other coefficients below: + // 7.787f = (29/3)^3/(29*4) + // 0.008856f = (6/29)^3 + // 903.3 = (29/3)^3 + + float x = X/Xn, y = Y/Yn, z = Z/Zn; + + auto f = [](float t){ return t>0.008856f? std::cbrt(t): (7.787f*t + 16.f/116.f); }; + + float fx = f(x), fy = f(y), fz = f(z); + + L = y > 0.008856f ? (116.f*std::cbrt(y) - 16.f) : (903.3f * y); + a = 500.f * (fx - fy); + b = 200.f * (fy - fz); +} + +static inline void f_xyz2luv(float X, float Y, float Z, + float& L, float& u, float& v) +{ + static const float un = 0.19793943f, vn = 0.46831096f; + + float u1 = 4*X / (X + 15*Y + 3*Z); + float v1 = 9*Y / (X + 15*Y + 3*Z); + + L = Y > 0.008856f ? (116.f*std::cbrt(Y) - 16.f) : (903.3f * Y); + u = 13*L * (u1 - un); + v = 13*L * (v1 - vn); +} + +// compile-time parameters: output format (Lab/LUV), +// and position of blue channel in BGR/RGB (0 or 2) +template +static void run_rgb2labluv(Buffer &dst, const View &src) +{ + GAPI_Assert(src.meta().depth == CV_8U); + GAPI_Assert(dst.meta().depth == CV_8U); + GAPI_Assert(src.meta().chan == 3); + GAPI_Assert(dst.meta().chan == 3); + GAPI_Assert(src.length() == dst.length()); + + const auto *in = src.InLine(0); + auto *out = dst.OutLine(); + + int width = dst.length(); + + for (int w=0; w < width; w++) + { + float R, G, B; + B = in[3*w + blue ] / 255.f; + G = in[3*w + 1 ] / 255.f; + R = in[3*w + (2^blue)] / 255.f; + + B = f_gamma( B ); + G = f_gamma( G ); + R = f_gamma( R ); + + float X, Y, Z; + f_rgb2xyz(R, G, B, X, Y, Z); + + // compile-time `if` + if (LL_Lab == labluv) + { + float L, a, b; + f_xyz2lab(X, Y, Z, L, a, b); + + out[3*w ] = saturate(L * 255.f/100, roundf); + out[3*w + 1] = saturate(a + 128, roundf); + out[3*w + 2] = saturate(b + 128, roundf); + } + else if (LL_LUV == labluv) + { + float L, u, v; + f_xyz2luv(X, Y, Z, L, u, v); + + out[3*w ] = saturate( L * 255.f/100, roundf); + out[3*w + 1] = saturate((u + 134) * 255.f/354, roundf); + out[3*w + 2] = saturate((v + 140) * 255.f/262, roundf); + } + else + CV_Error(cv::Error::StsBadArg, "unsupported color conversion");; + } +} + +GAPI_FLUID_KERNEL(GFluidRGB2Lab, cv::gapi::imgproc::GRGB2Lab, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + static const int blue = 2; // RGB: 0=red, 1=green, 2=blue + run_rgb2labluv(dst, src); + } +}; + +GAPI_FLUID_KERNEL(GFluidBGR2LUV, cv::gapi::imgproc::GBGR2LUV, false) +{ + static const int Window = 1; + + static void run(const View &src, Buffer &dst) + { + static const int blue = 0; // BGR: 0=blue, 1=green, 2=red + run_rgb2labluv(dst, src); + } +}; + +//------------------------------- +// +// Fluid kernels: blur, boxFilter +// +//------------------------------- + +static const int maxKernelSize = 9; + +template +static void run_boxfilter(Buffer &dst, const View &src, const cv::Size &kernelSize, + const cv::Point& /* anchor */, bool normalize) +{ + GAPI_Assert(kernelSize.width <= maxKernelSize); + GAPI_Assert(kernelSize.width == kernelSize.height); + + int kernel = kernelSize.width; + int border = (kernel - 1) / 2; + + const SRC *in[ maxKernelSize ]; + DST *out; + + for (int i=0; i < kernel; i++) + { + in[i] = src.InLine(i - border); + } + + out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + GAPI_DbgAssert(chan <= 4); + + for (int w=0; w < width; w++) + { + float sum[4] = {0, 0, 0, 0}; + + for (int i=0; i < kernel; i++) + { + for (int j=0; j < kernel; j++) + { + for (int c=0; c < chan; c++) + sum[c] += in[i][(w + j - border)*chan + c]; + } + } + + for (int c=0; c < chan; c++) + { + float result = normalize? sum[c]/(kernel * kernel) : sum[c]; + + out[w*chan + c] = saturate(result, rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidBlur, cv::gapi::imgproc::GBlur, false) +{ + static const int Window = 3; + + static void run(const View &src, const cv::Size& kernelSize, const cv::Point& anchor, + int /* borderType */, const cv::Scalar& /* borderValue */, Buffer &dst) + { + // TODO: support sizes 3, 5, 7, 9, ... + GAPI_Assert(kernelSize.width == 3 && kernelSize.height == 3); + + // TODO: suport non-trivial anchor + GAPI_Assert(anchor.x == -1 && anchor.y == -1); + + static const bool normalize = true; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_(ushort, ushort, run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_( short, short, run_boxfilter, dst, src, kernelSize, anchor, normalize); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static Border getBorder(const cv::GMatDesc& /* src */, + const cv::Size & /* kernelSize */, + const cv::Point & /* anchor */, + int borderType, + const cv::Scalar & borderValue) + { + return { borderType, borderValue}; + } +}; + +GAPI_FLUID_KERNEL(GFluidBoxFilter, cv::gapi::imgproc::GBoxFilter, false) +{ + static const int Window = 3; + + static void run(const View & src, + int /* ddepth */, + const cv::Size & kernelSize, + const cv::Point & anchor, + bool normalize, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst) + { + // TODO: support sizes 3, 5, 7, 9, ... + GAPI_Assert(kernelSize.width == 3 && kernelSize.height == 3); + + // TODO: suport non-trivial anchor + GAPI_Assert(anchor.x == -1 && anchor.y == -1); + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_(ushort, ushort, run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_( short, short, run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_( float, uchar , run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_( float, ushort, run_boxfilter, dst, src, kernelSize, anchor, normalize); + UNARY_( float, short, run_boxfilter, dst, src, kernelSize, anchor, normalize); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static Border getBorder(const cv::GMatDesc& /* src */, + int /* ddepth */, + const cv::Size & /* kernelSize */, + const cv::Point & /* anchor */, + bool /* normalize */, + int borderType, + const cv::Scalar & borderValue) + { + return { borderType, borderValue}; + } +}; + +//------------------------- +// +// Fluid kernels: sepFilter +// +//------------------------- + +template +static void getKernel(T k[], const cv::Mat& kernel) +{ + GAPI_Assert(kernel.channels() == 1); + + int depth = CV_MAT_DEPTH(kernel.type()); + int cols = kernel.cols; + int rows = kernel.rows; + + switch ( depth ) + { + case CV_8U: + for (int h=0; h < rows; h++) + for (int w=0; w < cols; w++) + k[h*cols + w] = static_cast( kernel.at(h, w) ); + break; + case CV_16U: + for (int h=0; h < rows; h++) + for (int w=0; w < cols; w++) + k[h*cols + w] = static_cast( kernel.at(h, w) ); + break; + case CV_16S: + for (int h=0; h < rows; h++) + for (int w=0; w < cols; w++) + k[h*cols + w] = static_cast( kernel.at(h, w) ); + break; + case CV_32F: + for (int h=0; h < rows; h++) + for (int w=0; w < cols; w++) + k[h*cols + w] = static_cast( kernel.at(h, w) ); + break; + default: CV_Error(cv::Error::StsBadArg, "unsupported kernel type"); + } +} + +template +static void run_sepfilter(Buffer& dst, const View& src, + const float kx[], int kxLen, + const float ky[], int kyLen, + const cv::Point& /* anchor */, + float delta=0) +{ + static const int maxLines = 9; + GAPI_Assert(kyLen <= maxLines); + + const SRC *in[ maxLines ]; + DST *out; + + int border = (kyLen - 1) / 2; + for (int i=0; i < kyLen; i++) + { + in[i] = src.InLine(i - border); + } + + out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + for (int w=0; w < width; w++) + { + // TODO: make this cycle innermost + for (int c=0; c < chan; c++) + { + float sum=0; + + for (int i=0; i < kyLen; i++) + { + float sumi=0; + + for (int j=0; j < kxLen; j++) + { + sumi += in[i][(w + j - border)*chan + c] * kx[j]; + } + + sum += sumi * ky[i]; + } + + float result = sum + delta; + + out[w*chan + c] = saturate(result, rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidSepFilter, cv::gapi::imgproc::GSepFilter, true) +{ + static const int Window = 3; + + static void run(const View& src, + int /* ddepth */, + const cv::Mat& kernX, + const cv::Mat& kernY, + const cv::Point& anchor, + const cv::Scalar& delta_, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst, + Buffer& scratch) + { + // TODO: support non-trivial anchors + GAPI_Assert(anchor.x == -1 && anchor.y == -1); + + // TODO: support kernel heights 3, 5, 7, 9, ... + GAPI_Assert((kernY.rows == 1 || kernY.cols == 1) && (kernY.cols * kernY.rows == 3)); + GAPI_Assert((kernX.rows == 1 || kernX.cols == 1)); + + int kxLen = kernX.rows * kernX.cols; + int kyLen = kernY.rows * kernY.cols; + + float *kx = scratch.OutLine(); + float *ky = kx + kxLen; + + float delta = static_cast(delta_[0]); + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_sepfilter, dst, src, kx, kxLen, ky, kyLen, anchor, delta); + UNARY_(ushort, ushort, run_sepfilter, dst, src, kx, kxLen, ky, kyLen, anchor, delta); + UNARY_( short, short, run_sepfilter, dst, src, kx, kxLen, ky, kyLen, anchor, delta); + UNARY_( float, float, run_sepfilter, dst, src, kx, kxLen, ky, kyLen, anchor, delta); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static void initScratch(const GMatDesc& /* in */, + int /* ddepth */, + const Mat & kernX, + const Mat & kernY, + const Point & /* anchor */, + const Scalar & /* delta */, + int /* borderType */, + const Scalar & /* borderValue */, + Buffer & scratch) + { + int kxLen = kernX.rows * kernX.cols; + int kyLen = kernY.rows * kernY.cols; + + cv::gapi::own::Size bufsize(kxLen + kyLen, 1); + GMatDesc bufdesc = {CV_32F, 1, bufsize}; + Buffer buffer(bufdesc); + scratch = std::move(buffer); + + // FIXME: move to resetScratch stage ? + float *kx = scratch.OutLine(); + float *ky = kx + kxLen; + getKernel(kx, kernX); + getKernel(ky, kernY); + } + + static void resetScratch(Buffer& /* scratch */) + { + } + + static Border getBorder(const cv::GMatDesc& /* src */, + int /* ddepth */, + const cv::Mat& /* kernX */, + const cv::Mat& /* kernY */, + const cv::Point& /* anchor */, + const cv::Scalar& /* delta */, + int borderType, + const cv::Scalar& borderValue) + { + return { borderType, borderValue}; + } +}; + +//---------------------------- +// +// Fluid kernels: gaussianBlur +// +//---------------------------- + +GAPI_FLUID_KERNEL(GFluidGaussBlur, cv::gapi::imgproc::GGaussBlur, true) +{ + // TODO: support kernel height 3, 5, 7, 9, ... + static const int Window = 3; + + static void run(const View & src, + const cv::Size & ksize, + double /* sigmaX */, + double /* sigmaY */, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst, + Buffer& scratch) + { + GAPI_Assert(ksize.height == 3); + + int kxsize = ksize.width; + int kysize = ksize.height; + + auto *kx = scratch.OutLine(); // cached kernX data + auto *ky = kx + kxsize; // cached kernY data + + auto anchor = cv::Point(-1, -1); + float delta = 0.f; + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_sepfilter, dst, src, kx, kxsize, ky, kysize, anchor, delta); + UNARY_(ushort, ushort, run_sepfilter, dst, src, kx, kxsize, ky, kysize, anchor, delta); + UNARY_( short, short, run_sepfilter, dst, src, kx, kxsize, ky, kysize, anchor, delta); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static void initScratch(const GMatDesc& /* in */, + const cv::Size & ksize, + double sigmaX, + double sigmaY, + int /* borderType */, + const cv::Scalar & /* borderValue */, + Buffer & scratch) + { + int kxsize = ksize.width; + int kysize = ksize.height; + + cv::gapi::own::Size bufsize(kxsize + kysize, 1); + GMatDesc bufdesc = {CV_32F, 1, bufsize}; + Buffer buffer(bufdesc); + scratch = std::move(buffer); + + // FIXME: fill buffer at resetScratch stage? + + if (sigmaX == 0) + sigmaX = 0.3 * ((kxsize - 1)/2. - 1) + 0.8; + + if (sigmaY == 0) + sigmaY = sigmaX; + + Mat kernX = getGaussianKernel(kxsize, sigmaX, CV_32F); + + Mat kernY = kernX; + if (sigmaY != sigmaX) + kernY = getGaussianKernel(kysize, sigmaY, CV_32F); + + auto *kx = scratch.OutLine(); + auto *ky = kx + kxsize; + + getKernel(kx, kernX); + getKernel(ky, kernY); + } + + static void resetScratch(Buffer& /* scratch */) + { + } + + static Border getBorder(const cv::GMatDesc& /* src */, + const cv::Size & /* ksize */, + double /* sigmaX */, + double /* sigmaY */, + int borderType, + const cv::Scalar & borderValue) + { + return { borderType, borderValue}; + } +}; + +//--------------------- +// +// Fluid kernels: Sobel +// +//--------------------- + +template +static void run_sobel(Buffer& dst, + const View & src, + float kx[], + float ky[], + int ksize, + float scale=1, + float delta=0) +{ + static const int kmax = 11; + GAPI_Assert(ksize <= kmax); + + const SRC *in[ kmax ]; + DST *out; + + int border = (ksize - 1) / 2; + for (int i=0; i < ksize; i++) + { + in[i] = src.InLine(i - border); + } + + out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + for (int w=0; w < width; w++) + { + // TODO: make this cycle innermost + for (int c=0; c < chan; c++) + { + float sum=0; + + for (int i=0; i < ksize; i++) + { + float sumi=0; + + for (int j=0; j < ksize; j++) + { + sumi += in[i][(w + j - border)*chan + c] * kx[j]; + } + + sum += sumi * ky[i]; + } + + float result = sum*scale + delta; + + out[w*chan + c] = saturate(result, rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidSobel, cv::gapi::imgproc::GSobel, true) +{ + static const int Window = 3; + + static void run(const View & src, + int /* ddepth */, + int /* dx */, + int /* dy */, + int ksize, + double _scale, + double _delta, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst, + Buffer& scratch) + { + // TODO: support kernel height 3, 5, 7, 9, ... + GAPI_Assert(ksize == 3 || ksize == CV_SCHARR); + + if (ksize == CV_SCHARR) + ksize = 3; + + auto *kx = scratch.OutLine(); + auto *ky = kx + ksize; + + auto scale = static_cast(_scale); + auto delta = static_cast(_delta); + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_sobel, dst, src, kx, ky, ksize, scale, delta); + UNARY_(ushort, ushort, run_sobel, dst, src, kx, ky, ksize, scale, delta); + UNARY_( short, short, run_sobel, dst, src, kx, ky, ksize, scale, delta); + UNARY_( float, uchar , run_sobel, dst, src, kx, ky, ksize, scale, delta); + UNARY_( float, ushort, run_sobel, dst, src, kx, ky, ksize, scale, delta); + UNARY_( float, short, run_sobel, dst, src, kx, ky, ksize, scale, delta); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static void initScratch(const GMatDesc& /* in */, + int /* ddepth */, + int dx, + int dy, + int ksize, + double /* scale */, + double /* delta */, + int /* borderType */, + const Scalar & /* borderValue */, + Buffer & scratch) + { + cv::gapi::own::Size bufsize(ksize + ksize, 1); + GMatDesc bufdesc = {CV_32F, 1, bufsize}; + Buffer buffer(bufdesc); + scratch = std::move(buffer); + + // FIXME: move to resetScratch stage ? + auto *kx = scratch.OutLine(); + auto *ky = kx + ksize; + Mat kxmat(1, ksize, CV_32FC1, kx); + Mat kymat(ksize, 1, CV_32FC1, ky); + getDerivKernels(kxmat, kymat, dx, dy, ksize); + } + + static void resetScratch(Buffer& /* scratch */) + { + } + + static Border getBorder(const cv::GMatDesc& /* src */, + int /* ddepth */, + int /* dx */, + int /* dy */, + int /* ksize */, + double /* scale */, + double /* delta */, + int borderType, + const cv::Scalar & borderValue) + { + return { borderType, borderValue}; + } +}; + +//------------------------ +// +// Fluid kernels: filter2D +// +//------------------------ + +template +static void run_filter2d(Buffer& dst, const View& src, + const float k[], int k_rows, int k_cols, + const cv::Point& /* anchor */, + float delta=0) +{ + static const int maxLines = 9; + GAPI_Assert(k_rows <= maxLines); + + const SRC *in[ maxLines ]; + DST *out; + + int border_x = (k_cols - 1) / 2; + int border_y = (k_rows - 1) / 2; + + for (int i=0; i < k_rows; i++) + { + in[i] = src.InLine(i - border_y); + } + + out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + for (int w=0; w < width; w++) + { + // TODO: make this cycle innermost + for (int c=0; c < chan; c++) + { + float sum = 0; + + for (int i=0; i < k_rows; i++) + for (int j=0; j < k_cols; j++) + { + sum += in[i][(w + j - border_x)*chan + c] * k[k_cols*i + j]; + } + + float result = sum + delta; + + out[w*chan + c] = saturate(result, rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidFilter2D, cv::gapi::imgproc::GFilter2D, true) +{ + static const int Window = 3; + + static void run(const View & src, + int /* ddepth */, + const cv::Mat & kernel, + const cv::Point & anchor, + const cv::Scalar& delta_, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst, + Buffer& scratch) + { + // TODO: support non-trivial anchors + GAPI_Assert(anchor.x == -1 && anchor.y == -1); + + // TODO: support kernel heights 3, 5, 7, 9, ... + GAPI_Assert(kernel.rows == 3 && kernel.cols == 3); + + float delta = static_cast(delta_[0]); + + int k_rows = kernel.rows; + int k_cols = kernel.cols; + const float *k = scratch.OutLine(); // copy of kernel.data + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + UNARY_(ushort, ushort, run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + UNARY_( short, short, run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + UNARY_( float, uchar , run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + UNARY_( float, ushort, run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + UNARY_( float, short, run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + UNARY_( float, float, run_filter2d, dst, src, k, k_rows, k_cols, anchor, delta); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static void initScratch(const cv::GMatDesc& /* in */, + int /* ddepth */, + const cv::Mat & kernel, + const cv::Point & /* anchor */, + const cv::Scalar & /* delta */, + int /* borderType */, + const cv::Scalar & /* borderValue */, + Buffer & scratch) + { + cv::gapi::own::Size bufsize(kernel.rows * kernel.cols, 1); + GMatDesc bufdesc = {CV_32F, 1, bufsize}; + Buffer buffer(bufdesc); + scratch = std::move(buffer); + + // FIXME: move to resetScratch stage ? + float *data = scratch.OutLine(); + getKernel(data, kernel); + } + + static void resetScratch(Buffer& /* scratch */) + { + } + + static Border getBorder(const cv::GMatDesc& /* src */, + int /* ddepth */, + const cv::Mat& /* kernel */, + const cv::Point& /* anchor */, + const cv::Scalar& /* delta */, + int borderType, + const cv::Scalar& borderValue) + { + return { borderType, borderValue}; + } +}; + +//----------------------------- +// +// Fluid kernels: erode, dilate +// +//----------------------------- + +enum Morphology { M_ERODE, M_DILATE }; + +template +static void run_morphology( Buffer& dst, + const View & src, + const uchar k[], + int k_rows, + int k_cols, + const cv::Point & /* anchor */, + Morphology morphology) +{ + static const int maxLines = 9; + GAPI_Assert(k_rows <= maxLines); + + const SRC *in[ maxLines ]; + DST *out; + + int border_x = (k_cols - 1) / 2; + int border_y = (k_rows - 1) / 2; + + for (int i=0; i < k_rows; i++) + { + in[i] = src.InLine(i - border_y); + } + + out = dst.OutLine(); + + int width = dst.length(); + int chan = dst.meta().chan; + + for (int w=0; w < width; w++) + { + // TODO: make this cycle innermost + for (int c=0; c < chan; c++) + { + SRC result=0; + if (M_ERODE == morphology) + { + result = std::numeric_limits::max(); + } + else if (M_DILATE == morphology) + { + result = std::numeric_limits::min(); + } + else + CV_Error(cv::Error::StsBadArg, "unsupported morphology operation"); + + for (int i=0; i < k_rows; i++) + for (int j=0; j < k_cols; j++) + { + if ( k[k_cols*i + j] ) + { + if (M_ERODE == morphology) + { + result = std::min(result, in[i][(w + j - border_x)*chan + c]); + } + else if (M_DILATE == morphology) + { + result = std::max(result, in[i][(w + j - border_x)*chan + c]); + } + else + CV_Error(cv::Error::StsBadArg, "unsupported morphology operation"); + } + } + + out[w*chan + c] = saturate(result, rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidErode, cv::gapi::imgproc::GErode, true) +{ + static const int Window = 3; + + static void run(const View & src, + const cv::Mat & kernel, + const cv::Point & anchor, + int iterations, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst, + Buffer& scratch) + { + // TODO: support non-trivial anchors + GAPI_Assert(anchor.x == -1 && anchor.y == -1); + + // TODO: support kernel heights 3, 5, 7, 9, ... + GAPI_Assert(kernel.rows == 3 && kernel.cols == 3); + + // TODO: support iterations > 1 + GAPI_Assert(iterations == 1); + + int k_rows = kernel.rows; + int k_cols = kernel.cols; + + auto *k = scratch.OutLine(); // copy of kernel.data + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_morphology, dst, src, k, k_rows, k_cols, anchor, M_ERODE); + UNARY_(ushort, ushort, run_morphology, dst, src, k, k_rows, k_cols, anchor, M_ERODE); + UNARY_( short, short, run_morphology, dst, src, k, k_rows, k_cols, anchor, M_ERODE); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static void initScratch(const GMatDesc& /* in */, + const Mat & kernel, + const Point & /* anchor */, + int /* iterations */, + int /* borderType */, + const cv::Scalar & /* borderValue */, + Buffer & scratch) + { + int k_rows = kernel.rows; + int k_cols = kernel.cols; + + cv::gapi::own::Size bufsize(k_rows * k_cols, 1); + GMatDesc bufdesc = {CV_8U, 1, bufsize}; + Buffer buffer(bufdesc); + scratch = std::move(buffer); + + // FIXME: move to resetScratch stage ? + auto *k = scratch.OutLine(); + getKernel(k, kernel); + } + + static void resetScratch(Buffer& /* scratch */) + { + } + + static Border getBorder(const cv::GMatDesc& /* src */, + const cv::Mat & /* kernel */, + const cv::Point & /* anchor */, + int /* iterations */, + int borderType, + const cv::Scalar& borderValue) + { + #if 1 + // TODO: saturate borderValue to image type in general case (not only maximal border) + GAPI_Assert(borderType == cv::BORDER_CONSTANT && borderValue[0] == DBL_MAX); + return { borderType, cv::gapi::own::Scalar::all(INT_MAX) }; + #else + return { borderType, borderValue }; + #endif + } +}; + +GAPI_FLUID_KERNEL(GFluidDilate, cv::gapi::imgproc::GDilate, true) +{ + static const int Window = 3; + + static void run(const View & src, + const cv::Mat & kernel, + const cv::Point & anchor, + int iterations, + int /* borderType */, + const cv::Scalar& /* borderValue */, + Buffer& dst, + Buffer& scratch) + { + // TODO: support non-trivial anchors + GAPI_Assert(anchor.x == -1 && anchor.y == -1); + + // TODO: support kernel heights 3, 5, 7, 9, ... + GAPI_Assert(kernel.rows == 3 && kernel.cols == 3); + + // TODO: support iterations > 1 + GAPI_Assert(iterations == 1); + + int k_rows = kernel.rows; + int k_cols = kernel.cols; + + auto *k = scratch.OutLine(); // copy of kernel.data + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_morphology, dst, src, k, k_rows, k_cols, anchor, M_DILATE); + UNARY_(ushort, ushort, run_morphology, dst, src, k, k_rows, k_cols, anchor, M_DILATE); + UNARY_( short, short, run_morphology, dst, src, k, k_rows, k_cols, anchor, M_DILATE); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static void initScratch(const GMatDesc& /* in */, + const Mat & kernel, + const Point & /* anchor */, + int /* iterations */, + int /* borderType */, + const cv::Scalar & /* borderValue */, + Buffer & scratch) + { + int k_rows = kernel.rows; + int k_cols = kernel.cols; + + cv::gapi::own::Size bufsize(k_rows * k_cols, 1); + GMatDesc bufdesc = {CV_8U, 1, bufsize}; + Buffer buffer(bufdesc); + scratch = std::move(buffer); + + // FIXME: move to resetScratch stage ? + auto *k = scratch.OutLine(); + getKernel(k, kernel); + } + + static void resetScratch(Buffer& /* scratch */) + { + } + + static Border getBorder(const cv::GMatDesc& /* src */, + const cv::Mat & /* kernel */, + const cv::Point & /* anchor */, + int /* iterations */, + int borderType, + const cv::Scalar& borderValue) + { + #if 1 + // TODO: fix borderValue for Dilate in general case (not only minimal border) + GAPI_Assert(borderType == cv::BORDER_CONSTANT && borderValue[0] == DBL_MAX); + return { borderType, cv::gapi::own::Scalar::all(INT_MIN) }; + #else + return { borderType, borderValue }; + #endif + } +}; + +//-------------------------- +// +// Fluid kernels: medianBlur +// +//-------------------------- + +template +static void run_medianblur( Buffer& dst, + const View & src, + int ksize) +{ + static const int kmax = 9; + GAPI_Assert(ksize <= kmax); + + const SRC *in[ kmax ]; + DST *out; + + int border = (ksize - 1) / 2; + + for (int i=0; i < ksize; i++) + { + in[i] = src.InLine(i - border); + } + + out = dst.OutLine(0); + + int width = dst.length(); + int chan = dst.meta().chan; + + for (int w=0; w < width; w++) + { + // TODO: make this cycle innermost + for (int c=0; c < chan; c++) + { + SRC neighbours[kmax * kmax]; + + for (int i=0; i < ksize; i++) + for (int j=0; j < ksize; j++) + { + neighbours[i*ksize + j] = in[i][(w + j - border)*chan + c]; + } + + int length = ksize * ksize; + std::nth_element(neighbours, neighbours + length/2, neighbours + length); + + out[w*chan + c] = saturate(neighbours[length/2], rintf); + } + } +} + +GAPI_FLUID_KERNEL(GFluidMedianBlur, cv::gapi::imgproc::GMedianBlur, false) +{ + static const int Window = 3; + + static void run(const View & src, + int ksize, + Buffer& dst) + { + // TODO: support kernel sizes: 3, 5, 7, 9, ... + GAPI_Assert(ksize == 3); + + // DST SRC OP __VA_ARGS__ + UNARY_(uchar , uchar , run_medianblur, dst, src, ksize); + UNARY_(ushort, ushort, run_medianblur, dst, src, ksize); + UNARY_( short, short, run_medianblur, dst, src, ksize); + + CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); + } + + static Border getBorder(const cv::GMatDesc& /* src */, + int /* ksize */) + { + int borderType = cv::BORDER_REPLICATE; + auto borderValue = cv::Scalar(); + return { borderType, borderValue }; + } +}; + +} // namespace fliud +} // namespace gapi +} // namespace cv + +cv::gapi::GKernelPackage cv::gapi::imgproc::fluid::kernels() +{ + using namespace cv::gapi::fluid; + + return cv::gapi::kernels + < GFluidBGR2Gray + , GFluidRGB2Gray + , GFluidRGB2GrayCustom + , GFluidRGB2YUV + , GFluidYUV2RGB + , GFluidRGB2Lab + , GFluidBGR2LUV + , GFluidBlur + , GFluidSepFilter + , GFluidBoxFilter + , GFluidFilter2D + , GFluidErode + , GFluidDilate + , GFluidMedianBlur + , GFluidGaussBlur + , GFluidSobel + #if 0 + , GFluidCanny -- not fluid (?) + , GFluidEqualizeHist -- not fluid + #endif + >(); +} diff --git a/modules/gapi/src/backends/fluid/gfluidimgproc.hpp b/modules/gapi/src/backends/fluid/gfluidimgproc.hpp new file mode 100644 index 0000000000..810f736811 --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidimgproc.hpp @@ -0,0 +1,19 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GFLUIDIMGPROC_HPP +#define OPENCV_GAPI_GFLUIDIMGPROC_HPP + +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + +namespace cv { namespace gapi { namespace imgproc { namespace fluid { + +GAPI_EXPORTS GKernelPackage kernels(); + +}}}} + +#endif // OPENCV_GAPI_GFLUIDIMGPROC_HPP diff --git a/modules/gapi/src/backends/fluid/gfluidutils.hpp b/modules/gapi/src/backends/fluid/gfluidutils.hpp new file mode 100644 index 0000000000..2749525dde --- /dev/null +++ b/modules/gapi/src/backends/fluid/gfluidutils.hpp @@ -0,0 +1,154 @@ +// 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) 2018 Intel Corporation + + +#ifndef GFLUIDUTILS_HPP +#define GFLUIDUTILS_HPP + +#include +#include +#include //UNUSED + +namespace cv { +namespace gapi { +namespace fluid { + +//----------------------------- +// +// Numeric cast with saturation +// +//----------------------------- + +template +static inline DST saturate(SRC x) +{ + // only integral types please! + GAPI_DbgAssert(std::is_integral::value && + std::is_integral::value); + + if (std::is_same::value) + return static_cast(x); + + if (sizeof(DST) > sizeof(SRC)) + return static_cast(x); + + // compiler must recognize this saturation, + // so compile saturate(a + b) with adds + // instruction (e.g.: _mm_adds_epi16 if x86) + return x < std::numeric_limits::min()? + std::numeric_limits::min(): + x > std::numeric_limits::max()? + std::numeric_limits::max(): + static_cast(x); +} + +// Note, that OpenCV rounds differently: +// - like std::round() for add, subtract +// - like std::rint() for multiply, divide +template +static inline DST saturate(SRC x, R round) +{ + if (std::is_floating_point::value) + { + return static_cast(x); + } + else if (std::is_integral::value) + { + GAPI_DbgAssert(std::is_integral::value && + std::is_integral::value); + return saturate(x); + } + else + { + GAPI_DbgAssert(std::is_integral::value && + std::is_floating_point::value); +#ifdef _WIN32 +// Suppress warning about convering x to floating-point +// Note that x is already floating-point at this point +#pragma warning(disable: 4244) +#endif + int ix = static_cast(round(x)); +#ifdef _WIN32 +#pragma warning(default: 4244) +#endif + return saturate(ix); + } +} + +// explicit suffix 'd' for double type +static inline double ceild(double x) { return std::ceil(x); } +static inline double floord(double x) { return std::floor(x); } +static inline double roundd(double x) { return std::round(x); } +static inline double rintd(double x) { return std::rint(x); } + +//-------------------------------- +// +// Macros for mappig of data types +// +//-------------------------------- + +#define UNARY_(DST, SRC, OP, ...) \ + if (cv::DataType::depth == dst.meta().depth && \ + cv::DataType::depth == src.meta().depth) \ + { \ + GAPI_DbgAssert(dst.length() == src.length()); \ + GAPI_DbgAssert(dst.meta().chan == src.meta().chan); \ + \ + OP(__VA_ARGS__); \ + return; \ + } + +// especial unary operation: dst is always 8UC1 image +#define INRANGE_(DST, SRC, OP, ...) \ + if (cv::DataType::depth == dst.meta().depth && \ + cv::DataType::depth == src.meta().depth) \ + { \ + GAPI_DbgAssert(dst.length() == src.length()); \ + GAPI_DbgAssert(dst.meta().chan == 1); \ + \ + OP(__VA_ARGS__); \ + return; \ + } + +#define BINARY_(DST, SRC1, SRC2, OP, ...) \ + if (cv::DataType::depth == dst.meta().depth && \ + cv::DataType::depth == src1.meta().depth && \ + cv::DataType::depth == src2.meta().depth) \ + { \ + GAPI_DbgAssert(dst.length() == src1.length()); \ + GAPI_DbgAssert(dst.length() == src2.length()); \ + \ + GAPI_DbgAssert(dst.meta().chan == src1.meta().chan); \ + GAPI_DbgAssert(dst.meta().chan == src2.meta().chan); \ + \ + OP(__VA_ARGS__); \ + return; \ + } + +// especial ternary operation: src3 has only one channel +#define SELECT_(DST, SRC1, SRC2, SRC3, OP, ...) \ + if (cv::DataType::depth == dst.meta().depth && \ + cv::DataType::depth == src1.meta().depth && \ + cv::DataType::depth == src2.meta().depth && \ + cv::DataType::depth == src3.meta().depth) \ + { \ + GAPI_DbgAssert(dst.length() == src1.length()); \ + GAPI_DbgAssert(dst.length() == src2.length()); \ + GAPI_DbgAssert(dst.length() == src3.length()); \ + \ + GAPI_DbgAssert(dst.meta().chan == src1.meta().chan); \ + GAPI_DbgAssert(dst.meta().chan == src2.meta().chan); \ + GAPI_DbgAssert( 1 == src3.meta().chan); \ + \ + OP(__VA_ARGS__); \ + return; \ + } + +} // namespace fluid +} // namespace gapi +} // namespace cv + +#endif // GFLUIDUTILS_HPP diff --git a/modules/gapi/src/compiler/README.md b/modules/gapi/src/compiler/README.md new file mode 100644 index 0000000000..995aa39774 --- /dev/null +++ b/modules/gapi/src/compiler/README.md @@ -0,0 +1 @@ +This directory contains G-API graph compiler logic. \ No newline at end of file diff --git a/modules/gapi/src/compiler/gcompiled.cpp b/modules/gapi/src/compiler/gcompiled.cpp new file mode 100644 index 0000000000..49f5ed877e --- /dev/null +++ b/modules/gapi/src/compiler/gcompiled.cpp @@ -0,0 +1,131 @@ +// 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) 2018 Intel Corporation + + +#include + +#include "opencv2/gapi/gproto.hpp" // descr_of +#include "opencv2/gapi/gcompiled.hpp" + +#include "compiler/gcompiled_priv.hpp" +#include "backends/common/gbackend.hpp" + +// GCompiled private implementation //////////////////////////////////////////// +void cv::GCompiled::Priv::setup(const GMetaArgs &_metaArgs, + const GMetaArgs &_outMetas, + std::unique_ptr &&_pE) +{ + m_metas = _metaArgs; + m_outMetas = _outMetas; + m_exec = std::move(_pE); +} + +bool cv::GCompiled::Priv::isEmpty() const +{ + return !m_exec; +} + +void cv::GCompiled::Priv::run(cv::gimpl::GRuntimeArgs &&args) +{ + // Strip away types since ADE knows nothing about that + // args will be taken by specific GBackendExecutables + checkArgs(args); + m_exec->run(std::move(args)); +} + +const cv::GMetaArgs& cv::GCompiled::Priv::metas() const +{ + return m_metas; +} + +const cv::GMetaArgs& cv::GCompiled::Priv::outMetas() const +{ + return m_outMetas; +} + +void cv::GCompiled::Priv::checkArgs(const cv::gimpl::GRuntimeArgs &args) const +{ + const auto runtime_metas = descr_of(args.inObjs); + if (runtime_metas != m_metas) + { + util::throw_error(std::logic_error("This object was compiled " + "for different metadata!")); + // FIXME: Add details on what is actually wrong + } +} + +const cv::gimpl::GModel::Graph& cv::GCompiled::Priv::model() const +{ + GAPI_Assert(nullptr != m_exec); + return m_exec->model(); +} + +// GCompiled public implementation ///////////////////////////////////////////// +cv::GCompiled::GCompiled() + : m_priv(new Priv()) +{ +} + +cv::GCompiled::operator bool() const +{ + return !m_priv->isEmpty(); +} + +void cv::GCompiled::operator() (GRunArgs &&ins, GRunArgsP &&outs) +{ + // FIXME: Check that matches the protocol + m_priv->run(cv::gimpl::GRuntimeArgs{std::move(ins),std::move(outs)}); +} + +void cv::GCompiled::operator ()(cv::Mat in, cv::Mat &out) +{ + (*this)(cv::gin(in), cv::gout(out)); +} + +void cv::GCompiled::operator() (cv::Mat in, cv::Scalar &out) +{ + (*this)(cv::gin(in), cv::gout(out)); +} + +void cv::GCompiled::operator() (cv::Mat in1, cv::Mat in2, cv::Mat &out) +{ + (*this)(cv::gin(in1, in2), cv::gout(out)); +} + +void cv::GCompiled::operator() (cv::Mat in1, cv::Mat in2, cv::Scalar &out) +{ + (*this)(cv::gin(in1, in2), cv::gout(out)); +} + +void cv::GCompiled::operator ()(const std::vector &ins, + const std::vector &outs) +{ + GRunArgs call_ins; + GRunArgsP call_outs; + + // Make a temporary copy of vector outs - cv::Mats are copies anyway + auto tmp = outs; + for (const cv::Mat &m : ins) { call_ins.emplace_back(m); } + for ( cv::Mat &m : tmp) { call_outs.emplace_back(&m); } + + (*this)(std::move(call_ins), std::move(call_outs)); +} + +const cv::GMetaArgs& cv::GCompiled::metas() const +{ + return m_priv->metas(); +} + +const cv::GMetaArgs& cv::GCompiled::outMetas() const +{ + return m_priv->outMetas(); +} + + +cv::GCompiled::Priv& cv::GCompiled::priv() +{ + return *m_priv; +} diff --git a/modules/gapi/src/compiler/gcompiled_priv.hpp b/modules/gapi/src/compiler/gcompiled_priv.hpp new file mode 100644 index 0000000000..1bb3bbfdef --- /dev/null +++ b/modules/gapi/src/compiler/gcompiled_priv.hpp @@ -0,0 +1,58 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMPILED_PRIV_HPP +#define OPENCV_GAPI_GCOMPILED_PRIV_HPP + +#include // unique_ptr + +#include "opencv2/gapi/util/optional.hpp" +#include "compiler/gmodel.hpp" +#include "executor/gexecutor.hpp" + +// NB: BTW, GCompiled is the only "public API" class which +// private part (implementaion) is hosted in the "compiler/" module. +// +// This file is here just to keep ADE hidden from the top-level APIs. +// +// As the thing becomes more complex, appropriate API and implementation +// part will be placed to api/ and compiler/ modules respectively. + +namespace cv { + +namespace gimpl +{ + struct GRuntimeArgs; +}; + +// FIXME: GAPI_EXPORTS is here only due to tests and Windows linker issues +class GAPI_EXPORTS GCompiled::Priv +{ + // NB: For now, a GCompiled keeps the original ade::Graph alive. + // If we want to go autonomous, we might to do something with this. + GMetaArgs m_metas; // passed by user + GMetaArgs m_outMetas; // inferred by compiler + std::unique_ptr m_exec; + + void checkArgs(const cv::gimpl::GRuntimeArgs &args) const; + +public: + void setup(const GMetaArgs &metaArgs, + const GMetaArgs &outMetas, + std::unique_ptr &&pE); + bool isEmpty() const; + + void run(cv::gimpl::GRuntimeArgs &&args); + const GMetaArgs& metas() const; + const GMetaArgs& outMetas() const; + + const cv::gimpl::GModel::Graph& model() const; +}; + +} + +#endif // OPENCV_GAPI_GCOMPILED_PRIV_HPP diff --git a/modules/gapi/src/compiler/gcompiler.cpp b/modules/gapi/src/compiler/gcompiler.cpp new file mode 100644 index 0000000000..e1c0d3ace5 --- /dev/null +++ b/modules/gapi/src/compiler/gcompiler.cpp @@ -0,0 +1,273 @@ +// 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) 2018 Intel Corporation + + +#include +#include +#include + +#include // any_of +#include // zip_range, indexed + +#include +#include + +#include "api/gcomputation_priv.hpp" +#include "api/gnode_priv.hpp" // FIXME: why it is here? +#include "api/gproto_priv.hpp" // FIXME: why it is here? +#include "api/gcall_priv.hpp" // FIXME: why it is here? +#include "api/gapi_priv.hpp" // FIXME: why it is here? +#include "api/gbackend_priv.hpp" // Backend basic API (newInstance, etc) + +#include "compiler/gmodel.hpp" +#include "compiler/gmodelbuilder.hpp" +#include "compiler/gcompiler.hpp" +#include "compiler/gcompiled_priv.hpp" +#include "compiler/passes/passes.hpp" + +#include "executor/gexecutor.hpp" +#include "backends/common/gbackend.hpp" + +// +#include "opencv2/gapi/cpu/core.hpp" // Also directly refer to Core +#include "opencv2/gapi/cpu/imgproc.hpp" // ...and Imgproc kernel implementations +// + +#include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend() + +#include "opencv2/core/cvdef.h" +#include "logger.hpp" + +namespace +{ + cv::gapi::GKernelPackage getKernelPackage(cv::GCompileArgs &args) + { + static auto ocv_pkg = combine(cv::gapi::core::cpu::kernels(), + cv::gapi::imgproc::cpu::kernels(), + cv::unite_policy::KEEP); + auto user_pkg = cv::gimpl::getCompileArg(args); + return combine(ocv_pkg, user_pkg.value_or(cv::gapi::GKernelPackage{}), cv::unite_policy::REPLACE); + } + + cv::util::optional getGraphDumpDirectory(cv::GCompileArgs& args) + { + auto dump_info = cv::gimpl::getCompileArg(args); + if (!dump_info.has_value()) + { + const char* path = std::getenv("GRAPH_DUMP_PATH"); + return path + ? cv::util::make_optional(std::string(path)) + : cv::util::optional(); + } + else + { + return cv::util::make_optional(dump_info.value().m_dump_path); + } + } +} // anonymous namespace + + +// GCompiler implementation //////////////////////////////////////////////////// + +cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c, + GMetaArgs &&metas, + GCompileArgs &&args) + : m_c(c), m_metas(std::move(metas)), m_args(std::move(args)) +{ + using namespace std::placeholders; + m_all_kernels = getKernelPackage(m_args); + auto lookup_order = getCompileArg(m_args).value_or(gapi::GLookupOrder()); + auto dump_path = getGraphDumpDirectory(m_args); + + m_e.addPassStage("init"); + m_e.addPass("init", "check_cycles", ade::passes::CheckCycles()); + m_e.addPass("init", "expand_kernels", std::bind(passes::expandKernels, _1, + m_all_kernels)); // NB: package is copied + m_e.addPass("init", "topo_sort", ade::passes::TopologicalSort()); + m_e.addPass("init", "init_islands", passes::initIslands); + m_e.addPass("init", "check_islands", passes::checkIslands); + // TODO: + // - Check basic graph validity (i.e., all inputs are connected) + // - Complex dependencies (i.e. parent-child) unrolling + // - etc, etc, etc + + // Remove GCompoundBackend to avoid calling setupBackend() with it in the list + m_all_kernels.remove(cv::gapi::compound::backend()); + m_e.addPass("init", "resolve_kernels", std::bind(passes::resolveKernels, _1, + std::ref(m_all_kernels), // NB: and not copied here + lookup_order)); + + m_e.addPass("init", "check_islands_content", passes::checkIslandsContent); + m_e.addPassStage("meta"); + m_e.addPass("meta", "initialize", std::bind(passes::initMeta, _1, std::ref(m_metas))); + m_e.addPass("meta", "propagate", passes::inferMeta); + m_e.addPass("meta", "finalize", passes::storeResultingMeta); + // moved to another stage, FIXME: two dumps? + // m_e.addPass("meta", "dump_dot", passes::dumpDotStdout); + + // Special stage for backend-specific transformations + // FIXME: document passes hierarchy and order for backend developers + m_e.addPassStage("transform"); + + m_e.addPassStage("exec"); + m_e.addPass("exec", "fuse_islands", passes::fuseIslands); + m_e.addPass("exec", "sync_islands", passes::syncIslandTags); + + if (dump_path.has_value()) + { + m_e.addPass("exec", "dump_dot", std::bind(passes::dumpGraph, _1, + dump_path.value())); + } + + // Process backends at the last moment (after all G-API passes are added). + ade::ExecutionEngineSetupContext ectx(m_e); + auto backends = m_all_kernels.backends(); + for (auto &b : backends) + { + b.priv().addBackendPasses(ectx); + } +} + +void cv::gimpl::GCompiler::validateInputMeta() +{ + if (m_metas.size() != m_c.priv().m_ins.size()) + { + util::throw_error(std::logic_error + ("COMPILE: GComputation interface / metadata mismatch! " + "(expected " + std::to_string(m_c.priv().m_ins.size()) + ", " + "got " + std::to_string(m_metas.size()) + " meta arguments)")); + } + + const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) { + switch (proto.index()) + { + // FIXME: Auto-generate methods like this from traits: + case GProtoArg::index_of(): + return util::holds_alternative(meta); + + case GProtoArg::index_of(): + return util::holds_alternative(meta); + + case GProtoArg::index_of(): + return util::holds_alternative(meta); + + default: + GAPI_Assert(false); + } + return false; // should never happen + }; + + for (const auto &meta_arg_idx : ade::util::indexed(ade::util::zip(m_metas, m_c.priv().m_ins))) + { + const auto &meta = std::get<0>(ade::util::value(meta_arg_idx)); + const auto &proto = std::get<1>(ade::util::value(meta_arg_idx)); + + if (!meta_matches(meta, proto)) + { + const auto index = ade::util::index(meta_arg_idx); + util::throw_error(std::logic_error + ("GComputation object type / metadata descriptor mismatch " + "(argument " + std::to_string(index) + ")")); + // FIXME: report what we've got and what we've expected + } + } + // All checks are ok +} + +void cv::gimpl::GCompiler::validateOutProtoArgs() +{ + for (const auto &out_pos : ade::util::indexed(m_c.priv().m_outs)) + { + const auto &node = proto::origin_of(ade::util::value(out_pos)).node; + if (node.shape() != cv::GNode::NodeShape::CALL) + { + auto pos = ade::util::index(out_pos); + util::throw_error(std::logic_error + ("Computation output " + std::to_string(pos) + + " is not a result of any operation")); + } + } +} + +cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph() +{ + validateInputMeta(); + validateOutProtoArgs(); + + // Generate ADE graph from expression-based computation + std::unique_ptr pG(new ade::Graph); + ade::Graph& g = *pG; + + GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + cv::gimpl::GModelBuilder builder(g); + auto proto_slots = builder.put(m_c.priv().m_ins, m_c.priv().m_outs); + GAPI_LOG_INFO(NULL, "Generated graph: " << g.nodes().size() << " nodes" << std::endl); + + // Store Computation's protocol in metadata + Protocol p; + std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots; + gm.metadata().set(p); + + return pG; +} + +void cv::gimpl::GCompiler::runPasses(ade::Graph &g) +{ + m_e.runPasses(g); + GAPI_LOG_INFO(NULL, "All compiler passes are successful"); +} + +void cv::gimpl::GCompiler::compileIslands(ade::Graph &g) +{ + GModel::Graph gm(g); + std::shared_ptr gptr(gm.metadata().get().model); + GIslandModel::Graph gim(*gptr); + + // Run topological sort on GIslandModel first + auto pass_ctx = ade::passes::PassContext{*gptr}; + ade::passes::TopologicalSort{}(pass_ctx); + + // Now compile islands + GIslandModel::compileIslands(gim, g, m_args); +} + +cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg) +{ + // This is the final compilation step. Here: + // - An instance of GExecutor is created. Depening on the platform, + // build configuration, etc, a GExecutor may be: + // - a naive single-thread graph interpreter; + // - a std::thread-based thing + // - a TBB-based thing, etc. + // - All this stuff is wrapped into a GCompiled object and returned + // to user. + + // Note: this happens in the last pass ("compile_islands"): + // - Each GIsland of GIslandModel instantiates its own, + // backend-specific executable object + // - Every backend gets a subgraph to execute, and builds + // an execution plan for it (backend-specific execution) + // ...before call to produceCompiled(); + + const auto &outMetas = GModel::ConstGraph(*pg).metadata() + .get().outMeta; + std::unique_ptr pE(new GExecutor(std::move(pg))); + // FIXME: select which executor will be actually used, + // make GExecutor abstract. + + GCompiled compiled; + compiled.priv().setup(m_metas, outMetas, std::move(pE)); + return compiled; +} + +cv::GCompiled cv::gimpl::GCompiler::compile() +{ + std::unique_ptr pG = generateGraph(); + runPasses(*pG); + compileIslands(*pG); + return produceCompiled(std::move(pG)); +} diff --git a/modules/gapi/src/compiler/gcompiler.hpp b/modules/gapi/src/compiler/gcompiler.hpp new file mode 100644 index 0000000000..b369c14d12 --- /dev/null +++ b/modules/gapi/src/compiler/gcompiler.hpp @@ -0,0 +1,51 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GCOMPILER_HPP +#define OPENCV_GAPI_GCOMPILER_HPP + + +#include "opencv2/gapi/gcommon.hpp" +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/gcomputation.hpp" + +#include + +namespace cv { namespace gimpl { + +// FIXME: exported for internal tests only! +class GAPI_EXPORTS GCompiler +{ + const GComputation& m_c; + const GMetaArgs m_metas; + GCompileArgs m_args; + ade::ExecutionEngine m_e; + + cv::gapi::GKernelPackage m_all_kernels; + + void validateInputMeta(); + void validateOutProtoArgs(); + +public: + explicit GCompiler(const GComputation &c, + GMetaArgs &&metas, + GCompileArgs &&args); + + // The method which does everything... + GCompiled compile(); + + // But is actually composed of this: + using GPtr = std::unique_ptr; + GPtr generateGraph(); // Unroll GComputation into a GModel + void runPasses(ade::Graph &g); // Apply all G-API passes on a GModel + void compileIslands(ade::Graph &g); // Instantiate GIslandExecutables in GIslandModel + GCompiled produceCompiled(GPtr &&pg); // Produce GCompiled from processed GModel +}; + +}} + +#endif // OPENCV_GAPI_GCOMPILER_HPP diff --git a/modules/gapi/src/compiler/gislandmodel.cpp b/modules/gapi/src/compiler/gislandmodel.cpp new file mode 100644 index 0000000000..f51709931c --- /dev/null +++ b/modules/gapi/src/compiler/gislandmodel.cpp @@ -0,0 +1,287 @@ +// 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) 2018 Intel Corporation + + +#include +#include +#include + +#include + +#include "api/gbackend_priv.hpp" // GBackend::Priv().compile() +#include "compiler/gmodel.hpp" +#include "compiler/gislandmodel.hpp" +#include "logger.hpp" // GAPI_LOG + +namespace cv { namespace gimpl { + +GIsland::GIsland(const gapi::GBackend &bknd, + ade::NodeHandle op, + util::optional &&user_tag) + : m_backend(bknd) + , m_user_tag(std::move(user_tag)) +{ + m_all.insert(op); + m_in_ops.insert(op); + m_out_ops.insert(op); +} + +// _ because of gcc4.8 wanings on ARM +GIsland::GIsland(const gapi::GBackend &_bknd, + node_set &&_all, + node_set &&_in_ops, + node_set &&_out_ops, + util::optional &&_user_tag) + : m_backend(_bknd) + , m_all(std::move(_all)) + , m_in_ops(std::move(_in_ops)) + , m_out_ops(std::move(_out_ops)) + , m_user_tag(std::move(_user_tag)) +{ +} + +const GIsland::node_set& GIsland::contents() const +{ + return m_all; +} + +const GIsland::node_set& GIsland::in_ops() const +{ + return m_in_ops; +} + +const GIsland::node_set& GIsland::out_ops() const +{ + return m_out_ops; +} + +gapi::GBackend GIsland::backend() const +{ + return m_backend; +} + +bool GIsland::is_user_specified() const +{ + return m_user_tag.has_value(); +} + +void GIsland::debug() const +{ + std::stringstream stream; + stream << name() << " {{\n input ops: "; + for (const auto& nh : m_in_ops) stream << nh << "; "; + stream << "\n output ops: "; + for (const auto& nh : m_out_ops) stream << nh << "; "; + stream << "\n contents: "; + for (const auto& nh : m_all) stream << nh << "; "; + stream << "\n}}" << std::endl; + GAPI_LOG_INFO(NULL, stream.str()); +} + +GIsland::node_set GIsland::consumers(const ade::Graph &g, + const ade::NodeHandle &slot_nh) const +{ + GIslandModel::ConstGraph gim(g); + auto data_nh = gim.metadata(slot_nh).get().original_data_node; + GIsland::node_set result; + for (const auto& in_op : m_in_ops) + { + auto it = std::find(in_op->inNodes().begin(), + in_op->inNodes().end(), + data_nh); + if (it != in_op->inNodes().end()) + result.insert(in_op); + } + return result; +} + +ade::NodeHandle GIsland::producer(const ade::Graph &g, + const ade::NodeHandle &slot_nh) const +{ + GIslandModel::ConstGraph gim(g); + auto data_nh = gim.metadata(slot_nh).get().original_data_node; + for (const auto& out_op : m_out_ops) + { + auto it = std::find(out_op->outNodes().begin(), + out_op->outNodes().end(), + data_nh); + if (it != out_op->outNodes().end()) + return out_op; + } + // Consistency: A GIsland requested for producer() of slot_nh should + // always had the appropriate GModel node handle in its m_out_ops vector. + GAPI_Assert(false); + return ade::NodeHandle(); +} + +std::string GIsland::name() const +{ + if (is_user_specified()) + return m_user_tag.value(); + + std::stringstream ss; + ss << "island_#" << std::hex << static_cast(this); + return ss.str(); +} + +void GIslandModel::generateInitial(GIslandModel::Graph &g, + const ade::Graph &src_graph) +{ + const GModel::ConstGraph src_g(src_graph); + + // Initially GIslandModel is a 1:1 projection from GModel: + // 1) Every GModel::OP becomes a separate GIslandModel::FusedIsland; + // 2) Every GModel::DATA becomes GIslandModel::DataSlot; + // 3) Single-operation FusedIslands are connected with DataSlots in the + // same way as OPs and DATA (edges with the same metadata) + + using node_set = std::unordered_set + < ade::NodeHandle + , ade::HandleHasher + >; + using node_map = std::unordered_map + < ade::NodeHandle + , ade::NodeHandle + , ade::HandleHasher + >; + + node_set all_operations; + node_map data_to_slot; + + // First, list all operations and build create DataSlots in + for (auto src_nh : src_g.nodes()) + { + switch (src_g.metadata(src_nh).get().t) + { + case NodeType::OP: all_operations.insert(src_nh); break; + case NodeType::DATA: data_to_slot[src_nh] = mkSlotNode(g, src_nh); break; + default: GAPI_Assert(false); break; + } + } // for (src_g.nodes) + + // Now put single-op islands and connect it with DataSlots + for (auto src_op_nh : all_operations) + { + auto nh = mkIslandNode(g, src_g.metadata(src_op_nh).get().backend, src_op_nh, src_graph); + for (auto in_edge : src_op_nh->inEdges()) + { + auto src_data_nh = in_edge->srcNode(); + auto isl_slot_nh = data_to_slot.at(src_data_nh); + g.link(isl_slot_nh, nh); // no other data stored yet + } + for (auto out_edge : src_op_nh->outEdges()) + { + auto dst_data_nh = out_edge->dstNode(); + auto isl_slot_nh = data_to_slot.at(dst_data_nh); + g.link(nh, isl_slot_nh); + } + } // for(all_operations) +} + +ade::NodeHandle GIslandModel::mkSlotNode(Graph &g, const ade::NodeHandle &data_nh) +{ + auto nh = g.createNode(); + g.metadata(nh).set(DataSlot{data_nh}); + g.metadata(nh).set(NodeKind{NodeKind::SLOT}); + return nh; +} + +ade::NodeHandle GIslandModel::mkIslandNode(Graph &g, const gapi::GBackend& bknd, const ade::NodeHandle &op_nh, const ade::Graph &orig_g) +{ + const GModel::ConstGraph src_g(orig_g); + util::optional user_tag; + if (src_g.metadata(op_nh).contains()) + { + user_tag = util::make_optional(src_g.metadata(op_nh).get().island); + } + + auto nh = g.createNode(); + std::shared_ptr island(new GIsland(bknd, op_nh, std::move(user_tag))); + g.metadata(nh).set(FusedIsland{std::move(island)}); + g.metadata(nh).set(NodeKind{NodeKind::ISLAND}); + return nh; +} + +ade::NodeHandle GIslandModel::mkIslandNode(Graph &g, std::shared_ptr&& isl) +{ + ade::NodeHandle nh = g.createNode(); + g.metadata(nh).set(cv::gimpl::NodeKind{cv::gimpl::NodeKind::ISLAND}); + g.metadata(nh).set({std::move(isl)}); + return nh; +} + +void GIslandModel::syncIslandTags(Graph &g, ade::Graph &orig_g) +{ + GModel::Graph gm(orig_g); + for (auto nh : g.nodes()) + { + if (NodeKind::ISLAND == g.metadata(nh).get().k) + { + auto island = g.metadata(nh).get().object; + auto isl_tag = island->name(); + for (const auto& orig_nh_inside : island->contents()) + { + gm.metadata(orig_nh_inside).set(Island{isl_tag}); + } + } + } +} + +void GIslandModel::compileIslands(Graph &g, const ade::Graph &orig_g, const GCompileArgs &args) +{ + GModel::ConstGraph gm(orig_g); + + auto original_sorted = gm.metadata().get(); + for (auto nh : g.nodes()) + { + if (NodeKind::ISLAND == g.metadata(nh).get().k) + { + auto island_obj = g.metadata(nh).get().object; + auto island_ops = island_obj->contents(); + + std::vector topo_sorted_list; + ade::util::copy_if(original_sorted.nodes(), + std::back_inserter(topo_sorted_list), + [&](ade::NodeHandle sorted_nh) { + return ade::util::contains(island_ops, sorted_nh); + }); + + auto island_exe = island_obj->backend().priv() + .compile(orig_g, args, topo_sorted_list); + GAPI_Assert(nullptr != island_exe); + g.metadata(nh).set(IslandExec{std::move(island_exe)}); + } + } +} + +ade::NodeHandle GIslandModel::producerOf(const ConstGraph &g, ade::NodeHandle &data_nh) +{ + for (auto nh : g.nodes()) + { + // find a data slot... + if (NodeKind::SLOT == g.metadata(nh).get().k) + { + // which is associated with the given data object... + if (data_nh == g.metadata(nh).get().original_data_node) + { + // which probably has a produrer... + if (0u != nh->inNodes().size()) + { + // ...then the answer is that producer + return nh->inNodes().front(); + } + else return ade::NodeHandle(); // input data object? + // return empty to break the cycle + } + } + } + // No appropriate data slot found - probably, the object has been + // optimized out during fusion + return ade::NodeHandle(); +} + +} // namespace cv +} // namespace gimpl diff --git a/modules/gapi/src/compiler/gislandmodel.hpp b/modules/gapi/src/compiler/gislandmodel.hpp new file mode 100644 index 0000000000..a0e44c902b --- /dev/null +++ b/modules/gapi/src/compiler/gislandmodel.hpp @@ -0,0 +1,184 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GISLANDMODEL_HPP +#define OPENCV_GAPI_GISLANDMODEL_HPP + +#include +#include // shared_ptr + +#include +#include +#include + +#include "opencv2/gapi/util/optional.hpp" +#include "opencv2/gapi/gkernel.hpp" + +#include "compiler/gobjref.hpp" + +namespace cv { namespace gimpl { + + +// FIXME: GAPI_EXPORTS only because of tests! +class GAPI_EXPORTS GIsland +{ +public: + using node_set = std::unordered_set + < ade::NodeHandle + , ade::HandleHasher + >; + + // Initial constructor (constructs a single-op Island) + GIsland(const gapi::GBackend &bknd, + ade::NodeHandle op, + util::optional&& user_tag); + + // Merged constructor + GIsland(const gapi::GBackend &bknd, + node_set &&all, + node_set &&in_ops, + node_set &&out_ops, + util::optional&& user_tag); + + const node_set& contents() const; + const node_set& in_ops() const; + const node_set& out_ops() const; + + std::string name() const; + gapi::GBackend backend() const; + + /** + * Returns all GModel operation node handles which are _reading_ + * from a GModel data object associated (wrapped in) the given + * Slot object. + * + * @param g an ade::Graph with GIslandModel information inside + * @param slot_nh Slot object node handle of interest + * @return a set of GModel operation node handles + */ + node_set consumers(const ade::Graph &g, + const ade::NodeHandle &slot_nh) const; + + /** + * Returns a GModel operation node handle which is _writing_ + * to a GModel data object associated (wrapped in) the given + * Slot object. + * + * @param g an ade::Graph with GIslandModel information inside + * @param slot_nh Slot object node handle of interest + * @return a node handle of original GModel + */ + ade::NodeHandle producer(const ade::Graph &g, + const ade::NodeHandle &slot_nh) const; + + void debug() const; + bool is_user_specified() const; + +protected: + gapi::GBackend m_backend; // backend which handles this Island execution + + node_set m_all; // everything (data + operations) within an island + node_set m_in_ops; // operations island begins with + node_set m_out_ops; // operations island ends with + + // has island name IF specified by user. Empty for internal (inferred) islands + util::optional m_user_tag; +}; + + + +// GIslandExecutable - a backend-specific thing which executes +// contents of an Island +// * Is instantiated by the last step of the Islands fusion procedure; +// * Is orchestrated by a GExecutor instance. +// +class GIslandExecutable +{ +public: + using InObj = std::pair; + using OutObj = std::pair; + + // FIXME: now run() requires full input vector to be available. + // actually, parts of subgraph may execute even if there's no all data + // slots in place. + // TODO: Add partial execution capabilities + virtual void run(std::vector &&input_objs, + std::vector &&output_objs) = 0; + + virtual ~GIslandExecutable() = default; +}; + + + +// Couldn't reuse NodeType here - FIXME unify (move meta to a shared place) +struct NodeKind +{ + static const char *name() { return "NodeKind"; } + enum { ISLAND, SLOT} k; +}; + +// FIXME: Rename to Island (as soon as current GModel::Island is renamed +// to IslandTag). +struct FusedIsland +{ + static const char *name() { return "FusedIsland"; } + std::shared_ptr object; +}; + +struct DataSlot +{ + static const char *name() { return "DataSlot"; } + ade::NodeHandle original_data_node; // direct link to GModel +}; + +struct IslandExec +{ + static const char *name() { return "IslandExecutable"; } + std::shared_ptr object; +}; + +namespace GIslandModel +{ + using Graph = ade::TypedGraph + < NodeKind + , FusedIsland + , DataSlot + , IslandExec + , ade::passes::TopologicalSortData + >; + + // FIXME: derive from TypedGraph + using ConstGraph = ade::ConstTypedGraph + < NodeKind + , FusedIsland + , DataSlot + , IslandExec + , ade::passes::TopologicalSortData + >; + + // Top-level function + void generateInitial(Graph &g, const ade::Graph &src_g); + // "Building blocks" + ade::NodeHandle mkSlotNode(Graph &g, const ade::NodeHandle &data_nh); + ade::NodeHandle mkIslandNode(Graph &g, const gapi::GBackend &bknd, const ade::NodeHandle &op_nh, const ade::Graph &orig_g); + ade::NodeHandle mkIslandNode(Graph &g, std::shared_ptr&& isl); + + // GIslandModel API + void syncIslandTags(Graph &g, ade::Graph &orig_g); + void compileIslands(Graph &g, const ade::Graph &orig_g, const GCompileArgs &args); + + // Debug routines + // producerOf - returns an Island handle which produces given data object + // from the original model (! don't mix with DataSlot) + // FIXME: GAPI_EXPORTS because of tests only! + ade::NodeHandle GAPI_EXPORTS producerOf(const ConstGraph &g, ade::NodeHandle &data_nh); + +} // namespace GIslandModel + +}} // namespace cv::gimpl + +#endif // OPENCV_GAPI_GISLANDMODEL_HPP diff --git a/modules/gapi/src/compiler/gmodel.cpp b/modules/gapi/src/compiler/gmodel.cpp new file mode 100644 index 0000000000..cda1cf1af2 --- /dev/null +++ b/modules/gapi/src/compiler/gmodel.cpp @@ -0,0 +1,245 @@ +// 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) 2018 Intel Corporation + + +#include +#include // used in GModel::log + + +#include // util::indexed +#include + +#include "opencv2/gapi/gproto.hpp" +#include "api/gnode_priv.hpp" +#include "compiler/gobjref.hpp" +#include "compiler/gmodel.hpp" + +namespace cv { namespace gimpl { + +ade::NodeHandle GModel::mkOpNode(GModel::Graph &g, const GKernel &k, const std::vector &args, const std::string &island) +{ + ade::NodeHandle op_h = g.createNode(); + g.metadata(op_h).set(NodeType{NodeType::OP}); + //These extra empty {} are to please GCC (-Wmissing-field-initializers) + g.metadata(op_h).set(Op{k, args, {}, {}, {}}); + if (!island.empty()) + g.metadata(op_h).set(Island{island}); + return op_h; +} + +ade::NodeHandle GModel::mkDataNode(GModel::Graph &g, const GOrigin& origin) +{ + ade::NodeHandle op_h = g.createNode(); + const auto id = g.metadata().get().GetNewId(origin.shape); + g.metadata(op_h).set(NodeType{NodeType::DATA}); + + GMetaArg meta; + Data::Storage storage = Data::Storage::INTERNAL; // By default, all objects are marked INTERNAL + + if (origin.node.shape() == GNode::NodeShape::CONST_BOUNDED) + { + auto value = value_of(origin); + meta = descr_of(value); + storage = Data::Storage::CONST; + g.metadata(op_h).set(ConstValue{value}); + } + g.metadata(op_h).set(Data{origin.shape, id, meta, origin.ctor, storage}); + return op_h; +} + +void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t in_port) +{ + // Check if input is already connected + for (const auto& in_e : opH->inEdges()) + { + GAPI_Assert(g.metadata(in_e).get().port != in_port); + } + + auto &op = g.metadata(opH).get(); + auto &gm = g.metadata(objH).get(); + + // FIXME: check validity using kernel prototype + GAPI_Assert(in_port < op.args.size()); + + ade::EdgeHandle eh = g.link(objH, opH); + g.metadata(eh).set(Input{in_port}); + + // Replace an API object with a REF (G* -> GOBJREF) + op.args[in_port] = cv::GArg(RcDesc{gm.rc, gm.shape, {}}); +} + +void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port) +{ + // FIXME: check validity using kernel prototype + + // Check if output is already connected + for (const auto& out_e : opH->outEdges()) + { + GAPI_Assert(g.metadata(out_e).get().port != out_port); + } + + auto &op = g.metadata(opH).get(); + auto &gm = g.metadata(objH).get(); + + GAPI_Assert(objH->inNodes().size() == 0u); + + ade::EdgeHandle eh = g.link(opH, objH); + g.metadata(eh).set(Output{out_port}); + + // TODO: outs must be allocated according to kernel protocol! + const auto storage_with_port = ade::util::checked_cast(out_port+1); + const auto min_out_size = std::max(op.outs.size(), storage_with_port); + op.outs.resize(min_out_size, RcDesc{-1,GShape::GMAT,{}}); // FIXME: Invalid shape instead? + op.outs[out_port] = RcDesc{gm.rc, gm.shape, {}}; +} + +std::vector GModel::orderedInputs(Graph &g, ade::NodeHandle nh) +{ + std::vector sorted_in_nhs(nh->inEdges().size()); + for (const auto& in_eh : nh->inEdges()) + { + const auto port = g.metadata(in_eh).get().port; + GAPI_Assert(port < sorted_in_nhs.size()); + sorted_in_nhs[port] = in_eh->srcNode(); + } + return sorted_in_nhs; +} + +std::vector GModel::orderedOutputs(Graph &g, ade::NodeHandle nh) +{ + std::vector sorted_out_nhs(nh->outEdges().size()); + for (const auto& out_eh : nh->outEdges()) + { + const auto port = g.metadata(out_eh).get().port; + GAPI_Assert(port < sorted_out_nhs.size()); + sorted_out_nhs[port] = out_eh->dstNode(); + } + return sorted_out_nhs; +} + +void GModel::init(Graph& g) +{ + g.metadata().set(DataObjectCounter()); +} + +void GModel::log(Graph &g, ade::NodeHandle nh, std::string &&msg, ade::NodeHandle updater) +{ + std::string s = std::move(msg); + if (updater != nullptr) + { + std::stringstream fmt; + fmt << " (via " << updater << ")"; + s += fmt.str(); + } + + if (g.metadata(nh).contains()) + { + g.metadata(nh).get().messages.push_back(s); + } + else + { + g.metadata(nh).set(Journal{{s}}); + } +} + +// FIXME: +// Unify with GModel::log(.. ade::NodeHandle ..) +void GModel::log(Graph &g, ade::EdgeHandle eh, std::string &&msg, ade::NodeHandle updater) +{ + std::string s = std::move(msg); + if (updater != nullptr) + { + std::stringstream fmt; + fmt << " (via " << updater << ")"; + s += fmt.str(); + } + + if (g.metadata(eh).contains()) + { + g.metadata(eh).get().messages.push_back(s); + } + else + { + g.metadata(eh).set(Journal{{s}}); + } +} + +ade::NodeHandle GModel::detail::dataNodeOf(const ConstGraph &g, const GOrigin &origin) +{ + // FIXME: Does it still work with graph transformations, e.g. redirectWriter()?? + return g.metadata().get().object_nodes.at(origin); +} + +void GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to) +{ + std::vector ehh(from->outEdges().begin(), from->outEdges().end()); + for (auto e : ehh) + { + auto dst = e->dstNode(); + auto input = g.metadata(e).get(); + g.erase(e); + linkIn(g, dst, to, input.port); + } +} + +void GModel::redirectWriter(Graph &g, ade::NodeHandle from, ade::NodeHandle to) +{ + GAPI_Assert(from->inEdges().size() == 1); + auto e = from->inEdges().front(); + auto op = e->srcNode(); + auto output = g.metadata(e).get(); + g.erase(e); + linkOut(g, op, to, output.port); +} + +GMetaArgs GModel::collectInputMeta(GModel::ConstGraph cg, ade::NodeHandle node) +{ + GAPI_Assert(cg.metadata(node).get().t == NodeType::OP); + GMetaArgs in_meta_args(cg.metadata(node).get().args.size()); + + for (const auto &e : node->inEdges()) + { + const auto& in_data = cg.metadata(e->srcNode()).get(); + in_meta_args[cg.metadata(e).get().port] = in_data.meta; + } + + return in_meta_args; +} + + +ade::EdgeHandle GModel::getInEdgeByPort(const GModel::ConstGraph& cg, + const ade::NodeHandle& nh, + std::size_t in_port) +{ + auto inEdges = nh->inEdges(); + const auto& edge = ade::util::find_if(inEdges, [&](ade::EdgeHandle eh) { + return cg.metadata(eh).get().port == in_port; + }); + GAPI_Assert(edge != inEdges.end()); + return *edge; +} + +GMetaArgs GModel::collectOutputMeta(GModel::ConstGraph cg, ade::NodeHandle node) +{ + GAPI_Assert(cg.metadata(node).get().t == NodeType::OP); + GMetaArgs out_meta_args(cg.metadata(node).get().outs.size()); + + for (const auto &e : node->outEdges()) + { + const auto& out_data = cg.metadata(e->dstNode()).get(); + out_meta_args[cg.metadata(e).get().port] = out_data.meta; + } + + return out_meta_args; +} + +bool GModel::isActive(const GModel::Graph &cg, const cv::gapi::GBackend &backend) +{ + return ade::util::contains(cg.metadata().get().backends, + backend); +} + +}} // cv::gimpl diff --git a/modules/gapi/src/compiler/gmodel.hpp b/modules/gapi/src/compiler/gmodel.hpp new file mode 100644 index 0000000000..003519b82b --- /dev/null +++ b/modules/gapi/src/compiler/gmodel.hpp @@ -0,0 +1,251 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GMODEL_HPP +#define OPENCV_GAPI_GMODEL_HPP + +#include // shared_ptr +#include +#include // std::function + +#include +#include +#include + +// /!\ ATTENTION: +// +// No API includes like GMat, GNode, GCall here! +// This part of the system is API-unaware by its design. +// + +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gkernel.hpp" +#include "api/gapi_priv.hpp" // GShape +#include "api/gproto_priv.hpp" // origin_of +#include "backends/common/gbackend.hpp" + +#include "compiler/gobjref.hpp" +#include "compiler/gislandmodel.hpp" + +namespace cv { namespace gimpl { + +// TODO: Document all metadata types + +struct NodeType +{ + static const char *name() { return "NodeType"; } + enum { OP, DATA } t; +}; + +struct Input +{ + static const char *name() { return "Input"; } + std::size_t port; +}; + +struct Output +{ + static const char *name() { return "Output"; } + std::size_t port; +}; + +struct Op +{ + static const char *name() { return "Op"; } + cv::GKernel k; + std::vector args; // TODO: Introduce a new type for internal args? + std::vector outs; // TODO: Introduce a new type for resource references + + cv::gapi::GBackend backend; + util::any opaque; +}; + +struct Data +{ + static const char *name() { return "Data"; } + + // FIXME: This is a _pure_ duplication of RcDesc now! (except storage) + GShape shape; // FIXME: Probably to be replaced by GMetaArg? + int rc; + GMetaArg meta; + HostCtor ctor; // T-specific helper to deal with unknown types in our code + // FIXME: Why rc+shape+meta is not represented as RcDesc here? + + enum class Storage + { + INTERNAL, // data object is not listed in GComputation protocol + INPUT, // data object is listed in GComputation protocol as Input + OUTPUT, // data object is listed in GComputation protocol as Output + CONST, // data object is constant + }; + Storage storage; +}; + +struct ConstValue +{ + static const char *name() { return "ConstValue"; } + GRunArg arg; +}; + +// This metadata is valid for both DATA and OP kinds of nodes +// FIXME: Rename to IslandTag +struct Island +{ + static const char *name() { return "Island"; } + std::string island; // can be set by user, otherwise is set by fusion +}; + +struct Protocol +{ + static const char *name() { return "Protocol"; } + // TODO: Replace the whole thing with a "Protocol" object + std::vector inputs; + std::vector outputs; + + std::vector in_nhs; + std::vector out_nhs; +}; + +struct OutputMeta +{ + static const char *name() { return "OutputMeta"; } + GMetaArgs outMeta; +}; + +struct Journal +{ + static const char *name() { return "Journal"; } + std::vector messages; +}; + +// The mapping between user-side GMat/GScalar/... objects +// and its appropriate nodes. Can be stored in graph optionally +// (NOT used by any compiler or backends, introspection purposes +// only) +struct Layout +{ + static const char *name() { return "Layout"; } + GOriginMap object_nodes; +}; + +// Unique data object counter (per-type) +class DataObjectCounter +{ +public: + static const char* name() { return "DataObjectCounter"; } + int GetNewId(GShape shape) { return m_next_data_id[shape]++; } +private: + std::unordered_map m_next_data_id; +}; + +// A projected graph of Islands (generated from graph of Operations) +struct IslandModel +{ + static const char* name() { return "IslandModel"; } + std::shared_ptr model; +}; + +// List of backends selected for current graph execution +struct ActiveBackends +{ + static const char *name() { return "ActiveBackends"; } + std::unordered_set backends; +}; + +namespace GModel +{ + using Graph = ade::TypedGraph + < NodeType + , Input + , Output + , Op + , Data + , ConstValue + , Island + , Protocol + , OutputMeta + , Journal + , ade::passes::TopologicalSortData + , DataObjectCounter + , Layout + , IslandModel + , ActiveBackends + >; + + // FIXME: How to define it based on GModel??? + using ConstGraph = ade::ConstTypedGraph + < NodeType + , Input + , Output + , Op + , Data + , ConstValue + , Island + , Protocol + , OutputMeta + , Journal + , ade::passes::TopologicalSortData + , DataObjectCounter + , Layout + , IslandModel + , ActiveBackends + >; + + // User should initialize graph before using it + // GAPI_EXPORTS for tests + GAPI_EXPORTS void init (Graph& g); + + ade::NodeHandle mkOpNode(Graph &g, const GKernel &k, const std::vector& args, const std::string &island); + + // FIXME: change it to take GMeta instead of GShape? + ade::NodeHandle mkDataNode(Graph &g, const GOrigin& origin); + + // Adds a string message to a node. Any node can be subject of log, messages then + // appear in the dumped .dot file.x + void log(Graph &g, ade::NodeHandle op, std::string &&message, ade::NodeHandle updater = ade::NodeHandle()); + void log(Graph &g, ade::EdgeHandle op, std::string &&message, ade::NodeHandle updater = ade::NodeHandle()); + + void linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port); + void linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port); + + // FIXME: Align this GModel API properly, it is a mess now + namespace detail + { + // FIXME: GAPI_EXPORTS only because of tests!!! + GAPI_EXPORTS ade::NodeHandle dataNodeOf(const ConstGraph& g, const GOrigin &origin); + } + template inline ade::NodeHandle dataNodeOf(const ConstGraph& g, T &&t) + { + return detail::dataNodeOf(g, cv::gimpl::proto::origin_of(GProtoArg{t})); + } + + void linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port); + void linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port); + + void redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to); + void redirectWriter (Graph &g, ade::NodeHandle from, ade::NodeHandle to); + + std::vector orderedInputs (Graph &g, ade::NodeHandle nh); + std::vector orderedOutputs(Graph &g, ade::NodeHandle nh); + + // Returns input meta array for given op node + // Array is sparse, as metadata for non-gapi input objects is empty + // TODO: + // Cover with tests!! + GMetaArgs collectInputMeta(GModel::ConstGraph cg, ade::NodeHandle node); + GMetaArgs collectOutputMeta(GModel::ConstGraph cg, ade::NodeHandle node); + + ade::EdgeHandle getInEdgeByPort(const GModel::ConstGraph& cg, const ade::NodeHandle& nh, std::size_t in_port); + + // Returns true if the given backend participates in the execution + bool isActive(const GModel::Graph &cg, const cv::gapi::GBackend &backend); +} // namespace GModel + + +}} // namespace cv::gimpl + +#endif // OPENCV_GAPI_GMODEL_HPP diff --git a/modules/gapi/src/compiler/gmodelbuilder.cpp b/modules/gapi/src/compiler/gmodelbuilder.cpp new file mode 100644 index 0000000000..90a73bd6b2 --- /dev/null +++ b/modules/gapi/src/compiler/gmodelbuilder.cpp @@ -0,0 +1,303 @@ +// 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) 2018 Intel Corporation + + +//////////////////////////////////////////////////////////////////////////////// +// +// FIXME: "I personally hate this file" +// - Dmitry +// +//////////////////////////////////////////////////////////////////////////////// +#include // tuple +#include // stack +#include // vector +#include // unordered_set +#include // is_same + +#include // util::indexed + +#include "api/gapi_priv.hpp" // GOrigin +#include "api/gproto_priv.hpp" // descriptor_of and other GProtoArg-related +#include "api/gcall_priv.hpp" +#include "api/gnode_priv.hpp" + +#include "compiler/gmodelbuilder.hpp" + +namespace { + + +// TODO: move to helpers and cover with internal tests? +template struct GVisited +{ + typedef std::unordered_set VTs; + + bool visited(const T& t) const { return m_visited.find(t) != m_visited.end(); } + void visit (const T& t) { m_visited.insert(t); } + const VTs& visited() const { return m_visited; } + +private: + VTs m_visited; +}; + +template struct GVisitedTracker: protected GVisited +{ + typedef std::vector TUs; + + void visit(const T& t, const U& u) { GVisited::visit(t); m_tracked.push_back(u); } + const TUs& tracked() const { return m_tracked; } + using GVisited::visited; + +private: + TUs m_tracked; +}; + +} // namespace + + +cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins, + const GProtoArgs &outs) +{ + // FIXME: Who's gonna check if ins/outs are not EMPTY? + // FIXME: operator== for GObjects? (test if the same object or not) + using GObjId = const cv::GOrigin*; + + GVisitedTracker ops; + GVisited reached_sources; + cv::GOriginSet origins; + + // Cache input argument objects for a faster look-up + // While the only reliable way to identify a Data object is Origin + // (multiple data objects may refer to the same Origin as result of + // multuple yield() calls), input objects can be uniquely identified + // by its `priv` address. Here we rely on this to verify if the expression + // we unroll actually matches the protocol specified to us by user. + std::unordered_set in_objs_p; + for (const auto& in_obj : ins) + { + // Objects are guarnateed to remain alive while this method + // is working, so it is safe to keep pointers here and below + in_objs_p.insert(&proto::origin_of(in_obj)); + } + + // Recursive expression traversal + std::stack data_objs(std::deque(outs.begin(), outs.end())); + while (!data_objs.empty()) + { + const auto obj = data_objs.top(); + const auto &obj_p = proto::origin_of(obj); + data_objs.pop(); + + const auto &origin = obj_p; + origins.insert(origin); // TODO: Put Object description here later on + + // If this Object is listed in the protocol, don't dive deeper (even + // if it is in fact a result of operation). Our computation is + // bounded by this data slot, so terminate this recursion path early. + if (in_objs_p.find(&obj_p) != in_objs_p.end()) + { + reached_sources.visit(&obj_p); + continue; + } + + const cv::GNode &node = origin.node; + switch (node.shape()) + { + case cv::GNode::NodeShape::EMPTY: + // TODO: Own exception type? + util::throw_error(std::logic_error("Empty node reached!")); + break; + + case cv::GNode::NodeShape::PARAM: + case cv::GNode::NodeShape::CONST_BOUNDED: + // No preceding operation to this data object - so the data object is either a GComputation + // parameter or a constant (compile-time) value + // Record it to check if protocol matches expression tree later + if (!reached_sources.visited(&obj_p)) + reached_sources.visit(&obj_p); + break; + + case cv::GNode::NodeShape::CALL: + if (!ops.visited(&node.priv())) + { + // This operation hasn't been visited yet - mark it so, + // then add its operands to stack to continue recursion. + ops.visit(&node.priv(), node); + + const cv::GCall call = origin.node.call(); + const cv::GCall::Priv& call_p = call.priv(); + + // Put the outputs object description of the node + // so that they are not lost if they are not consumed by other operations + for (const auto &it : ade::util::indexed(call_p.m_k.outShapes)) + { + std::size_t port = ade::util::index(it); + GShape shape = ade::util::value(it); + + GOrigin org { shape, node, port}; + origins.insert(org); + } + + for (const auto &arg : call_p.m_args) + { + if (proto::is_dynamic(arg)) + { + data_objs.push(proto::rewrap(arg)); // Dive deeper + } + } + } + break; + + default: + // Unsupported node shape + GAPI_Assert(false); + break; + } + } + + // Check if protocol mentions data_objs which weren't reached during traversal + const auto missing_reached_sources = [&reached_sources](GObjId p) { + return reached_sources.visited().find(p) == reached_sources.visited().end(); + }; + if (ade::util::any_of(in_objs_p, missing_reached_sources)) + { + // TODO: Own exception type or a return code? + util::throw_error(std::logic_error("Data object listed in Protocol " + "wasn\'t reached during unroll")); + } + + // Check if there endpoint (parameter) data_objs which are not listed in protocol + const auto missing_in_proto = [&in_objs_p](GObjId p) { + return p->node.shape() != cv::GNode::NodeShape::CONST_BOUNDED && + in_objs_p.find(p) == in_objs_p.end(); + }; + if (ade::util::any_of(reached_sources.visited(), missing_in_proto)) + { + // TODO: Own exception type or a return code? + util::throw_error(std::logic_error("Data object reached during unroll " + "wasn\'t found in Protocol")); + } + + return cv::gimpl::Unrolled{ops.tracked(), origins}; +} + + +cv::gimpl::GModelBuilder::GModelBuilder(ade::Graph &g) + : m_g(g) +{ +} + +cv::gimpl::GModelBuilder::ProtoSlots +cv::gimpl::GModelBuilder::put(const GProtoArgs &ins, const GProtoArgs &outs) +{ + const auto unrolled = cv::gimpl::unrollExpr(ins, outs); + + // First, put all operations and its arguments into graph. + for (const auto &op_expr_node : unrolled.all_ops) + { + GAPI_Assert(op_expr_node.shape() == GNode::NodeShape::CALL); + const GCall& call = op_expr_node.call(); + const GCall::Priv& call_p = call.priv(); + ade::NodeHandle call_h = put_OpNode(op_expr_node); + + for (const auto &it : ade::util::indexed(call_p.m_args)) + { + const auto in_port = ade::util::index(it); + const auto& in_arg = ade::util::value(it); + + if (proto::is_dynamic(in_arg)) + { + ade::NodeHandle data_h = put_DataNode(proto::origin_of(in_arg)); + cv::gimpl::GModel::linkIn(m_g, call_h, data_h, in_port); + } + } + } + + // Then iterate via all "origins", instantiate (if not yet) Data graph nodes + // and connect these nodes with their producers in graph + for (const auto &origin : unrolled.all_data) + { + const cv::GNode& prod = origin.node; + GAPI_Assert(prod.shape() != cv::GNode::NodeShape::EMPTY); + + ade::NodeHandle data_h = put_DataNode(origin); + if (prod.shape() == cv::GNode::NodeShape::CALL) + { + ade::NodeHandle call_h = put_OpNode(prod); + cv::gimpl::GModel::linkOut(m_g, call_h, data_h, origin.port); + } + } + + // Mark graph data nodes as INPUTs and OUTPUTs respectively (according to the protocol) + for (const auto &arg : ins) + { + ade::NodeHandle nh = put_DataNode(proto::origin_of(arg)); + m_g.metadata(nh).get().storage = Data::Storage::INPUT; + } + for (const auto &arg : outs) + { + ade::NodeHandle nh = put_DataNode(proto::origin_of(arg)); + m_g.metadata(nh).get().storage = Data::Storage::OUTPUT; + } + + // And, finally, store data object layout in meta + m_g.metadata().set(Layout{m_graph_data}); + + // After graph is generated, specify which data objects are actually + // computation entry/exit points. + using NodeDescr = std::pair, + std::vector >; + + const auto get_proto_slots = [&](const GProtoArgs &proto) -> NodeDescr + { + NodeDescr slots; + + slots.first.reserve(proto.size()); + slots.second.reserve(proto.size()); + + for (const auto &arg : proto) + { + ade::NodeHandle nh = put_DataNode(proto::origin_of(arg)); + const auto &desc = m_g.metadata(nh).get(); + //These extra empty {} are to please GCC (-Wmissing-field-initializers) + slots.first.push_back(RcDesc{desc.rc, desc.shape, {}}); + slots.second.push_back(nh); + } + return slots; + }; + + auto in_slots = get_proto_slots(ins); + auto out_slots = get_proto_slots(outs); + return ProtoSlots{in_slots.first, out_slots.first, + in_slots.second, out_slots.second}; +} + +ade::NodeHandle cv::gimpl::GModelBuilder::put_OpNode(const cv::GNode &node) +{ + const auto& node_p = node.priv(); + const auto it = m_graph_ops.find(&node_p); + if (it == m_graph_ops.end()) + { + GAPI_Assert(node.shape() == GNode::NodeShape::CALL); + const auto &call_p = node.call().priv(); + auto nh = cv::gimpl::GModel::mkOpNode(m_g, call_p.m_k, call_p.m_args, node_p.m_island); + m_graph_ops[&node_p] = nh; + return nh; + } + else return it->second; +} + +// FIXME: rename to get_DataNode (and same for Op) +ade::NodeHandle cv::gimpl::GModelBuilder::put_DataNode(const GOrigin &origin) +{ + const auto it = m_graph_data.find(origin); + if (it == m_graph_data.end()) + { + auto nh = cv::gimpl::GModel::mkDataNode(m_g, origin); + m_graph_data[origin] = nh; + return nh; + } + else return it->second; +} diff --git a/modules/gapi/src/compiler/gmodelbuilder.hpp b/modules/gapi/src/compiler/gmodelbuilder.hpp new file mode 100644 index 0000000000..ce12c7e11b --- /dev/null +++ b/modules/gapi/src/compiler/gmodelbuilder.hpp @@ -0,0 +1,77 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GMODEL_BUILDER_HPP +#define OPENCV_GAPI_GMODEL_BUILDER_HPP + +#include +#include + +#include "opencv2/gapi/gproto.hpp" +#include "opencv2/gapi/gcall.hpp" + +#include "api/gapi_priv.hpp" +#include "api/gnode.hpp" +#include "compiler/gmodel.hpp" + +namespace cv { namespace gimpl { + +struct Unrolled +{ + std::vector all_ops; + GOriginSet all_data; + + // NB.: Right now, as G-API operates with GMats only and that + // GMats have no type or dimensions (when a computation is built), + // track only origins (data links) with no any additional meta. +}; + +// FIXME: GAPI_EXPORTS only because of tests!!! +GAPI_EXPORTS Unrolled unrollExpr(const GProtoArgs &ins, const GProtoArgs &outs); + +// This class generates an ADE graph with G-API specific metadata +// to represent user-specified computation in terms of graph model +// +// Resulting graph is built according to the following rules: +// - Every operation is a node +// - Every dynamic object (GMat) is a node +// - Edges between nodes represent producer/consumer relationships +// between operations and data objects. +// FIXME: GAPI_EXPORTS only because of tests!!! +class GAPI_EXPORTS GModelBuilder +{ + GModel::Graph m_g; + + // Mappings of G-API user framework entities to ADE node handles + std::unordered_map m_graph_ops; + GOriginMap m_graph_data; + + // Internal methods for mapping APIs into ADE during put() + ade::NodeHandle put_OpNode(const cv::GNode &node); + ade::NodeHandle put_DataNode(const cv::GOrigin &origin); + +public: + explicit GModelBuilder(ade::Graph &g); + + // TODO: replace GMat with a generic type + // TODO: Cover with tests! (as the rest of internal stuff) + // FIXME: Calling this method multiple times is currently UB + // TODO: add a semantic link between "ints" returned and in-model data IDs. + typedef std::tuple, + std::vector, + std::vector, + std::vector > ProtoSlots; + + ProtoSlots put(const GProtoArgs &ins, const GProtoArgs &outs); + +protected: + ade::NodeHandle opNode(cv::GMat gmat); +}; + +}} + +#endif // OPENCV_GAPI_GMODEL_BUILDER_HPP diff --git a/modules/gapi/src/compiler/gobjref.hpp b/modules/gapi/src/compiler/gobjref.hpp new file mode 100644 index 0000000000..be365c90e4 --- /dev/null +++ b/modules/gapi/src/compiler/gobjref.hpp @@ -0,0 +1,50 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GMATREF_HPP +#define OPENCV_GAPI_GMATREF_HPP + +#include "opencv2/gapi/util/variant.hpp" +#include "opencv2/gapi/garg.hpp" + +#include "api/gapi_priv.hpp" // GShape, HostCtor + +namespace cv +{ + +namespace gimpl +{ + struct RcDesc + { + int id; // id is unique but local to shape + GShape shape; // pair IS the unique ID + HostCtor ctor; // FIXME: is it really used here? Or in ? + + bool operator==(const RcDesc &rhs) const + { + // FIXME: ctor is not checked (should be?) + return id == rhs.id && shape == rhs.shape; + } + + bool operator< (const RcDesc &rhs) const + { + return (id == rhs.id) ? shape < rhs.shape : id < rhs.id; + } + }; +} // gimpl + +namespace detail +{ + template<> struct GTypeTraits + { + static constexpr const ArgKind kind = ArgKind::GOBJREF; + }; +} + +} // cv + +#endif // OPENCV_GAPI_GMATREF_HPP diff --git a/modules/gapi/src/compiler/passes/dump_dot.cpp b/modules/gapi/src/compiler/passes/dump_dot.cpp new file mode 100644 index 0000000000..333afa5e73 --- /dev/null +++ b/modules/gapi/src/compiler/passes/dump_dot.cpp @@ -0,0 +1,221 @@ +// 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) 2018 Intel Corporation + + +#include // cout +#include // stringstream +#include // ofstream + +#include + +#include "opencv2/gapi/gproto.hpp" +#include "compiler/gmodel.hpp" +#include "compiler/gislandmodel.hpp" +#include "compiler/passes/passes.hpp" + +namespace cv { namespace gimpl { namespace passes { + +// TODO: FIXME: Ideally all this low-level stuff with accessing ADE APIs directly +// should be incapsulated somewhere into GModel, so here we'd operate not +// with raw nodes and edges, but with Operations and Data it produce/consume. +void dumpDot(const ade::Graph &g, std::ostream& os) +{ + GModel::ConstGraph gr(g); + + const std::unordered_map data_labels = { + {cv::GShape::GMAT, "GMat"}, + {cv::GShape::GSCALAR, "GScalar"}, + {cv::GShape::GARRAY, "GArray"}, + }; + + auto format_op_label = [&gr](ade::NodeHandle nh) -> std::string { + std::stringstream ss; + const cv::GKernel k = gr.metadata(nh).get().k; + ss << k.name << "_" << nh; + return ss.str(); + }; + + auto format_op = [&format_op_label](ade::NodeHandle nh) -> std::string { + return "\"" + format_op_label(nh) + "\""; + }; + + auto format_obj = [&gr, &data_labels](ade::NodeHandle nh) -> std::string { + std::stringstream ss; + const auto &data = gr.metadata(nh).get(); + ss << data_labels.at(data.shape) << "_" << data.rc; + return ss.str(); + }; + + auto format_log = [&gr](ade::NodeHandle nh, const std::string &obj_name) { + std::stringstream ss; + const auto &msgs = gr.metadata(nh).get().messages; + ss << "xlabel=\""; + if (!obj_name.empty()) { ss << "*** " << obj_name << " ***:\n"; }; + for (const auto &msg : msgs) { ss << msg << "\n"; } + ss << "\""; + return ss.str(); + }; + + // FIXME: + // Unify with format_log + auto format_log_e = [&gr](ade::EdgeHandle nh) { + std::stringstream ss; + const auto &msgs = gr.metadata(nh).get().messages; + for (const auto &msg : msgs) { ss << "\n" << msg; } + return ss.str(); + }; + + auto sorted = gr.metadata().get(); + + os << "digraph GAPI_Computation {\n"; + + // Prior to dumping the graph itself, list Data and Op nodes individually + // and put type information in labels. + // Also prepare list of nodes in islands, if any + std::map > islands; + for (auto &nh : sorted.nodes()) + { + const auto node_type = gr.metadata(nh).get().t; + if (NodeType::DATA == node_type) + { + const auto obj_data = gr.metadata(nh).get(); + const auto obj_name = format_obj(nh); + + os << obj_name << " [label=\"" << obj_name << "\n" << obj_data.meta << "\""; + if (gr.metadata(nh).contains()) { os << ", " << format_log(nh, obj_name); } + os << " ]\n"; + + if (gr.metadata(nh).contains()) + islands[gr.metadata(nh).get().island].push_back(obj_name); + } + else if (NodeType::OP == gr.metadata(nh).get().t) + { + const auto obj_name = format_op(nh); + const auto obj_name_label = format_op_label(nh); + + os << obj_name << " [label=\"" << obj_name_label << "\""; + if (gr.metadata(nh).contains()) { os << ", " << format_log(nh, obj_name_label); } + os << " ]\n"; + + if (gr.metadata(nh).contains()) + islands[gr.metadata(nh).get().island].push_back(obj_name); + } + } + + // Then, dump Islands (only nodes, operations and data, without links) + for (const auto &isl : islands) + { + os << "subgraph \"cluster " + isl.first << "\" {\n"; + for(auto isl_node : isl.second) os << isl_node << ";\n"; + os << "label=\"" << isl.first << "\";"; + os << "}\n"; + } + + // Now dump the graph + for (auto &nh : sorted.nodes()) + { + // FIXME: Alan Kay probably hates me. + switch (gr.metadata(nh).get().t) + { + case NodeType::DATA: + { + const auto obj_name = format_obj(nh); + for (const auto &eh : nh->outEdges()) + { + os << obj_name << " -> " << format_op(eh->dstNode()) + << " [ label = \"in_port: " + << gr.metadata(eh).get().port; + if (gr.metadata(eh).contains()) { os << format_log_e(eh); } + os << "\" ] \n"; + } + } + break; + case NodeType::OP: + { + for (const auto &eh : nh->outEdges()) + { + os << format_op(nh) << " -> " << format_obj(eh->dstNode()) + << " [ label = \"out_port: " + << gr.metadata(eh).get().port + << " \" ]; \n"; + } + } + break; + default: GAPI_Assert(false); + } + } + + // And finally dump a GIslandModel (not connected with GModel directly, + // but projected in the same .dot file side-by-side) + auto pIG = gr.metadata().get().model; + GIslandModel::Graph gim(*pIG); + for (auto nh : gim.nodes()) + { + switch (gim.metadata(nh).get().k) + { + case NodeKind::ISLAND: + { + const auto island = gim.metadata(nh).get().object; + const auto isl_name = "\"" + island->name() + "\""; + for (auto out_nh : nh->outNodes()) + { + os << isl_name << " -> \"slot:" + << format_obj(gim.metadata(out_nh).get() + .original_data_node) + << "\"\n"; + } + } + break; + + case NodeKind::SLOT: + { + const auto obj_name = format_obj(gim.metadata(nh).get() + .original_data_node); + for (auto cons_nh : nh->outNodes()) + { + os << "\"slot:" << obj_name << "\" -> \"" + << gim.metadata(cons_nh).get().object->name() + << "\"\n"; + } + } + break; + + default: + GAPI_Assert(false); + break; + } + } + + os << "}" << std::endl; +} + +void dumpDot(ade::passes::PassContext &ctx, std::ostream& os) +{ + dumpDot(ctx.graph, os); +} + +void dumpDotStdout(ade::passes::PassContext &ctx) +{ + dumpDot(ctx, std::cout); +} + +void dumpDotToFile(ade::passes::PassContext &ctx, const std::string& dump_path) +{ + std::ofstream dump_file(dump_path); + + if (dump_file.is_open()) + { + dumpDot(ctx, dump_file); + dump_file << std::endl; + } +} + +void dumpGraph(ade::passes::PassContext &ctx, const std::string& dump_path) +{ + dump_path.empty() ? dumpDotStdout(ctx) : dumpDotToFile(ctx, dump_path); +} + +}}} // cv::gimpl::passes diff --git a/modules/gapi/src/compiler/passes/exec.cpp b/modules/gapi/src/compiler/passes/exec.cpp new file mode 100644 index 0000000000..137bbc18e3 --- /dev/null +++ b/modules/gapi/src/compiler/passes/exec.cpp @@ -0,0 +1,640 @@ +// 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) 2018 Intel Corporation + + +#include +#include // list +#include // setw, etc +#include // ofstream +#include +#include + +#include // contains +#include // chain + +#include "opencv2/gapi/util/optional.hpp" // util::optional +#include "opencv2/core/cvdef.h" +#include "logger.hpp" // GAPI_LOG + +#include "compiler/gmodel.hpp" +#include "compiler/gislandmodel.hpp" +#include "compiler/passes/passes.hpp" +#include "compiler/passes/helpers.hpp" +#include "compiler/transactions.hpp" + +//////////////////////////////////////////////////////////////////////////////// +// +// N.B. +// Merge is a binary operation (LHS `Merge` RHS) where LHS may be arbitrary +// +// After every merge, the procedure starts from the beginning (in the topological +// order), thus trying to merge next "unmerged" island to the latest merged one. +// +//////////////////////////////////////////////////////////////////////////////// + +// Uncomment to dump more info on merge process +// FIXME: make it user-configurable run-time option +// #define DEBUG_MERGE + +namespace cv +{ +namespace gimpl +{ +namespace +{ + bool fusionIsTrivial(const ade::Graph &src_graph) + { + // Fusion is considered trivial if there only one + // active backend and no user-defined islands + // FIXME: + // Also check the cases backend can't handle + // (e.x. GScalar connecting two fluid ops should split the graph) + const GModel::ConstGraph g(src_graph); + const auto& active_backends = g.metadata().get().backends; + return active_backends.size() == 1 && + ade::util::all_of(g.nodes(), [&](ade::NodeHandle nh) { + return !g.metadata(nh).contains(); + }); + } + + void fuseTrivial(GIslandModel::Graph &g, const ade::Graph &src_graph) + { + const GModel::ConstGraph src_g(src_graph); + + const auto& backend = *src_g.metadata().get().backends.cbegin(); + const auto& proto = src_g.metadata().get(); + GIsland::node_set all, in_ops, out_ops; + + all.insert(src_g.nodes().begin(), src_g.nodes().end()); + + for (const auto nh : proto.in_nhs) + { + all.erase(nh); + in_ops.insert(nh->outNodes().begin(), nh->outNodes().end()); + } + for (const auto nh : proto.out_nhs) + { + all.erase(nh); + out_ops.insert(nh->inNodes().begin(), nh->inNodes().end()); + } + + auto isl = std::make_shared(backend, + std::move(all), + std::move(in_ops), + std::move(out_ops), + util::optional{}); + + auto ih = GIslandModel::mkIslandNode(g, std::move(isl)); + + for (const auto nh : proto.in_nhs) + { + auto slot = GIslandModel::mkSlotNode(g, nh); + g.link(slot, ih); + } + for (const auto nh : proto.out_nhs) + { + auto slot = GIslandModel::mkSlotNode(g, nh); + g.link(ih, slot); + } + } + + struct MergeContext + { + using CycleCausers = std::pair< std::shared_ptr, + std::shared_ptr >; + + struct CycleHasher final + { + std::size_t operator()(const CycleCausers& p) const + { + std::size_t a = std::hash()(p.first.get()); + std::size_t b = std::hash()(p.second.get()); + return a ^ (b << 1); + } + }; + + // Set of Islands pairs which cause a cycle if merged. + // Every new merge produces a new Island, and if Islands were + // merged (and thus dropped from GIslandModel), the objects may + // still be alive as included into this set. + std::unordered_set cycle_causers; + }; + + bool canMerge(const GIslandModel::Graph &g, + const ade::NodeHandle a_nh, + const ade::NodeHandle /*slot_nh*/, + const ade::NodeHandle b_nh, + const MergeContext &ctx = MergeContext()) + { + auto a_ptr = g.metadata(a_nh).get().object; + auto b_ptr = g.metadata(b_nh).get().object; + GAPI_Assert(a_ptr.get()); + GAPI_Assert(b_ptr.get()); + + // Islands with different affinity can't be merged + if (a_ptr->backend() != b_ptr->backend()) + return false; + + // Islands which cause a cycle can't be merged as well + // (since the flag is set, the procedure already tried to + // merge these islands in the past) + if (ade::util::contains(ctx.cycle_causers, std::make_pair(a_ptr, b_ptr))|| + ade::util::contains(ctx.cycle_causers, std::make_pair(b_ptr, a_ptr))) + return false; + + // There may be user-defined islands. Initially user-defined + // islands also are built from single operations and then merged + // by this procedure, but there is some exceptions. + // User-specified island can't be merged to an internal island + if ( ( a_ptr->is_user_specified() && !b_ptr->is_user_specified()) + || (!a_ptr->is_user_specified() && b_ptr->is_user_specified())) + { + return false; + } + else if (a_ptr->is_user_specified() && b_ptr->is_user_specified()) + { + // These islads are _different_ user-specified Islands + // FIXME: today it may only differ by name + if (a_ptr->name() != b_ptr->name()) + return false; + } + + // FIXME: add a backend-specified merge checker + return true; + } + + inline bool isProducedBy(const ade::NodeHandle &slot, + const ade::NodeHandle &island) + { + // A data slot may have only 0 or 1 producer + if (slot->inNodes().size() == 0) + return false; + + return slot->inNodes().front() == island; + } + + inline bool isConsumedBy(const ade::NodeHandle &slot, + const ade::NodeHandle &island) + { + auto it = std::find_if(slot->outNodes().begin(), + slot->outNodes().end(), + [&](const ade::NodeHandle &nh) { + return nh == island; + }); + return it != slot->outNodes().end(); + } + + /** + * Find a candidate Island for merge for the given Island nh. + * + * @param g Island Model where merge occurs + * @param nh GIsland node, either LHS or RHS of probable merge + * @param ctx Merge context, may contain some cached stuff to avoid + * double/triple/etc checking + * @return Tuple of Island handle, Data slot handle (which connects them), + * and a position of found handle with respect to nh (IN/OUT) + */ + std::tuple + findCandidate(const GIslandModel::Graph &g, + ade::NodeHandle nh, + const MergeContext &ctx = MergeContext()) + { + using namespace std::placeholders; + + // Find a first matching candidate GIsland for merge + // among inputs + for (const auto& input_data_nh : nh->inNodes()) + { + if (input_data_nh->inNodes().size() != 0) + { + // Data node must have a single producer only + GAPI_DbgAssert(input_data_nh->inNodes().size() == 1); + auto input_data_prod_nh = input_data_nh->inNodes().front(); + if (canMerge(g, input_data_prod_nh, input_data_nh, nh, ctx)) + return std::make_tuple(input_data_prod_nh, + input_data_nh, + Direction::In); + } + } // for(inNodes) + + // Ok, now try to find it among the outputs + for (const auto& output_data_nh : nh->outNodes()) + { + auto mergeTest = [&](ade::NodeHandle cons_nh) -> bool { + return canMerge(g, nh, output_data_nh, cons_nh, ctx); + }; + auto cand_it = std::find_if(output_data_nh->outNodes().begin(), + output_data_nh->outNodes().end(), + mergeTest); + if (cand_it != output_data_nh->outNodes().end()) + return std::make_tuple(*cand_it, + output_data_nh, + Direction::Out); + } // for(outNodes) + // Empty handle, no good candidates + return std::make_tuple(ade::NodeHandle(), + ade::NodeHandle(), + Direction::Invalid); + } + + // A cancellable merge of two GIslands, "a" and "b", connected via "slot" + class MergeAction + { + ade::Graph &m_g; + const ade::Graph &m_orig_g; + GIslandModel::Graph m_gim; + ade::NodeHandle m_prod; + ade::NodeHandle m_slot; + ade::NodeHandle m_cons; + + Change::List m_changes; + + struct MergeObjects + { + using NS = GIsland::node_set; + NS all; // same as in GIsland + NS in_ops; // same as in GIsland + NS out_ops; // same as in GIsland + NS opt_interim_slots; // can be dropped (optimized out) + NS non_opt_interim_slots;// can't be dropped (extern. link) + }; + MergeObjects identify() const; + + public: + MergeAction(ade::Graph &g, + const ade::Graph &orig_g, + ade::NodeHandle prod, + ade::NodeHandle slot, + ade::NodeHandle cons) + : m_g(g) + , m_orig_g(orig_g) + , m_gim(GIslandModel::Graph(m_g)) + , m_prod(prod) + , m_slot(slot) + , m_cons(cons) + { + } + + void tryMerge(); // Try to merge islands Prod and Cons + void rollback(); // Roll-back changes if merge has been done but broke the model + void commit(); // Fix changes in the model after successful merge + }; + + // Merge proceduce is a replacement of two Islands, Prod and Cons, + // connected via !Slot!, with a new Island, which contain all Prod + // nodes + all Cons nodes, and reconnected in the graph properly: + // + // Merge(Prod, !Slot!, Cons) + // + // [Slot 2] + // : + // v + // ... [Slot 0] -> Prod -> !Slot! -> Cons -> [Slot 3] -> ... + // ... [Slot 1] -' : '-> [Slot 4] -> ... + // V + // ... + // results into: + // + // ... [Slot 0] -> Merged -> [Slot 3] ... + // ... [Slot 1] : :-> [Slot 4] ... + // ... [Slot 2] ' '-> !Slot! ... + // + // The rules are the following: + // 1) All Prod input slots become Merged input slots; + // - Example: Slot 0 Slot 1 + // 2) Any Cons input slots which come from Islands different to Prod + // also become Merged input slots; + // - Example: Slot 2 + // 3) All Cons output slots become Merged output slots; + // - Example: Slot 3, Slot 4 + // 4) All Prod output slots which are not consumed by Cons + // also become Merged output slots; + // - (not shown on the example) + // 5) If the !Slot! which connects Prod and Cons is consumed + // exclusively by Cons, it is optimized out (dropped) from the model; + // 6) If the !Slot! is used by consumers other by Cons, it + // becomes an extra output of Merged + // 7) !Slot! may be not the only connection between Prod and Cons, + // but as a result of merge operation, all such connections + // should be handles as described for !Slot! + + MergeAction::MergeObjects MergeAction::identify() const + { + auto lhs = m_gim.metadata(m_prod).get().object; + auto rhs = m_gim.metadata(m_cons).get().object; + + GIsland::node_set interim_slots; + + GIsland::node_set merged_all(lhs->contents()); + merged_all.insert(rhs->contents().begin(), rhs->contents().end()); + + GIsland::node_set merged_in_ops(lhs->in_ops()); // (1) + for (auto cons_in_slot_nh : m_cons->inNodes()) // (2) + { + if (isProducedBy(cons_in_slot_nh, m_prod)) + { + interim_slots.insert(cons_in_slot_nh); + // at this point, interim_slots are not sync with merged_all + // (merged_all will be updated with interim_slots which + // will be optimized out). + } + else + { + const auto extra_in_ops = rhs->consumers(m_g, cons_in_slot_nh); + merged_in_ops.insert(extra_in_ops.begin(), extra_in_ops.end()); + } + } + + GIsland::node_set merged_out_ops(rhs->out_ops()); // (3) + for (auto prod_out_slot_nh : m_prod->outNodes()) // (4) + { + if (!isConsumedBy(prod_out_slot_nh, m_cons)) + { + merged_out_ops.insert(lhs->producer(m_g, prod_out_slot_nh)); + } + } + + // (5,6,7) + GIsland::node_set opt_interim_slots; + GIsland::node_set non_opt_interim_slots; + + auto is_non_opt = [&](const ade::NodeHandle &slot_nh) { + // If a data object associated with this slot is a part + // of GComputation _output_ protocol, it can't be optimzied out + const auto data_nh = m_gim.metadata(slot_nh).get().original_data_node; + const auto &data = GModel::ConstGraph(m_orig_g).metadata(data_nh).get(); + if (data.storage == Data::Storage::OUTPUT) + return true; + + // Otherwise, a non-optimizeable data slot is the one consumed + // by some other island than "cons" + const auto it = std::find_if(slot_nh->outNodes().begin(), + slot_nh->outNodes().end(), + [&](ade::NodeHandle &&nh) + {return nh != m_cons;}); + return it != slot_nh->outNodes().end(); + }; + for (auto slot_nh : interim_slots) + { + // Put all intermediate data nodes (which are BOTH optimized + // and not-optimized out) to Island contents. + merged_all.insert(m_gim.metadata(slot_nh) + .get().original_data_node); + + GIsland::node_set &dst = is_non_opt(slot_nh) + ? non_opt_interim_slots // there are consumers other than m_cons + : opt_interim_slots; // there's no consumers other than m_cons + dst.insert(slot_nh); + } + + // (4+6). + // BTW, (4) could be "All Prod output slots read NOT ONLY by Cons" + for (auto non_opt_slot_nh : non_opt_interim_slots) + { + merged_out_ops.insert(lhs->producer(m_g, non_opt_slot_nh)); + } + return MergeObjects{ + merged_all, merged_in_ops, merged_out_ops, + opt_interim_slots, non_opt_interim_slots + }; + } + + // FIXME(DM): Probably this procedure will be refactored dramatically one day... + void MergeAction::tryMerge() + { + // _: Understand the contents and I/O connections of a new merged Island + MergeObjects mo = identify(); + auto lhs_obj = m_gim.metadata(m_prod).get().object; + auto rhs_obj = m_gim.metadata(m_cons).get().object; + GAPI_Assert( ( lhs_obj->is_user_specified() && rhs_obj->is_user_specified()) + || (!lhs_obj->is_user_specified() && !rhs_obj->is_user_specified())); + cv::util::optional maybe_user_tag; + if (lhs_obj->is_user_specified() && rhs_obj->is_user_specified()) + { + GAPI_Assert(lhs_obj->name() == rhs_obj->name()); + maybe_user_tag = cv::util::make_optional(lhs_obj->name()); + } + + // A: Create a new Island and add it to the graph + auto backend = m_gim.metadata(m_prod).get() + .object->backend(); + auto merged = std::make_shared(backend, + std::move(mo.all), + std::move(mo.in_ops), + std::move(mo.out_ops), + std::move(maybe_user_tag)); + // FIXME: move this debugging to some user-controllable log-level +#ifdef DEBUG_MERGE + merged->debug(); +#endif + auto new_nh = GIslandModel::mkIslandNode(m_gim, std::move(merged)); + m_changes.enqueue(new_nh); + + // B: Disconnect all Prod's input Slots from Prod, + // connect it to Merged + std::vector input_edges(m_prod->inEdges().begin(), + m_prod->inEdges().end()); + for (auto in_edge : input_edges) + { + m_changes.enqueue(m_g, in_edge->srcNode(), new_nh); + m_changes.enqueue(m_g, m_prod, in_edge); + } + + // C: Disconnect all Cons' output Slots from Cons, + // connect it to Merged + std::vector output_edges(m_cons->outEdges().begin(), + m_cons->outEdges().end()); + for (auto out_edge : output_edges) + { + m_changes.enqueue(m_g, new_nh, out_edge->dstNode()); + m_changes.enqueue(m_g, m_cons, out_edge); + } + + // D: Process the intermediate slots (betweed Prod n Cons). + // D/1 - Those which are optimized out are just removed from the model + for (auto opt_slot_nh : mo.opt_interim_slots) + { + GAPI_Assert(1 == opt_slot_nh->inNodes().size() ); + GAPI_Assert(m_prod == opt_slot_nh->inNodes().front()); + + std::vector edges_to_drop; + ade::util::copy(ade::util::chain(opt_slot_nh->inEdges(), + opt_slot_nh->outEdges()), + std::back_inserter(edges_to_drop)); + for (auto eh : edges_to_drop) + { + m_changes.enqueue(m_g, opt_slot_nh, eh); + } + m_changes.enqueue(opt_slot_nh); + } + // D/2 - Those which are used externally are connected to new nh + // as outputs. + for (auto non_opt_slot_nh : mo.non_opt_interim_slots) + { + GAPI_Assert(1 == non_opt_slot_nh->inNodes().size() ); + GAPI_Assert(m_prod == non_opt_slot_nh->inNodes().front()); + m_changes.enqueue(m_g, non_opt_slot_nh, + non_opt_slot_nh->inEdges().front()); + + std::vector edges_to_probably_drop + (non_opt_slot_nh->outEdges().begin(), + non_opt_slot_nh->outEdges().end());; + for (auto eh : edges_to_probably_drop) + { + if (eh->dstNode() == m_cons) + { + // drop only edges to m_cons, as there's other consumers + m_changes.enqueue(m_g, non_opt_slot_nh, eh); + } + } + m_changes.enqueue(m_g, new_nh, non_opt_slot_nh); + } + + // E. All Prod's output edges which are directly related to Merge (e.g. + // connected to Cons) were processed on step (D). + // Relink the remaining output links + std::vector prod_extra_out_edges + (m_prod->outEdges().begin(), + m_prod->outEdges().end()); + for (auto extra_out : prod_extra_out_edges) + { + m_changes.enqueue(m_g, new_nh, extra_out->dstNode()); + m_changes.enqueue(m_g, m_prod, extra_out); + } + + // F. All Cons' input edges which are directly related to merge (e.g. + // connected to Prod) were processed on step (D) as well, + // remaining should become Merged island's input edges + std::vector cons_extra_in_edges + (m_cons->inEdges().begin(), + m_cons->inEdges().end()); + for (auto extra_in : cons_extra_in_edges) + { + m_changes.enqueue(m_g, extra_in->srcNode(), new_nh); + m_changes.enqueue(m_g, m_cons, extra_in); + } + + // G. Finally, drop the original Island nodes. DropNode() does + // the sanity check for us (both nodes should have 0 edges). + m_changes.enqueue(m_prod); + m_changes.enqueue(m_cons); + } + + void MergeAction::rollback() + { + m_changes.rollback(m_g); + } + void MergeAction::commit() + { + m_changes.commit(m_g); + } + +#ifdef DEBUG_MERGE + void merge_debug(const ade::Graph &g, int iteration) + { + std::stringstream filename; + filename << "fusion_" << static_cast(&g) + << "_" << std::setw(4) << std::setfill('0') << iteration + << ".dot"; + std::ofstream ofs(filename.str()); + passes::dumpDot(g, ofs); + } +#endif + + void fuseGeneral(ade::Graph& im, const ade::Graph& g) + { + GIslandModel::Graph gim(im); + MergeContext mc; + + bool there_was_a_merge = false; + std::size_t iteration = 0u; + do + { + there_was_a_merge = false; + + // FIXME: move this debugging to some user-controllable log level + #ifdef DEBUG_MERGE + GAPI_LOG_INFO(NULL, "Before next merge attempt " << iteration << "..."); + merge_debug(g, iteration); + #endif + iteration++; + auto sorted = pass_helpers::topoSort(im); + for (auto nh : sorted) + { + if (NodeKind::ISLAND == gim.metadata(nh).get().k) + { + ade::NodeHandle cand_nh; + ade::NodeHandle cand_slot; + Direction dir = Direction::Invalid; + std::tie(cand_nh, cand_slot, dir) = findCandidate(gim, nh, mc); + if (cand_nh != nullptr && dir != Direction::Invalid) + { + auto lhs_nh = (dir == Direction::In ? cand_nh : nh); + auto rhs_nh = (dir == Direction::Out ? cand_nh : nh); + + auto l_obj = gim.metadata(lhs_nh).get().object; + auto r_obj = gim.metadata(rhs_nh).get().object; + GAPI_LOG_INFO(NULL, r_obj->name() << " can be merged into " << l_obj->name()); + // Try to do a merge. If merge was succesfull, check if the + // graph have cycles (cycles are prohibited at this point). + // If there are cycles, roll-back the merge and mark a pair of + // these Islands with a special tag - "cycle-causing". + MergeAction action(im, g, lhs_nh, cand_slot, rhs_nh); + action.tryMerge(); + if (pass_helpers::hasCycles(im)) + { + GAPI_LOG_INFO(NULL, + "merge(" << l_obj->name() << "," << r_obj->name() << + ") caused cycle, rolling back..."); + action.rollback(); + // don't try to merge these two islands next time (findCandidate will use that) + mc.cycle_causers.insert({l_obj, r_obj}); + } + else + { + GAPI_LOG_INFO(NULL, + "merge(" << l_obj->name() << "," << r_obj->name() << + ") was successful!"); + action.commit(); + #ifdef DEBUG_MERGE + GIslandModel::syncIslandTags(gim, g); + #endif + there_was_a_merge = true; + break; // start do{}while from the beginning + } + } // if(can merge) + } // if(ISLAND) + } // for(all nodes) + } + while (there_was_a_merge); + } +} // anonymous namespace + +void passes::fuseIslands(ade::passes::PassContext &ctx) +{ + std::shared_ptr gptr(new ade::Graph); + GIslandModel::Graph gim(*gptr); + + if (fusionIsTrivial(ctx.graph)) + { + fuseTrivial(gim, ctx.graph); + } + else + { + GIslandModel::generateInitial(gim, ctx.graph); + fuseGeneral(*gptr.get(), ctx.graph); + } + GModel::Graph(ctx.graph).metadata().set(IslandModel{std::move(gptr)}); +} + +void passes::syncIslandTags(ade::passes::PassContext &ctx) +{ + GModel::Graph gm(ctx.graph); + std::shared_ptr gptr(gm.metadata().get().model); + GIslandModel::Graph gim(*gptr); + GIslandModel::syncIslandTags(gim, ctx.graph); +} +}} // namespace cv::gimpl diff --git a/modules/gapi/src/compiler/passes/helpers.cpp b/modules/gapi/src/compiler/passes/helpers.cpp new file mode 100644 index 0000000000..e77a72b70e --- /dev/null +++ b/modules/gapi/src/compiler/passes/helpers.cpp @@ -0,0 +1,120 @@ +// 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) 2018 Intel Corporation + + +#include // copy +#include +#include + +#include + +#include "opencv2/gapi/own/assert.hpp" // GAPI_Assert +#include "compiler/passes/helpers.hpp" + +namespace { +namespace Cycles +{ + // FIXME: This code is taken directly from ADE. + // export a bool(ade::Graph) function with pass instead + enum class TraverseState + { + visiting, + visited, + }; + using state_t = std::unordered_map; + + bool inline checkCycle(state_t& state, const ade::NodeHandle& node) + { + GAPI_Assert(nullptr != node); + state[node.get()] = TraverseState::visiting; + for (auto adj: node->outNodes()) + { + auto it = state.find(adj.get()); + if (state.end() == it) // not visited + { + // FIXME: use std::stack instead on-stack recursion + if (checkCycle(state, adj)) + { + return true; // detected! (deeper frame) + } + } + else if (TraverseState::visiting == it->second) + { + return true; // detected! (this frame) + } + } + state[node.get()] = TraverseState::visited; + return false; // not detected + } + + bool inline hasCycles(const ade::Graph &graph) + { + state_t state; + bool detected = false; + for (auto node: graph.nodes()) + { + if (state.end() == state.find(node.get())) + { + // not yet visited during recursion + detected |= checkCycle(state, node); + if (detected) break; + } + } + return detected; + } +} // namespace Cycles + +namespace TopoSort +{ + using sorted_t = std::vector; + using visited_t = std::unordered_set; + + struct NonEmpty final + { + bool operator()(const ade::NodeHandle& node) const + { + return nullptr != node; + } + }; + + void inline visit(sorted_t& sorted, visited_t& visited, const ade::NodeHandle& node) + { + if (visited.end() == visited.find(node.get())) + { + for (auto adj: node->inNodes()) + { + visit(sorted, visited, adj); + } + sorted.push_back(node); + visited.insert(node.get()); + } + } + + sorted_t inline topoSort(const ade::Graph &g) + { + sorted_t sorted; + visited_t visited; + for (auto node: g.nodes()) + { + visit(sorted, visited, node); + } + + auto r = ade::util::filter(ade::util::toRange(sorted)); + return sorted_t(r.begin(), r.end()); + } +} // namespace TopoSort + +} // anonymous namespace + +bool cv::gimpl::pass_helpers::hasCycles(const ade::Graph &g) +{ + return Cycles::hasCycles(g); +} + +std::vector cv::gimpl::pass_helpers::topoSort(const ade::Graph &g) +{ + return TopoSort::topoSort(g); +} diff --git a/modules/gapi/src/compiler/passes/helpers.hpp b/modules/gapi/src/compiler/passes/helpers.hpp new file mode 100644 index 0000000000..3aa18e6277 --- /dev/null +++ b/modules/gapi/src/compiler/passes/helpers.hpp @@ -0,0 +1,31 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_COMPILER_PASSES_HELPERS_HPP +#define OPENCV_GAPI_COMPILER_PASSES_HELPERS_HPP + +// FIXME: DROP THIS and REUSE ADE utilities +// (which serve as passes already but are not exposed as standalone functions) + +#include + +#include +#include // FIXME: Forward declarations instead? +#include + +namespace cv { +namespace gimpl { +namespace pass_helpers { + +bool hasCycles(const ade::Graph &graph); +std::vector topoSort(const ade::Graph &graph); + +} // namespace pass_helpers +} // namespace gimpl +} // name + +#endif // OPENCV_GAPI_COMPILER_PASSES_HELPERS_HPP diff --git a/modules/gapi/src/compiler/passes/islands.cpp b/modules/gapi/src/compiler/passes/islands.cpp new file mode 100644 index 0000000000..8773f2c189 --- /dev/null +++ b/modules/gapi/src/compiler/passes/islands.cpp @@ -0,0 +1,231 @@ +// 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) 2018 Intel Corporation + + +#include +#include +#include +#include + +#include "compiler/gmodel.hpp" +#include "compiler/passes/passes.hpp" + +namespace +{ + bool is_within_same_island(const cv::gimpl::GModel::Graph &gr, + const ade::NodeHandle &dataNode, + const std::string &island) + { + // A data node is within the same island as it's reader node + // if and only if data object's producer island (if there's a producer) + // is the same as the specified one. + // + // An object may have only a single producer, but multiple consumers, + // and these consumers may be assigned to different Islands. + // Since "initIslands" traversal direction is op-to-args, i.e. reverse, + // a single Data object may be visited twice during Islands initialization. + // + // In this case, Data object is part of Island A if and only if: + // - Data object's producer is part of Island A, + // - AND any of Data obejct's consumers is part of Island A. + // + // Op["island0"] --> Data[ ? ] --> Op["island0"] + // : + // '---------> Op["island1"] + // + // In the above example, Data object is assigned to "island0" as + // it is surrounded by operations assigned to "island0" + + using namespace cv::gimpl; + + if ( gr.metadata(dataNode).contains() + && gr.metadata(dataNode).get().island != island) + return false; + + if (dataNode->inNodes().empty()) + return false; + + GAPI_Assert(dataNode->inNodes().size() == 1u); + const auto prod_h = dataNode->inNodes().front(); + + // FIXME: ADE should have something like get_or<> or get<>(default) + GAPI_Assert(gr.metadata(prod_h).get().t == NodeType::OP); + return ( gr.metadata(prod_h).contains() + && gr.metadata(prod_h).get().island == island) + && (ade::util::any_of(dataNode->outNodes(), [&](ade::NodeHandle cons_h) + { + return ( gr.metadata(cons_h).contains() + && gr.metadata(cons_h).get().island == island); + })); + } +} // anonymous namespace + +// Initially only Operations have Island tag. This pass adds Island tag +// to all data objects within an Island. +// A data object is considered within an Island if and only if +// its reader and writer are assigned to this Island (see above). +void cv::gimpl::passes::initIslands(ade::passes::PassContext &ctx) +{ + GModel::Graph gr(ctx.graph); + for (const auto &nh : gr.nodes()) + { + if (gr.metadata(nh).get().t == NodeType::OP) + { + if (gr.metadata(nh).contains()) + { + const auto island = gr.metadata(nh).get().island; + + // It is enough to check only input nodes + for (const auto &in_data_node : nh->inNodes()) + { + if (is_within_same_island(gr, in_data_node, island)) + { + gr.metadata(in_data_node).set(Island{island}); + } + } // for(in_data_node) + } // if (contains) + } // if (OP) + } // for (nodes) +} + +// There should be no multiple (disconnected) islands with the same name. +// This may occur if user assigns the same islands name to multiple ranges +// in the graph. +// FIXME: How it could be avoided on an earlier stage? +void cv::gimpl::passes::checkIslands(ade::passes::PassContext &ctx) +{ + GModel::ConstGraph gr(ctx.graph); + + // The algorithm is teh following: + // + // 1. Put all Tagged nodes (both Operations and Data) into a set + // 2. Initialize Visited set as (empty) + // 3. Initialize Traversal stack as (empty) + // 4. Initialize Islands map (String -> Integer) as (empty) + // 5. For every Tagged node from a set + // a. Skip if it is Visited + // b. For every input/output node: + // * if it is tagged with the same island: + // - add it to Traversal stack + // - remove from Tagged nodes if it is t + // c. While (stack is not empty): + // - Take a node from Stack + // - Repeat (b) + // d. Increment Islands map [this island] by 1 + // + // + // If whatever Island has counter is more than 1, it is a disjoint + // one (i.e. there's two islands with the same name). + + using node_set = std::unordered_set + < ade::NodeHandle + , ade::HandleHasher + >; + node_set tagged_nodes; + node_set visited_tagged_nodes; + std::unordered_map island_counters; + + for (const auto &nh : gr.nodes()) + { + if (gr.metadata(nh).contains()) + { + tagged_nodes.insert(nh); + island_counters[gr.metadata(nh).get().island] = 0; + } + } + + // Make a copy to allow list modifications during traversal + for (const auto &tagged_nh : tagged_nodes) + { + if (visited_tagged_nodes.end() != ade::util::find(visited_tagged_nodes, tagged_nh)) + continue; + + // Run the recursive traversal process as described in 5/a-d. + // This process is like a flood-fill traversal for island. + // If there's to distint successful flood-fills happened for the same island + // name, there are two islands with this name. + std::stack stack; + stack.push(tagged_nh); + + while (!stack.empty()) + { + const auto this_nh = stack.top(); + stack.pop(); + + // Since _this_ node is visited, it is a part of processed island + // so mark it as visited to skip in other recursive processes + visited_tagged_nodes.insert(this_nh); + + GAPI_DbgAssert(gr.metadata(this_nh).contains()); + GAPI_DbgAssert( gr.metadata(this_nh ).get().island + == gr.metadata(tagged_nh).get().island); + const auto &this_island = gr.metadata(this_nh).get().island; + + for (const auto neighbor_nh : ade::util::chain(this_nh->inNodes(), this_nh->outNodes())) + { + if ( gr.metadata(neighbor_nh).contains() + && gr.metadata(neighbor_nh).get().island == this_island + && !visited_tagged_nodes.count(neighbor_nh)) + { + stack.push(neighbor_nh); + } + } // for (neighbor) + } // while (stack) + + // Flood-fill is over, now increment island counter for this island + island_counters[gr.metadata(tagged_nh).get().island]++; + } // for(tagged) + + bool check_failed = false; + std::stringstream ss; + for (const auto &ic : island_counters) + { + GAPI_Assert(ic.second > 0); + if (ic.second > 1) + { + check_failed = true; + ss << "\"" << ic.first << "\"(" << ic.second << ") "; + } + } + if (check_failed) + { + util::throw_error + (std::logic_error("There are multiple distinct islands " + "with the same name: [" + ss.str() + "], " + "please check your cv::gapi::island() parameters!")); + } +} + +void cv::gimpl::passes::checkIslandsContent(ade::passes::PassContext &ctx) +{ + GModel::ConstGraph gr(ctx.graph); + std::unordered_map backends_of_islands; + for (const auto& nh : gr.nodes()) + { + if (NodeType::OP == gr.metadata(nh).get().t && + gr.metadata(nh).contains()) + { + const auto island = gr.metadata(nh).get().island; + auto island_backend_it = backends_of_islands.find(island); + const auto& op = gr.metadata(nh).get(); + + if (island_backend_it != backends_of_islands.end()) + { + // Check that backend of the operation coincides with the backend of the island + // Backend of the island is determined by the backend of the first operation from this island + if (island_backend_it->second != op.backend) + { + util::throw_error(std::logic_error(island + " contains kernels " + op.k.name + + " with different backend")); + } + } + else + { + backends_of_islands.emplace(island, op.backend); + } + } + } +} diff --git a/modules/gapi/src/compiler/passes/kernels.cpp b/modules/gapi/src/compiler/passes/kernels.cpp new file mode 100644 index 0000000000..ad8555b408 --- /dev/null +++ b/modules/gapi/src/compiler/passes/kernels.cpp @@ -0,0 +1,155 @@ +// 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) 2018 Intel Corporation + + +#include // util::indexed +#include +#include + +#include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend() + +#include "compiler/gmodel.hpp" +#include "compiler/passes/passes.hpp" + +#include "api/gbackend_priv.hpp" +#include "backends/common/gbackend.hpp" +#include "compiler/gmodelbuilder.hpp" +#include "logger.hpp" // GAPI_LOG + +namespace +{ + struct ImplInfo + { + cv::GKernelImpl impl; + cv::GArgs in_args; + }; + + // Generaly the algorithm is following + // + // 1. Get GCompoundKernel implementation + // 2. Create GCompoundContext + // 3. Run GCompoundKernel with GCompoundContext + // 4. Build subgraph from imputs/outputs GCompoundKernel + // 5. Replace compound node to subgraph + + void expand(ade::Graph& g, ade::NodeHandle nh, const ImplInfo& impl_info) + { + cv::gimpl::GModel::Graph gr(g); + auto compound_impl = cv::util::any_cast(impl_info.impl.opaque); + + // GCompoundContext instantiates its own objects + // in accordance with the RcDescs from in_args + cv::detail::GCompoundContext context(impl_info.in_args); + compound_impl.apply(context); + + cv::GProtoArgs ins, outs; + ins.reserve(context.m_args.size()); + outs.reserve(context.m_results.size()); + + // Inputs can be non-dynamic types. + // Such inputs are not used when building a graph + for (const auto& arg : context.m_args) + { + if (cv::gimpl::proto::is_dynamic(arg)) + { + ins.emplace_back(cv::gimpl::proto::rewrap(arg)); + } + } + + ade::util::transform(context.m_results, std::back_inserter(outs), &cv::gimpl::proto::rewrap); + + cv::gimpl::GModelBuilder builder(g); + + // Build the subgraph graph which will need to replace the compound node + const auto& proto_slots = builder.put(ins, outs); + + const auto& in_nhs = std::get<2>(proto_slots); + const auto& out_nhs = std::get<3>(proto_slots); + + auto sorted_in_nhs = cv::gimpl::GModel::orderedInputs(gr, nh); + auto sorted_out_nhs = cv::gimpl::GModel::orderedOutputs(gr, nh); + + // Reconnect expanded kernels from graph data objects + // to subgraph data objects, then drop that graph data objects + for (const auto& it : ade::util::zip(in_nhs, sorted_in_nhs)) + { + const auto& subgr_in_nh = std::get<0>(it); + const auto& comp_in_nh = std::get<1>(it); + + cv::gimpl::GModel::redirectReaders(gr, subgr_in_nh, comp_in_nh); + gr.erase(subgr_in_nh); + } + + gr.erase(nh); + + for (const auto& it : ade::util::zip(out_nhs, sorted_out_nhs)) + { + const auto& subgr_out_nh = std::get<0>(it); + const auto& comp_out_nh = std::get<1>(it); + + cv::gimpl::GModel::redirectWriter(gr, subgr_out_nh, comp_out_nh); + gr.erase(subgr_out_nh); + } + } +} +// This pass, given the kernel package, selects a kernel implementation +// for every operation in the graph +void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx, + const gapi::GKernelPackage &kernels, + const gapi::GLookupOrder &order) +{ + std::unordered_set active_backends; + + GModel::Graph gr(ctx.graph); + for (const auto &nh : gr.nodes()) + { + if (gr.metadata(nh).get().t == NodeType::OP) + { + auto &op = gr.metadata(nh).get(); + cv::gapi::GBackend selected_backend; + cv::GKernelImpl selected_impl; + std::tie(selected_backend, selected_impl) + = kernels.lookup(op.k.name, order); + + selected_backend.priv().unpackKernel(ctx.graph, nh, selected_impl); + op.backend = selected_backend; + active_backends.insert(selected_backend); + } + } + gr.metadata().set(ActiveBackends{active_backends}); +} + +void cv::gimpl::passes::expandKernels(ade::passes::PassContext &ctx, const gapi::GKernelPackage &kernels) +{ + GModel::Graph gr(ctx.graph); + + // Repeat the loop while there are compound kernels. + // Restart procedure after every successfull unrolling + bool has_compound_kernel = true; + while (has_compound_kernel) + { + has_compound_kernel = false; + for (const auto& nh : gr.nodes()) + { + if (gr.metadata(nh).get().t == NodeType::OP) + { + const auto& op = gr.metadata(nh).get(); + + cv::gapi::GBackend selected_backend; + cv::GKernelImpl selected_impl; + std::tie(selected_backend, selected_impl) = kernels.lookup(op.k.name); + + if (selected_backend == cv::gapi::compound::backend()) + { + has_compound_kernel = true; + expand(ctx.graph, nh, ImplInfo{selected_impl, op.args}); + break; + } + } + } + } + GAPI_LOG_INFO(NULL, "Final graph: " << ctx.graph.nodes().size() << " nodes" << std::endl); +} diff --git a/modules/gapi/src/compiler/passes/meta.cpp b/modules/gapi/src/compiler/passes/meta.cpp new file mode 100644 index 0000000000..0be6cac62f --- /dev/null +++ b/modules/gapi/src/compiler/passes/meta.cpp @@ -0,0 +1,123 @@ +// 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) 2018 Intel Corporation + + +#include // util::indexed +#include +#include + +#include "compiler/gmodel.hpp" +#include "compiler/passes/passes.hpp" +#include "logger.hpp" // GAPI_LOG + + +// Iterate over all nodes and initialize meta of objects taken from the +// outside (i.e., computation input/output arguments) +void cv::gimpl::passes::initMeta(ade::passes::PassContext &ctx, const GMetaArgs &metas) +{ + GModel::Graph gr(ctx.graph); + + const auto &proto = gr.metadata().get(); + + for (const auto& it : ade::util::indexed(proto.in_nhs)) + { + auto& data = gr.metadata(ade::util::value(it)).get(); + data.meta = metas.at(ade::util::index(it)); + } +} + +// Iterate over all operations in the topological order, trigger kernels +// validate() function, update output objects metadata. +void cv::gimpl::passes::inferMeta(ade::passes::PassContext &ctx) +{ + // FIXME: ADE pass dependency on topo_sort? + // FIXME: ADE pass dependency on initMeta? + GModel::Graph gr(ctx.graph); + + const auto sorted = gr.metadata().get() ; + for (const auto &nh : sorted.nodes()) + { + if (gr.metadata(nh).get().t == NodeType::OP) + { + const auto& op = gr.metadata(nh).get(); + GAPI_Assert(op.k.outMeta != nullptr); + + // Prepare operation's input metadata vector + // Note that it's size is usually different from nh.inEdges.size(), + // and its element count is equal to operation's arguments count. + GMetaArgs input_meta_args(op.args.size()); + + // Iterate through input edges, update input_meta_args's slots + // appropriately. Not all of them will be updated due to (see above). + GAPI_Assert(nh->inEdges().size() > 0); + for (const auto &in_eh : nh->inEdges()) + { + const auto& input_port = gr.metadata(in_eh).get().port; + const auto& input_nh = in_eh->srcNode(); + GAPI_Assert(gr.metadata(input_nh).get().t == NodeType::DATA); + + const auto& input_meta = gr.metadata(input_nh).get().meta; + if (util::holds_alternative(input_meta)) + { + // No meta in an input argument - a fatal error + // (note graph is traversed here in topoligcal order) + util::throw_error(std::logic_error("Fatal: input object's metadata " + "not found!")); + // FIXME: Add more details!!! + } + input_meta_args.at(input_port) = input_meta; + } + // Now ask kernel for it's output meta. + // Resulting out_args may have a larger size than op.outs, since some + // outputs could stay unused (unconnected) + const GMetaArgs out_metas = op.k.outMeta(input_meta_args, op.args); + + // Walk through operation's outputs, update meta of output objects + // appropriately + GAPI_Assert(nh->outEdges().size() > 0); + for (const auto &out_eh : nh->outEdges()) + { + const auto &output_port = gr.metadata(out_eh).get().port; + const auto &output_nh = out_eh->dstNode(); + GAPI_Assert(gr.metadata(output_nh).get().t == NodeType::DATA); + + auto &output_meta = gr.metadata(output_nh).get().meta; + if (!util::holds_alternative(output_meta)) + { + GAPI_LOG_INFO(NULL, + "!!! Output object has an initialized meta - " + "how it is possible today?" << std::endl; ); + if (output_meta != out_metas.at(output_port)) + { + util::throw_error(std::logic_error("Fatal: meta mismatch")); + // FIXME: New exception type? + // FIXME: More details! + } + } + // Store meta in graph + output_meta = out_metas.at(output_port); + } + } // if(OP) + } // for(sorted) +} + +// After all metadata in graph is infered, store a vector of inferred metas +// for computation output values. +void cv::gimpl::passes::storeResultingMeta(ade::passes::PassContext &ctx) +{ + GModel::Graph gr(ctx.graph); + + const auto &proto = gr.metadata().get(); + GMetaArgs output_metas(proto.out_nhs.size()); + + for (const auto& it : ade::util::indexed(proto.out_nhs)) + { + auto& data = gr.metadata(ade::util::value(it)).get(); + output_metas[ade::util::index(it)] = data.meta; + } + + gr.metadata().set(OutputMeta{output_metas}); +} diff --git a/modules/gapi/src/compiler/passes/passes.hpp b/modules/gapi/src/compiler/passes/passes.hpp new file mode 100644 index 0000000000..d73653b18a --- /dev/null +++ b/modules/gapi/src/compiler/passes/passes.hpp @@ -0,0 +1,58 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_COMPILER_PASSES_HPP +#define OPENCV_GAPI_COMPILER_PASSES_HPP + +#include +#include + +#include "opencv2/gapi/garg.hpp" +#include "opencv2/gapi/gcommon.hpp" + +// Forward declarations - external +namespace ade { + class Graph; + + namespace passes { + struct PassContext; + } +} + +namespace cv { + +namespace gimpl { namespace passes { + +void dumpDot(const ade::Graph &g, std::ostream& os); +void dumpDot(ade::passes::PassContext &ctx, std::ostream& os); +void dumpDotStdout(ade::passes::PassContext &ctx); +void dumpGraph(ade::passes::PassContext &ctx, const std::string& dump_path); +void dumpDotToFile(ade::passes::PassContext &ctx, const std::string& dump_path); + +void initIslands(ade::passes::PassContext &ctx); +void checkIslands(ade::passes::PassContext &ctx); +void checkIslandsContent(ade::passes::PassContext &ctx); + +void initMeta(ade::passes::PassContext &ctx, const GMetaArgs &metas); +void inferMeta(ade::passes::PassContext &ctx); +void storeResultingMeta(ade::passes::PassContext &ctx); + +void expandKernels(ade::passes::PassContext &ctx, + const gapi::GKernelPackage& kernels); + +void resolveKernels(ade::passes::PassContext &ctx, + const gapi::GKernelPackage &kernels, + const gapi::GLookupOrder &order); + +void fuseIslands(ade::passes::PassContext &ctx); +void syncIslandTags(ade::passes::PassContext &ctx); + +}} // namespace gimpl::passes + +} // namespace cv + +#endif // OPENCV_GAPI_COMPILER_PASSES_HPP diff --git a/modules/gapi/src/compiler/transactions.hpp b/modules/gapi/src/compiler/transactions.hpp new file mode 100644 index 0000000000..6b1800f018 --- /dev/null +++ b/modules/gapi/src/compiler/transactions.hpp @@ -0,0 +1,147 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP +#define OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP + +#include // find_if +#include +#include + +#include + +#include "opencv2/core/base.hpp" + +enum class Direction: int {Invalid, In, Out}; + +//////////////////////////////////////////////////////////////////////////// +//// +// TODO: Probably it can be moved to ADE + +namespace Change +{ + struct Base + { + virtual void commit (ade::Graph & ) {}; + virtual void rollback(ade::Graph & ) {}; + virtual ~Base() = default; + }; + + class NodeCreated final: public Base + { + ade::NodeHandle m_node; + public: + explicit NodeCreated(const ade::NodeHandle &nh) : m_node(nh) {} + virtual void rollback(ade::Graph &g) override { g.erase(m_node); } + }; + + // NB: Drops all metadata stored in the EdgeHandle, + // which is not restored even in the rollback + + // FIXME: either add a way for users to preserve meta manually + // or extend ADE to manipulate with meta such way + class DropLink final: public Base + { + ade::NodeHandle m_node; + Direction m_dir; + + ade::NodeHandle m_sibling; + + public: + DropLink(ade::Graph &g, + const ade::NodeHandle &node, + const ade::EdgeHandle &edge) + : m_node(node), m_dir(node == edge->srcNode() + ? Direction::Out + : Direction::In) + { + m_sibling = (m_dir == Direction::In + ? edge->srcNode() + : edge->dstNode()); + g.erase(edge); + } + + virtual void rollback(ade::Graph &g) override + { + switch(m_dir) + { + case Direction::In: g.link(m_sibling, m_node); break; + case Direction::Out: g.link(m_node, m_sibling); break; + default: CV_Assert(false); + } + } + }; + + class NewLink final: public Base + { + ade::EdgeHandle m_edge; + + public: + NewLink(ade::Graph &g, + const ade::NodeHandle &prod, + const ade::NodeHandle &cons) + : m_edge(g.link(prod, cons)) + { + } + + virtual void rollback(ade::Graph &g) override + { + g.erase(m_edge); + } + }; + + class DropNode final: public Base + { + ade::NodeHandle m_node; + + public: + explicit DropNode(const ade::NodeHandle &nh) + : m_node(nh) + { + // According to the semantic, node should be disconnected + // manually before it is dropped + CV_Assert(m_node->inEdges().size() == 0); + CV_Assert(m_node->outEdges().size() == 0); + } + + virtual void commit(ade::Graph &g) override + { + g.erase(m_node); + } + }; + + class List + { + std::list< std::unique_ptr > m_changes; + + public: + template + void enqueue(Args&&... args) + { + std::unique_ptr p(new T(args...)); + m_changes.push_back(std::move(p)); + } + + void commit(ade::Graph &g) + { + // Commit changes in the forward order + for (auto& ch : m_changes) ch->commit(g); + } + + void rollback(ade::Graph &g) + { + // Rollback changes in the reverse order + for (auto it = m_changes.rbegin(); it != m_changes.rend(); ++it) + { + (*it)->rollback(g); + } + } + }; +} // namespace Change +//////////////////////////////////////////////////////////////////////////// + +#endif // OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP diff --git a/modules/gapi/src/executor/gexecutor.cpp b/modules/gapi/src/executor/gexecutor.cpp new file mode 100644 index 0000000000..4709f9d0c1 --- /dev/null +++ b/modules/gapi/src/executor/gexecutor.cpp @@ -0,0 +1,211 @@ +// 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) 2018 Intel Corporation + + +#include + +#include + +#include "opencv2/core/types.hpp" +#include "executor/gexecutor.hpp" + +cv::gimpl::GExecutor::GExecutor(std::unique_ptr &&g_model) + : m_orig_graph(std::move(g_model)) + , m_island_graph(GModel::Graph(*m_orig_graph).metadata() + .get().model) + , m_gm(*m_orig_graph) + , m_gim(*m_island_graph) +{ + // NB: Right now GIslandModel is acyclic, so for a naive execution, + // simple unrolling to a list of triggers is enough + + // Naive execution model is similar to current CPU (OpenCV) plugin + // execution model: + // 1. Allocate all internal resources first (NB - CPU plugin doesn't do it) + // 2. Put input/output GComputation arguments to the storage + // 3. For every Island, prepare vectors of input/output parameter descs + // 4. Iterate over a list of operations (sorted in the topological order) + // 5. For every operation, form a list of input/output data objects + // 6. Run GIslandExecutable + // 7. writeBack + + m_ops.reserve(m_gim.nodes().size()); + auto sorted = m_gim.metadata().get(); + for (auto nh : sorted.nodes()) + { + switch (m_gim.metadata(nh).get().k) + { + case NodeKind::ISLAND: + { + std::vector input_rcs; + std::vector output_rcs; + input_rcs.reserve(nh->inNodes().size()); + output_rcs.reserve(nh->outNodes().size()); + + auto xtract = [&](ade::NodeHandle slot_nh, std::vector &vec) { + const auto orig_data_nh + = m_gim.metadata(slot_nh).get().original_data_node; + const auto &orig_data_info + = m_gm.metadata(orig_data_nh).get(); + vec.emplace_back(RcDesc{ orig_data_info.rc + , orig_data_info.shape + , orig_data_info.ctor}); + }; + // (3) + for (auto in_slot_nh : nh->inNodes()) xtract(in_slot_nh, input_rcs); + for (auto out_slot_nh : nh->outNodes()) xtract(out_slot_nh, output_rcs); + m_ops.emplace_back(OpDesc{ std::move(input_rcs) + , std::move(output_rcs) + , m_gim.metadata(nh).get().object}); + } + break; + + case NodeKind::SLOT: + { + const auto orig_data_nh + = m_gim.metadata(nh).get().original_data_node; + // (1) + initResource(orig_data_nh); + m_slots.emplace_back(DataDesc{nh, orig_data_nh}); + } + break; + + default: + GAPI_Assert(false); + break; + } // switch(kind) + } // for(gim nodes) +} + +void cv::gimpl::GExecutor::initResource(const ade::NodeHandle &orig_nh) +{ + const Data &d = m_gm.metadata(orig_nh).get(); + + if ( d.storage != Data::Storage::INTERNAL + && d.storage != Data::Storage::CONST) + return; + + // INTERNALS+CONST only! no need to allocate/reset output objects + // to as it is bound externally (e.g. already in the m_res) + + switch (d.shape) + { + case GShape::GMAT: + { + const auto desc = util::get(d.meta); + const auto type = CV_MAKETYPE(desc.depth, desc.chan); + m_res.slot()[d.rc].create(desc.size, type); + } + break; + + case GShape::GSCALAR: + if (d.storage == Data::Storage::CONST) + { + auto rc = RcDesc{d.rc, d.shape, d.ctor}; + magazine::bindInArg(m_res, rc, m_gm.metadata(orig_nh).get().arg); + } + break; + + case GShape::GARRAY: + // Constructed on Reset, do nothing here + break; + + default: + GAPI_Assert(false); + } +} + +void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args) +{ + // (2) + const auto proto = m_gm.metadata().get(); + + // Basic check if input/output arguments are correct + // FIXME: Move to GCompiled (do once for all GExecutors) + if (proto.inputs.size() != args.inObjs.size()) // TODO: Also check types + { + util::throw_error(std::logic_error + ("Computation's input protocol doesn\'t " + "match actual arguments!")); + } + if (proto.outputs.size() != args.outObjs.size()) // TODO: Also check types + { + util::throw_error(std::logic_error + ("Computation's output protocol doesn\'t " + "match actual arguments!")); + } + + namespace util = ade::util; + + //ensure that output Mat parameters are correctly allocated + for (auto index : util::iota(proto.out_nhs.size()) ) //FIXME: avoid copy of NodeHandle and GRunRsltComp ? + { + auto& nh = proto.out_nhs.at(index); + const Data &d = m_gm.metadata(nh).get(); + if (d.shape == GShape::GMAT) + { + using cv::util::get; + const auto desc = get(d.meta); + const auto type = CV_MAKETYPE(desc.depth, desc.chan); + auto& out_mat = *get(args.outObjs.at(index)); + out_mat.create(cv::gapi::own::to_ocv(desc.size), type); + } + } + // Update storage with user-passed objects + for (auto it : ade::util::zip(ade::util::toRange(proto.inputs), + ade::util::toRange(args.inObjs))) + { + magazine::bindInArg(m_res, std::get<0>(it), std::get<1>(it)); + } + for (auto it : ade::util::zip(ade::util::toRange(proto.outputs), + ade::util::toRange(args.outObjs))) + { + magazine::bindOutArg(m_res, std::get<0>(it), std::get<1>(it)); + } + + // Reset internal data + for (auto &sd : m_slots) + { + const auto& data = m_gm.metadata(sd.data_nh).get(); + magazine::resetInternalData(m_res, data); + } + + // Run the script + for (auto &op : m_ops) + { + // (5) + using InObj = GIslandExecutable::InObj; + using OutObj = GIslandExecutable::OutObj; + std::vector in_objs; + std::vector out_objs; + in_objs.reserve (op.in_objects.size()); + out_objs.reserve(op.out_objects.size()); + + for (const auto &rc : op.in_objects) + { + in_objs.emplace_back(InObj{rc, magazine::getArg(m_res, rc)}); + } + for (const auto &rc : op.out_objects) + { + out_objs.emplace_back(OutObj{rc, magazine::getObjPtr(m_res, rc)}); + } + + // (6) + op.isl_exec->run(std::move(in_objs), std::move(out_objs)); + } + + // (7) + for (auto it : ade::util::zip(ade::util::toRange(proto.outputs), + ade::util::toRange(args.outObjs))) + { + magazine::writeBack(m_res, std::get<0>(it), std::get<1>(it)); + } +} + +const cv::gimpl::GModel::Graph& cv::gimpl::GExecutor::model() const +{ + return m_gm; +} diff --git a/modules/gapi/src/executor/gexecutor.hpp b/modules/gapi/src/executor/gexecutor.hpp new file mode 100644 index 0000000000..47c5582891 --- /dev/null +++ b/modules/gapi/src/executor/gexecutor.hpp @@ -0,0 +1,94 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_GEXECUTOR_HPP +#define OPENCV_GAPI_GEXECUTOR_HPP + +#include // unique_ptr, shared_ptr + +#include // tuple, required by magazine +#include // required by magazine + +#include + +#include "backends/common/gbackend.hpp" + +namespace cv { +namespace gimpl { + +// Graph-level executor interface. +// +// This class specifies API for a "super-executor" which orchestrates +// the overall Island graph execution. +// +// Every Island (subgraph) execution is delegated to a particular +// backend and is done opaquely to the GExecutor. +// +// Inputs to a GExecutor instance are: +// - GIslandModel - a high-level graph model which may be seen as a +// "procedure" to execute. +// - GModel - a low-level graph of operations (from which a GIslandModel +// is projected) +// - GComputation runtime arguments - vectors of input/output objects +// +// Every GExecutor is responsible for +// a. Maintaining non-island (intermediate) data objects within graph +// b. Providing GIslandExecutables with input/output data according to +// their protocols +// c. Triggering execution of GIslandExecutables when task/data dependencies +// are met. +// +// By default G-API stores all data on host, and cross-Island +// exchange happens via host buffers (and CV data objects). +// +// Today's exchange data objects are: +// - cv::Mat - for image buffers +// - cv::Scalar - for single values (with up to four components inside) +// - cv::detail::VectorRef - an untyped wrapper over std::vector +// + +class GExecutor +{ +protected: + std::unique_ptr m_orig_graph; + std::shared_ptr m_island_graph; + + cv::gimpl::GModel::Graph m_gm; // FIXME: make const? + cv::gimpl::GIslandModel::Graph m_gim; // FIXME: make const? + + // FIXME: Naive executor details are here for now + // but then it should be moved to another place + struct OpDesc + { + std::vector in_objects; + std::vector out_objects; + std::shared_ptr isl_exec; + }; + std::vector m_ops; + + struct DataDesc + { + ade::NodeHandle slot_nh; + ade::NodeHandle data_nh; + }; + std::vector m_slots; + + Mag m_res; + + void initResource(const ade::NodeHandle &orig_nh); // FIXME: shouldn't it be RcDesc? + +public: + explicit GExecutor(std::unique_ptr &&g_model); + void run(cv::gimpl::GRuntimeArgs &&args); + + const GModel::Graph& model() const; // FIXME: make it ConstGraph? +}; + +} // namespace gimpl +} // namespace cv + +#endif // OPENCV_GAPI_GEXECUTOR_HPP diff --git a/modules/gapi/src/logger.hpp b/modules/gapi/src/logger.hpp new file mode 100644 index 0000000000..047f6e4106 --- /dev/null +++ b/modules/gapi/src/logger.hpp @@ -0,0 +1,24 @@ +// 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) 2018 Intel Corporation + + +#ifndef __OPENCV_GAPI_LOGGER_HPP__ +#define __OPENCV_GAPI_LOGGER_HPP__ + +#if 1 +#include "opencv2/core/utils/logger.hpp" + +#define GAPI_LOG_INFO(tag, ...) CV_LOG_INFO(tag, __VA_ARGS__) +#define GAPI_LOG_WARNING(tag, ...) CV_LOG_WARNING(tag, __VA_ARGS__) + +#else +#define GAPI_LOG_INFO(tag, ...) +#define GAPI_LOG_WARNING(tag, ...) + +#endif + + +#endif // __OPENCV_GAPI_LOGGER_HPP__ diff --git a/modules/gapi/src/precomp.hpp b/modules/gapi/src/precomp.hpp new file mode 100644 index 0000000000..ad15bc4dcc --- /dev/null +++ b/modules/gapi/src/precomp.hpp @@ -0,0 +1,19 @@ +// 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) 2018 Intel Corporation + + +#ifndef __OPENCV_GAPI_PRECOMP_HPP__ +#define __OPENCV_GAPI_PRECOMP_HPP__ + +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +#include "opencv2/gapi.hpp" +#include "opencv2/gapi/gkernel.hpp" +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/imgproc.hpp" + +#endif // __OPENCV_GAPI_PRECOMP_HPP__ diff --git a/modules/gapi/test/common/gapi_compoundkernel_tests.cpp b/modules/gapi/test/common/gapi_compoundkernel_tests.cpp new file mode 100644 index 0000000000..1f5de7a920 --- /dev/null +++ b/modules/gapi/test/common/gapi_compoundkernel_tests.cpp @@ -0,0 +1,500 @@ +// 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) 2018 Intel Corporation + + +// FIXME: move out from Common + +#include "test_precomp.hpp" +#include "opencv2/gapi/cpu/core.hpp" + +#include + +namespace opencv_test +{ +namespace +{ + G_TYPED_KERNEL(GCompoundDoubleAddC, , "org.opencv.test.compound_double_addC") + { + static GMatDesc outMeta(GMatDesc in, GScalarDesc) { return in; } + }; + + GAPI_COMPOUND_KERNEL(GCompoundDoubleAddCImpl, GCompoundDoubleAddC) + { + static GMat expand(cv::GMat in, cv::GScalar s) + { + return cv::gapi::addC(cv::gapi::addC(in, s), s); + } + }; + + G_TYPED_KERNEL(GCompoundAddC, , "org.opencv.test.compound_addC") + { + static GMatDesc outMeta(GMatDesc in, GScalarDesc) { return in; } + }; + + GAPI_COMPOUND_KERNEL(GCompoundAddCImpl, GCompoundAddC) + { + static GMat expand(cv::GMat in, cv::GScalar s) + { + return cv::gapi::addC(in, s); + } + }; + + using GMat3 = std::tuple; + using GMat2 = std::tuple; + + G_TYPED_KERNEL_M(GCompoundMergeWithSplit, , "org.opencv.test.compound_merge_split") + { + static std::tuple outMeta(GMatDesc a, GMatDesc b, GMatDesc c) + { + return std::make_tuple(a, b, c); + } + }; + + GAPI_COMPOUND_KERNEL(GCompoundMergeWithSplitImpl, GCompoundMergeWithSplit) + { + static GMat3 expand(cv::GMat a, cv::GMat b, cv::GMat c) + { + return cv::gapi::split3(cv::gapi::merge3(a, b, c)); + } + }; + + G_TYPED_KERNEL(GCompoundAddWithAddC, , "org.opencv.test.compound_add_with_addc") + { + static GMatDesc outMeta(GMatDesc in, GMatDesc, GScalarDesc) + { + return in; + } + }; + + GAPI_COMPOUND_KERNEL(GCompoundAddWithAddCImpl, GCompoundAddWithAddC) + { + static GMat expand(cv::GMat in1, cv::GMat in2, cv::GScalar s) + { + return cv::gapi::addC(cv::gapi::add(in1, in2), s); + } + }; + + G_TYPED_KERNEL_M(GCompoundSplitWithAdd, , "org.opencv.test.compound_split_with_add") + { + static std::tuple outMeta(GMatDesc in) + { + const auto out_depth = in.depth; + const auto out_desc = in.withType(out_depth, 1); + return std::make_tuple(out_desc, out_desc); + } + }; + + GAPI_COMPOUND_KERNEL(GCompoundSplitWithAddImpl, GCompoundSplitWithAdd) + { + static GMat2 expand(cv::GMat in) + { + cv::GMat a, b, c; + std::tie(a, b, c) = cv::gapi::split3(in); + return std::make_tuple(cv::gapi::add(a, b), c); + } + }; + + G_TYPED_KERNEL_M(GCompoundParallelAddC, , "org.opencv.test.compound_parallel_addc") + { + static std::tuple outMeta(GMatDesc in, GScalarDesc) + { + return std::make_tuple(in, in); + } + }; + + GAPI_COMPOUND_KERNEL(GCompoundParallelAddCImpl, GCompoundParallelAddC) + { + static GMat2 expand(cv::GMat in, cv::GScalar s) + { + return std::make_tuple(cv::gapi::addC(in, s), cv::gapi::addC(in, s)); + } + }; + + GAPI_COMPOUND_KERNEL(GCompoundAddImpl, cv::gapi::core::GAdd) + { + static GMat expand(cv::GMat in1, cv::GMat in2, int) + { + return cv::gapi::sub(cv::gapi::sub(in1, in2), in2); + } + }; + + G_TYPED_KERNEL(GCompoundAddWithAddCWithDoubleAddC, , "org.opencv.test.compound_add_with_addC_with_double_addC") + { + static GMatDesc outMeta(GMatDesc in, GMatDesc, GScalarDesc) + { + return in; + } + }; + + GAPI_COMPOUND_KERNEL(GCompoundAddWithAddCWithDoubleAddCImpl, GCompoundAddWithAddCWithDoubleAddC) + { + static GMat expand(cv::GMat in1, cv::GMat in2, cv::GScalar s) + { + return GCompoundDoubleAddC::on(GCompoundAddWithAddC::on(in1, in2, s), s); + } + }; + + using GDoubleArray = cv::GArray; + G_TYPED_KERNEL(GNegateArray, , "org.opencv.test.negate_array") + { + static GArrayDesc outMeta(const GArrayDesc&) { return empty_array_desc(); } + }; + + GAPI_OCV_KERNEL(GNegateArrayImpl, GNegateArray) + { + static void run(const std::vector& in, std::vector& out) + { + ade::util::transform(in, std::back_inserter(out), std::negate()); + } + }; + + G_TYPED_KERNEL(GMaxInArray, , "org.opencv.test.max_in_array") + { + static GScalarDesc outMeta(const GArrayDesc&) { return empty_scalar_desc(); } + }; + + GAPI_OCV_KERNEL(GMaxInArrayImpl, GMaxInArray) + { + static void run(const std::vector& in, cv::Scalar& out) + { + out = *std::max_element(in.begin(), in.end()); + } + }; + + G_TYPED_KERNEL(GCompoundMaxInArray, , "org.opencv.test.compound_max_in_array") + { + static GScalarDesc outMeta(const GArrayDesc&) { return empty_scalar_desc(); } + }; + + GAPI_COMPOUND_KERNEL(GCompoundMaxInArrayImpl, GCompoundMaxInArray) + { + static GScalar expand(GDoubleArray in) + { + return GMaxInArray::on(in); + } + }; + + G_TYPED_KERNEL(GCompoundNegateArray, , "org.opencv.test.compound_negate_array") + { + static GArrayDesc outMeta(const GArrayDesc&) { return empty_array_desc(); } + }; + + GAPI_COMPOUND_KERNEL(GCompoundNegateArrayImpl, GCompoundNegateArray) + { + static GDoubleArray expand(GDoubleArray in) + { + return GNegateArray::on(in); + } + }; + + G_TYPED_KERNEL(SetDiagKernel, , "org.opencv.test.empty_kernel") + { + static GMatDesc outMeta(GMatDesc in, GArrayDesc) { return in; } + }; + + void setDiag(cv::Mat& in, const std::vector& diag) + { + GAPI_Assert(in.rows == static_cast(diag.size())); + GAPI_Assert(in.cols == static_cast(diag.size())); + for (int i = 0; i < in.rows; ++i) + { + in.at(i, i) = static_cast(diag[i]); + } + } + + GAPI_OCV_KERNEL(SetDiagKernelImpl, SetDiagKernel) + { + static void run(const cv::Mat& in, const std::vector& v, cv::Mat& out) + { + in.copyTo(out); + setDiag(out, v); + } + }; + + G_TYPED_KERNEL(GCompoundGMatGArrayGMat, , "org.opencv.test.compound_gmat_garray_gmat") + { + static GMatDesc outMeta(GMatDesc in, GArrayDesc, GMatDesc) { return in; } + }; + + GAPI_COMPOUND_KERNEL(GCompoundGMatGArrayGMatImpl, GCompoundGMatGArrayGMat) + { + static GMat expand(GMat a, GDoubleArray b, GMat c) + { + return SetDiagKernel::on(cv::gapi::add(a, c), b); + } + }; + +} // namespace + +// FIXME avoid cv::combine that use custom and default kernels together +TEST(GCompoundKernel, ReplaceDefaultKernel) +{ + cv::GMat in1, in2; + auto out = cv::gapi::add(in1, in2); + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(cv::gapi::core::cpu::kernels(), custom_pkg, cv::unite_policy::REPLACE); + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out)); + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat(3, 3, CV_8UC1); + + comp.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat1 - in_mat2 - in_mat2; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, DoubleAddC) +{ + cv::GMat in1, in2; + cv::GScalar s; + auto add_res = cv::gapi::add(in1, in2); + auto super = GCompoundDoubleAddC::on(add_res, s); + auto out = cv::gapi::addC(super, s); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in1, in2, s), cv::GOut(out)); + + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat(3, 3, CV_8UC1); + + cv::Scalar scalar = 2; + + comp.apply(cv::gin(in_mat1, in_mat2, scalar), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat1 + in_mat2 + scalar + scalar + scalar; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, AddC) +{ + cv::GMat in1, in2; + cv::GScalar s; + auto add_res = cv::gapi::add(in1, in2); + auto super = GCompoundAddC::on(add_res, s); + auto out = cv::gapi::addC(super, s); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in1, in2, s), cv::GOut(out)); + + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat(3, 3, CV_8UC1); + + cv::Scalar scalar = 2; + + comp.apply(cv::gin(in_mat1, in_mat2, scalar), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat1 + in_mat2 + scalar + scalar; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, MergeWithSplit) +{ + cv::GMat in, a1, b1, c1, + a2, b2, c2; + + std::tie(a1, b1, c1) = cv::gapi::split3(in); + std::tie(a2, b2, c2) = GCompoundMergeWithSplit::on(a1, b1, c1); + auto out = cv::gapi::merge3(a2, b2, c2); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC3), out_mat, ref_mat; + comp.apply(cv::gin(in_mat), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, AddWithAddC) +{ + cv::GMat in1, in2; + cv::GScalar s; + auto out = GCompoundAddWithAddC::on(in1, in2, s); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in1, in2, s), cv::GOut(out)); + + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat(3, 3, CV_8UC1); + + cv::Scalar scalar = 2; + + comp.apply(cv::gin(in_mat1, in_mat2, scalar), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat1 + in_mat2 + scalar; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, SplitWithAdd) +{ + cv::GMat in, out1, out2; + std::tie(out1, out2) = GCompoundSplitWithAdd::on(in); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in), cv::GOut(out1, out2)); + + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC3), + out_mat1(3, 3, CV_8UC1), + out_mat2(3, 3, CV_8UC1), + ref_mat1(3, 3, CV_8UC1), + ref_mat2(3, 3, CV_8UC1); + + comp.apply(cv::gin(in_mat), cv::gout(out_mat1, out_mat2), cv::compile_args(full_pkg)); + + std::vector channels(3); + cv::split(in_mat, channels); + + ref_mat1 = channels[0] + channels[1]; + ref_mat2 = channels[2]; + + EXPECT_EQ(0, cv::countNonZero(out_mat1 != ref_mat1)); + EXPECT_EQ(0, cv::countNonZero(out_mat2 != ref_mat2)); +} + +TEST(GCompoundKernel, ParallelAddC) +{ + cv::GMat in1, out1, out2; + cv::GScalar in2; + std::tie(out1, out2) = GCompoundParallelAddC::on(in1, in2); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(out1, out2)); + + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1), + out_mat1(3, 3, CV_8UC1), + out_mat2(3, 3, CV_8UC1), + ref_mat1(3, 3, CV_8UC1), + ref_mat2(3, 3, CV_8UC1); + + cv::Scalar scalar = 2; + + comp.apply(cv::gin(in_mat, scalar), cv::gout(out_mat1, out_mat2), cv::compile_args(full_pkg)); + + ref_mat1 = in_mat + scalar; + ref_mat2 = in_mat + scalar; + + EXPECT_EQ(0, cv::countNonZero(out_mat1 != ref_mat1)); + EXPECT_EQ(0, cv::countNonZero(out_mat2 != ref_mat2)); +} + +TEST(GCompoundKernel, GCompundKernelAndDefaultUseOneData) +{ + cv::GMat in1, in2; + cv::GScalar s; + auto out = cv::gapi::add(GCompoundAddWithAddC::on(in1, in2, s), cv::gapi::addC(in2, s)); + + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in1, in2, s), cv::GOut(out)); + + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat(3, 3, CV_8UC1); + + cv::Scalar scalar = 2; + + comp.apply(cv::gin(in_mat1, in_mat2, scalar), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat1 + in_mat2 + scalar + in_mat2 + scalar; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, CompoundExpandedToCompound) +{ + cv::GMat in1, in2; + cv::GScalar s; + auto out = GCompoundAddWithAddCWithDoubleAddC::on(in1, in2, s); + + const auto custom_pkg = cv::gapi::kernels(); + + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in1, in2, s), cv::GOut(out)); + + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat(3, 3, CV_8UC1); + + cv::Scalar scalar = 2; + + comp.apply(cv::gin(in_mat1, in_mat2, scalar), cv::gout(out_mat), cv::compile_args(full_pkg)); + ref_mat = in_mat1 + in_mat2 + scalar + scalar + scalar; + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GCompoundKernel, MaxInArray) +{ + GDoubleArray in; + auto out = GCompoundMaxInArray::on(in); + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + std::vector v = { 1, 5, -2, 3, 10, 2}; + cv::Scalar out_scl; + cv::Scalar ref_scl(*std::max_element(v.begin(), v.end())); + + comp.apply(cv::gin(v), cv::gout(out_scl), cv::compile_args(full_pkg)); + + EXPECT_EQ(out_scl, ref_scl); +} + +TEST(GCompoundKernel, NegateArray) +{ + GDoubleArray in; + GDoubleArray out = GCompoundNegateArray::on(in); + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + std::vector in_v = {1, 5, -2, -10, 3}; + std::vector out_v; + std::vector ref_v; + ade::util::transform(in_v, std::back_inserter(ref_v), std::negate()); + + comp.apply(cv::gin(in_v), cv::gout(out_v), cv::compile_args(full_pkg)); + + EXPECT_EQ(out_v, ref_v); +} + +TEST(GCompoundKernel, RightGArrayHandle) +{ + cv::GMat in[2]; + GDoubleArray a; + cv::GMat out = GCompoundGMatGArrayGMat::on(in[0], a, in[1]); + const auto custom_pkg = cv::gapi::kernels(); + const auto full_pkg = cv::gapi::combine(custom_pkg, cv::gapi::core::cpu::kernels(), cv::unite_policy::KEEP); + cv::GComputation comp(cv::GIn(in[0], a, in[1]), cv::GOut(out)); + std::vector in_v(3, 1.0); + cv::Mat in_mat1 = cv::Mat::eye(cv::Size(3, 3), CV_8UC1), + in_mat2 = cv::Mat::eye(cv::Size(3, 3), CV_8UC1), + out_mat; + cv::Mat ref_mat= in_mat1 + in_mat2; + setDiag(ref_mat, in_v); + + comp.apply(cv::gin(in_mat1, in_v, in_mat2), cv::gout(out_mat), cv::compile_args(full_pkg)); + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); + +} +} // opencv_test diff --git a/modules/gapi/test/common/gapi_core_tests.cpp b/modules/gapi/test/common/gapi_core_tests.cpp new file mode 100644 index 0000000000..1b3e7f7896 --- /dev/null +++ b/modules/gapi/test/common/gapi_core_tests.cpp @@ -0,0 +1,8 @@ +// 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) 2018 Intel Corporation + + +#include "gapi_core_tests_inl.hpp" diff --git a/modules/gapi/test/common/gapi_core_tests.hpp b/modules/gapi/test/common/gapi_core_tests.hpp new file mode 100644 index 0000000000..b0b15a502a --- /dev/null +++ b/modules/gapi/test/common/gapi_core_tests.hpp @@ -0,0 +1,151 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_CORE_TESTS_HPP +#define OPENCV_GAPI_CORE_TESTS_HPP + +#include + +#include "gapi_tests_common.hpp" + +namespace opencv_test +{ +enum mathOp +{ + ADD = 0, + SUB = 1, + MUL = 2, + DIV = 3 +}; + +enum bitwiseOp +{ + AND = 0, + OR = 1, + XOR = 2, + NOT = 3 +}; + +namespace +{ +const char *MathOperations[] = {"ADD", "SUB", "MUL", "DIV"}; +const char *BitwiseOperations[] = {"And", "Or", "Xor"}; +const char *CompareOperations[] = {"CMP_EQ", "CMP_GT", "CMP_GE", "CMP_LT", "CMP_LE", "CMP_NE"}; +//corresponds to OpenCV +const char *NormOperations[] = {"", "NORM_INF", "NORM_L1", "","NORM_L2"}; +} + + +struct PrintMathOpCoreParams +{ + template + std::string operator()(const ::testing::TestParamInfo& info) const + { + std::stringstream ss; + cv::Size sz = std::get<4>(info.param); + ss<(info.param)] + <<"_"<(info.param) + <<"_"<(info.param) + <<"_"<<(int)std::get<3>(info.param) + <<"_"<(info.param)+1) + <<"_"<(info.param) + <<"_"<(info.param); + return ss.str(); + } +}; + +struct PrintCmpCoreParams +{ + template + std::string operator()(const ::testing::TestParamInfo& info) const + { + std::stringstream ss; + cv::Size sz = std::get<3>(info.param); + ss<(info.param)] + <<"_"<(info.param) + <<"_"<(info.param) + <<"_"<(info.param); + return ss.str(); + } +}; + +struct PrintBWCoreParams +{ + template + std::string operator()(const ::testing::TestParamInfo& info) const + { + std::stringstream ss; + cv::Size sz = std::get<2>(info.param); + ss<(info.param)] + <<"_"<(info.param) + <<"_"<(info.param); + return ss.str(); + } +}; + +struct PrintNormCoreParams +{ + template + std::string operator()(const ::testing::TestParamInfo& info) const + { + std::stringstream ss; + cv::Size sz = std::get<2>(info.param); + ss<(info.param)] + <<"_"<(info.param) + <<"_"<>{}; +struct MulDoubleTest : public TestParams>{}; +struct DivTest : public TestParams>{}; +struct DivCTest : public TestParams>{}; +struct MeanTest : public TestParams> {}; +struct MaskTest : public TestParams> {}; +struct Polar2CartTest : public TestParams> {}; +struct Cart2PolarTest : public TestParams> {}; +struct CmpTest : public TestParams>{}; +struct BitwiseTest : public TestParams>{}; +struct NotTest : public TestParams> {}; +struct SelectTest : public TestParams> {}; +struct MinTest : public TestParams>{}; +struct MaxTest : public TestParams>{}; +struct AbsDiffTest : public TestParams>{}; +struct AbsDiffCTest : public TestParams> {}; +struct SumTest : public TestParams> {}; +struct AddWeightedTest : public TestParams>{}; +struct NormTest : public TestParams>{}; +struct IntegralTest : public TestWithParam> {}; +struct ThresholdTest : public TestParams> {}; +struct ThresholdOTTest : public TestParams> {}; +struct InRangeTest : public TestParams> {}; +struct Split3Test : public TestParams> {}; +struct Split4Test : public TestParams> {}; +struct ResizeTest : public TestWithParam> {}; +struct ResizeTestFxFy : public TestWithParam> {}; +struct Merge3Test : public TestParams> {}; +struct Merge4Test : public TestParams> {}; +struct RemapTest : public TestParams> {}; +struct FlipTest : public TestParams> {}; +struct CropTest : public TestParams> {}; +struct ConcatHorTest : public TestWithParam> {}; +struct ConcatVertTest : public TestWithParam> {}; +struct ConcatVertVecTest : public TestWithParam> {}; +struct ConcatHorVecTest : public TestWithParam> {}; +struct LUTTest : public TestParams> {}; +struct ConvertToTest : public TestParams> {}; +} // opencv_test + +#endif //OPENCV_GAPI_CORE_TESTS_HPP diff --git a/modules/gapi/test/common/gapi_core_tests_inl.hpp b/modules/gapi/test/common/gapi_core_tests_inl.hpp new file mode 100644 index 0000000000..fb9b336c6d --- /dev/null +++ b/modules/gapi/test/common/gapi_core_tests_inl.hpp @@ -0,0 +1,1420 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_CORE_TESTS_INL_HPP +#define OPENCV_GAPI_CORE_TESTS_INL_HPP + +#include "opencv2/gapi/core.hpp" +#include "gapi_core_tests.hpp" + +namespace opencv_test +{ + +TEST_P(MathOpTest, MatricesAccuracyTest ) +{ + mathOp opType = ADD; + int type = 0, dtype = 0; + cv::Size sz; + double scale = 1; // mul, div + bool testWithScalar = false, initOutMatr = false, doReverseOp = false; + cv::GCompileArgs compile_args; + std::tie(opType, testWithScalar, type, scale, sz, dtype, initOutMatr, doReverseOp, compile_args) = GetParam(); + initMatsRandU(type, sz, dtype, initOutMatr); + + // G-API code & corresponding OpenCV code //////////////////////////////// + cv::GMat in1, in2, out; + if( testWithScalar ) + { + cv::GScalar sc1; + switch(opType) + { + case (ADD): + { + out = cv::gapi::addC(in1, sc1, dtype); + cv::add(in_mat1, sc, out_mat_ocv, cv::noArray(), dtype); + break; + } + case (SUB): + { + if( doReverseOp ) + { + out = cv::gapi::subRC(sc1, in1, dtype); + cv::subtract(sc, in_mat1, out_mat_ocv, cv::noArray(), dtype); + } + else + { + out = cv::gapi::subC(in1, sc1, dtype); + cv::subtract(in_mat1, sc, out_mat_ocv, cv::noArray(), dtype); + } + break; + } + case (DIV): + { + if( doReverseOp ) + { + out = cv::gapi::divRC(sc1, in1, scale, dtype); + cv::divide(sc, in_mat1, out_mat_ocv, scale, dtype); + break; + } + else + { + out = cv::gapi::divC(in1, sc1, scale, dtype); + cv::divide(in_mat1, sc, out_mat_ocv, scale, dtype); + break; + } + } + case (MUL): + { + // FIXME: add `scale` parameter to mulC + out = cv::gapi::mulC(in1, sc1, /* scale, */ dtype); + cv::multiply(in_mat1, sc, out_mat_ocv, 1., dtype); + break; + } + default: + { + FAIL() << "no such math operation type for scalar and matrix!"; + } + } + cv::GComputation c(GIn(in1, sc1), GOut(out)); + c.apply(gin(in_mat1, sc), gout(out_mat_gapi), std::move(compile_args)); + } + else + { + switch(opType) + { + case (ADD): + { + out = cv::gapi::add(in1, in2, dtype); + cv::add(in_mat1, in_mat2, out_mat_ocv, cv::noArray(), dtype); + break; + } + case (SUB): + { + out = cv::gapi::sub(in1, in2, dtype); + cv::subtract(in_mat1, in_mat2, out_mat_ocv, cv::noArray(), dtype); + break; + } + case (DIV): + { + out = cv::gapi::div(in1, in2, scale, dtype); + cv::divide(in_mat1, in_mat2, out_mat_ocv, scale, dtype); + break; + } + case (MUL): + { + out = cv::gapi::mul(in1, in2, scale, dtype); + cv::multiply(in_mat1, in_mat2, out_mat_ocv, scale, dtype); + break; + } + default: + { + FAIL() << "no such math operation type for matrix and matrix!"; + }} + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: make threshold vs bit-exact criteria be driven by testing parameter + #if 1 + if (CV_MAT_DEPTH(out_mat_ocv.type()) != CV_32F && + CV_MAT_DEPTH(out_mat_ocv.type()) != CV_64F) + { + // integral: allow 1% of differences, and no diffs by >1 unit + EXPECT_LE(countNonZeroPixels(cv::abs(out_mat_gapi - out_mat_ocv) > 0), + 0.01*out_mat_ocv.total()); + EXPECT_LE(countNonZeroPixels(cv::abs(out_mat_gapi - out_mat_ocv) > 1), 0); + } + else + { + // floating-point: expect 6 decimal digits - best we expect of F32 + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > + 1e-6*cv::abs(out_mat_ocv))); + } + #else + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(MulDoubleTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + int dtype = std::get<2>(param); + cv::Size sz_in = std::get<1>(param); + bool initOut = std::get<3>(param); + + auto& rng = cv::theRNG(); + double d = rng.uniform(0.0, 10.0); + auto compile_args = std::get<4>(param); + initMatrixRandU(type, sz_in, dtype, initOut); + + // G-API code //////////////////////////////////////////////////////////// + cv::GMat in1, out; + out = cv::gapi::mulC(in1, d, dtype); + cv::GComputation c(in1, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + + // OpenCV code /////////////////////////////////////////////////////////// + cv::multiply(in_mat1, d, out_mat_ocv, 1, dtype); + + // Comparison //////////////////////////////////////////////////////////// +#if 1 + if (CV_MAT_DEPTH(out_mat_ocv.type()) != CV_32F && + CV_MAT_DEPTH(out_mat_ocv.type()) != CV_64F) + { + // integral: allow 1% of differences, and no diffs by >1 unit + EXPECT_LE(countNonZeroPixels(cv::abs(out_mat_gapi - out_mat_ocv) > 0), + 0.01*out_mat_ocv.total()); + EXPECT_LE(countNonZeroPixels(cv::abs(out_mat_gapi - out_mat_ocv) > 1), 0); + } + else + { + // floating-point: expect 6 decimal digits - best we expect of F32 + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > + 1e-6*cv::abs(out_mat_ocv))); + } +#else + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); +#endif + EXPECT_EQ(out_mat_gapi.size(), sz_in); +} + +TEST_P(DivTest, DivByZeroTest) +{ + int type = 0, dtype = 0; + cv::Size sz_in; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, sz_in, dtype, initOut, compile_args) = GetParam(); + + initMatrixRandU(type, sz_in, dtype, initOut); + in_mat2 = cv::Mat(sz_in, type); + in_mat2.setTo(cv::Scalar::all(0)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::div(in1, in2, 1.0, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::divide(in_mat1, in_mat2, out_mat_ocv, 1.0, dtype); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(DivCTest, DivByZeroTest) +{ + int type = 0, dtype = 0; + cv::Size sz_in; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, sz_in, dtype, initOut, compile_args) = GetParam(); + + initMatrixRandU(type, sz_in, dtype, initOut); + sc = cv::Scalar::all(0); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1; + cv::GScalar sc1; + auto out = cv::gapi::divC(in1, sc1, dtype); + cv::GComputation c(GIn(in1, sc1), GOut(out)); + + c.apply(gin(in_mat1, sc), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::divide(in_mat1, sc, out_mat_ocv, dtype); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + cv::Mat zeros = cv::Mat::zeros(sz_in, type); + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != zeros)); + } +} + +TEST_P(MeanTest, AccuracyTest) +{ + int type = 0; + bool initOut = false; + cv::Size sz_in; + cv::GCompileArgs compile_args; + std::tie(type, sz_in, initOut, compile_args) = GetParam(); + initMatrixRandU(type, sz_in, initOut); + cv::Scalar out_norm; + cv::Scalar out_norm_ocv; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::mean(in); + + cv::GComputation c(cv::GIn(in), cv::GOut(out)); + c.apply(cv::gin(in_mat1), cv::gout(out_norm), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + out_norm_ocv = cv::mean(in_mat1); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(out_norm[0], out_norm_ocv[0]); + } +} + +TEST_P(MaskTest, AccuracyTest) +{ + int type = 0; + bool initOut = false; + cv::Size sz_in; + cv::GCompileArgs compile_args; + std::tie(type, sz_in, initOut, compile_args) = GetParam(); + initMatrixRandU(type, sz_in, type, initOut); + + in_mat2 = cv::Mat(sz_in, CV_8UC1); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + in_mat2 = in_mat2 > 128; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in, m; + auto out = cv::gapi::mask(in, m); + + cv::GComputation c(cv::GIn(in, m), cv::GOut(out)); + c.apply(cv::gin(in_mat1, in_mat2), cv::gout(out_mat_gapi), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + out_mat_ocv = cv::Mat::zeros(in_mat1.size(), in_mat1.type()); + in_mat1.copyTo(out_mat_ocv, in_mat2); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + } +} + +TEST_P(Polar2CartTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<0>(param); + auto compile_args = std::get<2>(param); + initMatsRandU(CV_32FC1, sz_in, CV_32FC1, std::get<1>(param)); + + cv::Mat out_mat2; + cv::Mat out_mat_ocv2; + if(std::get<1>(param) == true) + { + out_mat2 = cv::Mat(sz_in, CV_32FC1); + out_mat_ocv2 = cv::Mat(sz_in, CV_32FC1); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2, out1, out2; + std::tie(out1, out2) = cv::gapi::polarToCart(in1, in2); + + cv::GComputation c(GIn(in1, in2), GOut(out1, out2)); + c.apply(gin(in_mat1,in_mat2), gout(out_mat_gapi, out_mat2), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::polarToCart(in_mat1, in_mat2, out_mat_ocv, out_mat_ocv2); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // Note that we cannot rely on bit-exact sin/cos functions used for this + // transform, so we need a threshold for verifying results vs reference. + // + // Relative threshold like 1e-6 is very restrictive, nearly best we can + // expect of single-precision elementary functions implementation. + // + // However, good idea is making such threshold configurable: parameter + // of this test - which a specific test istantiation could setup. + // + // Note that test instantiation for the OpenCV back-end could even let + // the threshold equal to zero, as CV back-end calls the same kernel. + // + // TODO: Make threshold a configurable parameter of this test (ADE-221) + + cv::Mat &outx = out_mat_gapi, + &outy = out_mat2; + cv::Mat &refx = out_mat_ocv, + &refy = out_mat_ocv2; + cv::Mat difx = cv::abs(refx - outx), + dify = cv::abs(refy - outy); + cv::Mat absx = cv::abs(refx), + absy = cv::abs(refy); + + EXPECT_EQ(0, cv::countNonZero(difx > 1e-6*absx)); + EXPECT_EQ(0, cv::countNonZero(dify > 1e-6*absy)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(Cart2PolarTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<0>(param); + auto compile_args = std::get<2>(param); + initMatsRandU(CV_32FC1, sz_in, CV_32FC1, std::get<1>(param)); + + cv::Mat out_mat2(sz_in, CV_32FC1); + cv::Mat out_mat_ocv2(sz_in, CV_32FC1); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2, out1, out2; + std::tie(out1, out2) = cv::gapi::cartToPolar(in1, in2); + + cv::GComputation c(GIn(in1, in2), GOut(out1, out2)); + c.apply(gin(in_mat1,in_mat2), gout(out_mat_gapi, out_mat2)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cartToPolar(in_mat1, in_mat2, out_mat_ocv, out_mat_ocv2); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // Note that we cannot rely on bit-exact sin/cos functions used for this + // transform, so we need a threshold for verifying results vs reference. + // + // Relative threshold like 1e-6 is very restrictive, nearly best we can + // expect of single-precision elementary functions implementation. + // + // However, good idea is making such threshold configurable: parameter + // of this test - which a specific test istantiation could setup. + // + // Note that test instantiation for the OpenCV back-end could even let + // the threshold equal to zero, as CV back-end calls the same kernel. + // + // TODO: Make threshold a configurable parameter of this test (ADE-221) + + cv::Mat &outm = out_mat_gapi, + &outa = out_mat2; + cv::Mat &refm = out_mat_ocv, + &refa = out_mat_ocv2; + cv::Mat difm = cv::abs(refm - outm), + difa = cv::abs(refa - outa); + cv::Mat absm = cv::abs(refm), + absa = cv::abs(refa); + + // FIXME: Angle result looks inaccurate at OpenCV + // (expected relative accuracy like 1e-6) + EXPECT_EQ(0, cv::countNonZero(difm > 1e-6*absm)); + EXPECT_EQ(0, cv::countNonZero(difa > 1e-3*absa)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(CmpTest, AccuracyTest) +{ + CmpTypes opType = CMP_EQ; + int type = 0; + cv::Size sz; + bool testWithScalar = false, initOutMatr = false; + cv::GCompileArgs compile_args; + std::tie(opType, testWithScalar, type, sz, initOutMatr, compile_args) = GetParam(); + initMatsRandU(type, sz, CV_8U, initOutMatr); + + // G-API code & corresponding OpenCV code //////////////////////////////// + cv::GMat in1, out; + if( testWithScalar ) + { + cv::GScalar in2; + switch(opType) + { + case CMP_EQ: out = cv::gapi::cmpEQ(in1, in2); break; + case CMP_GT: out = cv::gapi::cmpGT(in1, in2); break; + case CMP_GE: out = cv::gapi::cmpGE(in1, in2); break; + case CMP_LT: out = cv::gapi::cmpLT(in1, in2); break; + case CMP_LE: out = cv::gapi::cmpLE(in1, in2); break; + case CMP_NE: out = cv::gapi::cmpNE(in1, in2); break; + default: FAIL() << "no such compare operation type for matrix and scalar!"; + } + + cv::compare(in_mat1, sc, out_mat_ocv, opType); + + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, sc), gout(out_mat_gapi), std::move(compile_args)); + } + else + { + cv::GMat in2; + switch(opType) + { + case CMP_EQ: out = cv::gapi::cmpEQ(in1, in2); break; + case CMP_GT: out = cv::gapi::cmpGT(in1, in2); break; + case CMP_GE: out = cv::gapi::cmpGE(in1, in2); break; + case CMP_LT: out = cv::gapi::cmpLT(in1, in2); break; + case CMP_LE: out = cv::gapi::cmpLE(in1, in2); break; + case CMP_NE: out = cv::gapi::cmpNE(in1, in2); break; + default: FAIL() << "no such compare operation type for two matrices!"; + } + + cv::compare(in_mat1, in_mat2, out_mat_ocv, opType); + + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(BitwiseTest, AccuracyTest) +{ + bitwiseOp opType = AND; + int type = 0; + cv::Size sz; + bool initOutMatr = false; + cv::GCompileArgs compile_args; + std::tie(opType, type, sz, initOutMatr, compile_args) = GetParam(); + initMatsRandU(type, sz, type, initOutMatr); + + // G-API code & corresponding OpenCV code //////////////////////////////// + cv::GMat in1, in2, out; + switch(opType) + { + case AND: + { + out = cv::gapi::bitwise_and(in1, in2); + cv::bitwise_and(in_mat1, in_mat2, out_mat_ocv); + break; + } + case OR: + { + out = cv::gapi::bitwise_or(in1, in2); + cv::bitwise_or(in_mat1, in_mat2, out_mat_ocv); + break; + } + case XOR: + { + out = cv::gapi::bitwise_xor(in1, in2); + cv::bitwise_xor(in_mat1, in_mat2, out_mat_ocv); + break; + } + default: + { + FAIL() << "no such bitwise operation type!"; + } + } + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(NotTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatrixRandU(std::get<0>(param), sz_in, std::get<0>(param), std::get<2>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::bitwise_not(in); + cv::GComputation c(in, out); + + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::bitwise_not(in_mat1, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(SelectTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatsRandU(type, sz_in, type, std::get<2>(param)); + cv::Mat in_mask(sz_in, CV_8UC1); + cv::randu(in_mask, cv::Scalar::all(0), cv::Scalar::all(255)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2, in3; + auto out = cv::gapi::select(in1, in2, in3); + cv::GComputation c(GIn(in1, in2, in3), GOut(out)); + + c.apply(gin(in_mat1, in_mat2, in_mask), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + in_mat2.copyTo(out_mat_ocv); + in_mat1.copyTo(out_mat_ocv, in_mask); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(MinTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatsRandU(std::get<0>(param), sz_in, std::get<0>(param), std::get<2>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::min(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::min(in_mat1, in_mat2, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(MaxTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatsRandU(std::get<0>(param), sz_in, std::get<0>(param), std::get<2>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::max(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::max(in_mat1, in_mat2, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(AbsDiffTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatsRandU(std::get<0>(param), sz_in, std::get<0>(param), std::get<2>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::absDiff(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::absdiff(in_mat1, in_mat2, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(AbsDiffCTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatsRandU(std::get<0>(param), sz_in, std::get<0>(param), std::get<2>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1; + cv::GScalar sc1; + auto out = cv::gapi::absDiffC(in1, sc1); + cv::GComputation c(cv::GIn(in1, sc1), cv::GOut(out)); + + c.apply(gin(in_mat1, sc), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::absdiff(in_mat1, sc, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(SumTest, AccuracyTest) +{ + auto param = GetParam(); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatrixRandU(std::get<0>(param), sz_in, std::get<2>(param)); + + cv::Scalar out_sum; + cv::Scalar out_sum_ocv; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sum(in); + + cv::GComputation c(cv::GIn(in), cv::GOut(out)); + c.apply(cv::gin(in_mat1), cv::gout(out_sum), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + out_sum_ocv = cv::sum(in_mat1); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(out_sum[0], out_sum_ocv[0]); + } +} + +TEST_P(AddWeightedTest, AccuracyTest) +{ + int type = 0, dtype = 0; + cv::Size sz_in; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, sz_in, dtype, initOut, compile_args) = GetParam(); + + auto& rng = cv::theRNG(); + double alpha = rng.uniform(0.0, 1.0); + double beta = rng.uniform(0.0, 1.0); + double gamma = rng.uniform(0.0, 1.0); + initMatsRandU(type, sz_in, dtype, initOut); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::addWeighted(in1, alpha, in2, beta, gamma, dtype); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::addWeighted(in_mat1, alpha, in_mat2, beta, gamma, out_mat_ocv, dtype); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // Note, that we cannot expect bitwise results for add-weighted: + // + // tmp = src1*alpha + src2*beta + gamma; + // dst = saturate( round(tmp) ); + // + // Because tmp is floating-point, dst depends on compiler optimizations + // + // However, we must expect good accuracy of tmp, and rounding correctly + + cv::Mat failures; + + if (out_mat_ocv.type() == CV_32FC1) + { + // result: float - may vary in 7th decimal digit + failures = abs(out_mat_gapi - out_mat_ocv) > abs(out_mat_ocv) * 1e-6; + } + else + { + // result: integral - rounding may vary if fractional part of tmp + // is nearly 0.5 + + cv::Mat inexact, incorrect, diff, tmp; + + inexact = out_mat_gapi != out_mat_ocv; + + // even if rounded differently, check if still rounded correctly + cv::addWeighted(in_mat1, alpha, in_mat2, beta, gamma, tmp, CV_32F); + cv::subtract(out_mat_gapi, tmp, diff, cv::noArray(), CV_32F); + incorrect = abs(diff) >= 0.5000005f; // relative to 6 digits + + failures = inexact & incorrect; + } + + EXPECT_EQ(0, cv::countNonZero(failures)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(NormTest, AccuracyTest) +{ + NormTypes opType = NORM_INF; + int type = 0; + cv::Size sz; + cv::GCompileArgs compile_args; + std::tie(opType, type, sz, compile_args) = GetParam(); + initMatrixRandU(type, sz, type, false); + + cv::Scalar out_norm; + cv::Scalar out_norm_ocv; + + // G-API code & corresponding OpenCV code //////////////////////////////// + cv::GMat in1; + cv::GScalar out; + switch(opType) + { + case NORM_L1: out = cv::gapi::normL1(in1); break; + case NORM_L2: out = cv::gapi::normL2(in1); break; + case NORM_INF: out = cv::gapi::normInf(in1); break; + default: FAIL() << "no such norm operation type!"; + } + out_norm_ocv = cv::norm(in_mat1, opType); + cv::GComputation c(GIn(in1), GOut(out)); + c.apply(gin(in_mat1), gout(out_norm), std::move(compile_args)); + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(out_norm[0], out_norm_ocv[0]); + } +} + +TEST_P(IntegralTest, AccuracyTest) +{ + int type = std::get<0>(GetParam()); + cv::Size sz_in = std::get<1>(GetParam()); + auto compile_args = std::get<2>(GetParam()); + + int type_out = (type == CV_8U) ? CV_32SC1 : CV_64FC1; + cv::Mat in_mat1(sz_in, type); + + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + + cv::Size sz_out = cv::Size(sz_in.width + 1, sz_in.height + 1); + cv::Mat out_mat1(sz_out, type_out); + cv::Mat out_mat_ocv1(sz_out, type_out); + + cv::Mat out_mat2(sz_out, CV_64FC1); + cv::Mat out_mat_ocv2(sz_out, CV_64FC1); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, out1, out2; + std::tie(out1, out2) = cv::gapi::integral(in1, type_out, CV_64FC1); + cv::GComputation c(cv::GIn(in1), cv::GOut(out1, out2)); + + c.apply(cv::gin(in_mat1), cv::gout(out_mat1, out_mat2), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::integral(in_mat1, out_mat_ocv1, out_mat_ocv2); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv1 != out_mat1)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + } +} + +TEST_P(ThresholdTest, AccuracyTestBinary) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_in = std::get<1>(param); + int tt = std::get<2>(param); + + auto compile_args = std::get<4>(param); + auto& rng = cv::theRNG(); + cv::Scalar thr = cv::Scalar(rng(50),rng(50),rng(50),rng(50)); + cv::Scalar maxval = cv::Scalar(50 + rng(50),50 + rng(50),50 + rng(50),50 + rng(50)); + initMatrixRandU(type, sz_in, type, std::get<3>(param)); + cv::Scalar out_scalar; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar th1, mv1; + out = cv::gapi::threshold(in1, th1, mv1, tt); + cv::GComputation c(GIn(in1, th1, mv1), GOut(out)); + + c.apply(gin(in_mat1, thr, maxval), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::threshold(in_mat1, out_mat_ocv, thr.val[0], maxval.val[0], tt); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(ThresholdOTTest, AccuracyTestOtsu) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_in = std::get<1>(param); + int tt = std::get<2>(param); + + auto compile_args = std::get<4>(param); + auto& rng = cv::theRNG(); + cv::Scalar maxval = cv::Scalar(50 + rng(50),50 + rng(50),50 + rng(50),50 + rng(50)); + initMatrixRandU(type, sz_in, type, std::get<3>(param)); + cv::Scalar out_gapi_scalar; + double ocv_res; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, out; + cv::GScalar mv1, scout; + std::tie(out, scout) = cv::gapi::threshold(in1, mv1, tt); + cv::GComputation c(cv::GIn(in1, mv1), cv::GOut(out, scout)); + + c.apply(gin(in_mat1, maxval), gout(out_mat_gapi, out_gapi_scalar), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + ocv_res = cv::threshold(in_mat1, out_mat_ocv, maxval.val[0], maxval.val[0], tt); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + EXPECT_EQ(ocv_res, out_gapi_scalar.val[0]); + } +} + +TEST_P(InRangeTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_in = std::get<1>(param); + + auto compile_args = std::get<3>(param); + auto& rng = cv::theRNG(); + cv::Scalar thrLow = cv::Scalar(rng(100),rng(100),rng(100),rng(100)); + cv::Scalar thrUp = cv::Scalar(100 + rng(100),100 + rng(100),100 + rng(100),100 + rng(100)); + initMatrixRandU(type, sz_in, type, std::get<2>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1; + cv::GScalar th1, mv1; + auto out = cv::gapi::inRange(in1, th1, mv1); + cv::GComputation c(GIn(in1, th1, mv1), GOut(out)); + + c.apply(gin(in_mat1, thrLow, thrUp), gout(out_mat_gapi), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::inRange(in_mat1, thrLow, thrUp, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(Split3Test, AccuracyTest) +{ + cv::Size sz_in = std::get<0>(GetParam()); + auto compile_args = std::get<1>(GetParam()); + initMatrixRandU(CV_8UC3, sz_in, CV_8UC1); + + cv::Mat out_mat2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat3 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv3 = cv::Mat(sz_in, CV_8UC1); + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, out1, out2, out3; + std::tie(out1, out2, out3) = cv::gapi::split3(in1); + cv::GComputation c(cv::GIn(in1), cv::GOut(out1, out2, out3)); + + c.apply(cv::gin(in_mat1), cv::gout(out_mat_gapi, out_mat2, out_mat3), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + std::vector out_mats_ocv = {out_mat_ocv, out_mat_ocv2, out_mat_ocv3}; + cv::split(in_mat1, out_mats_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv3 != out_mat3)); + } +} + +TEST_P(Split4Test, AccuracyTest) +{ + cv::Size sz_in = std::get<0>(GetParam()); + auto compile_args = std::get<1>(GetParam()); + initMatrixRandU(CV_8UC4, sz_in, CV_8UC1); + cv::Mat out_mat2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat3 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat4 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv3 = cv::Mat(sz_in, CV_8UC1); + cv::Mat out_mat_ocv4 = cv::Mat(sz_in, CV_8UC1); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, out1, out2, out3, out4; + std::tie(out1, out2, out3, out4) = cv::gapi::split4(in1); + cv::GComputation c(cv::GIn(in1), cv::GOut(out1, out2, out3, out4)); + + c.apply(cv::gin(in_mat1), cv::gout(out_mat_gapi, out_mat2, out_mat3, out_mat4), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + std::vector out_mats_ocv = {out_mat_ocv, out_mat_ocv2, out_mat_ocv3, out_mat_ocv4}; + cv::split(in_mat1, out_mats_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv2 != out_mat2)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv3 != out_mat3)); + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv4 != out_mat4)); + } +} + +static void ResizeAccuracyTest(int type, int interp, cv::Size sz_in, cv::Size sz_out, double fx, double fy, double tolerance, cv::GCompileArgs&& compile_args) +{ + cv::Mat in_mat1 (sz_in, type ); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + + auto out_mat_sz = sz_out.area() == 0 ? cv::Size(saturate_cast(sz_in.width *fx), + saturate_cast(sz_in.height*fy)) + : sz_out; + cv::Mat out_mat(out_mat_sz, type); + cv::Mat out_mat_ocv(out_mat_sz, type); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::resize(in, sz_out, fx, fy, interp); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::resize(in_mat1, out_mat_ocv, sz_out, fx, fy, interp); + } + // Comparison ////////////////////////////////////////////////////////////// + { + cv::Mat absDiff; + cv::absdiff(out_mat, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + } +} + +TEST_P(ResizeTest, AccuracyTest) +{ + int type = 0, interp = 0; + cv::Size sz_in, sz_out; + double tolerance = 0.0; + cv::GCompileArgs compile_args; + std::tie(type, interp, sz_in, sz_out, tolerance, compile_args) = GetParam(); + ResizeAccuracyTest(type, interp, sz_in, sz_out, 0.0, 0.0, tolerance, std::move(compile_args)); +} + +TEST_P(ResizeTestFxFy, AccuracyTest) +{ + int type = 0, interp = 0; + cv::Size sz_in; + double fx = 0.0, fy = 0.0, tolerance = 0.0; + cv::GCompileArgs compile_args; + std::tie(type, interp, sz_in, fx, fy, tolerance, compile_args) = GetParam(); + ResizeAccuracyTest(type, interp, sz_in, cv::Size{0, 0}, fx, fy, tolerance, std::move(compile_args)); +} + +TEST_P(Merge3Test, AccuracyTest) +{ + cv::Size sz_in = std::get<0>(GetParam()); + initMatsRandU(CV_8UC1, sz_in, CV_8UC3); + auto compile_args = std::get<1>(GetParam()); + cv::Mat in_mat3(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat3, mean, stddev); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2, in3; + auto out = cv::gapi::merge3(in1, in2, in3); + + cv::GComputation c(cv::GIn(in1, in2, in3), cv::GOut(out)); + c.apply(cv::gin(in_mat1, in_mat2, in_mat3), cv::gout(out_mat_gapi), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + std::vector in_mats_ocv = {in_mat1, in_mat2, in_mat3}; + cv::merge(in_mats_ocv, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + } +} + +TEST_P(Merge4Test, AccuracyTest) +{ + cv::Size sz_in = std::get<0>(GetParam()); + initMatsRandU(CV_8UC1, sz_in, CV_8UC4); + auto compile_args = std::get<1>(GetParam()); + cv::Mat in_mat3(sz_in, CV_8UC1); + cv::Mat in_mat4(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat3, mean, stddev); + cv::randn(in_mat4, mean, stddev); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2, in3, in4; + auto out = cv::gapi::merge4(in1, in2, in3, in4); + + cv::GComputation c(cv::GIn(in1, in2, in3, in4), cv::GOut(out)); + c.apply(cv::gin(in_mat1, in_mat2, in_mat3, in_mat4), cv::gout(out_mat_gapi), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + std::vector in_mats_ocv = {in_mat1, in_mat2, in_mat3, in_mat4}; + cv::merge(in_mats_ocv, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + } +} + +TEST_P(RemapTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_in = std::get<1>(param); + auto compile_args = std::get<3>(param); + initMatrixRandU(type, sz_in, type, std::get<2>(param)); + cv::Mat in_map1(sz_in, CV_16SC2); + cv::Mat in_map2 = cv::Mat(); + cv::randu(in_map1, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::Scalar bv = cv::Scalar(); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1; + auto out = cv::gapi::remap(in1, in_map1, in_map2, cv::INTER_NEAREST, cv::BORDER_REPLICATE, bv); + cv::GComputation c(in1, out); + + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::remap(in_mat1, out_mat_ocv, in_map1, in_map2, cv::INTER_NEAREST, cv::BORDER_REPLICATE, bv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(FlipTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + int flipCode = std::get<1>(param); + cv::Size sz_in = std::get<2>(param); + initMatrixRandU(type, sz_in, type, false); + auto compile_args = std::get<4>(GetParam()); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::flip(in, flipCode); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::flip(in_mat1, out_mat_ocv, flipCode); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(CropTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Rect rect_to = std::get<1>(param); + cv::Size sz_in = std::get<2>(param); + auto compile_args = std::get<4>(param); + + initMatrixRandU(type, sz_in, type, false); + cv::Size sz_out = cv::Size(rect_to.width, rect_to.height); + if( std::get<3>(param) == true ) + { + out_mat_gapi = cv::Mat(sz_out, type); + out_mat_ocv = cv::Mat(sz_out, type); + } + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::crop(in, rect_to); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Mat(in_mat1, rect_to).copyTo(out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_out); + } +} + +TEST_P(ConcatHorTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_out = std::get<1>(param); + auto compile_args = std::get<2>(param); + + int wpart = sz_out.width / 4; + cv::Size sz_in1 = cv::Size(wpart, sz_out.height); + cv::Size sz_in2 = cv::Size(sz_out.width - wpart, sz_out.height); + + cv::Mat in_mat1 (sz_in1, type ); + cv::Mat in_mat2 (sz_in2, type); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + + cv::Mat out_mat(sz_out, type); + cv::Mat out_mat_ocv(sz_out, type); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::concatHor(in1, in2); + + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, in_mat2), gout(out_mat), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::hconcat(in_mat1, in_mat2, out_mat_ocv ); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat)); + } +} + +TEST_P(ConcatVertTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_out = std::get<1>(param); + auto compile_args = std::get<2>(param); + + int hpart = sz_out.height * 2/3; + cv::Size sz_in1 = cv::Size(sz_out.width, hpart); + cv::Size sz_in2 = cv::Size(sz_out.width, sz_out.height - hpart); + + cv::Mat in_mat1 (sz_in1, type); + cv::Mat in_mat2 (sz_in2, type); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + + cv::Mat out_mat(sz_out, type); + cv::Mat out_mat_ocv(sz_out, type); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in1, in2; + auto out = cv::gapi::concatVert(in1, in2); + + cv::GComputation c(GIn(in1, in2), GOut(out)); + c.apply(gin(in_mat1, in_mat2), gout(out_mat), std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::vconcat(in_mat1, in_mat2, out_mat_ocv ); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat)); + } +} + +TEST_P(ConcatVertVecTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_out = std::get<1>(param); + auto compile_args = std::get<2>(param); + + int hpart1 = sz_out.height * 2/5; + int hpart2 = sz_out.height / 5; + cv::Size sz_in1 = cv::Size(sz_out.width, hpart1); + cv::Size sz_in2 = cv::Size(sz_out.width, hpart2); + cv::Size sz_in3 = cv::Size(sz_out.width, sz_out.height - hpart1 - hpart2); + + cv::Mat in_mat1 (sz_in1, type); + cv::Mat in_mat2 (sz_in2, type); + cv::Mat in_mat3 (sz_in3, type); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + cv::randn(in_mat3, mean, stddev); + + cv::Mat out_mat(sz_out, type); + cv::Mat out_mat_ocv(sz_out, type); + + // G-API code ////////////////////////////////////////////////////////////// + std::vector mats(3); + auto out = cv::gapi::concatVert(mats); + + std::vector cvmats = {in_mat1, in_mat2, in_mat3}; + + cv::GComputation c({mats[0], mats[1], mats[2]}, {out}); + c.apply(gin(in_mat1, in_mat2, in_mat3), gout(out_mat), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::vconcat(cvmats, out_mat_ocv ); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat)); + } +} + +TEST_P(ConcatHorVecTest, AccuracyTest) +{ + auto param = GetParam(); + int type = std::get<0>(param); + cv::Size sz_out = std::get<1>(param); + auto compile_args = std::get<2>(param); + + int wpart1 = sz_out.width / 3; + int wpart2 = sz_out.width / 4; + cv::Size sz_in1 = cv::Size(wpart1, sz_out.height); + cv::Size sz_in2 = cv::Size(wpart2, sz_out.height); + cv::Size sz_in3 = cv::Size(sz_out.width - wpart1 - wpart2, sz_out.height); + + cv::Mat in_mat1 (sz_in1, type); + cv::Mat in_mat2 (sz_in2, type); + cv::Mat in_mat3 (sz_in3, type); + cv::Scalar mean = cv::Scalar::all(127); + cv::Scalar stddev = cv::Scalar::all(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + cv::randn(in_mat3, mean, stddev); + + cv::Mat out_mat(sz_out, type); + cv::Mat out_mat_ocv(sz_out, type); + + // G-API code ////////////////////////////////////////////////////////////// + std::vector mats(3); + auto out = cv::gapi::concatHor(mats); + + std::vector cvmats = {in_mat1, in_mat2, in_mat3}; + + cv::GComputation c({mats[0], mats[1], mats[2]}, {out}); + c.apply(gin(in_mat1, in_mat2, in_mat3), gout(out_mat), std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::hconcat(cvmats, out_mat_ocv ); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat)); + } +} + +TEST_P(LUTTest, AccuracyTest) +{ + auto param = GetParam(); + int type_mat = std::get<0>(param); + int type_lut = std::get<1>(param); + int type_out = CV_MAKETYPE(CV_MAT_DEPTH(type_lut), CV_MAT_CN(type_mat)); + cv::Size sz_in = std::get<2>(param); + auto compile_args = std::get<4>(GetParam()); + + initMatrixRandU(type_mat, sz_in, type_out); + cv::Size sz_lut = cv::Size(1, 256); + cv::Mat in_lut (sz_lut, type_lut); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::LUT(in, in_lut); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::LUT(in_mat1, in_lut, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +TEST_P(ConvertToTest, AccuracyTest) +{ + auto param = GetParam(); + int type_mat = std::get<0>(param); + int depth_to = std::get<1>(param); + cv::Size sz_in = std::get<2>(param); + int type_out = CV_MAKETYPE(depth_to, CV_MAT_CN(type_mat)); + initMatrixRandU(type_mat, sz_in, type_out); + auto compile_args = std::get<3>(GetParam()); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::convertTo(in, depth_to); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + in_mat1.convertTo(out_mat_ocv, depth_to); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} + +} // opencv_test + +#endif //OPENCV_GAPI_CORE_TESTS_INL_HPP diff --git a/modules/gapi/test/common/gapi_imgproc_tests.cpp b/modules/gapi/test/common/gapi_imgproc_tests.cpp new file mode 100644 index 0000000000..ea84191a44 --- /dev/null +++ b/modules/gapi/test/common/gapi_imgproc_tests.cpp @@ -0,0 +1,8 @@ +// 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) 2018 Intel Corporation + + +#include "gapi_imgproc_tests_inl.hpp" diff --git a/modules/gapi/test/common/gapi_imgproc_tests.hpp b/modules/gapi/test/common/gapi_imgproc_tests.hpp new file mode 100644 index 0000000000..517c1253e3 --- /dev/null +++ b/modules/gapi/test/common/gapi_imgproc_tests.hpp @@ -0,0 +1,41 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_IMGPROC_TESTS_HPP +#define OPENCV_GAPI_IMGPROC_TESTS_HPP + +#include + +#include "gapi_tests_common.hpp" + +namespace opencv_test +{ +struct Filter2DTest : public TestParams > {}; +struct BoxFilterTest : public TestParams > {}; +struct SepFilterTest : public TestParams > {}; +struct BlurTest : public TestParams > {}; +struct GaussianBlurTest : public TestParams > {}; +struct MedianBlurTest : public TestParams > {}; +struct ErodeTest : public TestParams > {}; +struct Erode3x3Test : public TestParams > {}; +struct DilateTest : public TestParams > {}; +struct Dilate3x3Test : public TestParams > {}; +struct SobelTest : public TestParams > {}; +struct EqHistTest : public TestParams > {}; +struct CannyTest : public TestParams > {}; +struct RGB2GrayTest : public TestParams> {}; +struct BGR2GrayTest : public TestParams> {}; +struct RGB2YUVTest : public TestParams> {}; +struct YUV2RGBTest : public TestParams> {}; +struct RGB2LabTest : public TestParams> {}; +struct BGR2LUVTest : public TestParams> {}; +struct LUV2BGRTest : public TestParams> {}; +struct BGR2YUVTest : public TestParams> {}; +struct YUV2BGRTest : public TestParams> {}; +} // opencv_test + +#endif //OPENCV_GAPI_IMGPROC_TESTS_HPP diff --git a/modules/gapi/test/common/gapi_imgproc_tests_inl.hpp b/modules/gapi/test/common/gapi_imgproc_tests_inl.hpp new file mode 100644 index 0000000000..9462ee32b3 --- /dev/null +++ b/modules/gapi/test/common/gapi_imgproc_tests_inl.hpp @@ -0,0 +1,751 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_IMGPROC_TESTS_INL_HPP +#define OPENCV_GAPI_IMGPROC_TESTS_INL_HPP + +#include "opencv2/gapi/imgproc.hpp" +#include "gapi_imgproc_tests.hpp" + +namespace opencv_test +{ +TEST_P(Filter2DTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0, borderType = 0, dtype = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, borderType, dtype, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, dtype, initOut); + + cv::Point anchor = {-1, -1}; + double delta = 0; + + cv::Mat kernel = cv::Mat(kernSize, kernSize, CV_32FC1 ); + cv::Scalar kernMean = cv::Scalar(1.0); + cv::Scalar kernStddev = cv::Scalar(2.0/3); + randn(kernel, kernMean, kernStddev); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::filter2D(in, dtype, kernel, anchor, delta, borderType); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::filter2D(in_mat1, out_mat_ocv, dtype, kernel, anchor, delta, borderType); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: Control this choice with test's especial parameter + #if 1 + // Allow some rounding error + if (CV_MAT_DEPTH(out_mat_gapi.type()) == CV_32F) + { + // 6 decimal digits is nearly best accuracy we can expect of FP32 arithmetic here + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > 1e-6*cv::abs(out_mat_ocv))); + } + else + { + // allow wrong rounding if result fractional part is nearly 0.5, + // assume there would be not more than 0.01% of such cases + EXPECT_LE(cv::countNonZero(out_mat_gapi != out_mat_ocv), 1e-4*out_mat_ocv.total()); + } + #else + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(BoxFilterTest, AccuracyTest) +{ + MatType type = 0; + int filterSize = 0, borderType = 0, dtype = 0; + cv::Size sz; + double tolerance = 0.0; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, filterSize, sz, borderType, dtype, tolerance, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, dtype, initOut); + + cv::Point anchor = {-1, -1}; + bool normalize = true; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::boxFilter(in, dtype, cv::Size(filterSize, filterSize), anchor, normalize, borderType); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::boxFilter(in_mat1, out_mat_ocv, dtype, cv::Size(filterSize, filterSize), anchor, normalize, borderType); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: Control this choice with test's especial parameter + #if 1 + // Allow some rounding error + if (CV_MAT_DEPTH(out_mat_gapi.type()) == CV_32F) + { + // 6 decimal digits is nearly best accuracy we can expect of FP32 arithmetic here + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > 1e-6*cv::abs(out_mat_ocv))); + } + else + { + // allow wrong rounding if result fractional part is nearly 0.5, + // assume there would be not more than 0.01% of such cases + EXPECT_LE(cv::countNonZero(out_mat_gapi != out_mat_ocv), 1e-4*out_mat_ocv.total()); + } + #else + cv::Mat absDiff; + cv::absdiff(out_mat_gapi, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + #endif + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(SepFilterTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0, dtype = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, dtype, initOut, compile_args) = GetParam(); + + cv::Mat kernelX(kernSize, 1, CV_32F); + cv::Mat kernelY(kernSize, 1, CV_32F); + randu(kernelX, -1, 1); + randu(kernelY, -1, 1); + initMatsRandN(type, sz, dtype, initOut); + + cv::Point anchor = cv::Point(-1, -1); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sepFilter(in, dtype, kernelX, kernelY, anchor, cv::Scalar() ); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::sepFilter2D(in_mat1, out_mat_ocv, dtype, kernelX, kernelY ); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: Control this choice with test's especial parameter + #if 1 + // Expect some rounding error + EXPECT_LE(cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > 1e-5 * cv::abs(out_mat_ocv)), + 0.01 * out_mat_ocv.total()); + #else + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(BlurTest, AccuracyTest) +{ + MatType type = 0; + int filterSize = 0, borderType = 0; + cv::Size sz; + double tolerance = 0.0; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, filterSize, sz, borderType, tolerance, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + cv::Point anchor = {-1, -1}; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::blur(in, cv::Size(filterSize, filterSize), anchor, borderType); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::blur(in_mat1, out_mat_ocv, cv::Size(filterSize, filterSize), anchor, borderType); + } + // Comparison ////////////////////////////////////////////////////////////// + { + cv::Mat absDiff; + cv::absdiff(out_mat_gapi, out_mat_ocv, absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(GaussianBlurTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + cv::Size kSize = cv::Size(kernSize, kernSize); + double sigmaX = rand(); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::gaussianBlur(in, kSize, sigmaX); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::GaussianBlur(in_mat1, out_mat_ocv, kSize, sigmaX); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: Control this choice with test's especial parameter + #if 1 + // Expect some rounding error + if (CV_MAT_DEPTH(out_mat_gapi.type()) == CV_32F || + CV_MAT_DEPTH(out_mat_gapi.type()) == CV_64F) + { + // Note that 1e-6 is nearly best accuracy we can expect of FP32 arithetic + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > + 1e-6*cv::abs(out_mat_ocv))); + } + else if (CV_MAT_DEPTH(out_mat_gapi.type()) == CV_8U) + { + // OpenCV uses 16-bits fixed-point for 8U data, so may produce wrong results + EXPECT_LE(cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > 1), + 0.05*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(cv::abs(out_mat_gapi - out_mat_ocv) > 2), 0); + } + else + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + } + #else + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(MedianBlurTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::medianBlur(in, kernSize); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::medianBlur(in_mat1, out_mat_ocv, kernSize); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(ErodeTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0, kernType = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, kernType, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + cv::Mat kernel = cv::getStructuringElement(kernType, cv::Size(kernSize, kernSize)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::erode(in, kernel); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::erode(in_mat1, out_mat_ocv, kernel); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(Erode3x3Test, AccuracyTest) +{ + MatType type = 0; + int numIters = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, sz, initOut, numIters, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + cv::Mat kernel = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT, cv::Size(3,3)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::erode3x3(in, numIters); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::erode(in_mat1, out_mat_ocv, kernel, cv::Point(-1, -1), numIters); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(DilateTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0, kernType = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, kernType, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + cv::Mat kernel = cv::getStructuringElement(kernType, cv::Size(kernSize, kernSize)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::dilate(in, kernel); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::dilate(in_mat1, out_mat_ocv, kernel); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(Dilate3x3Test, AccuracyTest) +{ + MatType type = 0; + int numIters = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, sz, initOut, numIters, compile_args) = GetParam(); + initMatsRandN(type, sz, type, initOut); + + cv::Mat kernel = cv::getStructuringElement(cv::MorphShapes::MORPH_RECT, cv::Size(3,3)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::dilate3x3(in, numIters); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::dilate(in_mat1, out_mat_ocv, kernel, cv::Point(-1,-1), numIters); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + + +TEST_P(SobelTest, AccuracyTest) +{ + MatType type = 0; + int kernSize = 0, dtype = 0, dx = 0, dy = 0; + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, kernSize, sz, dtype, dx, dy, initOut, compile_args) = GetParam(); + initMatsRandN(type, sz, dtype, initOut); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sobel(in, dtype, dx, dy, kernSize ); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Sobel(in_mat1, out_mat_ocv, dtype, dx, dy, kernSize); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(EqHistTest, AccuracyTest) +{ + cv::Size sz; + bool initOut = false; + cv::GCompileArgs compile_args; + std::tie(sz, initOut, compile_args) = GetParam(); + initMatsRandN(CV_8UC1, sz, CV_8UC1, initOut); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::equalizeHist(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::equalizeHist(in_mat1, out_mat_ocv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(GetParam())); + } +} + +TEST_P(CannyTest, AccuracyTest) +{ + MatType type; + int apSize = 0; + double thrLow = 0.0, thrUp = 0.0; + cv::Size sz; + bool l2gr = false, initOut = false; + cv::GCompileArgs compile_args; + std::tie(type, sz, thrLow, thrUp, apSize, l2gr, initOut, compile_args) = GetParam(); + + initMatsRandN(type, sz, CV_8UC1, initOut); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::Canny(in, thrLow, thrUp, apSize, l2gr); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Canny(in_mat1, out_mat_ocv, thrLow, thrUp, apSize, l2gr); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(RGB2GrayTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC1, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::RGB2Gray(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_RGB2GRAY); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding if result's fractional part is nearly 0.5 + // - assume not more than 0.1% of pixels may deviate this way + // - deviation must not exceed 1 unit anyway + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.001*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0); + #else + // insist of bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(BGR2GrayTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC1, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::BGR2Gray(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_BGR2GRAY); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding if result's fractional part is nearly 0.5 + // - assume not more than 0.1% of pixels may deviate this way + // - deviation must not exceed 1 unit anyway + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.001*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0); + #else + // insist of bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(RGB2YUVTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::RGB2YUV(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_RGB2YUV); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding if result's fractional part is nearly 0.5 + // - assume not more than 15% of pixels may deviate this way + // - deviation must not exceed 1 unit anyway + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.15*3*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0); + #else + // insist of bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(YUV2RGBTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::YUV2RGB(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_YUV2RGB); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding if result's fractional part is nearly 0.5 + // - assume not more than 1% of pixels may deviate this way + // - deviation must not exceed 1 unit anyway + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.01*3*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0); + #else + // insist of bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(RGB2LabTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::RGB2Lab(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_RGB2Lab); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding, if result's fractional part is nearly 0.5 + // - assume not more than 25% of pixels may deviate this way + // - not more than 1% of pixels may deviate by 1 unit + // - deviation must not exceed 2 units anyway + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.25*3*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0.01*3*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 2), 1e-5*3*out_mat_ocv.total()); + #else + // insist on bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(BGR2LUVTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::BGR2LUV(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_BGR2Luv); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding, if result's fractional part is nearly 0.5 + // - assume not more than 25% of pixels may deviate this way + // - not more than 1% of pixels may deviate by 2+ units + // - not more than 0.01% pixels may deviate by 5+ units + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.25 * 3 * out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0.01 * 3 * out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 5), 0.0001 * 3 * out_mat_ocv.total()); + #else + // insist on bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(LUV2BGRTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::LUV2BGR(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_Luv2BGR); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(BGR2YUVTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::BGR2YUV(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_BGR2YUV); + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} + +TEST_P(YUV2BGRTest, AccuracyTest) +{ + auto param = GetParam(); + auto compile_args = std::get<2>(param); + initMatsRandN(CV_8UC3, std::get<0>(param), CV_8UC3, std::get<1>(param)); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::YUV2BGR(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::cvtColor(in_mat1, out_mat_ocv, cv::COLOR_YUV2BGR); + } + // Comparison ////////////////////////////////////////////////////////////// + { + // TODO: control this choice with especial parameter of this test + #if 1 + // allow faithful rounding, if result's fractional part is nearly 0.5 + // - assume not more than 25% of pixels may deviate this way + // - not more than 1% of pixels may deviate by 1 unit + // - deviation must not exceed 2 units anyway + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 0), 0.25*3*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 1), 0.01*3*out_mat_ocv.total()); + EXPECT_LE(cv::countNonZero(out_mat_gapi - out_mat_ocv > 2), 1e-5*3*out_mat_ocv.total()); + #else + // insist on bit-exact results + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + #endif + EXPECT_EQ(out_mat_gapi.size(), std::get<0>(param)); + } +} +} // opencv_test + +#endif //OPENCV_GAPI_IMGPROC_TESTS_INL_HPP diff --git a/modules/gapi/test/common/gapi_operators_tests.cpp b/modules/gapi/test/common/gapi_operators_tests.cpp new file mode 100644 index 0000000000..3fcf3f5641 --- /dev/null +++ b/modules/gapi/test/common/gapi_operators_tests.cpp @@ -0,0 +1,8 @@ +// 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) 2018 Intel Corporation + + +#include "gapi_operators_tests_inl.hpp" diff --git a/modules/gapi/test/common/gapi_operators_tests.hpp b/modules/gapi/test/common/gapi_operators_tests.hpp new file mode 100644 index 0000000000..37bed923a1 --- /dev/null +++ b/modules/gapi/test/common/gapi_operators_tests.hpp @@ -0,0 +1,192 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OPERATOR_TESTS_COMMON_HPP +#define OPENCV_GAPI_OPERATOR_TESTS_COMMON_HPP + +#include "gapi_tests_common.hpp" + +namespace opencv_test +{ + +struct g_api_ocv_pair_mat_scalar { + using g_api_function_t = std::function; + using ocv_function_t = std::function; + + std::string name; + g_api_function_t g_api_function; + ocv_function_t ocv_function; + + + g_api_ocv_pair_mat_scalar(std::string const& n, g_api_function_t const& g, ocv_function_t const& o) + : name(n), g_api_function(g), ocv_function(o) {} + + g_api_ocv_pair_mat_scalar() = default; + + friend std::ostream& operator<<(std::ostream& o, const g_api_ocv_pair_mat_scalar& p) + { + return o<; + using ocv_function_t = std::function; + + std::string name; + g_api_function_t g_api_function; + ocv_function_t ocv_function; + + + g_api_ocv_pair_mat_mat(std::string const& n, g_api_function_t const& g, ocv_function_t const& o) + : name(n), g_api_function(g), ocv_function(o) {} + + g_api_ocv_pair_mat_mat() = default; + + friend std::ostream& operator<<(std::ostream& o, const g_api_ocv_pair_mat_mat& p) + { + return o<"}, + [](cv::GMat in,cv::GScalar c){return in>c;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(in, c, out,cv::CMP_GT);}}; +g_api_ocv_pair_mat_scalar opLT = {std::string{"operator<"}, + [](cv::GMat in,cv::GScalar c){return in="}, + [](cv::GMat in,cv::GScalar c){return in>=c;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(in, c, out,cv::CMP_GE);}}; +g_api_ocv_pair_mat_scalar opLE = {std::string{"operator<="}, + [](cv::GMat in,cv::GScalar c){return in<=c;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(in, c, out,cv::CMP_LE);}}; +g_api_ocv_pair_mat_scalar opEQ = {std::string{"operator=="}, + [](cv::GMat in,cv::GScalar c){return in==c;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(in, c, out,cv::CMP_EQ);}}; +g_api_ocv_pair_mat_scalar opNE = {std::string{"operator!="}, + [](cv::GMat in,cv::GScalar c){return in!=c;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(in, c, out,cv::CMP_NE);}}; +g_api_ocv_pair_mat_scalar opGTR = {std::string{"rev_operator>"}, + [](cv::GMat in,cv::GScalar c){return c>in;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(c, in, out,cv::CMP_GT);}}; +g_api_ocv_pair_mat_scalar opLTR = {std::string{"rev_operator<"}, + [](cv::GMat in,cv::GScalar c){return c="}, + [](cv::GMat in,cv::GScalar c){return c>=in;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(c, in, out,cv::CMP_GE);}}; +g_api_ocv_pair_mat_scalar opLER = {std::string{"rev_operator<="}, + [](cv::GMat in,cv::GScalar c){return c<=in;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(c, in, out,cv::CMP_LE);}}; +g_api_ocv_pair_mat_scalar opEQR = {std::string{"rev_operator=="}, + [](cv::GMat in,cv::GScalar c){return c==in;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(c, in, out,cv::CMP_EQ);}}; +g_api_ocv_pair_mat_scalar opNER = {std::string{"rev_operator!="}, + [](cv::GMat in,cv::GScalar c){return c!=in;}, + [](const cv::Mat& in, cv::Scalar c, cv::Mat& out){cv::compare(c, in, out,cv::CMP_NE);}}; + +g_api_ocv_pair_mat_scalar opAND = {std::string{"operator&"}, + [](cv::GMat in1,cv::GScalar in2){return in1&in2;}, + [](const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out){cv::bitwise_and(in1, in2, out);}}; +g_api_ocv_pair_mat_scalar opOR = {std::string{"operator|"}, + [](cv::GMat in1,cv::GScalar in2){return in1|in2;}, + [](const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out){cv::bitwise_or(in1, in2, out);}}; +g_api_ocv_pair_mat_scalar opXOR = {std::string{"operator^"}, + [](cv::GMat in1,cv::GScalar in2){return in1^in2;}, + [](const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out){cv::bitwise_xor(in1, in2, out);}}; +g_api_ocv_pair_mat_scalar opANDR = {std::string{"rev_operator&"}, + [](cv::GMat in1,cv::GScalar in2){return in2&in1;}, + [](const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out){cv::bitwise_and(in2, in1, out);}}; +g_api_ocv_pair_mat_scalar opORR = {std::string{"rev_operator|"}, + [](cv::GMat in1,cv::GScalar in2){return in2|in1;}, + [](const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out){cv::bitwise_or(in2, in1, out);}}; +g_api_ocv_pair_mat_scalar opXORR = {std::string{"rev_operator^"}, + [](cv::GMat in1,cv::GScalar in2){return in2^in1;}, + [](const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out){cv::bitwise_xor(in2, in1, out);}}; + +// declare test cases for matrix and matrix operators +g_api_ocv_pair_mat_mat opPlusM = {std::string{"operator+"}, + [](cv::GMat in1,cv::GMat in2){return in1+in2;}, + [](const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out){cv::add(in1, in2, out);}}; +g_api_ocv_pair_mat_mat opMinusM = {std::string{"operator-"}, + [](cv::GMat in,cv::GMat c){return in-c;}, + [](const cv::Mat& in, const cv::Mat& c, cv::Mat& out){cv::subtract(in, c, out);}}; +g_api_ocv_pair_mat_mat opDivM = {std::string{"operator/"}, + [](cv::GMat in,cv::GMat c){return in/c;}, + [](const cv::Mat& in, const cv::Mat& c, cv::Mat& out){cv::divide(in, c, out);}}; +g_api_ocv_pair_mat_mat opGreater = {std::string{"operator>"}, + [](cv::GMat in1,cv::GMat in2){return in1>in2;}, + [](const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out){cv::compare(in1, in2, out, cv::CMP_GT);}}; +g_api_ocv_pair_mat_mat opGreaterEq = {std::string{"operator>="}, + [](cv::GMat in1,cv::GMat in2){return in1>=in2;}, + [](const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out){cv::compare(in1, in2, out, cv::CMP_GE);}}; +g_api_ocv_pair_mat_mat opLess = {std::string{"operator<"}, + [](cv::GMat in1,cv::GMat in2){return in1>{}; +struct MathOperatorMatMatTest : public TestParams>{}; +struct NotOperatorTest : public TestParams> {}; +} // opencv_test + +#endif // OPENCV_GAPI_OPERATOR_TESTS_COMMON_HPP diff --git a/modules/gapi/test/common/gapi_operators_tests_inl.hpp b/modules/gapi/test/common/gapi_operators_tests_inl.hpp new file mode 100644 index 0000000000..5ac0e36210 --- /dev/null +++ b/modules/gapi/test/common/gapi_operators_tests_inl.hpp @@ -0,0 +1,102 @@ +// 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) 2018 Intel Corporation + + +#ifndef OPENCV_GAPI_OPERATOR_TESTS_INL_COMMON_HPP +#define OPENCV_GAPI_OPERATOR_TESTS_INL_COMMON_HPP + +#include "gapi_operators_tests.hpp" + +namespace opencv_test +{ +TEST_P(MathOperatorMatScalarTest, OperatorAccuracyTest ) +{ + g_api_ocv_pair_mat_scalar op; + int type = 0, dtype = 0; + cv::Size sz; + bool initOutMatr = false; + cv::GCompileArgs compile_args; + std::tie(op, type, sz, dtype, initOutMatr, compile_args) = GetParam(); + initMatsRandU(type, sz, dtype, initOutMatr); + + auto fun_gapi = op.g_api_function; + auto fun_ocv = op.ocv_function ; + + // G-API code & corresponding OpenCV code //////////////////////////////// + + cv::GMat in1; + cv::GScalar in2; + auto out = fun_gapi(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + c.apply(gin(in_mat1, sc), gout(out_mat_gapi), std::move(compile_args)); + + fun_ocv(in_mat1, sc, out_mat_ocv); + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(MathOperatorMatMatTest, OperatorAccuracyTest ) +{ + g_api_ocv_pair_mat_mat op; + int type = 0, dtype = 0; + cv::Size sz; + bool initOutMatr = false; + cv::GCompileArgs compile_args; + std::tie(op, type, sz, dtype, initOutMatr, compile_args) = GetParam(); + initMatsRandU(type, sz, dtype, initOutMatr); + + auto fun_gapi = op.g_api_function; + auto fun_ocv = op.ocv_function ; + + // G-API code & corresponding OpenCV code //////////////////////////////// + + cv::GMat in1; + cv::GMat in2; + auto out = fun_gapi(in1, in2); + cv::GComputation c(GIn(in1, in2), GOut(out)); + + c.apply(gin(in_mat1, in_mat2), gout(out_mat_gapi), std::move(compile_args)); + + fun_ocv(in_mat1, in_mat2, out_mat_ocv); + + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); + EXPECT_EQ(out_mat_gapi.size(), sz); + } +} + +TEST_P(NotOperatorTest, OperatorAccuracyTest) +{ + cv::Size sz_in = std::get<1>(GetParam()); + initMatrixRandU(std::get<0>(GetParam()), sz_in, std::get<0>(GetParam()), std::get<2>(GetParam())); + cv::GCompileArgs compile_args; + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = ~in; + cv::GComputation c(in, out); + + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + out_mat_ocv =~in_mat1; + } + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + EXPECT_EQ(out_mat_gapi.size(), sz_in); + } +} +} // opencv_test + +#endif // OPENCV_GAPI_OPERATOR_TESTS_INL_COMMON_HPP diff --git a/modules/gapi/test/common/gapi_tests_common.hpp b/modules/gapi/test/common/gapi_tests_common.hpp new file mode 100644 index 0000000000..0c9e375f8c --- /dev/null +++ b/modules/gapi/test/common/gapi_tests_common.hpp @@ -0,0 +1,108 @@ +// 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) 2018 Intel Corporation + + +#include + +#include "opencv2/ts.hpp" +#include "opencv2/gapi.hpp" + +namespace +{ + inline std::ostream& operator<<(std::ostream& o, const cv::GCompileArg& arg) + { + return o << (arg.tag.empty() ? "empty" : arg.tag); + } +} + +namespace opencv_test +{ + +class TestFunctional +{ +public: + cv::Mat in_mat1; + cv::Mat in_mat2; + cv::Mat out_mat_gapi; + cv::Mat out_mat_ocv; + + cv::Scalar sc; + + void initMatsRandU(int type, cv::Size sz_in, int dtype, bool createOutputMatrices = true) + { + in_mat1 = cv::Mat(sz_in, type); + in_mat2 = cv::Mat(sz_in, type); + + auto& rng = cv::theRNG(); + sc = cv::Scalar(rng(100),rng(100),rng(100),rng(100)); + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + + if (createOutputMatrices && dtype != -1) + { + out_mat_gapi = cv::Mat (sz_in, dtype); + out_mat_ocv = cv::Mat (sz_in, dtype); + } + } + + void initMatrixRandU(int type, cv::Size sz_in, int dtype, bool createOutputMatrices = true) + { + in_mat1 = cv::Mat(sz_in, type); + + auto& rng = cv::theRNG(); + sc = cv::Scalar(rng(100),rng(100),rng(100),rng(100)); + + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + + if (createOutputMatrices && dtype != -1) + { + out_mat_gapi = cv::Mat (sz_in, dtype); + out_mat_ocv = cv::Mat (sz_in, dtype); + } + } + + void initMatsRandN(int type, cv::Size sz_in, int dtype, bool createOutputMatrices = true) + { + in_mat1 = cv::Mat(sz_in, type); + cv::randn(in_mat1, cv::Scalar::all(127), cv::Scalar::all(40.f)); + + if (createOutputMatrices && dtype != -1) + { + out_mat_gapi = cv::Mat(sz_in, dtype); + out_mat_ocv = cv::Mat(sz_in, dtype); + } + } + + static cv::Mat nonZeroPixels(const cv::Mat& mat) + { + int channels = mat.channels(); + std::vector split(channels); + cv::split(mat, split); + cv::Mat result; + for (int c=0; c < channels; c++) + { + if (c == 0) + result = split[c] != 0; + else + result = result | (split[c] != 0); + } + return result; + } + + static int countNonZeroPixels(const cv::Mat& mat) + { + return cv::countNonZero( nonZeroPixels(mat) ); + } + +}; + +template +class TestParams: public TestFunctional, public TestWithParam{}; + +template +class TestPerfParams: public TestFunctional, public perf::TestBaseWithParam{}; + +} diff --git a/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp b/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp new file mode 100644 index 0000000000..41d885fc1c --- /dev/null +++ b/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp @@ -0,0 +1,386 @@ +// 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) 2018 Intel Corporation + + +#include "../test_precomp.hpp" +#include "../common/gapi_core_tests.hpp" +#include "opencv2/gapi/cpu/core.hpp" + +#define CORE_CPU cv::gapi::core::cpu::kernels() + +namespace opencv_test +{ + +// FIXME: Wut? See MulTestCPU/MathOpTest below (duplicate?) +INSTANTIATE_TEST_CASE_P(AddTestCPU, MathOpTest, + Combine(Values(ADD, MUL), + testing::Bool(), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(1.0), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + Values(false), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(MulTestCPU, MathOpTest, + Combine(Values(MUL), + testing::Bool(), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(1.0, 0.5, 2.0), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + Values(false), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(SubTestCPU, MathOpTest, + Combine(Values(SUB), + testing::Bool(), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values (1.0), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + testing::Bool(), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(DivTestCPU, MathOpTest, + Combine(Values(DIV), + testing::Bool(), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values (1.0, 0.5, 2.0), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + testing::Bool(), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(MulTestCPU, MulDoubleTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(DivTestCPU, DivTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(DivCTestCPU, DivCTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(MeanTestCPU, MeanTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(MaskTestCPU, MaskTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(SelectTestCPU, SelectTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(Polar2CartCPU, Polar2CartTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(Cart2PolarCPU, Cart2PolarTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(CompareTestCPU, CmpTest, + Combine(Values(CMP_EQ, CMP_GE, CMP_NE, CMP_GT, CMP_LT, CMP_LE), + testing::Bool(), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintCmpCoreParams()); + +INSTANTIATE_TEST_CASE_P(BitwiseTestCPU, BitwiseTest, + Combine(Values(AND, OR, XOR), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintBWCoreParams()); + +INSTANTIATE_TEST_CASE_P(BitwiseNotTestCPU, NotTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(MinTestCPU, MinTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(MaxTestCPU, MaxTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(SumTestCPU, SumTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(AbsDiffTestCPU, AbsDiffTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(AbsDiffCTestCPU, AbsDiffCTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +// FIXME: Comparison introduced by YL doesn't work with C3 +INSTANTIATE_TEST_CASE_P(AddWeightedTestCPU, AddWeightedTest, + Combine(Values( CV_8UC1/*, CV_8UC3*/, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values( -1, CV_8U, CV_16U, CV_32F ), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(NormTestCPU, NormTest, + Combine(Values(NORM_INF, NORM_L1, NORM_L2), + Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU))), + opencv_test::PrintNormCoreParams()); + +INSTANTIATE_TEST_CASE_P(IntegralTestCPU, IntegralTest, + Combine(Values( CV_8UC1, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ThresholdTestCPU, ThresholdTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::THRESH_BINARY, cv::THRESH_BINARY_INV, cv::THRESH_TRUNC, cv::THRESH_TOZERO, cv::THRESH_TOZERO_INV), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ThresholdTestCPU, ThresholdOTTest, + Combine(Values(CV_8UC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::THRESH_OTSU, cv::THRESH_TRIANGLE), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + + +INSTANTIATE_TEST_CASE_P(InRangeTestCPU, InRangeTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(Split3TestCPU, Split3Test, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(Split4TestCPU, Split4Test, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ResizeTestCPU, ResizeTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::Size(64,64), + cv::Size(30,30)), + Values(0.0), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ResizeTestCPU, ResizeTestFxFy, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(0.5, 0.1), + Values(0.5, 0.1), + Values(0.0), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(Merge3TestCPU, Merge3Test, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(Merge4TestCPU, Merge4Test, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(RemapTestCPU, RemapTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(FlipTestCPU, FlipTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(0,1,-1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(CropTestCPU, CropTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Rect(10, 8, 20, 35), cv::Rect(4, 10, 37, 50)), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(LUTTestCPU, LUTTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(CV_8UC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(LUTTestCustomCPU, LUTTest, + Combine(Values(CV_8UC3), + Values(CV_8UC3), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ConvertToCPU, ConvertToTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(CV_8U, CV_16U, CV_16S, CV_32F), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ConcatHorTestCPU, ConcatHorTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ConcatVertTestCPU, ConcatVertTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ConcatVertVecTestCPU, ConcatVertVecTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(ConcatHorVecTestCPU, ConcatHorVecTest, + Combine(Values( CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1 ), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_CPU)))); +} diff --git a/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp b/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp new file mode 100644 index 0000000000..83bc6c12a2 --- /dev/null +++ b/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp @@ -0,0 +1,486 @@ +// 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) 2018 Intel Corporation + + +#include "../test_precomp.hpp" +#include "../common/gapi_core_tests.hpp" +#include "backends/fluid/gfluidcore.hpp" + +namespace opencv_test +{ + +#define CORE_FLUID cv::gapi::core::fluid::kernels() + +// FIXME: Windows accuracy problems after recent update! +INSTANTIATE_TEST_CASE_P(MathOpTestFluid, MathOpTest, + Combine(Values(ADD, SUB, DIV, MUL), + testing::Bool(), + Values(CV_8UC3, CV_8UC1, CV_16SC1, CV_32FC1), + Values(1.0), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), + testing::Bool(), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID))), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(MulSTestFluid, MulDoubleTest, + Combine(Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1), // FIXME: extend with more types + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(DivCTestFluid, DivCTest, + Combine(Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(CV_8U, CV_32F), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(AbsDiffTestFluid, AbsDiffTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(AbsDiffCTestFluid, AbsDiffCTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(BitwiseTestFluid, BitwiseTest, + Combine(Values(AND, OR, XOR), + Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID))), + opencv_test::PrintBWCoreParams()); + +INSTANTIATE_TEST_CASE_P(BitwiseNotTestFluid, NotTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(MinTestFluid, MinTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(MaxTestFluid, MaxTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(CompareTestFluid, CmpTest, + Combine(Values(CMP_EQ, CMP_GE, CMP_NE, CMP_GT, CMP_LT, CMP_LE), + testing::Bool(), + Values(CV_8UC3, CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID))), + opencv_test::PrintCmpCoreParams()); + +INSTANTIATE_TEST_CASE_P(AddWeightedTestFluid, AddWeightedTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(LUTTestFluid, LUTTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(CV_8UC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(ConvertToFluid, ConvertToTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_32FC1), + Values(CV_8U, CV_16U, CV_32F), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(Split3TestFluid, Split3Test, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(Split4TestFluid, Split4Test, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(Merge3TestFluid, Merge3Test, + Combine(Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(Merge4TestFluid, Merge4Test, + Combine(Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(SelectTestFluid, SelectTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(Polar2CartFluid, Polar2CartTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(Cart2PolarFluid, Cart2PolarTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(ThresholdTestFluid, ThresholdTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::THRESH_BINARY, cv::THRESH_BINARY_INV, + cv::THRESH_TRUNC, + cv::THRESH_TOZERO, cv::THRESH_TOZERO_INV), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(InRangeTestFluid, InRangeTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1920, 1080), + cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + +INSTANTIATE_TEST_CASE_P(ResizeTestFluid, ResizeTest, + Combine(Values(CV_8UC3/*CV_8UC1, CV_16UC1, CV_16SC1*/), + Values(/*cv::INTER_NEAREST,*/ cv::INTER_LINEAR/*, cv::INTER_AREA*/), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128), + cv::Size(64, 64), + cv::Size(30, 30)), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128), + cv::Size(64, 64), + cv::Size(30, 30)), + Values(0.0), + Values(cv::compile_args(CORE_FLUID)))); + +//---------------------------------------------------------------------- +// FIXME: Clean-up test configurations which are enabled already +#if 0 +INSTANTIATE_TEST_CASE_P(MathOpTestCPU, MathOpTest, + Combine(Values(ADD, DIV, MUL), + testing::Bool(), + Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), +/*init output matrices or not*/ testing::Bool(), + Values(false)), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(SubTestCPU, MathOpTest, + Combine(Values(SUB), + testing::Bool(), + Values(CV_8UC1, CV_16SC1 , CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), +/*init output matrices or not*/ testing::Bool(), + testing::Bool()), + opencv_test::PrintMathOpCoreParams()); + +INSTANTIATE_TEST_CASE_P(MulSTestCPU, MulSTest, + Combine(Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(DivCTestCPU, DivCTest, + Combine(Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(CV_8U, CV_32F), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(MeanTestCPU, MeanTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(SelectTestCPU, SelectTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(Polar2CartCPU, Polar2CartTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(Cart2PolarCPU, Cart2PolarTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(CompareTestCPU, CmpTest, + Combine(Values(CMP_EQ, CMP_GE, CMP_NE, CMP_GT, CMP_LT, CMP_LE), + testing::Bool(), + Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool()), + opencv_test::PrintCmpCoreParams()); + +INSTANTIATE_TEST_CASE_P(BitwiseTestCPU, BitwiseTest, + Combine(Values(AND, OR, XOR), + Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool()), + opencv_test::PrintBWCoreParams()); + +INSTANTIATE_TEST_CASE_P(BitwiseNotTestCPU, NotTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + /*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(MinTestCPU, MinTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(MaxTestCPU, MaxTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(SumTestCPU, SumTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(AbsDiffTestCPU, AbsDiffTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(AbsDiffCTestCPU, AbsDiffCTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(AddWeightedTestCPU, AddWeightedTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(NormTestCPU, NormTest, + Combine(Values(NORM_INF, NORM_L1, NORM_L2), + Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128))), + opencv_test::PrintNormCoreParams()); + +INSTANTIATE_TEST_CASE_P(IntegralTestCPU, IntegralTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +INSTANTIATE_TEST_CASE_P(ThresholdTestCPU, ThresholdTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::THRESH_BINARY, cv::THRESH_BINARY_INV, cv::THRESH_TRUNC, cv::THRESH_TOZERO, cv::THRESH_TOZERO_INV), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(ThresholdTestCPU, ThresholdOTTest, + Combine(Values(CV_8UC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::THRESH_OTSU, cv::THRESH_TRIANGLE), +/*init output matrices or not*/ testing::Bool())); + + +INSTANTIATE_TEST_CASE_P(InRangeTestCPU, InRangeTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(Split3TestCPU, Split3Test, + (Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +INSTANTIATE_TEST_CASE_P(Split4TestCPU, Split4Test, + (Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +INSTANTIATE_TEST_CASE_P(Merge3TestCPU, Merge3Test, + (Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +INSTANTIATE_TEST_CASE_P(Merge4TestCPU, Merge4Test, + (Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +INSTANTIATE_TEST_CASE_P(RemapTestCPU, RemapTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(FlipTestCPU, FlipTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(0,1,-1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(CropTestCPU, CropTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Rect(10, 8, 20, 35), cv::Rect(4, 10, 37, 50)), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(LUTTestCPU, LUTTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(CV_8UC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(LUTTestCustomCPU, LUTTest, + Combine(Values(CV_8UC3), + Values(CV_8UC3), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), +/*init output matrices or not*/ testing::Bool())); + +INSTANTIATE_TEST_CASE_P(ConvertToCPU, ConvertToTest, + Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_32FC1), + Values(CV_8U, CV_16U, CV_32F), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +INSTANTIATE_TEST_CASE_P(ConcatHorTestCPU, ConcatHorTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); +INSTANTIATE_TEST_CASE_P(ConcatVertTestCPU, ConcatVertTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)))); + +//---------------------------------------------------------------------- +#endif // 0 + +} diff --git a/modules/gapi/test/cpu/gapi_imgproc_tests_cpu.cpp b/modules/gapi/test/cpu/gapi_imgproc_tests_cpu.cpp new file mode 100644 index 0000000000..dc6a560908 --- /dev/null +++ b/modules/gapi/test/cpu/gapi_imgproc_tests_cpu.cpp @@ -0,0 +1,204 @@ +// 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) 2018 Intel Corporation + + +#include "../test_precomp.hpp" + +#include "../common/gapi_imgproc_tests.hpp" +#include "opencv2/gapi/cpu/imgproc.hpp" + +#define IMGPROC_CPU cv::gapi::imgproc::cpu::kernels() + +namespace opencv_test +{ + +INSTANTIATE_TEST_CASE_P(Filter2DTestCPU, Filter2DTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 4, 5, 7), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::BORDER_DEFAULT), + Values(-1, CV_32F), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(BoxFilterTestCPU, BoxFilterTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3,5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::BORDER_DEFAULT), + Values(-1, CV_32F), + Values(0.0), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(SepFilterTestCPU_8U, SepFilterTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(3), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(-1, CV_16S, CV_32F), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(SepFilterTestCPU_other, SepFilterTest, + Combine(Values(CV_16UC1, CV_16SC1, CV_32FC1), + Values(3), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(-1, CV_32F), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(BlurTestCPU, BlurTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3,5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::BORDER_DEFAULT), + Values(0.0), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(gaussBlurTestCPU, GaussianBlurTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(MedianBlurTestCPU, MedianBlurTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(ErodeTestCPU, ErodeTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::MorphShapes::MORPH_RECT, + cv::MorphShapes::MORPH_CROSS, + cv::MorphShapes::MORPH_ELLIPSE), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(Erode3x3TestCPU, Erode3x3Test, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(1,2,4), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(DilateTestCPU, DilateTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::MorphShapes::MORPH_RECT, + cv::MorphShapes::MORPH_CROSS, + cv::MorphShapes::MORPH_ELLIPSE), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(Dilate3x3TestCPU, Dilate3x3Test, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(1,2,4), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(SobelTestCPU, SobelTest, + Combine(Values(CV_8UC1, CV_8UC3, CV_16UC1, CV_16SC1, CV_32FC1), + Values(3, 5), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(-1, CV_32F), + Values(0, 1), + Values(1, 2), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(EqHistTestCPU, EqHistTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(CannyTestCPU, CannyTest, + Combine(Values(CV_8UC1, CV_8UC3), + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(3.0, 120.0), + Values(125.0, 240.0), + Values(3, 5), + testing::Bool(), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(RGB2GrayTestCPU, RGB2GrayTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(BGR2GrayTestCPU, BGR2GrayTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(RGB2YUVTestCPU, RGB2YUVTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(YUV2RGBTestCPU, YUV2RGBTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(RGB2LabTestCPU, RGB2LabTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(BGR2LUVTestCPU, BGR2LUVTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(LUV2BGRTestCPU, LUV2BGRTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(BGR2YUVTestCPU, BGR2YUVTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +INSTANTIATE_TEST_CASE_P(YUV2BGRTestCPU, YUV2BGRTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), +/*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(IMGPROC_CPU)))); + +} // opencv_test diff --git a/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp b/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp new file mode 100644 index 0000000000..387967fc9f --- /dev/null +++ b/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp @@ -0,0 +1,145 @@ +// 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) 2018 Intel Corporation + + +#include "../test_precomp.hpp" +#include "../common/gapi_imgproc_tests.hpp" +#include "backends/fluid/gfluidimgproc.hpp" + +#define IMGPROC_FLUID cv::gapi::imgproc::fluid::kernels() + +namespace opencv_test +{ + +INSTANTIATE_TEST_CASE_P(RGB2GrayTestFluid, RGB2GrayTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(BGR2GrayTestFluid, BGR2GrayTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(RGB2YUVTestFluid, RGB2YUVTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(YUV2RGBTestFluid, YUV2RGBTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(RGB2LabTestFluid, RGB2LabTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +// FIXME: Not supported by Fluid yet (no kernel implemented) +INSTANTIATE_TEST_CASE_P(BGR2LUVTestFluid, BGR2LUVTest, + Combine(Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(blurTestFluid, BlurTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::BORDER_DEFAULT), + Values(0.0), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(gaussBlurTestFluid, GaussianBlurTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(medianBlurTestFluid, MedianBlurTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(erodeTestFluid, ErodeTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::MorphShapes::MORPH_RECT, + cv::MorphShapes::MORPH_CROSS, + cv::MorphShapes::MORPH_ELLIPSE), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(dilateTestFluid, DilateTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::MorphShapes::MORPH_RECT, + cv::MorphShapes::MORPH_CROSS, + cv::MorphShapes::MORPH_ELLIPSE), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(SobelTestFluid, SobelTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(-1, CV_32F), + Values(0, 1), + Values(1, 2), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(boxFilterTestFluid, BoxFilterTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(cv::BORDER_DEFAULT), + Values(-1, CV_32F), + Values(0.0), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +// FIXME: Tests are failing on Fluid backend (accuracy issue?) +INSTANTIATE_TEST_CASE_P(sepFilterTestFluid, SepFilterTest, + Combine(Values(CV_32FC1), + Values(3), // add kernel size=5 when implementation is ready + Values(cv::Size(1280, 720), + cv::Size(640, 480)), + Values(-1, CV_32F), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +INSTANTIATE_TEST_CASE_P(filter2DTestFluid, Filter2DTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(3), // add kernel size=4,5,7 when implementation ready + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(cv::BORDER_DEFAULT), + Values(-1, CV_32F), + Values(true, false), + Values(cv::compile_args(IMGPROC_FLUID)))); + +} // opencv_test diff --git a/modules/gapi/test/cpu/gapi_operators_tests_cpu.cpp b/modules/gapi/test/cpu/gapi_operators_tests_cpu.cpp new file mode 100644 index 0000000000..92246783db --- /dev/null +++ b/modules/gapi/test/cpu/gapi_operators_tests_cpu.cpp @@ -0,0 +1,67 @@ +// 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) 2018 Intel Corporation + + +#include "../test_precomp.hpp" +#include "../common/gapi_operators_tests.hpp" +#include "opencv2/gapi/cpu/core.hpp" + +#define CORE_CPU cv::gapi::core::cpu::kernels() + +namespace opencv_test +{ + // FIXME: CPU test runs are disabled since Fluid is an exclusive plugin now! + INSTANTIATE_TEST_CASE_P(MathOperatorTestCPU, MathOperatorMatMatTest, + Combine(Values( opPlusM, opMinusM, opDivM, + opGreater, opLess, opGreaterEq, opLessEq, opEq, opNotEq), + Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + + INSTANTIATE_TEST_CASE_P(MathOperatorTestCPU, MathOperatorMatScalarTest, + Combine(Values( opPlus, opPlusR, opMinus, opMinusR, opMul, opMulR, opDiv, opDivR, + opGT, opLT, opGE, opLE, opEQ, opNE, + opGTR, opLTR, opGER, opLER, opEQR, opNER), + Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + + INSTANTIATE_TEST_CASE_P(BitwiseOperatorTestCPU, MathOperatorMatMatTest, + Combine(Values( opAnd, opOr, opXor ), + Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + + INSTANTIATE_TEST_CASE_P(BitwiseOperatorTestCPU, MathOperatorMatScalarTest, + Combine(Values( opAND, opOR, opXOR, opANDR, opORR, opXORR ), + Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + + INSTANTIATE_TEST_CASE_P(BitwiseNotOperatorTestCPU, NotOperatorTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); +} diff --git a/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp b/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp new file mode 100644 index 0000000000..7ad834bd4b --- /dev/null +++ b/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp @@ -0,0 +1,67 @@ +// 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) 2018 Intel Corporation + + +#include "../common/gapi_operators_tests.hpp" +#include "opencv2/gapi/cpu/core.hpp" + +#define CORE_FLUID cv::gapi::core::cpu::kernels() + +namespace opencv_test +{ + INSTANTIATE_TEST_CASE_P(MathOperatorTestFluid, MathOperatorMatMatTest, + Combine(Values( opPlusM, opMinusM, opDivM, + opGreater, opLess, opGreaterEq, opLessEq, opEq, opNotEq), + Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + + //FIXME: Some Mat/Scalar Fluid kernels are not there yet! + INSTANTIATE_TEST_CASE_P(DISABLED_MathOperatorTestFluid, MathOperatorMatScalarTest, + Combine(Values( opPlus, opPlusR, opMinus, opMinusR, opMul, opMulR, opDiv, opDivR, + opGT, opLT, opGE, opLE, opEQ, opNE, + opGTR, opLTR, opGER, opLER, opEQR, opNER), + Values(CV_8UC1, CV_16SC1, CV_32FC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1, CV_8U, CV_32F), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + + INSTANTIATE_TEST_CASE_P(BitwiseOperatorTestFluid, MathOperatorMatMatTest, + Combine(Values( opAnd, opOr, opXor ), + Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + + //FIXME: Some Mat/Scalar Fluid kernels are not there yet! + INSTANTIATE_TEST_CASE_P(DISABLED_BitwiseOperatorTestFluid, MathOperatorMatScalarTest, + Combine(Values( opAND, opOR, opXOR, opANDR, opORR, opXORR ), + Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + Values(-1), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); + + INSTANTIATE_TEST_CASE_P(BitwiseNotOperatorTestFluid, NotOperatorTest, + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + /*init output matrices or not*/ testing::Bool(), + Values(cv::compile_args(CORE_FLUID)))); +} diff --git a/modules/gapi/test/gapi_array_tests.cpp b/modules/gapi/test/gapi_array_tests.cpp new file mode 100644 index 0000000000..e5765624cf --- /dev/null +++ b/modules/gapi/test/gapi_array_tests.cpp @@ -0,0 +1,166 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include +#include + +namespace opencv_test +{ + +namespace ThisTest +{ +using GPointArray = cv::GArray; +G_TYPED_KERNEL(GeneratePoints, , "test.array.out_const") +{ + static GArrayDesc outMeta(const GMatDesc&) { return empty_array_desc(); } +}; +G_TYPED_KERNEL(FindCorners, , "test.array.out") +{ + static GArrayDesc outMeta(const GMatDesc&) { return empty_array_desc(); } +}; +G_TYPED_KERNEL(CountCorners, , "test.array.in") +{ + static GScalarDesc outMeta(const GArrayDesc &) { return empty_scalar_desc(); } +}; +} // namespace ThisTest + +namespace +{ +GAPI_OCV_KERNEL(OCVGeneratePoints, ThisTest::GeneratePoints) +{ + static void run(cv::Mat, std::vector &out) + { + for (int i = 0; i < 10; i++) + out.emplace_back(i, i); + } +}; + +GAPI_OCV_KERNEL(OCVFindCorners, ThisTest::FindCorners) +{ + static void run(cv::Mat in, std::vector &out) + { + cv::goodFeaturesToTrack(in, out, 1024, 0.01, 3); + } +}; + +GAPI_OCV_KERNEL(OCVCountCorners, ThisTest::CountCorners) +{ + static void run(const std::vector &in, cv::Scalar &out) + { + out[0] = static_cast(in.size()); + } +}; + +cv::Mat cross(int w, int h) +{ + cv::Mat mat = cv::Mat::eye(h, w, CV_8UC1)*255; + cv::Mat yee; + cv::flip(mat, yee, 0); // X-axis + mat |= yee; // make an "X" matrix; + return mat; +} +} // (anonymous namespace) + +TEST(GArray, TestReturnValue) +{ + // FIXME: Make .apply() able to take compile arguments + cv::GComputationT c(ThisTest::FindCorners::on); + auto cc = c.compile(cv::GMatDesc{CV_8U,1,{32,32}}, + cv::compile_args(cv::gapi::kernels())); + + // Prepare input matrix + cv::Mat input = cross(32, 32); + + std::vector points; + cc(input, points); + + // OCV goodFeaturesToTrack should find 5 points here (with these settings) + EXPECT_EQ(5u, points.size()); + EXPECT_TRUE(ade::util::find(points, cv::Point(16,16)) != points.end()); + EXPECT_TRUE(ade::util::find(points, cv::Point(30,30)) != points.end()); + EXPECT_TRUE(ade::util::find(points, cv::Point( 1,30)) != points.end()); + EXPECT_TRUE(ade::util::find(points, cv::Point(30, 1)) != points.end()); + EXPECT_TRUE(ade::util::find(points, cv::Point( 1, 1)) != points.end()); +} + +TEST(GArray, TestInputArg) +{ + cv::GComputationT c(ThisTest::CountCorners::on); + auto cc = c.compile(cv::empty_array_desc(), + cv::compile_args(cv::gapi::kernels())); + + const std::vector arr = {cv::Point(1,1), cv::Point(2,2)}; + cv::Scalar out; + cc(arr, out); + EXPECT_EQ(2, out[0]); +} + +TEST(GArray, TestPipeline) +{ + cv::GComputationT c([](cv::GMat in) + { + return ThisTest::CountCorners::on(ThisTest::FindCorners::on(in)); + }); + auto cc = c.compile(cv::GMatDesc{CV_8U,1,{32,32}}, + cv::compile_args(cv::gapi::kernels())); + + cv::Mat input = cross(32, 32); + cv::Scalar out; + cc(input, out); + EXPECT_EQ(5, out[0]); +} + +TEST(GArray, NoAggregationBetweenRuns) +{ + cv::GComputationT c([](cv::GMat in) + { + return ThisTest::CountCorners::on(ThisTest::GeneratePoints::on(in)); + }); + auto cc = c.compile(cv::GMatDesc{CV_8U,1,{32,32}}, + cv::compile_args(cv::gapi::kernels())); + + cv::Mat input = cv::Mat::eye(32, 32, CV_8UC1); + cv::Scalar out; + + cc(input, out); + EXPECT_EQ(10, out[0]); + + // Last kernel in the graph counts number of elements in array, returned by the previous kernel + // (in this test, this variable is constant). + // After 10 executions, this number MUST remain the same - 1st kernel is adding new values on every + // run, but it is graph's responsibility to reset internal object state. + cv::Scalar out2; + for (int i = 0; i < 10; i++) + { + cc(input, out2); + } + EXPECT_EQ(10, out2[0]); +} + +TEST(GArray, TestIntermediateOutput) +{ + using Result = std::tuple; + cv::GComputationT c([](cv::GMat in) + { + auto corners = ThisTest::GeneratePoints::on(in); + return std::make_tuple(corners, ThisTest::CountCorners::on(corners)); + }); + + cv::Mat in_mat = cv::Mat::eye(32, 32, CV_8UC1); + std::vector out_points; + cv::Scalar out_count; + + auto cc = c.compile(cv::descr_of(in_mat), + cv::compile_args(cv::gapi::kernels())); + cc(in_mat, out_points, out_count); + + EXPECT_EQ(10u, out_points.size()); + EXPECT_EQ(10, out_count[0]); +} +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_basic_hetero_tests.cpp b/modules/gapi/test/gapi_basic_hetero_tests.cpp new file mode 100644 index 0000000000..0bf67b1290 --- /dev/null +++ b/modules/gapi/test/gapi_basic_hetero_tests.cpp @@ -0,0 +1,124 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "gapi_mock_kernels.hpp" + +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + +namespace opencv_test +{ + +namespace +{ + GAPI_OCV_KERNEL(OCVFoo, I::Foo) + { + static void run(const cv::Mat &in, cv::Mat &out) + { + out = in + 2; + } + }; + + GAPI_OCV_KERNEL(OCVBar, I::Bar) + { + static void run(const cv::Mat &a, const cv::Mat &b, cv::Mat &out) + { + out = 4*(a + b); + } + }; + + GAPI_FLUID_KERNEL(FFoo, I::Foo, false) + { + static const int Window = 1; + + static void run(const cv::gapi::fluid::View &in, + cv::gapi::fluid::Buffer &out) + { + const uint8_t* in_ptr = in.InLine(0); + uint8_t *out_ptr = out.OutLine(); + for (int i = 0; i < in.length(); i++) + { + out_ptr[i] = in_ptr[i] + 3; + } + } + }; + + GAPI_FLUID_KERNEL(FBar, I::Bar, false) + { + static const int Window = 1; + + static void run(const cv::gapi::fluid::View &in1, + const cv::gapi::fluid::View &in2, + cv::gapi::fluid::Buffer &out) + { + const uint8_t* in1_ptr = in1.InLine(0); + const uint8_t* in2_ptr = in2.InLine(0); + uint8_t *out_ptr = out.OutLine(); + for (int i = 0; i < in1.length(); i++) + { + out_ptr[i] = 3*(in1_ptr[i] + in2_ptr[i]); + } + } + }; +} // anonymous namespace + +struct GAPIHeteroTest: public ::testing::Test +{ + cv::GComputation m_comp; + cv::gapi::GKernelPackage m_ocv_kernels; + cv::gapi::GKernelPackage m_fluid_kernels; + cv::gapi::GKernelPackage m_hetero_kernels; + + cv::Mat m_in_mat; + cv::Mat m_out_mat; + + GAPIHeteroTest(); +}; + +GAPIHeteroTest::GAPIHeteroTest() + : m_comp([](){ + cv::GMat in; + cv::GMat out = I::Bar::on(I::Foo::on(in), + I::Foo::on(in)); + return cv::GComputation(in, out); + }) + , m_ocv_kernels(cv::gapi::kernels()) + , m_fluid_kernels(cv::gapi::kernels()) + , m_hetero_kernels(cv::gapi::kernels()) + , m_in_mat(cv::Mat::eye(cv::Size(64, 64), CV_8UC1)) +{ +} + +TEST_F(GAPIHeteroTest, TestOCV) +{ + EXPECT_TRUE(cv::gapi::cpu::backend() == m_ocv_kernels.lookup()); + EXPECT_TRUE(cv::gapi::cpu::backend() == m_ocv_kernels.lookup()); + + cv::Mat ref = 4*(m_in_mat+2 + m_in_mat+2); + EXPECT_NO_THROW(m_comp.apply(m_in_mat, m_out_mat, cv::compile_args(m_ocv_kernels))); + EXPECT_EQ(0, cv::countNonZero(ref != m_out_mat)); +} + +TEST_F(GAPIHeteroTest, TestFluid) +{ + EXPECT_TRUE(cv::gapi::fluid::backend() == m_fluid_kernels.lookup()); + EXPECT_TRUE(cv::gapi::fluid::backend() == m_fluid_kernels.lookup()); + + cv::Mat ref = 3*(m_in_mat+3 + m_in_mat+3); + EXPECT_NO_THROW(m_comp.apply(m_in_mat, m_out_mat, cv::compile_args(m_fluid_kernels))); + EXPECT_EQ(0, cv::countNonZero(ref != m_out_mat)); +} + +TEST_F(GAPIHeteroTest, TestBoth_ExpectFailure) +{ + EXPECT_TRUE(cv::gapi::cpu::backend() == m_hetero_kernels.lookup()); + EXPECT_TRUE(cv::gapi::fluid::backend() == m_hetero_kernels.lookup()); + EXPECT_ANY_THROW(m_comp.apply(m_in_mat, m_out_mat, cv::compile_args(m_hetero_kernels))); +} + + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_desc_tests.cpp b/modules/gapi/test/gapi_desc_tests.cpp new file mode 100644 index 0000000000..711211da2a --- /dev/null +++ b/modules/gapi/test/gapi_desc_tests.cpp @@ -0,0 +1,202 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "opencv2/gapi/cpu/gcpukernel.hpp" + +namespace opencv_test +{ + +namespace +{ + G_TYPED_KERNEL(KTest, , "org.opencv.test.scalar_kernel") { + static cv::GScalarDesc outMeta(cv::GScalarDesc in) { return in; } + }; + GAPI_OCV_KERNEL(GOCVScalarTest, KTest) + { + static void run(const cv::Scalar &in, cv::Scalar &out) { out = in+cv::Scalar(1); } + }; +} + +TEST(GAPI_MetaDesc, MatDesc) +{ + cv::Mat m1(240, 320, CV_8U); + const auto desc1 = cv::descr_of(m1); + EXPECT_EQ(CV_8U, desc1.depth); + EXPECT_EQ(1, desc1.chan); + EXPECT_EQ(320, desc1.size.width); + EXPECT_EQ(240, desc1.size.height); + + cv::Mat m2(480, 640, CV_8UC3); + const auto desc2 = cv::descr_of(m2); + EXPECT_EQ(CV_8U, desc2.depth); + EXPECT_EQ(3, desc2.chan); + EXPECT_EQ(640, desc2.size.width); + EXPECT_EQ(480, desc2.size.height); +} + +TEST(GAPI_MetaDesc, Compare_Equal_MatDesc) +{ + const auto desc1 = cv::GMatDesc{CV_8U, 1, {64, 64}}; + const auto desc2 = cv::GMatDesc{CV_8U, 1, {64, 64}}; + + EXPECT_TRUE(desc1 == desc2); +} + +TEST(GAPI_MetaDesc, Compare_Not_Equal_MatDesc) +{ + const auto desc1 = cv::GMatDesc{CV_8U, 1, {64, 64}}; + const auto desc2 = cv::GMatDesc{CV_32F, 1, {64, 64}}; + + EXPECT_TRUE(desc1 != desc2); +} + +TEST(GAPI_MetaDesc, Compile_MatchMetaNumber_1) +{ + cv::GMat in; + cv::GComputation cc(in, in+in); + + const auto desc1 = cv::GMatDesc{CV_8U,1,{64,64}}; + const auto desc2 = cv::GMatDesc{CV_32F,1,{128,128}}; + + EXPECT_NO_THROW(cc.compile(desc1)); + EXPECT_NO_THROW(cc.compile(desc2)); + + // FIXME: custom exception type? + // It is worth checking if compilation fails with different number + // of meta parameters + EXPECT_THROW(cc.compile(desc1, desc1), std::logic_error); + EXPECT_THROW(cc.compile(desc1, desc2, desc2), std::logic_error); +} + +TEST(GAPI_MetaDesc, Compile_MatchMetaNumber_2) +{ + cv::GMat a, b; + cv::GComputation cc(cv::GIn(a, b), cv::GOut(a+b)); + + const auto desc1 = cv::GMatDesc{CV_8U,1,{64,64}}; + EXPECT_NO_THROW(cc.compile(desc1, desc1)); + + const auto desc2 = cv::GMatDesc{CV_32F,1,{128,128}}; + EXPECT_NO_THROW(cc.compile(desc2, desc2)); + + // FIXME: custom exception type? + EXPECT_THROW(cc.compile(desc1), std::logic_error); + EXPECT_THROW(cc.compile(desc2), std::logic_error); + EXPECT_THROW(cc.compile(desc2, desc2, desc2), std::logic_error); +} + +TEST(GAPI_MetaDesc, Compile_MatchMetaType_Mat) +{ + cv::GMat in; + cv::GComputation cc(in, in+in); + + EXPECT_NO_THROW(cc.compile(cv::GMatDesc{CV_8U,1,{64,64}})); + + // FIXME: custom exception type? + EXPECT_THROW(cc.compile(cv::empty_scalar_desc()), std::logic_error); +} + +TEST(GAPI_MetaDesc, Compile_MatchMetaType_Scalar) +{ + cv::GScalar in; + cv::GComputation cc(cv::GIn(in), cv::GOut(KTest::on(in))); + + const auto desc1 = cv::descr_of(cv::Scalar(128)); + const auto desc2 = cv::GMatDesc{CV_8U,1,{64,64}}; + const auto pkg = cv::gapi::kernels(); + EXPECT_NO_THROW(cc.compile(desc1, cv::compile_args(pkg))); + + // FIXME: custom exception type? + EXPECT_THROW(cc.compile(desc2, cv::compile_args(pkg)), std::logic_error); +} + +TEST(GAPI_MetaDesc, Compile_MatchMetaType_Mixed) +{ + cv::GMat a; + cv::GScalar v; + cv::GComputation cc(cv::GIn(a, v), cv::GOut(cv::gapi::addC(a, v))); + + const auto desc1 = cv::GMatDesc{CV_8U,1,{64,64}}; + const auto desc2 = cv::descr_of(cv::Scalar(4)); + + EXPECT_NO_THROW(cc.compile(desc1, desc2)); + + // FIXME: custom exception type(s)? + EXPECT_THROW(cc.compile(desc1), std::logic_error); + EXPECT_THROW(cc.compile(desc2), std::logic_error); + EXPECT_THROW(cc.compile(desc2, desc1), std::logic_error); + EXPECT_THROW(cc.compile(desc1, desc1, desc1), std::logic_error); + EXPECT_THROW(cc.compile(desc1, desc2, desc1), std::logic_error); +} + +TEST(GAPI_MetaDesc, Typed_Compile_MatchMetaNumber_1) +{ + cv::GComputationT cc([](cv::GMat in) + { + return in+in; + }); + + const auto desc1 = cv::GMatDesc{CV_8U,1,{64,64}}; + const auto desc2 = cv::GMatDesc{CV_32F,1,{128,128}}; + + EXPECT_NO_THROW(cc.compile(desc1)); + EXPECT_NO_THROW(cc.compile(desc2)); +} + +TEST(GAPI_MetaDesc, Typed_Compile_MatchMetaNumber_2) +{ + cv::GComputationT cc([](cv::GMat a, cv::GMat b) + { + return a + b; + }); + + const auto desc1 = cv::GMatDesc{CV_8U,1,{64,64}}; + EXPECT_NO_THROW(cc.compile(desc1, desc1)); + + const auto desc2 = cv::GMatDesc{CV_32F,1,{128,128}}; + EXPECT_NO_THROW(cc.compile(desc2, desc2)); +} + +TEST(GAPI_MetaDesc, Typed_Compile_MatchMetaType_Mat) +{ + cv::GComputationT cc([](cv::GMat in) + { + return in+in; + }); + + EXPECT_NO_THROW(cc.compile(cv::GMatDesc{CV_8U,1,{64,64}})); +} + +TEST(GAPI_MetaDesc, Typed_Compile_MatchMetaType_Scalar) +{ + cv::GComputationT cc([](cv::GScalar in) + { + return KTest::on(in); + }); + + const auto desc1 = cv::descr_of(cv::Scalar(128)); + const auto pkg = cv::gapi::kernels(); + // EXPECT_NO_THROW(cc.compile(desc1, cv::compile_args(pkg))); + cc.compile(desc1, cv::compile_args(pkg)); +} + +TEST(GAPI_MetaDesc, Typed_Compile_MatchMetaType_Mixed) +{ + cv::GComputationT cc([](cv::GMat a, cv::GScalar v) + { + return cv::gapi::addC(a, v); + }); + + const auto desc1 = cv::GMatDesc{CV_8U,1,{64,64}}; + const auto desc2 = cv::descr_of(cv::Scalar(4)); + + EXPECT_NO_THROW(cc.compile(desc1, desc2)); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_fluid_resize_test.cpp b/modules/gapi/test/gapi_fluid_resize_test.cpp new file mode 100644 index 0000000000..de6d4ea91b --- /dev/null +++ b/modules/gapi/test/gapi_fluid_resize_test.cpp @@ -0,0 +1,649 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "gapi_fluid_test_kernels.hpp" + +namespace opencv_test +{ + +using namespace cv::gapi_test_kernels; + +G_TYPED_KERNEL(TCopy, , "test.fluid.copy") +{ + static GMatDesc outMeta(const cv::GMatDesc &in) { + return in; + } +}; + +GAPI_FLUID_KERNEL(FCopy, TCopy, false) +{ + static const int Window = 1; + + static void run(const cv::gapi::fluid::View &in, + cv::gapi::fluid::Buffer &out) + { + const uint8_t* in_row = in .InLine (0); + uint8_t* out_row = out.OutLine(); + + for (int i = 0, w = in.length(); i < w; i++) + { + //std::cout << std::setw(4) << int(in_row[i]); + out_row[i] = in_row[i]; + } + //std::cout << std::endl; + } +}; + +GAPI_FLUID_KERNEL(FResizeNN, cv::gapi::core::GResize, false) +{ + static const int Window = 1; + static const auto Kind = GFluidKernel::Kind::Resize; + + static void run(const cv::gapi::fluid::View& in, cv::Size /*sz*/, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer& out) + + { + double vRatio = (double)in.meta().size.height / out.meta().size.height; + auto y = out.y(); + auto inY = in.y(); + + auto sy = static_cast(y * vRatio); + int idx = sy - inY; + + const auto src = in.InLine (idx); + auto dst = out.OutLine(); + + double horRatio = (double)in.length() / out.length(); + + for (int x = 0; x < out.length(); x++) + { + auto inX = static_cast(x * horRatio); + dst[x] = src[inX]; + } + } +}; + +namespace +{ +namespace func +{ +template +void initScratch(const cv::GMatDesc& in, cv::Size outSz, cv::gapi::fluid::Buffer &scratch) +{ + CV_Assert(in.depth == CV_8U && in.chan == 1); + + cv::Size scratch_size{static_cast(outSz.width * sizeof(typename Mapper::Unit)), 1}; + + cv::GMatDesc desc; + desc.chan = 1; + desc.depth = CV_8UC1; + desc.size = scratch_size; + + cv::gapi::fluid::Buffer buffer(desc); + scratch = std::move(buffer); + + auto mapX = scratch.OutLine(); + double hRatio = (double)in.size.width / outSz.width; + + for (int x = 0, w = outSz.width; x < w; x++) + { + mapX[x] = Mapper::map(hRatio, 0, in.size.width, x); + } +} + +template +inline void calcRow(const cv::gapi::fluid::View& in, cv::gapi::fluid::Buffer& out, cv::gapi::fluid::Buffer &scratch) +{ + double vRatio = (double)in.meta().size.height / out.meta().size.height; + auto mapY = Mapper::map(vRatio, in.y(), in.meta().size.height, out.y()); + + const auto src0 = in.InLine (mapY.s0); + const auto src1 = in.InLine (mapY.s1); + + auto dst = out.OutLine(); + auto mapX = scratch.OutLine(); + + for (int x = 0; x < out.length(); x++) + { + auto alpha0 = mapX[x].alpha0; + auto alpha1 = mapX[x].alpha1; + auto sx0 = mapX[x].s0; + auto sx1 = mapX[x].s1; + + int res0 = src0[sx0]*alpha0 + src0[sx1]*alpha1; + int res1 = src1[sx0]*alpha0 + src1[sx1]*alpha1; + + dst[x] = uchar(( ((mapY.alpha0 * (res0 >> 4)) >> 16) + ((mapY.alpha1 * (res1 >> 4)) >> 16) + 2)>>2); + } +} +} // namespace func + +constexpr static const int INTER_RESIZE_COEF_BITS = 11; +constexpr static const int INTER_RESIZE_COEF_SCALE = 1 << INTER_RESIZE_COEF_BITS; + +namespace linear +{ +struct Mapper +{ + struct Unit + { + short alpha0; + short alpha1; + int s0; + int s1; + }; + + static inline Unit map(double ratio, int start, int max, int outCoord) + { + auto f = static_cast((outCoord + 0.5f) * ratio - 0.5f); + int s = cvFloor(f); + f -= s; + + Unit u; + + u.s0 = std::max(s - start, 0); + u.s1 = ((f == 0.0) || s + 1 >= max) ? s - start : s - start + 1; + + u.alpha0 = saturate_cast((1.0f - f) * INTER_RESIZE_COEF_SCALE); + u.alpha1 = saturate_cast((f) * INTER_RESIZE_COEF_SCALE); + + return u; + } +}; + +} // namespace linear + +namespace areaUpscale +{ +struct Mapper +{ + struct Unit + { + short alpha0; + short alpha1; + int s0; + int s1; + }; + + static inline Unit map(double ratio, int start, int max, int outCoord) + { + int s = cvFloor(outCoord*ratio); + float f = (float)((outCoord+1) - (s+1)/ratio); + f = f <= 0 ? 0.f : f - cvFloor(f); + + Unit u; + + u.s0 = std::max(s - start, 0); + u.s1 = ((f == 0.0) || s + 1 >= max) ? s - start : s - start + 1; + + u.alpha0 = saturate_cast((1.0f - f) * INTER_RESIZE_COEF_SCALE); + u.alpha1 = saturate_cast((f) * INTER_RESIZE_COEF_SCALE); + + return u; + } +}; +} // namespace areaUpscale +} // anonymous namespace + +GAPI_FLUID_KERNEL(FResizeLinear, cv::gapi::core::GResize, true) +{ + static const int Window = 1; + static const auto Kind = GFluidKernel::Kind::Resize; + + static void initScratch(const cv::GMatDesc& in, + cv::Size outSz, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer &scratch) + { + func::initScratch(in, outSz, scratch); + } + + static void resetScratch(cv::gapi::fluid::Buffer& /*scratch*/) + {} + + static void run(const cv::gapi::fluid::View& in, cv::Size /*sz*/, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer& out, cv::gapi::fluid::Buffer &scratch) + + { + func::calcRow(in, out, scratch); + } +}; + +namespace +{ +// FIXME +// Move to some common place (to reuse/align with ResizeAgent) +auto startInCoord = [](int outCoord, double ratio) { + return static_cast(outCoord * ratio + 1e-3); +}; +auto endInCoord = [](int outCoord, double ratio) { + return static_cast(std::ceil((outCoord + 1) * ratio - 1e-3)); +}; +} // namespace + +GAPI_FLUID_KERNEL(FResizeArea, cv::gapi::core::GResize, false) +{ + static const int Window = 1; + static const auto Kind = GFluidKernel::Kind::Resize; + + static void run(const cv::gapi::fluid::View& in, cv::Size /*sz*/, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer& out) + + { + auto y = out.y(); + double vRatio = (double)in.meta().size.height / out.meta().size.height; + + int startY = startInCoord(y, vRatio); + int endY = endInCoord (y, vRatio); + + auto dst = out.OutLine(); + + double hRatio = (double)in.length() / out.length(); + + for (int x = 0; x < out.length(); x++) + { + float res = 0.0; + + int startX = startInCoord(x, hRatio); + int endX = endInCoord (x, hRatio); + + for (int inY = startY; inY < endY; inY++) + { + double startCoordY = inY / vRatio; + double endCoordY = startCoordY + 1/vRatio; + + if (startCoordY < y) startCoordY = y; + if (endCoordY > y + 1) endCoordY = y + 1; + + float fracY = static_cast((inY == startY || inY == endY - 1) ? endCoordY - startCoordY : 1/vRatio); + + const auto src = in.InLine (inY - startY); + + float rowSum = 0.0f; + + for (int inX = startX; inX < endX; inX++) + { + double startCoordX = inX / hRatio; + double endCoordX = startCoordX + 1/hRatio; + + if (startCoordX < x) startCoordX = x; + if (endCoordX > x + 1) endCoordX = x + 1; + + float fracX = static_cast((inX == startX || inX == endX - 1) ? endCoordX - startCoordX : 1/hRatio); + + rowSum += src[inX] * fracX; + } + res += rowSum * fracY; + } + dst[x] = static_cast(std::rint(res)); + } + } +}; + +GAPI_FLUID_KERNEL(FResizeAreaUpscale, cv::gapi::core::GResize, true) +{ + static const int Window = 1; + static const auto Kind = GFluidKernel::Kind::Resize; + + static void initScratch(const cv::GMatDesc& in, + cv::Size outSz, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer &scratch) + { + func::initScratch(in, outSz, scratch); + } + + static void resetScratch(cv::gapi::fluid::Buffer& /*scratch*/) + {} + + static void run(const cv::gapi::fluid::View& in, cv::Size /*sz*/, double /*fx*/, double /*fy*/, int /*interp*/, + cv::gapi::fluid::Buffer& out, cv::gapi::fluid::Buffer &scratch) + { + func::calcRow(in, out, scratch); + } +}; + +static auto fluidResizeTestPackage = [](int interpolation, cv::Size szIn, cv::Size szOut) +{ + bool upscale = szIn.width < szOut.width || szIn.height < szOut.height; + + cv::gapi::GKernelPackage pkg; + switch (interpolation) + { + case cv::INTER_NEAREST: pkg = cv::gapi::kernels(); break; + case cv::INTER_LINEAR: pkg = cv::gapi::kernels(); break; + case cv::INTER_AREA: pkg = upscale ? cv::gapi::kernels() + : cv::gapi::kernels(); break; + default: CV_Assert(false); + } + return cv::gapi::combine(pkg, fluidTestPackage, cv::unite_policy::KEEP); +}; + +struct ResizeTestFluid : public TestWithParam, double>> {}; +TEST_P(ResizeTestFluid, SanityTest) +{ + int type = 0, interp = 0; + cv::Size sz_in, sz_out; + double tolerance = 0.0; + cv::Rect outRoi; + std::tuple outSizeAndRoi; + std::tie(type, interp, sz_in, outSizeAndRoi, tolerance) = GetParam(); + std::tie(sz_out, outRoi) = outSizeAndRoi; + if (outRoi == cv::Rect{}) outRoi = {0,0,sz_out.width,sz_out.height}; + if (outRoi.width == 0) outRoi.width = sz_out.width; + double fx = 0, fy = 0; + + cv::Mat in_mat1 (sz_in, type ); + cv::Scalar mean = cv::Scalar(127); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat1, mean, stddev); + + cv::Mat out_mat = cv::Mat::zeros(sz_out, type); + cv::Mat out_mat_ocv = cv::Mat::zeros(sz_out, type); + + cv::GMat in; + auto mid = TBlur3x3::on(in, cv::BORDER_REPLICATE, {}); + auto out = cv::gapi::resize(mid, sz_out, fx, fy, interp); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat, cv::compile_args(GFluidOutputRois{{outRoi}}, fluidResizeTestPackage(interp, sz_in, sz_out))); + + cv::Mat mid_mat; + cv::blur(in_mat1, mid_mat, {3,3}, {-1,-1}, cv::BORDER_REPLICATE); + cv::resize(mid_mat, out_mat_ocv, sz_out, fx, fy, interp); + + cv::Mat absDiff; + cv::absdiff(out_mat(outRoi), out_mat_ocv(outRoi), absDiff); + EXPECT_EQ(0, cv::countNonZero(absDiff > tolerance)); +} + +INSTANTIATE_TEST_CASE_P(ResizeTestCPU, ResizeTestFluid, + Combine(Values(CV_8UC1), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR), + Values(cv::Size(8, 7), + cv::Size(8, 8), + cv::Size(8, 64), + cv::Size(8, 25), + cv::Size(16, 8), + cv::Size(16, 7)), + Values(std::make_tuple(cv::Size(5, 4), cv::Rect{}), + std::make_tuple(cv::Size(5, 4), cv::Rect{0, 0, 0, 2}), + std::make_tuple(cv::Size(5, 4), cv::Rect{0, 1, 0, 2}), + std::make_tuple(cv::Size(5, 4), cv::Rect{0, 2, 0, 2}), + std::make_tuple(cv::Size(7, 7), cv::Rect{}), + std::make_tuple(cv::Size(7, 7), cv::Rect{0, 0, 0, 3}), + std::make_tuple(cv::Size(7, 7), cv::Rect{0, 2, 0, 2}), + std::make_tuple(cv::Size(7, 7), cv::Rect{0, 4, 0, 3}), + std::make_tuple(cv::Size(8, 4), cv::Rect{}), + std::make_tuple(cv::Size(8, 4), cv::Rect{0, 0, 0, 3}), + std::make_tuple(cv::Size(8, 4), cv::Rect{0, 1, 0, 2}), + std::make_tuple(cv::Size(8, 4), cv::Rect{0, 3, 0, 1})), + Values(0.0))); + +INSTANTIATE_TEST_CASE_P(ResizeAreaTestCPU, ResizeTestFluid, + Combine(Values(CV_8UC1), + Values(cv::INTER_AREA), + Values(cv::Size(8, 7), + cv::Size(8, 8), + cv::Size(8, 64), + cv::Size(8, 25), + cv::Size(16, 8), + cv::Size(16, 7)), + Values(std::make_tuple(cv::Size(5, 4), cv::Rect{}), + std::make_tuple(cv::Size(5, 4), cv::Rect{0, 0, 0, 2}), + std::make_tuple(cv::Size(5, 4), cv::Rect{0, 1, 0, 2}), + std::make_tuple(cv::Size(5, 4), cv::Rect{0, 2, 0, 2}), + std::make_tuple(cv::Size(7, 7), cv::Rect{}), + std::make_tuple(cv::Size(7, 7), cv::Rect{0, 0, 0, 3}), + std::make_tuple(cv::Size(7, 7), cv::Rect{0, 2, 0, 2}), + std::make_tuple(cv::Size(7, 7), cv::Rect{0, 4, 0, 3}), + std::make_tuple(cv::Size(8, 4), cv::Rect{}), + std::make_tuple(cv::Size(8, 4), cv::Rect{0, 0, 0, 3}), + std::make_tuple(cv::Size(8, 4), cv::Rect{0, 1, 0, 2}), + std::make_tuple(cv::Size(8, 4), cv::Rect{0, 3, 0, 1})), + // Actually this tolerance only for cases where OpenCV + // uses ResizeAreaFast + Values(1.0))); + +INSTANTIATE_TEST_CASE_P(ResizeUpscaleTestCPU, ResizeTestFluid, + Combine(Values(CV_8UC1), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values(cv::Size(1, 5), + cv::Size(3, 5), + cv::Size(7, 5), + cv::Size(1, 7), + cv::Size(3, 7), + cv::Size(7, 7)), + Values(std::make_tuple(cv::Size(8, 8), cv::Rect{0,0,8,2}), + std::make_tuple(cv::Size(8, 8), cv::Rect{0,2,8,2}), + std::make_tuple(cv::Size(8, 8), cv::Rect{0,4,8,2}), + std::make_tuple(cv::Size(8, 8), cv::Rect{0,6,8,2}), + std::make_tuple(cv::Size(8, 8), cv::Rect{0,0,8,8}), + std::make_tuple(cv::Size(16, 8), cv::Rect{}), + std::make_tuple(cv::Size(16, 64), cv::Rect{0, 0,16,16}), + std::make_tuple(cv::Size(16, 64), cv::Rect{0,16,16,16}), + std::make_tuple(cv::Size(16, 64), cv::Rect{0,32,16,16}), + std::make_tuple(cv::Size(16, 64), cv::Rect{0,48,16,16}), + std::make_tuple(cv::Size(16, 64), cv::Rect{0, 0,16,64}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0, 0,16, 7}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0, 7,16, 6}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0,13,16, 6}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0,19,16, 6}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0, 0,16, 7}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0, 7,16, 7}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0,14,16, 7}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0,21,16, 4}), + std::make_tuple(cv::Size(16, 25), cv::Rect{0, 0,16,25}), + std::make_tuple(cv::Size(16, 7), cv::Rect{}), + std::make_tuple(cv::Size(16, 8), cv::Rect{})), + Values(0.0))); + +INSTANTIATE_TEST_CASE_P(ResizeUpscaleOneDimDownscaleAnother, ResizeTestFluid, + Combine(Values(CV_8UC1), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values(cv::Size(6, 6), + cv::Size(8, 7), + cv::Size(8, 8), + cv::Size(8, 10), + cv::Size(10, 8), + cv::Size(10, 7)), + Values(std::make_tuple(cv::Size(11, 5), cv::Rect{}), + std::make_tuple(cv::Size(11, 5), cv::Rect{0, 0, 0, 2}), + std::make_tuple(cv::Size(11, 5), cv::Rect{0, 2, 0, 2}), + std::make_tuple(cv::Size(11, 5), cv::Rect{0, 4, 0, 1}), + std::make_tuple(cv::Size(12, 2), cv::Rect{}), + std::make_tuple(cv::Size(12, 2), cv::Rect{0, 0, 0, 1}), + std::make_tuple(cv::Size(12, 2), cv::Rect{0, 1, 0, 1}), + std::make_tuple(cv::Size(23, 3), cv::Rect{}), + std::make_tuple(cv::Size(23, 3), cv::Rect{0, 0, 0, 1}), + std::make_tuple(cv::Size(23, 3), cv::Rect{0, 1, 0, 1}), + std::make_tuple(cv::Size(23, 3), cv::Rect{0, 2, 0, 1}), + std::make_tuple(cv::Size(3, 24), cv::Rect{}), + std::make_tuple(cv::Size(3, 24), cv::Rect{0, 0, 0, 6}), + std::make_tuple(cv::Size(3, 24), cv::Rect{0, 6, 0, 6}), + std::make_tuple(cv::Size(3, 24), cv::Rect{0, 12, 0, 6}), + std::make_tuple(cv::Size(3, 24), cv::Rect{0, 18, 0, 6}), + std::make_tuple(cv::Size(5, 11), cv::Rect{}), + std::make_tuple(cv::Size(5, 11), cv::Rect{0, 0, 0, 3}), + std::make_tuple(cv::Size(5, 11), cv::Rect{0, 3, 0, 3}), + std::make_tuple(cv::Size(5, 11), cv::Rect{0, 6, 0, 3}), + std::make_tuple(cv::Size(5, 11), cv::Rect{0, 9, 0, 2})), + Values(0.0))); + +INSTANTIATE_TEST_CASE_P(Resize400_384TestCPU, ResizeTestFluid, + Combine(Values(CV_8UC1), + Values(cv::INTER_NEAREST, cv::INTER_LINEAR, cv::INTER_AREA), + Values(cv::Size(128, 400)), + Values(std::make_tuple(cv::Size(128, 384), cv::Rect{})), + Values(0.0))); + +INSTANTIATE_TEST_CASE_P(Resize220_400TestCPU, ResizeTestFluid, + Combine(Values(CV_8UC1), + Values(cv::INTER_LINEAR), + Values(cv::Size(220, 220)), + Values(std::make_tuple(cv::Size(400, 400), cv::Rect{})), + Values(0.0))); + +static auto cvBlur = [](const cv::Mat& in, cv::Mat& out, int kernelSize) +{ + if (kernelSize == 1) + { + out = in; + } + else + { + cv::blur(in, out, {kernelSize, kernelSize}); + } +}; + +using SizesWithRois = std::tuple; +struct ResizeAndAnotherReaderTest : public TestWithParam>{}; +TEST_P(ResizeAndAnotherReaderTest, SanityTest) +{ + bool readFromInput = false; + int interp = -1, kernelSize = -1; + SizesWithRois sizesWithRois; + std::tie(interp, kernelSize, readFromInput, sizesWithRois) = GetParam(); + + cv::Size sz, resizedSz; + cv::Rect roi, resizedRoi; + std::tie(sz, roi, resizedSz, resizedRoi) = sizesWithRois; + + cv::Mat in_mat(sz, CV_8UC1); + cv::Scalar mean = cv::Scalar(127); + cv::Scalar stddev = cv::Scalar(40.f); + cv::randn(in_mat, mean, stddev); + + cv::Mat gapi_resize_out = cv::Mat::zeros(resizedSz, CV_8UC1); + cv::Mat gapi_blur_out = cv::Mat::zeros(sz, CV_8UC1); + + auto blur = kernelSize == 1 ? &TBlur1x1::on : kernelSize == 3 ? &TBlur3x3::on : &TBlur5x5::on; + + cv::GMat in, resize_out, blur_out; + + if (readFromInput) + { + resize_out = gapi::resize(in, resizedSz, 0, 0, interp); + blur_out = blur(in, cv::BORDER_DEFAULT, {}); + } + else + { + auto mid = TCopy::on(in); + resize_out = gapi::resize(mid, resizedSz, 0, 0, interp); + blur_out = blur(mid, cv::BORDER_DEFAULT, {}); + } + + cv::GComputation c(GIn(in), GOut(resize_out, blur_out)); + c.apply(gin(in_mat), gout(gapi_resize_out, gapi_blur_out), cv::compile_args(GFluidOutputRois{{resizedRoi, roi}}, + fluidResizeTestPackage(interp, sz, resizedSz))); + + cv::Mat ocv_resize_out = cv::Mat::zeros(resizedSz, CV_8UC1); + cv::resize(in_mat, ocv_resize_out, resizedSz, 0, 0, interp); + cv::Mat ocv_blur_out = cv::Mat::zeros(sz, CV_8UC1); + cvBlur(in_mat, ocv_blur_out, kernelSize); + + EXPECT_EQ(0, cv::countNonZero(gapi_resize_out(resizedRoi) != ocv_resize_out(resizedRoi))); + EXPECT_EQ(0, cv::countNonZero(gapi_blur_out(roi) != ocv_blur_out(roi))); +} + +INSTANTIATE_TEST_CASE_P(ResizeTestCPU, ResizeAndAnotherReaderTest, + Combine(Values(cv::INTER_NEAREST, cv::INTER_LINEAR), + Values(1, 3, 5), + testing::Bool(), // Read from input directly or place a copy node at start + Values(std::make_tuple(cv::Size{8,8}, cv::Rect{0,0,8,8}, + cv::Size{4,4}, cv::Rect{0,0,4,4}), + std::make_tuple(cv::Size{8,8}, cv::Rect{0,0,8,2}, + cv::Size{4,4}, cv::Rect{0,0,4,1}), + std::make_tuple(cv::Size{8,8}, cv::Rect{0,2,8,4}, + cv::Size{4,4}, cv::Rect{0,1,4,2}), + std::make_tuple(cv::Size{8,8}, cv::Rect{0,4,8,4}, + cv::Size{4,4}, cv::Rect{0,2,4,2}), + std::make_tuple(cv::Size{64,64}, cv::Rect{0, 0,64,64}, + cv::Size{49,49}, cv::Rect{0, 0,49,49}), + std::make_tuple(cv::Size{64,64}, cv::Rect{0, 0,64,15}, + cv::Size{49,49}, cv::Rect{0, 0,49,11}), + std::make_tuple(cv::Size{64,64}, cv::Rect{0,11,64,23}, + cv::Size{49,49}, cv::Rect{0, 9,49,17}), + std::make_tuple(cv::Size{64,64}, cv::Rect{0,50,64,14}, + cv::Size{49,49}, cv::Rect{0,39,49,10})))); + +struct BlursAfterResizeTest : public TestWithParam>>{}; +TEST_P(BlursAfterResizeTest, SanityTest) +{ + bool readFromInput = false; + int interp = -1, kernelSize1 = -1, kernelSize2 = -1; + std::tuple sizesWithRoi; + std::tie(interp, kernelSize1, kernelSize2, readFromInput, sizesWithRoi) = GetParam(); + + cv::Size inSz, outSz; + cv::Rect outRoi; + std::tie(inSz, outSz, outRoi) = sizesWithRoi; + + cv::Mat in_mat(inSz, CV_8UC1); + cv::Scalar mean = cv::Scalar(127); + cv::Scalar stddev = cv::Scalar(40.f); + cv::randn(in_mat, mean, stddev); + cv::Mat gapi_out1 = cv::Mat::zeros(outSz, CV_8UC1); + cv::Mat gapi_out2 = cv::Mat::zeros(outSz, CV_8UC1); + + auto blur1 = kernelSize1 == 1 ? &TBlur1x1::on : kernelSize1 == 3 ? &TBlur3x3::on : &TBlur5x5::on; + auto blur2 = kernelSize2 == 1 ? &TBlur1x1::on : kernelSize2 == 3 ? &TBlur3x3::on : &TBlur5x5::on; + + cv::GMat in, out1, out2; + if (readFromInput) + { + auto resized = gapi::resize(in, outSz, 0, 0, interp); + out1 = blur1(resized, cv::BORDER_DEFAULT, {}); + out2 = blur2(resized, cv::BORDER_DEFAULT, {}); + } + else + { + auto mid = TCopy::on(in); + auto resized = gapi::resize(mid, outSz, 0, 0, interp); + out1 = blur1(resized, cv::BORDER_DEFAULT, {}); + out2 = blur2(resized, cv::BORDER_DEFAULT, {}); + } + + cv::GComputation c(GIn(in), GOut(out1, out2)); + c.apply(gin(in_mat), gout(gapi_out1, gapi_out2), cv::compile_args(GFluidOutputRois{{outRoi, outRoi}}, + fluidResizeTestPackage(interp, inSz, outSz))); + + cv::Mat ocv_out1 = cv::Mat::zeros(outSz, CV_8UC1); + cv::Mat ocv_out2 = cv::Mat::zeros(outSz, CV_8UC1); + cv::Mat resized = cv::Mat::zeros(outSz, CV_8UC1); + cv::resize(in_mat, resized, outSz, 0, 0, interp); + cvBlur(resized, ocv_out1, kernelSize1); + cvBlur(resized, ocv_out2, kernelSize2); + + EXPECT_EQ(0, cv::countNonZero(gapi_out1(outRoi) != ocv_out1(outRoi))); + EXPECT_EQ(0, cv::countNonZero(gapi_out2(outRoi) != ocv_out2(outRoi))); +} + +INSTANTIATE_TEST_CASE_P(ResizeTestCPU, BlursAfterResizeTest, + Combine(Values(cv::INTER_NEAREST, cv::INTER_LINEAR), + Values(1, 3, 5), + Values(1, 3, 5), + testing::Bool(), // Read from input directly or place a copy node at start + Values(std::make_tuple(cv::Size{8,8}, + cv::Size{4,4}, cv::Rect{0,0,4,4}), + std::make_tuple(cv::Size{8,8}, + cv::Size{4,4}, cv::Rect{0,0,4,1}), + std::make_tuple(cv::Size{8,8}, + cv::Size{4,4}, cv::Rect{0,1,4,2}), + std::make_tuple(cv::Size{8,8}, + cv::Size{4,4}, cv::Rect{0,2,4,2}), + std::make_tuple(cv::Size{64,64}, + cv::Size{49,49}, cv::Rect{0, 0,49,49}), + std::make_tuple(cv::Size{64,64}, + cv::Size{49,49}, cv::Rect{0, 0,49,11}), + std::make_tuple(cv::Size{64,64}, + cv::Size{49,49}, cv::Rect{0, 9,49,17}), + std::make_tuple(cv::Size{64,64}, + cv::Size{49,49}, cv::Rect{0,39,49,10})))); + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_fluid_roi_test.cpp b/modules/gapi/test/gapi_fluid_roi_test.cpp new file mode 100644 index 0000000000..ee8674ede2 --- /dev/null +++ b/modules/gapi/test/gapi_fluid_roi_test.cpp @@ -0,0 +1,197 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "gapi_fluid_test_kernels.hpp" + +namespace opencv_test +{ + +using namespace cv::gapi_test_kernels; + +struct PartialComputation : public TestWithParam > {}; +TEST_P(PartialComputation, Test) +{ + cv::Rect roi; + std::tie(roi) = GetParam(); + + int borderType = BORDER_REPLICATE; + int kernelSize = 3; + cv::Point anchor = {-1, -1}; + + cv::GMat in; + cv::GMat out = TBlur3x3::on(in, borderType, {}); + cv::GComputation c(cv::GIn(in), cv::GOut(out)); + + const auto sz = cv::Size(8, 10); + cv::Mat in_mat(sz, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + cv::randn(in_mat, mean, stddev); + + cv::Mat out_mat_gapi = cv::Mat::zeros(sz, CV_8UC1); + cv::Mat out_mat_ocv = cv::Mat::zeros(sz, CV_8UC1); + + // Run G-API + auto cc = c.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage, GFluidOutputRois{{to_own(roi)}})); + cc(cv::gin(in_mat), cv::gout(out_mat_gapi)); + + // Check with OpenCV + if (roi == cv::Rect{}) roi = cv::Rect{0,0,sz.width,sz.height}; + cv::blur(in_mat(roi), out_mat_ocv(roi), {kernelSize, kernelSize}, anchor, borderType); + + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); +} + +INSTANTIATE_TEST_CASE_P(Fluid, PartialComputation, + Values(cv::Rect{}, cv::Rect{0,0,8,6}, cv::Rect{0,1,8,3}, + cv::Rect{0,2,8,3}, cv::Rect{0,3,8,5}, cv::Rect{0,4,8,6})); + +struct PartialComputationAddC : public TestWithParam > {}; +TEST_P(PartialComputationAddC, Test) +{ + cv::Rect roi; + std::tie(roi) = GetParam(); + + cv::GMat in; + cv::GMat out = TAddCSimple::on(in, 1); + cv::GComputation c(cv::GIn(in), cv::GOut(out)); + + const auto sz = cv::Size(8, 10); + cv::Mat in_mat(sz, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + cv::randn(in_mat, mean, stddev); + + cv::Mat out_mat_gapi = cv::Mat::zeros(sz, CV_8UC1); + cv::Mat out_mat_ocv = cv::Mat::zeros(sz, CV_8UC1); + + // Run G-API + auto cc = c.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage, GFluidOutputRois{{to_own(roi)}})); + cc(cv::gin(in_mat), cv::gout(out_mat_gapi)); + + // Check with OpenCV + if (roi == cv::Rect{}) roi = cv::Rect{0,0,sz.width,sz.height}; + out_mat_ocv(roi) = in_mat(roi) + 1; + + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); +} + +INSTANTIATE_TEST_CASE_P(FluidRoi, PartialComputationAddC, + Values(cv::Rect{}, cv::Rect{0,0,8,6}, cv::Rect{0,1,8,3}, + cv::Rect{0,2,8,3}, cv::Rect{0,3,8,5}, cv::Rect{0,4,8,6})); + +struct SequenceOfBlursRoiTest : public TestWithParam > {}; +TEST_P(SequenceOfBlursRoiTest, Test) +{ + cv::Size sz_in = { 320, 240 }; + + int borderType = 0; + cv::Rect roi; + std::tie(borderType, roi) = GetParam(); + cv::Mat in_mat(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat, mean, stddev); + + cv::Point anchor = {-1, -1}; + cv::Scalar borderValue(0); + + GMat in; + auto mid = TBlur3x3::on(in, borderType, borderValue); + auto out = TBlur5x5::on(mid, borderType, borderValue); + + Mat out_mat_gapi = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in), GOut(out)); + auto cc = c.compile(descr_of(in_mat), cv::compile_args(fluidTestPackage, GFluidOutputRois{{to_own(roi)}})); + cc(gin(in_mat), gout(out_mat_gapi)); + + cv::Mat mid_mat_ocv = Mat::zeros(sz_in, CV_8UC1); + cv::Mat out_mat_ocv = Mat::zeros(sz_in, CV_8UC1); + + cv::blur(in_mat, mid_mat_ocv, {3,3}, anchor, borderType); + + if (roi == cv::Rect{}) + { + roi = cv::Rect{0, 0, sz_in.width, sz_in.height}; + } + + cv::blur(mid_mat_ocv(roi), out_mat_ocv(roi), {5,5}, anchor, borderType); + + EXPECT_EQ(0, countNonZero(out_mat_ocv != out_mat_gapi)); +} + +INSTANTIATE_TEST_CASE_P(FluidRoi, SequenceOfBlursRoiTest, + Combine(Values(BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT_101), + Values(cv::Rect{0,0,320,240}, cv::Rect{0,64,320,128}, cv::Rect{0,128,320,112}))); + +struct TwoBlursRoiTest : public TestWithParam > {}; +TEST_P(TwoBlursRoiTest, Test) +{ + cv::Size sz_in = { 320, 240 }; + + int kernelSize1 = 0, kernelSize2 = 0; + int borderType1 = -1, borderType2 = -1; + cv::Scalar borderValue1{}, borderValue2{}; + bool readFromInput = false; + cv::Rect outRoi; + std::tie(kernelSize1, borderType1, borderValue1, kernelSize2, borderType2, borderValue2, readFromInput, outRoi) = GetParam(); + cv::Mat in_mat(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat, mean, stddev); + + cv::Point anchor = {-1, -1}; + + auto blur1 = kernelSize1 == 3 ? &TBlur3x3::on : TBlur5x5::on; + auto blur2 = kernelSize2 == 3 ? &TBlur3x3::on : TBlur5x5::on; + + GMat in, out1, out2; + if (readFromInput) + { + out1 = blur1(in, borderType1, borderValue1); + out2 = blur2(in, borderType2, borderValue2); + } + else + { + auto mid = TAddCSimple::on(in, 0); + out1 = blur1(mid, borderType1, borderValue1); + out2 = blur2(mid, borderType2, borderValue2); + } + + Mat out_mat_gapi1 = Mat::zeros(sz_in, CV_8UC1); + Mat out_mat_gapi2 = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in), GOut(out1, out2)); + auto cc = c.compile(descr_of(in_mat), cv::compile_args(fluidTestPackage, GFluidOutputRois{{outRoi, outRoi}})); + cc(gin(in_mat), gout(out_mat_gapi1, out_mat_gapi2)); + + cv::Mat out_mat_ocv1 = Mat::zeros(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = Mat::zeros(sz_in, CV_8UC1); + + cv::blur(in_mat(outRoi), out_mat_ocv1(outRoi), {kernelSize1, kernelSize1}, anchor, borderType1); + cv::blur(in_mat(outRoi), out_mat_ocv2(outRoi), {kernelSize2, kernelSize2}, anchor, borderType2); + + EXPECT_EQ(0, countNonZero(out_mat_ocv1 != out_mat_gapi1)); + EXPECT_EQ(0, countNonZero(out_mat_ocv2 != out_mat_gapi2)); +} + +INSTANTIATE_TEST_CASE_P(FluidRoi, TwoBlursRoiTest, + Combine(Values(3, 5), + Values(cv::BORDER_CONSTANT, cv::BORDER_REPLICATE, cv::BORDER_REFLECT_101), + Values(0), + Values(3, 5), + Values(cv::BORDER_CONSTANT, cv::BORDER_REPLICATE, cv::BORDER_REFLECT_101), + Values(0), + testing::Bool(), // Read from input directly or place a copy node at start + Values(cv::Rect{0,0,320,240}, cv::Rect{0,64,320,128}, cv::Rect{0,128,320,112}))); + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_fluid_test.cpp b/modules/gapi/test/gapi_fluid_test.cpp new file mode 100644 index 0000000000..0996a8f5a5 --- /dev/null +++ b/modules/gapi/test/gapi_fluid_test.cpp @@ -0,0 +1,710 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "opencv2/gapi/core.hpp" + +#include "opencv2/gapi/fluid/gfluidbuffer.hpp" +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + + // FIXME: move these tests with priv() to internal suite +#include "backends/fluid/gfluidbuffer_priv.hpp" + +#include "gapi_fluid_test_kernels.hpp" +#include "logger.hpp" + +namespace opencv_test +{ + +using namespace cv::gapi_test_kernels; + +namespace +{ + void WriteFunction(uint8_t* row, int nr, int w) { + for (int i = 0; i < w; i++) + row[i] = static_cast(nr+i); + }; + void ReadFunction1x1(const uint8_t* row, int w) { + for (int i = 0; i < w; i++) + std::cout << std::setw(4) << static_cast(row[i]) << " "; + std::cout << "\n"; + }; + void ReadFunction3x3(const uint8_t* rows[3], int w) { + for (int i = 0; i < 3; i++) { + for (int j = -1; j < w+1; j++) { + std::cout << std::setw(4) << static_cast(rows[i][j]) << " "; + } + std::cout << "\n"; + } + std::cout << "\n"; + }; +} + +TEST(FluidBuffer, InputTest) +{ + const cv::Size buffer_size = {8,8}; + cv::Mat in_mat = cv::Mat::eye(buffer_size, CV_8U); + + cv::gapi::fluid::Buffer buffer(in_mat, true); + cv::gapi::fluid::View view = buffer.mkView(1, 0, {}, false); + view.priv().reset(1); + int this_y = 0; + + while (this_y < buffer_size.height) + { + const uint8_t* rrow = view.InLine(0); + ReadFunction1x1(rrow, buffer_size.width); + view.priv().readDone(1,1); + + cv::Mat from_buffer(1, buffer_size.width, CV_8U, const_cast(rrow)); + EXPECT_EQ(0, cv::countNonZero(in_mat.row(this_y) != from_buffer)); + + this_y++; + } +} + +TEST(FluidBuffer, CircularTest) +{ + const cv::Size buffer_size = {8,16}; + + cv::gapi::fluid::Buffer buffer(cv::GMatDesc{CV_8U,1,buffer_size}, 3, 1, 0, 1, + util::make_optional(cv::gapi::fluid::Border{cv::BORDER_CONSTANT, cv::gapi::own::Scalar(255)})); + cv::gapi::fluid::View view = buffer.mkView(3, 1, {}, false); + view.priv().reset(3); + buffer.debug(std::cout); + + const auto whole_line_is = [](const uint8_t *line, int len, int value) + { + return std::all_of(line, line+len, [&](const uint8_t v){return v == value;}); + }; + + // Store all read/written data in separate Mats to compare with + cv::Mat written_data(buffer_size, CV_8U); + + // Simulate write/read process + int num_reads = 0, num_writes = 0; + while (num_reads < buffer_size.height) + { + if (num_writes < buffer_size.height) + { + uint8_t* wrow = buffer.OutLine(); + WriteFunction(wrow, num_writes, buffer_size.width); + buffer.priv().writeDone(); + + cv::Mat(1, buffer_size.width, CV_8U, wrow) + .copyTo(written_data.row(num_writes)); + num_writes++; + } + buffer.debug(std::cout); + + if (view.ready()) + { + view.priv().prepareToRead(); + const uint8_t* rrow[3] = { + view.InLine(-1), + view.InLine( 0), + view.InLine( 1), + }; + ReadFunction3x3(rrow, buffer_size.width); + view.priv().readDone(1,3); + buffer.debug(std::cout); + + // Check borders right here + EXPECT_EQ(255u, rrow[0][-1]); + EXPECT_EQ(255u, rrow[0][buffer_size.width]); + if (num_reads == 0) + { + EXPECT_TRUE(whole_line_is(rrow[0]-1, buffer_size.width+2, 255u)); + } + if (num_reads == buffer_size.height-1) + { + EXPECT_TRUE(whole_line_is(rrow[2]-1, buffer_size.width+2, 255u)); + } + + // Check window (without borders) + if (num_reads > 0 && num_reads < buffer_size.height-1) + { + // +1 everywhere since num_writes was just incremented above + cv::Mat written_lastLine2 = written_data.row(num_writes - (2+1)); + cv::Mat written_lastLine1 = written_data.row(num_writes - (1+1)); + cv::Mat written_lastLine0 = written_data.row(num_writes - (0+1)); + + cv::Mat read_prevLine(1, buffer_size.width, CV_8U, const_cast(rrow[0])); + cv::Mat read_thisLine(1, buffer_size.width, CV_8U, const_cast(rrow[1])); + cv::Mat read_nextLine(1, buffer_size.width, CV_8U, const_cast(rrow[2])); + + EXPECT_EQ(0, cv::countNonZero(written_lastLine2 != read_prevLine)); + EXPECT_EQ(0, cv::countNonZero(written_lastLine1 != read_thisLine)); + EXPECT_EQ(0, cv::countNonZero(written_lastLine0 != read_nextLine)); + } + num_reads++; + } + } +} + +TEST(FluidBuffer, OutputTest) +{ + const cv::Size buffer_size = {8,16}; + cv::Mat out_mat = cv::Mat(buffer_size, CV_8U); + + cv::gapi::fluid::Buffer buffer(out_mat, false); + int num_writes = 0; + while (num_writes < buffer_size.height) + { + uint8_t* wrow = buffer.OutLine(); + WriteFunction(wrow, num_writes, buffer_size.width); + buffer.priv().writeDone(); + num_writes++; + } + + GAPI_LOG_INFO(NULL, "\n" << out_mat); + + // Validity check + for (int r = 0; r < buffer_size.height; r++) + { + for (int c = 0; c < buffer_size.width; c++) + { + EXPECT_EQ(r+c, out_mat.at(r, c)); + } + } +} + +TEST(Fluid, AddC_WithScalar) +{ + cv::GMat in; + cv::GScalar s; + + cv::GComputation c(cv::GIn(in, s), cv::GOut(TAddScalar::on(in, s))); + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1), out_mat(3, 3, CV_8UC1), ref_mat; + cv::Scalar in_s(100); + + auto cc = c.compile(cv::descr_of(in_mat), cv::descr_of(in_s), cv::compile_args(fluidTestPackage)); + + cc(cv::gin(in_mat, in_s), cv::gout(out_mat)); + ref_mat = in_mat + in_s; + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(Fluid, Scalar_In_Middle_Graph) +{ + cv::GMat in; + cv::GScalar s; + + cv::GComputation c(cv::GIn(in, s), cv::GOut(TAddScalar::on(TAddCSimple::on(in, 5), s))); + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1), out_mat(3, 3, CV_8UC1), ref_mat; + cv::Scalar in_s(100); + + auto cc = c.compile(cv::descr_of(in_mat), cv::descr_of(in_s), cv::compile_args(fluidTestPackage)); + + cc(cv::gin(in_mat, in_s), cv::gout(out_mat)); + ref_mat = (in_mat + 5) + in_s; + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(Fluid, Add_Scalar_To_Mat) +{ + cv::GMat in; + cv::GScalar s; + + cv::GComputation c(cv::GIn(s, in), cv::GOut(TAddScalarToMat::on(s, in))); + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1), out_mat(3, 3, CV_8UC1), ref_mat; + cv::Scalar in_s(100); + + auto cc = c.compile(cv::descr_of(in_s), cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + + cc(cv::gin(in_s, in_mat), cv::gout(out_mat)); + ref_mat = in_mat + in_s; + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(Fluid, Sum_2_Mats_And_Scalar) +{ + cv::GMat a, b; + cv::GScalar s; + + cv::GComputation c(cv::GIn(a, s, b), cv::GOut(TSum2MatsAndScalar::on(a, s, b))); + cv::Mat in_mat1 = cv::Mat::eye(3, 3, CV_8UC1), + in_mat2 = cv::Mat::eye(3, 3, CV_8UC1), + out_mat(3, 3, CV_8UC1), + ref_mat; + cv::Scalar in_s(100); + + auto cc = c.compile(cv::descr_of(in_mat1), cv::descr_of(in_s), cv::descr_of(in_mat2), cv::compile_args(fluidTestPackage)); + + cc(cv::gin(in_mat1, in_s, in_mat2), cv::gout(out_mat)); + ref_mat = in_mat1 + in_mat2 + in_s; + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(Fluid, Split3) +{ + cv::GMat bgr; + cv::GMat r,g,b; + std::tie(b,g,r) = cv::gapi::split3(bgr); + auto rr = TAddSimple::on(r, TId::on(b)); + auto rrr = TAddSimple::on(TId::on(rr), g); + cv::GComputation c(bgr, TId::on(rrr)); + + cv::Size sz(5120, 5120); + cv::Mat eye_1 = cv::Mat::eye(sz, CV_8UC1); + std::vector eyes = {eye_1, eye_1, eye_1}; + cv::Mat in_mat; + cv::merge(eyes, in_mat); + cv::Mat out_mat(sz, CV_8UC1); + + // G-API + auto cc = c.compile(cv::descr_of(in_mat), + cv::compile_args(fluidTestPackage)); + cc(in_mat, out_mat); + + // OCV + std::vector chans; + cv::split(in_mat, chans); + + // Compare + EXPECT_EQ(0, cv::countNonZero(out_mat != (chans[2]*3))); +} + +TEST(Fluid, ScratchTest) +{ + cv::GMat in; + cv::GMat out = TPlusRow0::on(TPlusRow0::on(in)); + cv::GComputation c(in, out); + + cv::Size sz(8, 8); + cv::Mat in_mat = cv::Mat::eye(sz, CV_8UC1); + cv::Mat out_mat(sz, CV_8UC1); + + // OpenCV (reference) + cv::Mat ref; + { + cv::Mat first_row = cv::Mat::zeros(1, sz.width, CV_8U); + cv::Mat remaining = cv::repeat(in_mat.row(0), sz.height-1, 1); + cv::Mat operand; + cv::vconcat(first_row, 2*remaining, operand); + ref = in_mat + operand; + } + GAPI_LOG_INFO(NULL, "\n" << ref); + + // G-API + auto cc = c.compile(cv::descr_of(in_mat), + cv::compile_args(fluidTestPackage)); + cc(in_mat, out_mat); + GAPI_LOG_INFO(NULL, "\n" << out_mat); + EXPECT_EQ(0, cv::countNonZero(ref != out_mat)); + + cc(in_mat, out_mat); + GAPI_LOG_INFO(NULL, "\n" << out_mat); + EXPECT_EQ(0, cv::countNonZero(ref != out_mat)); +} + +TEST(Fluid, MultipleOutRowsTest) +{ + cv::GMat in; + cv::GMat out = TAddCSimple::on(TAddCSimple::on(in, 1), 2); + cv::GComputation c(in, out); + + cv::Size sz(4, 4); + cv::Mat in_mat = cv::Mat::eye(sz, CV_8UC1); + cv::Mat out_mat(sz, CV_8UC1); + + auto cc = c.compile(cv::descr_of(in_mat), + cv::compile_args(fluidTestPackage)); + cc(in_mat, out_mat); + + std::cout << out_mat << std::endl; + + cv::Mat ocv_ref = in_mat + 1 + 2; + EXPECT_EQ(0, cv::countNonZero(ocv_ref != out_mat)); +} + + +TEST(Fluid, LPIWindow) +{ + cv::GMat in; + cv::GMat r,g,b; + std::tie(r,g,b) = cv::gapi::split3(in); + cv::GMat rr = TId7x7::on(r); + cv::GMat tmp = TAddSimple::on(rr, g); + cv::GMat out = TAddSimple::on(tmp, b); + + cv::GComputation c(in, out); + + cv::Size sz(8, 8); + + cv::Mat eye_1 = cv::Mat::eye(sz, CV_8UC1); + std::vector eyes = {eye_1, eye_1, eye_1}; + cv::Mat in_mat; + cv::merge(eyes, in_mat); + + cv::Mat out_mat(sz, CV_8U); + auto cc = c.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(in_mat, out_mat); + + //std::cout << out_mat << std::endl; + + // OpenCV reference + cv::Mat ocv_ref = eyes[0]+eyes[1]+eyes[2]; + + EXPECT_EQ(0, cv::countNonZero(ocv_ref != out_mat)); +} + +TEST(Fluid, MultipleReaders_SameLatency) +{ + // in -> AddC -> a -> AddC -> b -> Add -> out + // '--> AddC -> c -' + // + // b and c have the same skew + + cv::GMat in; + cv::GMat a = TAddCSimple::on(in, 1); // FIXME - align naming (G, non-G) + cv::GMat b = TAddCSimple::on(a, 2); + cv::GMat c = TAddCSimple::on(a, 3); + cv::GMat out = TAddSimple::on(b, c); + cv::GComputation comp(in, out); + + const auto sz = cv::Size(32, 32); + cv::Mat in_mat = cv::Mat::eye(sz, CV_8UC1); + cv::Mat out_mat_gapi(sz, CV_8UC1); + cv::Mat out_mat_ocv (sz, CV_8UC1); + + // Run G-API + auto cc = comp.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(in_mat, out_mat_gapi); + + // Check with OpenCV + cv::Mat tmp = in_mat + 1; + out_mat_ocv = (tmp+2) + (tmp+3); + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); +} + +TEST(Fluid, MultipleReaders_DifferentLatency) +{ + // in1 -> AddC -> a -> AddC -------------> b -> Add -> out + // '--------------> Add --> c -' + // '--> Id7x7-> d -' + // + // b and c have different skew (due to latency introduced by Id7x7) + // a is ready by multiple views with different latency. + + cv::GMat in; + cv::GMat a = TAddCSimple::on(in, 1); // FIXME - align naming (G, non-G) + cv::GMat b = TAddCSimple::on(a, 2); + cv::GMat d = TId7x7::on(a); + cv::GMat c = TAddSimple::on(a, d); + cv::GMat out = TAddSimple::on(b, c); + cv::GComputation comp(in, out); + + const auto sz = cv::Size(32, 32); + cv::Mat in_mat = cv::Mat::eye(sz, CV_8UC1); + cv::Mat out_mat_gapi(sz, CV_8UC1); + + // Run G-API + auto cc = comp.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(in_mat, out_mat_gapi); + + // Check with OpenCV + cv::Mat ocv_a = in_mat + 1; + cv::Mat ocv_b = ocv_a + 2; + cv::Mat ocv_d = ocv_a; + cv::Mat ocv_c = ocv_a + ocv_d; + cv::Mat out_mat_ocv = ocv_b + ocv_c; + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); +} + +TEST(Fluid, DISABLED_MultipleOutputs) +{ + // in -> AddC -> a -> AddC ------------------> out1 + // `--> Id7x7 --> b --> AddC -> out2 + + cv::GMat in; + cv::GMat a = TAddCSimple::on(in, 1); + cv::GMat b = TId7x7::on(a); + cv::GMat out1 = TAddCSimple::on(a, 2); + cv::GMat out2 = TAddCSimple::on(b, 7); + cv::GComputation comp(cv::GIn(in), cv::GOut(out1, out2)); + + const auto sz = cv::Size(32, 32); + cv::Mat in_mat = cv::Mat::eye(sz, CV_8UC1); + cv::Mat out_mat_gapi1(sz, CV_8UC1), out_mat_gapi2(sz, CV_8UC1); + cv::Mat out_mat_ocv1(sz, CV_8UC1), out_mat_ocv2(sz, CV_8UC1); + + // Run G-API + auto cc = comp.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(cv::gin(in_mat), cv::gout(out_mat_gapi1, out_mat_gapi2)); + + // Check with OpenCV + out_mat_ocv1 = in_mat + 1 + 2; + out_mat_ocv2 = in_mat + 1 + 7; + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi1 != out_mat_ocv1)); + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi2 != out_mat_ocv2)); +} + +TEST(Fluid, EmptyOutputMatTest) +{ + cv::GMat in; + cv::GMat out = TAddCSimple::on(in, 2); + cv::GComputation c(in, out); + + cv::Mat in_mat = cv::Mat::eye(cv::Size(32, 24), CV_8UC1); + cv::Mat out_mat; + + auto cc = c.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + + cc(in_mat, out_mat); + EXPECT_EQ(CV_8UC1, out_mat.type()); + EXPECT_EQ(32, out_mat.cols); + EXPECT_EQ(24, out_mat.rows); + EXPECT_TRUE(out_mat.ptr() != nullptr); +} + +struct LPISequenceTest : public TestWithParam{}; +TEST_P(LPISequenceTest, DISABLED_LPISequenceTest) +{ + // in -> AddC -> a -> Blur (2lpi) -> out + + int kernelSize = GetParam(); + cv::GMat in; + cv::GMat a = TAddCSimple::on(in, 1); + auto blur = kernelSize == 3 ? &TBlur3x3_2lpi::on : &TBlur5x5_2lpi::on; + cv::GMat out = blur(a, cv::BORDER_CONSTANT, cv::Scalar(0)); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + + const auto sz = cv::Size(8, 10); + cv::Mat in_mat = cv::Mat::eye(sz, CV_8UC1); + cv::Mat out_mat_gapi(sz, CV_8UC1); + cv::Mat out_mat_ocv(sz, CV_8UC1); + + // Run G-API + auto cc = comp.compile(cv::descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(cv::gin(in_mat), cv::gout(out_mat_gapi)); + + // Check with OpenCV + cv::blur(in_mat + 1, out_mat_ocv, {kernelSize,kernelSize}, {-1,-1}, cv::BORDER_CONSTANT); + EXPECT_EQ(0, cv::countNonZero(out_mat_gapi != out_mat_ocv)); +} + +INSTANTIATE_TEST_CASE_P(Fluid, LPISequenceTest, + Values(3, 5)); + +struct InputImageBorderTest : public TestWithParam > {}; +TEST_P(InputImageBorderTest, InputImageBorderTest) +{ + cv::Size sz_in = { 320, 240 }; + + int ks = 0; + int borderType = 0; + std::tie(ks, borderType) = GetParam(); + cv::Mat in_mat1(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat1, mean, stddev); + + cv::Size kernelSize = {ks, ks}; + cv::Point anchor = {-1, -1}; + cv::Scalar borderValue(0); + + auto gblur = ks == 3 ? &TBlur3x3::on : &TBlur5x5::on; + + GMat in; + auto out = gblur(in, borderType, borderValue); + + Mat out_mat_gapi = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in), GOut(out)); + auto cc = c.compile(descr_of(in_mat1), cv::compile_args(fluidTestPackage)); + cc(gin(in_mat1), gout(out_mat_gapi)); + + cv::Mat out_mat_ocv = Mat::zeros(sz_in, CV_8UC1); + cv::blur(in_mat1, out_mat_ocv, kernelSize, anchor, borderType); + + EXPECT_EQ(0, countNonZero(out_mat_ocv != out_mat_gapi)); +} + +INSTANTIATE_TEST_CASE_P(Fluid, InputImageBorderTest, + Combine(Values(3, 5), + Values(BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT_101))); + +struct SequenceOfBlursTest : public TestWithParam > {}; +TEST_P(SequenceOfBlursTest, Test) +{ + cv::Size sz_in = { 320, 240 }; + + int borderType = 0;; + std::tie(borderType) = GetParam(); + cv::Mat in_mat(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat, mean, stddev); + + cv::Point anchor = {-1, -1}; + cv::Scalar borderValue(0); + + GMat in; + auto mid = TBlur3x3::on(in, borderType, borderValue); + auto out = TBlur5x5::on(mid, borderType, borderValue); + + Mat out_mat_gapi = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in), GOut(out)); + auto cc = c.compile(descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(gin(in_mat), gout(out_mat_gapi)); + + cv::Mat mid_mat_ocv = Mat::zeros(sz_in, CV_8UC1); + cv::Mat out_mat_ocv = Mat::zeros(sz_in, CV_8UC1); + cv::blur(in_mat, mid_mat_ocv, {3,3}, anchor, borderType); + cv::blur(mid_mat_ocv, out_mat_ocv, {5,5}, anchor, borderType); + + EXPECT_EQ(0, countNonZero(out_mat_ocv != out_mat_gapi)); +} + +INSTANTIATE_TEST_CASE_P(Fluid, SequenceOfBlursTest, + Values(BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT_101)); + +struct TwoBlursTest : public TestWithParam > {}; +TEST_P(TwoBlursTest, Test) +{ + cv::Size sz_in = { 320, 240 }; + + int kernelSize1 = 0, kernelSize2 = 0; + int borderType1 = -1, borderType2 = -1; + cv::Scalar borderValue1{}, borderValue2{}; + bool readFromInput = false; + std::tie(kernelSize1, borderType1, borderValue1, kernelSize2, borderType2, borderValue2, readFromInput) = GetParam(); + cv::Mat in_mat(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat, mean, stddev); + + cv::Point anchor = {-1, -1}; + + auto blur1 = kernelSize1 == 3 ? &TBlur3x3::on : TBlur5x5::on; + auto blur2 = kernelSize2 == 3 ? &TBlur3x3::on : TBlur5x5::on; + + GMat in, out1, out2; + if (readFromInput) + { + out1 = blur1(in, borderType1, borderValue1); + out2 = blur2(in, borderType2, borderValue2); + } + else + { + auto mid = TAddCSimple::on(in, 0); + out1 = blur1(mid, borderType1, borderValue1); + out2 = blur2(mid, borderType2, borderValue2); + } + + Mat out_mat_gapi1 = Mat::zeros(sz_in, CV_8UC1); + Mat out_mat_gapi2 = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in), GOut(out1, out2)); + auto cc = c.compile(descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(gin(in_mat), gout(out_mat_gapi1, out_mat_gapi2)); + + cv::Mat out_mat_ocv1 = Mat::zeros(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = Mat::zeros(sz_in, CV_8UC1); + cv::blur(in_mat, out_mat_ocv1, {kernelSize1, kernelSize1}, anchor, borderType1); + cv::blur(in_mat, out_mat_ocv2, {kernelSize2, kernelSize2}, anchor, borderType2); + + EXPECT_EQ(0, countNonZero(out_mat_ocv1 != out_mat_gapi1)); + EXPECT_EQ(0, countNonZero(out_mat_ocv2 != out_mat_gapi2)); +} + +INSTANTIATE_TEST_CASE_P(Fluid, TwoBlursTest, + Combine(Values(3, 5), + Values(cv::BORDER_CONSTANT, cv::BORDER_REPLICATE, cv::BORDER_REFLECT_101), + Values(0), + Values(3, 5), + Values(cv::BORDER_CONSTANT, cv::BORDER_REPLICATE, cv::BORDER_REFLECT_101), + Values(0), + testing::Bool())); // Read from input directly or place a copy node at start + +struct TwoReadersTest : public TestWithParam > {}; +TEST_P(TwoReadersTest, Test) +{ + cv::Size sz_in = { 320, 240 }; + + int kernelSize = 0; + int borderType = -1; + cv::Scalar borderValue; + bool readFromInput = false; + std::tie(kernelSize, borderType, borderValue, readFromInput) = GetParam(); + cv::Mat in_mat(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat, mean, stddev); + + cv::Point anchor = {-1, -1}; + + auto blur = kernelSize == 3 ? &TBlur3x3::on : TBlur5x5::on; + + GMat in, out1, out2; + if (readFromInput) + { + out1 = TAddCSimple::on(in, 0); + out2 = blur(in, borderType, borderValue); + } + else + { + auto mid = TAddCSimple::on(in, 0); + out1 = TAddCSimple::on(mid, 0); + out2 = blur(mid, borderType, borderValue); + } + + Mat out_mat_gapi1 = Mat::zeros(sz_in, CV_8UC1); + Mat out_mat_gapi2 = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in), GOut(out1, out2)); + auto cc = c.compile(descr_of(in_mat), cv::compile_args(fluidTestPackage)); + cc(gin(in_mat), gout(out_mat_gapi1, out_mat_gapi2)); + + cv::Mat out_mat_ocv1 = Mat::zeros(sz_in, CV_8UC1); + cv::Mat out_mat_ocv2 = Mat::zeros(sz_in, CV_8UC1); + out_mat_ocv1 = in_mat; + cv::blur(in_mat, out_mat_ocv2, {kernelSize, kernelSize}, anchor, borderType); + + EXPECT_EQ(0, countNonZero(out_mat_ocv1 != out_mat_gapi1)); + EXPECT_EQ(0, countNonZero(out_mat_ocv2 != out_mat_gapi2)); +} + +INSTANTIATE_TEST_CASE_P(Fluid, TwoReadersTest, + Combine(Values(3, 5), + Values(cv::BORDER_CONSTANT, cv::BORDER_REPLICATE, cv::BORDER_REFLECT_101), + Values(0), + testing::Bool())); // Read from input directly or place a copy node at start + +TEST(FluidTwoIslands, SanityTest) +{ + cv::Size sz_in{8,8}; + + GMat in1, in2; + auto out1 = TAddScalar::on(in1, {0}); + auto out2 = TAddScalar::on(in2, {0}); + + cv::Mat in_mat1(sz_in, CV_8UC1); + cv::Mat in_mat2(sz_in, CV_8UC1); + cv::Scalar mean = cv::Scalar(127.0f); + cv::Scalar stddev = cv::Scalar(40.f); + + cv::randn(in_mat1, mean, stddev); + cv::randn(in_mat2, mean, stddev); + + Mat out_mat1 = Mat::zeros(sz_in, CV_8UC1); + Mat out_mat2 = Mat::zeros(sz_in, CV_8UC1); + + GComputation c(GIn(in1, in2), GOut(out1, out2)); + EXPECT_NO_THROW(c.apply(gin(in_mat1, in_mat2), gout(out_mat1, out_mat2), cv::compile_args(fluidTestPackage))); + EXPECT_EQ(0, countNonZero(in_mat1 != out_mat1)); + EXPECT_EQ(0, countNonZero(in_mat2 != out_mat2)); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_fluid_test_kernels.cpp b/modules/gapi/test/gapi_fluid_test_kernels.cpp new file mode 100644 index 0000000000..058d9f3f96 --- /dev/null +++ b/modules/gapi/test/gapi_fluid_test_kernels.cpp @@ -0,0 +1,434 @@ +// 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) 2018 Intel Corporation + +#include +#include "gapi_fluid_test_kernels.hpp" +#include + +namespace cv +{ +namespace gapi_test_kernels +{ + +GAPI_FLUID_KERNEL(FAddSimple, TAddSimple, false) +{ + static const int Window = 1; + + static void run(const cv::gapi::fluid::View &a, + const cv::gapi::fluid::View &b, + cv::gapi::fluid::Buffer &o) + { + // std::cout << "AddSimple {{{\n"; + // std::cout << " a - "; a.debug(std::cout); + // std::cout << " b - "; b.debug(std::cout); + // std::cout << " o - "; o.debug(std::cout); + + const uint8_t* in1 = a.InLine(0); + const uint8_t* in2 = b.InLine(0); + uint8_t* out = o.OutLine(); + + // std::cout << "a: "; + // for (int i = 0, w = a.length(); i < w; i++) + // { + // std::cout << std::setw(4) << int(in1[i]); + // } + // std::cout << "\n"; + + // std::cout << "b: "; + // for (int i = 0, w = a.length(); i < w; i++) + // { + // std::cout << std::setw(4) << int(in2[i]); + // } + // std::cout << "\n"; + + for (int i = 0, w = a.length(); i < w; i++) + { + out[i] = in1[i] + in2[i]; + } + + // std::cout << "}}} " << std::endl;; + } +}; + +GAPI_FLUID_KERNEL(FAddCSimple, TAddCSimple, false) +{ + static const int Window = 1; + static const int LPI = 2; + + static void run(const cv::gapi::fluid::View &in, + const int cval, + cv::gapi::fluid::Buffer &out) + { + for (int l = 0, lpi = out.lpi(); l < lpi; l++) + { + const uint8_t* in_row = in .InLine (l); + uint8_t* out_row = out.OutLine(l); + //std::cout << "l=" << l << ": "; + for (int i = 0, w = in.length(); i < w; i++) + { + //std::cout << std::setw(4) << int(in_row[i]); + out_row[i] = static_cast(in_row[i] + cval); + } + //std::cout << std::endl; + } + } +}; + +GAPI_FLUID_KERNEL(FAddScalar, TAddScalar, false) +{ + static const int Window = 1; + static const int LPI = 2; + + static void run(const cv::gapi::fluid::View &in, + const cv::Scalar &cval, + cv::gapi::fluid::Buffer &out) + { + for (int l = 0, lpi = out.lpi(); l < lpi; l++) + { + const uint8_t* in_row = in .InLine (l); + uint8_t* out_row = out.OutLine(l); + std::cout << "l=" << l << ": "; + for (int i = 0, w = in.length(); i < w; i++) + { + std::cout << std::setw(4) << int(in_row[i]); + out_row[i] = static_cast(in_row[i] + cval[0]); + } + std::cout << std::endl; + } + } +}; + +GAPI_FLUID_KERNEL(FAddScalarToMat, TAddScalarToMat, false) +{ + static const int Window = 1; + static const int LPI = 2; + + static void run(const cv::Scalar &cval, + const cv::gapi::fluid::View &in, + cv::gapi::fluid::Buffer &out) + { + for (int l = 0, lpi = out.lpi(); l < lpi; l++) + { + const uint8_t* in_row = in .InLine (l); + uint8_t* out_row = out.OutLine(l); + std::cout << "l=" << l << ": "; + for (int i = 0, w = in.length(); i < w; i++) + { + std::cout << std::setw(4) << int(in_row[i]); + out_row[i] = static_cast(in_row[i] + cval[0]); + } + std::cout << std::endl; + } + } +}; + +template +static void runBlur(const cv::gapi::fluid::View& src, cv::gapi::fluid::Buffer& dst) +{ + const auto borderSize = (kernelSize - 1) / 2; + const unsigned char* ins[kernelSize]; + + for (int l = 0; l < lpi; l++) + { + for (int i = 0; i < kernelSize; i++) + { + ins[i] = src.InLine(i - borderSize + l); + } + + auto out = dst.OutLine(l); + const auto width = dst.length(); + + for (int w = 0; w < width; w++) + { + float res = 0.0f; + for (int i = 0; i < kernelSize; i++) + { + for (int j = -borderSize; j < borderSize + 1; j++) + { + res += ins[i][w+j]; + } + } + out[w] = static_cast(std::rint(res / (kernelSize * kernelSize))); + } + } +} + +GAPI_FLUID_KERNEL(FBlur1x1, TBlur1x1, false) +{ + static const int Window = 1; + + static void run(const cv::gapi::fluid::View &src, int /*borderType*/, + cv::Scalar /*borderValue*/, cv::gapi::fluid::Buffer &dst) + { + runBlur(src, dst); + } +}; + +GAPI_FLUID_KERNEL(FBlur3x3, TBlur3x3, false) +{ + static const int Window = 3; + + static void run(const cv::gapi::fluid::View &src, int /*borderType*/, + cv::Scalar /*borderValue*/, cv::gapi::fluid::Buffer &dst) + { + runBlur(src, dst); + } + + static cv::gapi::fluid::Border getBorder(const cv::GMatDesc &/*src*/, int borderType, cv::Scalar borderValue) + { + return { borderType, to_own(borderValue)}; + } +}; + +GAPI_FLUID_KERNEL(FBlur5x5, TBlur5x5, false) +{ + static const int Window = 5; + + static void run(const cv::gapi::fluid::View &src, int /*borderType*/, + cv::Scalar /*borderValue*/, cv::gapi::fluid::Buffer &dst) + { + runBlur(src, dst); + } + + static cv::gapi::fluid::Border getBorder(const cv::GMatDesc &/*src*/, int borderType, cv::Scalar borderValue) + { + return { borderType, to_own(borderValue)}; + } +}; + +GAPI_FLUID_KERNEL(FBlur3x3_2lpi, TBlur3x3_2lpi, false) +{ + static const int Window = 3; + static const int LPI = 2; + + static void run(const cv::gapi::fluid::View &src, int /*borderType*/, + cv::Scalar /*borderValue*/, cv::gapi::fluid::Buffer &dst) + { + runBlur(src, dst); + } + + static cv::gapi::fluid::Border getBorder(const cv::GMatDesc &/*src*/, int borderType, cv::Scalar borderValue) + { + return { borderType, to_own(borderValue)}; + } +}; + +GAPI_FLUID_KERNEL(FBlur5x5_2lpi, TBlur5x5_2lpi, false) +{ + static const int Window = 5; + static const int LPI = 2; + + static void run(const cv::gapi::fluid::View &src, int /*borderType*/, + cv::Scalar /*borderValue*/, cv::gapi::fluid::Buffer &dst) + { + runBlur(src, dst); + } + + static cv::gapi::fluid::Border getBorder(const cv::GMatDesc &/*src*/, int borderType, cv::Scalar borderValue) + { + return { borderType, to_own(borderValue )}; + } +}; + +GAPI_FLUID_KERNEL(FIdentity, TId, false) +{ + static const int Window = 3; + + static void run(const cv::gapi::fluid::View &a, + cv::gapi::fluid::Buffer &o) + { + const uint8_t* in[3] = { + a.InLine(-1), + a.InLine( 0), + a.InLine(+1) + }; + uint8_t* out = o.OutLine(); + + // ReadFunction3x3(in, a.length()); + for (int i = 0, w = a.length(); i < w; i++) + { + out[i] = in[1][i]; + } + } + + static gapi::fluid::Border getBorder(const cv::GMatDesc &) + { + return { cv::BORDER_REPLICATE, cv::gapi::own::Scalar{} }; + } +}; + +GAPI_FLUID_KERNEL(FId7x7, TId7x7, false) +{ + static const int Window = 7; + static const int LPI = 2; + + static void run(const cv::gapi::fluid::View &a, + cv::gapi::fluid::Buffer &o) + { + for (int l = 0, lpi = o.lpi(); l < lpi; l++) + { + const uint8_t* in[Window] = { + a.InLine(-3 + l), + a.InLine(-2 + l), + a.InLine(-1 + l), + a.InLine( 0 + l), + a.InLine(+1 + l), + a.InLine(+2 + l), + a.InLine(+3 + l), + }; + uint8_t* out = o.OutLine(l); + + // std::cout << "Id7x7 " << l << " of " << lpi << " {{{\n"; + // std::cout << " a - "; a.debug(std::cout); + // std::cout << " o - "; o.debug(std::cout); + // std::cout << "}}} " << std::endl;; + + // // std::cout << "Id7x7 at " << a.y() << "/L" << l << " {{{" << std::endl; + // for (int j = 0; j < Window; j++) + // { + // // std::cout << std::setw(2) << j-(Window-1)/2 << ": "; + // for (int i = 0, w = a.length(); i < w; i++) + // std::cout << std::setw(4) << int(in[j][i]); + // std::cout << std::endl; + // } + // std::cout << "}}}" << std::endl; + + for (int i = 0, w = a.length(); i < w; i++) + out[i] = in[(Window-1)/2][i]; + } + } + + static cv::gapi::fluid::Border getBorder(const cv::GMatDesc&/* src*/) + { + return { cv::BORDER_REPLICATE, cv::gapi::own::Scalar{} }; + } +}; + +GAPI_FLUID_KERNEL(FPlusRow0, TPlusRow0, true) +{ + static const int Window = 1; + + static void initScratch(const cv::GMatDesc &in, + cv::gapi::fluid::Buffer &scratch) + { + cv::Size scratch_size{in.size.width, 1}; + cv::gapi::fluid::Buffer buffer(in.withSize(scratch_size)); + scratch = std::move(buffer); + } + + static void resetScratch(cv::gapi::fluid::Buffer &scratch) + { + // FIXME: only 1 line can be used! + uint8_t* out_row = scratch.OutLine(); + for (int i = 0, w = scratch.length(); i < w; i++) + { + out_row[i] = 0; + } + } + + static void run(const cv::gapi::fluid::View &in, + cv::gapi::fluid::Buffer &out, + cv::gapi::fluid::Buffer &scratch) + { + const uint8_t* in_row = in .InLine (0); + uint8_t* out_row = out .OutLine(); + uint8_t* tmp_row = scratch.OutLine(); + + if (in.y() == 0) + { + // Copy 1st row to scratch buffer + for (int i = 0, w = in.length(); i < w; i++) + { + out_row[i] = in_row[i]; + tmp_row[i] = in_row[i]; + } + } + else + { + // Output is 1st row + in + for (int i = 0, w = in.length(); i < w; i++) + { + out_row[i] = in_row[i] + tmp_row[i]; + } + } + } +}; + +GAPI_FLUID_KERNEL(FTestSplit3, cv::gapi::core::GSplit3, false) +{ + static const int Window = 1; + + static void run(const cv::gapi::fluid::View &in, + cv::gapi::fluid::Buffer &o1, + cv::gapi::fluid::Buffer &o2, + cv::gapi::fluid::Buffer &o3) + { + // std::cout << "Split3 {{{\n"; + // std::cout << " a - "; in.debug(std::cout); + // std::cout << " 1 - "; o1.debug(std::cout); + // std::cout << " 2 - "; o2.debug(std::cout); + // std::cout << " 3 - "; o3.debug(std::cout); + // std::cout << "}}} " << std::endl;; + + const uint8_t* in_rgb = in.InLine(0); + uint8_t* out_r = o1.OutLine(); + uint8_t* out_g = o2.OutLine(); + uint8_t* out_b = o3.OutLine(); + + for (int i = 0, w = in.length(); i < w; i++) + { + out_r[i] = in_rgb[3*i]; + out_g[i] = in_rgb[3*i+1]; + out_b[i] = in_rgb[3*i+2]; + } + } +}; + +GAPI_FLUID_KERNEL(FSum2MatsAndScalar, TSum2MatsAndScalar, false) +{ + static const int Window = 1; + static const int LPI = 2; + + static void run(const cv::gapi::fluid::View &a, + const cv::Scalar &cval, + const cv::gapi::fluid::View &b, + cv::gapi::fluid::Buffer &out) + { + for (int l = 0, lpi = out.lpi(); l < lpi; l++) + { + const uint8_t* in_row1 = a .InLine (l); + const uint8_t* in_row2 = b .InLine (l); + uint8_t* out_row = out.OutLine(l); + std::cout << "l=" << l << ": "; + for (int i = 0, w = a.length(); i < w; i++) + { + std::cout << std::setw(4) << int(in_row1[i]); + std::cout << std::setw(4) << int(in_row2[i]); + out_row[i] = static_cast(in_row1[i] + in_row2[i] + cval[0]); + } + std::cout << std::endl; + } + } +}; + +cv::gapi::GKernelPackage fluidTestPackage = cv::gapi::kernels + (); +} // namespace gapi_test_kernels +} // namespace cv diff --git a/modules/gapi/test/gapi_fluid_test_kernels.hpp b/modules/gapi/test/gapi_fluid_test_kernels.hpp new file mode 100644 index 0000000000..f5d83edf5d --- /dev/null +++ b/modules/gapi/test/gapi_fluid_test_kernels.hpp @@ -0,0 +1,105 @@ +// 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) 2018 Intel Corporation + + +#ifndef GAPI_FLUID_TEST_KERNELS_HPP +#define GAPI_FLUID_TEST_KERNELS_HPP + +#include "opencv2/gapi/fluid/gfluidkernel.hpp" + +namespace cv +{ +namespace gapi_test_kernels +{ + +G_TYPED_KERNEL(TAddSimple, , "test.fluid.add_simple") { + static cv::GMatDesc outMeta(cv::GMatDesc a, cv::GMatDesc) { + return a; + } +}; + +G_TYPED_KERNEL(TAddCSimple, , "test.fluid.addc_simple") +{ + static GMatDesc outMeta(const cv::GMatDesc &in, int) { + return in; + } +}; + +G_TYPED_KERNEL(TAddScalar, , "test.fluid.addc_scalar") +{ + static GMatDesc outMeta(const cv::GMatDesc &in, const cv::GScalarDesc&) { + return in; + } +}; + +G_TYPED_KERNEL(TAddScalarToMat, , "test.fluid.add_scalar_to_mat") +{ + static GMatDesc outMeta(const cv::GScalarDesc&, const cv::GMatDesc &in) { + return in; + } +}; + +G_TYPED_KERNEL(TBlur1x1, , "org.opencv.imgproc.filters.blur1x1"){ + static GMatDesc outMeta(GMatDesc in, int, Scalar) { + return in; + } +}; + +G_TYPED_KERNEL(TBlur3x3, , "org.opencv.imgproc.filters.blur3x3"){ + static GMatDesc outMeta(GMatDesc in, int, Scalar) { + return in; + } +}; + +G_TYPED_KERNEL(TBlur5x5, , "org.opencv.imgproc.filters.blur5x5"){ + static GMatDesc outMeta(GMatDesc in, int, Scalar) { + return in; + } +}; + +G_TYPED_KERNEL(TBlur3x3_2lpi, , "org.opencv.imgproc.filters.blur3x3_2lpi"){ + static GMatDesc outMeta(GMatDesc in, int, Scalar) { + return in; + } +}; + +G_TYPED_KERNEL(TBlur5x5_2lpi, , "org.opencv.imgproc.filters.blur5x5_2lpi"){ + static GMatDesc outMeta(GMatDesc in, int, Scalar) { + return in; + } +}; + +G_TYPED_KERNEL(TId, , "test.fluid.identity") { + static cv::GMatDesc outMeta(cv::GMatDesc a) { + return a; + } +}; + +G_TYPED_KERNEL(TId7x7, , "test.fluid.identity7x7") { + static cv::GMatDesc outMeta(cv::GMatDesc a) { + return a; + } +}; + +G_TYPED_KERNEL(TPlusRow0, , "test.fluid.plus_row0") { + static cv::GMatDesc outMeta(cv::GMatDesc a) { + return a; + } +}; + +G_TYPED_KERNEL(TSum2MatsAndScalar, , "test.fluid.sum_2_mats_and_scalar") +{ + static GMatDesc outMeta(const cv::GMatDesc &in, const cv::GScalarDesc&, const cv::GMatDesc&) { + return in; + } +}; + +extern cv::gapi::GKernelPackage fluidTestPackage; + +} // namespace gapi_test_kernels +} // namespace cv + +#endif // GAPI_FLUID_TEST_KERNELS_HPP diff --git a/modules/gapi/test/gapi_gcompiled_tests.cpp b/modules/gapi/test/gapi_gcompiled_tests.cpp new file mode 100644 index 0000000000..e482e2e364 --- /dev/null +++ b/modules/gapi/test/gapi_gcompiled_tests.cpp @@ -0,0 +1,173 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +namespace opencv_test +{ + +namespace +{ + static cv::GMat DemoCC(cv::GMat in, cv::GScalar scale) + { + return cv::gapi::medianBlur(in + in*scale, 3); + } + + struct GCompiledValidateMetaTyped: public ::testing::Test + { + cv::GComputationT m_cc; + + GCompiledValidateMetaTyped() : m_cc(DemoCC) + { + } + }; + + struct GCompiledValidateMetaUntyped: public ::testing::Test + { + cv::GMat in; + cv::GScalar scale; + cv::GComputation m_ucc; + + GCompiledValidateMetaUntyped() : m_ucc(cv::GIn(in, scale), + cv::GOut(DemoCC(in, scale))) + { + } + }; +} // anonymous namespace + +TEST_F(GCompiledValidateMetaTyped, ValidMeta) +{ + cv::Mat in = cv::Mat::eye(cv::Size(128, 32), CV_8UC1); + cv::Scalar sc(127); + + auto f = m_cc.compile(cv::descr_of(in), + cv::descr_of(sc)); + + // Correct operation when meta is exactly the same + cv::Mat out; + EXPECT_NO_THROW(f(in, sc, out)); + + // Correct operation on next invocation with same meta + // taken from different input objects + cv::Mat in2 = cv::Mat::zeros(cv::Size(128, 32), CV_8UC1); + cv::Scalar sc2(64); + cv::Mat out2; + EXPECT_NO_THROW(f(in2, sc2, out2)); +} + +TEST_F(GCompiledValidateMetaTyped, InvalidMeta) +{ + auto f = m_cc.compile(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(64,32)}, + cv::empty_scalar_desc()); + + cv::Scalar sc(33); + cv::Mat out; + + // 3 channels intead 1 + cv::Mat in1 = cv::Mat::eye(cv::Size(64,32), CV_8UC3); + EXPECT_THROW(f(in1, sc, out), std::logic_error); + + // 32f intead 8u + cv::Mat in2 = cv::Mat::eye(cv::Size(64,32), CV_32F); + EXPECT_THROW(f(in2, sc, out), std::logic_error); + + // 32x32 instead of 64x32 + cv::Mat in3 = cv::Mat::eye(cv::Size(32,32), CV_8UC1); + EXPECT_THROW(f(in3, sc, out), std::logic_error); + + // All is wrong + cv::Mat in4 = cv::Mat::eye(cv::Size(128,64), CV_32FC3); + EXPECT_THROW(f(in4, sc, out), std::logic_error); +} + +TEST_F(GCompiledValidateMetaUntyped, ValidMeta) +{ + cv::Mat in1 = cv::Mat::eye(cv::Size(128, 32), CV_8UC1); + cv::Scalar sc(127); + + auto f = m_ucc.compile(cv::descr_of(in1), + cv::descr_of(sc)); + + // Correct operation when meta is exactly the same + cv::Mat out1; + EXPECT_NO_THROW(f(cv::gin(in1, sc), cv::gout(out1))); + + // Correct operation on next invocation with same meta + // taken from different input objects + cv::Mat in2 = cv::Mat::zeros(cv::Size(128, 32), CV_8UC1); + cv::Scalar sc2(64); + cv::Mat out2; + EXPECT_NO_THROW(f(cv::gin(in2, sc2), cv::gout(out2))); +} + +TEST_F(GCompiledValidateMetaUntyped, InvalidMetaValues) +{ + auto f = m_ucc.compile(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(64,32)}, + cv::empty_scalar_desc()); + + cv::Scalar sc(33); + cv::Mat out; + + // 3 channels intead 1 + cv::Mat in1 = cv::Mat::eye(cv::Size(64,32), CV_8UC3); + EXPECT_THROW(f(cv::gin(in1, sc), cv::gout(out)), std::logic_error); + + // 32f intead 8u + cv::Mat in2 = cv::Mat::eye(cv::Size(64,32), CV_32F); + EXPECT_THROW(f(cv::gin(in2, sc), cv::gout(out)), std::logic_error); + + // 32x32 instead of 64x32 + cv::Mat in3 = cv::Mat::eye(cv::Size(32,32), CV_8UC1); + EXPECT_THROW(f(cv::gin(in3, sc), cv::gout(out)), std::logic_error); + + // All is wrong + cv::Mat in4 = cv::Mat::eye(cv::Size(128,64), CV_32FC3); + EXPECT_THROW(f(cv::gin(in4, sc), cv::gout(out)), std::logic_error); +} + +TEST_F(GCompiledValidateMetaUntyped, InvalidMetaShape) +{ + auto f = m_ucc.compile(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(64,32)}, + cv::empty_scalar_desc()); + + cv::Mat in1 = cv::Mat::eye(cv::Size(64,32), CV_8UC1); + cv::Scalar sc(33); + cv::Mat out1; + + // call as f(Mat,Mat) while f(Mat,Scalar) is expected + EXPECT_THROW(f(cv::gin(in1, in1), cv::gout(out1)), std::logic_error); + + // call as f(Scalar,Mat) while f(Mat,Scalar) is expected + EXPECT_THROW(f(cv::gin(sc, in1), cv::gout(out1)), std::logic_error); + + // call as f(Scalar,Scalar) while f(Mat,Scalar) is expected + EXPECT_THROW(f(cv::gin(sc, sc), cv::gout(out1)), std::logic_error); +} + +TEST_F(GCompiledValidateMetaUntyped, InvalidMetaNumber) +{ + auto f = m_ucc.compile(cv::GMatDesc{CV_8U,1,cv::Size(64,32)}, + cv::empty_scalar_desc()); + + cv::Mat in1 = cv::Mat::eye(cv::Size(64,32), CV_8UC1); + cv::Scalar sc(33); + cv::Mat out1, out2; + + // call as f(Mat,Scalar,Scalar) while f(Mat,Scalar) is expected + EXPECT_THROW(f(cv::gin(in1, sc, sc), cv::gout(out1)), std::logic_error); + + // call as f(Scalar,Mat,Scalar) while f(Mat,Scalar) is expected + EXPECT_THROW(f(cv::gin(sc, in1, sc), cv::gout(out1)), std::logic_error); + + // call as f(Scalar) while f(Mat,Scalar) is expected + EXPECT_THROW(f(cv::gin(sc), cv::gout(out1)), std::logic_error); + + // call as f(Mat,Scalar,[out1],[out2]) while f(Mat,Scalar,[out]) is expected + EXPECT_THROW(f(cv::gin(in1, sc), cv::gout(out1, out2)), std::logic_error); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_gcomputation_tests.cpp b/modules/gapi/test/gapi_gcomputation_tests.cpp new file mode 100644 index 0000000000..070cea6927 --- /dev/null +++ b/modules/gapi/test/gapi_gcomputation_tests.cpp @@ -0,0 +1,68 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/cpu/gcpukernel.hpp" + +namespace opencv_test +{ + + namespace + { + G_TYPED_KERNEL(CustomResize, , "org.opencv.customk.resize") + { + static cv::GMatDesc outMeta(cv::GMatDesc in, cv::Size sz, double fx, double fy, int) { + if (sz.width != 0 && sz.height != 0) + { + return in.withSize(to_own(sz)); + } + else + { + GAPI_Assert(fx != 0. && fy != 0.); + return in.withSize + (cv::gapi::own::Size(static_cast(std::round(in.size.width * fx)), + static_cast(std::round(in.size.height * fy)))); + } + } + }; + + GAPI_OCV_KERNEL(CustomResizeImpl, CustomResize) + { + static void run(const cv::Mat& in, cv::Size sz, double fx, double fy, int interp, cv::Mat &out) + { + cv::resize(in, out, sz, fx, fy, interp); + } + }; + + struct GComputationApplyTest: public ::testing::Test + { + cv::GMat in; + cv::Mat in_mat; + cv::Mat out_mat; + cv::GComputation m_c; + + GComputationApplyTest() : in_mat(300, 300, CV_8UC1), + m_c(cv::GIn(in), cv::GOut(CustomResize::on(in, cv::Size(100, 100), + 0.0, 0.0, cv::INTER_LINEAR))) + { + } + }; + } + + TEST_F(GComputationApplyTest, ThrowDontPassCustomKernel) + { + EXPECT_THROW(m_c.apply(in_mat, out_mat), std::logic_error); + } + + TEST_F(GComputationApplyTest, NoThrowPassCustomKernel) + { + const auto pkg = cv::gapi::kernels(); + + ASSERT_NO_THROW(m_c.apply(in_mat, out_mat, cv::compile_args(pkg))); + } + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_kernel_tests.cpp b/modules/gapi/test/gapi_kernel_tests.cpp new file mode 100644 index 0000000000..509c862e55 --- /dev/null +++ b/modules/gapi/test/gapi_kernel_tests.cpp @@ -0,0 +1,202 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/cpu/gcpukernel.hpp" +#include "gapi_mock_kernels.hpp" + +namespace opencv_test +{ + +namespace +{ + G_TYPED_KERNEL(GClone, , "org.opencv.test.clone") + { + static GMatDesc outMeta(GMatDesc in) { return in; } + + }; + + GAPI_OCV_KERNEL(GCloneImpl, GClone) + { + static void run(const cv::Mat& in, cv::Mat &out) + { + out = in.clone(); + } + }; +} + +TEST(KernelPackage, Create) +{ + namespace J = Jupiter; + auto pkg = cv::gapi::kernels(); + EXPECT_EQ(3u, pkg.size()); +} + +TEST(KernelPackage, Includes) +{ + namespace J = Jupiter; + auto pkg = cv::gapi::kernels(); + EXPECT_TRUE (pkg.includes()); + EXPECT_TRUE (pkg.includes()); + EXPECT_TRUE (pkg.includes()); + EXPECT_FALSE(pkg.includes()); +} + +TEST(KernelPackage, Include) +{ + namespace J = Jupiter; + auto pkg = cv::gapi::kernels(); + EXPECT_FALSE(pkg.includes()); + + pkg.include(); + EXPECT_TRUE(pkg.includes()); +} + +TEST(KernelPackage, CreateHetero) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_EQ(4u, pkg.size()); +} + +TEST(KernelPackage, IncludesHetero) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_TRUE (pkg.includes()); + EXPECT_TRUE (pkg.includes()); + EXPECT_TRUE (pkg.includes()); + EXPECT_FALSE(pkg.includes()); + EXPECT_TRUE (pkg.includes()); +} + +TEST(KernelPackage, IncludeHetero) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_FALSE(pkg.includes()); + EXPECT_FALSE(pkg.includes()); + + pkg.include(); + EXPECT_FALSE(pkg.includes()); + EXPECT_TRUE (pkg.includes()); +} + +TEST(KernelPackage, Unite_REPLACE_Full) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto j_pkg = cv::gapi::kernels(); + auto s_pkg = cv::gapi::kernels(); + auto u_pkg = cv::gapi::combine(j_pkg, s_pkg, cv::unite_policy::REPLACE); + + EXPECT_EQ(3u, u_pkg.size()); + EXPECT_FALSE(u_pkg.includes()); + EXPECT_FALSE(u_pkg.includes()); + EXPECT_FALSE(u_pkg.includes()); + EXPECT_TRUE (u_pkg.includes()); + EXPECT_TRUE (u_pkg.includes()); + EXPECT_TRUE (u_pkg.includes()); +} + +TEST(KernelPackage, Unite_REPLACE_Partial) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto j_pkg = cv::gapi::kernels(); + auto s_pkg = cv::gapi::kernels(); + auto u_pkg = cv::gapi::combine(j_pkg, s_pkg, cv::unite_policy::REPLACE); + + EXPECT_EQ(2u, u_pkg.size()); + EXPECT_TRUE (u_pkg.includes()); + EXPECT_FALSE(u_pkg.includes()); + EXPECT_TRUE (u_pkg.includes()); +} + +TEST(KernelPackage, Unite_REPLACE_Append) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto j_pkg = cv::gapi::kernels(); + auto s_pkg = cv::gapi::kernels(); + auto u_pkg = cv::gapi::combine(j_pkg, s_pkg, cv::unite_policy::REPLACE); + + EXPECT_EQ(3u, u_pkg.size()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); +} + +TEST(KernelPackage, Unite_KEEP_AllDups) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto j_pkg = cv::gapi::kernels(); + auto s_pkg = cv::gapi::kernels(); + auto u_pkg = cv::gapi::combine(j_pkg ,s_pkg, cv::unite_policy::KEEP); + + EXPECT_EQ(6u, u_pkg.size()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); +} + +TEST(KernelPackage, Unite_KEEP_Append_NoDups) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto j_pkg = cv::gapi::kernels(); + auto s_pkg = cv::gapi::kernels(); + auto u_pkg = cv::gapi::combine(j_pkg, s_pkg, cv::unite_policy::KEEP); + + EXPECT_EQ(3u, u_pkg.size()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); + EXPECT_TRUE(u_pkg.includes()); +} + +TEST(KernelPackage, TestWithEmptyLHS) +{ + namespace J = Jupiter; + auto lhs = cv::gapi::kernels<>(); + auto rhs = cv::gapi::kernels(); + auto pkg = cv::gapi::combine(lhs, rhs, cv::unite_policy::KEEP); + + EXPECT_EQ(1u, pkg.size()); + EXPECT_TRUE(pkg.includes()); +} + +TEST(KernelPackage, TestWithEmptyRHS) +{ + namespace J = Jupiter; + auto lhs = cv::gapi::kernels(); + auto rhs = cv::gapi::kernels<>(); + auto pkg = cv::gapi::combine(lhs, rhs, cv::unite_policy::KEEP); + + EXPECT_EQ(1u, pkg.size()); + EXPECT_TRUE(pkg.includes()); +} + +TEST(KernelPackage, Can_Use_Custom_Kernel) +{ + cv::GMat in[2]; + auto out = GClone::on(cv::gapi::add(in[0], in[1])); + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::Size(32,32)}); + + auto pkg = cv::gapi::kernels(); + + EXPECT_NO_THROW(cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out)). + compile({in_meta, in_meta}, cv::compile_args(pkg))); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_mock_kernels.hpp b/modules/gapi/test/gapi_mock_kernels.hpp new file mode 100644 index 0000000000..cd876efdbe --- /dev/null +++ b/modules/gapi/test/gapi_mock_kernels.hpp @@ -0,0 +1,123 @@ +// 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) 2018 Intel Corporation + + +#include "opencv2/gapi/cpu/gcpukernel.hpp" + +#include "api/gbackend_priv.hpp" // directly instantiate GBackend::Priv + +namespace opencv_test +{ +namespace { + // FIXME: Currently every Kernel implementation in this test file has + // its own backend() method and it is incorrect! API classes should + // provide it out of the box. + +namespace I +{ + G_TYPED_KERNEL(Foo, , "test.kernels.foo") + { + static cv::GMatDesc outMeta(const cv::GMatDesc &in) { return in; } + }; + + G_TYPED_KERNEL(Bar, , "test.kernels.bar") + { + static cv::GMatDesc outMeta(const cv::GMatDesc &in, const cv::GMatDesc &) { return in; } + }; + + G_TYPED_KERNEL(Baz, , "test.kernels.baz") + { + static cv::GScalarDesc outMeta(const cv::GMatDesc &) { return cv::empty_scalar_desc(); } + }; + + G_TYPED_KERNEL(Qux, , "test.kernels.qux") + { + static cv::GMatDesc outMeta(const cv::GMatDesc &in, const cv::GScalarDesc &) { return in; } + }; + + G_TYPED_KERNEL(Quux, , "test.kernels.quux") + { + static cv::GMatDesc outMeta(const cv::GScalarDesc &, const cv::GMatDesc& in) { return in; } + }; +} + +// Kernel implementations for imaginary Jupiter device +namespace Jupiter +{ + namespace detail + { + static cv::gapi::GBackend backend(std::make_shared()); + } + + inline cv::gapi::GBackend backend() { return detail::backend; } + + GAPI_OCV_KERNEL(Foo, I::Foo) + { + static void run(const cv::Mat &, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + GAPI_OCV_KERNEL(Bar, I::Bar) + { + static void run(const cv::Mat &, const cv::Mat &, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + GAPI_OCV_KERNEL(Baz, I::Baz) + { + static void run(const cv::Mat &, cv::Scalar &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + GAPI_OCV_KERNEL(Qux, I::Qux) + { + static void run(const cv::Mat &, const cv::Scalar&, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + + GAPI_OCV_KERNEL(Quux, I::Quux) + { + static void run(const cv::Scalar&, const cv::Mat&, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; +} // namespace Jupiter + +// Kernel implementations for imaginary Saturn device +namespace Saturn +{ + namespace detail + { + static cv::gapi::GBackend backend(std::make_shared()); + } + + inline cv::gapi::GBackend backend() { return detail::backend; } + + GAPI_OCV_KERNEL(Foo, I::Foo) + { + static void run(const cv::Mat &, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + GAPI_OCV_KERNEL(Bar, I::Bar) + { + static void run(const cv::Mat &, const cv::Mat &, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + GAPI_OCV_KERNEL(Baz, I::Baz) + { + static void run(const cv::Mat &, cv::Scalar &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + GAPI_OCV_KERNEL(Qux, I::Qux) + { + static void run(const cv::Mat &, const cv::Scalar&, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; + + GAPI_OCV_KERNEL(Quux, I::Quux) + { + static void run(const cv::Scalar&, const cv::Mat&, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return detail::backend; } // FIXME: Must be removed + }; +} // namespace Saturn +} // anonymous namespace +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_sample_pipelines.cpp b/modules/gapi/test/gapi_sample_pipelines.cpp new file mode 100644 index 0000000000..815aa0d872 --- /dev/null +++ b/modules/gapi/test/gapi_sample_pipelines.cpp @@ -0,0 +1,301 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include +#include +#include "logger.hpp" + +namespace opencv_test +{ + +namespace +{ + G_TYPED_KERNEL(GInvalidResize, , "org.opencv.test.invalid_resize") + { + static GMatDesc outMeta(GMatDesc in, Size, double, double, int) { return in; } + }; + + GAPI_OCV_KERNEL(GOCVInvalidResize, GInvalidResize) + { + static void run(const cv::Mat& in, cv::Size sz, double fx, double fy, int interp, cv::Mat &out) + { + cv::resize(in, out, sz, fx, fy, interp); + } + }; + + G_TYPED_KERNEL(GReallocatingCopy, , "org.opencv.test.reallocating_copy") + { + static GMatDesc outMeta(GMatDesc in) { return in; } + }; + + GAPI_OCV_KERNEL(GOCVReallocatingCopy, GReallocatingCopy) + { + static void run(const cv::Mat& in, cv::Mat &out) + { + out = in.clone(); + } + }; +} + +TEST(GAPI_Pipeline, OverloadUnary_MatMat) +{ + cv::GMat in; + cv::GComputation comp(in, cv::gapi::bitwise_not(in)); + + cv::Mat in_mat = cv::Mat::eye(32, 32, CV_8UC1); + cv::Mat ref_mat = ~in_mat; + + cv::Mat out_mat; + comp.apply(in_mat, out_mat); + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); + + out_mat = cv::Mat(); + auto cc = comp.compile(cv::descr_of(in_mat)); + cc(in_mat, out_mat); + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GAPI_Pipeline, OverloadUnary_MatScalar) +{ + cv::GMat in; + cv::GComputation comp(in, cv::gapi::sum(in)); + + cv::Mat in_mat = cv::Mat::eye(32, 32, CV_8UC1); + cv::Scalar ref_scl = cv::sum(in_mat); + + cv::Scalar out_scl; + comp.apply(in_mat, out_scl); + EXPECT_EQ(out_scl, ref_scl); + + out_scl = cv::Scalar(); + auto cc = comp.compile(cv::descr_of(in_mat)); + cc(in_mat, out_scl); + EXPECT_EQ(out_scl, ref_scl); +} + +TEST(GAPI_Pipeline, OverloadBinary_Mat) +{ + cv::GMat a, b; + cv::GComputation comp(a, b, cv::gapi::add(a, b)); + + cv::Mat in_mat = cv::Mat::eye(32, 32, CV_8UC1); + cv::Mat ref_mat = (in_mat+in_mat); + + cv::Mat out_mat; + comp.apply(in_mat, in_mat, out_mat); + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); + + out_mat = cv::Mat(); + auto cc = comp.compile(cv::descr_of(in_mat), cv::descr_of(in_mat)); + cc(in_mat, in_mat, out_mat); + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GAPI_Pipeline, OverloadBinary_Scalar) +{ + cv::GMat a, b; + cv::GComputation comp(a, b, cv::gapi::sum(a + b)); + + cv::Mat in_mat = cv::Mat::eye(32, 32, CV_8UC1); + cv::Scalar ref_scl = cv::sum(in_mat+in_mat); + + cv::Scalar out_scl; + comp.apply(in_mat, in_mat, out_scl); + EXPECT_EQ(out_scl, ref_scl); + + out_scl = cv::Scalar(); + auto cc = comp.compile(cv::descr_of(in_mat), cv::descr_of(in_mat)); + cc(in_mat, in_mat, out_scl); + EXPECT_EQ(out_scl, ref_scl); +} + +TEST(GAPI_Pipeline, Sharpen) +{ + const cv::Size sz_in (1280, 720); + const cv::Size sz_out( 640, 480); + cv::Mat in_mat (sz_in, CV_8UC3); + in_mat = cv::Scalar(128, 33, 53); + + cv::Mat out_mat(sz_out, CV_8UC3); + cv::Mat out_mat_y; + cv::Mat out_mat_ocv(sz_out, CV_8UC3); + + float sharpen_coeffs[] = { + 0.0f, -1.f, 0.0f, + -1.0f, 5.f, -1.0f, + 0.0f, -1.f, 0.0f + }; + cv::Mat sharpen_kernel(3, 3, CV_32F, sharpen_coeffs); + + // G-API code ////////////////////////////////////////////////////////////// + + cv::GMat in; + auto vga = cv::gapi::resize(in, sz_out); + auto yuv = cv::gapi::RGB2YUV(vga); + auto yuv_p = cv::gapi::split3(yuv); + auto y_sharp = cv::gapi::filter2D(std::get<0>(yuv_p), -1, sharpen_kernel); + auto yuv_new = cv::gapi::merge3(y_sharp, std::get<1>(yuv_p), std::get<2>(yuv_p)); + auto out = cv::gapi::YUV2RGB(yuv_new); + + cv::GComputation c(cv::GIn(in), cv::GOut(y_sharp, out)); + c.apply(cv::gin(in_mat), cv::gout(out_mat_y, out_mat)); + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Mat smaller; + cv::resize(in_mat, smaller, sz_out); + + cv::Mat yuv_mat; + cv::cvtColor(smaller, yuv_mat, cv::COLOR_RGB2YUV); + std::vector yuv_planar(3); + cv::split(yuv_mat, yuv_planar); + cv::filter2D(yuv_planar[0], yuv_planar[0], -1, sharpen_kernel); + cv::merge(yuv_planar, yuv_mat); + cv::cvtColor(yuv_mat, out_mat_ocv, cv::COLOR_YUV2RGB); + } + + // Comparison ////////////////////////////////////////////////////////////// + { + cv::Mat diff = out_mat_ocv != out_mat; + std::vector diffBGR(3); + cv::split(diff, diffBGR); + EXPECT_EQ(0, cv::countNonZero(diffBGR[0])); + EXPECT_EQ(0, cv::countNonZero(diffBGR[1])); + EXPECT_EQ(0, cv::countNonZero(diffBGR[2])); + } + + // Metadata check ///////////////////////////////////////////////////////// + { + auto cc = c.compile(cv::descr_of(in_mat)); + auto metas = cc.outMetas(); + ASSERT_EQ(2u, metas.size()); + + auto out_y_meta = cv::util::get(metas[0]); + auto out_meta = cv::util::get(metas[1]); + + // Y-output + EXPECT_EQ(CV_8U, out_y_meta.depth); + EXPECT_EQ(1, out_y_meta.chan); + EXPECT_EQ(640, out_y_meta.size.width); + EXPECT_EQ(480, out_y_meta.size.height); + + // Final output + EXPECT_EQ(CV_8U, out_meta.depth); + EXPECT_EQ(3, out_meta.chan); + EXPECT_EQ(640, out_meta.size.width); + EXPECT_EQ(480, out_meta.size.height); + } +} + +TEST(GAPI_Pipeline, CustomRGB2YUV) +{ + const cv::Size sz(1280, 720); + + // BEWARE: + // + // std::vector out_mats_cv(3, cv::Mat(sz, CV_8U)) + // + // creates a vector of 3 elements pointing to the same Mat! + // FIXME: Make a G-API check for that + const int INS = 3; + std::vector in_mats(INS); + for (auto i : ade::util::iota(INS)) + { + in_mats[i].create(sz, CV_8U); + cv::randu(in_mats[i], cv::Scalar::all(0), cv::Scalar::all(255)); + } + + const int OUTS = 3; + std::vector out_mats_cv(OUTS); + std::vector out_mats_gapi(OUTS); + for (auto i : ade::util::iota(OUTS)) + { + out_mats_cv [i].create(sz, CV_8U); + out_mats_gapi[i].create(sz, CV_8U); + } + + // G-API code ////////////////////////////////////////////////////////////// + { + cv::GMat r, g, b; + cv::GMat y = 0.299f*r + 0.587f*g + 0.114f*b; + cv::GMat u = 0.492f*(b - y); + cv::GMat v = 0.877f*(r - y); + + cv::GComputation customCvt({r, g, b}, {y, u, v}); + customCvt.apply(in_mats, out_mats_gapi); + } + + // OpenCV code ///////////////////////////////////////////////////////////// + { + cv::Mat r = in_mats[0], g = in_mats[1], b = in_mats[2]; + cv::Mat y = 0.299f*r + 0.587f*g + 0.114f*b; + cv::Mat u = 0.492f*(b - y); + cv::Mat v = 0.877f*(r - y); + + out_mats_cv[0] = y; + out_mats_cv[1] = u; + out_mats_cv[2] = v; + } + + // Comparison ////////////////////////////////////////////////////////////// + { + const auto diff = [](cv::Mat m1, cv::Mat m2, int t) { + return cv::abs(m1-m2) > t; + }; + + // FIXME: Not bit-accurate even now! + cv::Mat + diff_y = diff(out_mats_cv[0], out_mats_gapi[0], 2), + diff_u = diff(out_mats_cv[1], out_mats_gapi[1], 2), + diff_v = diff(out_mats_cv[2], out_mats_gapi[2], 2); + + EXPECT_EQ(0, cv::countNonZero(diff_y)); + EXPECT_EQ(0, cv::countNonZero(diff_u)); + EXPECT_EQ(0, cv::countNonZero(diff_v)); + } +} + +TEST(GAPI_Pipeline, PipelineWithInvalidKernel) +{ + cv::GMat in, out; + cv::Mat in_mat(500, 500, CV_8UC1), out_mat; + out = GInvalidResize::on(in, cv::Size(300, 300), 0.0, 0.0, cv::INTER_LINEAR); + + const auto pkg = cv::gapi::kernels(); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + + EXPECT_THROW(comp.apply(in_mat, out_mat, cv::compile_args(pkg)), std::logic_error); +} + +TEST(GAPI_Pipeline, InvalidOutputComputation) +{ + cv::GMat in1, out1, out2, out3; + + std::tie(out1, out2, out2) = cv::gapi::split3(in1); + cv::GComputation c({in1}, {out1, out2, out3}); + cv::Mat in_mat; + cv::Mat out_mat1, out_mat2, out_mat3, out_mat4; + std::vector u_outs = {out_mat1, out_mat2, out_mat3, out_mat4}; + std::vector u_ins = {in_mat}; + + EXPECT_THROW(c.apply(u_ins, u_outs), std::logic_error); +} + +TEST(GAPI_Pipeline, PipelineAllocatingKernel) +{ + cv::GMat in, out; + cv::Mat in_mat(500, 500, CV_8UC1), out_mat; + out = GReallocatingCopy::on(in); + + const auto pkg = cv::gapi::kernels(); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + + EXPECT_THROW(comp.apply(in_mat, out_mat, cv::compile_args(pkg)), std::logic_error); +} +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_scalar_tests.cpp b/modules/gapi/test/gapi_scalar_tests.cpp new file mode 100644 index 0000000000..4c2a4f4ab5 --- /dev/null +++ b/modules/gapi/test/gapi_scalar_tests.cpp @@ -0,0 +1,116 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include + +namespace opencv_test +{ + +TEST(GAPI_Scalar, Argument) +{ + cv::Size sz(2, 2); + cv::Mat in_mat(sz, CV_8U); + + cv::GComputationT mulS([](cv::GMat in, cv::GScalar c) + { + return in*c; + }); + + cv::Mat out_mat(sz, CV_8U); + mulS.apply(in_mat, cv::Scalar(2), out_mat); + + cv::Mat reference = in_mat*2; + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat - reference))); +} + +TEST(GAPI_Scalar, ReturnValue) +{ + const cv::Size sz(2, 2); + cv::Mat in_mat(sz, CV_8U, cv::Scalar(1)); + + cv::GComputationT sum_of_sum([](cv::GMat in) + { + return cv::gapi::sum(in + in); + }); + + cv::Scalar out; + sum_of_sum.apply(in_mat, out); + + EXPECT_EQ(8, out[0]); +} + +TEST(GAPI_Scalar, TmpScalar) +{ + const cv::Size sz(2, 2); + cv::Mat in_mat(sz, CV_8U, cv::Scalar(1)); + + cv::GComputationT mul_by_sum([](cv::GMat in) + { + return in * cv::gapi::sum(in); + }); + + cv::Mat out_mat(sz, CV_8U); + mul_by_sum.apply(in_mat, out_mat); + + cv::Mat reference = cv::Mat(sz, CV_8U, cv::Scalar(4)); + EXPECT_EQ(0, cv::countNonZero(cv::abs(out_mat - reference))); +} + +TEST(GAPI_ScalarWithValue, Simple_Arithmetic_Pipeline) +{ + GMat in; + GMat out = (in + 1) * 2; + cv::GComputation comp(in, out); + + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1); + cv::Mat ref_mat, out_mat; + + ref_mat = (in_mat + 1) * 2; + comp.apply(in_mat, out_mat); + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GAPI_ScalarWithValue, GScalar_Initilization) +{ + cv::Scalar sc(2); + cv::GMat in; + cv::GScalar s(sc); + cv::GComputation comp(in, cv::gapi::mulC(in, s)); + + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1); + cv::Mat ref_mat, out_mat; + cv::multiply(in_mat, sc, ref_mat, 1, CV_8UC1); + comp.apply(cv::gin(in_mat), cv::gout(out_mat)); + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +TEST(GAPI_ScalarWithValue, Constant_GScalar_In_Middle_Graph) +{ + cv::Scalar sc(5); + cv::GMat in1; + cv::GScalar in2; + cv::GScalar s(sc); + + auto add_out = cv::gapi::addC(in1, in2); + cv::GComputation comp(cv::GIn(in1, in2), cv::GOut(cv::gapi::mulC(add_out, s))); + + cv::Mat in_mat = cv::Mat::eye(3, 3, CV_8UC1); + cv::Scalar in_scalar(3); + + cv::Mat ref_mat, out_mat, add_mat; + cv::add(in_mat, in_scalar, add_mat); + cv::multiply(add_mat, sc, ref_mat, 1, CV_8UC1); + comp.apply(cv::gin(in_mat, in_scalar), cv::gout(out_mat)); + + EXPECT_EQ(0, cv::countNonZero(out_mat != ref_mat)); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/gapi_smoke_test.cpp b/modules/gapi/test/gapi_smoke_test.cpp new file mode 100644 index 0000000000..9ac47f6d74 --- /dev/null +++ b/modules/gapi/test/gapi_smoke_test.cpp @@ -0,0 +1,97 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +namespace opencv_test +{ + +TEST(GAPI, Mat_Create_NoLink) +{ + cv::Mat m1; + cv::Mat m2 = m1; + m2.create(32, 32, CV_8U); + + EXPECT_NE(m1.rows, m2.rows); + EXPECT_NE(m1.cols, m2.cols); + EXPECT_NE(m1.data, m2.data); +} + +TEST(GAPI, Mat_Recreate) +{ + cv::Mat m1 = cv::Mat::zeros(480, 640, CV_8U); + m1.at(0, 0) = 128; + cv::Mat m2 = m1; + + EXPECT_EQ(m1.rows, m2.rows); + EXPECT_EQ(m1.cols, m2.cols); + EXPECT_EQ(m1.data, m2.data); + EXPECT_EQ(m1.at(0, 0), m2.at(0, 0)); + + // Calling "create" with the same meta is NOOP - both m1 and m2 are the same + m1.create(480, 640, CV_8U); + EXPECT_EQ(m1.rows, m2.rows); + EXPECT_EQ(m1.cols, m2.cols); + EXPECT_EQ(m1.data, m2.data); + EXPECT_EQ(m1.at(0, 0), m2.at(0, 0)); + + // Calling "create" on m2 with different meta doesn't update original m1 + // Now m1 and m2 are distinct + m2.create(720, 1280, CV_8U); + m2.at(0, 0) = 64; // Initialize 0,0 element since m2 is a new buffer + EXPECT_NE(m1.rows, m2.rows); + EXPECT_NE(m1.cols, m2.cols); + EXPECT_NE(m1.data, m2.data); + EXPECT_NE(m1.at(0, 0), m2.at(0, 0)); + + // What if a Mat is created from handle? + uchar data[] = { + 32, 0, 0, + 0, 0, 0, + 0, 0, 0 + }; + cv::Mat m3(3, 3, CV_8U, data); + cv::Mat m4 = m3; + EXPECT_EQ(m3.rows, m4.rows); + EXPECT_EQ(m3.cols, m4.cols); + EXPECT_EQ(m3.data, m4.data); + EXPECT_EQ(data, m3.data); + EXPECT_EQ(data, m4.data); + EXPECT_EQ(m3.at(0, 0), m4.at(0, 0)); + + // cv::Mat::create must be NOOP if we don't change the meta, + // even if the origianl mat is created from handle. + m4.create(3, 3, CV_8U); + EXPECT_EQ(m3.rows, m4.rows); + EXPECT_EQ(m3.cols, m4.cols); + EXPECT_EQ(m3.data, m4.data); + EXPECT_EQ(data, m3.data); + EXPECT_EQ(data, m4.data); + EXPECT_EQ(m3.at(0, 0), m4.at(0, 0)); +} + +TEST(GAPI, EmptyOutMat) +{ + cv::Mat in_mat = cv::Mat(480, 640, CV_8U, cv::Scalar(64)); + + cv::GComputation cc([]() + { + cv::GMat in; + cv::GMat out = in + in; + return cv::GComputation(in, out); + }); + + cv::Mat out; + cc.apply(in_mat, out); + + EXPECT_EQ(640, out.cols); + EXPECT_EQ(480, out.rows); + EXPECT_EQ(CV_8U, out.type()); + EXPECT_EQ(0, cv::countNonZero(out - (in_mat+in_mat))); +} + +} diff --git a/modules/gapi/test/gapi_typed_tests.cpp b/modules/gapi/test/gapi_typed_tests.cpp new file mode 100644 index 0000000000..1716b55054 --- /dev/null +++ b/modules/gapi/test/gapi_typed_tests.cpp @@ -0,0 +1,185 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +namespace opencv_test +{ + +namespace +{ + cv::Mat diff(cv::Mat m1, cv::Mat m2, int t) + { + return cv::abs(m1-m2) > t; + } + + int non_zero3(cv::Mat m3c) + { + std::vector mm(3); + cv::split(m3c, mm); + return ( cv::countNonZero(mm[0]) + + cv::countNonZero(mm[1]) + + cv::countNonZero(mm[2])); + } +} + +TEST(GAPI_Typed, UnaryOp) +{ + // Initialization ////////////////////////////////////////////////////////// + const cv::Size sz(32, 32); + cv::Mat + in_mat (sz, CV_8UC3), + out_mat_untyped(sz, CV_8UC3), + out_mat_typed1 (sz, CV_8UC3), + out_mat_typed2 (sz, CV_8UC3), + out_mat_cv (sz, CV_8UC3); + cv::randu(in_mat, cv::Scalar::all(0), cv::Scalar::all(255)); + + // Untyped G-API /////////////////////////////////////////////////////////// + cv::GComputation cvtU([]() + { + cv::GMat in; + cv::GMat out = cv::gapi::RGB2YUV(in); + return cv::GComputation(in, out); + }); + cvtU.apply(in_mat, out_mat_untyped); + + // Typed G-API ///////////////////////////////////////////////////////////// + cv::GComputationT cvtT(cv::gapi::RGB2YUV); + auto cvtTComp = cvtT.compile(cv::descr_of(in_mat)); + + cvtT.apply(in_mat, out_mat_typed1); + cvtTComp(in_mat, out_mat_typed2); + + // Plain OpenCV //////////////////////////////////////////////////////////// + cv::cvtColor(in_mat, out_mat_cv, cv::COLOR_RGB2YUV); + + // Comparison ////////////////////////////////////////////////////////////// + // FIXME: There must be OpenCV comparison test functions already available! + cv::Mat + diff_u = diff(out_mat_cv, out_mat_untyped, 0), + diff_t = diff(out_mat_cv, out_mat_typed1, 0), + diff_tc = diff(out_mat_cv, out_mat_typed2, 0); + + EXPECT_EQ(0, non_zero3(diff_u)); + EXPECT_EQ(0, non_zero3(diff_t)); + EXPECT_EQ(0, non_zero3(diff_tc)); +} + +TEST(GAPI_Typed, BinaryOp) +{ + // Initialization ////////////////////////////////////////////////////////// + const cv::Size sz(32, 32); + cv::Mat + in_mat1 (sz, CV_8UC1), + in_mat2 (sz, CV_8UC1), + out_mat_untyped(sz, CV_8UC1), + out_mat_typed1 (sz, CV_8UC1), + out_mat_typed2 (sz, CV_8UC1), + out_mat_cv (sz, CV_8UC1); + cv::randu(in_mat1, cv::Scalar::all(0), cv::Scalar::all(255)); + cv::randu(in_mat2, cv::Scalar::all(0), cv::Scalar::all(255)); + + // Untyped G-API /////////////////////////////////////////////////////////// + cv::GComputation cvtU([]() + { + cv::GMat in1, in2; + cv::GMat out = cv::gapi::add(in1, in2); + return cv::GComputation({in1, in2}, {out}); + }); + std::vector u_ins = {in_mat1, in_mat2}; + std::vector u_outs = {out_mat_untyped}; + cvtU.apply(u_ins, u_outs); + + // Typed G-API ///////////////////////////////////////////////////////////// + cv::GComputationT cvtT([](cv::GMat m1, cv::GMat m2) + { + return m1+m2; + }); + auto cvtTC = cvtT.compile(cv::descr_of(in_mat1), + cv::descr_of(in_mat2)); + + cvtT.apply(in_mat1, in_mat2, out_mat_typed1); + cvtTC(in_mat1, in_mat2, out_mat_typed2); + + // Plain OpenCV //////////////////////////////////////////////////////////// + cv::add(in_mat1, in_mat2, out_mat_cv); + + // Comparison ////////////////////////////////////////////////////////////// + // FIXME: There must be OpenCV comparison test functions already available! + cv::Mat + diff_u = diff(out_mat_cv, out_mat_untyped, 0), + diff_t = diff(out_mat_cv, out_mat_typed1, 0), + diff_tc = diff(out_mat_cv, out_mat_typed2, 0); + + EXPECT_EQ(0, cv::countNonZero(diff_u)); + EXPECT_EQ(0, cv::countNonZero(diff_t)); + EXPECT_EQ(0, cv::countNonZero(diff_tc)); +} + + +TEST(GAPI_Typed, MultipleOuts) +{ + // Initialization ////////////////////////////////////////////////////////// + const cv::Size sz(32, 32); + cv::Mat + in_mat (sz, CV_8UC1), + out_mat_unt1 (sz, CV_8UC1), + out_mat_unt2 (sz, CV_8UC1), + out_mat_typed1(sz, CV_8UC1), + out_mat_typed2(sz, CV_8UC1), + out_mat_comp1 (sz, CV_8UC1), + out_mat_comp2 (sz, CV_8UC1), + out_mat_cv1 (sz, CV_8UC1), + out_mat_cv2 (sz, CV_8UC1); + cv::randu(in_mat, cv::Scalar::all(0), cv::Scalar::all(255)); + + // Untyped G-API /////////////////////////////////////////////////////////// + cv::GComputation cvtU([]() + { + cv::GMat in; + cv::GMat out1 = in * 2.f; + cv::GMat out2 = in * 4.f; + return cv::GComputation({in}, {out1, out2}); + }); + std::vector u_ins = {in_mat}; + std::vector u_outs = {out_mat_unt1, out_mat_unt2}; + cvtU.apply(u_ins, u_outs); + + // Typed G-API ///////////////////////////////////////////////////////////// + cv::GComputationT (cv::GMat)> cvtT([](cv::GMat in) + { + return std::make_tuple(in*2.f, in*4.f); + }); + auto cvtTC = cvtT.compile(cv::descr_of(in_mat)); + + cvtT.apply(in_mat, out_mat_typed1, out_mat_typed2); + cvtTC(in_mat, out_mat_comp1, out_mat_comp2); + + // Plain OpenCV //////////////////////////////////////////////////////////// + out_mat_cv1 = in_mat * 2.f; + out_mat_cv2 = in_mat * 4.f; + + // Comparison ////////////////////////////////////////////////////////////// + // FIXME: There must be OpenCV comparison test functions already available! + cv::Mat + diff_u1 = diff(out_mat_cv1, out_mat_unt1, 0), + diff_u2 = diff(out_mat_cv2, out_mat_unt2, 0), + diff_t1 = diff(out_mat_cv1, out_mat_typed1, 0), + diff_t2 = diff(out_mat_cv2, out_mat_typed2, 0), + diff_c1 = diff(out_mat_cv1, out_mat_comp1, 0), + diff_c2 = diff(out_mat_cv2, out_mat_comp2, 0); + + EXPECT_EQ(0, cv::countNonZero(diff_u1)); + EXPECT_EQ(0, cv::countNonZero(diff_u2)); + EXPECT_EQ(0, cv::countNonZero(diff_t1)); + EXPECT_EQ(0, cv::countNonZero(diff_t2)); + EXPECT_EQ(0, cv::countNonZero(diff_c1)); + EXPECT_EQ(0, cv::countNonZero(diff_c2)); +} + +} // opencv_test diff --git a/modules/gapi/test/gapi_util_tests.cpp b/modules/gapi/test/gapi_util_tests.cpp new file mode 100644 index 0000000000..574c0ab542 --- /dev/null +++ b/modules/gapi/test/gapi_util_tests.cpp @@ -0,0 +1,43 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include + +#include "opencv2/gapi/util/util.hpp" + +namespace opencv_test +{ + +TEST(GAPIUtil, AllSatisfy) +{ + static_assert(true == cv::detail::all_satisfy::value, + "[long, int, char] are all integral types"); + static_assert(true == cv::detail::all_satisfy::value, + "char is an integral type"); + + static_assert(false == cv::detail::all_satisfy::value, + "[float, int, char] are NOT all integral types"); + static_assert(false == cv::detail::all_satisfy::value, + "[int, char, float] are NOT all integral types"); + static_assert(false == cv::detail::all_satisfy::value, + "float is not an integral types"); +} + +TEST(GAPIUtil, AllButLast) +{ + using test1 = cv::detail::all_but_last::type; + static_assert(true == cv::detail::all_satisfy::value, + "[long, int] are all integral types (float skipped)"); + + using test2 = cv::detail::all_but_last::type; + static_assert(false == cv::detail::all_satisfy::value, + "[int, float] are NOT all integral types"); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_backend_tests.cpp b/modules/gapi/test/internal/gapi_int_backend_tests.cpp new file mode 100644 index 0000000000..67b6273138 --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_backend_tests.cpp @@ -0,0 +1,86 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "gapi_mock_kernels.hpp" + +#include "compiler/gmodel.hpp" +#include "compiler/gcompiler.hpp" + +namespace opencv_test { + +namespace { + +struct MockMeta +{ + static const char* name() { return "MockMeta"; } +}; + +class GMockBackendImpl final: public cv::gapi::GBackend::Priv +{ + virtual void unpackKernel(ade::Graph &, + const ade::NodeHandle &, + const cv::GKernelImpl &) override + { + // Do nothing here + } + + virtual EPtr compile(const ade::Graph &, + const cv::GCompileArgs &, + const std::vector &) const override + { + // Do nothing here as well + return {}; + } + + virtual void addBackendPasses(ade::ExecutionEngineSetupContext &ectx) override + { + ectx.addPass("transform", "set_mock_meta", [](ade::passes::PassContext &ctx) { + ade::TypedGraph me(ctx.graph); + for (const auto &nh : me.nodes()) + { + me.metadata(nh).set(MockMeta{}); + } + }); + } +}; + +static cv::gapi::GBackend mock_backend(std::make_shared()); + +GAPI_OCV_KERNEL(MockFoo, I::Foo) +{ + static void run(const cv::Mat &, cv::Mat &) { /*Do nothing*/ } + static cv::gapi::GBackend backend() { return mock_backend; } // FIXME: Must be removed +}; + +} // anonymous namespace + +TEST(GBackend, CustomPassesExecuted) +{ + cv::GMat in; + cv::GMat out = I::Foo::on(in); + cv::GComputation c(in, out); + + // Prepare compilation parameters manually + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::Size(32,32)}); + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(c, {in_meta}, cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + // Inspect the graph and verify the metadata written by Mock backend + ade::TypedGraph me(*graph); + EXPECT_LT(0u, static_cast(me.nodes().size())); + for (const auto &nh : me.nodes()) + { + EXPECT_TRUE(me.metadata(nh).contains()); + } +} + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_executor_tests.cpp b/modules/gapi/test/internal/gapi_int_executor_tests.cpp new file mode 100644 index 0000000000..20aad89b66 --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_executor_tests.cpp @@ -0,0 +1,83 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +namespace opencv_test +{ + +// FIXME: avoid code duplication +// The below graph and config is taken from ComplexIslands test suite +TEST(GExecutor, SmokeTest) +{ + cv::GMat in[2]; + cv::GMat tmp[4]; + cv::GScalar scl; + cv::GMat out[2]; + + tmp[0] = cv::gapi::bitwise_not(cv::gapi::bitwise_not(in[0])); + tmp[1] = cv::gapi::boxFilter(in[1], -1, cv::Size(3,3)); + tmp[2] = tmp[0] + tmp[1]; // FIXME: handle tmp[2] = tmp[0]+tmp[2] typo + scl = cv::gapi::sum(tmp[1]); + tmp[3] = cv::gapi::medianBlur(tmp[1], 3); + out[0] = tmp[2] + scl; + out[1] = cv::gapi::boxFilter(tmp[3], -1, cv::Size(3,3)); + + // isl0 #internal1 + // ........................... ......... + // (in1) -> NotNot ->(tmp0) --> Add ---------> (tmp2) --> AddC -------> (out1) + // :.....................^...: :..^....: + // : : + // : : + // #internal0 : : + // .....................:......... : + // (in2) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // :..........:..................: isl1 + // : .............................. + // `------------> Median -> (tmp3) --> Blur -------> (out2) + // :............................: + + cv::gapi::island("isl0", cv::GIn(in[0], tmp[1]), cv::GOut(tmp[2])); + cv::gapi::island("isl1", cv::GIn(tmp[1]), cv::GOut(out[1])); + + cv::Mat in_mat1 = cv::Mat::eye(32, 32, CV_8UC1); + cv::Mat in_mat2 = cv::Mat::eye(32, 32, CV_8UC1); + cv::Mat out_gapi[2]; + + // Run G-API: + cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .apply(cv::gin(in_mat1, in_mat2), cv::gout(out_gapi[0], out_gapi[1])); + + // Run OpenCV + cv::Mat out_ocv[2]; + { + cv::Mat ocv_tmp0; + cv::Mat ocv_tmp1; + cv::Mat ocv_tmp2; + cv::Mat ocv_tmp3; + cv::Scalar ocv_scl; + + ocv_tmp0 = in_mat1; // skip !(!) + cv::boxFilter(in_mat2, ocv_tmp1, -1, cv::Size(3,3)); + ocv_tmp2 = ocv_tmp0 + ocv_tmp1; + ocv_scl = cv::sum(ocv_tmp1); + cv::medianBlur(ocv_tmp1, ocv_tmp3, 3); + out_ocv[0] = ocv_tmp2 + ocv_scl; + cv::boxFilter(ocv_tmp3, out_ocv[1], -1, cv::Size(3,3)); + } + + EXPECT_EQ(0, cv::countNonZero(out_gapi[0] != out_ocv[0])); + EXPECT_EQ(0, cv::countNonZero(out_gapi[1] != out_ocv[1])); + + // FIXME: check that GIslandModel has more than 1 island (e.g. fusion + // with breakdown worked) +} + +// FIXME: Add explicit tests on GMat/GScalar/GArray being connectors +// between executed islands + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_garg_test.cpp b/modules/gapi/test/internal/gapi_int_garg_test.cpp new file mode 100644 index 0000000000..67696dbb03 --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_garg_test.cpp @@ -0,0 +1,100 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +namespace opencv_test { +// Tests on T/Kind matching //////////////////////////////////////////////////// +// {{ + +template +struct Expected +{ + using type = T; + static const constexpr cv::detail::ArgKind kind = Exp; +}; + +template +struct GArgKind: public ::testing::Test +{ + using Type = typename T::type; + const cv::detail::ArgKind Kind = T::kind; +}; + +// The reason here is to _manually_ list types and their kinds +// (and NOT reuse cv::detail::ArgKind::Traits<>, since it is a subject of testing) +using GArg_Test_Types = ::testing::Types + < + // G-API types + Expected + , Expected + , Expected, cv::detail::ArgKind::GARRAY> + , Expected, cv::detail::ArgKind::GARRAY> + , Expected, cv::detail::ArgKind::GARRAY> + , Expected, cv::detail::ArgKind::GARRAY> + + // Built-in types + , Expected + , Expected + , Expected + , Expected + , Expected + , Expected + , Expected, cv::detail::ArgKind::OPAQUE> + , Expected, cv::detail::ArgKind::OPAQUE> + >; + +TYPED_TEST_CASE(GArgKind, GArg_Test_Types); + +TYPED_TEST(GArgKind, LocalVar) +{ + typename TestFixture::Type val{}; + cv::GArg arg(val); + EXPECT_EQ(TestFixture::Kind, arg.kind); +} + +TYPED_TEST(GArgKind, ConstLocalVar) +{ + const typename TestFixture::Type val{}; + cv::GArg arg(val); + EXPECT_EQ(TestFixture::Kind, arg.kind); +} + +TYPED_TEST(GArgKind, RValue) +{ + cv::GArg arg = cv::GArg(typename TestFixture::Type()); + EXPECT_EQ(TestFixture::Kind, arg.kind); +} + +// }} +//////////////////////////////////////////////////////////////////////////////// + +TEST(GArg, HasWrap) +{ + static_assert(!cv::detail::has_custom_wrap::value, + "GMat has no custom marshalling logic"); + static_assert(!cv::detail::has_custom_wrap::value, + "GScalar has no custom marshalling logic"); + + static_assert(cv::detail::has_custom_wrap >::value, + "GArray has custom marshalling logic"); + static_assert(cv::detail::has_custom_wrap >::value, + "GArray has custom marshalling logic"); +} + +TEST(GArg, GArrayU) +{ + // Placing a GArray into GArg automatically strips it to GArrayU + cv::GArg arg1 = cv::GArg(cv::GArray()); + EXPECT_NO_THROW(arg1.get()); + + cv::GArg arg2 = cv::GArg(cv::GArray()); + EXPECT_NO_THROW(arg2.get()); +} + + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_gmetaarg_test.cpp b/modules/gapi/test/internal/gapi_int_gmetaarg_test.cpp new file mode 100644 index 0000000000..6dbf7778f0 --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_gmetaarg_test.cpp @@ -0,0 +1,136 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "api/gcomputation_priv.hpp" + +namespace opencv_test +{ + +TEST(GMetaArg, Traits_Is_Positive) +{ + using namespace cv::detail; + + static_assert(is_meta_descr::value, + "GScalarDesc is a meta description type"); + + static_assert(is_meta_descr::value, + "GMatDesc is a meta description type"); +} + +TEST(GMetaArg, Traits_Is_Negative) +{ + using namespace cv::detail; + + static_assert(!is_meta_descr::value, + "GCompileArgs is NOT a meta description type"); + + static_assert(!is_meta_descr::value, + "int is NOT a meta description type"); + + static_assert(!is_meta_descr::value, + "str::string is NOT a meta description type"); +} + +TEST(GMetaArg, Traits_Are_EntireList_Positive) +{ + using namespace cv::detail; + + static_assert(are_meta_descrs::value, + "GScalarDesc is a meta description type"); + + static_assert(are_meta_descrs::value, + "GMatDesc is a meta description type"); + + static_assert(are_meta_descrs::value, + "Both GMatDesc and GScalarDesc are meta types"); +} + +TEST(GMetaArg, Traits_Are_EntireList_Negative) +{ + using namespace cv::detail; + + static_assert(!are_meta_descrs::value, + "GCompileArgs is NOT among meta types"); + + static_assert(!are_meta_descrs::value, + "Both int and std::string is NOT among meta types"); + + static_assert(!are_meta_descrs::value, + "List of type is not valid for meta as there\'s int"); + + static_assert(!are_meta_descrs::value, + "List of type is not valid for meta as there\'s GCompileArgs"); +} + +TEST(GMetaArg, Traits_Are_ButLast_Positive) +{ + using namespace cv::detail; + + static_assert(are_meta_descrs_but_last::value, + "List is valid (int is ommitted)"); + + static_assert(are_meta_descrs_but_last::value, + "List is valid (GCompileArgs are omitted)"); +} + +TEST(GMetaArg, Traits_Are_ButLast_Negative) +{ + using namespace cv::detail; + + static_assert(!are_meta_descrs_but_last::value, + "Both int is NOT among meta types (std::string is omitted)"); + + static_assert(!are_meta_descrs_but_last::value, + "List of type is not valid for meta as there\'s two ints"); + + static_assert(!are_meta_descrs_but_last::value, + "List of type is not valid for meta as there\'s GCompileArgs"); +} + +TEST(GMetaArg, Can_Get_Metas_From_Input_Run_Args) +{ + cv::Mat m(3, 3, CV_8UC3); + cv::Scalar s; + std::vector v; + + GMatDesc m_desc; + GMetaArgs meta_args = descr_of(cv::gin(m, s, v)); + + EXPECT_EQ(meta_args.size(), 3u); + EXPECT_NO_THROW(m_desc = util::get(meta_args[0])); + EXPECT_NO_THROW(util::get(meta_args[1])); + EXPECT_NO_THROW(util::get(meta_args[2])); + + EXPECT_EQ(CV_8U, m_desc.depth); + EXPECT_EQ(3, m_desc.chan); + EXPECT_EQ(cv::gapi::own::Size(3, 3), m_desc.size); +} + +TEST(GMetaArg, Can_Get_Metas_From_Output_Run_Args) +{ + cv::Mat m(3, 3, CV_8UC3); + cv::Scalar s; + std::vector v; + + GMatDesc m_desc; + GRunArgsP out_run_args = cv::gout(m, s, v); + GMetaArg m_meta = descr_of(out_run_args[0]); + GMetaArg s_meta = descr_of(out_run_args[1]); + GMetaArg v_meta = descr_of(out_run_args[2]); + + EXPECT_NO_THROW(m_desc = util::get(m_meta)); + EXPECT_NO_THROW(util::get(s_meta)); + EXPECT_NO_THROW(util::get(v_meta)); + + EXPECT_EQ(CV_8U, m_desc.depth); + EXPECT_EQ(3, m_desc.chan); + EXPECT_EQ(cv::Size(3, 3), m_desc.size); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp b/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp new file mode 100644 index 0000000000..a815e0d22d --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp @@ -0,0 +1,364 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include // util::indexed + +#include "opencv2/gapi/gkernel.hpp" +#include "compiler/gmodelbuilder.hpp" +#include "compiler/gmodel.hpp" // RcDesc, GModel::init + +namespace opencv_test +{ + +namespace test +{ + +namespace +{ + cv::GMat unaryOp(cv::GMat m) + { + return cv::GCall(cv::GKernel{"gapi.test.unaryop", nullptr, { GShape::GMAT } }).pass(m).yield(0); + } + + cv::GMat binaryOp(cv::GMat m1, cv::GMat m2) + { + return cv::GCall(cv::GKernel{"gapi.test.binaryOp", nullptr, { GShape::GMAT } }).pass(m1, m2).yield(0); + } + + std::vector collectOperations(const cv::gimpl::GModel::Graph& gr) + { + std::vector ops; + for (const auto& nh : gr.nodes()) + { + if (gr.metadata(nh).get().t == cv::gimpl::NodeType::OP) + ops.push_back(nh); + } + return ops; + } + + ade::NodeHandle inputOf(cv::gimpl::GModel::Graph& gm, ade::NodeHandle nh, std::size_t port) + { + for (const auto& eh : nh->inEdges()) + { + if (gm.metadata(eh).get().port == port) + { + return eh->srcNode(); + } + } + util::throw_error(std::logic_error("port " + std::to_string(port) + " not found")); + } +} +}// namespace opencv_test::test + +TEST(GModelBuilder, Unroll_TestUnary) +{ + cv::GMat in; + cv::GMat out = test::unaryOp(in); + + auto unrolled = cv::gimpl::unrollExpr(cv::GIn(in).m_args, cv::GOut(out).m_args); + + EXPECT_EQ(1u, unrolled.all_ops.size()); // There is one operation + EXPECT_EQ(2u, unrolled.all_data.size()); // And two data objects (in, out) + + // TODO check what the operation is, and so on, and so on +} + +TEST(GModelBuilder, Unroll_TestUnaryOfUnary) +{ + cv::GMat in; + cv::GMat out = test::unaryOp(test::unaryOp(in)); + + auto unrolled = cv::gimpl::unrollExpr(cv::GIn(in).m_args, cv::GOut(out).m_args); + + EXPECT_EQ(2u, unrolled.all_ops.size()); // There're two operations + EXPECT_EQ(3u, unrolled.all_data.size()); // And three data objects (in, out) + + // TODO check what the operation is, and so on, and so on +} + +TEST(GModelBuilder, Unroll_Not_All_Protocol_Inputs_Are_Reached) +{ + cv::GMat in1, in2; // in1 -> unaryOp() -> u_op1 -> unaryOp() -> out + auto u_op1 = test::unaryOp(in1); // in2 -> unaryOp() -> u_op2 + auto u_op2 = test::unaryOp(in2); + auto out = test::unaryOp(u_op1); + + EXPECT_THROW(cv::gimpl::unrollExpr(cv::GIn(in1, in2).m_args, cv::GOut(out).m_args), std::logic_error); +} + +TEST(GModelBuilder, Unroll_Parallel_Path) +{ + cv::GMat in1, in2; // in1 -> unaryOp() -> out1 + auto out1 = test::unaryOp(in1); // in2 -> unaryOp() -> out2 + auto out2 = test::unaryOp(in2); + + auto unrolled = cv::gimpl::unrollExpr(cv::GIn(in1, in2).m_args, cv::GOut(out1, out2).m_args); + + EXPECT_EQ(unrolled.all_ops.size(), 2u); + EXPECT_EQ(unrolled.all_data.size(), 4u); +} + +TEST(GModelBuilder, Unroll_WithBranch) +{ + // in -> unaryOp() -> tmp -->unaryOp() -> out1 + // `---->unaryOp() -> out2 + + GMat in; + auto tmp = test::unaryOp(in); + auto out1 = test::unaryOp(tmp); + auto out2 = test::unaryOp(tmp); + + auto unrolled = cv::gimpl::unrollExpr(cv::GIn(in).m_args, cv::GOut(out1, out2).m_args); + + EXPECT_EQ(unrolled.all_ops.size(), 3u); + EXPECT_EQ(unrolled.all_data.size(), 4u); +} + +TEST(GModelBuilder, Build_Unary) +{ + cv::GMat in; + cv::GMat out = test::unaryOp(in); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + cv::gimpl::GModelBuilder(g).put(cv::GIn(in).m_args, cv::GOut(out).m_args); + + EXPECT_EQ(3u, static_cast(g.nodes().size())); // Generated graph should have three nodes + + // TODO: Check what the nodes are +} + +TEST(GModelBuilder, Constant_GScalar) +{ + // in -> addC()-----(GMat)---->mulC()-----(GMat)---->unaryOp()----out + // ^ ^ + // | | + // 3-------` c_s-------' + + cv::GMat in; + cv::GScalar c_s = 5; + auto out = test::unaryOp((in + 3) * c_s); // 3 converted to GScalar + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + auto proto_slots = cv::gimpl::GModelBuilder(g).put(cv::GIn(in).m_args, cv::GOut(out).m_args); + cv::gimpl::Protocol p; + std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots; + + auto in_nh = p.in_nhs.front(); + auto addC_nh = in_nh->outNodes().front(); + auto mulC_nh = addC_nh->outNodes().front()->outNodes().front(); + + ASSERT_TRUE(gm.metadata(addC_nh).get().t == cv::gimpl::NodeType::OP); + ASSERT_TRUE(gm.metadata(mulC_nh).get().t == cv::gimpl::NodeType::OP); + + auto s_3 = test::inputOf(gm, addC_nh, 1); + auto s_5 = test::inputOf(gm, mulC_nh, 1); + + EXPECT_EQ(9u, static_cast(g.nodes().size())); // 6 data nodes (1 -input, 1 output, 2 constant, 2 temp) and 3 op nodes + EXPECT_EQ(2u, static_cast(addC_nh->inNodes().size())); // in and 3 + EXPECT_EQ(2u, static_cast(mulC_nh->inNodes().size())); // addC output and c_s + EXPECT_EQ(3, (util::get(gm.metadata(s_3).get().arg))[0]); + EXPECT_EQ(5, (util::get(gm.metadata(s_5).get().arg))[0]); +} + +TEST(GModelBuilder, Check_Multiple_Outputs) +{ + // ------------------------------> r + // ' + // ' -----------> i_out1 + // ' ' + // in ----> split3() ---> g ---> integral() + // ' ' + // ' -----------> i_out2 + // ' + // '---------> b ---> unaryOp() ---> u_out + + cv::GMat in, r, g, b, i_out1, i_out2, u_out; + std::tie(r, g, b) = cv::gapi::split3(in); + std::tie(i_out1, i_out2) = cv::gapi::integral(g, 1, 1); + u_out = test::unaryOp(b); + + ade::Graph gr; + cv::gimpl::GModel::Graph gm(gr); + cv::gimpl::GModel::init(gm); + auto proto_slots = cv::gimpl::GModelBuilder(gr).put(cv::GIn(in).m_args, cv::GOut(r, i_out1, i_out2, u_out).m_args); + cv::gimpl::Protocol p; + std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots; + + EXPECT_EQ(4u, static_cast(p.out_nhs.size())); + EXPECT_EQ(0u, gm.metadata(p.out_nhs[0]->inEdges().front()).get().port); + EXPECT_EQ(0u, gm.metadata(p.out_nhs[1]->inEdges().front()).get().port); + EXPECT_EQ(1u, gm.metadata(p.out_nhs[2]->inEdges().front()).get().port); + EXPECT_EQ(0u, gm.metadata(p.out_nhs[3]->inEdges().front()).get().port); + for (const auto& it : ade::util::indexed(p.out_nhs)) + { + const auto& out_nh = ade::util::value(it); + + EXPECT_EQ(cv::gimpl::NodeType::DATA, gm.metadata(out_nh).get().t); + EXPECT_EQ(GShape::GMAT, gm.metadata(out_nh).get().shape); + } +} + +TEST(GModelBuilder, Unused_Outputs) +{ + cv::GMat in; + auto yuv_p = cv::gapi::split3(in); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + cv::gimpl::GModelBuilder(g).put(cv::GIn(in).m_args, cv::GOut(std::get<0>(yuv_p)).m_args); + + EXPECT_EQ(5u, static_cast(g.nodes().size())); // 1 input, 1 operation, 3 outputs +} + +TEST(GModelBuilder, Work_With_One_Channel_From_Split3) +{ + cv::GMat in, y, u, v; + std::tie(y, u, v) = cv::gapi::split3(in); + auto y_blur = cv::gapi::gaussianBlur(y, cv::Size(3, 3), 1); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + cv::gimpl::GModelBuilder(g).put(cv::GIn(in).m_args, cv::GOut(y_blur).m_args); + + EXPECT_EQ(7u, static_cast(g.nodes().size())); // 1 input, 2 operation, 3 nodes from split3, 1 output +} + +TEST(GModelBuilder, Add_Nodes_To_Unused_Nodes) +{ + cv::GMat in, y, u, v; + std::tie(y, u, v) = cv::gapi::split3(in); + auto y_blur = cv::gapi::gaussianBlur(y, cv::Size(3, 3), 1); + // unused nodes + auto u_blur = cv::gapi::gaussianBlur(y, cv::Size(3, 3), 1); + auto v_blur = cv::gapi::gaussianBlur(y, cv::Size(3, 3), 1); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + cv::gimpl::GModelBuilder(g).put(cv::GIn(in).m_args, cv::GOut(y_blur).m_args); + + EXPECT_EQ(7u, static_cast(g.nodes().size())); // 1 input, 2 operation, 3 nodes from split3, 1 output +} + +TEST(GModelBuilder, Unlisted_Inputs) +{ + // in1 -> binaryOp() -> out + // ^ + // | + // in2 ----' + + cv::GMat in1, in2; + auto out = test::binaryOp(in1, in2); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + // add required 2 inputs but pass 1 + EXPECT_THROW(cv::gimpl::GModelBuilder(g).put(cv::GIn(in1).m_args, cv::GOut(out).m_args), std::logic_error); +} + +TEST(GModelBuilder, Unroll_No_Link_Between_In_And_Out) +{ + // in -> unaryOp() -> u_op + // other -> unaryOp() -> out + + cv::GMat in, other; + auto u_op = test::unaryOp(in); + auto out = test::unaryOp(other); + + EXPECT_THROW(cv::gimpl::unrollExpr(cv::GIn(in).m_args, cv::GOut(out).m_args), std::logic_error); +} + + +TEST(GModel_builder, Check_Binary_Op) +{ + // in1 -> binaryOp() -> out + // ^ + // | + // in2 -----' + + cv::GMat in1, in2; + auto out = test::binaryOp(in1, in2); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + auto proto_slots = cv::gimpl::GModelBuilder(g).put(cv::GIn(in1, in2).m_args, cv::GOut(out).m_args); + + cv::gimpl::Protocol p; + std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots; + auto ops = test::collectOperations(g); + + EXPECT_EQ(1u, ops.size()); + EXPECT_EQ("gapi.test.binaryOp", gm.metadata(ops.front()).get().k.name); + EXPECT_EQ(2u, static_cast(ops.front()->inEdges().size())); + EXPECT_EQ(1u, static_cast(ops.front()->outEdges().size())); + EXPECT_EQ(1u, static_cast(ops.front()->outNodes().size())); +} + +TEST(GModelBuilder, Add_Operation_With_Two_Out_One_Time) +{ + // in -> integral() --> out_b1 -> unaryOp() -> out1 + // | + // '-------> out_b2 -> unaryOp() -> out2 + + cv::GMat in, out_b1, out_b2; + std::tie(out_b1, out_b2) = cv::gapi::integral(in, 1, 1); + auto out1 = test::unaryOp(out_b1); + auto out2 = test::unaryOp(out_b1); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + auto proto_slots = cv::gimpl::GModelBuilder(g).put(cv::GIn(in).m_args, cv::GOut(out1, out2).m_args); + + auto ops = test::collectOperations(gm); + + cv::gimpl::Protocol p; + std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots; + auto integral_nh = p.in_nhs.front()->outNodes().front(); + + EXPECT_EQ(3u, ops.size()); + EXPECT_EQ("org.opencv.core.matrixop.integral", gm.metadata(integral_nh).get().k.name); + EXPECT_EQ(1u, static_cast(integral_nh->inEdges().size())); + EXPECT_EQ(2u, static_cast(integral_nh->outEdges().size())); + EXPECT_EQ(2u, static_cast(integral_nh->outNodes().size())); +} +TEST(GModelBuilder, Add_Operation_With_One_Out_One_Time) +{ + // in1 -> binaryOp() -> b_out -> unaryOp() -> out1 + // ^ | + // | | + // in2 ------- '----> unaryOp() -> out2 + + cv::GMat in1, in2; + auto b_out = test::binaryOp(in1, in2); + auto out1 = test::unaryOp(b_out); + auto out2 = test::unaryOp(b_out); + + ade::Graph g; + cv::gimpl::GModel::Graph gm(g); + cv::gimpl::GModel::init(gm); + auto proto_slots = cv::gimpl::GModelBuilder(g).put(cv::GIn(in1, in2).m_args, cv::GOut(out1, out2).m_args); + cv::gimpl::Protocol p; + std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots; + cv::gimpl::GModel::Graph gr(g); + auto binaryOp_nh = p.in_nhs.front()->outNodes().front(); + + EXPECT_EQ(2u, static_cast(binaryOp_nh->inEdges().size())); + EXPECT_EQ(1u, static_cast(binaryOp_nh->outEdges().size())); + EXPECT_EQ(8u, static_cast(g.nodes().size())); +} +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_island_fusion_tests.cpp b/modules/gapi/test/internal/gapi_int_island_fusion_tests.cpp new file mode 100644 index 0000000000..91e55bed7d --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_island_fusion_tests.cpp @@ -0,0 +1,527 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "compiler/transactions.hpp" + +#include "gapi_mock_kernels.hpp" + +#include "compiler/gmodel.hpp" +#include "compiler/gislandmodel.hpp" +#include "compiler/gcompiler.hpp" + +namespace opencv_test +{ + +TEST(IslandFusion, TwoOps_OneIsland) +{ + namespace J = Jupiter; // see mock_kernels.cpp + + // Define a computation: + // + // (in) -> J::Foo1 -> (tmp0) -> J::Foo2 -> (out) + // : : + // : "island0" : + // :<----------------------------->: + + cv::GMat in; + cv::GMat tmp0 = I::Foo::on(in); + cv::GMat out = I::Foo::on(tmp0); + cv::GComputation cc(in, out); + + // Prepare compilation parameters manually + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}); + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, {in_meta}, cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + // Inspect the graph and verify the islands configuration + cv::gimpl::GModel::ConstGraph gm(*graph); + + auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in); + auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp0); + auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out); + + // in/out mats shouldn't be assigned to any Island + EXPECT_FALSE(gm.metadata(in_nh ).contains()); + EXPECT_FALSE(gm.metadata(out_nh).contains()); + + // Since tmp is surrounded by two J kernels, tmp should be assigned + // to island J + EXPECT_TRUE(gm.metadata(tmp_nh).contains()); +} + +TEST(IslandFusion, TwoOps_TwoIslands) +{ + namespace J = Jupiter; // see mock_kernels.cpp + namespace S = Saturn; // see mock_kernels.cpp + + // Define a computation: + // + // (in) -> J::Foo --> (tmp0) -> S::Bar --> (out) + // : : -> : + // : : : : + // :<-------->: :<-------->: + + cv::GMat in; + cv::GMat tmp0 = I::Foo::on(in); + cv::GMat out = I::Bar::on(tmp0, tmp0); + cv::GComputation cc(in, out); + + // Prepare compilation parameters manually + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}); + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, {in_meta}, cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + // Inspect the graph and verify the islands configuration + cv::gimpl::GModel::ConstGraph gm(*graph); + + auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in); + auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp0); + auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out); + + // in/tmp/out mats shouldn't be assigned to any Island + EXPECT_FALSE(gm.metadata(in_nh ).contains()); + EXPECT_FALSE(gm.metadata(out_nh).contains()); + EXPECT_FALSE(gm.metadata(tmp_nh).contains()); + + auto isl_model = gm.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + // There should be two islands in the GIslandModel + const auto is_island = [&](ade::NodeHandle nh) { + return (cv::gimpl::NodeKind::ISLAND + == gim.metadata(nh).get().k); + }; + const std::size_t num_isl = std::count_if(gim.nodes().begin(), + gim.nodes().end(), + is_island); + EXPECT_EQ(2u, num_isl); + + auto isl_foo_nh = cv::gimpl::GIslandModel::producerOf(gim, tmp_nh); + auto isl_bar_nh = cv::gimpl::GIslandModel::producerOf(gim, out_nh); + ASSERT_NE(nullptr, isl_foo_nh); + ASSERT_NE(nullptr, isl_bar_nh); + + // Islands should be different + auto isl_foo_obj = gim.metadata(isl_foo_nh).get().object; + auto isl_bar_obj = gim.metadata(isl_bar_nh).get().object; + EXPECT_FALSE(isl_foo_obj == isl_bar_obj); +} + +TEST(IslandFusion, ConsumerHasTwoInputs) +{ + namespace J = Jupiter; // see mock_kernels.cpp + + // Define a computation: island + // ............................ + // (in0) ->:J::Foo -> (tmp) -> S::Bar :--> (out) + // :....................^.....: + // | + // (in1) -----------------------` + // + + // Check that island is build correctly, when consumer has two inputs + + GMat in[2]; + GMat tmp = I::Foo::on(in[0]); + GMat out = I::Bar::on(tmp, in[1]); + + cv::GComputation cc(cv::GIn(in[0], in[1]), cv::GOut(out)); + + // Prepare compilation parameters manually + cv::GMetaArgs in_metas = {GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}), + GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)})}; + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, std::move(in_metas), cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + cv::gimpl::GModel::ConstGraph gm(*graph); + + auto in0_nh = cv::gimpl::GModel::dataNodeOf(gm, in[0]); + auto in1_nh = cv::gimpl::GModel::dataNodeOf(gm, in[1]); + auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp); + auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out); + + EXPECT_FALSE(gm.metadata(in0_nh ).contains()); + EXPECT_FALSE(gm.metadata(in1_nh ).contains()); + EXPECT_FALSE(gm.metadata(out_nh).contains()); + EXPECT_TRUE(gm.metadata(tmp_nh).contains()); + + auto isl_model = gm.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + const auto is_island = [&](ade::NodeHandle nh) { + return (cv::gimpl::NodeKind::ISLAND + == gim.metadata(nh).get().k); + }; + const std::size_t num_isl = std::count_if(gim.nodes().begin(), + gim.nodes().end(), + is_island); + EXPECT_EQ(1u, num_isl); + + auto isl_nh = cv::gimpl::GIslandModel::producerOf(gim, out_nh); + auto isl_obj = gim.metadata(isl_nh).get().object; + + EXPECT_TRUE(ade::util::contains(isl_obj->contents(), tmp_nh)); + + EXPECT_EQ(2u, static_cast(isl_nh->inNodes().size())); + EXPECT_EQ(1u, static_cast(isl_nh->outNodes().size())); +} + +TEST(IslandFusion, DataNodeUsedDifferentBackend) +{ + // Define a computation: + // + // internal isl isl0 + // ........................... + // (in1) -> :J::Foo--> (tmp) -> J::Foo: --> (out0) + // :............|............: + // | ........ + // `---->:S::Baz: --> (out1) + // :......: + + // Check that the node was not dropped out of the island + // because it is used by the kernel from another backend + + namespace J = Jupiter; + namespace S = Saturn; + + cv::GMat in, tmp, out0; + cv::GScalar out1; + tmp = I::Foo::on(in); + out0 = I::Foo::on(tmp); + out1 = I::Baz::on(tmp); + + cv::GComputation cc(cv::GIn(in), cv::GOut(out0, out1)); + + // Prepare compilation parameters manually + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}); + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, {in_meta}, cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + // Inspect the graph and verify the islands configuration + cv::gimpl::GModel::ConstGraph gm(*graph); + + auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in); + auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp); + auto out0_nh = cv::gimpl::GModel::dataNodeOf(gm, out0); + auto out1_nh = cv::gimpl::GModel::dataNodeOf(gm, out1); + + EXPECT_TRUE(gm.metadata(tmp_nh).contains()); + + auto isl_model = gm.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + auto isl_nh = cv::gimpl::GIslandModel::producerOf(gim, tmp_nh); + auto isl_obj = gim.metadata(isl_nh).get().object; + + EXPECT_TRUE(ade::util::contains(isl_obj->contents(), tmp_nh)); + + EXPECT_EQ(2u, static_cast(isl_nh->outNodes().size())); + EXPECT_EQ(7u, static_cast(gm.nodes().size())); + EXPECT_EQ(6u, static_cast(gim.nodes().size())); +} + +TEST(IslandFusion, LoopBetweenDifferentBackends) +{ + // Define a computation: + // + // + // ............................. + // (in) -> :J::Baz -> (tmp0) -> J::Quux: -> (out0) + // | :............|..........^.... + // | ........ | | ........ + // `---->:S::Foo: `----------|-------->:S::Qux:-> (out1) + // :....|.: | :....^.: + // | | | + // `-------------- (tmp1) -----------` + + // Kernels S::Foo and S::Qux cannot merge, because there will be a cycle between islands + + namespace J = Jupiter; + namespace S = Saturn; + + cv::GScalar tmp0; + cv::GMat in, tmp1, out0, out1; + + tmp0 = I::Baz::on(in); + tmp1 = I::Foo::on(in); + out1 = I::Qux::on(tmp1, tmp0); + out0 = I::Quux::on(tmp0, tmp1); + + cv::GComputation cc(cv::GIn(in), cv::GOut(out1, out0)); + + // Prepare compilation parameters manually + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}); + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, {in_meta}, cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + cv::gimpl::GModel::ConstGraph gm(*graph); + auto isl_model = gm.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in); + auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp0); + auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp1); + auto out0_nh = cv::gimpl::GModel::dataNodeOf(gm, out0); + auto out1_nh = cv::gimpl::GModel::dataNodeOf(gm, out1); + + EXPECT_FALSE(gm.metadata(in_nh ).contains()); + EXPECT_FALSE(gm.metadata(out0_nh).contains()); + EXPECT_FALSE(gm.metadata(out1_nh).contains()); + // The node does not belong to the island so as not to form a cycle + EXPECT_FALSE(gm.metadata(tmp1_nh).contains()); + + EXPECT_TRUE(gm.metadata(tmp0_nh).contains()); + + // There should be three islands in the GIslandModel + const auto is_island = [&](ade::NodeHandle nh) { + return (cv::gimpl::NodeKind::ISLAND + == gim.metadata(nh).get().k); + }; + const std::size_t num_isl = std::count_if(gim.nodes().begin(), + gim.nodes().end(), + is_island); + EXPECT_EQ(3u, num_isl); +} + +TEST(IslandsFusion, PartionOverlapUserIsland) +{ + // Define a computation: + // + // internal isl isl0 + // ........ ........ + // (in0) -> :J::Foo:--> (tmp) ->:S::Bar: --> (out) + // :......: :......: + // ^ + // | + // (in1) --------------------------` + + // Check that internal islands does't overlap user island + + namespace J = Jupiter; + namespace S = Saturn; + + GMat in[2]; + GMat tmp = I::Foo::on(in[0]); + GMat out = I::Bar::on(tmp, in[1]); + + cv::gapi::island("isl0", cv::GIn(tmp, in[1]), cv::GOut(out)); + cv::GComputation cc(cv::GIn(in[0], in[1]), cv::GOut(out)); + + // Prepare compilation parameters manually + cv::GMetaArgs in_metas = {GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}), + GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)})}; + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, std::move(in_metas), cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + cv::gimpl::GModel::ConstGraph gm(*graph); + auto isl_model = gm.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + auto in0_nh = cv::gimpl::GModel::dataNodeOf(gm, in[0]); + auto in1_nh = cv::gimpl::GModel::dataNodeOf(gm, in[1]); + auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp); + auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out); + + auto foo_nh = cv::gimpl::GIslandModel::producerOf(gim, tmp_nh); + auto foo_obj = gim.metadata(foo_nh).get().object; + + auto bar_nh = cv::gimpl::GIslandModel::producerOf(gim, out_nh); + auto bar_obj = gim.metadata(bar_nh).get().object; + + EXPECT_FALSE(gm.metadata(in0_nh ).contains()); + EXPECT_FALSE(gm.metadata(in1_nh ).contains()); + EXPECT_FALSE(gm.metadata(out_nh).contains()); + EXPECT_FALSE(gm.metadata(tmp_nh).contains()); + EXPECT_FALSE(foo_obj->is_user_specified()); + EXPECT_TRUE(bar_obj->is_user_specified()); +} + +TEST(IslandsFusion, DISABLED_IslandContainsDifferentBackends) +{ + // Define a computation: + // + // isl0 + // ............................ + // (in0) -> :J::Foo:--> (tmp) -> S::Bar: --> (out) + // :..........................: + // ^ + // | + // (in1) --------------------------` + + // Try create island contains different backends + + namespace J = Jupiter; + namespace S = Saturn; + + GMat in[2]; + GMat tmp = I::Foo::on(in[0]); + GMat out = I::Bar::on(tmp, in[1]); + + cv::gapi::island("isl0", cv::GIn(in[0], in[1]), cv::GOut(out)); + cv::GComputation cc(cv::GIn(in[0], in[1]), cv::GOut(out)); + + // Prepare compilation parameters manually + cv::GMetaArgs in_metas = {GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}), + GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)})}; + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, std::move(in_metas), cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + EXPECT_ANY_THROW(compiler.runPasses(*graph)); +} + +TEST(IslandFusion, WithLoop) +{ + namespace J = Jupiter; // see mock_kernels.cpp + + // Define a computation: + // + // (in) -> J::Foo --> (tmp0) -> J::Foo --> (tmp1) -> J::Qux -> (out) + // : ^ + // '--> J::Baz --> (scl0) --' + // + // The whole thing should be merged to a single island + // There's a cycle warning if Foo/Foo/Qux are merged first + // Then this island both produces data for Baz and consumes data + // from Baz. This is a cycle and it should be avoided by the merging code. + // + cv::GMat in; + cv::GMat tmp0 = I::Foo::on(in); + cv::GMat tmp1 = I::Foo::on(tmp0); + cv::GScalar scl0 = I::Baz::on(tmp0); + cv::GMat out = I::Qux::on(tmp1, scl0); + cv::GComputation cc(in, out); + + // Prepare compilation parameters manually + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::gapi::own::Size(32,32)}); + const auto pkg = cv::gapi::kernels(); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(cc, {in_meta}, cv::compile_args(pkg)); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + // Inspect the graph and verify the islands configuration + cv::gimpl::GModel::ConstGraph gm(*graph); + + auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in); + auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp0); + auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp1); + auto scl0_nh = cv::gimpl::GModel::dataNodeOf(gm, scl0); + auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out); + + // in/out mats shouldn't be assigned to any Island + EXPECT_FALSE(gm.metadata(in_nh ).contains()); + EXPECT_FALSE(gm.metadata(out_nh).contains()); + + // tmp0/tmp1/scl should be assigned to island + EXPECT_TRUE(gm.metadata(tmp0_nh).contains()); + EXPECT_TRUE(gm.metadata(tmp1_nh).contains()); + EXPECT_TRUE(gm.metadata(scl0_nh).contains()); + + // Check that there's a single island object and it contains all + // that data object handles + + cv::gimpl::GModel::ConstGraph cg(*graph); + auto isl_model = cg.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + const auto is_island = [&](ade::NodeHandle nh) { + return (cv::gimpl::NodeKind::ISLAND + == gim.metadata(nh).get().k); + }; + const std::size_t num_isl = std::count_if(gim.nodes().begin(), + gim.nodes().end(), + is_island); + EXPECT_EQ(1u, num_isl); + + auto isl_nh = cv::gimpl::GIslandModel::producerOf(gim, out_nh); + auto isl_obj = gim.metadata(isl_nh).get().object; + EXPECT_TRUE(ade::util::contains(isl_obj->contents(), tmp0_nh)); + EXPECT_TRUE(ade::util::contains(isl_obj->contents(), tmp1_nh)); + EXPECT_TRUE(ade::util::contains(isl_obj->contents(), scl0_nh)); +} + +TEST(IslandFusion, Regression_ShouldFuseAll) +{ + // Initially the merge procedure didn't work as expected and + // stopped fusion even if it could be continued (e.g. full + // GModel graph could be fused into a single GIsland node). + // Example of this is custom RGB 2 YUV pipeline as shown below: + + cv::GMat r, g, b; + cv::GMat y = 0.299f*r + 0.587f*g + 0.114f*b; + cv::GMat u = 0.492f*(b - y); + cv::GMat v = 0.877f*(r - y); + + cv::GComputation customCvt({r, g, b}, {y, u, v}); + + const auto in_meta = cv::GMetaArg(cv::GMatDesc{CV_8U,1,cv::Size(32,32)}); + + // Directly instantiate G-API graph compiler and run partial compilation + cv::gimpl::GCompiler compiler(customCvt, {in_meta,in_meta,in_meta}, cv::compile_args()); + cv::gimpl::GCompiler::GPtr graph = compiler.generateGraph(); + compiler.runPasses(*graph); + + cv::gimpl::GModel::ConstGraph cg(*graph); + auto isl_model = cg.metadata().get().model; + cv::gimpl::GIslandModel::ConstGraph gim(*isl_model); + + std::vector data_nhs; + std::vector isl_nhs; + for (auto &&nh : gim.nodes()) + { + if (gim.metadata(nh).contains()) + isl_nhs.push_back(std::move(nh)); + else if (gim.metadata(nh).contains()) + data_nhs.push_back(std::move(nh)); + else FAIL() << "GIslandModel node with unexpected metadata type"; + } + + EXPECT_EQ(6u, data_nhs.size()); // 3 input nodes + 3 output nodes + EXPECT_EQ(1u, isl_nhs.size()); // 1 island +} + +// FIXME: add more tests on mixed (hetero) graphs +// ADE-222, ADE-223 + +// FIXME: add test on combination of user-specified island +// which should be heterogeneous (based on kernel availability) +// but as we don't support this, compilation should fail + +// FIXME: add tests on automatic inferred islands which are +// connected via 1) gmat 2) gscalar 3) garray, +// check the case with executor +// check the case when this 1/2/3 interim object is also gcomputation output + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_island_tests.cpp b/modules/gapi/test/internal/gapi_int_island_tests.cpp new file mode 100644 index 0000000000..09f1880322 --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_island_tests.cpp @@ -0,0 +1,653 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "compiler/gmodel.hpp" +#include "compiler/gcompiled_priv.hpp" + +namespace opencv_test +{ + +//////////////////////////////////////////////////////////////////////////////// +// Tests on a plain graph +// +// (in) -> Blur1 -> (tmp0) -> Blur2 -> (tmp1) -> Blur3 -> (tmp2) -> Blur4 -> (out) +// +namespace +{ + struct PlainIslandsFixture + { + cv::GMat in; + cv::GMat tmp[3]; + cv::GMat out; + + PlainIslandsFixture() + { + tmp[0] = cv::gapi::boxFilter(in, -1, cv::Size(3,3)); + tmp[1] = cv::gapi::boxFilter(tmp[0], -1, cv::Size(3,3)); + tmp[2] = cv::gapi::boxFilter(tmp[1], -1, cv::Size(3,3)); + out = cv::gapi::boxFilter(tmp[2], -1, cv::Size(3,3)); + } + }; + + struct Islands: public ::testing::Test, public PlainIslandsFixture {}; + + using GIntArray = GArray; + + G_TYPED_KERNEL(CreateMatWithDiag, , "test.array.create_mat_with_diag") + { + static GMatDesc outMeta(const GArrayDesc&) { return cv::GMatDesc{CV_32S, 1,{3, 3}}; } + }; + + GAPI_OCV_KERNEL(CreateMatWithDiagImpl, CreateMatWithDiag) + { + static void run(const std::vector &in, cv::Mat& out) + { + auto size = static_cast(in.size()); + out = Mat::zeros(size, size, CV_32SC1); + for(int i = 0; i < out.rows; i++) + { + auto* row = out.ptr(i); + row[i] = in[i]; + } + } + }; + + G_TYPED_KERNEL(Mat2Array, , "test.array.mat2array") + { + static GArrayDesc outMeta(const GMatDesc&) { return empty_array_desc(); } + }; + + GAPI_OCV_KERNEL(Mat2ArrayImpl, Mat2Array) + { + static void run(const cv::Mat& in, std::vector &out) + { + GAPI_Assert(in.depth() == CV_32S && in.isContinuous()); + out.reserve(in.cols * in.rows); + out.assign((int*)in.datastart, (int*)in.dataend); + } + }; +} + +TEST_F(Islands, SmokeTest) +{ + // (in) -> Blur1 -> (tmp0) -> Blur2 -> (tmp1) -> Blur3 -> (tmp2) -> Blur4 -> (out) + // : "test" : + // :<------------------------->: + cv::gapi::island("test", cv::GIn(tmp[0]), cv::GOut(tmp[2])); + auto cc = cv::GComputation(in, out).compile(cv::GMatDesc{CV_8U,1,{640,480}}); + + const auto &gm = cc.priv().model(); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + + // tmp1 and tmp3 is not a part of any island + EXPECT_FALSE(gm.metadata(tmp0_nh).contains()); + EXPECT_FALSE(gm.metadata(tmp2_nh).contains()); + + // tmp2 is part of "test" island + EXPECT_TRUE(gm.metadata(tmp1_nh).contains()); + EXPECT_EQ("test", gm.metadata(tmp1_nh).get().island); +} + +TEST_F(Islands, TwoIslands) +{ + // (in) -> Blur1 -> (tmp0) -> Blur2 -> (tmp1) -> Blur3 -> (tmp2) -> Blur4 -> (out) + // : "test1" : : "test2" : + // :<---------------------------->: :<---------------------------------> + EXPECT_NO_THROW(cv::gapi::island("test1", cv::GIn(in), cv::GOut(tmp[1]))); + EXPECT_NO_THROW(cv::gapi::island("test2", cv::GIn(tmp[1]), cv::GOut(out))); + + auto cc = cv::GComputation(in, out).compile(cv::GMatDesc{CV_8U,1,{640,480}}); + const auto &gm = cc.priv().model(); + const auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + const auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out); + + // Only tmp0 and tmp2 should be listed in islands. + EXPECT_TRUE (gm.metadata(tmp0_nh).contains()); + EXPECT_TRUE (gm.metadata(tmp2_nh).contains()); + EXPECT_FALSE(gm.metadata(in_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp1_nh).contains()); + EXPECT_FALSE(gm.metadata(out_nh) .contains()); + + EXPECT_EQ("test1", gm.metadata(tmp0_nh).get().island); + EXPECT_EQ("test2", gm.metadata(tmp2_nh).get().island); +} + +// FIXME: Disabled since currently merge procedure merges two into one +// succesfully +TEST_F(Islands, DISABLED_Two_Islands_With_Same_Name_Should_Fail) +{ + // (in) -> Blur1 -> (tmp0) -> Blur2 -> (tmp1) -> Blur3 -> (tmp2) -> Blur4 -> (out) + // : "test1" : : "test1" : + // :<---------------------------->: :<---------------------------------> + + EXPECT_NO_THROW(cv::gapi::island("test1", cv::GIn(in), cv::GOut(tmp[1]))); + EXPECT_NO_THROW(cv::gapi::island("test1", cv::GIn(tmp[1]), cv::GOut(out))); + + EXPECT_ANY_THROW(cv::GComputation(in, out).compile(cv::GMatDesc{CV_8U,1,{640,480}})); +} + + +// (in) -> Blur1 -> (tmp0) -> Blur2 -> (tmp1) -> Blur3 -> (tmp2) -> Blur4 -> (out) +// : "test1": : : +// :<----------------:----------->: : +// : : +// : "test2" : +// :<------------------------->: +TEST_F(Islands, OverlappingIslands1) +{ + EXPECT_NO_THROW (cv::gapi::island("test1", cv::GIn(in), cv::GOut(tmp[1]))); + EXPECT_ANY_THROW(cv::gapi::island("test2", cv::GIn(tmp[0]), cv::GOut(tmp[2]))); +} + +TEST_F(Islands, OverlappingIslands2) +{ + EXPECT_NO_THROW (cv::gapi::island("test2", cv::GIn(tmp[0]), cv::GOut(tmp[2]))); + EXPECT_ANY_THROW(cv::gapi::island("test1", cv::GIn(in), cv::GOut(tmp[1]))); +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests on a complex graph +// +// (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) +// ^ ^ +// (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' +// : +// `------------> Median -> (tmp3) --> Blur -------> (out1) +// +namespace +{ + struct ComplexIslandsFixture + { + cv::GMat in[2]; + cv::GMat tmp[4]; + cv::GScalar scl; + cv::GMat out[2]; + + ComplexIslandsFixture() + { + tmp[0] = cv::gapi::bitwise_not(in[0]); + tmp[1] = cv::gapi::boxFilter(in[1], -1, cv::Size(3,3)); + tmp[2] = tmp[0] + tmp[1]; // FIXME: handle tmp[2] = tmp[0]+tmp[2] typo + scl = cv::gapi::sum(tmp[1]); + tmp[3] = cv::gapi::medianBlur(tmp[1], 3); + out[0] = tmp[2] + scl; + out[1] = cv::gapi::boxFilter(tmp[3], -1, cv::Size(3,3)); + } + }; + + struct ComplexIslands: public ::testing::Test, public ComplexIslandsFixture {}; +} // namespace + +TEST_F(ComplexIslands, SmokeTest) +{ + // isl0 #internal1 + // ........................... ........ + // (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) + // :............ ........^...: :.^....: + // ... : : + // (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // : isl1 + // : .............................. + // `------------> Median -> (tmp3) --> Blur -------> (out1) + // :............................: + + cv::gapi::island("isl0", cv::GIn(in[0], tmp[1]), cv::GOut(tmp[2])); + cv::gapi::island("isl1", cv::GIn(tmp[1]), cv::GOut(out[1])); + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + const auto &gm = cc.priv().model(); + const auto in0_nh = cv::gimpl::GModel::dataNodeOf(gm, in[0]); + const auto in1_nh = cv::gimpl::GModel::dataNodeOf(gm, in[1]); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + const auto tmp3_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[3]); + const auto scl_nh = cv::gimpl::GModel::dataNodeOf(gm, scl); + const auto out0_nh = cv::gimpl::GModel::dataNodeOf(gm, out[0]); + const auto out1_nh = cv::gimpl::GModel::dataNodeOf(gm, out[1]); + + // tmp0, tmp3 are in islands, others are not + EXPECT_TRUE(gm.metadata(tmp0_nh) .contains()); // isl0 + EXPECT_TRUE(gm.metadata(tmp3_nh) .contains()); // isl1 + EXPECT_FALSE(gm.metadata(in0_nh) .contains()); // (input is never fused) + EXPECT_FALSE(gm.metadata(in1_nh) .contains()); // (input is never fused) + EXPECT_TRUE (gm.metadata(tmp1_nh).contains()); // + EXPECT_FALSE(gm.metadata(tmp2_nh).contains()); // #not fused as cycle-causing# + EXPECT_FALSE(gm.metadata(scl_nh) .contains()); // #not fused as cycle-causing# + EXPECT_FALSE(gm.metadata(out0_nh).contains()); // (output is never fused) + EXPECT_FALSE(gm.metadata(out1_nh).contains()); // (output is never fused) + + EXPECT_EQ("isl0", gm.metadata(tmp0_nh).get().island); + EXPECT_EQ("isl1", gm.metadata(tmp3_nh).get().island); + + EXPECT_NE("isl0", gm.metadata(tmp1_nh).get().island); + EXPECT_NE("isl1", gm.metadata(tmp1_nh).get().island); + + // FIXME: Add a test with same graph for Fusion and check GIslandModel +} + +TEST_F(ComplexIslands, DistinictIslandsWithSameName) +{ + // isl0 + // ........................... + // (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) + // :............ ........^...: ^ + // ... : : + // (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // : isl0 + // : .............................. + // `------------> Median -> (tmp3) --> Blur -------> (out1) + // :............................: + + cv::gapi::island("isl0", cv::GIn(in[0], tmp[1]), cv::GOut(tmp[2])); + cv::gapi::island("isl0", cv::GIn(tmp[1]), cv::GOut(out[1])); + + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])); + + EXPECT_ANY_THROW(cc.compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}})); +} + +TEST_F(ComplexIslands, FullGraph) +{ + cv::gapi::island("isl0", cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])); + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + const auto &gm = cc.priv().model(); + std::vector handles_inside = { + cv::gimpl::GModel::dataNodeOf(gm, tmp[0]), + cv::gimpl::GModel::dataNodeOf(gm, tmp[1]), + cv::gimpl::GModel::dataNodeOf(gm, tmp[2]), + cv::gimpl::GModel::dataNodeOf(gm, tmp[3]), + cv::gimpl::GModel::dataNodeOf(gm, scl), + }; + std::vector handles_outside = { + cv::gimpl::GModel::dataNodeOf(gm, in[0]), + cv::gimpl::GModel::dataNodeOf(gm, in[1]), + cv::gimpl::GModel::dataNodeOf(gm, out[0]), + cv::gimpl::GModel::dataNodeOf(gm, out[1]), + }; + + for (auto nh_inside : handles_inside) + { + EXPECT_EQ("isl0", gm.metadata(nh_inside).get().island); + } + for (auto nh_outside : handles_outside) + { + EXPECT_FALSE(gm.metadata(nh_outside).contains()); + } +} + +TEST_F(ComplexIslands, ViaScalar) +{ + // + // .........................................#internal0. + // (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) + // :....................^.........................^...: + // : : + // .....................:.........(isl0). : + // (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // :..........:.........................: + // : + // : ..................#internal1. + // `------------> Median -> (tmp3) --> Blur -------> (out1) + // :...........................: + + cv::gapi::island("isl0", cv::GIn(in[1]), cv::GOut(scl)); + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + const auto &gm = cc.priv().model(); + + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + const auto tmp3_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[3]); + + EXPECT_NE("isl0", gm.metadata(tmp0_nh).get().island); // + EXPECT_EQ("isl0", gm.metadata(tmp1_nh).get().island); // isl0 + EXPECT_NE("isl0", gm.metadata(tmp2_nh).get().island); // + EXPECT_NE("isl0", gm.metadata(tmp3_nh).get().island); // + + std::vector handles_outside = { + cv::gimpl::GModel::dataNodeOf(gm, in[0]), + cv::gimpl::GModel::dataNodeOf(gm, in[1]), + cv::gimpl::GModel::dataNodeOf(gm, scl), + cv::gimpl::GModel::dataNodeOf(gm, out[0]), + cv::gimpl::GModel::dataNodeOf(gm, out[1]), + }; + for (auto nh_outside : handles_outside) + { + EXPECT_FALSE(gm.metadata(nh_outside).contains()); + } +} + +TEST_F(ComplexIslands, BorderDataIsland) +{ + // .................................(isl0).. + // : : + // (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) + // : ^ : ^ + // : : : : + // (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // :...........:...........................: + // : : : + // : : :.........................................(isl1).. + // : `------------> Median -> (tmp3) --> Blur -------> (out1) + // : : + // :......................................................: + + cv::gapi::island("isl0", cv::GIn(in[0], in[1]), cv::GOut(tmp[2], scl)); + cv::gapi::island("isl1", cv::GIn(tmp[1]), cv::GOut(out[1])); + + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + const auto &gm = cc.priv().model(); + const auto in0_nh = cv::gimpl::GModel::dataNodeOf(gm, in[0]); + const auto in1_nh = cv::gimpl::GModel::dataNodeOf(gm, in[1]); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + const auto tmp3_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[3]); + const auto scl_nh = cv::gimpl::GModel::dataNodeOf(gm, scl); + const auto out0_nh = cv::gimpl::GModel::dataNodeOf(gm, out[0]); + const auto out1_nh = cv::gimpl::GModel::dataNodeOf(gm, out[1]); + + // Check handles inside isl0 + EXPECT_EQ("isl0", gm.metadata(tmp0_nh).get().island); + EXPECT_EQ("isl0", gm.metadata(tmp1_nh).get().island); + // ^^^ Important - tmp1 is assigned to isl0, not isl1 + + // Check handles inside isl1 + EXPECT_EQ("isl1", gm.metadata(tmp3_nh).get().island); + + // Check outside handles + EXPECT_FALSE(gm.metadata(in0_nh) .contains()); + EXPECT_FALSE(gm.metadata(in1_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp2_nh).contains()); + EXPECT_FALSE(gm.metadata(scl_nh) .contains()); + EXPECT_FALSE(gm.metadata(out0_nh).contains()); + EXPECT_FALSE(gm.metadata(out1_nh).contains()); +} + + +TEST_F(ComplexIslands, IncompleteSpec) +{ + // isl0 + // ........................... + // (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) + // :...........xxx.......^...: ^ + // : : + // (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // : + // : + // `------------> Median -> (tmp3) --> Blur -------> (out1) + // + + // tmp1 is missing in the below spec + EXPECT_ANY_THROW(cv::gapi::island("isl0", cv::GIn(in[0]), cv::GOut(tmp[2]))); + + // empty range + EXPECT_ANY_THROW(cv::gapi::island("isl1", cv::GIn(tmp[2]), cv::GOut(tmp[2]))); +} + +TEST_F(ComplexIslands, InputOperationFromDifferentIslands) +{ + // isl1 + // ........................... ........ + // (in0)--> Not -> (tmp0) --> Add :--------> (tmp2)-->: AddC : -------> (out0) + // :......................^..: : ^ : + // isl0 : : : : + // .......................:....................... : : + // (in1) :-> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----- : + // :....................................................: + // isl0 : + // `------------> Median -> (tmp3) --> Blur -------> (out1) + // + + cv::gapi::island("isl0", cv::GIn(in[1], tmp[2]), cv::GOut(out[0])); + cv::gapi::island("isl1", cv::GIn(in[0], tmp[1]), cv::GOut(tmp[2])); + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + + const auto &gm = cc.priv().model(); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + + EXPECT_EQ("isl1", gm.metadata(tmp0_nh).get().island); + EXPECT_EQ("isl0", gm.metadata(tmp1_nh).get().island); + EXPECT_FALSE(gm.metadata(tmp2_nh).contains()); +} + +TEST_F(ComplexIslands, NoWayBetweenNodes) +{ + // (in0) -> Not -> (tmp0) --> Add ---------> (tmp2) --> AddC -------> (out0) + // ^ ^ + // (in1) -> Blur -> (tmp1) ----'--> Sum ----> (scl0) ----' + // : + // `------------> Median -> (tmp3) --> Blur -------> (out1) + + EXPECT_ANY_THROW(cv::gapi::island("isl0", cv::GIn(in[1]), cv::GOut(tmp[0]))); +} + +TEST_F(ComplexIslands, IslandsContainUnusedPart) +{ + // Unused part of the graph + // x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x + // x x + // x(in0) -> Not -> (tmp0) --> Add ---------> (tmp2)---> AddC ---------> (out0) x + // x ^ ^ x + // x x x x x x x x x x x x x x x | x x | x + // | x | x + // ...... | x | x + // (in1) -> :Blur:----------> (tmp1) x-----> Sum ------> (scl0) x + // ...... : x x x x x x x x x x x x x x x x x x x x x x x x + // isl0 + // : + // `------------> Median -> (tmp3) --> Blur -------> (out1) + + cv::gapi::island("isl0", cv::GIn(in[1]), cv::GOut(scl)); + auto cc = cv::GComputation(cv::GIn(in[1]), cv::GOut(out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}); + + const auto &gm = cc.priv().model(); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + + //The output 0 is not specified in the graph + //means that there will not be a node scl, so that tmp1 will not assign to the island + // FIXME Check that blur assigned to island using the function producerOf + // After merge islands fusion + EXPECT_FALSE(gm.metadata(tmp1_nh) .contains()); +} + +TEST_F(ComplexIslands, FullGraphInTwoIslands) +{ + // isl0 + // .................................................. + // (in0) -> :Not -> (tmp0) --> Add ---------> (tmp2) --> AddC: -------> (out0) + // ...................^.... ^ : + // ............... | : : : + // (in1) -> :Blur-> (tmp1):----'-->:Sum ----> (scl0) ----' : + // ........ | : ........................... + // isl1 : | :............................................ + // : `------------> Median -> (tmp3) --> Blur ------->:(out1) + // .................................................... + + cv::gapi::island("isl0", cv::GIn(in[0], tmp[1]), cv::GOut(out[0])); + cv::gapi::island("isl1", cv::GIn(in[1]), cv::GOut(out[1])); + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + + const auto &gm = cc.priv().model(); + const auto in0_nh = cv::gimpl::GModel::dataNodeOf(gm, in[0]); + const auto in1_nh = cv::gimpl::GModel::dataNodeOf(gm, in[1]); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + const auto tmp3_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[3]); + const auto scl_nh = cv::gimpl::GModel::dataNodeOf(gm, scl); + const auto out0_nh = cv::gimpl::GModel::dataNodeOf(gm, out[0]); + const auto out1_nh = cv::gimpl::GModel::dataNodeOf(gm, out[1]); + + // Check handles inside isl0 + EXPECT_EQ("isl0", gm.metadata(tmp0_nh).get().island); + EXPECT_EQ("isl0", gm.metadata(tmp2_nh).get().island); + EXPECT_EQ("isl0", gm.metadata(scl_nh).get().island); + + // Check handles inside isl1 + EXPECT_EQ("isl1", gm.metadata(tmp1_nh).get().island); + EXPECT_EQ("isl1", gm.metadata(tmp3_nh).get().island); + + // Check outside handles + EXPECT_FALSE(gm.metadata(in0_nh) .contains()); + EXPECT_FALSE(gm.metadata(in1_nh) .contains()); + EXPECT_FALSE(gm.metadata(out0_nh).contains()); + EXPECT_FALSE(gm.metadata(out1_nh).contains()); +} + +TEST_F(ComplexIslands, OnlyOperationsAssignedToIslands) +{ + cv::gapi::island("isl0", cv::GIn(in[1]), cv::GOut(tmp[1])); + cv::gapi::island("isl1", cv::GIn(tmp[1]), cv::GOut(scl)); + cv::gapi::island("isl2", cv::GIn(scl, tmp[2]), cv::GOut(out[0])); + cv::gapi::island("isl3", cv::GIn(in[0]), cv::GOut(tmp[0])); + cv::gapi::island("isl4", cv::GIn(tmp[0], tmp[1]), cv::GOut(tmp[2])); + cv::gapi::island("isl5", cv::GIn(tmp[1]), cv::GOut(tmp[3])); + cv::gapi::island("isl6", cv::GIn(tmp[3]), cv::GOut(out[1])); + + auto cc = cv::GComputation(cv::GIn(in[0], in[1]), cv::GOut(out[0], out[1])) + .compile(cv::GMatDesc{CV_8U,1,{640,480}}, + cv::GMatDesc{CV_8U,1,{640,480}}); + + const auto &gm = cc.priv().model(); + //FIXME: Check that operation handles are really assigned to isl0..isl6 + const auto in0_nh = cv::gimpl::GModel::dataNodeOf(gm, in[0]); + const auto in1_nh = cv::gimpl::GModel::dataNodeOf(gm, in[1]); + const auto tmp0_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[0]); + const auto tmp1_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[1]); + const auto tmp2_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[2]); + const auto tmp3_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp[3]); + const auto scl_nh = cv::gimpl::GModel::dataNodeOf(gm, scl); + const auto out0_nh = cv::gimpl::GModel::dataNodeOf(gm, out[0]); + const auto out1_nh = cv::gimpl::GModel::dataNodeOf(gm, out[1]); + + EXPECT_FALSE(gm.metadata(in0_nh) .contains()); + EXPECT_FALSE(gm.metadata(in1_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp0_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp1_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp2_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp3_nh) .contains()); + EXPECT_FALSE(gm.metadata(scl_nh) .contains()); + EXPECT_FALSE(gm.metadata(out0_nh).contains()); + EXPECT_FALSE(gm.metadata(out1_nh).contains()); +} + +namespace +{ + struct IslandStructureWithGArray + { + GIntArray in, out; + GMat tmp; + + IslandStructureWithGArray() + { + tmp = CreateMatWithDiag::on(in); + out = Mat2Array::on(tmp); + } + }; + + struct IslandsWithGArray: public ::testing::Test, public IslandStructureWithGArray {}; +} // namespace + +TEST_F(IslandsWithGArray, IslandWithGArrayAsInput) +{ + cv::gapi::island("isl0", cv::GIn(in), cv::GOut(tmp)); + + const auto pkg = cv::gapi::kernels(); + auto cc = cv::GComputation(cv::GIn(in), GOut(out)).compile(cv::empty_array_desc(), cv::compile_args(pkg)); + const auto &gm = cc.priv().model(); + + const auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in.strip()); + const auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out.strip()); + const auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp); + GAPI_Assert(tmp_nh->inNodes().size() == 1); + const auto create_diag_mat_nh = tmp_nh->inNodes().front(); + + EXPECT_EQ("isl0", gm.metadata(create_diag_mat_nh).get().island); + EXPECT_FALSE(gm.metadata(in_nh) .contains()); + EXPECT_FALSE(gm.metadata(out_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp_nh) .contains()); +} + +TEST_F(IslandsWithGArray, IslandWithGArrayAsOutput) +{ + cv::gapi::island("isl0", cv::GIn(tmp), cv::GOut(out)); + + const auto pkg = cv::gapi::kernels(); + auto cc = cv::GComputation(cv::GIn(in), GOut(out)).compile(cv::empty_array_desc(), cv::compile_args(pkg)); + const auto &gm = cc.priv().model(); + + const auto in_nh = cv::gimpl::GModel::dataNodeOf(gm, in.strip()); + const auto out_nh = cv::gimpl::GModel::dataNodeOf(gm, out.strip()); + const auto tmp_nh = cv::gimpl::GModel::dataNodeOf(gm, tmp); + GAPI_Assert(tmp_nh->inNodes().size() == 1); + const auto mat2array_nh = out_nh->inNodes().front(); + + EXPECT_EQ("isl0", gm.metadata(mat2array_nh).get().island); + EXPECT_FALSE(gm.metadata(in_nh) .contains()); + EXPECT_FALSE(gm.metadata(out_nh) .contains()); + EXPECT_FALSE(gm.metadata(tmp_nh) .contains()); +} +//////////////////////////////////////////////////////////////////////////////// +// Wrong input tests on island name +// +namespace +{ + struct CheckName : public TestWithParam >, + public PlainIslandsFixture + { + void assignIsland(const std::string &s) + { + cv::gapi::island(s, cv::GIn(tmp[0]), cv::GOut(tmp[2])); + }; + }; + TEST_P(CheckName, Test) + { + bool correct = false; + const char *name = ""; + std::tie(correct, name) = GetParam(); + if (correct) EXPECT_NO_THROW(assignIsland(name)); + else EXPECT_ANY_THROW(assignIsland(name)); + } +} // namespace +INSTANTIATE_TEST_CASE_P(IslandName, CheckName, + Values(std::make_tuple(true, "name"), + std::make_tuple(true, " name "), + std::make_tuple(true, " n a m e "), + std::make_tuple(true, " 123 $$ %%"), + std::make_tuple(true, ".: -"), + std::make_tuple(false, ""), + std::make_tuple(false, " "), + std::make_tuple(false, " \t "), + std::make_tuple(false, " \t \t "))); + +// FIXME: add test on unrollExpr() use for islands + +} // opencv_test diff --git a/modules/gapi/test/internal/gapi_int_recompilation_test.cpp b/modules/gapi/test/internal/gapi_int_recompilation_test.cpp new file mode 100644 index 0000000000..de8d46e677 --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_recompilation_test.cpp @@ -0,0 +1,71 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "api/gcomputation_priv.hpp" + +namespace opencv_test +{ + +TEST(GComputationCompile, NoRecompileWithSameMeta) +{ + cv::GMat in; + cv::GComputation cc(in, in+in); + + cv::Mat in_mat1 = cv::Mat::eye (32, 32, CV_8UC1); + cv::Mat in_mat2 = cv::Mat::zeros(32, 32, CV_8UC1); + cv::Mat out_mat; + + cc.apply(in_mat1, out_mat); + auto comp1 = cc.priv().m_lastCompiled; + + cc.apply(in_mat2, out_mat); + auto comp2 = cc.priv().m_lastCompiled; + + // Both compiled objects are actually the same unique executable + EXPECT_EQ(&comp1.priv(), &comp2.priv()); +} + +TEST(GComputationCompile, NoRecompileWithWrongMeta) +{ + cv::GMat in; + cv::GComputation cc(in, in+in); + + cv::Mat in_mat1 = cv::Mat::eye (32, 32, CV_8UC1); + cv::Mat in_mat2 = cv::Mat::zeros(32, 32, CV_8UC1); + cv::Mat out_mat; + + cc.apply(in_mat1, out_mat); + auto comp1 = cc.priv().m_lastCompiled; + + EXPECT_THROW(cc.apply(cv::gin(cv::Scalar(128)), cv::gout(out_mat)), std::logic_error); + auto comp2 = cc.priv().m_lastCompiled; + + // Both compiled objects are actually the same unique executable + EXPECT_EQ(&comp1.priv(), &comp2.priv()); +} + +TEST(GComputationCompile, RecompileWithDifferentMeta) +{ + cv::GMat in; + cv::GComputation cc(in, in+in); + + cv::Mat in_mat1 = cv::Mat::eye (32, 32, CV_8UC1); + cv::Mat in_mat2 = cv::Mat::zeros(64, 64, CV_32F); + cv::Mat out_mat; + + cc.apply(in_mat1, out_mat); + auto comp1 = cc.priv().m_lastCompiled; + + cc.apply(in_mat2, out_mat); + auto comp2 = cc.priv().m_lastCompiled; + + // Both compiled objects are different + EXPECT_NE(&comp1.priv(), &comp2.priv()); +} + +} // opencv_test diff --git a/modules/gapi/test/internal/gapi_int_resolve_kernel_test.cpp b/modules/gapi/test/internal/gapi_int_resolve_kernel_test.cpp new file mode 100644 index 0000000000..d4b16f627e --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_resolve_kernel_test.cpp @@ -0,0 +1,119 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +#include "gapi_mock_kernels.hpp" + +namespace opencv_test +{ + +TEST(Lookup, CreateOrder) +{ + const auto order = cv::gapi::lookup_order({Jupiter::backend(), + Saturn::backend()}); + EXPECT_EQ(2u, order.size()); + EXPECT_EQ(Jupiter::backend(), order[0]); + EXPECT_EQ(Saturn ::backend(), order[1]); +} + +TEST(Lookup, NoOrder) +{ + namespace J = Jupiter; + namespace S = Saturn; + const auto pkg = cv::gapi::kernels(); + + EXPECT_NO_THROW (pkg.lookup()); + EXPECT_NO_THROW (pkg.lookup()); + EXPECT_NO_THROW (pkg.lookup()); + EXPECT_ANY_THROW(pkg.lookup()); +} + +TEST(Lookup, Only_Jupiter) +{ + namespace J = Jupiter; + namespace S = Saturn; + const auto pkg = cv::gapi::kernels(); + + auto order = cv::gapi::lookup_order({J::backend()}); + + EXPECT_EQ(J::backend(), pkg.lookup(order)); + EXPECT_EQ(J::backend(), pkg.lookup(order)); + EXPECT_EQ(J::backend(), pkg.lookup(order)); + EXPECT_ANY_THROW(pkg.lookup(order)); +} + +TEST(Lookup, Only_Saturn) +{ + namespace J = Jupiter; + namespace S = Saturn; + const auto pkg = cv::gapi::kernels(); + + auto order = cv::gapi::lookup_order({S::backend()}); + + EXPECT_EQ(S::backend(), pkg.lookup(order)); + EXPECT_EQ(S::backend(), pkg.lookup(order)); + EXPECT_EQ(S::backend(), pkg.lookup(order)); + EXPECT_ANY_THROW(pkg.lookup(order)); +} + +TEST(Lookup, With_Order) +{ + namespace J = Jupiter; + namespace S = Saturn; + const auto pkg = cv::gapi::kernels(); + + auto prefer_j = cv::gapi::lookup_order({J::backend(), S::backend()}); + EXPECT_EQ(J::backend(), pkg.lookup(prefer_j)); + EXPECT_EQ(J::backend(), pkg.lookup(prefer_j)); + EXPECT_EQ(J::backend(), pkg.lookup(prefer_j)); + EXPECT_ANY_THROW(pkg.lookup(prefer_j)); + + auto prefer_s = cv::gapi::lookup_order({S::backend(), J::backend()}); + EXPECT_EQ(S::backend(), pkg.lookup(prefer_s)); + EXPECT_EQ(S::backend(), pkg.lookup(prefer_s)); + EXPECT_EQ(S::backend(), pkg.lookup(prefer_s)); + EXPECT_ANY_THROW(pkg.lookup(prefer_s)); +} + +TEST(Lookup, NoOverlap) +{ + namespace J = Jupiter; + namespace S = Saturn; + const auto pkg = cv::gapi::kernels(); + EXPECT_EQ(J::backend(), pkg.lookup()); + EXPECT_EQ(J::backend(), pkg.lookup()); + EXPECT_EQ(S::backend(), pkg.lookup()); + EXPECT_EQ(S::backend(), pkg.lookup()); +} + +TEST(Lookup, ExtraBackend) +{ + namespace J = Jupiter; + namespace S = Saturn; + const auto pkg = cv::gapi::kernels(); + + // Even if pkg doesn't contain S kernels while S is preferable, + // it should work. + const auto prefer_sj = cv::gapi::lookup_order({S::backend(), J::backend()}); + EXPECT_EQ(J::backend(), pkg.lookup(prefer_sj)); + EXPECT_EQ(J::backend(), pkg.lookup(prefer_sj)); + EXPECT_EQ(J::backend(), pkg.lookup(prefer_sj)); + + // If search scope is limited to S only, neither J nor S kernels + // shouldn't be found + const auto only_s = cv::gapi::lookup_order({S::backend()}); + EXPECT_ANY_THROW(pkg.lookup(only_s)); + EXPECT_ANY_THROW(pkg.lookup(only_s)); + EXPECT_ANY_THROW(pkg.lookup(only_s)); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_vectorref_test.cpp b/modules/gapi/test/internal/gapi_int_vectorref_test.cpp new file mode 100644 index 0000000000..1b14e0670a --- /dev/null +++ b/modules/gapi/test/internal/gapi_int_vectorref_test.cpp @@ -0,0 +1,207 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" + +namespace opencv_test +{ + +typedef ::testing::Types VectorRef_Test_Types; + +template struct VectorRefT: public ::testing::Test { using Type = T; }; + +TYPED_TEST_CASE(VectorRefT, VectorRef_Test_Types); + +TYPED_TEST(VectorRefT, Reset_Valid) +{ + using T = typename TestFixture::Type; + cv::detail::VectorRefT ref; // vector ref created empty + EXPECT_NO_THROW(ref.reset()); // 1st reset is OK (initializes) + EXPECT_NO_THROW(ref.reset()); // 2nd reset is also OK (resets) +} + +TYPED_TEST(VectorRefT, Reset_Invalid) +{ + using T = typename TestFixture::Type; + std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRefT ref(vec); // RO_EXT (since reference is const) + EXPECT_ANY_THROW(ref.reset()); // data-bound vector ref can't be reset +} + +TYPED_TEST(VectorRefT, ReadRef_External) +{ + using T = typename TestFixture::Type; + const std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRefT ref(vec); // RO_EXT (since reference is const) + auto &vref = ref.rref(); + EXPECT_EQ(vec.data(), vref.data()); + EXPECT_EQ(vec.size(), vref.size()); +} + +TYPED_TEST(VectorRefT, ReadRef_Internal) +{ + using T = typename TestFixture::Type; + cv::detail::VectorRefT ref; + ref.reset(); // RW_OWN (reset on empty ref) + auto &vref = ref.rref(); // read access is valid for RW_OWN + EXPECT_EQ(0u, vref.size()); // by default vector is empty +} + +TYPED_TEST(VectorRefT, WriteRef_External) +{ + using T = typename TestFixture::Type; + std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRefT ref(vec); // RW_EXT (since reference is not const) + auto &vref = ref.wref(); // write access is valid with RW_EXT + EXPECT_EQ(vec.data(), vref.data()); + EXPECT_EQ(vec.size(), vref.size()); +} + +TYPED_TEST(VectorRefT, WriteRef_Internal) +{ + using T = typename TestFixture::Type; + cv::detail::VectorRefT ref; + ref.reset(); // RW_OWN (reset on empty ref) + auto &vref = ref.wref(); // write access is valid for RW_OWN + EXPECT_EQ(0u, vref.size()); // empty vector by default +} + +TYPED_TEST(VectorRefT, WriteToRO) +{ + using T = typename TestFixture::Type; + const std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRefT ref(vec); // RO_EXT (since reference is const) + EXPECT_ANY_THROW(ref.wref()); +} + +TYPED_TEST(VectorRefT, ReadAfterWrite) +{ + using T = typename TestFixture::Type; + std::vector vec; // Initial data holder (empty vector) + cv::detail::VectorRefT writer(vec); // RW_EXT + + const auto& ro_ref = vec; + cv::detail::VectorRefT reader(ro_ref); // RO_EXT + + EXPECT_EQ(0u, writer.wref().size()); // Check the initial state + EXPECT_EQ(0u, reader.rref().size()); + + writer.wref().emplace_back(); // Check that write is successfull + EXPECT_EQ(1u, writer.wref().size()); + + EXPECT_EQ(1u, vec.size()); // Check that changes are reflected to the original container + EXPECT_EQ(1u, reader.rref().size()); // Check that changes are reflected to reader's view + + EXPECT_EQ(T(), vec.at(0)); // Check the value (must be default-initialized) + EXPECT_EQ(T(), reader.rref().at(0)); + EXPECT_EQ(T(), writer.wref().at(0)); +} + +template struct VectorRefU: public ::testing::Test { using Type = T; }; + +TYPED_TEST_CASE(VectorRefU, VectorRef_Test_Types); + +template struct custom_struct { T a; T b; }; + +TYPED_TEST(VectorRefU, Reset_Valid) +{ + using T = typename TestFixture::Type; + cv::detail::VectorRef ref; // vector ref created empty + EXPECT_NO_THROW(ref.reset()); // 1st reset is OK (initializes) + EXPECT_NO_THROW(ref.reset()); // 2nd reset is also OK (resets) + + EXPECT_ANY_THROW(ref.reset >()); // type change is not allowed +} + +TYPED_TEST(VectorRefU, Reset_Invalid) +{ + using T = typename TestFixture::Type; + std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRef ref(vec); // RO_EXT (since reference is const) + EXPECT_ANY_THROW(ref.reset()); // data-bound vector ref can't be reset +} + +TYPED_TEST(VectorRefU, ReadRef_External) +{ + using T = typename TestFixture::Type; + const std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRef ref(vec); // RO_EXT (since reference is const) + auto &vref = ref.rref(); + EXPECT_EQ(vec.data(), vref.data()); + EXPECT_EQ(vec.size(), vref.size()); +} + +TYPED_TEST(VectorRefU, ReadRef_Internal) +{ + using T = typename TestFixture::Type; + cv::detail::VectorRef ref; + ref.reset(); // RW_OWN (reset on empty ref) + auto &vref = ref.rref(); // read access is valid for RW_OWN + EXPECT_EQ(0u, vref.size()); // by default vector is empty +} + +TYPED_TEST(VectorRefU, WriteRef_External) +{ + using T = typename TestFixture::Type; + std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRef ref(vec); // RW_EXT (since reference is not const) + auto &vref = ref.wref(); // write access is valid with RW_EXT + EXPECT_EQ(vec.data(), vref.data()); + EXPECT_EQ(vec.size(), vref.size()); +} + +TYPED_TEST(VectorRefU, WriteRef_Internal) +{ + using T = typename TestFixture::Type; + cv::detail::VectorRef ref; + ref.reset(); // RW_OWN (reset on empty ref) + auto &vref = ref.wref(); // write access is valid for RW_OWN + EXPECT_EQ(0u, vref.size()); // empty vector by default +} + +TYPED_TEST(VectorRefU, WriteToRO) +{ + using T = typename TestFixture::Type; + const std::vector vec(42); // create a std::vector of 42 elements + cv::detail::VectorRef ref(vec); // RO_EXT (since reference is const) + EXPECT_ANY_THROW(ref.wref()); +} + +TYPED_TEST(VectorRefU, ReadAfterWrite) +{ + using T = typename TestFixture::Type; + std::vector vec; // Initial data holder (empty vector) + cv::detail::VectorRef writer(vec); // RW_EXT + + const auto& ro_ref = vec; + cv::detail::VectorRef reader(ro_ref); // RO_EXT + + EXPECT_EQ(0u, writer.wref().size()); // Check the initial state + EXPECT_EQ(0u, reader.rref().size()); + + writer.wref().emplace_back(); // Check that write is successfull + EXPECT_EQ(1u, writer.wref().size()); + + EXPECT_EQ(1u, vec.size()); // Check that changes are reflected to the original container + EXPECT_EQ(1u, reader.rref().size()); // Check that changes are reflected to reader's view + + EXPECT_EQ(T(), vec.at(0)); // Check the value (must be default-initialized) + EXPECT_EQ(T(), reader.rref().at(0)); + EXPECT_EQ(T(), writer.wref().at(0)); +} + +TEST(VectorRefU, TypeCheck) +{ + cv::detail::VectorRef ref; + ref.reset(); // RW_OWN + + EXPECT_ANY_THROW(ref.reset()); + EXPECT_ANY_THROW(ref.rref()); + EXPECT_ANY_THROW(ref.wref()); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_transactions_test.cpp b/modules/gapi/test/internal/gapi_transactions_test.cpp new file mode 100644 index 0000000000..f550340e81 --- /dev/null +++ b/modules/gapi/test/internal/gapi_transactions_test.cpp @@ -0,0 +1,222 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include +#include "compiler/transactions.hpp" + +namespace opencv_test +{ +namespace +{ + +bool contains(const ade::Graph& graph, const ade::NodeHandle& node) +{ + auto nodes = graph.nodes(); + return nodes.end() != std::find(nodes.begin(), nodes.end(), node); +} + +bool connected(const ade::NodeHandle& src_node, const ade::NodeHandle& dst_node) +{ + auto nodes = src_node->outNodes(); + return nodes.end() != std::find(nodes.begin(), nodes.end(), dst_node); +} + +struct SimpleGraph +{ + // ehs[0] ehs[1] ehs[2] ehs[3] + // nhs[0] -- > nhs[1] --> nhs[2] --> nhs[3] --> nhs[4] + + enum { node_nums = 5 }; + ade::Graph graph; + ade::NodeHandle fused_nh; /* For check that fusion node is connected to the + inputs of the prod and the outputs of the cons */ + std::array nhs; + std::array ehs; + Change::List changes; + + SimpleGraph() + { + nhs[0] = graph.createNode(); + for (int i = 1; i < node_nums; ++i) + { + nhs[i ] = graph.createNode(); + ehs[i - 1] = graph.link(nhs[i - 1], nhs[i]); + } + } + + void fuse() + { + // nhs[0] --> fused_nh --> nhs[4] + + fused_nh = graph.createNode(); + changes.enqueue(fused_nh); + changes.enqueue (graph, nhs[0], fused_nh); + changes.enqueue(graph, nhs[1], ehs[0]); + changes.enqueue (graph, fused_nh, nhs[4]); + changes.enqueue(graph, nhs[3], ehs[3]); + changes.enqueue(graph, nhs[1], ehs[1]); + changes.enqueue(graph, nhs[2], ehs[2]); + changes.enqueue(nhs[1]); + changes.enqueue(nhs[2]); + changes.enqueue(nhs[3]); + } + + void commit() { changes.commit(graph); } + void rollback() { changes.rollback(graph); } + +}; + +struct Transactions: public ::testing::Test, public SimpleGraph {}; + +} // anonymous namespace + +TEST_F(Transactions, NodeCreated_Create) +{ + auto new_nh = graph.createNode(); + Change::NodeCreated node_created(new_nh); + + EXPECT_EQ(6u, static_cast(graph.nodes().size())); + EXPECT_TRUE(contains(graph, new_nh)); +} + +TEST_F(Transactions, NodeCreated_RollBack) +{ + auto new_nh = graph.createNode(); + Change::NodeCreated node_created(new_nh); + + node_created.rollback(graph); + + EXPECT_EQ(5u, static_cast(graph.nodes().size())); + EXPECT_FALSE(contains(graph, new_nh)); +} + +TEST_F(Transactions, NodeCreated_Commit) +{ + auto new_nh = graph.createNode(); + Change::NodeCreated node_created(new_nh); + + node_created.commit(graph); + + EXPECT_EQ(6u, static_cast(graph.nodes().size())); + EXPECT_TRUE(contains(graph, new_nh)); +} + +TEST_F(Transactions, DropLink_Create) +{ + Change::DropLink drop_link(graph, nhs[0], ehs[0]); + + EXPECT_FALSE(connected(nhs[0], nhs[1])); +} + +TEST_F(Transactions, DropLink_RollBack) +{ + Change::DropLink drop_link(graph, nhs[0], ehs[0]); + + drop_link.rollback(graph); + + EXPECT_TRUE(connected(nhs[0], nhs[1])); +} + +TEST_F(Transactions, DropLink_Commit) +{ + Change::DropLink drop_link(graph, nhs[0], ehs[0]); + + drop_link.commit(graph); + + EXPECT_FALSE(connected(nhs[0], nhs[1])); +} + +TEST_F(Transactions, NewLink_Create) +{ + auto new_nh = graph.createNode(); + Change::NewLink new_link(graph, new_nh, nhs[0]); + + EXPECT_TRUE(connected(new_nh, nhs[0])); +} + +TEST_F(Transactions, NewLink_RollBack) +{ + auto new_nh = graph.createNode(); + Change::NewLink new_link(graph, new_nh, nhs[0]); + + new_link.rollback(graph); + + EXPECT_FALSE(connected(new_nh, nhs[0])); +} + +TEST_F(Transactions, NewLink_Commit) +{ + auto new_nh = graph.createNode(); + Change::NewLink new_link(graph, new_nh, nhs[0]); + + new_link.commit(graph); + + EXPECT_TRUE(connected(new_nh, nhs[0])); +} + +TEST_F(Transactions, DropNode_Create) +{ + auto new_nh = graph.createNode(); + Change::DropNode drop_node(new_nh); + + EXPECT_EQ(6u, static_cast(graph.nodes().size())); + EXPECT_TRUE(contains(graph, new_nh)); +} + +TEST_F(Transactions, DropNode_RollBack) +{ + auto new_nh = graph.createNode(); + Change::DropNode drop_node(new_nh); + + drop_node.rollback(graph); + + EXPECT_EQ(6u, static_cast(graph.nodes().size())); + EXPECT_TRUE(contains(graph, new_nh)); +} + +TEST_F(Transactions, DropNode_Commit) +{ + auto new_nh = graph.createNode(); + Change::DropNode drop_node(new_nh); + + drop_node.commit(graph); + + EXPECT_EQ(5u, static_cast(graph.nodes().size())); + EXPECT_FALSE(contains(graph, new_nh)); +} + +TEST_F(Transactions, Fusion_Commit) +{ + namespace C = Change; + + fuse(); + commit(); + + EXPECT_EQ(3u, static_cast(graph.nodes().size())); + EXPECT_TRUE(connected(nhs[0] , fused_nh)); + EXPECT_TRUE(connected(fused_nh, nhs[4])); +} + +TEST_F(Transactions, Fusion_RollBack) +{ + namespace C = Change; + + fuse(); + rollback(); + + EXPECT_EQ(static_cast(node_nums), + static_cast(graph.nodes().size())); + EXPECT_FALSE(contains(graph, fused_nh)); + + for (int i = 0; i < static_cast(node_nums) - 1; ++i) + { + EXPECT_TRUE(connected(nhs[i], nhs[i + 1])); + } +} + +} // opencv_test diff --git a/modules/gapi/test/own/gapi_types_tests.cpp b/modules/gapi/test/own/gapi_types_tests.cpp new file mode 100644 index 0000000000..c254357074 --- /dev/null +++ b/modules/gapi/test/own/gapi_types_tests.cpp @@ -0,0 +1,159 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/own/types.hpp" + +namespace opencv_test +{ + +TEST(Point, CreateEmpty) +{ + cv::gapi::own::Point p; + + EXPECT_EQ(0, p.x); + EXPECT_EQ(0, p.y); +} + +TEST(Point, CreateWithParams) +{ + cv::gapi::own::Point p = {1, 2}; + + EXPECT_EQ(1, p.x); + EXPECT_EQ(2, p.y); +} + +TEST(Rect, CreateEmpty) +{ + cv::gapi::own::Rect r; + + EXPECT_EQ(0, r.x); + EXPECT_EQ(0, r.y); + EXPECT_EQ(0, r.width); + EXPECT_EQ(0, r.height); +} + +TEST(Rect, CreateWithParams) +{ + cv::gapi::own::Rect r(1, 2, 3, 4); + + EXPECT_EQ(1, r.x); + EXPECT_EQ(2, r.y); + EXPECT_EQ(3, r.width); + EXPECT_EQ(4, r.height); +} + +TEST(Rect, CompareEqual) +{ + cv::gapi::own::Rect r1(1, 2, 3, 4); + + cv::gapi::own::Rect r2(1, 2, 3, 4); + + EXPECT_TRUE(r1 == r2); +} + +TEST(Rect, CompareDefaultEqual) +{ + cv::gapi::own::Rect r1; + + cv::gapi::own::Rect r2; + + EXPECT_TRUE(r1 == r2); +} + +TEST(Rect, CompareNotEqual) +{ + cv::gapi::own::Rect r1(1, 2, 3, 4); + + cv::gapi::own::Rect r2; + + EXPECT_TRUE(r1 != r2); +} + +TEST(Rect, Intersection) +{ + cv::gapi::own::Rect r1(2, 2, 3, 3); + cv::gapi::own::Rect r2(3, 1, 3, 3); + + cv::gapi::own::Rect intersect = r1 & r2; + + EXPECT_EQ(3, intersect.x); + EXPECT_EQ(2, intersect.y); + EXPECT_EQ(2, intersect.width); + EXPECT_EQ(2, intersect.height); +} + +TEST(Rect, AssignIntersection) +{ + cv::gapi::own::Rect r1(2, 2, 3, 3); + cv::gapi::own::Rect r2(3, 1, 3, 3); + + r1 &= r2; + + EXPECT_EQ(3, r1.x); + EXPECT_EQ(2, r1.y); + EXPECT_EQ(2, r1.width); + EXPECT_EQ(2, r1.height); +} + +TEST(Size, CreateEmpty) +{ + cv::gapi::own::Size s; + + EXPECT_EQ(0, s.width); + EXPECT_EQ(0, s.height); +} + +TEST(Size, CreateWithParams) +{ + cv::gapi::own::Size s(640, 480); + + EXPECT_EQ(640, s.width); + EXPECT_EQ(480, s.height); +} + +TEST(Size, AdditionAssignment) +{ + cv::gapi::own::Size s1(1, 2); + cv::gapi::own::Size s2(2, 3); + + s1 += s2; + + EXPECT_EQ(3, s1.width); + EXPECT_EQ(5, s1.height); +} + +TEST(Size, CompareEqual) +{ + cv::gapi::own::Size s1(1, 2); + + cv::gapi::own::Size s2(1, 2); + + EXPECT_TRUE(s1 == s2); + EXPECT_FALSE(s1 != s2); +} + +TEST(Size, CompareDefaultEqual) +{ + cv::gapi::own::Size s1; + cv::gapi::own::Size s2; + + EXPECT_TRUE(s1 == s2); + EXPECT_FALSE(s1 != s2); +} + +TEST(Size, CompareNotEqual) +{ + cv::gapi::own::Size s1(1, 2); + + cv::gapi::own::Size s2(3, 4); + + EXPECT_FALSE(s1 == s2); + EXPECT_TRUE(s1 != s2); +} + +} // opencv_test diff --git a/modules/gapi/test/own/mat_tests.cpp b/modules/gapi/test/own/mat_tests.cpp new file mode 100644 index 0000000000..ffefe0c344 --- /dev/null +++ b/modules/gapi/test/own/mat_tests.cpp @@ -0,0 +1,164 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/own/mat.hpp" +#include //suppress_unused_warning + +namespace opencv_test +{ +using Mat = cv::gapi::own::Mat; + +TEST(OwnMat, DefaultConstruction) +{ + Mat m; + ASSERT_EQ(m.data, nullptr); + ASSERT_EQ(m.cols, 0); + ASSERT_EQ(m.rows, 0); + ASSERT_EQ(m.cols, 0); + ASSERT_EQ(m.type(), 0); + ASSERT_EQ(m.depth(), 0); +} + +TEST(OwnMat, Create) +{ + auto size = cv::gapi::own::Size{32,16}; + Mat m; + m.create(size, CV_8UC1); + + ASSERT_NE(m.data, nullptr); + ASSERT_EQ((cv::gapi::own::Size{m.cols, m.rows}), size); + + ASSERT_EQ(m.type(), CV_8UC1); + ASSERT_EQ(m.depth(), CV_8U); + ASSERT_EQ(m.channels(), 1); + ASSERT_EQ(m.step, sizeof(uint8_t) * m.cols); +} + +struct NonEmptyMat { + cv::gapi::own::Size size{32,16}; + Mat m; + NonEmptyMat() { + m.create(size, CV_8UC1); + } +}; + +struct OwnMatSharedSemantics : NonEmptyMat, ::testing::Test {}; + + +namespace { + auto state_of = [](Mat const& mat) { + return std::make_tuple( + mat.data, + cv::Size{mat.cols, mat.rows}, + mat.type(), + mat.depth(), + mat.channels() + ); + }; + + void ensure_mats_are_same(Mat const& copy, Mat const& m){ + EXPECT_NE(copy.data, nullptr); + EXPECT_EQ(state_of(copy), state_of(m)); + } +} +TEST_F(OwnMatSharedSemantics, CopyConstruction) +{ + Mat copy(m); + ensure_mats_are_same(copy, m); +} + +TEST_F(OwnMatSharedSemantics, CopyAssignment) +{ + Mat copy; + copy = m; + ensure_mats_are_same(copy, m); +} + +struct OwnMatMoveSemantics : NonEmptyMat, ::testing::Test { + Mat& moved_from = m; + decltype(state_of(moved_from)) initial_state = state_of(moved_from); + + void ensure_state_moved_to(Mat const& moved_to) + { + EXPECT_EQ(state_of(moved_to), initial_state); + EXPECT_EQ(state_of(moved_from), state_of(Mat{})); + } +}; + +TEST_F(OwnMatMoveSemantics, MoveConstruction) +{ + Mat moved_to(std::move(moved_from)); + + ensure_state_moved_to(moved_to); +} + +TEST_F(OwnMatMoveSemantics, MoveAssignment) +{ + Mat moved_to(std::move(moved_from)); + ensure_state_moved_to(moved_to); +} + +struct OwnMatNonOwningView : NonEmptyMat, ::testing::Test { + decltype(state_of(m)) initial_state = state_of(m); + + void TearDown() override { + EXPECT_EQ(state_of(m), initial_state)<<"State of the source matrix changed?"; + //ASAN should complain here if memory is freed here (e.g. by bug in non owning logic of own::Mat) + volatile uchar dummy = m.data[0]; + cv::util::suppress_unused_warning(dummy); + } + +}; + +TEST_F(OwnMatNonOwningView, Construction) +{ + Mat non_owning_view(m.rows, m.cols, m.type(), static_cast(m.data)); + + ensure_mats_are_same(non_owning_view, m); +} + +TEST_F(OwnMatNonOwningView, CopyConstruction) +{ + Mat non_owning_view{m.rows, m.cols, m.type(), static_cast(m.data)}; + + Mat non_owning_view_copy = non_owning_view; + ensure_mats_are_same(non_owning_view_copy, m); +} + +TEST_F(OwnMatNonOwningView, Assignment) +{ + Mat non_owning_view{m.rows, m.cols, m.type(), static_cast(m.data)}; + Mat non_owning_view_copy; + + non_owning_view_copy = non_owning_view; + ensure_mats_are_same(non_owning_view_copy, m); +} + +TEST(OwnMatConversion, WithStep) +{ + constexpr int width = 8; + constexpr int height = 8; + constexpr int stepInPixels = 16; + + std::array data; + for (size_t i = 0; i < data.size(); i++) + { + data[i] = static_cast(i); + } + cv::Mat cvMat(cv::Size{width, height}, CV_32S, data.data(), stepInPixels * sizeof(int)); + + auto ownMat = to_own(cvMat); + auto cvMatFromOwn = cv::gapi::own::to_ocv(ownMat); + + EXPECT_EQ(0, cv::countNonZero(cvMat != cvMatFromOwn)) + << cvMat << std::endl + << (cvMat != cvMatFromOwn); +} + + +} // namespace opencv_test diff --git a/modules/gapi/test/own/scalar_tests.cpp b/modules/gapi/test/own/scalar_tests.cpp new file mode 100644 index 0000000000..a9c5c01235 --- /dev/null +++ b/modules/gapi/test/own/scalar_tests.cpp @@ -0,0 +1,44 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/own/scalar.hpp" + +namespace opencv_test +{ + +TEST(Scalar, CreateEmpty) +{ + cv::gapi::own::Scalar s; + + for (int i = 0; i < 4; ++i) + { + EXPECT_EQ(s[i], 0.0); + } +} + +TEST(Scalar, CreateFromVal) +{ + cv::gapi::own::Scalar s(5.0); + + EXPECT_EQ(s[0], 5.0); + EXPECT_EQ(s[1], 0.0); + EXPECT_EQ(s[2], 0.0); + EXPECT_EQ(s[3], 0.0); +} + +TEST(Scalar, CreateFromVals) +{ + cv::gapi::own::Scalar s(5.3, 3.3, 4.1, -2.0); + + EXPECT_EQ(s[0], 5.3); + EXPECT_EQ(s[1], 3.3); + EXPECT_EQ(s[2], 4.1); + EXPECT_EQ(s[3], -2.0); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/test_main.cpp b/modules/gapi/test/test_main.cpp new file mode 100644 index 0000000000..fa5862fa19 --- /dev/null +++ b/modules/gapi/test/test_main.cpp @@ -0,0 +1,12 @@ +// 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) 2018 Intel Corporation + + +// FIXME: OpenCV license header + +#include "test_precomp.hpp" + +CV_TEST_MAIN("gapi") diff --git a/modules/gapi/test/test_precomp.hpp b/modules/gapi/test/test_precomp.hpp new file mode 100644 index 0000000000..899ebdd4b3 --- /dev/null +++ b/modules/gapi/test/test_precomp.hpp @@ -0,0 +1,24 @@ +// 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) 2018 Intel Corporation + + +// FIXME: OpenCV header + +#ifndef __OPENCV_GAPI_TEST_PRECOMP_HPP__ +#define __OPENCV_GAPI_TEST_PRECOMP_HPP__ + +#include +#include + +#include "opencv2/ts.hpp" +#include "opencv2/gapi.hpp" +#include "opencv2/gapi/imgproc.hpp" +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/cpu/gcpukernel.hpp" +#include "opencv2/gapi/gcompoundkernel.hpp" +#include "opencv2/gapi/operators.hpp" + +#endif // __OPENCV_GAPI_TEST_PRECOMP_HPP__ diff --git a/modules/gapi/test/util/any_tests.cpp b/modules/gapi/test/util/any_tests.cpp new file mode 100644 index 0000000000..60bbcc13b6 --- /dev/null +++ b/modules/gapi/test/util/any_tests.cpp @@ -0,0 +1,121 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/util/any.hpp" + +namespace opencv_test +{ + +TEST(Any, basic) +{ + using namespace util; + any a(8); + auto casted_pointer = any_cast(&a); + ASSERT_NE(nullptr, casted_pointer); + ASSERT_EQ(*casted_pointer, 8); + + *casted_pointer = 7; + ASSERT_EQ(any_cast(a), 7); +} + +TEST(Any, any_cast_ref_throws_on_empty) +{ + using namespace util; + any a; + + ASSERT_THROW(util::any_cast(a), bad_any_cast); +} + +TEST(Any, copy) +{ + using namespace util; + any a(8); + + ASSERT_EQ(any_cast(a), 8); + + any b (a); + + ASSERT_NE(nullptr, any_cast(&b)); + ASSERT_EQ(8 , any_cast(b)); + ASSERT_EQ(8 , any_cast(a)); +} + +TEST(Any, copy_empty) +{ + using namespace util; + any a; + + ASSERT_EQ(nullptr, any_cast(&a)); + + any b (a); + + ASSERT_EQ(nullptr, any_cast(&a)); + ASSERT_EQ(nullptr, any_cast(&b)); +} + +TEST(Any, move) +{ + using namespace util; + any a(8); + + ASSERT_EQ(any_cast(a), 8); + + any b (std::move(a)); + + ASSERT_NE(nullptr, any_cast(&b)); + ASSERT_EQ(8 , any_cast(b)); + ASSERT_EQ(nullptr, any_cast(&a)); +} + +TEST(Any, swap) +{ + using namespace util; + any a(8); + any b(7); + + ASSERT_EQ(7, any_cast(b)); + ASSERT_EQ(8, any_cast(a)); + + swap(a,b); + + ASSERT_EQ(8, any_cast(b)); + ASSERT_EQ(7, any_cast(a)); +} + +TEST(Any, move_assign) +{ + using namespace util; + any a(8); + any b; + + ASSERT_EQ(any_cast(a), 8); + + b = (std::move(a)); + + ASSERT_NE(nullptr, any_cast(&b)); + ASSERT_EQ(8 , any_cast(b)); + ASSERT_EQ(nullptr, any_cast(&a)); +} + +TEST(Any, copy_assign) +{ + using namespace util; + any a(8); + any b; + + ASSERT_EQ(any_cast(a), 8); + ASSERT_EQ(nullptr, any_cast(&b)); + + b = a; + + ASSERT_NE(nullptr, any_cast(&b)); + ASSERT_EQ(8 , any_cast(b)); + ASSERT_EQ(8 , any_cast(a)); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/util/optional_tests.cpp b/modules/gapi/test/util/optional_tests.cpp new file mode 100644 index 0000000000..b7fabd530c --- /dev/null +++ b/modules/gapi/test/util/optional_tests.cpp @@ -0,0 +1,175 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/util/optional.hpp" +#include //suppress_unused_warning + +namespace opencv_test +{ + +TEST(Optional, EmptyCtor) +{ + util::optional o; + EXPECT_FALSE(o.has_value()); + EXPECT_FALSE(static_cast(o)); +} + +TEST(Optional, ValueCTor) +{ + util::optional o(42); + EXPECT_TRUE(o.has_value()); + EXPECT_TRUE(static_cast(o)); +} + +TEST(Optional, MoveCtr) +{ + util::optional os1(std::string("text")); + EXPECT_TRUE(os1.has_value()); + + util::optional os2(std::move(os1)); + EXPECT_FALSE(os1.has_value()); + EXPECT_TRUE(os2.has_value()); + EXPECT_EQ("text", os2.value()); +} + +TEST(Optional, EmptyThrows) +{ + struct foo { int bar; }; + util::optional om; + const util::optional oc; + + int dummy; + + EXPECT_THROW(dummy = om->bar, util::bad_optional_access); + EXPECT_THROW(dummy = oc->bar, util::bad_optional_access); + cv::util::suppress_unused_warning(dummy); + EXPECT_THROW(*om, util::bad_optional_access); + EXPECT_THROW(*oc, util::bad_optional_access); + EXPECT_THROW(om.value(), util::bad_optional_access); + EXPECT_THROW(oc.value(), util::bad_optional_access); +} + +TEST(Optional, ValueNoThrow) +{ + struct foo { int bar; }; + util::optional om(foo{42}); + const util::optional oc(foo{42}); + + int dummy; + EXPECT_NO_THROW(dummy = om->bar); + EXPECT_NO_THROW(dummy = oc->bar); + cv::util::suppress_unused_warning(dummy); + EXPECT_NO_THROW(*om); + EXPECT_NO_THROW(*oc); + EXPECT_NO_THROW(om.value()); + EXPECT_NO_THROW(oc.value()); +} + +TEST(Optional, Value) +{ + util::optional oi(42); + + struct foo { int bar; }; + util::optional of(foo{42}); + + EXPECT_EQ(42, oi.value()); + EXPECT_EQ(42, *oi); + + EXPECT_EQ(42, of.value().bar); + EXPECT_EQ(42, of->bar); +} + +TEST(Optional, Mutable) +{ + util::optional oi(42); + *oi = 43; + EXPECT_EQ(43, *oi); + + struct foo { int bar; int baz; }; + util::optional of(foo{11,22}); + + (*of).bar = 42; + EXPECT_EQ(42, of->bar); + EXPECT_EQ(22, of->baz); + + of->baz = 33; + EXPECT_EQ(42, of->bar); + EXPECT_EQ(33, of->baz); +} + +TEST(Optional, MoveAssign) +{ + util::optional e, i(42); + + EXPECT_FALSE(e.has_value()); + EXPECT_TRUE(i.has_value()); + EXPECT_EQ(42, *i); + + e = std::move(i); + EXPECT_TRUE(e.has_value()); + EXPECT_FALSE(i.has_value()); + EXPECT_EQ(42, *e); +} + +TEST(Optional, CopyAssign) +{ + util::optional e; + const util::optional i(42); + + EXPECT_FALSE(e.has_value()); + EXPECT_TRUE(i.has_value()); + EXPECT_EQ(42, *i); + + e = i; + EXPECT_TRUE(e.has_value()); + EXPECT_TRUE(i.has_value()); + EXPECT_EQ(42, *e); + EXPECT_EQ(42, *i); +} + +TEST(Optional, ValueOr) +{ + util::optional e; + EXPECT_FALSE(e.has_value()); + EXPECT_EQ(42, e.value_or(42)); + EXPECT_EQ(42, e.value_or(42.1)); +} + +TEST(Optional, Swap) +{ + util::optional e, i(42); + + EXPECT_FALSE(e.has_value()); + EXPECT_TRUE(i.has_value()); + EXPECT_EQ(42, *i); + + e.swap(i); + + EXPECT_TRUE(e.has_value()); + EXPECT_FALSE(i.has_value()); + EXPECT_EQ(42, *e); +} + +TEST(Optional, Reset) +{ + util::optional i(42); + EXPECT_TRUE(i.has_value()); + + i.reset(); + EXPECT_FALSE(i.has_value()); +} + +TEST(Optional, MakeOptional) +{ + std::string s("text"); + auto os = util::make_optional(s); + EXPECT_TRUE(os.has_value()); + EXPECT_EQ(s, os.value()); +} + +} // namespace opencv_test diff --git a/modules/gapi/test/util/variant_tests.cpp b/modules/gapi/test/util/variant_tests.cpp new file mode 100644 index 0000000000..a95b6aa80b --- /dev/null +++ b/modules/gapi/test/util/variant_tests.cpp @@ -0,0 +1,386 @@ +// 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) 2018 Intel Corporation + + +#include "test_precomp.hpp" +#include "opencv2/gapi/util/variant.hpp" +#include //std::max_align_t + +namespace opencv_test +{ + +namespace +{ + typedef util::variant TestVar; + typedef util::variant TestVar2; +} + +TEST(Variant, EmptyCTor) +{ + util::variant vi; + EXPECT_EQ(0, util::get(vi)); + + util::variant vis; + EXPECT_EQ(0, util::get(vis)); + + util::variant vs; + EXPECT_EQ("", util::get(vs)); + + util::variant vsi; + EXPECT_EQ("", util::get(vsi)); +} + +TEST(Variant, ValueMoveCTor) +{ + util::variant vi(42); + EXPECT_EQ(0u, vi.index()); + EXPECT_EQ(42, util::get(vi)); + + util::variant vis(2017); + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(2017, util::get(vis)); + + util::variant vis2(std::string("2017")); + EXPECT_EQ(1u, vis2.index()); + EXPECT_EQ("2017", util::get(vis2)); + + util::variant vs(std::string("2017")); + EXPECT_EQ(0u, vs.index()); + EXPECT_EQ("2017", util::get(vs)); + + util::variant vsi(std::string("2017")); + EXPECT_EQ(0u, vsi.index()); + EXPECT_EQ("2017", util::get(vsi)); + + util::variant vsi2(42); + EXPECT_EQ(1u, vsi2.index()); + EXPECT_EQ(42, util::get(vsi2)); +} + +TEST(Variant, ValueCopyCTor) +{ + const int i42 = 42; + const int i17 = 2017; + const std::string s17 = "2017"; + + util::variant vi(i42); + EXPECT_EQ(0u, vi.index()); + EXPECT_EQ(i42, util::get(vi)); + + util::variant vis(i17); + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(i17, util::get(vis)); + + util::variant vis2(s17); + EXPECT_EQ(1u, vis2.index()); + EXPECT_EQ(s17, util::get(vis2)); + + util::variant vs(s17); + EXPECT_EQ(0u, vs.index()); + EXPECT_EQ(s17, util::get(vs)); + + util::variant vsi(s17); + EXPECT_EQ(0u, vsi.index()); + EXPECT_EQ(s17, util::get(vsi)); + + util::variant vsi2(i42); + EXPECT_EQ(1u, vsi2.index()); + EXPECT_EQ(i42, util::get(vsi2)); +} + +TEST(Variant, CopyMoveCTor) +{ + const TestVar tvconst(std::string("42")); + + TestVar tv = tvconst; + EXPECT_EQ( 1u, tv.index()); + EXPECT_EQ("42", util::get(tv)); + + TestVar tv2(TestVar(40+2)); + EXPECT_EQ( 0u, tv2.index()); + EXPECT_EQ( 42, util::get(tv2)); +} + +TEST(Variant, Assign_Basic) +{ + TestVar vis; + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(0, util::get(vis)); + + vis = 42; + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(42, util::get(vis)); +} + +TEST(Variant, Assign_ValueUpdate_SameType) +{ + TestVar vis(42); + + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(42, util::get(vis)); + + vis = 43; + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(43, util::get(vis)); +} + +TEST(Variant, Assign_ValueUpdate_DiffType) +{ + TestVar vis(42); + + EXPECT_EQ(0u, vis.index()); + EXPECT_EQ(42, util::get(vis)); + + vis = std::string("42"); + EXPECT_EQ(1u, vis.index()); + EXPECT_EQ("42", util::get(vis)); +} + +TEST(Variant, Assign_ValueUpdate_Const) +{ + TestVar va(42); + const TestVar vb(43); + + EXPECT_EQ(0u, va.index()); + EXPECT_EQ(42, util::get(va)); + + EXPECT_EQ(0u, vb.index()); + EXPECT_EQ(43, util::get(vb)); + + va = vb; + + EXPECT_EQ(0u, va.index()); + EXPECT_EQ(43, util::get(va)); +} + +TEST(Variant, Assign_ValueUpdate_Const_DiffType) +{ + TestVar va(42); + const TestVar vb(std::string("42")); + + EXPECT_EQ(0u, va.index()); + EXPECT_EQ(42, util::get(va)); + + EXPECT_EQ(1u, vb.index()); + EXPECT_EQ("42", util::get(vb)); + + va = vb; + + EXPECT_EQ(1u, va.index()); + EXPECT_EQ("42", util::get(va)); +} + +TEST(Variant, Assign_Move) +{ + TestVar va(42); + TestVar vb(std::string("42")); + TestVar vc(43); + + EXPECT_EQ(0u, va.index()); + EXPECT_EQ(42, util::get(va)); + + EXPECT_EQ(1u, vb.index()); + EXPECT_EQ("42", util::get(vb)); + + EXPECT_EQ(0u, vc.index()); + EXPECT_EQ(43, util::get(vc)); + + va = std::move(vb); + EXPECT_EQ(1u, va.index()); + EXPECT_EQ("42", util::get(va)); + + va = std::move(vc); + EXPECT_EQ(0u, va.index()); + EXPECT_EQ(43, util::get(va)); +} + +TEST(Variant, Swap_SameIndex) +{ + TestVar tv1(42); + TestVar tv2(43); + + EXPECT_EQ(0u, tv1.index()); + EXPECT_EQ(42, util::get(tv1)); + + EXPECT_EQ(0u, tv2.index()); + EXPECT_EQ(43, util::get(tv2)); + + tv1.swap(tv2); + + EXPECT_EQ(0u, tv1.index()); + EXPECT_EQ(43, util::get(tv1)); + + EXPECT_EQ(0u, tv2.index()); + EXPECT_EQ(42, util::get(tv2)); +} + +TEST(Variant, Swap_DiffIndex) +{ + TestVar2 tv1(42); + TestVar2 tv2(3.14f); + + EXPECT_EQ(0u, tv1.index()); + EXPECT_EQ(42, util::get(tv1)); + + EXPECT_EQ(1u, tv2.index()); + EXPECT_EQ(3.14f, util::get(tv2)); + + tv1.swap(tv2); + + EXPECT_EQ(0u, tv2.index()); + EXPECT_EQ(42, util::get(tv2)); + + EXPECT_EQ(1u, tv1.index()); + EXPECT_EQ(3.14f, util::get(tv1)); +} + +TEST(Variant, Get) +{ + const TestVar cv(42); + + // Test const& get() + EXPECT_EQ(42, util::get(cv)); + EXPECT_THROW(util::get(cv), util::bad_variant_access); + + // Test &get + TestVar cv2(std::string("42")); + EXPECT_EQ("42", util::get(cv2)); + EXPECT_THROW(util::get(cv2), util::bad_variant_access); +} + +TEST(Variant, GetWrite) +{ + util::variant v(42); + EXPECT_EQ(42, util::get(v)); + + util::get(v) = 43; + EXPECT_EQ(43, util::get(v)); +} + +TEST(Variant, NoDefaultCtor) +{ + struct MyType + { + int m_a; + MyType() = delete; + }; + + // This code MUST compile + util::variant var; + SUCCEED() << "Code compiled"; + + // At the same time, util::variant MUST NOT. +} + +TEST(Variant, MonoState) +{ + struct MyType + { + int m_a; + explicit MyType(int a) : m_a(a) {} + MyType() = delete; + }; + + util::variant var; + EXPECT_EQ(0u, var.index()); + + var = MyType{42}; + EXPECT_EQ(1u, var.index()); + EXPECT_EQ(42, util::get(var).m_a); +} + + +TEST(Variant, Eq) +{ + TestVar v1(42), v2(std::string("42")); + TestVar v3(v1), v4(v2); + + EXPECT_TRUE(v1 == v3); + EXPECT_TRUE(v2 == v4); + EXPECT_TRUE(v1 != v2); + EXPECT_TRUE(v3 != v4); + + EXPECT_FALSE(v1 == v2); + EXPECT_FALSE(v3 == v4); + EXPECT_FALSE(v1 != v3); + EXPECT_FALSE(v2 != v4); +} + +TEST(Variant, Eq_Monostate) +{ + using TestVar3 = util::variant; + TestVar3 v1; + TestVar3 v2(42); + + EXPECT_NE(v1, v2); + + v2 = util::monostate{}; + EXPECT_EQ(v1, v2); +} + +TEST(Variant, VectorOfVariants) +{ + std::vector vv1(1024); + std::vector vv2(1024); + + EXPECT_TRUE(vv1 == vv2); + + std::vector vv3(2048, TestVar(std::string("42"))); + + // Just test chat the below code compiles: + // 1: internal copy of variants from one vector to another, + // with probable reallocation of 1st vector to host all elements + std::copy(vv1.begin(), vv1.end(), std::back_inserter(vv2)); + EXPECT_EQ(2048u, vv2.size()); + + // 2: truncation of vector, with probable destruction of its tail memory + vv2.resize(1024); + EXPECT_EQ(1024u, vv2.size()); + + // 3. vector assignment, with overwriting underlying variants + vv2 = vv3; + EXPECT_EQ(2048u, vv2.size()); + EXPECT_TRUE(vv2 == vv3); +} + +TEST(Variant, HoldsAlternative) +{ + TestVar v(42); + EXPECT_TRUE (util::holds_alternative (v)); + EXPECT_FALSE(util::holds_alternative(v)); + + v = std::string("42"); + EXPECT_FALSE(util::holds_alternative (v)); + EXPECT_TRUE (util::holds_alternative(v)); +} + +TEST(Variant, Sizeof) +{ + //variant has to store index of the contained type as well as the type itself + EXPECT_EQ(2 * sizeof(size_t), (sizeof(util::variant))); +#if !defined(__GNUG__) || __GNUG__ >= 5 + // GCC versions prior to 5.0 have limited C++11 support, e.g. + // no std::max_align_t defined + EXPECT_EQ((sizeof(std::max_align_t) + std::max(sizeof(size_t), alignof(std::max_align_t))), (sizeof(util::variant))); +#endif +} + +TEST(Variant, EXT_IndexOf) +{ + struct MyType{}; + class MyClass{}; + + using V = util::variant; + static_assert(0u == V::index_of(), "Index is incorrect"); + static_assert(1u == V::index_of(), "Index is incorrect"); + static_assert(2u == V::index_of(), "Index is incorrect"); + static_assert(3u == V::index_of(), "Index is incorrect"); + static_assert(4u == V::index_of(), "Index is incorrect"); + static_assert(5u == V::index_of(), "Index is incorrect"); + static_assert(6u == V::index_of(), "Index is incorrect"); +} + +} // namespace opencv_test