Blame view

3rdparty/opencv-4.5.4/modules/gapi/doc/20-kernel-api.markdown 7.65 KB
f4334277   Hu Chunming   提交3rdparty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  # 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 modules/gapi/samples/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 modules/gapi/samples/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 modules/gapi/samples/kernel_api_snippets.cpp filter2d_wrap
  
  so now it can be used like:
  
  @snippet modules/gapi/samples/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 modules/gapi/samples/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 modules/gapi/samples/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.