Merge branch 4.x

pull/23979/head
Alexander Smorkalov 2 years ago
commit cea26341a5
  1. 1
      3rdparty/openjpeg/CMakeLists.txt
  2. 10
      CMakeLists.txt
  3. 46
      cmake/OpenCVFindAVIF.cmake
  4. 10
      cmake/OpenCVFindLibsGrfmt.cmake
  5. 3
      cmake/templates/cvconfig.h.in
  6. 2
      cmake/templates/opencv_run_all_tests_unix.sh.in
  7. 2
      doc/py_tutorials/py_feature2d/py_fast/py_fast.markdown
  8. 76
      doc/tutorials/others/barcode_detect_and_decode.markdown
  9. BIN
      doc/tutorials/others/images/barcode_book.jpg
  10. BIN
      doc/tutorials/others/images/barcode_book_res.jpg
  11. 2
      doc/tutorials/others/introduction_to_svm.markdown
  12. 1
      doc/tutorials/others/table_of_content_other.markdown
  13. 2
      doc/tutorials/others/traincascade.markdown
  14. 4
      modules/3d/src/usac/quality.cpp
  15. 2
      modules/3d/test/test_odometry.cpp
  16. 1
      modules/core/CMakeLists.txt
  17. 8
      modules/core/include/opencv2/core.hpp
  18. 24
      modules/core/perf/opencl/perf_arithm.cpp
  19. 16
      modules/core/perf/perf_stat.cpp
  20. 107
      modules/core/src/has_non_zero.dispatch.cpp
  21. 327
      modules/core/src/has_non_zero.simd.hpp
  22. 201
      modules/core/test/test_hasnonzero.cpp
  23. 6
      modules/dnn/misc/python/test/test_dnn.py
  24. 6
      modules/dnn/src/caffe/caffe_importer.cpp
  25. 11
      modules/dnn/src/dnn_utils.cpp
  26. 21
      modules/dnn/src/layers/cpu_kernels/conv_winograd_f63.cpp
  27. 33
      modules/dnn/src/layers/cpu_kernels/convolution.cpp
  28. 23
      modules/dnn/src/layers/cpu_kernels/convolution.hpp
  29. 34
      modules/dnn/src/onnx/onnx_importer.cpp
  30. 2
      modules/dnn/test/test_onnx_importer.cpp
  31. 5
      modules/gapi/CMakeLists.txt
  32. 128
      modules/gapi/include/opencv2/gapi/infer/bindings_ov.hpp
  33. 143
      modules/gapi/include/opencv2/gapi/infer/ie.hpp
  34. 685
      modules/gapi/include/opencv2/gapi/infer/ov.hpp
  35. 31
      modules/gapi/include/opencv2/gapi/streaming/cap.hpp
  36. 7
      modules/gapi/misc/python/pyopencv_gapi.hpp
  37. 1
      modules/gapi/misc/python/shadow_gapi.hpp
  38. 238
      modules/gapi/misc/python/test/test_gapi_infer_ov.py
  39. 2
      modules/gapi/perf/common/gapi_core_perf_tests.hpp
  40. 7
      modules/gapi/perf/common/gapi_core_perf_tests_inl.hpp
  41. 1
      modules/gapi/perf/cpu/gapi_core_perf_tests_cpu.cpp
  42. 1
      modules/gapi/perf/cpu/gapi_core_perf_tests_fluid.cpp
  43. 1
      modules/gapi/perf/gpu/gapi_core_perf_tests_gpu.cpp
  44. 202
      modules/gapi/samples/semantic_segmentation.cpp
  45. 16
      modules/gapi/src/api/gmat.cpp
  46. 6
      modules/gapi/src/backends/common/gbackend.hpp
  47. 66
      modules/gapi/src/backends/fluid/gfluidcore.cpp
  48. 18
      modules/gapi/src/backends/fluid/gfluidcore_func.dispatch.cpp
  49. 12
      modules/gapi/src/backends/fluid/gfluidcore_func.hpp
  50. 80
      modules/gapi/src/backends/fluid/gfluidcore_func.simd.hpp
  51. 17
      modules/gapi/src/backends/fluid/gfluidutils.hpp
  52. 518
      modules/gapi/src/backends/ie/giebackend.cpp
  53. 168
      modules/gapi/src/backends/ov/bindings_ov.cpp
  54. 1001
      modules/gapi/src/backends/ov/govbackend.cpp
  55. 66
      modules/gapi/src/backends/ov/govbackend.hpp
  56. 35
      modules/gapi/src/backends/ov/util.hpp
  57. 76
      modules/gapi/test/infer/gapi_infer_ie_test.cpp
  58. 540
      modules/gapi/test/infer/gapi_infer_ov_tests.cpp
  59. 5
      modules/imgcodecs/CMakeLists.txt
  60. 6
      modules/imgcodecs/include/opencv2/imgcodecs.hpp
  61. 369
      modules/imgcodecs/src/grfmt_avif.cpp
  62. 62
      modules/imgcodecs/src/grfmt_avif.hpp
  63. 1
      modules/imgcodecs/src/grfmts.hpp
  64. 4
      modules/imgcodecs/src/loadsave.cpp
  65. 355
      modules/imgcodecs/test/test_avif.cpp
  66. 3
      modules/imgproc/src/color_hsv.simd.hpp
  67. 101
      modules/imgproc/src/intelligent_scissors.cpp
  68. 135
      modules/imgproc/test/test_intelligent_scissors.cpp
  69. 39
      modules/js/generator/embindgen.py
  70. 11
      modules/js/src/core_bindings.cpp
  71. 100
      modules/js/test/test_objdetect.js
  72. 29
      modules/objdetect/doc/objdetect.bib
  73. 124
      modules/objdetect/include/opencv2/objdetect.hpp
  74. 6
      modules/objdetect/include/opencv2/objdetect/aruco_board.hpp
  75. 4
      modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp
  76. 3
      modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp
  77. 65
      modules/objdetect/include/opencv2/objdetect/barcode.hpp
  78. 81
      modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp
  79. 50
      modules/objdetect/misc/java/test/BarcodeDetectorTest.java
  80. 7
      modules/objdetect/misc/objc/gen_dict.json
  81. 33
      modules/objdetect/misc/python/test/test_barcode_detector.py
  82. 6
      modules/objdetect/perf/perf_aruco.cpp
  83. 114
      modules/objdetect/perf/perf_barcode.cpp
  84. 84
      modules/objdetect/perf/perf_qrcode_pipeline.cpp
  85. 12
      modules/objdetect/src/aruco/aruco_detector.cpp
  86. 4
      modules/objdetect/src/aruco/aruco_dictionary.cpp
  87. 2
      modules/objdetect/src/aruco/predefined_dictionaries.hpp
  88. 374
      modules/objdetect/src/barcode.cpp
  89. 118
      modules/objdetect/src/barcode_decoder/abs_decoder.cpp
  90. 99
      modules/objdetect/src/barcode_decoder/abs_decoder.hpp
  91. 195
      modules/objdetect/src/barcode_decoder/common/hybrid_binarizer.cpp
  92. 22
      modules/objdetect/src/barcode_decoder/common/hybrid_binarizer.hpp
  93. 77
      modules/objdetect/src/barcode_decoder/common/super_scale.cpp
  94. 69
      modules/objdetect/src/barcode_decoder/common/super_scale.hpp
  95. 36
      modules/objdetect/src/barcode_decoder/common/utils.cpp
  96. 26
      modules/objdetect/src/barcode_decoder/common/utils.hpp
  97. 92
      modules/objdetect/src/barcode_decoder/ean13_decoder.cpp
  98. 31
      modules/objdetect/src/barcode_decoder/ean13_decoder.hpp
  99. 79
      modules/objdetect/src/barcode_decoder/ean8_decoder.cpp
  100. 32
      modules/objdetect/src/barcode_decoder/ean8_decoder.hpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -15,6 +15,7 @@ ocv_warnings_disable(CMAKE_C_FLAGS
-Wimplicit-const-int-float-conversion # clang
-Wunused-but-set-variable # clang15
-Wmissing-prototypes # clang, function opj_t1_ht_decode_cblk
-Wmissing-declarations # gcc, function opj_t1_ht_decode_cblk
)
#-----------------------------------------------------------------------------

@ -241,6 +241,8 @@ OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON
OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O (iOS/Mac)" ON
VISIBLE_IF APPLE
VERIFY HAVE_AVFOUNDATION)
OCV_OPTION(WITH_AVIF "Enable AVIF support" OFF
VERIFY HAVE_AVIF)
OCV_OPTION(WITH_CAP_IOS "Enable iOS video capture" ON
VISIBLE_IF IOS
VERIFY HAVE_CAP_IOS)
@ -1390,6 +1392,14 @@ if(WITH_WEBP OR HAVE_WEBP)
status(" WEBP:" WEBP_FOUND THEN "${WEBP_LIBRARY} (ver ${WEBP_VERSION})" ELSE "build (ver ${WEBP_VERSION})")
endif()
if(WITH_AVIF OR HAVE_AVIF)
if(AVIF_VERSION)
status(" AVIF:" AVIF_FOUND THEN "${AVIF_LIBRARY} (ver ${AVIF_VERSION})" ELSE "NO")
else()
status(" AVIF:" AVIF_FOUND THEN "${AVIF_LIBRARY}" ELSE "NO")
endif()
endif()
if(WITH_PNG OR HAVE_PNG OR WITH_SPNG)
if(WITH_SPNG)
status(" PNG:" "build-${SPNG_LIBRARY} (ver ${SPNG_VERSION})")

@ -0,0 +1,46 @@
#=============================================================================
# Find AVIF library
#=============================================================================
# Find the native AVIF headers and libraries.
#
# AVIF_INCLUDE_DIRS - where to find avif/avif.h, etc.
# AVIF_LIBRARIES - List of libraries when using AVIF.
# AVIF_FOUND - True if AVIF is found.
#=============================================================================
# Look for the header file.
unset(AVIF_FOUND)
find_package(libavif QUIET)
if(TARGET avif)
MARK_AS_ADVANCED(AVIF_INCLUDE_DIR)
MARK_AS_ADVANCED(AVIF_LIBRARY)
SET(AVIF_FOUND TRUE)
GET_TARGET_PROPERTY(AVIF_LIBRARY avif LOCATION)
GET_TARGET_PROPERTY(AVIF_INCLUDE_DIR1 avif INCLUDE_DIRECTORIES)
GET_TARGET_PROPERTY(AVIF_INCLUDE_DIR2 avif INTERFACE_INCLUDE_DIRECTORIES)
set(AVIF_INCLUDE_DIR)
if(AVIF_INCLUDE_DIR1)
LIST(APPEND AVIF_INCLUDE_DIR ${AVIF_INCLUDE_DIR1})
endif()
if(AVIF_INCLUDE_DIR2)
LIST(APPEND AVIF_INCLUDE_DIR ${AVIF_INCLUDE_DIR2})
endif()
else()
FIND_PATH(AVIF_INCLUDE_DIR NAMES avif/avif.h)
# Look for the library.
FIND_LIBRARY(AVIF_LIBRARY NAMES avif)
MARK_AS_ADVANCED(AVIF_LIBRARY)
# handle the QUIETLY and REQUIRED arguments and set AVIF_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVIF DEFAULT_MSG AVIF_LIBRARY AVIF_INCLUDE_DIR)
SET(AVIF_LIBRARIES ${AVIF_LIBRARY})
SET(AVIF_INCLUDE_DIRS ${AVIF_INCLUDE_DIR})
endif()

@ -37,6 +37,16 @@ if(NOT ZLIB_FOUND)
ocv_parse_header2(ZLIB "${${ZLIB_LIBRARY}_SOURCE_DIR}/zlib.h" ZLIB_VERSION)
endif()
# --- libavif (optional) ---
if(WITH_AVIF)
ocv_clear_internal_cache_vars(AVIF_LIBRARY AVIF_INCLUDE_DIR)
include(cmake/OpenCVFindAVIF.cmake)
if(AVIF_FOUND)
set(HAVE_AVIF 1)
endif()
endif()
# --- libjpeg (optional) ---
if(WITH_JPEG)
if(BUILD_JPEG)

@ -72,6 +72,9 @@
#cmakedefine HAVE_OPENJPEG
#cmakedefine HAVE_JASPER
/* AVIF codec */
#cmakedefine HAVE_AVIF
/* IJG JPEG codec */
#cmakedefine HAVE_JPEG

@ -18,7 +18,7 @@ EOF
# Parse options
COLOR_OUTPUT=0
while getopts “hc” OPTION
while getopts "hc" OPTION
do
case $OPTION in
h)

@ -133,7 +133,7 @@ nonmaxSuppression:
Additional Resources
--------------------
-# Edward Rosten and Tom Drummond, “Machine learning for high speed corner detection” in 9th
-# Edward Rosten and Tom Drummond, "Machine learning for high speed corner detection" in 9th
European Conference on Computer Vision, vol. 1, 2006, pp. 430–443.
2. Edward Rosten, Reid Porter, and Tom Drummond, "Faster and better: a machine learning approach to
corner detection" in IEEE Trans. Pattern Analysis and Machine Intelligence, 2010, vol 32, pp.

@ -0,0 +1,76 @@
Barcode Recognition {#tutorial_barcode_detect_and_decode}
===================
@tableofcontents
@prev_tutorial{tutorial_traincascade}
@next_tutorial{tutorial_introduction_to_svm}
| | |
| -: | :- |
| Compatibility | OpenCV >= 4.8 |
Goal
----
In this chapter we will familiarize with the barcode detection and decoding methods available in OpenCV.
Basics
----
Barcode is major technique to identify commodity in real life. A common barcode is a pattern of parallel lines arranged by black bars and white bars with vastly different reflectivity. Barcode recognition is to scan the barcode in the horizontal direction to get a string of binary codes composed of bars of different widths and colors, that is, the code information of the barcode. The content of barcode can be decoded by matching with various barcode encoding methods. Currently, we support EAN-8, EAN-13, UPC-A and UPC-E standards.
See https://en.wikipedia.org/wiki/Universal_Product_Code and https://en.wikipedia.org/wiki/International_Article_Number
Related papers: @cite Xiangmin2015research , @cite kass1987analyzing , @cite bazen2002systematic
Code example
------------
### Main class
Several algorithms were introduced for barcode recognition.
While coding, we firstly need to create a cv::barcode::BarcodeDetector object. It has mainly three member functions, which will be introduced in the following.
#### Initialization
Optionally user can construct barcode detector with super resolution model which should be downloaded from https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode (`sr.caffemodel`, `sr.prototxt`).
@snippet cpp/barcode.cpp initialize
We need to create variables to store the outputs.
@snippet cpp/barcode.cpp output
#### Detecting
cv::barcode::BarcodeDetector::detect method uses an algorithm based on directional coherence. First, we compute the average squared gradients of every pixel, @cite bazen2002systematic . Then we divide an image into square patches and compute the **gradient orientation coherence** and **mean gradient direction** of each patch. Then, we connect all patches that have **high gradient orientation coherence** and **similar gradient direction**. At this stage we use multiscale patches to capture the gradient distribution of multi-size barcodes, and apply non-maximum suppression to filter duplicate proposals. At last, we use cv::minAreaRect to bound the ROI, and output the corners of the rectangles.
Detect codes in the input image, and output the corners of detected rectangles:
@snippet cpp/barcode.cpp detect
#### Decoding
cv::barcode::BarcodeDetector::decode method first super-scales the image (_optionally_) if it is smaller than threshold, sharpens the image and then binaries it by OTSU or local binarization. Then it reads the contents of the barcode by matching the similarity of the specified barcode pattern.
#### Detecting and decoding
cv::barcode::BarcodeDetector::detectAndDecode combines `detect` and `decode` in a single call. A simple example below shows how to use this function:
@snippet cpp/barcode.cpp detectAndDecode
Visualize the results:
@snippet cpp/barcode.cpp visualize
Results
-------
Original image:
![image](images/barcode_book.jpg)
After detection:
![image](images/barcode_book_res.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@ -3,7 +3,7 @@ Introduction to Support Vector Machines {#tutorial_introduction_to_svm}
@tableofcontents
@prev_tutorial{tutorial_traincascade}
@prev_tutorial{tutorial_barcode_detect_and_decode}
@next_tutorial{tutorial_non_linear_svms}
| | |

@ -8,6 +8,7 @@ Other tutorials (ml, objdetect, photo, stitching, video) {#tutorial_table_of_con
- video. @subpage tutorial_optical_flow
- objdetect. @subpage tutorial_cascade_classifier
- objdetect. @subpage tutorial_traincascade
- objdetect. @subpage tutorial_barcode_detect_and_decode
- ml. @subpage tutorial_introduction_to_svm
- ml. @subpage tutorial_non_linear_svms
- ml. @subpage tutorial_introduction_to_pca

@ -4,7 +4,7 @@ Cascade Classifier Training {#tutorial_traincascade}
@tableofcontents
@prev_tutorial{tutorial_cascade_classifier}
@next_tutorial{tutorial_introduction_to_svm}
@next_tutorial{tutorial_barcode_detect_and_decode}
Introduction
------------

@ -410,9 +410,9 @@ public:
* 1. Check whether j-th data point is consistent with the
* model
* 2. Compute the likelihood ratio λj eq. (1)
* 3. If λj > A, decide the model is bad (model re-jected),
* 3. If λj > A, decide the model is bad (model "re-jected"),
* else increment j or continue testing
* 4. If j = N the number of correspondences decide model accepted
* 4. If j = N the number of correspondences decide model "accepted"
*
* Verifies model and returns model score.

@ -420,7 +420,7 @@ void OdometryTest::prepareFrameCheck()
odf.getPyramidAt(normi, OdometryFramePyramidType::PYR_NORM, i);
ASSERT_FALSE(normi.empty());
double nnorm = cv::norm(normi, gtNormal, NORM_INF, normmaski);
EXPECT_LE(nnorm, 1.8e-7) << "Normals diff is too big at pyr level " << i;
EXPECT_LE(nnorm, 3.3e-7) << "Normals diff is too big at pyr level " << i;
if (i == 0)
{

@ -6,6 +6,7 @@ ocv_add_dispatched_file(arithm SSE2 SSE4_1 AVX2 VSX3)
ocv_add_dispatched_file(convert SSE2 AVX2 VSX3)
ocv_add_dispatched_file(convert_scale SSE2 AVX2)
ocv_add_dispatched_file(count_non_zero SSE2 AVX2)
ocv_add_dispatched_file(has_non_zero SSE2 AVX2)
ocv_add_dispatched_file(matmul SSE2 SSE4_1 AVX2 AVX512_SKX NEON_DOTPROD)
ocv_add_dispatched_file(mean SSE2 AVX2)
ocv_add_dispatched_file(merge SSE2 AVX2)

@ -572,6 +572,14 @@ independently for each channel.
*/
CV_EXPORTS_AS(sumElems) Scalar sum(InputArray src);
/** @brief Checks for the presence of at least one non-zero array element.
The function returns whether there are non-zero elements in src
@param src single-channel array.
@sa mean, meanStdDev, norm, minMaxLoc, calcCovarMatrix
*/
CV_EXPORTS_W bool hasNonZero( InputArray src );
/** @brief Counts non-zero array elements.
The function returns the number of non-zero elements in src :

@ -460,6 +460,30 @@ OCL_PERF_TEST_P(CountNonZeroFixture, CountNonZero,
SANITY_CHECK(result);
}
///////////// countNonZero ////////////////////////
typedef Size_MatType HasNonZeroFixture;
OCL_PERF_TEST_P(HasNonZeroFixture, HasNonZero,
::testing::Combine(OCL_TEST_SIZES,
OCL_PERF_ENUM(CV_8UC1, CV_32FC1)))
{
const Size_MatType_t params = GetParam();
const Size srcSize = get<0>(params);
const int type = get<1>(params);
checkDeviceMaxMemoryAllocSize(srcSize, type);
UMat src(srcSize, type);
/*bool result = false;*/
randu(src, 0, 10);
declare.in(src);
OCL_TEST_CYCLE() /*result =*/ cv::hasNonZero(src);
SANITY_CHECK_NOTHING();
}
///////////// Phase ////////////////////////
typedef Size_MatType PhaseFixture;

@ -101,4 +101,20 @@ PERF_TEST_P(Size_MatType, countNonZero, testing::Combine( testing::Values( TYPIC
SANITY_CHECK(cnt);
}
PERF_TEST_P(Size_MatType, hasNonZero, testing::Combine( testing::Values( TYPICAL_MAT_SIZES ), testing::Values( CV_8UC1, CV_8SC1, CV_16UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_64FC1 ) ))
{
Size sz = get<0>(GetParam());
int matType = get<1>(GetParam());
Mat src(sz, matType);
/*bool hnz = false;*/
declare.in(src, WARMUP_RNG);
int runs = (sz.width <= 640) ? 8 : 1;
TEST_CYCLE_MULTIRUN(runs) /*hnz =*/ hasNonZero(src);
SANITY_CHECK_NOTHING();
}
} // namespace

@ -0,0 +1,107 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "opencl_kernels_core.hpp"
#include "stat.hpp"
#include "has_non_zero.simd.hpp"
#include "has_non_zero.simd_declarations.hpp" // defines CV_CPU_DISPATCH_MODES_ALL=AVX2,...,BASELINE based on CMakeLists.txt content
namespace cv {
static HasNonZeroFunc getHasNonZeroTab(int depth)
{
CV_INSTRUMENT_REGION();
CV_CPU_DISPATCH(getHasNonZeroTab, (depth),
CV_CPU_DISPATCH_MODES_ALL);
}
#ifdef HAVE_OPENCL
static bool ocl_hasNonZero( InputArray _src, bool & res )
{
int type = _src.type(), depth = CV_MAT_DEPTH(type), kercn = ocl::predictOptimalVectorWidth(_src);
bool doubleSupport = ocl::Device::getDefault().doubleFPConfig() > 0;
if (depth == CV_64F && !doubleSupport)
return false;
int dbsize = ocl::Device::getDefault().maxComputeUnits();
size_t wgs = ocl::Device::getDefault().maxWorkGroupSize();
int wgs2_aligned = 1;
while (wgs2_aligned < (int)wgs)
wgs2_aligned <<= 1;
wgs2_aligned >>= 1;
ocl::Kernel k("reduce", ocl::core::reduce_oclsrc,
format("-D srcT=%s -D srcT1=%s -D cn=1 -D OP_COUNT_NON_ZERO"
" -D WGS=%d -D kercn=%d -D WGS2_ALIGNED=%d%s%s",
ocl::typeToStr(CV_MAKE_TYPE(depth, kercn)),
ocl::typeToStr(depth), (int)wgs, kercn,
wgs2_aligned, doubleSupport ? " -D DOUBLE_SUPPORT" : "",
_src.isContinuous() ? " -D HAVE_SRC_CONT" : ""));
if (k.empty())
return false;
UMat src = _src.getUMat(), db(1, dbsize, CV_32SC1);
k.args(ocl::KernelArg::ReadOnlyNoSize(src), src.cols, (int)src.total(),
dbsize, ocl::KernelArg::PtrWriteOnly(db));
size_t globalsize = dbsize * wgs;
if (k.run(1, &globalsize, &wgs, true))
return res = (saturate_cast<int>(cv::sum(db.getMat(ACCESS_READ))[0])>0), true;
return false;
}
#endif
bool hasNonZero(InputArray _src)
{
CV_INSTRUMENT_REGION();
int type = _src.type(), cn = CV_MAT_CN(type);
CV_Assert( cn == 1 );
bool res = false;
#ifdef HAVE_OPENCL
CV_OCL_RUN_(OCL_PERFORMANCE_CHECK(_src.isUMat()) && _src.dims() <= 2,
ocl_hasNonZero(_src, res),
res)
#endif
Mat src = _src.getMat();
HasNonZeroFunc func = getHasNonZeroTab(src.depth());
CV_Assert( func != 0 );
if (src.dims == 2)//fast path to avoid creating planes of single rows
{
if (src.isContinuous())
res |= func(src.ptr<uchar>(0), src.total());
else
for(int row = 0, rowsCount = src.rows ; !res && (row<rowsCount) ; ++row)
res |= func(src.ptr<uchar>(row), src.cols);
}
else//if (src.dims != 2)
{
const Mat* arrays[] = {&src, nullptr};
Mat planes[1];
NAryMatIterator itNAry(arrays, planes, 1);
for(size_t p = 0 ; !res && (p<itNAry.nplanes) ; ++p, ++itNAry)
{
const Mat& plane = itNAry.planes[0];
if (plane.isContinuous())
res |= func(plane.ptr<uchar>(0), plane.total());
else
for(int row = 0, rowsCount = plane.rows ; !res && (row<rowsCount) ; ++row)
res |= func(plane.ptr<uchar>(row), plane.cols);
}
}
return res;
}
} // namespace

@ -0,0 +1,327 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
namespace cv {
typedef bool (*HasNonZeroFunc)(const uchar*, size_t);
CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN
HasNonZeroFunc getHasNonZeroTab(int depth);
#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
template<typename T>
inline bool hasNonZero_(const T* src, size_t len )
{
bool res = false;
if (len > 0)
{
size_t i=0;
#if CV_ENABLE_UNROLLED
for(; !res && (i+4 <= len); i += 4 )
res |= ((src[i] | src[i+1] | src[i+2] | src[i+3]) != 0);
#endif
for( ; !res && (i < len); i++ )
res |= (src[i] != 0);
}
return res;
}
template<>
inline bool hasNonZero_(const float* src, size_t len )
{
bool res = false;
if (len > 0)
{
size_t i=0;
if (sizeof(float) == sizeof(unsigned int))
{
#if CV_ENABLE_UNROLLED
typedef unsigned int float_as_uint_t;
const float_as_uint_t* src_as_ui = reinterpret_cast<const float_as_uint_t*>(src);
for(; !res && (i+4 <= len); i += 4 )
{
const float_as_uint_t gathered = (src_as_ui[i] | src_as_ui[i+1] | src_as_ui[i+2] | src_as_ui[i+3]);
res |= ((gathered<<1) != 0);//remove what would be the sign bit
}
#endif
}
for( ; !res && (i < len); i++ )
res |= (src[i] != 0);
}
return res;
}
template<>
inline bool hasNonZero_(const double* src, size_t len )
{
bool res = false;
if (len > 0)
{
size_t i=0;
if (sizeof(double) == sizeof(uint64_t))
{
#if CV_ENABLE_UNROLLED
typedef uint64_t double_as_uint_t;
const double_as_uint_t* src_as_ui = reinterpret_cast<const double_as_uint_t*>(src);
for(; !res && (i+4 <= len); i += 4 )
{
const double_as_uint_t gathered = (src_as_ui[i] | src_as_ui[i+1] | src_as_ui[i+2] | src_as_ui[i+3]);
res |= ((gathered<<1) != 0);//remove what would be the sign bit
}
#endif
}
for( ; !res && (i < len); i++ )
res |= (src[i] != 0);
}
return res;
}
static bool hasNonZero8u( const uchar* src, size_t len )
{
bool res = false;
const uchar* srcEnd = src+len;
#if CV_SIMD
typedef v_uint8 v_type;
const v_type v_zero = vx_setzero_u8();
constexpr const int unrollCount = 2;
int step = v_type::nlanes * unrollCount;
int len0 = len & -step;
const uchar* srcSimdEnd = src+len0;
int countSIMD = static_cast<int>((srcSimdEnd-src)/step);
while(!res && countSIMD--)
{
v_type v0 = vx_load(src);
src += v_type::nlanes;
v_type v1 = vx_load(src);
src += v_type::nlanes;
res = v_check_any(((v0 | v1) != v_zero));
}
v_cleanup();
#endif
return res || hasNonZero_(src, srcEnd-src);
}
static bool hasNonZero16u( const ushort* src, size_t len )
{
bool res = false;
const ushort* srcEnd = src+len;
#if CV_SIMD
typedef v_uint16 v_type;
const v_type v_zero = vx_setzero_u16();
constexpr const int unrollCount = 4;
int step = v_type::nlanes * unrollCount;
int len0 = len & -step;
const ushort* srcSimdEnd = src+len0;
int countSIMD = static_cast<int>((srcSimdEnd-src)/step);
while(!res && countSIMD--)
{
v_type v0 = vx_load(src);
src += v_type::nlanes;
v_type v1 = vx_load(src);
src += v_type::nlanes;
v_type v2 = vx_load(src);
src += v_type::nlanes;
v_type v3 = vx_load(src);
src += v_type::nlanes;
v0 |= v1;
v2 |= v3;
res = v_check_any(((v0 | v2) != v_zero));
}
v_cleanup();
#endif
return res || hasNonZero_(src, srcEnd-src);
}
static bool hasNonZero32s( const int* src, size_t len )
{
bool res = false;
const int* srcEnd = src+len;
#if CV_SIMD
typedef v_int32 v_type;
const v_type v_zero = vx_setzero_s32();
constexpr const int unrollCount = 8;
int step = v_type::nlanes * unrollCount;
int len0 = len & -step;
const int* srcSimdEnd = src+len0;
int countSIMD = static_cast<int>((srcSimdEnd-src)/step);
while(!res && countSIMD--)
{
v_type v0 = vx_load(src);
src += v_type::nlanes;
v_type v1 = vx_load(src);
src += v_type::nlanes;
v_type v2 = vx_load(src);
src += v_type::nlanes;
v_type v3 = vx_load(src);
src += v_type::nlanes;
v_type v4 = vx_load(src);
src += v_type::nlanes;
v_type v5 = vx_load(src);
src += v_type::nlanes;
v_type v6 = vx_load(src);
src += v_type::nlanes;
v_type v7 = vx_load(src);
src += v_type::nlanes;
v0 |= v1;
v2 |= v3;
v4 |= v5;
v6 |= v7;
v0 |= v2;
v4 |= v6;
res = v_check_any(((v0 | v4) != v_zero));
}
v_cleanup();
#endif
return res || hasNonZero_(src, srcEnd-src);
}
static bool hasNonZero32f( const float* src, size_t len )
{
bool res = false;
const float* srcEnd = src+len;
#if CV_SIMD
typedef v_float32 v_type;
const v_type v_zero = vx_setzero_f32();
constexpr const int unrollCount = 8;
int step = v_type::nlanes * unrollCount;
int len0 = len & -step;
const float* srcSimdEnd = src+len0;
int countSIMD = static_cast<int>((srcSimdEnd-src)/step);
while(!res && countSIMD--)
{
v_type v0 = vx_load(src);
src += v_type::nlanes;
v_type v1 = vx_load(src);
src += v_type::nlanes;
v_type v2 = vx_load(src);
src += v_type::nlanes;
v_type v3 = vx_load(src);
src += v_type::nlanes;
v_type v4 = vx_load(src);
src += v_type::nlanes;
v_type v5 = vx_load(src);
src += v_type::nlanes;
v_type v6 = vx_load(src);
src += v_type::nlanes;
v_type v7 = vx_load(src);
src += v_type::nlanes;
v0 |= v1;
v2 |= v3;
v4 |= v5;
v6 |= v7;
v0 |= v2;
v4 |= v6;
//res = v_check_any(((v0 | v4) != v_zero));//beware : (NaN != 0) returns "false" since != is mapped to _CMP_NEQ_OQ and not _CMP_NEQ_UQ
res = !v_check_all(((v0 | v4) == v_zero));
}
v_cleanup();
#endif
return res || hasNonZero_(src, srcEnd-src);
}
static bool hasNonZero64f( const double* src, size_t len )
{
bool res = false;
const double* srcEnd = src+len;
#if CV_SIMD_64F
typedef v_float64 v_type;
const v_type v_zero = vx_setzero_f64();
constexpr const int unrollCount = 16;
int step = v_type::nlanes * unrollCount;
int len0 = len & -step;
const double* srcSimdEnd = src+len0;
int countSIMD = static_cast<int>((srcSimdEnd-src)/step);
while(!res && countSIMD--)
{
v_type v0 = vx_load(src);
src += v_type::nlanes;
v_type v1 = vx_load(src);
src += v_type::nlanes;
v_type v2 = vx_load(src);
src += v_type::nlanes;
v_type v3 = vx_load(src);
src += v_type::nlanes;
v_type v4 = vx_load(src);
src += v_type::nlanes;
v_type v5 = vx_load(src);
src += v_type::nlanes;
v_type v6 = vx_load(src);
src += v_type::nlanes;
v_type v7 = vx_load(src);
src += v_type::nlanes;
v_type v8 = vx_load(src);
src += v_type::nlanes;
v_type v9 = vx_load(src);
src += v_type::nlanes;
v_type v10 = vx_load(src);
src += v_type::nlanes;
v_type v11 = vx_load(src);
src += v_type::nlanes;
v_type v12 = vx_load(src);
src += v_type::nlanes;
v_type v13 = vx_load(src);
src += v_type::nlanes;
v_type v14 = vx_load(src);
src += v_type::nlanes;
v_type v15 = vx_load(src);
src += v_type::nlanes;
v0 |= v1;
v2 |= v3;
v4 |= v5;
v6 |= v7;
v8 |= v9;
v10 |= v11;
v12 |= v13;
v14 |= v15;
v0 |= v2;
v4 |= v6;
v8 |= v10;
v12 |= v14;
v0 |= v4;
v8 |= v12;
//res = v_check_any(((v0 | v8) != v_zero));//beware : (NaN != 0) returns "false" since != is mapped to _CMP_NEQ_OQ and not _CMP_NEQ_UQ
res = !v_check_all(((v0 | v8) == v_zero));
}
v_cleanup();
#endif
return res || hasNonZero_(src, srcEnd-src);
}
HasNonZeroFunc getHasNonZeroTab(int depth)
{
static HasNonZeroFunc hasNonZeroTab[] =
{
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero8u), (HasNonZeroFunc)GET_OPTIMIZED(hasNonZero8u),
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero16u), (HasNonZeroFunc)GET_OPTIMIZED(hasNonZero16u),
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero32s), (HasNonZeroFunc)GET_OPTIMIZED(hasNonZero32f),
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero64f), 0
};
return hasNonZeroTab[depth];
}
#endif
CV_CPU_OPTIMIZATION_NAMESPACE_END
} // namespace

@ -0,0 +1,201 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "test_precomp.hpp"
namespace opencv_test { namespace {
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroAllZeros;
TEST_P(HasNonZeroAllZeros, hasNonZeroAllZeros)
{
const int type = std::get<0>(GetParam());
const Size size = std::get<1>(GetParam());
Mat m = Mat::zeros(size, type);
EXPECT_FALSE(hasNonZero(m));
}
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroAllZeros,
testing::Combine(
testing::Values(CV_8UC1, CV_8SC1, CV_16UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_64FC1),
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113))
)
);
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroNegZeros;
TEST_P(HasNonZeroNegZeros, hasNonZeroNegZeros)
{
const int type = std::get<0>(GetParam());
const Size size = std::get<1>(GetParam());
Mat m = Mat(size, type);
m.setTo(Scalar::all(-0.));
EXPECT_FALSE(hasNonZero(m));
}
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroNegZeros,
testing::Combine(
testing::Values(CV_32FC1, CV_64FC1),
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113))
)
);
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroLimitValues;
TEST_P(HasNonZeroLimitValues, hasNonZeroLimitValues)
{
const int type = std::get<0>(GetParam());
const Size size = std::get<1>(GetParam());
Mat m = Mat(size, type);
m.setTo(Scalar::all(std::numeric_limits<double>::infinity()));
EXPECT_TRUE(hasNonZero(m));
m.setTo(Scalar::all(-std::numeric_limits<double>::infinity()));
EXPECT_TRUE(hasNonZero(m));
m.setTo(Scalar::all(std::numeric_limits<double>::quiet_NaN()));
EXPECT_TRUE(hasNonZero(m));
m.setTo((CV_MAT_DEPTH(type) == CV_64F) ? Scalar::all(std::numeric_limits<double>::epsilon()) : Scalar::all(std::numeric_limits<float>::epsilon()));
EXPECT_TRUE(hasNonZero(m));
m.setTo((CV_MAT_DEPTH(type) == CV_64F) ? Scalar::all(std::numeric_limits<double>::min()) : Scalar::all(std::numeric_limits<float>::min()));
EXPECT_TRUE(hasNonZero(m));
m.setTo((CV_MAT_DEPTH(type) == CV_64F) ? Scalar::all(std::numeric_limits<double>::denorm_min()) : Scalar::all(std::numeric_limits<float>::denorm_min()));
EXPECT_TRUE(hasNonZero(m));
}
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroLimitValues,
testing::Combine(
testing::Values(CV_32FC1, CV_64FC1),
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113))
)
);
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroRandom;
TEST_P(HasNonZeroRandom, hasNonZeroRandom)
{
const int type = std::get<0>(GetParam());
const Size size = std::get<1>(GetParam());
RNG& rng = theRNG();
const size_t N = std::min(100, size.area());
for(size_t i = 0 ; i<N ; ++i)
{
const int nz_pos_x = rng.uniform(0, size.width);
const int nz_pos_y = rng.uniform(0, size.height);
Mat m = Mat::zeros(size, type);
Mat nzROI = Mat(m, Rect(nz_pos_x, nz_pos_y, 1, 1));
nzROI.setTo(Scalar::all(1));
EXPECT_TRUE(hasNonZero(m));
}
}
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroRandom,
testing::Combine(
testing::Values(CV_8UC1, CV_8SC1, CV_16UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_64FC1),
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113))
)
);
typedef testing::TestWithParam<tuple<int, int, bool> > HasNonZeroNd;
TEST_P(HasNonZeroNd, hasNonZeroNd)
{
const int type = get<0>(GetParam());
const int ndims = get<1>(GetParam());
const bool continuous = get<2>(GetParam());
RNG& rng = theRNG();
const size_t N = 10;
for(size_t i = 0 ; i<N ; ++i)
{
std::vector<size_t> steps(ndims);
std::vector<int> sizes(ndims);
size_t totalBytes = 1;
for(int dim = 0 ; dim<ndims ; ++dim)
{
const bool isFirstDim = (dim == 0);
const bool isLastDim = (dim+1 == ndims);
const int length = rng.uniform(1, 64);
steps[dim] = (isLastDim ? 1 : static_cast<size_t>(length))*CV_ELEM_SIZE(type);
sizes[dim] = (isFirstDim || continuous) ? length : rng.uniform(1, length);
totalBytes *= steps[dim]*static_cast<size_t>(sizes[dim]);
}
std::vector<unsigned char> buffer(totalBytes);
void* data = buffer.data();
Mat m = Mat(ndims, sizes.data(), type, data, steps.data());
std::vector<Range> nzRange(ndims);
for(int dim = 0 ; dim<ndims ; ++dim)
{
const int pos = rng.uniform(0, sizes[dim]);
nzRange[dim] = Range(pos, pos+1);
}
Mat nzROI = Mat(m, nzRange.data());
nzROI.setTo(Scalar::all(1));
const int nzCount = countNonZero(m);
EXPECT_EQ((nzCount>0), hasNonZero(m));
}
}
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroNd,
testing::Combine(
testing::Values(CV_8UC1),
testing::Values(2, 3),
testing::Values(true, false)
)
);
}} // namespace

