1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-28 23:03:03 +04:00

Compare commits

...

30 Commits

Author SHA1 Message Date
Maksim Shabunin e6d9486a6c Fixed several issues found by static analysis 2018-10-25 16:39:54 +03:00
Maksim Shabunin 9a8e47a766 Version update for OpenVINO 2018-10-25 16:38:30 +03:00
Dmitry Matveev dbed39a931 Merge pull request #12857 from dmatveev:hld
* G-API Documentation: first submission

This PR introduces a number of new OpenCV documentation chapters for
Graph API module.

In particular, the following topics are covered:
- Introduction & background information;
- High-level design overview;
- Kernel API;
- Pipeline example.

All changes are done in Markdown files, no headers, etc modified.

Doxygen references for main API classes will be added later.

Also, a tutorial will be introduced soon (in the common Tutorials place)

* G-API Documentation - fix warnings & trailing whitespaces

* G-API Documentation: address review issues

* G-API Documentation: export code snippets to compileable files

* gapi: move documentation samples
2018-10-24 07:47:56 +03:00
Vadim Pisarevsky ea31c09384 rewrote the line segment intersection function to make the static analyzer happy (#12902)
* rewrote the line segment intersection function to make the static analyzer happy

* fixed bug with improper "no intersection" detection in some of corner cases

* fixed bug with improper "no intersection" detection in some of corner cases
2018-10-23 17:09:23 +03:00
Alexander Alekhin 9c23f2f1a6 Merge remote-tracking branch 'upstream/3.4' into merge-3.4 2018-10-20 11:37:54 +00:00
Apoorv Goel d8ffddd075 Merge pull request #12871 from UnderscoreAsterisk:document-Distance
* Document distance functors in dist.h

* Add spec for Distance

* Generate appropriate links for symbols
2018-10-20 11:15:13 +03:00
Alexander Alekhin a692d87cfb Merge pull request #12875 from dkurt:dnn_enet_accuracy 2018-10-19 15:33:48 +00:00
Dmitry Matveev 5e9750d1f5 Merge pull request #12870 from dmatveev:gapi_fluid_basic_hetero_support
* G-API Fluid basic heterogeneity support: initial upload

* G-API Fluid heterogeneity: address some coding style issues

* G-API Fluid heterogeneity: fix compiler warnings

* G-API Fluid heterogeneity: fix warnings on Windows & ARMv7

* G-API Fluid heterogeneity: finally fix Windows warnings

* G-API Fluid heterogeneity: fix dangling reference problem
2018-10-19 18:32:48 +03:00
Dmitry Kurtaev e7015f6ae8 Fix ENet test 2018-10-19 17:43:26 +03:00
Alexander Alekhin 9c9fb1c6d0 Merge pull request #12867 from UnderscoreAsterisk:document-radiusSearch 2018-10-18 07:52:49 +00:00
Alexander Alekhin 80c64fce11 Merge pull request #12832 from kmansoo:fix-compile-errors-on-nvcc10 2018-10-18 07:51:47 +00:00
Apoorv a7dfa261d8 Add documentation for radiusSearch 2018-10-18 04:09:16 +05:30
Mansoo Kim 4d1f0ef2d9 cuda: fix build with CUDA 10.x 2018-10-17 17:35:40 +00:00
Alexander Alekhin df6728e64c Merge pull request #12852 from nangchoo:bugfix/increase_magic_threshold_for_perf_test 2018-10-17 17:31:19 +00:00
Alexander Alekhin 7a73941f22 Merge pull request #12844 from jasjuang:3.4 2018-10-17 17:31:00 +00:00
Alexander Alekhin 6d9e66eca1 Merge pull request #12864 from dkurt:dnn_ie_get_batch_size 2018-10-17 14:02:25 +00:00
Alexander Alekhin 2180a67670 Merge pull request #12863 from alalek:disable_wshadow_gcc_4.x 2018-10-17 13:59:13 +00:00
Dmitry Kurtaev 365451dab0 Implement getBatchSize for Intel's Inference Engine networks 2018-10-17 14:02:37 +03:00
Alexander Alekhin 1188c6fe14 Merge pull request #12838 from dmatveev:gapi_fluid_resize_lpi 2018-10-17 09:22:28 +00:00
Alexander Alekhin a2e754c136 build: eliminate warnings 2018-10-17 08:43:18 +00:00
Alexander Alekhin c09a3cf098 Merge pull request #12860 from janisozaur:include-guards 2018-10-17 08:20:31 +00:00
Alexander Alekhin 842341fc16 Merge pull request #12859 from janisozaur:empty-block 2018-10-17 08:20:08 +00:00
Alexander Alekhin 32a5e5fae1 Merge pull request #12858 from janisozaur:catch-by-ref 2018-10-17 08:19:12 +00:00
Michał Janiszewski 85b9960f62 Fix clashing include guards
Relevant guards can be found in
https://github.com/opencv/opencv/blob/ef5579dc8667e5eb5e149acc4af898421eed99da/modules/features2d/src/kaze/AKAZEConfig.h#L8
and
https://github.com/opencv/opencv/blob/ef5579dc8667e5eb5e149acc4af898421eed99da/modules/ml/include/opencv2/ml.hpp#L44
2018-10-16 22:59:38 +02:00
Michał Janiszewski 5640b36f6d Remove unused empty block 2018-10-16 22:51:03 +02:00
Michał Janiszewski c8e6ce304f Catch exceptions by const-reference
Exceptions caught by value incur needless cost in C++, most of them can
be caught by const-reference, especially as nearly none are actually
used. This could allow compiler generate a slightly more efficient code.
2018-10-16 22:43:54 +02:00
Paul Shin 1c468add20 Increased the acceptable error margin for perf testing
- This is to accommodate the variabiilty in floating-point operations in new platforms/compilers
- Specifically due to the error margin found in NVIDIA Jetson TX2
2018-10-15 20:03:39 -07:00
Alexander Alekhin 5339ef3004 Merge tag '4.0.0-beta' 2018-10-15 20:33:41 +00:00
jasjuang a66fd527b0 add support for latest Turing gpu and cuda 10 2018-10-15 10:40:24 -07:00
Dmitry Matveev 922d5796b9 G-API: Introduce LPI (multiple Lines-Per-Iteration) support for Resize
Several Resize optimizations count on fetching multiple input lines at
once to do interpolation more efficiently.

At the moment, Fluid backend supports only LPI=1 for Resize kernels.

This patch introduces scheduling support for Resizes with LPI>1 and
covers these cases with new tests.

The support is initially written by Ruslan Garnov.
2018-10-15 17:11:55 +03:00
47 changed files with 1572 additions and 404 deletions
+3 -3
View File
@@ -38,8 +38,8 @@
//
//M*/
#ifndef OPENCV_ML_HPP
#define OPENCV_ML_HPP
#ifndef OPENCV_OLD_ML_HPP
#define OPENCV_OLD_ML_HPP
#ifdef __cplusplus
# include "opencv2/core.hpp"
@@ -2038,6 +2038,6 @@ template<> struct DefaultDeleter<CvDTreeSplit>{ void operator ()(CvDTreeSplit* o
}
#endif // __cplusplus
#endif // OPENCV_ML_HPP
#endif // OPENCV_OLD_ML_HPP
/* End of file. */
+3 -1
View File
@@ -103,7 +103,9 @@ if(CV_GCC OR CV_CLANG)
add_extra_compiler_option(-Wundef)
add_extra_compiler_option(-Winit-self)
add_extra_compiler_option(-Wpointer-arith)
add_extra_compiler_option(-Wshadow)
if(NOT (CV_GCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0"))
add_extra_compiler_option(-Wshadow) # old GCC emits warnings for variables + methods combination
endif()
add_extra_compiler_option(-Wsign-promo)
add_extra_compiler_option(-Wuninitialized)
add_extra_compiler_option(-Winit-self)
+6 -2
View File
@@ -87,6 +87,8 @@ if(CUDA_FOUND)
set(__cuda_arch_bin "6.0 6.1")
elseif(CUDA_GENERATION STREQUAL "Volta")
set(__cuda_arch_bin "7.0")
elseif(CUDA_GENERATION STREQUAL "Turing")
set(__cuda_arch_bin "7.5")
elseif(CUDA_GENERATION STREQUAL "Auto")
execute_process( COMMAND ${DETECT_ARCHS_COMMAND}
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/"
@@ -113,7 +115,7 @@ if(CUDA_FOUND)
string(REGEX REPLACE ".*\n" "" _nvcc_out "${_nvcc_out}") #Strip leading warning messages, if any
if(NOT _nvcc_res EQUAL 0)
message(STATUS "Automatic detection of CUDA generation failed. Going to build for all known architectures.")
set(__cuda_arch_bin "5.3 6.2 7.0")
set(__cuda_arch_bin "5.3 6.2 7.0 7.5")
else()
set(__cuda_arch_bin "${_nvcc_out}")
string(REPLACE "2.1" "2.1(2.0)" __cuda_arch_bin "${__cuda_arch_bin}")
@@ -122,8 +124,10 @@ if(CUDA_FOUND)
else()
if(${CUDA_VERSION} VERSION_LESS "9.0")
set(__cuda_arch_bin "2.0 3.0 3.5 3.7 5.0 5.2 6.0 6.1")
else()
elseif(${CUDA_VERSION} VERSION_LESS "10.0")
set(__cuda_arch_bin "3.0 3.5 3.7 5.0 5.2 6.0 6.1 7.0")
else()
set(__cuda_arch_bin "3.0 3.5 3.7 5.0 5.2 6.0 6.1 7.0 7.5")
endif()
endif()
endif()
+1
View File
@@ -1133,6 +1133,7 @@ CV_IMPL void cvFindExtrinsicCameraParams2( const CvMat* objectPoints,
if( cvDet(&_RR) < 0 )
cvScale( &_RRt, &_RRt, -1 );
sc = cvNorm(&_RR);
CV_Assert(fabs(sc) > DBL_EPSILON);
cvSVD( &_RR, &matW, &matU, &matV, CV_SVD_MODIFY_A + CV_SVD_U_T + CV_SVD_V_T );
cvGEMM( &matU, &matV, 1, 0, 0, &matR, CV_GEMM_A_T );
cvScale( &_tt, &_t, cvNorm(&matR)/sc );
+3 -1
View File
@@ -164,7 +164,9 @@ cv::Mat findHomography1D(cv::InputArray _src,cv::InputArray _dst)
Mat H = dst_T.inv()*Mat(H_, false)*src_T;
// enforce frobeniusnorm of one
double scale = 1.0/cv::norm(H);
double scale = cv::norm(H);
CV_Assert(fabs(scale) > DBL_EPSILON);
scale = 1.0 / scale;
return H*scale;
}
void polyfit(const Mat& src_x, const Mat& src_y, Mat& dst, int order)
+2 -2
View File
@@ -654,7 +654,7 @@ class float16_t
public:
#if CV_FP16_TYPE
float16_t() {}
float16_t() : h(0) {}
explicit float16_t(float x) { h = (__fp16)x; }
operator float() const { return (float)h; }
static float16_t fromBits(ushort w)
@@ -681,7 +681,7 @@ protected:
__fp16 h;
#else
float16_t() {}
float16_t() : w(0) {}
explicit float16_t(float x)
{
#if CV_AVX2
+1 -1
View File
@@ -1353,7 +1353,7 @@ CvSlice;
CV_INLINE CvSlice cvSlice( int start, int end )
{
#if !(defined(CV__ENABLE_C_API_CTORS) && defined(__cplusplus))
#if !(defined(CV__ENABLE_C_API_CTORS) && defined(__cplusplus) && !defined(__CUDACC__))
CvSlice slice = { start, end };
#else
CvSlice slice(start, end);
@@ -8,7 +8,7 @@
#define CV_VERSION_MAJOR 4
#define CV_VERSION_MINOR 0
#define CV_VERSION_REVISION 0
#define CV_VERSION_STATUS "-beta"
#define CV_VERSION_STATUS "-openvino.2018R4"
#define CVAUX_STR_EXP(__A) #__A
#define CVAUX_STR(__A) CVAUX_STR_EXP(__A)
+1 -1
View File
@@ -729,7 +729,7 @@ static bool ipp_flip(Mat &src, Mat &dst, int flip_mode)
CV_INSTRUMENT_FUN_IPP(::ipp::iwiMirror, iwSrc, iwDst, ippMode);
}
catch(::ipp::IwException)
catch(const ::ipp::IwException &)
{
return false;
}
+5 -5
View File
@@ -1032,7 +1032,7 @@ void Core_SeqBaseTest::run( int )
cvClearMemStorage( storage );
}
}
catch(int)
catch(const int &)
{
}
}
@@ -1200,7 +1200,7 @@ void Core_SeqSortInvTest::run( int )
storage.release();
}
}
catch (int)
catch (const int &)
{
}
}
@@ -1416,7 +1416,7 @@ void Core_SetTest::run( int )
storage.release();
}
}
catch(int)
catch(const int &)
{
}
}
@@ -1859,7 +1859,7 @@ void Core_GraphTest::run( int )
storage.release();
}
}
catch(int)
catch(const int &)
{
}
}
@@ -2121,7 +2121,7 @@ void Core_GraphScanTest::run( int )
storage.release();
}
}
catch(int)
catch(const int &)
{
}
}
+2 -1
View File
@@ -332,7 +332,8 @@ public:
int poolingType;
float spatialScale;
PoolingInvoker() : src(0), rois(0), dst(0), mask(0), avePoolPaddedArea(false), nstripes(0),
PoolingInvoker() : src(0), rois(0), dst(0), mask(0), pad_l(0), pad_t(0), pad_r(0), pad_b(0),
avePoolPaddedArea(false), nstripes(0),
computeMaxIdx(0), poolingType(MAX), spatialScale(0) {}
static void run(const Mat& src, const Mat& rois, Mat& dst, Mat& mask, Size kernel,
+12 -2
View File
@@ -331,8 +331,18 @@ InferenceEngine::StatusCode InfEngineBackendNet::setBatchSize(size_t size, Infer
size_t InfEngineBackendNet::getBatchSize() const CV_NOEXCEPT
{
CV_Error(Error::StsNotImplemented, "");
return 0;
size_t batchSize = 0;
for (const auto& inp : inputs)
{
CV_Assert(inp.second);
std::vector<size_t> dims = inp.second->getDims();
CV_Assert(!dims.empty());
if (batchSize != 0)
CV_Assert(batchSize == dims.back());
else
batchSize = dims.back();
}
return batchSize;
}
#if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2018R2)
+44 -2
View File
@@ -287,6 +287,46 @@ TEST_P(Test_Torch_nets, OpenFace_accuracy)
normAssert(out, outRef, "", default_l1, default_lInf);
}
static Mat getSegmMask(const Mat& scores)
{
const int rows = scores.size[2];
const int cols = scores.size[3];
const int numClasses = scores.size[1];
Mat maxCl = Mat::zeros(rows, cols, CV_8UC1);
Mat maxVal(rows, cols, CV_32FC1, Scalar(0));
for (int ch = 0; ch < numClasses; ch++)
{
for (int row = 0; row < rows; row++)
{
const float *ptrScore = scores.ptr<float>(0, ch, row);
uint8_t *ptrMaxCl = maxCl.ptr<uint8_t>(row);
float *ptrMaxVal = maxVal.ptr<float>(row);
for (int col = 0; col < cols; col++)
{
if (ptrScore[col] > ptrMaxVal[col])
{
ptrMaxVal[col] = ptrScore[col];
ptrMaxCl[col] = (uchar)ch;
}
}
}
}
return maxCl;
}
// Computer per-class intersection over union metric.
static void normAssertSegmentation(const Mat& ref, const Mat& test)
{
CV_Assert_N(ref.dims == 4, test.dims == 4);
const int numClasses = ref.size[1];
CV_Assert(numClasses == test.size[1]);
Mat refMask = getSegmMask(ref);
Mat testMask = getSegmMask(test);
EXPECT_EQ(countNonZero(refMask != testMask), 0);
}
TEST_P(Test_Torch_nets, ENet_accuracy)
{
checkBackend();
@@ -313,14 +353,16 @@ TEST_P(Test_Torch_nets, ENet_accuracy)
// Due to numerical instability in Pooling-Unpooling layers (indexes jittering)
// thresholds for ENet must be changed. Accuracy of results was checked on
// Cityscapes dataset and difference in mIOU with Torch is 10E-4%
normAssert(ref, out, "", 0.00044, /*target == DNN_TARGET_CPU ? 0.453 : */0.5);
normAssert(ref, out, "", 0.00044, /*target == DNN_TARGET_CPU ? 0.453 : */0.552);
normAssertSegmentation(ref, out);
const int N = 3;
for (int i = 0; i < N; i++)
{
net.setInput(inputBlob, "");
Mat out = net.forward();
normAssert(ref, out, "", 0.00044, /*target == DNN_TARGET_CPU ? 0.453 : */0.5);
normAssert(ref, out, "", 0.00044, /*target == DNN_TARGET_CPU ? 0.453 : */0.552);
normAssertSegmentation(ref, out);
}
}
+2 -2
View File
@@ -5,8 +5,8 @@
* @author Pablo F. Alcantarilla
*/
#ifndef __OPENCV_FEATURES_2D_AKAZE_CONFIG_H__
#define __OPENCV_FEATURES_2D_AKAZE_CONFIG_H__
#ifndef __OPENCV_FEATURES_2D_KAZE_CONFIG_H__
#define __OPENCV_FEATURES_2D_KAZE_CONFIG_H__
// OpenCV Includes
#include "../precomp.hpp"
+63
View File
@@ -103,6 +103,58 @@ using ::cvflann::KL_Divergence;
/** @brief The FLANN nearest neighbor index class. This class is templated with the type of elements for which
the index is built.
`Distance` functor specifies the metric to be used to calculate the distance between two points.
There are several `Distance` functors that are readily available:
@link cvflann::L2_Simple cv::flann::L2_Simple @endlink- Squared Euclidean distance functor.
This is the simpler, unrolled version. This is preferable for very low dimensionality data (eg 3D points)
@link cvflann::L2 cv::flann::L2 @endlink- Squared Euclidean distance functor, optimized version.
@link cvflann::L1 cv::flann::L1 @endlink - Manhattan distance functor, optimized version.
@link cvflann::MinkowskiDistance cv::flann::MinkowskiDistance @endlink - The Minkowsky distance functor.
This is highly optimised with loop unrolling.
The computation of squared root at the end is omitted for efficiency.
@link cvflann::MaxDistance cv::flann::MaxDistance @endlink - The max distance functor. It computes the
maximum distance between two vectors. This distance is not a valid kdtree distance, it's not
dimensionwise additive.
@link cvflann::HammingLUT cv::flann::HammingLUT @endlink - %Hamming distance functor. It counts the bit
differences between two strings using a lookup table implementation.
@link cvflann::Hamming cv::flann::Hamming @endlink - %Hamming distance functor. Population count is
performed using library calls, if available. Lookup table implementation is used as a fallback.
@link cvflann::Hamming2 cv::flann::Hamming2 @endlink- %Hamming distance functor. Population count is
implemented in 12 arithmetic operations (one of which is multiplication).
@link cvflann::HistIntersectionDistance cv::flann::HistIntersectionDistance @endlink - The histogram
intersection distance functor.
@link cvflann::HellingerDistance cv::flann::HellingerDistance @endlink - The Hellinger distance functor.
@link cvflann::ChiSquareDistance cv::flann::ChiSquareDistance @endlink - The chi-square distance functor.
@link cvflann::KL_Divergence cv::flann::KL_Divergence @endlink - The Kullback-Leibler divergence functor.
Although the provided implementations cover a vast range of cases, it is also possible to use
a custom implementation. The distance functor is a class whose `operator()` computes the distance
between two features. If the distance is also a kd-tree compatible distance, it should also provide an
`accum_dist()` method that computes the distance between individual feature dimensions.
In addition to `operator()` and `accum_dist()`, a distance functor should also define the
`ElementType` and the `ResultType` as the types of the elements it operates on and the type of the
result it computes. If a distance functor can be used as a kd-tree distance (meaning that the full
distance between a pair of features can be accumulated from the partial distances between the
individual dimensions) a typedef `is_kdtree_distance` should be present inside the distance functor.
If the distance is not a kd-tree distance, but it's a distance in a vector space (the individual
dimensions of the elements it operates on can be accessed independently) a typedef
`is_vector_space_distance` should be defined inside the functor. If neither typedef is defined, the
distance is assumed to be a metric distance and will only be used with indexes operating on
generic metric distances.
*/
template <typename Distance>
class GenericIndex
@@ -217,6 +269,17 @@ public:
std::vector<DistanceType>& dists, int knn, const ::cvflann::SearchParams& params);
void knnSearch(const Mat& queries, Mat& indices, Mat& dists, int knn, const ::cvflann::SearchParams& params);
/** @brief Performs a radius nearest neighbor search for a given query point using the index.
@param query The query point.
@param indices Vector that will contain the indices of the nearest neighbors found.
@param dists Vector that will contain the distances to the nearest neighbors found. It has the same
number of elements as indices.
@param radius The search radius.
@param params SearchParams
This function returns the number of nearest neighbors found.
*/
int radiusSearch(const std::vector<ElementType>& query, std::vector<int>& indices,
std::vector<DistanceType>& dists, DistanceType radius, const ::cvflann::SearchParams& params);
int radiusSearch(const Mat& query, Mat& indices, Mat& dists,
+1
View File
@@ -92,3 +92,4 @@ if(TARGET opencv_test_gapi)
endif()
ocv_add_perf_tests()
ocv_add_samples()
+109
View File
@@ -0,0 +1,109 @@
# Graph API {#gapi}
# Introduction {#gapi_root_intro}
OpenCV Graph API (or G-API) is a new OpenCV module targeted to make
regular image processing fast and portable. These two goals are
achieved by introducing a new graph-based model of execution.
G-API is a special module in OpenCV -- in contrast with the majority
of other main modules, this one acts as a framework rather than some
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.
# Contents
G-API documentation is organized into the following chapters:
- @subpage gapi_purposes
The motivation behind G-API and its goals.
- @subpage gapi_hld
General overview of G-API architecture and its major internal
components.
- @subpage gapi_kernel_api
Learn how to introduce new operations in G-API and implement it for
various backends.
- @subpage gapi_impl
Low-level implementation details of G-API, for those who want to
contribute.
- API Reference: functions and classes
- @subpage gapi_core
Core G-API operations - arithmetic, boolean, and other matrix
operations;
- @subpage gapi_imgproc
Image processing functions: color space conversions, various
filters, etc.
# API Example {#gapi_example}
A very basic example of G-API pipeline is shown below:
@include modules/gapi/samples/api_example.cpp
<!-- TODO align this code with text using marks and itemized list -->
G-API is a separate OpenCV module so its header files have to be
included explicitly. The first four lines of `main()` create and
initialize OpenCV's standard video capture object, which fetches
video frames from either an attached camera or a specified file.
G-API pipelie is constructed next. In fact, it is a series of G-API
operation calls on cv::GMat data. The important aspect of G-API is
that this code block is just a declaration of actions, but not the
actions themselves. No processing happens at this point, G-API only
tracks which operations form pipeline and how it is connected. G-API
_Data objects_ (here it is cv::GMat) are used to connect operations
each other. `in` is an _empty_ cv::GMat signalling that it is a
beginning of computation.
After G-API code is written, it is captured into a call graph with
instantiation of cv::GComputation object. This object takes
input/output data references (in this example, `in` and `out`
cv::GMat objects, respectively) as parameters and reconstructs the
call graph based on all the data flow between `in` and `out`.
cv::GComputation is a thin object in sense that it just captures which
operations form up a computation. However, it can be used to execute
computations -- in the following processing loop, every captured frame (a
cv::Mat `input_frame`) is passed to cv::GComputation::apply().
![Example pipeline running on sample video 'vtest.avi'](pics/demo.jpg)
cv::GComputation::apply() is a polimorphic method which accepts a
variadic number of arguments. Since this computation is defined on one
input, one output, a special overload of cv::GComputation::apply() is
used to pass input data and get output data.
Internally, cv::GComputation::apply() compiles the captured graph for
the given input parameters and executes the compiled graph on data
immediately.
There is a number important concepts can be outlines with this examle:
* Graph declaration and graph execution are distinct steps;
* Graph is built implicitly from a sequence of G-API expressions;
* G-API supports function-like calls -- e.g. cv::gapi::resize(), and
operators, e.g operator|() which is used to compute bitwise OR;
* G-API syntax aims to look pure: every operation call within a graph
yields a new result, thus forming a directed acyclic graph (DAG);
* Graph declaration is not bound to any data -- real data objects
(cv::Mat) come into picture after the graph is already declared.
<!-- FIXME: The above operator|() link links to MatExpr not GAPI -->
See Tutorial[TBD] and Porting examples[TBD] to learn more on various
G-API features and concepts.
<!-- TODO Add chapter on declaration, compilation, execution -->
+76
View File
@@ -0,0 +1,76 @@
# Why Graph API? {#gapi_purposes}
# Motivation behind G-API {#gapi_intro_why}
G-API module brings graph-based model of execution to OpenCV. This
chapter briefly describes how this new model can help software
developers in two aspects: optimizing and porting image processing
algorithms.
## Optimizing with Graph API {#gapi_intro_opt}
Traditionally OpenCV provided a lot of stand-alone image processing
functions (see modules `core` and `imgproc`). Many of that functions
are well-optimized (e.g. vectorized for specific CPUs, parallel, etc)
but still the out-of-box optimization scope has been limited to a
single function only -- optimizing the whole algorithm built atop of that
functions was a responsibility of a programmer.
OpenCV 3.0 introduced _Transparent API_ (or _T-API_) which allowed to
offload OpenCV function calls transparently to OpenCL devices and save
on Host/Device data transfers with cv::UMat -- and it was a great step
forward. However, T-API is a dynamic API -- user code still remains
unconstrained and OpenCL kernels are enqueued in arbitrary order, thus
eliminating further pipeline-level optimization potential.
G-API brings implicit graph model to OpenCV 4.0. Graph model captures
all operations and its data dependencies in a pipeline and so provides
G-API framework with extra information to do pipeline-level
optimizations.
The cornerstone of graph-based optimizations is _Tiling_. Tiling
allows to break the processing into smaller parts and reorganize
operations to enable data parallelism, improve data locality, and save
memory footprint. Data locality is an especially important aspect of
software optimization due to diffent costs of memory access on modern
computer architectures -- the more data is reused in the first level
cache, the more efficient pipeline is.
Definitely the aforementioned techinques can be applied manually --
but it requires extra skills and knowledge of the target platform and
the algorithm implementation changes irrevocably -- becoming more
specific, less flexible, and harder to extend and maintain.
G-API takes this responsiblity and complexity from user and does the
majority of the work by itself, keeping the algorithm code clean from
device or optimization details. This approach has its own limitations,
though, as graph model is a _constrained_ model and not every
algorithm can be represented as a graph, so the G-API scope is limited
only to regular image processing -- various filters, arithmentic,
binary operations, and well-defined geometrical transformations.
## Porting with Graph API {#gapi_intro_port}
The essense of G-API is declaring a sequence of operations to run, and
then executing that sequence. G-API is a constrained API, so it puts a
number of limitations on which operations can form a pipeline and
which data these operations may exchange each other.
This formalization in fact helps to make an algorithm portable. G-API
clearly separates operation _interfaces_ from its _implementations_.
One operation (_kernel_) may have multiple implementations even for a
single device (e.g., OpenCV-based "reference" implementation and a
tiled optimized implementation, both running on CPU). Graphs (or
_Computations_ in G-API terms) are built only using operation
interfaces, not implementations -- thus the same graph can be executed
on different devices (and, of course, using different optimization
techniques) with little-to-no changes in the graph itself.
G-API supports plugins (_Backends_) which aggreate logic and
intelligence on what is the best way to execute on a particular
platform. Once a pipeline is built with G-API, it can be parametrized
to use either of the backends (or a combination of it) and so a graph
can be ported easily to a new platform.
@sa @ref gapi_hld
+159
View File
@@ -0,0 +1,159 @@
# High-level design overview {#gapi_hld}
# G-API High-level design overview
[TOC]
G-API is a heterogeneous framework and provides single API to program
image processing pipelines with a number of supported backends.
The key design idea is to keep pipeline code itself platform-neutral
while specifying which kernels to use and which devices to utilize
using extra parameters at graph compile (configuration) time. This
requirement has led to the following architecture:
<!-- FIXME: Render from dot directly -->
![G-API framework architecture](pics/gapi_scheme.png)
There are three layers in this architecture:
* **API Layer** -- this is the top layer, which implements G-API
public interface, its building blocks and semantics.
When user constructs a pipeline with G-API, he interacts with this
layer directly, and the entities the user operates on (like cv::GMat
or cv::GComputation) are provided by this layer.
* **Graph Compiler Layer** -- this is the intermediate layer which
unrolls user computation into a graph and then applies a number of
transformations to it (e.g. optimizations). This layer is built atop
of [ADE Framework](@ref gapi_detail_ade).
* **Backends Layer** -- this is the lowest level layer, which lists a
number of _Backends_. In contrast with the above two layers,
backends are highly coupled with low-level platform details, with
every backend standing for every platform. A backend operates on a
processed graph (coming from the graph compiler) and executes this
graph optimally for a specific platform or device.
# API layer {#gapi_api_layer}
API layer is what user interacts with when defining and using a
pipeline (a Computation in G-API terms). API layer defines a set of
G-API _dynamic_ objects which can be used as inputs, outputs, and
intermediate data objects within a graph:
* cv::GMat
* cv::GScalar
* cv::GArray (template class)
API layer specifies a list of Operations which are defined on these
data objects -- so called kernels. See G-API [core](@ref gapi_core)
and [imgproc](@ref gapi_imgproc) namespaces for details on which
operations G-API provides by default.
G-API is not limited to these operations only -- users can define
their own kernels easily using a special macro G_TYPED_KERNEL().
API layer is also responsible for marshalling and storing operation
parameters on pipeline creation. In addition to the aforementioned
G-API dynamic objects, operations may also accept arbitrary
parameters (more on this [below](@ref gapi_detail_params)), so API
layer captures its values and stores internally upon the moment of
execution.
Finally, cv::GComputation and cv::GCompiled are the remaining
important components of API layer. The former wraps a series of G-API
expressions into an object (graph), and the latter is a product of
graph _compilation_ (see [this chapter](@ref gapi_detail_compiler) for
details).
# Graph compiler layer {#gapi_compiler}
Every G-API computation is compiled before it executes. Compilation
process is triggered in two ways:
* _implicitly_, when cv::GComputation::apply() is used. In this case,
graph compilation is then immediately followed by execution.
* _explicitly_, when cv::GComputation::compile() is used. In this case,
a cv::GCompiled object is returned which then can be invoked as a
C++ functor.
The first way is recommended for cases when input data format is not
known in advance -- e.g. when it comes from an arbitrary input file.
The second way is recommended for deployment (production) scenarios
where input data characteristics are usually predefined.
Graph compilation process is built atop of ADE Framework. Initially, a
bipartite graph is generated from expressions captured by API layer.
This graph contains nodes of two types: _Data_ and _Operations_. Graph
always starts and ends with a Data node(s), with Operations nodes
in-between. Every Operation node has inputs and outputs, both are Data
nodes.
After the initial graph is generated, it is actually processed by a
number of graph transformations, called _passes_. ADE Framework acts
as a compiler pass management engine, and passes are written
specifically for G-API.
There are different passes which check graph validity, refine details
on operations and data, organize nodes into clusters ("Islands") based
on affinity or user-specified regioning[TBD], and more. Backends also
are able to inject backend-specific passes into the compilation
process, see more on this in the [dedicated chapter](@ref gapi_detail_meta).
Result of graph compilation is a compiled object, represented by class
cv::GCompiled. A new cv::GCompiled object is always created regardless
if there was an explicit or implicit compilation request (see
above). Actual graph execution happens within cv::GCompiled and is
determined by backends which participated in the graph compilation.
@sa cv::GComputation::apply(), cv::GComputation::compile(), cv::GCompiled
# Backends layer {#gapi_backends}
The above diagram lists two backends, _OpenCV_ and _Fluid_. _OpenCV_
is so-called "reference backend", which implements G-API operations
using plain old OpenCV functions. This backend is useful for
prototyping on a familiar development system. _Fluid_ is a plugin for
cache-efficient execution on CPU -- it implements a different
execution policy and operates with its own, special kernels. Fluid
backend allows to achieve less memory footprint and better memory
locality when running on CPU.
There may be more backends available, e.g. Halide, OpenCL, etc. --
G-API provides an uniform internal API to develop backends so any
enthusiast or a company are free to scale G-API on a new platform or
accelerator. In terms of OpenCV infrastructure, every new backend is a
new distinct OpenCV module, which extends G-API when build as a part
of OpenCV.
# Graph execution {#gapi_compiled}
The way graph executed is defined by backends selected for
compilation. In fact, every backend builds its own execution script as
the final stage of graph compilation process, when an executable
(compiled) object is being generated. For example, in OpenCV backend,
this script is just a topologically-sorted sequence of OpenCV
functions to call; for Fluid backend, it is a similar thing -- a
topologically sorted list of _Agents_ processing lines of input on
every iteration.
Graph execution is triggered in two ways:
* via cv::GComputation::apply(), with graph compiled in-place exactly
for the given input data;
* via cv::GCompiled::operator()(), when the graph has been precompiled.
Both methods are polimorphic and take a variadic number of arguments,
with validity checks performed in runtime. If a number, shapes, and
formats of passed data objects differ from expected, a run-time
exception is thrown. G-API also provides _typed_ wrappers to move
these checks to the compile time -- see cv::GComputationT<>.
G-API graph execution is declared stateless -- it means that a
compiled functor (cv::GCompiled) acts like a pure C++ function and
provides the same result for the same set of input arguments.
Both execution methods take \f$N+M\f$ parameters, where \f$N\f$ is a
number of inputs, and \f$M\f$ is a number of outputs on which a
cv::GComputation is defined. Note that while G-API types (cv::GMat,
etc) are used in definition, the execution methods accept OpenCV's
traditional data types (like cv::Mat) which hold actual data -- see
table in [parameter marshalling](@#gapi_detail_params).
@sa @ref gapi_impl, @ref gapi_kernel_api
+170
View File
@@ -0,0 +1,170 @@
# 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:
![Kernel API/implementation hierarchy example](pics/kernel_hierarchy.png)
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
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.
@@ -0,0 +1,27 @@
# Implementation details {#gapi_impl}
[TOC]
# G-API Implementation details {#gapi_impl_header}
## Api layer details {#gapi_detail_api}
### Expression unrolling {#gapi_detail_expr}
### Parameter marshalling {#gapi_detail_params}
### Operations representation {#gapi_detail_operations}
## Graph compiler details {#gapi_detail_compiler}
### ADE basics {#gapi_detail_ade}
### Graph model representation {#gapi_detail_gmodel}
### G-API metadata and passes {#gapi_detail_meta}
## Backends details {#gapi_detail_backends}
### Backend scope of work {#gapi_backend_scope}
### Graph transformation {#gapi_backend_pass}
+17
View File
@@ -0,0 +1,17 @@
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"];
}
-3
View File
@@ -1,3 +0,0 @@
# Graph API {#gapi}
Introduction to G-API (WIP).
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

@@ -8,10 +8,10 @@
#ifndef OPENCV_GAPI_GCPUKERNEL_HPP
#define OPENCV_GAPI_GCPUKERNEL_HPP
#include <vector>
#include <functional>
#include <map>
#include <unordered_map>
#include <utility>
#include <vector>
#include <opencv2/core/mat.hpp>
#include <opencv2/gapi/gcommon.hpp>
@@ -10,11 +10,11 @@
#include <functional>
#include <iostream>
#include <unordered_map> // map (for GKernelPackage)
#include <vector> // lookup order
#include <string> // string
#include <utility> // tuple
#include <type_traits> // false_type, true_type
#include <unordered_map> // map (for GKernelPackage)
#include <utility> // tuple
#include <vector> // lookup order
#include <opencv2/gapi/gcommon.hpp> // CompileArgTag
#include <opencv2/gapi/util/util.hpp> // Seq
+34
View File
@@ -0,0 +1,34 @@
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/gapi.hpp>
#include <opencv2/gapi/core.hpp>
#include <opencv2/gapi/imgproc.hpp>
int main(int argc, char *argv[])
{
cv::VideoCapture cap;
if (argc > 1) cap.open(argv[1]);
else cap.open(0);
CV_Assert(cap.isOpened());
cv::GMat in;
cv::GMat vga = cv::gapi::resize(in, cv::Size(), 0.5, 0.5);
cv::GMat gray = cv::gapi::BGR2Gray(vga);
cv::GMat blurred = cv::gapi::blur(gray, cv::Size(5,5));
cv::GMat edges = cv::gapi::Canny(blurred, 32, 128, 3);
cv::GMat b,g,r;
std::tie(b,g,r) = cv::gapi::split3(vga);
cv::GMat out = cv::gapi::merge3(b, g | edges, r);
cv::GComputation ac(in, out);
cv::Mat input_frame;
cv::Mat output_frame;
CV_Assert(cap.read(input_frame));
do
{
ac.apply(input_frame, output_frame);
cv::imshow("output", output_frame);
} while (cap.read(input_frame) && cv::waitKey(30) < 0);
return 0;
}
@@ -0,0 +1,157 @@
// [filter2d_api]
#include <opencv2/gapi.hpp>
G_TYPED_KERNEL(GFilter2D,
<cv::GMat(cv::GMat,int,cv::Mat,cv::Point,double,int,cv::Scalar)>,
"org.opencv.imgproc.filters.filter2D")
{
static cv::GMatDesc // outMeta's return value type
outMeta(cv::GMatDesc in , // descriptor of input GMat
int ddepth , // depth parameter
cv::Mat /* coeffs */, // (unused)
cv::Point /* anchor */, // (unused)
double /* scale */, // (unused)
int /* border */, // (unused)
cv::Scalar /* bvalue */ ) // (unused)
{
return in.withDepth(ddepth);
}
};
// [filter2d_api]
cv::GMat filter2D(cv::GMat ,
int ,
cv::Mat ,
cv::Point ,
double ,
int ,
cv::Scalar);
// [filter2d_wrap]
cv::GMat filter2D(cv::GMat in,
int ddepth,
cv::Mat k,
cv::Point anchor = cv::Point(-1,-1),
double scale = 0.,
int border = cv::BORDER_DEFAULT,
cv::Scalar bval = cv::Scalar(0))
{
return GFilter2D::on(in, ddepth, k, anchor, scale, border, bval);
}
// [filter2d_wrap]
// [compound]
#include <opencv2/gapi/gcompoundkernel.hpp> // GAPI_COMPOUND_KERNEL()
using PointArray2f = cv::GArray<cv::Point2f>;
G_TYPED_KERNEL(HarrisCorners,
<PointArray2f(cv::GMat,int,double,double,int,double)>,
"org.opencv.imgproc.harris_corner")
{
static cv::GArrayDesc outMeta(const cv::GMatDesc &,
int,
double,
double,
int,
double)
{
// No special metadata for arrays in G-API (yet)
return cv::empty_array_desc();
}
};
// Define Fluid-backend-local kernels which form GoodFeatures
G_TYPED_KERNEL(HarrisResponse,
<cv::GMat(cv::GMat,double,int,double)>,
"org.opencv.fluid.harris_response")
{
static cv::GMatDesc outMeta(const cv::GMatDesc &in,
double,
int,
double)
{
return in.withType(CV_32F, 1);
}
};
G_TYPED_KERNEL(ArrayNMS,
<PointArray2f(cv::GMat,int,double)>,
"org.opencv.cpu.nms_array")
{
static cv::GArrayDesc outMeta(const cv::GMatDesc &,
int,
double)
{
return cv::empty_array_desc();
}
};
GAPI_COMPOUND_KERNEL(GFluidHarrisCorners, HarrisCorners)
{
static PointArray2f
expand(cv::GMat in,
int maxCorners,
double quality,
double minDist,
int blockSize,
double k)
{
cv::GMat response = HarrisResponse::on(in, quality, blockSize, k);
return ArrayNMS::on(response, maxCorners, minDist);
}
};
// Then implement HarrisResponse as Fluid kernel and NMSresponse
// as a generic (OpenCV) kernel
// [compound]
// [filter2d_ocv]
#include <opencv2/gapi/cpu/gcpukernel.hpp> // GAPI_OCV_KERNEL()
#include <opencv2/imgproc.hpp> // cv::filter2D()
GAPI_OCV_KERNEL(GCPUFilter2D, GFilter2D)
{
static void
run(const cv::Mat &in, // in - derived from GMat
const int ddepth, // opaque (passed as-is)
const cv::Mat &k, // opaque (passed as-is)
const cv::Point &anchor, // opaque (passed as-is)
const double delta, // opaque (passed as-is)
const int border, // opaque (passed as-is)
const cv::Scalar &, // opaque (passed as-is)
cv::Mat &out) // out - derived from GMat (retval)
{
cv::filter2D(in, out, ddepth, k, anchor, delta, border);
}
};
// [filter2d_ocv]
int main(int, char *[])
{
std::cout << "This sample is non-complete. It is used as code snippents in documentation." << std::endl;
cv::Mat conv_kernel_mat;
{
// [filter2d_on]
cv::GMat in;
cv::GMat out = GFilter2D::on(/* GMat */ in,
/* int */ -1,
/* Mat */ conv_kernel_mat,
/* Point */ cv::Point(-1,-1),
/* double */ 0.,
/* int */ cv::BORDER_DEFAULT,
/* Scalar */ cv::Scalar(0));
// [filter2d_on]
}
{
// [filter2d_wrap_call]
cv::GMat in;
cv::GMat out = filter2D(in, -1, conv_kernel_mat);
// [filter2d_wrap_call]
}
return 0;
}
+237 -171
View File
@@ -76,8 +76,23 @@ namespace
const cv::GCompileArgs &args,
const std::vector<ade::NodeHandle> &nodes) const override
{
const auto out_rois = cv::gimpl::getCompileArg<cv::GFluidOutputRois>(args).value_or(cv::GFluidOutputRois());
return EPtr{new cv::gimpl::GFluidExecutable(graph, nodes, out_rois.rois)};
using namespace cv::gimpl;
GModel::ConstGraph g(graph);
auto isl_graph = g.metadata().get<IslandModel>().model;
GIslandModel::Graph gim(*isl_graph);
const auto num_islands = std::count_if
(gim.nodes().begin(), gim.nodes().end(),
[&](const ade::NodeHandle &nh) {
return gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND;
});
const auto out_rois = cv::gimpl::getCompileArg<cv::GFluidOutputRois>(args);
if (num_islands > 1 && out_rois.has_value())
cv::util::throw_error(std::logic_error("GFluidOutputRois feature supports only one-island graphs"));
auto rois = out_rois.value_or(cv::GFluidOutputRois());
return EPtr{new cv::gimpl::GFluidExecutable(graph, nodes, std::move(rois.rois))};
}
virtual void addBackendPasses(ade::ExecutionEngineSetupContext &ectx) override;
@@ -102,6 +117,7 @@ private:
virtual int linesRead() const override;
public:
using FluidAgent::FluidAgent;
virtual void setInHeight(int) override { /* nothing */ }
};
struct FluidResizeAgent : public FluidAgent
@@ -112,6 +128,7 @@ private:
virtual int linesRead() const override;
public:
using FluidAgent::FluidAgent;
virtual void setInHeight(int) override { /* nothing */ }
};
struct FluidUpscaleAgent : public FluidAgent
@@ -120,8 +137,11 @@ private:
virtual int firstWindow() const override;
virtual int nextWindow() const override;
virtual int linesRead() const override;
int m_inH;
public:
using FluidAgent::FluidAgent;
virtual void setInHeight(int h) override { m_inH = h; }
};
}} // namespace cv::gimpl
@@ -207,21 +227,24 @@ static int calcResizeWindow(int inH, int outH)
}
}
static int maxReadWindow(const cv::GFluidKernel& k, int inH, int outH)
static int maxLineConsumption(const cv::GFluidKernel& k, int inH, int outH, int lpi)
{
switch (k.m_kind)
{
case cv::GFluidKernel::Kind::Filter: return k.m_window; break;
case cv::GFluidKernel::Kind::Filter: return k.m_window + lpi - 1; break;
case cv::GFluidKernel::Kind::Resize:
{
if (inH >= outH)
{
return calcResizeWindow(inH, outH);
// FIXME:
// This is a suboptimal value, can be reduced
return calcResizeWindow(inH, outH) * lpi;
}
else
{
// Upscale always has window of 2
return (inH == 1) ? 1 : 2;
// FIXME:
// This is a suboptimal value, can be reduced
return (inH == 1) ? 1 : 2 + lpi - 1;
}
} break;
default: GAPI_Assert(false); return 0;
@@ -297,37 +320,45 @@ int cv::gimpl::FluidFilterAgent::linesRead() const
int cv::gimpl::FluidResizeAgent::firstWindow() const
{
auto outIdx = out_buffers[0]->priv().y();
return windowEnd(outIdx, m_ratio) - windowStart(outIdx, m_ratio);
auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);
return windowEnd(outIdx + lpi - 1, 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);
auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);
auto nextStartIdx = outIdx + 1 + k.m_lpi - 1;
auto nextEndIdx = nextStartIdx + lpi - 1;
return windowEnd(nextEndIdx, m_ratio) - windowStart(nextStartIdx, 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);
return windowStart(outIdx + 1 + k.m_lpi - 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);
auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);
return upscaleWindowEnd(outIdx + lpi - 1, m_ratio, m_inH) - 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);
auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);
auto nextStartIdx = outIdx + 1 + k.m_lpi - 1;
auto nextEndIdx = nextStartIdx + lpi - 1;
return upscaleWindowEnd(nextEndIdx, m_ratio, m_inH) - upscaleWindowStart(nextStartIdx, 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);
return upscaleWindowStart(outIdx + 1 + k.m_lpi - 1, m_ratio) - upscaleWindowStart(outIdx, m_ratio);
}
bool cv::gimpl::FluidAgent::canRead() const
@@ -416,111 +447,17 @@ void cv::gimpl::FluidAgent::debug(std::ostream &os)
}
// GCPUExcecutable implementation //////////////////////////////////////////////
cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
const std::vector<ade::NodeHandle> &nodes,
const std::vector<cv::gapi::own::Rect> &outputRois)
: m_g(g), m_gm(m_g), m_outputRois(outputRois)
void cv::gimpl::GFluidExecutable::initBufferRois(std::vector<int>& readStarts, std::vector<cv::gapi::own::Rect>& rois)
{
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<std::size_t, ade::NodeHandle> all_gmat_ids;
auto grab_mat_nh = [&](ade::NodeHandle nh) {
auto rc = m_gm.metadata(nh).get<Data>().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<NodeType>().t)
{
case NodeType::DATA:
if (m_gm.metadata(nh).get<Data>().shape == GShape::GMAT)
grab_mat_nh(nh);
break;
case NodeType::OP:
{
const auto& fu = fg.metadata(nh).get<FluidUnit>();
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: GAPI_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<Op>().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<Data>().shape == GShape::GMAT)
{
const auto in_port = m_gm.metadata(eh).get<Input>().port;
const auto in_buf = m_gm.metadata(eh->srcNode()).get<Data>().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<Data>();
const auto out_port = m_gm.metadata(eh).get<Output>().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<Protocol>();
std::vector<int> readStarts(mat_count);
std::vector<cv::gapi::own::Rect> rois(mat_count);
std::stack<ade::NodeHandle> nodesToVisit;
if (proto.outputs.size() != m_outputRois.size())
{
GAPI_Assert(m_outputRois.size() == 0);
m_outputRois.resize(proto.outputs.size());
return;
}
// First, initialize rois for output nodes, add them to traversal stack
@@ -569,6 +506,7 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
{
GAPI_Assert(startNode->inNodes().size() == 1);
const auto& oh = startNode->inNodes().front();
const auto& data = m_gm.metadata(startNode).get<Data>();
// only GMats participate in the process so it's valid to obtain GMatDesc
const auto& meta = util::get<GMatDesc>(data.meta);
@@ -577,7 +515,7 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
{
const auto& in_data = m_gm.metadata(inNode).get<Data>();
if (in_data.shape == GShape::GMAT)
if (in_data.shape == GShape::GMAT && fg.metadata(inNode).contains<FluidData>())
{
const auto& in_meta = util::get<GMatDesc>(in_data.meta);
const auto& fd = fg.metadata(inNode).get<FluidData>();
@@ -636,7 +574,8 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
{
readStarts[in_id] = readStart;
rois[in_id] = roi;
nodesToVisit.push(inNode);
// Continue traverse on internal (w.r.t Island) data nodes only.
if (fd.internal) nodesToVisit.push(inNode);
}
else
{
@@ -647,6 +586,106 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
} // for (const auto& inNode : oh->inNodes())
} // if (!startNode->inNodes().empty())
} // while (!nodesToVisit.empty())
}
cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
const std::vector<ade::NodeHandle> &nodes,
const std::vector<cv::gapi::own::Rect> &outputRois)
: m_g(g), m_gm(m_g), m_nodes(nodes), 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<std::size_t, ade::NodeHandle> all_gmat_ids;
auto grab_mat_nh = [&](ade::NodeHandle nh) {
auto rc = m_gm.metadata(nh).get<Data>().rc;
if (m_id_map.count(rc) == 0)
{
all_gmat_ids[mat_count] = nh;
m_id_map[rc] = mat_count++;
}
};
for (const auto &nh : m_nodes)
{
switch (m_gm.metadata(nh).get<NodeType>().t)
{
case NodeType::DATA:
if (m_gm.metadata(nh).get<Data>().shape == GShape::GMAT)
grab_mat_nh(nh);
break;
case NodeType::OP:
{
const auto& fu = fg.metadata(nh).get<FluidUnit>();
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: GAPI_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<Op>().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<Data>().shape == GShape::GMAT)
{
const auto in_port = m_gm.metadata(eh).get<Input>().port;
const int in_buf = m_gm.metadata(eh->srcNode()).get<Data>().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<Data>();
const auto out_port = m_gm.metadata(eh).get<Output>().port;
const int 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() > 0);
GAPI_Assert(m_id_map.size() == static_cast<size_t>(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();
std::vector<int> readStarts(mat_count);
std::vector<cv::gapi::own::Rect> rois(mat_count);
initBufferRois(readStarts, rois);
// NB: Allocate ALL buffer object at once, and avoid any further reallocations
// (since raw pointers-to-elements are taken)
@@ -659,12 +698,13 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
const auto &fd = fg.metadata(nh).get<FluidData>();
const auto meta = cv::util::get<GMatDesc>(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]);
m_buffers[id].priv().init(meta, fd.lpi_write, readStarts[id], rois[id]);
if (d.storage == Data::Storage::INTERNAL)
// TODO:
// Introduce Storage::INTERNAL_GRAPH and Storage::INTERNAL_ISLAND?
if (fd.internal == true)
{
m_buffers[id].priv().allocate(fd.border);
m_buffers[id].priv().allocate(fd.border, fd.border_size, fd.max_consumption, fd.skew);
std::stringstream stream;
m_buffers[id].debug(stream);
GAPI_LOG_INFO(NULL, stream.str());
@@ -707,6 +747,13 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
}
}
// cache input height to avoid costly meta() call
// (actually cached and used only in upscale)
if (agent->in_views[0])
{
agent->setInHeight(agent->in_views[0].meta().size.height);
}
// 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()),
@@ -723,7 +770,7 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
if (num_scratch)
{
GAPI_LOG_INFO(NULL, "Initializing " << num_scratch << " scratch buffer(s)" << std::endl);
unsigned last_scratch_id = 0;
std::size_t last_scratch_id = 0;
for (auto i : m_scratch_users)
{
@@ -751,14 +798,14 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
}
}
int total_size = 0;
std::size_t 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);
const auto idx = ade::util::index(i);
const auto b = ade::util::value(i);
if (idx >= m_num_int_buffers ||
m_gm.metadata(all_gmat_ids[idx]).get<Data>().storage == Data::Storage::INTERNAL)
fg.metadata(all_gmat_ids[idx]).get<FluidData>().internal == true)
{
GAPI_Assert(b.priv().size() > 0);
}
@@ -888,7 +935,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
// 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)
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!
@@ -897,32 +944,46 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto isl_graph = g.metadata().get<IslandModel>().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<NodeKind>().k == NodeKind::ISLAND &&
gim.metadata(nh).get<FusedIsland>().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())
const auto setFluidData = [&](ade::NodeHandle nh, bool internal) {
FluidData fd;
fd.internal = internal;
fg.metadata(nh).set(fd);
};
for (const auto& nh : gim.nodes())
{
if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
if (gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND)
{
fg.metadata(node).set(FluidData());
}
}
const auto isl = gim.metadata(nh).get<FusedIsland>().object;
if (isl->backend() == cv::gapi::fluid::backend())
{
// add FluidData to all data nodes inside island
for (const auto node : isl->contents())
{
if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
setFluidData(node, true);
}
// add FluidData to slot if it's read/written by fluid
std::vector<ade::NodeHandle> io_handles;
for (const auto &in_op : isl->in_ops())
{
ade::util::copy(in_op->inNodes(), std::back_inserter(io_handles));
}
for (const auto &out_op : isl->out_ops())
{
ade::util::copy(out_op->outNodes(), std::back_inserter(io_handles));
}
for (const auto &io_node : io_handles)
{
if (!fg.metadata(io_node).contains<FluidData>())
setFluidData(io_node, false);
}
} // if (fluid backend)
} // if (ISLAND)
} // for (gim.nodes())
});
// FIXME:
// move to unpackKernel method
@@ -938,7 +999,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
for (auto node : sorted)
{
if (g.metadata(node).get<NodeType>().t == NodeType::OP)
if (fg.metadata(node).contains<FluidUnit>())
{
// FIXME: check that op has only one data node on input
auto &fu = fg.metadata(node).get<FluidUnit>();
@@ -960,7 +1021,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
for (auto node : sorted)
{
if (g.metadata(node).get<NodeType>().t == NodeType::OP)
if (fg.metadata(node).contains<FluidUnit>())
{
std::set<int> in_hs, out_ws, out_hs;
@@ -993,8 +1054,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto &fu = fg.metadata(node).get<FluidUnit>();
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 line_consumption = maxLineConsumption(fu.k, in_h, out_h, fu.k.m_lpi);
int border_size = borderSize(fu.k);
fu.border_size = border_size;
@@ -1014,7 +1074,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
GFluidModel fg(ctx.graph);
for (const auto node : g.nodes())
{
if (g.metadata(node).get<NodeType>().t == NodeType::OP)
if (fg.metadata(node).contains<FluidUnit>())
{
const auto &fu = fg.metadata(node).get<FluidUnit>();
@@ -1045,7 +1105,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
for (auto node : sorted)
{
if (g.metadata(node).get<NodeType>().t == NodeType::OP)
if (fg.metadata(node).contains<FluidUnit>())
{
const auto &fu = fg.metadata(node).get<FluidUnit>();
@@ -1083,7 +1143,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
for (auto node : sorted)
{
if (g.metadata(node).get<NodeType>().t == NodeType::OP)
if (fg.metadata(node).contains<FluidUnit>())
{
int max_latency = 0;
for (auto in_data_node : node->inNodes())
@@ -1105,6 +1165,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
}
}
});
ectx.addPass("exec", "init_buffer_borders", [](ade::passes::PassContext &ctx)
{
GModel::Graph g(ctx.graph);
@@ -1115,7 +1176,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
for (auto node : sorted)
{
if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
if (fg.metadata(node).contains<FluidData>())
{
auto &fd = fg.metadata(node).get<FluidData>();
@@ -1123,7 +1184,7 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
// In/out data nodes are bound to user data directly,
// so cannot be extended with a border
if (g.metadata(node).get<Data>().storage == Data::Storage::INTERNAL)
if (fd.internal == true)
{
// For now border of the buffer's storage is the border
// of the first reader whose border size is the same.
@@ -1134,9 +1195,10 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
// 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<FluidUnit>();
return fu.border_size == fd.border_size;
return fg.metadata(nh).contains<FluidUnit>() &&
fg.metadata(nh).get<FluidUnit>().border_size == fd.border_size;
});
GAPI_Assert(candidate != readers.end());
const auto &fu = fg.metadata(*candidate).get<FluidUnit>();
@@ -1159,26 +1221,30 @@ void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
GFluidModel fg(ctx.graph);
for (auto node : g.nodes())
{
if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
if (fg.metadata(node).contains<FluidData>())
{
auto &fd = fg.metadata(node).get<FluidData>();
for (auto out_edge : node->outEdges())
{
const auto &fu = fg.metadata(out_edge->dstNode()).get<FluidUnit>();
const auto dstNode = out_edge->dstNode();
if (fg.metadata(dstNode).contains<FluidUnit>())
{
const auto &fu = fg.metadata(dstNode).get<FluidUnit>();
// 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");
// 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");
}
}
}
}
@@ -21,7 +21,7 @@ namespace cv { namespace gimpl {
struct FluidUnit
{
static const char *name() { return "FluidKernel"; }
static const char *name() { return "FluidUnit"; }
GFluidKernel k;
gapi::fluid::BorderOpt border;
int border_size;
@@ -40,11 +40,12 @@ 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;
int latency = 0;
int skew = 0;
int max_consumption = 1;
int border_size = 0;
int lpi_write = 1;
bool internal = false; // is node internal to any fluid island
gapi::fluid::BorderOpt border;
};
@@ -82,7 +83,10 @@ public:
bool done() const;
void debug(std::ostream& os);
// FIXME:
// refactor (implement a more solid replacement or
// drop this method completely)
virtual void setInHeight(int h) = 0;
private:
// FIXME!!!
// move to another class
@@ -95,6 +99,7 @@ class GFluidExecutable final: public GIslandExecutable
{
const ade::Graph &m_g;
GModel::ConstGraph m_gm;
const std::vector<ade::NodeHandle> m_nodes;
std::vector<std::unique_ptr<FluidAgent>> m_agents;
std::vector<cv::gapi::fluid::Buffer> m_buffers;
@@ -114,6 +119,8 @@ class GFluidExecutable final: public GIslandExecutable
void bindOutArg(const RcDesc &rc, const GRunArgP &arg);
void packArg (GArg &in_arg, const GArg &op_arg);
void initBufferRois(std::vector<int>& readStarts, std::vector<cv::gapi::own::Rect>& rois);
public:
GFluidExecutable(const ade::Graph &g,
const std::vector<ade::NodeHandle> &nodes,
@@ -20,12 +20,6 @@
namespace cv {
namespace gapi {
//namespace own {
// class Mat;
// CV_EXPORTS cv::GMatDesc descr_of(const Mat &mat);
//}//own
namespace fluid {
bool operator == (const fluid::Border& b1, const fluid::Border& b2)
{
@@ -503,38 +497,34 @@ fluid::Buffer::Priv::Priv(int read_start, cv::gapi::own::Rect 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;
m_writer_lpi = wlpi;
m_desc = desc;
m_readStart = readStartPos;
m_roi = roi == cv::Rect{} ? cv::Rect{0, 0, desc.size.width, desc.size.height}
: roi;
}
void fluid::Buffer::Priv::allocate(BorderOpt border)
void fluid::Buffer::Priv::allocate(BorderOpt border,
int border_size,
int line_consumption,
int skew)
{
GAPI_Assert(!m_storage);
GAPI_Assert(line_consumption > 0);
// Init physical buffer
// FIXME? combine line_consumption with skew?
auto data_height = std::max(m_line_consumption, m_skew) + m_writer_lpi - 1;
auto data_height = std::max(line_consumption, skew) + m_writer_lpi - 1;
m_storage = createStorage(data_height,
m_desc.size.width,
CV_MAKETYPE(m_desc.depth, m_desc.chan),
m_border_size,
border_size,
border);
// Finally, initialize carets
@@ -544,9 +534,15 @@ void fluid::Buffer::Priv::allocate(BorderOpt border)
void fluid::Buffer::Priv::bindTo(const cv::gapi::own::Mat &data, bool is_input)
{
// FIXME: move all these fields into a separate structure
GAPI_Assert(m_skew == 0);
GAPI_Assert(m_desc == descr_of(data));
if ( is_input) GAPI_Assert(m_writer_lpi == 1);
// Currently m_writer_lpi is obtained from metadata which is shared between islands
// and this assert can trigger for slot which connects two fluid islands.
// m_writer_lpi is used only in write-related functions and doesn't affect
// buffer which is island's input so it's safe to skip this check.
// FIXME:
// Bring back this check when we move to 1 buffer <-> 1 metadata model
// if (is_input) GAPI_Assert(m_writer_lpi == 1);
m_storage = createStorage(data, m_roi);
@@ -638,8 +634,8 @@ fluid::Buffer::Buffer(const cv::GMatDesc &desc)
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({});
m_priv->init(desc, wlpi, readStart, roi);
m_priv->allocate({}, border, lineConsumption, skew);
}
fluid::Buffer::Buffer(const cv::GMatDesc &desc,
@@ -652,17 +648,16 @@ fluid::Buffer::Buffer(const cv::GMatDesc &desc,
{
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);
m_priv->init(desc, wlpi, readStart, roi);
m_priv->allocate(border, border_size, max_line_consumption, skew);
}
fluid::Buffer::Buffer(const cv::gapi::own::Mat &data, bool is_input)
: m_priv(new Priv())
{
int lineConsumption = 1;
int border = 0, skew = 0, wlpi = 1, readStart = 0;
int 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->init(descr_of(data), wlpi, readStart, roi);
m_priv->bindTo(data, is_input);
}
@@ -11,14 +11,8 @@
#include <vector>
#include "opencv2/gapi/fluid/gfluidbuffer.hpp"
#include "opencv2/gapi/own/convert.hpp" // cv::gapi::own::to_ocv
#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS
namespace gapi { namespace own {
class Mat;
GAPI_EXPORTS cv::GMatDesc descr_of(const Mat &mat);
}}//gapi::own
namespace cv {
namespace gapi {
namespace fluid {
@@ -233,9 +227,6 @@ void debugBufferPriv(const Buffer& buffer, std::ostream &os);
// 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}};
@@ -262,14 +253,11 @@ public:
// 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 allocate(BorderOpt border, int border_size, int line_consumption, int skew);
void bindTo(const cv::gapi::own::Mat &data, bool is_input);
inline void addView(const View& view) { m_views.push_back(view); }
+203 -15
View File
@@ -31,6 +31,22 @@ namespace
}
};
void FluidFooRow(const uint8_t* in, uint8_t* out, int length)
{
for (int i = 0; i < length; i++)
{
out[i] = in[i] + 3;
}
}
void FluidBarRow(const uint8_t* in1, const uint8_t* in2, uint8_t* out, int length)
{
for (int i = 0; i < length; i++)
{
out[i] = 3*(in1[i] + in2[i]);
}
}
GAPI_FLUID_KERNEL(FFoo, I::Foo, false)
{
static const int Window = 1;
@@ -38,12 +54,7 @@ namespace
static void run(const cv::gapi::fluid::View &in,
cv::gapi::fluid::Buffer &out)
{
const uint8_t* in_ptr = in.InLine<uint8_t>(0);
uint8_t *out_ptr = out.OutLine<uint8_t>();
for (int i = 0; i < in.length(); i++)
{
out_ptr[i] = in_ptr[i] + 3;
}
FluidFooRow(in.InLineB(0), out.OutLineB(), in.length());
}
};
@@ -55,15 +66,88 @@ namespace
const cv::gapi::fluid::View &in2,
cv::gapi::fluid::Buffer &out)
{
const uint8_t* in1_ptr = in1.InLine<uint8_t>(0);
const uint8_t* in2_ptr = in2.InLine<uint8_t>(0);
uint8_t *out_ptr = out.OutLine<uint8_t>();
for (int i = 0; i < in1.length(); i++)
FluidBarRow(in1.InLineB(0), in2.InLineB(0), out.OutLineB(), in1.length());
}
};
G_TYPED_KERNEL(FluidFooI, <cv::GMat(cv::GMat)>, "test.kernels.fluid_foo")
{
static cv::GMatDesc outMeta(const cv::GMatDesc &in) { return in; }
};
G_TYPED_KERNEL(FluidBarI, <cv::GMat(cv::GMat,cv::GMat)>, "test.kernels.fluid_bar")
{
static cv::GMatDesc outMeta(const cv::GMatDesc &in, const cv::GMatDesc &) { return in; }
};
GAPI_FLUID_KERNEL(FluidFoo, FluidFooI, false)
{
static const int Window = 1;
static void run(const cv::gapi::fluid::View &in,
cv::gapi::fluid::Buffer &out)
{
FluidFooRow(in.InLineB(0), out.OutLineB(), in.length());
}
};
GAPI_FLUID_KERNEL(FluidBar, FluidBarI, 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)
{
FluidBarRow(in1.InLineB(0), in2.InLineB(0), out.OutLineB(), in1.length());
}
};
GAPI_FLUID_KERNEL(FluidFoo2lpi, FluidFooI, false)
{
static const int Window = 1;
static const int LPI = 2;
static void run(const cv::gapi::fluid::View &in,
cv::gapi::fluid::Buffer &out)
{
for (int l = 0; l < out.lpi(); l++)
{
out_ptr[i] = 3*(in1_ptr[i] + in2_ptr[i]);
FluidFooRow(in.InLineB(l), out.OutLineB(l), in.length());
}
}
};
cv::Mat ocvFoo(const cv::Mat &in)
{
cv::Mat out;
OCVFoo::run(in, out);
return out;
}
cv::Mat ocvBar(const cv::Mat &in1, const cv::Mat &in2)
{
cv::Mat out;
OCVBar::run(in1, in2, out);
return out;
}
cv::Mat fluidFoo(const cv::Mat &in)
{
cv::Mat out(in.rows, in.cols, in.type());
for (int y = 0; y < in.rows; y++)
{
FluidFooRow(in.ptr(y), out.ptr(y), in.cols);
}
return out;
}
cv::Mat fluidBar(const cv::Mat &in1, const cv::Mat &in2)
{
cv::Mat out(in1.rows, in1.cols, in1.type());
for (int y = 0; y < in1.rows; y++)
{
FluidBarRow(in1.ptr(y), in2.ptr(y), out.ptr(y), in1.cols);
}
return out;
}
} // anonymous namespace
struct GAPIHeteroTest: public ::testing::Test
@@ -98,7 +182,7 @@ TEST_F(GAPIHeteroTest, TestOCV)
EXPECT_TRUE(cv::gapi::cpu::backend() == m_ocv_kernels.lookup<I::Foo>());
EXPECT_TRUE(cv::gapi::cpu::backend() == m_ocv_kernels.lookup<I::Bar>());
cv::Mat ref = 4*(m_in_mat+2 + m_in_mat+2);
cv::Mat ref = ocvBar(ocvFoo(m_in_mat), ocvFoo(m_in_mat));
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));
}
@@ -108,17 +192,121 @@ TEST_F(GAPIHeteroTest, TestFluid)
EXPECT_TRUE(cv::gapi::fluid::backend() == m_fluid_kernels.lookup<I::Foo>());
EXPECT_TRUE(cv::gapi::fluid::backend() == m_fluid_kernels.lookup<I::Bar>());
cv::Mat ref = 3*(m_in_mat+3 + m_in_mat+3);
cv::Mat ref = fluidBar(fluidFoo(m_in_mat), fluidFoo(m_in_mat));
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)
TEST_F(GAPIHeteroTest, TestBoth)
{
EXPECT_TRUE(cv::gapi::cpu::backend() == m_hetero_kernels.lookup<I::Foo>());
EXPECT_TRUE(cv::gapi::fluid::backend() == m_hetero_kernels.lookup<I::Bar>());
EXPECT_ANY_THROW(m_comp.apply(m_in_mat, m_out_mat, cv::compile_args(m_hetero_kernels)));
cv::Mat ref = fluidBar(ocvFoo(m_in_mat), ocvFoo(m_in_mat));
EXPECT_NO_THROW(m_comp.apply(m_in_mat, m_out_mat, cv::compile_args(m_hetero_kernels)));
EXPECT_EQ(0, cv::countNonZero(ref != m_out_mat));
}
struct GAPIBigHeteroTest : public ::testing::TestWithParam<std::array<int, 9>>
{
cv::GComputation m_comp;
cv::gapi::GKernelPackage m_kernels;
cv::Mat m_in_mat;
cv::Mat m_out_mat1;
cv::Mat m_out_mat2;
cv::Mat m_ref_mat1;
cv::Mat m_ref_mat2;
GAPIBigHeteroTest();
};
// Foo7
// .-> Foo2 -> Foo3 -<
// Foo0 -> Foo1 Bar -> Foo6
// `-> Foo4 -> Foo5 -`
GAPIBigHeteroTest::GAPIBigHeteroTest()
: m_comp([&](){
auto flags = GetParam();
std::array<std::function<cv::GMat(cv::GMat)>, 8> foos;
for (int i = 0; i < 8; i++)
{
foos[i] = flags[i] ? &I::Foo::on : &FluidFooI::on;
}
auto bar = flags[8] ? &I::Bar::on : &FluidBarI::on;
cv::GMat in;
auto foo1Out = foos[1](foos[0](in));
auto foo3Out = foos[3](foos[2](foo1Out));
auto foo6Out = foos[6](bar(foo3Out,
foos[5](foos[4](foo1Out))));
auto foo7Out = foos[7](foo3Out);
return cv::GComputation(GIn(in), GOut(foo6Out, foo7Out));
})
, m_kernels(cv::gapi::kernels<OCVFoo, OCVBar, FluidFoo, FluidBar>())
, m_in_mat(cv::Mat::eye(cv::Size(64, 64), CV_8UC1))
{
auto flags = GetParam();
std::array<std::function<cv::Mat(cv::Mat)>, 8> foos;
for (int i = 0; i < 8; i++)
{
foos[i] = flags[i] ? ocvFoo : fluidFoo;
}
auto bar = flags[8] ? ocvBar : fluidBar;
cv::Mat foo1OutMat = foos[1](foos[0](m_in_mat));
cv::Mat foo3OutMat = foos[3](foos[2](foo1OutMat));
m_ref_mat1 = foos[6](bar(foo3OutMat,
foos[5](foos[4](foo1OutMat))));
m_ref_mat2 = foos[7](foo3OutMat);
}
TEST_P(GAPIBigHeteroTest, Test)
{
EXPECT_NO_THROW(m_comp.apply(gin(m_in_mat), gout(m_out_mat1, m_out_mat2), cv::compile_args(m_kernels)));
EXPECT_EQ(0, cv::countNonZero(m_ref_mat1 != m_out_mat1));
EXPECT_EQ(0, cv::countNonZero(m_ref_mat2 != m_out_mat2));
}
static auto configurations = []()
{
// Fill all possible configurations
// from 000000000 to 111111111
std::array<std::array<int, 9>, 512> arr;
for (auto n = 0; n < 512; n++)
{
for (auto i = 0; i < 9; i++)
{
arr[n][i] = (n >> (8 - i)) & 1;
}
}
return arr;
}();
INSTANTIATE_TEST_CASE_P(GAPIBigHeteroTest, GAPIBigHeteroTest,
::testing::ValuesIn(configurations));
TEST(GAPIHeteroTestLPI, Test)
{
cv::GMat in;
auto mid = FluidFooI::on(in);
auto out = FluidFooI::on(mid);
cv::gapi::island("isl0", GIn(in), GOut(mid));
cv::gapi::island("isl1", GIn(mid), GOut(out));
cv::GComputation c(in, out);
cv::Mat in_mat = cv::Mat::eye(cv::Size(64, 64), CV_8UC1);
cv::Mat out_mat;
EXPECT_NO_THROW(c.apply(in_mat, out_mat, cv::compile_args(cv::gapi::kernels<FluidFoo2lpi>())));
cv::Mat ref = fluidFoo(fluidFoo(in_mat));
EXPECT_EQ(0, cv::countNonZero(ref != out_mat));
}
} // namespace opencv_test
+142 -71
View File
@@ -40,7 +40,7 @@ GAPI_FLUID_KERNEL(FCopy, TCopy, false)
}
};
GAPI_FLUID_KERNEL(FResizeNN, cv::gapi::core::GResize, false)
GAPI_FLUID_KERNEL(FResizeNN1Lpi, cv::gapi::core::GResize, false)
{
static const int Window = 1;
static const auto Kind = GFluidKernel::Kind::Resize;
@@ -49,22 +49,25 @@ GAPI_FLUID_KERNEL(FResizeNN, cv::gapi::core::GResize, false)
cv::gapi::fluid::Buffer& out)
{
auto length = out.length();
double vRatio = (double)in.meta().size.height / out.meta().size.height;
double hRatio = (double)in.length() / length;
auto y = out.y();
auto inY = in.y();
auto sy = static_cast<int>(y * vRatio);
int idx = sy - inY;
const auto src = in.InLine <unsigned char>(idx);
auto dst = out.OutLine<unsigned char>();
double horRatio = (double)in.length() / out.length();
for (int x = 0; x < out.length(); x++)
for (int l = 0; l < out.lpi(); l++)
{
auto inX = static_cast<int>(x * horRatio);
dst[x] = src[inX];
auto sy = static_cast<int>((y+l) * vRatio);
int idx = sy - inY;
const auto src = in.InLine <unsigned char>(idx);
auto dst = out.OutLine<unsigned char>(l);
for (int x = 0; x < length; x++)
{
auto inX = static_cast<int>(x * hRatio);
dst[x] = src[inX];
}
}
}
};
@@ -101,25 +104,33 @@ template <class Mapper>
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 <unsigned char>(mapY.s0);
const auto src1 = in.InLine <unsigned char>(mapY.s1);
auto dst = out.OutLine<unsigned char>();
auto mapX = scratch.OutLine<typename Mapper::Unit>();
auto inY = in.y();
auto inH = in.meta().size.height;
auto outY = out.y();
auto length = out.length();
for (int x = 0; x < out.length(); x++)
for (int l = 0; l < out.lpi(); l++)
{
auto alpha0 = mapX[x].alpha0;
auto alpha1 = mapX[x].alpha1;
auto sx0 = mapX[x].s0;
auto sx1 = mapX[x].s1;
auto mapY = Mapper::map(vRatio, inY, inH, outY + l);
int res0 = src0[sx0]*alpha0 + src0[sx1]*alpha1;
int res1 = src1[sx0]*alpha0 + src1[sx1]*alpha1;
const auto src0 = in.InLine <unsigned char>(mapY.s0);
const auto src1 = in.InLine <unsigned char>(mapY.s1);
dst[x] = uchar(( ((mapY.alpha0 * (res0 >> 4)) >> 16) + ((mapY.alpha1 * (res1 >> 4)) >> 16) + 2)>>2);
auto dst = out.OutLine<unsigned char>(l);
for (int x = 0; x < 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
@@ -191,7 +202,7 @@ struct Mapper
} // namespace areaUpscale
} // anonymous namespace
GAPI_FLUID_KERNEL(FResizeLinear, cv::gapi::core::GResize, true)
GAPI_FLUID_KERNEL(FResizeLinear1Lpi, cv::gapi::core::GResize, true)
{
static const int Window = 1;
static const auto Kind = GFluidKernel::Kind::Resize;
@@ -226,7 +237,7 @@ auto endInCoord = [](int outCoord, double ratio) {
};
} // namespace
GAPI_FLUID_KERNEL(FResizeArea, cv::gapi::core::GResize, false)
GAPI_FLUID_KERNEL(FResizeArea1Lpi, cv::gapi::core::GResize, false)
{
static const int Window = 1;
static const auto Kind = GFluidKernel::Kind::Resize;
@@ -235,57 +246,62 @@ GAPI_FLUID_KERNEL(FResizeArea, cv::gapi::core::GResize, false)
cv::gapi::fluid::Buffer& out)
{
auto y = out.y();
auto firstOutLineIdx = out.y();
auto firstViewLineIdx = in.y();
auto length = out.length();
double vRatio = (double)in.meta().size.height / out.meta().size.height;
double hRatio = (double)in.length() / length;
int startY = startInCoord(y, vRatio);
int endY = endInCoord (y, vRatio);
auto dst = out.OutLine<unsigned char>();
double hRatio = (double)in.length() / out.length();
for (int x = 0; x < out.length(); x++)
for (int l = 0; l < out.lpi(); l++)
{
float res = 0.0;
int outY = firstOutLineIdx + l;
int startY = startInCoord(outY, vRatio);
int endY = endInCoord (outY, vRatio);
int startX = startInCoord(x, hRatio);
int endX = endInCoord (x, hRatio);
auto dst = out.OutLine<unsigned char>(l);
for (int inY = startY; inY < endY; inY++)
for (int x = 0; x < length; x++)
{
double startCoordY = inY / vRatio;
double endCoordY = startCoordY + 1/vRatio;
float res = 0.0;
if (startCoordY < y) startCoordY = y;
if (endCoordY > y + 1) endCoordY = y + 1;
int startX = startInCoord(x, hRatio);
int endX = endInCoord (x, hRatio);
float fracY = static_cast<float>((inY == startY || inY == endY - 1) ? endCoordY - startCoordY : 1/vRatio);
const auto src = in.InLine <unsigned char>(inY - startY);
float rowSum = 0.0f;
for (int inX = startX; inX < endX; inX++)
for (int inY = startY; inY < endY; inY++)
{
double startCoordX = inX / hRatio;
double endCoordX = startCoordX + 1/hRatio;
double startCoordY = inY / vRatio;
double endCoordY = startCoordY + 1/vRatio;
if (startCoordX < x) startCoordX = x;
if (endCoordX > x + 1) endCoordX = x + 1;
if (startCoordY < outY) startCoordY = outY;
if (endCoordY > outY + 1) endCoordY = outY + 1;
float fracX = static_cast<float>((inX == startX || inX == endX - 1) ? endCoordX - startCoordX : 1/hRatio);
float fracY = static_cast<float>((inY == startY || inY == endY - 1) ? endCoordY - startCoordY : 1/vRatio);
rowSum += src[inX] * fracX;
const auto src = in.InLine <unsigned char>(inY - firstViewLineIdx);
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<float>((inX == startX || inX == endX - 1) ? endCoordX - startCoordX : 1/hRatio);
rowSum += src[inX] * fracX;
}
res += rowSum * fracY;
}
res += rowSum * fracY;
dst[x] = static_cast<unsigned char>(std::rint(res));
}
dst[x] = static_cast<unsigned char>(std::rint(res));
}
}
};
GAPI_FLUID_KERNEL(FResizeAreaUpscale, cv::gapi::core::GResize, true)
GAPI_FLUID_KERNEL(FResizeAreaUpscale1Lpi, cv::gapi::core::GResize, true)
{
static const int Window = 1;
static const auto Kind = GFluidKernel::Kind::Resize;
@@ -307,31 +323,80 @@ GAPI_FLUID_KERNEL(FResizeAreaUpscale, cv::gapi::core::GResize, true)
}
};
static auto fluidResizeTestPackage = [](int interpolation, cv::Size szIn, cv::Size szOut)
#define ADD_RESIZE_KERNEL_WITH_LPI(interp, lpi, scratch) \
struct Resize##interp##lpi##LpiHelper : public FResize##interp##1Lpi { static const int LPI = lpi; }; \
struct FResize##interp##lpi##Lpi : public cv::GFluidKernelImpl<Resize##interp##lpi##LpiHelper, cv::gapi::core::GResize, scratch>{};
ADD_RESIZE_KERNEL_WITH_LPI(NN, 2, false)
ADD_RESIZE_KERNEL_WITH_LPI(NN, 3, false)
ADD_RESIZE_KERNEL_WITH_LPI(NN, 4, false)
ADD_RESIZE_KERNEL_WITH_LPI(Linear, 2, true)
ADD_RESIZE_KERNEL_WITH_LPI(Linear, 3, true)
ADD_RESIZE_KERNEL_WITH_LPI(Linear, 4, true)
ADD_RESIZE_KERNEL_WITH_LPI(Area, 2, false)
ADD_RESIZE_KERNEL_WITH_LPI(Area, 3, false)
ADD_RESIZE_KERNEL_WITH_LPI(Area, 4, false)
ADD_RESIZE_KERNEL_WITH_LPI(AreaUpscale, 2, true)
ADD_RESIZE_KERNEL_WITH_LPI(AreaUpscale, 3, true)
ADD_RESIZE_KERNEL_WITH_LPI(AreaUpscale, 4, true)
#undef ADD_RESIZE_KERNEL_WITH_LPI
static auto fluidResizeTestPackage = [](int interpolation, cv::Size szIn, cv::Size szOut, int lpi = 1)
{
using namespace cv;
using namespace cv::gapi;
bool upscale = szIn.width < szOut.width || szIn.height < szOut.height;
cv::gapi::GKernelPackage pkg;
#define RESIZE_CASE(interp, lpi) \
case lpi: pkg = kernels<FCopy, FResize##interp##lpi##Lpi>(); break;
#define RESIZE_SWITCH(interp) \
switch(lpi) \
{ \
RESIZE_CASE(interp, 1) \
RESIZE_CASE(interp, 2) \
RESIZE_CASE(interp, 3) \
RESIZE_CASE(interp, 4) \
default: CV_Assert(false); \
}
GKernelPackage pkg;
switch (interpolation)
{
case cv::INTER_NEAREST: pkg = cv::gapi::kernels<FCopy, FResizeNN >(); break;
case cv::INTER_LINEAR: pkg = cv::gapi::kernels<FCopy, FResizeLinear>(); break;
case cv::INTER_AREA: pkg = upscale ? cv::gapi::kernels<FCopy, FResizeAreaUpscale>()
: cv::gapi::kernels<FCopy, FResizeArea>(); break;
case INTER_NEAREST: RESIZE_SWITCH(NN); break;
case INTER_LINEAR: RESIZE_SWITCH(Linear); break;
case INTER_AREA:
{
if (upscale)
{
RESIZE_SWITCH(AreaUpscale)
}
else
{
RESIZE_SWITCH(Area);
}
}break;
default: CV_Assert(false);
}
return cv::gapi::combine(pkg, fluidTestPackage, cv::unite_policy::KEEP);
return combine(pkg, fluidTestPackage, unite_policy::KEEP);
#undef RESIZE_SWITCH
#undef RESIZE_CASE
};
struct ResizeTestFluid : public TestWithParam<std::tuple<int, int, cv::Size, std::tuple<cv::Size, cv::Rect>, double>> {};
struct ResizeTestFluid : public TestWithParam<std::tuple<int, int, cv::Size, std::tuple<cv::Size, cv::Rect>, int, double>> {};
TEST_P(ResizeTestFluid, SanityTest)
{
int type = 0, interp = 0;
cv::Size sz_in, sz_out;
int lpi = 0;
double tolerance = 0.0;
cv::Rect outRoi;
std::tuple<cv::Size, cv::Rect> outSizeAndRoi;
std::tie(type, interp, sz_in, outSizeAndRoi, tolerance) = GetParam();
std::tie(type, interp, sz_in, outSizeAndRoi, lpi, 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;
@@ -351,7 +416,7 @@ TEST_P(ResizeTestFluid, SanityTest)
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)));
c.apply(in_mat1, out_mat, cv::compile_args(GFluidOutputRois{{outRoi}}, fluidResizeTestPackage(interp, sz_in, sz_out, lpi)));
cv::Mat mid_mat;
cv::blur(in_mat1, mid_mat, {3,3}, {-1,-1}, cv::BORDER_REPLICATE);
@@ -383,6 +448,7 @@ INSTANTIATE_TEST_CASE_P(ResizeTestCPU, ResizeTestFluid,
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(1, 2, 3, 4), // lpi
Values(0.0)));
INSTANTIATE_TEST_CASE_P(ResizeAreaTestCPU, ResizeTestFluid,
@@ -406,6 +472,7 @@ INSTANTIATE_TEST_CASE_P(ResizeAreaTestCPU, ResizeTestFluid,
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(1, 2, 3, 4), // lpi
// Actually this tolerance only for cases where OpenCV
// uses ResizeAreaFast
Values(1.0)));
@@ -441,6 +508,7 @@ INSTANTIATE_TEST_CASE_P(ResizeUpscaleTestCPU, ResizeTestFluid,
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(1, 2, 3, 4), // lpi
Values(0.0)));
INSTANTIATE_TEST_CASE_P(ResizeUpscaleOneDimDownscaleAnother, ResizeTestFluid,
@@ -473,6 +541,7 @@ INSTANTIATE_TEST_CASE_P(ResizeUpscaleOneDimDownscaleAnother, ResizeTestFluid,
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(1, 2, 3, 4), // lpi
Values(0.0)));
INSTANTIATE_TEST_CASE_P(Resize400_384TestCPU, ResizeTestFluid,
@@ -480,6 +549,7 @@ INSTANTIATE_TEST_CASE_P(Resize400_384TestCPU, ResizeTestFluid,
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(1, 2, 3, 4), // lpi
Values(0.0)));
INSTANTIATE_TEST_CASE_P(Resize220_400TestCPU, ResizeTestFluid,
@@ -487,6 +557,7 @@ INSTANTIATE_TEST_CASE_P(Resize220_400TestCPU, ResizeTestFluid,
Values(cv::INTER_LINEAR),
Values(cv::Size(220, 220)),
Values(std::make_tuple(cv::Size(400, 400), cv::Rect{})),
Values(1, 2, 3, 4), // lpi
Values(0.0)));
static auto cvBlur = [](const cv::Mat& in, cv::Mat& out, int kernelSize)
+1 -1
View File
@@ -79,7 +79,7 @@ PFMDecoder::~PFMDecoder()
{
}
PFMDecoder::PFMDecoder()
PFMDecoder::PFMDecoder() : m_scale_factor(0), m_swap_byte_order(false)
{
m_strm.close();
}
+2 -2
View File
@@ -93,7 +93,7 @@ static bool ipp_Canny(const Mat& src , const Mat& dx_, const Mat& dy_, Mat& dst,
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterCannyDeriv, iwSrcDx, iwSrcDy, iwDst, low, high, ::ipp::IwiFilterCannyDerivParams(norm));
}
catch (::ipp::IwException ex)
catch (const ::ipp::IwException &)
{
return false;
}
@@ -119,7 +119,7 @@ static bool ipp_Canny(const Mat& src , const Mat& dx_, const Mat& dy_, Mat& dst,
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterCanny, iwSrc, iwDst, low, high, ::ipp::IwiFilterCannyParams(ippFilterSobel, kernel, norm), ippBorderRepl);
}
catch (::ipp::IwException)
catch (const ::ipp::IwException &)
{
return false;
}
+1
View File
@@ -1532,6 +1532,7 @@ icvFindContoursInInterval( const CvArr* src,
tmp_prev->link = 0;
// First line. None of runs is binded
tmp.pt.x = 0;
tmp.pt.y = 0;
CV_WRITE_SEQ_ELEM( tmp, writer );
upper_line = (CvLinkedRunPoint*)CV_GET_WRITTEN_ELEM( writer );
+2 -2
View File
@@ -337,7 +337,7 @@ static bool ipp_Deriv(InputArray _src, OutputArray _dst, int dx, int dy, int ksi
if(useScale)
CV_INSTRUMENT_FUN_IPP(::ipp::iwiScale, iwDstProc, iwDst, scale, delta, ::ipp::IwiScaleParams(ippAlgHintFast));
}
catch (::ipp::IwException)
catch (const ::ipp::IwException &)
{
return false;
}
@@ -765,7 +765,7 @@ static bool ipp_Laplacian(InputArray _src, OutputArray _dst, int ksize, double s
CV_INSTRUMENT_FUN_IPP(::ipp::iwiScale, iwDstProc, iwDst, scale, delta);
}
catch (::ipp::IwException ex)
catch (const ::ipp::IwException &)
{
return false;
}
+28 -47
View File
@@ -295,11 +295,19 @@ static bool between( Point2f a, Point2f b, Point2f c )
((a.y >= c.y) && (c.y >= b.y));
}
static char parallelInt( Point2f a, Point2f b, Point2f c, Point2f d, Point2f& p, Point2f& q )
enum LineSegmentIntersection
{
char code = 'e';
LS_NO_INTERSECTION = 0,
LS_SINGLE_INTERSECTION = 1,
LS_OVERLAP = 2,
LS_ENDPOINT_INTERSECTION = 3
};
static LineSegmentIntersection parallelInt( Point2f a, Point2f b, Point2f c, Point2f d, Point2f& p, Point2f& q )
{
LineSegmentIntersection code = LS_OVERLAP;
if( areaSign(a, b, c) != 0 )
code = '0';
code = LS_NO_INTERSECTION;
else if( between(a, b, c) && between(a, b, d))
p = c, q = d;
else if( between(c, d, a) && between(c, d, b))
@@ -313,60 +321,33 @@ static char parallelInt( Point2f a, Point2f b, Point2f c, Point2f d, Point2f& p,
else if( between(a, b, d) && between(c, d, a))
p = d, q = a;
else
code = '0';
code = LS_NO_INTERSECTION;
return code;
}
//---------------------------------------------------------------------
// segSegInt: Finds the point of intersection p between two closed
// segments ab and cd. Returns p and a char with the following meaning:
// 'e': The segments collinearly overlap, sharing a point.
// 'v': An endpoint (vertex) of one segment is on the other segment,
// but 'e' doesn't hold.
// '1': The segments intersect properly (i.e., they share a point and
// neither 'v' nor 'e' holds).
// '0': The segments do not intersect (i.e., they share no points).
// Note that two collinear segments that share just one point, an endpoint
// of each, returns 'e' rather than 'v' as one might expect.
//---------------------------------------------------------------------
static char segSegInt( Point2f a, Point2f b, Point2f c, Point2f d, Point2f& p, Point2f& q )
// Finds intersection of two line segments: (a, b) and (c, d).
static LineSegmentIntersection intersectLineSegments( Point2f a, Point2f b, Point2f c,
Point2f d, Point2f& p, Point2f& q )
{
double s, t; // The two parameters of the parametric eqns.
double num, denom; // Numerator and denoninator of equations.
char code = '?'; // Return char characterizing intersection.
denom = a.x * (double)( d.y - c.y ) +
b.x * (double)( c.y - d.y ) +
d.x * (double)( b.y - a.y ) +
c.x * (double)( a.y - b.y );
double denom = a.x * (double)(d.y - c.y) + b.x * (double)(c.y - d.y) +
d.x * (double)(b.y - a.y) + c.x * (double)(a.y - b.y);
// If denom is zero, then segments are parallel: handle separately.
if (denom == 0.0)
if( denom == 0. )
return parallelInt(a, b, c, d, p, q);
num = a.x * (double)( d.y - c.y ) +
c.x * (double)( a.y - d.y ) +
d.x * (double)( c.y - a.y );
if ( (num == 0.0) || (num == denom) ) code = 'v';
s = num / denom;
double num = a.x * (double)(d.y - c.y) + c.x * (double)(a.y - d.y) + d.x * (double)(c.y - a.y);
double s = num / denom;
num = -( a.x * (double)( c.y - b.y ) +
b.x * (double)( a.y - c.y ) +
c.x * (double)( b.y - a.y ) );
if ( (num == 0.0) || (num == denom) ) code = 'v';
t = num / denom;
if ( (0.0 < s) && (s < 1.0) &&
(0.0 < t) && (t < 1.0) )
code = '1';
else if ( (0.0 > s) || (s > 1.0) ||
(0.0 > t) || (t > 1.0) )
code = '0';
num = a.x * (double)(b.y - c.y) + b.x * (double)(c.y - a.y) + c.x * (double)(a.y - b.y);
double t = num / denom;
p.x = (float)(a.x + s*(b.x - a.x));
p.y = (float)(a.y + s*(b.y - a.y));
q = p;
return code;
return s < 0. || s > 1. || t < 0. || t > 1. ? LS_NO_INTERSECTION :
s == 0. || s == 1. || t == 0. || t == 1. ? LS_ENDPOINT_INTERSECTION : LS_SINGLE_INTERSECTION;
}
static tInFlag inOut( Point2f p, tInFlag inflag, int aHB, int bHA, Point2f*& result )
@@ -424,8 +405,8 @@ static int intersectConvexConvex_( const Point2f* P, int n, const Point2f* Q, in
// If A & B intersect, update inflag.
Point2f p, q;
int code = segSegInt( P[a1], P[a], Q[b1], Q[b], p, q );
if( code == '1' || code == 'v' )
LineSegmentIntersection code = intersectLineSegments( P[a1], P[a], Q[b1], Q[b], p, q );
if( code == LS_SINGLE_INTERSECTION || code == LS_ENDPOINT_INTERSECTION )
{
if( inflag == Unknown && FirstPoint )
{
@@ -440,7 +421,7 @@ static int intersectConvexConvex_( const Point2f* P, int n, const Point2f* Q, in
//-----Advance rules-----
// Special case: A & B overlap and oppositely oriented.
if( code == 'e' && A.ddot(B) < 0 )
if( code == LS_OVERLAP && A.ddot(B) < 0 )
{
addSharedSeg( p, q, result );
return (int)(result - result0);
+1 -1
View File
@@ -1299,7 +1299,7 @@ static bool ippMorph(int op, int src_type, int dst_type,
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterMorphology, iwSrc, iwDst, morphType, iwMask, ::ipp::IwDefault(), iwBorderType);
}
}
catch(::ipp::IwException ex)
catch(const ::ipp::IwException &)
{
return false;
}
+3 -3
View File
@@ -3241,7 +3241,7 @@ public:
::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start);
CV_INSTRUMENT_FUN_IPP(iwiResize, m_src, m_dst, ippBorderRepl, tile);
}
catch(::ipp::IwException)
catch(const ::ipp::IwException &)
{
m_ok = false;
return;
@@ -3291,7 +3291,7 @@ public:
::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start);
CV_INSTRUMENT_FUN_IPP(iwiWarpAffine, m_src, m_dst, tile);
}
catch(::ipp::IwException)
catch(const ::ipp::IwException &)
{
m_ok = false;
return;
@@ -3387,7 +3387,7 @@ static bool ipp_resize(const uchar * src_data, size_t src_step, int src_width, i
if(!ok)
return false;
}
catch(::ipp::IwException)
catch(const ::ipp::IwException &)
{
return false;
}
+5 -5
View File
@@ -1510,7 +1510,7 @@ static bool ipp_boxfilter(Mat &src, Mat &dst, Size ksize, Point anchor, bool nor
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBox, iwSrc, iwDst, iwKSize, ::ipp::IwDefault(), ippBorder);
}
catch (::ipp::IwException)
catch (const ::ipp::IwException &)
{
return false;
}
@@ -4000,7 +4000,7 @@ public:
::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start);
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterGaussian, m_src, m_dst, m_kernelSize, m_sigma, ::ipp::IwDefault(), m_border, tile);
}
catch(::ipp::IwException e)
catch(const ::ipp::IwException &)
{
*m_pOk = false;
return;
@@ -4067,7 +4067,7 @@ static bool ipp_GaussianBlur(InputArray _src, OutputArray _dst, Size ksize,
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterGaussian, iwSrc, iwDst, ksize.width, sigma1, ::ipp::IwDefault(), ippBorder);
}
}
catch (::ipp::IwException ex)
catch (const ::ipp::IwException &)
{
return false;
}
@@ -5873,7 +5873,7 @@ public:
::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, dst.m_size.width, range.end - range.start);
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBilateral, src, dst, radius, valSquareSigma, posSquareSigma, ::ipp::IwDefault(), borderType, tile);
}
catch(::ipp::IwException)
catch(const ::ipp::IwException &)
{
*pOk = false;
return;
@@ -5928,7 +5928,7 @@ static bool ipp_bilateralFilter(Mat &src, Mat &dst, int d, double sigmaColor, do
CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBilateral, iwSrc, iwDst, radius, valSquareSigma, posSquareSigma, ::ipp::IwDefault(), ippBorder);
}
}
catch (::ipp::IwException)
catch (const ::ipp::IwException &)
{
return false;
}
+1
View File
@@ -240,6 +240,7 @@ void HOGDescriptor::computeGradient(InputArray _img, InputOutputArray _grad, Inp
CV_INSTRUMENT_REGION();
Mat img = _img.getMat();
CV_Assert(!img.empty());
CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 );
Size gradsize(img.cols + paddingTL.width + paddingBR.width,
+1 -1
View File
@@ -103,7 +103,7 @@ PERF_TEST_P( match, bestOf2Nearest, TEST_DETECTORS)
Mat R (pairwise_matches.H, Range::all(), Range(0, 2));
// separate transform matrix, use lower error on rotations
SANITY_CHECK(dist, 1., ERROR_ABSOLUTE);
SANITY_CHECK(R, .015, ERROR_ABSOLUTE);
SANITY_CHECK(R, .06, ERROR_ABSOLUTE);
}
PERF_TEST_P( matchVector, bestOf2NearestVectorFeatures, testing::Combine(
-2
View File
@@ -1519,8 +1519,6 @@ static AVFrame * icv_alloc_picture_FFMPEG(int pix_fmt, int width, int height, bo
_opencv_ffmpeg_av_image_fill_arrays(picture, picture_buf,
(AVPixelFormat) pix_fmt, width, height);
}
else {
}
return picture;
}