mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
7.8 KiB
188 lines
7.8 KiB
# Kernel API {#gapi_kernel_api} |
|
|
|
[TOC] |
|
|
|
# G-API Kernel API |
|
|
|
The core idea behind G-API is portability -- a pipeline built with |
|
G-API must be portable (or at least able to be portable). It means |
|
that either it works out-of-the box when compiled for new platform, |
|
_or_ G-API provides necessary tools to make it running there, with |
|
little-to-no changes in the algorithm itself. |
|
|
|
This idea can be achieved by separating kernel interface from its |
|
implementation. Once a pipeline is built using kernel interfaces, it |
|
becomes implementation-neutral -- the implementation details |
|
(i.e. which kernels to use) are passed on a separate stage (graph |
|
compilation). |
|
|
|
Kernel-implementation hierarchy may look like: |
|
|
|
@dot Kernel API/implementation hierarchy example |
|
digraph { |
|
rankdir=BT; |
|
node [shape=record]; |
|
|
|
ki_a [label="{<f0> interface\nA}"]; |
|
ki_b [label="{<f0> interface\nB}"]; |
|
|
|
{rank=same; ki_a ki_b}; |
|
|
|
"CPU::A" -> ki_a [dir="forward"]; |
|
"OpenCL::A" -> ki_a [dir="forward"]; |
|
"Halide::A" -> ki_a [dir="forward"]; |
|
|
|
"CPU::B" -> ki_b [dir="forward"]; |
|
"OpenCL::B" -> ki_b [dir="forward"]; |
|
"Halide::B" -> ki_b [dir="forward"]; |
|
} |
|
@enddot |
|
|
|
A pipeline itself then can be expressed only in terms of `A`, `B`, and |
|
so on, and choosing which implementation to use in execution becomes |
|
an external parameter. |
|
|
|
# Defining a kernel {#gapi_defining_kernel} |
|
|
|
G-API provides a macro to define a new kernel interface -- |
|
G_TYPED_KERNEL(): |
|
|
|
@snippet samples/cpp/tutorial_code/gapi/doc_snippets/kernel_api_snippets.cpp filter2d_api |
|
|
|
This macro is a shortcut to a new type definition. It takes three |
|
arguments to register a new type, and requires type body to be present |
|
(see [below](@ref gapi_kernel_supp_info)). The macro arguments are: |
|
1. Kernel interface name -- also serves as a name of new type defined |
|
with this macro; |
|
2. Kernel signature -- an `std::function<>`-like signature which defines |
|
API of the kernel; |
|
3. Kernel's unique name -- used to identify kernel when its type |
|
informattion is stripped within the system. |
|
|
|
Kernel declaration may be seen as function declaration -- in both cases |
|
a new entity must be used then according to the way it was defined. |
|
|
|
Kernel signature defines kernel's usage syntax -- which parameters |
|
it takes during graph construction. Implementations can also use this |
|
signature to derive it into backend-specific callback signatures (see |
|
next chapter). |
|
|
|
Kernel may accept values of any type, and G-API _dynamic_ types are |
|
handled in a special way. All other types are opaque to G-API and |
|
passed to kernel in `outMeta()` or in execution callbacks as-is. |
|
|
|
Kernel's return value can _only_ be of G-API dynamic type -- cv::GMat, |
|
cv::GScalar, or `cv::GArray<T>`. If an operation has more than one |
|
output, it should be wrapped into an `std::tuple<>` (which can contain |
|
only mentioned G-API types). Arbitrary-output-number operations are |
|
not supported. |
|
|
|
Once a kernel is defined, it can be used in pipelines with special, |
|
G-API-supplied method "::on()". This method has the same signature as |
|
defined in kernel, so this code: |
|
|
|
@snippet samples/cpp/tutorial_code/gapi/doc_snippets/kernel_api_snippets.cpp filter2d_on |
|
|
|
is a perfectly legal construction. This example has some verbosity, |
|
though, so usually a kernel declaration comes with a C++ function |
|
wrapper ("factory method") which enables optional parameters, more |
|
compact syntax, Doxygen comments, etc: |
|
|
|
@snippet samples/cpp/tutorial_code/gapi/doc_snippets/kernel_api_snippets.cpp filter2d_wrap |
|
|
|
so now it can be used like: |
|
|
|
@snippet samples/cpp/tutorial_code/gapi/doc_snippets/kernel_api_snippets.cpp filter2d_wrap_call |
|
|
|
# Extra information {#gapi_kernel_supp_info} |
|
|
|
In the current version, kernel declaration body (everything within the |
|
curly braces) must contain a static function `outMeta()`. This function |
|
establishes a functional dependency between operation's input and |
|
output metadata. |
|
|
|
_Metadata_ is an information about data kernel operates on. Since |
|
non-G-API types are opaque to G-API, G-API cares only about `G*` data |
|
descriptors (i.e. dimensions and format of cv::GMat, etc). |
|
|
|
`outMeta()` is also an example of how kernel's signature can be |
|
transformed into a derived callback -- note that in this example, |
|
`outMeta()` signature exactly follows the kernel signature (defined |
|
within the macro) but is different -- where kernel expects cv::GMat, |
|
`outMeta()` takes and returns cv::GMatDesc (a G-API structure metadata |
|
for cv::GMat). |
|
|
|
The point of `outMeta()` is to propagate metadata information within |
|
computation from inputs to outputs and infer metadata of internal |
|
(intermediate, temporary) data objects. This information is required |
|
for further pipeline optimizations, memory allocation, and other |
|
operations done by G-API framework during graph compilation. |
|
|
|
<!-- TODO add examples --> |
|
|
|
# Implementing a kernel {#gapi_kernel_implementing} |
|
|
|
Once a kernel is declared, its interface can be used to implement |
|
versions of this kernel in different backends. This concept is |
|
naturally projected from object-oriented programming |
|
"Interface/Implementation" idiom: an interface can be implemented |
|
multiple times, and different implementations of a kernel should be |
|
substitutable with each other without breaking the algorithm |
|
(pipeline) logic (Liskov Substitution Principle). |
|
|
|
Every backend defines its own way to implement a kernel interface. |
|
This way is regular, though -- whatever plugin is, its kernel |
|
implementation must be "derived" from a kernel interface type. |
|
|
|
Kernel implementation are then organized into _kernel |
|
packages_. Kernel packages are passed to cv::GComputation::compile() |
|
as compile arguments, with some hints to G-API on how to select proper |
|
kernels (see more on this in "Heterogeneity"[TBD]). |
|
|
|
For example, the aforementioned `Filter2D` is implemented in |
|
"reference" CPU (OpenCV) plugin this way (*NOTE* -- this is a |
|
simplified form with improper border handling): |
|
|
|
@snippet samples/cpp/tutorial_code/gapi/doc_snippets/kernel_api_snippets.cpp filter2d_ocv |
|
|
|
Note how CPU (OpenCV) plugin has transformed the original kernel |
|
signature: |
|
- Input cv::GMat has been substituted with cv::Mat, holding actual input |
|
data for the underlying OpenCV function call; |
|
- Output cv::GMat has been transformed into extra output parameter, thus |
|
`GCPUFilter2D::run()` takes one argument more than the original |
|
kernel signature. |
|
|
|
The basic intuition for kernel developer here is _not to care_ where |
|
that cv::Mat objects come from instead of the original cv::GMat -- and |
|
just follow the signature conventions defined by the plugin. G-API |
|
will call this method during execution and supply all the necessary |
|
information (and forward the original opaque data as-is). |
|
|
|
# Compound kernels {#gapi_kernel_compound} |
|
|
|
Sometimes kernel is a single thing only on API level. It is convenient |
|
for users, but on a particular implementation side it would be better to |
|
have multiple kernels (a subgraph) doing the thing instead. An example |
|
is goodFeaturesToTrack() -- while in OpenCV backend it may remain a |
|
single kernel, with Fluid it becomes compound -- Fluid can handle Harris |
|
response calculation but can't do sparse non-maxima suppression and |
|
point extraction to an STL vector: |
|
|
|
<!-- PIC --> |
|
|
|
A compound kernel _implementation_ can be defined using a generic |
|
macro GAPI_COMPOUND_KERNEL(): |
|
|
|
@snippet samples/cpp/tutorial_code/gapi/doc_snippets/kernel_api_snippets.cpp compound |
|
|
|
<!-- TODO: ADD on how Compound kernels may simplify dispatching --> |
|
<!-- TODO: Add details on when expand() is called! --> |
|
|
|
It is important to distinguish a compound kernel from G-API high-order |
|
function, i.e. a C++ function which looks like a kernel but in fact |
|
generates a subgraph. The core difference is that a compound kernel is |
|
an _implementation detail_ and a kernel implementation may be either |
|
compound or not (depending on backend capabilities), while a |
|
high-order function is a "macro" in terms of G-API and so cannot act as |
|
an interface which then needs to be implemented by a backend.
|
|
|