@ -446,5 +446,11 @@ class dnn_test(NewOpenCVTests):
normAssert(self, real_output, gold_output, "", getDefaultThreshold(target))
def test_scalefactor_assign(self):
params = cv.dnn.Image2BlobParams()
self.assertEqual(params.scalefactor, (1.0, 1.0, 1.0, 1.0))
params.scalefactor = 2.0
self.assertEqual(params.scalefactor, (2.0, 0.0, 0.0, 0.0))
if __name__ == '__main__':
NewOpenCVTests.bootstrap()

@ -97,11 +97,11 @@ class CaffeImporter
public:
CaffeImporter(const char *pototxt, const char *caffeModel)
CaffeImporter(const char *prototxt, const char *caffeModel)
{
CV_TRACE_FUNCTION();
ReadNetParamsFromTextFileOrDie(pototxt, &net);
ReadNetParamsFromTextFileOrDie(prototxt, &net);
if (caffeModel && caffeModel[0])
ReadNetParamsFromBinaryFileOrDie(caffeModel, &netBinary);
@ -193,7 +193,6 @@ public:
break;
default:
CV_Error(Error::StsError, "Unknown type \"" + String(field->type_name()) + "\" in prototxt");
break;
}
}
@ -556,7 +555,6 @@ public:
if (idx < 0)
{
CV_Error(Error::StsObjectNotFound, "Can't find output blob \"" + name + "\"");
return;
}
dstNet.connect(addedBlobs[idx].layerId, addedBlobs[idx].outNum, layerId, inNum);

@ -51,6 +51,11 @@ void blobFromImages(InputArrayOfArrays images_, OutputArray blob_, double scalef
Size size, const Scalar& mean_, bool swapRB, bool crop, int ddepth)
{
CV_TRACE_FUNCTION();
if (images_.kind() != _InputArray::STD_VECTOR_MAT && images_.kind() != _InputArray::STD_ARRAY_MAT &&
images_.kind() != _InputArray::STD_VECTOR_VECTOR) {
String error_message = "The data is expected as vectors of vectors or vectors of matrices.";
CV_Error(Error::StsBadArg, error_message);
}
Image2BlobParams param(Scalar::all(scalefactor), size, mean_, swapRB, ddepth);
if (crop)
param.paddingmode = DNN_PMODE_CROP_CENTER;
@ -83,9 +88,13 @@ Mat blobFromImagesWithParams(InputArrayOfArrays images, const Image2BlobParams&
void blobFromImagesWithParams(InputArrayOfArrays images_, OutputArray blob_, const Image2BlobParams& param)
{
CV_TRACE_FUNCTION();
if (images_.kind() != _InputArray::STD_VECTOR_MAT && images_.kind() != _InputArray::STD_ARRAY_MAT &&
images_.kind() != _InputArray::STD_VECTOR_VECTOR) {
String error_message = "The data is expected as vectors of vectors or vectors of matrices.";
CV_Error(Error::StsBadArg, error_message);
}
CV_CheckType(param.ddepth, param.ddepth == CV_32F || param.ddepth == CV_8U,
"Blob depth should be CV_32F or CV_8U");
Size size = param.size;
std::vector<Mat> images;
images_.getMatVector(images);

@ -31,7 +31,6 @@ void winofunc_BtXB_8x8_f32(const float* inptr, int inpstep,
void winofunc_AtXA_8x8_f32(const float* inptr, int inpstep, float* bpptr, int bpstep, float* outptr, int outstep,
float bias, float minval, float maxval, bool ifMinMaxAct);
int runWinograd63(InputArray _input, InputArray _fusedAddMat, OutputArray _output, const Ptr<FastConv>& conv,
int ntasks, float minval, float maxval, ActivationLayer* activ, bool ifMinMaxAct)
{
@ -51,6 +50,23 @@ int runWinograd63(InputArray _input, InputArray _fusedAddMat, OutputArray _outpu
int pad_left = conv->pad_left;
int ngroups = conv->ngroups, Cg = C/ngroups, Kg = K/ngroups;
const int CONV_WINO_KBLOCK = 4;
#if (CV_NEON && CV_NEON_AARCH64)
const int CONV_WINO_IBLOCK = 6;
#elif CV_TRY_AVX || CV_TRY_AVX2
const int CONV_WINO_IBLOCK = (conv->useAVX || conv->useAVX2) ? 6 : 3;
#else
const int CONV_WINO_IBLOCK = 3;
#endif
#if CV_TRY_AVX || CV_TRY_AVX2
const int CONV_WINO_ATOM_F32 = (conv->useAVX || conv->useAVX2) ? 8 : 4;
#else
const int CONV_WINO_ATOM_F32 = 4;
#endif
const int CONV_WINO_NATOMS_F32 = CONV_WINO_AREA / CONV_WINO_ATOM_F32; // for AVX2, it is 8, otherwise, it's 16.
int Kg_nblocks = (Kg + CONV_WINO_KBLOCK - 1)/CONV_WINO_KBLOCK;
const size_t inp_planesize = (size_t)Hi*Wi;
const size_t out_planesize = (size_t)H0*W0;
@ -398,7 +414,7 @@ void winofunc_accum_f32(const float* inwptr, const float* wptr, float* outbuf, i
void winofunc_BtXB_8x8_f32(const float* inptr, int inpstep,
float* outptr, int Cg, const int winoIblock, const int winoAtomF32)
{
CV_Assert(CONV_WINO_IBLOCK == 3 && CONV_WINO_KBLOCK == 4 && CONV_WINO_ATOM_F32 == 4);
CV_Assert(winoIblock == 3 && winoAtomF32 == 4);
v_float32x4 x00 = v_load(inptr), x01 = v_load(inptr + 4);
v_float32x4 x10 = v_load(inptr + inpstep), x11 = v_load(inptr + inpstep + 4);
v_float32x4 x20 = v_load(inptr + inpstep*2), x21 = v_load(inptr + inpstep*2 + 4);
@ -573,7 +589,6 @@ void winofunc_AtXA_8x8_f32(const float* inptr, int inpstep,
float* bpptr, int bpstep, float* outptr, int outstep,
float bias, float minval, float maxval, bool ifMinMaxAct)
{
CV_Assert(CONV_WINO_IBLOCK == 3 && CONV_WINO_KBLOCK == 4 && CONV_WINO_ATOM_F32 == 4);
v_float32x4 x00 = v_load(inptr), x01 = v_load(inptr + 4);
v_float32x4 x10 = v_load(inptr + inpstep), x11 = v_load(inptr + inpstep + 4);
v_float32x4 x20 = v_load(inptr + inpstep*2), x21 = v_load(inptr + inpstep*2 + 4);

@ -181,6 +181,21 @@ Ptr<FastConv> initFastConv(
{0.0f, 0.0f, 1.0f}
};
const int CONV_WINO_KBLOCK = 4;
#if CV_TRY_AVX || CV_TRY_AVX2
const int CONV_WINO_ATOM_F32 = (conv->useAVX || conv->useAVX2) ? 8 : 4;
#else
const int CONV_WINO_ATOM_F32 = 4;
#endif
const int CONV_WINO_NATOMS_F32 = CONV_WINO_AREA / CONV_WINO_ATOM_F32; // for AVX2, it is 8, otherwise, it's 16.
#ifdef CONV_ARM_FP16
// FP 16
const int CONV_WINO_ATOM_F16 = CONV_WINO_ATOM_F32 * 2;
const int CONV_WINO_NATOMS_F16 = CONV_WINO_AREA / CONV_WINO_ATOM_F16;
#endif
// the weights are packed as 6-dim tensor:
// ngroups * ceil((K/ngroups)/KBLOCK) * (W*W/ATOM_SIZE) * (C/ngroups) * KBLOCK * ATOM_SIZE,
// where W is the size of Winograd-transformed kernel (8x8),
@ -1275,7 +1290,7 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
else
Kg_nblocks = 1;
bool seperateIm2col = fast_1x1 || stripes_per_plane == 1;
bool separateIm2col = fast_1x1 || stripes_per_plane == 1;
int Kstripes = Kg_nblocks * stripes_per_plane;
int nsubtasks = N * ngroups * Kstripes;
@ -1285,12 +1300,12 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
size_t taskbufsize = cbufsize * sizeof(float );
if (!seperateIm2col)
if (!separateIm2col)
taskbufsize += MAX_STRIPES * stripesize * esz;
size_t totalbufsize_base = taskbufsize * ntasks;
size_t totalbufsize = totalbufsize_base;
if (seperateIm2col)
if (separateIm2col)
totalbufsize += N * ngroups * stripes_per_plane0 * stripesize * esz;
AutoBuffer<char> inpbuf_all_;
@ -1308,7 +1323,7 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
// In general, im2row results in Hk*Wk-x unrolling factor
// (e.g. 3*3=9x unrolling for 3x3 convolution), thus for 1x1 convolution
// the reordered tensor will take as much space as the original tensor.
if (seperateIm2col)
if (separateIm2col)
{
// the optional phase 1. im2row
parallel_for_(Range(0, ntasks), [&](const Range& r0) {
@ -1409,7 +1424,7 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
CV_Assert(nstripes <= MAX_STRIPES);
if (!seperateIm2col)
if (!separateIm2col)
{
packInputData(inpbuf_task, inp, ofstab, dhwTab, zyx0, zyx_block_limit, ksize, stride_d, stride_h,
stride_w, pad_front, pad_top, pad_left, Dk, Hk, Wk, dilation_d, dilation_h, dilation_w,
@ -1442,8 +1457,8 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
int out_width = zyx_block_limit - zyx0;
float *outptr = out + outofs;
const float biasVal = *(conv->biasBuf.data() + g);
const char *inptr_ = seperateIm2col ? inpbuf_all_0 + (ng*stripes_per_plane0 + zyx0/CONV_NR) * stripesize * esz:
inpbuf_task;
const char *inptr_ = separateIm2col ? inpbuf_all_0 + (ng * stripes_per_plane0 + zyx0 / CONV_NR) * stripesize * esz :
inpbuf_task;
for (int stripe = 0; stripe < nstripes; stripe++)
{
@ -1496,8 +1511,8 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
for (int c0 = 0; c0 < DkHkWkCg; c0 += C_BLOCK_SIZE)
{
int c1 = c0 + C_BLOCK_SIZE < DkHkWkCg ? c0 + C_BLOCK_SIZE : DkHkWkCg;
const char *inptr = seperateIm2col ? inpbuf_all_0 + (ng*stripes_per_plane0 + zyx0/CONV_NR)*stripesize*esz:
inpbuf_task;
const char *inptr = separateIm2col ? inpbuf_all_0 + (ng * stripes_per_plane0 + zyx0 / CONV_NR) * stripesize * esz :
inpbuf_task;
inptr += (c0 * CONV_NR) * esz;
for (int stripe = 0; stripe < nstripes; stripe++, inptr += stripesize * esz)
{

@ -33,36 +33,17 @@ typedef __fp16 float16_t; // Fix conflict between float16_t in arm_neon.h and fl
#define CONV_NR_FP32 24
#endif
// Winograd Params
enum {
CONV_WINO_STEP=6,
CONV_WINO_KSIZE=3,
CONV_WINO_SIZE=CONV_WINO_STEP+CONV_WINO_KSIZE-1, // 8
CONV_WINO_SIZE=CONV_WINO_STEP+CONV_WINO_KSIZE - 1, // 8
CONV_WINO_AREA=CONV_WINO_SIZE*CONV_WINO_SIZE,
CONV_WINO_KBLOCK = 4,
#if (CV_NEON && CV_NEON_AARCH64) || CV_TRY_AVX || CV_TRY_AVX2
CONV_WINO_IBLOCK = 6,
#else
CONV_WINO_IBLOCK = 3,
#endif
#if CV_TRY_AVX || CV_TRY_AVX2
CONV_WINO_ATOM_F32 = 8,
#else
CONV_WINO_ATOM_F32 = 4,
#endif
CONV_WINO_NATOMS_F32 = CONV_WINO_AREA / CONV_WINO_ATOM_F32, // for AVX2, it is 8, otherwise, it's 16.
// FP 16
CONV_WINO_ATOM_F16 = CONV_WINO_ATOM_F32 * 2,
CONV_WINO_NATOMS_F16 = CONV_WINO_AREA / CONV_WINO_ATOM_F16,
};
// NOTE that: CONV_TYPE_DEPTHWISE is for 3x3 depthwise conv, and others depthwise will be set as CONV_TYPE_DEPTHWISE_REMAIN.
enum { CONV_TYPE_GENERIC=0, CONV_TYPE_DEPTHWISE=1, CONV_TYPE_WINOGRAD3X3=2, CONV_TYPE_DEPTHWISE_REMAIN=3 };
enum { CONV_1D = 0, CONV_2D = 1, CONV_3D = 2 };
#endif
namespace cv {

@ -1363,14 +1363,22 @@ void ONNXImporter::parseSplit(LayerParams& layerParams, const opencv_onnx::NodeP
{
DictValue splits = layerParams.get("split");
const int numSplits = splits.size();
CV_Assert(numSplits > 1);
std::vector<int> slicePoints(numSplits - 1, splits.get<int>(0));
for (int i = 1; i < splits.size() - 1; ++i)
if (numSplits == 1)
{
slicePoints[i] = slicePoints[i - 1] + splits.get<int>(i);
layerParams.set("num_split", 1);
}
else
{
CV_Assert(numSplits >= 1);
std::vector<int> slicePoints(numSplits - 1, splits.get<int>(0));
for (int i = 1; i < splits.size() - 1; ++i)
{
slicePoints[i] = slicePoints[i - 1] + splits.get<int>(i);
}
layerParams.set("slice_point", DictValue::arrayInt(&slicePoints[0], slicePoints.size()));
}
layerParams.set("slice_point", DictValue::arrayInt(&slicePoints[0], slicePoints.size()));
}
else if (node_proto.input_size() == 2) // opset >= 13, the split will be stored at the second input, instead of the attribute.
{
@ -2771,13 +2779,15 @@ void ONNXImporter::parseResize(LayerParams& layerParams, const opencv_onnx::Node
if (layerParams.has("coordinate_transformation_mode"))
{
String interp_mode = layerParams.get<String>("coordinate_transformation_mode");
CV_Assert_N(interp_mode != "tf_crop_and_resize", interp_mode != "tf_half_pixel_for_nn");
CV_Assert(interp_mode != "tf_crop_and_resize");
bool halfPixel = interp_mode == "tf_half_pixel_for_nn" || interp_mode == "half_pixel" || interp_mode == "pytorch_half_pixel";
layerParams.set("align_corners", interp_mode == "align_corners");
layerParams.set("half_pixel_centers", halfPixel);
if (layerParams.get<String>("mode") == "linear")
{
layerParams.set("mode", interp_mode == "pytorch_half_pixel" || interp_mode == "half_pixel" ?
"opencv_linear" : "bilinear");
layerParams.set("mode", halfPixel ? "opencv_linear" : "bilinear");
}
}
if (layerParams.get<String>("mode") == "linear" && framework_name == "pytorch")
@ -2830,13 +2840,15 @@ void ONNXImporter::parseUpsample(LayerParams& layerParams, const opencv_onnx::No
if (layerParams.has("coordinate_transformation_mode"))
{
String interp_mode = layerParams.get<String>("coordinate_transformation_mode");
CV_Assert_N(interp_mode != "tf_crop_and_resize", interp_mode != "tf_half_pixel_for_nn");
CV_Assert(interp_mode != "tf_crop_and_resize");
bool halfPixel = interp_mode == "tf_half_pixel_for_nn" || interp_mode == "half_pixel" || interp_mode == "pytorch_half_pixel";
layerParams.set("align_corners", interp_mode == "align_corners");
layerParams.set("half_pixel_centers", halfPixel);
if (layerParams.get<String>("mode") == "linear")
{
layerParams.set("mode", interp_mode == "pytorch_half_pixel" ?
"opencv_linear" : "bilinear");
layerParams.set("mode", halfPixel ? "opencv_linear" : "bilinear");
}
}
if (layerParams.get<String>("mode") == "linear" && framework_name == "pytorch")

@ -1017,6 +1017,7 @@ TEST_P(Test_ONNX_layers, Padding)
TEST_P(Test_ONNX_layers, Resize)
{
testONNXModels("resize_nearest");
testONNXModels("tf_half_pixel_for_nn");
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019)
applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER);
testONNXModels("resize_bilinear");
@ -1146,6 +1147,7 @@ TEST_P(Test_ONNX_layers, Split)
applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER);
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH);
testONNXModels("split_0");
testONNXModels("split_1");
testONNXModels("split_2");
testONNXModels("split_3");

@ -156,6 +156,10 @@ set(gapi_srcs
src/backends/ie/giebackend.cpp
src/backends/ie/giebackend/giewrapper.cpp
# OV Backend. FIXME: should be included by CMake
# if and only if OV support is enabled
src/backends/ov/govbackend.cpp
# ONNX backend
src/backends/onnx/gonnxbackend.cpp
@ -182,6 +186,7 @@ set(gapi_srcs
# Python bridge
src/backends/ie/bindings_ie.cpp
src/backends/onnx/bindings_onnx.cpp
src/backends/ov/bindings_ov.cpp
src/backends/python/gpythonbackend.cpp
# OpenVPL Streaming source

@ -0,0 +1,128 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2023 Intel Corporation
#ifndef OPENCV_GAPI_INFER_BINDINGS_OV_HPP
#define OPENCV_GAPI_INFER_BINDINGS_OV_HPP
#include <opencv2/gapi/util/any.hpp>
#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage
#include <opencv2/gapi/infer/ov.hpp> // Params
#include <string>
namespace cv {
namespace gapi {
namespace ov {
// NB: Used by python wrapper
// This class can be marked as SIMPLE, because it's implemented as pimpl
class GAPI_EXPORTS_W_SIMPLE PyParams {
public:
GAPI_WRAP
PyParams() = default;
GAPI_WRAP
PyParams(const std::string &tag,
const std::string &model_path,
const std::string &bin_path,
const std::string &device);
GAPI_WRAP
PyParams(const std::string &tag,
const std::string &blob_path,
const std::string &device);
GAPI_WRAP
PyParams& cfgPluginConfig(
const std::map<std::string, std::string> &config);
GAPI_WRAP
PyParams& cfgInputTensorLayout(std::string tensor_layout);
GAPI_WRAP
PyParams& cfgInputTensorLayout(
std::map<std::string, std::string> layout_map);
GAPI_WRAP
PyParams& cfgInputModelLayout(std::string tensor_layout);
GAPI_WRAP
PyParams& cfgInputModelLayout(
std::map<std::string, std::string> layout_map);
GAPI_WRAP
PyParams& cfgOutputTensorLayout(std::string tensor_layout);
GAPI_WRAP
PyParams& cfgOutputTensorLayout(
std::map<std::string, std::string> layout_map);
GAPI_WRAP
PyParams& cfgOutputModelLayout(std::string tensor_layout);
GAPI_WRAP
PyParams& cfgOutputModelLayout(
std::map<std::string, std::string> layout_map);
GAPI_WRAP
PyParams& cfgOutputTensorPrecision(int precision);
GAPI_WRAP
PyParams& cfgOutputTensorPrecision(
std::map<std::string, int> precision_map);
GAPI_WRAP
PyParams& cfgReshape(std::vector<size_t> new_shape);
GAPI_WRAP
PyParams& cfgReshape(
std::map<std::string, std::vector<size_t>> new_shape_map);
GAPI_WRAP
PyParams& cfgNumRequests(const size_t nireq);
GAPI_WRAP
PyParams& cfgMean(std::vector<float> mean_values);
GAPI_WRAP
PyParams& cfgMean(
std::map<std::string, std::vector<float>> mean_map);
GAPI_WRAP
PyParams& cfgScale(std::vector<float> scale_values);
GAPI_WRAP
PyParams& cfgScale(
std::map<std::string, std::vector<float>> scale_map);
GAPI_WRAP
PyParams& cfgResize(int interpolation);
GAPI_WRAP
PyParams& cfgResize(std::map<std::string, int> interpolation);
GBackend backend() const;
std::string tag() const;
cv::util::any params() const;
private:
std::shared_ptr<Params<cv::gapi::Generic>> m_priv;
};
GAPI_EXPORTS_W PyParams params(const std::string &tag,
const std::string &model_path,
const std::string &weights,
const std::string &device);
GAPI_EXPORTS_W PyParams params(const std::string &tag,
const std::string &bin_path,
const std::string &device);
} // namespace ov
} // namespace gapi
} // namespace cv
#endif // OPENCV_GAPI_INFER_BINDINGS_OV_HPP

@ -2,7 +2,7 @@
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019-2021 Intel Corporation
// Copyright (C) 2019-2023 Intel Corporation
#ifndef OPENCV_GAPI_INFER_IE_HPP
#define OPENCV_GAPI_INFER_IE_HPP
@ -55,6 +55,21 @@ using IEConfig = std::map<std::string, std::string>;
enum InferMode {Sync, Async};
namespace detail {
template <typename T>
using AttrMap = std::map<std::string, T>;
// NB: This type is used to hold in/out layers
// attributes such as precision, layout, shape etc.
//
// User can provide attributes either:
// 1. cv::util::monostate - No value specified explicitly.
// 2. Attr - value specified explicitly that should be broadcasted to all layers.
// 3. AttrMap[str->T] - map specifies value for particular layer.
template <typename Attr>
using LayerVariantAttr = cv::util::variant< cv::util::monostate
, AttrMap<Attr>
, Attr>;
struct ParamDesc {
std::string model_path;
std::string weights_path;
@ -103,7 +118,11 @@ struct ParamDesc {
using PrecisionVariantT = cv::util::variant<cv::util::monostate,
PrecisionT,
PrecisionMapT>;
PrecisionVariantT output_precision;
LayerVariantAttr<std::string> input_layout;
LayerVariantAttr<std::string> output_layout;
LayerVariantAttr<int> interpolation;
};
} // namespace detail
@ -150,6 +169,9 @@ public:
, {}
, {}
, InferMode::Async
, {}
, {}
, {}
, {} } {
};
@ -176,6 +198,9 @@ public:
, {}
, {}
, InferMode::Async
, {}
, {}
, {}
, {} } {
};
@ -412,6 +437,80 @@ public:
return *this;
}
/** @brief Specifies the input layout for model.
The function is used to set an input layout for model.
@param layout Layout in string representation ("NCHW", "NHWC", etc)
will be applied to all input layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgInputLayout(std::string layout) {
desc.input_layout = std::move(layout);
return *this;
}
/** @overload
@param layout_map Map of pairs: name of corresponding input layer
and its layout in string representation ("NCHW", "NHWC", etc)
@return reference to this parameter structure.
*/
Params<Net>&
cfgInputLayout(detail::AttrMap<std::string> layout_map) {
desc.input_layout = std::move(layout_map);
return *this;
}
/** @brief Specifies the output layout for model.
The function is used to set an output layout for model.
@param layout Layout in string representation ("NCHW", "NHWC", etc)
will be applied to all output layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgOutputLayout(std::string layout) {
desc.output_layout = std::move(layout);
return *this;
}
/** @overload
@param layout_map Map of pairs: name of corresponding output layer
and its layout in string representation ("NCHW", "NHWC", etc)
@return reference to this parameter structure.
*/
Params<Net>&
cfgOutputLayout(detail::AttrMap<std::string> layout_map) {
desc.output_layout = std::move(layout_map);
return *this;
}
/** @brief Specifies resize interpolation algorithm.
*
The function is used to configure resize preprocessing for input layer.
@param interpolation Resize interpolation algorithm.
Supported algorithms: #INTER_LINEAR, #INTER_AREA.
@return reference to this parameter structure.
*/
Params<Net>& cfgResize(int interpolation) {
desc.interpolation = interpolation;
return *this;
}
/** @overload
@param interpolation Map of pairs: name of corresponding input layer
and its resize algorithm.
@return reference to this parameter structure.
*/
Params<Net>& cfgResize(detail::AttrMap<int> interpolation) {
desc.interpolation = std::move(interpolation);
return *this;
}
// BEGIN(G-API's network parametrization API)
GBackend backend() const { return cv::gapi::ie::backend(); }
std::string tag() const { return Net::tag(); }
@ -446,7 +545,7 @@ public:
const std::string &device)
: desc{ model, weights, device, {}, {}, {}, 0u, 0u,
detail::ParamDesc::Kind::Load, true, {}, {}, {}, 1u,
{}, {}, {}, {}, InferMode::Async, {} },
{}, {}, {}, {}, InferMode::Async, {}, {}, {}, {} },
m_tag(tag) {
};
@ -464,7 +563,7 @@ public:
const std::string &device)
: desc{ model, {}, device, {}, {}, {}, 0u, 0u,
detail::ParamDesc::Kind::Import, true, {}, {}, {}, 1u,
{}, {}, {}, {}, InferMode::Async, {} },
{}, {}, {}, {}, InferMode::Async, {}, {}, {}, {} },
m_tag(tag) {
};
@ -556,6 +655,44 @@ public:
return *this;
}
/** @see ie::Params::cfgInputLayout */
Params& cfgInputLayout(std::string layout) {
desc.input_layout = std::move(layout);
return *this;
}
/** @overload */
Params&
cfgInputLayout(detail::AttrMap<std::string> layout_map) {
desc.input_layout = std::move(layout_map);
return *this;
}
/** @see ie::Params::cfgOutputLayout */
Params& cfgOutputLayout(std::string layout) {
desc.output_layout = std::move(layout);
return *this;
}
/** @overload */
Params&
cfgOutputLayout(detail::AttrMap<std::string> layout_map) {
desc.output_layout = std::move(layout_map);
return *this;
}
/** @see ie::Params::cfgResize */
Params& cfgResize(int interpolation) {
desc.interpolation = interpolation;
return *this;
}
/** @overload */
Params& cfgResize(detail::AttrMap<int> interpolation) {
desc.interpolation = std::move(interpolation);
return *this;
}
// BEGIN(G-API's network parametrization API)
GBackend backend() const { return cv::gapi::ie::backend(); }
std::string tag() const { return m_tag; }

@ -0,0 +1,685 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2023 Intel Corporation
#ifndef OPENCV_GAPI_INFER_OV_HPP
#define OPENCV_GAPI_INFER_OV_HPP
#include <string>
#include <opencv2/gapi/util/any.hpp>
#include <opencv2/gapi/own/exports.hpp> // GAPI_EXPORTS
#include <opencv2/gapi/gkernel.hpp> // GKernelType[M], GBackend
#include <opencv2/gapi/infer.hpp> // Generic
#include <map>
namespace cv {
namespace gapi {
/**
* @brief This namespace contains G-API OpenVINO 2.0 backend functions,
* structures, and symbols.
*/
namespace ov {
GAPI_EXPORTS cv::gapi::GBackend backend();
namespace detail {
template <typename T>
using AttrMap = std::map<std::string, T>;
// NB: This type is supposed to be used to hold in/out layers
// attributes such as precision, layout, shape etc.
//
// User can provide attributes either:
// 1. cv::util::monostate - No value specified explicitly.
// 2. Attr - value specified explicitly that should be broadcasted to all layers.
// 3. AttrMap[str->T] - map specifies value for particular layer.
template <typename Attr>
using LayerVariantAttr = cv::util::variant< cv::util::monostate
, AttrMap<Attr>
, Attr>;
struct ParamDesc {
struct Model {
Model(const std::string &model_path_,
const std::string &bin_path_)
: model_path(model_path_), bin_path(bin_path_) {
}
std::string model_path;
std::string bin_path;
LayerVariantAttr<std::string> input_tensor_layout;
LayerVariantAttr<std::string> input_model_layout;
LayerVariantAttr<std::string> output_tensor_layout;
LayerVariantAttr<std::string> output_model_layout;
LayerVariantAttr<int> output_tensor_precision;
LayerVariantAttr<std::vector<size_t>> new_shapes;
LayerVariantAttr<std::vector<float>> mean_values;
LayerVariantAttr<std::vector<float>> scale_values;
LayerVariantAttr<int> interpolation;
};
struct CompiledModel {
std::string blob_path;
};
using Kind = cv::util::variant<Model, CompiledModel>;
ParamDesc(Kind &&kind_,
const std::string &device_,
const bool is_generic_,
const size_t num_in_,
const size_t num_out_)
: kind(std::move(kind_)), device(device_),
is_generic(is_generic_),
num_in(num_in_), num_out(num_out_) {
}
Kind kind;
std::string device;
bool is_generic;
std::size_t num_in;
std::size_t num_out;
std::vector<std::string> input_names;
std::vector<std::string> output_names;
using PluginConfigT = std::map<std::string, std::string>;
PluginConfigT config;
size_t nireq = 1;
};
// NB: Just helper to avoid code duplication.
static detail::ParamDesc::Model&
getModelToSetAttrOrThrow(detail::ParamDesc::Kind &kind,
const std::string &attr_name) {
if (cv::util::holds_alternative<detail::ParamDesc::CompiledModel>(kind)) {
cv::util::throw_error(
std::logic_error("Specifying " + attr_name + " isn't"
" possible for compiled model."));
}
GAPI_Assert(cv::util::holds_alternative<detail::ParamDesc::Model>(kind));
return cv::util::get<detail::ParamDesc::Model>(kind);
}
} // namespace detail
/**
* @brief This structure provides functions
* that fill inference parameters for "OpenVINO Toolkit" model.
*/
template<typename Net> struct Params {
public:
/** @brief Class constructor.
Constructs Params based on model information and specifies default values for other
inference description parameters. Model is loaded and compiled using "OpenVINO Toolkit".
@param model_path Path to a model.
@param bin_path Path to a data file.
For IR format (*.bin):
If path is empty, will try to read a bin file with the same name as xml.
If the bin file with the same name is not found, will load IR without weights.
For PDPD (*.pdmodel) and ONNX (*.onnx) formats bin_path isn't used.
@param device target device to use.
*/
Params(const std::string &model_path,
const std::string &bin_path,
const std::string &device)
: m_desc( detail::ParamDesc::Kind{detail::ParamDesc::Model{model_path, bin_path}}
, device
, false /* is generic */
, std::tuple_size<typename Net::InArgs>::value
, std::tuple_size<typename Net::OutArgs>::value) {
}
/** @overload
Use this constructor to work with pre-compiled network.
Model is imported from a pre-compiled blob.
@param blob_path path to the compiled model (*.blob).
@param device target device to use.
*/
Params(const std::string &blob_path,
const std::string &device)
: m_desc( detail::ParamDesc::Kind{detail::ParamDesc::CompiledModel{blob_path}}
, device
, false /* is generic */
, std::tuple_size<typename Net::InArgs>::value
, std::tuple_size<typename Net::OutArgs>::value) {
}
/** @brief Specifies sequence of network input layers names for inference.
The function is used to associate cv::gapi::infer<> inputs with the model inputs.
Number of names has to match the number of network inputs as defined in G_API_NET().
In case a network has only single input layer, there is no need to specify name manually.
@param layer_names std::array<std::string, N> where N is the number of inputs
as defined in the @ref G_API_NET. Contains names of input layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgInputLayers(const std::vector<std::string> &layer_names) {
m_desc.input_names = layer_names;
return *this;
}
/** @brief Specifies sequence of network output layers names for inference.
The function is used to associate cv::gapi::infer<> outputs with the model outputs.
Number of names has to match the number of network outputs as defined in G_API_NET().
In case a network has only single output layer, there is no need to specify name manually.
@param layer_names std::array<std::string, N> where N is the number of outputs
as defined in the @ref G_API_NET. Contains names of output layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgOutputLayers(const std::vector<std::string> &layer_names) {
m_desc.output_names = layer_names;
return *this;
}
/** @brief Specifies OpenVINO plugin configuration.
The function is used to set configuration for OpenVINO plugin. Some parameters
can be different for each plugin. Please follow https://docs.openvinotoolkit.org/latest/index.html
to check information about specific plugin.
@param config Map of pairs: (config parameter name, config parameter value).
@return reference to this parameter structure.
*/
Params<Net>& cfgPluginConfig(const detail::ParamDesc::PluginConfigT &config) {
m_desc.config = config;
return *this;
}
/** @brief Specifies tensor layout for an input layer.
The function is used to set tensor layout for an input layer.
@param layout Tensor layout ("NCHW", "NWHC", etc)
will be applied to all input layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgInputTensorLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout")
.input_tensor_layout = std::move(layout);
return *this;
}
/** @overload
@param layout_map Map of pairs: name of corresponding input layer
and its tensor layout represented in std::string ("NCHW", "NHWC", etc)
@return reference to this parameter structure.
*/
Params<Net>&
cfgInputTensorLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout")
.input_tensor_layout = std::move(layout_map);
return *this;
}
/** @brief Specifies model layout for an input layer.
The function is used to set model layout for an input layer.
@param layout Model layout ("NCHW", "NHWC", etc)
will be applied to all input layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgInputModelLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout")
.input_model_layout = std::move(layout);
return *this;
}
/** @overload
@param layout_map Map of pairs: name of corresponding input layer
and its model layout ("NCHW", "NHWC", etc)
@return reference to this parameter structure.
*/
Params<Net>&
cfgInputModelLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout")
.input_model_layout = std::move(layout_map);
return *this;
}
/** @brief Specifies tensor layout for an output layer.
The function is used to set tensor layout for an output layer.
@param layout Tensor layout ("NCHW", "NWHC", etc)
will be applied to all output layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgOutputTensorLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout")
.output_tensor_layout = std::move(layout);
return *this;
}
/** @overload
@param layout_map Map of pairs: name of corresponding output layer
and its tensor layout represented in std::string ("NCHW", "NHWC", etc)
@return reference to this parameter structure.
*/
Params<Net>&
cfgOutputTensorLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout")
.output_tensor_layout = std::move(layout_map);
return *this;
}
/** @brief Specifies model layout for an output layer.
The function is used to set model layout for an output layer.
@param layout Model layout ("NCHW", "NHWC", etc)
will be applied to all output layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgOutputModelLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout")
.output_model_layout = std::move(layout);
return *this;
}
/** @overload
@param layout_map Map of pairs: name of corresponding output layer
and its model layout ("NCHW", "NHWC", etc)
@return reference to this parameter structure.
*/
Params<Net>&
cfgOutputModelLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout")
.output_model_layout = std::move(layout_map);
return *this;
}
/** @brief Specifies tensor precision for an output layer.
The function is used to set tensor precision for an output layer..
@param precision Precision in OpenCV format (CV_8U, CV_32F, ...)
will be applied to all output layers.
@return reference to this parameter structure.
*/
Params<Net>& cfgOutputTensorPrecision(int precision) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision")
.output_tensor_precision = precision;
return *this;
}
/** @overload
@param precision_map Map of pairs: name of corresponding output layer
and its precision in OpenCV format (CV_8U, CV_32F, ...)
@return reference to this parameter structure.
*/
Params<Net>&
cfgOutputTensorPrecision(detail::AttrMap<int> precision_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision")
.output_tensor_precision = std::move(precision_map);
return *this;
}
/** @brief Specifies the new shape for input layers.
The function is used to set new shape for input layers.
@param new_shape New shape will be applied to all input layers.
@return reference to this parameter structure.
*/
Params<Net>&
cfgReshape(std::vector<size_t> new_shape) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape")
.new_shapes = std::move(new_shape);
return *this;
}
/** @overload
@param new_shape_map Map of pairs: name of corresponding output layer
and its new shape.
@return reference to this parameter structure.
*/
Params<Net>&
cfgReshape(detail::AttrMap<std::vector<size_t>> new_shape_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape")
.new_shapes = std::move(new_shape_map);
return *this;
}
/** @brief Specifies number of asynchronous inference requests.
@param nireq Number of inference asynchronous requests.
@return reference to this parameter structure.
*/
Params<Net>& cfgNumRequests(const size_t nireq) {
if (nireq == 0) {
cv::util::throw_error(
std::logic_error("Number of inference requests"
" must be greater than zero."));
}
m_desc.nireq = nireq;
return *this;
}
/** @brief Specifies mean values for preprocessing.
*
The function is used to set mean values for input layer preprocessing.
@param mean_values Float vector contains mean values
@return reference to this parameter structure.
*/
Params<Net>& cfgMean(std::vector<float> mean_values) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values")
.mean_values = std::move(mean_values);
return *this;
}
/** @overload
@param mean_map Map of pairs: name of corresponding input layer
and its mean values.
@return reference to this parameter structure.
*/
Params<Net>& cfgMean(detail::AttrMap<std::vector<float>> mean_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values")
.mean_values = std::move(mean_map);
return *this;
}
/** @brief Specifies scale values for preprocessing.
*
The function is used to set scale values for input layer preprocessing.
@param scale_values Float vector contains scale values
@return reference to this parameter structure.
*/
Params<Net>& cfgScale(std::vector<float> scale_values) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values")
.scale_values = std::move(scale_values);
return *this;
}
/** @overload
@param scale_map Map of pairs: name of corresponding input layer
and its mean values.
@return reference to this parameter structure.
*/
Params<Net>& cfgScale(detail::AttrMap<std::vector<float>> scale_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values")
.scale_values = std::move(scale_map);
return *this;
}
/** @brief Specifies resize interpolation algorithm.
*
The function is used to configure resize preprocessing for input layer.
@param interpolation Resize interpolation algorithm.
Supported algorithms: #INTER_NEAREST, #INTER_LINEAR, #INTER_CUBIC.
@return reference to this parameter structure.
*/
Params<Net>& cfgResize(int interpolation) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing")
.interpolation = std::move(interpolation);
return *this;
}
/** @overload
@param interpolation Map of pairs: name of corresponding input layer
and its resize algorithm.
@return reference to this parameter structure.
*/
Params<Net>& cfgResize(detail::AttrMap<int> interpolation) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing")
.interpolation = std::move(interpolation);
return *this;
}
// BEGIN(G-API's network parametrization API)
GBackend backend() const { return cv::gapi::ov::backend(); }
std::string tag() const { return Net::tag(); }
cv::util::any params() const { return { m_desc }; }
// END(G-API's network parametrization API)
protected:
detail::ParamDesc m_desc;
};
/*
* @brief This structure provides functions for generic network type that
* fill inference parameters.
* @see struct Generic
*/
template<>
class Params<cv::gapi::Generic> {
public:
/** @brief Class constructor.
Constructs Params based on model information and specifies default values for other
inference description parameters. Model is loaded and compiled using "OpenVINO Toolkit".
@param tag string tag of the network for which these parameters are intended.
@param model_path Path to a model.
@param bin_path Path to a data file.
For IR format (*.bin):
If path is empty, will try to read a bin file with the same name as xml.
If the bin file with the same name is not found, will load IR without weights.
For PDPD (*.pdmodel) and ONNX (*.onnx) formats bin_path isn't used.
@param device target device to use.
*/
Params(const std::string &tag,
const std::string &model_path,
const std::string &bin_path,
const std::string &device)
: m_tag(tag),
m_desc( detail::ParamDesc::Kind{detail::ParamDesc::Model{model_path, bin_path}}
, device
, true /* is generic */
, 0u
, 0u) {
}
/** @overload
This constructor for pre-compiled networks. Model is imported from pre-compiled
blob.
@param tag string tag of the network for which these parameters are intended.
@param blob_path path to the compiled model (*.blob).
@param device target device to use.
*/
Params(const std::string &tag,
const std::string &blob_path,
const std::string &device)
: m_tag(tag),
m_desc( detail::ParamDesc::Kind{detail::ParamDesc::CompiledModel{blob_path}}
, device
, true /* is generic */
, 0u
, 0u) {
}
/** @see ov::Params::cfgPluginConfig. */
Params& cfgPluginConfig(const detail::ParamDesc::PluginConfigT &config) {
m_desc.config = config;
return *this;
}
/** @see ov::Params::cfgInputTensorLayout. */
Params& cfgInputTensorLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout")
.input_tensor_layout = std::move(layout);
return *this;
}
/** @overload */
Params&
cfgInputTensorLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout")
.input_tensor_layout = std::move(layout_map);
return *this;
}
/** @see ov::Params::cfgInputModelLayout. */
Params& cfgInputModelLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout")
.input_model_layout = std::move(layout);
return *this;
}
/** @overload */
Params&
cfgInputModelLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout")
.input_model_layout = std::move(layout_map);
return *this;
}
/** @see ov::Params::cfgOutputTensorLayout. */
Params& cfgOutputTensorLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout")
.output_tensor_layout = std::move(layout);
return *this;
}
/** @overload */
Params&
cfgOutputTensorLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout")
.output_tensor_layout = std::move(layout_map);
return *this;
}
/** @see ov::Params::cfgOutputModelLayout. */
Params& cfgOutputModelLayout(std::string layout) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout")
.output_model_layout = std::move(layout);
return *this;
}
/** @overload */
Params&
cfgOutputModelLayout(detail::AttrMap<std::string> layout_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout")
.output_model_layout = std::move(layout_map);
return *this;
}
/** @see ov::Params::cfgOutputTensorPrecision. */
Params& cfgOutputTensorPrecision(int precision) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision")
.output_tensor_precision = precision;
return *this;
}
/** @overload */
Params&
cfgOutputTensorPrecision(detail::AttrMap<int> precision_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision")
.output_tensor_precision = std::move(precision_map);
return *this;
}
/** @see ov::Params::cfgReshape. */
Params& cfgReshape(std::vector<size_t> new_shape) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape")
.new_shapes = std::move(new_shape);
return *this;
}
/** @overload */
Params&
cfgReshape(detail::AttrMap<std::vector<size_t>> new_shape_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape")
.new_shapes = std::move(new_shape_map);
return *this;
}
/** @see ov::Params::cfgNumRequests. */
Params& cfgNumRequests(const size_t nireq) {
if (nireq == 0) {
cv::util::throw_error(
std::logic_error("Number of inference requests"
" must be greater than zero."));
}
m_desc.nireq = nireq;
return *this;
}
/** @see ov::Params::cfgMean. */
Params& cfgMean(std::vector<float> mean_values) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values")
.mean_values = std::move(mean_values);
return *this;
}
/** @overload */
Params& cfgMean(detail::AttrMap<std::vector<float>> mean_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values")
.mean_values = std::move(mean_map);
return *this;
}
/** @see ov::Params::cfgScale. */
Params& cfgScale(std::vector<float> scale_values) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values")
.scale_values = std::move(scale_values);
return *this;
}
/** @overload */
Params& cfgScale(detail::AttrMap<std::vector<float>> scale_map) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values")
.scale_values = std::move(scale_map);
return *this;
}
/** @see ov::Params::cfgResize. */
Params& cfgResize(int interpolation) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing")
.interpolation = std::move(interpolation);
return *this;
}
/** @overload */
Params& cfgResize(detail::AttrMap<int> interpolation) {
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing")
.interpolation = std::move(interpolation);
return *this;
}
// BEGIN(G-API's network parametrization API)
GBackend backend() const { return cv::gapi::ov::backend(); }
std::string tag() const { return m_tag; }
cv::util::any params() const { return { m_desc }; }
// END(G-API's network parametrization API)
protected:
std::string m_tag;
detail::ParamDesc m_desc;
};
} // namespace ov
} // namespace gapi
} // namespace cv
#endif // OPENCV_GAPI_INFER_OV_HPP

