diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dd53e9508c..2b856bea24 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,12 +15,12 @@ body: Please provide the following system information to help us diagnose the bug. For example: // example for c++ user - OpenCV version: 4.6.0 + OpenCV version: 4.8.0 Operating System / Platform: Ubuntu 20.04 Compiler & compiler version: GCC 9.3.0 // example for python user - OpenCV python version: 4.6.0.66 + OpenCV python version: 4.8.0.74 Operating System / Platform: Ubuntu 20.04 Python version: 3.9.6 validations: diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml index d33c030972..8d4d3e440b 100644 --- a/.github/ISSUE_TEMPLATE/documentation.yml +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -12,7 +12,7 @@ body: attributes: label: Describe the doc issue description: > - Please provide a clear and concise description of what content in https://docs.opencv.org/ is an issue. Note that there are multiple active branches, such as 3.4, 4.x and 5.x, so please specify the branch with the problem. + Please provide a clear and concise description of what content in https://docs.opencv.org/ is an issue. Note that there are multiple active branches, such as 4.x and 5.x, so please specify the branch with the problem. placeholder: | A clear and concise description of what content in https://docs.opencv.org/ is an issue. diff --git a/3rdparty/ffmpeg/ffmpeg.cmake b/3rdparty/ffmpeg/ffmpeg.cmake index f3ad63770a..da75e3d2ca 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -1,8 +1,8 @@ -# Binaries branch name: ffmpeg/4.x_20221225 -# Binaries were created for OpenCV: 4abe6dc48d4ec6229f332cc6cf6c7e234ac8027e -ocv_update(FFMPEG_BINARIES_COMMIT "7dd0d4f1d6fe75f05f3d3b5e38cbc96c1a2d2809") -ocv_update(FFMPEG_FILE_HASH_BIN32 "e598ae2ece1ddf310bc49b58202fd87a") -ocv_update(FFMPEG_FILE_HASH_BIN64 "b2a40c142c20aef9fd663fc8f85c2971") +# Binaries branch name: ffmpeg/4.x_20230622 +# Binaries were created for OpenCV: 61d48dd0f8d1cc1a115d26998705a61478f64a3c +ocv_update(FFMPEG_BINARIES_COMMIT "7da61f0695eabf8972a2c302bf1632a3d99fb0d5") +ocv_update(FFMPEG_FILE_HASH_BIN32 "4aaef1456e282e5ef665d65555f47f56") +ocv_update(FFMPEG_FILE_HASH_BIN64 "38a638851e064c591ce812e27ed43f1f") ocv_update(FFMPEG_FILE_HASH_CMAKE "8862c87496e2e8c375965e1277dee1c7") function(download_win_ffmpeg script_var) diff --git a/3rdparty/libtiff/tif_dirread.c b/3rdparty/libtiff/tif_dirread.c index ba127ca917..45c1107697 100644 --- a/3rdparty/libtiff/tif_dirread.c +++ b/3rdparty/libtiff/tif_dirread.c @@ -4371,7 +4371,7 @@ static void TIFFReadDirectoryCheckOrder(TIFF* tif, TIFFDirEntry* dir, uint16 dircount) { static const char module[] = "TIFFReadDirectoryCheckOrder"; - uint16 m; + uint32 m; uint16 n; TIFFDirEntry* o; m=0; diff --git a/3rdparty/openjpeg/openjp2/ht_dec.c b/3rdparty/openjpeg/openjp2/ht_dec.c index 1eb4d525f1..e2f3afd6a3 100644 --- a/3rdparty/openjpeg/openjp2/ht_dec.c +++ b/3rdparty/openjpeg/openjp2/ht_dec.c @@ -69,7 +69,7 @@ static OPJ_BOOL only_cleanup_pass_is_decoded = OPJ_FALSE; static INLINE OPJ_UINT32 population_count(OPJ_UINT32 val) { -#ifdef OPJ_COMPILER_MSVC +#if defined(OPJ_COMPILER_MSVC) && (defined(_M_IX86) || defined(_M_AMD64)) return (OPJ_UINT32)__popcnt(val); #elif (defined OPJ_COMPILER_GNUC) return (OPJ_UINT32)__builtin_popcount(val); diff --git a/3rdparty/protobuf/CMakeLists.txt b/3rdparty/protobuf/CMakeLists.txt index e39de9823a..5e8e3a9ed2 100644 --- a/3rdparty/protobuf/CMakeLists.txt +++ b/3rdparty/protobuf/CMakeLists.txt @@ -26,6 +26,7 @@ else() -Wsuggest-override -Winconsistent-missing-override -Wimplicit-fallthrough -Warray-bounds # GCC 9+ + -Wstringop-overflow -Wstringop-overread # GCC 11-12 ) endif() if(CV_ICC) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24e8fee7c4..64d89ed60b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -784,6 +784,15 @@ if(BUILD_JAVA) include(cmake/android/OpenCVDetectAndroidSDK.cmake) else() include(cmake/OpenCVDetectApacheAnt.cmake) + if(ANT_EXECUTABLE AND NOT OPENCV_JAVA_IGNORE_ANT) + ocv_update(OPENCV_JAVA_SDK_BUILD_TYPE "ANT") + elseif(NOT ANDROID) + find_package(Java) + if(Java_FOUND) + include(UseJava) + ocv_update(OPENCV_JAVA_SDK_BUILD_TYPE "JAVA") + endif() + endif() find_package(JNI) endif() endif() @@ -1800,9 +1809,10 @@ if(BUILD_JAVA) status(" Java:" BUILD_FAT_JAVA_LIB THEN "export all functions" ELSE "") status(" ant:" ANT_EXECUTABLE THEN "${ANT_EXECUTABLE} (ver ${ANT_VERSION})" ELSE NO) if(NOT ANDROID) + status(" Java:" Java_FOUND THEN "YES (ver ${Java_VERSION})" ELSE NO) status(" JNI:" JNI_INCLUDE_DIRS THEN "${JNI_INCLUDE_DIRS}" ELSE NO) endif() - status(" Java wrappers:" HAVE_opencv_java THEN YES ELSE NO) + status(" Java wrappers:" HAVE_opencv_java THEN "YES (${OPENCV_JAVA_SDK_BUILD_TYPE})" ELSE NO) status(" Java tests:" BUILD_TESTS AND opencv_test_java_BINARY_DIR THEN YES ELSE NO) endif() diff --git a/apps/createsamples/utility.cpp b/apps/createsamples/utility.cpp index 0b6439daaf..5bc1a89443 100644 --- a/apps/createsamples/utility.cpp +++ b/apps/createsamples/utility.cpp @@ -70,7 +70,7 @@ using namespace cv; static int icvMkDir( const char* filename ) { - char path[PATH_MAX]; + char path[PATH_MAX+1]; char* p; int pos; @@ -83,7 +83,8 @@ static int icvMkDir( const char* filename ) mode = 0755; #endif /* _WIN32 */ - strcpy( path, filename ); + path[0] = '\0'; + strncat( path, filename, PATH_MAX ); p = path; for( ; ; ) diff --git a/cmake/FindONNX.cmake b/cmake/FindONNX.cmake index 56dd6d5098..b2c79a9031 100644 --- a/cmake/FindONNX.cmake +++ b/cmake/FindONNX.cmake @@ -16,7 +16,22 @@ if(ONNXRT_ROOT_DIR) CMAKE_FIND_ROOT_PATH_BOTH) endif() +macro(detect_onxxrt_ep filename dir have_ep_var) + find_path(ORT_EP_INCLUDE ${filename} ${dir} CMAKE_FIND_ROOT_PATH_BOTH) + if(ORT_EP_INCLUDE) + set(${have_ep_var} TRUE) + endif() +endmacro() + if(ORT_LIB AND ORT_INCLUDE) + # Check DirectML Execution Provider availability + get_filename_component(dml_dir ${ONNXRT_ROOT_DIR}/include/onnxruntime/core/providers/dml ABSOLUTE) + detect_onxxrt_ep( + dml_provider_factory.h + ${dml_dir} + HAVE_ONNX_DML + ) + set(HAVE_ONNX TRUE) # For CMake output only set(ONNX_LIBRARIES "${ORT_LIB}" CACHE STRING "ONNX Runtime libraries") diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index 3f3358aae5..8bd8668130 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -108,6 +108,7 @@ elseif(CV_ICC) elseif(CV_GCC OR CV_CLANG) if(ENABLE_FAST_MATH) add_extra_compiler_option(-ffast-math) + add_extra_compiler_option(-fno-finite-math-only) endif() endif() @@ -260,7 +261,11 @@ if(CV_GCC OR CV_CLANG) endif() if(ENABLE_LTO) - add_extra_compiler_option(-flto) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12) + add_extra_compiler_option(-flto=auto) + else() + add_extra_compiler_option(-flto) + endif() endif() if(ENABLE_THIN_LTO) add_extra_compiler_option(-flto=thin) diff --git a/cmake/OpenCVFindCANN.cmake b/cmake/OpenCVFindCANN.cmake index b0b8e35c6b..e1cd054a37 100644 --- a/cmake/OpenCVFindCANN.cmake +++ b/cmake/OpenCVFindCANN.cmake @@ -5,13 +5,24 @@ if("cann${CANN_INSTALL_DIR}" STREQUAL "cann" AND DEFINED ENV{ASCEND_TOOLKIT_HOME message(STATUS "CANN: updated CANN_INSTALL_DIR from ASCEND_TOOLKIT_HOME=$ENV{ASCEND_TOOLKIT_HOME}") endif() +if(EXISTS "${CANN_INSTALL_DIR}/opp/op_proto/built-in/inc") + set(CANN_VERSION_BELOW_6_3_ALPHA002 "YES" ) + add_definitions(-DCANN_VERSION_BELOW_6_3_ALPHA002="YES") +endif() + if(CANN_INSTALL_DIR) + # Supported system: UNIX + if(NOT UNIX) + set(HAVE_CANN OFF) + message(WARNING "CANN: CANN toolkit supports unix but not ${CMAKE_SYSTEM_NAME}. Turning off HAVE_CANN") + return() + endif() # Supported platforms: x86-64, arm64 if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64") else() set(HAVE_CANN OFF) - message(STATUS "CANN: CANN toolkit supports x86-64 and arm64 but not ${CMAKE_SYSTEM_PROCESSOR}. Turning off HAVE_CANN") + message(WARNING "CANN: CANN toolkit supports x86-64 and arm64 but not ${CMAKE_SYSTEM_PROCESSOR}. Turning off HAVE_CANN") return() endif() @@ -31,7 +42,7 @@ if(CANN_INSTALL_DIR) set(lib_ascendcl ${found_lib_ascendcl}) message(STATUS "CANN: libascendcl.so is found at ${lib_ascendcl}") else() - message(STATUS "CANN: Missing libascendcl.so. Turning off HAVE_CANN") + message(WARNING "CANN: Missing libascendcl.so. Turning off HAVE_CANN") set(HAVE_CANN OFF) return() endif() @@ -42,7 +53,7 @@ if(CANN_INSTALL_DIR) set(lib_graph ${found_lib_graph}) message(STATUS "CANN: libgraph.so is found at ${lib_graph}") else() - message(STATUS "CANN: Missing libgraph.so. Turning off HAVE_CANN") + message(WARNING "CANN: Missing libgraph.so. Turning off HAVE_CANN") set(HAVE_CANN OFF) return() endif() @@ -53,29 +64,49 @@ if(CANN_INSTALL_DIR) set(lib_ge_compiler ${found_lib_ge_compiler}) message(STATUS "CANN: libge_compiler.so is found at ${lib_ge_compiler}") else() - message(STATUS "CANN: Missing libge_compiler.so. Turning off HAVE_CANN") + message(WARNING "CANN: Missing libge_compiler.so. Turning off HAVE_CANN") set(HAVE_CANN OFF) return() endif() # * libopsproto.so - set(lib_opsproto "${CANN_INSTALL_DIR}/opp/op_proto/built-in") + if (CANN_VERSION_BELOW_6_3_ALPHA002) + set(lib_opsproto "${CANN_INSTALL_DIR}/opp/op_proto/built-in/") + else() + if(EXISTS "${CANN_INSTALL_DIR}/opp/built-in/op_proto/lib/linux") + set(lib_opsproto "${CANN_INSTALL_DIR}/opp/built-in/op_proto/lib/linux/${CMAKE_HOST_SYSTEM_PROCESSOR}") + else() + set(lib_opsproto "${CANN_INSTALL_DIR}/opp/built-in/op_proto") + endif() + endif() find_library(found_lib_opsproto NAMES opsproto PATHS ${lib_opsproto} NO_DEFAULT_PATH) if(found_lib_opsproto) set(lib_opsproto ${found_lib_opsproto}) message(STATUS "CANN: libopsproto.so is found at ${lib_opsproto}") else() - message(STATUS "CANN: Missing libopsproto.so. Turning off HAVE_CANN") + message(WARNING "CANN: Missing libopsproto.so can't found at ${lib_opsproto}. Turning off HAVE_CANN") set(HAVE_CANN OFF) return() endif() - set(libs_cann "") list(APPEND libs_cann ${lib_ascendcl}) list(APPEND libs_cann ${lib_opsproto}) list(APPEND libs_cann ${lib_graph}) list(APPEND libs_cann ${lib_ge_compiler}) + # * lib_graph_base.so + if(NOT CANN_VERSION_BELOW_6_3_ALPHA002) + set(lib_graph_base "${CANN_INSTALL_DIR}/compiler/lib64") + find_library(found_libgraph_base NAMES graph_base PATHS ${lib_graph_base} NO_DEFAULT_PATH) + if(found_libgraph_base) + set(lib_graph_base ${found_libgraph_base}) + message(STATUS "CANN: lib_graph_base.so is found at ${lib_graph_base}") + list(APPEND libs_cann ${lib_graph_base}) + else() + message(STATUS "CANN: Missing lib_graph_base.so. It is only required after cann version 6.3.RC1.alpha002") + endif() + endif() + try_compile(VALID_ASCENDCL "${OpenCV_BINARY_DIR}" "${OpenCV_SOURCE_DIR}/cmake/checks/cann.cpp" diff --git a/cmake/OpenCVPackaging.cmake b/cmake/OpenCVPackaging.cmake index 67a65e262f..eee1c96869 100644 --- a/cmake/OpenCVPackaging.cmake +++ b/cmake/OpenCVPackaging.cmake @@ -52,8 +52,8 @@ else() set(OPENCV_PACKAGE_ARCH_SUFFIX ${CMAKE_SYSTEM_PROCESSOR}) endif() -set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${OPENCV_VCSVERSION}-${OPENCV_PACKAGE_ARCH_SUFFIX}") -set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${OPENCV_VCSVERSION}-${OPENCV_PACKAGE_ARCH_SUFFIX}") +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}-${OPENCV_PACKAGE_ARCH_SUFFIX}") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}-${OPENCV_PACKAGE_ARCH_SUFFIX}") #rpm options set(CPACK_RPM_COMPONENT_INSTALL TRUE) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index a0741a3e84..04a4441c92 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -1632,13 +1632,19 @@ function(ocv_add_external_target name inc link def) endif() endfunction() +set(__OPENCV_EXPORTED_EXTERNAL_TARGETS "" CACHE INTERNAL "") function(ocv_install_used_external_targets) if(NOT BUILD_SHARED_LIBS AND NOT (CMAKE_VERSION VERSION_LESS "3.13.0") # upgrade CMake: https://gitlab.kitware.com/cmake/cmake/-/merge_requests/2152 ) foreach(tgt in ${ARGN}) if(tgt MATCHES "^ocv\.3rdparty\.") - install(TARGETS ${tgt} EXPORT OpenCVModules) + list(FIND __OPENCV_EXPORTED_EXTERNAL_TARGETS "${tgt}" _found) + if(_found EQUAL -1) # don't export target twice + install(TARGETS ${tgt} EXPORT OpenCVModules) + list(APPEND __OPENCV_EXPORTED_EXTERNAL_TARGETS "${tgt}") + set(__OPENCV_EXPORTED_EXTERNAL_TARGETS "${__OPENCV_EXPORTED_EXTERNAL_TARGETS}" CACHE INTERNAL "") + endif() endif() endforeach() endif() diff --git a/cmake/copy_files.cmake b/cmake/copy_files.cmake index 423f7fff9c..f7e13a45d4 100644 --- a/cmake/copy_files.cmake +++ b/cmake/copy_files.cmake @@ -21,7 +21,7 @@ macro(copy_file_ src dst prefix) endif() if(use_symlink) if(local_update OR NOT IS_SYMLINK "${dst}") - message("${prefix}Symlink: '${dst_name}' ...") + #message("${prefix}Symlink: '${dst_name}' ...") endif() get_filename_component(target_path "${dst}" PATH) file(MAKE_DIRECTORY "${target_path}") @@ -38,7 +38,7 @@ macro(copy_file_ src dst prefix) set(local_update 1) endif() if(local_update) - message("${prefix}Copying: '${dst_name}' ...") + #message("${prefix}Copying: '${dst_name}' ...") configure_file(${src} ${dst} COPYONLY) else() #message("${prefix}Up-to-date: '${dst_name}'") @@ -55,7 +55,7 @@ if(NOT DEFINED COPYLIST_VAR) set(COPYLIST_VAR "COPYLIST") endif() list(LENGTH ${COPYLIST_VAR} __length) -message("${prefix}... ${__length} entries (${COPYLIST_VAR})") +#message("${prefix}... ${__length} entries (${COPYLIST_VAR})") foreach(id ${${COPYLIST_VAR}}) set(src "${${COPYLIST_VAR}_SRC_${id}}") set(dst "${${COPYLIST_VAR}_DST_${id}}") @@ -80,7 +80,7 @@ foreach(id ${${COPYLIST_VAR}}) endif() file(GLOB_RECURSE _files RELATIVE "${src}" ${src_glob}) list(LENGTH _files __length) - message("${prefix} ... directory '.../${src_name2}/${src_name}' with ${__length} files") + #message("${prefix} ... directory '.../${src_name2}/${src_name}' with ${__length} files") foreach(f ${_files}) if(NOT EXISTS "${src}/${f}") message(FATAL_ERROR "COPY ERROR: Source file is missing: ${src}/${f}") @@ -98,12 +98,12 @@ else() endif() if(NOT "${__state}" STREQUAL "${__prev_state}") file(WRITE "${STATE_FILE}" "${__state}") - message("${prefix}Updated!") + #message("${prefix}Updated!") set(update_dephelper 1) endif() if(NOT update_dephelper) - message("${prefix}All files are up-to-date.") + #message("${prefix}All files are up-to-date.") elseif(DEFINED DEPHELPER) file(WRITE "${DEPHELPER}" "") endif() diff --git a/doc/opencv.bib b/doc/opencv.bib index 412c5379f9..62c038de08 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1383,3 +1383,11 @@ year={2005}, pages={70-74} } +@inproceedings{wang2016iros, + AUTHOR = {John Wang and Edwin Olson}, + TITLE = {{AprilTag} 2: Efficient and robust fiducial detection}, + BOOKTITLE = {Proceedings of the {IEEE/RSJ} International Conference on Intelligent + Robots and Systems {(IROS)}}, + YEAR = {2016}, + MONTH = {October}, +} diff --git a/doc/tutorials/app/orbbec_astra.markdown b/doc/tutorials/app/orbbec_astra.markdown index 849b2d7d19..84921fe121 100644 --- a/doc/tutorials/app/orbbec_astra.markdown +++ b/doc/tutorials/app/orbbec_astra.markdown @@ -9,7 +9,7 @@ Using Orbbec Astra 3D cameras {#tutorial_orbbec_astra} ### Introduction -This tutorial is devoted to the Astra Series of Orbbec 3D cameras (https://orbbec3d.com/product-astra-pro/). +This tutorial is devoted to the Astra Series of Orbbec 3D cameras (https://orbbec3d.com/index/Product/info.html?cate=38&id=36). That cameras have a depth sensor in addition to a common color sensor. The depth sensors can be read using the open source OpenNI API with @ref cv::VideoCapture class. The video stream is provided through the regular camera interface. @@ -18,9 +18,11 @@ camera interface. In order to use the Astra camera's depth sensor with OpenCV you should do the following steps: --# Download the latest version of Orbbec OpenNI SDK (from here ). +-# Download the latest version of Orbbec OpenNI SDK (from here ). Unzip the archive, choose the build according to your operating system and follow installation - steps provided in the Readme file. For instance, if you use 64bit GNU/Linux run: + steps provided in the Readme file. + +-# For instance, if you use 64bit GNU/Linux run: @code{.bash} $ cd Linux/OpenNI-Linux-x64-2.3.0.63/ $ sudo ./install.sh @@ -31,17 +33,44 @@ In order to use the Astra camera's depth sensor with OpenCV you should do the fo @code{.bash} $ source OpenNIDevEnvironment @endcode - --# Run the following commands to verify that OpenNI library and header files can be found. You should see - something similar in your terminal: + To verify that the source command works and OpenNI library and header files can be found, run the following + command and you should see something similar in your terminal: @code{.bash} $ echo $OPENNI2_INCLUDE /home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Include $ echo $OPENNI2_REDIST /home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Redist @endcode - If the above two variables are empty, then you need to source `OpenNIDevEnvironment` again. Now you can - configure OpenCV with OpenNI support enabled by setting the `WITH_OPENNI2` flag in CMake. + If the above two variables are empty, then you need to source `OpenNIDevEnvironment` again. + + @note Orbbec OpenNI SDK version 2.3.0.86 and newer does not provide `install.sh` any more. + You can use the following script to initialize environment: + @code{.text} + # Check if user is root/running with sudo + if [ `whoami` != root ]; then + echo Please run this script with sudo + exit + fi + + ORIG_PATH=`pwd` + cd `dirname $0` + SCRIPT_PATH=`pwd` + cd $ORIG_PATH + + if [ "`uname -s`" != "Darwin" ]; then + # Install UDEV rules for USB device + cp ${SCRIPT_PATH}/orbbec-usb.rules /etc/udev/rules.d/558-orbbec-usb.rules + echo "usb rules file install at /etc/udev/rules.d/558-orbbec-usb.rules" + fi + + OUT_FILE="$SCRIPT_PATH/OpenNIDevEnvironment" + echo "export OPENNI2_INCLUDE=$SCRIPT_PATH/../sdk/Include" > $OUT_FILE + echo "export OPENNI2_REDIST=$SCRIPT_PATH/../sdk/libs" >> $OUT_FILE + chmod a+r $OUT_FILE + echo "exit" + @endcode + +-# Now you can configure OpenCV with OpenNI support enabled by setting the `WITH_OPENNI2` flag in CMake. You may also like to enable the `BUILD_EXAMPLES` flag to get a code sample working with your Astra camera. Run the following commands in the directory containing OpenCV source code to enable OpenNI support: @code{.bash} diff --git a/doc/tutorials/introduction/config_reference/config_reference.markdown b/doc/tutorials/introduction/config_reference/config_reference.markdown index 684355106f..16acc315f5 100644 --- a/doc/tutorials/introduction/config_reference/config_reference.markdown +++ b/doc/tutorials/introduction/config_reference/config_reference.markdown @@ -2,7 +2,7 @@ OpenCV configuration options reference {#tutorial_config_reference} ====================================== @prev_tutorial{tutorial_general_install} -@next_tutorial{tutorial_linux_install} +@next_tutorial{tutorial_env_reference} @tableofcontents diff --git a/doc/tutorials/introduction/env_reference/env_reference.markdown b/doc/tutorials/introduction/env_reference/env_reference.markdown new file mode 100644 index 0000000000..2a850dc299 --- /dev/null +++ b/doc/tutorials/introduction/env_reference/env_reference.markdown @@ -0,0 +1,345 @@ +OpenCV environment variables reference {#tutorial_env_reference} +====================================== + +@prev_tutorial{tutorial_config_reference} +@next_tutorial{tutorial_linux_install} + +@tableofcontents + +### Introduction + +OpenCV can change its behavior depending on the runtime environment: +- enable extra debugging output or performance tracing +- modify default locations and search paths +- tune some algorithms or general behavior +- enable or disable workarounds, safety features and optimizations + +**Notes:** +- ⭐ marks most popular variables +- variables with names like this `VAR_${NAME}` describes family of variables, where `${NAME}` should be changed to one of predefined values, e.g. `VAR_TBB`, `VAR_OPENMP`, ... + +##### Setting environment variable in Windows +In terminal or cmd-file (bat-file): +```.bat +set MY_ENV_VARIABLE=true +C:\my_app.exe +``` +In GUI: +- Go to "Settings -> System -> About" +- Click on "Advanced system settings" in the right part +- In new window click on the "Environment variables" button +- Add an entry to the "User variables" list + +##### Setting environment variable in Linux + +In terminal or shell script: +```.sh +export MY_ENV_VARIABLE=true +./my_app +``` +or as a single command: +```.sh +MY_ENV_VARIABLE=true ./my_app +``` + +##### Setting environment variable in Python + +```.py +import os +os.environ["MY_ENV_VARIABLE"] = True +import cv2 # variables set after this may not have effect +``` + + +### Types + +- _non-null_ - set to anything to enable feature, in some cases can be interpreted as other types (e.g. path) +- _bool_ - `1`, `True`, `true`, `TRUE` / `0`, `False`, `false`, `FALSE` +- _number_/_size_ - unsigned number, suffixes `MB`, `Mb`, `mb`, `KB`, `Kb`, `kb` +- _string_ - plain string or can have a structure +- _path_ - to file, to directory +- _paths_ - `;`-separated on Windows, `:`-separated on others + + +### General, core +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_SKIP_CPU_BASELINE_CHECK | non-null | | do not check that current CPU supports all features used by the build (baseline) | +| OPENCV_CPU_DISABLE | `,` or `;`-separated | | disable code branches which use CPU features (dispatched code) | +| OPENCV_SETUP_TERMINATE_HANDLER | bool | true (Windows) | use std::set_terminate to install own termination handler | +| OPENCV_LIBVA_RUNTIME | file path | | libva for VA interoperability utils | +| OPENCV_ENABLE_MEMALIGN | bool | true (except static analysis, memory sanitizer, fuzzying, _WIN32?) | enable aligned memory allocations | +| OPENCV_BUFFER_AREA_ALWAYS_SAFE | bool | false | enable safe mode for multi-buffer allocations (each buffer separately) | +| OPENCV_KMEANS_PARALLEL_GRANULARITY | num | 1000 | tune algorithm parallel work distribution parameter `parallel_for_(..., ..., ..., granularity)` | +| OPENCV_DUMP_ERRORS | bool | true (Debug or Android), false (others) | print extra information on exception (log to Android) | +| OPENCV_DUMP_CONFIG | non-null | | print build configuration to stderr (`getBuildInformation`) | +| OPENCV_PYTHON_DEBUG | bool | false | enable extra warnings in Python bindings | +| OPENCV_TEMP_PATH | non-null / path | `/tmp/` (Linux), `/data/local/tmp/` (Android), `GetTempPathA` (Windows) | directory for temporary files | +| OPENCV_DATA_PATH_HINT | paths | | paths for findDataFile | +| OPENCV_DATA_PATH | paths | | paths for findDataFile | +| OPENCV_SAMPLES_DATA_PATH_HINT | paths | | paths for findDataFile | +| OPENCV_SAMPLES_DATA_PATH | paths | | paths for findDataFile | + +Links: +- https://github.com/opencv/opencv/wiki/CPU-optimizations-build-options + + +### Logging +| name | type | default | description | +|------|------|---------|-------------| +| ⭐ OPENCV_LOG_LEVEL | string | | logging level (see accepted values below) | +| OPENCV_LOG_TIMESTAMP | bool | true | logging with timestamps | +| OPENCV_LOG_TIMESTAMP_NS | bool | false | add nsec to logging timestamps | + +##### Levels: +- `0`, `O`, `OFF`, `S`, `SILENT`, `DISABLE`, `DISABLED` +- `F`, `FATAL` +- `E`, `ERROR` +- `W`, `WARNING`, `WARN`, `WARNINGS` +- `I`, `INFO` +- `D`, `DEBUG` +- `V`, `VERBOSE` + + +### core/parallel_for +| name | type | default | description | +|------|------|---------|-------------| +| ⭐ OPENCV_FOR_THREADS_NUM | num | 0 | set number of threads | +| OPENCV_THREAD_POOL_ACTIVE_WAIT_PAUSE_LIMIT | num | 16 | tune pthreads parallel_for backend | +| OPENCV_THREAD_POOL_ACTIVE_WAIT_WORKER | num | 2000 | tune pthreads parallel_for backend | +| OPENCV_THREAD_POOL_ACTIVE_WAIT_MAIN | num | 10000 | tune pthreads parallel_for backend | +| OPENCV_THREAD_POOL_ACTIVE_WAIT_THREADS_LIMIT | num | 0 | tune pthreads parallel_for backend | +| OPENCV_FOR_OPENMP_DYNAMIC_DISABLE | bool | false | use single OpenMP thread | + + +### backends +OPENCV_LEGACY_WAITKEY +Some modules have multiple available backends, following variables allow choosing specific backend or changing default priorities in which backends will be probed (e.g. when opening a video file). + +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_PARALLEL_BACKEND | string | | choose specific paralel_for backend (one of `TBB`, `ONETBB`, `OPENMP`) | +| OPENCV_PARALLEL_PRIORITY_${NAME} | num | | set backend priority, default is 1000 | +| OPENCV_PARALLEL_PRIORITY_LIST | string, `,`-separated | | list of backends in priority order | +| OPENCV_UI_BACKEND | string | | choose highgui backend for window rendering (one of `GTK`, `GTK3`, `GTK2`, `QT`, `WIN32`) | +| OPENCV_UI_PRIORITY_${NAME} | num | | set highgui backend priority, default is 1000 | +| OPENCV_UI_PRIORITY_LIST | string, `,`-separated | | list of hioghgui backends in priority order | +| OPENCV_VIDEOIO_PRIORITY_${NAME} | num | | set videoio backend priority, default is 1000 | +| OPENCV_VIDEOIO_PRIORITY_LIST | string, `,`-separated | | list of videoio backends in priority order | + + +### plugins +Some external dependencies can be detached into a dynamic library, which will be loaded at runtime (plugin). Following variables allow changing default search locations and naming pattern for these plugins. +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_CORE_PLUGIN_PATH | paths | | directories to search for _core_ plugins | +| OPENCV_CORE_PARALLEL_PLUGIN_${NAME} | string, glob | | parallel_for plugin library name (glob), e.g. default for TBB is "opencv_core_parallel_tbb*.so" | +| OPENCV_DNN_PLUGIN_PATH | paths | | directories to search for _dnn_ plugins | +| OPENCV_DNN_PLUGIN_${NAME} | string, glob | | parallel_for plugin library name (glob), e.g. default for TBB is "opencv_core_parallel_tbb*.so" | +| OPENCV_CORE_PLUGIN_PATH | paths | | directories to search for _highgui_ plugins (YES it is CORE) | +| OPENCV_UI_PLUGIN_${NAME} | string, glob | | _highgui_ plugin library name (glob) | +| OPENCV_VIDEOIO_PLUGIN_PATH | paths | | directories to search for _videoio_ plugins | +| OPENCV_VIDEOIO_PLUGIN_${NAME} | string, glob | | _videoio_ plugin library name (glob) | + +### OpenCL + +**Note:** OpenCL device specification format is `::`, e.g. `AMD:GPU:` + +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_OPENCL_RUNTIME | filepath or `disabled` | | path to OpenCL runtime library (e.g. `OpenCL.dll`, `libOpenCL.so`) | +| ⭐ OPENCV_OPENCL_DEVICE | string or `disabled` | | choose specific OpenCL device. See specification format in the note above. See more details in the Links section. | +| OPENCV_OPENCL_RAISE_ERROR | bool | false | raise exception if something fails during OpenCL kernel preparation and execution (Release builds only) | +| OPENCV_OPENCL_ABORT_ON_BUILD_ERROR | bool | false | abort if OpenCL kernel compilation failed | +| OPENCV_OPENCL_CACHE_ENABLE | bool | true | enable OpenCL kernel cache | +| OPENCV_OPENCL_CACHE_WRITE | bool | true | allow writing to the cache, otherwise cache will be read-only | +| OPENCV_OPENCL_CACHE_LOCK_ENABLE | bool | true | use .lock files to synchronize between multiple applications using the same OpenCL cache (may not work on network drives) | +| OPENCV_OPENCL_CACHE_CLEANUP | bool | true | automatically remove old entries from cache (leftovers from older OpenCL runtimes) | +| OPENCV_OPENCL_VALIDATE_BINARY_PROGRAMS | bool | false | validate loaded binary OpenCL kernels | +| OPENCV_OPENCL_DISABLE_BUFFER_RECT_OPERATIONS | bool | true (Apple), false (others) | enable workaround for non-continuos data downloads | +| OPENCV_OPENCL_BUILD_EXTRA_OPTIONS | string | | pass extra options to OpenCL kernel compilation | +| OPENCV_OPENCL_ENABLE_MEM_USE_HOST_PTR | bool | true | workaround/optimization for buffer allocation | +| OPENCV_OPENCL_ALIGNMENT_MEM_USE_HOST_PTR | num | 4 | parameter for OPENCV_OPENCL_ENABLE_MEM_USE_HOST_PTR | +| OPENCV_OPENCL_DEVICE_MAX_WORK_GROUP_SIZE | num | 0 | allow to decrease maxWorkGroupSize | +| OPENCV_OPENCL_PROGRAM_CACHE | num | 0 | limit number of programs in OpenCL kernel cache | +| OPENCV_OPENCL_RAISE_ERROR_REUSE_ASYNC_KERNEL | bool | false | raise exception if async kernel failed | +| OPENCV_OPENCL_BUFFERPOOL_LIMIT | num | 1 << 27 (Intel device), 0 (others) | limit memory used by buffer bool | +| OPENCV_OPENCL_HOST_PTR_BUFFERPOOL_LIMIT | num | | same as OPENCV_OPENCL_BUFFERPOOL_LIMIT, but for HOST_PTR buffers | +| OPENCV_OPENCL_BUFFER_FORCE_MAPPING | bool | false | force clEnqueueMapBuffer | +| OPENCV_OPENCL_BUFFER_FORCE_COPYING | bool | false | force clEnqueueReadBuffer/clEnqueueWriteBuffer | +| OPENCV_OPENCL_FORCE | bool | false | force running OpenCL kernel even if usual conditions are not met (e.g. dst.isUMat) | +| OPENCV_OPENCL_PERF_CHECK_BYPASS | bool | false | force running OpenCL kernel even if usual performance-related conditions are not met (e.g. image is very small) | + +##### SVM (Shared Virtual Memory) - disabled by default +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_OPENCL_SVM_DISABLE | bool | false | disable SVM | +| OPENCV_OPENCL_SVM_FORCE_UMAT_USAGE | bool | false | | +| OPENCV_OPENCL_SVM_DISABLE_UMAT_USAGE | bool | false | | +| OPENCV_OPENCL_SVM_CAPABILITIES_MASK | num | | | +| OPENCV_OPENCL_SVM_BUFFERPOOL_LIMIT | num | | same as OPENCV_OPENCL_BUFFERPOOL_LIMIT, but for SVM buffers | + +##### Links: +- https://github.com/opencv/opencv/wiki/OpenCL-optimizations + + +### Tracing/Profiling +| name | type | default | description | +|------|------|---------|-------------| +| ⭐ OPENCV_TRACE | bool | false | enable trace | +| OPENCV_TRACE_LOCATION | string | `OpenCVTrace` | trace file name ("${name}-$03d.txt") | +| OPENCV_TRACE_DEPTH_OPENCV | num | 1 | | +| OPENCV_TRACE_MAX_CHILDREN_OPENCV | num | 1000 | | +| OPENCV_TRACE_MAX_CHILDREN | num | 1000 | | +| OPENCV_TRACE_SYNC_OPENCL | bool | false | wait for OpenCL kernels to finish | +| OPENCV_TRACE_ITT_ENABLE | bool | true | | +| OPENCV_TRACE_ITT_PARENT | bool | false | set parentID for ITT task | +| OPENCV_TRACE_ITT_SET_THREAD_NAME | bool | false | set name for OpenCV's threads "OpenCVThread-%03d" | + +##### Links: +- https://github.com/opencv/opencv/wiki/Profiling-OpenCV-Applications + + +##### Cache +**Note:** Default tmp location is `%TMPDIR%` (Windows); `$XDG_CACHE_HOME`, `$HOME/.cache`, `/var/tmp`, `/tmp` (others) +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_CACHE_SHOW_CLEANUP_MESSAGE | bool | true | show cache cleanup message | +| OPENCV_DOWNLOAD_CACHE_DIR | path | default tmp location | cache directory for downloaded files (subdirectory `downloads`) | +| OPENCV_DNN_IE_GPU_CACHE_DIR | path | default tmp location | cache directory for OpenVINO OpenCL kernels (subdirectory `dnn_ie_cache_${device}`) | +| OPENCV_OPENCL_CACHE_DIR | path | default tmp location | cache directory for OpenCL kernels cache (subdirectory `opencl_cache`) | + + +### dnn +**Note:** In the table below `dump_base_name` equals to `ocv_dnn_net_%05d_%02d` where first argument is internal network ID and the second - dump level. +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_DNN_BACKEND_DEFAULT | num | 3 (OpenCV) | set default DNN backend, see dnn.hpp for backends enumeration | +| OPENCV_DNN_NETWORK_DUMP | num | 0 | level of information dumps, 0 - no dumps (default file name `${dump_base_name}.dot`) | +| OPENCV_DNN_DISABLE_MEMORY_OPTIMIZATIONS | bool | false | | +| OPENCV_DNN_CHECK_NAN_INF | bool | false | check for NaNs in layer outputs | +| OPENCV_DNN_CHECK_NAN_INF_DUMP | bool | false | print layer data when NaN check has failed | +| OPENCV_DNN_CHECK_NAN_INF_RAISE_ERROR | bool | false | also raise exception when NaN check has failed | +| OPENCV_DNN_ONNX_USE_LEGACY_NAMES | bool | false | use ONNX node names as-is instead of "onnx_node!${node_name}" | +| OPENCV_DNN_CUSTOM_ONNX_TYPE_INCLUDE_DOMAIN_NAME | bool | true | prepend layer domain to layer types ("domain.type") | +| OPENCV_VULKAN_RUNTIME | file path | | set location of Vulkan runtime library for DNN Vulkan backend | +| OPENCV_DNN_IE_SERIALIZE | bool | false | dump intermediate OpenVINO graph (default file names `${dump_base_name}_ngraph.xml`, `${dump_base_name}_ngraph.bin`) | +| OPENCV_DNN_IE_EXTRA_PLUGIN_PATH | path | | path to extra OpenVINO plugins | +| OPENCV_DNN_IE_VPU_TYPE | string | | Force using specific OpenVINO VPU device type ("Myriad2" or "MyriadX") | +| OPENCV_TEST_DNN_IE_VPU_TYPE | string | | same as OPENCV_DNN_IE_VPU_TYPE, but for tests | +| OPENCV_DNN_INFERENCE_ENGINE_HOLD_PLUGINS | bool | true | always hold one existing OpenVINO instance to avoid crashes on unloading | +| OPENCV_DNN_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND | bool | true (Windows), false (other) | another OpenVINO lifetime workaround | +| OPENCV_DNN_OPENCL_ALLOW_ALL_DEVICES | bool | false | allow running on CPU devices, allow FP16 on non-Intel device | +| OPENCV_OCL4DNN_CONVOLUTION_IGNORE_INPUT_DIMS_4_CHECK | bool | false | workaround for OpenCL backend, see https://github.com/opencv/opencv/issues/20833 | +| OPENCV_OCL4DNN_WORKAROUND_IDLF | bool | true | another workaround for OpenCL backend | +| OPENCV_OCL4DNN_CONFIG_PATH | path | | path to kernel configuration cache for auto-tuning (must be existing directory), set this variable to enable auto-tuning | +| OPENCV_OCL4DNN_DISABLE_AUTO_TUNING | bool | false | disable auto-tuning | +| OPENCV_OCL4DNN_FORCE_AUTO_TUNING | bool | false | force auto-tuning | +| OPENCV_OCL4DNN_TEST_ALL_KERNELS | num | 0 | test convolution kernels, number of iterations (auto-tuning) | +| OPENCV_OCL4DNN_DUMP_FAILED_RESULT | bool | false | dump extra information on errors (auto-tuning) | +| OPENCV_OCL4DNN_TUNING_RAISE_CHECK_ERROR | bool | false | raise exception on errors (auto-tuning) | + + +### Tests +| name | type | default | description | +|------|------|---------|-------------| +| ⭐ OPENCV_TEST_DATA_PATH | dir path | | set test data search location (e.g. `/home/user/opencv_extra/testdata`) | +| ⭐ OPENCV_DNN_TEST_DATA_PATH | dir path | `$OPENCV_TEST_DATA_PATH/dnn` | set DNN model search location for tests (used by _dnn_, _gapi_, _objdetect_, _video_ modules) | +| OPENCV_OPEN_MODEL_ZOO_DATA_PATH | dir path | `$OPENCV_DNN_TEST_DATA_PATH/omz_intel_models` | set OpenVINO models search location for tests (used by _dnn_, _gapi_ modules) | +| INTEL_CVSDK_DIR | | | some _dnn_ tests can search OpenVINO models here too | +| OPENCV_TEST_DEBUG | num | 0 | debug level for tests, same as `--test_debug` (0 - no debug (default), 1 - basic test debug information, >1 - extra debug information) | +| OPENCV_TEST_REQUIRE_DATA | bool | false | same as `--test_require_data` option (fail on missing non-required test data instead of skip) | +| OPENCV_TEST_CHECK_OPTIONAL_DATA | bool | false | assert when optional data is not found | +| OPENCV_IPP_CHECK | bool | false | default value for `--test_ipp_check` and `--perf_ipp_check` | +| OPENCV_PERF_VALIDATION_DIR | dir path | | location of files read/written by `--perf_read_validation_results`/`--perf_write_validation_results` | +| ⭐ OPENCV_PYTEST_FILTER | string (glob) | | test filter for Python tests | + +##### Links: +* https://github.com/opencv/opencv/wiki/QA_in_OpenCV + + +### videoio +**Note:** extra FFmpeg options should be pased in form `key;value|key;value|key;value`, for example `hwaccel;cuvid|video_codec;h264_cuvid|vsync;0` or `vcodec;x264|vprofile;high|vlevel;4.0` + +| name | type | default | description | +|------|------|---------|-------------| +| ⭐ OPENCV_FFMPEG_CAPTURE_OPTIONS | string (see note) | | extra options for VideoCapture FFmpeg backend | +| ⭐ OPENCV_FFMPEG_WRITER_OPTIONS | string (see note) | | extra options for VideoWriter FFmpeg backend | +| OPENCV_FFMPEG_THREADS | num | | set FFmpeg thread count | +| OPENCV_FFMPEG_DEBUG | non-null | | enable logging messages from FFmpeg | +| OPENCV_FFMPEG_LOGLEVEL | num | | set FFmpeg logging level | +| OPENCV_FFMPEG_DLL_DIR | dir path | | directory with FFmpeg plugin (legacy) | +| OPENCV_FFMPEG_IS_THREAD_SAFE | bool | false | enabling this option will turn off thread safety locks in the FFmpeg backend (use only if you are sure FFmpeg is built with threading support, tested on Linux) | +| OPENCV_FFMPEG_READ_ATTEMPTS | num | 4096 | number of failed `av_read_frame` attempts before failing read procedure | +| OPENCV_FFMPEG_DECODE_ATTEMPTS | num | 64 | number of failed `avcodec_receive_frame` attempts before failing decoding procedure | +| OPENCV_VIDEOIO_GSTREAMER_CALL_DEINIT | bool | false | close GStreamer instance on end | +| OPENCV_VIDEOIO_GSTREAMER_START_MAINLOOP | bool | false | start GStreamer loop in separate thread | +| OPENCV_VIDEOIO_MFX_IMPL | num | | set specific MFX implementation (see MFX docs for enumeration) | +| OPENCV_VIDEOIO_MFX_EXTRA_SURFACE_NUM | num | 1 | add extra surfaces to the surface pool | +| OPENCV_VIDEOIO_MFX_POOL_TIMEOUT | num | 1 | timeout for waiting for free surface from the pool (in seconds) | +| OPENCV_VIDEOIO_MFX_BITRATE_DIVISOR | num | 300 | this option allows to tune encoding bitrate (video quality/size) | +| OPENCV_VIDEOIO_MFX_WRITER_TIMEOUT | num | 1 | timeout for encoding operation (in seconds) | +| OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS | bool | true | allow HW-accelerated transformations (DXVA) in MediaFoundation processing graph (may slow down camera probing process) | +| OPENCV_DSHOW_DEBUG | non-null | | enable verbose logging in the DShow backend | +| OPENCV_DSHOW_SAVEGRAPH_FILENAME | file path | | enable processing graph tump in the DShow backend | +| OPENCV_VIDEOIO_V4L_RANGE_NORMALIZED | bool | false | use (0, 1) range for properties (V4L) | +| OPENCV_VIDEOIO_V4L_SELECT_TIMEOUT | num | 10 | timeout for select call (in seconds) (V4L) | +| OPENCV_VIDEOCAPTURE_DEBUG | bool | false | enable debug messages for VideoCapture | +| OPENCV_VIDEOWRITER_DEBUG | bool | false | enable debug messages for VideoWriter | +| ⭐ OPENCV_VIDEOIO_DEBUG | bool | false | debug messages for both VideoCapture and VideoWriter | + +##### videoio tests +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_TEST_VIDEOIO_BACKEND_REQUIRE_FFMPEG | | | test app will exit if no FFmpeg backend is available | +| OPENCV_TEST_V4L2_VIVID_DEVICE | file path | | path to VIVID virtual camera device for V4L2 test (e.g. `/dev/video5`) | +| OPENCV_TEST_PERF_CAMERA_LIST | paths | | cameras to use in performance test (waitAny_V4L test) | +| OPENCV_TEST_CAMERA_%d_FPS | num | | fps to set for N-th camera (0-based index) (waitAny_V4L test) | + + +### gapi +| name | type | default | description | +|------|------|---------|-------------| +| ⭐ GRAPH_DUMP_PATH | file path | | dump graph (dot format) | +| PIPELINE_MODELS_PATH | dir path | | pipeline_modeling_tool sample application uses this var | +| OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND | bool | true (Windows, Apple), false (others) | similar to OPENCV_DNN_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND | + +##### gapi tests/samples +| name | type | default | description | +|------|------|---------|-------------| +| PLAIDML_DEVICE | string | | specific to PlaidML backend test | +| PLAIDML_TARGET | string | | specific to PlaidML backend test | +| OPENCV_GAPI_ONNX_MODEL_PATH | dir path | | search location for ONNX models test | +| OPENCV_TEST_FREETYPE_FONT_PATH | file path | | location of TrueType font for one of tests | + +##### Links: +* https://github.com/opencv/opencv/wiki/Using-G-API-with-OpenVINO-Toolkit +* https://github.com/opencv/opencv/wiki/Using-G-API-with-MS-ONNX-Runtime + + +### highgui + +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_LEGACY_WAITKEY | non-null | | switch `waitKey` return result (default behavior: `return code & 0xff` (or -1), legacy behavior: `return code`) | +| $XDG_RUNTIME_DIR | | | Wayland backend specific - create shared memory-mapped file for interprocess communication (named `opencv-shared-??????`) | + + +### imgproc +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_OPENCL_IMGPROC_MORPH_SPECIAL_KERNEL | bool | true (Apple), false (others) | use special OpenCL kernel for small morph kernel (Intel devices) | +| OPENCV_GAUSSIANBLUR_CHECK_BITEXACT_KERNELS | bool | false | validate Gaussian kernels before running (src is CV_16U, bit-exact version) | + + +### imgcodecs +| name | type | default | description | +|------|------|---------|-------------| +| OPENCV_IMGCODECS_AVIF_MAX_FILE_SIZE | num | 64MB | limit input AVIF size | +| OPENCV_IMGCODECS_WEBP_MAX_FILE_SIZE | num | 64MB | limit input WEBM size | +| OPENCV_IO_MAX_IMAGE_PARAMS | num | 50 | limit maximum allowed number of parameters in imwrite and imencode | +| OPENCV_IO_MAX_IMAGE_WIDTH | num | 1 << 20, limit input image size to avoid large memory allocations | | +| OPENCV_IO_MAX_IMAGE_HEIGHT | num | 1 << 20 | | +| OPENCV_IO_MAX_IMAGE_PIXELS | num | 1 << 30 | | +| OPENCV_IO_ENABLE_OPENEXR | bool | true (set build option OPENCV_IO_FORCE_OPENEXR or use external OpenEXR), false (otherwise) | enable OpenEXR backend | +| OPENCV_IO_ENABLE_JASPER | bool | true (set build option OPENCV_IO_FORCE_JASPER), false (otherwise) | enable Jasper backend | diff --git a/doc/tutorials/introduction/table_of_content_introduction.markdown b/doc/tutorials/introduction/table_of_content_introduction.markdown index 8fa89d7d7f..4df7ad2c78 100644 --- a/doc/tutorials/introduction/table_of_content_introduction.markdown +++ b/doc/tutorials/introduction/table_of_content_introduction.markdown @@ -3,6 +3,7 @@ Introduction to OpenCV {#tutorial_table_of_content_introduction} - @subpage tutorial_general_install - @subpage tutorial_config_reference +- @subpage tutorial_env_reference ##### Linux - @subpage tutorial_linux_install diff --git a/modules/3d/src/usac/essential_solver.cpp b/modules/3d/src/usac/essential_solver.cpp index 6dad2a4b88..504fec6ab5 100644 --- a/modules/3d/src/usac/essential_solver.cpp +++ b/modules/3d/src/usac/essential_solver.cpp @@ -142,7 +142,7 @@ public: } std::vector c(11), rs; - // filling coefficients of 10-degree polynomial satysfying zero-determinant constraint of essential matrix, ie., det(E) = 0 + // filling coefficients of 10-degree polynomial satisfying zero-determinant constraint of essential matrix, ie., det(E) = 0 // based on "An Efficient Solution to the Five-Point Relative Pose Problem" (David Nister) // same as in five-point.cpp c[10] = (b[0]*b[17]*b[34]+b[26]*b[4]*b[21]-b[26]*b[17]*b[8]-b[13]*b[4]*b[34]-b[0]*b[21]*b[30]+b[13]*b[30]*b[8]); diff --git a/modules/3d/src/usac/ransac_solvers.cpp b/modules/3d/src/usac/ransac_solvers.cpp index d1748fc24e..35c09950cc 100644 --- a/modules/3d/src/usac/ransac_solvers.cpp +++ b/modules/3d/src/usac/ransac_solvers.cpp @@ -322,7 +322,7 @@ void UniversalRANSAC::initialize (int state, Ptr &min_solver, Ptr params->getUpperIncompleteOfSigmaQuantile()); break; case ScoreMethod::SCORE_METHOD_LMEDS : quality = LMedsQuality::create(points_size, threshold, error); break; - default: CV_Error(cv::Error::StsNotImplemented, "Score is not imeplemeted!"); + default: CV_Error(cv::Error::StsNotImplemented, "Score is not implemented!"); } const auto is_ge_solver = params->getRansacSolver() == GEM_SOLVER; diff --git a/modules/3d/src/usac/sampler.cpp b/modules/3d/src/usac/sampler.cpp index 2095ee8b4d..1938bde918 100644 --- a/modules/3d/src/usac/sampler.cpp +++ b/modules/3d/src/usac/sampler.cpp @@ -62,8 +62,8 @@ Ptr UniformSampler::create(int state, int sample_size_, int poin /////////////////////////////////// PROSAC (SIMPLE) SAMPLER /////////////////////////////////////// /* * PROSAC (simple) sampler does not use array of precalculated T_n (n is subset size) samples, but computes T_n for -* specific n directy in generateSample() function. -* Also, the stopping length (or maximum subset size n*) by default is set to points_size (N) and does not updating +* specific n directly in generateSample() function. +* Also, the stopping length (or maximum subset size n*) by default is set to points_size (N) and does not update * during computation. */ class ProsacSimpleSamplerImpl : public ProsacSimpleSampler { @@ -176,7 +176,7 @@ protected: // In our experiments, the parameter was set to T_N = 200000 int growth_max_samples; - // how many time PROSAC generateSample() was called + // how many times PROSAC generateSample() was called int kth_sample_number; Ptr random_gen; public: @@ -488,7 +488,7 @@ public: points_large_neighborhood_size = 0; - // find indicies of points that have sufficient neighborhood (at least sample_size-1) + // find indices of points that have sufficient neighborhood (at least sample_size-1) for (int pt_idx = 0; pt_idx < points_size; pt_idx++) if ((int)neighborhood_graph->getNeighbors(pt_idx).size() >= sample_size-1) points_large_neighborhood[points_large_neighborhood_size++] = pt_idx; diff --git a/modules/3d/src/usac/termination.cpp b/modules/3d/src/usac/termination.cpp index 803b060e41..26a9e331ed 100644 --- a/modules/3d/src/usac/termination.cpp +++ b/modules/3d/src/usac/termination.cpp @@ -19,7 +19,7 @@ public: /* * Get upper bound iterations for any sample number - * n is points size, w is inlier ratio, p is desired probability, k is expceted number of iterations. + * n is points size, w is inlier ratio, p is desired probability, k is expected number of iterations. * 1 - p = (1 - w^n)^k, * k = log_(1-w^n) (1-p) * k = ln (1-p) / ln (1-w^n) diff --git a/modules/3d/test/test_solvepnp_ransac.cpp b/modules/3d/test/test_solvepnp_ransac.cpp index 60c765b8af..be6f1342a7 100644 --- a/modules/3d/test/test_solvepnp_ransac.cpp +++ b/modules/3d/test/test_solvepnp_ransac.cpp @@ -1531,8 +1531,8 @@ TEST(Calib3d_SolvePnP, generic) } else { - p3f = p3f_; - p2f = p2f_; + p3f = vector(p3f_.begin(), p3f_.end()); + p2f = vector(p2f_.begin(), p2f_.end()); } vector reprojectionErrors; diff --git a/modules/calib3d/src/usac/bundle.cpp b/modules/calib3d/src/usac/bundle.cpp new file mode 100644 index 0000000000..a621490575 --- /dev/null +++ b/modules/calib3d/src/usac/bundle.cpp @@ -0,0 +1,308 @@ +// Copyright (c) 2020, Viktor Larsson +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions 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. +// +// * Neither the name of the copyright holder nor the +// names of its contributors may 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 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. + +#include "../precomp.hpp" +#include "../usac.hpp" + +namespace cv { namespace usac { +class MlesacLoss { + public: + MlesacLoss(double threshold) : squared_thr(threshold * threshold), norm_thr(squared_thr*3), one_over_thr(1/norm_thr), inv_sq_thr(1/squared_thr) {} + double loss(double r2) const { + return r2 < norm_thr ? r2 * one_over_thr - 1 : 0; + } + double weight(double r2) const { + // use Cauchly weight + return 1.0 / (1.0 + r2 * inv_sq_thr); + } + const double squared_thr; + private: + const double norm_thr, one_over_thr, inv_sq_thr; +}; + +class RelativePoseJacobianAccumulator { +private: + const Mat* correspondences; + const std::vector &sample; + const int sample_size; + const MlesacLoss &loss_fn; + const double *weights; + +public: + RelativePoseJacobianAccumulator( + const Mat& correspondences_, + const std::vector &sample_, + const int sample_size_, + const MlesacLoss &l, + const double *w = nullptr) : + correspondences(&correspondences_), + sample(sample_), + sample_size(sample_size_), + loss_fn(l), + weights(w) {} + + Matx33d essential_from_motion(const CameraPose &pose) const { + return Matx33d(0.0, -pose.t(2), pose.t(1), + pose.t(2), 0.0, -pose.t(0), + -pose.t(1), pose.t(0), 0.0) * pose.R; + } + + double residual(const CameraPose &pose) const { + const Matx33d E = essential_from_motion(pose); + const float m11=static_cast(E(0,0)), m12=static_cast(E(0,1)), m13=static_cast(E(0,2)); + const float m21=static_cast(E(1,0)), m22=static_cast(E(1,1)), m23=static_cast(E(1,2)); + const float m31=static_cast(E(2,0)), m32=static_cast(E(2,1)), m33=static_cast(E(2,2)); + const auto * const pts = (float *) correspondences->data; + double cost = 0.0; + for (int k = 0; k < sample_size; ++k) { + const int idx = 4*sample[k]; + const float x1=pts[idx], y1=pts[idx+1], x2=pts[idx+2], y2=pts[idx+3]; + const float F_pt1_x = m11 * x1 + m12 * y1 + m13, + F_pt1_y = m21 * x1 + m22 * y1 + m23; + const float pt2_F_x = x2 * m11 + y2 * m21 + m31, + pt2_F_y = x2 * m12 + y2 * m22 + m32; + const float pt2_F_pt1 = x2 * F_pt1_x + y2 * F_pt1_y + m31 * x1 + m32 * y1 + m33; + const float r2 = pt2_F_pt1 * pt2_F_pt1 / (F_pt1_x * F_pt1_x + F_pt1_y * F_pt1_y + + pt2_F_x * pt2_F_x + pt2_F_y * pt2_F_y); + if (weights == nullptr) + cost += loss_fn.loss(r2); + else cost += weights[k] * loss_fn.loss(r2); + } + return cost; + } + + void accumulate(const CameraPose &pose, Matx &JtJ, Matx &Jtr, Matx &tangent_basis) const { + const auto * const pts = (float *) correspondences->data; + // We start by setting up a basis for the updates in the translation (orthogonal to t) + // We find the minimum element of t and cross product with the corresponding basis vector. + // (this ensures that the first cross product is not close to the zero vector) + Vec3d tangent_basis_col0; + if (std::abs(pose.t(0)) < std::abs(pose.t(1))) { + // x < y + if (std::abs(pose.t(0)) < std::abs(pose.t(2))) { + tangent_basis_col0 = pose.t.cross(Vec3d(1,0,0)); + } else { + tangent_basis_col0 = pose.t.cross(Vec3d(0,0,1)); + } + } else { + // x > y + if (std::abs(pose.t(1)) < std::abs(pose.t(2))) { + tangent_basis_col0 = pose.t.cross(Vec3d(0,1,0)); + } else { + tangent_basis_col0 = pose.t.cross(Vec3d(0,0,1)); + } + } + tangent_basis_col0 /= norm(tangent_basis_col0); + Vec3d tangent_basis_col1 = pose.t.cross(tangent_basis_col0); + tangent_basis_col1 /= norm(tangent_basis_col1); + for (int i = 0; i < 3; i++) { + tangent_basis(i,0) = tangent_basis_col0(i); + tangent_basis(i,1) = tangent_basis_col1(i); + } + + const Matx33d E = essential_from_motion(pose); + + // Matrices contain the jacobians of E w.r.t. the rotation and translation parameters + // Each column is vec(E*skew(e_k)) where e_k is k:th basis vector + const Matx dR = {0., -E(0,2), E(0,1), + 0., -E(1,2), E(1,1), + 0., -E(2,2), E(2,1), + E(0,2), 0., -E(0,0), + E(1,2), 0., -E(1,0), + E(2,2), 0., -E(2,0), + -E(0,1), E(0,0), 0., + -E(1,1), E(1,0), 0., + -E(2,1), E(2,0), 0.}; + + Matx dt; + // Each column is vec(skew(tangent_basis[k])*R) + for (int i = 0; i <= 2; i+=1) { + const Vec3d r_i(pose.R(0,i), pose.R(1,i), pose.R(2,i)); + for (int j = 0; j <= 1; j+= 1) { + const Vec3d v = (j == 0 ? tangent_basis_col0 : tangent_basis_col1).cross(r_i); + for (int k = 0; k < 3; k++) { + dt(3*i+k,j) = v[k]; + } + } + } + + for (int k = 0; k < sample_size; ++k) { + const auto point_idx = 4*sample[k]; + const Vec3d pt1 (pts[point_idx], pts[point_idx+1], 1), pt2 (pts[point_idx+2], pts[point_idx+3], 1); + const double C = pt2.dot(E * pt1); + + // J_C is the Jacobian of the epipolar constraint w.r.t. the image points + const Vec4d J_C ((E.col(0).t() * pt2)[0], (E.col(1).t() * pt2)[0], (E.row(0) * pt1)[0], (E.row(1) * pt1)[0]); + const double nJ_C = norm(J_C); + const double inv_nJ_C = 1.0 / nJ_C; + const double r = C * inv_nJ_C; + + if (r*r > loss_fn.squared_thr) continue; + + // Compute weight from robust loss function (used in the IRLS) + double weight = loss_fn.weight(r * r) / sample_size; + if (weights != nullptr) + weight = weights[k] * weight; + + if(weight < DBL_EPSILON) + continue; + + // Compute Jacobian of Sampson error w.r.t the fundamental/essential matrix (3x3) + Matx dF (pt1(0) * pt2(0), pt1(0) * pt2(1), pt1(0), pt1(1) * pt2(0), pt1(1) * pt2(1), pt1(1), pt2(0), pt2(1), 1.0); + const double s = C * inv_nJ_C * inv_nJ_C; + dF(0) -= s * (J_C(2) * pt1(0) + J_C(0) * pt2(0)); + dF(1) -= s * (J_C(3) * pt1(0) + J_C(0) * pt2(1)); + dF(2) -= s * (J_C(0)); + dF(3) -= s * (J_C(2) * pt1(1) + J_C(1) * pt2(0)); + dF(4) -= s * (J_C(3) * pt1(1) + J_C(1) * pt2(1)); + dF(5) -= s * (J_C(1)); + dF(6) -= s * (J_C(2)); + dF(7) -= s * (J_C(3)); + dF *= inv_nJ_C; + + // and then w.r.t. the pose parameters (rotation + tangent basis for translation) + const Matx13d dFdR = dF * dR; + const Matx12d dFdt = dF * dt; + const Matx J (dFdR(0), dFdR(1), dFdR(2), dFdt(0), dFdt(1)); + + // Accumulate into JtJ and Jtr + Jtr += weight * C * inv_nJ_C * J.t(); + JtJ(0, 0) += weight * (J(0) * J(0)); + JtJ(1, 0) += weight * (J(1) * J(0)); + JtJ(1, 1) += weight * (J(1) * J(1)); + JtJ(2, 0) += weight * (J(2) * J(0)); + JtJ(2, 1) += weight * (J(2) * J(1)); + JtJ(2, 2) += weight * (J(2) * J(2)); + JtJ(3, 0) += weight * (J(3) * J(0)); + JtJ(3, 1) += weight * (J(3) * J(1)); + JtJ(3, 2) += weight * (J(3) * J(2)); + JtJ(3, 3) += weight * (J(3) * J(3)); + JtJ(4, 0) += weight * (J(4) * J(0)); + JtJ(4, 1) += weight * (J(4) * J(1)); + JtJ(4, 2) += weight * (J(4) * J(2)); + JtJ(4, 3) += weight * (J(4) * J(3)); + JtJ(4, 4) += weight * (J(4) * J(4)); + } + } +}; + +bool satisfyCheirality (const Matx33d& R, const Vec3d &t, const Vec3d &x1, const Vec3d &x2) { + // This code assumes that x1 and x2 are unit vectors + const auto Rx1 = R * x1; + // lambda_2 * x2 = R * ( lambda_1 * x1 ) + t + // [1 a; a 1] * [lambda1; lambda2] = [b1; b2] + // [lambda1; lambda2] = [1 -a; -a 1] * [b1; b2] / (1 - a*a) + const double a = -Rx1.dot(x2), b1 = -Rx1.dot(t), b2 = x2.dot(t); + // Note that we drop the factor 1.0/(1-a*a) since it is always positive. + return (b1 - a * b2 > 0) && (-a * b1 + b2 > 0); +} + +int refine_relpose(const Mat &correspondences_, + const std::vector &sample_, + const int sample_size_, + CameraPose *pose, + const BundleOptions &opt, + const double* weights) { + MlesacLoss loss_fn(opt.loss_scale); + RelativePoseJacobianAccumulator accum(correspondences_, sample_, sample_size_, loss_fn, weights); + // return lm_5dof_impl(accum, pose, opt); + + Matx JtJ; + Matx Jtr; + Matx tangent_basis; + Matx33d sw = Matx33d::zeros(); + double lambda = opt.initial_lambda; + + // Compute initial cost + double cost = accum.residual(*pose); + bool recompute_jac = true; + int iter; + for (iter = 0; iter < opt.max_iterations; ++iter) { + // We only recompute jacobian and residual vector if last step was successful + if (recompute_jac) { + std::fill(JtJ.val, JtJ.val+25, 0); + std::fill(Jtr.val, Jtr.val +5, 0); + accum.accumulate(*pose, JtJ, Jtr, tangent_basis); + if (norm(Jtr) < opt.gradient_tol) + break; + } + + // Add dampening + JtJ(0, 0) += lambda; + JtJ(1, 1) += lambda; + JtJ(2, 2) += lambda; + JtJ(3, 3) += lambda; + JtJ(4, 4) += lambda; + + Matx sol; + Matx JtJ_symm = JtJ; + for (int i = 0; i < 5; i++) + for (int j = i+1; j < 5; j++) + JtJ_symm(i,j) = JtJ(j,i); + + const bool success = solve(-JtJ_symm, Jtr, sol); + if (!success || norm(sol) < opt.step_tol) + break; + + Vec3d w (sol(0,0), sol(1,0), sol(2,0)); + const double theta = norm(w); + w /= theta; + const double a = std::sin(theta); + const double b = std::cos(theta); + sw(0, 1) = -w(2); + sw(0, 2) = w(1); + sw(1, 2) = -w(0); + sw(1, 0) = w(2); + sw(2, 0) = -w(1); + sw(2, 1) = w(0); + + CameraPose pose_new; + pose_new.R = pose->R + pose->R * (a * sw + (1 - b) * sw * sw); + // In contrast to the 6dof case, we don't apply R here + // (since this can already be added into tangent_basis) + pose_new.t = pose->t + Vec3d(Mat(tangent_basis * Matx21d(sol(3,0), sol(4,0)))); + double cost_new = accum.residual(pose_new); + + if (cost_new < cost) { + *pose = pose_new; + lambda /= 10; + cost = cost_new; + recompute_jac = true; + } else { + JtJ(0, 0) -= lambda; + JtJ(1, 1) -= lambda; + JtJ(2, 2) -= lambda; + JtJ(3, 3) -= lambda; + JtJ(4, 4) -= lambda; + lambda *= 10; + recompute_jac = false; + } + } + return iter; +} +}} \ No newline at end of file diff --git a/modules/core/include/opencv2/core/cuda.hpp b/modules/core/include/opencv2/core/cuda.hpp index 9c948ce00a..5dca06df98 100644 --- a/modules/core/include/opencv2/core/cuda.hpp +++ b/modules/core/include/opencv2/core/cuda.hpp @@ -577,7 +577,7 @@ CV_EXPORTS_W void ensureSizeIsEnough(int rows, int cols, int type, OutputArray a */ CV_EXPORTS_W GpuMat inline createGpuMatFromCudaMemory(int rows, int cols, int type, size_t cudaMemoryAddress, size_t step = Mat::AUTO_STEP) { return GpuMat(rows, cols, type, reinterpret_cast(cudaMemoryAddress), step); -}; +} /** @overload @param size 2D array size: Size(cols, rows). In the Size() constructor, the number of rows and the number of columns go in the reverse order. @@ -588,7 +588,7 @@ CV_EXPORTS_W GpuMat inline createGpuMatFromCudaMemory(int rows, int cols, int ty */ CV_EXPORTS_W inline GpuMat createGpuMatFromCudaMemory(Size size, int type, size_t cudaMemoryAddress, size_t step = Mat::AUTO_STEP) { return GpuMat(size, type, reinterpret_cast(cudaMemoryAddress), step); -}; +} /** @brief BufferPool for use with CUDA streams diff --git a/modules/core/include/opencv2/core/fast_math.hpp b/modules/core/include/opencv2/core/fast_math.hpp index 47a2948222..08401afbb8 100644 --- a/modules/core/include/opencv2/core/fast_math.hpp +++ b/modules/core/include/opencv2/core/fast_math.hpp @@ -201,7 +201,7 @@ cvRound( double value ) { #if defined CV_INLINE_ROUND_DBL CV_INLINE_ROUND_DBL(value); -#elif (defined _MSC_VER && defined _M_X64) && !defined(__CUDACC__) +#elif ((defined _MSC_VER && defined _M_X64) || (defined __GNUC__ && defined __SSE2__)) && !defined(__CUDACC__) __m128d t = _mm_set_sd( value ); return _mm_cvtsd_si32(t); #elif defined _MSC_VER && defined _M_IX86 @@ -323,7 +323,7 @@ CV_INLINE int cvRound(float value) { #if defined CV_INLINE_ROUND_FLT CV_INLINE_ROUND_FLT(value); -#elif (defined _MSC_VER && defined _M_X64) && !defined(__CUDACC__) +#elif ((defined _MSC_VER && defined _M_X64) || (defined __GNUC__ && defined __SSE2__)) && !defined(__CUDACC__) __m128 t = _mm_set_ss( value ); return _mm_cvtss_si32(t); #elif defined _MSC_VER && defined _M_IX86 @@ -354,7 +354,7 @@ CV_INLINE int cvFloor( float value ) #if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \ defined CV__FASTMATH_ENABLE_CLANG_MATH_BUILTINS return (int)__builtin_floorf(value); -#elif defined __loongarch +#elif defined __loongarch__ int i; float tmp; __asm__ ("ftintrm.w.s %[tmp], %[in] \n\t" @@ -381,7 +381,7 @@ CV_INLINE int cvCeil( float value ) #if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \ defined CV__FASTMATH_ENABLE_CLANG_MATH_BUILTINS return (int)__builtin_ceilf(value); -#elif defined __loongarch +#elif defined __loongarch__ int i; float tmp; __asm__ ("ftintrp.w.s %[tmp], %[in] \n\t" diff --git a/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp b/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp index 60066ba041..dab82489f8 100644 --- a/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp @@ -1651,6 +1651,10 @@ inline v_uint32 v_popcount(const v_uint32& a) { return v_hadd(v_hadd(v_popcount(vreinterpret_u8m1(a)))); } +inline v_uint64 v_popcount(const v_uint64& a) +{ + return v_hadd(v_hadd(v_hadd(v_popcount(vreinterpret_u8m1(a))))); +} inline v_uint8 v_popcount(const v_int8& a) { @@ -1664,6 +1668,11 @@ inline v_uint32 v_popcount(const v_int32& a) { return v_popcount(v_abs(a));\ } +inline v_uint64 v_popcount(const v_int64& a) +{ + // max(0 - a) is used, since v_abs does not support 64-bit integers. + return v_popcount(v_reinterpret_as_u64(vmax(a, v_sub(v_setzero_s64(), a), VTraits::vlanes()))); +} //////////// SignMask //////////// diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 67653526c1..a5f244e8c0 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -1288,15 +1288,36 @@ public: t(); // finally, transpose the Nx3 matrix. // This involves copying all the elements @endcode + 3-channel 2x2 matrix reshaped to 1-channel 4x3 matrix, each column has values from one of original channels: + @code + Mat m(Size(2, 2), CV_8UC3, Scalar(1, 2, 3)); + vector new_shape {4, 3}; + m = m.reshape(1, new_shape); + @endcode + or: + @code + Mat m(Size(2, 2), CV_8UC3, Scalar(1, 2, 3)); + const int new_shape[] = {4, 3}; + m = m.reshape(1, 2, new_shape); + @endcode @param cn New number of channels. If the parameter is 0, the number of channels remains the same. @param rows New number of rows. If the parameter is 0, the number of rows remains the same. */ Mat reshape(int cn, int rows=0) const; - /** @overload */ + /** @overload + * @param cn New number of channels. If the parameter is 0, the number of channels remains the same. + * @param newndims New number of dimentions. + * @param newsz Array with new matrix size by all dimentions. If some sizes are zero, + * the original sizes in those dimensions are presumed. + */ Mat reshape(int cn, int newndims, const int* newsz) const; - /** @overload */ + /** @overload + * @param cn New number of channels. If the parameter is 0, the number of channels remains the same. + * @param newshape Vector with new matrix size by all dimentions. If some sizes are zero, + * the original sizes in those dimensions are presumed. + */ Mat reshape(int cn, const std::vector& newshape) const; /** @brief Transposes a matrix. diff --git a/modules/core/include/opencv2/core/mat.inl.hpp b/modules/core/include/opencv2/core/mat.inl.hpp index c6db72f267..c9fc1d67a6 100644 --- a/modules/core/include/opencv2/core/mat.inl.hpp +++ b/modules/core/include/opencv2/core/mat.inl.hpp @@ -51,7 +51,7 @@ #ifdef _MSC_VER #pragma warning( push ) -#pragma warning( disable: 4127 ) +#pragma warning( disable: 4127 5054 ) #endif #if defined(CV_SKIP_DISABLE_CLANG_ENUM_WARNINGS) diff --git a/modules/core/include/opencv2/core/optim.hpp b/modules/core/include/opencv2/core/optim.hpp index f61a2b9407..59fe978c26 100644 --- a/modules/core/include/opencv2/core/optim.hpp +++ b/modules/core/include/opencv2/core/optim.hpp @@ -256,6 +256,7 @@ public: //! return codes for cv::solveLP() function enum SolveLPResult { + SOLVELP_LOST = -3, //!< problem is feasible, but solver lost solution due to floating-point arithmetic errors SOLVELP_UNBOUNDED = -2, //!< problem is unbounded (target function can achieve arbitrary high values) SOLVELP_UNFEASIBLE = -1, //!< problem is unfeasible (there are no points that satisfy all the constraints imposed) SOLVELP_SINGLE = 0, //!< there is only one maximum for target function @@ -291,8 +292,12 @@ in the latter case it is understood to correspond to \f$c^T\f$. and the remaining to \f$A\f$. It should contain 32- or 64-bit floating point numbers. @param z The solution will be returned here as a column-vector - it corresponds to \f$c\f$ in the formulation above. It will contain 64-bit floating point numbers. +@param constr_eps allowed numeric disparity for constraints @return One of cv::SolveLPResult */ +CV_EXPORTS_W int solveLP(InputArray Func, InputArray Constr, OutputArray z, double constr_eps); + +/** @overload */ CV_EXPORTS_W int solveLP(InputArray Func, InputArray Constr, OutputArray z); //! @} diff --git a/modules/core/include/opencv2/core/types.hpp b/modules/core/include/opencv2/core/types.hpp index cf26309949..0e0aa980e1 100644 --- a/modules/core/include/opencv2/core/types.hpp +++ b/modules/core/include/opencv2/core/types.hpp @@ -527,23 +527,23 @@ The sample below demonstrates how to use RotatedRect: @sa CamShift, fitEllipse, minAreaRect, CvBox2D */ -class CV_EXPORTS RotatedRect +class CV_EXPORTS_W_SIMPLE RotatedRect { public: //! default constructor - RotatedRect(); + CV_WRAP RotatedRect(); /** full constructor @param center The rectangle mass center. @param size Width and height of the rectangle. @param angle The rotation angle in a clockwise direction. When the angle is 0, 90, 180, 270 etc., the rectangle becomes an up-right rectangle. */ - RotatedRect(const Point2f& center, const Size2f& size, float angle); + CV_WRAP RotatedRect(const Point2f& center, const Size2f& size, float angle); /** Any 3 end points of the RotatedRect. They must be given in order (either clockwise or anticlockwise). */ - RotatedRect(const Point2f& point1, const Point2f& point2, const Point2f& point3); + CV_WRAP RotatedRect(const Point2f& point1, const Point2f& point2, const Point2f& point3); /** returns 4 vertices of the rotated rectangle @param pts The points array for storing rectangle vertices. The order is _bottomLeft_, _topLeft_, topRight, bottomRight. @@ -552,16 +552,19 @@ public: rectangle. */ void points(Point2f pts[]) const; + + CV_WRAP void points(CV_OUT std::vector& pts) const; + //! returns the minimal up-right integer rectangle containing the rotated rectangle - Rect boundingRect() const; + CV_WRAP Rect boundingRect() const; //! returns the minimal (exact) floating point rectangle containing the rotated rectangle, not intended for use with images Rect_ boundingRect2f() const; //! returns the rectangle mass center - Point2f center; + CV_PROP_RW Point2f center; //! returns width and height of the rectangle - Size2f size; + CV_PROP_RW Size2f size; //! returns the rotation angle. When the angle is 0, 90, 180, 270 etc., the rectangle becomes an up-right rectangle. - float angle; + CV_PROP_RW float angle; }; template<> class DataType< RotatedRect > diff --git a/modules/core/misc/java/src/java/core+Mat.java b/modules/core/misc/java/src/java/core+Mat.java index 2d9ee314a3..8e98284dde 100644 --- a/modules/core/misc/java/src/java/core+Mat.java +++ b/modules/core/misc/java/src/java/core+Mat.java @@ -470,6 +470,7 @@ public class Mat { * Element-wise multiplication with scale factor * @param m operand with with which to perform element-wise multiplication * @param scale scale factor + * @return reference to a new Mat object */ public Mat mul(Mat m, double scale) { return new Mat(n_mul(nativeObj, m.nativeObj, scale)); @@ -478,6 +479,7 @@ public class Mat { /** * Element-wise multiplication * @param m operand with with which to perform element-wise multiplication + * @return reference to a new Mat object */ public Mat mul(Mat m) { return new Mat(n_mul(nativeObj, m.nativeObj)); @@ -487,6 +489,7 @@ public class Mat { * Matrix multiplication * @param m operand with with which to perform matrix multiplication * @see Core#gemm(Mat, Mat, double, Mat, double, Mat, int) + * @return reference to a new Mat object */ public Mat matMul(Mat m) { return new Mat(n_matMul(nativeObj, m.nativeObj)); diff --git a/modules/core/misc/python/package/mat_wrapper/__init__.py b/modules/core/misc/python/package/mat_wrapper/__init__.py index 7309c32b01..7cbc0645de 100644 --- a/modules/core/misc/python/package/mat_wrapper/__init__.py +++ b/modules/core/misc/python/package/mat_wrapper/__init__.py @@ -1,12 +1,18 @@ __all__ = [] -import sys import numpy as np import cv2 as cv +from typing import TYPE_CHECKING, Any + +# Same as cv2.typing.NumPyArrayGeneric, but avoids circular dependencies +if TYPE_CHECKING: + _NumPyArrayGeneric = np.ndarray[Any, np.dtype[np.generic]] +else: + _NumPyArrayGeneric = np.ndarray # NumPy documentation: https://numpy.org/doc/stable/user/basics.subclassing.html -class Mat(np.ndarray): +class Mat(_NumPyArrayGeneric): ''' cv.Mat wrapper for numpy array. diff --git a/modules/core/src/channels.cpp b/modules/core/src/channels.cpp index 6ceed44a28..efaeb91068 100644 --- a/modules/core/src/channels.cpp +++ b/modules/core/src/channels.cpp @@ -46,44 +46,44 @@ mixChannels_( const T** src, const int* sdelta, } -static void mixChannels8u( const uchar** src, const int* sdelta, - uchar** dst, const int* ddelta, +static void mixChannels8u( const void** src, const int* sdelta, + void** dst, const int* ddelta, int len, int npairs ) { - mixChannels_(src, sdelta, dst, ddelta, len, npairs); + mixChannels_((const uchar**)src, sdelta, (uchar**)dst, ddelta, len, npairs); } -static void mixChannels16u( const ushort** src, const int* sdelta, - ushort** dst, const int* ddelta, +static void mixChannels16u( const void** src, const int* sdelta, + void** dst, const int* ddelta, int len, int npairs ) { - mixChannels_(src, sdelta, dst, ddelta, len, npairs); + mixChannels_((const ushort**)src, sdelta, (ushort**)dst, ddelta, len, npairs); } -static void mixChannels32s( const int** src, const int* sdelta, - int** dst, const int* ddelta, +static void mixChannels32s( const void** src, const int* sdelta, + void** dst, const int* ddelta, int len, int npairs ) { - mixChannels_(src, sdelta, dst, ddelta, len, npairs); + mixChannels_((const int**)src, sdelta, (int**)dst, ddelta, len, npairs); } -static void mixChannels64s( const int64** src, const int* sdelta, - int64** dst, const int* ddelta, +static void mixChannels64s( const void** src, const int* sdelta, + void** dst, const int* ddelta, int len, int npairs ) { - mixChannels_(src, sdelta, dst, ddelta, len, npairs); + mixChannels_((const int64**)src, sdelta, (int64**)dst, ddelta, len, npairs); } -typedef void (*MixChannelsFunc)( const uchar** src, const int* sdelta, - uchar** dst, const int* ddelta, int len, int npairs ); +typedef void (*MixChannelsFunc)( const void** src, const int* sdelta, + void** dst, const int* ddelta, int len, int npairs ); static MixChannelsFunc getMixchFunc(int depth) { static MixChannelsFunc mixchTab[] = { - (MixChannelsFunc)mixChannels8u, (MixChannelsFunc)mixChannels8u, (MixChannelsFunc)mixChannels16u, - (MixChannelsFunc)mixChannels16u, (MixChannelsFunc)mixChannels32s, (MixChannelsFunc)mixChannels32s, - (MixChannelsFunc)mixChannels64s, 0 + mixChannels8u, mixChannels8u, mixChannels16u, + mixChannels16u, mixChannels32s, mixChannels32s, + mixChannels64s, 0 }; return mixchTab[depth]; @@ -158,7 +158,7 @@ void cv::mixChannels( const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts, cons for( int t = 0; t < total; t += blocksize ) { int bsz = std::min(total - t, blocksize); - func( srcs, sdelta, dsts, ddelta, bsz, (int)npairs ); + func( (const void**)srcs, sdelta, (void **)dsts, ddelta, bsz, (int)npairs ); if( t + blocksize < total ) for( k = 0; k < npairs; k++ ) diff --git a/modules/core/src/lpsolver.cpp b/modules/core/src/lpsolver.cpp index 43fa73a025..b09fb0128b 100644 --- a/modules/core/src/lpsolver.cpp +++ b/modules/core/src/lpsolver.cpp @@ -90,7 +90,7 @@ static void swap_columns(Mat_& A,int col1,int col2); #define SWAP(type,a,b) {type tmp=(a);(a)=(b);(b)=tmp;} //return codes:-2 (no_sol - unbdd),-1(no_sol - unfsbl), 0(single_sol), 1(multiple_sol=>least_l2_norm) -int solveLP(InputArray Func_, InputArray Constr_, OutputArray z_) +int solveLP(InputArray Func_, InputArray Constr_, OutputArray z_, double constr_eps) { dprintf(("call to solveLP\n")); @@ -143,9 +143,25 @@ int solveLP(InputArray Func_, InputArray Constr_, OutputArray z_) } z.copyTo(z_); + + //check constraints feasibility + Mat prod = Constr(Rect(0, 0, Constr.cols - 1, Constr.rows)) * z; + Mat constr_check = Constr.col(Constr.cols - 1) - prod; + double min_value = 0.0; + minMaxIdx(constr_check, &min_value); + if (min_value < -constr_eps) + { + return SOLVELP_LOST; + } + return res; } +int solveLP(InputArray Func, InputArray Constr, OutputArray z) +{ + return solveLP(Func, Constr, z, 1e-12); +} + static int initialize_simplex(Mat_& c, Mat_& b,double& v,vector& N,vector& B,vector& indexToRow){ N.resize(c.cols); N[0]=0; @@ -255,7 +271,7 @@ static int initialize_simplex(Mat_& c, Mat_& b,double& v,vector< static int inner_simplex(Mat_& c, Mat_& b,double& v,vector& N,vector& B,vector& indexToRow){ for(;;){ - static MatIterator_ pos_ptr; + MatIterator_ pos_ptr; int e=-1,pos_ctr=0,min_var=INT_MAX; bool all_nonzero=true; for(pos_ptr=c.begin();pos_ptr!=c.end();pos_ptr++,pos_ctr++){ diff --git a/modules/core/src/matrix_transform.cpp b/modules/core/src/matrix_transform.cpp index 57fd0c6509..7f1043fbbe 100644 --- a/modules/core/src/matrix_transform.cpp +++ b/modules/core/src/matrix_transform.cpp @@ -603,10 +603,10 @@ flipVert( const uchar* src0, size_t sstep, uchar* dst0, size_t dstep, Size size, { for (; i <= size.width - CV_SIMD_WIDTH; i += CV_SIMD_WIDTH) { - v_int32 t0 = vx_load((int*)(src0 + i)); - v_int32 t1 = vx_load((int*)(src1 + i)); - v_store((int*)(dst0 + i), t1); - v_store((int*)(dst1 + i), t0); + v_int32 t0 = v_reinterpret_as_s32(vx_load(src0 + i)); + v_int32 t1 = v_reinterpret_as_s32(vx_load(src1 + i)); + v_store(dst0 + i, v_reinterpret_as_u8(t1)); + v_store(dst1 + i, v_reinterpret_as_u8(t0)); } } #if CV_STRONG_ALIGNMENT diff --git a/modules/core/src/mean.simd.hpp b/modules/core/src/mean.simd.hpp index d94c887223..bb815adc1c 100644 --- a/modules/core/src/mean.simd.hpp +++ b/modules/core/src/mean.simd.hpp @@ -24,7 +24,7 @@ struct SumSqr_SIMD } }; -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE template <> struct SumSqr_SIMD @@ -39,37 +39,37 @@ struct SumSqr_SIMD v_int32 v_sum = vx_setzero_s32(); v_int32 v_sqsum = vx_setzero_s32(); - const int len0 = len & -v_uint8::nlanes; + const int len0 = len & -VTraits::vlanes(); while(x < len0) { - const int len_tmp = min(x + 256*v_uint16::nlanes, len0); + const int len_tmp = min(x + 256*VTraits::vlanes(), len0); v_uint16 v_sum16 = vx_setzero_u16(); - for ( ; x < len_tmp; x += v_uint8::nlanes) + for ( ; x < len_tmp; x += VTraits::vlanes()) { v_uint16 v_src0 = vx_load_expand(src0 + x); - v_uint16 v_src1 = vx_load_expand(src0 + x + v_uint16::nlanes); - v_sum16 += v_src0 + v_src1; + v_uint16 v_src1 = vx_load_expand(src0 + x + VTraits::vlanes()); + v_sum16 = v_add(v_sum16, v_add(v_src0, v_src1)); v_int16 v_tmp0, v_tmp1; v_zip(v_reinterpret_as_s16(v_src0), v_reinterpret_as_s16(v_src1), v_tmp0, v_tmp1); - v_sqsum += v_dotprod(v_tmp0, v_tmp0) + v_dotprod(v_tmp1, v_tmp1); + v_sqsum = v_add(v_sqsum, v_add(v_dotprod(v_tmp0, v_tmp0), v_dotprod(v_tmp1, v_tmp1))); } v_uint32 v_half0, v_half1; v_expand(v_sum16, v_half0, v_half1); - v_sum += v_reinterpret_as_s32(v_half0 + v_half1); + v_sum = v_add(v_sum, v_reinterpret_as_s32(v_add(v_half0, v_half1))); } - if (x <= len - v_uint16::nlanes) + if (x <= len - VTraits::vlanes()) { v_uint16 v_src = vx_load_expand(src0 + x); v_uint16 v_half = v_combine_high(v_src, v_src); v_uint32 v_tmp0, v_tmp1; - v_expand(v_src + v_half, v_tmp0, v_tmp1); - v_sum += v_reinterpret_as_s32(v_tmp0); + v_expand(v_add(v_src, v_half), v_tmp0, v_tmp1); + v_sum = v_add(v_sum, v_reinterpret_as_s32(v_tmp0)); v_int16 v_tmp2, v_tmp3; v_zip(v_reinterpret_as_s16(v_src), v_reinterpret_as_s16(v_half), v_tmp2, v_tmp3); - v_sqsum += v_dotprod(v_tmp2, v_tmp2); - x += v_uint16::nlanes; + v_sqsum = v_add(v_sqsum, v_dotprod(v_tmp2, v_tmp2)); + x += VTraits::vlanes(); } if (cn == 1) @@ -79,13 +79,13 @@ struct SumSqr_SIMD } else { - int CV_DECL_ALIGNED(CV_SIMD_WIDTH) ar[2 * v_int32::nlanes]; + int CV_DECL_ALIGNED(CV_SIMD_WIDTH) ar[2 * VTraits::max_nlanes]; v_store(ar, v_sum); - v_store(ar + v_int32::nlanes, v_sqsum); - for (int i = 0; i < v_int32::nlanes; ++i) + v_store(ar + VTraits::vlanes(), v_sqsum); + for (int i = 0; i < VTraits::vlanes(); ++i) { sum[i % cn] += ar[i]; - sqsum[i % cn] += ar[v_int32::nlanes + i]; + sqsum[i % cn] += ar[VTraits::vlanes() + i]; } } v_cleanup(); @@ -106,37 +106,37 @@ struct SumSqr_SIMD v_int32 v_sum = vx_setzero_s32(); v_int32 v_sqsum = vx_setzero_s32(); - const int len0 = len & -v_int8::nlanes; + const int len0 = len & -VTraits::vlanes(); while (x < len0) { - const int len_tmp = min(x + 256 * v_int16::nlanes, len0); + const int len_tmp = min(x + 256 * VTraits::vlanes(), len0); v_int16 v_sum16 = vx_setzero_s16(); - for (; x < len_tmp; x += v_int8::nlanes) + for (; x < len_tmp; x += VTraits::vlanes()) { v_int16 v_src0 = vx_load_expand(src0 + x); - v_int16 v_src1 = vx_load_expand(src0 + x + v_int16::nlanes); - v_sum16 += v_src0 + v_src1; + v_int16 v_src1 = vx_load_expand(src0 + x + VTraits::vlanes()); + v_sum16 = v_add(v_sum16, v_add(v_src0, v_src1)); v_int16 v_tmp0, v_tmp1; v_zip(v_src0, v_src1, v_tmp0, v_tmp1); - v_sqsum += v_dotprod(v_tmp0, v_tmp0) + v_dotprod(v_tmp1, v_tmp1); + v_sqsum = v_add(v_sqsum, v_add(v_dotprod(v_tmp0, v_tmp0), v_dotprod(v_tmp1, v_tmp1))); } v_int32 v_half0, v_half1; v_expand(v_sum16, v_half0, v_half1); - v_sum += v_half0 + v_half1; + v_sum = v_add(v_sum, v_add(v_half0, v_half1)); } - if (x <= len - v_int16::nlanes) + if (x <= len - VTraits::vlanes()) { v_int16 v_src = vx_load_expand(src0 + x); v_int16 v_half = v_combine_high(v_src, v_src); v_int32 v_tmp0, v_tmp1; - v_expand(v_src + v_half, v_tmp0, v_tmp1); - v_sum += v_tmp0; + v_expand(v_add(v_src, v_half), v_tmp0, v_tmp1); + v_sum = v_add(v_sum, v_tmp0); v_int16 v_tmp2, v_tmp3; v_zip(v_src, v_half, v_tmp2, v_tmp3); - v_sqsum += v_dotprod(v_tmp2, v_tmp2); - x += v_int16::nlanes; + v_sqsum = v_add(v_sqsum, v_dotprod(v_tmp2, v_tmp2)); + x += VTraits::vlanes(); } if (cn == 1) @@ -146,13 +146,13 @@ struct SumSqr_SIMD } else { - int CV_DECL_ALIGNED(CV_SIMD_WIDTH) ar[2 * v_int32::nlanes]; + int CV_DECL_ALIGNED(CV_SIMD_WIDTH) ar[2 * VTraits::max_nlanes]; v_store(ar, v_sum); - v_store(ar + v_int32::nlanes, v_sqsum); - for (int i = 0; i < v_int32::nlanes; ++i) + v_store(ar + VTraits::vlanes(), v_sqsum); + for (int i = 0; i < VTraits::vlanes(); ++i) { sum[i % cn] += ar[i]; - sqsum[i % cn] += ar[v_int32::nlanes + i]; + sqsum[i % cn] += ar[VTraits::vlanes() + i]; } } v_cleanup(); diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index 946c9b16e6..e201d4a5af 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -51,7 +51,6 @@ #include #include #include -#include // std::cerr #include #if !(defined _MSC_VER) || (defined _MSC_VER && _MSC_VER > 1700) #include diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index c7c8b28f64..63e699f02c 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -128,6 +128,8 @@ #include #elif defined HAVE_CONCURRENCY #include +#elif defined HAVE_PTHREADS_PF + #include #endif diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index 0828e9cb89..4f26ff305e 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -2522,7 +2522,7 @@ public: ippStatus = ippGetCpuFeatures(&cpuFeatures, NULL); if(ippStatus < 0) { - std::cerr << "ERROR: IPP cannot detect CPU features, IPP was disabled " << std::endl; + CV_LOG_ERROR(NULL, "ERROR: IPP cannot detect CPU features, IPP was disabled"); useIPP = false; return; } @@ -2560,7 +2560,7 @@ public: if(env == "disabled") { - std::cerr << "WARNING: IPP was disabled by OPENCV_IPP environment variable" << std::endl; + CV_LOG_WARNING(NULL, "WARNING: IPP was disabled by OPENCV_IPP environment variable"); useIPP = false; } else if(env == "sse42") @@ -2574,7 +2574,7 @@ public: #endif #endif else - std::cerr << "ERROR: Improper value of OPENCV_IPP: " << env.c_str() << ". Correct values are: disabled, sse42, avx2, avx512 (Intel64 only)" << std::endl; + CV_LOG_ERROR(NULL, "ERROR: Improper value of OPENCV_IPP: " << env.c_str() << ". Correct values are: disabled, sse42, avx2, avx512 (Intel64 only)"); // Trim unsupported features ippFeatures &= cpuFeatures; diff --git a/modules/core/src/types.cpp b/modules/core/src/types.cpp index 15ba83a0f6..43e25ef045 100644 --- a/modules/core/src/types.cpp +++ b/modules/core/src/types.cpp @@ -186,6 +186,11 @@ void RotatedRect::points(Point2f pt[]) const pt[3].y = 2*center.y - pt[1].y; } +void RotatedRect::points(std::vector& pts) const { + pts.resize(4); + points(pts.data()); +} + Rect RotatedRect::boundingRect() const { Point2f pt[4]; diff --git a/modules/core/test/test_intrin_utils.hpp b/modules/core/test/test_intrin_utils.hpp index 01ca50a26e..481e6bb1f2 100644 --- a/modules/core/test/test_intrin_utils.hpp +++ b/modules/core/test/test_intrin_utils.hpp @@ -2048,6 +2048,7 @@ void test_hal_intrin_uint64() .test_rotate<0>().test_rotate<1>() .test_extract_n<0>().test_extract_n<1>() .test_extract_highest() + .test_popcount() //.test_broadcast_element<0>().test_broadcast_element<1>() ; } @@ -2069,6 +2070,7 @@ void test_hal_intrin_int64() .test_extract_highest() //.test_broadcast_element<0>().test_broadcast_element<1>() .test_cvt64_double() + .test_popcount() ; } diff --git a/modules/core/test/test_lpsolver.cpp b/modules/core/test/test_lpsolver.cpp index 99829cbefa..b46dc7ef79 100644 --- a/modules/core/test/test_lpsolver.cpp +++ b/modules/core/test/test_lpsolver.cpp @@ -151,4 +151,18 @@ TEST(Core_LPSolver, issue_12337) EXPECT_ANY_THROW(Mat1b z_8u; cv::solveLP(A, B, z_8u)); } +// NOTE: Test parameters found experimentally to get numerically inaccurate result. +// The test behaviour may change after algorithm tuning and may removed. +TEST(Core_LPSolver, issue_12343) +{ + Mat A = (cv::Mat_(4, 1) << 3., 3., 3., 4.); + Mat B = (cv::Mat_(4, 5) << 0., 1., 4., 4., 3., + 3., 1., 2., 2., 3., + 4., 4., 0., 1., 4., + 4., 0., 4., 1., 4.); + Mat z; + int result = cv::solveLP(A, B, z); + EXPECT_EQ(SOLVELP_LOST, result); +} + }} // namespace diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index df2c42fdb8..dd7a49f22a 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -18,7 +18,7 @@ class Core_ReduceTest : public cvtest::BaseTest public: Core_ReduceTest() {} protected: - void run( int); + void run( int) CV_OVERRIDE; int checkOp( const Mat& src, int dstType, int opType, const Mat& opRes, int dim ); int checkCase( int srcType, int dstType, int dim, Size sz ); int checkDim( int dim, Size sz ); @@ -495,7 +495,7 @@ public: Core_ArrayOpTest(); ~Core_ArrayOpTest(); protected: - void run(int); + void run(int) CV_OVERRIDE; }; @@ -599,6 +599,11 @@ static void setValue(SparseMat& M, const int* idx, double value, RNG& rng) CV_Error(CV_StsUnsupportedFormat, ""); } +#if defined(__GNUC__) && (__GNUC__ == 11 || __GNUC__ == 12) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + template struct InitializerFunctor{ /// Initializer for cv::Mat::forEach test @@ -621,6 +626,11 @@ struct InitializerFunctor5D{ } }; +#if defined(__GNUC__) && (__GNUC__ == 11 || __GNUC__ == 12) +#pragma GCC diagnostic pop +#endif + + template struct EmptyFunctor { @@ -1023,7 +1033,7 @@ class Core_MergeSplitBaseTest : public cvtest::BaseTest protected: virtual int run_case(int depth, size_t channels, const Size& size, RNG& rng) = 0; - virtual void run(int) + virtual void run(int) CV_OVERRIDE { // m is Mat // mv is vector @@ -1068,7 +1078,7 @@ public: ~Core_MergeTest() {} protected: - virtual int run_case(int depth, size_t matCount, const Size& size, RNG& rng) + virtual int run_case(int depth, size_t matCount, const Size& size, RNG& rng) CV_OVERRIDE { const int maxMatChannels = 10; @@ -1126,7 +1136,7 @@ public: ~Core_SplitTest() {} protected: - virtual int run_case(int depth, size_t channels, const Size& size, RNG& rng) + virtual int run_case(int depth, size_t channels, const Size& size, RNG& rng) CV_OVERRIDE { Mat src(size, CV_MAKETYPE(depth, (int)channels)); rng.fill(src, RNG::UNIFORM, 0, 100, true); @@ -1990,7 +2000,6 @@ TEST(Core_InputArray, fetch_MatExpr) } -#ifdef CV_CXX11 class TestInputArrayRangeChecking { static const char *kind2str(cv::_InputArray ia) { @@ -2136,8 +2145,6 @@ TEST(Core_InputArray, range_checking) { TestInputArrayRangeChecking::run(); } -#endif - TEST(Core_Vectors, issue_13078) { diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index 60f68fce6e..d61f7191bc 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -894,7 +894,6 @@ CV__DNN_INLINE_NS_BEGIN * @param cfgFile path to the .cfg file with text description of the network architecture. * @param darknetModel path to the .weights file with learned network. * @returns Network object that ready to do forward, throw an exception in failure cases. - * @returns Net object. */ CV_EXPORTS_W Net readNetFromDarknet(const String &cfgFile, const String &darknetModel = String()); diff --git a/modules/dnn/include/opencv2/dnn/version.hpp b/modules/dnn/include/opencv2/dnn/version.hpp index ea5a218904..a1cfca0e1d 100644 --- a/modules/dnn/include/opencv2/dnn/version.hpp +++ b/modules/dnn/include/opencv2/dnn/version.hpp @@ -6,7 +6,7 @@ #define OPENCV_DNN_VERSION_HPP /// Use with major OpenCV version only. -#define OPENCV_DNN_API_VERSION 20221220 +#define OPENCV_DNN_API_VERSION 20230620 #if !defined CV_DOXYGEN && !defined CV_STATIC_ANALYSIS && !defined CV_DNN_DONT_ADD_INLINE_NS #define CV__DNN_INLINE_NS __CV_CAT(dnn5_v, OPENCV_DNN_API_VERSION) diff --git a/modules/dnn/perf/perf_convolution.cpp b/modules/dnn/perf/perf_convolution.cpp index bb890c6a00..99f425b712 100644 --- a/modules/dnn/perf/perf_convolution.cpp +++ b/modules/dnn/perf/perf_convolution.cpp @@ -744,7 +744,8 @@ static const ConvParam_t testConvolutionConfigs[] = { /* GFLOPS 0.000 x 1 = 0.000 */ {{1, 1}, {{1, 4, 1, 1}}, 96, 1, {1, 1}, {1, 1}, {0, 0}, {0, 0}, "SAME", true, 864.}, /* GFLOPS 0.000 x 1 = 0.000 */ {{1, 1}, {{1, 96, 1, 1}}, 4, 1, {1, 1}, {1, 1}, {0, 0}, {0, 0}, "SAME", true, 772.}, /* GFLOPS 0.000 x 1 = 0.000 */ {{1, 1}, {{1, 8, 1, 1}}, 32, 1, {1, 1}, {1, 1}, {0, 0}, {0, 0}, "SAME", true, 544.}, - /* GFLOPS 0.000 x 1 = 0.000 */ {{1, 1}, {{1, 32, 1, 1}}, 8, 1, {1, 1}, {1, 1}, {0, 0}, {0, 0}, "SAME", true, 520.} + /* GFLOPS 0.000 x 1 = 0.000 */ {{1, 1}, {{1, 32, 1, 1}}, 8, 1, {1, 1}, {1, 1}, {0, 0}, {0, 0}, "SAME", true, 520.}, + /* GFLOPS 0.472 x 1 = 0.472 */ {{5, 5}, {{1, 32, 96, 96}}, 32, 32, {1, 1}, {1, 1}, {2, 2}, {0, 0}, "", true, 472154112.} }; struct ConvParamID { diff --git a/modules/dnn/src/layers/cpu_kernels/conv_block.simd.hpp b/modules/dnn/src/layers/cpu_kernels/conv_block.simd.hpp index 27b0d4ba1f..3310a91b6b 100644 --- a/modules/dnn/src/layers/cpu_kernels/conv_block.simd.hpp +++ b/modules/dnn/src/layers/cpu_kernels/conv_block.simd.hpp @@ -452,13 +452,13 @@ void convBlockMR1_F32(int np, const float * a, const float * b, float *c, const if (init_c) { - c0 += vld1q_f32(c); - c1 += vld1q_f32(c + 4); - c2 += vld1q_f32(c + 8); - c3 += vld1q_f32(c + 12); - c4 += vld1q_f32(c + 16); - c5 += vld1q_f32(c + 20); - c6 += vld1q_f32(c + 24); + c0 = vaddq_f32(c0, vld1q_f32(c)); + c1 = vaddq_f32(c1, vld1q_f32(c + 4)); + c2 = vaddq_f32(c2, vld1q_f32(c + 8)); + c3 = vaddq_f32(c3, vld1q_f32(c + 12)); + c4 = vaddq_f32(c4, vld1q_f32(c + 16)); + c5 = vaddq_f32(c5, vld1q_f32(c + 20)); + c6 = vaddq_f32(c6, vld1q_f32(c + 24)); } if (ifMinMaxAct) diff --git a/modules/dnn/src/layers/cpu_kernels/convolution.cpp b/modules/dnn/src/layers/cpu_kernels/convolution.cpp index c76b3494e2..68777ff65e 100644 --- a/modules/dnn/src/layers/cpu_kernels/convolution.cpp +++ b/modules/dnn/src/layers/cpu_kernels/convolution.cpp @@ -1290,7 +1290,7 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr& co else Kg_nblocks = 1; - bool separateIm2col = fast_1x1 || stripes_per_plane == 1; + bool separateIm2col = (fast_1x1 || stripes_per_plane == 1) && conv->conv_type != CONV_TYPE_DEPTHWISE_REMAIN; int Kstripes = Kg_nblocks * stripes_per_plane; int nsubtasks = N * ngroups * Kstripes; diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 28cf737044..2a34b9400b 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -138,7 +138,7 @@ public: { const float* srcptr = src_->ptr(i) + stripeStart; float* dstptr = dst_->ptr(i) + stripeStart; - func_->apply(srcptr, dstptr, (int)(stripeEnd - stripeStart), planeSize, 0, outCn); + func_->apply(srcptr, dstptr, stripeStart, (int)(stripeEnd - stripeStart), planeSize, 0, outCn); } } }; @@ -268,7 +268,7 @@ public: void forwardSlice(const float* src, float* dst, int len, size_t planeSize, int cn0, int cn1) const CV_OVERRIDE { - func.apply(src, dst, len, planeSize, cn0, cn1); + func.apply(src, dst, -1, len, planeSize, cn0, cn1); } #ifdef HAVE_CUDA @@ -355,8 +355,9 @@ struct ReLUFunctor : public BaseFunctor backendId == DNN_BACKEND_CANN; } - void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { + CV_UNUSED(stripeStart); float s = slope; for( int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize ) { @@ -559,8 +560,9 @@ struct ReLU6Functor : public BaseFunctor backendId == DNN_BACKEND_CANN; } - void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { + CV_UNUSED(stripeStart); for( int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize ) { int i = 0; @@ -704,8 +706,9 @@ struct ReLU6Functor : public BaseFunctor template struct BaseDefaultFunctor : public BaseFunctor { - void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { + CV_UNUSED(stripeStart); for( int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize ) { for( int i = 0; i < len; i++ ) @@ -1100,7 +1103,14 @@ struct SigmoidFunctor : public BaseDefaultFunctor inline float calculate(float x) const { - return 1.f / (1.f + exp(-x)); + float y; + if (x >= 0) + y = 1.f / (1.f + exp(-x)); + else { + y = exp(x); + y = y / (1 + y); + } + return y; } #ifdef HAVE_CUDA @@ -2219,8 +2229,9 @@ struct PowerFunctor : public BaseFunctor shift = originShift; } - void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { + CV_UNUSED(stripeStart); float a = scale, b = shift, p = power; if( p == 1.f ) { @@ -2445,6 +2456,7 @@ struct ChannelsPReLUFunctor : public BaseFunctor Mat scale; #ifdef HAVE_OPENCL UMat scale_umat; + std::string oclKernelName = "ChannelsPReLUForward"; #endif explicit ChannelsPReLUFunctor(const Mat& scale_=Mat()) : scale(scale_) @@ -2463,8 +2475,9 @@ struct ChannelsPReLUFunctor : public BaseFunctor backendId == DNN_BACKEND_CANN; } - void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { + CV_UNUSED(stripeStart); CV_Assert(scale.isContinuous() && scale.type() == CV_32F); const float* scaleptr = scale.ptr(); @@ -2518,7 +2531,7 @@ struct ChannelsPReLUFunctor : public BaseFunctor UMat& src = inputs[i]; UMat& dst = outputs[i]; - ocl::Kernel kernel("PReLUForward", ocl::dnn::activations_oclsrc, buildopt); + ocl::Kernel kernel(oclKernelName.c_str(), ocl::dnn::activations_oclsrc, buildopt); kernel.set(0, (int)src.total()); kernel.set(1, (int)src.size[1]); kernel.set(2, (int)total(shape(src), 2)); @@ -2598,6 +2611,75 @@ struct ChannelsPReLUFunctor : public BaseFunctor int64 getFLOPSPerElement() const { return 1; } }; +struct PReLUFunctor : public ChannelsPReLUFunctor +{ + explicit PReLUFunctor(const Mat& scale_=Mat()) : ChannelsPReLUFunctor(scale_) + { +#ifdef HAVE_OPENCL + oclKernelName = "PReLUForward"; +#endif + } + + bool supportBackend(int backendId, int) + { + return backendId == DNN_BACKEND_OPENCV || + backendId == DNN_BACKEND_CANN || + backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + } + + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + CV_Assert(scale.isContinuous() && scale.type() == CV_32F); + + if (stripeStart < 0) + CV_Error(Error::StsNotImplemented, "PReLUFunctor requires stripe offset parameter"); + + const float* scaleptr = scale.ptr() + cn0 * planeSize + stripeStart; + for( int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize, scaleptr += planeSize ) + { + int i = 0; + #if CV_SIMD128 + v_float32x4 z = v_setzero_f32(); + for( ; i <= len - 16; i += 16 ) + { + v_float32x4 x0 = v_load(srcptr + i); + v_float32x4 x1 = v_load(srcptr + i + 4); + v_float32x4 x2 = v_load(srcptr + i + 8); + v_float32x4 x3 = v_load(srcptr + i + 12); + v_float32x4 s0 = v_load(scaleptr + i); + v_float32x4 s1 = v_load(scaleptr + i + 4); + v_float32x4 s2 = v_load(scaleptr + i + 8); + v_float32x4 s3 = v_load(scaleptr + i + 12); + x0 = v_select(x0 >= z, x0, x0*s0); + x1 = v_select(x1 >= z, x1, x1*s1); + x2 = v_select(x2 >= z, x2, x2*s2); + x3 = v_select(x3 >= z, x3, x3*s3); + v_store(dstptr + i, x0); + v_store(dstptr + i + 4, x1); + v_store(dstptr + i + 8, x2); + v_store(dstptr + i + 12, x3); + } + #endif + for( ; i < len; i++ ) + { + float x = srcptr[i]; + float s = scaleptr[i]; + dstptr[i] = x >= 0.f ? x : s*x; + } + } + } + +#ifdef HAVE_DNN_NGRAPH + std::shared_ptr initNgraphAPI(const std::shared_ptr& node) + { + auto shape = getShape(scale); + auto slope = std::make_shared(ngraph::element::f32, shape, scale.ptr()); + return std::make_shared(node, slope); + } +#endif // HAVE_DNN_NGRAPH +}; + struct SignFunctor : public BaseDefaultFunctor { typedef SignLayer Layer; @@ -3033,13 +3115,26 @@ Ptr ExpLayer::create(const LayerParams& params) Ptr ChannelsPReLULayer::create(const LayerParams& params) { CV_Assert(params.blobs.size() == 1); - if (params.blobs[0].total() == 1) + Mat scale = params.blobs[0]; + float slope = *scale.ptr(); + if (scale.total() == 1 || countNonZero(scale != slope) == 0) { LayerParams reluParams = params; - reluParams.set("negative_slope", *params.blobs[0].ptr()); + reluParams.set("negative_slope", slope); return ReLULayer::create(reluParams); } - Ptr l(new ElementWiseLayer(ChannelsPReLUFunctor(params.blobs[0]))); + + Ptr l; + // Check first two dimensions of scale (batch, channels) + MatShape scaleShape = shape(scale); + if (std::count_if(scaleShape.begin(), scaleShape.end(), [](int d){ return d != 1;}) > 1) + { + l = new ElementWiseLayer(PReLUFunctor(scale)); + } + else + { + l = new ElementWiseLayer(ChannelsPReLUFunctor(scale)); + } l->setParamsFrom(params); return l; diff --git a/modules/dnn/src/layers/max_unpooling_layer.cpp b/modules/dnn/src/layers/max_unpooling_layer.cpp index a44d25ce89..fd47c4c919 100644 --- a/modules/dnn/src/layers/max_unpooling_layer.cpp +++ b/modules/dnn/src/layers/max_unpooling_layer.cpp @@ -14,6 +14,7 @@ Implementation of Batch Normalization layer. #include "../op_cuda.hpp" #include "../op_halide.hpp" #include +#include #ifdef HAVE_CUDA #include "../cuda4dnn/primitives/max_unpooling.hpp" @@ -110,17 +111,12 @@ public: int index = idxptr[i_wh]; if (!(0 <= index && index < outPlaneTotal)) { - std::cerr - << "i_n=" << i_n << std::endl - << "i_c=" << i_c << std::endl - << "i_wh=" << i_wh << std::endl - << "index=" << index << std::endl - << "maxval=" << inptr[i_wh] << std::endl - << "outPlaneTotal=" << outPlaneTotal << std::endl - << "input.size=" << input.size << std::endl - << "indices.size=" << indices.size << std::endl - << "outBlob=" << outBlob.size << std::endl - ; + CV_LOG_ERROR(NULL, cv::format( + "i_n=%d\ni_c=%d\ni_wh=%d\nindex=%d\nmaxval=%lf\noutPlaneTotal=%d\n", + i_n, i_c, i_wh, index, inptr[i_wh], outPlaneTotal)); + CV_LOG_ERROR(NULL, "input.size=" << input.size); + CV_LOG_ERROR(NULL, "indices.size=" << indices.size); + CV_LOG_ERROR(NULL, "outBlob=" << outBlob.size); CV_Assert(0 <= index && index < outPlaneTotal); } outptr[index] = inptr[i_wh]; diff --git a/modules/dnn/src/net_cann.cpp b/modules/dnn/src/net_cann.cpp index a3eb52200f..103c7c8dd2 100644 --- a/modules/dnn/src/net_cann.cpp +++ b/modules/dnn/src/net_cann.cpp @@ -304,9 +304,9 @@ std::shared_ptr compileCannGraph(std::shared_ptr bool ok; if ((child=fork()) == 0) { - // initialize engine + // initialize engine Ascend310/Ascend310P3/Ascend910B/Ascend310B std::map options = { - {ge::AscendString(ge::ir_option::SOC_VERSION), ge::AscendString("Ascend310")}, + {ge::AscendString(ge::ir_option::SOC_VERSION), ge::AscendString(aclrtGetSocName())}, }; ACL_CHECK_GRAPH_RET(ge::aclgrphBuildInitialize(options)); diff --git a/modules/dnn/src/net_impl.cpp b/modules/dnn/src/net_impl.cpp index c8341e4c6f..16ae6d7bfb 100644 --- a/modules/dnn/src/net_impl.cpp +++ b/modules/dnn/src/net_impl.cpp @@ -662,14 +662,14 @@ void Net::Impl::forwardLayer(LayerData& ld) m = u.getMat(ACCESS_READ); if (!checkRange(m)) { - std::cerr << "WARNING: NaN detected in layer output: id=" << ld.id << " name=" << layer->name << std::endl; - std::cerr << "output id=" << i << " output shape=" << shape(m) << std::endl; + CV_LOG_WARNING(NULL, "NaN detected in layer output: id=" << ld.id << " name=" << layer->name + << " output id=" << i << " output shape=" << shape(m)); fail = true; } else if (!checkRange(m, true, NULL, -1e6, 1e6)) { - std::cerr << "WARNING: Inf detected in layer output: id=" << ld.id << " name=" << layer->name << std::endl; - std::cerr << "output id=" << i << " output shape=" << shape(m) << std::endl; + CV_LOG_WARNING(NULL, "Inf detected in layer output: id=" << ld.id << " name=" << layer->name + << " output id=" << i << " output shape=" << shape(m)); fail = true; } } @@ -738,14 +738,14 @@ void Net::Impl::forwardLayer(LayerData& ld) const Mat& m = ld.outputBlobs[i]; if (!checkRange(m)) { - std::cerr << "WARNING: NaN detected in layer output: id=" << ld.id << " name=" << layer->name << std::endl; - std::cerr << "output id=" << i << " output shape=" << shape(m) << std::endl; + CV_LOG_WARNING(NULL, "NaN detected in layer output: " + << cv::format("id=%d name=%s output id=%zu output shape=", ld.id, layer->name.c_str(), i) << shape(m)); fail = true; } else if (!checkRange(m, true, NULL, -1e6, 1e6)) { - std::cerr << "WARNING: Inf detected in layer output: id=" << ld.id << " name=" << layer->name << std::endl; - std::cerr << "output id=" << i << " output shape=" << shape(m) << std::endl; + CV_LOG_WARNING(NULL, "Inf detected in layer output: " + << cv::format("id=%d name=%s output id=%zu output shape=", ld.id, layer->name.c_str(), i) << shape(m)); fail = true; } } diff --git a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp index 120cd936b7..8f9be6e961 100644 --- a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp +++ b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp @@ -1125,7 +1125,7 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto) else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT16) { // FIXME, for now, we only load FP16 Tensor as FP32 Mat, full support for FP16 is required in the future. - CV_LOG_ONCE_WARNING(NULL, "DNN: load FP16 model as FP32 model, and it takes twice the FP16 RAM requirement."); + CV_LOG_ONCE_INFO(NULL, "DNN: load FP16 model as FP32 model, and it takes twice the FP16 RAM requirement."); // ONNX saves float 16 data in two format: int32 and raw_data. // Link: https://github.com/onnx/onnx/issues/4460#issuecomment-1224373746 diff --git a/modules/dnn/src/op_cann.hpp b/modules/dnn/src/op_cann.hpp index c60c311b7f..1d15eab6a3 100644 --- a/modules/dnn/src/op_cann.hpp +++ b/modules/dnn/src/op_cann.hpp @@ -10,7 +10,11 @@ #include "graph/graph.h" // ge::Graph; ge::Operator from operator.h #include "graph/ge_error_codes.h" // GRAPH_SUCCESS, ... -#include "op_proto/built-in/inc/all_ops.h" // ge::Conv2D, ... +#ifdef CANN_VERSION_BELOW_6_3_ALPHA002 + #include "op_proto/built-in/inc/all_ops.h" // ge::Conv2D, ... +#else + #include "built-in/op_proto/inc/all_ops.h" // ge::Conv2D, ... +#endif #include "graph/tensor.h" // ge::Shape, ge::Tensor, ge::TensorDesc #include "graph/types.h" // DT_FLOAT, ... ; FORMAT_NCHW, ... diff --git a/modules/dnn/src/opencl/activations.cl b/modules/dnn/src/opencl/activations.cl index 317d2c1e62..96b56725fb 100644 --- a/modules/dnn/src/opencl/activations.cl +++ b/modules/dnn/src/opencl/activations.cl @@ -73,14 +73,23 @@ __kernel void ReLU6Forward(const int count, __global const T* in, __global T* ou } } +__kernel void ChannelsPReLUForward(const int count, const int channels, const int plane_size, + __global const T* in, __global T* out, + __global const KERNEL_ARG_DTYPE* slope_data) +{ + int index = get_global_id(0); + int c = (index / plane_size) % channels; + if(index < count) + out[index] = in[index] > 0 ? in[index] : in[index] * slope_data[c]; +} + __kernel void PReLUForward(const int count, const int channels, const int plane_size, __global const T* in, __global T* out, __global const KERNEL_ARG_DTYPE* slope_data) { int index = get_global_id(0); - int c = (index / plane_size) % channels; if(index < count) - out[index] = in[index] > 0 ? in[index] : in[index] * slope_data[c]; + out[index] = in[index] > 0 ? in[index] : in[index] * slope_data[index]; } __kernel void TanHForward(const int count, __global T* in, __global T* out) { @@ -352,4 +361,4 @@ __kernel void ReciprocalForward(const int n, __global T* in, __global T* out) int index = get_global_id(0); if(index < n) out[index] = 1.0f/in[index]; -} \ No newline at end of file +} diff --git a/modules/dnn/src/tensorflow/tf_importer.cpp b/modules/dnn/src/tensorflow/tf_importer.cpp index 8e9970528a..a23fac187b 100644 --- a/modules/dnn/src/tensorflow/tf_importer.cpp +++ b/modules/dnn/src/tensorflow/tf_importer.cpp @@ -589,6 +589,7 @@ private: void parsePack (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); void parseClipByValue (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); void parseLeakyRelu (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); + void parsePReLU (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); void parseActivation (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); void parseExpandDims (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); void parseSquare (tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams); @@ -668,6 +669,7 @@ TFImporter::DispatchMap TFImporter::buildDispatchMap() dispatch["Pack"] = &TFImporter::parsePack; dispatch["ClipByValue"] = &TFImporter::parseClipByValue; dispatch["LeakyRelu"] = &TFImporter::parseLeakyRelu; + dispatch["PReLU"] = &TFImporter::parsePReLU; dispatch["Abs"] = dispatch["Tanh"] = dispatch["Sigmoid"] = dispatch["Relu"] = dispatch["Elu"] = dispatch["Exp"] = dispatch["Identity"] = dispatch["Relu6"] = &TFImporter::parseActivation; dispatch["ExpandDims"] = &TFImporter::parseExpandDims; @@ -2622,6 +2624,27 @@ void TFImporter::parseLeakyRelu(tensorflow::GraphDef& net, const tensorflow::Nod connectToAllBlobs(layer_id, dstNet, parsePin(layer.input(0)), id, num_inputs); } +void TFImporter::parsePReLU(tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams) +{ + const std::string& name = layer.name(); + + Mat scales; + blobFromTensor(getConstBlob(layer, value_id, 1), scales); + + layerParams.blobs.resize(1); + + if (scales.dims == 3) { + // Considering scales from Keras wih HWC layout; + transposeND(scales, {2, 0, 1}, layerParams.blobs[0]); + } else { + layerParams.blobs[0] = scales; + } + + int id = dstNet.addLayer(name, "PReLU", layerParams); + layer_id[name] = id; + connect(layer_id, dstNet, parsePin(layer.input(0)), id, 0); +} + // "Abs" "Tanh" "Sigmoid" "Relu" "Elu" "Exp" "Identity" "Relu6" void TFImporter::parseActivation(tensorflow::GraphDef& net, const tensorflow::NodeDef& layer, LayerParams& layerParams) { diff --git a/modules/dnn/src/tflite/tflite_importer.cpp b/modules/dnn/src/tflite/tflite_importer.cpp index 4a186eaee0..7feded69ce 100644 --- a/modules/dnn/src/tflite/tflite_importer.cpp +++ b/modules/dnn/src/tflite/tflite_importer.cpp @@ -59,6 +59,7 @@ private: void parseUnpooling(const Operator& op, const std::string& opcode, LayerParams& layerParams); void parseReshape(const Operator& op, const std::string& opcode, LayerParams& layerParams); void parseConcat(const Operator& op, const std::string& opcode, LayerParams& layerParams); + void parsePack(const Operator& op, const std::string& opcode, LayerParams& layerParams); void parseResize(const Operator& op, const std::string& opcode, LayerParams& layerParams); void parseDeconvolution(const Operator& op, const std::string& opcode, LayerParams& layerParams); void parseQuantize(const Operator& op, const std::string& opcode, LayerParams& layerParams); @@ -70,6 +71,8 @@ private: void parseActivation(const Operator& op, const std::string& opcode, LayerParams& layerParams, bool isFused); void addLayer(LayerParams& layerParams, const Operator& op); int addPermuteLayer(const std::vector& order, const std::string& permName, const std::pair& inpId, int dtype); + int addReshapeLayer(const std::vector& shape, int axis, int num_axes, + const std::string& name, const std::pair& inpId, int dtype); inline bool isInt8(const Operator& op); inline void getQuantParams(const Operator& op, float& inpScale, int& inpZero, float& outScale, int& outZero); }; @@ -267,6 +270,7 @@ TFLiteImporter::DispatchMap TFLiteImporter::buildDispatchMap() dispatch["PAD"] = &TFLiteImporter::parsePadding; dispatch["RESHAPE"] = &TFLiteImporter::parseReshape; dispatch["CONCATENATION"] = &TFLiteImporter::parseConcat; + dispatch["PACK"] = &TFLiteImporter::parsePack; dispatch["RESIZE_BILINEAR"] = dispatch["RESIZE_NEAREST_NEIGHBOR"] = &TFLiteImporter::parseResize; dispatch["Convolution2DTransposeBias"] = &TFLiteImporter::parseDeconvolution; dispatch["QUANTIZE"] = &TFLiteImporter::parseQuantize; @@ -596,16 +600,6 @@ void TFLiteImporter::parseUnpooling(const Operator& op, const std::string& opcod void TFLiteImporter::parseReshape(const Operator& op, const std::string& opcode, LayerParams& layerParams) { DataLayout inpLayout = layouts[op.inputs()->Get(0)]; - if (inpLayout == DNN_LAYOUT_NHWC) { - // Permute to NCHW - std::vector order = {0, 2, 3, 1}; - const std::string name = layerParams.name + "/permute"; - auto inpId = layerIds[op.inputs()->Get(0)]; - int permId = addPermuteLayer(order, name, inpId, isInt8(op) ? CV_8S : CV_32F); // NCHW -> NHWC - layerIds[op.inputs()->Get(0)] = std::make_pair(permId, 0); - layouts[op.outputs()->Get(0)] = DNN_LAYOUT_NCHW; - } - layerParams.type = "Reshape"; std::vector shape; if (op.inputs()->size() > 1) { @@ -615,6 +609,22 @@ void TFLiteImporter::parseReshape(const Operator& op, const std::string& opcode, CV_Assert(options); shape.assign(options->new_shape()->begin(), options->new_shape()->end()); } + + if (inpLayout == DNN_LAYOUT_NHWC) { + if (shape.size() == 4) { + // Keep data but change a shape to OpenCV's NCHW order + std::swap(shape[2], shape[3]); + std::swap(shape[1], shape[2]); + } else { + // Permute to NCHW entire data and reshape to given a shape + std::vector order = {0, 2, 3, 1}; + const std::string name = layerParams.name + "/permute"; + auto inpId = layerIds[op.inputs()->Get(0)]; + int permId = addPermuteLayer(order, name, inpId, isInt8(op) ? CV_8S : CV_32F); // NCHW -> NHWC + layerIds[op.inputs()->Get(0)] = std::make_pair(permId, 0); + layouts[op.outputs()->Get(0)] = DNN_LAYOUT_NCHW; + } + } layerParams.set("dim", DictValue::arrayInt(shape.data(), shape.size())); addLayer(layerParams, op); } @@ -636,6 +646,47 @@ void TFLiteImporter::parseConcat(const Operator& op, const std::string& opcode, parseFusedActivation(op, options->fused_activation_function()); } +void TFLiteImporter::parsePack(const Operator& op, const std::string& opcode, LayerParams& layerParams) { + auto options = reinterpret_cast(op.builtin_options()); + int axis = options->axis(); + + DataLayout inpLayout = layouts[op.inputs()->Get(0)]; + if (inpLayout == DNN_LAYOUT_NHWC) { + // OpenCV works in NCHW data layout. So change the axis correspondingly. + axis = normalize_axis(axis, 5); // 5 because Pack adds a new axis so -1 would mean 4 + static const int remap[] = {0, 1, 3, 4, 2}; + axis = remap[axis]; + } + + // Replace Pack layer to Reshape + Concat + // Use a set because there are models which replicate single layer data by Pack. + std::set op_inputs(op.inputs()->begin(), op.inputs()->end()); + std::map > originLayerIds; + for (int inp : op_inputs) { + auto inpId = layerIds[inp]; + int dims = modelTensors->Get(inp)->shape()->size(); + + std::vector shape{1, -1}; + if (axis == dims) { + std::swap(shape[0], shape[1]); + } + const auto name = modelTensors->Get(inp)->name()->str() + "/reshape"; + int reshapeId = addReshapeLayer(shape, axis == dims ? dims - 1 : axis, 1, + name, inpId, isInt8(op) ? CV_8S : CV_32F); + + originLayerIds[inp] = layerIds[inp]; + layerIds[inp] = std::make_pair(reshapeId, 0); + } + layerParams.type = "Concat"; + layerParams.set("axis", axis); + addLayer(layerParams, op); + + // Restore origin layer inputs + for (const auto& ids : originLayerIds) { + layerIds[ids.first] = ids.second; + } +} + void TFLiteImporter::parseResize(const Operator& op, const std::string& opcode, LayerParams& layerParams) { layerParams.type = "Resize"; @@ -666,6 +717,18 @@ int TFLiteImporter::addPermuteLayer(const std::vector& order, const std::st return permId; } +int TFLiteImporter::addReshapeLayer(const std::vector& shape, int axis, int num_axes, + const std::string& name, const std::pair& inpId, int dtype) +{ + LayerParams lp; + lp.set("axis", axis); + lp.set("dim", DictValue::arrayInt(shape.data(), shape.size())); + lp.set("num_axes", num_axes); + int id = dstNet.addLayer(name, "Reshape", dtype, lp); + dstNet.connect(inpId.first, inpId.second, id, 0); + return id; +} + void TFLiteImporter::parseDeconvolution(const Operator& op, const std::string& opcode, LayerParams& layerParams) { layerParams.type = "Deconvolution"; @@ -771,6 +834,8 @@ void TFLiteImporter::parseDetectionPostProcess(const Operator& op, const std::st parameters[keys[i]] = *reinterpret_cast(data + offset + i * 4); } + parameters["num_classes"] = modelTensors->Get(op.inputs()->Get(1))->shape()->Get(2); + layerParams.type = "DetectionOutput"; layerParams.set("num_classes", parameters["num_classes"]); layerParams.set("share_location", true); @@ -780,7 +845,6 @@ void TFLiteImporter::parseDetectionPostProcess(const Operator& op, const std::st layerParams.set("top_k", parameters["max_detections"]); layerParams.set("keep_top_k", parameters["max_detections"]); layerParams.set("code_type", "CENTER_SIZE"); - layerParams.set("variance_encoded_in_target", true); layerParams.set("loc_pred_transposed", true); // Replace third input from tensor to Const layer with the priors @@ -796,10 +860,27 @@ void TFLiteImporter::parseDetectionPostProcess(const Operator& op, const std::st priors.col(2) = priors.col(0) + priors.col(3); priors.col(3) = priors.col(1) + tmp; + float x_scale = *(float*)¶meters["x_scale"]; + float y_scale = *(float*)¶meters["y_scale"]; + float w_scale = *(float*)¶meters["w_scale"]; + float h_scale = *(float*)¶meters["h_scale"]; + if (x_scale != 1.0f || y_scale != 1.0f || w_scale != 1.0f || h_scale != 1.0f) { + int numPriors = priors.rows; + priors.resize(numPriors * 2); + Mat_ scales({1, 4}, {1.f / x_scale, 1.f / y_scale, + 1.f / w_scale, 1.f / h_scale}); + repeat(scales, numPriors, 1, priors.rowRange(numPriors, priors.rows)); + priors = priors.reshape(1, {1, 2, (int)priors.total() / 2}); + layerParams.set("variance_encoded_in_target", false); + } else { + priors = priors.reshape(1, {1, 1, (int)priors.total()}); + layerParams.set("variance_encoded_in_target", true); + } + LayerParams priorsLP; priorsLP.name = layerParams.name + "/priors"; priorsLP.type = "Const"; - priorsLP.blobs.resize(1, priors.reshape(1, {1, 1, (int)priors.total()})); + priorsLP.blobs.resize(1, priors); int priorsId = dstNet.addLayer(priorsLP.name, priorsLP.type, priorsLP); layerIds[op.inputs()->Get(2)] = std::make_pair(priorsId, 0); diff --git a/modules/dnn/test/test_tf_importer.cpp b/modules/dnn/test/test_tf_importer.cpp index b795076f55..e2dfbc706e 100644 --- a/modules/dnn/test/test_tf_importer.cpp +++ b/modules/dnn/test/test_tf_importer.cpp @@ -1675,6 +1675,7 @@ TEST_P(Test_TensorFlow_layers, clip_by_value) TEST_P(Test_TensorFlow_layers, tf2_prelu) { + double l1 = 0, lInf = 0; if (backend == DNN_BACKEND_CUDA) applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA); // not supported; only across channels is supported #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) @@ -1686,6 +1687,11 @@ TEST_P(Test_TensorFlow_layers, tf2_prelu) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION ); +#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_GE(2023000000) + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL) { + l1 = 1e-4; + lInf = 1e-3; + } #elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { @@ -1705,7 +1711,7 @@ TEST_P(Test_TensorFlow_layers, tf2_prelu) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); #endif - runTensorFlowNet("tf2_prelu"); + runTensorFlowNet("tf2_prelu", false, l1, lInf); } TEST_P(Test_TensorFlow_layers, tf2_permute_nhwc_ncwh) diff --git a/modules/dnn/test/test_tflite_importer.cpp b/modules/dnn/test/test_tflite_importer.cpp index 5a1742ed97..c5bee0c086 100644 --- a/modules/dnn/test/test_tflite_importer.cpp +++ b/modules/dnn/test/test_tflite_importer.cpp @@ -31,9 +31,8 @@ void testInputShapes(const Net& net, const std::vector& inps) { } } -void testModel(const std::string& modelName, const Mat& input, double l1 = 1e-5, double lInf = 1e-4) +void testModel(Net& net, const std::string& modelName, const Mat& input, double l1 = 1e-5, double lInf = 1e-4) { - Net net = readNet(findDataFile("dnn/tflite/" + modelName + ".tflite", false)); testInputShapes(net, {input}); net.setInput(input); @@ -49,6 +48,12 @@ void testModel(const std::string& modelName, const Mat& input, double l1 = 1e-5, } } +void testModel(const std::string& modelName, const Mat& input, double l1 = 1e-5, double lInf = 1e-4) +{ + Net net = readNet(findDataFile("dnn/tflite/" + modelName + ".tflite", false)); + testModel(net, modelName, input, l1, lInf); +} + void testModel(const std::string& modelName, const Size& inpSize, double l1 = 1e-5, double lInf = 1e-4) { Mat input = imread(findDataFile("cv/shared/lena.png")); @@ -56,6 +61,13 @@ void testModel(const std::string& modelName, const Size& inpSize, double l1 = 1e testModel(modelName, input, l1, lInf); } +void testLayer(const std::string& modelName, double l1 = 1e-5, double lInf = 1e-4) +{ + Mat inp = blobFromNPY(findDataFile("dnn/tflite/" + modelName + "_inp.npy")); + Net net = readNet(findDataFile("dnn/tflite/" + modelName + ".tflite")); + testModel(net, modelName, inp, l1, lInf); +} + // https://google.github.io/mediapipe/solutions/face_mesh TEST(Test_TFLite, face_landmark) { @@ -146,6 +158,10 @@ TEST(Test_TFLite, EfficientDet_int8) { normAssertDetections(ref, out, "", 0.5, 0.05, 0.1); } +TEST(Test_TFLite, replicate_by_pack) { + testLayer("replicate_by_pack"); +} + }} // namespace #endif // OPENCV_TEST_DNN_TFLITE diff --git a/modules/dnn/test/test_torch_importer.cpp b/modules/dnn/test/test_torch_importer.cpp index 8510ec4e64..ae39d5d22e 100644 --- a/modules/dnn/test/test_torch_importer.cpp +++ b/modules/dnn/test/test_torch_importer.cpp @@ -566,14 +566,14 @@ TEST_P(Test_Torch_nets, FastNeuralStyle_accuracy) } else if(target == DNN_TARGET_CUDA_FP16) { - normAssert(out, refBlob, "", 0.6, 25); + normAssert(out, refBlob, "", 0.6, 26); } else if (target == DNN_TARGET_CPU_FP16) { normAssert(out, refBlob, "", 0.62, 25); } else - normAssert(out, refBlob, "", 0.5, 1.1); + normAssert(out, refBlob, "", 0.5, 1.11); } } diff --git a/modules/flann/include/opencv2/flann/any.h b/modules/flann/include/opencv2/flann/any.h index 4906fec081..2228bd1cfc 100644 --- a/modules/flann/include/opencv2/flann/any.h +++ b/modules/flann/include/opencv2/flann/any.h @@ -19,16 +19,39 @@ #include #include +#include "opencv2/core/cvdef.h" +#include "opencv2/core/utility.hpp" + namespace cvflann { namespace anyimpl { -struct bad_any_cast +struct bad_any_cast : public std::exception { + bad_any_cast() = default; + + bad_any_cast(const char* src, const char* dst) + : message_(cv::format("cvflann::bad_any_cast(from %s to %s)", src, dst)) {} + + + const char* what() const noexcept override + { + return message_.c_str(); + } + +private: + std::string message_{"cvflann::bad_any_cast"}; }; +#ifndef CV_THROW_IF_TYPE_MISMATCH +#define CV_THROW_IF_TYPE_MISMATCH(src_type_info, dst_type_info) \ + if ((src_type_info) != (dst_type_info)) \ + throw cvflann::anyimpl::bad_any_cast((src_type_info).name(), \ + (dst_type_info).name()) +#endif + struct empty_any { }; @@ -271,7 +294,7 @@ public: template T& cast() { - if (policy->type() != typeid(T)) throw anyimpl::bad_any_cast(); + CV_THROW_IF_TYPE_MISMATCH(policy->type(), typeid(T)); T* r = reinterpret_cast(policy->get_value(&object)); return *r; } @@ -280,7 +303,7 @@ public: template const T& cast() const { - if (policy->type() != typeid(T)) throw anyimpl::bad_any_cast(); + CV_THROW_IF_TYPE_MISMATCH(policy->type(), typeid(T)); const T* r = reinterpret_cast(policy->get_value(&object)); return *r; } diff --git a/modules/flann/include/opencv2/flann/general.h b/modules/flann/include/opencv2/flann/general.h index 29fa8be121..e65cba2f8a 100644 --- a/modules/flann/include/opencv2/flann/general.h +++ b/modules/flann/include/opencv2/flann/general.h @@ -31,6 +31,8 @@ #ifndef OPENCV_FLANN_GENERAL_H_ #define OPENCV_FLANN_GENERAL_H_ +#include "opencv2/core/version.hpp" + #if CV_VERSION_MAJOR <= 4 //! @cond IGNORED diff --git a/modules/flann/include/opencv2/flann/matrix.h b/modules/flann/include/opencv2/flann/matrix.h index fb871bd73c..bfbf91ef5c 100644 --- a/modules/flann/include/opencv2/flann/matrix.h +++ b/modules/flann/include/opencv2/flann/matrix.h @@ -35,6 +35,9 @@ #include +#include "opencv2/core/cvdef.h" +#include "opencv2/flann/defines.h" + namespace cvflann { diff --git a/modules/flann/include/opencv2/flann/params.h b/modules/flann/include/opencv2/flann/params.h index c9093cde8c..1a8e127035 100644 --- a/modules/flann/include/opencv2/flann/params.h +++ b/modules/flann/include/opencv2/flann/params.h @@ -72,11 +72,16 @@ struct SearchParams : public IndexParams template -T get_param(const IndexParams& params, cv::String name, const T& default_value) +T get_param(const IndexParams& params, const cv::String& name, const T& default_value) { IndexParams::const_iterator it = params.find(name); if (it != params.end()) { - return it->second.cast(); + try { + return it->second.cast(); + } catch (const std::exception& e) { + CV_Error_(cv::Error::StsBadArg, + ("FLANN '%s' param type mismatch: %s", name.c_str(), e.what())); + } } else { return default_value; @@ -84,11 +89,16 @@ T get_param(const IndexParams& params, cv::String name, const T& default_value) } template -T get_param(const IndexParams& params, cv::String name) +T get_param(const IndexParams& params, const cv::String& name) { IndexParams::const_iterator it = params.find(name); if (it != params.end()) { - return it->second.cast(); + try { + return it->second.cast(); + } catch (const std::exception& e) { + CV_Error_(cv::Error::StsBadArg, + ("FLANN '%s' param type mismatch: %s", name.c_str(), e.what())); + } } else { FLANN_THROW(cv::Error::StsBadArg, cv::String("Missing parameter '")+name+cv::String("' in the parameters given")); diff --git a/modules/flann/include/opencv2/flann/result_set.h b/modules/flann/include/opencv2/flann/result_set.h index 47ad231105..c5d31e8ade 100644 --- a/modules/flann/include/opencv2/flann/result_set.h +++ b/modules/flann/include/opencv2/flann/result_set.h @@ -40,6 +40,9 @@ #include #include +#include "opencv2/core/base.hpp" +#include "opencv2/core/cvdef.h" + namespace cvflann { diff --git a/modules/flann/misc/python/pyopencv_flann.hpp b/modules/flann/misc/python/pyopencv_flann.hpp index 3d97edbb59..086ca5f09f 100644 --- a/modules/flann/misc/python/pyopencv_flann.hpp +++ b/modules/flann/misc/python/pyopencv_flann.hpp @@ -17,57 +17,89 @@ PyObject* pyopencv_from(const cvflann_flann_distance_t& value) template<> bool pyopencv_to(PyObject *o, cv::flann::IndexParams& p, const ArgInfo& info) { - CV_UNUSED(info); - bool ok = true; - PyObject* key = NULL; - PyObject* item = NULL; - Py_ssize_t pos = 0; - if (!o || o == Py_None) + { return true; + } + + if(!PyDict_Check(o)) + { + failmsg("Argument '%s' is not a dictionary", info.name); + return false; + } + + PyObject* key_obj = NULL; + PyObject* value_obj = NULL; + Py_ssize_t key_pos = 0; - if(PyDict_Check(o)) { - while(PyDict_Next(o, &pos, &key, &item)) + while(PyDict_Next(o, &key_pos, &key_obj, &value_obj)) + { + // get key + std::string key; + if (!getUnicodeString(key_obj, key)) { - // get key - std::string k; - if (!getUnicodeString(key, k)) + failmsg("Key at pos %lld is not a string", static_cast(key_pos)); + return false; + } + // key_arg_info.name is bound to key lifetime + const ArgInfo key_arg_info(key.c_str(), false); + + // get value + if (isBool(value_obj)) + { + npy_bool npy_value = NPY_FALSE; + if (PyArray_BoolConverter(value_obj, &npy_value) >= 0) { - ok = false; - break; + p.setBool(key, npy_value == NPY_TRUE); + continue; } - // get value - if( !!PyBool_Check(item) ) + PyErr_Clear(); + } + + int int_value = 0; + if (pyopencv_to(value_obj, int_value, key_arg_info)) + { + if (key == "algorithm") { - p.setBool(k, item == Py_True); + p.setAlgorithm(int_value); } - else if( PyInt_Check(item) ) + else { - int value = (int)PyInt_AsLong(item); - if( strcmp(k.c_str(), "algorithm") == 0 ) - p.setAlgorithm(value); - else - p.setInt(k, value); + p.setInt(key, int_value); } - else if( PyFloat_Check(item) ) + continue; + } + PyErr_Clear(); + + double flt_value = 0.0; + if (pyopencv_to(value_obj, flt_value, key_arg_info)) + { + if (key == "eps") { - double value = PyFloat_AsDouble(item); - p.setDouble(k, value); + p.setFloat(key, static_cast(flt_value)); } else { - std::string val_str; - if (!getUnicodeString(item, val_str)) - { - ok = false; - break; - } - p.setString(k, val_str); + p.setDouble(key, flt_value); } + continue; } - } + PyErr_Clear(); - return ok && !PyErr_Occurred(); + std::string str_value; + if (getUnicodeString(value_obj, str_value)) + { + p.setString(key, str_value); + continue; + } + PyErr_Clear(); + // All conversions are failed + failmsg("Failed to parse IndexParam with key '%s'. " + "Supported types: [bool, int, float, str]", key.c_str()); + return false; + + } + return true; } template<> diff --git a/modules/flann/misc/python/test/test_flann_based_matcher.py b/modules/flann/misc/python/test/test_flann_based_matcher.py new file mode 100644 index 0000000000..cf8c2ededd --- /dev/null +++ b/modules/flann/misc/python/test/test_flann_based_matcher.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# Python 2/3 compatibility +from __future__ import print_function + +import cv2 +import numpy as np + +from tests_common import NewOpenCVTests + + +class FlannBasedMatcher(NewOpenCVTests): + def test_all_parameters_can_be_passed(self): + img1 = self.get_sample("samples/data/right01.jpg") + img2 = self.get_sample("samples/data/right02.jpg") + + orb = cv2.ORB.create() + + kp1, des1 = orb.detectAndCompute(img1, None) + kp2, des2 = orb.detectAndCompute(img2, None) + FLANN_INDEX_KDTREE = 1 + index_param = dict(algorithm=FLANN_INDEX_KDTREE, trees=4) + search_param = dict(checks=32, sorted=True, eps=0.5, + explore_all_trees=False) + matcher = cv2.FlannBasedMatcher(index_param, search_param) + matches = matcher.knnMatch(np.float32(des1), np.float32(des2), k=2) + self.assertGreater(len(matches), 0) + + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index 2433c41d95..9ecbb6d514 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -162,6 +162,7 @@ set(gapi_srcs # ONNX backend src/backends/onnx/gonnxbackend.cpp + src/backends/onnx/dml_ep.cpp # Render backend src/backends/render/grenderocv.cpp @@ -254,6 +255,7 @@ ocv_target_link_libraries(${the_module} PRIVATE ade) if(TARGET ocv.3rdparty.openvino AND OPENCV_GAPI_WITH_OPENVINO) ocv_target_link_libraries(${the_module} PRIVATE ocv.3rdparty.openvino) + ocv_install_used_external_targets(ocv.3rdparty.openvino) endif() if(HAVE_TBB) @@ -365,6 +367,9 @@ endif() if(HAVE_ONNX) ocv_target_link_libraries(${the_module} PRIVATE ${ONNX_LIBRARY}) ocv_target_compile_definitions(${the_module} PRIVATE HAVE_ONNX=1) + if(HAVE_ONNX_DML) + ocv_target_compile_definitions(${the_module} PRIVATE HAVE_ONNX_DML=1) + endif() if(TARGET opencv_test_gapi) ocv_target_compile_definitions(opencv_test_gapi PRIVATE HAVE_ONNX=1) ocv_target_link_libraries(opencv_test_gapi PRIVATE ${ONNX_LIBRARY}) diff --git a/modules/gapi/include/opencv2/gapi/gkernel.hpp b/modules/gapi/include/opencv2/gapi/gkernel.hpp index 50d2efdd55..1b910adc82 100644 --- a/modules/gapi/include/opencv2/gapi/gkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/gkernel.hpp @@ -51,6 +51,7 @@ struct GAPI_EXPORTS GKernel GShapes outShapes; // types (shapes) kernel's outputs GKinds inKinds; // kinds of kernel's inputs (fixme: below) GCtors outCtors; // captured constructors for template output types + GKinds outKinds; // kinds of kernel's outputs (fixme: below) }; // TODO: It's questionable if inKinds should really be here. Instead, // this information could come from meta. @@ -227,7 +228,8 @@ public: , &K::getOutMeta , {detail::GTypeTraits::shape...} , {detail::GTypeTraits::op_kind...} - , {detail::GObtainCtor::get()...}}); + , {detail::GObtainCtor::get()...} + , {detail::GTypeTraits::op_kind...}}); call.pass(args...); // TODO: std::forward() here? return yield(call, typename detail::MkSeq::type()); } @@ -251,7 +253,8 @@ public: , &K::getOutMeta , {detail::GTypeTraits::shape} , {detail::GTypeTraits::op_kind...} - , {detail::GObtainCtor::get()}}); + , {detail::GObtainCtor::get()} + , {detail::GTypeTraits::op_kind}}); call.pass(args...); return detail::Yield::yield(call, 0); } diff --git a/modules/gapi/include/opencv2/gapi/infer.hpp b/modules/gapi/include/opencv2/gapi/infer.hpp index c1f9501873..abbd32ba20 100644 --- a/modules/gapi/include/opencv2/gapi/infer.hpp +++ b/modules/gapi/include/opencv2/gapi/infer.hpp @@ -101,8 +101,10 @@ public: if (it == m_priv->blobs.end()) { // FIXME: Avoid modifying GKernel auto shape = cv::detail::GTypeTraits::shape; + auto kind = cv::detail::GTypeTraits::op_kind; m_priv->call->kernel().outShapes.push_back(shape); m_priv->call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor::get()); + m_priv->call->kernel().outKinds.emplace_back(kind); auto out_idx = static_cast(m_priv->blobs.size()); it = m_priv->blobs.emplace(name, cv::detail::Yield::yield(*(m_priv->call), out_idx)).first; @@ -175,6 +177,7 @@ std::shared_ptr makeCall(const std::string &tag, {}, // outShape will be filled later std::move(kinds), {}, // outCtors will be filled later + {}, // outKinds will be filled later }); call->setArgs(std::move(args)); diff --git a/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp b/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp index af9f3c6f6f..4ba829df09 100644 --- a/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp @@ -33,6 +33,15 @@ public: GAPI_WRAP PyParams& cfgNormalize(const std::string &layer_name, bool flag); + GAPI_WRAP + PyParams& cfgAddExecutionProvider(ep::OpenVINO ep); + + GAPI_WRAP + PyParams& cfgAddExecutionProvider(ep::DirectML ep); + + GAPI_WRAP + PyParams& cfgDisableMemPattern(); + GBackend backend() const; std::string tag() const; cv::util::any params() const; diff --git a/modules/gapi/include/opencv2/gapi/infer/onnx.hpp b/modules/gapi/include/opencv2/gapi/infer/onnx.hpp index dc9a51e541..64b855acd7 100644 --- a/modules/gapi/include/opencv2/gapi/infer/onnx.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/onnx.hpp @@ -27,6 +27,126 @@ namespace gapi { */ namespace onnx { +/** + * @brief This namespace contains Execution Providers structures for G-API ONNX Runtime backend. + */ +namespace ep { + +/** + * @brief This structure provides functions + * that fill inference options for ONNX OpenVINO Execution Provider. + * Please follow https://onnxruntime.ai/docs/execution-providers/OpenVINO-ExecutionProvider.html#summary-of-options + */ +struct GAPI_EXPORTS_W_SIMPLE OpenVINO { + // NB: Used from python. + /// @private -- Exclude this constructor from OpenCV documentation + GAPI_WRAP + OpenVINO() = default; + + /** @brief Class constructor. + + Constructs OpenVINO parameters based on device type information. + + @param dev_type Target device type to use. ("CPU_FP32", "GPU_FP16", etc) + */ + GAPI_WRAP + explicit OpenVINO(const std::string &dev_type) + : device_type(dev_type) { + } + + /** @brief Specifies OpenVINO Execution Provider cache dir. + + This function is used to explicitly specify the path to save and load + the blobs enabling model caching feature. + + @param dir Path to the directory what will be used as cache. + @return reference to this parameter structure. + */ + GAPI_WRAP + OpenVINO& cfgCacheDir(const std::string &dir) { + cache_dir = dir; + return *this; + } + + /** @brief Specifies OpenVINO Execution Provider number of threads. + + This function is used to override the accelerator default value + of number of threads with this value at runtime. + + @param nthreads Number of threads. + @return reference to this parameter structure. + */ + GAPI_WRAP + OpenVINO& cfgNumThreads(size_t nthreads) { + num_of_threads = nthreads; + return *this; + } + + /** @brief Enables OpenVINO Execution Provider opencl throttling. + + This function is used to enable OpenCL queue throttling for GPU devices + (reduces CPU utilization when using GPU). + + @return reference to this parameter structure. + */ + GAPI_WRAP + OpenVINO& cfgEnableOpenCLThrottling() { + enable_opencl_throttling = true; + return *this; + } + + /** @brief Enables OpenVINO Execution Provider dynamic shapes. + + This function is used to enable OpenCL queue throttling for GPU devices + (reduces CPU utilization when using GPU). + This function is used to enable work with dynamic shaped models + whose shape will be set dynamically based on the infer input + image/data shape at run time in CPU. + + @return reference to this parameter structure. + */ + GAPI_WRAP + OpenVINO& cfgEnableDynamicShapes() { + enable_dynamic_shapes = true; + return *this; + } + + std::string device_type; + std::string cache_dir; + size_t num_of_threads = 0; + bool enable_opencl_throttling = false; + bool enable_dynamic_shapes = false; +}; + +/** + * @brief This structure provides functions + * that fill inference options for ONNX DirectML Execution Provider. + * Please follow https://onnxruntime.ai/docs/execution-providers/DirectML-ExecutionProvider.html#directml-execution-provider + */ +class GAPI_EXPORTS_W_SIMPLE DirectML { +public: + // NB: Used from python. + /// @private -- Exclude this constructor from OpenCV documentation + GAPI_WRAP + DirectML() = default; + + /** @brief Class constructor. + + Constructs DirectML parameters based on device id. + + @param device_id Target device id to use. ("0", "1", etc) + */ + GAPI_WRAP + explicit DirectML(const int device_id) : ddesc(device_id) { }; + + using DeviceDesc = cv::util::variant; + DeviceDesc ddesc; +}; + +using EP = cv::util::variant; + +} // namespace ep + GAPI_EXPORTS cv::gapi::GBackend backend(); enum class TraitAs: int { @@ -78,6 +198,9 @@ struct ParamDesc { // when the generic infer parameters are unpacked (see GONNXBackendImpl::unpackKernel) std::unordered_map > generic_mstd; std::unordered_map generic_norm; + + std::vector execution_providers; + bool disable_mem_pattern; }; } // namespace detail @@ -115,6 +238,7 @@ public: desc.num_in = std::tuple_size::value; desc.num_out = std::tuple_size::value; desc.is_generic = false; + desc.disable_mem_pattern = false; }; /** @brief Specifies sequence of network input layers names for inference. @@ -279,6 +403,43 @@ public: return *this; } + /** @brief Adds execution provider for runtime. + + The function is used to add ONNX Runtime OpenVINO Execution Provider options. + + @param ep OpenVINO Execution Provider options. + @see cv::gapi::onnx::ep::OpenVINO. + + @return the reference on modified object. + */ + Params& cfgAddExecutionProvider(ep::OpenVINO&& ep) { + desc.execution_providers.emplace_back(std::move(ep)); + return *this; + } + + /** @brief Adds execution provider for runtime. + + The function is used to add ONNX Runtime DirectML Execution Provider options. + + @param ep DirectML Execution Provider options. + @see cv::gapi::onnx::ep::DirectML. + + @return the reference on modified object. + */ + Params& cfgAddExecutionProvider(ep::DirectML&& ep) { + desc.execution_providers.emplace_back(std::move(ep)); + return *this; + } + + /** @brief Disables the memory pattern optimization. + + @return the reference on modified object. + */ + Params& cfgDisableMemPattern() { + desc.disable_mem_pattern = true; + return *this; + } + // BEGIN(G-API's network parametrization API) GBackend backend() const { return cv::gapi::onnx::backend(); } std::string tag() const { return Net::tag(); } @@ -306,18 +467,35 @@ public: @param model_path path to model file (.onnx file). */ Params(const std::string& tag, const std::string& model_path) - : desc{model_path, 0u, 0u, {}, {}, {}, {}, {}, {}, {}, {}, {}, true, {}, {} }, m_tag(tag) {} + : desc{model_path, 0u, 0u, {}, {}, {}, {}, {}, {}, {}, {}, {}, true, {}, {}, {}, false }, m_tag(tag) {} + /** @see onnx::Params::cfgMeanStdDev. */ void cfgMeanStdDev(const std::string &layer, const cv::Scalar &m, const cv::Scalar &s) { desc.generic_mstd[layer] = std::make_pair(m, s); } + /** @see onnx::Params::cfgNormalize. */ void cfgNormalize(const std::string &layer, bool flag) { desc.generic_norm[layer] = flag; } + /** @see onnx::Params::cfgAddExecutionProvider. */ + void cfgAddExecutionProvider(ep::OpenVINO&& ep) { + desc.execution_providers.emplace_back(std::move(ep)); + } + + /** @see onnx::Params::cfgAddExecutionProvider. */ + void cfgAddExecutionProvider(ep::DirectML&& ep) { + desc.execution_providers.emplace_back(std::move(ep)); + } + + /** @see onnx::Params::cfgDisableMemPattern. */ + void cfgDisableMemPattern() { + desc.disable_mem_pattern = true; + } + // BEGIN(G-API's network parametrization API) GBackend backend() const { return cv::gapi::onnx::backend(); } std::string tag() const { return m_tag; } diff --git a/modules/gapi/include/opencv2/gapi/streaming/desync.hpp b/modules/gapi/include/opencv2/gapi/streaming/desync.hpp index 9e927872a3..0e04f5beb9 100644 --- a/modules/gapi/include/opencv2/gapi/streaming/desync.hpp +++ b/modules/gapi/include/opencv2/gapi/streaming/desync.hpp @@ -46,6 +46,7 @@ G desync(const G &g) { , {cv::detail::GTypeTraits::shape} // output Shape , {cv::detail::GTypeTraits::op_kind} // input data kinds , {cv::detail::GObtainCtor::get()} // output template ctors + , {cv::detail::GTypeTraits::op_kind} // output data kinds }; cv::GCall call(std::move(k)); call.pass(g); diff --git a/modules/gapi/include/opencv2/gapi/streaming/meta.hpp b/modules/gapi/include/opencv2/gapi/streaming/meta.hpp index cbcfc3aa37..cdd3d371cb 100644 --- a/modules/gapi/include/opencv2/gapi/streaming/meta.hpp +++ b/modules/gapi/include/opencv2/gapi/streaming/meta.hpp @@ -50,6 +50,7 @@ cv::GOpaque meta(G g, const std::string &tag) { , {cv::detail::GTypeTraits::shape} // output Shape , {cv::detail::GTypeTraits::op_kind} // input data kinds , {cv::detail::GObtainCtor::get()} // output template ctors + , {cv::detail::GTypeTraits::op_kind} // output data kind }; cv::GCall call(std::move(k)); call.pass(g); diff --git a/modules/gapi/include/opencv2/gapi/util/variant.hpp b/modules/gapi/include/opencv2/gapi/util/variant.hpp index f412110deb..48b55646c5 100644 --- a/modules/gapi/include/opencv2/gapi/util/variant.hpp +++ b/modules/gapi/include/opencv2/gapi/util/variant.hpp @@ -509,6 +509,11 @@ namespace util return v.index() == util::variant::template index_of(); } +#if defined(__GNUC__) && (__GNUC__ == 11 || __GNUC__ == 12) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + template bool operator==(const variant &lhs, const variant &rhs) { @@ -524,6 +529,10 @@ namespace util return (eqs[lhs.index()])(lhs.memory, rhs.memory); } +#if defined(__GNUC__) && (__GNUC__ == 11 || __GNUC__ == 12) +#pragma GCC diagnostic pop +#endif + template bool operator!=(const variant &lhs, const variant &rhs) { diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp index 70698ffd48..60d5f85479 100644 --- a/modules/gapi/misc/python/pyopencv_gapi.hpp +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -29,6 +29,8 @@ using map_string_and_string = std::map; using map_string_and_vector_size_t = std::map>; using map_string_and_vector_float = std::map>; using map_int_and_double = std::map; +using ep_OpenVINO = cv::gapi::onnx::ep::OpenVINO; +using ep_DirectML = cv::gapi::onnx::ep::DirectML; // NB: Python wrapper generate T_U for T // This behavior is only observed for inputs diff --git a/modules/gapi/misc/python/python_bridge.hpp b/modules/gapi/misc/python/python_bridge.hpp index 07d056abb7..53edf38b30 100644 --- a/modules/gapi/misc/python/python_bridge.hpp +++ b/modules/gapi/misc/python/python_bridge.hpp @@ -267,13 +267,14 @@ cv::gapi::wip::GOutputs::Priv::Priv(const std::string& id, cv::GKernel::M outMet std::transform(args.begin(), args.end(), std::back_inserter(kinds), [](const cv::GArg& arg) { return arg.opaque_kind; }); - m_call.reset(new cv::GCall{cv::GKernel{id, {}, outMeta, {}, std::move(kinds), {}}}); + m_call.reset(new cv::GCall{cv::GKernel{id, {}, outMeta, {}, std::move(kinds), {}, {}}}); m_call->setArgs(std::move(args)); } cv::GMat cv::gapi::wip::GOutputs::Priv::getGMat() { m_call->kernel().outShapes.push_back(cv::GShape::GMAT); + m_call->kernel().outKinds.push_back(cv::detail::OpaqueKind::CV_UNKNOWN); // ...so _empty_ constructor is passed here. m_call->kernel().outCtors.emplace_back(cv::util::monostate{}); return m_call->yield(output++); @@ -282,6 +283,7 @@ cv::GMat cv::gapi::wip::GOutputs::Priv::getGMat() cv::GScalar cv::gapi::wip::GOutputs::Priv::getGScalar() { m_call->kernel().outShapes.push_back(cv::GShape::GSCALAR); + m_call->kernel().outKinds.push_back(cv::detail::OpaqueKind::CV_UNKNOWN); // ...so _empty_ constructor is passed here. m_call->kernel().outCtors.emplace_back(cv::util::monostate{}); return m_call->yieldScalar(output++); @@ -290,10 +292,14 @@ cv::GScalar cv::gapi::wip::GOutputs::Priv::getGScalar() cv::GArrayT cv::gapi::wip::GOutputs::Priv::getGArray(cv::gapi::ArgType type) { m_call->kernel().outShapes.push_back(cv::GShape::GARRAY); -#define HC(T, K) \ - case K: \ - m_call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor>::get()); \ - return cv::GArrayT(m_call->yieldArray(output++)); \ + +#define HC(T, K) \ + case K: { \ + const auto kind = cv::detail::GTypeTraits>::op_kind; \ + m_call->kernel().outKinds.emplace_back(kind); \ + m_call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor>::get()); \ + return cv::GArrayT(m_call->yieldArray(output++)); \ + } SWITCH(type, GARRAY_TYPE_LIST_G, HC) #undef HC @@ -302,10 +308,13 @@ cv::GArrayT cv::gapi::wip::GOutputs::Priv::getGArray(cv::gapi::ArgType type) cv::GOpaqueT cv::gapi::wip::GOutputs::Priv::getGOpaque(cv::gapi::ArgType type) { m_call->kernel().outShapes.push_back(cv::GShape::GOPAQUE); -#define HC(T, K) \ - case K: \ - m_call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor>::get()); \ - return cv::GOpaqueT(m_call->yieldOpaque(output++)); \ +#define HC(T, K) \ + case K: { \ + const auto kind = cv::detail::GTypeTraits>::op_kind; \ + m_call->kernel().outKinds.emplace_back(kind); \ + m_call->kernel().outCtors.emplace_back(cv::detail::GObtainCtor>::get()); \ + return cv::GOpaqueT(m_call->yieldOpaque(output++)); \ + } SWITCH(type, GOPAQUE_TYPE_LIST_G, HC) #undef HC diff --git a/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py b/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py index 8e15d457d9..fbe9aafa54 100644 --- a/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py +++ b/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py @@ -207,7 +207,48 @@ try: return Op + # NB: Just mock operation to test different kinds for output G-types. + @cv.gapi.op('custom.square_mean', in_types=[cv.GArray.Int], out_types=[cv.GOpaque.Float, cv.GArray.Int]) + class GSquareMean: + @staticmethod + def outMeta(desc): + return cv.empty_gopaque_desc(), cv.empty_array_desc() + + + @cv.gapi.kernel(GSquareMean) + class GSquareMeanImpl: + @staticmethod + def run(arr): + squares = [val**2 for val in arr] + return sum(arr) / len(arr), squares + + @cv.gapi.op('custom.squares', in_types=[cv.GArray.Int], out_types=[cv.GArray.Int]) + class GSquare: + @staticmethod + def outMeta(desc): + return cv.empty_array_desc() + + + @cv.gapi.kernel(GSquare) + class GSquareImpl: + @staticmethod + def run(arr): + squares = [val**2 for val in arr] + return squares + + class gapi_sample_pipelines(NewOpenCVTests): + def test_different_output_opaque_kinds(self): + g_in = cv.GArray.Int() + g_mean, g_squares = GSquareMean.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_mean, g_squares)) + + pkg = cv.gapi.kernels(GSquareMeanImpl) + mean, squares = comp.apply(cv.gin([1,2,3]), args=cv.gapi.compile_args(pkg)) + + self.assertEqual([1,4,9], list(squares)) + self.assertEqual(2.0, mean) + def test_custom_op_add(self): sz = (3, 3) diff --git a/modules/gapi/src/backends/ie/giebackend.cpp b/modules/gapi/src/backends/ie/giebackend.cpp index a257c21252..6026f29ae5 100644 --- a/modules/gapi/src/backends/ie/giebackend.cpp +++ b/modules/gapi/src/backends/ie/giebackend.cpp @@ -949,7 +949,11 @@ inline IE::Blob::Ptr extractBlob(IECallContext& ctx, auto y_blob = ctx.uu.rctx->CreateBlob(blob_params->first.first, blob_params->first.second); auto uv_blob = ctx.uu.rctx->CreateBlob(blob_params->second.first, blob_params->second.second); -#if INF_ENGINE_RELEASE >= 2021010000 +#if INF_ENGINE_RELEASE > 2023000000 + cv::util::throw_error(std::logic_error( + "IE Backend: NV12 feature has been deprecated in OpenVINO 1.0 API." + " The last version which supports this is 2023.0")); +#elif INF_ENGINE_RELEASE >= 2021010000 return IE::make_shared_blob(y_blob, uv_blob); #else return IE::make_shared_blob(y_blob, uv_blob); @@ -982,7 +986,14 @@ static void setBlob(InferenceEngine::InferRequest& req, req.SetBlob(layer_name, blob); } else { GAPI_Assert(ctx.uu.params.kind == ParamDesc::Kind::Import); +#if INF_ENGINE_RELEASE > 2023000000 + // NB: SetBlob overload which accepts IE::PreProcessInfo + // has been deprecated - preprocessing can't be configured + // for "Import" networks anymore. + req.SetBlob(layer_name, blob); +#else req.SetBlob(layer_name, blob, ctx.uu.preproc_map.at(layer_name)); +#endif } } @@ -1370,7 +1381,14 @@ static void cfgImagePreprocessing(const IE::InputInfo::Ptr &ii, if (cv::util::holds_alternative(mm)) { const auto &meta = util::get(mm); if (meta.fmt == cv::MediaFormat::NV12) { +#if INF_ENGINE_RELEASE > 2023000000 + cv::util::throw_error(std::logic_error( + "IE Backend: cv::MediaFrame with NV12 format is no longer supported" + " because NV12 feature has been deprecated in OpenVINO 1.0 API." + " The last version which supports this is 2023.0")); +#else ii->getPreProcess().setColorFormat(IE::ColorFormat::NV12); +#endif } } } @@ -1426,7 +1444,14 @@ static IE::PreProcessInfo createImagePreProcInfo(const cv::GMetaArg &mm, if (cv::util::holds_alternative(mm)) { const auto &meta = util::get(mm); if (meta.fmt == cv::MediaFormat::NV12) { +#if INF_ENGINE_RELEASE > 2023000000 + cv::util::throw_error(std::logic_error( + "IE Backend: cv::MediaFrame with NV12 format is no longer supported" + " because NV12 feature has been deprecated in OpenVINO 1.0 API." + " The last version which supports this is 2023.0")); +#else info.setColorFormat(IE::ColorFormat::NV12); +#endif } } return info; @@ -2299,7 +2324,11 @@ IE::Blob::Ptr cv::gapi::ie::util::to_ie(const cv::Mat &blob) { IE::Blob::Ptr cv::gapi::ie::util::to_ie(const cv::Mat &y_plane, const cv::Mat &uv_plane) { auto y_blob = wrapIE(y_plane, cv::gapi::ie::TraitAs::IMAGE); auto uv_blob = wrapIE(uv_plane, cv::gapi::ie::TraitAs::IMAGE); -#if INF_ENGINE_RELEASE >= 2021010000 +#if INF_ENGINE_RELEASE > 2023000000 + cv::util::throw_error(std::logic_error( + "IE Backend: NV12 feature has been deprecated in OpenVINO 1.0 API." + " The last version which supports this is 2023.0")); +#elif INF_ENGINE_RELEASE >= 2021010000 return IE::make_shared_blob(y_blob, uv_blob); #else return IE::make_shared_blob(y_blob, uv_blob); diff --git a/modules/gapi/src/backends/onnx/bindings_onnx.cpp b/modules/gapi/src/backends/onnx/bindings_onnx.cpp index c9c5fc58fa..6051c6bb4d 100644 --- a/modules/gapi/src/backends/onnx/bindings_onnx.cpp +++ b/modules/gapi/src/backends/onnx/bindings_onnx.cpp @@ -21,6 +21,24 @@ cv::gapi::onnx::PyParams& cv::gapi::onnx::PyParams::cfgNormalize(const std::stri return *this; } +cv::gapi::onnx::PyParams& +cv::gapi::onnx::PyParams::cfgAddExecutionProvider(cv::gapi::onnx::ep::OpenVINO ep) { + m_priv->cfgAddExecutionProvider(std::move(ep)); + return *this; +} + +cv::gapi::onnx::PyParams& +cv::gapi::onnx::PyParams::cfgAddExecutionProvider(cv::gapi::onnx::ep::DirectML ep) { + m_priv->cfgAddExecutionProvider(std::move(ep)); + return *this; +} + +cv::gapi::onnx::PyParams& +cv::gapi::onnx::PyParams::cfgDisableMemPattern() { + m_priv->cfgDisableMemPattern(); + return *this; +} + cv::gapi::GBackend cv::gapi::onnx::PyParams::backend() const { return m_priv->backend(); } diff --git a/modules/gapi/src/backends/onnx/dml_ep.cpp b/modules/gapi/src/backends/onnx/dml_ep.cpp new file mode 100644 index 0000000000..7f59e1f3d6 --- /dev/null +++ b/modules/gapi/src/backends/onnx/dml_ep.cpp @@ -0,0 +1,40 @@ +// 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 + +#include "backends/onnx/dml_ep.hpp" +#include "logger.hpp" + +#ifdef HAVE_ONNX +#include + +#ifdef HAVE_ONNX_DML +#include "../providers/dml/dml_provider_factory.h" + +void cv::gimpl::onnx::addDMLExecutionProvider(Ort::SessionOptions *session_options, + const cv::gapi::onnx::ep::DirectML &dml_ep) { + namespace ep = cv::gapi::onnx::ep; + GAPI_Assert(cv::util::holds_alternative(dml_ep.ddesc)); + const int device_id = cv::util::get(dml_ep.ddesc); + try { + OrtSessionOptionsAppendExecutionProvider_DML(*session_options, device_id); + } catch (const std::exception &e) { + std::stringstream ss; + ss << "ONNX Backend: Failed to enable DirectML" + << " Execution Provider: " << e.what(); + cv::util::throw_error(std::runtime_error(ss.str())); + } +} + +#else // HAVE_ONNX_DML + +void cv::gimpl::onnx::addDMLExecutionProvider(Ort::SessionOptions*, + const cv::gapi::onnx::ep::DirectML&) { + util::throw_error(std::runtime_error("G-API has been compiled with ONNXRT" + " without DirectML support")); +} + +#endif // HAVE_ONNX_DML +#endif // HAVE_ONNX diff --git a/modules/gapi/src/backends/onnx/dml_ep.hpp b/modules/gapi/src/backends/onnx/dml_ep.hpp new file mode 100644 index 0000000000..d7e43dc888 --- /dev/null +++ b/modules/gapi/src/backends/onnx/dml_ep.hpp @@ -0,0 +1,23 @@ +// 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_DML_EP_HPP +#define OPENCV_GAPI_DML_EP_HPP + +#include "opencv2/gapi/infer/onnx.hpp" +#ifdef HAVE_ONNX + +#include + +namespace cv { +namespace gimpl { +namespace onnx { +void addDMLExecutionProvider(Ort::SessionOptions *session_options, + const cv::gapi::onnx::ep::DirectML &dml_ep); +}}} + +#endif // HAVE_ONNX +#endif // OPENCV_GAPI_DML_EP_HPP diff --git a/modules/gapi/src/backends/onnx/gonnxbackend.cpp b/modules/gapi/src/backends/onnx/gonnxbackend.cpp index 1194caeeb3..b90d4d6974 100644 --- a/modules/gapi/src/backends/onnx/gonnxbackend.cpp +++ b/modules/gapi/src/backends/onnx/gonnxbackend.cpp @@ -9,6 +9,8 @@ #ifdef HAVE_ONNX +#include "backends/onnx/dml_ep.hpp" + #include // any_of #include #include @@ -143,6 +145,48 @@ public: void run(); }; +static void addOpenVINOExecutionProvider(Ort::SessionOptions *session_options, + const cv::gapi::onnx::ep::OpenVINO &ov_ep) { + OrtOpenVINOProviderOptions options; + options.device_type = ov_ep.device_type.c_str(); + options.cache_dir = ov_ep.cache_dir.c_str(); + options.num_of_threads = ov_ep.num_of_threads; + options.enable_opencl_throttling = ov_ep.enable_opencl_throttling; + options.enable_dynamic_shapes = ov_ep.enable_dynamic_shapes; + options.context = nullptr; + + try { + session_options->AppendExecutionProvider_OpenVINO(options); + } catch (const std::exception &e) { + std::stringstream ss; + ss << "ONNX Backend: Failed to enable OpenVINO" + << " Execution Provider: " << e.what(); + cv::util::throw_error(std::runtime_error(ss.str())); + } +} + +static void addExecutionProvider(Ort::SessionOptions *session_options, + const cv::gapi::onnx::ep::EP &execution_provider) { + namespace ep = cv::gapi::onnx::ep; + switch (execution_provider.index()) { + case ep::EP::index_of(): { + GAPI_LOG_INFO(NULL, "OpenVINO Execution Provider is added."); + const auto &ov_ep = cv::util::get(execution_provider); + addOpenVINOExecutionProvider(session_options, ov_ep); + break; + } + case ep::EP::index_of(): { + GAPI_LOG_INFO(NULL, "DirectML Execution Provider is added."); + const auto &dml_ep = cv::util::get(execution_provider); + addDMLExecutionProvider(session_options, dml_ep); + break; + } + default: + GAPI_LOG_INFO(NULL, "CPU Execution Provider is added."); + break; + } +} + } // namespace onnx } // namespace gimpl } // namespace cv @@ -592,9 +636,16 @@ ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp) cv::util::throw_error(std::logic_error("Please specify output layer names for " + params.model_path)); } - // Create and initialize the ONNX session Ort::SessionOptions session_options; + GAPI_LOG_INFO(NULL, "Adding Execution Providers for \"" << pp.model_path << "\""); + for (const auto &ep : pp.execution_providers) { + cv::gimpl::onnx::addExecutionProvider(&session_options, ep); + } + + if (pp.disable_mem_pattern) { + session_options.DisableMemPattern(); + } this_env = Ort::Env(ORT_LOGGING_LEVEL_WARNING, ""); #ifndef _WIN32 this_session = Ort::Session(this_env, params.model_path.data(), session_options); diff --git a/modules/gapi/src/backends/ov/govbackend.cpp b/modules/gapi/src/backends/ov/govbackend.cpp index 46eccd2bbd..8384a9d188 100644 --- a/modules/gapi/src/backends/ov/govbackend.cpp +++ b/modules/gapi/src/backends/ov/govbackend.cpp @@ -18,6 +18,7 @@ #include #include +#include // getConfigurationParameterBool #if defined(HAVE_TBB) # include // FIXME: drop it from here! @@ -37,11 +38,37 @@ template using QueueClass = cv::gapi::own::concurrent_bounded_queue< using ParamDesc = cv::gapi::ov::detail::ParamDesc; -static ov::Core getCore() { +// NB: Some of OV plugins fail during ov::Core destroying in specific cases. +// Solution is allocate ov::Core in heap and doesn't destroy it, which cause +// leak, but fixes tests on CI. This behaviour is configurable by using +// OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND=0 +static ov::Core create_OV_Core_pointer() { + // NB: 'delete' is never called + static ov::Core* core = new ov::Core(); + return *core; +} + +static ov::Core create_OV_Core_instance() { static ov::Core core; return core; } +ov::Core cv::gapi::ov::wrap::getCore() { + // NB: to make happy memory leak tools use: + // - OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND=0 + static bool param_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND = + utils::getConfigurationParameterBool( + "OPENCV_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND", +#if defined(_WIN32) || defined(__APPLE__) + true +#else + false +#endif + ); + return param_GAPI_INFERENCE_ENGINE_CORE_LIFETIME_WORKAROUND + ? create_OV_Core_pointer() : create_OV_Core_instance(); +} + static ov::AnyMap toOV(const ParamDesc::PluginConfigT &config) { return {config.begin(), config.end()}; } @@ -101,8 +128,8 @@ static int toCV(const ov::element::Type &type) { static void copyFromOV(const ov::Tensor &tensor, cv::Mat &mat) { const auto total = mat.total() * mat.channels(); - if (tensor.get_element_type() != toOV(mat.depth()) || - tensor.get_size() != total ) { + if (toCV(tensor.get_element_type()) != mat.depth() || + tensor.get_size() != total ) { std::stringstream ss; ss << "Failed to copy data from ov::Tensor to cv::Mat." << " Data type or number of elements mismatch." @@ -128,8 +155,8 @@ static void copyToOV(const cv::Mat &mat, ov::Tensor &tensor) { // TODO: Ideally there should be check that mat and tensor // dimensions are compatible. const auto total = mat.total() * mat.channels(); - if (tensor.get_element_type() != toOV(mat.depth()) || - tensor.get_size() != total) { + if (toCV(tensor.get_element_type()) != mat.depth() || + tensor.get_size() != total) { std::stringstream ss; ss << "Failed to copy data from cv::Mat to ov::Tensor." << " Data type or number of elements mismatch." @@ -158,6 +185,14 @@ int cv::gapi::ov::util::to_ocv(const ::ov::element::Type &type) { return toCV(type); } +void cv::gapi::ov::util::to_ov(const cv::Mat &mat, ::ov::Tensor &tensor) { + copyToOV(mat, tensor); +} + +void cv::gapi::ov::util::to_ocv(const ::ov::Tensor &tensor, cv::Mat &mat) { + copyFromOV(tensor, mat); +} + struct OVUnit { static const char *name() { return "OVUnit"; } @@ -167,7 +202,8 @@ struct OVUnit { // FIXME: Can this logic be encapsulated to prevent checking every time? if (cv::util::holds_alternative(params.kind)) { const auto desc = cv::util::get(params.kind); - model = getCore().read_model(desc.model_path, desc.bin_path); + model = cv::gapi::ov::wrap::getCore() + .read_model(desc.model_path, desc.bin_path); GAPI_Assert(model); if (params.num_in == 1u && params.input_names.empty()) { @@ -182,9 +218,8 @@ struct OVUnit { std::ifstream file(cv::util::get(params.kind).blob_path, std::ios_base::in | std::ios_base::binary); GAPI_Assert(file.is_open()); - compiled_model = getCore().import_model(file, - params.device, - toOV(params.config)); + compiled_model = cv::gapi::ov::wrap::getCore() + .import_model(file, params.device, toOV(params.config)); if (params.num_in == 1u && params.input_names.empty()) { params.input_names = { compiled_model.inputs().begin()->get_any_name() }; @@ -197,9 +232,8 @@ struct OVUnit { cv::gimpl::ov::OVCompiled compile() { if (cv::util::holds_alternative(params.kind)) { - compiled_model = getCore().compile_model(model, - params.device, - toOV(params.config)); + compiled_model = cv::gapi::ov::wrap::getCore() + .compile_model(model, params.device, toOV(params.config)); } return {compiled_model}; } @@ -343,6 +377,15 @@ cv::GArg OVCallContext::packArg(const cv::GArg &arg) { switch (ref.shape) { case cv::GShape::GMAT: return cv::GArg(m_res.slot()[ref.id]); + + // Note: .at() is intentional for GArray as object MUST be already there + // (and constructed by either bindIn/Out or resetInternal) + case cv::GShape::GARRAY: return cv::GArg(m_res.slot().at(ref.id)); + + // Note: .at() is intentional for GOpaque as object MUST be already there + // (and constructed by either bindIn/Out or resetInternal) + case cv::GShape::GOPAQUE: return cv::GArg(m_res.slot().at(ref.id)); + default: cv::util::throw_error(std::logic_error("Unsupported GShape type")); break; @@ -547,6 +590,62 @@ static void PostOutputs(::ov::InferRequest &infer_request, } } +class PostOutputsList { +public: + PostOutputsList(size_t size, + std::shared_ptr ctx); + + void operator()(::ov::InferRequest &infer_request, + std::exception_ptr eptr, + size_t pos) const; + +private: + struct Priv { + std::atomic finished{0u}; + size_t size; + std::shared_ptr ctx; + }; + std::shared_ptr m_priv; +}; + +PostOutputsList::PostOutputsList(size_t size, + std::shared_ptr ctx) + : m_priv(new Priv{}) { + m_priv->size = size; + m_priv->ctx = ctx; +} + +void PostOutputsList::operator()(::ov::InferRequest &infer_request, + std::exception_ptr eptr, + size_t pos) const { + auto&& ctx = m_priv->ctx; + auto&& finished = m_priv->finished; + auto&& size = m_priv->size; + + ctx->eptr = eptr; + if (!ctx->eptr) { + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + std::vector &out_vec = ctx->outVecR(i); + + const auto &out_name = ctx->uu.params.output_names[i]; + const auto &out_tensor = infer_request.get_tensor(out_name); + + out_vec[pos].create(toCV(out_tensor.get_shape()), + toCV(out_tensor.get_element_type())); + copyFromOV(out_tensor, out_vec[pos]); + } + } + ++finished; + + if (finished == size) { + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + auto output = ctx->output(i); + ctx->out.meta(output, ctx->getMeta()); + ctx->out.post(std::move(output), ctx->eptr); + } + } +} + namespace cv { namespace gimpl { namespace ov { @@ -594,6 +693,34 @@ cv::optional lookUp(const std::map &map, const K& key) { return cv::util::make_optional(std::move(it->second)); } +// NB: This function is used to preprocess input image +// for InferROI, InferList, InferList2 kernels. +static cv::Mat preprocess(const cv::Mat &in_mat, + const cv::Rect &roi, + const ::ov::Shape &model_shape) { + cv::Mat out; + // FIXME: Since there is no information about H and W positions + // among tensor dimmensions assume that model layout is "NHWC". + // (In fact "NHWC" is the only right layout for preprocessing because + // it works only with images. + GAPI_Assert(model_shape.size() == 4u); + const auto H = model_shape[1]; + const auto W = model_shape[2]; + const auto C = model_shape[3]; + // NB: Soft check that at least number of channels matches. + if (static_cast(C) != in_mat.channels()) { + std::stringstream ss; + ss << "OV Backend: Failed to preprocess input data " + " (Number of channels mismatch)." + " Provided data: " << cv::descr_of(in_mat) << + " and Model shape: " << model_shape; + util::throw_error(std::logic_error(ss.str())); + } + // NB: Crop roi and resize to model size. + cv::resize(in_mat(roi), out, cv::Size(W, H)); + return out; +} + static bool isImage(const cv::GMatDesc &desc, const ::ov::Shape &model_shape) { return (model_shape.size() == 4u) && @@ -603,6 +730,203 @@ static bool isImage(const cv::GMatDesc &desc, (desc.depth == CV_8U); } +class PrePostProcWrapper { +public: + PrePostProcWrapper(std::shared_ptr<::ov::Model> &model, + const ParamDesc::Model &model_info, + const std::vector &input_names, + const std::vector &output_names) + : m_ppp(model), + m_model(model), + m_model_info(model_info), + m_input_names(input_names), + m_output_names(output_names) { + // NB: Do Reshape right away since it must be the first step of model modification + // and applicable for all infer kernels. + const auto new_shapes = broadcastLayerAttr(model_info.new_shapes, input_names); + m_model->reshape(toOV(new_shapes)); + + const auto &mi = m_model_info; + m_input_tensor_layout = broadcastLayerAttr(mi.input_tensor_layout, m_input_names); + m_input_model_layout = broadcastLayerAttr(mi.input_model_layout, m_input_names); + m_interpolation = broadcastLayerAttr(mi.interpolation, m_input_names); + m_mean_values = broadcastLayerAttr(mi.mean_values, m_input_names); + m_scale_values = broadcastLayerAttr(mi.scale_values, m_input_names); + m_interpolation = broadcastLayerAttr(mi.interpolation, m_input_names); + + m_output_tensor_layout = broadcastLayerAttr(mi.output_tensor_layout, m_output_names); + m_output_model_layout = broadcastLayerAttr(mi.output_model_layout, m_output_names); + m_output_tensor_precision = broadcastLayerAttr(mi.output_tensor_precision, m_output_names); + }; + + void cfgLayouts(const std::string &input_name) { + auto &input_info = m_ppp.input(input_name); + const auto explicit_in_model_layout = lookUp(m_input_model_layout, input_name); + if (explicit_in_model_layout) { + input_info.model().set_layout(::ov::Layout(*explicit_in_model_layout)); + } else if (m_model->input(input_name).get_shape().size() == 4u) { + // NB: Back compatibility with IR's without any layout information. + // Note that default is only applicable for 4D inputs in order to + // support auto resize for image use cases. + GAPI_LOG_WARNING(NULL, "Failed to find layout for input layer \"" + << input_name << "\" - NCHW is set by default"); + const std::string default_layout = "NCHW"; + input_info.model().set_layout(::ov::Layout(default_layout)); + m_input_model_layout.emplace(input_name, default_layout); + } + const auto explicit_in_tensor_layout = lookUp(m_input_tensor_layout, input_name); + if (explicit_in_tensor_layout) { + input_info.tensor().set_layout(::ov::Layout(*explicit_in_tensor_layout)); + } + } + + void cfgScaleMean(const std::string &input_name) { + auto &input_info = m_ppp.input(input_name); + const auto mean_vec = lookUp(m_mean_values, input_name); + if (mean_vec) { + input_info.preprocess().mean(*mean_vec); + } + const auto scale_vec = lookUp(m_scale_values, input_name); + if (scale_vec) { + input_info.preprocess().scale(*scale_vec); + } + } + + // FIXME: Decompose this... + void cfgPreProcessing(const std::string &input_name, + const cv::GMetaArg &input_meta, + const bool disable_img_resize = false) { + GAPI_Assert(cv::util::holds_alternative(input_meta)); + const auto &matdesc = cv::util::get(input_meta); + + const auto explicit_in_tensor_layout = lookUp(m_input_tensor_layout, input_name); + const auto explicit_in_model_layout = lookUp(m_input_model_layout, input_name); + const auto explicit_resize = lookUp(m_interpolation, input_name); + + if (disable_img_resize && explicit_resize.has_value()) { + std::stringstream ss; + util::throw_error(std::logic_error( + "OV Backend: Resize for layer \"" + input_name + "\" will be performed" + " on host via OpenCV so explicitly configured resize is prohibited.")); + } + + const auto &input_shape = m_model->input(input_name).get_shape(); + auto &input_info = m_ppp.input(input_name); + + m_ppp.input(input_name).tensor().set_element_type(toOV(matdesc.depth)); + if (isImage(matdesc, input_shape)) { + // NB: Image case - all necessary preprocessng is configured automatically. + GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is image."); + if (explicit_in_tensor_layout && + *explicit_in_tensor_layout != "NHWC") { + std::stringstream ss; + ss << "OV Backend: Provided tensor layout " << *explicit_in_tensor_layout + << " is not compatible with input data " << matdesc << " for layer \"" + << input_name << "\". Expecting NHWC"; + util::throw_error(std::logic_error(ss.str())); + } else { + input_info.tensor().set_layout(::ov::Layout("NHWC")); + } + + if (!disable_img_resize) { + input_info.tensor().set_spatial_static_shape(matdesc.size.height, + matdesc.size.width); + // NB: Even though resize is automatically configured + // user have an opportunity to specify the interpolation algorithm. + auto interp = explicit_resize + ? toOVInterp(*explicit_resize) + : ::ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR; + input_info.preprocess().resize(interp); + } + } else { + // NB: Tensor case - resize or layout conversions must be explicitly specified. + GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is tensor."); + + if (explicit_resize) { + if (matdesc.isND()) { + // NB: ND case - need to obtain "H" and "W" positions + // in order to configure resize. + const auto model_layout = explicit_in_model_layout + ? ::ov::Layout(*explicit_in_model_layout) + : ::ov::layout::get_layout(m_model->input(input_name)); + if (!explicit_in_tensor_layout && model_layout.empty()) { + std::stringstream ss; + ss << "Resize for input layer: " << input_name + << "can't be configured." + << " Failed to extract H and W positions from layout."; + util::throw_error(std::logic_error(ss.str())); + } else { + const auto layout = explicit_in_tensor_layout + ? ::ov::Layout(*explicit_in_tensor_layout) : model_layout; + auto H_idx = ::ov::layout::height_idx(layout); + auto W_idx = ::ov::layout::width_idx(layout); + // NB: If layout is "...HW", H position is -2. + if (H_idx < 0) H_idx = matdesc.dims.size() + H_idx; + if (W_idx < 0) W_idx = matdesc.dims.size() + W_idx; + GAPI_Assert(H_idx >= 0 && H_idx < static_cast(matdesc.dims.size())); + GAPI_Assert(W_idx >= 0 && W_idx < static_cast(matdesc.dims.size())); + input_info.tensor().set_spatial_static_shape(matdesc.dims[H_idx], + matdesc.dims[W_idx]); + input_info.preprocess().resize(toOVInterp(*explicit_resize)); + } + } else { + // NB: 2D case - We know exactly where H and W... + input_info.tensor().set_spatial_static_shape(matdesc.size.height, + matdesc.size.width); + input_info.preprocess().resize(toOVInterp(*explicit_resize)); + } + } + } + } + + void cfgPostProcessing() { + for (const auto &output_name : m_output_names) { + const auto explicit_out_tensor_layout = + lookUp(m_output_tensor_layout, output_name); + if (explicit_out_tensor_layout) { + m_ppp.output(output_name).tensor() + .set_layout(::ov::Layout(*explicit_out_tensor_layout)); + } + + const auto explicit_out_model_layout = + lookUp(m_output_model_layout, output_name); + if (explicit_out_model_layout) { + m_ppp.output(output_name).model() + .set_layout(::ov::Layout(*explicit_out_model_layout)); + } + + const auto explicit_out_tensor_prec = + lookUp(m_output_tensor_precision, output_name); + if (explicit_out_tensor_prec) { + m_ppp.output(output_name).tensor() + .set_element_type(toOV(*explicit_out_tensor_prec)); + } + } + } + + void finalize() { + GAPI_LOG_DEBUG(NULL, "OV Backend: PrePostProcessor: " << m_ppp); + m_model = m_ppp.build(); + } + +private: + ::ov::preprocess::PrePostProcessor m_ppp; + + std::shared_ptr<::ov::Model> &m_model; + const ParamDesc::Model &m_model_info; + const std::vector &m_input_names; + const std::vector &m_output_names; + + cv::gimpl::ov::AttrMap m_input_tensor_layout; + cv::gimpl::ov::AttrMap m_input_model_layout; + cv::gimpl::ov::AttrMap m_interpolation; + cv::gimpl::ov::AttrMap> m_mean_values; + cv::gimpl::ov::AttrMap> m_scale_values; + cv::gimpl::ov::AttrMap m_output_tensor_layout; + cv::gimpl::ov::AttrMap m_output_model_layout; + cv::gimpl::ov::AttrMap m_output_tensor_precision; +}; + struct Infer: public cv::detail::KernelTag { using API = cv::GInferBase; static cv::gapi::GBackend backend() { return cv::gapi::ov::backend(); } @@ -625,156 +949,21 @@ struct Infer: public cv::detail::KernelTag { // NB: Pre/Post processing configuration avaiable only for read models. if (cv::util::holds_alternative(uu.params.kind)) { const auto &model_info = cv::util::get(uu.params.kind); - const auto new_shapes = - broadcastLayerAttr(model_info.new_shapes, - uu.params.input_names); - const_cast&>(uu.model)->reshape(toOV(new_shapes)); - - const auto input_tensor_layout = - broadcastLayerAttr(model_info.input_tensor_layout, - uu.params.input_names); - const auto input_model_layout = - broadcastLayerAttr(model_info.input_model_layout, - uu.params.input_names); - - const auto interpolation = broadcastLayerAttr(model_info.interpolation, - uu.params.input_names); - const auto mean_values = broadcastLayerAttr(model_info.mean_values, - uu.params.input_names); - const auto scale_values = broadcastLayerAttr(model_info.scale_values, - uu.params.input_names); - // FIXME: Pre/Post processing step shouldn't be configured in this method. - ::ov::preprocess::PrePostProcessor ppp(uu.model); + auto& model = const_cast&>(uu.model); + PrePostProcWrapper ppp {model, model_info, + uu.params.input_names, uu.params.output_names}; + for (auto &&it : ade::util::zip(ade::util::toRange(uu.params.input_names), ade::util::toRange(in_metas))) { - const auto &mm = std::get<1>(it); - GAPI_Assert(cv::util::holds_alternative(mm)); - const auto &matdesc = cv::util::get(mm); - const auto &input_name = std::get<0>(it); - auto &input_info = ppp.input(input_name); - input_info.tensor().set_element_type(toOV(matdesc.depth)); - - const auto explicit_in_model_layout = lookUp(input_model_layout, input_name); - if (explicit_in_model_layout) { - input_info.model().set_layout(::ov::Layout(*explicit_in_model_layout)); - } - const auto explicit_in_tensor_layout = lookUp(input_tensor_layout, input_name); - if (explicit_in_tensor_layout) { - input_info.tensor().set_layout(::ov::Layout(*explicit_in_tensor_layout)); - } - const auto explicit_resize = lookUp(interpolation, input_name); - // NB: Note that model layout still can't be empty. - // e.g If model converted to IRv11 without any additional - // info about layout via Model Optimizer. - const auto model_layout = ::ov::layout::get_layout(uu.model->input(input_name)); - const auto &input_shape = uu.model->input(input_name).get_shape(); - if (isImage(matdesc, input_shape)) { - // NB: Image case - all necessary preprocessng is configured automatically. - GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is image."); - // NB: Layout is already set just double check that - // user provided the correct one. In fact, there is only one correct for image. - if (explicit_in_tensor_layout && - *explicit_in_tensor_layout != "NHWC") { - std::stringstream ss; - ss << "OV Backend: Provided tensor layout " << *explicit_in_tensor_layout - << " is not compatible with input data " << matdesc << " for layer \"" - << input_name << "\". Expecting NHWC"; - util::throw_error(std::logic_error(ss.str())); - } - input_info.tensor().set_layout(::ov::Layout("NHWC")); - input_info.tensor().set_spatial_static_shape(matdesc.size.height, - matdesc.size.width); - // NB: Even though resize is automatically configured - // user have an opportunity to specify the interpolation algorithm. - auto interp = explicit_resize - ? toOVInterp(*explicit_resize) - : ::ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR; - input_info.preprocess().resize(interp); - } else { - // NB: Tensor case - resize or layout conversions must be explicitly specified. - GAPI_LOG_DEBUG(NULL, "OV Backend: Input: \"" << input_name << "\" is tensor."); - if (explicit_resize) { - if (matdesc.isND()) { - // NB: ND case - need to obtain "H" and "W" positions - // in order to configure resize. - if (!explicit_in_tensor_layout && model_layout.empty()) { - std::stringstream ss; - ss << "Resize for input layer: " << input_name - << "can't be configured." - << " Failed to extract H and W positions from layout."; - util::throw_error(std::logic_error(ss.str())); - } else { - const auto layout = explicit_in_tensor_layout - ? ::ov::Layout(*explicit_in_tensor_layout) : model_layout; - auto H_idx = ::ov::layout::height_idx(layout); - auto W_idx = ::ov::layout::width_idx(layout); - // NB: If layout is "...HW", H position is -2. - if (H_idx < 0) H_idx = matdesc.dims.size() + H_idx; - if (W_idx < 0) W_idx = matdesc.dims.size() + W_idx; - GAPI_Assert(H_idx >= 0 && H_idx < static_cast(matdesc.dims.size())); - GAPI_Assert(W_idx >= 0 && W_idx < static_cast(matdesc.dims.size())); - input_info.tensor().set_spatial_static_shape(matdesc.dims[H_idx], - matdesc.dims[W_idx]); - input_info.preprocess().resize(toOVInterp(*explicit_resize)); - } - } else { - // NB: 2D case - We know exactly where H and W... - input_info.tensor().set_spatial_static_shape(matdesc.size.height, - matdesc.size.width); - input_info.preprocess().resize(toOVInterp(*explicit_resize)); - } - } - } - // NB: Apply mean/scale as the last step of the preprocessing. - // Note that this can be applied to any input data if the - // position of "C" dimension is known. - const auto mean_vec = lookUp(mean_values, input_name); - if (mean_vec) { - input_info.preprocess().mean(*mean_vec); - } - - const auto scale_vec = lookUp(scale_values, input_name); - if (scale_vec) { - input_info.preprocess().scale(*scale_vec); - } - } - - const auto output_tensor_layout = - broadcastLayerAttr(model_info.output_tensor_layout, - uu.params.output_names); - const auto output_model_layout = - broadcastLayerAttr(model_info.output_model_layout, - uu.params.output_names); - const auto output_tensor_precision = - broadcastLayerAttr(model_info.output_tensor_precision, - uu.params.output_names); - - for (const auto &output_name : uu.params.output_names) { - const auto explicit_out_tensor_layout = - lookUp(output_tensor_layout, output_name); - if (explicit_out_tensor_layout) { - ppp.output(output_name).tensor() - .set_layout(::ov::Layout(*explicit_out_tensor_layout)); - } - - const auto explicit_out_model_layout = - lookUp(output_model_layout, output_name); - if (explicit_out_model_layout) { - ppp.output(output_name).model() - .set_layout(::ov::Layout(*explicit_out_model_layout)); - } + const auto &mm = std::get<1>(it); - const auto explicit_out_tensor_prec = - lookUp(output_tensor_precision, output_name); - if (explicit_out_tensor_prec) { - ppp.output(output_name).tensor() - .set_element_type(toOV(*explicit_out_tensor_prec)); - } + ppp.cfgLayouts(input_name); + ppp.cfgPreProcessing(input_name, mm); + ppp.cfgScaleMean(input_name); } - - GAPI_LOG_DEBUG(NULL, "OV Backend: PrePostProcessor: " << ppp); - const_cast&>(uu.model) = ppp.build(); + ppp.cfgPostProcessing(); + ppp.finalize(); } for (const auto &out_name : uu.params.output_names) { @@ -815,6 +1004,313 @@ struct Infer: public cv::detail::KernelTag { } }; +struct InferROI: public cv::detail::KernelTag { + using API = cv::GInferROIBase; + static cv::gapi::GBackend backend() { return cv::gapi::ov::backend(); } + static KImpl kernel() { return KImpl{outMeta, run}; } + + static cv::GMetaArgs outMeta(const ade::Graph &gr, + const ade::NodeHandle &nh, + const cv::GMetaArgs &in_metas, + const cv::GArgs &/*in_args*/) { + cv::GMetaArgs result; + + GConstGOVModel gm(gr); + const auto &uu = gm.metadata(nh).get(); + // Initialize input information + // FIXME: So far it is pretty limited + GAPI_Assert(1u == uu.params.input_names.size()); + GAPI_Assert(2u == in_metas.size()); + + const auto &input_name = uu.params.input_names.at(0); + const auto &mm = in_metas.at(1u); + GAPI_Assert(cv::util::holds_alternative(mm)); + const auto &matdesc = cv::util::get(mm); + + const bool is_model = cv::util::holds_alternative(uu.params.kind); + const auto &input_shape = is_model ? uu.model->input(input_name).get_shape() + : uu.compiled_model.input(input_name).get_shape(); + if (!isImage(matdesc, input_shape)) { + util::throw_error(std::runtime_error( + "OV Backend: InferROI supports only image as the 1th argument")); + } + + if (is_model) { + const auto &model_info = cv::util::get(uu.params.kind); + auto& model = const_cast&>(uu.model); + PrePostProcWrapper ppp {model, model_info, + uu.params.input_names, uu.params.output_names}; + + ppp.cfgLayouts(input_name); + ppp.cfgPreProcessing(input_name, mm, true /*disable_img_resize*/); + ppp.cfgScaleMean(input_name); + ppp.cfgPostProcessing(); + ppp.finalize(); + } + + for (const auto &out_name : uu.params.output_names) { + cv::GMatDesc outm; + if (cv::util::holds_alternative(uu.params.kind)) { + const auto &out = uu.model->output(out_name); + outm = cv::GMatDesc(toCV(out.get_element_type()), + toCV(out.get_shape())); + } else { + GAPI_Assert(cv::util::holds_alternative(uu.params.kind)); + const auto &out = uu.compiled_model.output(out_name); + outm = cv::GMatDesc(toCV(out.get_element_type()), + toCV(out.get_shape())); + } + result.emplace_back(std::move(outm)); + } + + return result; + } + + static void run(std::shared_ptr ctx, + cv::gimpl::ov::RequestPool &reqPool) { + using namespace std::placeholders; + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { + [ctx](::ov::InferRequest &infer_request) { + GAPI_Assert(ctx->uu.params.num_in == 1); + const auto &input_name = ctx->uu.params.input_names[0]; + auto input_tensor = infer_request.get_tensor(input_name); + const auto &shape = input_tensor.get_shape(); + const auto &roi = ctx->inArg(0).rref(); + const auto roi_mat = preprocess(ctx->inMat(1), roi, shape); + copyToOV(roi_mat, input_tensor); + }, + std::bind(PostOutputs, _1, _2, ctx) + } + ); + } +}; + +struct InferList: public cv::detail::KernelTag { + using API = cv::GInferListBase; + static cv::gapi::GBackend backend() { return cv::gapi::ov::backend(); } + static KImpl kernel() { return KImpl{outMeta, run}; } + + static cv::GMetaArgs outMeta(const ade::Graph &gr, + const ade::NodeHandle &nh, + const cv::GMetaArgs &in_metas, + const cv::GArgs &/*in_args*/) { + GConstGOVModel gm(gr); + const auto &uu = gm.metadata(nh).get(); + // Initialize input information + // Note our input layers list order matches the API order and so + // meta order. + GAPI_Assert(uu.params.input_names.size() == (in_metas.size() - 1u) + && "Known input layers count doesn't match input meta count"); + + // NB: Pre/Post processing configuration avaiable only for read models. + if (cv::util::holds_alternative(uu.params.kind)) { + const auto &model_info = cv::util::get(uu.params.kind); + auto& model = const_cast&>(uu.model); + PrePostProcWrapper ppp {model, model_info, + uu.params.input_names, uu.params.output_names}; + + size_t idx = 1u; + for (auto &&input_name : uu.params.input_names) { + const auto &mm = in_metas[idx++]; + GAPI_Assert(cv::util::holds_alternative(mm)); + const auto &matdesc = cv::util::get(mm); + const auto &input_shape = uu.model->input(input_name).get_shape(); + + if (!isImage(matdesc, input_shape)) { + util::throw_error(std::runtime_error( + "OV Backend: Only image is supported" + " as the " + std::to_string(idx) + "th argument for InferList")); + } + + ppp.cfgLayouts(input_name); + ppp.cfgPreProcessing(input_name, mm, true /*disable_img_resize*/); + ppp.cfgScaleMean(input_name); + } + ppp.cfgPostProcessing(); + ppp.finalize(); + } + + // roi-list version is much easier at the moment. + // All our outputs are vectors which don't have + // metadata at the moment - so just create a vector of + // "empty" array metadatas of the required size. + return cv::GMetaArgs(uu.params.output_names.size(), + cv::GMetaArg{cv::empty_array_desc()}); + } + + static void run(std::shared_ptr ctx, + cv::gimpl::ov::RequestPool &reqPool) { + const auto& in_roi_vec = ctx->inArg(0u).rref(); + // NB: In case there is no input data need to post output anyway + if (in_roi_vec.empty()) { + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + auto output = ctx->output(i); + ctx->out.meta(output, ctx->getMeta()); + ctx->out.post(std::move(output)); + } + return; + } + + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + // FIXME: Isn't this should be done automatically + // by some resetInternalData(), etc? (Probably at the GExecutor level) + auto& out_vec = ctx->outVecR(i); + out_vec.clear(); + out_vec.resize(in_roi_vec.size()); + } + + PostOutputsList callback(in_roi_vec.size(), ctx); + for (auto&& it : ade::util::indexed(in_roi_vec)) { + const auto pos = ade::util::index(it); + const auto &rc = ade::util::value(it); + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { + [ctx, rc](::ov::InferRequest &infer_request) { + const auto &input_name = ctx->uu.params.input_names[0]; + auto input_tensor = infer_request.get_tensor(input_name); + const auto &shape = input_tensor.get_shape(); + const auto roi_mat = preprocess(ctx->inMat(1), rc, shape); + copyToOV(roi_mat, input_tensor); + }, + std::bind(callback, std::placeholders::_1, std::placeholders::_2, pos) + } + ); + } + } +}; + +struct InferList2: public cv::detail::KernelTag { + using API = cv::GInferList2Base; + static cv::gapi::GBackend backend() { return cv::gapi::ov::backend(); } + static KImpl kernel() { return KImpl{outMeta, run}; } + + static cv::GMetaArgs outMeta(const ade::Graph &gr, + const ade::NodeHandle &nh, + const cv::GMetaArgs &in_metas, + const cv::GArgs &/*in_args*/) { + GConstGOVModel gm(gr); + const auto &uu = gm.metadata(nh).get(); + // Initialize input information + // Note our input layers list order matches the API order and so + // meta order. + GAPI_Assert(uu.params.input_names.size() == (in_metas.size() - 1u) + && "Known input layers count doesn't match input meta count"); + + const auto &op = gm.metadata(nh).get(); + + // In contrast to InferList, the InferList2 has only one + // "full-frame" image argument, and all the rest are arrays of + // ether ROI or blobs. So here we set the 0th arg image format + // to all inputs which are ROI-based (skipping the + // "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]; + const auto &matdesc = cv::util::get(mm_0); + + const bool is_model = cv::util::holds_alternative(uu.params.kind); + const auto &input_shape = is_model ? uu.model->input(input_name_0).get_shape() + : uu.compiled_model.input(input_name_0).get_shape(); + if (!isImage(matdesc, input_shape)) { + util::throw_error(std::runtime_error( + "OV Backend: InferList2 supports only image as the 0th argument")); + } + + if (is_model) { + const auto &model_info = cv::util::get(uu.params.kind); + auto& model = const_cast&>(uu.model); + PrePostProcWrapper ppp {model, model_info, + uu.params.input_names, uu.params.output_names}; + + size_t idx = 1u; + for (auto &&input_name : uu.params.input_names) { + GAPI_Assert(util::holds_alternative(in_metas[idx]) + && "Non-array inputs are not supported"); + + ppp.cfgLayouts(input_name); + if (op.k.inKinds[idx] == cv::detail::OpaqueKind::CV_RECT) { + ppp.cfgPreProcessing(input_name, mm_0, true /*disable_img_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); + } + + ppp.cfgScaleMean(input_name); + idx++; // NB: Never forget to increment the counter + } + ppp.cfgPostProcessing(); + ppp.finalize(); + } + + // roi-list version is much easier at the moment. + // All our outputs are vectors which don't have + // metadata at the moment - so just create a vector of + // "empty" array metadatas of the required size. + return cv::GMetaArgs(uu.params.output_names.size(), + cv::GMetaArg{cv::empty_array_desc()}); + } + + static void run(std::shared_ptr ctx, + cv::gimpl::ov::RequestPool &reqPool) { + GAPI_Assert(ctx->inArgs().size() > 1u + && "This operation must have at least two arguments"); + // NB: This blob will be used to make roi from its, so + // it should be treated as image + const auto list_size = ctx->inArg(1u).size(); + if (list_size == 0u) { + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + auto output = ctx->output(i); + ctx->out.meta(output, ctx->getMeta()); + ctx->out.post(std::move(output)); + } + return; + } + + for (auto i : ade::util::iota(ctx->uu.params.num_out)) { + // FIXME: Isn't this should be done automatically + // by some resetInternalData(), etc? (Probably at the GExecutor level) + auto& out_vec = ctx->outVecR(i); + out_vec.clear(); + out_vec.resize(list_size); + } + + PostOutputsList callback(list_size, ctx); + for (const auto &list_idx : ade::util::iota(list_size)) { + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { + [ctx, list_idx, list_size](::ov::InferRequest &infer_request) { + for (auto in_idx : ade::util::iota(ctx->uu.params.num_in)) { + const auto &this_vec = ctx->inArg(in_idx+1u); + GAPI_Assert(this_vec.size() == list_size); + const auto &input_name = ctx->uu.params.input_names[in_idx]; + auto input_tensor = infer_request.get_tensor(input_name); + const auto &shape = input_tensor.get_shape(); + if (this_vec.getKind() == cv::detail::OpaqueKind::CV_RECT) { + const auto &vec = this_vec.rref(); + const auto roi_mat = preprocess(ctx->inMat(0), vec[list_idx], shape); + copyToOV(roi_mat, input_tensor); + } else if (this_vec.getKind() == cv::detail::OpaqueKind::CV_MAT) { + const auto &vec = this_vec.rref(); + const auto &mat = vec[list_idx]; + copyToOV(mat, input_tensor); + } else { + GAPI_Assert(false && + "OV Backend: Only Rect and Mat types are supported for InferList2"); + } + } + }, + std::bind(callback, std::placeholders::_1, std::placeholders::_2, list_idx) + } // task + ); + } // for + } +}; + } // namespace ov } // namespace gimpl } // namespace cv @@ -858,7 +1354,10 @@ class GOVBackendImpl final: public cv::gapi::GBackend::Priv { } virtual cv::GKernelPackage auxiliaryKernels() const override { - return cv::gapi::kernels< cv::gimpl::ov::Infer >(); + return cv::gapi::kernels< cv::gimpl::ov::Infer + , cv::gimpl::ov::InferROI + , cv::gimpl::ov::InferList + , cv::gimpl::ov::InferList2 >(); } virtual bool controlsMerge() const override { @@ -904,8 +1403,10 @@ cv::gimpl::ov::GOVExecutable::GOVExecutable(const ade::Graph &g, case NodeType::OP: if (this_nh == nullptr) { this_nh = nh; - compiled = const_cast(ovm.metadata(this_nh).get()).compile(); - m_reqPool.reset(new RequestPool(createInferRequests(compiled.compiled_model, 1))); + const auto &unit = ovm.metadata(this_nh).get(); + compiled = const_cast(unit).compile(); + m_reqPool.reset(new RequestPool(createInferRequests( + compiled.compiled_model, unit.params.nireq))); } else util::throw_error(std::logic_error("Multi-node inference is not supported!")); @@ -937,6 +1438,7 @@ void cv::gimpl::ov::GOVExecutable::run(cv::gimpl::GIslandExecutable::IInput &in if (cv::util::holds_alternative(in_msg)) { + m_reqPool->waitAll(); out.post(cv::gimpl::EndOfStream{}); return; } diff --git a/modules/gapi/src/backends/ov/util.hpp b/modules/gapi/src/backends/ov/util.hpp index ea2aeb60a6..2c769d9ca5 100644 --- a/modules/gapi/src/backends/ov/util.hpp +++ b/modules/gapi/src/backends/ov/util.hpp @@ -22,13 +22,19 @@ 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 to_ocv(const ::ov::Shape &shape); GAPI_EXPORTS int to_ocv(const ::ov::element::Type &type); - -}}}} +GAPI_EXPORTS void to_ov(const cv::Mat &mat, ::ov::Tensor &tensor); +GAPI_EXPORTS void to_ocv(const ::ov::Tensor &tensor, cv::Mat &mat); +} // namespace util +namespace wrap { +GAPI_EXPORTS ::ov::Core getCore(); +} // namespace wrap +} // namespace ov +} // namespace gapi +} // namespace cv #endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000 diff --git a/modules/gapi/src/compiler/gmodelbuilder.cpp b/modules/gapi/src/compiler/gmodelbuilder.cpp index aed3428693..eb807e74cd 100644 --- a/modules/gapi/src/compiler/gmodelbuilder.cpp +++ b/modules/gapi/src/compiler/gmodelbuilder.cpp @@ -59,7 +59,6 @@ private: } // namespace - cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins, const GProtoArgs &outs) { @@ -135,18 +134,19 @@ cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins, // Put the outputs object description of the node // so that they are not lost if they are not consumed by other operations GAPI_Assert(call_p.m_k.outCtors.size() == call_p.m_k.outShapes.size()); - for (const auto it : ade::util::indexed(call_p.m_k.outShapes)) + for (const auto it : ade::util::indexed(ade::util::zip(call_p.m_k.outShapes, + call_p.m_k.outCtors, + call_p.m_k.outKinds))) { - std::size_t port = ade::util::index(it); - GShape shape = ade::util::value(it); - - // FIXME: then use ZIP - HostCtor ctor = call_p.m_k.outCtors[port]; - + auto port = ade::util::index(it); + auto &val = ade::util::value(it); + auto shape = std::get<0>(val); + auto ctor = std::get<1>(val); + auto kind = std::get<2>(val); // NB: Probably this fixes all other "missing host ctor" // problems. // TODO: Clean-up the old workarounds if it really is. - GOrigin org {shape, node, port, std::move(ctor), origin.kind}; + GOrigin org {shape, node, port, std::move(ctor), kind}; origins.insert(org); } diff --git a/modules/gapi/test/infer/gapi_infer_ie_test.cpp b/modules/gapi/test/infer/gapi_infer_ie_test.cpp index 4056dd323f..58e37040e8 100644 --- a/modules/gapi/test/infer/gapi_infer_ie_test.cpp +++ b/modules/gapi/test/infer/gapi_infer_ie_test.cpp @@ -62,6 +62,11 @@ public: return cv::MediaFrame::View(std::move(pp), std::move(ss), Cb{m_cb}); } cv::util::any blobParams() const override { +#if INF_ENGINE_RELEASE > 2023000000 + // NB: blobParams() shouldn't be used in tests + // if OpenVINO versions is higher than 2023.0 + GAPI_Assert(false && "NV12 feature has been deprecated in OpenVINO 1.0 API."); +#else return std::make_pair({IE::Precision::U8, {1, 3, 300, 300}, @@ -69,6 +74,7 @@ public: {{"HELLO", 42}, {"COLOR_FORMAT", InferenceEngine::ColorFormat::NV12}}); +#endif // INF_ENGINE_RELEASE > 2023000000 } }; @@ -138,7 +144,13 @@ void setNetParameters(IE::CNNNetwork& net, bool is_nv12 = false) { ii->setPrecision(IE::Precision::U8); ii->getPreProcess().setResizeAlgorithm(IE::RESIZE_BILINEAR); if (is_nv12) { +#if INF_ENGINE_RELEASE > 2023000000 + // NB: NV12 feature shouldn't be used in tests + // if OpenVINO versions is higher than 2023.0 + GAPI_Assert(false && "NV12 feature has been deprecated in OpenVINO 1.0 API."); +#else ii->getPreProcess().setColorFormat(IE::ColorFormat::NV12); +#endif // INF_ENGINE_RELEASE > 2023000000 } } @@ -392,10 +404,14 @@ struct InferWithReshapeNV12: public InferWithReshape { cv::randu(m_in_y, 0, 255); m_in_uv = cv::Mat{sz / 2, CV_8UC2}; cv::randu(m_in_uv, 0, 255); +// NB: NV12 feature shouldn't be used in tests +// if OpenVINO versions is higher than 2023.0 +#if INF_ENGINE_RELEASE <= 2023000000 setNetParameters(net, true); net.reshape({{"data", reshape_dims}}); auto frame_blob = cv::gapi::ie::util::to_ie(m_in_y, m_in_uv); inferROIs(frame_blob); +#endif // INF_ENGINE_RELEASE <= 2023000000 } }; @@ -505,8 +521,11 @@ struct ROIListNV12: public ::testing::Test { cv::Rect(cv::Point{50, 32}, cv::Size{128, 160}), }; - // Load & run IE network +// NB: NV12 feature shouldn't be used in tests +// if OpenVINO versions is higher than 2023.0 +#if INF_ENGINE_RELEASE <= 2023000000 { + // Load & run IE network auto plugin = cv::gimpl::ie::wrap::getPlugin(params); auto net = cv::gimpl::ie::wrap::readNetwork(params); setNetParameters(net, true); @@ -530,9 +549,11 @@ struct ROIListNV12: public ::testing::Test { m_out_ie_genders.push_back(to_ocv(infer_request.GetBlob("prob")).clone()); } } // namespace IE = .. +#endif // INF_ENGINE_RELEASE <= 2023000000 } // ROIList() void validate() { +#if INF_ENGINE_RELEASE <= 2023000000 // Validate with IE itself (avoid DNN module dependency here) ASSERT_EQ(2u, m_out_ie_ages.size()); ASSERT_EQ(2u, m_out_ie_genders.size()); @@ -543,6 +564,10 @@ struct ROIListNV12: public ::testing::Test { normAssert(m_out_ie_genders[0], m_out_gapi_genders[0], "0: Test gender output"); normAssert(m_out_ie_ages [1], m_out_gapi_ages [1], "1: Test age output"); normAssert(m_out_ie_genders[1], m_out_gapi_genders[1], "1: Test gender output"); +#else + GAPI_Assert(false && "Reference hasn't been calculated because" + " NV12 feature has been deprecated."); +#endif // INF_ENGINE_RELEASE <= 2023000000 } }; @@ -631,6 +656,9 @@ struct SingleROINV12: public ::testing::Test { m_roi = cv::Rect(cv::Point{64, 60}, cv::Size{96, 96}); +// NB: NV12 feature shouldn't be used in tests +// if OpenVINO versions is higher than 2023.0 +#if INF_ENGINE_RELEASE <= 2023000000 // Load & run IE network IE::Blob::Ptr ie_age, ie_gender; { @@ -657,12 +685,18 @@ struct SingleROINV12: public ::testing::Test { m_out_ie_age = to_ocv(infer_request.GetBlob("age_conv3")).clone(); m_out_ie_gender = to_ocv(infer_request.GetBlob("prob")).clone(); } +#endif // INF_ENGINE_RELEASE <= 2023000000 } void validate() { +#if INF_ENGINE_RELEASE <= 2023000000 // Validate with IE itself (avoid DNN module dependency here) normAssert(m_out_ie_age , m_out_gapi_age , "Test age output"); normAssert(m_out_ie_gender, m_out_gapi_gender, "Test gender output"); +#else + GAPI_Assert(false && "Reference hasn't been calculated because" + " NV12 feature has been deprecated."); +#endif } }; @@ -962,11 +996,20 @@ TEST_F(ROIListNV12, MediaInputNV12) auto pp = cv::gapi::ie::Params { params.model_path, params.weights_path, params.device_id }.cfgOutputLayers({ "age_conv3", "prob" }); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, m_roi_list), cv::gout(m_out_gapi_ages, m_out_gapi_genders), cv::compile_args(cv::gapi::networks(pp))); validate(); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, m_roi_list), + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST(TestAgeGenderIE, MediaInputNV12) @@ -986,6 +1029,9 @@ TEST(TestAgeGenderIE, MediaInputNV12) cv::Mat gapi_age, gapi_gender; +// NB: NV12 feature shouldn't be used in tests +// if OpenVINO versions is higher than 2023.0 +#if INF_ENGINE_RELEASE <= 2023000000 // Load & run IE network IE::Blob::Ptr ie_age, ie_gender; { @@ -999,6 +1045,7 @@ TEST(TestAgeGenderIE, MediaInputNV12) ie_age = infer_request.GetBlob("age_conv3"); ie_gender = infer_request.GetBlob("prob"); } +#endif // Configure & run G-API using AGInfo = std::tuple; @@ -1014,13 +1061,20 @@ TEST(TestAgeGenderIE, MediaInputNV12) auto pp = cv::gapi::ie::Params { params.model_path, params.weights_path, params.device_id }.cfgOutputLayers({ "age_conv3", "prob" }); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame), 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"); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST(TestAgeGenderIE, MediaInputBGR) @@ -1155,6 +1209,9 @@ TEST(InferROI, MediaInputNV12) cv::Mat gapi_age, gapi_gender; cv::Rect rect(cv::Point{64, 60}, cv::Size{96, 96}); +// NB: NV12 feature shouldn't be used in tests +// if OpenVINO versions is higher than 2023.0 +#if INF_ENGINE_RELEASE <= 2023000000 // Load & run IE network IE::Blob::Ptr ie_age, ie_gender; { @@ -1176,6 +1233,7 @@ TEST(InferROI, MediaInputNV12) ie_age = infer_request.GetBlob("age_conv3"); ie_gender = infer_request.GetBlob("prob"); } +#endif // Configure & run G-API using AGInfo = std::tuple; @@ -1192,13 +1250,20 @@ TEST(InferROI, MediaInputNV12) auto pp = cv::gapi::ie::Params { params.model_path, params.weights_path, params.device_id }.cfgOutputLayers({ "age_conv3", "prob" }); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, rect), 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"); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, rect), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST_F(ROIList, Infer2MediaInputBGR) @@ -1233,10 +1298,20 @@ TEST_F(ROIListNV12, Infer2MediaInputNV12) auto pp = cv::gapi::ie::Params { params.model_path, params.weights_path, params.device_id }.cfgOutputLayers({ "age_conv3", "prob" }); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, m_roi_list), cv::gout(m_out_gapi_ages, m_out_gapi_genders), cv::compile_args(cv::gapi::networks(pp))); + validate(); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, m_roi_list), + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST_F(SingleROI, GenericInfer) @@ -1310,10 +1385,19 @@ TEST_F(SingleROINV12, GenericInferMediaNV12) pp.cfgNumRequests(2u); auto frame = MediaFrame::Create(m_in_y, m_in_uv); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, m_roi), cv::gout(m_out_gapi_age, m_out_gapi_gender), cv::compile_args(cv::gapi::networks(pp))); validate(); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, m_roi), + cv::gout(m_out_gapi_age, m_out_gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST_F(ROIList, GenericInfer) @@ -1386,11 +1470,20 @@ TEST_F(ROIListNV12, GenericInferMediaNV12) pp.cfgNumRequests(2u); auto frame = MediaFrame::Create(m_in_y, m_in_uv); + + // NB: NV12 feature has been deprecated in OpenVINO versions higher + // than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, m_roi_list), - cv::gout(m_out_gapi_ages, m_out_gapi_genders), - cv::compile_args(cv::gapi::networks(pp))); + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp))); validate(); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, m_roi_list), + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST_F(ROIList, GenericInfer2) @@ -1461,10 +1554,20 @@ TEST_F(ROIListNV12, GenericInfer2MediaInputNV12) pp.cfgNumRequests(2u); auto frame = MediaFrame::Create(m_in_y, m_in_uv); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, m_roi_list), - cv::gout(m_out_gapi_ages, m_out_gapi_genders), - cv::compile_args(cv::gapi::networks(pp))); + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp))); + validate(); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, m_roi_list), + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST(Infer, SetInvalidNumberOfRequests) @@ -2050,11 +2153,20 @@ TEST_F(InferWithReshapeNV12, TestInferListYUV) auto pp = cv::gapi::ie::Params { params.model_path, params.weights_path, params.device_id }.cfgOutputLayers({ "age_conv3", "prob" }).cfgInputReshape({{"data", reshape_dims}}); + +// NB: NV12 feature has been deprecated in OpenVINO versions higher +// than 2023.0 so G-API must throw error in that case. +#if INF_ENGINE_RELEASE <= 2023000000 comp.apply(cv::gin(frame, m_roi_list), cv::gout(m_out_gapi_ages, m_out_gapi_genders), cv::compile_args(cv::gapi::networks(pp))); // Validate validate(); +#else + EXPECT_ANY_THROW(comp.apply(cv::gin(frame, m_roi_list), + cv::gout(m_out_gapi_ages, m_out_gapi_genders), + cv::compile_args(cv::gapi::networks(pp)))); +#endif } TEST_F(ROIList, CallInferMultipleTimes) @@ -2079,6 +2191,7 @@ TEST_F(ROIList, CallInferMultipleTimes) validate(); } +#if INF_ENGINE_RELEASE <= 2023000000 TEST(IEFrameAdapter, blobParams) { cv::Mat bgr = cv::Mat::eye(240, 320, CV_8UC3); @@ -2093,6 +2206,7 @@ TEST(IEFrameAdapter, blobParams) EXPECT_EQ(expected, actual); } +#endif namespace { @@ -2281,6 +2395,10 @@ TEST(TestAgeGenderIE, InferWithBatch) normAssert(cv::gapi::ie::util::to_ocv(ie_gender), gapi_gender, "Test gender output"); } +// NB: All tests below use preprocessing for "Import" networks +// passed as the last argument to SetBLob. This overload has +// been deprecated in OpenVINO 1.0 API. +#if INF_ENGINE_RELEASE <= 2023000000 TEST(ImportNetwork, Infer) { const std::string device = "MYRIAD"; @@ -2820,6 +2938,7 @@ TEST(ImportNetwork, InferList2NV12) normAssert(out_ie_genders[i], out_gapi_genders[i], "Test gender output"); } } +#endif TEST(TestAgeGender, ThrowBlobAndInputPrecisionMismatch) { diff --git a/modules/gapi/test/infer/gapi_infer_ov_tests.cpp b/modules/gapi/test/infer/gapi_infer_ov_tests.cpp index ef63f9e8f6..09b54c1a46 100644 --- a/modules/gapi/test/infer/gapi_infer_ov_tests.cpp +++ b/modules/gapi/test/infer/gapi_infer_ov_tests.cpp @@ -41,20 +41,6 @@ void initDLDTDataPath() 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(tensor.data()), - tensor.get_byte_size(), - mat.ptr()); -} - -void copyToOV(const cv::Mat &mat, ov::Tensor &tensor) { - GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize()); - std::copy_n(mat.ptr(), - tensor.get_byte_size(), - reinterpret_cast(tensor.data())); -} - // FIXME: taken from the DNN module void normAssert(cv::InputArray ref, cv::InputArray test, const char *comment /*= ""*/, @@ -66,15 +52,10 @@ void normAssert(cv::InputArray ref, cv::InputArray test, 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 { +struct AGNetGenParams { static constexpr const char* tag = "age-gender-generic"; using Params = cv::gapi::ov::Params; @@ -88,19 +69,9 @@ struct AGNetGenComp { 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(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 { +struct AGNetTypedParams { using AGInfo = std::tuple; G_API_NET(AgeGender, , "typed-age-gender"); using Params = cv::gapi::ov::Params; @@ -112,7 +83,9 @@ struct AGNetTypedComp { xml_path, bin_path, device }.cfgOutputLayers({ "age_conv3", "prob" }); } +}; +struct AGNetTypedComp : AGNetTypedParams { static cv::GComputation create() { cv::GMat in; cv::GMat age, gender; @@ -121,30 +94,104 @@ struct AGNetTypedComp { } }; +struct AGNetGenComp : public AGNetGenParams { + static cv::GComputation create() { + cv::GMat in; + GInferInputs inputs; + inputs["data"] = in; + auto outputs = cv::gapi::infer(tag, inputs); + auto age = outputs.at("age_conv3"); + auto gender = outputs.at("prob"); + return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)}; + } +}; + +struct AGNetROIGenComp : AGNetGenParams { + static cv::GComputation create() { + cv::GMat in; + cv::GOpaque roi; + GInferInputs inputs; + inputs["data"] = in; + auto outputs = cv::gapi::infer(tag, roi, inputs); + auto age = outputs.at("age_conv3"); + auto gender = outputs.at("prob"); + return cv::GComputation{cv::GIn(in, roi), cv::GOut(age, gender)}; + } +}; + +struct AGNetListGenComp : AGNetGenParams { + static cv::GComputation create() { + cv::GMat in; + cv::GArray rois; + GInferInputs inputs; + inputs["data"] = in; + auto outputs = cv::gapi::infer(tag, rois, inputs); + auto age = outputs.at("age_conv3"); + auto gender = outputs.at("prob"); + return cv::GComputation{cv::GIn(in, rois), cv::GOut(age, gender)}; + } +}; + +struct AGNetList2GenComp : AGNetGenParams { + static cv::GComputation create() { + cv::GMat in; + cv::GArray rois; + GInferListInputs list; + list["data"] = rois; + auto outputs = cv::gapi::infer2(tag, in, list); + auto age = outputs.at("age_conv3"); + auto gender = outputs.at("prob"); + return cv::GComputation{cv::GIn(in, rois), cv::GOut(age, gender)}; + } +}; + class AGNetOVCompiled { public: AGNetOVCompiled(ov::CompiledModel &&compiled_model) - : m_compiled_model(std::move(compiled_model)) { + : m_compiled_model(std::move(compiled_model)), + m_infer_request(m_compiled_model.create_infer_request()) { + } + + void operator()(const cv::Mat &in_mat, + const cv::Rect &roi, + cv::Mat &age_mat, + cv::Mat &gender_mat) { + // FIXME: W & H could be extracted from model shape + // but it's anyway used only for Age Gender model. + // (Well won't work in case of reshape) + const int W = 62; + const int H = 62; + cv::Mat resized_roi; + cv::resize(in_mat(roi), resized_roi, cv::Size(W, H)); + (*this)(resized_roi, age_mat, gender_mat); + } + + void operator()(const cv::Mat &in_mat, + const std::vector &rois, + std::vector &age_mats, + std::vector &gender_mats) { + for (size_t i = 0; i < rois.size(); ++i) { + (*this)(in_mat, rois[i], age_mats[i], gender_mats[i]); + } } 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); + auto input_tensor = m_infer_request.get_input_tensor(); + cv::gapi::ov::util::to_ov(in_mat, input_tensor); - infer_request.infer(); + m_infer_request.infer(); - auto age_tensor = infer_request.get_tensor("age_conv3"); + auto age_tensor = m_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); + cv::gapi::ov::util::to_ocv(age_tensor, age_mat); - auto gender_tensor = infer_request.get_tensor("prob"); + auto gender_tensor = m_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); + cv::gapi::ov::util::to_ocv(gender_tensor, gender_mat); } void export_model(const std::string &outpath) { @@ -155,6 +202,7 @@ public: private: ov::CompiledModel m_compiled_model; + ov::InferRequest m_infer_request; }; struct ImageInputPreproc { @@ -175,7 +223,8 @@ public: const std::string &bin_path, const std::string &device) : m_device(device) { - m_model = getCore().read_model(xml_path, bin_path); + m_model = cv::gapi::ov::wrap::getCore() + .read_model(xml_path, bin_path); } using PrePostProcessF = std::function; @@ -187,7 +236,8 @@ public: } AGNetOVCompiled compile() { - auto compiled_model = getCore().compile_model(m_model, m_device); + auto compiled_model = cv::gapi::ov::wrap::getCore() + .compile_model(m_model, m_device); return {std::move(compiled_model)}; } @@ -202,19 +252,78 @@ private: std::shared_ptr m_model; }; -} // anonymous namespace +struct BaseAgeGenderOV: public ::testing::Test { + BaseAgeGenderOV() { + initDLDTDataPath(); + xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + device = "CPU"; + blob_path = "age-gender-recognition-retail-0013.blob"; + } -// 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 getRandomImage(const cv::Size &sz) { + cv::Mat image(sz, CV_8UC3); + cv::randu(image, 0, 255); + return image; + } + + cv::Mat getRandomTensor(const std::vector &dims, + const int depth) { + cv::Mat tensor(dims, depth); + cv::randu(tensor, -1, 1); + return tensor; + } + + std::string xml_path; + std::string bin_path; + std::string blob_path; + std::string device; + +}; + +struct TestAgeGenderOV : public BaseAgeGenderOV { cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; + void validate() { + normAssert(ov_age, gapi_age, "Test age output" ); + normAssert(ov_gender, gapi_gender, "Test gender output"); + } +}; + +struct TestAgeGenderListOV : public BaseAgeGenderOV { + std::vector ov_age, ov_gender, + gapi_age, gapi_gender; + + std::vector roi_list = { + cv::Rect(cv::Point{64, 60}, cv::Size{ 96, 96}), + cv::Rect(cv::Point{50, 32}, cv::Size{128, 160}), + }; + + TestAgeGenderListOV() { + ov_age.resize(roi_list.size()); + ov_gender.resize(roi_list.size()); + gapi_age.resize(roi_list.size()); + gapi_gender.resize(roi_list.size()); + } + + void validate() { + ASSERT_EQ(ov_age.size(), ov_gender.size()); + + ASSERT_EQ(ov_age.size(), gapi_age.size()); + ASSERT_EQ(ov_gender.size(), gapi_gender.size()); + + for (size_t i = 0; i < ov_age.size(); ++i) { + normAssert(ov_age[i], gapi_age[i], "Test age output"); + normAssert(ov_gender[i], gapi_gender[i], "Test gender output"); + } + } +}; + +} // anonymous namespace + +// TODO: Make all of tests below parmetrized to avoid code duplication +TEST_F(TestAgeGenderOV, Infer_Tensor) { + const auto in_mat = getRandomTensor({1, 3, 62, 62}, CV_32F); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); ref.apply(in_mat, ov_age, ov_gender); @@ -226,19 +335,11 @@ TEST(TestAgeGenderOV, InferTypedTensor) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, Infer_Image) { + const auto in_mat = getRandomImage({300, 300}); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -252,19 +353,11 @@ TEST(TestAgeGenderOV, InferTypedImage) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, InferGeneric_Tensor) { + const auto in_mat = getRandomTensor({1, 3, 62, 62}, CV_32F); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -277,19 +370,11 @@ TEST(TestAgeGenderOV, InferGenericTensor) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, InferGenericImage) { + const auto in_mat = getRandomImage({300, 300}); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -303,20 +388,11 @@ TEST(TestAgeGenderOV, InferGenericImage) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, InferGeneric_ImageBlob) { + const auto in_mat = getRandomImage({300, 300}); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -333,20 +409,11 @@ TEST(TestAgeGenderOV, InferGenericImageBlob) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, InferGeneric_TensorBlob) { + const auto in_mat = getRandomTensor({1, 3, 62, 62}, CV_32F); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -361,19 +428,11 @@ TEST(TestAgeGenderOV, InferGenericTensorBlob) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, InferGeneric_BothOutputsFP16) { + const auto in_mat = getRandomTensor({1, 3, 62, 62}, CV_32F); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -392,19 +451,11 @@ TEST(TestAgeGenderOV, InferBothOutputsFP16) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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; +TEST_F(TestAgeGenderOV, InferGeneric_OneOutputFP16) { + const auto in_mat = getRandomTensor({1, 3, 62, 62}, CV_32F); // OpenVINO const std::string fp16_output_name = "prob"; @@ -423,17 +474,10 @@ TEST(TestAgeGenderOV, InferOneOutputFP16) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); } -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"; - +TEST_F(TestAgeGenderOV, InferGeneric_ThrowCfgOutputPrecForBlob) { // OpenVINO (Just for blob compilation) AGNetOVComp ref(xml_path, bin_path, device); auto cc_ref = ref.compile(); @@ -446,12 +490,7 @@ TEST(TestAgeGenderOV, ThrowCfgOutputPrecForBlob) { 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"; - +TEST_F(TestAgeGenderOV, InferGeneric_ThrowInvalidConfigIR) { // G-API auto comp = AGNetGenComp::create(); auto pp = AGNetGenComp::params(xml_path, bin_path, device); @@ -461,13 +500,7 @@ TEST(TestAgeGenderOV, ThrowInvalidConfigIR) { 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"; - +TEST_F(TestAgeGenderOV, InferGeneric_ThrowInvalidConfigBlob) { // OpenVINO (Just for blob compilation) AGNetOVComp ref(xml_path, bin_path, device); auto cc_ref = ref.compile(); @@ -482,16 +515,8 @@ TEST(TestAgeGenderOV, ThrowInvalidConfigBlob) { 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; +TEST_F(TestAgeGenderOV, Infer_ThrowInvalidImageLayout) { + const auto in_mat = getRandomImage({300, 300}); auto comp = AGNetTypedComp::create(); auto pp = AGNetTypedComp::params(xml_path, bin_path, device); @@ -501,15 +526,8 @@ TEST(TestAgeGenderOV, ThrowInvalidImageLayout) { 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; +TEST_F(TestAgeGenderOV, Infer_TensorWithPreproc) { + const auto in_mat = getRandomTensor({1, 240, 320, 3}, CV_32F); // OpenVINO AGNetOVComp ref(xml_path, bin_path, device); @@ -531,8 +549,112 @@ TEST(TestAgeGenderOV, InferTensorWithPreproc) { cv::compile_args(cv::gapi::networks(pp))); // Assert - normAssert(ov_age, gapi_age, "Test age output" ); - normAssert(ov_gender, gapi_gender, "Test gender output"); + validate(); +} + +TEST_F(TestAgeGenderOV, InferROIGeneric_Image) { + const auto in_mat = getRandomImage({300, 300}); + cv::Rect roi(cv::Rect(cv::Point{64, 60}, cv::Size{96, 96})); + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp) { + ppp.input().tensor().set_element_type(ov::element::u8); + ppp.input().tensor().set_layout("NHWC"); + }); + ref.compile()(in_mat, roi, ov_age, ov_gender); + + // G-API + auto comp = AGNetROIGenComp::create(); + auto pp = AGNetROIGenComp::params(xml_path, bin_path, device); + + comp.apply(cv::gin(in_mat, roi), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + validate(); +} + +TEST_F(TestAgeGenderOV, InferROIGeneric_ThrowIncorrectLayout) { + const auto in_mat = getRandomImage({300, 300}); + cv::Rect roi(cv::Rect(cv::Point{64, 60}, cv::Size{96, 96})); + + // G-API + auto comp = AGNetROIGenComp::create(); + auto pp = AGNetROIGenComp::params(xml_path, bin_path, device); + + pp.cfgInputTensorLayout("NCHW"); + EXPECT_ANY_THROW(comp.apply(cv::gin(in_mat, roi), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST_F(TestAgeGenderOV, InferROIGeneric_ThrowTensorInput) { + const auto in_mat = getRandomTensor({1, 3, 62, 62}, CV_32F); + cv::Rect roi(cv::Rect(cv::Point{64, 60}, cv::Size{96, 96})); + + // G-API + auto comp = AGNetROIGenComp::create(); + auto pp = AGNetROIGenComp::params(xml_path, bin_path, device); + + EXPECT_ANY_THROW(comp.apply(cv::gin(in_mat, roi), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST_F(TestAgeGenderOV, InferROIGeneric_ThrowExplicitResize) { + const auto in_mat = getRandomImage({300, 300}); + cv::Rect roi(cv::Rect(cv::Point{64, 60}, cv::Size{96, 96})); + + // G-API + auto comp = AGNetROIGenComp::create(); + auto pp = AGNetROIGenComp::params(xml_path, bin_path, device); + + pp.cfgResize(cv::INTER_LINEAR); + EXPECT_ANY_THROW(comp.apply(cv::gin(in_mat, roi), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST_F(TestAgeGenderListOV, InferListGeneric_Image) { + const auto in_mat = getRandomImage({300, 300}); + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp) { + ppp.input().tensor().set_element_type(ov::element::u8); + ppp.input().tensor().set_layout("NHWC"); + }); + ref.compile()(in_mat, roi_list, ov_age, ov_gender); + + // G-API + auto comp = AGNetListGenComp::create(); + auto pp = AGNetListGenComp::params(xml_path, bin_path, device); + + comp.apply(cv::gin(in_mat, roi_list), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + validate(); +} + +TEST_F(TestAgeGenderListOV, InferList2Generic_Image) { + const auto in_mat = getRandomImage({300, 300}); + + // OpenVINO + AGNetOVComp ref(xml_path, bin_path, device); + ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp) { + ppp.input().tensor().set_element_type(ov::element::u8); + ppp.input().tensor().set_layout("NHWC"); + }); + ref.compile()(in_mat, roi_list, ov_age, ov_gender); + + // G-API + auto comp = AGNetList2GenComp::create(); + auto pp = AGNetList2GenComp::params(xml_path, bin_path, device); + + comp.apply(cv::gin(in_mat, roi_list), cv::gout(gapi_age, gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + // Assert + validate(); } } // namespace opencv_test diff --git a/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp b/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp index 4291fd00e9..74de7aaf59 100644 --- a/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp +++ b/modules/gapi/test/internal/gapi_int_gmodel_builder_test.cpp @@ -30,7 +30,8 @@ namespace , nullptr , { GShape::GMAT } , { D::OpaqueKind::CV_UNKNOWN } - , { cv::detail::HostCtor{cv::util::monostate{}} } + , { D::HostCtor{cv::util::monostate{}} } + , { D::OpaqueKind::CV_UNKNOWN } }).pass(m).yield(0); } @@ -41,7 +42,8 @@ namespace , nullptr , { GShape::GMAT } , { D::OpaqueKind::CV_UNKNOWN, D::OpaqueKind::CV_UNKNOWN } - , { cv::detail::HostCtor{cv::util::monostate{}} } + , { D::HostCtor{cv::util::monostate{}} } + , { D::OpaqueKind::CV_UNKNOWN} }).pass(m1, m2).yield(0); } diff --git a/modules/highgui/src/window_wayland.cpp b/modules/highgui/src/window_wayland.cpp index a18ea6a72d..109cd795b6 100644 --- a/modules/highgui/src/window_wayland.cpp +++ b/modules/highgui/src/window_wayland.cpp @@ -1055,7 +1055,7 @@ void cv_wl_keyboard::handle_kb_keymap(void *data, struct wl_keyboard *kb, uint32 } catch (std::exception &e) { if (keyboard->xkb_.keymap) xkb_keymap_unref(keyboard->xkb_.keymap); - std::cerr << "OpenCV Error: " << e.what() << std::endl; + CV_LOG_ERROR(NULL, "OpenCV Error: " << e.what()); } close(fd); diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index c1bdf72291..5e201b52fb 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -95,11 +95,11 @@ enum ImwriteFlags { IMWRITE_PNG_STRATEGY = 17, //!< One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_RLE. IMWRITE_PNG_BILEVEL = 18, //!< Binary level PNG, 0 or 1, default is 0. IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1. - IMWRITE_EXR_TYPE = (3 << 4) + 0, /* 48 */ //!< override EXR storage type (FLOAT (FP32) is default) - IMWRITE_EXR_COMPRESSION = (3 << 4) + 1, /* 49 */ //!< override EXR compression type (ZIP_COMPRESSION = 3 is default) - IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2, /* 50 */ //!< override EXR DWA compression level (45 is default) + IMWRITE_EXR_TYPE = (3 << 4) + 0 /* 48 */, //!< override EXR storage type (FLOAT (FP32) is default) + IMWRITE_EXR_COMPRESSION = (3 << 4) + 1 /* 49 */, //!< override EXR compression type (ZIP_COMPRESSION = 3 is default) + IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2 /* 50 */, //!< override EXR DWA compression level (45 is default) IMWRITE_WEBP_QUALITY = 64, //!< For WEBP, it can be a quality from 1 to 100 (the higher is the better). By default (without any parameter) and for quality above 100 the lossless compression is used. - IMWRITE_HDR_COMPRESSION = (5 << 4) + 0, /* 80 */ //!< specify HDR compression + IMWRITE_HDR_COMPRESSION = (5 << 4) + 0 /* 80 */, //!< specify HDR compression IMWRITE_PAM_TUPLETYPE = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index e8d1446cbe..b2830b755a 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -148,11 +148,14 @@ AvifDecoder::~AvifDecoder() { size_t AvifDecoder::signatureLength() const { return kAvifSignatureSize; } bool AvifDecoder::checkSignature(const String &signature) const { - avifDecoderSetIOMemory(decoder_, + avifDecoder *decoder = avifDecoderCreate(); + if (!decoder) return false; + avifDecoderSetIOMemory(decoder, reinterpret_cast(signature.c_str()), signature.size()); - decoder_->io->sizeHint = 1e9; - const avifResult status = avifDecoderParse(decoder_); + decoder->io->sizeHint = 1e9; + const avifResult status = avifDecoderParse(decoder); + avifDecoderDestroy(decoder); return (status == AVIF_RESULT_OK || status == AVIF_RESULT_TRUNCATED_DATA); } diff --git a/modules/imgcodecs/src/grfmt_pxm.cpp b/modules/imgcodecs/src/grfmt_pxm.cpp index 8da2348728..76290c43de 100644 --- a/modules/imgcodecs/src/grfmt_pxm.cpp +++ b/modules/imgcodecs/src/grfmt_pxm.cpp @@ -43,7 +43,7 @@ #include "precomp.hpp" #include "utils.hpp" #include "grfmt_pxm.hpp" -#include +#include #ifdef HAVE_IMGCODEC_PXM @@ -191,7 +191,7 @@ bool PxMDecoder::readHeader() } catch (...) { - std::cerr << "PXM::readHeader(): unknown C++ exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "PXM::readHeader(): unknown C++ exception"); throw; } @@ -364,7 +364,7 @@ bool PxMDecoder::readData( Mat& img ) } catch (...) { - std::cerr << "PXM::readData(): unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "PXM::readData(): unknown exception"); throw; } diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index a1f49a882c..617463c339 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -437,12 +437,12 @@ imread_( const String& filename, int flags, Mat& mat ) } catch (const cv::Exception& e) { - std::cerr << "imread_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imread_('" << filename << "'): can't read header: " << e.what()); return 0; } catch (...) { - std::cerr << "imread_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imread_('" << filename << "'): can't read header: unknown exception"); return 0; } @@ -475,11 +475,11 @@ imread_( const String& filename, int flags, Mat& mat ) } catch (const cv::Exception& e) { - std::cerr << "imread_('" << filename << "'): can't read data: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imread_('" << filename << "'): can't read data: " << e.what()); } catch (...) { - std::cerr << "imread_('" << filename << "'): can't read data: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imread_('" << filename << "'): can't read data: unknown exception"); } if (!success) { @@ -542,12 +542,12 @@ imreadmulti_(const String& filename, int flags, std::vector& mats, int star } catch (const cv::Exception& e) { - std::cerr << "imreadmulti_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read header: " << e.what()); return 0; } catch (...) { - std::cerr << "imreadmulti_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read header: unknown exception"); return 0; } @@ -591,11 +591,11 @@ imreadmulti_(const String& filename, int flags, std::vector& mats, int star } catch (const cv::Exception& e) { - std::cerr << "imreadmulti_('" << filename << "'): can't read data: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read data: " << e.what()); } catch (...) { - std::cerr << "imreadmulti_('" << filename << "'): can't read data: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read data: unknown exception"); } if (!success) break; @@ -672,7 +672,7 @@ size_t imcount_(const String& filename, int flags) return collection.size(); } catch(cv::Exception const& e) { // Reading header or finding decoder for the filename is failed - std::cerr << "imcount_('" << filename << "'): can't read header or can't find decoder: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imcount_('" << filename << "'): can't read header or can't find decoder: " << e.what()); } return 0; } @@ -750,14 +750,13 @@ static bool imwrite_( const String& filename, const std::vector& img_vec, } catch (const cv::Exception& e) { - std::cerr << "imwrite_('" << filename << "'): can't write data: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imwrite_('" << filename << "'): can't write data: " << e.what()); } catch (...) { - std::cerr << "imwrite_('" << filename << "'): can't write data: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imwrite_('" << filename << "'): can't write data: unknown exception"); } - // CV_Assert( code ); return code; } @@ -833,11 +832,11 @@ imdecode_( const Mat& buf, int flags, Mat& mat ) } catch (const cv::Exception& e) { - std::cerr << "imdecode_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imdecode_('" << filename << "'): can't read header: " << e.what()); } catch (...) { - std::cerr << "imdecode_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imdecode_('" << filename << "'): can't read header: unknown exception"); } if (!success) { @@ -846,7 +845,7 @@ imdecode_( const Mat& buf, int flags, Mat& mat ) { if (0 != remove(filename.c_str())) { - std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush; + CV_LOG_WARNING(NULL, "unable to remove temporary file:" << filename); } } return 0; @@ -878,18 +877,18 @@ imdecode_( const Mat& buf, int flags, Mat& mat ) } catch (const cv::Exception& e) { - std::cerr << "imdecode_('" << filename << "'): can't read data: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imdecode_('" << filename << "'): can't read data: " << e.what()); } catch (...) { - std::cerr << "imdecode_('" << filename << "'): can't read data: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imdecode_('" << filename << "'): can't read data: unknown exception"); } if (!filename.empty()) { if (0 != remove(filename.c_str())) { - std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush; + CV_LOG_WARNING(NULL, "unable to remove temporary file: " << filename); } } @@ -982,11 +981,11 @@ imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int } catch (const cv::Exception& e) { - std::cerr << "imreadmulti_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read header: " << e.what()); } catch (...) { - std::cerr << "imreadmulti_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read header: unknown exception"); } int current = start; @@ -1007,7 +1006,7 @@ imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int { if (0 != remove(filename.c_str())) { - std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush; + CV_LOG_WARNING(NULL, "unable to remove temporary file: " << filename); } } return 0; @@ -1042,11 +1041,11 @@ imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int } catch (const cv::Exception& e) { - std::cerr << "imreadmulti_('" << filename << "'): can't read data: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read data: " << e.what()); } catch (...) { - std::cerr << "imreadmulti_('" << filename << "'): can't read data: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "imreadmulti_('" << filename << "'): can't read data: unknown exception"); } if (!success) break; @@ -1069,7 +1068,7 @@ imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int { if (0 != remove(filename.c_str())) { - std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush; + CV_LOG_WARNING(NULL, "unable to remove temporary file: " << filename); } } @@ -1280,10 +1279,10 @@ Mat ImageCollection::Impl::readData() { success = true; } catch (const cv::Exception &e) { - std::cerr << "ImageCollection class: can't read data: " << e.what() << std::endl << std::flush; + CV_LOG_ERROR(NULL, "ImageCollection class: can't read data: " << e.what()); } catch (...) { - std::cerr << "ImageCollection class:: can't read data: unknown exception" << std::endl << std::flush; + CV_LOG_ERROR(NULL, "ImageCollection class:: can't read data: unknown exception"); } if (!success) return cv::Mat(); diff --git a/modules/imgproc/src/demosaicing.cpp b/modules/imgproc/src/demosaicing.cpp index 27dfc1520c..627c052aea 100644 --- a/modules/imgproc/src/demosaicing.cpp +++ b/modules/imgproc/src/demosaicing.cpp @@ -184,9 +184,9 @@ public: for( ; bayer <= bayer_end - 18; bayer += 14, dst += 14 ) { - v_uint16x8 r0 = v_load((ushort*)bayer); - v_uint16x8 r1 = v_load((ushort*)(bayer+bayer_step)); - v_uint16x8 r2 = v_load((ushort*)(bayer+bayer_step*2)); + v_uint16x8 r0 = v_reinterpret_as_u16(v_load(bayer)); + v_uint16x8 r1 = v_reinterpret_as_u16(v_load(bayer+bayer_step)); + v_uint16x8 r2 = v_reinterpret_as_u16(v_load(bayer+bayer_step*2)); v_uint16x8 b1 = ((r0 << 8) >> 7) + ((r2 << 8) >> 7); v_uint16x8 b0 = v_rotate_right<1>(b1) + b1; @@ -265,9 +265,9 @@ public: for( ; bayer <= bayer_end - 18; bayer += 14, dst += 42 ) { - v_uint16x8 r0 = v_load((ushort*)bayer); - v_uint16x8 r1 = v_load((ushort*)(bayer+bayer_step)); - v_uint16x8 r2 = v_load((ushort*)(bayer+bayer_step*2)); + v_uint16x8 r0 = v_reinterpret_as_u16(v_load(bayer)); + v_uint16x8 r1 = v_reinterpret_as_u16(v_load(bayer+bayer_step)); + v_uint16x8 r2 = v_reinterpret_as_u16(v_load(bayer+bayer_step*2)); v_uint16x8 b1 = (r0 & masklo) + (r2 & masklo); v_uint16x8 nextb1 = v_rotate_right<1>(b1); @@ -398,9 +398,9 @@ public: for( ; bayer <= bayer_end - 18; bayer += 14, dst += 56 ) { - v_uint16x8 r0 = v_load((ushort*)bayer); - v_uint16x8 r1 = v_load((ushort*)(bayer+bayer_step)); - v_uint16x8 r2 = v_load((ushort*)(bayer+bayer_step*2)); + v_uint16x8 r0 = v_reinterpret_as_u16(v_load(bayer)); + v_uint16x8 r1 = v_reinterpret_as_u16(v_load(bayer+bayer_step)); + v_uint16x8 r2 = v_reinterpret_as_u16(v_load(bayer+bayer_step*2)); v_uint16x8 b1 = (r0 & masklo) + (r2 & masklo); v_uint16x8 nextb1 = v_rotate_right<1>(b1); @@ -494,9 +494,9 @@ public: B G B G | B G B G | B G B G | B G B G */ - v_uint16x8 r0 = v_load((ushort*)bayer); - v_uint16x8 r1 = v_load((ushort*)(bayer+bayer_step)); - v_uint16x8 r2 = v_load((ushort*)(bayer+bayer_step*2)); + v_uint16x8 r0 = v_reinterpret_as_u16(v_load(bayer)); + v_uint16x8 r1 = v_reinterpret_as_u16(v_load(bayer+bayer_step)); + v_uint16x8 r2 = v_reinterpret_as_u16(v_load(bayer+bayer_step*2)); v_uint16x8 b1 = (r0 & masklow) + (r2 & masklow); v_uint16x8 nextb1 = v_rotate_right<1>(b1); diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index 8eb62deb14..57940935d4 100755 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -448,7 +448,7 @@ static void getDistanceTransformMask( int maskType, float *metrics ) struct DTColumnInvoker : ParallelLoopBody { - DTColumnInvoker( const Mat* _src, Mat* _dst, const int* _sat_tab, const float* _sqr_tab) + DTColumnInvoker( const Mat* _src, Mat* _dst, const int* _sat_tab, const int* _sqr_tab) { src = _src; dst = _dst; @@ -481,7 +481,7 @@ struct DTColumnInvoker : ParallelLoopBody { dist = dist + 1 - sat_tab[dist - d[j]]; d[j] = dist; - dptr[0] = sqr_tab[dist]; + dptr[0] = (float)sqr_tab[dist]; } } } @@ -489,12 +489,12 @@ struct DTColumnInvoker : ParallelLoopBody const Mat* src; Mat* dst; const int* sat_tab; - const float* sqr_tab; + const int* sqr_tab; }; struct DTRowInvoker : ParallelLoopBody { - DTRowInvoker( Mat* _dst, const float* _sqr_tab, const float* _inv_tab ) + DTRowInvoker( Mat* _dst, const int* _sqr_tab, const float* _inv_tab ) { dst = _dst; sqr_tab = _sqr_tab; @@ -529,7 +529,7 @@ struct DTRowInvoker : ParallelLoopBody for(;;k--) { p = v[k]; - float s = (fq + sqr_tab[q] - d[p] - sqr_tab[p])*inv_tab[q - p]; + float s = (fq - d[p] + (sqr_tab[q]-sqr_tab[p]))*inv_tab[q - p]; if( s > z[k] ) { k++; @@ -552,28 +552,28 @@ struct DTRowInvoker : ParallelLoopBody } Mat* dst; - const float* sqr_tab; + const int* sqr_tab; const float* inv_tab; }; static void trueDistTrans( const Mat& src, Mat& dst ) { - const float inf = 1e15f; + const int inf = INT_MAX; CV_Assert( src.size() == dst.size() ); CV_Assert( src.type() == CV_8UC1 && dst.type() == CV_32FC1 ); int i, m = src.rows, n = src.cols; - cv::AutoBuffer _buf(std::max(m*2*sizeof(float) + (m*3+1)*sizeof(int), n*2*sizeof(float))); + cv::AutoBuffer _buf(std::max(m*2*sizeof(int) + (m*3+1)*sizeof(int), n*2*sizeof(float))); // stage 1: compute 1d distance transform of each column - float* sqr_tab = (float*)_buf.data(); + int* sqr_tab = (int*)_buf.data(); int* sat_tab = cv::alignPtr((int*)(sqr_tab + m*2), sizeof(int)); int shift = m*2; for( i = 0; i < m; i++ ) - sqr_tab[i] = (float)(i*i); + sqr_tab[i] = i*i; for( i = m; i < m*2; i++ ) sqr_tab[i] = inf; for( i = 0; i < shift; i++ ) @@ -584,13 +584,14 @@ trueDistTrans( const Mat& src, Mat& dst ) cv::parallel_for_(cv::Range(0, n), cv::DTColumnInvoker(&src, &dst, sat_tab, sqr_tab), src.total()/(double)(1<<16)); // stage 2: compute modified distance transform for each row - float* inv_tab = sqr_tab + n; + float* inv_tab = (float*)sqr_tab + n; - inv_tab[0] = sqr_tab[0] = 0.f; + inv_tab[0] = 0.f; + sqr_tab[0] = 0; for( i = 1; i < n; i++ ) { inv_tab[i] = (float)(0.5/i); - sqr_tab[i] = (float)(i*i); + sqr_tab[i] = i*i; } cv::parallel_for_(cv::Range(0, m), cv::DTRowInvoker(&dst, sqr_tab, inv_tab)); @@ -750,7 +751,9 @@ void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labe CV_IPP_CHECK() { #if IPP_DISABLE_PERF_TRUE_DIST_MT - if(cv::getNumThreads()<=1 || (src.total()<(int)(1<<14))) + // IPP uses floats, but 4097 cannot be squared into a float + if((cv::getNumThreads()<=1 || (src.total()<(int)(1<<14))) && + src.rows < 4097 && src.cols < 4097) #endif { IppStatus status; diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp old mode 100755 new mode 100644 index efe633e228..dae4c71bf2 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -939,6 +939,7 @@ void ellipse2Poly( Point center, Size axes, int angle, } // If there are no points, it's a zero-size polygon + CV_Assert( !pts.empty() ); if (pts.size() == 1) { pts.assign(2, center); } @@ -1001,6 +1002,7 @@ void ellipse2Poly( Point2d center, Size2d axes, int angle, } // If there are no points, it's a zero-size polygon + CV_Assert( !pts.empty() ); if( pts.size() == 1) { pts.assign(2,center); } @@ -1021,7 +1023,6 @@ EllipseEx( Mat& img, Point2l center, Size2l axes, std::vector v; Point2l prevPt(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF); - v.resize(0); for (unsigned int i = 0; i < _v.size(); ++i) { Point2l pt; @@ -1036,7 +1037,7 @@ EllipseEx( Mat& img, Point2l center, Size2l axes, } // If there are no points, it's a zero-size polygon - if (v.size() == 1) { + if (v.size() <= 1) { v.assign(2, center); } @@ -1556,7 +1557,7 @@ Circle( Mat& img, Point center, int radius, const void* color, int fill ) ICV_HLINE( tptr1, x21, x22, color, pix_size ); } } - else if( x11 < size.width && x12 >= 0 && y21 < size.height && y22 >= 0 ) + else if( x11 < size.width && x12 >= 0 && y21 < size.height && y22 >= 0) { if( fill ) { @@ -1564,7 +1565,7 @@ Circle( Mat& img, Point center, int radius, const void* color, int fill ) x12 = MIN( x12, size.width - 1 ); } - if( (unsigned)y11 < (unsigned)size.height ) + if( y11 >= 0 && y11 < size.height ) { uchar *tptr = ptr + y11 * step; @@ -1579,7 +1580,7 @@ Circle( Mat& img, Point center, int radius, const void* color, int fill ) ICV_HLINE( tptr, x11, x12, color, pix_size ); } - if( (unsigned)y12 < (unsigned)size.height ) + if( y12 >= 0 && y12 < size.height ) { uchar *tptr = ptr + y12 * step; @@ -1602,7 +1603,7 @@ Circle( Mat& img, Point center, int radius, const void* color, int fill ) x22 = MIN( x22, size.width - 1 ); } - if( (unsigned)y21 < (unsigned)size.height ) + if( y21 >= 0 && y21 < size.height ) { uchar *tptr = ptr + y21 * step; @@ -1617,7 +1618,7 @@ Circle( Mat& img, Point center, int radius, const void* color, int fill ) ICV_HLINE( tptr, x21, x22, color, pix_size ); } - if( (unsigned)y22 < (unsigned)size.height ) + if( y22 >= 0 && y22 < size.height ) { uchar *tptr = ptr + y22 * step; diff --git a/modules/imgproc/src/floodfill.cpp b/modules/imgproc/src/floodfill.cpp index 8877cae9b6..11e44cb95e 100644 --- a/modules/imgproc/src/floodfill.cpp +++ b/modules/imgproc/src/floodfill.cpp @@ -560,8 +560,15 @@ int cv::floodFill( InputOutputArray _image, InputOutputArray _mask, if( depth == CV_8U ) for( i = 0; i < cn; i++ ) { +#if defined(__GNUC__) && (__GNUC__ == 12) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif ld_buf.b[i] = saturate_cast(cvFloor(loDiff[i])); ud_buf.b[i] = saturate_cast(cvFloor(upDiff[i])); +#if defined(__GNUC__) && (__GNUC__ == 12) +#pragma GCC diagnostic pop +#endif } else if( depth == CV_32S ) for( i = 0; i < cn; i++ ) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 20ac611086..2164639127 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -757,10 +757,34 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, } else { - if( borderType == BORDER_TRANSPARENT && cn != 3 ) - { - D += (X1 - dx)*cn; - dx = X1; + if (borderType == BORDER_TRANSPARENT) { + for (; dx < X1; dx++, D += cn) { + if (dx >= dsize.width) continue; + const int sx = XY[dx * 2], sy = XY[dx * 2 + 1]; + // If the mapped point is still within bounds, it did not get computed + // because it lacked 4 neighbors. Still, it can be computed with an + // approximate formula. If it is outside, the point is left untouched. + if (sx >= 0 && sx <= ssize.width - 1 && sy >= 0 && sy <= ssize.height - 1) { + const AT* w = wtab + FXY[dx] * 4; + WT w_tot = 0; + if (sx >= 0 && sy >= 0) w_tot += w[0]; + if (sy >= 0 && sx < ssize.width - 1) w_tot += w[1]; + if (sx >= 0 && sy < ssize.height - 1) w_tot += w[2]; + if (sx < ssize.width - 1 && sy < ssize.height - 1) w_tot += w[3]; + if (w_tot == 0.f) continue; + const WT w_tot_ini = (WT)w[0] + w[1] + w[2] + w[3]; + const T* S = S0 + sy * sstep + sx * cn; + for (int k = 0; k < cn; k++) { + WT t0 = 0; + if (sx >= 0 && sy >= 0) t0 += S[k] * w[0]; + if (sy >= 0 && sx < ssize.width - 1) t0 += S[k + cn] * w[1]; + if (sx >= 0 && sy < ssize.height - 1) t0 += S[sstep + k] * w[2]; + if (sx < ssize.width - 1 && sy < ssize.height - 1) t0 += S[sstep + k + cn] * w[3]; + t0 = (WT)(t0 * (float)w_tot_ini / w_tot); + D[k] = castOp(t0); + } + } + } continue; } @@ -831,10 +855,6 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, v2 = S0 + sy1*sstep + sx0*cn; v3 = S0 + sy1*sstep + sx1*cn; } - else if( borderType == BORDER_TRANSPARENT && - ((unsigned)sx >= (unsigned)(ssize.width-1) || - (unsigned)sy >= (unsigned)(ssize.height-1))) - continue; else { sx0 = borderInterpolate(sx, ssize.width, borderType); diff --git a/modules/imgproc/test/test_distancetransform.cpp b/modules/imgproc/test/test_distancetransform.cpp index 8ebe8fde02..e8b9a8cb06 100644 --- a/modules/imgproc/test/test_distancetransform.cpp +++ b/modules/imgproc/test/test_distancetransform.cpp @@ -62,4 +62,46 @@ BIGDATA_TEST(Imgproc_DistanceTransform, large_image_12218) EXPECT_EQ(nz, (size.height*size.width / 2)); } +TEST(Imgproc_DistanceTransform, wide_image_22732) +{ + Mat src = Mat::zeros(1, 4099, CV_8U); // 4099 or larger used to be bad + Mat dist(src.rows, src.cols, CV_32F); + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE, CV_32F); + int nz = countNonZero(dist); + EXPECT_EQ(nz, 0); +} + +TEST(Imgproc_DistanceTransform, large_square_22732) +{ + Mat src = Mat::zeros(8000, 8005, CV_8U), dist; + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE, CV_32F); + int nz = countNonZero(dist); + EXPECT_EQ(dist.size(), src.size()); + EXPECT_EQ(dist.type(), CV_32F); + EXPECT_EQ(nz, 0); + + Point p0(src.cols-1, src.rows-1); + src.setTo(1); + src.at(p0) = 0; + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE, CV_32F); + EXPECT_EQ(dist.size(), src.size()); + EXPECT_EQ(dist.type(), CV_32F); + bool first = true; + int nerrs = 0; + for (int y = 0; y < dist.rows; y++) + for (int x = 0; x < dist.cols; x++) { + float d = dist.at(y, x); + double dx = (double)(x - p0.x), dy = (double)(y - p0.y); + float d0 = (float)sqrt(dx*dx + dy*dy); + if (std::abs(d0 - d) > 1) { + if (first) { + printf("y=%d, x=%d. dist_ref=%.2f, dist=%.2f\n", y, x, d0, d); + first = false; + } + nerrs++; + } + } + EXPECT_EQ(0, nerrs) << "reference distance map is different from computed one at " << nerrs << " pixels\n"; +} + }} // namespace diff --git a/modules/imgproc/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp index 51c586ec10..8589d7e9df 100755 --- a/modules/imgproc/test/test_drawing.cpp +++ b/modules/imgproc/test/test_drawing.cpp @@ -1091,5 +1091,19 @@ INSTANTIATE_TEST_CASE_P( ) ); +TEST(Drawing, circle_overflow) +{ + applyTestTag(CV_TEST_TAG_VERYLONG); + cv::Mat1b matrix = cv::Mat1b::zeros(600, 600); + cv::Scalar kBlue = cv::Scalar(0, 0, 255); + cv::circle(matrix, cv::Point(275, -2147483318), 2147483647, kBlue, 1, 8, 0); +} + +TEST(Drawing, circle_memory_access) +{ + cv::Mat1b matrix = cv::Mat1b::zeros(10, 10); + cv::Scalar kBlue = cv::Scalar(0, 0, 255); + cv::circle(matrix, cv::Point(-1, -1), 0, kBlue, 2, 8, 16); +} }} // namespace diff --git a/modules/imgproc/test/test_houghlines.cpp b/modules/imgproc/test/test_houghlines.cpp index 61b67d9873..003420ae65 100644 --- a/modules/imgproc/test/test_houghlines.cpp +++ b/modules/imgproc/test/test_houghlines.cpp @@ -53,7 +53,7 @@ struct SimilarWith T value; float theta_eps; float rho_eps; - SimilarWith(T val, float e, float r_e): value(val), theta_eps(e), rho_eps(r_e) { }; + SimilarWith(T val, float e, float r_e): value(val), theta_eps(e), rho_eps(r_e) { }; bool operator()(const T& other); }; diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 30185b78cc..2d6796bc75 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -1093,5 +1093,43 @@ TEST(Imgproc_warpPolar, identity) #endif } +TEST(Imgproc_Remap, issue_23562) +{ + cv::RNG rng(17); + Mat_ mapx({3, 3}, {0, 1, 2, 0, 1, 2, 0, 1, 2}); + Mat_ mapy({3, 3}, {0, 0, 0, 1, 1, 1, 2, 2, 2}); + for (int cn = 1; cn <= 4; ++cn) { + Mat src(3, 3, CV_32FC(cn)); + rng.fill(src, cv::RNG::UNIFORM, -1, 1); + Mat dst = Mat::zeros(3, 3, CV_32FC(cn)); + Mat ref = src.clone(); + + remap(src, dst, mapx, mapy, INTER_LINEAR, BORDER_TRANSPARENT); + ASSERT_EQ(0.0, cvtest::norm(ref, dst, NORM_INF)) << "channels=" << cn; + } + + mapx = Mat1f({3, 3}, {0, 1, 2, 0, 1, 2, 0, 1, 2}); + mapy = Mat1f({3, 3}, {0, 0, 0, 1, 1, 1, 2, 2, 1.5}); + for (int cn = 1; cn <= 4; ++cn) { + Mat src = cv::Mat(3, 3, CV_32FC(cn)); + Mat dst = 10 * Mat::ones(3, 3, CV_32FC(cn)); + for(int y = 0; y < 3; ++y) { + for(int x = 0; x < 3; ++x) { + for(int k = 0; k < cn; ++k) { + src.ptr(y,x)[k] = 10.f * y + x; + } + } + } + + Mat ref = src.clone(); + for(int k = 0; k < cn; ++k) { + ref.ptr(2,2)[k] = (src.ptr(1, 2)[k] + src.ptr(2, 2)[k]) / 2.f; + } + + remap(src, dst, mapx, mapy, INTER_LINEAR, BORDER_TRANSPARENT); + ASSERT_EQ(0.0, cvtest::norm(ref, dst, NORM_INF)) << "channels=" << cn; + } +} + }} // namespace /* End of file. */ diff --git a/modules/java/CMakeLists.txt b/modules/java/CMakeLists.txt index 33eca05988..7fe90a0cb3 100644 --- a/modules/java/CMakeLists.txt +++ b/modules/java/CMakeLists.txt @@ -3,7 +3,9 @@ if(OPENCV_INITIAL_PASS) add_subdirectory(generator) endif() -if(APPLE_FRAMEWORK OR WINRT OR NOT PYTHON_DEFAULT_AVAILABLE OR NOT (ANT_EXECUTABLE OR ANDROID_PROJECTS_BUILD_TYPE STREQUAL "GRADLE") +if(APPLE_FRAMEWORK OR WINRT + OR NOT PYTHON_DEFAULT_AVAILABLE + OR NOT (ANT_EXECUTABLE OR Java_FOUND OR ANDROID_PROJECTS_BUILD_TYPE STREQUAL "GRADLE") OR NOT (JNI_FOUND OR (ANDROID AND (NOT DEFINED ANDROID_NATIVE_API_LEVEL OR ANDROID_NATIVE_API_LEVEL GREATER 7))) OR BUILD_opencv_world ) diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index 43486aec9c..6a4ece5603 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -699,7 +699,7 @@ class JavaWrapperGenerator(object): msg = "// Return type '%s' is not supported, skipping the function\n\n" % fi.ctype self.skipped_func_list.append(c_decl + "\n" + msg) j_code.write( " "*4 + msg ) - logging.warning("SKIP:" + c_decl.strip() + "\t due to RET type " + fi.ctype) + logging.info("SKIP:" + c_decl.strip() + "\t due to RET type " + fi.ctype) return for a in fi.args: if a.ctype not in type_dict: @@ -711,7 +711,7 @@ class JavaWrapperGenerator(object): msg = "// Unknown type '%s' (%s), skipping the function\n\n" % (a.ctype, a.out or "I") self.skipped_func_list.append(c_decl + "\n" + msg) j_code.write( " "*4 + msg ) - logging.warning("SKIP:" + c_decl.strip() + "\t due to ARG type " + a.ctype + "/" + (a.out or "I")) + logging.info("SKIP:" + c_decl.strip() + "\t due to ARG type " + a.ctype + "/" + (a.out or "I")) return self.ported_func_list.append(c_decl) diff --git a/modules/java/jar/CMakeLists.txt b/modules/java/jar/CMakeLists.txt index 33817bcc62..97533d0ee4 100644 --- a/modules/java/jar/CMakeLists.txt +++ b/modules/java/jar/CMakeLists.txt @@ -5,12 +5,13 @@ set(OPENCV_JAVA_DIR "${CMAKE_CURRENT_BINARY_DIR}/opencv" CACHE INTERNAL "") file(REMOVE_RECURSE "${OPENCV_JAVA_DIR}") file(REMOVE "${OPENCV_DEPHELPER}/${the_module}_jar_source_copy") -file(MAKE_DIRECTORY "${OPENCV_JAVA_DIR}/build/classes") set(java_src_dir "${OPENCV_JAVA_DIR}/java") file(MAKE_DIRECTORY "${java_src_dir}") -set(JAR_NAME opencv-${OPENCV_JAVA_LIB_NAME_SUFFIX}.jar) -set(OPENCV_JAR_FILE "${OpenCV_BINARY_DIR}/bin/${JAR_NAME}" CACHE INTERNAL "") +set(JAR_NAME_WE opencv-${OPENCV_JAVA_LIB_NAME_SUFFIX}) +set(JAR_NAME ${JAR_NAME_WE}.jar) +set(OPENCV_JAR_DIR "${OpenCV_BINARY_DIR}/bin/" CACHE INTERNAL "") +set(OPENCV_JAR_FILE "${OPENCV_JAR_DIR}${JAR_NAME}" CACHE INTERNAL "") ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/java" "${java_src_dir}") @@ -34,38 +35,98 @@ if(OPENCV_JAVADOC_LINK_URL) set(CMAKE_CONFIG_OPENCV_JAVADOC_LINK "link=\"${OPENCV_JAVADOC_LINK_URL}\"") endif() -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build.xml.in" "${OPENCV_JAVA_DIR}/build.xml" @ONLY) -list(APPEND depends "${OPENCV_JAVA_DIR}/build.xml") +if(OPENCV_JAVA_SDK_BUILD_TYPE STREQUAL "ANT") + file(MAKE_DIRECTORY "${OPENCV_JAVA_DIR}/build/classes") -ocv_cmake_byproducts(__byproducts BYPRODUCTS "${OPENCV_JAR_FILE}") -add_custom_command(OUTPUT "${OPENCV_DEPHELPER}/${the_module}_jar" - ${__byproducts} # required for add_custom_target() by ninja - COMMAND ${ANT_EXECUTABLE} -noinput -k jar - COMMAND ${CMAKE_COMMAND} -E touch "${OPENCV_DEPHELPER}/${the_module}_jar" - WORKING_DIRECTORY "${OPENCV_JAVA_DIR}" - DEPENDS ${depends} - COMMENT "Generating ${JAR_NAME}" -) -add_custom_target(${the_module}_jar DEPENDS "${OPENCV_DEPHELPER}/${the_module}_jar") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/build.xml.in" "${OPENCV_JAVA_DIR}/build.xml" @ONLY) + list(APPEND depends "${OPENCV_JAVA_DIR}/build.xml") + + ocv_cmake_byproducts(__byproducts BYPRODUCTS "${OPENCV_JAR_FILE}") + add_custom_command(OUTPUT "${OPENCV_DEPHELPER}/${the_module}_jar" + ${__byproducts} # required for add_custom_target() by ninja + COMMAND ${ANT_EXECUTABLE} -noinput -k jar + COMMAND ${CMAKE_COMMAND} -E touch "${OPENCV_DEPHELPER}/${the_module}_jar" + WORKING_DIRECTORY "${OPENCV_JAVA_DIR}" + DEPENDS ${depends} + COMMENT "Generating ${JAR_NAME}" + ) + add_custom_target(${the_module}_jar DEPENDS "${OPENCV_DEPHELPER}/${the_module}_jar") +elseif(OPENCV_JAVA_SDK_BUILD_TYPE STREQUAL "JAVA") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST.MF.in" "${OPENCV_JAVA_DIR}/MANIFEST.MF" @ONLY) + list(APPEND depends "${OPENCV_JAVA_DIR}/MANIFEST.MF") + + ocv_cmake_byproducts(__byproducts BYPRODUCTS "${OPENCV_JAVA_DIR}/java_sources") + add_custom_command(OUTPUT "${OPENCV_DEPHELPER}/${the_module}_jar" + BYPRODUCTS ${__byproducts} # required for add_custom_target() by ninja + DEPENDS ${depends} + COMMAND ${CMAKE_COMMAND} -E touch "${OPENCV_DEPHELPER}/${the_module}_jar" + COMMAND ${CMAKE_COMMAND} + -D OPENCV_JAVA_DIR="${OPENCV_JAVA_DIR}/java" + -D OUTPUT="${OPENCV_JAVA_DIR}/java_sources" + -P "${CMAKE_CURRENT_SOURCE_DIR}/list_java_sources.cmake" + ) + + add_custom_target(${the_module}_jar_sources + DEPENDS "${OPENCV_DEPHELPER}/${the_module}_jar" + ) + + list(APPEND CMAKE_JAVA_COMPILE_FLAGS -encoding utf-8 ${OPENCV_EXTRA_JAVA_COMPILE_FLAGS}) + + add_jar(${the_module}_jar + SOURCES "@${OPENCV_JAVA_DIR}/java_sources" + MANIFEST "${OPENCV_JAVA_DIR}/MANIFEST.MF" + OUTPUT_NAME "${JAR_NAME_WE}" + OUTPUT_DIR "${OPENCV_JAR_DIR}") + + add_dependencies(${the_module}_jar ${the_module}_jar_sources) +else() + ocv_assert(0) +endif() install(FILES ${OPENCV_JAR_FILE} OPTIONAL DESTINATION ${OPENCV_JAR_INSTALL_PATH} COMPONENT java) add_dependencies(${the_module} ${the_module}_jar) if(BUILD_DOCS) - add_custom_command(OUTPUT "${OPENCV_DEPHELPER}/${the_module}doc" - COMMAND ${ANT_EXECUTABLE} -noinput -k javadoc - COMMAND ${CMAKE_COMMAND} -E touch "${OPENCV_DEPHELPER}/${the_module}doc" - WORKING_DIRECTORY "${OPENCV_JAVA_DIR}" - DEPENDS ${depends} - COMMENT "Generating Javadoc" - ) - add_custom_target(${the_module}doc DEPENDS "${OPENCV_DEPHELPER}/${the_module}doc") - install(DIRECTORY ${OpenCV_BINARY_DIR}/doc/doxygen/html/javadoc - DESTINATION "${OPENCV_DOC_INSTALL_PATH}/html" - COMPONENT "docs" OPTIONAL - ${compatible_MESSAGE_NEVER} - ) + if(OPENCV_JAVA_SDK_BUILD_TYPE STREQUAL "ANT") + add_custom_command(OUTPUT "${OPENCV_DEPHELPER}/${the_module}doc" + COMMAND ${ANT_EXECUTABLE} -noinput -k javadoc + COMMAND ${CMAKE_COMMAND} -E touch "${OPENCV_DEPHELPER}/${the_module}doc" + WORKING_DIRECTORY "${OPENCV_JAVA_DIR}" + DEPENDS ${depends} + COMMENT "Generating Javadoc" + ) + add_custom_target(${the_module}doc DEPENDS "${OPENCV_DEPHELPER}/${the_module}doc") + + install(DIRECTORY ${OpenCV_BINARY_DIR}/doc/doxygen/html/javadoc + DESTINATION "${OPENCV_DOC_INSTALL_PATH}/html" + COMPONENT "docs" OPTIONAL + ${compatible_MESSAGE_NEVER} + ) + elseif(OPENCV_JAVA_SDK_BUILD_TYPE STREQUAL "JAVA") + set(Java_JAVADOC_EXECUTABLE ${Java_JAVADOC_EXECUTABLE} -encoding utf-8) + + # create_javadoc produces target ${_target}_javadoc + create_javadoc(${the_module} + FILES "@${OPENCV_JAVA_DIR}/java_sources" + SOURCEPATH "${OPENCV_JAVA_DIR}/java" + INSTALLPATH "${OPENCV_JAVADOC_DESTINATION}" + WINDOWTITLE "OpenCV ${OPENCV_VERSION_PLAIN} Java documentation" + DOCTITLE "OpenCV Java documentation (${OPENCV_VERSION})" + VERSION TRUE + ) + add_dependencies(${the_module}_javadoc ${the_module}_jar_sources) + add_custom_target(${the_module}doc DEPENDS ${the_module}_javadoc) + + install(DIRECTORY ${OpenCV_BINARY_DIR}/doc/doxygen/html/javadoc/${the_module}/ + DESTINATION "${OPENCV_DOC_INSTALL_PATH}/html/javadoc" + COMPONENT "docs" OPTIONAL + ${compatible_MESSAGE_NEVER} + ) + else() + ocv_assert(0) + endif() + set(CMAKE_DOXYGEN_JAVADOC_NODE "" CACHE INTERNAL "Link to the Java documentation") # set to the cache to make it global diff --git a/modules/java/jar/MANIFEST.MF.in b/modules/java/jar/MANIFEST.MF.in new file mode 100644 index 0000000000..07d2fda79b --- /dev/null +++ b/modules/java/jar/MANIFEST.MF.in @@ -0,0 +1,5 @@ +Specification-Title: OpenCV +Specification-Version: @OPENCV_VERSION@ +Implementation-Title: OpenCV +Implementation-Version: @OPENCV_VCSVERSION@ +Implementation-Date: @OPENCV_TIMESTAMP@ diff --git a/modules/java/jar/list_java_sources.cmake b/modules/java/jar/list_java_sources.cmake new file mode 100644 index 0000000000..e39a630e18 --- /dev/null +++ b/modules/java/jar/list_java_sources.cmake @@ -0,0 +1,19 @@ +file(GLOB_RECURSE java_sources "${OPENCV_JAVA_DIR}/*.java") + +set(__sources "") + +foreach(dst ${java_sources}) + set(__sources "${__sources}${dst}\n") +endforeach() + +function(ocv_update_file filepath content) + if(EXISTS "${filepath}") + file(READ "${filepath}" actual_content) + else() + set(actual_content "") + endif() + if(NOT ("${actual_content}" STREQUAL "${content}")) + file(WRITE "${filepath}" "${content}") + endif() +endfunction() +ocv_update_file("${OUTPUT}" "${__sources}") diff --git a/modules/js/src/loader.js b/modules/js/src/loader.js index ea100e8601..1690e0468d 100644 --- a/modules/js/src/loader.js +++ b/modules/js/src/loader.js @@ -73,6 +73,11 @@ async function loadOpenCV(paths, onloadCallback) { console.log("The OpenCV.js for wasm is loaded now"); } else if (wasmSupported) { console.log("The browser supports wasm, but the path of OpenCV.js for wasm is empty"); + + if (asmPath != "") { + OPENCV_URL = asmPath; + console.log("The OpenCV.js for Asm.js is loaded as fallback."); + } } if (OPENCV_URL === "") { diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp index c46b5fbfb5..bc7b934b2a 100644 --- a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp +++ b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp @@ -13,32 +13,39 @@ namespace aruco { //! @{ -/** @brief Dictionary/Set of markers, it contains the inner codification +/** @brief Dictionary is a set of unique ArUco markers of the same size * - * BytesList contains the marker codewords where: + * `bytesList` storing as 2-dimensions Mat with 4-th channels (CV_8UC4 type was used) and contains the marker codewords where: * - bytesList.rows is the dictionary size - * - each marker is encoded using `nbytes = ceil(markerSize*markerSize/8.)` + * - each marker is encoded using `nbytes = ceil(markerSize*markerSize/8.)` bytes * - each row contains all 4 rotations of the marker, so its length is `4*nbytes` - * - * `bytesList.ptr(i)[k*nbytes + j]` is then the j-th byte of i-th marker, in its k-th rotation. + * - the byte order in the bytesList[i] row: + * `//bytes without rotation/bytes with rotation 1/bytes with rotation 2/bytes with rotation 3//` + * So `bytesList.ptr(i)[k*nbytes + j]` is the j-th byte of i-th marker, in its k-th rotation. + * @note Python bindings generate matrix with shape of bytesList `dictionary_size x nbytes x 4`, + * but it should be indexed like C++ version. Python example for j-th byte of i-th marker, in its k-th rotation: + * `aruco_dict.bytesList[id].ravel()[k*nbytes + j]` */ class CV_EXPORTS_W_SIMPLE Dictionary { public: - CV_PROP_RW Mat bytesList; // marker code information - CV_PROP_RW int markerSize; // number of bits per dimension - CV_PROP_RW int maxCorrectionBits; // maximum number of bits that can be corrected - + CV_PROP_RW Mat bytesList; ///< marker code information. See class description for more details + CV_PROP_RW int markerSize; ///< number of bits per dimension + CV_PROP_RW int maxCorrectionBits; ///< maximum number of bits that can be corrected CV_WRAP Dictionary(); + /** @brief Basic ArUco dictionary constructor + * + * @param bytesList bits for all ArUco markers in dictionary see memory layout in the class description + * @param _markerSize ArUco marker size in units + * @param maxcorr maximum number of bits that can be corrected + */ CV_WRAP Dictionary(const Mat &bytesList, int _markerSize, int maxcorr = 0); - - /** @brief Read a new dictionary from FileNode. * - * Dictionary format:\n + * Dictionary example in YAML format:\n * nmarkers: 35\n * markersize: 6\n * maxCorrectionBits: 5\n @@ -54,13 +61,13 @@ class CV_EXPORTS_W_SIMPLE Dictionary { /** @brief Given a matrix of bits. Returns whether if marker is identified or not. * - * It returns by reference the correct id (if any) and the correct rotation + * Returns reference to the marker id in the dictionary (if any) and its rotation. */ CV_WRAP bool identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const; - /** @brief Returns the distance of the input bits to the specific id. + /** @brief Returns Hamming distance of the input bits to the specific id. * - * If allRotations is true, the four posible bits rotation are considered + * If `allRotations` flag is set, the four posible marker rotations are considered */ CV_WRAP int getDistanceToId(InputArray bits, int id, bool allRotations = true) const; @@ -70,7 +77,7 @@ class CV_EXPORTS_W_SIMPLE Dictionary { CV_WRAP void generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits = 1) const; - /** @brief Transform matrix of bits to list of bytes in the 4 rotations + /** @brief Transform matrix of bits to list of bytes with 4 marker rotations */ CV_WRAP static Mat getByteListFromBits(const Mat &bits); diff --git a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp index 15170b4d2e..a23960d557 100644 --- a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp @@ -104,7 +104,7 @@ public: */ CV_WRAP void detectDiamonds(InputArray image, OutputArrayOfArrays diamondCorners, OutputArray diamondIds, InputOutputArrayOfArrays markerCorners = noArray(), - InputOutputArrayOfArrays markerIds = noArray()) const; + InputOutputArray markerIds = noArray()) const; protected: struct CharucoDetectorImpl; Ptr charucoDetectorImpl; diff --git a/modules/objdetect/misc/python/test/test_objdetect_aruco.py b/modules/objdetect/misc/python/test/test_objdetect_aruco.py index eb88f2c8f6..d63a19cd2f 100644 --- a/modules/objdetect/misc/python/test/test_objdetect_aruco.py +++ b/modules/objdetect/misc/python/test/test_objdetect_aruco.py @@ -238,6 +238,27 @@ class aruco_objdetect_test(NewOpenCVTests): self.assertEqual(charucoIds[i], i) np.testing.assert_allclose(gold_corners, charucoCorners.reshape(-1, 2), 0.01, 0.1) + def test_detect_diamonds(self): + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250) + board_size = (3, 3) + board = cv.aruco.CharucoBoard(board_size, 1.0, .8, aruco_dict) + charuco_detector = cv.aruco.CharucoDetector(board) + cell_size = 120 + + image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1])) + + list_gold_corners = [(cell_size, cell_size), (2*cell_size, cell_size), (2*cell_size, 2*cell_size), + (cell_size, 2*cell_size)] + gold_corners = np.array(list_gold_corners, dtype=np.float32) + + diamond_corners, diamond_ids, marker_corners, marker_ids = charuco_detector.detectDiamonds(image) + + self.assertEqual(diamond_ids.size, 4) + self.assertEqual(marker_ids.size, 4) + for i in range(0, 4): + self.assertEqual(diamond_ids[0][0][i], i) + np.testing.assert_allclose(gold_corners, np.array(diamond_corners, dtype=np.float32).reshape(-1, 2), 0.01, 0.1) + # check no segfault when cameraMatrix or distCoeffs are not initialized def test_charuco_no_segfault_params(self): dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000) diff --git a/modules/objdetect/src/aruco/charuco_detector.cpp b/modules/objdetect/src/aruco/charuco_detector.cpp index a6ecd6398c..809fb9c774 100644 --- a/modules/objdetect/src/aruco/charuco_detector.cpp +++ b/modules/objdetect/src/aruco/charuco_detector.cpp @@ -23,6 +23,54 @@ struct CharucoDetector::CharucoDetectorImpl { arucoDetector(_arucoDetector) {} + bool checkBoard(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray charucoCorners, InputArray charucoIds) { + vector mCorners; + markerCorners.getMatVector(mCorners); + Mat mIds = markerIds.getMat(); + + Mat chCorners = charucoCorners.getMat(); + Mat chIds = charucoIds.getMat(); + + vector > nearestMarkerIdx = board.getNearestMarkerIdx(); + vector distance(board.getNearestMarkerIdx().size(), Point2f(0.f, std::numeric_limits::max())); + // distance[i].x: max distance from the i-th charuco corner to charuco corner-forming markers. + // The two charuco corner-forming markers of i-th charuco corner are defined in getNearestMarkerIdx()[i] + // distance[i].y: min distance from the charuco corner to other markers. + for (size_t i = 0ull; i < chIds.total(); i++) { + int chId = chIds.ptr(0)[i]; + Point2f charucoCorner(chCorners.ptr(0)[i]); + for (size_t j = 0ull; j < mIds.total(); j++) { + int idMaker = mIds.ptr(0)[j]; + Point2f centerMarker((mCorners[j].ptr(0)[0] + mCorners[j].ptr(0)[1] + + mCorners[j].ptr(0)[2] + mCorners[j].ptr(0)[3]) / 4.f); + float dist = sqrt(normL2Sqr(centerMarker - charucoCorner)); + // check distance from the charuco corner to charuco corner-forming markers + if (nearestMarkerIdx[chId][0] == idMaker || nearestMarkerIdx[chId][1] == idMaker) { + int nearestCornerId = nearestMarkerIdx[chId][0] == idMaker ? board.getNearestMarkerCorners()[chId][0] : board.getNearestMarkerCorners()[chId][1]; + Point2f nearestCorner = mCorners[j].ptr(0)[nearestCornerId]; + float distToNearest = sqrt(normL2Sqr(nearestCorner - charucoCorner)); + distance[chId].x = max(distance[chId].x, distToNearest); + // check that nearestCorner is nearest point + { + Point2f mid1 = (mCorners[j].ptr(0)[(nearestCornerId + 1) % 4]+nearestCorner)*0.5f; + Point2f mid2 = (mCorners[j].ptr(0)[(nearestCornerId + 3) % 4]+nearestCorner)*0.5f; + float tmpDist = min(sqrt(normL2Sqr(mid1 - charucoCorner)), sqrt(normL2Sqr(mid2 - charucoCorner))); + if (tmpDist < distToNearest) + return false; + } + } + // check distance from the charuco corner to other markers + else + distance[chId].y = min(distance[chId].y, dist); + } + // if distance from the charuco corner to charuco corner-forming markers more then distance from the charuco corner to other markers, + // then a false board is found. + if (distance[chId].x > 0.f && distance[chId].y < std::numeric_limits::max() && distance[chId].x > distance[chId].y) + return false; + } + return true; + } + /** Calculate the maximum window sizes for corner refinement for each charuco corner based on the distance * to their closest markers */ vector getMaximumSubPixWindowSizes(InputArrayOfArrays markerCorners, InputArray markerIds, @@ -246,6 +294,31 @@ struct CharucoDetector::CharucoDetectorImpl { Mat(filteredCharucoIds).copyTo(_filteredCharucoIds); return (int)_filteredCharucoIds.total(); } + + void detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, + InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) { + CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.total() == markerIds.total())); + vector> tmpMarkerCorners; + vector tmpMarkerIds; + InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; + InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; + + if (markerCorners.empty() && markerIds.empty()) { + vector > rejectedMarkers; + arucoDetector.detectMarkers(image, _markerCorners, _markerIds, rejectedMarkers); + if (charucoParameters.tryRefineMarkers) + arucoDetector.refineDetectedMarkers(image, board, _markerCorners, _markerIds, rejectedMarkers); + } + // if camera parameters are avaible, use approximated calibration + if(!charucoParameters.cameraMatrix.empty()) + interpolateCornersCharucoApproxCalib(_markerCorners, _markerIds, image, charucoCorners, charucoIds); + // else use local homography + else + interpolateCornersCharucoLocalHom(_markerCorners, _markerIds, image, charucoCorners, charucoIds); + // to return a charuco corner, its closest aruco markers should have been detected + filterCornersWithoutMinMarkers(charucoCorners, charucoIds, _markerIds, charucoCorners, charucoIds); +} + }; CharucoDetector::CharucoDetector(const CharucoBoard &board, const CharucoParameters &charucoParams, @@ -288,34 +361,15 @@ void CharucoDetector::setRefineParameters(const RefineParameters& refineParamete void CharucoDetector::detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) const { - CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.total() == markerIds.total())); - vector> tmpMarkerCorners; - vector tmpMarkerIds; - InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; - InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; - - if (markerCorners.empty() && markerIds.empty()) { - vector > rejectedMarkers; - charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds, rejectedMarkers); - if (charucoDetectorImpl->charucoParameters.tryRefineMarkers) - charucoDetectorImpl->arucoDetector.refineDetectedMarkers(image, charucoDetectorImpl->board, _markerCorners, - _markerIds, rejectedMarkers); + charucoDetectorImpl->detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds); + if (charucoDetectorImpl->checkBoard(markerCorners, markerIds, charucoCorners, charucoIds) == false) { + charucoCorners.release(); + charucoIds.release(); } - // if camera parameters are avaible, use approximated calibration - if(!charucoDetectorImpl->charucoParameters.cameraMatrix.empty()) - charucoDetectorImpl->interpolateCornersCharucoApproxCalib(_markerCorners, _markerIds, image, charucoCorners, - charucoIds); - // else use local homography - else - charucoDetectorImpl->interpolateCornersCharucoLocalHom(_markerCorners, _markerIds, image, charucoCorners, - charucoIds); - // to return a charuco corner, its closest aruco markers should have been detected - charucoDetectorImpl->filterCornersWithoutMinMarkers(charucoCorners, charucoIds, _markerIds, charucoCorners, - charucoIds); } void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diamondCorners, OutputArray _diamondIds, - InputOutputArrayOfArrays inMarkerCorners, InputOutputArrayOfArrays inMarkerIds) const { + InputOutputArrayOfArrays inMarkerCorners, InputOutputArray inMarkerIds) const { CV_Assert(getBoard().getChessboardSize() == Size(3, 3)); CV_Assert((inMarkerCorners.empty() && inMarkerIds.empty() && !image.empty()) || (inMarkerCorners.total() == inMarkerIds.total())); @@ -416,7 +470,7 @@ void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diam // interpolate the charuco corners of the diamond vector currentMarkerCorners; Mat aux; - detectBoard(grey, currentMarkerCorners, aux, currentMarker, currentMarkerId); + charucoDetectorImpl->detectBoard(grey, currentMarkerCorners, aux, currentMarker, currentMarkerId); // if everything is ok, save the diamond if(currentMarkerCorners.size() > 0ull) { diff --git a/modules/objdetect/src/barcode_decoder/common/super_scale.cpp b/modules/objdetect/src/barcode_decoder/common/super_scale.cpp index 0c9f75f156..4b7099a27d 100644 --- a/modules/objdetect/src/barcode_decoder/common/super_scale.cpp +++ b/modules/objdetect/src/barcode_decoder/common/super_scale.cpp @@ -8,11 +8,14 @@ #include "../../precomp.hpp" #include "super_scale.hpp" - -#ifdef HAVE_OPENCV_DNN +#include "opencv2/core.hpp" +#include "opencv2/core/utils/logger.hpp" namespace cv { namespace barcode { + +#ifdef HAVE_OPENCV_DNN + constexpr static float MAX_SCALE = 4.0f; int SuperScale::init(const std::string &proto_path, const std::string &model_path) @@ -71,7 +74,26 @@ int SuperScale::superResolutionScale(const Mat &src, Mat &dst) } return 0; } -} // namespace barcode -} // namespace cv +#else // HAVE_OPENCV_DNN + +int SuperScale::init(const std::string &proto_path, const std::string &model_path) +{ + CV_UNUSED(proto_path); + CV_UNUSED(model_path); + return 0; +} + +void SuperScale::processImageScale(const Mat &src, Mat &dst, float scale, const bool & isEnabled, int sr_max_size) +{ + CV_UNUSED(sr_max_size); + 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); +} #endif // HAVE_OPENCV_DNN + +} // namespace barcode +} // namespace cv diff --git a/modules/objdetect/src/barcode_decoder/common/super_scale.hpp b/modules/objdetect/src/barcode_decoder/common/super_scale.hpp index 70e47424e4..024ea86e54 100644 --- a/modules/objdetect/src/barcode_decoder/common/super_scale.hpp +++ b/modules/objdetect/src/barcode_decoder/common/super_scale.hpp @@ -9,8 +9,8 @@ #define OPENCV_BARCODE_SUPER_SCALE_HPP #ifdef HAVE_OPENCV_DNN - -#include "opencv2/dnn.hpp" +# include "opencv2/dnn.hpp" +#endif namespace cv { namespace barcode { @@ -26,44 +26,16 @@ public: void processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size = 160); +#ifdef HAVE_OPENCV_DNN private: dnn::Net srnet_; bool net_loaded_ = false; int superResolutionScale(const cv::Mat &src, cv::Mat &dst); +#endif }; -} // 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 +} // namespace barcode +} // namespace cv #endif // OPENCV_BARCODE_SUPER_SCALE_HPP diff --git a/modules/objdetect/test/test_charucodetection.cpp b/modules/objdetect/test/test_charucodetection.cpp index 3a459e11fc..9e561bc40a 100644 --- a/modules/objdetect/test/test_charucodetection.cpp +++ b/modules/objdetect/test/test_charucodetection.cpp @@ -689,4 +689,32 @@ TEST(Charuco, testmatchImagePoints) } } +typedef testing::TestWithParam CharucoBoard; +INSTANTIATE_TEST_CASE_P(/**/, CharucoBoard, testing::Values(Size(3, 2), Size(3, 2), Size(6, 2), Size(2, 6), + Size(3, 4), Size(4, 3), Size(7, 3), Size(3, 7))); +TEST_P(CharucoBoard, testWrongSizeDetection) +{ + cv::Size boardSize = GetParam(); + ASSERT_FALSE(boardSize.width == boardSize.height); + aruco::CharucoBoard board(boardSize, 1.f, 0.5f, aruco::getPredefinedDictionary(aruco::DICT_4X4_50)); + + vector detectedCharucoIds, detectedArucoIds; + vector detectedCharucoCorners; + vector> detectedArucoCorners; + Mat boardImage; + board.generateImage(boardSize*40, boardImage); + + swap(boardSize.width, boardSize.height); + aruco::CharucoDetector detector(aruco::CharucoBoard(boardSize, 1.f, 0.5f, aruco::getPredefinedDictionary(aruco::DICT_4X4_50))); + // try detect board with wrong size + detector.detectBoard(boardImage, detectedCharucoCorners, detectedCharucoIds, detectedArucoCorners, detectedArucoIds); + + // aruco markers must be found + ASSERT_EQ(detectedArucoIds.size(), board.getIds().size()); + ASSERT_EQ(detectedArucoCorners.size(), board.getIds().size()); + // charuco corners should not be found in board with wrong size + ASSERT_TRUE(detectedCharucoCorners.empty()); + ASSERT_TRUE(detectedCharucoIds.empty()); +} + }} // namespace diff --git a/modules/objdetect/test/test_qr_utils.hpp b/modules/objdetect/test/test_qr_utils.hpp index cfbe1a5078..115c767f71 100644 --- a/modules/objdetect/test/test_qr_utils.hpp +++ b/modules/objdetect/test/test_qr_utils.hpp @@ -10,6 +10,9 @@ void check_qr(const string& root, const string& name_current_image, const string const std::vector& corners, const std::vector& decoded_info, const int max_pixel_error, bool isMulti = false) { +#ifndef HAVE_QUIRC + CV_UNUSED(decoded_info); +#endif const std::string dataset_config = findDataFile(root + "dataset_config.json"); FileStorage file_config(dataset_config, FileStorage::READ); ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index 5e6ec6faf5..9b7d8ceda4 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -374,8 +374,8 @@ TEST_P(Objdetect_QRCode_Multi, regression) qrcode = QRCodeDetectorAruco(); } std::vector corners; -#ifdef HAVE_QUIRC std::vector decoded_info; +#ifdef HAVE_QUIRC std::vector straight_barcode; EXPECT_TRUE(qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode)); ASSERT_FALSE(corners.empty()); @@ -538,7 +538,6 @@ TEST(Objdetect_QRCode_detect_flipped, regression_23249) for(const auto &flipped_image : flipped_images){ const std::string &image_name = flipped_image.first; - const std::string &expect_msg = flipped_image.second; std::string image_path = findDataFile(root + image_name); Mat src = imread(image_path); @@ -551,6 +550,7 @@ TEST(Objdetect_QRCode_detect_flipped, regression_23249) EXPECT_TRUE(!corners.empty()); std::string decoded_msg; #ifdef HAVE_QUIRC + const std::string &expect_msg = flipped_image.second; EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode)); ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage."; EXPECT_EQ(expect_msg, decoded_msg); diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index f03a2e2d86..e9e1fed4fd 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -5,6 +5,7 @@ #include "cv2_convert.hpp" #include "cv2_numpy.hpp" +#include "cv2_util.hpp" #include "opencv2/core/utils/logger.hpp" PyTypeObject* pyopencv_Mat_TypePtr = nullptr; @@ -24,6 +25,26 @@ static std::string pycv_dumpArray(const T* arr, int n) return out.str(); } +static inline std::string getArrayTypeName(PyArrayObject* arr) +{ + PyArray_Descr* dtype = PyArray_DESCR(arr); + PySafeObject dtype_str(PyObject_Str(reinterpret_cast(dtype))); + if (!dtype_str) + { + // Fallback to typenum value + return cv::format("%d", PyArray_TYPE(arr)); + } + std::string type_name; + if (!getUnicodeString(dtype_str, type_name)) + { + // Failed to get string from bytes object - clear set TypeError and + // fallback to typenum value + PyErr_Clear(); + return cv::format("%d", PyArray_TYPE(arr)); + } + return type_name; +} + //====================================================================================================================== // --- Mat @@ -80,6 +101,13 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info) PyArrayObject* oarr = (PyArrayObject*) o; + if (info.outputarg && !PyArray_ISWRITEABLE(oarr)) + { + failmsg("%s marked as output argument, but provided NumPy array " + "marked as readonly", info.name); + return false; + } + bool needcopy = false, needcast = false; int typenum = PyArray_TYPE(oarr), new_typenum = typenum; int type = typenum == NPY_UBYTE ? CV_8U : @@ -102,7 +130,9 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info) } else { - failmsg("%s data type = %d is not supported", info.name, typenum); + const std::string dtype_name = getArrayTypeName(oarr); + failmsg("%s data type = %s is not supported", info.name, + dtype_name.c_str()); return false; } } @@ -728,6 +758,26 @@ PyObject* pyopencv_from(const Rect2d& r) // --- RotatedRect +static inline bool convertToRotatedRect(PyObject* obj, RotatedRect& dst) +{ + PyObject* type = PyObject_Type(obj); + if (getPyObjectAttr(type, "__module__") == MODULESTR && + getPyObjectNameAttr(type) == "RotatedRect") + { + struct pyopencv_RotatedRect_t + { + PyObject_HEAD + cv::RotatedRect v; + }; + dst = reinterpret_cast(obj)->v; + + Py_DECREF(type); + return true; + } + Py_DECREF(type); + return false; +} + template<> bool pyopencv_to(PyObject* obj, RotatedRect& dst, const ArgInfo& info) { @@ -735,6 +785,12 @@ bool pyopencv_to(PyObject* obj, RotatedRect& dst, const ArgInfo& info) { return true; } + // This is a workaround for compatibility with an initialization from tuple. + // Allows import RotatedRect as an object. + if (convertToRotatedRect(obj, dst)) + { + return true; + } if (!PySequence_Check(obj)) { failmsg("Can't parse '%s' as RotatedRect." diff --git a/modules/python/src2/cv2_util.hpp b/modules/python/src2/cv2_util.hpp index 0d27e98825..a7deb5b575 100644 --- a/modules/python/src2/cv2_util.hpp +++ b/modules/python/src2/cv2_util.hpp @@ -42,7 +42,7 @@ private: /** * Light weight RAII wrapper for `PyObject*` owning references. - * In comparisson to C++11 `std::unique_ptr` with custom deleter, it provides + * In comparison to C++11 `std::unique_ptr` with custom deleter, it provides * implicit conversion functions that might be useful to initialize it with * Python functions those returns owning references through the `PyObject**` * e.g. `PyErr_Fetch` or directly pass it to functions those want to borrow @@ -70,6 +70,10 @@ public: return &obj_; } + operator bool() { + return obj_ != nullptr; + } + PyObject* release() { PyObject* obj = obj_; diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 6be488c36a..13b6d7ed1e 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -231,6 +231,8 @@ simple_argtype_mapping = { "c_string": ArgTypeInfo("char*", FormatStrings.string, '(char*)""'), "string": ArgTypeInfo("std::string", FormatStrings.object, None, True), "Stream": ArgTypeInfo("Stream", FormatStrings.object, 'Stream::Null()', True), + "cuda_Stream": ArgTypeInfo("cuda::Stream", FormatStrings.object, "cuda::Stream::Null()", True), + "cuda_GpuMat": ArgTypeInfo("cuda::GpuMat", FormatStrings.object, "cuda::GpuMat()", True), "UMat": ArgTypeInfo("UMat", FormatStrings.object, 'UMat()', True), # FIXIT: switch to CV_EXPORTS_W_SIMPLE as UMat is already a some kind of smart pointer } @@ -509,14 +511,17 @@ class ArgInfo(object): return self.enclosing_arg.name + '.' + self.name def isbig(self): - return self.tp in ["Mat", "vector_Mat", "cuda::GpuMat", "GpuMat", "vector_GpuMat", "UMat", "vector_UMat"] # or self.tp.startswith("vector") + return self.tp in ["Mat", "vector_Mat", + "cuda::GpuMat", "cuda_GpuMat", "GpuMat", + "vector_GpuMat", "vector_cuda_GpuMat", + "UMat", "vector_UMat"] # or self.tp.startswith("vector") def crepr(self): return "ArgInfo(\"%s\", %d)" % (self.name, self.outputarg) def find_argument_class_info(argument_type, function_namespace, - function_class_name, known_classes): + function_class_name, known_classes): # type: (str, str, str, dict[str, ClassInfo]) -> ClassInfo | None """Tries to find corresponding class info for the provided argument type diff --git a/modules/python/src2/pycompat.hpp b/modules/python/src2/pycompat.hpp index bd3956dbc0..c8806dc812 100644 --- a/modules/python/src2/pycompat.hpp +++ b/modules/python/src2/pycompat.hpp @@ -98,10 +98,10 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str) } static inline -std::string getPyObjectNameAttr(PyObject* obj) +std::string getPyObjectAttr(PyObject* obj, const char* attrName) { std::string obj_name; - PyObject* cls_name_obj = PyObject_GetAttrString(obj, "__name__"); + PyObject* cls_name_obj = PyObject_GetAttrString(obj, attrName); if (cls_name_obj && !getUnicodeString(cls_name_obj, obj_name)) { obj_name.clear(); } @@ -117,6 +117,12 @@ std::string getPyObjectNameAttr(PyObject* obj) return obj_name; } +static inline +std::string getPyObjectNameAttr(PyObject* obj) +{ + return getPyObjectAttr(obj, "__name__"); +} + //================================================================================================== #define CV_PY_FN_WITH_KW_(fn, flags) (PyCFunction)(void*)(PyCFunctionWithKeywords)(fn), (flags) | METH_VARARGS | METH_KEYWORDS diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py new file mode 100644 index 0000000000..b04bb4a196 --- /dev/null +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -0,0 +1,337 @@ +__all__ = [ + "apply_manual_api_refinement" +] + +from typing import cast, Sequence, Callable, Iterable + +from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode, + ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode, + AggregatedTypeNode, CallableTypeNode, AnyTypeNode, + TupleTypeNode, UnionTypeNode, ProtocolClassNode, + DictTypeNode, ClassTypeNode) +from .ast_utils import (find_function_node, SymbolName, + for_each_function_overload) +from .types_conversion import create_type_node + + +def apply_manual_api_refinement(root: NamespaceNode) -> None: + refine_highgui_module(root) + refine_cuda_module(root) + export_matrix_type_constants(root) + refine_dnn_module(root) + # Export OpenCV exception class + builtin_exception = root.add_class("Exception") + builtin_exception.is_exported = False + root.add_class("error", (builtin_exception, ), ERROR_CLASS_PROPERTIES) + for symbol_name, refine_symbol in NODES_TO_REFINE.items(): + refine_symbol(root, symbol_name) + version_constant = root.add_constant("__version__", "") + version_constant._value_type = "str" + + """ + def redirectError( + onError: Callable[[int, str, str, str, int], None] | None + ) -> None: ... + """ + root.add_function("redirectError", [ + FunctionNode.Arg( + "onError", + OptionalTypeNode( + CallableTypeNode( + "ErrorCallback", + [ + PrimitiveTypeNode.int_(), + PrimitiveTypeNode.str_(), + PrimitiveTypeNode.str_(), + PrimitiveTypeNode.str_(), + PrimitiveTypeNode.int_() + ] + ) + ) + ) + ]) + + +def export_matrix_type_constants(root: NamespaceNode) -> None: + MAX_PREDEFINED_CHANNELS = 4 + + depth_names = ("CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32S", + "CV_32F", "CV_64F", "CV_16F") + for depth_value, depth_name in enumerate(depth_names): + # Export depth constants + root.add_constant(depth_name, str(depth_value)) + # Export predefined types + for c in range(MAX_PREDEFINED_CHANNELS): + root.add_constant(f"{depth_name}C{c + 1}", + f"{depth_value + 8 * c}") + # Export type creation function + root.add_function( + f"{depth_name}C", + (FunctionNode.Arg("channels", PrimitiveTypeNode.int_()), ), + FunctionNode.RetType(PrimitiveTypeNode.int_()) + ) + # Export CV_MAKETYPE + root.add_function( + "CV_MAKETYPE", + (FunctionNode.Arg("depth", PrimitiveTypeNode.int_()), + FunctionNode.Arg("channels", PrimitiveTypeNode.int_())), + FunctionNode.RetType(PrimitiveTypeNode.int_()) + ) + + +def make_optional_arg(arg_name: str) -> Callable[[NamespaceNode, SymbolName], None]: + def _make_optional_arg(root_node: NamespaceNode, + function_symbol_name: SymbolName) -> None: + function = find_function_node(root_node, function_symbol_name) + for overload in function.overloads: + arg_idx = _find_argument_index(overload.arguments, arg_name) + # Avoid multiplying optional qualification + if isinstance(overload.arguments[arg_idx].type_node, OptionalTypeNode): + continue + + overload.arguments[arg_idx].type_node = OptionalTypeNode( + cast(TypeNode, overload.arguments[arg_idx].type_node) + ) + + return _make_optional_arg + + +def refine_cuda_module(root: NamespaceNode) -> None: + def fix_cudaoptflow_enums_names() -> None: + for class_name in ("NvidiaOpticalFlow_1_0", "NvidiaOpticalFlow_2_0"): + if class_name not in cuda_root.classes: + continue + opt_flow_class = cuda_root.classes[class_name] + _trim_class_name_from_argument_types( + for_each_function_overload(opt_flow_class), class_name + ) + + def fix_namespace_usage_scope(cuda_ns: NamespaceNode) -> None: + USED_TYPES = ("GpuMat", "Stream") + + def fix_type_usage(type_node: TypeNode) -> None: + if isinstance(type_node, AggregatedTypeNode): + for item in type_node.items: + fix_type_usage(item) + if isinstance(type_node, ASTNodeTypeNode): + if type_node._typename in USED_TYPES: + type_node._typename = f"cuda_{type_node._typename}" + + for overload in for_each_function_overload(cuda_ns): + if overload.return_type is not None: + fix_type_usage(overload.return_type.type_node) + for type_node in [arg.type_node for arg in overload.arguments + if arg.type_node is not None]: + fix_type_usage(type_node) + + if "cuda" not in root.namespaces: + return + cuda_root = root.namespaces["cuda"] + fix_cudaoptflow_enums_names() + for ns in [ns for ns_name, ns in root.namespaces.items() + if ns_name.startswith("cuda")]: + fix_namespace_usage_scope(ns) + + +def refine_highgui_module(root: NamespaceNode) -> None: + # Check if library is built with enabled highgui module + if "destroyAllWindows" not in root.functions: + return + """ + def createTrackbar(trackbarName: str, + windowName: str, + value: int, + count: int, + onChange: Callable[[int], None]) -> None: ... + """ + root.add_function( + "createTrackbar", + [ + FunctionNode.Arg("trackbarName", PrimitiveTypeNode.str_()), + FunctionNode.Arg("windowName", PrimitiveTypeNode.str_()), + FunctionNode.Arg("value", PrimitiveTypeNode.int_()), + FunctionNode.Arg("count", PrimitiveTypeNode.int_()), + FunctionNode.Arg("onChange", + CallableTypeNode("TrackbarCallback", + PrimitiveTypeNode.int_("int"))), + ] + ) + """ + def createButton(buttonName: str, + onChange: Callable[[tuple[int] | tuple[int, Any]], None], + userData: Any | None = ..., + buttonType: int = ..., + initialButtonState: int = ...) -> None: ... + """ + root.add_function( + "createButton", + [ + FunctionNode.Arg("buttonName", PrimitiveTypeNode.str_()), + FunctionNode.Arg( + "onChange", + CallableTypeNode( + "ButtonCallback", + UnionTypeNode( + "onButtonChangeCallbackData", + [ + TupleTypeNode("onButtonChangeCallbackData", + [PrimitiveTypeNode.int_(), ]), + TupleTypeNode("onButtonChangeCallbackData", + [PrimitiveTypeNode.int_(), + AnyTypeNode("void*")]) + ] + ) + )), + FunctionNode.Arg("userData", + OptionalTypeNode(AnyTypeNode("void*")), + default_value="None"), + FunctionNode.Arg("buttonType", PrimitiveTypeNode.int_(), + default_value="0"), + FunctionNode.Arg("initialButtonState", PrimitiveTypeNode.int_(), + default_value="0") + ] + ) + """ + def setMouseCallback( + windowName: str, + onMouse: Callback[[int, int, int, int, Any | None], None], + param: Any | None = ... + ) -> None: ... + """ + root.add_function( + "setMouseCallback", + [ + FunctionNode.Arg("windowName", PrimitiveTypeNode.str_()), + FunctionNode.Arg( + "onMouse", + CallableTypeNode("MouseCallback", [ + PrimitiveTypeNode.int_(), + PrimitiveTypeNode.int_(), + PrimitiveTypeNode.int_(), + PrimitiveTypeNode.int_(), + OptionalTypeNode(AnyTypeNode("void*")) + ]) + ), + FunctionNode.Arg("param", OptionalTypeNode(AnyTypeNode("void*")), + default_value="None") + ] + ) + + +def refine_dnn_module(root: NamespaceNode) -> None: + if "dnn" not in root.namespaces: + return + dnn_module = root.namespaces["dnn"] + + """ + class LayerProtocol(Protocol): + def __init__( + self, params: dict[str, DictValue], + blobs: typing.Sequence[cv2.typing.MatLike] + ) -> None: ... + + def getMemoryShapes( + self, inputs: typing.Sequence[typing.Sequence[int]] + ) -> typing.Sequence[typing.Sequence[int]]: ... + + def forward( + self, inputs: typing.Sequence[cv2.typing.MatLike] + ) -> typing.Sequence[cv2.typing.MatLike]: ... + """ + layer_proto = ProtocolClassNode("LayerProtocol", dnn_module) + layer_proto.add_function( + "__init__", + arguments=[ + FunctionNode.Arg( + "params", + DictTypeNode( + "LayerParams", PrimitiveTypeNode.str_(), + create_type_node("cv::dnn::DictValue") + ) + ), + FunctionNode.Arg("blobs", create_type_node("vector")) + ] + ) + layer_proto.add_function( + "getMemoryShapes", + arguments=[ + FunctionNode.Arg("inputs", + create_type_node("vector>")) + ], + return_type=FunctionNode.RetType( + create_type_node("vector>") + ) + ) + layer_proto.add_function( + "forward", + arguments=[ + FunctionNode.Arg("inputs", create_type_node("vector")) + ], + return_type=FunctionNode.RetType(create_type_node("vector")) + ) + + """ + def dnn_registerLayer(layerTypeName: str, + layerClass: typing.Type[LayerProtocol]) -> None: ... + """ + root.add_function( + "dnn_registerLayer", + arguments=[ + FunctionNode.Arg("layerTypeName", PrimitiveTypeNode.str_()), + FunctionNode.Arg( + "layerClass", + ClassTypeNode(ASTNodeTypeNode( + layer_proto.export_name, f"dnn.{layer_proto.export_name}" + )) + ) + ] + ) + + """ + def dnn_unregisterLayer(layerTypeName: str) -> None: ... + """ + root.add_function( + "dnn_unregisterLayer", + arguments=[ + FunctionNode.Arg("layerTypeName", PrimitiveTypeNode.str_()) + ] + ) + + +def _trim_class_name_from_argument_types( + overloads: Iterable[FunctionNode.Overload], + class_name: str +) -> None: + separator = f"{class_name}_" + for overload in overloads: + for arg in [arg for arg in overload.arguments + if arg.type_node is not None]: + ast_node = cast(ASTNodeTypeNode, arg.type_node) + if class_name in ast_node.ctype_name: + fixed_name = ast_node._typename.split(separator)[-1] + ast_node._typename = fixed_name + + +def _find_argument_index(arguments: Sequence[FunctionNode.Arg], + name: str) -> int: + for i, arg in enumerate(arguments): + if arg.name == name: + return i + raise RuntimeError( + f"Failed to find argument with name: '{name}' in {arguments}" + ) + + +NODES_TO_REFINE = { + SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"), + SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"), +} + +ERROR_CLASS_PROPERTIES = ( + ClassProperty("code", PrimitiveTypeNode.int_(), False), + ClassProperty("err", PrimitiveTypeNode.str_(), False), + ClassProperty("file", PrimitiveTypeNode.str_(), False), + ClassProperty("func", PrimitiveTypeNode.str_(), False), + ClassProperty("line", PrimitiveTypeNode.int_(), False), + ClassProperty("msg", PrimitiveTypeNode.str_(), False), +) diff --git a/modules/python/src2/typing_stubs_generation/ast_utils.py b/modules/python/src2/typing_stubs_generation/ast_utils.py index 1f802f6da5..e8ef52d19a 100644 --- a/modules/python/src2/typing_stubs_generation/ast_utils.py +++ b/modules/python/src2/typing_stubs_generation/ast_utils.py @@ -1,4 +1,5 @@ -from typing import NamedTuple, Sequence, Tuple, Union, List, Dict +from typing import (NamedTuple, Sequence, Tuple, Union, List, + Dict, Callable, Optional, Generator, cast) import keyword from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode, @@ -142,13 +143,24 @@ because 'GOpaque' class is not registered yet return scope -def find_class_node(root: NamespaceNode, full_class_name: str, - namespaces: Sequence[str]) -> ClassNode: - symbol_name = SymbolName.parse(full_class_name, namespaces) - scope = find_scope(root, symbol_name) - if symbol_name.name not in scope.classes: - raise SymbolNotFoundError("Can't find {} in its scope".format(symbol_name)) - return scope.classes[symbol_name.name] +def find_class_node(root: NamespaceNode, class_symbol: SymbolName, + create_missing_namespaces: bool = False) -> ClassNode: + scope = find_scope(root, class_symbol, create_missing_namespaces) + if class_symbol.name not in scope.classes: + raise SymbolNotFoundError( + "Can't find {} in its scope".format(class_symbol) + ) + return scope.classes[class_symbol.name] + + +def find_function_node(root: NamespaceNode, function_symbol: SymbolName, + create_missing_namespaces: bool = False) -> FunctionNode: + scope = find_scope(root, function_symbol, create_missing_namespaces) + if function_symbol.name not in scope.functions: + raise SymbolNotFoundError( + "Can't find {} in its scope".format(function_symbol) + ) + return scope.functions[function_symbol.name] def create_function_node_in_scope(scope: Union[NamespaceNode, ClassNode], @@ -192,9 +204,7 @@ def create_function_node_in_scope(scope: Union[NamespaceNode, ClassNode], outlist = variant.py_outlist for _, argno in outlist: assert argno >= 0, \ - "Logic Error! Outlist contains function return type: {}".format( - outlist - ) + f"Logic Error! Outlist contains function return type: {outlist}" ret_types.append(create_type_node(variant.args[argno].tp)) @@ -323,12 +333,18 @@ def resolve_enum_scopes(root: NamespaceNode, enum_node.parent = scope -def get_enclosing_namespace(node: ASTNode) -> NamespaceNode: +def get_enclosing_namespace( + node: ASTNode, + class_node_callback: Optional[Callable[[ClassNode], None]] = None +) -> NamespaceNode: """Traverses up nodes hierarchy to find closest enclosing namespace of the passed node Args: node (ASTNode): Node to find a namespace for. + class_node_callback (Optional[Callable[[ClassNode], None]]): Optional + callable object invoked for each traversed class node in bottom-up + order. Defaults: None. Returns: NamespaceNode: Closest enclosing namespace of the provided node. @@ -360,10 +376,61 @@ def get_enclosing_namespace(node: ASTNode) -> NamespaceNode: "Can't find enclosing namespace for '{}' known as: '{}'".format( node.full_export_name, node.native_name ) + if class_node_callback: + class_node_callback(cast(ClassNode, parent_node)) parent_node = parent_node.parent return parent_node +def get_enum_module_and_export_name(enum_node: EnumerationNode) -> Tuple[str, str]: + """Get export name of the enum node with its module name. + + Note: Enumeration export names are prefixed with enclosing class names. + + Args: + enum_node (EnumerationNode): Enumeration node to construct name for. + + Returns: + Tuple[str, str]: a pair of enum export name and its full module name. + """ + enum_export_name = enum_node.export_name + + def update_full_export_name(class_node: ClassNode) -> None: + nonlocal enum_export_name + enum_export_name = class_node.export_name + "_" + enum_export_name + + namespace_node = get_enclosing_namespace(enum_node, + update_full_export_name) + return enum_export_name, namespace_node.full_export_name + + +def for_each_class( + node: Union[NamespaceNode, ClassNode] +) -> Generator[ClassNode, None, None]: + for cls in node.classes.values(): + yield cls + if len(cls.classes): + yield from for_each_class(cls) + + +def for_each_function( + node: Union[NamespaceNode, ClassNode], + traverse_class_nodes: bool = True +) -> Generator[FunctionNode, None, None]: + yield from node.functions.values() + if traverse_class_nodes: + for cls in for_each_class(node): + yield from for_each_function(cls) + + +def for_each_function_overload( + node: Union[NamespaceNode, ClassNode], + traverse_class_nodes: bool = True +) -> Generator[FunctionNode.Overload, None, None]: + for func in for_each_function(node, traverse_class_nodes): + yield from func.overloads + + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/modules/python/src2/typing_stubs_generation/generation.py b/modules/python/src2/typing_stubs_generation/generation.py index 4330683774..c5578bb3a8 100644 --- a/modules/python/src2/typing_stubs_generation/generation.py +++ b/modules/python/src2/typing_stubs_generation/generation.py @@ -2,19 +2,26 @@ __all__ = ("generate_typing_stubs", ) from io import StringIO from pathlib import Path -from typing import (Generator, Type, Callable, NamedTuple, Union, Set, Dict, - Collection) +import re +from typing import (Callable, NamedTuple, Union, Set, Dict, + Collection, Tuple, List) import warnings -from .ast_utils import get_enclosing_namespace +from .ast_utils import (get_enclosing_namespace, + get_enum_module_and_export_name, + for_each_function_overload, + for_each_class) from .predefined_types import PREDEFINED_TYPES +from .api_refinement import apply_manual_api_refinement -from .nodes import (ASTNode, NamespaceNode, ClassNode, FunctionNode, - EnumerationNode, ConstantNode) +from .nodes import (ASTNode, ASTNodeType, NamespaceNode, ClassNode, + FunctionNode, EnumerationNode, ConstantNode, + ProtocolClassNode) from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode, - AggregatedTypeNode) + AggregatedTypeNode, ASTNodeTypeNode, + ConditionalAliasTypeNode, PrimitiveTypeNode) def generate_typing_stubs(root: NamespaceNode, output_path: Path): @@ -45,6 +52,18 @@ def generate_typing_stubs(root: NamespaceNode, output_path: Path): root (NamespaceNode): Root namespace node of the library AST. output_path (Path): Path to output directory. """ + # Perform special handling for function arguments that has some conventions + # not expressed in their API e.g. optionality of mutually exclusive arguments + # without default values: + # ```cxx + # cv::resize(cv::InputArray src, cv::OutputArray dst, cv::Size dsize, + # double fx = 0.0, double fy = 0.0, int interpolation); + # ``` + # should accept `None` as `dsize`: + # ```python + # cv2.resize(image, dsize=None, fx=0.5, fy=0.5) + # ``` + apply_manual_api_refinement(root) # Most of the time type nodes miss their full name (especially function # arguments and return types), so resolution should start from the narrowest # scope and gradually expanded. @@ -70,10 +89,11 @@ def generate_typing_stubs(root: NamespaceNode, output_path: Path): # checked and at least 1 node is still unresolved. root.resolve_type_nodes() _generate_typing_module(root, output_path) + _populate_reexported_symbols(root) _generate_typing_stubs(root, output_path) -def _generate_typing_stubs(root: NamespaceNode, output_path: Path): +def _generate_typing_stubs(root: NamespaceNode, output_path: Path) -> None: output_path = Path(output_path) / root.export_name output_path.mkdir(parents=True, exist_ok=True) @@ -82,19 +102,24 @@ def _generate_typing_stubs(root: NamespaceNode, output_path: Path): output_stream = StringIO() + # Add empty __all__ dunder on top of the module + output_stream.write("__all__: list[str] = []\n\n") + # Write required imports at the top of file _write_required_imports(required_imports, output_stream) - # Write constants section, because constants don't impose any dependencies - _generate_section_stub(StubSection("# Constants", ConstantNode), root, - output_stream, 0) + _write_reexported_symbols_section(root, output_stream) + # NOTE: Enumerations require special handling, because all enumeration # constants are exposed as module attributes - has_enums = _generate_section_stub(StubSection("# Enumerations", EnumerationNode), - root, output_stream, 0) + has_enums = _generate_section_stub( + StubSection("# Enumerations", ASTNodeType.Enumeration), root, + output_stream, 0 + ) # Collect all enums from class level and export them to module level for class_node in root.classes.values(): - if _generate_enums_from_classes_tree(class_node, output_stream, indent=0): + if _generate_enums_from_classes_tree(class_node, output_stream, + indent=0): has_enums = True # 2 empty lines between enum and classes definitions if has_enums: @@ -112,14 +137,15 @@ def _generate_typing_stubs(root: NamespaceNode, output_path: Path): class StubSection(NamedTuple): name: str - node_type: Type[ASTNode] + node_type: ASTNodeType STUB_SECTIONS = ( - StubSection("# Constants", ConstantNode), - # StubSection("# Enumerations", EnumerationNode), # Skipped for now (special rules) - StubSection("# Classes", ClassNode), - StubSection("# Functions", FunctionNode) + StubSection("# Constants", ASTNodeType.Constant), + # Enumerations are skipped due to special handling rules + # StubSection("# Enumerations", ASTNodeType.Enumeration), + StubSection("# Classes", ASTNodeType.Class), + StubSection("# Functions", ASTNodeType.Function) ) @@ -228,9 +254,9 @@ def _generate_class_stub(class_node: ClassNode, output_stream: StringIO, else: bases.append(base.export_name) - inheritance_str = "({})".format( - ', '.join(bases) - ) + inheritance_str = f"({', '.join(bases)})" + elif isinstance(class_node, ProtocolClassNode): + inheritance_str = "(Protocol)" else: inheritance_str = "" @@ -269,7 +295,8 @@ def _generate_class_stub(class_node: ClassNode, output_stream: StringIO, def _generate_constant_stub(constant_node: ConstantNode, output_stream: StringIO, indent: int = 0, - extra_export_prefix: str = "") -> None: + extra_export_prefix: str = "", + generate_uppercase_version: bool = True) -> Tuple[str, ...]: """Generates stub for the provided constant node. Args: @@ -277,18 +304,36 @@ def _generate_constant_stub(constant_node: ConstantNode, output_stream (StringIO): Output stream for constant stub. indent (int, optional): Indent used for each line written to `output_stream`. Defaults to 0. - extra_export_prefix (str, optional) Extra prefix added to the export + extra_export_prefix (str, optional): Extra prefix added to the export constant name. Defaults to empty string. + generate_uppercase_version (bool, optional): Generate uppercase version + alongside the normal one. Defaults to True. + + Returns: + Tuple[str, ...]: exported constants names. """ - output_stream.write( - "{indent}{prefix}{name}: {value_type}\n".format( - prefix=extra_export_prefix, - name=constant_node.export_name, - value_type=constant_node.value_type, - indent=" " * indent + def write_constant_to_stream(export_name: str) -> None: + output_stream.write( + "{indent}{name}: {value_type}\n".format( + name=export_name, + value_type=constant_node.value_type, + indent=" " * indent + ) ) - ) + + export_name = extra_export_prefix + constant_node.export_name + write_constant_to_stream(export_name) + if generate_uppercase_version: + # Handle Python "magic" constants like __version__ + if re.match(r"^__.*__$", export_name) is not None: + return export_name, + + uppercase_name = re.sub(r"([a-z])([A-Z])", r"\1_\2", export_name).upper() + if export_name != uppercase_name: + write_constant_to_stream(uppercase_name) + return export_name, uppercase_name + return export_name, def _generate_enumeration_stub(enumeration_node: EnumerationNode, @@ -353,18 +398,20 @@ def _generate_enumeration_stub(enumeration_node: EnumerationNode, entries_extra_prefix = extra_export_prefix if enumeration_node.is_scoped: entries_extra_prefix += enumeration_node.export_name + "_" + generated_constants_entries: List[str] = [] for entry in enumeration_node.constants.values(): - _generate_constant_stub(entry, output_stream, indent, entries_extra_prefix) + generated_constants_entries.extend( + _generate_constant_stub(entry, output_stream, indent, entries_extra_prefix) + ) # Unnamed enumerations are skipped as definition if enumeration_node.export_name.endswith(""): output_stream.write("\n") return output_stream.write( - "{indent}{export_prefix}{name} = int # One of [{entries}]\n\n".format( + '{indent}{export_prefix}{name} = int\n{indent}"""One of [{entries}]"""\n\n'.format( export_prefix=extra_export_prefix, name=enumeration_node.export_name, - entries=", ".join(entry.export_name - for entry in enumeration_node.constants.values()), + entries=", ".join(generated_constants_entries), indent=" " * indent ) ) @@ -500,35 +547,12 @@ def check_overload_presence(node: Union[NamespaceNode, ClassNode]) -> bool: otherwise. """ for func_node in node.functions.values(): - if len(func_node.overloads): + if len(func_node.overloads) > 1: return True return False -def _for_each_class(node: Union[NamespaceNode, ClassNode]) \ - -> Generator[ClassNode, None, None]: - for cls in node.classes.values(): - yield cls - if len(cls.classes): - yield from _for_each_class(cls) - - -def _for_each_function(node: Union[NamespaceNode, ClassNode]) \ - -> Generator[FunctionNode, None, None]: - for func in node.functions.values(): - yield func - for cls in node.classes.values(): - yield from _for_each_function(cls) - - -def _for_each_function_overload(node: Union[NamespaceNode, ClassNode]) \ - -> Generator[FunctionNode.Overload, None, None]: - for func in _for_each_function(node): - for overload in func.overloads: - yield overload - - -def _collect_required_imports(root: NamespaceNode) -> Set[str]: +def _collect_required_imports(root: NamespaceNode) -> Collection[str]: """Collects all imports required for classes and functions typing stubs declarations. @@ -536,8 +560,8 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]: root (NamespaceNode): Namespace node to collect imports for Returns: - Set[str]: Collection of unique `import smth` statements required for - classes and function declarations of `root` node. + Collection[str]: Collection of unique `import smth` statements required + for classes and function declarations of `root` node. """ def _add_required_usage_imports(type_node: TypeNode, imports: Set[str]): @@ -550,7 +574,8 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]: has_overload = check_overload_presence(root) # if there is no module-level functions with overload, check its presence # during class traversing, including their inner-classes - for cls in _for_each_class(root): + has_protocol = False + for cls in for_each_class(root): if not has_overload and check_overload_presence(cls): has_overload = True required_imports.add("import typing") @@ -564,12 +589,15 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]: required_imports.add( "import " + base_namespace.full_export_name ) + if isinstance(cls, ProtocolClassNode): + has_protocol = True if has_overload: required_imports.add("import typing") # Importing modules required to resolve functions arguments - for overload in _for_each_function_overload(root): - for arg in filter(lambda a: a.type_node is not None, overload.arguments): + for overload in for_each_function_overload(root): + for arg in filter(lambda a: a.type_node is not None, + overload.arguments): _add_required_usage_imports(arg.type_node, required_imports) # type: ignore if overload.return_type is not None: _add_required_usage_imports(overload.return_type.type_node, @@ -579,7 +607,74 @@ def _collect_required_imports(root: NamespaceNode) -> Set[str]: if root_import in required_imports: required_imports.remove(root_import) - return required_imports + if has_protocol: + required_imports.add("import sys") + ordered_required_imports = sorted(required_imports) + + # Protocol import always goes as last import statement + if has_protocol: + ordered_required_imports.append( + """if sys.version_info >= (3, 8): + from typing import Protocol +else: + from typing_extensions import Protocol""" + ) + + return ordered_required_imports + + +def _populate_reexported_symbols(root: NamespaceNode) -> None: + # Re-export all submodules to allow referencing symbols in submodules + # without submodule import. Example: + # `cv2.aruco.ArucoDetector` should be accessible without `import cv2.aruco` + def _reexport_submodule(ns: NamespaceNode) -> None: + for submodule in ns.namespaces.values(): + ns.reexported_submodules.append(submodule.export_name) + _reexport_submodule(submodule) + + _reexport_submodule(root) + + # Special cases, symbols defined in possible pure Python submodules + # should be + root.reexported_submodules_symbols["mat_wrapper"].append("Mat") + + +def _write_reexported_symbols_section(module: NamespaceNode, + output_stream: StringIO) -> None: + """Write re-export section for the given module. + + Re-export statements have from `from module_name import smth as smth`. + Example: + ```python + from cv2 import aruco as aruco + from cv2 import cuda as cuda + from cv2 import ml as ml + from cv2.mat_wrapper import Mat as Mat + ``` + + Args: + module (NamespaceNode): Module with re-exported symbols. + output_stream (StringIO): Output stream for re-export statements. + """ + + parent_name = module.full_export_name + for submodule in sorted(module.reexported_submodules): + output_stream.write( + "from {0} import {1} as {1}\n".format(parent_name, submodule) + ) + + for submodule, symbols in sorted(module.reexported_submodules_symbols.items(), + key=lambda kv: kv[0]): + for symbol in symbols: + output_stream.write( + "from {0}.{1} import {2} as {2}\n".format( + parent_name, submodule, symbol + ) + ) + + if len(module.reexported_submodules) or \ + len(module.reexported_submodules_symbols): + output_stream.write("\n\n") def _write_required_imports(required_imports: Collection[str], @@ -592,7 +687,7 @@ def _write_required_imports(required_imports: Collection[str], output_stream (StringIO): Output stream for import statements. """ - for required_import in sorted(required_imports): + for required_import in required_imports: output_stream.write(required_import) output_stream.write("\n") if len(required_imports): @@ -614,31 +709,82 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None: f"Provided type node '{type_node.ctype_name}' is not an aggregated type" for item in filter(lambda i: isinstance(i, AliasRefTypeNode), type_node): - register_alias(PREDEFINED_TYPES[item.ctype_name]) # type: ignore + type_node = PREDEFINED_TYPES[item.ctype_name] + if isinstance(type_node, AliasTypeNode): + register_alias(type_node) + elif isinstance(type_node, ConditionalAliasTypeNode): + conditional_type_nodes[type_node.ctype_name] = type_node + + def create_alias_for_enum_node(enum_node_alias: AliasTypeNode) -> ConditionalAliasTypeNode: + """Create conditional int alias corresponding to the given enum node. + + Args: + enum_node (AliasTypeNode): Enumeration node to create conditional + int alias for. + + Returns: + ConditionalAliasTypeNode: conditional int alias node with same + export name as enum. + """ + enum_node = enum_node_alias.ast_node + assert enum_node.node_type == ASTNodeType.Enumeration, \ + f"{enum_node} has wrong node type. Expected type: Enumeration." + + enum_export_name, enum_module_name = get_enum_module_and_export_name( + enum_node + ) + return ConditionalAliasTypeNode( + enum_export_name, + "typing.TYPE_CHECKING", + positive_branch_type=enum_node_alias, + negative_branch_type=PrimitiveTypeNode.int_(enum_export_name), + condition_required_imports=("import typing", ) + ) def register_alias(alias_node: AliasTypeNode) -> None: typename = alias_node.typename # Check if alias is already registered if typename in aliases: return + + # Collect required imports for alias definition + for required_import in alias_node.required_definition_imports: + required_imports.add(required_import) + if isinstance(alias_node.value, AggregatedTypeNode): # Check if collection contains a link to another alias register_alias_links_from_aggregated_type(alias_node.value) + # Remove references to alias nodes + for i, item in enumerate(alias_node.value.items): + # Process enumerations only + if not isinstance(item, ASTNodeTypeNode) or item.ast_node is None: + continue + if item.ast_node.node_type != ASTNodeType.Enumeration: + continue + enum_node = create_alias_for_enum_node(item) + alias_node.value.items[i] = enum_node + conditional_type_nodes[enum_node.ctype_name] = enum_node + + if isinstance(alias_node.value, ASTNodeTypeNode) \ + and alias_node.value.ast_node == ASTNodeType.Enumeration: + enum_node = create_alias_for_enum_node(alias_node.ast_node) + conditional_type_nodes[enum_node.ctype_name] = enum_node + return + # Strip module prefix from aliased types aliases[typename] = alias_node.value.full_typename.replace( root.export_name + ".typing.", "" ) if alias_node.doc is not None: aliases[typename] += f'\n"""{alias_node.doc}"""' - for required_import in alias_node.required_definition_imports: - required_imports.add(required_import) output_path = Path(output_path) / root.export_name / "typing" output_path.mkdir(parents=True, exist_ok=True) required_imports: Set[str] = set() aliases: Dict[str, str] = {} + conditional_type_nodes: Dict[str, ConditionalAliasTypeNode] = {} # Resolve each node and register aliases TypeNode.compatible_to_runtime_usage = True @@ -646,6 +792,12 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None: node.resolve(root) if isinstance(node, AliasTypeNode): register_alias(node) + elif isinstance(node, ConditionalAliasTypeNode): + conditional_type_nodes[node.ctype_name] = node + + for node in conditional_type_nodes.values(): + for required_import in node.required_definition_imports: + required_imports.add(required_import) output_stream = StringIO() output_stream.write("__all__ = [\n") @@ -655,11 +807,14 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None: _write_required_imports(required_imports, output_stream) + # Add type checking time definitions as generated __init__.py content + for _, type_node in conditional_type_nodes.items(): + output_stream.write(f"if {type_node.condition}:\n ") + output_stream.write(f"{type_node.typename} = {type_node.positive_branch_type.full_typename}\nelse:\n") + output_stream.write(f" {type_node.typename} = {type_node.negative_branch_type.full_typename}\n\n\n") + for alias_name, alias_type in aliases.items(): - output_stream.write(alias_name) - output_stream.write(" = ") - output_stream.write(alias_type) - output_stream.write("\n") + output_stream.write(f"{alias_name} = {alias_type}\n") TypeNode.compatible_to_runtime_usage = False (output_path / "__init__.py").write_text(output_stream.getvalue()) @@ -669,8 +824,8 @@ StubGenerator = Callable[[ASTNode, StringIO, int], None] NODE_TYPE_TO_STUB_GENERATOR = { - ClassNode: _generate_class_stub, - ConstantNode: _generate_constant_stub, - EnumerationNode: _generate_enumeration_stub, - FunctionNode: _generate_function_stub + ASTNodeType.Class: _generate_class_stub, + ASTNodeType.Constant: _generate_constant_stub, + ASTNodeType.Enumeration: _generate_enumeration_stub, + ASTNodeType.Function: _generate_function_stub } diff --git a/modules/python/src2/typing_stubs_generation/nodes/__init__.py b/modules/python/src2/typing_stubs_generation/nodes/__init__.py index fafcd9bc2b..a2dbc49964 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/__init__.py +++ b/modules/python/src2/typing_stubs_generation/nodes/__init__.py @@ -1,11 +1,12 @@ -from .node import ASTNode +from .node import ASTNode, ASTNodeType from .namespace_node import NamespaceNode -from .class_node import ClassNode, ClassProperty +from .class_node import ClassNode, ClassProperty, ProtocolClassNode from .function_node import FunctionNode from .enumeration_node import EnumerationNode from .constant_node import ConstantNode from .type_node import ( TypeNode, OptionalTypeNode, UnionTypeNode, NoneTypeNode, TupleTypeNode, ASTNodeTypeNode, AliasTypeNode, SequenceTypeNode, AnyTypeNode, - AggregatedTypeNode, NDArrayTypeNode, AliasRefTypeNode, + AggregatedTypeNode, NDArrayTypeNode, AliasRefTypeNode, PrimitiveTypeNode, + CallableTypeNode, DictTypeNode, ClassTypeNode ) diff --git a/modules/python/src2/typing_stubs_generation/nodes/class_node.py b/modules/python/src2/typing_stubs_generation/nodes/class_node.py index b3d394786f..78bd01d61f 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/class_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/class_node.py @@ -63,8 +63,9 @@ class ClassNode(ASTNode): return 1 + sum(base.weight for base in self.bases) @property - def children_types(self) -> Tuple[Type[ASTNode], ...]: - return (ClassNode, FunctionNode, EnumerationNode, ConstantNode) + def children_types(self) -> Tuple[ASTNodeType, ...]: + return (ASTNodeType.Class, ASTNodeType.Function, + ASTNodeType.Enumeration, ASTNodeType.Constant) @property def node_type(self) -> ASTNodeType: @@ -72,19 +73,19 @@ class ClassNode(ASTNode): @property def classes(self) -> Dict[str, "ClassNode"]: - return self._children[ClassNode] + return self._children[ASTNodeType.Class] @property def functions(self) -> Dict[str, FunctionNode]: - return self._children[FunctionNode] + return self._children[ASTNodeType.Function] @property def enumerations(self) -> Dict[str, EnumerationNode]: - return self._children[EnumerationNode] + return self._children[ASTNodeType.Enumeration] @property def constants(self) -> Dict[str, ConstantNode]: - return self._children[ConstantNode] + return self._children[ASTNodeType.Constant] def add_class(self, name: str, bases: Sequence["weakref.ProxyType[ClassNode]"] = (), @@ -179,3 +180,11 @@ class ClassNode(ASTNode): self.full_export_name, root.full_export_name, errors ) ) + + +class ProtocolClassNode(ClassNode): + def __init__(self, name: str, parent: Optional[ASTNode] = None, + export_name: Optional[str] = None, + properties: Sequence[ClassProperty] = ()) -> None: + super().__init__(name, parent, export_name, bases=(), + properties=properties) diff --git a/modules/python/src2/typing_stubs_generation/nodes/constant_node.py b/modules/python/src2/typing_stubs_generation/nodes/constant_node.py index 63abd8bfb4..ad18b80b50 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/constant_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/constant_node.py @@ -1,4 +1,4 @@ -from typing import Type, Optional, Tuple +from typing import Optional, Tuple from .node import ASTNode, ASTNodeType @@ -11,9 +11,10 @@ class ConstantNode(ASTNode): export_name: Optional[str] = None) -> None: super().__init__(name, parent, export_name) self.value = value + self._value_type = "int" @property - def children_types(self) -> Tuple[Type[ASTNode], ...]: + def children_types(self) -> Tuple[ASTNodeType, ...]: return () @property @@ -22,7 +23,7 @@ class ConstantNode(ASTNode): @property def value_type(self) -> str: - return 'int' + return self._value_type def __str__(self) -> str: return "Constant('{}' exported as '{}': {})".format( diff --git a/modules/python/src2/typing_stubs_generation/nodes/enumeration_node.py b/modules/python/src2/typing_stubs_generation/nodes/enumeration_node.py index 249f29db1c..12b996d1a1 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/enumeration_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/enumeration_node.py @@ -18,8 +18,8 @@ class EnumerationNode(ASTNode): self.is_scoped = is_scoped @property - def children_types(self) -> Tuple[Type[ASTNode], ...]: - return (ConstantNode, ) + def children_types(self) -> Tuple[ASTNodeType, ...]: + return (ASTNodeType.Constant, ) @property def node_type(self) -> ASTNodeType: @@ -27,7 +27,7 @@ class EnumerationNode(ASTNode): @property def constants(self) -> Dict[str, ConstantNode]: - return self._children[ConstantNode] + return self._children[ASTNodeType.Constant] def add_constant(self, name: str, value: str) -> ConstantNode: return self._add_child(ConstantNode, name, value=value) diff --git a/modules/python/src2/typing_stubs_generation/nodes/function_node.py b/modules/python/src2/typing_stubs_generation/nodes/function_node.py index a4e4f56561..568676f69a 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/function_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/function_node.py @@ -1,4 +1,4 @@ -from typing import NamedTuple, Sequence, Type, Optional, Tuple, List +from typing import NamedTuple, Sequence, Optional, Tuple, List from .node import ASTNode, ASTNodeType from .type_node import TypeNode, NoneTypeNode, TypeResolutionError @@ -10,10 +10,12 @@ class FunctionNode(ASTNode): This class defines an overload set rather then function itself, because function without overloads is represented as FunctionNode with 1 overload. """ - class Arg(NamedTuple): - name: str - type_node: Optional[TypeNode] = None - default_value: Optional[str] = None + class Arg: + def __init__(self, name: str, type_node: Optional[TypeNode] = None, + default_value: Optional[str] = None) -> None: + self.name = name + self.type_node = type_node + self.default_value = default_value @property def typename(self) -> Optional[str]: @@ -24,8 +26,18 @@ class FunctionNode(ASTNode): return self.type_node.relative_typename(root) return None - class RetType(NamedTuple): - type_node: TypeNode = NoneTypeNode("void") + def __str__(self) -> str: + return ( + f"Arg(name={self.name}, type_node={self.type_node}," + f" default_value={self.default_value})" + ) + + def __repr__(self) -> str: + return str(self) + + class RetType: + def __init__(self, type_node: TypeNode = NoneTypeNode("void")) -> None: + self.type_node = type_node @property def typename(self) -> str: @@ -34,6 +46,12 @@ class FunctionNode(ASTNode): def relative_typename(self, root: str) -> Optional[str]: return self.type_node.relative_typename(root) + def __str__(self) -> str: + return f"RetType(type_node={self.type_node})" + + def __repr__(self) -> str: + return str(self) + class Overload(NamedTuple): arguments: Sequence["FunctionNode.Arg"] = () return_type: Optional["FunctionNode.RetType"] = None @@ -80,7 +98,7 @@ class FunctionNode(ASTNode): return ASTNodeType.Function @property - def children_types(self) -> Tuple[Type[ASTNode], ...]: + def children_types(self) -> Tuple[ASTNodeType, ...]: return () def add_overload(self, arguments: Sequence["FunctionNode.Arg"] = (), diff --git a/modules/python/src2/typing_stubs_generation/nodes/namespace_node.py b/modules/python/src2/typing_stubs_generation/nodes/namespace_node.py index 2206f0e586..445ef77b7d 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/namespace_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/namespace_node.py @@ -1,14 +1,13 @@ -from typing import Type, Iterable, Sequence, Tuple, Optional, Dict import itertools import weakref - -from .node import ASTNode, ASTNodeType +from collections import defaultdict +from typing import Dict, List, Optional, Sequence, Tuple from .class_node import ClassNode, ClassProperty -from .function_node import FunctionNode -from .enumeration_node import EnumerationNode from .constant_node import ConstantNode - +from .enumeration_node import EnumerationNode +from .function_node import FunctionNode +from .node import ASTNode, ASTNodeType from .type_node import TypeResolutionError @@ -18,34 +17,45 @@ class NamespaceNode(ASTNode): NamespaceNode can have other namespaces, classes, functions, enumerations and global constants as its children nodes. """ + def __init__(self, name: str, parent: Optional[ASTNode] = None, + export_name: Optional[str] = None) -> None: + super().__init__(name, parent, export_name) + self.reexported_submodules: List[str] = [] + """List of reexported submodules""" + + self.reexported_submodules_symbols: Dict[str, List[str]] = defaultdict(list) + """Mapping between submodules export names and their symbols re-exported + in this module""" + + @property def node_type(self) -> ASTNodeType: return ASTNodeType.Namespace @property - def children_types(self) -> Tuple[Type[ASTNode], ...]: - return (NamespaceNode, ClassNode, FunctionNode, - EnumerationNode, ConstantNode) + def children_types(self) -> Tuple[ASTNodeType, ...]: + return (ASTNodeType.Namespace, ASTNodeType.Class, ASTNodeType.Function, + ASTNodeType.Enumeration, ASTNodeType.Constant) @property def namespaces(self) -> Dict[str, "NamespaceNode"]: - return self._children[NamespaceNode] + return self._children[ASTNodeType.Namespace] @property def classes(self) -> Dict[str, ClassNode]: - return self._children[ClassNode] + return self._children[ASTNodeType.Class] @property def functions(self) -> Dict[str, FunctionNode]: - return self._children[FunctionNode] + return self._children[ASTNodeType.Function] @property def enumerations(self) -> Dict[str, EnumerationNode]: - return self._children[EnumerationNode] + return self._children[ASTNodeType.Enumeration] @property def constants(self) -> Dict[str, ConstantNode]: - return self._children[ConstantNode] + return self._children[ASTNodeType.Constant] def add_namespace(self, name: str) -> "NamespaceNode": return self._add_child(NamespaceNode, name) diff --git a/modules/python/src2/typing_stubs_generation/nodes/node.py b/modules/python/src2/typing_stubs_generation/nodes/node.py index cba74c8977..20efb47145 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/node.py @@ -1,7 +1,7 @@ import abc import enum import itertools -from typing import (Iterator, Type, TypeVar, Iterable, Dict, +from typing import (Iterator, Type, TypeVar, Dict, Optional, Tuple, DefaultDict) from collections import defaultdict @@ -70,22 +70,22 @@ class ASTNode: self._parent: Optional["ASTNode"] = None self.parent = parent self.is_exported = True - self._children: DefaultDict[NodeType, NameToNode] = defaultdict(dict) + self._children: DefaultDict[ASTNodeType, NameToNode] = defaultdict(dict) def __str__(self) -> str: return "{}('{}' exported as '{}')".format( - type(self).__name__.replace("Node", ""), self.name, self.export_name + self.node_type.name, self.name, self.export_name ) def __repr__(self) -> str: return str(self) @abc.abstractproperty - def children_types(self) -> Tuple[Type["ASTNode"], ...]: + def children_types(self) -> Tuple[ASTNodeType, ...]: """Set of ASTNode types that are allowed to be children of this node Returns: - Tuple[Type[ASTNode], ...]: Types of children nodes + Tuple[ASTNodeType, ...]: Types of children nodes """ pass @@ -99,6 +99,9 @@ class ASTNode: """ pass + def node_type_name(self) -> str: + return f"{self.node_type.name}::{self.name}" + @property def name(self) -> str: return self.__name @@ -126,11 +129,11 @@ class ASTNode: "but got: {}".format(type(value)) if value is not None: - value.__check_child_before_add(type(self), self.name) + value.__check_child_before_add(self, self.name) # Detach from previous parent if self._parent is not None: - self._parent._children[type(self)].pop(self.name) + self._parent._children[self.node_type].pop(self.name) if value is None: self._parent = None @@ -138,28 +141,26 @@ class ASTNode: # Set a weak reference to a new parent and add self to its children self._parent = weakref.proxy(value) - value._children[type(self)][self.name] = self + value._children[self.node_type][self.name] = self - def __check_child_before_add(self, child_type: Type[ASTNodeSubtype], + def __check_child_before_add(self, child: ASTNodeSubtype, name: str) -> None: - assert len(self.children_types) > 0, \ - "Trying to add child node '{}::{}' to node '{}::{}' " \ - "that can't have children nodes".format(child_type.__name__, name, - type(self).__name__, - self.name) - - assert child_type in self.children_types, \ - "Trying to add child node '{}::{}' to node '{}::{}' " \ + assert len(self.children_types) > 0, ( + f"Trying to add child node '{child.node_type_name}' to node " + f"'{self.node_type_name}' that can't have children nodes" + ) + + assert child.node_type in self.children_types, \ + "Trying to add child node '{}' to node '{}' " \ "that supports only ({}) as its children types".format( - child_type.__name__, name, type(self).__name__, self.name, - ",".join(t.__name__ for t in self.children_types) + child.node_type_name, self.node_type_name, + ",".join(t.name for t in self.children_types) ) - if self._find_child(child_type, name) is not None: + if self._find_child(child.node_type, name) is not None: raise ValueError( - "Node '{}::{}' already has a child '{}::{}'".format( - type(self).__name__, self.name, child_type.__name__, name - ) + f"Node '{self.node_type_name}' already has a " + f"child '{child.node_type_name}'" ) def _add_child(self, child_type: Type[ASTNodeSubtype], name: str, @@ -180,15 +181,14 @@ class ASTNode: Returns: ASTNodeSubtype: Created ASTNode """ - self.__check_child_before_add(child_type, name) return child_type(name, parent=self, **kwargs) - def _find_child(self, child_type: Type[ASTNodeSubtype], + def _find_child(self, child_type: ASTNodeType, name: str) -> Optional[ASTNodeSubtype]: """Looks for child node with the given type and name. Args: - child_type (Type[ASTNodeSubtype]): Type of the child node. + child_type (ASTNodeType): Type of the child node. name (str): Name of the child node. Returns: diff --git a/modules/python/src2/typing_stubs_generation/nodes/type_node.py b/modules/python/src2/typing_stubs_generation/nodes/type_node.py index a21f983b20..089ff2ee9d 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/type_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/type_node.py @@ -307,14 +307,31 @@ class AliasTypeNode(TypeNode): return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc) @classmethod - def array_(cls, ctype_name: str, shape: Optional[Tuple[int, ...]], - dtype: Optional[str] = None, export_name: Optional[str] = None, - doc: Optional[str] = None): + def array_ref_(cls, ctype_name: str, array_ref_name: str, + shape: Optional[Tuple[int, ...]], + dtype: Optional[str] = None, + export_name: Optional[str] = None, + doc: Optional[str] = None): + """Create alias to array reference alias `array_ref_name`. + + This is required to preserve backward compatibility with Python < 3.9 + and NumPy 1.20, when NumPy module introduces generics support. + + Args: + ctype_name (str): Name of the alias. + array_ref_name (str): Name of the conditional array alias. + shape (Optional[Tuple[int, ...]]): Array shape. + dtype (Optional[str], optional): Array type. Defaults to None. + export_name (Optional[str], optional): Alias export name. + Defaults to None. + doc (Optional[str], optional): Documentation string for alias. + Defaults to None. + """ if doc is None: - doc = "Shape: " + str(shape) + doc = f"NDArray(shape={shape}, dtype={dtype})" else: - doc += ". Shape: " + str(shape) - return cls(ctype_name, NDArrayTypeNode(ctype_name, shape, dtype), + doc += f". NDArray(shape={shape}, dtype={dtype})" + return cls(ctype_name, AliasRefTypeNode(array_ref_name), export_name, doc) @classmethod @@ -376,23 +393,114 @@ class AliasTypeNode(TypeNode): export_name, doc) +class ConditionalAliasTypeNode(TypeNode): + """Type node representing an alias protected by condition checked in runtime. + For typing-related conditions, prefer using typing.TYPE_CHECKING. For a full explanation, see: + https://github.com/opencv/opencv/pull/23927#discussion_r1256326835 + + Example: + ```python + if typing.TYPE_CHECKING + NumPyArray = numpy.ndarray[typing.Any, numpy.dtype[numpy.generic]] + else: + NumPyArray = numpy.ndarray + ``` + is defined as follows: + ```python + + ConditionalAliasTypeNode( + "NumPyArray", + 'typing.TYPE_CHECKING', + NDArrayTypeNode("NumPyArray"), + NDArrayTypeNode("NumPyArray", use_numpy_generics=False), + condition_required_imports=("import typing",) + ) + ``` + """ + def __init__(self, ctype_name: str, condition: str, + positive_branch_type: TypeNode, + negative_branch_type: TypeNode, + export_name: Optional[str] = None, + condition_required_imports: Sequence[str] = ()) -> None: + super().__init__(ctype_name) + self.condition = condition + self.positive_branch_type = positive_branch_type + self.positive_branch_type.ctype_name = self.ctype_name + self.negative_branch_type = negative_branch_type + self.negative_branch_type.ctype_name = self.ctype_name + self._export_name = export_name + self._condition_required_imports = condition_required_imports + + @property + def typename(self) -> str: + if self._export_name is not None: + return self._export_name + return self.ctype_name + + @property + def full_typename(self) -> str: + return "cv2.typing." + self.typename + + @property + def required_definition_imports(self) -> Generator[str, None, None]: + yield from self.positive_branch_type.required_usage_imports + yield from self.negative_branch_type.required_usage_imports + yield from self._condition_required_imports + + @property + def required_usage_imports(self) -> Generator[str, None, None]: + yield "import cv2.typing" + + @property + def is_resolved(self) -> bool: + return self.positive_branch_type.is_resolved \ + and self.negative_branch_type.is_resolved + + def resolve(self, root: ASTNode): + try: + self.positive_branch_type.resolve(root) + self.negative_branch_type.resolve(root) + except TypeResolutionError as e: + raise TypeResolutionError( + 'Failed to resolve alias "{}" exposed as "{}"'.format( + self.ctype_name, self.typename + ) + ) from e + + @classmethod + def numpy_array_(cls, ctype_name: str, export_name: Optional[str] = None, + shape: Optional[Tuple[int, ...]] = None, + dtype: Optional[str] = None): + """Type subscription is not possible in python 3.8 and older numpy versions.""" + return cls( + ctype_name, + "typing.TYPE_CHECKING", + NDArrayTypeNode(ctype_name, shape, dtype), + NDArrayTypeNode(ctype_name, shape, dtype, + use_numpy_generics=False), + condition_required_imports=("import typing",) + ) + + class NDArrayTypeNode(TypeNode): """Type node representing NumPy ndarray. """ - def __init__(self, ctype_name: str, shape: Optional[Tuple[int, ...]] = None, - dtype: Optional[str] = None) -> None: + def __init__(self, ctype_name: str, + shape: Optional[Tuple[int, ...]] = None, + dtype: Optional[str] = None, + use_numpy_generics: bool = True) -> None: super().__init__(ctype_name) self.shape = shape self.dtype = dtype + self._use_numpy_generics = use_numpy_generics @property def typename(self) -> str: - return "numpy.ndarray[{shape}, numpy.dtype[{dtype}]]".format( + if self._use_numpy_generics: # NOTE: Shape is not fully supported yet - # shape=self.shape if self.shape is not None else "typing.Any", - shape="typing.Any", - dtype=self.dtype if self.dtype is not None else "numpy.generic" - ) + dtype = self.dtype if self.dtype is not None else "numpy.generic" + return f"numpy.ndarray[typing.Any, numpy.dtype[{dtype}]]" + return "numpy.ndarray" @property def required_usage_imports(self) -> Generator[str, None, None]: @@ -417,6 +525,10 @@ class ASTNodeTypeNode(TypeNode): self._module_name = module_name self._ast_node: Optional[weakref.ProxyType[ASTNode]] = None + @property + def ast_node(self): + return self._ast_node + @property def typename(self) -> str: if self._ast_node is None: @@ -557,13 +669,14 @@ class ContainerTypeNode(AggregatedTypeNode): @property def required_definition_imports(self) -> Generator[str, None, None]: yield "import typing" - return super().required_definition_imports + yield from super().required_definition_imports @property def required_usage_imports(self) -> Generator[str, None, None]: if TypeNode.compatible_to_runtime_usage: yield "import typing" - return super().required_usage_imports + yield from super().required_usage_imports + @abc.abstractproperty def type_format(self) -> str: pass @@ -726,6 +839,21 @@ class CallableTypeNode(AggregatedTypeNode): yield from super().required_usage_imports +class ClassTypeNode(ContainerTypeNode): + """Type node representing types themselves (refer to typing.Type) + """ + def __init__(self, value: TypeNode) -> None: + super().__init__(value.ctype_name, (value,)) + + @property + def type_format(self) -> str: + return "typing.Type[{}]" + + @property + def types_separator(self) -> str: + return ", " + + def _resolve_symbol(root: Optional[ASTNode], full_symbol_name: str) -> Optional[ASTNode]: """Searches for a symbol with the given full export name in the AST starting from the `root`. diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index 842ab3be6e..ce4f901e79 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -1,7 +1,7 @@ from .nodes.type_node import ( AliasTypeNode, AliasRefTypeNode, PrimitiveTypeNode, ASTNodeTypeNode, NDArrayTypeNode, NoneTypeNode, SequenceTypeNode, - TupleTypeNode, UnionTypeNode, AnyTypeNode + TupleTypeNode, UnionTypeNode, AnyTypeNode, ConditionalAliasTypeNode ) # Set of predefined types used to cover cases when library doesn't @@ -22,6 +22,10 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.int_("uchar"), PrimitiveTypeNode.int_("unsigned"), PrimitiveTypeNode.int_("int64"), + PrimitiveTypeNode.int_("uint8_t"), + PrimitiveTypeNode.int_("int8_t"), + PrimitiveTypeNode.int_("int32_t"), + PrimitiveTypeNode.int_("uint32_t"), PrimitiveTypeNode.int_("size_t"), PrimitiveTypeNode.float_("float"), PrimitiveTypeNode.float_("double"), @@ -30,12 +34,15 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.str_("char"), PrimitiveTypeNode.str_("String"), PrimitiveTypeNode.str_("c_string"), + ConditionalAliasTypeNode.numpy_array_("NumPyArrayGeneric"), + ConditionalAliasTypeNode.numpy_array_("NumPyArrayFloat32", dtype="numpy.float32"), + ConditionalAliasTypeNode.numpy_array_("NumPyArrayFloat64", dtype="numpy.float64"), NoneTypeNode("void"), AliasTypeNode.int_("void*", "IntPointer", "Represents an arbitrary pointer"), AliasTypeNode.union_( "Mat", items=(ASTNodeTypeNode("Mat", module_name="cv2.mat_wrapper"), - NDArrayTypeNode("Mat")), + AliasRefTypeNode("NumPyArrayGeneric")), export_name="MatLike" ), AliasTypeNode.sequence_("MatShape", PrimitiveTypeNode.int_()), @@ -137,10 +144,22 @@ _PREDEFINED_TYPES = ( ASTNodeTypeNode("gapi.wip.draw.Mosaic"), ASTNodeTypeNode("gapi.wip.draw.Poly"))), SequenceTypeNode("Prims", AliasRefTypeNode("Prim")), - AliasTypeNode.array_("Matx33f", (3, 3), "numpy.float32"), - AliasTypeNode.array_("Matx33d", (3, 3), "numpy.float64"), - AliasTypeNode.array_("Matx44f", (4, 4), "numpy.float32"), - AliasTypeNode.array_("Matx44d", (4, 4), "numpy.float64"), + AliasTypeNode.array_ref_("Matx33f", + array_ref_name="NumPyArrayFloat32", + shape=(3, 3), + dtype="numpy.float32"), + AliasTypeNode.array_ref_("Matx33d", + array_ref_name="NumPyArrayFloat64", + shape=(3, 3), + dtype="numpy.float64"), + AliasTypeNode.array_ref_("Matx44f", + array_ref_name="NumPyArrayFloat32", + shape=(4, 4), + dtype="numpy.float32"), + AliasTypeNode.array_ref_("Matx44d", + array_ref_name="NumPyArrayFloat64", + shape=(4, 4), + dtype="numpy.float64"), NDArrayTypeNode("vector", dtype="numpy.uint8"), NDArrayTypeNode("vector_uchar", dtype="numpy.uint8"), TupleTypeNode("GMat2", items=(ASTNodeTypeNode("GMat"), diff --git a/modules/python/src2/typing_stubs_generator.py b/modules/python/src2/typing_stubs_generator.py index fa0b2b7a28..448c455a11 100644 --- a/modules/python/src2/typing_stubs_generator.py +++ b/modules/python/src2/typing_stubs_generator.py @@ -12,6 +12,7 @@ if sys.version_info >= (3, 6): from contextlib import contextmanager from typing import Dict, Set, Any, Sequence, Generator, Union + import traceback from pathlib import Path @@ -46,10 +47,12 @@ if sys.version_info >= (3, 6): try: ret_type = func(*args, **kwargs) - except Exception as e: + except Exception: self.has_failure = True warnings.warn( - 'Typing stubs generation has failed. Reason: {}'.format(e) + "Typing stubs generation has failed.\n{}".format( + traceback.format_exc() + ) ) if ret_type_on_failure is None: return None @@ -120,7 +123,11 @@ if sys.version_info >= (3, 6): @failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub) def find_class_node(self, class_info, namespaces): # type: (Any, Sequence[str]) -> ClassNode - return find_class_node(self.cv_root, class_info.full_original_name, namespaces) + return find_class_node( + self.cv_root, + SymbolName.parse(class_info.full_original_name, namespaces), + create_missing_namespaces=True + ) @failures_wrapper.wrap_exceptions_as_warnings(ret_type_on_failure=ClassNodeStub) def create_class_node(self, class_info, namespaces): diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index e8c4ab8849..9f7406587c 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -219,6 +219,25 @@ class Arguments(NewOpenCVTests): #res6 = cv.utils.dumpInputArray([a, b]) #self.assertEqual(res6, "InputArrayOfArrays: empty()=false kind=0x00050000 flags=0x01050000 total(-1)=2 dims(-1)=1 size(-1)=2x1 type(0)=CV_32FC1 dims(0)=4 size(0)=[2 3 4 5]") + def test_unsupported_numpy_data_types_string_description(self): + for dtype in (object, str, np.complex128): + test_array = np.zeros((4, 4, 3), dtype=dtype) + msg = ".*type = {} is not supported".format(test_array.dtype) + if sys.version_info[0] < 3: + self.assertRaisesRegexp( + Exception, msg, cv.utils.dumpInputArray, test_array + ) + else: + self.assertRaisesRegex( + Exception, msg, cv.utils.dumpInputArray, test_array + ) + + def test_numpy_writeable_flag_is_preserved(self): + array = np.zeros((10, 10, 1), dtype=np.uint8) + array.setflags(write=False) + with self.assertRaises(Exception): + cv.rectangle(array, (0, 0), (5, 5), (255), 2) + def test_20968(self): pixel = np.uint8([[[40, 50, 200]]]) _ = cv.cvtColor(pixel, cv.COLOR_RGB2BGR) # should not raise exception @@ -482,6 +501,27 @@ class Arguments(NewOpenCVTests): self.assertEqual(expected, actual, msg=get_conversion_error_msg(convertible, expected, actual)) + + def test_wrap_rotated_rect(self): + center = (34.5, 52.) + size = (565.0, 140.0) + angle = -177.5 + rect1 = cv.RotatedRect(center, size, angle) + self.assertEqual(rect1.center, center) + self.assertEqual(rect1.size, size) + self.assertEqual(rect1.angle, angle) + + pts = [[ 319.7845, -5.6109037], + [ 313.6778, 134.25586], + [-250.78448, 109.6109], + [-244.6778, -30.25586]] + self.assertLess(np.max(np.abs(rect1.points() - pts)), 1e-4) + + rect2 = cv.RotatedRect(pts[0], pts[1], pts[2]) + _, inter_pts = cv.rotatedRectangleIntersection(rect1, rect2) + self.assertLess(np.max(np.abs(inter_pts.reshape(-1, 2) - pts)), 1e-4) + + def test_parse_to_rotated_rect_not_convertible(self): for not_convertible in ([], (), np.array([]), (123, (45, 34), 1), {1: 2, 3: 4}, 123, np.array([[123, 123, 14], [1, 3], 56], dtype=object), '123'): diff --git a/modules/ts/include/opencv2/ts/cuda_test.hpp b/modules/ts/include/opencv2/ts/cuda_test.hpp index 87b217fc13..73e8f8ba84 100644 --- a/modules/ts/include/opencv2/ts/cuda_test.hpp +++ b/modules/ts/include/opencv2/ts/cuda_test.hpp @@ -202,6 +202,11 @@ namespace cvtest { \ UnsafeTestBody(); \ } \ + catch (const cvtest::details::SkipTestExceptionBase& e) \ + { \ + printf("[ SKIP ] %s\n", e.what()); \ + cv::cuda::resetDevice(); \ + } \ catch (...) \ { \ cv::cuda::resetDevice(); \ diff --git a/modules/ts/include/opencv2/ts/ts_gtest.h b/modules/ts/include/opencv2/ts/ts_gtest.h index b1c6c12152..b6a953592f 100644 --- a/modules/ts/include/opencv2/ts/ts_gtest.h +++ b/modules/ts/include/opencv2/ts/ts_gtest.h @@ -17501,6 +17501,7 @@ CartesianProductHolder2(const Generator1& g1, const Generator2& g2) static_cast >(g2_))); } + CartesianProductHolder2(const CartesianProductHolder2 & other) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder2& other) = delete; @@ -17523,7 +17524,7 @@ CartesianProductHolder3(const Generator1& g1, const Generator2& g2, static_cast >(g2_), static_cast >(g3_))); } - + CartesianProductHolder3(const CartesianProductHolder3 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder3& other) = delete; @@ -17549,7 +17550,7 @@ CartesianProductHolder4(const Generator1& g1, const Generator2& g2, static_cast >(g3_), static_cast >(g4_))); } - + CartesianProductHolder4(const CartesianProductHolder4 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder4& other) = delete; @@ -17577,7 +17578,7 @@ CartesianProductHolder5(const Generator1& g1, const Generator2& g2, static_cast >(g4_), static_cast >(g5_))); } - + CartesianProductHolder5(const CartesianProductHolder5 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder5& other) = delete; @@ -17609,7 +17610,7 @@ CartesianProductHolder6(const Generator1& g1, const Generator2& g2, static_cast >(g5_), static_cast >(g6_))); } - + CartesianProductHolder6(const CartesianProductHolder6 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder6& other) = delete; @@ -17644,7 +17645,7 @@ CartesianProductHolder7(const Generator1& g1, const Generator2& g2, static_cast >(g6_), static_cast >(g7_))); } - + CartesianProductHolder7(const CartesianProductHolder7 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder7& other) = delete; @@ -17683,7 +17684,7 @@ CartesianProductHolder8(const Generator1& g1, const Generator2& g2, static_cast >(g7_), static_cast >(g8_))); } - + CartesianProductHolder8(const CartesianProductHolder8 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder8& other) = delete; @@ -17726,7 +17727,7 @@ CartesianProductHolder9(const Generator1& g1, const Generator2& g2, static_cast >(g8_), static_cast >(g9_))); } - + CartesianProductHolder9(const CartesianProductHolder9 &) = default; private: // No implementation - assignment is unsupported. void operator=(const CartesianProductHolder9& other) = delete; diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index 9d28a087ac..442fa08ec5 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -305,6 +305,10 @@ enum { CAP_PROP_OPENNI_OUTPUT_MODE = 100, CAP_PROP_OPENNI2_MIRROR = 111 }; +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable: 5054 ) +#endif //! OpenNI shortcuts enum { CAP_OPENNI_IMAGE_GENERATOR_PRESENT = CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE = CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_OUTPUT_MODE, @@ -315,6 +319,9 @@ enum { CAP_OPENNI_IMAGE_GENERATOR_PRESENT = CAP_OPENNI_IMAGE_GENERATOR + CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION_ON = CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION, CAP_OPENNI_IR_GENERATOR_PRESENT = CAP_OPENNI_IR_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, }; +#ifdef _MSC_VER +#pragma warning( pop ) +#endif //! OpenNI data given from depth generator enum { CAP_OPENNI_DEPTH_MAP = 0, //!< Depth values in mm (CV_16UC1) diff --git a/modules/videoio/include/opencv2/videoio/utils.private.hpp b/modules/videoio/include/opencv2/videoio/utils.private.hpp new file mode 100644 index 0000000000..e331aaf2ac --- /dev/null +++ b/modules/videoio/include/opencv2/videoio/utils.private.hpp @@ -0,0 +1,15 @@ +// 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_VIDEOIO_UTILS_PRIVATE_HPP +#define OPENCV_VIDEOIO_UTILS_PRIVATE_HPP + +#include "opencv2/core/cvdef.h" +#include + +namespace cv { +CV_EXPORTS std::string icvExtractPattern(const std::string& filename, unsigned *offset); +} + +#endif // OPENCV_VIDEOIO_UTILS_PRIVATE_HPP diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index f4d0785f0f..982bc5c87d 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -1707,14 +1707,36 @@ bool CvCapture_FFMPEG::retrieveHWFrame(cv::OutputArray output) #endif } +static inline double getCodecTag(const AVCodecID codec_id) { + const struct AVCodecTag* fallback_tags[] = { + // APIchanges: + // 2012-01-31 - dd6d3b0 - lavf 54.01.0 + // Add avformat_get_riff_video_tags() and avformat_get_riff_audio_tags(). + avformat_get_riff_video_tags(), +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 25, 100) && defined LIBAVFORMAT_VERSION_MICRO && LIBAVFORMAT_VERSION_MICRO >= 100 + // APIchanges: ffmpeg only + // 2014-01-19 - 1a193c4 - lavf 55.25.100 - avformat.h + // Add avformat_get_mov_video_tags() and avformat_get_mov_audio_tags(). + avformat_get_mov_video_tags(), +#endif + codec_bmp_tags, // fallback for avformat < 54.1 + NULL }; + return av_codec_get_tag(fallback_tags, codec_id); +} + +static inline double getCodecIdFourcc(const AVCodecID codec_id) +{ + if (codec_id == AV_CODEC_ID_NONE) return -1; + const char* codec_fourcc = _opencv_avcodec_get_name(codec_id); + if (!codec_fourcc || strcmp(codec_fourcc, "unknown_codec") == 0 || strlen(codec_fourcc) != 4) + return getCodecTag(codec_id); + return (double)CV_FOURCC(codec_fourcc[0], codec_fourcc[1], codec_fourcc[2], codec_fourcc[3]); +} + double CvCapture_FFMPEG::getProperty( int property_id ) const { if( !video_st || !context ) return 0; - double codec_tag = 0; - CV_CODEC_ID codec_id = AV_CODEC_ID_NONE; - const char* codec_fourcc = NULL; - switch( property_id ) { case CAP_PROP_POS_MSEC: @@ -1738,34 +1760,11 @@ double CvCapture_FFMPEG::getProperty( int property_id ) const case CAP_PROP_FPS: return get_fps(); case CAP_PROP_FOURCC: { - codec_id = video_st->CV_FFMPEG_CODEC_FIELD->codec_id; - codec_tag = (double) video_st->CV_FFMPEG_CODEC_FIELD->codec_tag; - - if(codec_tag || codec_id == AV_CODEC_ID_NONE) - { - return codec_tag; - } - - codec_fourcc = _opencv_avcodec_get_name(codec_id); - if (!codec_fourcc || strcmp(codec_fourcc, "unknown_codec") == 0 || strlen(codec_fourcc) != 4) - { - const struct AVCodecTag* fallback_tags[] = { - // APIchanges: - // 2012-01-31 - dd6d3b0 - lavf 54.01.0 - // Add avformat_get_riff_video_tags() and avformat_get_riff_audio_tags(). - avformat_get_riff_video_tags(), -#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 25, 100) && defined LIBAVFORMAT_VERSION_MICRO && LIBAVFORMAT_VERSION_MICRO >= 100 - // APIchanges: ffmpeg only - // 2014-01-19 - 1a193c4 - lavf 55.25.100 - avformat.h - // Add avformat_get_mov_video_tags() and avformat_get_mov_audio_tags(). - avformat_get_mov_video_tags(), -#endif - codec_bmp_tags, // fallback for avformat < 54.1 - NULL }; - return av_codec_get_tag(fallback_tags, codec_id); - } - - return (double) CV_FOURCC(codec_fourcc[0], codec_fourcc[1], codec_fourcc[2], codec_fourcc[3]); + const double fourcc = getCodecIdFourcc(video_st->CV_FFMPEG_CODEC_FIELD->codec_id); + if (fourcc != -1) return fourcc; + const double codec_tag = (double)video_st->CV_FFMPEG_CODEC_FIELD->codec_tag; + if (codec_tag) return codec_tag; + else return -1; } case CAP_PROP_SAR_NUM: return _opencv_ffmpeg_get_sample_aspect_ratio(ic->streams[video_stream]).num; @@ -1847,7 +1846,7 @@ int64_t CvCapture_FFMPEG::get_bitrate() const double CvCapture_FFMPEG::get_fps() const { -#if 0 && LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 1, 100) && LIBAVFORMAT_VERSION_MICRO >= 100 +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 1, 100) && LIBAVFORMAT_VERSION_MICRO >= 100 double fps = r2d(av_guess_frame_rate(ic, ic->streams[video_stream], NULL)); #else double fps = r2d(ic->streams[video_stream]->avg_frame_rate); diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index b506dd1c06..32b180b4e3 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -51,8 +51,8 @@ #include "precomp.hpp" #include "opencv2/imgcodecs.hpp" - #include "opencv2/core/utils/filesystem.hpp" +#include "opencv2/videoio/utils.private.hpp" #if 0 #define CV_WARN(message) @@ -113,7 +113,16 @@ void CvCapture_Images::close() bool CvCapture_Images::grabFrame() { - cv::String filename = cv::format(filename_pattern.c_str(), (int)(firstframe + currentframe)); + cv::String filename; + if (length == 1) + if (currentframe < length) + filename = filename_pattern; + else + { + return false; + } + else + filename = cv::format(filename_pattern.c_str(), (int)(firstframe + currentframe)); CV_Assert(!filename.empty()); if (grabbedInOpen) @@ -200,7 +209,7 @@ bool CvCapture_Images::setProperty(int id, double value) return false; } -static +// static std::string icvExtractPattern(const std::string& filename, unsigned *offset) { size_t len = filename.size(); @@ -249,9 +258,7 @@ std::string icvExtractPattern(const std::string& filename, unsigned *offset) while (pos < len && !isdigit(filename[pos])) pos++; if (pos == len) - { - CV_Error_(Error::StsBadArg, ("CAP_IMAGES: can't find starting number (in the name of file): %s", filename.c_str())); - } + return ""; std::string::size_type pos0 = pos; @@ -292,44 +299,61 @@ bool CvCapture_Images::open(const std::string& _filename) CV_Assert(!_filename.empty()); filename_pattern = icvExtractPattern(_filename, &offset); - CV_Assert(!filename_pattern.empty()); - - // determine the length of the sequence - for (length = 0; ;) + if (filename_pattern.empty()) + { + filename_pattern = _filename; + if (!utils::fs::exists(filename_pattern)) + { + CV_LOG_INFO(NULL, "CAP_IMAGES: File does not exist: " << filename_pattern); + close(); + return false; + } + if (!haveImageReader(filename_pattern)) + { + CV_LOG_INFO(NULL, "CAP_IMAGES: File is not an image: " << filename_pattern); + close(); + return false; + } + length = 1; + } + else { - cv::String filename = cv::format(filename_pattern.c_str(), (int)(offset + length)); - if (!utils::fs::exists(filename)) + // determine the length of the sequence + for (length = 0; ;) { - if (length == 0 && offset == 0) // allow starting with 0 or 1 + cv::String filename = cv::format(filename_pattern.c_str(), (int)(offset + length)); + if (!utils::fs::exists(filename)) + { + if (length == 0 && offset == 0) // allow starting with 0 or 1 + { + offset++; + continue; + } + CV_LOG_INFO(NULL, "CAP_IMAGES: File does not exist: " << filename); + break; + } + + if(!haveImageReader(filename)) { - offset++; - continue; + CV_LOG_INFO(NULL, "CAP_IMAGES: File is not an image: " << filename); + break; } - break; + + length++; } - if(!haveImageReader(filename)) + if (length == 0) { - CV_LOG_INFO(NULL, "CAP_IMAGES: Stop scanning. Can't read image file: " << filename); - break; + close(); + return false; } - length++; + firstframe = offset; } - - if (length == 0) - { - close(); - return false; - } - - firstframe = offset; - // grab frame to enable properties retrieval - bool grabRes = grabFrame(); + bool grabRes = CvCapture_Images::grabFrame(); grabbedInOpen = true; currentframe = 0; - return grabRes; } diff --git a/modules/videoio/src/cap_v4l.cpp b/modules/videoio/src/cap_v4l.cpp index a5d8561c6b..e3c53d7cdd 100644 --- a/modules/videoio/src/cap_v4l.cpp +++ b/modules/videoio/src/cap_v4l.cpp @@ -268,6 +268,14 @@ typedef uint32_t __u32; #define V4L2_PIX_FMT_Y12 v4l2_fourcc('Y', '1', '2', ' ') #endif +#ifndef V4L2_PIX_FMT_Y16 +#define V4L2_PIX_FMT_Y16 v4l2_fourcc('Y', '1', '6', ' ') +#endif + +#ifndef V4L2_PIX_FMT_Y16_BE +#define V4L2_PIX_FMT_Y16_BE v4l2_fourcc_be('Y', '1', '6', ' ') +#endif + #ifndef V4L2_PIX_FMT_ABGR32 #define V4L2_PIX_FMT_ABGR32 v4l2_fourcc('A', 'R', '2', '4') #endif @@ -609,6 +617,7 @@ bool CvCaptureCAM_V4L::autosetup_capture_mode_v4l2() V4L2_PIX_FMT_JPEG, #endif V4L2_PIX_FMT_Y16, + V4L2_PIX_FMT_Y16_BE, V4L2_PIX_FMT_Y12, V4L2_PIX_FMT_Y10, V4L2_PIX_FMT_GREY, @@ -668,6 +677,7 @@ bool CvCaptureCAM_V4L::convertableToRgb() const case V4L2_PIX_FMT_SGBRG8: case V4L2_PIX_FMT_RGB24: case V4L2_PIX_FMT_Y16: + case V4L2_PIX_FMT_Y16_BE: case V4L2_PIX_FMT_Y10: case V4L2_PIX_FMT_GREY: case V4L2_PIX_FMT_BGR24: @@ -716,6 +726,7 @@ void CvCaptureCAM_V4L::v4l2_create_frame() size.height = size.height * 3 / 2; // "1.5" channels break; case V4L2_PIX_FMT_Y16: + case V4L2_PIX_FMT_Y16_BE: case V4L2_PIX_FMT_Y12: case V4L2_PIX_FMT_Y10: depth = IPL_DEPTH_16U; @@ -1731,8 +1742,21 @@ void CvCaptureCAM_V4L::convertToRgb(const Buffer ¤tBuffer) return; case V4L2_PIX_FMT_Y16: { + // https://www.kernel.org/doc/html/v4.10/media/uapi/v4l/pixfmt-y16.html + // This is a grey-scale image with a depth of 16 bits per pixel. The least significant byte is stored at lower memory addresses (little-endian). + // Note: 10-bits precision is not supported + cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); + cv::extractChannel(cv::Mat(imageSize, CV_8UC2, start), temp, 1); // 1 - second channel + cv::cvtColor(temp, destination, COLOR_GRAY2BGR); + return; + } + case V4L2_PIX_FMT_Y16_BE: + { + // https://www.kernel.org/doc/html/v4.10/media/uapi/v4l/pixfmt-y16-be.html + // This is a grey-scale image with a depth of 16 bits per pixel. The most significant byte is stored at lower memory addresses (big-endian). + // Note: 10-bits precision is not supported cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); - cv::Mat(imageSize, CV_16UC1, start).convertTo(temp, CV_8U, 1.0 / 256); + cv::extractChannel(cv::Mat(imageSize, CV_8UC2, start), temp, 0); // 0 - first channel cv::cvtColor(temp, destination, COLOR_GRAY2BGR); return; } @@ -1854,8 +1878,12 @@ static inline cv::String capPropertyName(int prop) return "auto wb"; case CAP_PROP_WB_TEMPERATURE: return "wb temperature"; + case CAP_PROP_ORIENTATION_META: + return "orientation meta"; + case CAP_PROP_ORIENTATION_AUTO: + return "orientation auto"; default: - return "unknown"; + return cv::format("unknown (%d)", prop); } } @@ -1970,7 +1998,7 @@ bool CvCaptureCAM_V4L::controlInfo(int property_id, __u32 &_v4l2id, cv::Range &r v4l2_queryctrl queryctrl = v4l2_queryctrl(); queryctrl.id = __u32(v4l2id); if (v4l2id == -1 || !tryIoctl(VIDIOC_QUERYCTRL, &queryctrl)) { - CV_LOG_INFO(NULL, "VIDEOIO(V4L2:" << deviceName << "): property " << capPropertyName(property_id) << " is not supported"); + CV_LOG_INFO(NULL, "VIDEOIO(V4L2:" << deviceName << "): property '" << capPropertyName(property_id) << "' is not supported"); return false; } _v4l2id = __u32(v4l2id); diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index d3f0d5b8fc..35d425d5c1 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -226,7 +226,7 @@ const videoio_container_params_t videoio_container_params[] = videoio_container_params_t(CAP_FFMPEG, "video/big_buck_bunny", "h264", "h264", "h264", "I420"), videoio_container_params_t(CAP_FFMPEG, "video/big_buck_bunny", "h265", "h265", "hevc", "I420"), videoio_container_params_t(CAP_FFMPEG, "video/big_buck_bunny", "mjpg.avi", "mjpg", "MJPG", "I420"), - videoio_container_params_t(CAP_FFMPEG, "video/sample_322x242_15frames.yuv420p.libx264", "mp4", "h264", "avc1", "I420") + videoio_container_params_t(CAP_FFMPEG, "video/sample_322x242_15frames.yuv420p.libx264", "mp4", "h264", "h264", "I420") //videoio_container_params_t(CAP_FFMPEG, "video/big_buck_bunny", "h264.mkv", "mkv.h264", "h264", "I420"), //videoio_container_params_t(CAP_FFMPEG, "video/big_buck_bunny", "h265.mkv", "mkv.h265", "hevc", "I420"), //videoio_container_params_t(CAP_FFMPEG, "video/big_buck_bunny", "h264.mp4", "mp4.avc1", "avc1", "I420"), @@ -448,10 +448,16 @@ TEST_P(ffmpeg_get_fourcc, check_short_codecs) const ffmpeg_get_fourcc_param_t ffmpeg_get_fourcc_param[] = { ffmpeg_get_fourcc_param_t("../cv/tracking/faceocc2/data/faceocc2.webm", "VP80"), - ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libvpx-vp9.mp4", "vp09"), - ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libaom-av1.mp4", "av01"), ffmpeg_get_fourcc_param_t("video/big_buck_bunny.h265", "hevc"), - ffmpeg_get_fourcc_param_t("video/big_buck_bunny.h264", "h264") + ffmpeg_get_fourcc_param_t("video/big_buck_bunny.h264", "h264"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libvpx-vp9.mp4", "VP90"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libaom-av1.mp4", "AV01"), + ffmpeg_get_fourcc_param_t("video/big_buck_bunny.mp4", "FMP4"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.mpeg2video.mp4", "mpg2"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.mjpeg.mp4", "MJPG"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libxvid.mp4", "FMP4"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libx265.mp4", "hevc"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libx264.mp4", "h264") }; INSTANTIATE_TEST_CASE_P(videoio, ffmpeg_get_fourcc, testing::ValuesIn(ffmpeg_get_fourcc_param)); diff --git a/modules/videoio/test/test_images.cpp b/modules/videoio/test/test_images.cpp new file mode 100644 index 0000000000..ccf507a50d --- /dev/null +++ b/modules/videoio/test/test_images.cpp @@ -0,0 +1,294 @@ +// 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 "test_precomp.hpp" +#include "opencv2/core/utils/filesystem.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/videoio/utils.private.hpp" + +using namespace std; + +namespace opencv_test { namespace { + +struct ImageCollection +{ + string dirname; + string base; + string ext; + size_t first_idx; + size_t last_idx; + size_t width; +public: + ImageCollection(const char *dirname_template = "opencv_test_images") + : first_idx(0), last_idx(0), width(0) + { + dirname = cv::tempfile(dirname_template); + cv::utils::fs::createDirectory(dirname); + } + ~ImageCollection() + { + cleanup(); + } + void cleanup() + { + cv::utils::fs::remove_all(dirname); + } + void generate(size_t count, size_t first = 0, size_t width_ = 4, const string & base_ = "test", const string & ext_ = "png") + { + base = base_; + ext = ext_; + first_idx = first; + last_idx = first + count - 1; + width = width_; + for (size_t idx = first_idx; idx <= last_idx; ++idx) + { + const string filename = getFilename(idx); + imwrite(filename, getFrame(idx)); + } + } + string getFilename(size_t idx = 0) const + { + ostringstream buf; + buf << dirname << "/" << base << setw(width) << setfill('0') << idx << "." << ext; + return buf.str(); + } + string getPatternFilename() const + { + ostringstream buf; + buf << dirname << "/" << base << "%0" << width << "d" << "." << ext; + return buf.str(); + } + string getFirstFilename() const + { + return getFilename(first_idx); + } + Mat getFirstFrame() const + { + return getFrame(first_idx); + } + size_t getCount() const + { + return last_idx - first_idx + 1; + } + string getDirname() const + { + return dirname; + } + static Mat getFrame(size_t idx) + { + const int sz = 100; // 100x100 or bigger + Mat res(sz, sz, CV_8UC3, Scalar::all(0)); + circle(res, Point(idx % 100), idx % 50, Scalar::all(255), 2, LINE_8); + return res; + } +}; + +//================================================================================================== + +TEST(videoio_images, basic_read) +{ + ImageCollection col; + col.generate(20); + VideoCapture cap(col.getFirstFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + size_t idx = 0; + while (cap.isOpened()) // TODO: isOpened is always true, even if there are no more images + { + Mat img; + const bool read_res = cap.read(img); + if (!read_res) + break; + EXPECT_MAT_N_DIFF(img, col.getFrame(idx), 0); + ++idx; + } + EXPECT_EQ(col.getCount(), idx); +} + +TEST(videoio_images, basic_write) +{ + // writer should create files: test0000.png, ... test0019.png + ImageCollection col; + col.generate(1); + VideoWriter wri(col.getFirstFilename(), CAP_IMAGES, 0, 0, col.getFrame(0).size()); + ASSERT_TRUE(wri.isOpened()); + size_t idx = 0; + while (wri.isOpened()) + { + wri << col.getFrame(idx); + Mat actual = imread(col.getFilename(idx)); + EXPECT_MAT_N_DIFF(col.getFrame(idx), actual, 0); + if (++idx >= 20) + break; + } + wri.release(); + ASSERT_FALSE(wri.isOpened()); +} + +TEST(videoio_images, bad) +{ + ImageCollection col; + { + ostringstream buf; buf << col.getDirname() << "/missing0000.png"; + VideoCapture cap(buf.str(), CAP_IMAGES); + EXPECT_FALSE(cap.isOpened()); + Mat img; + EXPECT_FALSE(cap.read(img)); + } +} + +TEST(videoio_images, seek) +{ + // check files: test0005.png, ..., test0024.png + // seek to valid and invalid frame numbers + // position is zero-based: valid frame numbers are 0, ..., 19 + const int count = 20; + ImageCollection col; + col.generate(count, 5); + VideoCapture cap(col.getFirstFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + EXPECT_EQ((size_t)count, (size_t)cap.get(CAP_PROP_FRAME_COUNT)); + vector positions { count / 2, 0, 1, count - 1, count, count + 100, -1, -100 }; + for (const auto &pos : positions) + { + Mat img; + const bool res = cap.set(CAP_PROP_POS_FRAMES, pos); + if (pos >= count || pos < 0) // invalid position + { +// EXPECT_FALSE(res); // TODO: backend clamps invalid value to valid range, actual result is 'true' + } + else + { + EXPECT_TRUE(res); + EXPECT_GE(1., cap.get(CAP_PROP_POS_AVI_RATIO)); + EXPECT_NEAR((double)pos / (count - 1), cap.get(CAP_PROP_POS_AVI_RATIO), 1e-2); + EXPECT_EQ(pos, static_cast(cap.get(CAP_PROP_POS_FRAMES))); + EXPECT_TRUE(cap.read(img)); + EXPECT_MAT_N_DIFF(img, col.getFrame(col.first_idx + pos), 0); + } + } +} + +TEST(videoio_images, pattern_overflow) +{ + // check files: test0.png, ..., test11.png + ImageCollection col; + col.generate(12, 0, 1); + + { + VideoCapture cap(col.getFirstFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + for (size_t idx = col.first_idx; idx <= col.last_idx; ++idx) + { + Mat img; + EXPECT_TRUE(cap.read(img)); + EXPECT_MAT_N_DIFF(img, col.getFrame(idx), 0); + } + } + { + VideoCapture cap(col.getPatternFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + for (size_t idx = col.first_idx; idx <= col.last_idx; ++idx) + { + Mat img; + EXPECT_TRUE(cap.read(img)); + EXPECT_MAT_N_DIFF(img, col.getFrame(idx), 0); + } + } +} + +TEST(videoio_images, pattern_max) +{ + // max supported number width for starting image is 9 digits + // but following images can be read as well + // test999999999.png ; test1000000000.png + ImageCollection col; + col.generate(2, 1000000000 - 1); + { + VideoCapture cap(col.getFirstFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + Mat img; + EXPECT_TRUE(cap.read(img)); + EXPECT_MAT_N_DIFF(img, col.getFrame(col.first_idx), 0); + EXPECT_TRUE(cap.read(img)); + EXPECT_MAT_N_DIFF(img, col.getFrame(col.first_idx + 1), 0); + } + { + VideoWriter wri(col.getFirstFilename(), CAP_IMAGES, 0, 0, col.getFirstFrame().size()); + ASSERT_TRUE(wri.isOpened()); + Mat img = col.getFrame(0); + wri.write(img); + wri.write(img); + Mat actual; + actual = imread(col.getFilename(col.first_idx)); + EXPECT_MAT_N_DIFF(actual, img, 0); + actual = imread(col.getFilename(col.first_idx)); + EXPECT_MAT_N_DIFF(actual, img, 0); + } +} + +TEST(videoio_images, extract_pattern) +{ + unsigned offset = 0; + + // Min and max values + EXPECT_EQ("%01d.png", cv::icvExtractPattern("0.png", &offset)); + EXPECT_EQ(0u, offset); + EXPECT_EQ("%09d.png", cv::icvExtractPattern("999999999.png", &offset)); + EXPECT_EQ(999999999u, offset); + + // Regular usage - start, end, middle + EXPECT_EQ("abc%04ddef.png", cv::icvExtractPattern("abc0048def.png", &offset)); + EXPECT_EQ(48u, offset); + EXPECT_EQ("%05dabcdef.png", cv::icvExtractPattern("00049abcdef.png", &offset)); + EXPECT_EQ(49u, offset); + EXPECT_EQ("abcdef%06d.png", cv::icvExtractPattern("abcdef000050.png", &offset)); + EXPECT_EQ(50u, offset); + + // Minus handling (should not handle) + EXPECT_EQ("abcdef-%01d.png", cv::icvExtractPattern("abcdef-8.png", &offset)); + EXPECT_EQ(8u, offset); + + // Two numbers (should select first) + // TODO: shouldn't it be last number? + EXPECT_EQ("%01d-abcdef-8.png", cv::icvExtractPattern("7-abcdef-8.png", &offset)); + EXPECT_EQ(7u, offset); + + // Paths (should select filename) + EXPECT_EQ("images005/abcdef%03d.png", cv::icvExtractPattern("images005/abcdef006.png", &offset)); + EXPECT_EQ(6u, offset); + // TODO: fix + // EXPECT_EQ("images03\\abcdef%02d.png", cv::icvExtractPattern("images03\\abcdef04.png", &offset)); + // EXPECT_EQ(4, offset); + EXPECT_EQ("/home/user/test/0/3348/../../3442/./0/1/3/4/5/14304324234/%01d.png", + cv::icvExtractPattern("/home/user/test/0/3348/../../3442/./0/1/3/4/5/14304324234/2.png", &offset)); + EXPECT_EQ(2u, offset); + + // Patterns '%0?[0-9][du]' + EXPECT_EQ("test%d.png", cv::icvExtractPattern("test%d.png", &offset)); + EXPECT_EQ(0u, offset); + EXPECT_EQ("test%0d.png", cv::icvExtractPattern("test%0d.png", &offset)); + EXPECT_EQ(0u, offset); + EXPECT_EQ("test%09d.png", cv::icvExtractPattern("test%09d.png", &offset)); + EXPECT_EQ(0u, offset); + EXPECT_EQ("test%5u.png", cv::icvExtractPattern("test%5u.png", &offset)); + EXPECT_EQ(0u, offset); + + // Invalid arguments + EXPECT_THROW(cv::icvExtractPattern(string(), &offset), cv::Exception); + // TODO: fix? + // EXPECT_EQ(0u, offset); + EXPECT_THROW(cv::icvExtractPattern("test%010d.png", &offset), cv::Exception); + EXPECT_EQ(0u, offset); + EXPECT_THROW(cv::icvExtractPattern("1000000000.png", &offset), cv::Exception); + EXPECT_EQ(0u, offset); + EXPECT_THROW(cv::icvExtractPattern("1.png", NULL), cv::Exception); +} + +// TODO: should writer overwrite files? +// TODO: is clamping good for seeking? +// TODO: missing files? E.g. 3, 4, 6, 7, 8 (should it finish OR jump over OR return empty frame?) +// TODO: non-numbered files (https://github.com/opencv/opencv/pull/23815) +// TODO: when opening with pattern (e.g. test%01d.png), first frame can be only 0 (test0.png) + +}} // opencv_test:::: diff --git a/modules/videoio/test/test_precomp.hpp b/modules/videoio/test/test_precomp.hpp index cffdf2bef4..b4f340897e 100644 --- a/modules/videoio/test/test_precomp.hpp +++ b/modules/videoio/test/test_precomp.hpp @@ -5,8 +5,10 @@ #define __OPENCV_TEST_PRECOMP_HPP__ #include +#include #include "opencv2/ts.hpp" +#include "opencv2/ts/ocl_test.hpp" #include "opencv2/videoio.hpp" #include "opencv2/videoio/registry.hpp" #include "opencv2/core/private.hpp" @@ -55,6 +57,14 @@ inline std::string fourccToString(int fourcc) return cv::format("%c%c%c%c", fourcc & 255, (fourcc >> 8) & 255, (fourcc >> 16) & 255, (fourcc >> 24) & 255); } +inline std::string fourccToStringSafe(int fourcc) +{ + std::string res = fourccToString(fourcc); + // TODO: return hex values for invalid characters + std::transform(res.begin(), res.end(), res.begin(), [](uint8_t c) { return (c >= '0' && c <= 'z') ? c : (c == ' ' ? '_' : 'x'); }); + return res; +} + inline int fourccFromString(const std::string &fourcc) { if (fourcc.size() != 4) return 0; diff --git a/modules/videoio/test/test_v4l2.cpp b/modules/videoio/test/test_v4l2.cpp new file mode 100644 index 0000000000..5d56ac097c --- /dev/null +++ b/modules/videoio/test/test_v4l2.cpp @@ -0,0 +1,138 @@ +// 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. + +// Reference: https://www.kernel.org/doc/html/v4.8/media/v4l-drivers/vivid.html + +// create 1 virtual device of type CAP (0x1) at /dev/video10 +// sudo modprobe vivid ndevs=1 node_types=0x1 vid_cap_nr=10 +// make sure user have read/write access (e.g. via group 'video') +// $ ls -l /dev/video10 +// crw-rw----+ 1 root video ... /dev/video10 +// set environment variable: +// export OPENCV_TEST_V4L2_VIVID_DEVICE=/dev/video10 +// run v4l2 tests: +// opencv_test_videoio --gtest_filter=*videoio_v4l2* + + +#ifdef HAVE_CAMV4L2 + +#include "test_precomp.hpp" +#include +#include + +// workarounds for older versions +#ifndef V4L2_PIX_FMT_Y10 +#define V4L2_PIX_FMT_Y10 v4l2_fourcc('Y', '1', '0', ' ') +#endif +#ifndef V4L2_PIX_FMT_Y12 +#define V4L2_PIX_FMT_Y12 v4l2_fourcc('Y', '1', '2', ' ') +#endif +#ifndef V4L2_PIX_FMT_ABGR32 +#define V4L2_PIX_FMT_ABGR32 v4l2_fourcc('A', 'R', '2', '4') +#endif +#ifndef V4L2_PIX_FMT_XBGR32 +#define V4L2_PIX_FMT_XBGR32 v4l2_fourcc('X', 'R', '2', '4') +#endif +#ifndef V4L2_PIX_FMT_Y16 +#define V4L2_PIX_FMT_Y16 v4l2_fourcc('Y', '1', '6', ' ') +#endif +#ifndef V4L2_PIX_FMT_Y16_BE +#define V4L2_PIX_FMT_Y16_BE v4l2_fourcc_be('Y', '1', '6', ' ') +#endif + + +using namespace cv; + +namespace opencv_test { namespace { + +struct Format_Channels_Depth +{ + uint32_t pixel_format; + uint8_t channels; + uint8_t depth; + float mul_width; + float mul_height; +}; + +typedef testing::TestWithParam videoio_v4l2; + +TEST_P(videoio_v4l2, formats) +{ + utils::Paths devs = utils::getConfigurationParameterPaths("OPENCV_TEST_V4L2_VIVID_DEVICE"); + if (devs.size() != 1) + { + throw SkipTestException("OPENCV_TEST_V4L2_VIVID_DEVICE is not set"); + } + const string device = devs[0]; + const Size sz(640, 480); + const Format_Channels_Depth params = GetParam(); + + { + // Case with RAW output + VideoCapture cap; + ASSERT_TRUE(cap.open(device, CAP_V4L2)); + // VideoCapture will set device's format automatically, vivid device will accept it + ASSERT_TRUE(cap.set(CAP_PROP_FOURCC, params.pixel_format)); + ASSERT_TRUE(cap.set(CAP_PROP_CONVERT_RGB, false)); + for (size_t idx = 0; idx < 3; ++idx) + { + Mat img; + EXPECT_TRUE(cap.grab()); + EXPECT_TRUE(cap.retrieve(img)); + EXPECT_EQ(Size(sz.width * params.mul_width, sz.height * params.mul_height), img.size()); + EXPECT_EQ(params.channels, img.channels()); + EXPECT_EQ(params.depth, img.depth()); + } + } + { + // case with BGR output + VideoCapture cap; + ASSERT_TRUE(cap.open(device, CAP_V4L2)); + // VideoCapture will set device's format automatically, vivid device will accept it + ASSERT_TRUE(cap.set(CAP_PROP_FOURCC, params.pixel_format)); + for (size_t idx = 0; idx < 3; ++idx) + { + Mat img; + EXPECT_TRUE(cap.grab()); + EXPECT_TRUE(cap.retrieve(img)); + EXPECT_EQ(sz, img.size()); + EXPECT_EQ(3, img.channels()); + EXPECT_EQ(CV_8U, img.depth()); + } + } +} + +vector all_params = { + { V4L2_PIX_FMT_YVU420, 1, CV_8U, 1.f, 1.5f }, + { V4L2_PIX_FMT_YUV420, 1, CV_8U, 1.f, 1.5f }, + { V4L2_PIX_FMT_NV12, 1, CV_8U, 1.f, 1.5f }, + { V4L2_PIX_FMT_NV21, 1, CV_8U, 1.f, 1.5f }, + { V4L2_PIX_FMT_YUV411P, 3, CV_8U, 1.f, 1.f }, +// { V4L2_PIX_FMT_MJPEG, 1, CV_8U, 1.f, 1.f }, +// { V4L2_PIX_FMT_JPEG, 1, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_YUYV, 2, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_UYVY, 2, CV_8U, 1.f, 1.f }, +// { V4L2_PIX_FMT_SBGGR8, 1, CV_8U, 1.f, 1.f }, +// { V4L2_PIX_FMT_SN9C10X, 3, CV_8U, 1.f, 1.f }, +// { V4L2_PIX_FMT_SGBRG8, 1, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_RGB24, 3, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_Y16, 1, CV_16U, 1.f, 1.f }, + { V4L2_PIX_FMT_Y16_BE, 1, CV_16U, 1.f, 1.f }, + { V4L2_PIX_FMT_Y10, 1, CV_16U, 1.f, 1.f }, + { V4L2_PIX_FMT_GREY, 1, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_BGR24, 3, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_XBGR32, 3, CV_8U, 1.f, 1.f }, + { V4L2_PIX_FMT_ABGR32, 3, CV_8U, 1.f, 1.f }, +}; + +inline static std::string param_printer(const testing::TestParamInfo& info) +{ + return fourccToStringSafe(info.param.pixel_format); +} + +INSTANTIATE_TEST_CASE_P(/*videoio_v4l2*/, videoio_v4l2, ValuesIn(all_params), param_printer); + +}} // opencv_test:::: + +#endif // HAVE_CAMV4L2 diff --git a/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake b/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake index a5ec05a821..9918c468b4 100644 --- a/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake +++ b/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake @@ -101,8 +101,17 @@ if(NOT DEFINED IPHONEOS_DEPLOYMENT_TARGET AND NOT MAC_CATALYST) endif() if(NOT __IN_TRY_COMPILE) - set(_xcodebuild_wrapper "${CMAKE_BINARY_DIR}/xcodebuild_wrapper") - if(NOT EXISTS "${_xcodebuild_wrapper}") + set(_xcodebuild_wrapper "") + if(NOT (CMAKE_VERSION VERSION_LESS "3.25.1")) # >= 3.25.1 + # no workaround is required (#23156) + elseif(NOT (CMAKE_VERSION VERSION_LESS "3.25.0")) # >= 3.25.0 < 3.25.1 + if(NOT OPENCV_SKIP_MESSAGE_ISSUE_23156) + message(FATAL_ERROR "OpenCV: Please upgrade CMake to 3.25.1+. Current CMake version is ${CMAKE_VERSION}. Details: https://github.com/opencv/opencv/issues/23156") + endif() + else() # < 3.25.0, apply workaround from #13912 + set(_xcodebuild_wrapper "${CMAKE_BINARY_DIR}/xcodebuild_wrapper") + endif() + if(_xcodebuild_wrapper AND NOT EXISTS "${_xcodebuild_wrapper}") set(_xcodebuild_wrapper_tmp "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/xcodebuild_wrapper") if(NOT DEFINED CMAKE_MAKE_PROGRAM) # empty since CMake 3.10 find_program(XCODEBUILD_PATH "xcodebuild") @@ -122,7 +131,9 @@ if(NOT __IN_TRY_COMPILE) configure_file("${CMAKE_CURRENT_LIST_DIR}/xcodebuild_wrapper.in" "${_xcodebuild_wrapper_tmp}" @ONLY) file(COPY "${_xcodebuild_wrapper_tmp}" DESTINATION ${CMAKE_BINARY_DIR} FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif() - set(CMAKE_MAKE_PROGRAM "${_xcodebuild_wrapper}" CACHE INTERNAL "" FORCE) + if(_xcodebuild_wrapper) + set(CMAKE_MAKE_PROGRAM "${_xcodebuild_wrapper}" CACHE INTERNAL "" FORCE) + endif() endif() # Standard settings diff --git a/samples/python/calibrate.py b/samples/python/calibrate.py index 9528f67aa4..33c87b6a52 100755 --- a/samples/python/calibrate.py +++ b/samples/python/calibrate.py @@ -16,11 +16,13 @@ default values: -w: 4 -h: 6 -t: chessboard - --square_size: 50 - --marker_size: 25 + --square_size: 10 + --marker_size: 5 --aruco_dict: DICT_4X4_50 --threads: 4 defaults to ../data/left*.jpg + +NOTE: Chessboard size is defined in inner corners. Charuco board size is defined in units. ''' # Python 2/3 compatibility @@ -67,45 +69,38 @@ def main(): marker_size = float(args.get('--marker_size')) aruco_dict_name = str(args.get('--aruco_dict')) - pattern_size = (height, width) + pattern_size = (width, height) if pattern_type == 'chessboard': pattern_points = np.zeros((np.prod(pattern_size), 3), np.float32) pattern_points[:, :2] = np.indices(pattern_size).T.reshape(-1, 2) pattern_points *= square_size - elif pattern_type == 'charucoboard': - pattern_points = np.zeros((np.prod((height-1, width-1)), 3), np.float32) - pattern_points[:, :2] = np.indices((height-1, width-1)).T.reshape(-1, 2) - pattern_points *= square_size - else: - print("unknown pattern") - return None obj_points = [] img_points = [] h, w = cv.imread(img_names[0], cv.IMREAD_GRAYSCALE).shape[:2] # TODO: use imquery call to retrieve results aruco_dicts = { - 'DICT_4X4_50':cv.aruco.DICT_4X4_50, - 'DICT_4X4_100':cv.aruco.DICT_4X4_100, - 'DICT_4X4_250':cv.aruco.DICT_4X4_250, - 'DICT_4X4_1000':cv.aruco.DICT_4X4_1000, - 'DICT_5X5_50':cv.aruco.DICT_5X5_50, - 'DICT_5X5_100':cv.aruco.DICT_5X5_100, - 'DICT_5X5_250':cv.aruco.DICT_5X5_250, - 'DICT_5X5_1000':cv.aruco.DICT_5X5_1000, - 'DICT_6X6_50':cv.aruco.DICT_6X6_50, - 'DICT_6X6_100':cv.aruco.DICT_6X6_100, - 'DICT_6X6_250':cv.aruco.DICT_6X6_250, - 'DICT_6X6_1000':cv.aruco.DICT_6X6_1000, - 'DICT_7X7_50':cv.aruco.DICT_7X7_50, - 'DICT_7X7_100':cv.aruco.DICT_7X7_100, - 'DICT_7X7_250':cv.aruco.DICT_7X7_250, - 'DICT_7X7_1000':cv.aruco.DICT_7X7_1000, - 'DICT_ARUCO_ORIGINAL':cv.aruco.DICT_ARUCO_ORIGINAL, - 'DICT_APRILTAG_16h5':cv.aruco.DICT_APRILTAG_16h5, - 'DICT_APRILTAG_25h9':cv.aruco.DICT_APRILTAG_25h9, - 'DICT_APRILTAG_36h10':cv.aruco.DICT_APRILTAG_36h10, - 'DICT_APRILTAG_36h11':cv.aruco.DICT_APRILTAG_36h11 + 'DICT_4X4_50': cv.aruco.DICT_4X4_50, + 'DICT_4X4_100': cv.aruco.DICT_4X4_100, + 'DICT_4X4_250': cv.aruco.DICT_4X4_250, + 'DICT_4X4_1000': cv.aruco.DICT_4X4_1000, + 'DICT_5X5_50': cv.aruco.DICT_5X5_50, + 'DICT_5X5_100': cv.aruco.DICT_5X5_100, + 'DICT_5X5_250': cv.aruco.DICT_5X5_250, + 'DICT_5X5_1000': cv.aruco.DICT_5X5_1000, + 'DICT_6X6_50': cv.aruco.DICT_6X6_50, + 'DICT_6X6_100': cv.aruco.DICT_6X6_100, + 'DICT_6X6_250': cv.aruco.DICT_6X6_250, + 'DICT_6X6_1000': cv.aruco.DICT_6X6_1000, + 'DICT_7X7_50': cv.aruco.DICT_7X7_50, + 'DICT_7X7_100': cv.aruco.DICT_7X7_100, + 'DICT_7X7_250': cv.aruco.DICT_7X7_250, + 'DICT_7X7_1000': cv.aruco.DICT_7X7_1000, + 'DICT_ARUCO_ORIGINAL': cv.aruco.DICT_ARUCO_ORIGINAL, + 'DICT_APRILTAG_16h5': cv.aruco.DICT_APRILTAG_16h5, + 'DICT_APRILTAG_25h9': cv.aruco.DICT_APRILTAG_25h9, + 'DICT_APRILTAG_36h10': cv.aruco.DICT_APRILTAG_36h10, + 'DICT_APRILTAG_36h11': cv.aruco.DICT_APRILTAG_36h11 } if (aruco_dict_name not in set(aruco_dicts.keys())): @@ -130,19 +125,27 @@ def main(): if found: term = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_COUNT, 30, 0.1) cv.cornerSubPix(img, corners, (5, 5), (-1, -1), term) + frame_img_points = corners.reshape(-1, 2) + frame_obj_points = pattern_points elif pattern_type == 'charucoboard': - corners, _charucoIds, _markerCorners_svg, _markerIds_svg = charuco_detector.detectBoard(img) - if (len(corners) == (height-1)*(width-1)): + corners, charucoIds, _, _ = charuco_detector.detectBoard(img) + if (len(corners) > 0): + frame_obj_points, frame_img_points = board.matchImagePoints(corners, charucoIds) found = True + else: + found = False else: print("unknown pattern type", pattern_type) return None if debug_dir: vis = cv.cvtColor(img, cv.COLOR_GRAY2BGR) - cv.drawChessboardCorners(vis, pattern_size, corners, found) + if pattern_type == 'chessboard': + cv.drawChessboardCorners(vis, pattern_size, corners, found) + elif pattern_type == 'charucoboard': + cv.aruco.drawDetectedCornersCharuco(vis, corners, charucoIds=charucoIds) _path, name, _ext = splitfn(fn) - outfile = os.path.join(debug_dir, name + '_chess.png') + outfile = os.path.join(debug_dir, name + '_board.png') cv.imwrite(outfile, vis) if not found: @@ -150,7 +153,7 @@ def main(): return None print(' %s... OK' % fn) - return (corners.reshape(-1, 2), pattern_points) + return (frame_img_points, frame_obj_points) threads_num = int(args.get('--threads')) if threads_num <= 1: @@ -177,7 +180,7 @@ def main(): print('') for fn in img_names if debug_dir else []: _path, name, _ext = splitfn(fn) - img_found = os.path.join(debug_dir, name + '_chess.png') + img_found = os.path.join(debug_dir, name + '_board.png') outfile = os.path.join(debug_dir, name + '_undistorted.png') img = cv.imread(img_found)