diff --git a/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_gapi.png b/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_gapi.png new file mode 100644 index 0000000000..6f3147f549 Binary files /dev/null and b/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_gapi.png differ diff --git a/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_gapi_fluid.png b/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_gapi_fluid.png new file mode 100644 index 0000000000..2a643d5a70 Binary files /dev/null and b/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_gapi_fluid.png differ diff --git a/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_ocv.png b/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_ocv.png new file mode 100644 index 0000000000..af2751f0c2 Binary files /dev/null and b/doc/tutorials/gapi/anisotropic_segmentation/pics/massif_export_ocv.png differ diff --git a/doc/tutorials/gapi/anisotropic_segmentation/pics/result.jpg b/doc/tutorials/gapi/anisotropic_segmentation/pics/result.jpg new file mode 100644 index 0000000000..8c030eb020 Binary files /dev/null and b/doc/tutorials/gapi/anisotropic_segmentation/pics/result.jpg differ diff --git a/doc/tutorials/gapi/anisotropic_segmentation/pics/segm.gif b/doc/tutorials/gapi/anisotropic_segmentation/pics/segm.gif new file mode 100644 index 0000000000..dbf7ec3342 Binary files /dev/null and b/doc/tutorials/gapi/anisotropic_segmentation/pics/segm.gif differ diff --git a/doc/tutorials/gapi/anisotropic_segmentation/pics/segm_fluid.gif b/doc/tutorials/gapi/anisotropic_segmentation/pics/segm_fluid.gif new file mode 100644 index 0000000000..3a887e86df Binary files /dev/null and b/doc/tutorials/gapi/anisotropic_segmentation/pics/segm_fluid.gif differ diff --git a/doc/tutorials/gapi/anisotropic_segmentation/porting_anisotropic_segmentation.markdown b/doc/tutorials/gapi/anisotropic_segmentation/porting_anisotropic_segmentation.markdown new file mode 100644 index 0000000000..efb1eb1032 --- /dev/null +++ b/doc/tutorials/gapi/anisotropic_segmentation/porting_anisotropic_segmentation.markdown @@ -0,0 +1,411 @@ +# Porting anisotropic image segmentation on G-API {#tutorial_gapi_anisotropic_segmentation} + +[TOC] + +# Introduction {#gapi_anisotropic_intro} + +In this tutorial you will learn: +* How an existing algorithm can be transformed into a G-API + computation (graph); +* How to inspect and profile G-API graphs; +* How to customize graph execution without changing its code. + +This tutorial is based on @ref +tutorial_anisotropic_image_segmentation_by_a_gst. + +# Quick start: using OpenCV backend {#gapi_anisotropic_start} + +Before we start, let's review the original algorithm implementation: + +@include cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp + +## Examining calcGST() {#gapi_anisotropic_calcgst} + +The function calcGST() is clearly an image processing pipeline: +* It is just a sequence of operations over a number of cv::Mat; +* No logic (conditionals) and loops involved in the code; +* All functions operate on 2D images (like cv::Sobel, cv::multiply, +cv::boxFilter, cv::sqrt, etc). + +Considering the above, calcGST() is a great candidate to start +with. In the original code, its prototype is defined like this: + +@snippet cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp calcGST_proto + +With G-API, we can define it as follows: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp calcGST_proto + +It is important to understand that the new G-API based version of +calcGST() will just produce a compute graph, in contrast to its +original version, which actually calculates the values. This is a +principial difference -- G-API based functions like this are used to +construct graphs, not to process the actual data. + +Let's start implementing calcGST() with calculation of \f$J\f$ +matrix. This is how the original code looks like: + +@snippet cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp calcJ_header + +Here we need to declare output objects for every new operation (see +img as a result for cv::Mat::convertTo, imgDiffX and others as results for +cv::Sobel and cv::multiply). + +The G-API analogue is listed below: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp calcGST_header + +This snippet demonstrates the following syntactic difference between +G-API and traditional OpenCV: +* All standard G-API functions are by default placed in "cv::gapi" +namespace; +* G-API operations _return_ its results -- there's no need to pass +extra "output" parameters to the functions. + +Note -- this code is also using `auto` -- types of intermediate objects +like `img`, `imgDiffX`, and so on are inferred automatically by the +C++ compiler. In this example, the types are determined by G-API +operation return values which all are cv::GMat. + +G-API standard kernels are trying to follow OpenCV API conventions +whenever possible -- so cv::gapi::sobel takes the same arguments as +cv::Sobel, cv::gapi::mul follows cv::multiply, and so on (except +having a return value). + +The rest of calcGST() function can be implemented the same +way trivially. Below is its full source code: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp calcGST + +## Running G-API graph {#gapi_anisotropic_running} + +After calcGST() is defined in G-API language, we can construct a graph +based on it and finally run it -- pass input image and obtain +result. Before we do it, let's have a look how original code looked +like: + +@snippet cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp main_extra + +G-API-based functions like calcGST() can't be applied to input data +directly, since it is a _construction_ code, not the _processing_ code. +In order to _run_ computations, a special object of class +cv::GComputation needs to be created. This object wraps our G-API code +(which is a composition of G-API data and operations) into a callable +object, similar to C++11 +[std::function<>](https://en.cppreference.com/w/cpp/utility/functional/function). + +cv::GComputation class has a number of constructors which can be used +to define a graph. Generally, user needs to pass graph boundaries +-- _input_ and _output_ objects, on which a GComputation is +defined. Then G-API analyzes the call flow from _outputs_ to _inputs_ +and reconstructs the graph with operations in-between the specified +boundaries. This may sound complex, however in fact the code looks +like this: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp main + +Note that this code slightly changes from the original one: forming up +the resulting image is also a part of the pipeline (done with +cv::gapi::addWeighted). Normalization of orientation and coherency +images is still done by traditional OpenCV (using cv::normalize) as +G-API doesn't provide such kernel at the moment. + +Result of this G-API pipeline bit-exact matches the original one +(given the same input image): + +![Segmentation result with G-API](pics/result.jpg) + +## G-API initial version: full listing {#gapi_anisotropic_ocv} + +Below is the full listing of the initial anisotropic image +segmentation port on G-API: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp full_sample + +# Inspecting the initial version {#gapi_anisotropic_inspect} + +After we have got the initial working version of our algorithm working +with G-API, we can use it to inspect and learn how G-API works. This +chapter covers two aspects: understanding the graph structure, and +memory profiling. + +## Understanding the graph structure {#gapi_anisotropic_inspect_graph} + +G-API stands for "Graph API", but did you mention any graphs in the +above example? It was one of the initial design goals -- G-API was +designed with expressions in mind to make adoption and porting process +more straightforward. People _usually_ don't think in terms of +_Nodes_ and _Edges_ when writing ordinary code, and so G-API, while +being a Graph API, doesn't force its users to do that. + +However, a graph is still built implicitly when a cv::GComputation +object is defined. It may be useful to inspect how the resulting graph +looks like to check if it is generated correctly and if it really +represents our alrogithm. It is also useful to learn the structure of +the graph to see if it has any redundancies. + +G-API allows to dump generated graphs to `.dot` files which then +could be visualized with [Graphviz](https://www.graphviz.org/), a +popular open graph visualization software. + + + +In order to dump our graph to a `.dot` file, set `GRAPH_DUMP_PATH` to a +file name before running the application, e.g.: + + $ GRAPH_DUMP_PATH=segm.dot ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi + +Now this file can be visalized with a `dot` command like this: + + $ dot segm.dot -Tpng -o segm.png + +or viewed instantly with `xdot` command (please refer to your +distribution/operating system documentation on how to install these +packages). + +![Anisotropic image segmentation graph](pics/segm.gif) + +The above diagram demonstrates a number of interesting aspects of +G-API's internal algorithm representation: +1. G-API underlying graph is a bipartite graph: it consists of + _Operation_ and _Data_ nodes such that a _Data_ node can only be + connected to an _Operation_ node, _Operation_ node can only be + connected to a _Data_ node, and nodes of a single kind are never + connected directly. +2. Graph is directed - every edge in the graph has a direction. +3. Graph "begins" and "ends" with a _Data_ kind of nodes. +4. A _Data_ node can have only a single writer and multiple readers. +5. An _Operation_ node may have multiple inputs, though every input + must have an unique _port number_ (among inputs). +6. An _Operation_ node may have multiple outputs, and every output + must have an unique _port number_ (among outputs). + +## Measuring memory footprint {#gapi_anisotropic_memory_ocv} + +Let's measure and compare memory footprint of the algorithm in its two +versions: G-API-based and OpenCV-based. At the moment, G-API version +is also OpenCV-based since it fallbacks to OpenCV functions inside. + +On GNU/Linux, application memory footprint can be profiled with +[Valgrind](http://valgrind.org/). On Debian/Ubuntu systems it can be +installed like this (assuming you have administrator priveleges): + + $ sudo apt-get install valgrind massif-visualizer + +Once installed, we can collect memory profiles easily for our two +algorithm versions: + + $ valgrind --tool=massif --massif-out-file=ocv.out ./bin/example_tutorial_anisotropic_image_segmentation + ==6101== Massif, a heap profiler + ==6101== Copyright (C) 2003-2015, and GNU GPL'd, by Nicholas Nethercote + ==6101== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info + ==6101== Command: ./bin/example_tutorial_anisotropic_image_segmentation + ==6101== + ==6101== + $ valgrind --tool=massif --massif-out-file=gapi.out ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi + ==6117== Massif, a heap profiler + ==6117== Copyright (C) 2003-2015, and GNU GPL'd, by Nicholas Nethercote + ==6117== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info + ==6117== Command: ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi + ==6117== + ==6117== + +Once done, we can inspect the collected profiles with +[Massif Visualizer](@https://github.com/KDE/massif-visualizer) +(installed in the above step). + +Below is the visualized memory profile of the original OpenCV version +of the algorithm: + +![Memory profile: original Anisotropic Image Segmentation sample](pics/massif_export_ocv.png) + +We see that memory is allocated as the application +executes, reaching its peak in the calcGST() function; then the +footprint drops as calcGST() completes its execution and all temporary +buffers are freed. Massif reports us peak memory consumption of 7.6 MiB. + +Now let's have a look on the profile of G-API version: + +![Memory profile: G-API port of Anisotropic Image Segmentation sample](pics/massif_export_gapi.png) + +Once G-API computation is created and its execution starts, G-API +allocates all required memory at once and then the memory profile +remains flat until the termination of the program. Massif reports us +peak memory consumption of 10.6 MiB. + +A reader may ask a right question at this point -- is G-API that bad? +What is the reason in using it than? + +Hopefully, it is not. The reason why we see here an increased memory +consumption is because the default naive OpenCV-based backend is used to +execute this graph. This backend serves mostly for quick prototyping +and debugging algorithms before offload/further optimization. + +This backend doesn't utilize any complex memory mamagement strategies yet +since it is not its point at the moment. In the following chapter, +we'll learn about Fluid backend and see how the same G-API code can +run in a completely different model (and the footprint shrinked to a +number of kilobytes). + +# Backends and kernels {#gapi_anisotropic_backends} + +This chapter covers how a G-API computation can be executed in a +special way -- e.g. offloaded to another device, or scheduled with a +special intelligence. G-API is designed to make its graphs portable -- +it means that once a graph is defined in G-API terms, no changes +should be required in it if we want to run it on CPU or on GPU or on +both devices at once. [G-API High-level overview](@ref gapi_hld) and +[G-API Kernel API](@ref gapi_kernel_api) shed more light on technical +details which make it possible. In this chapter, we will utilize G-API +Fluid backend to make our graph cache-efficient on CPU. + +G-API defines _backend_ as the lower-level entity which knows how to +run kernels. Backends may have (and, in fact, do have) different +_Kernel APIs_ which are used to program and integrate kernels for that +backends. In this context, _kernel_ is an implementaion of an +_operation_, which is defined on the top API level (see +G_TYPED_KERNEL() macro). + +Backend is a thing which is aware of device & platform specifics, and +which executes its kernels with keeping that specifics in mind. For +example, there may be [Halide](http://halide-lang.org/) backend which +allows to write (implement) G-API operations in Halide language and +then generate functional Halide code for portions of G-API graph which +map well there. + +## Running a graph with a Fluid backend {#gapi_anisotropic_fluid} + +OpenCV 4.0 is bundled with two G-API backends -- the default "OpenCV" +which we just used, and a special "Fluid" backend. + +Fluid backend reorganizes the execution to save memory and to achieve +near-perfect cache locality, implementing so-called "streaming" model +of execution. + +In order to start using Fluid kernels, we need first to include +appropriate header files (which are not included by default): + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp fluid_includes + +Once these headers are included, we can form up a new _kernel package_ +and specify it to G-API: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp kernel_pkg + +In G-API, kernels (or operation implementations) are objects. Kernels are +organized into collections, or _kernel packages_, represented by class +cv::gapi::GKernelPackage. The main purpose of a kernel package is to +capture which kernels we would like to use in our graph, and pass it +as a _graph compilation option_: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp kernel_pkg_use + +Traditional OpenCV is logically divided into modules, whith every +module providing a set of functions. In G-API, there are also +"modules" which are represented as kernel packages provided by a +particular backend. In this example, we pass Fluid kernel packages to +G-API to utilize appropriate Fluid functions in our graph. + +Kernel packages are combinable -- in the above example, we take "Core" +and "ImgProc" Fluid kernel packages and combine it into a single +one. See documentation reference on cv::gapi::combine and +cv::unite_policy on package combination options. + +If no kernel packages are specified in options, G-API is using +_default_ package which consists of default OpenCV implementations and +thus G-API graphs are executed via OpenCV functions by default. OpenCV +backend provides broader functional coverage than any other +backend. If a kernel package is specified, like in this example, then +it is being combined with the _default_ one with +cv::unite_policy::REPLACE. It means that user-specified +implementations will replace default implementations in case of +conflict. + +Kernel packages may contain a mix of kernels, in particular, multiple +implementations of the same kernel. For example, a single kernel +package may contain both OpenCV and Fluid implementations of kernel +"Filter2D". In this case, the implementation selection preference can +be specified with a special compilation parameter cv::gapi::lookup_order. + + + + +## Troubleshooting and customization {#gapi_anisotropic_trouble} + +After the above modifications, (in OpenCV 4.0) the app should crash +with a message like this: + +``` +$ ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi_fluid +terminate called after throwing an instance of 'std::logic_error' + what(): .../modules/gapi/src/backends/fluid/gfluidimgproc.cpp:436: Assertion kernelSize.width == 3 && kernelSize.height == 3 in function run failed + +Aborted (core dumped) +``` + +Fluid backend has a number of limitations in OpenCV 4.0 (see this +[wiki page](https://github.com/opencv/opencv/wiki/Graph-API) for a +more up-to-date status). In particular, the Box filter used in this +sample supports only static 3x3 kernel size. + +We can overcome this problem easily by avoiding G-API using Fluid +version of Box filter kernel in this sample. It can be done by +removing the appropriate kernel from the kernel package we've just +created: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp kernel_hotfix + +Now this kernel package doesn't have _any_ implementation of Box +filter kernel interface (specified as a template parameter). As +described above, G-API will fall-back to OpenCV to run this kernel +now. The resulting code with this change now looks like: + +@snippet cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp kernel_pkg_proper + +Let's examine the memory profile for this sample after we switched to +Fluid backend. Now it looks like this: + +![Memory profile: G-API/Fluid port of Anisotropic Image Segmentation sample](pics/massif_export_gapi_fluid.png) + +Now the tool reports 3.8MiB -- and we just changed a few lines in our +code, without modifying the graph itself! It is a ~2.8X improvement of +the previous G-API result, and 2X improvement of the original OpenCV +version. + +Let's also examine how the internal representation of the graph now +looks like. Dumping the graph into `.dot` would result into a +visualization like this: + +![Anisotropic image segmentation graph with OpenCV & Fluid kernels](pics/segm_fluid.gif) + +This graph doesn't differ structually from its previous version (in +terms of operations and data objects), though a changed layout (on the +left side of the dump) is easily noticeable. + +The visualization reflects how G-API deals with mixed graphs, also +called _heterogeneous_ graphs. The majority of operations in this +graph are implemented with Fluid backend, but Box filters are executed +by the OpenCV backend. One can easily see that the graph is partioned +(with rectangles). G-API groups connected operations based on their +affinity, forming _subgraphs_ (or _islands_ in G-API terminology), and +our top-level graph becomes a composition of multiple smaller +subgraphs. Every backend determines how its subgraph (island) is +executed, so Fluid backend optimizes out memory where possible, and +six intermediate buffers accessed by OpenCV Box filters are allocated +fully and can't be optimized out. + + + + + +# Conclusion {#gapi_tutor_conclusion} + +This tutorial demonstrates what G-API is and what its key design +concepts are, how an algorithm can be ported to G-API, and +how to utilize graph model benefits after that. + +In OpenCV 4.0, G-API is still in its inception stage -- it is more a +foundation for all future work, though ready for use even now. + +Further, this tutorial will be extended with new chapters on custom +kernels programming, parallelism, and more. diff --git a/doc/tutorials/gapi/table_of_content_gapi.markdown b/doc/tutorials/gapi/table_of_content_gapi.markdown new file mode 100644 index 0000000000..bd66940178 --- /dev/null +++ b/doc/tutorials/gapi/table_of_content_gapi.markdown @@ -0,0 +1,17 @@ +# Graph API (gapi module) {#tutorial_table_of_content_gapi} + +In this section you will learn about graph-based image processing and +how G-API module can be used for that. + +- @subpage tutorial_gapi_anisotropic_segmentation + + *Languages:* C++ + + *Compatibility:* \> OpenCV 4.0 + + *Author:* Dmitry Matveev + + This is an end-to-end tutorial where an existing sample algorithm + is ported on G-API, covering the basic intuition behind this + transition process, and examining benefits which a graph model + brings there. diff --git a/doc/tutorials/tutorials.markdown b/doc/tutorials/tutorials.markdown index 202a6dccf2..2934165aef 100644 --- a/doc/tutorials/tutorials.markdown +++ b/doc/tutorials/tutorials.markdown @@ -67,6 +67,10 @@ As always, we would be happy to hear your comments and receive your contribution Use the powerful machine learning classes for statistical classification, regression and clustering of data. +- @subpage tutorial_table_of_content_gapi + + Learn how to use Graph API (G-API) and port algorithms from "traditional" OpenCV to a graph model. + - @subpage tutorial_table_of_content_photo Use OpenCV for diff --git a/modules/gapi/doc/00-root.markdown b/modules/gapi/doc/00-root.markdown index 0e4984106f..bbc90ab44f 100644 --- a/modules/gapi/doc/00-root.markdown +++ b/modules/gapi/doc/00-root.markdown @@ -12,6 +12,10 @@ specific CV algorithm. G-API provides means to define CV operations, construct graphs (in form of expressions) using it, and finally implement and run the operations for a particular backend. +@note G-API is a new module and now is in active development. It's API +is volatile at the moment and there may be minor but +compatibility-breaking changes in the future. + # Contents G-API documentation is organized into the following chapters: @@ -103,7 +107,7 @@ There is a number important concepts can be outlines with this examle: -See Tutorial[TBD] and Porting examples[TBD] to learn more on various -G-API features and concepts. +See [tutorials and porting examples](@ref tutorial_table_of_content_gapi) +to learn more on various G-API features and concepts. diff --git a/modules/gapi/include/opencv2/gapi/core.hpp b/modules/gapi/include/opencv2/gapi/core.hpp index bdc7af6d39..9af3620fe9 100644 --- a/modules/gapi/include/opencv2/gapi/core.hpp +++ b/modules/gapi/include/opencv2/gapi/core.hpp @@ -144,6 +144,12 @@ namespace core { } }; + G_TYPED_KERNEL(GPhase, , "org.opencv.core.math.phase") { + static GMatDesc outMeta(const GMatDesc &inx, const GMatDesc &, bool) { + return inx; + } + }; + G_TYPED_KERNEL(GMask, , "org.opencv.core.pixelwise.mask") { static GMatDesc outMeta(GMatDesc in, GMatDesc) { return in; @@ -447,6 +453,12 @@ namespace core { return rdepth < 0 ? in : in.withDepth(rdepth); } }; + + G_TYPED_KERNEL(GSqrt, , "org.opencv.core.math.sqrt") { + static GMatDesc outMeta(GMatDesc in) { + return in; + } + }; } //! @addtogroup gapi_math @@ -738,6 +750,35 @@ in radians (which is by default), or in degrees. */ GAPI_EXPORTS std::tuple cartToPolar(const GMat& x, const GMat& y, bool angleInDegrees = false); + +/** @brief Calculates the rotation angle of 2D vectors. + +The function cv::phase calculates the rotation angle of each 2D vector that +is formed from the corresponding elements of x and y : +\f[\texttt{angle} (I) = \texttt{atan2} ( \texttt{y} (I), \texttt{x} (I))\f] + +The angle estimation accuracy is about 0.3 degrees. When x(I)=y(I)=0 , +the corresponding angle(I) is set to 0. +@param x input floating-point array of x-coordinates of 2D vectors. +@param y input array of y-coordinates of 2D vectors; it must have the +same size and the same type as x. +@param angleInDegrees when true, the function calculates the angle in +degrees, otherwise, they are measured in radians. +@return array of vector angles; it has the same size and same type as x. +*/ +GAPI_EXPORTS GMat phase(const GMat& x, const GMat &y, bool angleInDegrees = false); + +/** @brief Calculates a square root of array elements. + +The function cv::gapi::sqrt calculates a square root of each input array element. +In case of multi-channel arrays, each channel is processed +independently. The accuracy is approximately the same as of the built-in +std::sqrt . +@param src input floating-point array. +@return output array of the same size and type as src. +*/ +GAPI_EXPORTS GMat sqrt(const GMat &src); + //! @} gapi_math //! //! @addtogroup gapi_pixelwise diff --git a/modules/gapi/src/backends/fluid/gfluidcore.hpp b/modules/gapi/include/opencv2/gapi/fluid/core.hpp similarity index 62% rename from modules/gapi/src/backends/fluid/gfluidcore.hpp rename to modules/gapi/include/opencv2/gapi/fluid/core.hpp index eeae8d3742..8c21f5760a 100644 --- a/modules/gapi/src/backends/fluid/gfluidcore.hpp +++ b/modules/gapi/include/opencv2/gapi/fluid/core.hpp @@ -5,10 +5,11 @@ // Copyright (C) 2018 Intel Corporation -#ifndef OPENCV_GAPI_GFLUIDCORE_HPP -#define OPENCV_GAPI_GFLUIDCORE_HPP +#ifndef OPENCV_GAPI_FLUID_CORE_HPP +#define OPENCV_GAPI_FLUID_CORE_HPP -#include "opencv2/gapi/fluid/gfluidkernel.hpp" +#include // GKernelPackage +#include // GAPI_EXPORTS namespace cv { namespace gapi { namespace core { namespace fluid { @@ -16,4 +17,4 @@ GAPI_EXPORTS GKernelPackage kernels(); }}}} -#endif // OPENCV_GAPI_GFLUIDCORE_HPP +#endif // OPENCV_GAPI_FLUID_CORE_HPP diff --git a/modules/gapi/src/backends/fluid/gfluidimgproc.hpp b/modules/gapi/include/opencv2/gapi/fluid/imgproc.hpp similarity index 61% rename from modules/gapi/src/backends/fluid/gfluidimgproc.hpp rename to modules/gapi/include/opencv2/gapi/fluid/imgproc.hpp index 810f736811..dedfa9dbe1 100644 --- a/modules/gapi/src/backends/fluid/gfluidimgproc.hpp +++ b/modules/gapi/include/opencv2/gapi/fluid/imgproc.hpp @@ -5,10 +5,11 @@ // Copyright (C) 2018 Intel Corporation -#ifndef OPENCV_GAPI_GFLUIDIMGPROC_HPP -#define OPENCV_GAPI_GFLUIDIMGPROC_HPP +#ifndef OPENCV_GAPI_FLUID_IMGPROC_HPP +#define OPENCV_GAPI_FLUID_IMGPROC_HPP -#include "opencv2/gapi/fluid/gfluidkernel.hpp" +#include // GKernelPackage +#include // GAPI_EXPORTS namespace cv { namespace gapi { namespace imgproc { namespace fluid { @@ -16,4 +17,4 @@ GAPI_EXPORTS GKernelPackage kernels(); }}}} -#endif // OPENCV_GAPI_GFLUIDIMGPROC_HPP +#endif // OPENCV_GAPI_FLUID_IMGPROC_HPP diff --git a/modules/gapi/include/opencv2/gapi/gkernel.hpp b/modules/gapi/include/opencv2/gapi/gkernel.hpp index 74caeca764..0b6c690034 100644 --- a/modules/gapi/include/opencv2/gapi/gkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/gkernel.hpp @@ -313,6 +313,9 @@ namespace gapi { // by API textual id. bool includesAPI(const std::string &id) const; + // Remove ALL implementations of the given API (identified by ID) + void removeAPI(const std::string &id); + public: // Return total number of kernels (accross all backends) std::size_t size() const; @@ -331,8 +334,16 @@ namespace gapi { // Removes all the kernels related to the given backend void remove(const GBackend& backend); + template + void remove() + { + removeAPI(KAPI::id()); + } + // Check if package contains ANY implementation of a kernel API // by API type. + // FIXME: Rename to includes() and distinguish API/impl case by + // statically? template bool includesAPI() const { @@ -354,11 +365,16 @@ namespace gapi { // Put a new kernel implementation into package // FIXME: No overwrites allowed? - template void include() + template + void include(const cv::unite_policy up = cv::unite_policy::KEEP) { auto backend = KImpl::backend(); auto kernel_id = KImpl::API::id(); auto kernel_impl = GKernelImpl{KImpl::kernel()}; + if (up == cv::unite_policy::REPLACE) removeAPI(kernel_id); + else GAPI_Assert(up == cv::unite_policy::KEEP); + + // Regardless of the policy, store new impl in its storage slot. m_backend_kernels[backend][kernel_id] = std::move(kernel_impl); } @@ -366,8 +382,8 @@ namespace gapi { std::vector backends() const; friend GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &, - const GKernelPackage &, - const cv::unite_policy); + const GKernelPackage &, + const cv::unite_policy); }; template GKernelPackage kernels() @@ -389,8 +405,8 @@ namespace gapi { // 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); + const GKernelPackage &rhs, + const cv::unite_policy policy); } // namespace gapi namespace detail diff --git a/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_fluid.cpp b/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_fluid.cpp index d9d4f28212..c6460e72e3 100644 --- a/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_fluid.cpp +++ b/modules/gapi/perf/cpu/gapi_imgproc_perf_tests_fluid.cpp @@ -7,8 +7,6 @@ #include "../perf_precomp.hpp" #include "../common/gapi_imgproc_perf_tests.hpp" -#include "../../src/backends/fluid/gfluidimgproc.hpp" - #define IMGPROC_FLUID cv::gapi::imgproc::fluid::kernels() diff --git a/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp b/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp index ef532ab9f8..48786b6a96 100644 --- a/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp +++ b/modules/gapi/perf/internal/gapi_compiler_perf_tests.cpp @@ -7,7 +7,6 @@ #include "perf_precomp.hpp" #include "../../test/common/gapi_tests_common.hpp" -#include "../../src/backends/fluid/gfluidcore.hpp" namespace opencv_test { diff --git a/modules/gapi/perf/perf_precomp.hpp b/modules/gapi/perf/perf_precomp.hpp index 7b50d6bd7e..abd7cbe66f 100644 --- a/modules/gapi/perf/perf_precomp.hpp +++ b/modules/gapi/perf/perf_precomp.hpp @@ -19,4 +19,7 @@ #include "opencv2/gapi/gpu/ggpukernel.hpp" #include "opencv2/gapi/operators.hpp" -#endif +#include "opencv2/gapi/fluid/core.hpp" +#include "opencv2/gapi/fluid/imgproc.hpp" + +#endif // __OPENCV_GAPI_PERF_PRECOMP_HPP__ diff --git a/modules/gapi/src/api/gkernel.cpp b/modules/gapi/src/api/gkernel.cpp index 5dc98f0a59..f8c851abf2 100644 --- a/modules/gapi/src/api/gkernel.cpp +++ b/modules/gapi/src/api/gkernel.cpp @@ -34,6 +34,12 @@ bool cv::gapi::GKernelPackage::includesAPI(const std::string &id) const return (it != m_backend_kernels.end()); } +void cv::gapi::GKernelPackage::removeAPI(const std::string &id) +{ + for (auto &bk : m_backend_kernels) + bk.second.erase(id); +} + std::size_t cv::gapi::GKernelPackage::size() const { return std::accumulate(m_backend_kernels.begin(), @@ -53,7 +59,7 @@ cv::gapi::GKernelPackage cv::gapi::combine(const GKernelPackage &lhs, { // REPLACE policy: if there is a collision, prefer RHS // to LHS - // since OTHER package has a prefernece, start with its copy + // since RHS package has a precedense, start with its copy GKernelPackage result(rhs); // now iterate over LHS package and put kernel if and only // if there's no such one diff --git a/modules/gapi/src/api/kernels_core.cpp b/modules/gapi/src/api/kernels_core.cpp index c4f8d89abb..c9fe19ed66 100644 --- a/modules/gapi/src/api/kernels_core.cpp +++ b/modules/gapi/src/api/kernels_core.cpp @@ -104,6 +104,11 @@ std::tuple cartToPolar(const GMat& x, const GMat& y, return core::GCartToPolar::on(x, y, angleInDegrees); } +GMat phase(const GMat &x, const GMat &y, bool angleInDegrees) +{ + return core::GPhase::on(x, y, angleInDegrees); +} + GMat cmpGT(const GMat& src1, const GMat& src2) { return core::GCmpGT::on(src1, src2); @@ -345,5 +350,10 @@ GMat convertTo(const GMat& m, int rtype, double alpha, double beta) return core::GConvertTo::on(m, rtype, alpha, beta); } +GMat sqrt(const GMat& src) +{ + return core::GSqrt::on(src); +} + } //namespace gapi } //namespace cv diff --git a/modules/gapi/src/backends/cpu/gcpucore.cpp b/modules/gapi/src/backends/cpu/gcpucore.cpp index 39cde424b1..c42f863bfe 100644 --- a/modules/gapi/src/backends/cpu/gcpucore.cpp +++ b/modules/gapi/src/backends/cpu/gcpucore.cpp @@ -132,6 +132,14 @@ GAPI_OCV_KERNEL(GCPUCartToPolar, cv::gapi::core::GCartToPolar) } }; +GAPI_OCV_KERNEL(GCPUPhase, cv::gapi::core::GPhase) +{ + static void run(const cv::Mat &x, const cv::Mat &y, bool angleInDegrees, cv::Mat &out) + { + cv::phase(x, y, out, angleInDegrees); + } +}; + GAPI_OCV_KERNEL(GCPUCmpGT, cv::gapi::core::GCmpGT) { static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out) @@ -509,6 +517,14 @@ GAPI_OCV_KERNEL(GCPUConvertTo, cv::gapi::core::GConvertTo) } }; +GAPI_OCV_KERNEL(GCPUSqrt, cv::gapi::core::GSqrt) +{ + static void run(const cv::Mat& in, cv::Mat &out) + { + cv::sqrt(in, out); + } +}; + cv::gapi::GKernelPackage cv::gapi::core::cpu::kernels() { static auto pkg = cv::gapi::kernels @@ -527,6 +543,7 @@ cv::gapi::GKernelPackage cv::gapi::core::cpu::kernels() , GCPUMask , GCPUPolarToCart , GCPUCartToPolar + , GCPUPhase , GCPUCmpGT , GCPUCmpGE , GCPUCmpLE @@ -572,6 +589,7 @@ cv::gapi::GKernelPackage cv::gapi::core::cpu::kernels() , GCPUConcatVert , GCPULUT , GCPUConvertTo + , GCPUSqrt >(); return pkg; } diff --git a/modules/gapi/src/backends/fluid/gfluidbackend.cpp b/modules/gapi/src/backends/fluid/gfluidbackend.cpp index 282766a895..7e21a4f86d 100644 --- a/modules/gapi/src/backends/fluid/gfluidbackend.cpp +++ b/modules/gapi/src/backends/fluid/gfluidbackend.cpp @@ -32,8 +32,6 @@ #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! diff --git a/modules/gapi/src/backends/fluid/gfluidcore.cpp b/modules/gapi/src/backends/fluid/gfluidcore.cpp index 3b6e7bdf1b..16a63e2174 100644 --- a/modules/gapi/src/backends/fluid/gfluidcore.cpp +++ b/modules/gapi/src/backends/fluid/gfluidcore.cpp @@ -10,17 +10,18 @@ #include "opencv2/gapi/own/assert.hpp" #include "opencv2/core/traits.hpp" +#include "opencv2/core/hal/hal.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 "opencv2/gapi/fluid/core.hpp" #include "gfluidbuffer_priv.hpp" #include "gfluidbackend.hpp" #include "gfluidutils.hpp" -#include "gfluidcore.hpp" #include #include @@ -1543,7 +1544,6 @@ 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(); @@ -1552,13 +1552,26 @@ static void run_inrange(Buffer &dst, const View &src, const cv::Scalar &upperb, 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); + if (std::is_integral::value) + { + // for integral input, in[i] >= lower equals in[i] >= ceil(lower) + // so we can optimize compare operations by rounding lower/upper + lower[c] = saturate(lowerb[c], ceild); + upper[c] = saturate(upperb[c], floord); + } + else + { + // FIXME: now values used in comparison are floats (while they + // have double precision initially). Comparison float/float + // may differ from float/double (how it should work in this case) + // + // Example: threshold=1/3 (or 1/10) + lower[c] = static_cast(lowerb[c]); + upper[c] = static_cast(upperb[c]); + } } // manually SIMD for important case if RGB/BGR @@ -1611,6 +1624,7 @@ GAPI_FLUID_KERNEL(GFluidInRange, cv::gapi::core::GInRange, false) 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); + INRANGE_(uchar, float, run_inrange, dst, src, upperb, lowerb); CV_Error(cv::Error::StsBadArg, "unsupported combination of types"); } @@ -1951,6 +1965,35 @@ GAPI_FLUID_KERNEL(GFluidCartToPolar, cv::gapi::core::GCartToPolar, false) } }; +GAPI_FLUID_KERNEL(GFluidPhase, cv::gapi::core::GPhase, false) +{ + static const int Window = 1; + + static void run(const View &src_x, + const View &src_y, + bool angleInDegrees, + Buffer &dst) + { + const auto w = dst.length() * dst.meta().chan; + if (src_x.meta().depth == CV_32F && src_y.meta().depth == CV_32F) + { + hal::fastAtan32f(src_y.InLine(0), + src_x.InLine(0), + dst.OutLine(), + w, + angleInDegrees); + } + else if (src_x.meta().depth == CV_64F && src_y.meta().depth == CV_64F) + { + hal::fastAtan64f(src_y.InLine(0), + src_x.InLine(0), + dst.OutLine(), + w, + angleInDegrees); + } else GAPI_Assert(false && !"Phase supports 32F/64F input only!"); + } +}; + GAPI_FLUID_KERNEL(GFluidResize, cv::gapi::core::GResize, true) { static const int Window = 1; @@ -2052,6 +2095,28 @@ GAPI_FLUID_KERNEL(GFluidResize, cv::gapi::core::GResize, true) } }; +GAPI_FLUID_KERNEL(GFluidSqrt, cv::gapi::core::GSqrt, false) +{ + static const int Window = 1; + + static void run(const View &in, Buffer &out) + { + const auto w = out.length() * out.meta().chan; + if (in.meta().depth == CV_32F) + { + hal::sqrt32f(in.InLine(0), + out.OutLine(0), + w); + } + else if (in.meta().depth == CV_64F) + { + hal::sqrt64f(in.InLine(0), + out.OutLine(0), + w); + } else GAPI_Assert(false && !"Sqrt supports 32F/64F input only!"); + } +}; + } // namespace fliud } // namespace gapi } // namespace cv @@ -2088,6 +2153,7 @@ cv::gapi::GKernelPackage cv::gapi::core::fluid::kernels() ,GFluidSelect ,GFluidPolarToCart ,GFluidCartToPolar + ,GFluidPhase ,GFluidAddC ,GFluidSubC ,GFluidSubRC @@ -2105,6 +2171,7 @@ cv::gapi::GKernelPackage cv::gapi::core::fluid::kernels() ,GFluidThreshold ,GFluidInRange ,GFluidResize + ,GFluidSqrt #if 0 ,GFluidMean -- not fluid ,GFluidSum -- not fluid diff --git a/modules/gapi/src/backends/fluid/gfluidimgproc.cpp b/modules/gapi/src/backends/fluid/gfluidimgproc.cpp index 71be5e02f8..79101be359 100644 --- a/modules/gapi/src/backends/fluid/gfluidimgproc.cpp +++ b/modules/gapi/src/backends/fluid/gfluidimgproc.cpp @@ -19,10 +19,10 @@ #include "opencv2/gapi/fluid/gfluidbuffer.hpp" #include "opencv2/gapi/fluid/gfluidkernel.hpp" +#include "opencv2/gapi/fluid/imgproc.hpp" #include "gfluidbuffer_priv.hpp" #include "gfluidbackend.hpp" -#include "gfluidimgproc.hpp" #include "gfluidutils.hpp" #include "gfluidimgproc_func.hpp" diff --git a/modules/gapi/test/common/gapi_core_tests.hpp b/modules/gapi/test/common/gapi_core_tests.hpp index 733b2f7a02..77a82dfd20 100644 --- a/modules/gapi/test/common/gapi_core_tests.hpp +++ b/modules/gapi/test/common/gapi_core_tests.hpp @@ -146,6 +146,8 @@ struct ConcatVertVecTest : public TestWithParam> {}; struct LUTTest : public TestParams> {}; struct ConvertToTest : public TestParams> {}; +struct PhaseTest : public TestParams> {}; +struct SqrtTest : 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 index 1c0d6a3de3..d33b5cc637 100644 --- a/modules/gapi/test/common/gapi_core_tests_inl.hpp +++ b/modules/gapi/test/common/gapi_core_tests_inl.hpp @@ -1422,6 +1422,58 @@ TEST_P(ConvertToTest, AccuracyTest) } } +TEST_P(PhaseTest, AccuracyTest) +{ + int img_type = -1; + cv::Size img_size; + bool angle_in_degrees = false; + cv::GCompileArgs compile_args; + std::tie(img_type, img_size, angle_in_degrees, compile_args) = GetParam(); + initMatsRandU(img_type, img_size, img_type); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in_x, in_y; + auto out = cv::gapi::phase(in_x, in_y, angle_in_degrees); + + cv::GComputation c(in_x, in_y, out); + c.apply(in_mat1, in_mat2, out_mat_gapi, std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + cv::phase(in_mat1, in_mat2, out_mat_ocv, angle_in_degrees); + + // Comparison ////////////////////////////////////////////////////////////// + // FIXME: use a comparison functor instead (after enabling OpenCL) + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + } +} + +TEST_P(SqrtTest, AccuracyTest) +{ + int img_type = -1; + cv::Size img_size; + cv::GCompileArgs compile_args; + std::tie(img_type, img_size, compile_args) = GetParam(); + initMatrixRandU(img_type, img_size, img_type); + + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + auto out = cv::gapi::sqrt(in); + + cv::GComputation c(in, out); + c.apply(in_mat1, out_mat_gapi, std::move(compile_args)); + + // OpenCV code ///////////////////////////////////////////////////////////// + cv::sqrt(in_mat1, out_mat_ocv); + + // Comparison ////////////////////////////////////////////////////////////// + // FIXME: use a comparison functor instead (after enabling OpenCL) + { + EXPECT_EQ(0, cv::countNonZero(out_mat_ocv != out_mat_gapi)); + } +} + + } // opencv_test #endif //OPENCV_GAPI_CORE_TESTS_INL_HPP diff --git a/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp b/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp index a775e551f6..11e78bd99d 100644 --- a/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp +++ b/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp @@ -137,6 +137,21 @@ INSTANTIATE_TEST_CASE_P(Cart2PolarCPU, Cart2PolarTest, /*init output matrices or not*/ testing::Bool(), Values(cv::compile_args(CORE_CPU)))); +INSTANTIATE_TEST_CASE_P(PhaseCPU, PhaseTest, + Combine(Values(CV_32F, CV_32FC3), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + testing::Bool(), + Values(cv::compile_args(CORE_CPU)))); + +INSTANTIATE_TEST_CASE_P(SqrtCPU, SqrtTest, + Combine(Values(CV_32F, CV_32FC3), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + 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(), @@ -255,7 +270,7 @@ INSTANTIATE_TEST_CASE_P(ThresholdTestCPU, ThresholdOTTest, INSTANTIATE_TEST_CASE_P(InRangeTestCPU, InRangeTest, - Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1), + Combine(Values(CV_8UC1, CV_16UC1, CV_16SC1, CV_32FC1), Values(cv::Size(1280, 720), cv::Size(640, 480), cv::Size(128, 128)), diff --git a/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp b/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp index ed281fa3af..c65052b367 100644 --- a/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp +++ b/modules/gapi/test/cpu/gapi_core_tests_fluid.cpp @@ -7,7 +7,6 @@ #include "../test_precomp.hpp" #include "../common/gapi_core_tests.hpp" -#include "backends/fluid/gfluidcore.hpp" namespace opencv_test { @@ -193,6 +192,21 @@ INSTANTIATE_TEST_CASE_P(Cart2PolarFluid, Cart2PolarTest, testing::Bool(), Values(cv::compile_args(CORE_FLUID)))); +INSTANTIATE_TEST_CASE_P(PhaseFluid, PhaseTest, + Combine(Values(CV_32F, CV_32FC3), + 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(SqrtFluid, SqrtTest, + Combine(Values(CV_32F, CV_32FC3), + Values(cv::Size(1280, 720), + cv::Size(640, 480), + cv::Size(128, 128)), + 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), @@ -206,7 +220,7 @@ INSTANTIATE_TEST_CASE_P(ThresholdTestFluid, ThresholdTest, Values(cv::compile_args(CORE_FLUID)))); INSTANTIATE_TEST_CASE_P(InRangeTestFluid, InRangeTest, - Combine(Values(CV_8UC3, CV_8UC1, CV_16UC1, CV_16SC1), + 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), diff --git a/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp b/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp index c830124b06..5dca2092a2 100644 --- a/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp +++ b/modules/gapi/test/cpu/gapi_imgproc_tests_fluid.cpp @@ -7,7 +7,6 @@ #include "../test_precomp.hpp" #include "../common/gapi_imgproc_tests.hpp" -#include "backends/fluid/gfluidimgproc.hpp" #define IMGPROC_FLUID cv::gapi::imgproc::fluid::kernels() diff --git a/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp b/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp index 603e17b7d4..4179fa53b0 100644 --- a/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp +++ b/modules/gapi/test/cpu/gapi_operators_tests_fluid.cpp @@ -7,9 +7,8 @@ #include "test_precomp.hpp" #include "../common/gapi_operators_tests.hpp" -#include "opencv2/gapi/cpu/core.hpp" -#define CORE_FLUID cv::gapi::core::cpu::kernels() +#define CORE_FLUID cv::gapi::core::fluid::kernels() namespace opencv_test { diff --git a/modules/gapi/test/gapi_kernel_tests.cpp b/modules/gapi/test/gapi_kernel_tests.cpp index 509c862e55..aeb47628e0 100644 --- a/modules/gapi/test/gapi_kernel_tests.cpp +++ b/modules/gapi/test/gapi_kernel_tests.cpp @@ -46,7 +46,29 @@ TEST(KernelPackage, Includes) EXPECT_FALSE(pkg.includes()); } -TEST(KernelPackage, Include) +TEST(KernelPackage, IncludesAPI) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_TRUE (pkg.includesAPI()); + EXPECT_TRUE (pkg.includesAPI()); + EXPECT_FALSE(pkg.includesAPI()); + EXPECT_FALSE(pkg.includesAPI()); +} + +TEST(KernelPackage, IncludesAPI_Overlapping) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_TRUE (pkg.includesAPI()); + EXPECT_TRUE (pkg.includesAPI()); + EXPECT_FALSE(pkg.includesAPI()); + EXPECT_FALSE(pkg.includesAPI()); +} + +TEST(KernelPackage, Include_Add) { namespace J = Jupiter; auto pkg = cv::gapi::kernels(); @@ -56,6 +78,66 @@ TEST(KernelPackage, Include) EXPECT_TRUE(pkg.includes()); } +TEST(KernelPackage, Include_KEEP) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_FALSE(pkg.includes()); + EXPECT_FALSE(pkg.includes()); + + pkg.include(); // default (KEEP) + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); + + pkg.include(cv::unite_policy::KEEP); // explicit (KEEP) + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); +} + +TEST(KernelPackage, Include_REPLACE) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_FALSE(pkg.includes()); + + pkg.include(cv::unite_policy::REPLACE); + EXPECT_FALSE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); +} + +TEST(KernelPackage, RemoveBackend) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); + + pkg.remove(J::backend()); + EXPECT_FALSE(pkg.includes()); + EXPECT_FALSE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); +}; + +TEST(KernelPackage, RemoveAPI) +{ + namespace J = Jupiter; + namespace S = Saturn; + auto pkg = cv::gapi::kernels(); + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); + + pkg.remove(); + EXPECT_TRUE(pkg.includes()); + EXPECT_TRUE(pkg.includes()); + EXPECT_FALSE(pkg.includes()); + EXPECT_FALSE(pkg.includes()); +}; + TEST(KernelPackage, CreateHetero) { namespace J = Jupiter; @@ -89,7 +171,7 @@ TEST(KernelPackage, IncludeHetero) EXPECT_TRUE (pkg.includes()); } -TEST(KernelPackage, Unite_REPLACE_Full) +TEST(KernelPackage, Combine_REPLACE_Full) { namespace J = Jupiter; namespace S = Saturn; @@ -106,7 +188,7 @@ TEST(KernelPackage, Unite_REPLACE_Full) EXPECT_TRUE (u_pkg.includes()); } -TEST(KernelPackage, Unite_REPLACE_Partial) +TEST(KernelPackage, Combine_REPLACE_Partial) { namespace J = Jupiter; namespace S = Saturn; @@ -120,7 +202,7 @@ TEST(KernelPackage, Unite_REPLACE_Partial) EXPECT_TRUE (u_pkg.includes()); } -TEST(KernelPackage, Unite_REPLACE_Append) +TEST(KernelPackage, Combine_REPLACE_Append) { namespace J = Jupiter; namespace S = Saturn; @@ -134,7 +216,7 @@ TEST(KernelPackage, Unite_REPLACE_Append) EXPECT_TRUE(u_pkg.includes()); } -TEST(KernelPackage, Unite_KEEP_AllDups) +TEST(KernelPackage, Combine_KEEP_AllDups) { namespace J = Jupiter; namespace S = Saturn; @@ -151,7 +233,7 @@ TEST(KernelPackage, Unite_KEEP_AllDups) EXPECT_TRUE(u_pkg.includes()); } -TEST(KernelPackage, Unite_KEEP_Append_NoDups) +TEST(KernelPackage, Combine_KEEP_Append_NoDups) { namespace J = Jupiter; namespace S = Saturn; diff --git a/modules/gapi/test/internal/gapi_int_recompilation_test.cpp b/modules/gapi/test/internal/gapi_int_recompilation_test.cpp index 4d8bd87d4f..252af9c1a8 100644 --- a/modules/gapi/test/internal/gapi_int_recompilation_test.cpp +++ b/modules/gapi/test/internal/gapi_int_recompilation_test.cpp @@ -8,8 +8,9 @@ #include "test_precomp.hpp" #include "api/gcomputation_priv.hpp" -#include -#include +#include "opencv2/gapi/fluid/gfluidkernel.hpp" +#include "opencv2/gapi/fluid/core.hpp" +#include "opencv2/gapi/fluid/imgproc.hpp" namespace opencv_test { diff --git a/modules/gapi/test/test_precomp.hpp b/modules/gapi/test/test_precomp.hpp index f56e291b65..bcab803ba1 100644 --- a/modules/gapi/test/test_precomp.hpp +++ b/modules/gapi/test/test_precomp.hpp @@ -21,5 +21,7 @@ #include "opencv2/gapi/gpu/ggpukernel.hpp" #include "opencv2/gapi/gcompoundkernel.hpp" #include "opencv2/gapi/operators.hpp" +#include "opencv2/gapi/fluid/imgproc.hpp" +#include "opencv2/gapi/fluid/core.hpp" #endif // __OPENCV_GAPI_TEST_PRECOMP_HPP__ diff --git a/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp b/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp index 345fd060a2..af5a12aa8c 100755 --- a/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp +++ b/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp @@ -10,7 +10,9 @@ using namespace cv; using namespace std; +//! [calcGST_proto] void calcGST(const Mat& inputImg, Mat& imgCoherencyOut, Mat& imgOrientationOut, int w); +//! [calcGST_proto] int main() { @@ -26,6 +28,7 @@ int main() return -1; } + //! [main_extra] //! [main] Mat imgCoherency, imgOrientation; calcGST(imgIn, imgCoherency, imgOrientation, W); @@ -45,32 +48,36 @@ int main() normalize(imgCoherency, imgCoherency, 0, 255, NORM_MINMAX); normalize(imgOrientation, imgOrientation, 0, 255, NORM_MINMAX); + imwrite("result.jpg", 0.5*(imgIn + imgBin)); imwrite("Coherency.jpg", imgCoherency); imwrite("Orientation.jpg", imgOrientation); + //! [main_extra] return 0; } //! [calcGST] +//! [calcJ_header] void calcGST(const Mat& inputImg, Mat& imgCoherencyOut, Mat& imgOrientationOut, int w) { Mat img; - inputImg.convertTo(img, CV_64F); + inputImg.convertTo(img, CV_32F); // GST components calculation (start) // J = (J11 J12; J12 J22) - GST Mat imgDiffX, imgDiffY, imgDiffXY; - Sobel(img, imgDiffX, CV_64F, 1, 0, 3); - Sobel(img, imgDiffY, CV_64F, 0, 1, 3); + Sobel(img, imgDiffX, CV_32F, 1, 0, 3); + Sobel(img, imgDiffY, CV_32F, 0, 1, 3); multiply(imgDiffX, imgDiffY, imgDiffXY); + //! [calcJ_header] Mat imgDiffXX, imgDiffYY; multiply(imgDiffX, imgDiffX, imgDiffXX); multiply(imgDiffY, imgDiffY, imgDiffYY); Mat J11, J22, J12; // J11, J22 and J12 are GST components - boxFilter(imgDiffXX, J11, CV_64F, Size(w, w)); - boxFilter(imgDiffYY, J22, CV_64F, Size(w, w)); - boxFilter(imgDiffXY, J12, CV_64F, Size(w, w)); + boxFilter(imgDiffXX, J11, CV_32F, Size(w, w)); + boxFilter(imgDiffYY, J22, CV_32F, Size(w, w)); + boxFilter(imgDiffXY, J12, CV_32F, Size(w, w)); // GST components calculation (stop) // eigenvalue calculation (start) diff --git a/samples/cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp b/samples/cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp new file mode 100644 index 0000000000..447e395d2b --- /dev/null +++ b/samples/cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi.cpp @@ -0,0 +1,107 @@ +/** +* @brief You will learn how port an existing algorithm to G-API +* @author Dmitry Matveev, dmitry.matveev@intel.com, based +* on sample by Karpushin Vladislav, karpushin@ngs.ru +*/ +#include "opencv2/opencv_modules.hpp" +#ifdef HAVE_OPENCV_GAPI + +//! [full_sample] +#include +#include + +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/gapi.hpp" +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/imgproc.hpp" + +//! [calcGST_proto] +void calcGST(const cv::GMat& inputImg, cv::GMat& imgCoherencyOut, cv::GMat& imgOrientationOut, int w); +//! [calcGST_proto] + +int main() +{ + int W = 52; // window size is WxW + double C_Thr = 0.43; // threshold for coherency + int LowThr = 35; // threshold1 for orientation, it ranges from 0 to 180 + int HighThr = 57; // threshold2 for orientation, it ranges from 0 to 180 + + cv::Mat imgIn = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE); + if (imgIn.empty()) //check whether the image is loaded or not + { + std::cout << "ERROR : Image cannot be loaded..!!" << std::endl; + return -1; + } + + //! [main] + // Calculate Gradient Structure Tensor and post-process it for output with G-API + cv::GMat in; + cv::GMat imgCoherency, imgOrientation; + calcGST(in, imgCoherency, imgOrientation, W); + + cv::GMat imgCoherencyBin = imgCoherency > C_Thr; + cv::GMat imgOrientationBin = cv::gapi::inRange(imgOrientation, LowThr, HighThr); + cv::GMat imgBin = imgCoherencyBin & imgOrientationBin; + cv::GMat out = cv::gapi::addWeighted(in, 0.5, imgBin, 0.5, 0.0); + + // Capture the graph into object segm + cv::GComputation segm(cv::GIn(in), cv::GOut(out, imgCoherency, imgOrientation)); + + // Define cv::Mats for output data + cv::Mat imgOut, imgOutCoherency, imgOutOrientation; + + // Run the graph + segm.apply(cv::gin(imgIn), cv::gout(imgOut, imgOutCoherency, imgOutOrientation)); + + // Normalize extra outputs (out of the graph) + cv::normalize(imgOutCoherency, imgOutCoherency, 0, 255, cv::NORM_MINMAX); + cv::normalize(imgOutOrientation, imgOutOrientation, 0, 255, cv::NORM_MINMAX); + + cv::imwrite("result.jpg", imgOut); + cv::imwrite("Coherency.jpg", imgOutCoherency); + cv::imwrite("Orientation.jpg", imgOutOrientation); + //! [main] + + return 0; +} +//! [calcGST] +//! [calcGST_header] +void calcGST(const cv::GMat& inputImg, cv::GMat& imgCoherencyOut, cv::GMat& imgOrientationOut, int w) +{ + auto img = cv::gapi::convertTo(inputImg, CV_32F); + auto imgDiffX = cv::gapi::Sobel(img, CV_32F, 1, 0, 3); + auto imgDiffY = cv::gapi::Sobel(img, CV_32F, 0, 1, 3); + auto imgDiffXY = cv::gapi::mul(imgDiffX, imgDiffY); + //! [calcGST_header] + + auto imgDiffXX = cv::gapi::mul(imgDiffX, imgDiffX); + auto imgDiffYY = cv::gapi::mul(imgDiffY, imgDiffY); + + auto J11 = cv::gapi::boxFilter(imgDiffXX, CV_32F, cv::Size(w, w)); + auto J22 = cv::gapi::boxFilter(imgDiffYY, CV_32F, cv::Size(w, w)); + auto J12 = cv::gapi::boxFilter(imgDiffXY, CV_32F, cv::Size(w, w)); + + auto tmp1 = J11 + J22; + auto tmp2 = J11 - J22; + auto tmp22 = cv::gapi::mul(tmp2, tmp2); + auto tmp3 = cv::gapi::mul(J12, J12); + auto tmp4 = cv::gapi::sqrt(tmp22 + 4.0*tmp3); + + auto lambda1 = tmp1 + tmp4; + auto lambda2 = tmp1 - tmp4; + + imgCoherencyOut = (lambda1 - lambda2) / (lambda1 + lambda2); + imgOrientationOut = 0.5*cv::gapi::phase(J22 - J11, 2.0*J12, true); +} +//! [calcGST] + +//! [full_sample] + +#else +#include +int main() +{ + std::cerr << "This tutorial code requires G-API module to run" << std::endl; +} +#endif // HAVE_OPECV_GAPI diff --git a/samples/cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp b/samples/cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp new file mode 100644 index 0000000000..bdec8f0fdb --- /dev/null +++ b/samples/cpp/tutorial_code/gapi/porting_anisotropic_image_segmentation/porting_anisotropic_image_segmentation_gapi_fluid.cpp @@ -0,0 +1,128 @@ +/** +* @brief You will learn how port an existing algorithm to G-API +* @author Dmitry Matveev, dmitry.matveev@intel.com, based +* on sample by Karpushin Vladislav, karpushin@ngs.ru +*/ +#include "opencv2/opencv_modules.hpp" +#ifdef HAVE_OPENCV_GAPI + +//! [full_sample] +#include +#include + +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/gapi.hpp" +#include "opencv2/gapi/core.hpp" +#include "opencv2/gapi/imgproc.hpp" +//! [fluid_includes] +#include "opencv2/gapi/fluid/core.hpp" // Fluid Core kernel library +#include "opencv2/gapi/fluid/imgproc.hpp" // Fluid ImgProc kernel library +//! [fluid_includes] +#include "opencv2/gapi/fluid/gfluidkernel.hpp" // Fluid user kernel API + +//! [calcGST_proto] +void calcGST(const cv::GMat& inputImg, cv::GMat& imgCoherencyOut, cv::GMat& imgOrientationOut, int w); +//! [calcGST_proto] + +int main() +{ + int W = 52; // window size is WxW + double C_Thr = 0.43; // threshold for coherency + int LowThr = 35; // threshold1 for orientation, it ranges from 0 to 180 + int HighThr = 57; // threshold2 for orientation, it ranges from 0 to 180 + + cv::Mat imgIn = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE); + if (imgIn.empty()) //check whether the image is loaded or not + { + std::cout << "ERROR : Image cannot be loaded..!!" << std::endl; + return -1; + } + + //! [main] + // Calculate Gradient Structure Tensor and post-process it for output with G-API + cv::GMat in; + cv::GMat imgCoherency, imgOrientation; + calcGST(in, imgCoherency, imgOrientation, W); + + auto imgCoherencyBin = imgCoherency > C_Thr; + auto imgOrientationBin = cv::gapi::inRange(imgOrientation, LowThr, HighThr); + auto imgBin = imgCoherencyBin & imgOrientationBin; + cv::GMat out = cv::gapi::addWeighted(in, 0.5, imgBin, 0.5, 0.0); + + // Capture the graph into object segm + cv::GComputation segm(cv::GIn(in), cv::GOut(out, imgCoherency, imgOrientation)); + + // Define cv::Mats for output data + cv::Mat imgOut, imgOutCoherency, imgOutOrientation; + + //! [kernel_pkg_proper] + //! [kernel_pkg] + // Prepare the kernel package and run the graph + cv::gapi::GKernelPackage fluid_kernels = cv::gapi::combine // Define a custom kernel package: + (cv::gapi::core::fluid::kernels(), // ...with Fluid Core kernels + cv::gapi::imgproc::fluid::kernels(), // ...and Fluid ImgProc kernels + cv::unite_policy::KEEP); + //! [kernel_pkg] + //! [kernel_hotfix] + fluid_kernels.remove(); // Remove Fluid Box filter as unsuitable, + // G-API will fall-back to OpenCV there. + //! [kernel_hotfix] + //! [kernel_pkg_use] + segm.apply(cv::gin(imgIn), // Input data vector + cv::gout(imgOut, imgOutCoherency, imgOutOrientation), // Output data vector + cv::compile_args(fluid_kernels)); // Kernel package to use + //! [kernel_pkg_use] + //! [kernel_pkg_proper] + + // Normalize extra outputs (out of the graph) + cv::normalize(imgOutCoherency, imgOutCoherency, 0, 255, cv::NORM_MINMAX); + cv::normalize(imgOutOrientation, imgOutOrientation, 0, 255, cv::NORM_MINMAX); + + cv::imwrite("result.jpg", imgOut); + cv::imwrite("Coherency.jpg", imgOutCoherency); + cv::imwrite("Orientation.jpg", imgOutOrientation); + //! [main] + + return 0; +} +//! [calcGST] +//! [calcGST_header] +void calcGST(const cv::GMat& inputImg, cv::GMat& imgCoherencyOut, cv::GMat& imgOrientationOut, int w) +{ + auto img = cv::gapi::convertTo(inputImg, CV_32F); + auto imgDiffX = cv::gapi::Sobel(img, CV_32F, 1, 0, 3); + auto imgDiffY = cv::gapi::Sobel(img, CV_32F, 0, 1, 3); + auto imgDiffXY = cv::gapi::mul(imgDiffX, imgDiffY); + //! [calcGST_header] + + auto imgDiffXX = cv::gapi::mul(imgDiffX, imgDiffX); + auto imgDiffYY = cv::gapi::mul(imgDiffY, imgDiffY); + + auto J11 = cv::gapi::boxFilter(imgDiffXX, CV_32F, cv::Size(w, w)); + auto J22 = cv::gapi::boxFilter(imgDiffYY, CV_32F, cv::Size(w, w)); + auto J12 = cv::gapi::boxFilter(imgDiffXY, CV_32F, cv::Size(w, w)); + + auto tmp1 = J11 + J22; + auto tmp2 = J11 - J22; + auto tmp22 = cv::gapi::mul(tmp2, tmp2); + auto tmp3 = cv::gapi::mul(J12, J12); + auto tmp4 = cv::gapi::sqrt(tmp22 + 4.0*tmp3); + + auto lambda1 = tmp1 + tmp4; + auto lambda2 = tmp1 - tmp4; + + imgCoherencyOut = (lambda1 - lambda2) / (lambda1 + lambda2); + imgOrientationOut = 0.5*cv::gapi::phase(J22 - J11, 2.0*J12, true); +} +//! [calcGST] + +//! [full_sample] + +#else +#include +int main() +{ + std::cerr << "This tutorial code requires G-API module to run" << std::endl; +} +#endif // HAVE_OPECV_GAPI