@ -22,6 +22,7 @@
* because of this file.
*/
#include <chrono>
#include <map>
#include <opencv2/videoio.hpp>
#include <opencv2/gapi/garg.hpp>
@ -47,8 +48,16 @@ namespace wip {
class GCaptureSource: public IStreamSource
{
public:
explicit GCaptureSource(int id) : cap(id) { prep(); }
explicit GCaptureSource(const std::string &path) : cap(path) { prep(); }
explicit GCaptureSource(int id, const std::map<int, double> &properties = {})
: cap(id) { prep(properties); }
explicit GCaptureSource(const std::string &path,
const std::map<int, double> &properties = {})
: cap(path) { prep(properties); }
void set(int propid, double value) {
cap.set(propid, value);
}
// TODO: Add more constructor overloads to make it
// fully compatible with VideoCapture's interface.
@ -59,8 +68,12 @@ protected:
bool first_pulled = false;
int64_t counter = 0;
void prep()
void prep(const std::map<int, double> &properties)
{
for (const auto &it : properties) {
cap.set(it.first, it.second);
}
// Prepare first frame to report its meta to engine
// when needed
GAPI_Assert(first.empty());
@ -114,15 +127,19 @@ protected:
};
// NB: Overload for using from python
GAPI_EXPORTS_W cv::Ptr<IStreamSource> inline make_capture_src(const std::string& path)
GAPI_EXPORTS_W cv::Ptr<IStreamSource>
inline make_capture_src(const std::string& path,
const std::map<int, double>& properties = {})
{
return make_src<GCaptureSource>(path);
return make_src<GCaptureSource>(path, properties);
}
// NB: Overload for using from python
GAPI_EXPORTS_W cv::Ptr<IStreamSource> inline make_capture_src(const int id)
GAPI_EXPORTS_W cv::Ptr<IStreamSource>
inline make_capture_src(const int id,
const std::map<int, double>& properties = {})
{
return make_src<GCaptureSource>(id);
return make_src<GCaptureSource>(id, properties);
}
} // namespace wip

@ -15,6 +15,7 @@ using gapi_GKernelPackage = cv::GKernelPackage;
using gapi_GNetPackage = cv::gapi::GNetPackage;
using gapi_ie_PyParams = cv::gapi::ie::PyParams;
using gapi_onnx_PyParams = cv::gapi::onnx::PyParams;
using gapi_ov_PyParams = cv::gapi::ov::PyParams;
using gapi_wip_IStreamSource_Ptr = cv::Ptr<cv::gapi::wip::IStreamSource>;
using detail_ExtractArgsCallback = cv::detail::ExtractArgsCallback;
using detail_ExtractMetaCallback = cv::detail::ExtractMetaCallback;
@ -22,6 +23,12 @@ using vector_GNetParam = std::vector<cv::gapi::GNetParam>;
using vector_GMat = std::vector<cv::GMat>;
using gapi_streaming_queue_capacity = cv::gapi::streaming::queue_capacity;
using GStreamerSource_OutputType = cv::gapi::wip::GStreamerSource::OutputType;
using map_string_and_int = std::map<std::string, int>;
using map_string_and_string = std::map<std::string, std::string>;
using map_string_and_string = std::map<std::string, std::string>;
using map_string_and_vector_size_t = std::map<std::string, std::vector<size_t>>;
using map_string_and_vector_float = std::map<std::string, std::vector<float>>;
using map_int_and_double = std::map<int, double>;
// NB: Python wrapper generate T_U for T<U>
// This behavior is only observed for inputs

@ -80,5 +80,6 @@ namespace detail
{
gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ie::PyParams params);
gapi::GNetParam GAPI_EXPORTS_W strip(gapi::onnx::PyParams params);
gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ov::PyParams params);
} // namespace detail
} // namespace cv

@ -0,0 +1,238 @@
#!/usr/bin/env python
import numpy as np
import cv2 as cv
import os
import sys
import unittest
from tests_common import NewOpenCVTests
try:
if sys.version_info[:2] < (3, 0):
raise unittest.SkipTest('Python 2.x is not supported')
openvino_is_available = True
try:
from openvino.runtime import Core, Type, Layout, PartialShape
from openvino.preprocess import ResizeAlgorithm, PrePostProcessor
except ImportError:
openvino_is_available = False
def skip_if_openvino_not_available():
if not openvino_is_available:
raise unittest.SkipTest("OpenVINO isn't available from python.")
class AgeGenderOV:
def __init__(self, model_path, bin_path, device):
self.device = device
self.core = Core()
self.model = self.core.read_model(model_path, bin_path)
def reshape(self, new_shape):
self.model.reshape(new_shape)
def cfgPrePostProcessing(self, pp_callback):
ppp = PrePostProcessor(self.model)
pp_callback(ppp)
self.model = ppp.build()
def apply(self, in_data):
compiled_model = self.core.compile_model(self.model, self.device)
infer_request = compiled_model.create_infer_request()
results = infer_request.infer(in_data)
ov_age = results['age_conv3'].squeeze()
ov_gender = results['prob'].squeeze()
return ov_age, ov_gender
class AgeGenderGAPI:
tag = 'age-gender-net'
def __init__(self, model_path, bin_path, device):
g_in = cv.GMat()
inputs = cv.GInferInputs()
inputs.setInput('data', g_in)
# TODO: It'd be nice to pass dict instead.
# E.g cv.gapi.infer("net", {'data': g_in})
outputs = cv.gapi.infer(AgeGenderGAPI.tag, inputs)
age_g = outputs.at("age_conv3")
gender_g = outputs.at("prob")
self.comp = cv.GComputation(cv.GIn(g_in), cv.GOut(age_g, gender_g))
self.pp = cv.gapi.ov.params(AgeGenderGAPI.tag, \
model_path, bin_path, device)
def apply(self, in_data):
compile_args = cv.gapi.compile_args(cv.gapi.networks(self.pp))
gapi_age, gapi_gender = self.comp.apply(cv.gin(in_data), compile_args)
gapi_gender = gapi_gender.squeeze()
gapi_age = gapi_age.squeeze()
return gapi_age, gapi_gender
class test_gapi_infer_ov(NewOpenCVTests):
def test_age_gender_infer_image(self):
skip_if_openvino_not_available()
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013'
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
device_id = 'CPU'
img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
img = cv.imread(img_path)
# OpenVINO
def preproc(ppp):
ppp.input().model().set_layout(Layout("NCHW"))
ppp.input().tensor().set_element_type(Type.u8) \
.set_spatial_static_shape(img.shape[0], img.shape[1]) \
.set_layout(Layout("NHWC"))
ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR)
ref = AgeGenderOV(model_path, bin_path, device_id)
ref.cfgPrePostProcessing(preproc)
ov_age, ov_gender = ref.apply(np.expand_dims(img, 0))
# OpenCV G-API (No preproc required)
comp = AgeGenderGAPI(model_path, bin_path, device_id)
gapi_age, gapi_gender = comp.apply(img)
# Check
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF))
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF))
def test_age_gender_infer_tensor(self):
skip_if_openvino_not_available()
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013'
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
device_id = 'CPU'
img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
img = cv.imread(img_path)
# Prepare data manually
tensor = cv.resize(img, (62, 62)).astype(np.float32)
tensor = np.transpose(tensor, (2, 0, 1))
tensor = np.expand_dims(tensor, 0)
# OpenVINO (No preproce required)
ref = AgeGenderOV(model_path, bin_path, device_id)
ov_age, ov_gender = ref.apply(tensor)
# OpenCV G-API (No preproc required)
comp = AgeGenderGAPI(model_path, bin_path, device_id)
gapi_age, gapi_gender = comp.apply(tensor)
# Check
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF))
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF))
def test_age_gender_infer_batch(self):
skip_if_openvino_not_available()
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013'
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
device_id = 'CPU'
img_path1 = self.find_file('cv/face/david1.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
img_path2 = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
img1 = cv.imread(img_path1)
img2 = cv.imread(img_path2)
# img1 and img2 have the same size
batch_img = np.array([img1, img2])
# OpenVINO
def preproc(ppp):
ppp.input().model().set_layout(Layout("NCHW"))
ppp.input().tensor().set_element_type(Type.u8) \
.set_spatial_static_shape(img1.shape[0], img2.shape[1]) \
.set_layout(Layout("NHWC"))
ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR)
ref = AgeGenderOV(model_path, bin_path, device_id)
ref.reshape(PartialShape([2, 3, 62, 62]))
ref.cfgPrePostProcessing(preproc)
ov_age, ov_gender = ref.apply(batch_img)
# OpenCV G-API
comp = AgeGenderGAPI(model_path, bin_path, device_id)
comp.pp.cfgReshape([2, 3, 62, 62]) \
.cfgInputModelLayout("NCHW") \
.cfgInputTensorLayout("NHWC") \
.cfgResize(cv.INTER_LINEAR)
gapi_age, gapi_gender = comp.apply(batch_img)
# Check
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF))
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF))
def test_age_gender_infer_planar(self):
skip_if_openvino_not_available()
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013'
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')])
device_id = 'CPU'
img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')])
img = cv.imread(img_path)
planar_img = np.transpose(img, (2, 0, 1))
planar_img = np.expand_dims(planar_img, 0)
# OpenVINO
def preproc(ppp):
ppp.input().tensor().set_element_type(Type.u8) \
.set_spatial_static_shape(img.shape[0], img.shape[1])
ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR)
ref = AgeGenderOV(model_path, bin_path, device_id)
ref.cfgPrePostProcessing(preproc)
ov_age, ov_gender = ref.apply(planar_img)
# OpenCV G-API
comp = AgeGenderGAPI(model_path, bin_path, device_id)
comp.pp.cfgResize(cv.INTER_LINEAR)
gapi_age, gapi_gender = comp.apply(planar_img)
# Check
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF))
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF))
except unittest.SkipTest as e:
message = str(e)
class TestSkip(unittest.TestCase):
def setUp(self):
self.skipTest('Skip tests: ' + message)
def test_skip():
pass
pass
if __name__ == '__main__':
NewOpenCVTests.bootstrap()

@ -62,7 +62,7 @@ namespace opencv_test
class InRangePerfTest : public TestPerfParams<tuple<compare_f, cv::Size, MatType, cv::GCompileArgs>> {};
class Split3PerfTest : public TestPerfParams<tuple<compare_f, cv::Size, cv::GCompileArgs>> {};
class Split4PerfTest : public TestPerfParams<tuple<compare_f, cv::Size, cv::GCompileArgs>> {};
class Merge3PerfTest : public TestPerfParams<tuple<compare_f, cv::Size, cv::GCompileArgs>> {};
class Merge3PerfTest : public TestPerfParams<tuple<compare_f, cv::Size, MatType, cv::GCompileArgs>> {};
class Merge4PerfTest : public TestPerfParams<tuple<compare_f, cv::Size, cv::GCompileArgs>> {};
class RemapPerfTest : public TestPerfParams<tuple<compare_f, cv::Size, MatType, cv::GCompileArgs>> {};
class FlipPerfTest : public TestPerfParams<tuple<compare_f, cv::Size, MatType, int, cv::GCompileArgs>> {};

@ -1577,11 +1577,12 @@ PERF_TEST_P_(Merge3PerfTest, TestPerformance)
{
compare_f cmpF;
cv::Size sz;
MatType type = -1;
cv::GCompileArgs compile_args;
std::tie(cmpF, sz, compile_args) = GetParam();
std::tie(cmpF, sz, type, compile_args) = GetParam();
initMatsRandU(CV_8UC1, sz, CV_8UC3);
cv::Mat in_mat3(sz, CV_8UC1);
initMatsRandU(type, sz, CV_MAKETYPE(type, 3));
cv::Mat in_mat3(sz, type);
cv::Scalar mean = cv::Scalar::all(127);
cv::Scalar stddev = cv::Scalar::all(40.f);
cv::randn(in_mat3, mean, stddev);

@ -252,6 +252,7 @@ INSTANTIATE_TEST_CASE_P(Split4PerfTestCPU, Split4PerfTest,
INSTANTIATE_TEST_CASE_P(Merge3PerfTestCPU, Merge3PerfTest,
Combine(Values(AbsExact().to_compare_f()),
Values(szSmall128, szVGA, sz720p, sz1080p),
Values(CV_8U),
Values(cv::compile_args(CORE_CPU))));
INSTANTIATE_TEST_CASE_P(Merge4PerfTestCPU, Merge4PerfTest,

@ -253,6 +253,7 @@ INSTANTIATE_TEST_CASE_P(Split4PerfTestFluid, Split4PerfTest,
INSTANTIATE_TEST_CASE_P(Merge3PerfTestFluid, Merge3PerfTest,
Combine(Values(AbsExact().to_compare_f()),
Values(szSmall128, szVGA, sz720p, sz1080p),
Values(CV_8U, CV_16S, CV_16U, CV_32F),
Values(cv::compile_args(CORE_FLUID))));
INSTANTIATE_TEST_CASE_P(Merge4PerfTestFluid, Merge4PerfTest,

@ -242,6 +242,7 @@ INSTANTIATE_TEST_CASE_P(Split4PerfTestGPU, Split4PerfTest,
INSTANTIATE_TEST_CASE_P(Merge3PerfTestGPU, Merge3PerfTest,
Combine(Values(AbsExact().to_compare_f()),
Values( szSmall128, szVGA, sz720p, sz1080p ),
Values(CV_8U),
Values(cv::compile_args(CORE_GPU))));
INSTANTIATE_TEST_CASE_P(Merge4PerfTestGPU, Merge4PerfTest,

@ -5,34 +5,41 @@
#include <opencv2/gapi/operators.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/gapi/streaming/desync.hpp>
#include <opencv2/gapi/streaming/format.hpp>
#include <iomanip>
const std::string keys =
"{ h help | | Print this help message }"
"{ desync | false | Desynchronize inference }"
"{ input | | Path to the input video file }"
"{ output | | Path to the output video file }"
"{ ssm | semantic-segmentation-adas-0001.xml | Path to OpenVINO IE semantic segmentation model (.xml) }";
// 20 colors for 20 classes of semantic-segmentation-adas-0001
const std::vector<cv::Vec3b> colors = {
{ 128, 64, 128 },
{ 232, 35, 244 },
{ 70, 70, 70 },
{ 156, 102, 102 },
{ 153, 153, 190 },
{ 153, 153, 153 },
{ 30, 170, 250 },
{ 0, 220, 220 },
{ 35, 142, 107 },
{ 152, 251, 152 },
{ 180, 130, 70 },
{ 60, 20, 220 },
{ 0, 0, 255 },
{ 142, 0, 0 },
{ 70, 0, 0 },
{ 100, 60, 0 },
{ 90, 0, 0 },
{ 230, 0, 0 },
{ 32, 11, 119 },
{ 0, 74, 111 },
static std::vector<cv::Vec3b> colors = {
{ 0, 0, 0 },
{ 0, 0, 128 },
{ 0, 128, 0 },
{ 0, 128, 128 },
{ 128, 0, 0 },
{ 128, 0, 128 },
{ 128, 128, 0 },
{ 128, 128, 128 },
{ 0, 0, 64 },
{ 0, 0, 192 },
{ 0, 128, 64 },
{ 0, 128, 192 },
{ 128, 0, 64 },
{ 128, 0, 192 },
{ 128, 128, 64 },
{ 128, 128, 192 },
{ 0, 64, 0 },
{ 0, 64, 128 },
{ 0, 192, 0 },
{ 0, 192, 128 },
{ 128, 64, 0 }
};
namespace {
@ -43,12 +50,23 @@ std::string get_weights_path(const std::string &model_path) {
auto ext = model_path.substr(sz - EXT_LEN);
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){
return static_cast<unsigned char>(std::tolower(c));
});
return static_cast<unsigned char>(std::tolower(c));
});
CV_Assert(ext == ".xml");
return model_path.substr(0u, sz - EXT_LEN) + ".bin";
}
bool isNumber(const std::string &str) {
return !str.empty() && std::all_of(str.begin(), str.end(),
[](unsigned char ch) { return std::isdigit(ch); });
}
std::string toStr(double value) {
std::stringstream ss;
ss << std::fixed << std::setprecision(1) << value;
return ss.str();
}
void classesToColors(const cv::Mat &out_blob,
cv::Mat &mask_img) {
const int H = out_blob.size[0];
@ -97,6 +115,25 @@ void probsToClasses(const cv::Mat& probs, cv::Mat& classes) {
} // anonymous namespace
namespace vis {
static void putText(cv::Mat& mat, const cv::Point &position, const std::string &message) {
auto fontFace = cv::FONT_HERSHEY_COMPLEX;
int thickness = 2;
cv::Scalar color = {200, 10, 10};
double fontScale = 0.65;
cv::putText(mat, message, position, fontFace,
fontScale, cv::Scalar(255, 255, 255), thickness + 1);
cv::putText(mat, message, position, fontFace, fontScale, color, thickness);
}
static void drawResults(cv::Mat &img, const cv::Mat &color_mask) {
img = img / 2 + color_mask / 2;
}
} // namespace vis
namespace custom {
G_API_OP(PostProcessing, <cv::GMat(cv::GMat, cv::GMat)>, "sample.custom.post_processing") {
static cv::GMatDesc outMeta(const cv::GMatDesc &in, const cv::GMatDesc &) {
@ -106,19 +143,34 @@ G_API_OP(PostProcessing, <cv::GMat(cv::GMat, cv::GMat)>, "sample.custom.post_pro
GAPI_OCV_KERNEL(OCVPostProcessing, PostProcessing) {
static void run(const cv::Mat &in, const cv::Mat &out_blob, cv::Mat &out) {
int C = -1, H = -1, W = -1;
if (out_blob.size.dims() == 4u) {
C = 1; H = 2, W = 3;
} else if (out_blob.size.dims() == 3u) {
C = 0; H = 1, W = 2;
} else {
throw std::logic_error(
"Number of dimmensions for model output must be 3 or 4!");
}
cv::Mat classes;
// NB: If output has more than single plane, it contains probabilities
// otherwise class id.
if (out_blob.size[1] > 1) {
if (out_blob.size[C] > 1) {
probsToClasses(out_blob, classes);
} else {
out_blob.convertTo(classes, CV_8UC1);
classes = classes.reshape(1, out_blob.size[2]);
if (out_blob.depth() != CV_32S) {
throw std::logic_error(
"Single channel output must have integer precision!");
}
cv::Mat view(out_blob.size[H], // cols
out_blob.size[W], // rows
CV_32SC1,
out_blob.data);
view.convertTo(classes, CV_8UC1);
}
cv::Mat mask_img;
classesToColors(classes, mask_img);
cv::resize(mask_img, out, in.size());
cv::resize(mask_img, out, in.size(), 0, 0, cv::INTER_NEAREST);
}
};
} // namespace custom
@ -134,6 +186,7 @@ int main(int argc, char *argv[]) {
const std::string input = cmd.get<std::string>("input");
const std::string output = cmd.get<std::string>("output");
const auto model_path = cmd.get<std::string>("ssm");
const bool desync = cmd.get<bool>("desync");
const auto weights_path = get_weights_path(model_path);
const auto device = "CPU";
G_API_NET(SemSegmNet, <cv::GMat(cv::GMat)>, "semantic-segmentation");
@ -145,40 +198,87 @@ int main(int argc, char *argv[]) {
// Now build the graph
cv::GMat in;
cv::GMat out_blob = cv::gapi::infer<SemSegmNet>(in);
cv::GMat post_proc_out = custom::PostProcessing::on(in, out_blob);
cv::GMat blending_in = in * 0.3f;
cv::GMat blending_out = post_proc_out * 0.7f;
cv::GMat out = blending_in + blending_out;
cv::GMat bgr = cv::gapi::copy(in);
cv::GMat frame = desync ? cv::gapi::streaming::desync(bgr) : bgr;
cv::GMat out_blob = cv::gapi::infer<SemSegmNet>(frame);
cv::GMat out = custom::PostProcessing::on(frame, out_blob);
cv::GStreamingCompiled pipeline = cv::GComputation(cv::GIn(in), cv::GOut(out))
.compileStreaming(cv::compile_args(kernels, networks));
auto inputs = cv::gin(cv::gapi::wip::make_src<cv::gapi::wip::GCaptureSource>(input));
cv::GStreamingCompiled pipeline = cv::GComputation(cv::GIn(in), cv::GOut(bgr, out))
.compileStreaming(cv::compile_args(kernels, networks,
cv::gapi::streaming::queue_capacity{1}));
std::shared_ptr<cv::gapi::wip::GCaptureSource> source;
if (isNumber(input)) {
source = std::make_shared<cv::gapi::wip::GCaptureSource>(
std::stoi(input),
std::map<int, double> {
{cv::CAP_PROP_FRAME_WIDTH, 1280},
{cv::CAP_PROP_FRAME_HEIGHT, 720},
{cv::CAP_PROP_BUFFERSIZE, 1},
{cv::CAP_PROP_AUTOFOCUS, true}
}
);
} else {
source = std::make_shared<cv::gapi::wip::GCaptureSource>(input);
}
auto inputs = cv::gin(
static_cast<cv::gapi::wip::IStreamSource::Ptr>(source));
// The execution part
pipeline.setSource(std::move(inputs));
cv::VideoWriter writer;
cv::TickMeter tm;
cv::Mat outMat;
cv::VideoWriter writer;
cv::util::optional<cv::Mat> color_mask;
cv::util::optional<cv::Mat> image;
cv::Mat last_image;
cv::Mat last_color_mask;
std::size_t frames = 0u;
tm.start();
pipeline.start();
while (pipeline.pull(cv::gout(outMat))) {
++frames;
cv::imshow("Out", outMat);
cv::waitKey(1);
if (!output.empty()) {
if (!writer.isOpened()) {
const auto sz = cv::Size{outMat.cols, outMat.rows};
writer.open(output, cv::VideoWriter::fourcc('M','J','P','G'), 25.0, sz);
CV_Assert(writer.isOpened());
tm.start();
std::size_t frames = 0u;
std::size_t masks = 0u;
while (pipeline.pull(cv::gout(image, color_mask))) {
if (image.has_value()) {
++frames;
last_image = std::move(*image);
}
if (color_mask.has_value()) {
++masks;
last_color_mask = std::move(*color_mask);
}
if (!last_image.empty() && !last_color_mask.empty()) {
tm.stop();
std::string stream_fps = "Stream FPS: " + toStr(frames / tm.getTimeSec());
std::string inference_fps = "Inference FPS: " + toStr(masks / tm.getTimeSec());
cv::Mat tmp = last_image.clone();
vis::drawResults(tmp, last_color_mask);
vis::putText(tmp, {10, 22}, stream_fps);
vis::putText(tmp, {10, 22 + 30}, inference_fps);
cv::imshow("Out", tmp);
cv::waitKey(1);
if (!output.empty()) {
if (!writer.isOpened()) {
const auto sz = cv::Size{tmp.cols, tmp.rows};
writer.open(output, cv::VideoWriter::fourcc('M','J','P','G'), 25.0, sz);
CV_Assert(writer.isOpened());
}
writer << tmp;
}
writer << outMat;
tm.start();
}
}
tm.stop();
std::cout << "Processed " << frames << " frames" << " (" << frames / tm.getTimeSec() << " FPS)" << std::endl;
std::cout << "Processed " << frames << " frames" << " ("
<< frames / tm.getTimeSec()<< " FPS)" << std::endl;
return 0;
}

@ -153,10 +153,18 @@ std::ostream& operator<<(std::ostream& os, const cv::GMatDesc &desc)
break;
}
os << "C" << desc.chan;
if (desc.planar) os << "p";
os << " ";
os << desc.size.width << "x" << desc.size.height;
if (desc.isND()) {
os << " [";
for (size_t i = 0; i < desc.dims.size() - 1; ++i) {
os << desc.dims[i] << "x";
}
os << desc.dims.back() << "]";
} else {
os << "C" << desc.chan;
if (desc.planar) os << "p";
os << " ";
os << desc.size.width << "x" << desc.size.height;
}
return os;
}

@ -227,6 +227,12 @@ inline void convertInt64ToInt32(const int64_t* src, int* dst, size_t size)
[](int64_t el) { return static_cast<int>(el); });
}
inline void convertInt32ToInt64(const int* src, int64_t* dst, size_t size)
{
std::transform(src, src + size, dst,
[](int el) { return static_cast<int64_t>(el); });
}
}} // cv::gimpl
#endif // OPENCV_GAPI_GBACKEND_HPP

@ -2320,12 +2320,15 @@ GAPI_FLUID_KERNEL(GFluidSplit3, cv::gapi::core::GSplit3, false)
static void run(const View &src, Buffer &dst1, Buffer &dst2, Buffer &dst3)
{
GAPI_Assert((src.meta().depth == CV_8U) && (dst1.meta().depth == CV_8U) &&
(dst2.meta().depth == CV_8U) && (dst3.meta().depth == CV_8U) &&
(3 == src.meta().chan));
const auto *in = src.InLine<uchar>(0);
auto *out1 = dst1.OutLine<uchar>();
auto *out2 = dst2.OutLine<uchar>();
auto *out3 = dst3.OutLine<uchar>();
GAPI_Assert(3 == src.meta().chan);
int width = src.length();
int w = 0;
@ -2348,13 +2351,16 @@ GAPI_FLUID_KERNEL(GFluidSplit4, cv::gapi::core::GSplit4, false)
static void run(const View &src, Buffer &dst1, Buffer &dst2, Buffer &dst3, Buffer &dst4)
{
GAPI_Assert((src.meta().depth == CV_8U) && (dst1.meta().depth == CV_8U) &&
(dst2.meta().depth == CV_8U) && (dst3.meta().depth == CV_8U) &&
(dst4.meta().depth == CV_8U) && (4 == src.meta().chan));
const auto *in = src.InLine<uchar>(0);
auto *out1 = dst1.OutLine<uchar>();
auto *out2 = dst2.OutLine<uchar>();
auto *out3 = dst3.OutLine<uchar>();
auto *out4 = dst4.OutLine<uchar>();
GAPI_Assert(4 == src.meta().chan);
int width = src.length();
int w = 0;
@ -2372,31 +2378,46 @@ GAPI_FLUID_KERNEL(GFluidSplit4, cv::gapi::core::GSplit4, false)
}
};
template<typename T>
CV_ALWAYS_INLINE void run_merge3(Buffer& dst, const View& src1, const View& src2, const View& src3)
{
const auto* in1 = src1.InLine<T>(0);
const auto* in2 = src2.InLine<T>(0);
const auto* in3 = src3.InLine<T>(0);
auto* out = dst.OutLine<T>();
int width = dst.length();
int w = 0;
#if CV_SIMD
w = merge3_simd(in1, in2, in3, out, width);
#endif
for (; w < width; w++)
{
out[3 * w] = in1[w];
out[3 * w + 1] = in2[w];
out[3 * w + 2] = in3[w];
}
}
GAPI_FLUID_KERNEL(GFluidMerge3, cv::gapi::core::GMerge3, false)
{
static const int Window = 1;
static void run(const View &src1, const View &src2, const View &src3, Buffer &dst)
static void run(const View& src1, const View& src2, const View& src3, Buffer& dst)
{
const auto *in1 = src1.InLine<uchar>(0);
const auto *in2 = src2.InLine<uchar>(0);
const auto *in3 = src3.InLine<uchar>(0);
auto *out = dst.OutLine<uchar>();
GAPI_Assert(3 == dst.meta().chan);
int width = dst.length();
int w = 0;
GAPI_Assert((src1.meta().depth == dst.meta().depth) &&
(src1.meta().depth == src2.meta().depth) &&
(src1.meta().depth == src3.meta().depth));
#if CV_SIMD
w = merge3_simd(in1, in2, in3, out, width);
#endif
// SRC/DST TYPE OP __VA_ARGS__
MERGE3_(uchar, run_merge3, dst, src1, src2, src3);
MERGE3_(ushort, run_merge3, dst, src1, src2, src3);
MERGE3_(short, run_merge3, dst, src1, src2, src3);
MERGE3_(float, run_merge3, dst, src1, src2, src3);
for (; w < width; w++)
{
out[3*w ] = in1[w];
out[3*w + 1] = in2[w];
out[3*w + 2] = in3[w];
}
CV_Error(cv::Error::StsBadArg, "unsupported combination of types");
}
};
@ -2407,13 +2428,16 @@ GAPI_FLUID_KERNEL(GFluidMerge4, cv::gapi::core::GMerge4, false)
static void run(const View &src1, const View &src2, const View &src3, const View &src4,
Buffer &dst)
{
GAPI_Assert((dst.meta().depth == CV_8U) && (src1.meta().depth == CV_8U) &&
(src2.meta().depth == CV_8U) && (src3.meta().depth == CV_8U) &&
(4 == dst.meta().chan));
const auto *in1 = src1.InLine<uchar>(0);
const auto *in2 = src2.InLine<uchar>(0);
const auto *in3 = src3.InLine<uchar>(0);
const auto *in4 = src4.InLine<uchar>(0);
auto *out = dst.OutLine<uchar>();
GAPI_Assert(4 == dst.meta().chan);
int width = dst.length();
int w = 0; // cycle counter

@ -277,13 +277,21 @@ int split4_simd(const uchar in[], uchar out1[], uchar out2[],
CV_CPU_DISPATCH_MODES_ALL);
}
int merge3_simd(const uchar in1[], const uchar in2[], const uchar in3[],
uchar out[], const int width)
{
CV_CPU_DISPATCH(merge3_simd, (in1, in2, in3, out, width),
CV_CPU_DISPATCH_MODES_ALL);
#define MERGE3_SIMD(T) \
int merge3_simd(const T in1[], const T in2[], const T in3[], \
T out[], const int width) \
{ \
CV_CPU_DISPATCH(merge3_simd, (in1, in2, in3, out, width), \
CV_CPU_DISPATCH_MODES_ALL); \
}
MERGE3_SIMD(uchar)
MERGE3_SIMD(short)
MERGE3_SIMD(ushort)
MERGE3_SIMD(float)
#undef MERGE3_SIMD
int merge4_simd(const uchar in1[], const uchar in2[], const uchar in3[],
const uchar in4[], uchar out[], const int width)
{

@ -216,8 +216,16 @@ int split3_simd(const uchar in[], uchar out1[], uchar out2[],
int split4_simd(const uchar in[], uchar out1[], uchar out2[],
uchar out3[], uchar out4[], const int width);
int merge3_simd(const uchar in1[], const uchar in2[], const uchar in3[],
uchar out[], const int width);
#define MERGE3_SIMD(T) \
int merge3_simd(const T in1[], const T in2[], const T in3[], \
T out[], const int width);
MERGE3_SIMD(uchar)
MERGE3_SIMD(short)
MERGE3_SIMD(ushort)
MERGE3_SIMD(float)
#undef MERGE3_SIMD
int merge4_simd(const uchar in1[], const uchar in2[], const uchar in3[],
const uchar in4[], uchar out[], const int width);

@ -322,12 +322,21 @@ int split3_simd(const uchar in[], uchar out1[], uchar out2[],
int split4_simd(const uchar in[], uchar out1[], uchar out2[],
uchar out3[], uchar out4[], const int width);
int merge3_simd(const uchar in1[], const uchar in2[], const uchar in3[],
uchar out[], const int width);
#define MERGE3_SIMD(T) \
int merge3_simd(const T in1[], const T in2[], const T in3[], \
T out[], const int width);
MERGE3_SIMD(uchar)
MERGE3_SIMD(short)
MERGE3_SIMD(ushort)
MERGE3_SIMD(float)
#undef MERGE3_SIMD
int merge4_simd(const uchar in1[], const uchar in2[], const uchar in3[],
const uchar in4[], uchar out[], const int width);
#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
#define SRC_SHORT_OR_USHORT std::is_same<SRC, short>::value || std::is_same<SRC, ushort>::value
@ -2530,33 +2539,41 @@ int split4_simd(const uchar in[], uchar out1[], uchar out2[],
//
//-------------------------
int merge3_simd(const uchar in1[], const uchar in2[], const uchar in3[],
uchar out[], const int width)
{
constexpr int nlanes = v_uint8::nlanes;
if (width < nlanes)
return 0;
int x = 0;
for (;;)
{
for (; x <= width - nlanes; x += nlanes)
{
v_uint8 a, b, c;
a = vx_load(&in1[x]);
b = vx_load(&in2[x]);
c = vx_load(&in3[x]);
v_store_interleave(&out[3 * x], a, b, c);
}
if (x < width)
{
x = width - nlanes;
continue;
}
break;
}
return x;
}
#define MERGE3_SIMD(T) \
int merge3_simd(const T in1[], const T in2[], const T in3[], \
T out[], const int width) \
{ \
constexpr int nlanes = vector_type_of_t<T>::nlanes; \
if (width < nlanes) \
return 0; \
\
int x = 0; \
for (;;) \
{ \
for (; x <= width - nlanes; x += nlanes) \
{ \
vector_type_of_t<T> a, b, c; \
a = vx_load(&in1[x]); \
b = vx_load(&in2[x]); \
c = vx_load(&in3[x]); \
v_store_interleave(&out[3 * x], a, b, c); \
} \
if (x < width) \
{ \
x = width - nlanes; \
continue; \
} \
break; \
} \
return x; \
}
MERGE3_SIMD(uchar)
MERGE3_SIMD(short)
MERGE3_SIMD(ushort)
MERGE3_SIMD(float)
#undef MERGE3_SIMD
//-------------------------
//
@ -2926,6 +2943,8 @@ CV_ALWAYS_INLINE void convertto_simd_nocoeff_impl(const SRC* inx, float* outx)
int convertto_simd(const SRC in[], DST out[], const int length) \
{ \
constexpr int nlanes = vector_type_of_t<DST>::nlanes; \
if (length < nlanes) \
return 0; \
\
int x = 0; \
for (;;) \
@ -3093,6 +3112,9 @@ int convertto_scaled_simd(const SRC in[], DST out[], const float alpha, \
const float beta, const int length) \
{ \
constexpr int nlanes = vector_type_of_t<DST>::nlanes; \
if (length < nlanes) \
return 0; \
\
v_float32 v_alpha = vx_setall_f32(alpha); \
v_float32 v_beta = vx_setall_f32(beta); \
\

@ -86,6 +86,23 @@ using cv::gapi::own::rintd;
return; \
}
#define MERGE3_(T, OP, ...) \
if (cv::DataType<T>::depth == dst.meta().depth && \
cv::DataType<T>::depth == src1.meta().depth) \
{ \
GAPI_DbgAssert(dst.length() == src1.length()); \
GAPI_DbgAssert(dst.length() == src2.length()); \
GAPI_DbgAssert(dst.length() == src3.length()); \
\
GAPI_DbgAssert(1 == src1.meta().chan); \
GAPI_DbgAssert(1 == src2.meta().chan); \
GAPI_DbgAssert(1 == src3.meta().chan); \
GAPI_DbgAssert(3 == dst.meta().chan); \
\
OP<T>(__VA_ARGS__); \
return; \
}
} // namespace fluid
} // namespace gapi
} // namespace cv

@ -2,7 +2,7 @@
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018-2022 Intel Corporation
// Copyright (C) 2018-2023 Intel Corporation
#include "precomp.hpp"
@ -71,6 +71,28 @@ namespace IE = InferenceEngine;
namespace {
IE::Layout toIE(const std::string &layout) {
const std::unordered_map<std::string, IE::Layout> layouts = {
{"NCDHW", IE::Layout::NCDHW},
{"NDHWC", IE::Layout::NDHWC},
{"NHWC" , IE::Layout::NHWC },
{"NCHW" , IE::Layout::NCHW },
{"CHW" , IE::Layout::CHW },
{"HWC" , IE::Layout::HWC },
{"HW" , IE::Layout::HW },
{"NC" , IE::Layout::NC },
{"CN" , IE::Layout::CN },
{"C" , IE::Layout::C },
};
const auto it = layouts.find(layout);
if (it == layouts.end()) {
cv::util::throw_error(
std::logic_error("IE Backend: Unsupported layout: " + layout));
}
return it->second;
};
inline IE::ROI toIE(const cv::Rect &rc) {
return IE::ROI
{ 0u
@ -130,11 +152,90 @@ inline int toCV(IE::Precision prec) {
return -1;
}
inline IE::ResizeAlgorithm toIEInterp(int interpolation) {
switch (interpolation) {
case cv::INTER_LINEAR: return IE::RESIZE_BILINEAR;
case cv::INTER_AREA: return IE::RESIZE_AREA;
default: GAPI_Error("IE Backend: Unsupported resize algorithm");
}
// Unreachable code
GAPI_Assert(false);
}
template <typename Attr>
using AttrMap = cv::gapi::ie::detail::AttrMap<Attr>;
template <typename Attr>
using LayerVariantAttr = cv::gapi::ie::detail::LayerVariantAttr<Attr>;
template <typename Attr> AttrMap<Attr>
broadcastLayerAttr(const LayerVariantAttr<Attr> &layer_attr,
const std::vector<std::string> &layer_names) {
AttrMap<Attr> map;
if (cv::util::holds_alternative<AttrMap<Attr>>(layer_attr)) {
map = cv::util::get<AttrMap<Attr>>(layer_attr);
// NB: Validate map:
std::unordered_set<std::string> existing_layers =
{layer_names.begin(), layer_names.end()};
for (const auto &p : map) {
const auto it = existing_layers.find(p.first);
if (it == existing_layers.end()) {
cv::util::throw_error(
std::logic_error("IE Backend: Failed to"
" find layer with name: " + p.first));
}
}
} else if (cv::util::holds_alternative<Attr>(layer_attr)) {
// NB: Broadcast value to all layers.
auto elem = cv::util::get<Attr>(layer_attr);
for (auto &&layer_name : layer_names) {
map.emplace(layer_name, elem);
}
}
return map;
}
// TODO: Move it to some common place
template <typename K, typename V>
cv::optional<V> lookUp(const std::map<K, V> &map, const K& key) {
const auto it = map.find(key);
if (it == map.end()) {
return {};
}
return cv::util::make_optional(std::move(it->second));
}
static bool isImage(const cv::GMatDesc &desc,
const IE::SizeVector &model_dims) {
return (model_dims.size() == 4u) &&
(!desc.isND()) /* dims == 2 */ &&
(desc.chan == 1 || desc.chan == 3) &&
(desc.size.height != 1 && desc.size.width != 1) &&
(desc.depth == CV_8U);
}
cv::gapi::ie::TraitAs clarifyTrait(const cv::GMatDesc &mat_desc,
const IE::SizeVector &model_dims) {
if (isImage(mat_desc, model_dims)) {
return cv::gapi::ie::TraitAs::IMAGE;
}
return cv::gapi::ie::TraitAs::TENSOR;
}
cv::gapi::ie::TraitAs clarifyTrait(const cv::GMetaArg &meta,
const IE::SizeVector &model_dims) {
// NB: All media formats: BGR, NV12, Gray
// are traited as image.
if (cv::util::holds_alternative<cv::GFrameDesc>(meta)) {
return cv::gapi::ie::TraitAs::IMAGE;
}
GAPI_Assert(cv::util::holds_alternative<cv::GMatDesc>(meta));
return clarifyTrait(cv::util::get<cv::GMatDesc>(meta), model_dims);
}
inline IE::TensorDesc toIE(const cv::Mat &mat, cv::gapi::ie::TraitAs hint) {
const auto &sz = mat.size;
// NB: For some reason RGB image is 2D image
// (since channel component is not counted here).
// Note: regular 2D vectors also fall into this category
if (sz.dims() == 2 && hint == cv::gapi::ie::TraitAs::IMAGE)
{
// NB: This logic is mainly taken from IE samples
@ -155,8 +256,72 @@ inline IE::TensorDesc toIE(const cv::Mat &mat, cv::gapi::ie::TraitAs hint) {
return IE::TensorDesc(toIE(mat.depth()), toIE(sz), toIELayout(sz.dims()));
}
inline IE::Blob::Ptr wrapIE(const cv::Mat &mat, cv::gapi::ie::TraitAs hint) {
const auto tDesc = toIE(mat, hint);
// NB: Inference dimmensions always follow NCDHW order
// even though the real layout is different.
// E.g if user provided Mat({1, 240, 320, 3}, CV_8U) + NHWC layout
// need to create Blob(U8, {1, 3, 240, 320}, NHWC).
inline IE::SizeVector toIEDims(const IE::SizeVector &dims,
const IE::Layout layout) {
switch (layout) {
case IE::Layout::NDHWC: // NCDHW
return {dims[0], dims[4], dims[1], dims[2], dims[3]};
case IE::Layout::NHWC: // NCHW
return {dims[0], dims[3], dims[1], dims[2]};
case IE::Layout::HWC: // CHW
return {dims[2], dims[0], dims[1]};
default: return dims;
}
GAPI_Assert(false);
}
// NB: Inference dimmensions always follow NCDHW order
// even though the real layout is different.
// E.g if U8 blob has {1, 3, 240, 320} dims and NHWC layout
// need to create cv::Mat({1, 240, 320, 3}, CV_8U);
inline std::vector<int> toCVDims(const std::vector<int> &dims,
const IE::Layout layout) {
switch (layout) {
case IE::Layout::NDHWC: // NCDHW
return {dims[0], dims[2], dims[3], dims[4], dims[1]};
case IE::Layout::NHWC: // NCHW
return {dims[0], dims[2], dims[3], dims[1]};
case IE::Layout::HWC: // CHW
return {dims[1], dims[2], dims[0]};
default: return dims;
}
GAPI_Assert(false);
}
inline IE::TensorDesc toIE(const cv::Mat &mat,
const cv::gapi::ie::TraitAs hint,
const IE::Layout layout) {
const auto &sz = mat.size;
if (sz.dims() == 2 && hint == cv::gapi::ie::TraitAs::IMAGE)
{
// NB: This logic is mainly taken from IE samples
const size_t channels = mat.channels();
const size_t height = mat.size().height;
const size_t width = mat.size().width;
const size_t strideH = mat.step1();
IE::BlockingDesc bdesc({1, height, width, channels} /* blocking dims */,
{0, 2, 3, 1} /* order for NHWC */,
0 /* offset */,
{0, 0, 0, 0} /* offsets for dims */,
{strideH * height, strideH, channels, 1} /* strides for dims */);
return IE::TensorDesc(toIE(mat.depth()),
IE::SizeVector{1, channels, height, width}, bdesc);
}
return IE::TensorDesc(toIE(mat.depth()),
toIEDims(toIE(sz), layout),
layout);
}
inline IE::Blob::Ptr wrapIE(const cv::Mat &mat,
cv::gapi::ie::TraitAs hint,
const IE::Layout layout = IE::Layout::ANY) {
const auto tDesc = toIE(mat, hint, layout);
switch (mat.depth()) {
// NB: Seems there's no way to create an untyped (T-less) Blob::Ptr
// in IE given only precision via TensorDesc. So we have to do this:
@ -303,6 +468,7 @@ struct IEUnit {
};
InputFramesDesc net_input_params;
std::unordered_map<std::string, cv::gapi::ie::TraitAs> inputs_type;
explicit IEUnit(const cv::gapi::ie::detail::ParamDesc &pp)
: params(pp) {
@ -481,6 +647,8 @@ public:
cv::GRunArgP output (std::size_t idx);
cv::Mat& outMatR(std::size_t idx);
cv::gapi::ie::TraitAs getInputType(const std::string &layer_name) const;
const IEUnit &uu;
cv::gimpl::GIslandExecutable::IOutput &out;
@ -524,6 +692,9 @@ private:
// keep alive preprocessed frames
std::mutex keep_alive_frames_mutex;
std::unordered_map<req_key_t, cv::MediaFrame> keep_alive_pp_frames;
// NB: Hint to wrap input data properly into IE::Blob (see: wrapIE)
std::unordered_map<std::string, cv::gapi::ie::TraitAs> input_type;
};
IECallContext::IECallContext(const IEUnit & unit,
@ -558,6 +729,16 @@ IECallContext::IECallContext(const IEUnit &
}
}
cv::gapi::ie::TraitAs
IECallContext::getInputType(const std::string &layer_name) const {
const auto it = uu.inputs_type.find(layer_name);
if (it == uu.inputs_type.end()) {
cv::util::throw_error(std::logic_error(
"Failed to find input type for layer: \"" + layer_name + "\""));
}
return it->second;
}
const cv::GArgs& IECallContext::inArgs() const {
return m_args;
}
@ -732,7 +913,8 @@ cv::MediaFrame preprocess_frame_impl(cv::MediaFrame &&in_frame, const std::strin
inline IE::Blob::Ptr extractBlob(IECallContext& ctx,
std::size_t i,
cv::gapi::ie::TraitAs hint,
const cv::gapi::ie::TraitAs hint,
const IE::Layout &layout,
const std::string& layer_name,
const cv::util::optional<cv::Rect> &opt_roi,
cv::MediaFrame* out_keep_alive_frame = nullptr,
@ -780,7 +962,7 @@ inline IE::Blob::Ptr extractBlob(IECallContext& ctx,
return wrapIE(*(ctx.views.back()), frame.desc());
}
case cv::GShape::GMAT: {
return wrapIE(ctx.inMat(i), hint);
return wrapIE(ctx.inMat(i), hint, layout);
}
default:
GAPI_Assert("Unsupported input shape for IE backend");
@ -788,7 +970,6 @@ inline IE::Blob::Ptr extractBlob(IECallContext& ctx,
GAPI_Error("InternalError");
}
static void setBlob(InferenceEngine::InferRequest& req,
const std::string& layer_name,
const IE::Blob::Ptr& blob,
@ -1162,55 +1343,109 @@ static void configureInputReshapeByImage(const IE::InputInfo::Ptr& ii,
input_reshape_table.emplace(layer_name, input_dims);
}
static void configureInputInfo(const IE::InputInfo::Ptr& ii, const cv::GMetaArg mm) {
static void cfgInputPrecision(const IE::InputInfo::Ptr& ii, const cv::GMetaArg mm) {
switch (mm.index()) {
case cv::GMetaArg::index_of<cv::GMatDesc>():
{
ii->setPrecision(toIE(util::get<cv::GMatDesc>(mm).depth));
case cv::GMetaArg::index_of<cv::GMatDesc>(): {
const auto &desc = util::get<cv::GMatDesc>(mm);
ii->setPrecision(toIE(desc.depth));
break;
}
case cv::GMetaArg::index_of<cv::GFrameDesc>():
{
const auto &meta = util::get<cv::GFrameDesc>(mm);
switch (meta.fmt) {
case cv::MediaFormat::NV12:
ii->getPreProcess().setColorFormat(IE::ColorFormat::NV12);
break;
case cv::MediaFormat::BGR:
// NB: Do nothing
break;
case cv::MediaFormat::GRAY:
// NB: Do nothing
break;
default:
GAPI_Error("Unsupported media format for IE backend");
}
ii->setPrecision(toIE(CV_8U));
break;
}
default:
util::throw_error(std::runtime_error("Unsupported input meta for IE backend"));
}
}
static bool isApplicableForResize(const IE::TensorDesc& desc) {
const auto layout = desc.getLayout();
const auto prec = desc.getPrecision();
return (layout == IE::Layout::NCHW || layout == IE::Layout::NHWC) &&
(prec == IE::Precision::FP32 || prec == IE::Precision::U8);
static void cfgImagePreprocessing(const IE::InputInfo::Ptr &ii,
const cv::GMetaArg &mm,
const IE::ResizeAlgorithm interp) {
if (!cv::util::holds_alternative<cv::GMatDesc>(mm) &&
!cv::util::holds_alternative<cv::GFrameDesc>(mm)) {
util::throw_error(std::runtime_error("Unsupported input meta for IE backend"));
}
ii->getPreProcess().setResizeAlgorithm(interp);
if (cv::util::holds_alternative<cv::GFrameDesc>(mm)) {
const auto &meta = util::get<cv::GFrameDesc>(mm);
if (meta.fmt == cv::MediaFormat::NV12) {
ii->getPreProcess().setColorFormat(IE::ColorFormat::NV12);
}
}
}
static IE::PreProcessInfo configurePreProcInfo(const IE::InputInfo::CPtr& ii,
const cv::GMetaArg& mm) {
// NB: This function is used in order to configure
// preprocessing for "Load" case networks.
static void cfgInputPreprocessing(const cv::gapi::ie::TraitAs trait,
const IE::InputInfo::Ptr &ii,
const cv::GMetaArg &mm,
const std::string &layer_name,
const AttrMap<std::string> &layout_map,
const AttrMap<int> &interp_map) {
cfgInputPrecision(ii, mm);
const auto explicit_input_layout = lookUp(layout_map, layer_name);
const auto explicit_resize = lookUp(interp_map, layer_name);
if (trait == cv::gapi::ie::TraitAs::IMAGE) {
// NB: Image case - preprocessing is configured automatically.
GAPI_LOG_DEBUG(NULL, "IE Backend: Input: \"" <<
layer_name << " " << mm << "\" is image.");
// NB: BlockingDesc is used instead (see wrapIE)
if (explicit_input_layout) {
util::throw_error(std::logic_error("Input data provided for layer: \"" +
layer_name + "\" is recognized as \"image\". Explicitly" +
" specified layout is prohibited."));
}
const auto interp = explicit_resize ? toIEInterp(*explicit_resize)
: IE::RESIZE_BILINEAR;
cfgImagePreprocessing(ii, mm, interp);
} else {
// NB: Tensor case - preprocessing is configured only if user asked.
GAPI_LOG_DEBUG(NULL, "IE Backend: Input: \"" <<
layer_name << "\" " << mm << " is tensor.");
if (explicit_input_layout) {
GAPI_LOG_DEBUG(NULL, "IE Backend: Set input layout \"" <<
*explicit_input_layout << "\" for layer \"" << layer_name << "\"");
ii->setLayout(toIE(*explicit_input_layout));
}
if (explicit_resize) {
GAPI_LOG_DEBUG(NULL, "IE Backend: Set resize for layer \"" << layer_name << "\"");
ii->getPreProcess().setResizeAlgorithm(toIEInterp(*explicit_resize));
}
}
}
static IE::PreProcessInfo createImagePreProcInfo(const cv::GMetaArg &mm,
const IE::ResizeAlgorithm interp) {
if (!cv::util::holds_alternative<cv::GMatDesc>(mm) &&
!cv::util::holds_alternative<cv::GFrameDesc>(mm)) {
util::throw_error(std::runtime_error("Unsupported input meta for IE backend"));
}
IE::PreProcessInfo info;
info.setResizeAlgorithm(interp);
if (cv::util::holds_alternative<cv::GFrameDesc>(mm)) {
auto desc = cv::util::get<cv::GFrameDesc>(mm);
if (desc.fmt == cv::MediaFormat::NV12) {
const auto &meta = util::get<cv::GFrameDesc>(mm);
if (meta.fmt == cv::MediaFormat::NV12) {
info.setColorFormat(IE::ColorFormat::NV12);
}
}
if (isApplicableForResize(ii->getTensorDesc())) {
info.setResizeAlgorithm(IE::RESIZE_BILINEAR);
return info;
}
// NB: This function is used in order to create
// preprocessing for "Import" case networks.
static IE::PreProcessInfo createPreProcInfo(const cv::gapi::ie::TraitAs trait,
const cv::GMetaArg& mm,
const cv::optional<int> explicit_resize) {
if (trait == cv::gapi::ie::TraitAs::IMAGE) {
const auto interp = explicit_resize ? toIEInterp(*explicit_resize)
: IE::RESIZE_BILINEAR;
return createImagePreProcInfo(mm, interp);
}
// NB: In case "tensor" only resize can't be spefied for "import" models.
IE::PreProcessInfo info;
if (explicit_resize) {
info.setResizeAlgorithm(toIEInterp(*explicit_resize));
}
return info;
}
@ -1237,6 +1472,13 @@ static void configureOutputPrecision(const IE::OutputsDataMap &outputs
);
}
static void configureOutputLayout(const IE::OutputsDataMap &outputs_info,
const AttrMap<std::string> &output_layout) {
for (const auto it : output_layout) {
outputs_info.at(it.first)->setLayout(toIE(it.second));
}
}
// NB: This is a callback used by async infer
// to post outputs blobs (cv::GMat's).
static void PostOutputs(InferenceEngine::InferRequest &request,
@ -1356,6 +1598,10 @@ struct Infer: public cv::detail::KernelTag {
GAPI_Assert(uu.params.input_names.size() == in_metas.size()
&& "Known input layers count doesn't match input meta count");
const auto input_layout = broadcastLayerAttr(uu.params.input_layout,
uu.params.input_names);
const auto interpolation = broadcastLayerAttr(uu.params.interpolation,
uu.params.input_names);
// NB: Configuring input/output precision and network reshape must be done
// only in the loadNetwork case.
using namespace cv::gapi::ie::detail;
@ -1365,25 +1611,24 @@ struct Infer: public cv::detail::KernelTag {
ade::util::toRange(in_metas))) {
const auto &input_name = std::get<0>(it);
auto ii = inputs.at(input_name);
const auto & mm = std::get<1>(it);
const auto &mm = std::get<1>(it);
configureInputInfo(ii, mm);
if (uu.params.layer_names_to_reshape.find(input_name) !=
uu.params.layer_names_to_reshape.end()) {
configureInputReshapeByImage(ii, mm, input_reshape_table);
}
if (isApplicableForResize(ii->getTensorDesc())) {
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
}
const auto trait = clarifyTrait(mm, ii->getTensorDesc().getDims());
// FIXME: This is the only place where information about input type
// can be stored for the futher execution.
const_cast<IEUnit&>(uu).inputs_type.emplace(input_name, trait);
cfgInputPreprocessing(trait, ii, mm, input_name,
input_layout, interpolation);
// NB: configure input param for further preproc
if (uu.net_input_params.is_applicable(mm)) {
const_cast<IEUnit::InputFramesDesc &>(uu.net_input_params)
.set_param(input_name, ii->getTensorDesc());
}
}
for (auto &&p : uu.params.const_inputs) {
const auto ii = inputs.at(p.first);
ii->setPrecision(toIE(p.second.first.depth()));
@ -1395,6 +1640,10 @@ struct Infer: public cv::detail::KernelTag {
if (!input_reshape_table.empty()) {
const_cast<IE::CNNNetwork *>(&uu.net)->reshape(input_reshape_table);
}
const auto output_layout = broadcastLayerAttr(uu.params.output_layout,
uu.params.output_names);
configureOutputLayout(uu.net.getOutputsInfo(), output_layout);
configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision);
} else {
GAPI_Assert(uu.params.kind == ParamDesc::Kind::Import);
@ -1406,7 +1655,13 @@ struct Infer: public cv::detail::KernelTag {
const auto &input_name = std::get<0>(it);
auto ii = inputs.at(input_name);
const auto & mm = std::get<1>(it);
non_const_prepm->emplace(input_name, configurePreProcInfo(ii, mm));
const auto trait = clarifyTrait(mm, ii->getTensorDesc().getDims());
// FIXME: This is the only place where information about input type
// can be stored for the futher execution.
const_cast<IEUnit&>(uu).inputs_type.emplace(input_name, trait);
const auto explicit_resize = lookUp(interpolation, input_name);
non_const_prepm->emplace(
input_name, createPreProcInfo(trait, mm, explicit_resize));
// NB: configure input param for further preproc
if (uu.net_input_params.is_applicable(mm)) {
@ -1428,7 +1683,7 @@ struct Infer: public cv::detail::KernelTag {
: uu.this_network.GetOutputsInfo().at(out_name)->getTensorDesc();
cv::GMatDesc outm(toCV(desc.getPrecision()),
toCV(desc.getDims()));
toCVDims(toCV(desc.getDims()), desc.getLayout()));
result.emplace_back(outm);
}
return result;
@ -1444,15 +1699,10 @@ struct Infer: public cv::detail::KernelTag {
// - assumes all inputs/outputs are always Mats
for (auto i : ade::util::iota(ctx->uu.params.num_in)) {
const auto& layer_name = ctx->uu.params.input_names[i];
auto layout =
ctx->uu.this_network.GetInputsInfo().
at(layer_name)->getTensorDesc().getLayout();
auto hint =
(layout == IE::Layout::NCHW || layout == IE::Layout::NHWC)
? cv::gapi::ie::TraitAs::IMAGE : cv::gapi::ie::TraitAs::TENSOR;
const auto hint = ctx->getInputType(layer_name);
const auto layout = req.GetBlob(layer_name)->getTensorDesc().getLayout();
IE::Blob::Ptr this_blob = extractBlob(*ctx, i, hint,
layer_name,
layout, layer_name,
cv::util::optional<cv::Rect>{});
setBlob(req, layer_name, this_blob, *ctx);
}
@ -1485,20 +1735,43 @@ struct InferROI: public cv::detail::KernelTag {
const auto &input_name = uu.params.input_names.at(0);
auto &&mm = in_metas.at(1u);
const auto &tensor_desc =
(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load)
? uu.net.getInputsInfo().at(input_name)->getTensorDesc()
: uu.this_network.GetInputsInfo().at(input_name)->getTensorDesc();
if (cv::util::holds_alternative<cv::GMatDesc>(mm) ||
cv::util::holds_alternative<cv::GFrameDesc>(mm)) {
const auto trait = clarifyTrait(mm, tensor_desc.getDims());
if (trait != cv::gapi::ie::TraitAs::IMAGE) {
util::throw_error(std::runtime_error(
"IE Backend: Only image is supported"
" as the 1th argument for InferROI"));
}
} else {
util::throw_error(std::runtime_error(
"IE Backend: Unsupported input meta for"
" 1th argument for InferROI"));
}
// NB: Configuring input precision and network reshape must be done
// only in the loadNetwork case.
const auto input_layout = broadcastLayerAttr(uu.params.input_layout,
uu.params.input_names);
const auto interpolation = broadcastLayerAttr(uu.params.interpolation,
uu.params.input_names);
const auto trait = cv::gapi::ie::TraitAs::IMAGE;
if (uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load) {
// 0th is ROI, 1st is input image
auto inputs = uu.net.getInputsInfo();
auto ii = inputs.at(input_name);
configureInputInfo(ii, mm);
if (uu.params.layer_names_to_reshape.find(input_name) !=
uu.params.layer_names_to_reshape.end()) {
configureInputReshapeByImage(ii, mm, input_reshape_table);
}
if (isApplicableForResize(ii->getTensorDesc())) {
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
}
cfgInputPreprocessing(trait, ii, mm, input_name,
input_layout, interpolation);
// FIXME: This isn't the best place to call reshape function.
// Сorrect solution would be to do this in compile() method of network,
@ -1517,6 +1790,9 @@ struct InferROI: public cv::detail::KernelTag {
inputs.at(p.first)->setPrecision(toIE(p.second.first.depth()));
}
const auto output_layout = broadcastLayerAttr(uu.params.output_layout,
uu.params.output_names);
configureOutputLayout(uu.net.getOutputsInfo(), output_layout);
configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision);
} else {
GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import);
@ -1524,7 +1800,9 @@ struct InferROI: public cv::detail::KernelTag {
// FIXME: This isn't the best place to collect PreProcMap.
auto* non_const_prepm = const_cast<IEUnit::PreProcMap*>(&uu.preproc_map);
auto ii = inputs.at(input_name);
non_const_prepm->emplace(input_name, configurePreProcInfo(ii, mm));
const auto explicit_resize = lookUp(interpolation, input_name);
non_const_prepm->emplace(
input_name, createPreProcInfo(trait, mm, explicit_resize));
// NB: configure intput param for further preproc
if (uu.net_input_params.is_applicable(mm)) {
@ -1545,7 +1823,7 @@ struct InferROI: public cv::detail::KernelTag {
: uu.this_network.GetOutputsInfo().at(out_name)->getTensorDesc();
cv::GMatDesc outm(toCV(desc.getPrecision()),
toCV(desc.getDims()));
toCVDims(toCV(desc.getDims()), desc.getLayout()));
result.emplace_back(outm);
}
return result;
@ -1568,6 +1846,7 @@ struct InferROI: public cv::detail::KernelTag {
bool preprocessed = false;
IE::Blob::Ptr this_blob =
extractBlob(*ctx, 1, cv::gapi::ie::TraitAs::IMAGE,
IE::Layout::ANY,
*(ctx->uu.params.input_names.begin()),
cv::util::make_optional(this_roi),
slot_ptr, &preprocessed);
@ -1613,20 +1892,31 @@ struct InferList: public cv::detail::KernelTag {
// NB: Configuring input precision and network reshape must be done
// only in the loadNetwork case.
const auto input_layout = broadcastLayerAttr(uu.params.input_layout,
uu.params.input_names);
const auto interpolation = broadcastLayerAttr(uu.params.interpolation,
uu.params.input_names);
if (uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load) {
std::size_t idx = 1u;
auto inputs = uu.net.getInputsInfo();
for (auto &&input_name : uu.params.input_names) {
auto ii = inputs.at(input_name);
const auto & mm = in_metas[idx++];
configureInputInfo(ii, mm);
// NB: InferList expects the input starts with index 1 wil be the images.
const auto input_trait = clarifyTrait(mm, ii->getTensorDesc().getDims());
if (input_trait != cv::gapi::ie::TraitAs::IMAGE) {
util::throw_error(std::runtime_error(
"IE Backend: Only image is supported"
" as the " + std::to_string(idx) + "th argument for InferList"));
}
if (uu.params.layer_names_to_reshape.find(input_name) !=
uu.params.layer_names_to_reshape.end()) {
configureInputReshapeByImage(ii, mm, input_reshape_table);
}
if (isApplicableForResize(ii->getTensorDesc())) {
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
}
cfgInputPreprocessing(input_trait, ii, mm,
input_name, input_layout, interpolation);
}
// FIXME: This isn't the best place to call reshape function.
@ -1641,6 +1931,9 @@ struct InferList: public cv::detail::KernelTag {
ii->setPrecision(toIE(p.second.first.depth()));
}
const auto output_layout = broadcastLayerAttr(uu.params.output_layout,
uu.params.output_names);
configureOutputLayout(uu.net.getOutputsInfo(), output_layout);
configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision);
} else {
GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import);
@ -1650,7 +1943,18 @@ struct InferList: public cv::detail::KernelTag {
for (auto &&input_name : uu.params.input_names) {
auto ii = inputs.at(input_name);
const auto & mm = in_metas[idx++];
non_const_prepm->emplace(input_name, configurePreProcInfo(ii, mm));
// NB: InferList expects the input starts with index 1 wil be the images.
const auto input_trait = clarifyTrait(mm, ii->getTensorDesc().getDims());
if (input_trait != cv::gapi::ie::TraitAs::IMAGE) {
util::throw_error(std::runtime_error(
"IE Backend: Only image is supported"
" as the " + std::to_string(idx) + "th argument for InferList"));
}
const auto explicit_resize = lookUp(interpolation, input_name);
non_const_prepm->emplace(
input_name, createPreProcInfo(input_trait, mm, explicit_resize));
}
}
@ -1678,6 +1982,7 @@ struct InferList: public cv::detail::KernelTag {
// NB: This blob will be used to make roi from its, so
// it should be treated as image
IE::Blob::Ptr this_blob = extractBlob(*ctx, 1, cv::gapi::ie::TraitAs::IMAGE,
IE::Layout::ANY,
ctx->uu.params.input_names[0u],
cv::util::optional<cv::Rect>{});
@ -1688,7 +1993,7 @@ struct InferList: public cv::detail::KernelTag {
ctx->uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load
? ctx->uu.net.getOutputsInfo().at(out_name)->getTensorDesc()
: ctx->uu.this_network.GetOutputsInfo().at(out_name)->getTensorDesc();
cached_dims[i] = toCV(desc.getDims());
cached_dims[i] = toCVDims(toCV(desc.getDims()), desc.getLayout());
// FIXME: Isn't this should be done automatically
// by some resetInternalData(), etc? (Probably at the GExecutor level)
auto& out_vec = ctx->outVecR<cv::Mat>(i);
@ -1744,51 +2049,52 @@ struct InferList2: public cv::detail::KernelTag {
// "blob"-based ones)
// FIXME: this is filtering not done, actually! GArrayDesc has
// no hint for its underlying type!
const auto &input_name_0 = uu.params.input_names.front();
const auto &mm_0 = in_metas[0u];
switch (in_metas[0u].index()) {
case cv::GMetaArg::index_of<cv::GMatDesc>(): {
const auto &meta_0 = util::get<cv::GMatDesc>(mm_0);
GAPI_Assert( !meta_0.isND()
&& !meta_0.planar
&& "Only images are supported as the 0th argument");
break;
}
case cv::GMetaArg::index_of<cv::GFrameDesc>(): {
// FIXME: Is there any validation for GFrame ?
break;
const auto &tensor_desc_0 =
(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load)
? uu.net.getInputsInfo().at(input_name_0)->getTensorDesc()
: uu.this_network.GetInputsInfo().at(input_name_0)->getTensorDesc();
if (cv::util::holds_alternative<cv::GMatDesc>(mm_0) ||
cv::util::holds_alternative<cv::GFrameDesc>(mm_0)) {
const auto trait = clarifyTrait(mm_0, tensor_desc_0.getDims());
if (trait != cv::gapi::ie::TraitAs::IMAGE) {
util::throw_error(std::runtime_error(
"IE Backend: Only images is"
" supported as the 0th argument"));
}
default:
util::throw_error(std::runtime_error("Unsupported input meta for IE backend"));
}
if (util::holds_alternative<cv::GMatDesc>(mm_0)) {
const auto &meta_0 = util::get<cv::GMatDesc>(mm_0);
GAPI_Assert( !meta_0.isND()
&& !meta_0.planar
&& "Only images are supported as the 0th argument");
} else {
util::throw_error(std::runtime_error(
"IE Backend: Unsupported input meta"
" for 0th argument in IE backend"));
}
std::size_t idx = 1u;
const auto input_layout = broadcastLayerAttr(uu.params.input_layout,
uu.params.input_names);
const auto interpolation = broadcastLayerAttr(uu.params.interpolation,
uu.params.input_names);
for (auto &&input_name : uu.params.input_names) {
const auto &mm = in_metas[idx];
GAPI_Assert(util::holds_alternative<cv::GArrayDesc>(mm)
&& "Non-array inputs are not supported");
if (op.k.inKinds[idx] == cv::detail::OpaqueKind::CV_RECT) {
const auto input_trait = cv::gapi::ie::TraitAs::IMAGE;
// NB: Configuring input precision and network reshape must be done
// only in the loadNetwork case.
if (uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load) {
auto inputs = uu.net.getInputsInfo();
// This is a cv::Rect -- configure the IE preprocessing
auto ii = inputs.at(input_name);
configureInputInfo(ii, mm_0);
if (uu.params.layer_names_to_reshape.find(input_name) !=
uu.params.layer_names_to_reshape.end()) {
configureInputReshapeByImage(ii, mm_0, input_reshape_table);
}
if (isApplicableForResize(ii->getTensorDesc())) {
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
}
cfgInputPreprocessing(input_trait, ii, mm_0,
input_name, input_layout, interpolation);
for (auto &&p : uu.params.const_inputs) {
inputs.at(p.first)->setPrecision(toIE(p.second.first.depth()));
@ -1800,19 +2106,32 @@ struct InferList2: public cv::detail::KernelTag {
if (!input_reshape_table.empty()) {
const_cast<IE::CNNNetwork *>(&uu.net)->reshape(input_reshape_table);
}
const auto output_layout = broadcastLayerAttr(uu.params.output_layout,
uu.params.output_names);
configureOutputLayout(uu.net.getOutputsInfo(), output_layout);
configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision);
} else {
GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import);
auto inputs = uu.this_network.GetInputsInfo();
auto* non_const_prepm = const_cast<IEUnit::PreProcMap*>(&uu.preproc_map);
auto ii = inputs.at(input_name);
non_const_prepm->emplace(input_name, configurePreProcInfo(ii, mm_0));
const auto explicit_resize = lookUp(interpolation, input_name);
non_const_prepm->emplace(
input_name, createPreProcInfo(input_trait, mm_0, explicit_resize));
}
} else {
// This is a cv::GMat (equals to: cv::Mat)
// Just validate that it is really the type
// (other types are prohibited here)
GAPI_Assert(op.k.inKinds[idx] == cv::detail::OpaqueKind::CV_MAT);
// NB: Well, it's even impossible to specify the precision since
// there is not such info in GArray<cv::GMat>
const auto explicit_resize = lookUp(interpolation, input_name);
const auto explicit_layout = lookUp(input_layout , input_name);
if (explicit_resize || explicit_layout) {
util::throw_error(std::logic_error(
"InferList2 doesn't support preprocessing for \"tensor\"'s arguments!"));
}
}
idx++; // NB: Never forget to increment the counter
}
@ -1832,6 +2151,7 @@ struct InferList2: public cv::detail::KernelTag {
// NB: This blob will be used to make roi from its, so
// it should be treated as image
IE::Blob::Ptr blob_0 = extractBlob(*ctx, 0, cv::gapi::ie::TraitAs::IMAGE,
IE::Layout::ANY,
ctx->uu.params.input_names[0u],
cv::util::optional<cv::Rect>{});
const auto list_size = ctx->inArg<cv::detail::VectorRef>(1u).size();
@ -1851,7 +2171,7 @@ struct InferList2: public cv::detail::KernelTag {
ctx->uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Load
? ctx->uu.net.getOutputsInfo().at(out_name)->getTensorDesc()
: ctx->uu.this_network.GetOutputsInfo().at(out_name)->getTensorDesc();
cached_dims[i] = toCV(desc.getDims());
cached_dims[i] = toCVDims(toCV(desc.getDims()), desc.getLayout());
// FIXME: Isn't this should be done automatically
// by some resetInternalData(), etc? (Probably at the GExecutor level)
auto& out_vec = ctx->outVecR<cv::Mat>(i);
@ -1874,8 +2194,10 @@ struct InferList2: public cv::detail::KernelTag {
} else if (this_vec.getKind() == cv::detail::OpaqueKind::CV_MAT) {
const auto &vec = this_vec.rref<cv::Mat>();
const auto &mat = vec[list_idx];
setBlob(req, ctx->uu.params.input_names[in_idx],
wrapIE(mat, cv::gapi::ie::TraitAs::TENSOR),
const auto layer_name = ctx->uu.params.input_names[in_idx];
const auto layout = req.GetBlob(layer_name)->getTensorDesc().getLayout();
setBlob(req, layer_name,
wrapIE(mat, cv::gapi::ie::TraitAs::TENSOR, layout),
*ctx);
} else {
GAPI_Assert(false &&

@ -0,0 +1,168 @@
#include <opencv2/gapi/infer/bindings_ov.hpp>
cv::gapi::ov::PyParams::PyParams(const std::string &tag,
const std::string &model_path,
const std::string &bin_path,
const std::string &device)
: m_priv(std::make_shared<Params<cv::gapi::Generic>>(tag, model_path, bin_path, device)) {
}
cv::gapi::ov::PyParams::PyParams(const std::string &tag,
const std::string &blob_path,
const std::string &device)
: m_priv(std::make_shared<Params<cv::gapi::Generic>>(tag, blob_path, device)) {
}
cv::gapi::GBackend cv::gapi::ov::PyParams::backend() const {
return m_priv->backend();
}
std::string cv::gapi::ov::PyParams::tag() const {
return m_priv->tag();
}
cv::util::any cv::gapi::ov::PyParams::params() const {
return m_priv->params();
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgPluginConfig(
const std::map<std::string, std::string> &config) {
m_priv->cfgPluginConfig(config);
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgInputTensorLayout(std::string tensor_layout) {
m_priv->cfgInputTensorLayout(std::move(tensor_layout));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgInputTensorLayout(
std::map<std::string, std::string> layout_map) {
m_priv->cfgInputTensorLayout(std::move(layout_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgInputModelLayout(std::string tensor_layout) {
m_priv->cfgInputModelLayout(std::move(tensor_layout));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgInputModelLayout(
std::map<std::string, std::string> layout_map) {
m_priv->cfgInputModelLayout(std::move(layout_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgOutputTensorLayout(std::string tensor_layout) {
m_priv->cfgOutputTensorLayout(std::move(tensor_layout));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgOutputTensorLayout(
std::map<std::string, std::string> layout_map) {
m_priv->cfgOutputTensorLayout(std::move(layout_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgOutputModelLayout(std::string tensor_layout) {
m_priv->cfgOutputModelLayout(std::move(tensor_layout));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgOutputModelLayout(
std::map<std::string, std::string> layout_map) {
m_priv->cfgOutputModelLayout(std::move(layout_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgOutputTensorPrecision(int precision) {
m_priv->cfgOutputTensorPrecision(precision);
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgOutputTensorPrecision(
std::map<std::string, int> precision_map) {
m_priv->cfgOutputTensorPrecision(precision_map);
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgReshape(std::vector<size_t> new_shape) {
m_priv->cfgReshape(std::move(new_shape));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgReshape(
std::map<std::string, std::vector<size_t>> new_shape_map) {
m_priv->cfgReshape(std::move(new_shape_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgNumRequests(const size_t nireq) {
m_priv->cfgNumRequests(nireq);
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgMean(std::vector<float> mean_values) {
m_priv->cfgMean(std::move(mean_values));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgMean(
std::map<std::string, std::vector<float>> mean_map) {
m_priv->cfgMean(std::move(mean_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgScale(std::vector<float> scale_values) {
m_priv->cfgScale(std::move(scale_values));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgScale(
std::map<std::string, std::vector<float>> scale_map) {
m_priv->cfgScale(std::move(scale_map));
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgResize(int interpolation) {
m_priv->cfgResize(interpolation);
return *this;
}
cv::gapi::ov::PyParams&
cv::gapi::ov::PyParams::cfgResize(std::map<std::string, int> interpolation) {
m_priv->cfgResize(std::move(interpolation));
return *this;
}
cv::gapi::ov::PyParams cv::gapi::ov::params(const std::string &tag,
const std::string &model_path,
const std::string &weights,
const std::string &device) {
return {tag, model_path, weights, device};
}
cv::gapi::ov::PyParams cv::gapi::ov::params(const std::string &tag,
const std::string &blob_path,
const std::string &device) {
return {tag, blob_path, device};
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,66 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2023 Intel Corporation
#ifndef OPENCV_GAPI_GOVBACKEND_HPP
#define OPENCV_GAPI_GOVBACKEND_HPP
// Include anyway - cv::gapi::ov::backend() still needs to be defined
#include "opencv2/gapi/infer/ov.hpp"
#if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
#include <openvino/openvino.hpp>
#include "backends/common/gbackend.hpp"
namespace cv {
namespace gimpl {
namespace ov {
struct OVCompiled {
::ov::CompiledModel compiled_model;
};
class RequestPool;
class GOVExecutable final: public GIslandExecutable
{
const ade::Graph &m_g;
GModel::ConstGraph m_gm;
// The only executable stuff in this graph
// (assuming it is always single-op)
ade::NodeHandle this_nh;
OVCompiled compiled;
// List of all resources in graph (both internal and external)
std::vector<ade::NodeHandle> m_dataNodes;
// To manage multiple async requests
std::unique_ptr<RequestPool> m_reqPool;
public:
GOVExecutable(const ade::Graph &graph,
const std::vector<ade::NodeHandle> &nodes);
virtual inline bool canReshape() const override { return false; }
virtual inline void reshape(ade::Graph&, const GCompileArgs&) override {
GAPI_Error("InternalError"); // Not implemented yet
}
virtual void run(std::vector<InObj> &&,
std::vector<OutObj> &&) override {
GAPI_Error("Not implemented");
}
virtual void run(GIslandExecutable::IInput &in,
GIslandExecutable::IOutput &out) override;
};
}}}
#endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
#endif // OPENCV_GAPI_GOVBACKEND_HPP

@ -0,0 +1,35 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2023 Intel Corporation
#ifndef OPENCV_GAPI_INFER_OV_UTIL_HPP
#define OPENCV_GAPI_INFER_OV_UTIL_HPP
#if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
// NOTE: This file is not included by default in infer/ov.hpp
// and won't be. infer/ov.hpp doesn't depend on OV headers itself.
// This file does -- so needs to be included separately by those who care.
#include <openvino/openvino.hpp>
#include <opencv2/core/cvdef.h> // GAPI_EXPORTS
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage
namespace cv {
namespace gapi {
namespace ov {
namespace util {
// NB: These functions are EXPORTed to make them accessible by the
// test suite only.
GAPI_EXPORTS std::vector<int> to_ocv(const ::ov::Shape &shape);
GAPI_EXPORTS int to_ocv(const ::ov::element::Type &type);
}}}}
#endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
#endif // OPENCV_GAPI_INFER_OV_UTIL_HPP

@ -2,7 +2,7 @@
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019-2021 Intel Corporation
// Copyright (C) 2019-2023 Intel Corporation
#include "../test_precomp.hpp"
@ -2238,7 +2238,7 @@ TEST(TestAgeGenderIE, InferWithBatch)
params.weights_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
params.device_id = "CPU";
cv::Mat in_mat({batch_size, 3, 320, 240}, CV_8U);
cv::Mat in_mat({batch_size, 3, 62, 62}, CV_8U);
cv::randu(in_mat, 0, 255);
cv::Mat gapi_age, gapi_gender;
@ -2247,8 +2247,9 @@ TEST(TestAgeGenderIE, InferWithBatch)
IE::Blob::Ptr ie_age, ie_gender;
{
auto plugin = cv::gimpl::ie::wrap::getPlugin(params);
auto net = cv::gimpl::ie::wrap::readNetwork(params);
setNetParameters(net);
auto net = cv::gimpl::ie::wrap::readNetwork(params);
auto ii = net.getInputsInfo().at("data");
ii->setPrecision(IE::Precision::U8);
net.setBatchSize(batch_size);
auto this_network = cv::gimpl::ie::wrap::loadNetwork(plugin, net, params);
auto infer_request = this_network.CreateInferRequest();
@ -3056,6 +3057,73 @@ TEST_F(AgeGenderInferTest, ChangeSpecificOutputPrecison) {
validate();
}
TEST_F(AgeGenderInferTest, ThrowIfSetLayoutForImage) {
auto pp = cv::gapi::ie::Params<AgeGender> {
m_params.model_path, m_params.weights_path, m_params.device_id
}.cfgOutputLayers({ "age_conv3", "prob" })
.cfgOutputPrecision({{"prob", CV_8U}})
.cfgInputLayout("NHWC");
EXPECT_ANY_THROW(buildGraph().apply(cv::gin(m_in_mat), cv::gout(m_gapi_age, m_gapi_gender),
cv::compile_args(cv::gapi::networks(pp))));
}
TEST(TestAgeGenderIE, InferTensorWithPreproc) {
initDLDTDataPath();
cv::gapi::ie::detail::ParamDesc params;
params.model_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
params.weights_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
params.device_id = "CPU";
// Load IE network, initialize input data using that.
cv::Mat in_mat({1, 240, 320, 3}, CV_8U);
cv::randu(in_mat, 0, 255);
cv::Mat gapi_age, gapi_gender;
IE::Blob::Ptr ie_age, ie_gender;
{
auto plugin = cv::gimpl::ie::wrap::getPlugin(params);
auto net = cv::gimpl::ie::wrap::readNetwork(params);
auto ii = net.getInputsInfo().at("data");
ii->setPrecision(IE::Precision::U8);
ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR);
ii->setLayout(IE::Layout::NHWC);
auto this_network = cv::gimpl::ie::wrap::loadNetwork(plugin, net, params);
auto infer_request = this_network.CreateInferRequest();
IE::TensorDesc desc{IE::Precision::U8, {1, 3, 240, 320}, IE::Layout::NHWC};
auto blob = IE::make_shared_blob<uint8_t>(desc, const_cast<uint8_t*>(in_mat.ptr<uint8_t>()));
infer_request.SetBlob("data", blob);
infer_request.Infer();
ie_age = infer_request.GetBlob("age_conv3");
ie_gender = infer_request.GetBlob("prob");
}
// Configure & run G-API
using AGInfo = std::tuple<cv::GMat, cv::GMat>;
G_API_NET(AgeGender, <AGInfo(cv::GMat)>, "test-age-gender");
cv::GMat in;
cv::GMat age, gender;
std::tie(age, gender) = cv::gapi::infer<AgeGender>(in);
cv::GComputation comp(cv::GIn(in), cv::GOut(age, gender));
auto pp = cv::gapi::ie::Params<AgeGender> {
params.model_path, params.weights_path, params.device_id
}.cfgOutputLayers({ "age_conv3", "prob" })
.cfgResize(cv::INTER_LINEAR)
.cfgInputLayout("NHWC");
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Validate with IE itself (avoid DNN module dependency here)
normAssert(cv::gapi::ie::util::to_ocv(ie_age), gapi_age, "Test age output" );
normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output");
}
} // namespace opencv_test
#endif // HAVE_INF_ENGINE

@ -0,0 +1,540 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2023 Intel Corporation
#if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
#include "../test_precomp.hpp"
#include "backends/ov/util.hpp"
#include <opencv2/gapi/infer/ov.hpp>
#include <openvino/openvino.hpp>
namespace opencv_test
{
namespace {
// FIXME: taken from DNN module
void initDLDTDataPath()
{
#ifndef WINRT
static bool initialized = false;
if (!initialized)
{
const char* omzDataPath = getenv("OPENCV_OPEN_MODEL_ZOO_DATA_PATH");
if (omzDataPath)
cvtest::addDataSearchPath(omzDataPath);
const char* dnnDataPath = getenv("OPENCV_DNN_TEST_DATA_PATH");
if (dnnDataPath) {
// Add the dnnDataPath itself - G-API is using some images there directly
cvtest::addDataSearchPath(dnnDataPath);
cvtest::addDataSearchPath(dnnDataPath + std::string("/omz_intel_models"));
}
initialized = true;
}
#endif // WINRT
}
static const std::string SUBDIR = "intel/age-gender-recognition-retail-0013/FP32/";
void copyFromOV(ov::Tensor &tensor, cv::Mat &mat) {
GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize());
std::copy_n(reinterpret_cast<uint8_t*>(tensor.data()),
tensor.get_byte_size(),
mat.ptr<uint8_t>());
}
void copyToOV(const cv::Mat &mat, ov::Tensor &tensor) {
GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize());
std::copy_n(mat.ptr<uint8_t>(),
tensor.get_byte_size(),
reinterpret_cast<uint8_t*>(tensor.data()));
}
// FIXME: taken from the DNN module
void normAssert(cv::InputArray ref, cv::InputArray test,
const char *comment /*= ""*/,
double l1 = 0.00001, double lInf = 0.0001) {
double normL1 = cvtest::norm(ref, test, cv::NORM_L1) / ref.getMat().total();
EXPECT_LE(normL1, l1) << comment;
double normInf = cvtest::norm(ref, test, cv::NORM_INF);
EXPECT_LE(normInf, lInf) << comment;
}
ov::Core getCore() {
static ov::Core core;
return core;
}
// TODO: AGNetGenComp, AGNetTypedComp, AGNetOVComp, AGNetOVCompiled
// can be generalized to work with any model and used as parameters for tests.
struct AGNetGenComp {
static constexpr const char* tag = "age-gender-generic";
using Params = cv::gapi::ov::Params<cv::gapi::Generic>;
static Params params(const std::string &xml,
const std::string &bin,
const std::string &device) {
return {tag, xml, bin, device};
}
static Params params(const std::string &blob_path,
const std::string &device) {
return {tag, blob_path, device};
}
static cv::GComputation create() {
cv::GMat in;
GInferInputs inputs;
inputs["data"] = in;
auto outputs = cv::gapi::infer<cv::gapi::Generic>(tag, inputs);
auto age = outputs.at("age_conv3");
auto gender = outputs.at("prob");
return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)};
}
};
struct AGNetTypedComp {
using AGInfo = std::tuple<cv::GMat, cv::GMat>;
G_API_NET(AgeGender, <AGInfo(cv::GMat)>, "typed-age-gender");
using Params = cv::gapi::ov::Params<AgeGender>;
static Params params(const std::string &xml_path,
const std::string &bin_path,
const std::string &device) {
return Params {
xml_path, bin_path, device
}.cfgOutputLayers({ "age_conv3", "prob" });
}
static cv::GComputation create() {
cv::GMat in;
cv::GMat age, gender;
std::tie(age, gender) = cv::gapi::infer<AgeGender>(in);
return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)};
}
};
class AGNetOVCompiled {
public:
AGNetOVCompiled(ov::CompiledModel &&compiled_model)
: m_compiled_model(std::move(compiled_model)) {
}
void operator()(const cv::Mat &in_mat,
cv::Mat &age_mat,
cv::Mat &gender_mat) {
auto infer_request = m_compiled_model.create_infer_request();
auto input_tensor = infer_request.get_input_tensor();
copyToOV(in_mat, input_tensor);
infer_request.infer();
auto age_tensor = infer_request.get_tensor("age_conv3");
age_mat.create(cv::gapi::ov::util::to_ocv(age_tensor.get_shape()),
cv::gapi::ov::util::to_ocv(age_tensor.get_element_type()));
copyFromOV(age_tensor, age_mat);
auto gender_tensor = infer_request.get_tensor("prob");
gender_mat.create(cv::gapi::ov::util::to_ocv(gender_tensor.get_shape()),
cv::gapi::ov::util::to_ocv(gender_tensor.get_element_type()));
copyFromOV(gender_tensor, gender_mat);
}
void export_model(const std::string &outpath) {
std::ofstream file{outpath, std::ios::out | std::ios::binary};
GAPI_Assert(file.is_open());
m_compiled_model.export_model(file);
}
private:
ov::CompiledModel m_compiled_model;
};
struct ImageInputPreproc {
void operator()(ov::preprocess::PrePostProcessor &ppp) {
ppp.input().tensor().set_layout(ov::Layout("NHWC"))
.set_element_type(ov::element::u8)
.set_shape({1, size.height, size.width, 3});
ppp.input().model().set_layout(ov::Layout("NCHW"));
ppp.input().preprocess().resize(::ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR);
}
cv::Size size;
};
class AGNetOVComp {
public:
AGNetOVComp(const std::string &xml_path,
const std::string &bin_path,
const std::string &device)
: m_device(device) {
m_model = getCore().read_model(xml_path, bin_path);
}
using PrePostProcessF = std::function<void(ov::preprocess::PrePostProcessor&)>;
void cfgPrePostProcessing(PrePostProcessF f) {
ov::preprocess::PrePostProcessor ppp(m_model);
f(ppp);
m_model = ppp.build();
}
AGNetOVCompiled compile() {
auto compiled_model = getCore().compile_model(m_model, m_device);
return {std::move(compiled_model)};
}
void apply(const cv::Mat &in_mat,
cv::Mat &age_mat,
cv::Mat &gender_mat) {
compile()(in_mat, age_mat, gender_mat);
}
private:
std::string m_device;
std::shared_ptr<ov::Model> m_model;
};
} // anonymous namespace
// TODO: Make all of tests below parmetrized to avoid code duplication
TEST(TestAgeGenderOV, InferTypedTensor) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat({1, 3, 62, 62}, CV_32F);
cv::randu(in_mat, -1, 1);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetTypedComp::create();
auto pp = AGNetTypedComp::params(xml_path, bin_path, device);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferTypedImage) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat(300, 300, CV_8UC3);
cv::randu(in_mat, 0, 255);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()});
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetTypedComp::create();
auto pp = AGNetTypedComp::params(xml_path, bin_path, device);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferGenericTensor) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat({1, 3, 62, 62}, CV_32F);
cv::randu(in_mat, -1, 1);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(xml_path, bin_path, device);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferGenericImage) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat(300, 300, CV_8UC3);
cv::randu(in_mat, 0, 255);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()});
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(xml_path, bin_path, device);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferGenericImageBlob) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string blob_path = "age-gender-recognition-retail-0013.blob";
const std::string device = "CPU";
cv::Mat in_mat(300, 300, CV_8UC3);
cv::randu(in_mat, 0, 255);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()});
auto cc_ref = ref.compile();
// NB: Output blob will contain preprocessing inside.
cc_ref.export_model(blob_path);
cc_ref(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(blob_path, device);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferGenericTensorBlob) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string blob_path = "age-gender-recognition-retail-0013.blob";
const std::string device = "CPU";
cv::Mat in_mat({1, 3, 62, 62}, CV_32F);
cv::randu(in_mat, -1, 1);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
auto cc_ref = ref.compile();
cc_ref.export_model(blob_path);
cc_ref(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(blob_path, device);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferBothOutputsFP16) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat({1, 3, 62, 62}, CV_32F);
cv::randu(in_mat, -1, 1);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp){
ppp.output(0).tensor().set_element_type(ov::element::f16);
ppp.output(1).tensor().set_element_type(ov::element::f16);
});
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(xml_path, bin_path, device);
pp.cfgOutputTensorPrecision(CV_16F);
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, InferOneOutputFP16) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat({1, 3, 62, 62}, CV_32F);
cv::randu(in_mat, -1, 1);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
const std::string fp16_output_name = "prob";
AGNetOVComp ref(xml_path, bin_path, device);
ref.cfgPrePostProcessing([&](ov::preprocess::PrePostProcessor &ppp){
ppp.output(fp16_output_name).tensor().set_element_type(ov::element::f16);
});
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(xml_path, bin_path, device);
pp.cfgOutputTensorPrecision({{fp16_output_name, CV_16F}});
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
TEST(TestAgeGenderOV, ThrowCfgOutputPrecForBlob) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string blob_path = "age-gender-recognition-retail-0013.blob";
const std::string device = "CPU";
// OpenVINO (Just for blob compilation)
AGNetOVComp ref(xml_path, bin_path, device);
auto cc_ref = ref.compile();
cc_ref.export_model(blob_path);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(blob_path, device);
EXPECT_ANY_THROW(pp.cfgOutputTensorPrecision(CV_16F));
}
TEST(TestAgeGenderOV, ThrowInvalidConfigIR) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(xml_path, bin_path, device);
pp.cfgPluginConfig({{"some_key", "some_value"}});
EXPECT_ANY_THROW(comp.compile(cv::GMatDesc{CV_8U,3,cv::Size{320, 240}},
cv::compile_args(cv::gapi::networks(pp))));
}
TEST(TestAgeGenderOV, ThrowInvalidConfigBlob) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string blob_path = "age-gender-recognition-retail-0013.blob";
const std::string device = "CPU";
// OpenVINO (Just for blob compilation)
AGNetOVComp ref(xml_path, bin_path, device);
auto cc_ref = ref.compile();
cc_ref.export_model(blob_path);
// G-API
auto comp = AGNetGenComp::create();
auto pp = AGNetGenComp::params(blob_path, device);
pp.cfgPluginConfig({{"some_key", "some_value"}});
EXPECT_ANY_THROW(comp.compile(cv::GMatDesc{CV_8U,3,cv::Size{320, 240}},
cv::compile_args(cv::gapi::networks(pp))));
}
TEST(TestAgeGenderOV, ThrowInvalidImageLayout) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
// NB: This mat may only have "NHWC" layout.
cv::Mat in_mat(300, 300, CV_8UC3);
cv::randu(in_mat, 0, 255);
cv::Mat gender, gapi_age, gapi_gender;
auto comp = AGNetTypedComp::create();
auto pp = AGNetTypedComp::params(xml_path, bin_path, device);
pp.cfgInputTensorLayout("NCHW");
EXPECT_ANY_THROW(comp.compile(cv::descr_of(in_mat),
cv::compile_args(cv::gapi::networks(pp))));
}
TEST(TestAgeGenderOV, InferTensorWithPreproc) {
initDLDTDataPath();
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml");
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin");
const std::string device = "CPU";
cv::Mat in_mat({1, 240, 320, 3}, CV_32F);
cv::randu(in_mat, -1, 1);
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender;
// OpenVINO
AGNetOVComp ref(xml_path, bin_path, device);
ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp) {
auto& input = ppp.input();
input.tensor().set_spatial_static_shape(240, 320)
.set_layout("NHWC");
input.preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR);
});
ref.apply(in_mat, ov_age, ov_gender);
// G-API
auto comp = AGNetTypedComp::create();
auto pp = AGNetTypedComp::params(xml_path, bin_path, device);
pp.cfgResize(cv::INTER_LINEAR)
.cfgInputTensorLayout("NHWC");
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender),
cv::compile_args(cv::gapi::networks(pp)));
// Assert
normAssert(ov_age, gapi_age, "Test age output" );
normAssert(ov_gender, gapi_gender, "Test gender output");
}
} // namespace opencv_test
#endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000

@ -13,6 +13,11 @@ if(HAVE_WINRT_CX AND NOT WINRT)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /ZW")
endif()
if (HAVE_AVIF)
ocv_include_directories(${AVIF_INCLUDE_DIR})
list(APPEND GRFMT_LIBS ${AVIF_LIBRARY})
endif()
if(HAVE_JPEG)
ocv_include_directories(${JPEG_INCLUDE_DIR} ${${JPEG_LIBRARY}_BINARY_DIR})
list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES})

@ -105,7 +105,10 @@ enum ImwriteFlags {
IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI
IMWRITE_TIFF_YDPI = 258,//!< For TIFF, use to specify the Y direction DPI
IMWRITE_TIFF_COMPRESSION = 259,//!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272 //!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95.
IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8.
IMWRITE_AVIF_SPEED = 514 //!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9.
};
enum ImwriteJPEGSamplingFactorParams {
@ -185,6 +188,7 @@ Currently, the following file formats are supported:
- JPEG 2000 files - \*.jp2 (see the *Note* section)
- Portable Network Graphics - \*.png (see the *Note* section)
- WebP - \*.webp (see the *Note* section)
- AVIF - \*.avif (see the *Note* section)
- Portable image format - \*.pbm, \*.pgm, \*.ppm \*.pxm, \*.pnm (always supported)
- PFM files - \*.pfm (see the *Note* section)
- Sun rasters - \*.sr, \*.ras (always supported)

@ -0,0 +1,369 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#ifdef HAVE_AVIF
#include <avif/avif.h>
#include <fstream>
#include <opencv2/core/utils/configuration.private.hpp>
#include "opencv2/imgproc.hpp"
#include "grfmt_avif.hpp"
#define CV_AVIF_USE_QUALITY \
(AVIF_VERSION > ((0 * 1000000) + (11 * 10000) + (1 * 100)))
#if !CV_AVIF_USE_QUALITY
#define AVIF_QUALITY_LOSSLESS 100
#define AVIF_QUALITY_WORST 0
#define AVIF_QUALITY_BEST 100
#endif
namespace cv {
namespace {
struct AvifImageDeleter {
void operator()(avifImage *image) { avifImageDestroy(image); }
};
using AvifImageUniquePtr = std::unique_ptr<avifImage, AvifImageDeleter>;
avifResult CopyToMat(const avifImage *image, int channels, Mat *mat) {
CV_Assert((int)image->height == mat->rows);
CV_Assert((int)image->width == mat->cols);
if (channels == 1) {
const cv::Mat image_wrap =
cv::Mat(image->height, image->width,
CV_MAKE_TYPE((image->depth == 8) ? CV_8U : CV_16U, 1),
image->yuvPlanes[0], image->yuvRowBytes[0]);
if ((image->depth == 8 && mat->depth() == CV_8U) ||
(image->depth > 8 && mat->depth() == CV_16U)) {
image_wrap.copyTo(*mat);
} else {
CV_Assert(image->depth > 8 && mat->depth() == CV_8U);
image_wrap.convertTo(*mat, CV_8U, 1. / (1 << (image->depth - 8)));
}
return AVIF_RESULT_OK;
}
avifRGBImage rgba;
avifRGBImageSetDefaults(&rgba, image);
if (channels == 3) {
rgba.format = AVIF_RGB_FORMAT_BGR;
} else {
CV_Assert(channels == 4);
rgba.format = AVIF_RGB_FORMAT_BGRA;
}
rgba.rowBytes = mat->step[0];
rgba.depth = (mat->depth() == CV_16U) ? image->depth : 8;
rgba.pixels = reinterpret_cast<uint8_t *>(mat->data);
return avifImageYUVToRGB(image, &rgba);
}
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
int bit_depth) {
CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U);
const int width = img.cols;
const int height = img.rows;
avifImage *result;
if (img.channels() == 1) {
result = avifImageCreateEmpty();
if (result == nullptr) return nullptr;
result->width = width;
result->height = height;
result->depth = bit_depth;
result->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
result->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
result->yuvRange = AVIF_RANGE_FULL;
result->yuvPlanes[0] = img.data;
result->yuvRowBytes[0] = img.step[0];
result->imageOwnsYUVPlanes = AVIF_FALSE;
return AvifImageUniquePtr(result);
}
if (lossless) {
result =
avifImageCreate(width, height, bit_depth, AVIF_PIXEL_FORMAT_YUV444);
if (result == nullptr) return nullptr;
result->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
result->yuvRange = AVIF_RANGE_FULL;
} else {
result =
avifImageCreate(width, height, bit_depth, AVIF_PIXEL_FORMAT_YUV420);
if (result == nullptr) return nullptr;
result->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
result->yuvRange = AVIF_RANGE_FULL;
}
avifRGBImage rgba;
avifRGBImageSetDefaults(&rgba, result);
if (img.channels() == 3) {
rgba.format = AVIF_RGB_FORMAT_BGR;
} else {
CV_Assert(img.channels() == 4);
rgba.format = AVIF_RGB_FORMAT_BGRA;
}
rgba.rowBytes = img.step[0];
rgba.depth = bit_depth;
rgba.pixels =
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(img.data));
if (avifImageRGBToYUV(result, &rgba) != AVIF_RESULT_OK) {
avifImageDestroy(result);
return nullptr;
}
return AvifImageUniquePtr(result);
}
} // namespace
// 64Mb limit to avoid memory saturation.
static const size_t kParamMaxFileSize = utils::getConfigurationParameterSizeT(
"OPENCV_IMGCODECS_AVIF_MAX_FILE_SIZE", 64 * 1024 * 1024);
static constexpr size_t kAvifSignatureSize = 500;
AvifDecoder::AvifDecoder() {
m_buf_supported = true;
channels_ = 0;
decoder_ = avifDecoderCreate();
}
AvifDecoder::~AvifDecoder() {
if (decoder_ != nullptr) avifDecoderDestroy(decoder_);
}
size_t AvifDecoder::signatureLength() const { return kAvifSignatureSize; }
bool AvifDecoder::checkSignature(const String &signature) const {
avifDecoderSetIOMemory(decoder_,
reinterpret_cast<const uint8_t *>(signature.c_str()),
signature.size());
decoder_->io->sizeHint = 1e9;
const avifResult status = avifDecoderParse(decoder_);
return (status == AVIF_RESULT_OK || status == AVIF_RESULT_TRUNCATED_DATA);
}
#define OPENCV_AVIF_CHECK_STATUS(X, ENCDEC) \
{ \
const avifResult status = (X); \
if (status != AVIF_RESULT_OK) { \
const std::string error(ENCDEC->diag.error); \
CV_Error(Error::StsParseError, \
error + " " + avifResultToString(status)); \
return false; \
} \
}
ImageDecoder AvifDecoder::newDecoder() const { return makePtr<AvifDecoder>(); }
bool AvifDecoder::readHeader() {
if (!m_buf.empty()) {
CV_Assert(m_buf.type() == CV_8UC1);
CV_Assert(m_buf.rows == 1);
}
OPENCV_AVIF_CHECK_STATUS(
m_buf.empty()
? avifDecoderSetIOFile(decoder_, m_filename.c_str())
: avifDecoderSetIOMemory(
decoder_, reinterpret_cast<const uint8_t *>(m_buf.data),
m_buf.total()),
decoder_);
OPENCV_AVIF_CHECK_STATUS(avifDecoderParse(decoder_), decoder_);
m_width = decoder_->image->width;
m_height = decoder_->image->height;
channels_ = (decoder_->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
if (decoder_->alphaPresent) ++channels_;
bit_depth_ = decoder_->image->depth;
CV_Assert(bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12);
m_type = CV_MAKETYPE(bit_depth_ == 8 ? CV_8U : CV_16U, channels_);
is_first_image_ = true;
return true;
}
bool AvifDecoder::readData(Mat &img) {
CV_CheckGE(m_width, 0, "");
CV_CheckGE(m_height, 0, "");
CV_CheckEQ(img.cols, m_width, "");
CV_CheckEQ(img.rows, m_height, "");
CV_CheckType(
img.type(),
(img.channels() == 1 || img.channels() == 3 || img.channels() == 4) &&
(img.depth() == CV_8U || img.depth() == CV_16U),
"AVIF only supports 1, 3, 4 channels and CV_8U and CV_16U");
Mat read_img;
if (img.channels() == channels_) {
read_img = img;
} else {
// Use the asked depth but keep the number of channels. OpenCV and not
// libavif will do the color conversion.
read_img.create(m_height, m_width, CV_MAKE_TYPE(img.depth(), channels_));
}
if (is_first_image_) {
if (!nextPage()) return false;
is_first_image_ = false;
}
if (CopyToMat(decoder_->image, channels_, &read_img) != AVIF_RESULT_OK) {
CV_Error(Error::StsInternal, "Cannot convert from AVIF to Mat");
return false;
}
if (decoder_->image->exif.size > 0) {
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
}
if (img.channels() == channels_) {
// We already wrote to the right buffer.
} else {
if (channels_ == 1 && img.channels() == 3) {
cvtColor(read_img, img, COLOR_GRAY2BGR);
} else if (channels_ == 1 && img.channels() == 4) {
cvtColor(read_img, img, COLOR_GRAY2BGRA);
} else if (channels_ == 3 && img.channels() == 1) {
cvtColor(read_img, img, COLOR_BGR2GRAY);
} else if (channels_ == 3 && img.channels() == 4) {
cvtColor(read_img, img, COLOR_BGR2BGRA);
} else if (channels_ == 4 && img.channels() == 1) {
cvtColor(read_img, img, COLOR_BGRA2GRAY);
} else if (channels_ == 4 && img.channels() == 3) {
cvtColor(read_img, img, COLOR_BGRA2BGR);
} else {
CV_Error(Error::StsInternal, "");
}
}
return true;
}
bool AvifDecoder::nextPage() {
const avifResult status = avifDecoderNextImage(decoder_);
if (status == AVIF_RESULT_NO_IMAGES_REMAINING) return false;
if (status != AVIF_RESULT_OK) {
const std::string error(decoder_->diag.error);
CV_Error(Error::StsParseError, error + " " + avifResultToString(status));
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
AvifEncoder::AvifEncoder() {
m_description = "AVIF files (*.avif)";
m_buf_supported = true;
encoder_ = avifEncoderCreate();
}
AvifEncoder::~AvifEncoder() {
if (encoder_) avifEncoderDestroy(encoder_);
}
bool AvifEncoder::isFormatSupported(int depth) const {
return (depth == CV_8U || depth == CV_16U);
}
bool AvifEncoder::write(const Mat &img, const std::vector<int> &params) {
std::vector<Mat> img_vec(1, img);
return writeToOutput(img_vec, params);
}
bool AvifEncoder::writemulti(const std::vector<Mat> &img_vec,
const std::vector<int> &params) {
return writeToOutput(img_vec, params);
}
bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
const std::vector<int> &params) {
int bit_depth = 8;
int speed = AVIF_SPEED_FASTEST;
for (size_t i = 0; i < params.size(); i += 2) {
if (params[i] == IMWRITE_AVIF_QUALITY) {
const int quality = std::min(std::max(params[i + 1], AVIF_QUALITY_WORST),
AVIF_QUALITY_BEST);
#if CV_AVIF_USE_QUALITY
encoder_->quality = quality;
#else
encoder_->minQuantizer = encoder_->maxQuantizer =
(AVIF_QUANTIZER_BEST_QUALITY - AVIF_QUANTIZER_WORST_QUALITY) *
quality / (AVIF_QUALITY_BEST - AVIF_QUALITY_WORST) +
AVIF_QUANTIZER_WORST_QUALITY;
#endif
} else if (params[i] == IMWRITE_AVIF_DEPTH) {
bit_depth = params[i + 1];
} else if (params[i] == IMWRITE_AVIF_SPEED) {
speed = params[i + 1];
}
}
avifRWData output_ori = AVIF_DATA_EMPTY;
std::unique_ptr<avifRWData, decltype(&avifRWDataFree)> output(&output_ori,
avifRWDataFree);
#if CV_AVIF_USE_QUALITY
const bool do_lossless = (encoder_->quality == AVIF_QUALITY_LOSSLESS);
#else
const bool do_lossless =
(encoder_->minQuantizer == AVIF_QUANTIZER_BEST_QUALITY &&
encoder_->maxQuantizer == AVIF_QUANTIZER_BEST_QUALITY);
#endif
encoder_->speed = speed;
const avifAddImageFlags flag = (img_vec.size() == 1)
? AVIF_ADD_IMAGE_FLAG_SINGLE
: AVIF_ADD_IMAGE_FLAG_NONE;
std::vector<AvifImageUniquePtr> images;
std::vector<cv::Mat> imgs_scaled;
for (const cv::Mat &img : img_vec) {
CV_CheckType(
img.type(),
(bit_depth == 8 && img.depth() == CV_8U) ||
((bit_depth == 10 || bit_depth == 12) && img.depth() == CV_16U),
"AVIF only supports bit depth of 8 with CV_8U input or "
"bit depth of 10 or 12 with CV_16U input");
CV_Check(img.channels(),
img.channels() == 1 || img.channels() == 3 || img.channels() == 4,
"AVIF only supports 1, 3, 4 channels");
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth));
}
for (const AvifImageUniquePtr &image : images) {
OPENCV_AVIF_CHECK_STATUS(
avifEncoderAddImage(encoder_, image.get(), /*durationInTimescale=*/1,
flag),
encoder_);
}
OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_);
if (m_buf) {
m_buf->resize(output->size);
std::memcpy(m_buf->data(), output->data, output->size);
} else {
std::ofstream(m_filename, std::ofstream::binary)
.write(reinterpret_cast<char *>(output->data), output->size);
}
return (output->size > 0);
}
ImageEncoder AvifEncoder::newEncoder() const { return makePtr<AvifEncoder>(); }
} // namespace cv
#endif

@ -0,0 +1,62 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html
#ifndef _GRFMT_AVIF_H_
#define _GRFMT_AVIF_H_
#include "grfmt_base.hpp"
#ifdef HAVE_AVIF
struct avifDecoder;
struct avifEncoder;
struct avifRWData;
namespace cv {
class AvifDecoder CV_FINAL : public BaseImageDecoder {
public:
AvifDecoder();
~AvifDecoder();
bool readHeader() CV_OVERRIDE;
bool readData(Mat& img) CV_OVERRIDE;
bool nextPage() CV_OVERRIDE;
size_t signatureLength() const CV_OVERRIDE;
bool checkSignature(const String& signature) const CV_OVERRIDE;
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
int channels_;
int bit_depth_;
avifDecoder* decoder_;
bool is_first_image_;
};
class AvifEncoder CV_FINAL : public BaseImageEncoder {
public:
AvifEncoder();
~AvifEncoder() CV_OVERRIDE;
bool isFormatSupported(int depth) const CV_OVERRIDE;
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writemulti(const std::vector<Mat>& img_vec,
const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
private:
bool writeToOutput(const std::vector<Mat>& img_vec,
const std::vector<int>& params);
avifEncoder* encoder_;
};
} // namespace cv
#endif
#endif /*_GRFMT_AVIF_H_*/

@ -43,6 +43,7 @@
#define _GRFMTS_H_
#include "grfmt_base.hpp"
#include "grfmt_avif.hpp"
#include "grfmt_bmp.hpp"
#include "grfmt_sunras.hpp"
#include "grfmt_jpeg.hpp"

@ -132,6 +132,10 @@ struct ImageCodecInitializer
*/
ImageCodecInitializer()
{
#ifdef HAVE_AVIF
decoders.push_back(makePtr<AvifDecoder>());
encoders.push_back(makePtr<AvifEncoder>());
#endif
/// BMP Support
decoders.push_back( makePtr<BmpDecoder>() );
encoders.push_back( makePtr<BmpEncoder>() );

@ -0,0 +1,355 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html
#include <cstdint>
#include <fstream>
#include "test_precomp.hpp"
#ifdef HAVE_AVIF
namespace opencv_test {
namespace {
class Imgcodecs_Avif_RoundTripSuite
: public testing::TestWithParam<std::tuple<int, int, int, ImreadModes>> {
protected:
static cv::Mat modifyImage(const cv::Mat& img_original, int channels,
int bit_depth) {
cv::Mat img;
if (channels == 1) {
cv::cvtColor(img_original, img, cv::COLOR_BGR2GRAY);
} else if (channels == 4) {
std::vector<cv::Mat> imgs;
cv::split(img_original, imgs);
imgs.push_back(cv::Mat(imgs[0]));
imgs[imgs.size() - 1] = cv::Scalar::all(128);
cv::merge(imgs, img);
} else {
img = img_original.clone();
}
cv::Mat img_final = img;
// Convert image to CV_16U for some bit depths.
if (bit_depth > 8) img.convertTo(img_final, CV_16U, 1 << (bit_depth - 8));
return img_final;
}
void SetUp() {
bit_depth_ = std::get<0>(GetParam());
channels_ = std::get<1>(GetParam());
quality_ = std::get<2>(GetParam());
imread_mode_ = std::get<3>(GetParam());
encoding_params_ = {cv::IMWRITE_AVIF_QUALITY, quality_,
cv::IMWRITE_AVIF_DEPTH, bit_depth_};
}
bool IsBitDepthValid() const {
return (bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12);
}
// Makes sure images are close enough after encode/decode roundtrip.
void ValidateRead(const cv::Mat& img_original, const cv::Mat& img) const {
EXPECT_EQ(img_original.size(), img.size());
if (imread_mode_ == IMREAD_UNCHANGED) {
ASSERT_EQ(img_original.type(), img.type());
// Lossless.
if (quality_ == 100) {
EXPECT_EQ(0, cvtest::norm(img, img_original, NORM_INF));
} else {
const float norm = cvtest::norm(img, img_original, NORM_L2) /
img.channels() / img.cols / img.rows /
(1 << (bit_depth_ - 8));
if (quality_ == 50) {
EXPECT_LE(norm, 10);
} else if (quality_ == 0) {
EXPECT_LE(norm, 13);
} else {
EXPECT_FALSE(true);
}
}
}
}
public:
int bit_depth_;
int channels_;
int quality_;
int imread_mode_;
std::vector<int> encoding_params_;
};
////////////////////////////////////////////////////////////////////////////////
class Imgcodecs_Avif_Image_RoundTripSuite
: public Imgcodecs_Avif_RoundTripSuite {
public:
const cv::Mat& get_img_original() {
const Key key = {channels_, (bit_depth_ < 8) ? 8 : bit_depth_};
return imgs_[key];
}
// Prepare the original image modified for different number of channels and
// bit depth.
static void SetUpTestCase() {
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "../cv/shared/lena.png";
const cv::Mat img_original = cv::imread(filename);
cv::Mat img_resized;
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0);
for (int channels : {1, 3, 4}) {
for (int bit_depth : {8, 10, 12}) {
const Key key{channels, bit_depth};
imgs_[key] = modifyImage(img_resized, channels, bit_depth);
}
}
}
static const int kWidth;
static const int kHeight;
private:
typedef std::tuple<int, int> Key;
static std::map<Key, cv::Mat> imgs_;
};
std::map<std::tuple<int, int>, cv::Mat>
Imgcodecs_Avif_Image_RoundTripSuite::imgs_;
const int Imgcodecs_Avif_Image_RoundTripSuite::kWidth = 51;
const int Imgcodecs_Avif_Image_RoundTripSuite::kHeight = 31;
class Imgcodecs_Avif_Image_WriteReadSuite
: public Imgcodecs_Avif_Image_RoundTripSuite {};
TEST_P(Imgcodecs_Avif_Image_WriteReadSuite, imwrite_imread) {
const cv::Mat& img_original = get_img_original();
ASSERT_FALSE(img_original.empty());
// Encode.
const string output = cv::tempfile(".avif");
if (!IsBitDepthValid()) {
EXPECT_NO_FATAL_FAILURE(
cv::imwrite(output, img_original, encoding_params_));
EXPECT_NE(0, remove(output.c_str()));
return;
}
EXPECT_NO_THROW(cv::imwrite(output, img_original, encoding_params_));
// Read from file.
const cv::Mat img = cv::imread(output, imread_mode_);
ValidateRead(img_original, img);
EXPECT_EQ(0, remove(output.c_str()));
}
INSTANTIATE_TEST_CASE_P(
Imgcodecs_AVIF, Imgcodecs_Avif_Image_WriteReadSuite,
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}),
::testing::ValuesIn({1, 3, 4}),
::testing::ValuesIn({0, 50, 100}),
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
IMREAD_COLOR})));
class Imgcodecs_Avif_Image_EncodeDecodeSuite
: public Imgcodecs_Avif_Image_RoundTripSuite {};
TEST_P(Imgcodecs_Avif_Image_EncodeDecodeSuite, imencode_imdecode) {
const cv::Mat& img_original = get_img_original();
ASSERT_FALSE(img_original.empty());
// Encode.
std::vector<unsigned char> buf;
if (!IsBitDepthValid()) {
EXPECT_THROW(cv::imencode(".avif", img_original, buf, encoding_params_),
cv::Exception);
return;
}
bool result;
EXPECT_NO_THROW(
result = cv::imencode(".avif", img_original, buf, encoding_params_););
EXPECT_TRUE(result);
// Read back.
const cv::Mat img = cv::imdecode(buf, imread_mode_);
ValidateRead(img_original, img);
}
INSTANTIATE_TEST_CASE_P(
Imgcodecs_AVIF, Imgcodecs_Avif_Image_EncodeDecodeSuite,
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}),
::testing::ValuesIn({1, 3, 4}),
::testing::ValuesIn({0, 50, 100}),
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
IMREAD_COLOR})));
////////////////////////////////////////////////////////////////////////////////
typedef testing::TestWithParam<string> Imgcodecs_AVIF_Exif;
TEST_P(Imgcodecs_AVIF_Exif, exif_orientation) {
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + GetParam();
const int colorThresholdHigh = 250;
const int colorThresholdLow = 5;
Mat m_img = imread(filename);
ASSERT_FALSE(m_img.empty());
Vec3b vec;
// Checking the first quadrant (with supposed red)
vec = m_img.at<Vec3b>(2, 2); // some point inside the square
EXPECT_LE(vec.val[0], colorThresholdLow);
EXPECT_LE(vec.val[1], colorThresholdLow);
EXPECT_GE(vec.val[2], colorThresholdHigh);
// Checking the second quadrant (with supposed green)
vec = m_img.at<Vec3b>(2, 7); // some point inside the square
EXPECT_LE(vec.val[0], colorThresholdLow);
EXPECT_GE(vec.val[1], colorThresholdHigh);
EXPECT_LE(vec.val[2], colorThresholdLow);
// Checking the third quadrant (with supposed blue)
vec = m_img.at<Vec3b>(7, 2); // some point inside the square
EXPECT_GE(vec.val[0], colorThresholdHigh);
EXPECT_LE(vec.val[1], colorThresholdLow);
EXPECT_LE(vec.val[2], colorThresholdLow);
}
const string exif_files[] = {"readwrite/testExifOrientation_1.avif",
"readwrite/testExifOrientation_2.avif",
"readwrite/testExifOrientation_3.avif",
"readwrite/testExifOrientation_4.avif",
"readwrite/testExifOrientation_5.avif",
"readwrite/testExifOrientation_6.avif",
"readwrite/testExifOrientation_7.avif",
"readwrite/testExifOrientation_8.avif"};
INSTANTIATE_TEST_CASE_P(ExifFiles, Imgcodecs_AVIF_Exif,
testing::ValuesIn(exif_files));
////////////////////////////////////////////////////////////////////////////////
class Imgcodecs_Avif_Animation_RoundTripSuite
: public Imgcodecs_Avif_RoundTripSuite {
public:
const std::vector<cv::Mat>& get_anim_original() {
const Key key = {channels_, bit_depth_};
return anims_[key];
}
// Prepare the original image modified for different number of channels and
// bit depth.
static void SetUpTestCase() {
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "../cv/shared/lena.png";
const cv::Mat img_original = cv::imread(filename);
cv::Mat img_resized;
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0);
for (int channels : {1, 3, 4}) {
for (int bit_depth : {8, 10, 12}) {
const Key key{channels, bit_depth};
const cv::Mat img = modifyImage(img_resized, channels, bit_depth);
cv::Mat img2, img3;
cv::flip(img, img2, 0);
cv::flip(img, img3, -1);
anims_[key] = {img, img2, img3};
}
}
}
void ValidateRead(const std::vector<cv::Mat>& anim_original,
const std::vector<cv::Mat>& anim) const {
ASSERT_EQ(anim_original.size(), anim.size());
for (size_t i = 0; i < anim.size(); ++i) {
Imgcodecs_Avif_RoundTripSuite::ValidateRead(anim_original[i], anim[i]);
}
}
static const int kWidth;
static const int kHeight;
private:
typedef std::tuple<int, int> Key;
static std::map<Key, std::vector<cv::Mat>> anims_;
};
std::map<std::tuple<int, int>, std::vector<cv::Mat>>
Imgcodecs_Avif_Animation_RoundTripSuite::anims_;
const int Imgcodecs_Avif_Animation_RoundTripSuite::kWidth = 5;
const int Imgcodecs_Avif_Animation_RoundTripSuite::kHeight = 5;
class Imgcodecs_Avif_Animation_WriteReadSuite
: public Imgcodecs_Avif_Animation_RoundTripSuite {};
TEST_P(Imgcodecs_Avif_Animation_WriteReadSuite, encode_decode) {
const std::vector<cv::Mat>& anim_original = get_anim_original();
ASSERT_FALSE(anim_original.empty());
// Encode.
const string output = cv::tempfile(".avif");
if (!IsBitDepthValid()) {
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_),
cv::Exception);
EXPECT_NE(0, remove(output.c_str()));
return;
}
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_));
// Read from file.
std::vector<cv::Mat> anim;
ASSERT_TRUE(cv::imreadmulti(output, anim, imread_mode_));
ValidateRead(anim_original, anim);
EXPECT_EQ(0, remove(output.c_str()));
}
INSTANTIATE_TEST_CASE_P(
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteReadSuite,
::testing::Combine(::testing::ValuesIn({8, 10, 12}),
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}),
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
IMREAD_COLOR})));
class Imgcodecs_Avif_Animation_WriteDecodeSuite
: public Imgcodecs_Avif_Animation_RoundTripSuite {};
TEST_P(Imgcodecs_Avif_Animation_WriteDecodeSuite, encode_decode) {
const std::vector<cv::Mat>& anim_original = get_anim_original();
ASSERT_FALSE(anim_original.empty());
// Encode.
const string output = cv::tempfile(".avif");
if (!IsBitDepthValid()) {
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_),
cv::Exception);
EXPECT_NE(0, remove(output.c_str()));
return;
}
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_));
// Put file into buffer and read from buffer.
std::ifstream file(output, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buf(size);
EXPECT_TRUE(file.read(reinterpret_cast<char*>(buf.data()), size));
EXPECT_EQ(0, remove(output.c_str()));
std::vector<cv::Mat> anim;
ASSERT_TRUE(cv::imdecodemulti(buf, imread_mode_, anim));
ValidateRead(anim_original, anim);
}
INSTANTIATE_TEST_CASE_P(
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteDecodeSuite,
::testing::Combine(::testing::ValuesIn({8, 10, 12}),
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}),
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
IMREAD_COLOR})));
} // namespace
} // namespace opencv_test
#endif // HAVE_AVIF

@ -813,11 +813,10 @@ struct RGB2HLS_b
//TODO: fix that when v_interleave is available
float CV_DECL_ALIGNED(CV_SIMD_WIDTH) interTmpM[VTraits<v_float32>::max_nlanes*3];
v_store_interleave(interTmpM, vx_setall_f32(1.f), vx_setall_f32(255.f), vx_setall_f32(255.f));
v_float32 mhls0, mhls1, mhls2, mhls3;
v_float32 mhls0, mhls1, mhls2;
mhls0 = vx_load_aligned(interTmpM);
mhls1 = vx_load_aligned(interTmpM + fsize);
mhls2 = vx_load_aligned(interTmpM + 2*fsize);
mhls3 = vx_load_aligned(interTmpM + 3*fsize);
#endif
for(int i = 0; i < n; i += BLOCK_SIZE, dst += BLOCK_SIZE*3 )

@ -40,7 +40,7 @@ static const int neighbors_encode[8] = {
};
#define ACOS_TABLE_SIZE 64
// acos_table[x + ACOS_TABLE_SIZE] = acos(x / ACOS_TABLE_SIZE) / CV_PI (see local_cost)
// acos_table[x + ACOS_TABLE_SIZE] = acos(x / ACOS_TABLE_SIZE) / CV_PI (see add_local_cost)
// x = [ -ACOS_TABLE_SIZE .. ACOS_TABLE_SIZE ]
float* getAcosTable()
{
@ -495,55 +495,76 @@ struct IntelligentScissorsMB::Impl
// details: see section 3.1 of the article
const float* acos_table = getAcosTable();
float local_cost(const Point& p, const Point& q) const
const float sqrt2_inv = 0.7071067811865475f; // 1.0 / sqrt(2)
/** @brief Adds local_cost(p, q) to cost_p.
*
* local_cost(p, q) is computed as
weight_non_edge_compute * non_edge_feature.at<uchar>(q) +
weight_gradient_direction * fD +
weight_gradient_magnitude * fG
*
* @param p point p (input)
* @param q point q (input)
* @param cost_p cost for p (input/output)
* @param cost_q cost for q (input)
*
* @return The boolean result of the (cost_p < cost_q) comparison.
*
* @note The computed output cost_p can be partial if (cost_p < cost_q) is false.
*/
bool add_local_cost(const Point& p, const Point& q, float& cost_p, const float cost_q) const
{
const bool isDiag = (p.x != q.x) && (p.y != q.y);
if ((cost_p += weight_non_edge_compute * non_edge_feature.at<uchar>(q)) < cost_q)
{
const bool isDiag = (p.x != q.x) && (p.y != q.y);
float fG = gradient_magnitude.at<float>(q);
float fG = gradient_magnitude.at<float>(q);
if (!isDiag)
{
fG *= sqrt2_inv;
}
const Point2f diff((float)(q.x - p.x), (float)(q.y - p.y));
if ((cost_p += weight_gradient_magnitude * fG) < cost_q)
{
const Point2f Ip = gradient_direction(p);
const Point2f Iq = gradient_direction(q);
const Point2f diff((float)(q.x - p.x), (float)(q.y - p.y));
const Point2f Dp(Ip.y, -Ip.x); // D(p) - 90 degrees clockwise
const Point2f Dq(Iq.y, -Iq.x); // D(q) - 90 degrees clockwise
const Point2f Ip = gradient_direction(p);
const Point2f Iq = gradient_direction(q);
float dp = Dp.dot(diff); // dp(p, q)
float dq = Dq.dot(diff); // dq(p, q)
if (dp < 0)
{
dp = -dp; // ensure dp >= 0
dq = -dq;
}
const Point2f Dp(Ip.y, -Ip.x); // D(p) - 90 degrees clockwise
const Point2f Dq(Iq.y, -Iq.x); // D(q) - 90 degrees clockwise
const float sqrt2_inv = 0.7071067811865475f; // 1.0 / sqrt(2)
if (isDiag)
{
dp *= sqrt2_inv; // normalize length of (q - p)
dq *= sqrt2_inv; // normalize length of (q - p)
}
else
{
fG *= sqrt2_inv;
}
float dp = Dp.dot(diff); // dp(p, q)
float dq = Dq.dot(diff); // dq(p, q)
if (dp < 0)
{
dp = -dp; // ensure dp >= 0
dq = -dq;
}
if (isDiag)
{
dp *= sqrt2_inv; // normalize length of (q - p)
dq *= sqrt2_inv; // normalize length of (q - p)
}
#if 1
int dp_i = cvFloor(dp * ACOS_TABLE_SIZE); // dp is in range 0..1
dp_i = std::min(ACOS_TABLE_SIZE, std::max(0, dp_i));
int dq_i = cvFloor(dq * ACOS_TABLE_SIZE); // dq is in range -1..1
dq_i = std::min(ACOS_TABLE_SIZE, std::max(-ACOS_TABLE_SIZE, dq_i));
const float fD = acos_table[dp_i + ACOS_TABLE_SIZE] + acos_table[dq_i + ACOS_TABLE_SIZE];
int dp_i = cvFloor(dp * ACOS_TABLE_SIZE); // dp is in range 0..1
dp_i = std::min(ACOS_TABLE_SIZE, std::max(0, dp_i));
int dq_i = cvFloor(dq * ACOS_TABLE_SIZE); // dq is in range -1..1
dq_i = std::min(ACOS_TABLE_SIZE, std::max(-ACOS_TABLE_SIZE, dq_i));
const float fD = acos_table[dp_i + ACOS_TABLE_SIZE] + acos_table[dq_i + ACOS_TABLE_SIZE];
#else
const float CV_PI_inv = static_cast<float>(1.0 / CV_PI);
const float fD = (acosf(dp) + acosf(dq)) * CV_PI_inv; // TODO optimize acos calls (through tables)
const float CV_PI_inv = static_cast<float>(1.0 / CV_PI);
const float fD = (acosf(dp) + acosf(dq)) * CV_PI_inv; // TODO optimize acos calls (through tables)
#endif
float cost =
weight_non_edge_compute * non_edge_feature.at<uchar>(q) +
weight_gradient_direction * fD +
weight_gradient_magnitude * fG;
return cost;
cost_p += weight_gradient_direction * fD;
}
}
return cost_p < cost_q;
}
struct Pix
@ -625,8 +646,8 @@ struct IntelligentScissorsMB::Impl
CV_DbgCheckLE(cost_q, cost_r, "INTERNAL ERROR: sorted queue is corrupted");
#endif
float cost = cost_q + local_cost(q, r); // TODO(opt): compute partially until cost < cost_r
if (cost < cost_r)
float cost = cost_q;
if (add_local_cost(q, r, cost, cost_r))
{
#if 0 // avoid compiler warning
if (cost_r != FLT_MAX)

@ -147,11 +147,72 @@ void show(const Mat& img, const std::vector<Point> pts)
}
}
Size estimateContourSize(const std::vector<Point>& pts)
{
Size s(0,0);
for (size_t i = 0; i < pts.size(); i++)
{
if (s.width < pts[i].x)
s.width = pts[i].x;
if (s.height < pts[i].y)
s.height = pts[i].y;
}
return s;
}
int contoursAreaPixelsMismatch(const std::vector<Point>& pts, const std::vector<Point>& gt)
{
Size ptsSize = estimateContourSize(pts);
Size gtSize = estimateContourSize(gt);
Size imgSize(std::max(ptsSize.width, gtSize.width)+1, std::max(ptsSize.height, gtSize.height)+1);
Mat ptsArea = Mat::zeros(imgSize, CV_8UC1);
Mat gtArea = Mat::zeros(imgSize, CV_8UC1);
std::vector<std::vector<Point>> pts_wrapped = {pts};
std::vector<std::vector<Point>> gt_wrapped = {gt};
drawContours(ptsArea, pts_wrapped, -1, Scalar(255), FILLED);
drawContours(gtArea, gt_wrapped, -1, Scalar(255), FILLED);
Mat uni = ptsArea | gtArea;
Mat intersection = ptsArea & gtArea;
bitwise_not(intersection, intersection);
Mat delta = uni & intersection;
return countNonZero(delta);
}
void checkContour(std::vector<Point>& pts,
const bool backward = false,
int allowed_mismatch = 0)
{
const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
CV_Assert(test_info);
const std::string name = std::string(cvtest::TS::ptr()->get_data_path() + "imgproc/" + test_info->test_case_name() + "-" + test_info->name() + (backward ? "-backward" : "") + ".xml");
std::vector<Point> reference_pts;
#ifdef GENERATE_TEST_DATA
{
cv::FileStorage fs(name, cv::FileStorage::WRITE);
fs << "pts" << pts;
}
reference_pts = pts;
#else
FileStorage fs(name, FileStorage::READ);
read(fs["pts"], reference_pts, std::vector<Point>());
#endif
if (!allowed_mismatch)
EXPECT_EQ(pts, reference_pts);
else
EXPECT_LE(contoursAreaPixelsMismatch(pts, reference_pts), allowed_mismatch);
}
TEST(Imgproc_IntelligentScissorsMB, rect)
{
segmentation::IntelligentScissorsMB tool;
tool.applyImage(getTestImage1());
Mat image = getTestImage1();
tool.applyImage(image);
Point source_point(50, 30);
tool.buildMap(source_point);
@ -159,15 +220,18 @@ TEST(Imgproc_IntelligentScissorsMB, rect)
Point target_point(100, 30);
std::vector<Point> pts;
tool.getContour(target_point, pts);
checkContour(pts);
show(image, pts);
tool.applyImage(getTestImage2());
Mat image2 = getTestImage2();
tool.applyImage(image2);
tool.buildMap(source_point);
std::vector<Point> pts2;
tool.getContour(target_point, pts2, true/*backward*/);
EXPECT_EQ(pts.size(), pts2.size());
checkContour(pts2, true/*backward*/);
show(image2, pts2);
}
TEST(Imgproc_IntelligentScissorsMB, lines)
@ -182,8 +246,7 @@ TEST(Imgproc_IntelligentScissorsMB, lines)
Point target_point(150, 50);
std::vector<Point> pts;
tool.getContour(target_point, pts);
EXPECT_EQ((size_t)121, pts.size());
checkContour(pts);
show(image, pts);
}
@ -201,8 +264,7 @@ TEST(Imgproc_IntelligentScissorsMB, circles)
Point target_point(150, 50);
std::vector<Point> pts;
tool.getContour(target_point, pts);
EXPECT_EQ((size_t)101, pts.size());
checkContour(pts);
show(image, pts);
}
@ -218,13 +280,10 @@ TEST(Imgproc_IntelligentScissorsMB, circles_gradient)
Point target_point(150, 50);
std::vector<Point> pts;
tool.getContour(target_point, pts);
EXPECT_EQ((size_t)101, pts.size());
checkContour(pts);
show(image, pts);
}
#define PTS_SIZE_EPS 2
TEST(Imgproc_IntelligentScissorsMB, grayscale)
{
segmentation::IntelligentScissorsMB tool;
@ -238,10 +297,7 @@ TEST(Imgproc_IntelligentScissorsMB, grayscale)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 206;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 2);
show(image, pts);
}
@ -260,10 +316,7 @@ TEST(Imgproc_IntelligentScissorsMB, check_features_grayscale_1_0_0_zerro_crossin
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 207;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 11);
show(image, pts);
}
@ -282,10 +335,7 @@ TEST(Imgproc_IntelligentScissorsMB, check_features_grayscale_1_0_0_canny)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 201;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 6);
show(image, pts);
}
@ -303,10 +353,7 @@ TEST(Imgproc_IntelligentScissorsMB, check_features_grayscale_0_1_0)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 166;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 4);
show(image, pts);
}
@ -324,10 +371,7 @@ TEST(Imgproc_IntelligentScissorsMB, check_features_grayscale_0_0_1)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 197;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 2);
show(image, pts);
}
@ -344,10 +388,7 @@ TEST(Imgproc_IntelligentScissorsMB, color)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 205;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 2);
show(image, pts);
}
@ -365,10 +406,7 @@ TEST(Imgproc_IntelligentScissorsMB, color_canny)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 200;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 2);
show(image, pts);
}
@ -397,10 +435,7 @@ TEST(Imgproc_IntelligentScissorsMB, color_custom_features_edge)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 201;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 2);
show(image, pts);
}
@ -427,10 +462,7 @@ TEST(Imgproc_IntelligentScissorsMB, color_custom_features_all)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 201;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 9);
show(image, pts);
}
@ -456,10 +488,7 @@ TEST(Imgproc_IntelligentScissorsMB, color_custom_features_edge_magnitude)
Point target_point(413, 155);
std::vector<Point> pts;
tool.getContour(target_point, pts);
size_t gold = 201;
EXPECT_GE(pts.size(), gold - PTS_SIZE_EPS);
EXPECT_LE(pts.size(), gold + PTS_SIZE_EPS);
checkContour(pts, false, 9);
show(image, pts);
}

@ -319,19 +319,31 @@ class JSWrapperGenerator(object):
sys.exit(-1)
self.classes[class_info.name] = class_info
if class_info.bases:
chunks = class_info.bases[0].split('::')
base = '_'.join(chunks)
while base not in self.classes and len(chunks) > 1:
del chunks[-2]
def resolve_class_inheritance(self):
new_classes = {}
for name, class_info in self.classes.items():
if not hasattr(class_info, 'bases'):
new_classes[name] = class_info
continue # not class
if class_info.bases:
chunks = class_info.bases[0].split('::')
base = '_'.join(chunks)
if base not in self.classes:
print("Generator error: unable to resolve base %s for %s"
% (class_info.bases[0], class_info.name))
sys.exit(-1)
else:
class_info.bases[0] = "::".join(chunks)
class_info.isalgorithm |= self.classes[base].isalgorithm
while base not in self.classes and len(chunks) > 1:
del chunks[-2]
base = '_'.join(chunks)
if base not in self.classes:
print("Generator error: unable to resolve base %s for %s"
% (class_info.bases[0], class_info.name))
sys.exit(-1)
else:
class_info.bases[0] = "::".join(chunks)
class_info.isalgorithm |= self.classes[base].isalgorithm
new_classes[name] = class_info
self.classes = new_classes
def split_decl_name(self, name):
chunks = name.split('.')
@ -759,6 +771,8 @@ class JSWrapperGenerator(object):
else: # class/global function
self.add_func(decl)
self.resolve_class_inheritance()
# step 2: generate bindings
# Global functions
for ns_name, ns in sorted(self.namespaces.items()):
@ -812,6 +826,7 @@ class JSWrapperGenerator(object):
for name, class_info in sorted(self.classes.items()):
class_bindings = []
if not name in white_list:
#print('Not in whitelist: "{}" from ns={}'.format(name, ns_name))
continue
# Generate bindings for methods

@ -89,18 +89,21 @@ using namespace cv;
using namespace cv::segmentation; // FIXIT
using namespace cv::aruco;
typedef aruco::DetectorParameters aruco_DetectorParameters;
typedef QRCodeDetectorAruco::Params QRCodeDetectorAruco_Params;
#ifdef HAVE_OPENCV_DNN
using namespace cv::dnn;
#endif
#ifdef HAVE_OPENCV_ARUCO
using namespace aruco;
#endif
#ifdef HAVE_OPENCV_VIDEO
typedef TrackerMIL::Params TrackerMIL_Params;
#endif
// HACK: JS generator ommits namespace for parameter types for some reason. Added typedef to handle std::string correctly
typedef std::string string;
namespace binding_utils
{
template<typename classT, typename enumT>

@ -199,4 +199,102 @@ QUnit.test('QR code detect and decode', function (assert) {
mat.delete();
}
});
});
QUnit.test('Aruco-based QR code detect', function (assert) {
{
let qrcode_params = new cv.QRCodeDetectorAruco_Params();
let detector = new cv.QRCodeDetectorAruco();
let mat = cv.Mat.ones(800, 600, cv.CV_8U);
assert.ok(mat);
detector.setDetectorParameters(qrcode_params);
let points = new cv.Mat();
let qrCodeFound = detector.detect(mat, points);
assert.equal(points.rows, 0)
assert.equal(points.cols, 0)
assert.equal(qrCodeFound, false);
qrcode_params.delete();
detector.delete();
points.delete();
mat.delete();
}
});
QUnit.test('Bar code detect', function (assert) {
{
let detector = new cv.barcode_BarcodeDetector();
let mat = cv.Mat.ones(800, 600, cv.CV_8U);
assert.ok(mat);
let points = new cv.Mat();
let codeFound = detector.detect(mat, points);
assert.equal(points.rows, 0)
assert.equal(points.cols, 0)
assert.equal(codeFound, false);
codeContent = detector.detectAndDecode(mat);
assert.equal(typeof codeContent, 'string');
assert.equal(codeContent, '');
detector.delete();
points.delete();
mat.delete();
}
});
QUnit.test('Aruco detector', function (assert) {
{
let dictionary = cv.getPredefinedDictionary(cv.DICT_4X4_50);
let aruco_image = new cv.Mat();
let detectorParameters = new cv.aruco_DetectorParameters();
let refineParameters = new cv.aruco_RefineParameters(10, 3, true);
let detector = new cv.aruco_ArucoDetector(dictionary, detectorParameters,refineParameters);
let corners = new cv.MatVector();
let ids = new cv.Mat();
dictionary.generateImageMarker(10, 128, aruco_image);
assert.ok(!aruco_image.empty());
detector.detectMarkers(aruco_image, corners, ids);
dictionary.delete();
aruco_image.delete();
detectorParameters.delete();
refineParameters.delete();
detector.delete();
corners.delete();
ids.delete();
}
});
QUnit.test('Charuco detector', function (assert) {
{
let dictionary = new cv.getPredefinedDictionary(cv.DICT_4X4_50);
let boardIds = new cv.Mat();
let board = new cv.aruco_CharucoBoard(new cv.Size(3, 5), 64, 32, dictionary, boardIds);
let charucoParameters = new cv.aruco_CharucoParameters();
let detectorParameters = new cv.aruco_DetectorParameters();
let refineParameters = new cv.aruco_RefineParameters(10, 3, true);
let detector = new cv.aruco_CharucoDetector(board, charucoParameters, detectorParameters, refineParameters);
let board_image = new cv.Mat();
let corners = new cv.Mat();
let ids = new cv.Mat();
board.generateImage(new cv.Size(300, 500), board_image);
assert.ok(!board_image.empty());
detector.detectBoard(board_image, corners, ids);
assert.ok(!corners.empty());
assert.ok(!ids.empty());
dictionary.delete();
boardIds.delete();
board.delete();
board_image.delete();
charucoParameters.delete();
detectorParameters.delete();
refineParameters.delete();
detector.delete();
corners.delete();
ids.delete();
}
});

@ -18,3 +18,32 @@
year = {2016},
month = {October}
}
@mastersthesis{Xiangmin2015research,
title={Research on Barcode Recognition Technology In a Complex Background},
author={Xiangmin, Wang},
year={2015},
school={Huazhong University of Science and Technology}
}
@article{bazen2002systematic,
title={Systematic methods for the computation of the directional fields and singular points of fingerprints},
author={Bazen, Asker M and Gerez, Sabih H},
journal={IEEE transactions on pattern analysis and machine intelligence},
volume={24},
number={7},
pages={905--919},
year={2002},
publisher={IEEE}
}
@article{kass1987analyzing,
title={Analyzing oriented patterns},
author={Kass, Michael and Witkin, Andrew},
journal={Computer vision, graphics, and image processing},
volume={37},
number={3},
pages={362--385},
year={1987},
publisher={Elsevier}
}

@ -45,6 +45,8 @@
#define OPENCV_OBJDETECT_HPP
#include "opencv2/core.hpp"
#include "opencv2/objdetect/aruco_detector.hpp"
#include "opencv2/objdetect/graphical_code_detector.hpp"
/**
@defgroup objdetect Object Detection
@ -101,6 +103,7 @@ using a Boosted Cascade of Simple Features. IEEE CVPR, 2001. The paper is availa
<https://github.com/SvHey/thesis/blob/master/Literature/ObjectDetection/violaJones_CVPR2001.pdf>
@defgroup objdetect_hog HOG (Histogram of Oriented Gradients) descriptor and object detector
@defgroup objdetect_barcode Barcode detection and decoding
@defgroup objdetect_qrcode QRCode detection and encoding
@defgroup objdetect_dnn_face DNN-based face detection and recognition
Check @ref tutorial_dnn_face "the corresponding tutorial" for more details.
@ -753,44 +756,27 @@ public:
CV_WRAP virtual void encodeStructuredAppend(const String& encoded_info, OutputArrayOfArrays qrcodes) = 0;
};
class CV_EXPORTS_W QRCodeDetector
class CV_EXPORTS_W_SIMPLE QRCodeDetector : public GraphicalCodeDetector
{
public:
CV_WRAP QRCodeDetector();
~QRCodeDetector();
/** @brief sets the epsilon used during the horizontal scan of QR code stop marker detection.
@param epsX Epsilon neighborhood, which allows you to determine the horizontal pattern
of the scheme 1:1:3:1:1 according to QR code standard.
*/
CV_WRAP void setEpsX(double epsX);
CV_WRAP QRCodeDetector& setEpsX(double epsX);
/** @brief sets the epsilon used during the vertical scan of QR code stop marker detection.
@param epsY Epsilon neighborhood, which allows you to determine the vertical pattern
of the scheme 1:1:3:1:1 according to QR code standard.
*/
CV_WRAP void setEpsY(double epsY);
CV_WRAP QRCodeDetector& setEpsY(double epsY);
/** @brief use markers to improve the position of the corners of the QR code
*
* alignmentMarkers using by default
*/
CV_WRAP void setUseAlignmentMarkers(bool useAlignmentMarkers);
/** @brief Detects QR code in image and returns the quadrangle containing the code.
@param img grayscale or color (BGR) image containing (or not) QR code.
@param points Output vector of vertices of the minimum-area quadrangle containing the code.
*/
CV_WRAP bool detect(InputArray img, OutputArray points) const;
/** @brief Decodes QR code in image once it's found by the detect() method.
Returns UTF8-encoded output string or empty string if the code cannot be decoded.
@param img grayscale or color (BGR) image containing QR code.
@param points Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_qrcode The optional output image containing rectified and binarized QR code
*/
CV_WRAP std::string decode(InputArray img, InputArray points, OutputArray straight_qrcode = noArray());
CV_WRAP QRCodeDetector& setUseAlignmentMarkers(bool useAlignmentMarkers);
/** @brief Decodes QR code on a curved surface in image once it's found by the detect() method.
@ -801,15 +787,6 @@ public:
*/
CV_WRAP cv::String decodeCurved(InputArray img, InputArray points, OutputArray straight_qrcode = noArray());
/** @brief Both detects and decodes QR code
@param img grayscale or color (BGR) image containing QR code.
@param points optional output array of vertices of the found QR code quadrangle. Will be empty if not found.
@param straight_qrcode The optional output image containing rectified and binarized QR code
*/
CV_WRAP std::string detectAndDecode(InputArray img, OutputArray points=noArray(),
OutputArray straight_qrcode = noArray());
/** @brief Both detects and decodes QR code on a curved surface
@param img grayscale or color (BGR) image containing QR code.
@ -818,43 +795,58 @@ public:
*/
CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(),
OutputArray straight_qrcode = noArray());
};
/** @brief Detects QR codes in image and returns the vector of the quadrangles containing the codes.
@param img grayscale or color (BGR) image containing (or not) QR codes.
@param points Output vector of vector of vertices of the minimum-area quadrangle containing the codes.
*/
CV_WRAP
bool detectMulti(InputArray img, OutputArray points) const;
/** @brief Decodes QR codes in image once it's found by the detect() method.
@param img grayscale or color (BGR) image containing QR codes.
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
@param points vector of Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_qrcode The optional output vector of images containing rectified and binarized QR codes
*/
CV_WRAP
bool decodeMulti(
InputArray img, InputArray points,
CV_OUT std::vector<std::string>& decoded_info,
OutputArrayOfArrays straight_qrcode = noArray()
) const;
/** @brief Both detects and decodes QR codes
@param img grayscale or color (BGR) image containing QR codes.
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
@param points optional output vector of vertices of the found QR code quadrangles. Will be empty if not found.
@param straight_qrcode The optional output vector of images containing rectified and binarized QR codes
*/
CV_WRAP
bool detectAndDecodeMulti(
InputArray img, CV_OUT std::vector<std::string>& decoded_info,
OutputArray points = noArray(),
OutputArrayOfArrays straight_qrcode = noArray()
) const;
class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector {
public:
CV_WRAP QRCodeDetectorAruco();
protected:
struct Impl;
Ptr<Impl> p;
struct CV_EXPORTS_W_SIMPLE Params {
CV_WRAP Params();
/** @brief The minimum allowed pixel size of a QR module in the smallest image in the image pyramid, default 4.f */
CV_PROP_RW float minModuleSizeInPyramid;
/** @brief The maximum allowed relative rotation for finder patterns in the same QR code, default pi/12 */
CV_PROP_RW float maxRotation;
/** @brief The maximum allowed relative mismatch in module sizes for finder patterns in the same QR code, default 1.75f */
CV_PROP_RW float maxModuleSizeMismatch;
/** @brief The maximum allowed module relative mismatch for timing pattern module, default 2.f
*
* If relative mismatch of timing pattern module more this value, penalty points will be added.
* If a lot of penalty points are added, QR code will be rejected. */
CV_PROP_RW float maxTimingPatternMismatch;
/** @brief The maximum allowed percentage of penalty points out of total pins in timing pattern, default 0.4f */
CV_PROP_RW float maxPenalties;
/** @brief The maximum allowed relative color mismatch in the timing pattern, default 0.2f*/
CV_PROP_RW float maxColorsMismatch;
/** @brief The algorithm find QR codes with almost minimum timing pattern score and minimum size, default 0.9f
*
* The QR code with the minimum "timing pattern score" and minimum "size" is selected as the best QR code.
* If for the current QR code "timing pattern score" * scaleTimingPatternScore < "previous timing pattern score" and "size" < "previous size", then
* current QR code set as the best QR code. */
CV_PROP_RW float scaleTimingPatternScore;
};
/** @brief QR code detector constructor for Aruco-based algorithm. See cv::QRCodeDetectorAruco::Params */
CV_WRAP explicit QRCodeDetectorAruco(const QRCodeDetectorAruco::Params& params);
/** @brief Detector parameters getter. See cv::QRCodeDetectorAruco::Params */
CV_WRAP const QRCodeDetectorAruco::Params& getDetectorParameters() const;
/** @brief Detector parameters setter. See cv::QRCodeDetectorAruco::Params */
CV_WRAP QRCodeDetectorAruco& setDetectorParameters(const QRCodeDetectorAruco::Params& params);
/** @brief Aruco detector parameters are used to search for the finder patterns. */
CV_WRAP aruco::DetectorParameters getArucoParameters();
/** @brief Aruco detector parameters are used to search for the finder patterns. */
CV_WRAP void setArucoParameters(const aruco::DetectorParameters& params);
};
//! @}
@ -862,7 +854,7 @@ protected:
#include "opencv2/objdetect/detection_based_tracker.hpp"
#include "opencv2/objdetect/face.hpp"
#include "opencv2/objdetect/aruco_detector.hpp"
#include "opencv2/objdetect/charuco_detector.hpp"
#include "opencv2/objdetect/barcode.hpp"
#endif

@ -90,7 +90,7 @@ public:
*/
CV_WRAP void generateImage(Size outSize, OutputArray img, int marginSize = 0, int borderBits = 1) const;
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first)
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
Board();
struct Impl;
@ -122,7 +122,7 @@ public:
CV_WRAP float getMarkerLength() const;
CV_WRAP float getMarkerSeparation() const;
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first)
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
GridBoard();
};
@ -187,7 +187,7 @@ public:
*/
CV_WRAP bool checkCharucoCornersCollinear(InputArray charucoIds) const;
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first)
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
CharucoBoard();
};

@ -34,7 +34,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
minCornerDistanceRate = 0.05;
minDistanceToBorder = 3;
minMarkerDistanceRate = 0.05;
cornerRefinementMethod = CORNER_REFINE_NONE;
cornerRefinementMethod = (int)CORNER_REFINE_NONE;
cornerRefinementWinSize = 5;
cornerRefinementMaxIterations = 30;
cornerRefinementMinAccuracy = 0.1;
@ -106,7 +106,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
CV_PROP_RW double minMarkerDistanceRate;
/** @brief default value CORNER_REFINE_NONE */
CV_PROP_RW CornerRefineMethod cornerRefinementMethod;
CV_PROP_RW int cornerRefinementMethod;
/// window size for the corner refinement process (in pixels) (default 5).
CV_PROP_RW int cornerRefinementWinSize;

@ -110,7 +110,8 @@ enum PredefinedDictionaryType {
DICT_APRILTAG_16h5, ///< 4x4 bits, minimum hamming distance between any two codes = 5, 30 codes
DICT_APRILTAG_25h9, ///< 5x5 bits, minimum hamming distance between any two codes = 9, 35 codes
DICT_APRILTAG_36h10, ///< 6x6 bits, minimum hamming distance between any two codes = 10, 2320 codes
DICT_APRILTAG_36h11 ///< 6x6 bits, minimum hamming distance between any two codes = 11, 587 codes
DICT_APRILTAG_36h11, ///< 6x6 bits, minimum hamming distance between any two codes = 11, 587 codes
DICT_ARUCO_MIP_36h12 ///< 6x6 bits, minimum hamming distance between any two codes = 12, 250 codes
};

@ -0,0 +1,65 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_OBJDETECT_BARCODE_HPP
#define OPENCV_OBJDETECT_BARCODE_HPP
#include <opencv2/core.hpp>
#include <opencv2/objdetect/graphical_code_detector.hpp>
namespace cv {
namespace barcode {
//! @addtogroup objdetect_barcode
//! @{
class CV_EXPORTS_W_SIMPLE BarcodeDetector : public cv::GraphicalCodeDetector
{
public:
/** @brief Initialize the BarcodeDetector.
*/
CV_WRAP BarcodeDetector();
/** @brief Initialize the BarcodeDetector.
*
* Parameters allow to load _optional_ Super Resolution DNN model for better quality.
* @param prototxt_path prototxt file path for the super resolution model
* @param model_path model file path for the super resolution model
*/
CV_WRAP BarcodeDetector(const std::string &prototxt_path, const std::string &model_path);
~BarcodeDetector();
/** @brief Decodes barcode in image once it's found by the detect() method.
*
* @param img grayscale or color (BGR) image containing bar code.
* @param points vector of rotated rectangle vertices found by detect() method (or some other algorithm).
* For N detected barcodes, the dimensions of this array should be [N][4].
* Order of four points in vector<Point2f> is bottomLeft, topLeft, topRight, bottomRight.
* @param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
* @param decoded_type vector strings, specifies the type of these barcodes
* @return true if at least one valid barcode have been found
*/
CV_WRAP bool decodeWithType(InputArray img,
InputArray points,
CV_OUT std::vector<std::string> &decoded_info,
CV_OUT std::vector<std::string> &decoded_type) const;
/** @brief Both detects and decodes barcode
* @param img grayscale or color (BGR) image containing barcode.
* @param decoded_info UTF8-encoded output vector of string(s) or empty vector of string if the codes cannot be decoded.
* @param decoded_type vector of strings, specifies the type of these barcodes
* @param points optional output vector of vertices of the found barcode rectangle. Will be empty if not found.
* @return true if at least one valid barcode have been found
*/
CV_WRAP bool detectAndDecodeWithType(InputArray img,
CV_OUT std::vector<std::string> &decoded_info,
CV_OUT std::vector<std::string> &decoded_type,
OutputArray points = noArray()) const;
};
//! @}
}} // cv::barcode::
#endif // OPENCV_OBJDETECT_BARCODE_HPP

@ -0,0 +1,81 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_HPP
#define OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_HPP
#include <opencv2/core.hpp>
namespace cv {
//! @addtogroup objdetect_common
//! @{
class CV_EXPORTS_W_SIMPLE GraphicalCodeDetector {
public:
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
GraphicalCodeDetector();
GraphicalCodeDetector(const GraphicalCodeDetector&) = default;
GraphicalCodeDetector(GraphicalCodeDetector&&) = default;
GraphicalCodeDetector& operator=(const GraphicalCodeDetector&) = default;
GraphicalCodeDetector& operator=(GraphicalCodeDetector&&) = default;
/** @brief Detects graphical code in image and returns the quadrangle containing the code.
@param img grayscale or color (BGR) image containing (or not) graphical code.
@param points Output vector of vertices of the minimum-area quadrangle containing the code.
*/
CV_WRAP bool detect(InputArray img, OutputArray points) const;
/** @brief Decodes graphical code in image once it's found by the detect() method.
Returns UTF8-encoded output string or empty string if the code cannot be decoded.
@param img grayscale or color (BGR) image containing graphical code.
@param points Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_code The optional output image containing binarized code, will be empty if not found.
*/
CV_WRAP std::string decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const;
/** @brief Both detects and decodes graphical code
@param img grayscale or color (BGR) image containing graphical code.
@param points optional output array of vertices of the found graphical code quadrangle, will be empty if not found.
@param straight_code The optional output image containing binarized code
*/
CV_WRAP std::string detectAndDecode(InputArray img, OutputArray points = noArray(),
OutputArray straight_code = noArray()) const;
/** @brief Detects graphical codes in image and returns the vector of the quadrangles containing the codes.
@param img grayscale or color (BGR) image containing (or not) graphical codes.
@param points Output vector of vector of vertices of the minimum-area quadrangle containing the codes.
*/
CV_WRAP bool detectMulti(InputArray img, OutputArray points) const;
/** @brief Decodes graphical codes in image once it's found by the detect() method.
@param img grayscale or color (BGR) image containing graphical codes.
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
@param points vector of Quadrangle vertices found by detect() method (or some other algorithm).
@param straight_code The optional output vector of images containing binarized codes
*/
CV_WRAP bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector<std::string>& decoded_info,
OutputArrayOfArrays straight_code = noArray()) const;
/** @brief Both detects and decodes graphical codes
@param img grayscale or color (BGR) image containing graphical codes.
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded.
@param points optional output vector of vertices of the found graphical code quadrangles. Will be empty if not found.
@param straight_code The optional vector of images containing binarized codes
*/
CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<std::string>& decoded_info, OutputArray points = noArray(),
OutputArrayOfArrays straight_code = noArray()) const;
struct Impl;
protected:
Ptr<Impl> p;
};
//! @}
}
#endif

@ -0,0 +1,50 @@
package org.opencv.test.barcode;
import java.util.List;
import org.opencv.core.Mat;
import org.opencv.objdetect.BarcodeDetector;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.test.OpenCVTestCase;
import java.util.ArrayList;
public class BarcodeDetectorTest extends OpenCVTestCase {
private final static String ENV_OPENCV_TEST_DATA_PATH = "OPENCV_TEST_DATA_PATH";
private String testDataPath;
@Override
protected void setUp() throws Exception {
super.setUp();
testDataPath = System.getenv(ENV_OPENCV_TEST_DATA_PATH);
if (testDataPath == null)
throw new Exception(ENV_OPENCV_TEST_DATA_PATH + " has to be defined!");
}
public void testDetectAndDecode() {
Mat img = Imgcodecs.imread(testDataPath + "/cv/barcode/multiple/4_barcodes.jpg");
assertFalse(img.empty());
BarcodeDetector detector = new BarcodeDetector();
assertNotNull(detector);
List < String > infos = new ArrayList< String >();
List < String > types = new ArrayList< String >();
boolean result = detector.detectAndDecodeWithType(img, infos, types);
assertTrue(result);
assertEquals(infos.size(), 4);
assertEquals(types.size(), 4);
final String[] correctResults = {"9787122276124", "9787118081473", "9787564350840", "9783319200064"};
for (int i = 0; i < 4; i++) {
assertEquals(types.get(i), "EAN_13");
result = false;
for (int j = 0; j < 4; j++) {
if (correctResults[j].equals(infos.get(i))) {
result = true;
break;
}
}
assertTrue(result);
}
}
}

@ -0,0 +1,7 @@
{
"ManualFuncs" : {
"QRCodeDetectorAruco": {
"getDetectorParameters": { "declaration" : [""], "implementation" : [""] }
}
}
}

@ -0,0 +1,33 @@
#!/usr/bin/env python
'''
===============================================================================
Barcode detect and decode pipeline.
===============================================================================
'''
import os
import numpy as np
import cv2 as cv
from tests_common import NewOpenCVTests
class barcode_detector_test(NewOpenCVTests):
def test_detect(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/multiple/4_barcodes.jpg'))
self.assertFalse(img is None)
detector = cv.barcode_BarcodeDetector()
retval, corners = detector.detect(img)
self.assertTrue(retval)
self.assertEqual(corners.shape, (4, 4, 2))
def test_detect_and_decode(self):
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/single/book.jpg'))
self.assertFalse(img is None)
detector = cv.barcode_BarcodeDetector()
retval, decoded_info, decoded_type, corners = detector.detectAndDecodeWithType(img)
self.assertTrue(retval)
self.assertTrue(len(decoded_info) > 0)
self.assertTrue(len(decoded_type) > 0)
self.assertEqual(decoded_info[0], "9787115279460")
self.assertEqual(decoded_type[0], "EAN_13")
self.assertEqual(corners.shape, (1, 4, 2))

@ -171,7 +171,7 @@ PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS) {
aruco::DetectorParameters detectorParams;
detectorParams.minDistanceToBorder = 1;
detectorParams.markerBorderBits = 1;
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
detectorParams.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_SUBPIX;
const int markerSize = 100;
const int numMarkersInRow = 9;
@ -203,7 +203,7 @@ PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS) {
aruco::DetectorParameters detectorParams;
detectorParams.minDistanceToBorder = 1;
detectorParams.markerBorderBits = 1;
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
detectorParams.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_SUBPIX;
//USE_ARUCO3
detectorParams.useAruco3Detection = get<0>(testParams);
@ -255,7 +255,7 @@ PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS) {
aruco::DetectorParameters detectorParams;
detectorParams.minDistanceToBorder = 1;
detectorParams.markerBorderBits = 1;
detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
detectorParams.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_SUBPIX;
//USE_ARUCO3
detectorParams.useAruco3Detection = get<0>(testParams).useAruco3Detection;

@ -0,0 +1,114 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "perf_precomp.hpp"
#include "opencv2/objdetect/barcode.hpp"
namespace opencv_test{namespace{
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_multi;
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_single;
PERF_TEST_P_(Perf_Barcode_multi, detect)
{
const string root = "cv/barcode/multiple/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectMulti(src, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
PERF_TEST_P_(Perf_Barcode_multi, detect_decode)
{
const string root = "cv/barcode/multiple/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector<std::string> decoded_info;
vector<std::string> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecodeWithType(src, decoded_info, decoded_type, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
PERF_TEST_P_(Perf_Barcode_single, detect)
{
const string root = "cv/barcode/single/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectMulti(src, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
PERF_TEST_P_(Perf_Barcode_single, detect_decode)
{
const string root = "cv/barcode/single/";
const string name_current_image = get<0>(GetParam());
const cv::Size sz = get<1>(GetParam());
const string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
cv::resize(src, src, sz);
vector<std::string> decoded_info;
vector<std::string> decoded_type;
vector< Point > corners;
auto bardet = barcode::BarcodeDetector();
bool res = false;
TEST_CYCLE()
{
res = bardet.detectAndDecodeWithType(src, decoded_info, decoded_type, corners);
}
SANITY_CHECK_NOTHING();
ASSERT_TRUE(res);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_multi,
testing::Combine(
testing::Values("4_barcodes.jpg"),
testing::Values(cv::Size(2041, 2722), cv::Size(1361, 1815), cv::Size(680, 907))));
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_single,
testing::Combine(
testing::Values("book.jpg", "bottle_1.jpg", "bottle_2.jpg"),
testing::Values(cv::Size(480, 360), cv::Size(640, 480), cv::Size(800, 600))));
}} //namespace

@ -3,6 +3,7 @@
// of this distribution and at http://opencv.org/license.html.
#include "perf_precomp.hpp"
#include "../test/test_qr_utils.hpp"
namespace opencv_test
{
@ -23,7 +24,9 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, detect)
std::vector< Point > corners;
QRCodeDetector qrcode;
TEST_CYCLE() ASSERT_TRUE(qrcode.detect(src, corners));
SANITY_CHECK(corners);
const int pixels_error = 3;
check_qr(root, name_current_image, "test_images", corners, {}, pixels_error);
SANITY_CHECK_NOTHING();
}
#ifdef HAVE_QUIRC
@ -45,48 +48,52 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, decode)
decoded_info = qrcode.decode(src, corners, straight_barcode);
ASSERT_FALSE(decoded_info.empty());
}
std::vector<uint8_t> decoded_info_uint8_t(decoded_info.begin(), decoded_info.end());
SANITY_CHECK(decoded_info_uint8_t);
SANITY_CHECK(straight_barcode);
const int pixels_error = 3;
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
SANITY_CHECK_NOTHING();
}
#endif
typedef ::perf::TestBaseWithParam< std::string > Perf_Objdetect_QRCode_Multi;
typedef ::perf::TestBaseWithParam<std::tuple<std::string, std::string>> Perf_Objdetect_QRCode_Multi;
static inline bool compareCorners(const Point2f& corner1, const Point2f& corner2) {
return corner1.x == corner2.x ? corner1.y < corner2.y : corner1.x < corner2.x;
}
static std::set<std::pair<std::string, std::string>> disabled_samples = {{"5_qrcodes.png", "aruco_based"}};
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti)
{
const std::string name_current_image = GetParam();
const std::string name_current_image = get<0>(GetParam());
const std::string method = get<1>(GetParam());
const std::string root = "cv/qrcode/multiple/";
std::string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
std::vector<Point2f> corners;
QRCodeDetector qrcode;
std::vector<Point> corners;
GraphicalCodeDetector qrcode = QRCodeDetector();
if (method == "aruco_based") {
qrcode = QRCodeDetectorAruco();
}
TEST_CYCLE() ASSERT_TRUE(qrcode.detectMulti(src, corners));
sort(corners.begin(), corners.end(), compareCorners);
SANITY_CHECK(corners);
}
static inline bool compareQR(const pair<string, Mat>& v1, const pair<string, Mat>& v2) {
return v1.first < v2.first;
const int pixels_error = 7;
check_qr(root, name_current_image, "multiple_images", corners, {}, pixels_error, true);
SANITY_CHECK_NOTHING();
}
#ifdef HAVE_QUIRC
PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
{
const std::string name_current_image = GetParam();
const std::string name_current_image = get<0>(GetParam());
std::string method = get<1>(GetParam());
const std::string root = "cv/qrcode/multiple/";
std::string image_path = findDataFile(root + name_current_image);
Mat src = imread(image_path);
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
QRCodeDetector qrcode;
if (disabled_samples.find({name_current_image, method}) != disabled_samples.end()) {
throw SkipTestException(name_current_image + " is disabled sample for method " + method);
}
GraphicalCodeDetector qrcode = QRCodeDetector();
if (method == "aruco_based") {
qrcode = QRCodeDetectorAruco();
}
std::vector<Point2f> corners;
ASSERT_TRUE(qrcode.detectMulti(src, corners));
std::vector<Mat> straight_barcode;
@ -94,26 +101,20 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti)
TEST_CYCLE()
{
ASSERT_TRUE(qrcode.decodeMulti(src, corners, decoded_info, straight_barcode));
for(size_t i = 0; i < decoded_info.size(); i++)
{
ASSERT_FALSE(decoded_info[i].empty());
}
}
ASSERT_TRUE(decoded_info.size() > 0ull);
for(size_t i = 0; i < decoded_info.size(); i++) {
ASSERT_FALSE(decoded_info[i].empty());
}
ASSERT_EQ(decoded_info.size(), straight_barcode.size());
vector<pair<string, Mat> > result;
for (size_t i = 0ull; i < decoded_info.size(); i++) {
result.push_back(make_pair(decoded_info[i], straight_barcode[i]));
vector<Point> corners_result(corners.size());
for (size_t i = 0ull; i < corners_result.size(); i++) {
corners_result[i] = corners[i];
}
sort(result.begin(), result.end(), compareQR);
vector<vector<uint8_t> > decoded_info_sort;
vector<Mat> straight_barcode_sort;
for (size_t i = 0ull; i < result.size(); i++) {
vector<uint8_t> tmp(result[i].first.begin(), result[i].first.end());
decoded_info_sort.push_back(tmp);
straight_barcode_sort.push_back(result[i].second);
}
SANITY_CHECK(decoded_info_sort);
const int pixels_error = 7;
check_qr(root, name_current_image, "multiple_images", corners_result, decoded_info, pixels_error, true);
SANITY_CHECK_NOTHING();
}
#endif
@ -127,11 +128,10 @@ INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode,
// version_5_right.jpg DISABLED after tile fix, PR #22025
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode_Multi,
::testing::Values(
"2_qrcodes.png", "3_close_qrcodes.png", "3_qrcodes.png", "4_qrcodes.png",
"5_qrcodes.png", "6_qrcodes.png", "7_qrcodes.png", "8_close_qrcodes.png"
)
);
testing::Combine(testing::Values("2_qrcodes.png", "3_close_qrcodes.png", "3_qrcodes.png", "4_qrcodes.png",
"5_qrcodes.png", "6_qrcodes.png", "7_qrcodes.png", "8_close_qrcodes.png"),
testing::Values("contours_based", "aruco_based")));
typedef ::perf::TestBaseWithParam< tuple< std::string, Size > > Perf_Objdetect_Not_QRCode;

@ -881,7 +881,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
}
else {
// always turn on corner refinement in case of Aruco3, due to upsampling
detectorParams.cornerRefinementMethod = CORNER_REFINE_SUBPIX;
detectorParams.cornerRefinementMethod = (int)CORNER_REFINE_SUBPIX;
// only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection
// Todo: update other CORNER_REFINE methods
}
@ -923,7 +923,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
vector<vector<vector<Point> > > contoursSet;
/// STEP 2.a Detect marker candidates :: using AprilTag
if(detectorParams.cornerRefinementMethod == CORNER_REFINE_APRILTAG){
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_APRILTAG){
_apriltag(grey, detectorParams, candidates, contours);
candidatesSet.push_back(candidates);
@ -938,7 +938,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
candidates, contours, ids, detectorParams, _rejectedImgPoints);
/// STEP 3: Corner refinement :: use corner subpix
if (detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) {
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 &&
detectorParams.cornerRefinementMinAccuracy > 0);
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
@ -963,7 +963,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
}
/// STEP 3, Optional : Corner refinement :: use contour container
if (detectorParams.cornerRefinementMethod == CORNER_REFINE_CONTOUR){
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_CONTOUR){
if (!ids.empty()) {
@ -976,7 +976,7 @@ void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corner
}
}
if (detectorParams.cornerRefinementMethod != CORNER_REFINE_SUBPIX && fxfy != 1.f) {
if (detectorParams.cornerRefinementMethod != (int)CORNER_REFINE_SUBPIX && fxfy != 1.f) {
// only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection
// Todo: update other CORNER_REFINE methods
@ -1213,7 +1213,7 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board
if(closestCandidateIdx >= 0) {
// subpixel refinement
if(detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) {
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
CV_Assert(detectorParams.cornerRefinementWinSize > 0 &&
detectorParams.cornerRefinementMaxIterations > 0 &&
detectorParams.cornerRefinementMinAccuracy > 0);

@ -258,6 +258,8 @@ Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
static const Dictionary DICT_ARUCO_MIP_36h12_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_MIP_36h12_BYTES), 6, 12);
switch(name) {
case DICT_ARUCO_ORIGINAL:
@ -308,6 +310,8 @@ Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
case DICT_APRILTAG_36h11:
return Dictionary(DICT_APRILTAG_36h11_DATA);
case DICT_ARUCO_MIP_36h12:
return Dictionary(DICT_ARUCO_MIP_36h12_DATA);
}
return Dictionary(DICT_4X4_50_DATA);
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,374 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "precomp.hpp"
#include <opencv2/objdetect/barcode.hpp>
#include <opencv2/core/utils/filesystem.hpp>
#include "barcode_decoder/ean13_decoder.hpp"
#include "barcode_decoder/ean8_decoder.hpp"
#include "barcode_detector/bardetect.hpp"
#include "barcode_decoder/common/super_scale.hpp"
#include "barcode_decoder/common/utils.hpp"
#include "graphical_code_detector_impl.hpp"
using std::string;
using std::vector;
using std::make_shared;
using std::array;
using std::shared_ptr;
using std::dynamic_pointer_cast;
namespace cv {
namespace barcode {
//==================================================================================================
static bool checkBarInputImage(InputArray img, Mat &gray)
{
CV_Assert(!img.empty());
CV_CheckDepthEQ(img.depth(), CV_8U, "");
if (img.cols() <= 40 || img.rows() <= 40)
{
return false; // image data is not enough for providing reliable results
}
int incn = img.channels();
CV_Check(incn, incn == 1 || incn == 3 || incn == 4, "");
if (incn == 3 || incn == 4)
{
cvtColor(img, gray, COLOR_BGR2GRAY);
}
else
{
gray = img.getMat();
}
return true;
}
static void updatePointsResult(OutputArray points_, const vector<Point2f> &points)
{
if (points_.needed())
{
int N = int(points.size() / 4);
if (N > 0)
{
Mat m_p(N, 4, CV_32FC2, (void *) &points[0]);
int points_type = points_.fixedType() ? points_.type() : CV_32FC2;
m_p.reshape(2, points_.rows()).convertTo(points_, points_type); // Mat layout: N x 4 x 2cn
}
else
{
points_.release();
}
}
}
inline const array<shared_ptr<AbsDecoder>, 2> &getDecoders()
{
//indicate Decoder
static const array<shared_ptr<AbsDecoder>, 2> decoders{
shared_ptr<AbsDecoder>(new Ean13Decoder()), shared_ptr<AbsDecoder>(new Ean8Decoder())};
return decoders;
}
//==================================================================================================
class BarDecode
{
public:
void init(const vector<Mat> &bar_imgs_);
const vector<Result> &getDecodeInformation()
{ return result_info; }
bool decodeMultiplyProcess();
private:
vector<Mat> bar_imgs;
vector<Result> result_info;
};
void BarDecode::init(const vector<Mat> &bar_imgs_)
{
bar_imgs = bar_imgs_;
}
bool BarDecode::decodeMultiplyProcess()
{
static float constexpr THRESHOLD_CONF = 0.6f;
result_info.clear();
result_info.resize(bar_imgs.size());
parallel_for_(Range(0, int(bar_imgs.size())), [&](const Range &range) {
for (int i = range.start; i < range.end; i++)
{
Mat bin_bar;
Result max_res;
float max_conf = -1.f;
bool decoded = false;
for (const auto &decoder:getDecoders())
{
if (decoded)
{ break; }
for (const auto binary_type : binary_types)
{
binarize(bar_imgs[i], bin_bar, binary_type);
auto cur_res = decoder->decodeROI(bin_bar);
if (cur_res.second > max_conf)
{
max_res = cur_res.first;
max_conf = cur_res.second;
if (max_conf > THRESHOLD_CONF)
{
// code decoded
decoded = true;
break;
}
}
} //binary types
} //decoder types
result_info[i] = max_res;
}
});
return !result_info.empty();
}
//==================================================================================================
// Private class definition and implementation (pimpl)
struct BarcodeImpl : public GraphicalCodeDetector::Impl
{
public:
shared_ptr<SuperScale> sr;
bool use_nn_sr = false;
public:
//=================
// own methods
BarcodeImpl() = default;
vector<Mat> initDecode(const Mat &src, const vector<vector<Point2f>> &points) const;
bool decodeWithType(InputArray img,
InputArray points,
vector<string> &decoded_info,
vector<string> &decoded_type) const;
bool detectAndDecodeWithType(InputArray img,
vector<string> &decoded_info,
vector<string> &decoded_type,
OutputArray points_) const;
//=================
// implement interface
~BarcodeImpl() CV_OVERRIDE {}
bool detect(InputArray img, OutputArray points) const CV_OVERRIDE;
string decode(InputArray img, InputArray points, OutputArray straight_code) const CV_OVERRIDE;
string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const CV_OVERRIDE;
bool detectMulti(InputArray img, OutputArray points) const CV_OVERRIDE;
bool decodeMulti(InputArray img, InputArray points, vector<string>& decoded_info, OutputArrayOfArrays straight_code) const CV_OVERRIDE;
bool detectAndDecodeMulti(InputArray img, vector<string>& decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const CV_OVERRIDE;
};
// return cropped and scaled bar img
vector<Mat> BarcodeImpl::initDecode(const Mat &src, const vector<vector<Point2f>> &points) const
{
vector<Mat> bar_imgs;
for (auto &corners : points)
{
Mat bar_img;
cropROI(src, bar_img, corners);
// sharpen(bar_img, bar_img);
// empirical settings
if (bar_img.cols < 320 || bar_img.cols > 640)
{
float scale = 560.0f / static_cast<float>(bar_img.cols);
sr->processImageScale(bar_img, bar_img, scale, use_nn_sr);
}
bar_imgs.emplace_back(bar_img);
}
return bar_imgs;
}
bool BarcodeImpl::decodeWithType(InputArray img,
InputArray points,
vector<string> &decoded_info,
vector<string> &decoded_type) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
return false;
}
CV_Assert(points.size().width > 0);
CV_Assert((points.size().width % 4) == 0);
vector<vector<Point2f>> src_points;
Mat bar_points = points.getMat();
bar_points = bar_points.reshape(2, 1);
for (int i = 0; i < bar_points.size().width; i += 4)
{
vector<Point2f> tempMat = bar_points.colRange(i, i + 4);
if (contourArea(tempMat) > 0.0)
{
src_points.push_back(tempMat);
}
}
CV_Assert(!src_points.empty());
vector<Mat> bar_imgs = initDecode(inarr, src_points);
BarDecode bardec;
bardec.init(bar_imgs);
bardec.decodeMultiplyProcess();
const vector<Result> info = bardec.getDecodeInformation();
decoded_info.clear();
decoded_type.clear();
bool ok = false;
for (const auto &res : info)
{
if (res.isValid())
{
ok = true;
}
decoded_info.emplace_back(res.result);
decoded_type.emplace_back(res.typeString());
}
return ok;
}
bool BarcodeImpl::detectAndDecodeWithType(InputArray img,
vector<string> &decoded_info,
vector<string> &decoded_type,
OutputArray points_) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
points_.release();
return false;
}
vector<Point2f> points;
bool ok = this->detect(inarr, points);
if (!ok)
{
points_.release();
return false;
}
updatePointsResult(points_, points);
decoded_info.clear();
decoded_type.clear();
ok = decodeWithType(inarr, points, decoded_info, decoded_type);
return ok;
}
bool BarcodeImpl::detect(InputArray img, OutputArray points) const
{
Mat inarr;
if (!checkBarInputImage(img, inarr))
{
points.release();
return false;
}
Detect bardet;
bardet.init(inarr);
bardet.localization();
if (!bardet.computeTransformationPoints())
{ return false; }
vector<vector<Point2f>> pnts2f = bardet.getTransformationPoints();
vector<Point2f> trans_points;
for (auto &i : pnts2f)
{
for (const auto &j : i)
{
trans_points.push_back(j);
}
}
updatePointsResult(points, trans_points);
return true;
}
string BarcodeImpl::decode(InputArray img, InputArray points, OutputArray straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_info;
vector<string> decoded_type;
if (!decodeWithType(img, points, decoded_info, decoded_type))
return string();
if (decoded_info.size() < 1)
return string();
return decoded_info[0];
}
string BarcodeImpl::detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_info;
vector<string> decoded_type;
vector<Point> points_;
if (!detectAndDecodeWithType(img, decoded_info, decoded_type, points_))
return string();
if (points_.size() < 4 || decoded_info.size() < 1)
return string();
points_.resize(4);
points.setTo(points_);
return decoded_info[0];
}
bool BarcodeImpl::detectMulti(InputArray img, OutputArray points) const
{
return detect(img, points);
}
bool BarcodeImpl::decodeMulti(InputArray img, InputArray points, vector<string> &decoded_info, OutputArrayOfArrays straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_type;
return decodeWithType(img, points, decoded_info, decoded_type);
}
bool BarcodeImpl::detectAndDecodeMulti(InputArray img, vector<string> &decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const
{
CV_UNUSED(straight_code);
vector<string> decoded_type;
return detectAndDecodeWithType(img, decoded_info, decoded_type, points);
}
//==================================================================================================
// Public class implementation
BarcodeDetector::BarcodeDetector()
: BarcodeDetector(string(), string())
{
}
BarcodeDetector::BarcodeDetector(const string &prototxt_path, const string &model_path)
{
Ptr<BarcodeImpl> p_ = new BarcodeImpl();
p = p_;
if (!prototxt_path.empty() && !model_path.empty())
{
CV_Assert(utils::fs::exists(prototxt_path));
CV_Assert(utils::fs::exists(model_path));
p_->sr = make_shared<SuperScale>();
int res = p_->sr->init(prototxt_path, model_path);
CV_Assert(res == 0);
p_->use_nn_sr = true;
}
}
BarcodeDetector::~BarcodeDetector() = default;
bool BarcodeDetector::decodeWithType(InputArray img, InputArray points, vector<string> &decoded_info, vector<string> &decoded_type) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->decodeWithType(img, points, decoded_info, decoded_type);
}
bool BarcodeDetector::detectAndDecodeWithType(InputArray img, vector<string> &decoded_info, vector<string> &decoded_type, OutputArray points_) const
{
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p);
CV_Assert(p_);
return p_->detectAndDecodeWithType(img, decoded_info, decoded_type, points_);
}
}// namespace barcode
} // namespace cv

@ -0,0 +1,118 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "abs_decoder.hpp"
namespace cv {
namespace barcode {
void cropROI(const Mat &src, Mat &dst, const std::vector<Point2f> &rects)
{
std::vector<Point2f> vertices = rects;
int height = cvRound(norm(vertices[0] - vertices[1]));
int width = cvRound(norm(vertices[1] - vertices[2]));
if (height > width)
{
std::swap(height, width);
Point2f v0 = vertices[0];
vertices.erase(vertices.begin());
vertices.push_back(v0);
}
std::vector<Point2f> dst_vertices{
Point2f(0, (float) (height - 1)), Point2f(0, 0), Point2f((float) (width - 1), 0),
Point2f((float) (width - 1), (float) (height - 1))};
dst.create(Size(width, height), CV_8UC1);
Mat M = getPerspectiveTransform(vertices, dst_vertices);
warpPerspective(src, dst, M, dst.size(), cv::INTER_LINEAR, BORDER_CONSTANT, Scalar(255));
}
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter)
{
size_t counter_length = counter.pattern.size();
std::fill(counter.pattern.begin(), counter.pattern.end(), 0);
counter.sum = 0;
size_t end = row.size();
uchar color = row[start];
uint counterPosition = 0;
while (start < end)
{
if (row[start] == color)
{ // that is, exactly one is true
counter.pattern[counterPosition]++;
counter.sum++;
}
else
{
counterPosition++;
if (counterPosition == counter_length)
{
break;
}
else
{
counter.pattern[counterPosition] = 1;
counter.sum++;
color = 255 - color;
}
}
++start;
}
}
static inline uint
patternMatchVariance(const Counter &counter, const std::vector<int> &pattern, uint maxIndividualVariance)
{
size_t numCounters = counter.pattern.size();
int total = static_cast<int>(counter.sum);
int patternLength = std::accumulate(pattern.cbegin(), pattern.cend(), 0);
if (total < patternLength)
{
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
// and use constexpr functions
return WHITE;// max
}
// We're going to fake floating-point math in integers. We just need to use more bits.
// Scale up patternLength so that intermediate values below like scaledCounter will have
// more "significant digits"
int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
uint totalVariance = 0;
for (uint x = 0; x < numCounters; x++)
{
int cnt = counter.pattern[x] << INTEGER_MATH_SHIFT;
int scaledPattern = pattern[x] * unitBarWidth;
uint variance = std::abs(cnt - scaledPattern);
if (variance > maxIndividualVariance)
{
return WHITE;
}
totalVariance += variance;
}
return totalVariance / total;
}
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. This is reported as the ratio of the total variance from the expected pattern
* proportions across all pattern elements, to the length of the pattern.
*
* @param counters observed counters
* @param pattern expected pattern
* @param maxIndividualVariance The most any counter can differ before we give up
* @return ratio of total variance between counters and pattern compared to total pattern size,
* where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
* the total variance between counters and patterns equals the pattern length, higher values mean
* even more variance
*/
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual)
{
CV_Assert(counters.pattern.size() == pattern.size());
return patternMatchVariance(counters, pattern, maxIndividual);
}
}
}

@ -0,0 +1,99 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_ABS_DECODER_HPP
#define OPENCV_BARCODE_ABS_DECODER_HPP
#include "opencv2/objdetect/barcode.hpp"
namespace cv {
namespace barcode {
using std::string;
using std::vector;
constexpr static uchar BLACK = std::numeric_limits<uchar>::min();
// WHITE elemental area is 0xff
constexpr static uchar WHITE = std::numeric_limits<uchar>::max();
struct Result
{
enum BarcodeType
{
BARCODE_NONE,
BARCODE_EAN_8,
BARCODE_EAN_13,
BARCODE_UPC_A,
BARCODE_UPC_E,
BARCODE_UPC_EAN_EXTENSION
};
std::string result;
BarcodeType format = Result::BARCODE_NONE;
Result() = default;
Result(const std::string &_result, BarcodeType _format)
{
result = _result;
format = _format;
}
string typeString() const
{
switch (format)
{
case Result::BARCODE_EAN_8: return "EAN_8";
case Result::BARCODE_EAN_13: return "EAN_13";
case Result::BARCODE_UPC_E: return "UPC_E";
case Result::BARCODE_UPC_A: return "UPC_A";
case Result::BARCODE_UPC_EAN_EXTENSION: return "UPC_EAN_EXTENSION";
default: return string();
}
}
bool isValid() const
{
return format != BARCODE_NONE;
}
};
struct Counter
{
std::vector<int> pattern;
uint sum;
explicit Counter(const vector<int> &_pattern)
{
pattern = _pattern;
sum = 0;
}
};
class AbsDecoder
{
public:
virtual std::pair<Result, float> decodeROI(const Mat &bar_img) const = 0;
virtual ~AbsDecoder() = default;
protected:
virtual Result decode(const vector<uchar> &data) const = 0;
virtual bool isValid(const string &result) const = 0;
size_t bits_num{};
size_t digit_number{};
};
void cropROI(const Mat &_src, Mat &_dst, const std::vector<Point2f> &rect);
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter);
constexpr static uint INTEGER_MATH_SHIFT = 8;
constexpr static uint PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual);
}
} // namespace cv
#endif // OPENCV_BARCODE_ABS_DECODER_HPP

@ -0,0 +1,195 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Modified from ZXing. Copyright ZXing authors.
// Licensed under the Apache License, Version 2.0 (the "License").
#include "../../precomp.hpp"
#include "hybrid_binarizer.hpp"
namespace cv {
namespace barcode {
#define CLAMP(x, x1, x2) x < (x1) ? (x1) : ((x) > (x2) ? (x2) : (x))
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
// So this is the smallest dimension in each axis we can accept.
constexpr static int BLOCK_SIZE_POWER = 3;
constexpr static int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
constexpr static int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
constexpr static int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
constexpr static int MIN_DYNAMIC_RANGE = 24;
void
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height,
const Mat &black_points, Mat &dst)
{
int maxYOffset = height - BLOCK_SIZE;
int maxXOffset = width - BLOCK_SIZE;
for (int y = 0; y < sub_height; y++)
{
int yoffset = y << BLOCK_SIZE_POWER;
if (yoffset > maxYOffset)
{
yoffset = maxYOffset;
}
int top = CLAMP(y, 2, sub_height - 3);
for (int x = 0; x < sub_width; x++)
{
int xoffset = x << BLOCK_SIZE_POWER;
if (xoffset > maxXOffset)
{
xoffset = maxXOffset;
}
int left = CLAMP(x, 2, sub_width - 3);
int sum = 0;
const auto *black_row = black_points.ptr<uchar>(top - 2);
for (int z = 0; z <= 4; z++)
{
sum += black_row[left - 2] + black_row[left - 1] + black_row[left] + black_row[left + 1] +
black_row[left + 2];
black_row += black_points.cols;
}
int average = sum / 25;
int temp_y = 0;
auto *ptr = dst.ptr<uchar>(yoffset, xoffset);
for (int offset = yoffset * width + xoffset; temp_y < 8; offset += width)
{
for (int temp_x = 0; temp_x < 8; ++temp_x)
{
*(ptr + temp_x) = (luminances[offset + temp_x] & 255) <= average ? 0 : 255;
}
++temp_y;
ptr += width;
}
}
}
}
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height)
{
int maxYOffset = height - BLOCK_SIZE;
int maxXOffset = width - BLOCK_SIZE;
Mat black_points(Size(sub_width, sub_height), CV_8UC1);
for (int y = 0; y < sub_height; y++)
{
int yoffset = y << BLOCK_SIZE_POWER;
if (yoffset > maxYOffset)
{
yoffset = maxYOffset;
}
for (int x = 0; x < sub_width; x++)
{
int xoffset = x << BLOCK_SIZE_POWER;
if (xoffset > maxXOffset)
{
xoffset = maxXOffset;
}
int sum = 0;
int min = 0xFF;
int max = 0;
for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width)
{
for (int xx = 0; xx < BLOCK_SIZE; xx++)
{
int pixel = luminances[offset + xx] & 0xFF;
sum += pixel;
// still looking for good contrast
if (pixel < min)
{
min = pixel;
}
if (pixel > max)
{
max = pixel;
}
}
// short-circuit min/max tests once dynamic range is met
if (max - min > MIN_DYNAMIC_RANGE)
{
// finish the rest of the rows quickly
for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width)
{
for (int xx = 0; xx < BLOCK_SIZE; xx++)
{
sum += luminances[offset + xx] & 0xFF;
}
}
}
}
// The default estimate is the average of the values in the block.
int average = sum >> (BLOCK_SIZE_POWER * 2);
if (max - min <= MIN_DYNAMIC_RANGE)
{
// If variation within the block is low, assume this is a block with only light or only
// dark pixels. In that case we do not want to use the average, as it would divide this
// low contrast area into black and white pixels, essentially creating data out of noise.
//
// The default assumption is that the block is light/background. Since no estimate for
// the level of dark pixels exists locally, use half the min for the block.
average = min / 2;
if (y > 0 && x > 0)
{
// Correct the "white background" assumption for blocks that have neighbors by comparing
// the pixels in this block to the previously calculated black points. This is based on
// the fact that dark barcode symbology is always surrounded by some amount of light
// background for which reasonable black point estimates were made. The bp estimated at
// the boundaries is used for the interior.
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
int averageNeighborBlackPoint =
(black_points.at<uchar>(y - 1, x) + (2 * black_points.at<uchar>(y, x - 1)) +
black_points.at<uchar>(y - 1, x - 1)) / 4;
if (min < averageNeighborBlackPoint)
{
average = averageNeighborBlackPoint;
}
}
}
black_points.at<uchar>(y, x) = (uchar) average;
}
}
return black_points;
}
void hybridBinarization(const Mat &src, Mat &dst)
{
int width = src.cols;
int height = src.rows;
if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION)
{
std::vector<uchar> luminances(src.begin<uchar>(), src.end<uchar>());
int sub_width = width >> BLOCK_SIZE_POWER;
if ((width & BLOCK_SIZE_MASK) != 0)
{
sub_width++;
}
int sub_height = height >> BLOCK_SIZE_POWER;
if ((height & BLOCK_SIZE_MASK) != 0)
{
sub_height++;
}
Mat black_points = calculateBlackPoints(luminances, sub_width, sub_height, width, height);
dst.create(src.size(), src.type());
calculateThresholdForBlock(luminances, sub_width, sub_height, width, height, black_points, dst);
}
else
{
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY);
}
}
}
}

@ -0,0 +1,22 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Modified from ZXing. Copyright ZXing authors.
// Licensed under the Apache License, Version 2.0 (the "License").
#ifndef OPENCV_BARCODE_HYBRID_BINARIZER_HPP
#define OPENCV_BARCODE_HYBRID_BINARIZER_HPP
namespace cv {
namespace barcode {
void hybridBinarization(const Mat &src, Mat &dst);
void
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height,
const Mat &black_points, Mat &dst);
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height);
}
}
#endif // OPENCV_BARCODE_HYBRID_BINARIZER_HPP

@ -0,0 +1,77 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
// Modified by darkliang wangberlinT
#include "../../precomp.hpp"
#include "super_scale.hpp"
#ifdef HAVE_OPENCV_DNN
namespace cv {
namespace barcode {
constexpr static float MAX_SCALE = 4.0f;
int SuperScale::init(const std::string &proto_path, const std::string &model_path)
{
srnet_ = dnn::readNetFromCaffe(proto_path, model_path);
net_loaded_ = true;
return 0;
}
void SuperScale::processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size)
{
scale = min(scale, MAX_SCALE);
if (scale > .0 && scale < 1.0)
{ // down sample
resize(src, dst, Size(), scale, scale, INTER_AREA);
}
else if (scale > 1.5 && scale < 2.0)
{
resize(src, dst, Size(), scale, scale, INTER_CUBIC);
}
else if (scale >= 2.0)
{
int width = src.cols;
int height = src.rows;
if (use_sr && (int) sqrt(width * height * 1.0) < sr_max_size && net_loaded_)
{
superResolutionScale(src, dst);
if (scale > 2.0)
{
processImageScale(dst, dst, scale / 2.0f, use_sr);
}
}
else
{ resize(src, dst, Size(), scale, scale, INTER_CUBIC); }
}
}
int SuperScale::superResolutionScale(const Mat &src, Mat &dst)
{
Mat blob;
dnn::blobFromImage(src, blob, 1.0 / 255, Size(src.cols, src.rows), {0.0f}, false, false);
srnet_.setInput(blob);
auto prob = srnet_.forward();
dst = Mat(prob.size[2], prob.size[3], CV_8UC1);
for (int row = 0; row < prob.size[2]; row++)
{
const float *prob_score = prob.ptr<float>(0, 0, row);
auto *dst_row = dst.ptr<uchar>(row);
for (int col = 0; col < prob.size[3]; col++)
{
dst_row[col] = saturate_cast<uchar>(prob_score[col] * 255.0f);
}
}
return 0;
}
} // namespace barcode
} // namespace cv
#endif // HAVE_OPENCV_DNN

@ -0,0 +1,69 @@
/// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Tencent is pleased to support the open source community by making WeChat QRCode available.
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
#ifndef OPENCV_BARCODE_SUPER_SCALE_HPP
#define OPENCV_BARCODE_SUPER_SCALE_HPP
#ifdef HAVE_OPENCV_DNN
#include "opencv2/dnn.hpp"
namespace cv {
namespace barcode {
class SuperScale
{
public:
SuperScale() = default;
~SuperScale() = default;
int init(const std::string &proto_path, const std::string &model_path);
void processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size = 160);
private:
dnn::Net srnet_;
bool net_loaded_ = false;
int superResolutionScale(const cv::Mat &src, cv::Mat &dst);
};
} // namespace barcode
} // namespace cv
#else // HAVE_OPENCV_DNN
#include "opencv2/core.hpp"
#include "opencv2/core/utils/logger.hpp"
namespace cv {
namespace barcode {
class SuperScale
{
public:
int init(const std::string &, const std::string &)
{
return 0;
}
void processImageScale(const Mat &src, Mat &dst, float scale, const bool & isEnabled, int)
{
if (isEnabled)
{
CV_LOG_WARNING(NULL, "objdetect/barcode: SuperScaling disabled - OpenCV has been built without DNN support");
}
resize(src, dst, Size(), scale, scale, INTER_CUBIC);
}
};
} // namespace barcode
} // namespace cv
#endif // !HAVE_OPENCV_DNN
#endif // OPENCV_BARCODE_SUPER_SCALE_HPP

@ -0,0 +1,36 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../../precomp.hpp"
#include "utils.hpp"
#include "hybrid_binarizer.hpp"
namespace cv {
namespace barcode {
void sharpen(const Mat &src, const Mat &dst)
{
Mat blur;
GaussianBlur(src, blur, Size(0, 0), 25);
addWeighted(src, 2, blur, -1, -20, dst);
}
void binarize(const Mat &src, Mat &dst, BinaryType mode)
{
switch (mode)
{
case OTSU:
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY);
break;
case HYBRID:
hybridBinarization(src, dst);
break;
default:
CV_Error(Error::StsNotImplemented, "This binary type is not yet implemented");
}
}
}
}

@ -0,0 +1,26 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_UTILS_HPP
#define OPENCV_BARCODE_UTILS_HPP
namespace cv {
namespace barcode {
enum BinaryType
{
OTSU = 0, HYBRID = 1
};
static constexpr BinaryType binary_types[] = {OTSU, HYBRID};
void sharpen(const Mat &src, const Mat &dst);
void binarize(const Mat &src, Mat &dst, BinaryType mode);
}
}
#endif // OPENCV_BARCODE_UTILS_HPP

@ -0,0 +1,92 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "ean13_decoder.hpp"
// three digit decode method from https://baike.baidu.com/item/EAN-13
namespace cv {
namespace barcode {
static constexpr size_t EAN13BITS_NUM = 95;
static constexpr size_t EAN13DIGIT_NUM = 13;
// default thought that mat is a matrix after binary-transfer.
/**
* decode EAN-13
* @prama: data: the input array,
* @prama: start: the index of start order, begin at 0, max-value is data.size()-1
* it scan begin at the data[start]
*/
Result Ean13Decoder::decode(const vector<uchar> &data) const
{
string result;
char decode_result[EAN13DIGIT_NUM + 1]{'\0'};
if (data.size() < EAN13BITS_NUM)
{
return Result("Wrong Size", Result::BARCODE_NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", Result::BARCODE_NONE);
}
uint start = pattern.second;
Counter counter(vector<int>{0, 0, 0, 0});
size_t end = data.size();
int first_char_bit = 0;
// [1,6] are left part of EAN, [7,12] are right part, index 0 is calculated by left part
for (int i = 1; i < 7 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_AB_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i] = static_cast<char>('0' + bestMatch % 10);
start = counter.sum + start;
first_char_bit += (bestMatch >= 10) << i;
}
decode_result[0] = static_cast<char>(FIRST_CHAR_ARRAY()[first_char_bit >> 2] + '0');
// why there need >> 2?
// first, the i in for-cycle is begin in 1
// second, the first i = 1 is always
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size()));
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern))
{
return Result("Middle Pattern Not Found", Result::BARCODE_NONE);
}
start = pattern.second;
for (int i = 0; i < 6 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i + 7] = static_cast<char>('0' + bestMatch);
start = counter.sum + start;
}
Counter end_counter(vector<int>(BEGIN_PATTERN().size()));
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern))
{
return Result("End Pattern Not Found", Result::BARCODE_NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN13DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE);
}
return Result(result, Result::BARCODE_EAN_13);
}
Ean13Decoder::Ean13Decoder()
{
this->bits_num = EAN13BITS_NUM;
this->digit_number = EAN13DIGIT_NUM;
}
}
}

@ -0,0 +1,31 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_EAN13_DECODER_HPP
#define OPENCV_BARCODE_EAN13_DECODER_HPP
#include "upcean_decoder.hpp"
namespace cv {
namespace barcode {
//extern struct EncodePair;
using std::string;
using std::vector;
using std::pair;
class Ean13Decoder : public UPCEANDecoder
{
public:
Ean13Decoder();
~Ean13Decoder() override = default;
protected:
Result decode(const vector<uchar> &data) const override;
};
}
} // namespace cv
#endif // OPENCV_BARCODE_EAN13_DECODER_HPP

@ -0,0 +1,79 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#include "../precomp.hpp"
#include "ean8_decoder.hpp"
namespace cv {
namespace barcode {
static constexpr size_t EAN8BITS_NUM = 70;
static constexpr size_t EAN8DIGIT_NUM = 8;
Result Ean8Decoder::decode(const vector<uchar> &data) const
{
std::string result;
char decode_result[EAN8DIGIT_NUM + 1]{'\0'};
if (data.size() < EAN8BITS_NUM)
{
return Result("Wrong Size", Result::BARCODE_NONE);
}
pair<uint, uint> pattern;
if (!findStartGuardPatterns(data, pattern))
{
return Result("Begin Pattern Not Found", Result::BARCODE_NONE);
}
uint start = pattern.second;
Counter counter(vector<int>{0, 0, 0, 0});
size_t end = data.size();
for (int i = 0; i < 4 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i] = static_cast<char>('0' + bestMatch % 10);
start = counter.sum + start;
}
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size()));
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern))
{
return Result("Middle Pattern Not Found", Result::BARCODE_NONE);
}
start = pattern.second;
for (int i = 0; i < 4 && start < end; ++i)
{
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns());
if (bestMatch == -1)
{
return Result("Decode Error", Result::BARCODE_NONE);
}
decode_result[i + 4] = static_cast<char>('0' + bestMatch);
start = counter.sum + start;
}
Counter end_counter(vector<int>(BEGIN_PATTERN().size()));
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern))
{
return Result("End Pattern Not Found", Result::BARCODE_NONE);
}
result = string(decode_result);
if (!isValid(result))
{
return Result("Wrong: " + result.append(string(EAN8DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE);
}
return Result(result, Result::BARCODE_EAN_8);
}
Ean8Decoder::Ean8Decoder()
{
this->digit_number = EAN8DIGIT_NUM;
this->bits_num = EAN8BITS_NUM;
}
}
}

@ -0,0 +1,32 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
#ifndef OPENCV_BARCODE_EAN8_DECODER_HPP
#define OPENCV_BARCODE_EAN8_DECODER_HPP
#include "upcean_decoder.hpp"
namespace cv {
namespace barcode {
using std::string;
using std::vector;
using std::pair;
class Ean8Decoder : public UPCEANDecoder
{
public:
Ean8Decoder();
~Ean8Decoder() override = default;
protected:
Result decode(const vector<uchar> &data) const override;
};
}
}
#endif // OPENCV_BARCODE_EAN8_DECODER_HPP

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save