diff --git a/3rdparty/fastcv/fastcv.cmake b/3rdparty/fastcv/fastcv.cmake index 215c73af16..bd63ddd85a 100644 --- a/3rdparty/fastcv/fastcv.cmake +++ b/3rdparty/fastcv/fastcv.cmake @@ -1,23 +1,23 @@ function(download_fastcv root_dir) # Commit SHA in the opencv_3rdparty repo - set(FASTCV_COMMIT "2265e79b3b9a8512a9c615b8c4d0244e88f45a9d") + set(FASTCV_COMMIT "9e8d42b6d7e769548d70b2e5674e263b056de8b4") # Define actual FastCV versions if(ANDROID) if(AARCH64) message(STATUS "Download FastCV for Android aarch64") - set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2025_04_29.tgz") - set(FCV_PACKAGE_HASH "d9172a9a3e5d92d080a4192cc5691001") + set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2025_07_09.tgz") + set(FCV_PACKAGE_HASH "8b9497858cf3c3502a0be4369d06ebf8") else() message(STATUS "Download FastCV for Android armv7") - set(FCV_PACKAGE_NAME "fastcv_android_arm32_2025_04_29.tgz") - set(FCV_PACKAGE_HASH "246b5253233391cd2c74d01d49aee9c3") + set(FCV_PACKAGE_NAME "fastcv_android_arm32_2025_07_09.tgz") + set(FCV_PACKAGE_HASH "e0e6009c9f2f2b96140cd6a639c7383f") endif() elseif(UNIX AND NOT APPLE AND NOT IOS AND NOT XROS) if(AARCH64) - set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2025_05_29.tgz") - set(FCV_PACKAGE_HASH "decd490524f786e103125b8b948151f3") + set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2025_07_09.tgz") + set(FCV_PACKAGE_HASH "05e254e0eb3c13fa23eb7213f0fe6d82") else() message("FastCV: fastcv lib for 32-bit Linux is not supported for now!") endif() diff --git a/3rdparty/ippicv/ippicv.cmake b/3rdparty/ippicv/ippicv.cmake index 48901a1547..5396bfe8f3 100644 --- a/3rdparty/ippicv/ippicv.cmake +++ b/3rdparty/ippicv/ippicv.cmake @@ -2,7 +2,7 @@ function(download_ippicv root_var) set(${root_var} "" PARENT_SCOPE) # Commit SHA in the opencv_3rdparty repo - set(IPPICV_COMMIT "d1cbea44d326eb0421fedcdd16de4630fd8c7ed0") + set(IPPICV_COMMIT "767426b2a40a011eb2fa7f44c677c13e60e205ad") # Define actual ICV versions if(APPLE) set(IPPICV_COMMIT "0cc4aa06bf2bef4b05d237c69a5a96b9cd0cb85a") @@ -14,8 +14,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "linux") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_lnx") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2022.0.0_lnx_intel64_20240904_general.tgz") - set(OPENCV_ICV_HASH "63717ee0f918ad72fb5a737992a206d1") + set(OPENCV_ICV_NAME "ippicv_2022.1.0_lnx_intel64_20250130_general.tgz") + set(OPENCV_ICV_HASH "98ff71fc242d52db9cc538388e502f57") else() if(ANDROID) set(IPPICV_COMMIT "c7c6d527dde5fee7cb914ee9e4e20f7436aab3a1") @@ -31,8 +31,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "windows") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_win") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2022.0.0_win_intel64_20240904_general.zip") - set(OPENCV_ICV_HASH "3a6eca7cc3bce7159eb1443c6fca4e31") + set(OPENCV_ICV_NAME "ippicv_2022.1.0_win_intel64_20250130_general.zip") + set(OPENCV_ICV_HASH "67a611ab22410f392239bddff6f91df7") else() set(IPPICV_COMMIT "7f55c0c26be418d494615afca15218566775c725") set(OPENCV_ICV_NAME "ippicv_2021.12.0_win_ia32_20240425_general.zip") diff --git a/apps/interactive-calibration/calibCommon.hpp b/apps/interactive-calibration/calibCommon.hpp index 73cb4ab9fc..ed6c3613d1 100644 --- a/apps/interactive-calibration/calibCommon.hpp +++ b/apps/interactive-calibration/calibCommon.hpp @@ -92,6 +92,7 @@ namespace calib std::string videoFileName; bool flipVertical; int camID; + int camBackend; int fps; cv::Size cameraResolution; int maxFramesNum; diff --git a/apps/interactive-calibration/calibPipeline.cpp b/apps/interactive-calibration/calibPipeline.cpp index ac54764e41..4eae5469d6 100644 --- a/apps/interactive-calibration/calibPipeline.cpp +++ b/apps/interactive-calibration/calibPipeline.cpp @@ -34,7 +34,7 @@ PipelineExitStatus CalibPipeline::start(std::vector > pr auto open_camera = [this] () { if(mCaptureParams.source == Camera) { - mCapture.open(mCaptureParams.camID); + mCapture.open(mCaptureParams.camID, mCaptureParams.camBackend); cv::Size maxRes = getCameraResolution(); cv::Size neededRes = mCaptureParams.cameraResolution; @@ -55,7 +55,7 @@ PipelineExitStatus CalibPipeline::start(std::vector > pr mCapture.set(cv::CAP_PROP_AUTOFOCUS, 0); } else if (mCaptureParams.source == File) - mCapture.open(mCaptureParams.videoFileName); + mCapture.open(mCaptureParams.videoFileName, mCaptureParams.camBackend); }; if(!mCapture.isOpened()) { diff --git a/apps/interactive-calibration/main.cpp b/apps/interactive-calibration/main.cpp index 4efe7acf7f..9ee88c251d 100644 --- a/apps/interactive-calibration/main.cpp +++ b/apps/interactive-calibration/main.cpp @@ -7,7 +7,7 @@ #include #include #include - +#include #include #include @@ -24,9 +24,25 @@ using namespace calib; -const std::string keys = +static std::string getVideoIoBackendsString() +{ + std::string result; + auto backs = cv::videoio_registry::getBackends(); + for (const auto& b: backs) + { + if (!result.empty()) + result += ", "; + + result += cv::videoio_registry::getBackendName(b); + } + + return result; +} + +const char* keys = "{v | | Input from video file }" - "{ci | 0 | Default camera id }" + "{ci | 0 | Camera id }" + "{vb | | Video I/O back-end. One of: %s }" "{flip | false | Vertical flip of input frames }" "{t | circles | Template for calibration (circles, chessboard, dualCircles, charuco, symcircles) }" "{sz | 16.3 | Distance between two nearest centers of circles or squares on calibration board}" @@ -96,11 +112,13 @@ static void undistortButton(int state, void* data) int main(int argc, char** argv) { - cv::CommandLineParser parser(argc, argv, keys); + cv::CommandLineParser parser(argc, argv, cv::format(keys, getVideoIoBackendsString().c_str())); + if(parser.has("help")) { parser.printMessage(); return 0; } + std::cout << consoleHelp << std::endl; parametersController paramsController; diff --git a/apps/interactive-calibration/parametersController.cpp b/apps/interactive-calibration/parametersController.cpp index 4ce81f352a..90d24b04c9 100644 --- a/apps/interactive-calibration/parametersController.cpp +++ b/apps/interactive-calibration/parametersController.cpp @@ -4,7 +4,7 @@ #include "parametersController.hpp" #include - +#include #include template @@ -106,6 +106,27 @@ bool calib::parametersController::loadFromParser(cv::CommandLineParser &parser) mCapParams.camID = parser.get("ci"); } + mCapParams.camBackend = cv::CAP_ANY; + if (parser.has("vb")) + { + std::string backendName = parser.get("vb"); + auto backs = cv::videoio_registry::getBackends(); + bool backendSet = false; + for (const auto& b: backs) + { + if (backendName == cv::videoio_registry::getBackendName(b)) + { + mCapParams.camBackend = b; + backendSet = true; + } + } + if (!backendSet) + { + std::cout << "Unknown or unsupported backend " << backendName << std::endl; + return false; + } + } + std::string templateType = parser.get("t"); if(templateType.find("symcircles", 0) == 0) { diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index b302c67771..88d315781d 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -81,7 +81,13 @@ if(WITH_EIGEN AND NOT HAVE_EIGEN) set(EIGEN_WORLD_VERSION ${EIGEN3_WORLD_VERSION}) set(EIGEN_MAJOR_VERSION ${EIGEN3_MAJOR_VERSION}) set(EIGEN_MINOR_VERSION ${EIGEN3_MINOR_VERSION}) - else() # Eigen config file + elseif(DEFINED Eigen3_VERSION_MAJOR) # Recommended package config variables + # see https://github.com/opencv/opencv/issues/27530 + set(EIGEN_WORLD_VERSION ${Eigen3_VERSION_MAJOR}) + set(EIGEN_MAJOR_VERSION ${Eigen3_VERSION_MINOR}) + set(EIGEN_MINOR_VERSION ${Eigen3_VERSION_PATCH}) + else() # Deprecated package config variables + # Removed on master at https://gitlab.com/libeigen/eigen/-/commit/f2984cd0778dd0a1d7e74216d826eaff2bc6bfab set(EIGEN_WORLD_VERSION ${EIGEN3_VERSION_MAJOR}) set(EIGEN_MAJOR_VERSION ${EIGEN3_VERSION_MINOR}) set(EIGEN_MINOR_VERSION ${EIGEN3_VERSION_PATCH}) diff --git a/cmake/checks/cpu_neon.cpp b/cmake/checks/cpu_neon.cpp index 7af16f5ffc..30c144cdf8 100644 --- a/cmake/checks/cpu_neon.cpp +++ b/cmake/checks/cpu_neon.cpp @@ -1,6 +1,6 @@ #include -#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) +#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) # define _ARM64_DISTINCT_NEON_TYPES # include # include diff --git a/cmake/checks/cpu_neon_bf16.cpp b/cmake/checks/cpu_neon_bf16.cpp index a9045d7117..5aaf8f051f 100644 --- a/cmake/checks/cpu_neon_bf16.cpp +++ b/cmake/checks/cpu_neon_bf16.cpp @@ -1,4 +1,4 @@ -#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64) +#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM64EC)) #include #include "arm_neon.h" diff --git a/cmake/checks/cpu_neon_dotprod.cpp b/cmake/checks/cpu_neon_dotprod.cpp index 74f44a1832..2db6c4b1cc 100644 --- a/cmake/checks/cpu_neon_dotprod.cpp +++ b/cmake/checks/cpu_neon_dotprod.cpp @@ -1,6 +1,6 @@ #include -#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64) +#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM64EC)) #include "arm_neon.h" int test() { diff --git a/cmake/checks/cpu_neon_fp16.cpp b/cmake/checks/cpu_neon_fp16.cpp index bba5b97026..45f5b05906 100644 --- a/cmake/checks/cpu_neon_fp16.cpp +++ b/cmake/checks/cpu_neon_fp16.cpp @@ -1,6 +1,6 @@ #include -#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64) +#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM64EC)) #include "arm_neon.h" float16x8_t vld1q_as_f16(const float* src) diff --git a/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown b/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown index 9ffa218bb2..09cad1003c 100644 --- a/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown +++ b/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown @@ -158,7 +158,7 @@ Structuring Element ------------------- We manually created a structuring elements in the previous examples with help of cv.Mat.ones. It is -rectangular shape. But in some cases, you may need elliptical/circular shaped kernels. So for this +rectangular shape. But in some cases, you may need elliptical/circular shaped kernels or diamond-shaped kernels. So for this purpose, OpenCV has a function, **cv.getStructuringElement()**. You just pass the shape and size of the kernel, you get the desired kernel. diff --git a/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown b/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown index d904c640c3..bcbdbf40bf 100644 --- a/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown +++ b/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown @@ -63,7 +63,7 @@ There are some parameters when you get familiar with StereoBM, and you may need - uniqueness_ratio: Another post-filtering step. If the best matching disparity is not sufficiently better than every other disparity in the search range, the pixel is filtered out. You can try tweaking this if texture_threshold and the speckle filtering are still letting through spurious matches. - prefilter_size and prefilter_cap: The pre-filtering phase, which normalizes image brightness and enhances texture in preparation for block matching. Normally you should not need to adjust these. -These parameters are set with dedicated setters and getters after the algoritm +These parameters are set with dedicated setters and getters after the algorithm initialization, such as `setTextureThreshold`, `setSpeckleRange`, `setUniquenessRatio`, and more. See cv::StereoBM documentation for details. diff --git a/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown b/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown index 24b504914f..6d56d26095 100644 --- a/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown +++ b/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown @@ -147,6 +147,14 @@ array([[0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]], dtype=uint8) + +# Diamond-shaped Kernel +>>> cv.getStructuringElement(cv.MORPH_DIAMOND,(5,5)) +array([[0, 0, 1, 0, 0], + [0, 1, 1, 1, 0], + [1, 1, 1, 1, 1], + [0, 1, 1, 1, 0], + [0, 0, 1, 0, 0]], dtype=uint8) @endcode Additional Resources -------------------- diff --git a/doc/tutorials/calib3d/real_time_pose/images/pnp.jpg b/doc/tutorials/calib3d/real_time_pose/images/pnp.jpg index f7d01f5f22..cc34b217d9 100644 Binary files a/doc/tutorials/calib3d/real_time_pose/images/pnp.jpg and b/doc/tutorials/calib3d/real_time_pose/images/pnp.jpg differ diff --git a/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown b/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown index b5d79ab0bc..3517723035 100644 --- a/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown +++ b/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown @@ -10,7 +10,7 @@ Goal ---- Thrust is an extremely powerful library for various cuda accelerated algorithms. However thrust is designed -to work with vectors and not pitched matricies. The following tutorial will discuss wrapping cv::cuda::GpuMat's +to work with vectors and not pitched matrices. The following tutorial will discuss wrapping cv::cuda::GpuMat's into thrust iterators that can be used with thrust algorithms. This tutorial should show you how to: diff --git a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown index 03eef13eff..edd8f5d54d 100644 --- a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown +++ b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown @@ -129,6 +129,7 @@ receives three arguments: - Rectangular box: MORPH_RECT - Cross: MORPH_CROSS - Ellipse: MORPH_ELLIPSE + - Diamond: MORPH_DIAMOND Then, we just have to specify the size of our kernel and the *anchor point*. If not specified, it is assumed to be in the center. @@ -256,6 +257,7 @@ receives two arguments and returns the processed image: - Rectangular box: MORPH_RECT - Cross: MORPH_CROSS - Ellipse: MORPH_ELLIPSE + - Diamond: MORPH_DIAMOND Then, we just have to specify the size of our kernel and the *anchor point*. If the anchor point not specified, it is assumed to be in the center. diff --git a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown index 30b82143f9..de36354256 100644 --- a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown +++ b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown @@ -25,7 +25,7 @@ Theory - To compare two histograms ( \f$H_{1}\f$ and \f$H_{2}\f$ ), first we have to choose a *metric* (\f$d(H_{1}, H_{2})\f$) to express how well both histograms match. -- OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 4 +- OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 6 different metrics to compute the matching: -# **Correlation ( cv::HISTCMP_CORREL )** \f[d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}\f] @@ -36,12 +36,18 @@ Theory -# **Chi-Square ( cv::HISTCMP_CHISQR )** \f[d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}\f] - -# **Intersection ( method=cv::HISTCMP_INTERSECT )** + -# **Intersection ( cv::HISTCMP_INTERSECT )** \f[d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))\f] -# **Bhattacharyya distance ( cv::HISTCMP_BHATTACHARYYA )** \f[d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}\f] + -# **Alternative Chi-Square ( cv::HISTCMP_CHISQR_ALT )** + \f[d(H_1,H_2) = 2 * \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)+H_2(I)}\f] + + -# **Kullback-Leibler divergence ( cv::HISTCMP_KL_DIV )** + \f[d(H_1,H_2) = \sum _I H_1(I) \log \left(\frac{H_1(I)}{H_2(I)}\right)\f] + Code ---- @@ -123,7 +129,7 @@ Explanation @snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Convert to HSV half @end_toggle -- Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ). +- Initialize the arguments to calculate the histograms (bins, ranges and channels H and S). @add_toggle_cpp @snippet samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp Using 50 bins for hue and 60 for saturation @@ -151,7 +157,7 @@ Explanation @snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Calculate the histograms for the HSV images @end_toggle -- Apply sequentially the 4 comparison methods between the histogram of the base image (hist_base) +- Apply sequentially the 6 comparison methods between the histogram of the base image (hist_base) and the other histograms: @add_toggle_cpp @@ -182,15 +188,17 @@ Results are from the same source. For the other two test images, we can observe that they have very different lighting conditions, so the matching should not be very good: --# Here the numeric results we got with OpenCV 3.4.1: - *Method* | Base - Base | Base - Half | Base - Test 1 | Base - Test 2 - ----------------- | ------------ | ------------ | -------------- | --------------- - *Correlation* | 1.000000 | 0.880438 | 0.20457 | 0.0664547 - *Chi-square* | 0.000000 | 4.6834 | 2697.98 | 4763.8 - *Intersection* | 18.8947 | 13.022 | 5.44085 | 2.58173 - *Bhattacharyya* | 0.000000 | 0.237887 | 0.679826 | 0.874173 +-# Here the numeric results we got with OpenCV 4.12.0: + *Method* | Base - Base | Base - Half | Base - Test 1 | Base - Test 2 + ------------------- | ------------ | ------------ | -------------- | --------------- + *Correlation* | 1.000000 | 0.880438 | 0.20457 | 0.065752 + *Chi-square* | 0.000000 | 0.328307 | 181.674 | 80.1494 + *Intersection* | 1.000000 | 0.75005 | 0.315061 | 0.0908022 + *Bhattacharyya* | 0.000000 | 0.237866 | 0.679825 | 0.873709 + *Chi-Square alt.* | 0.000000 | 0.395046 | 2.31572 | 3.41024 + *KL divergence* | 0.000000 | 0.321064 | 2.6616 | 9.55412 + For the *Correlation* and *Intersection* methods, the higher the metric, the more accurate the match. As we can see, the match *base-base* is the highest of all as expected. Also we can observe - that the match *base-half* is the second best match (as we predicted). For the other two metrics, - the less the result, the better the match. We can observe that the matches between the test 1 and - test 2 with respect to the base are worse, which again, was expected. + that the match *base-half* is the second best match (as we predicted). For the other four metrics, + the less the result, the better the match. diff --git a/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown b/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown index c17ed2f059..232bf49839 100644 --- a/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown +++ b/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown @@ -166,7 +166,7 @@ for `cv::aruco::Dictionary`. The data member of board classes are public and can - Alright, but how can I render a 3d model to create an augmented reality application? To do so, you will need to use an external rendering engine library, such as OpenGL. The aruco module -only provides the functionality to obtain the camera pose, i.e. the rotation and traslation vectors, +only provides the functionality to obtain the camera pose, i.e. the rotation and translation vectors, which is necessary to create the augmented reality effect. However, you will need to adapt the rotation and traslation vectors from the OpenCV format to the format accepted by your 3d rendering library. The original ArUco library contains examples of how to do it for OpenGL and Ogre3D. diff --git a/hal/ipp/CMakeLists.txt b/hal/ipp/CMakeLists.txt index bf57db6f8e..203e38c6d6 100644 --- a/hal/ipp/CMakeLists.txt +++ b/hal/ipp/CMakeLists.txt @@ -5,6 +5,7 @@ set(IPP_HAL_LIBRARIES "ipphal" CACHE INTERNAL "") set(IPP_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE INTERNAL "") set(IPP_HAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/ipp_hal_core.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/ipp_hal_imgproc.hpp" CACHE INTERNAL "") add_library(ipphal STATIC @@ -13,6 +14,7 @@ add_library(ipphal STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/norm_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/cart_polar_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/transforms_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/warp_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/sum_ipp.cpp" ) @@ -34,6 +36,7 @@ ocv_warnings_disable(CMAKE_CXX_FLAGS -Wno-suggest-override) target_include_directories(ipphal PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ${CMAKE_SOURCE_DIR}/modules/core/include + ${CMAKE_SOURCE_DIR}/modules/imgproc/include ${IPP_INCLUDE_DIRS} ) diff --git a/hal/ipp/include/ipp_hal_core.hpp b/hal/ipp/include/ipp_hal_core.hpp index caa8c765b2..7b71c8d28a 100644 --- a/hal/ipp/include/ipp_hal_core.hpp +++ b/hal/ipp/include/ipp_hal_core.hpp @@ -21,7 +21,17 @@ int ipp_hal_minMaxIdxMaskStep(const uchar* src_data, size_t src_step, int width, #undef cv_hal_minMaxIdxMaskStep #define cv_hal_minMaxIdxMaskStep ipp_hal_minMaxIdxMaskStep -#define IPP_DISABLE_NORM_8U 1 // accuracy difference in perf test sanity check +#if (IPP_VERSION_X100 == 202200) +# define IPP_DISABLE_NORM_8U 1 // accuracy difference in perf test sanity check +# else +# define IPP_DISABLE_NORM_8U 0 +#endif + +#if (IPP_VERSION_X100 >= 202200 && IPP_VERSION_X100 < 202220) +# define IPP_DISABLE_NORM_INF_16U_C1MR 1 // segmentation fault in accuracy test +# else +# define IPP_DISABLE_NORM_INF_16U_C1MR 0 +#endif int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t mask_step, int width, int height, int type, int norm_type, double* result); @@ -29,7 +39,6 @@ int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t ma #undef cv_hal_norm #define cv_hal_norm ipp_hal_norm - int ipp_hal_normDiff(const uchar* src1, size_t src1_step, const uchar* src2, size_t src2_step, const uchar* mask, size_t mask_step, int width, int height, int type, int norm_type, double* result); diff --git a/hal/ipp/include/ipp_hal_imgproc.hpp b/hal/ipp/include/ipp_hal_imgproc.hpp new file mode 100644 index 0000000000..b97f14f252 --- /dev/null +++ b/hal/ipp/include/ipp_hal_imgproc.hpp @@ -0,0 +1,39 @@ +// 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 __IPP_HAL_IMGPROC_HPP__ +#define __IPP_HAL_IMGPROC_HPP__ + +#include +#include "ipp_utils.hpp" + +#ifdef HAVE_IPP_IW + +int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, + int dst_height, const double M[6], int interpolation, int borderType, const double borderValue[4]); + +// Does not pass tests in 5.x branch +//#undef cv_hal_warpAffine +//#define cv_hal_warpAffine ipp_hal_warpAffine +#endif + +#if IPP_VERSION_X100 >= 810 +int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, + int dst_height, const double M[9], int interpolation, int borderType, const double borderValue[4]); + +// Does not pass tests in 5.x branch +//#undef cv_hal_warpPerspective +//#define cv_hal_warpPerspective ipp_hal_warpPerspective +#endif + + +int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, + uchar *dst_data, size_t dst_step, int dst_width, int dst_height, + float* mapx, size_t mapx_step, float* mapy, size_t mapy_step, + int interpolation, int border_type, const double border_value[4]); +#undef cv_hal_remap32f +#define cv_hal_remap32f ipp_hal_remap32f + + +#endif //__IPP_HAL_IMGPROC_HPP__ diff --git a/hal/ipp/src/norm_ipp.cpp b/hal/ipp/src/norm_ipp.cpp index 95c428ac8a..313b98ca4d 100644 --- a/hal/ipp/src/norm_ipp.cpp +++ b/hal/ipp/src/norm_ipp.cpp @@ -20,7 +20,9 @@ int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t ma ippiMaskNormFuncC1 ippiNorm_C1MR = norm_type == cv::NORM_INF ? (type == CV_8UC1 ? (ippiMaskNormFuncC1)ippiNorm_Inf_8u_C1MR : + #if (!IPP_DISABLE_NORM_INF_16U_C1MR) type == CV_16UC1 ? (ippiMaskNormFuncC1)ippiNorm_Inf_16u_C1MR : + #endif type == CV_32FC1 ? (ippiMaskNormFuncC1)ippiNorm_Inf_32f_C1MR : 0) : norm_type == cv::NORM_L1 ? @@ -141,7 +143,9 @@ int ipp_hal_normDiff(const uchar* src1, size_t src1_step, const uchar* src2, siz ippiMaskNormDiffFuncC1 ippiNormRel_C1MR = norm_type == cv::NORM_INF ? (type == CV_8UC1 ? (ippiMaskNormDiffFuncC1)ippiNormRel_Inf_8u_C1MR : + #if (!IPP_DISABLE_NORM_INF_16U_C1MR) type == CV_16UC1 ? (ippiMaskNormDiffFuncC1)ippiNormRel_Inf_16u_C1MR : + #endif type == CV_32FC1 ? (ippiMaskNormDiffFuncC1)ippiNormRel_Inf_32f_C1MR : 0) : norm_type == cv::NORM_L1 ? @@ -230,7 +234,9 @@ int ipp_hal_normDiff(const uchar* src1, size_t src1_step, const uchar* src2, siz ippiMaskNormDiffFuncC1 ippiNormDiff_C1MR = norm_type == cv::NORM_INF ? (type == CV_8UC1 ? (ippiMaskNormDiffFuncC1)ippiNormDiff_Inf_8u_C1MR : + #if (!IPP_DISABLE_NORM_INF_16U_C1MR) type == CV_16UC1 ? (ippiMaskNormDiffFuncC1)ippiNormDiff_Inf_16u_C1MR : + #endif type == CV_32FC1 ? (ippiMaskNormDiffFuncC1)ippiNormDiff_Inf_32f_C1MR : 0) : norm_type == cv::NORM_L1 ? diff --git a/hal/ipp/src/precomp_ipp.hpp b/hal/ipp/src/precomp_ipp.hpp new file mode 100644 index 0000000000..bff2d499b1 --- /dev/null +++ b/hal/ipp/src/precomp_ipp.hpp @@ -0,0 +1,83 @@ +// 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 __PRECOMP_IPP_HPP__ +#define __PRECOMP_IPP_HPP__ + +#include + +#ifdef HAVE_IPP_IW +#include "iw++/iw.hpp" +#endif + +static inline IppiSize ippiSize(size_t width, size_t height) +{ + IppiSize size = { (int)width, (int)height }; + return size; +} + +static inline IppiSize ippiSize(const cv::Size & _size) +{ + IppiSize size = { _size.width, _size.height }; + return size; +} + +static inline IppDataType ippiGetDataType(int depth) +{ + depth = CV_MAT_DEPTH(depth); + return depth == CV_8U ? ipp8u : + depth == CV_8S ? ipp8s : + depth == CV_16U ? ipp16u : + depth == CV_16S ? ipp16s : + depth == CV_32S ? ipp32s : + depth == CV_32F ? ipp32f : + depth == CV_64F ? ipp64f : + (IppDataType)-1; +} + +static inline IppiInterpolationType ippiGetInterpolation(int inter) +{ + inter &= cv::InterpolationFlags::INTER_MAX; + return inter == cv::InterpolationFlags::INTER_NEAREST ? ippNearest : + inter == cv::InterpolationFlags::INTER_LINEAR ? ippLinear : + inter == cv::InterpolationFlags::INTER_CUBIC ? ippCubic : + inter == cv::InterpolationFlags::INTER_LANCZOS4 ? ippLanczos : + inter == cv::InterpolationFlags::INTER_AREA ? ippSuper : + (IppiInterpolationType)-1; +} + +static inline IppiBorderType ippiGetBorderType(int borderTypeNI) +{ + return borderTypeNI == cv::BorderTypes::BORDER_CONSTANT ? ippBorderConst : + borderTypeNI == cv::BorderTypes::BORDER_TRANSPARENT ? ippBorderTransp : + borderTypeNI == cv::BorderTypes::BORDER_REPLICATE ? ippBorderRepl : + (IppiBorderType)-1; +} + +static inline int ippiSuggestThreadsNum(size_t width, size_t height, size_t elemSize, double multiplier) +{ + int threads = cv::getNumThreads(); + if(threads > 1 && height >= 64) + { + size_t opMemory = (int)(width*height*elemSize*multiplier); + int l2cache = 0; +#if IPP_VERSION_X100 >= 201700 + ippGetL2CacheSize(&l2cache); +#endif + if(!l2cache) + l2cache = 1 << 18; + + return IPP_MAX(1, (IPP_MIN((int)(opMemory/l2cache), threads))); + } + return 1; +} + +#ifdef HAVE_IPP_IW +static inline int ippiSuggestThreadsNum(const ::ipp::IwiImage &image, double multiplier) +{ + return ippiSuggestThreadsNum(image.m_size.width, image.m_size.height, image.m_typeSize*image.m_channels, multiplier); +} +#endif + +#endif //__PRECOMP_IPP_HPP__ diff --git a/hal/ipp/src/transforms_ipp.cpp b/hal/ipp/src/transforms_ipp.cpp index 83e66d52a5..0d2e05f291 100644 --- a/hal/ipp/src/transforms_ipp.cpp +++ b/hal/ipp/src/transforms_ipp.cpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html #include "ipp_hal_core.hpp" +#include "precomp_ipp.hpp" #include #include @@ -72,19 +73,6 @@ int ipp_hal_transpose2d(const uchar* src_data, size_t src_step, uchar* dst_data, #ifdef HAVE_IPP_IW -static inline IppDataType ippiGetDataType(int depth) -{ - depth = CV_MAT_DEPTH(depth); - return depth == CV_8U ? ipp8u : - depth == CV_8S ? ipp8s : - depth == CV_16U ? ipp16u : - depth == CV_16S ? ipp16s : - depth == CV_32S ? ipp32s : - depth == CV_32F ? ipp32f : - depth == CV_64F ? ipp64f : - (IppDataType)-1; -} - static inline ::ipp::IwiImage ippiGetImage(int src_type, const uchar* src_data, size_t src_step, int src_width, int src_height) { ::ipp::IwiImage dst; diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp new file mode 100644 index 0000000000..867d28d954 --- /dev/null +++ b/hal/ipp/src/warp_ipp.cpp @@ -0,0 +1,488 @@ +// 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 "ipp_hal_imgproc.hpp" + +#include +#include +#include "precomp_ipp.hpp" + +#ifdef HAVE_IPP_IW +#include "iw++/iw.hpp" +#endif + +#define IPP_WARPAFFINE_PARALLEL 1 +#define CV_TYPE(src_type) (src_type & (CV_DEPTH_MAX - 1)) +#ifdef HAVE_IPP_IW + +class ipp_warpAffineParallel: public cv::ParallelLoopBody +{ +public: + ipp_warpAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, IppiInterpolationType _inter, double (&_coeffs)[2][3], ::ipp::IwiBorderType _borderType, IwTransDirection _iwTransDirection, bool *_ok):m_src(src), m_dst(dst) + { + pOk = _ok; + + inter = _inter; + borderType = _borderType; + iwTransDirection = _iwTransDirection; + + for( int i = 0; i < 2; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = _coeffs[i][j]; + + *pOk = true; + } + ~ipp_warpAffineParallel() {} + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + //CV_INSTRUMENT_REGION_IPP(); + + if(*pOk == false) + return; + + try + { + ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); + CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, m_src, m_dst, coeffs, iwTransDirection, inter, ::ipp::IwiWarpAffineParams(), borderType, tile); + } + catch(const ::ipp::IwException &) + { + *pOk = false; + return; + } + } +private: + ::ipp::IwiImage &m_src; + ::ipp::IwiImage &m_dst; + + IppiInterpolationType inter; + double coeffs[2][3]; + ::ipp::IwiBorderType borderType; + IwTransDirection iwTransDirection; + + bool *pOk; + const ipp_warpAffineParallel& operator= (const ipp_warpAffineParallel&); +}; + +#if (IPP_VERSION_X100 >= 700) +int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, + int dst_height, const double M[6], int interpolation, int borderType, const double borderValue[4]) +{ + //CV_INSTRUMENT_REGION_IPP(); + + IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); + if((int)ippInter < 0 || interpolation > 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + /* C1 C2 C3 C4 */ + char impl[CV_DEPTH_MAX][4][3]={{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {1, 0, 0}}, //16U + {{1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}}, //16S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32F + {{1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}}}; //64F + + if(impl[CV_TYPE(src_type)][CV_MAT_CN(src_type)-1][interpolation] == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + // Acquire data and begin processing + try + { + ::ipp::IwiImage iwSrc; + iwSrc.Init({src_width, src_height}, ippiGetDataType(src_type), CV_MAT_CN(src_type), NULL, src_data, IwSize(src_step)); + ::ipp::IwiImage iwDst({dst_width, dst_height}, ippiGetDataType(src_type), CV_MAT_CN(src_type), NULL, dst_data, dst_step); + ::ipp::IwiBorderType ippBorder(ippiGetBorderType(borderType), {borderValue[0], borderValue[1], borderValue[2], borderValue[3]}); + IwTransDirection iwTransDirection = iwTransForward; + + if((int)ippBorder == -1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + double coeffs[2][3]; + for( int i = 0; i < 2; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = M[i*3 + j]; + + const int threads = ippiSuggestThreadsNum(iwDst, 2); + + if(IPP_WARPAFFINE_PARALLEL && threads > 1) + { + bool ok = true; + cv::Range range(0, (int)iwDst.m_size.height); + ipp_warpAffineParallel invoker(iwSrc, iwDst, ippInter, coeffs, ippBorder, iwTransDirection, &ok); + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + parallel_for_(range, invoker, threads*4); + + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } else { + CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, iwSrc, iwDst, coeffs, iwTransDirection, ippInter, ::ipp::IwiWarpAffineParams(), ippBorder); + } + } + catch (const ::ipp::IwException &) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + return CV_HAL_ERROR_OK; +} +#endif +#endif + +typedef IppStatus (CV_STDCALL* ippiSetFunc)(const void*, void *, int, IppiSize); + +template +bool IPPSetSimple(const double value[4], void *dataPointer, int step, IppiSize &size, ippiSetFunc func) +{ + //CV_INSTRUMENT_REGION_IPP(); + + Type values[channels]; + for( int i = 0; i < channels; i++ ) + values[i] = cv::saturate_cast(value[i]); + return CV_INSTRUMENT_FUN_IPP(func, values, dataPointer, step, size) >= 0; +} + +static bool IPPSet(const double value[4], void *dataPointer, int step, IppiSize &size, int channels, int depth) +{ + //CV_INSTRUMENT_REGION_IPP(); + + if( channels == 1 ) + { + switch( depth ) + { + case CV_8U: + return CV_INSTRUMENT_FUN_IPP(ippiSet_8u_C1R, cv::saturate_cast(value[0]), (Ipp8u *)dataPointer, step, size) >= 0; + case CV_16U: + return CV_INSTRUMENT_FUN_IPP(ippiSet_16u_C1R, cv::saturate_cast(value[0]), (Ipp16u *)dataPointer, step, size) >= 0; + case CV_32F: + return CV_INSTRUMENT_FUN_IPP(ippiSet_32f_C1R, cv::saturate_cast(value[0]), (Ipp32f *)dataPointer, step, size) >= 0; + } + } + else + { + if( channels == 3 ) + { + switch( depth ) + { + case CV_8U: + return IPPSetSimple<3, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C3R); + case CV_16U: + return IPPSetSimple<3, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C3R); + case CV_32F: + return IPPSetSimple<3, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C3R); + } + } + else if( channels == 4 ) + { + switch( depth ) + { + case CV_8U: + return IPPSetSimple<4, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C4R); + case CV_16U: + return IPPSetSimple<4, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C4R); + case CV_32F: + return IPPSetSimple<4, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C4R); + } + } + } + return false; +} + +#if (IPP_VERSION_X100 >= 810) +typedef IppStatus (CV_STDCALL* ippiWarpPerspectiveFunc)(const Ipp8u*, int, Ipp8u*, int,IppiPoint, IppiSize, const IppiWarpSpec*,Ipp8u*); + +class IPPWarpPerspectiveInvoker : + public cv::ParallelLoopBody +{ +public: + IPPWarpPerspectiveInvoker(int _src_type, cv::Mat &_src, size_t _src_step, cv::Mat &_dst, size_t _dst_step, IppiInterpolationType _interpolation, + double (&_coeffs)[3][3], int &_borderType, const double _borderValue[4], ippiWarpPerspectiveFunc _func, + bool *_ok) : + ParallelLoopBody(), src_type(_src_type), src(_src), src_step(_src_step), dst(_dst), dst_step(_dst_step), inter(_interpolation), coeffs(_coeffs), + borderType(_borderType), func(_func), ok(_ok) + { + memcpy(this->borderValue, _borderValue, sizeof(this->borderValue)); + *ok = true; + } + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + //CV_INSTRUMENT_REGION_IPP(); + IppiWarpSpec* pSpec = 0; + int specSize = 0, initSize = 0, bufSize = 0; Ipp8u* pBuffer = 0; + IppiPoint dstRoiOffset = {0, 0}; + IppiWarpDirection direction = ippWarpBackward; //fixed for IPP + const Ipp32u numChannels = CV_MAT_CN(src_type); + + IppiSize srcsize = {src.cols, src.rows}; + IppiSize dstsize = {dst.cols, dst.rows}; + IppiRect srcroi = {0, 0, src.cols, src.rows}; + + /* Spec and init buffer sizes */ + IppStatus status = ippiWarpPerspectiveGetSize(srcsize, srcroi, dstsize, ippiGetDataType(src_type), coeffs, inter, ippWarpBackward, ippiGetBorderType(borderType), &specSize, &initSize); + + pSpec = (IppiWarpSpec*)ippMalloc_L(specSize); + + if (inter == ippLinear) + { + status = ippiWarpPerspectiveLinearInit(srcsize, srcroi, dstsize, ippiGetDataType(src_type), coeffs, direction, numChannels, ippiGetBorderType(borderType), + borderValue, 0, pSpec); + } else + { + status = ippiWarpPerspectiveNearestInit(srcsize, srcroi, dstsize, ippiGetDataType(src_type), coeffs, direction, numChannels, ippiGetBorderType(borderType), + borderValue, 0, pSpec); + } + + status = ippiWarpGetBufferSize(pSpec, dstsize, &bufSize); + pBuffer = (Ipp8u*)ippMalloc_L(bufSize); + IppiSize dstRoiSize = dstsize; + + int cnn = src.channels(); + + if( borderType == cv::BorderTypes::BORDER_CONSTANT ) + { + IppiSize setSize = {dst.cols, range.end - range.start}; + void *dataPointer = dst.ptr(range.start); + if( !IPPSet( borderValue, dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) + { + *ok = false; + ippsFree(pBuffer); + ippsFree(pSpec); + return; + } + } + + status = CV_INSTRUMENT_FUN_IPP(func, src.ptr(), (int)src_step, dst.ptr(), (int)dst_step, dstRoiOffset, dstRoiSize, pSpec, pBuffer); + if (status != ippStsNoErr) + *ok = false; + else + { + CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); + } + ippsFree(pBuffer); + ippsFree(pSpec); + } +private: + int src_type; + cv::Mat &src; + size_t src_step; + cv::Mat &dst; + size_t dst_step; + IppiInterpolationType inter; + double (&coeffs)[3][3]; + int borderType; + double borderValue[4]; + ippiWarpPerspectiveFunc func; + bool *ok; + + const IPPWarpPerspectiveInvoker& operator= (const IPPWarpPerspectiveInvoker&); +}; + +int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar * dst_data, size_t dst_step, + int dst_width, int dst_height, const double M[9], int interpolation, int borderType, const double borderValue[4]) +{ + //CV_INSTRUMENT_REGION_IPP(); + ippiWarpPerspectiveFunc ippFunc = 0; + if (interpolation == cv::InterpolationFlags::INTER_NEAREST) + { + ippFunc = src_type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_8u_C1R : + src_type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_8u_C3R : + src_type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_8u_C4R : + src_type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16u_C1R : + src_type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16u_C3R : + src_type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16u_C4R : + src_type == CV_16SC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16s_C1R : + src_type == CV_16SC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16s_C3R : + src_type == CV_16SC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16s_C4R : + src_type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_32f_C1R : + src_type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_32f_C3R : + src_type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_32f_C4R : 0; + } + else if (interpolation == cv::InterpolationFlags::INTER_LINEAR) + { + ippFunc = src_type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_8u_C1R : + src_type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_8u_C3R : + src_type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_8u_C4R : + src_type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16u_C1R : + src_type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16u_C3R : + src_type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16u_C4R : + src_type == CV_16SC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16s_C1R : + src_type == CV_16SC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16s_C3R : + src_type == CV_16SC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16s_C4R : + src_type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_32f_C1R : + src_type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_32f_C3R : + src_type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_32f_C4R : 0; + } + else + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + if(src_height == 1 || src_width == 1) return CV_HAL_ERROR_NOT_IMPLEMENTED; + + int mode = + interpolation == cv::InterpolationFlags::INTER_NEAREST ? IPPI_INTER_NN : + interpolation == cv::InterpolationFlags::INTER_LINEAR ? IPPI_INTER_LINEAR : 0; + + if (mode == 0 || ippFunc == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + /* C1 C2 C3 C4 */ + char impl[CV_DEPTH_MAX][4][2]={{{0, 0}, {1, 1}, {0, 0}, {0, 0}}, //8U + {{1, 1}, {1, 1}, {1, 1}, {1, 1}}, //8S + {{0, 0}, {1, 1}, {0, 1}, {0, 1}}, //16U + {{1, 1}, {1, 1}, {1, 1}, {1, 1}}, //16S + {{1, 1}, {1, 1}, {1, 0}, {1, 1}}, //32S + {{1, 0}, {1, 0}, {0, 0}, {1, 0}}, //32F + {{1, 1}, {1, 1}, {1, 1}, {1, 1}}}; //64F + + if(impl[CV_TYPE(src_type)][CV_MAT_CN(src_type)-1][interpolation] == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + double coeffs[3][3]; + for( int i = 0; i < 3; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = M[i*3 + j]; + + bool ok; + cv::Range range(0, dst_height); + cv::Mat src(cv::Size(src_width, src_height), src_type, const_cast(src_data), src_step); + cv::Mat dst(cv::Size(dst_width, dst_height), src_type, dst_data, dst_step); + IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); + IPPWarpPerspectiveInvoker invoker(src_type, src, src_step, dst, dst_step, ippInter, coeffs, borderType, borderValue, ippFunc, &ok); + parallel_for_(range, invoker, dst.total()/(double)(1<<16)); + + if( ok ) + { + CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); + } + return CV_HAL_ERROR_OK; +} +#endif + +typedef IppStatus(CV_STDCALL *ippiRemap)(const void *pSrc, IppiSize srcSize, int srcStep, IppiRect srcRoi, + const Ipp32f *pxMap, int xMapStep, const Ipp32f *pyMap, int yMapStep, + void *pDst, int dstStep, IppiSize dstRoiSize, int interpolation); + +class IPPRemapInvoker : public cv::ParallelLoopBody +{ +public: + IPPRemapInvoker(int _src_type, const uchar *_src_data, size_t _src_step, int _src_width, int _src_height, + uchar *_dst_data, size_t _dst_step, int _dst_width, float *_mapx, size_t _mapx_step, float *_mapy, + size_t _mapy_step, ippiRemap _ippFunc, int _ippInterpolation, int _borderType, const double _borderValue[4], bool *_ok) : + ParallelLoopBody(), + src_type(_src_type), src(_src_data), src_step(_src_step), src_width(_src_width), src_height(_src_height), + dst(_dst_data), dst_step(_dst_step), dst_width(_dst_width), mapx(_mapx), mapx_step(_mapx_step), mapy(_mapy), + mapy_step(_mapy_step), ippFunc(_ippFunc), ippInterpolation(_ippInterpolation), borderType(_borderType), ok(_ok) + { + memcpy(this->borderValue, _borderValue, sizeof(this->borderValue)); + *ok = true; + } + + virtual void operator()(const cv::Range &range) const + { + IppiRect srcRoiRect = {0, 0, src_width, src_height}; + uchar *dst_roi_data = dst + range.start * dst_step; + IppiSize dstRoiSize = ippiSize(dst_width, range.size()); + int depth = CV_MAT_DEPTH(src_type), cn = CV_MAT_CN(src_type); + + if (borderType == cv::BORDER_CONSTANT && + !IPPSet(borderValue, dst_roi_data, (int)dst_step, dstRoiSize, cn, depth)) + { + *ok = false; + return; + } + + if (CV_INSTRUMENT_FUN_IPP(ippFunc, src, {src_width, src_height}, (int)src_step, srcRoiRect, + mapx, (int)mapx_step, mapy, (int)mapy_step, + dst_roi_data, (int)dst_step, dstRoiSize, ippInterpolation) < 0) + *ok = false; + else + { + CV_IMPL_ADD(CV_IMPL_IPP | CV_IMPL_MT); + } + } + +private: + int src_type; + const uchar *src; + size_t src_step; + int src_width, src_height; + uchar *dst; + size_t dst_step; + int dst_width; + float *mapx; + size_t mapx_step; + float *mapy; + size_t mapy_step; + ippiRemap ippFunc; + int ippInterpolation, borderType; + double borderValue[4]; + bool *ok; +}; + +int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, + uchar *dst_data, size_t dst_step, int dst_width, int dst_height, + float *mapx, size_t mapx_step, float *mapy, size_t mapy_step, + int interpolation, int border_type, const double border_value[4]) +{ + if ((interpolation == cv::INTER_LINEAR || interpolation == cv::INTER_CUBIC || interpolation == cv::INTER_NEAREST) && + (border_type == cv::BORDER_CONSTANT || border_type == cv::BORDER_TRANSPARENT)) + { + int ippInterpolation = + interpolation == cv::INTER_NEAREST ? IPPI_INTER_NN : interpolation == cv::INTER_LINEAR ? IPPI_INTER_LINEAR + : IPPI_INTER_CUBIC; + + /* C1 C2 C3 C4 */ + char impl[CV_DEPTH_MAX][4][3]={{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //16U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //16S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32F + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}}; //64F + + if (impl[CV_TYPE(src_type)][CV_MAT_CN(src_type) - 1][interpolation] == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + ippiRemap ippFunc = + src_type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R : src_type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R + : src_type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R + : src_type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R + : src_type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R + : src_type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R + : src_type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R + : src_type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R + : src_type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R + : 0; + + if (ippFunc) + { + bool ok; + + IPPRemapInvoker invoker(src_type, src_data, src_step, src_width, src_height, dst_data, dst_step, dst_width, + mapx, mapx_step, mapy, mapy_step, ippFunc, ippInterpolation, border_type, border_value, &ok); + cv::Range range(0, dst_height); + cv::parallel_for_(range, invoker, dst_width * dst_height / (double)(1 << 16)); + + if (ok) + { + CV_IMPL_ADD(CV_IMPL_IPP | CV_IMPL_MT); + return CV_HAL_ERROR_OK; + } + } + } + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} diff --git a/hal/kleidicv/kleidicv.cmake b/hal/kleidicv/kleidicv.cmake index 2ea071dade..ebd6ea3c61 100644 --- a/hal/kleidicv/kleidicv.cmake +++ b/hal/kleidicv/kleidicv.cmake @@ -1,8 +1,8 @@ function(download_kleidicv root_var) set(${root_var} "" PARENT_SCOPE) - ocv_update(KLEIDICV_SRC_COMMIT "0.3.0") - ocv_update(KLEIDICV_SRC_HASH "51a77b0185c2bac2a968a2163869b1ed") + ocv_update(KLEIDICV_SRC_COMMIT "0.5.0") + ocv_update(KLEIDICV_SRC_HASH "ba5648f8df678548f337d19d8ac607d6") set(THE_ROOT "${OpenCV_BINARY_DIR}/3rdparty/kleidicv") ocv_download(FILENAME "kleidicv-${KLEIDICV_SRC_COMMIT}.tar.gz" diff --git a/hal/riscv-rvv/include/imgproc.hpp b/hal/riscv-rvv/include/imgproc.hpp index 17c414dd9b..f3a9f8596b 100644 --- a/hal/riscv-rvv/include/imgproc.hpp +++ b/hal/riscv-rvv/include/imgproc.hpp @@ -238,8 +238,10 @@ int integral(int depth, int sdepth, int sqdepth, uchar* tilted_data, [[maybe_unused]] size_t tilted_step, int width, int height, int cn); -#undef cv_hal_integral -#define cv_hal_integral cv::rvv_hal::imgproc::integral +// Diasbled due to accuracy issue. +// Details see https://github.com/opencv/opencv/issues/27407. +//#undef cv_hal_integral +//#define cv_hal_integral cv::rvv_hal::imgproc::integral #endif // CV_HAL_RVV_1P0_ENABLED diff --git a/modules/3d/doc/solvePnP.markdown b/modules/3d/doc/solvePnP.markdown index dd4fbaa15d..a7fdbd0fb8 100644 --- a/modules/3d/doc/solvePnP.markdown +++ b/modules/3d/doc/solvePnP.markdown @@ -4,9 +4,9 @@ The pose computation problem @cite Marchand16 consists in solving for the rotation and translation that minimizes the reprojection error from 3D-2D point correspondences. -The `solvePnP` and related functions estimate the object pose given a set of object points, their corresponding image projections, as well as the camera intrinsic matrix and the distortion coefficients, see the figure below (more precisely, the X-axis of the camera frame is pointing to the right, the Y-axis downward and the Z-axis forward). +The `solvePnP` and related functions estimate the object pose given a set of object points, their corresponding image projections, as well as the camera intrinsic matrix and the distortion coefficients, see the figure below (more precisely, the convention in the computer vision field is to have the X-axis of the camera frame pointing to the right, the Y-axis downward and the Z-axis forward). -![](pnp.jpg) +![Top: the 6 dof pose computed from a list of 3D-2D point correspondences. Bottom: the considered perspective projection model.](pnp.jpg) Points expressed in the world frame \f$ \bf{X}_w \f$ are projected into the image plane \f$ \left[ u, v \right] \f$ using the perspective projection model \f$ \Pi \f$ and the camera intrinsic parameters matrix \f$ \bf{A} \f$ (also denoted \f$ \bf{K} \f$ in the literature): diff --git a/modules/3d/include/opencv2/3d.hpp b/modules/3d/include/opencv2/3d.hpp index 71d3da9127..1fd0092ae2 100644 --- a/modules/3d/include/opencv2/3d.hpp +++ b/modules/3d/include/opencv2/3d.hpp @@ -973,7 +973,9 @@ An example program about homography from the camera displacement Check @ref tutorial_homography "the corresponding tutorial" for more details */ -/** @brief Finds an object pose from 3D-2D point correspondences. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from 3D-2D point correspondences: + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1043,7 +1045,9 @@ CV_EXPORTS_W bool solvePnP( InputArray objectPoints, InputArray imagePoints, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int flags = SOLVEPNP_ITERATIVE ); -/** @brief Finds an object pose from 3D-2D point correspondences using the RANSAC scheme. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from 3D-2D point correspondences using the RANSAC scheme to deal with bad matches. + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1076,8 +1080,8 @@ projections imagePoints and the projected (using @ref projectPoints ) objectPoin makes the function resistant to outliers. @note - - An example of how to use solvePNPRansac for object detection can be found at - opencv_source_code/samples/cpp/tutorial_code/3d/real_time_pose_estimation/ + - An example of how to use solvePnPRansac for object detection can be found at + @ref tutorial_real_time_pose - The default method used to estimate the camera pose for the Minimal Sample Sets step is #SOLVEPNP_EPNP. Exceptions are: - if you choose #SOLVEPNP_P3P or #SOLVEPNP_AP3P, these methods will be used. @@ -1102,7 +1106,9 @@ CV_EXPORTS_W bool solvePnPRansac( InputArray objectPoints, InputArray imagePoint OutputArray rvec, OutputArray tvec, OutputArray inliers, const UsacParams ¶ms=UsacParams()); -/** @brief Finds an object pose from 3 3D-2D point correspondences. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from **3** 3D-2D point correspondences. + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1197,7 +1203,9 @@ CV_EXPORTS_W void solvePnPRefineVVS( InputArray objectPoints, InputArray imagePo TermCriteria::COUNT, 20, FLT_EPSILON), double VVSlambda = 1); -/** @brief Finds an object pose from 3D-2D point correspondences. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from 3D-2D point correspondences. + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1268,6 +1276,7 @@ More information is described in @ref calib3d_solvePnP - point 1: [ squareLength / 2, squareLength / 2, 0] - point 2: [ squareLength / 2, -squareLength / 2, 0] - point 3: [-squareLength / 2, -squareLength / 2, 0] + - With @ref SOLVEPNP_SQPNP input points must be >= 3 */ CV_EXPORTS_W int solvePnPGeneric( InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, @@ -2615,9 +2624,9 @@ CV_EXPORTS_W void undistortImage(InputArray distorted, OutputArray undistorted, @brief Finds an object pose from 3D-2D point correspondences for fisheye camera moodel. @param objectPoints Array of object points in the object coordinate space, Nx3 1-channel or -1xN/Nx1 3-channel, where N is the number of points. vector\ can be also passed here. +1xN/Nx1 3-channel, where N is the number of points. vector\ can also be passed here. @param imagePoints Array of corresponding image points, Nx2 1-channel or 1xN/Nx1 2-channel, -where N is the number of points. vector\ can be also passed here. +where N is the number of points. vector\ can also be passed here. @param cameraMatrix Input camera intrinsic matrix \f$\cameramatrix{A}\f$ . @param distCoeffs Input vector of distortion coefficients (4x1/1x4). @param rvec Output rotation vector (see @ref Rodrigues ) that, together with tvec, brings points from diff --git a/modules/3d/src/solvepnp.cpp b/modules/3d/src/solvepnp.cpp index 90ea1c1232..ddb64b0321 100644 --- a/modules/3d/src/solvepnp.cpp +++ b/modules/3d/src/solvepnp.cpp @@ -121,7 +121,7 @@ void drawFrameAxes(InputOutputArray image, InputArray cameraMatrix, InputArray d if (!allIn) { - CV_LOG_WARNING(NULL, "Some of projected axes endpoints are out of frame. The drawn axes may be not relaible."); + CV_LOG_WARNING(NULL, "Some of projected axes endpoints are out of frame. The drawn axes may be not reliable."); } // draw axes lines diff --git a/modules/3d/src/sqpnp.cpp b/modules/3d/src/sqpnp.cpp index b66998e68f..37a19dff69 100644 --- a/modules/3d/src/sqpnp.cpp +++ b/modules/3d/src/sqpnp.cpp @@ -63,21 +63,6 @@ const double PoseSolver::POINT_VARIANCE_THRESHOLD = 1e-5; const double PoseSolver::SQRT3 = std::sqrt(3); const int PoseSolver::SQP_MAX_ITERATION = 15; -// No checking done here for overflow, since this is not public all call instances -// are assumed to be valid -template - void set(int row, int col, cv::Matx& dest, - const cv::Matx& source) -{ - for (int y = 0; y < snrows; y++) - { - for (int x = 0; x < sncols; x++) - { - dest(row + y, col + x) = source(y, x); - } - } -} PoseSolver::PoseSolver() : num_null_vectors_(-1), @@ -278,7 +263,7 @@ void PoseSolver::computeOmega(InputArray objectPoints, InputArray imagePoints) // EVD equivalent of the SVD; less accurate cv::eigen(omega_, s_, u_); u_ = u_.t(); // eigenvectors were returned as rows -#endif +#endif // EVD #endif // HAVE_EIGEN @@ -599,9 +584,10 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, H(5, 4) = r(8) - dot_j5q2 * H(5, 1) - dot_j5q4 * H(5, 3); H(6, 4) = r(3) - dot_j5q3 * H(6, 2); H(7, 4) = r(4) - dot_j5q3 * H(7, 2); H(8, 4) = r(5) - dot_j5q3 * H(8, 2); - Matx q4 = H.col(4); - q4 *= (1.0 / cv::norm(q4)); - set(0, 4, H, q4); + cv::Matx q4 = H.col(4); + const double inv_norm_q4 = 1.0 / cv::norm(q4); + for (int i = 0; i < 9; ++i) + H(i, 4) = q4(i) * inv_norm_q4; K(4, 0) = 0; K(4, 1) = r(6) * H(3, 1) + r(7) * H(4, 1) + r(8) * H(5, 1); @@ -628,9 +614,10 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, H(7, 5) = r(1) - dot_j6q3 * H(7, 2) - dot_j6q5 * H(7, 4); H(8, 5) = r(2) - dot_j6q3 * H(8, 2) - dot_j6q5 * H(8, 4); - Matx q5 = H.col(5); - q5 *= (1.0 / cv::norm(q5)); - set(0, 5, H, q5); + cv::Matx q5 = H.col(5); + const double inv_norm_q5 = 1.0 / cv::norm(q5); + for (int i = 0; i < 9; ++i) + H(i, 5) = q5(i) * inv_norm_q5; K(5, 0) = r(6) * H(0, 0) + r(7) * H(1, 0) + r(8) * H(2, 0); K(5, 1) = 0; K(5, 2) = r(0) * H(6, 2) + r(1) * H(7, 2) + r(2) * H(8, 2); @@ -668,9 +655,10 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, } } - Matx v1 = Pn.col(index1); - v1 /= max_norm1; - set(0, 0, N, v1); + const cv::Matx& v1 = Pn.col(index1); + const double inv_max_norm1 = 1.0 / max_norm1; + for (int i = 0; i < 9; ++i) + N(i, 0) = v1(i) * inv_max_norm1; col_norms[index1] = -1.0; // mark to avoid use in subsequent loops for (int i = 0; i < 9; i++) @@ -688,11 +676,12 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, } } - Matx v2 = Pn.col(index2); - Matx n0 = N.col(0); - v2 -= v2.dot(n0) * n0; - v2 *= (1.0 / cv::norm(v2)); - set(0, 1, N, v2); + const cv::Matx& v2 = Pn.col(index2); + const cv::Matx& n0 = N.col(0); + cv::Matx u2 = v2 - v2.dot(n0) * n0; + const double inv_norm_u2 = 1.0 / cv::norm(u2); + for (int i = 0; i < 9; ++i) + N(i, 1) = u2(i) * inv_norm_u2; col_norms[index2] = -1.0; // mark to avoid use in subsequent loops for (int i = 0; i < 9; i++) @@ -707,17 +696,17 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, if (cos_v1_x_col + cos_v2_x_col <= min_dot1323) { index3 = i; - min_dot1323 = cos_v2_x_col + cos_v2_x_col; + min_dot1323 = cos_v1_x_col + cos_v2_x_col; } } } - Matx v3 = Pn.col(index3); - Matx n1 = N.col(1); - v3 -= (v3.dot(n1)) * n1 - (v3.dot(n0)) * n0; - v3 *= (1.0 / cv::norm(v3)); - set(0, 2, N, v3); - + const cv::Matx& v3 = Pn.col(index3); + const cv::Matx& n1 = N.col(1); + cv::Matx u3 = v3 - (v3.dot(n1) * n1) - (v3.dot(n0) * n0); + const double inv_norm_u3 = 1.0 / cv::norm(u3); + for (int i = 0; i < 9; ++i) + N(i, 2) = u3(i) * inv_norm_u3; } // if e = u*w*vt then r=u*diag([1, 1, det(u)*det(v)])*vt diff --git a/modules/calib/include/opencv2/calib.hpp b/modules/calib/include/opencv2/calib.hpp index d8ed80537c..feb56de3e3 100644 --- a/modules/calib/include/opencv2/calib.hpp +++ b/modules/calib/include/opencv2/calib.hpp @@ -424,6 +424,9 @@ f_{\text{mm}} = \frac{\text{sensor_size_in_mm}}{2 \times \tan{\frac{\text{fov}}{ \f] This latter conversion can be useful when using a rendering software to mimic a physical camera device. +@note + - See also #calibrationMatrixValues + Additional references, notes
@note - Many functions in this module take a camera intrinsic matrix as an input parameter. Although all @@ -626,7 +629,8 @@ the board to make the detection more robust in various environments. Otherwise, border and the background is dark, the outer black squares cannot be segmented properly and so the square grouping and ordering algorithm fails. -Use gen_pattern.py (@ref tutorial_camera_calibration_pattern) to create checkerboard. +Use the `gen_pattern.py` Python script (@ref tutorial_camera_calibration_pattern) +to create the desired checkerboard pattern. */ CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE ); @@ -684,8 +688,9 @@ which are located on the outside of the board. The following figure illustrates a sample checkerboard optimized for the detection. However, any other checkerboard can be used as well. -Use gen_pattern.py (@ref tutorial_camera_calibration_pattern) to create checkerboard. -![Checkerboard](pics/checkerboard_radon.png) +Use the `gen_pattern.py` Python script (@ref tutorial_camera_calibration_pattern) +to create the corresponding checkerboard pattern: +\image html pics/checkerboard_radon.png width=60% */ CV_EXPORTS_AS(findChessboardCornersSBWithMeta) bool findChessboardCornersSB(InputArray image,Size patternSize, OutputArray corners, diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 3dab066a4c..29cb0bf532 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -749,6 +749,11 @@ Possible usage with some positive example data: normalize(positiveData, normalizedData_minmax, 1.0, 0.0, NORM_MINMAX); @endcode +@note Due to rounding issues, min-max normalization can result in values outside provided boundaries. +If exact range conformity is needed, following workarounds can be used: +- use double floating point precision (dtype = CV_64F) +- manually clip values (`cv::max(res, left_bound, res)`, `cv::min(res, right_bound, res)` or `np.clip`) + @param src input array. @param dst output array of the same size as src . @param alpha norm value to normalize to or the lower range boundary in case of the range diff --git a/modules/core/include/opencv2/core/cuda.hpp b/modules/core/include/opencv2/core/cuda.hpp index 0558081879..03f8dfdc67 100644 --- a/modules/core/include/opencv2/core/cuda.hpp +++ b/modules/core/include/opencv2/core/cuda.hpp @@ -240,6 +240,10 @@ public: //! converts GpuMat to another datatype (Blocking call) void convertTo(OutputArray dst, int rtype) const; + //! bindings overload which converts GpuMat to another datatype (Blocking call) + CV_WRAP void convertTo(CV_OUT GpuMat& dst, int rtype) const { + convertTo(static_cast(dst), rtype); + } //! converts GpuMat to another datatype (Non-Blocking call) void convertTo(OutputArray dst, int rtype, Stream& stream) const; @@ -250,10 +254,13 @@ public: //! converts GpuMat to another datatype with scaling (Blocking call) void convertTo(OutputArray dst, int rtype, double alpha, double beta = 0.0) const; + //! bindings overload which converts GpuMat to another datatype with scaling(Blocking call) - CV_WRAP void convertTo(CV_OUT GpuMat& dst, int rtype, double alpha = 1.0, double beta = 0.0) const { +#ifdef OPENCV_BINDINGS_PARSER + CV_WRAP void convertTo(CV_OUT GpuMat& dst, int rtype, double alpha=1.0, double beta = 0.0) const { convertTo(static_cast(dst), rtype, alpha, beta); } +#endif //! converts GpuMat to another datatype with scaling (Non-Blocking call) void convertTo(OutputArray dst, int rtype, double alpha, Stream& stream) const; diff --git a/modules/core/include/opencv2/core/cv_cpu_dispatch.h b/modules/core/include/opencv2/core/cv_cpu_dispatch.h index 853d91ca23..a53641de20 100644 --- a/modules/core/include/opencv2/core/cv_cpu_dispatch.h +++ b/modules/core/include/opencv2/core/cv_cpu_dispatch.h @@ -72,7 +72,7 @@ # define CV_AVX 1 #endif #ifdef CV_CPU_COMPILE_FP16 -# if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) +# if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) # include # elif defined(__riscv_vector) # include @@ -139,7 +139,7 @@ # define CV_FMA3 1 #endif -#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) +#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) # include # include # define CV_NEON 1 @@ -232,7 +232,7 @@ struct VZeroUpperGuard { # define CV_MMX 1 # define CV_SSE 1 # define CV_SSE2 1 -#elif defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) +#elif defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) # include # include # define CV_NEON 1 diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 3d2a4c28b1..d00a78b151 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -370,7 +370,7 @@ enum CpuFeatures { #include "cv_cpu_dispatch.h" -#if !defined(CV_STRONG_ALIGNMENT) && defined(__arm__) && !(defined(__aarch64__) || defined(_M_ARM64)) +#if !defined(CV_STRONG_ALIGNMENT) && defined(__arm__) && !(defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) // int*, int64* should be propertly aligned pointers on ARMv7 #define CV_STRONG_ALIGNMENT 1 #endif @@ -710,7 +710,7 @@ __CV_ENUM_FLAGS_BITWISE_XOR_EQ (EnumType, EnumType) #ifdef CV_XADD // allow to use user-defined macro #elif defined __GNUC__ || defined __clang__ -# if defined __clang__ && __clang_major__ >= 3 && !defined __ANDROID__ && !defined __EMSCRIPTEN__ && !defined(__CUDACC__) && !defined __INTEL_COMPILER +# if defined __clang__ && __clang_major__ >= 3 && !defined __EMSCRIPTEN__ && !defined __INTEL_COMPILER # ifdef __ATOMIC_ACQ_REL # define CV_XADD(addr, delta) __c11_atomic_fetch_add((_Atomic(int)*)(addr), delta, __ATOMIC_ACQ_REL) # else diff --git a/modules/core/include/opencv2/core/fast_math.hpp b/modules/core/include/opencv2/core/fast_math.hpp index a28c3fbedf..6f0ad67bc4 100644 --- a/modules/core/include/opencv2/core/fast_math.hpp +++ b/modules/core/include/opencv2/core/fast_math.hpp @@ -303,7 +303,7 @@ CV_INLINE int cvIsInf( double value ) { #if defined CV_INLINE_ISINF_DBL CV_INLINE_ISINF_DBL(value); -#elif defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__PPC64__) || defined(__loongarch64) +#elif defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__PPC64__) || defined(__loongarch64) Cv64suf ieee754; ieee754.f = value; return (ieee754.u & 0x7fffffffffffffff) == diff --git a/modules/core/include/opencv2/core/hal/intrin_neon.hpp b/modules/core/include/opencv2/core/hal/intrin_neon.hpp index 2ffa964dcc..364164e607 100644 --- a/modules/core/include/opencv2/core/hal/intrin_neon.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_neon.hpp @@ -56,7 +56,7 @@ namespace cv CV_CPU_OPTIMIZATION_HAL_NAMESPACE_BEGIN #define CV_SIMD128 1 -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define CV_SIMD128_64F 1 #else #define CV_SIMD128_64F 0 @@ -77,7 +77,7 @@ CV_CPU_OPTIMIZATION_HAL_NAMESPACE_BEGIN // // [1] https://developer.arm.com/documentation/101028/0012/13--Advanced-SIMD--Neon--intrinsics // [2] https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros -#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64) +#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64) || defined(_M_ARM64EC) #define CV_NEON_AARCH64 1 #else #define CV_NEON_AARCH64 0 @@ -1221,7 +1221,7 @@ OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_uint32x4, OPENCV_HAL_NOP, u32, u32) OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_int32x4, vreinterpretq_s32_u32, s32, u32) OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_float32x4, vreinterpretq_f32_u32, f32, u32) -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) static inline uint64x2_t vmvnq_u64(uint64x2_t a) { uint64x2_t vx = vreinterpretq_u64_u32(vdupq_n_u32(0xFFFFFFFF)); @@ -2053,7 +2053,7 @@ inline v_int32x4 v_load_expand_q(const schar* ptr) return v_int32x4(vmovl_s16(v1)); } -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define OPENCV_HAL_IMPL_NEON_UNPACKS(_Tpvec, suffix) \ inline void v_zip(const v_##_Tpvec& a0, const v_##_Tpvec& a1, v_##_Tpvec& b0, v_##_Tpvec& b1) \ { \ diff --git a/modules/core/include/opencv2/core/private.hpp b/modules/core/include/opencv2/core/private.hpp index 77cbd9dfdd..5cb624a62e 100644 --- a/modules/core/include/opencv2/core/private.hpp +++ b/modules/core/include/opencv2/core/private.hpp @@ -190,8 +190,6 @@ T* allocSingletonNew() { return new(allocSingletonNewBuffer(sizeof(T))) T(); } #define IPP_DISABLE_PYRAMIDS_UP 1 // Different results #define IPP_DISABLE_PYRAMIDS_DOWN 1 // Different results #define IPP_DISABLE_PYRAMIDS_BUILD 1 // Different results -#define IPP_DISABLE_WARPAFFINE 1 // Different results -#define IPP_DISABLE_WARPPERSPECTIVE 1 // Different results #define IPP_DISABLE_REMAP 1 // Different results #define IPP_DISABLE_YUV_RGB 1 // accuracy difference #define IPP_DISABLE_RGB_YUV 1 // breaks OCL accuracy tests @@ -202,7 +200,6 @@ T* allocSingletonNew() { return new(allocSingletonNewBuffer(sizeof(T))) T(); } #define IPP_DISABLE_XYZ_RGB 1 // big accuracy difference #define IPP_DISABLE_HOUGH 1 // improper integration/results #define IPP_DISABLE_FILTER2D_BIG_MASK 1 // different results on masks > 7x7 -#define IPP_DISABLE_NORM_8U 1 // accuracy difference in perf test sanity check // Temporary disabled named IPP region. Performance #define IPP_DISABLE_PERF_COPYMAKE 1 // performance variations diff --git a/modules/core/include/opencv2/core/utils/filesystem.private.hpp b/modules/core/include/opencv2/core/utils/filesystem.private.hpp index 80c9a5282c..bf5dea416d 100644 --- a/modules/core/include/opencv2/core/utils/filesystem.private.hpp +++ b/modules/core/include/opencv2/core/utils/filesystem.private.hpp @@ -11,7 +11,7 @@ /* no support */ # elif defined WINRT || defined _WIN32_WCE /* not supported */ -# elif defined __ANDROID__ || defined __linux__ || defined _WIN32 || \ +# elif defined __ANDROID__ || defined __linux__ || defined _WIN32 || defined __CYGWIN__ || \ defined __FreeBSD__ || defined __bsdi__ || defined __HAIKU__ || \ defined __GNU__ || defined __QNX__ # define OPENCV_HAVE_FILESYSTEM_SUPPORT 1 diff --git a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp index 23e48ee0eb..7f92cb5fa6 100644 --- a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp +++ b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp @@ -12,7 +12,7 @@ #if defined(_WIN32) #include -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) #include #endif @@ -65,7 +65,7 @@ void* getSymbol_(LibHandle_t h, const char* symbolName) { #if defined(_WIN32) return (void*)GetProcAddress(h, symbolName); -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) return dlsym(h, symbolName); #endif } @@ -79,7 +79,7 @@ LibHandle_t libraryLoad_(const FileSystemPath_t& filename) # else return LoadLibraryW(filename.c_str()); #endif -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) void* handle = dlopen(filename.c_str(), RTLD_NOW); CV_LOG_IF_DEBUG(NULL, !handle, "dlopen() error: " << dlerror()); return handle; @@ -91,7 +91,7 @@ void libraryRelease_(LibHandle_t h) { #if defined(_WIN32) FreeLibrary(h); -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) dlclose(h); #endif } diff --git a/modules/core/misc/java/src/java/core+MatAt.kt b/modules/core/misc/java/src/java/core+MatAt.kt index c81e21057f..d50c3fbb0f 100644 --- a/modules/core/misc/java/src/java/core+MatAt.kt +++ b/modules/core/misc/java/src/java/core+MatAt.kt @@ -47,49 +47,50 @@ inline fun Mat.at(idx: IntArray) : Atable = class AtableUByte(val mat: Mat, val indices: IntArray): Atable { + constructor(mat: Mat, row: Int, col: Int) : this(mat, intArrayOf(row, col)) override fun getV(): UByte { - val data = UByteArray(1) + val data = ByteArray(1) mat.get(indices, data) - return data[0] + return data[0].toUByte() } override fun setV(v: UByte) { - val data = ubyteArrayOf(v) + val data = byteArrayOf(v.toByte()) mat.put(indices, data) } override fun getV2c(): Tuple2 { - val data = UByteArray(2) + val data = ByteArray(2) mat.get(indices, data) - return Tuple2(data[0], data[1]) + return Tuple2(data[0].toUByte(), data[1].toUByte()) } override fun setV2c(v: Tuple2) { - val data = ubyteArrayOf(v._0, v._1) + val data = byteArrayOf(v._0.toByte(), v._1.toByte()) mat.put(indices, data) } override fun getV3c(): Tuple3 { - val data = UByteArray(3) + val data = ByteArray(3) mat.get(indices, data) - return Tuple3(data[0], data[1], data[2]) + return Tuple3(data[0].toUByte(), data[1].toUByte(), data[2].toUByte()) } override fun setV3c(v: Tuple3) { - val data = ubyteArrayOf(v._0, v._1, v._2) + val data = byteArrayOf(v._0.toByte(), v._1.toByte(), v._2.toByte()) mat.put(indices, data) } override fun getV4c(): Tuple4 { - val data = UByteArray(4) + val data = ByteArray(4) mat.get(indices, data) - return Tuple4(data[0], data[1], data[2], data[3]) + return Tuple4(data[0].toUByte(), data[1].toUByte(), data[2].toUByte(), data[3].toUByte()) } override fun setV4c(v: Tuple4) { - val data = ubyteArrayOf(v._0, v._1, v._2, v._3) + val data = byteArrayOf(v._0.toByte(), v._1.toByte(), v._2.toByte(), v._3.toByte()) mat.put(indices, data) } } @@ -99,46 +100,47 @@ class AtableUShort(val mat: Mat, val indices: IntArray): Atable { constructor(mat: Mat, row: Int, col: Int) : this(mat, intArrayOf(row, col)) override fun getV(): UShort { - val data = UShortArray(1) + val data = ShortArray(1) mat.get(indices, data) - return data[0] + return data[0].toUShort() } override fun setV(v: UShort) { - val data = ushortArrayOf(v) + val data = shortArrayOf(v.toShort()) mat.put(indices, data) } override fun getV2c(): Tuple2 { - val data = UShortArray(2) + val data = ShortArray(2) mat.get(indices, data) - return Tuple2(data[0], data[1]) + return Tuple2(data[0].toUShort(), data[1].toUShort()) } + override fun setV2c(v: Tuple2) { - val data = ushortArrayOf(v._0, v._1) + val data = shortArrayOf(v._0.toShort(), v._1.toShort()) mat.put(indices, data) } override fun getV3c(): Tuple3 { - val data = UShortArray(3) + val data = ShortArray(3) mat.get(indices, data) - return Tuple3(data[0], data[1], data[2]) + return Tuple3(data[0].toUShort(), data[1].toUShort(), data[2].toUShort()) } override fun setV3c(v: Tuple3) { - val data = ushortArrayOf(v._0, v._1, v._2) + val data = shortArrayOf(v._0.toShort(), v._1.toShort(), v._2.toShort()) mat.put(indices, data) } override fun getV4c(): Tuple4 { - val data = UShortArray(4) + val data = ShortArray(4) mat.get(indices, data) - return Tuple4(data[0], data[1], data[2], data[3]) + return Tuple4(data[0].toUShort(), data[1].toUShort(), data[2].toUShort(), data[3].toUShort()) } override fun setV4c(v: Tuple4) { - val data = ushortArrayOf(v._0, v._1, v._2, v._3) + val data = shortArrayOf(v._0.toShort(), v._1.toShort(), v._2.toShort(), v._3.toShort()) mat.put(indices, data) } } diff --git a/modules/core/src/cuda/gpu_mat.cu b/modules/core/src/cuda/gpu_mat.cu index a0c67d9277..0d80721ee7 100644 --- a/modules/core/src/cuda/gpu_mat.cu +++ b/modules/core/src/cuda/gpu_mat.cu @@ -548,7 +548,7 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, Stream& stream) co return; } - CV_DbgAssert( sdepth <= CV_64F && ddepth <= CV_64F ); + CV_Assert( sdepth <= CV_64F && ddepth <= CV_64F ); GpuMat src = *this; @@ -580,6 +580,8 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, double alpha, doub const int sdepth = depth(); const int ddepth = CV_MAT_DEPTH(rtype); + CV_Assert(sdepth <= CV_64F && ddepth <= CV_64F); + GpuMat src = *this; _dst.create(size(), rtype); diff --git a/modules/core/src/matmul.simd.hpp b/modules/core/src/matmul.simd.hpp index cb0a25bbc1..7f53f5583d 100644 --- a/modules/core/src/matmul.simd.hpp +++ b/modules/core/src/matmul.simd.hpp @@ -1598,7 +1598,7 @@ transform_32f( const float* src, float* dst, const float* m, int len, int scn, i // Disabled for RISC-V Vector (scalable), because of: // 1. v_matmuladd for RVV is 128-bit only but not scalable, this will fail the test `Core_Transform.accuracy`. // 2. Both gcc and clang can autovectorize this, with better performance than using Universal intrinsic. -#if (CV_SIMD || CV_SIMD_SCALABLE) && !defined(__aarch64__) && !defined(_M_ARM64) && !(CV_TRY_RVV && CV_RVV) +#if (CV_SIMD || CV_SIMD_SCALABLE) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(_M_ARM64EC) && !(CV_TRY_RVV && CV_RVV) int x = 0; if( scn == 3 && dcn == 3 ) { diff --git a/modules/core/src/norm.dispatch.cpp b/modules/core/src/norm.dispatch.cpp index b673f45603..930c3b758a 100644 --- a/modules/core/src/norm.dispatch.cpp +++ b/modules/core/src/norm.dispatch.cpp @@ -316,7 +316,7 @@ double norm( InputArray _src, int normType, InputArray _mask ) } NormFunc func = getNormFunc(normType >> 1, depth); - CV_Assert( func != 0 ); + CV_Assert( (normType >> 1) >= 3 || func != 0 ); if( src.isContinuous() && mask.empty() ) { diff --git a/modules/core/src/norm.simd.hpp b/modules/core/src/norm.simd.hpp index aadd31588e..cc82e403ca 100644 --- a/modules/core/src/norm.simd.hpp +++ b/modules/core/src/norm.simd.hpp @@ -1525,6 +1525,8 @@ NormFunc getNormFunc(int normType, int depth) } }; + if (normType >= 3 || normType < 0) return nullptr; + return normTab[normType][depth]; } diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index 01522b3b19..b1abaad678 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -947,7 +947,7 @@ int getNumberOfCPUs_() #if defined _WIN32 SYSTEM_INFO sysinfo = {}; -#if (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(WINRT)) && _WIN32_WINNT >= 0x501 +#if (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(_M_X64) || defined(WINRT)) && _WIN32_WINNT >= 0x501 GetNativeSystemInfo( &sysinfo ); #else GetSystemInfo( &sysinfo ); diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index e9decf64b4..9f73c04d00 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -44,6 +44,7 @@ #include "precomp.hpp" //#include "opencv2/core/core_c.h" #include +#include #include #include @@ -126,7 +127,6 @@ void* allocSingletonNewBuffer(size_t size) { return malloc(size); } #endif #ifdef CV_ERROR_SET_TERMINATE_HANDLER -#include // std::set_terminate #include // std::abort #endif @@ -728,7 +728,7 @@ struct HWFeatures #if defined _ARM_ && (defined(_WIN32_WCE) && _WIN32_WCE >= 0x800) have[CV_CPU_NEON] = true; #endif - #if defined _M_ARM64 + #if defined _M_ARM64 || defined _M_ARM64EC have[CV_CPU_NEON] = true; #endif #ifdef __riscv_vector diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp index db16ac3347..adc5d85c6c 100644 --- a/modules/core/src/utils/filesystem.cpp +++ b/modules/core/src/utils/filesystem.cpp @@ -34,7 +34,7 @@ #include #include #include -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ #include #include #include @@ -194,7 +194,7 @@ cv::String getcwd() sz = GetCurrentDirectoryA((DWORD)buf.size(), buf.data()); return cv::String(buf.data(), (size_t)sz); #endif -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ for(;;) { char* p = ::getcwd(buf.data(), buf.size()); @@ -228,7 +228,7 @@ bool createDirectory(const cv::String& path) #else int result = _mkdir(path.c_str()); #endif -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ int result = mkdir(path.c_str(), 0777); #else int result = -1; @@ -343,7 +343,7 @@ private: Impl& operator=(const Impl&); // disabled }; -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ struct FileLock::Impl { diff --git a/modules/dnn/include/opencv2/dnn/version.hpp b/modules/dnn/include/opencv2/dnn/version.hpp index c5a8c3b35a..9e5a56f0c9 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 20241127 +#define OPENCV_DNN_API_VERSION 20250619 #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/src/caffe/caffe_importer.cpp b/modules/dnn/src/caffe/caffe_importer.cpp index 188441a5a1..46e222b922 100644 --- a/modules/dnn/src/caffe/caffe_importer.cpp +++ b/modules/dnn/src/caffe/caffe_importer.cpp @@ -220,7 +220,7 @@ public: const google::protobuf::UnknownFieldSet& unknownFields = msgRefl->GetUnknownFields(msg); bool hasData = fd->is_required() || - (fd->is_optional() && msgRefl->HasField(msg, fd)) || + (!fd->is_repeated() && !fd->is_required() && msgRefl->HasField(msg, fd)) || (fd->is_repeated() && msgRefl->FieldSize(msg, fd) > 0) || !unknownFields.empty(); if (!hasData) diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 1830d6f7b0..a14e03cebf 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -716,8 +716,12 @@ struct GeluFunctor : public BaseFunctor { #endif } - bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + bool supportBackend(int backendId, int) + { + return backendId == DNN_BACKEND_OPENCV || + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH || + backendId == DNN_BACKEND_CANN; } void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { @@ -814,7 +818,19 @@ struct GeluFunctor : public BaseFunctor { const std::vector > &inputs, const std::vector >& nodes) { - CV_Error(Error::StsNotImplemented, ""); + auto input_wrapper = inputs[0].dynamicCast(); + + auto op = std::make_shared(name); + + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); } #endif // HAVE_CANN @@ -1555,7 +1571,9 @@ struct SqrtFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; + return backendId == DNN_BACKEND_OPENCV || + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const diff --git a/modules/dnn/src/layers/nary_eltwise_layers.cpp b/modules/dnn/src/layers/nary_eltwise_layers.cpp index 59841b4f96..6fed3f7400 100644 --- a/modules/dnn/src/layers/nary_eltwise_layers.cpp +++ b/modules/dnn/src/layers/nary_eltwise_layers.cpp @@ -254,7 +254,7 @@ public: if (backendId == DNN_BACKEND_CANN) return op == OPERATION::ADD || op == OPERATION::PROD || op == OPERATION::SUB || op == OPERATION::DIV || op == OPERATION::MAX || op == OPERATION::MIN || - op == OPERATION::MOD || op == OPERATION::FMOD; + op == OPERATION::MOD || op == OPERATION::FMOD || op == OPERATION::POW; #endif if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) return (op == OPERATION::ADD || @@ -1036,6 +1036,7 @@ public: BUILD_CANN_ELTWISE_OP(OPERATION::MIN, Minimum, name); BUILD_CANN_ELTWISE_OP(OPERATION::MOD, Mod, name); BUILD_CANN_ELTWISE_OP(OPERATION::FMOD, Mod, name); + BUILD_CANN_ELTWISE_OP(OPERATION::POW, Pow, name); #undef BUILD_CANN_ELTWISE_OP default: CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation"); } diff --git a/modules/dnn/src/layers/reduce_layer.cpp b/modules/dnn/src/layers/reduce_layer.cpp index fed75dd0cd..6ce05e7b2b 100644 --- a/modules/dnn/src/layers/reduce_layer.cpp +++ b/modules/dnn/src/layers/reduce_layer.cpp @@ -5,6 +5,8 @@ #include "../precomp.hpp" #include +#include "../op_cann.hpp" + namespace cv { namespace dnn { @@ -54,6 +56,13 @@ public: } virtual bool supportBackend(int backendId) CV_OVERRIDE { +#ifdef HAVE_CANN + if (backendId == DNN_BACKEND_CANN) + return reduce_type == ReduceType::MAX || reduce_type == ReduceType::MIN || + reduce_type == ReduceType::MEAN || reduce_type == ReduceType::SUM || + reduce_type == ReduceType::PROD || reduce_type == ReduceType::LOG_SUM || + reduce_type == ReduceType::LOG_SUM_EXP; +#endif return backendId == DNN_BACKEND_OPENCV; } @@ -523,6 +532,53 @@ public: } } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputs, + const std::vector > &outputs, + const std::vector >& nodes) CV_OVERRIDE + { + CV_CheckFalse(axes.empty(), "DNN/CANN: Reduce layers need axes to build CANN operators"); + + auto input_node = nodes[0].dynamicCast()->getOp(); + auto input_wrapper = inputs[0].dynamicCast(); + auto input_desc = input_wrapper->getTensorDesc(); + + std::vector axes_shape{(int)axes.size()}; + Mat axes_mat(axes_shape, CV_32S, &axes[0]); + auto axes_node = std::make_shared(axes_mat.data, axes_mat.type(), axes_shape, cv::format("%s_axes", name.c_str())); + auto axes_desc = axes_node->getTensorDesc(); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::shared_ptr reduce_op = nullptr; + switch (reduce_type) + { +#define BUILD_CANN_REDUCE_OP(op_type, class_name, op_name) \ + case op_type: { \ + auto op = std::make_shared(op_name); \ + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); \ + op->set_input_axes(*(axes_node)->getOp()); \ + op->set_attr_keep_dims(keepdims); \ + op->update_input_desc_x(*input_desc); \ + op->update_input_desc_axes(*axes_desc); \ + op->update_output_desc_y(*output_desc); \ + reduce_op = op; \ + } break; + BUILD_CANN_REDUCE_OP(ReduceType::MAX, ReduceMax, name); + BUILD_CANN_REDUCE_OP(ReduceType::MIN, ReduceMin, name); + BUILD_CANN_REDUCE_OP(ReduceType::MEAN, ReduceMean, name); + BUILD_CANN_REDUCE_OP(ReduceType::SUM, ReduceSum, name); + BUILD_CANN_REDUCE_OP(ReduceType::PROD, ReduceProd, name); + BUILD_CANN_REDUCE_OP(ReduceType::LOG_SUM, ReduceLogSum, name); + BUILD_CANN_REDUCE_OP(ReduceType::LOG_SUM_EXP, ReduceLogSumExp, name); +#undef BUILD_CANN_REDUCE_OP + default: CV_Error(Error::StsNotImplemented, "Unsupported reduce operation"); + } + + return Ptr(new CannBackendNode(reduce_op)); + } +#endif // HAVE_CANN + private: enum ReduceType { diff --git a/modules/dnn/src/layers/reshape_layer.cpp b/modules/dnn/src/layers/reshape_layer.cpp index eb30a3947a..b7d1147f1e 100644 --- a/modules/dnn/src/layers/reshape_layer.cpp +++ b/modules/dnn/src/layers/reshape_layer.cpp @@ -184,6 +184,16 @@ public: for (i = 0; i < dims; i++) newShapeDesc[i] = paramShape.get(i); } + if (params.has("unsqueeze_axes")) + { + const DictValue& param_unsqueeze_axes = params.get("unsqueeze_axes"); + int len_axes = param_unsqueeze_axes.size(); + unsqueeze_axes.resize(len_axes); + for (int i = 0; i < len_axes; ++i) + { + unsqueeze_axes[i] = (int64_t)param_unsqueeze_axes.get(i); + } + } if (hasDynamicShapes) { dynamicShapes.clear(); @@ -349,33 +359,56 @@ public: const std::vector > &outputs, const std::vector >& nodes) CV_OVERRIDE { - auto x = inputs[0].dynamicCast(); + auto input_wrapper = inputs[0].dynamicCast(); - // create operator - auto op = std::make_shared(name); + if (!unsqueeze_axes.empty()) + { + auto op = std::make_shared(name); - // set attributes - op->set_attr_axis(axis); - op->set_attr_num_axes(numAxes); + // set attributes + op->set_attr_axes(unsqueeze_axes); - // set inputs - // set inputs : x - auto op_x = nodes[0].dynamicCast()->getOp(); - op->set_input_x_by_name(*op_x, x->name.c_str()); - auto x_desc = x->getTensorDesc(); - op->update_input_desc_x(*x_desc); - // set inputs : shape - std::vector shape_of_shape{(int)newShapeDesc.size()}; - Mat shape_mat(shape_of_shape, CV_32S, newShapeDesc.data()); - auto op_const_shape = std::make_shared(shape_mat.data, shape_mat.type(), shape_of_shape, cv::format("%s_shape", name.c_str())); - op->set_input_shape(*(op_const_shape->getOp())); - op->update_input_desc_shape(*(op_const_shape->getTensorDesc())); + // set inputs + // set inputs : x + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); - // set outputs - auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); - op->update_output_desc_y(*output_y_desc); + // set outputs + auto desc_y = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*desc_y); - return Ptr(new CannBackendNode(op)); + return Ptr(new CannBackendNode(op)); + } + else + { + // create operator + auto op = std::make_shared(name); + + // set attributes + op->set_attr_axis(axis); + op->set_attr_num_axes(numAxes); + + // set inputs + // set inputs : x + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); + // set inputs : shape + std::vector shape_of_shape{(int)newShapeDesc.size()}; + Mat shape_mat(shape_of_shape, CV_32S, newShapeDesc.data()); + auto op_const_shape = std::make_shared(shape_mat.data, shape_mat.type(), shape_of_shape, cv::format("%s_shape", name.c_str())); + op->set_input_shape(*(op_const_shape->getOp())); + op->update_input_desc_shape(*(op_const_shape->getTensorDesc())); + + // set outputs + auto desc_y = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*desc_y); + + return Ptr(new CannBackendNode(op)); + } } #endif // HAVE_CANN @@ -524,6 +557,7 @@ private: bool shapesInitialized; float scale; int zeropoint; + std::vector unsqueeze_axes; }; Ptr ReshapeLayer::create(const LayerParams& params) diff --git a/modules/dnn/src/layers/slice_layer.cpp b/modules/dnn/src/layers/slice_layer.cpp index db6e4b2469..91264d2677 100644 --- a/modules/dnn/src/layers/slice_layer.cpp +++ b/modules/dnn/src/layers/slice_layer.cpp @@ -678,7 +678,7 @@ public: auto op = std::make_shared(name); // set attr - int n_split = static_cast(sliceRanges[0].size()); + int n_split = static_cast(outputs.size()); op->set_attr_num_split(n_split); // set inputs diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index aea263777a..38069dada5 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -2312,6 +2312,8 @@ void ONNXImporter::parseUnsqueeze(LayerParams& layerParams, const opencv_onnx::N if (axes.size() != 1) CV_Error(Error::StsNotImplemented, "Multidimensional unsqueeze"); + layerParams.set("unsqueeze_axes", axes); + int depth = layerParams.get("depth", CV_32F); MatShape inpShape = outShapes[node_proto.input(0)]; diff --git a/modules/dnn/src/op_cann.cpp b/modules/dnn/src/op_cann.cpp index 5894aef337..c36633dc15 100644 --- a/modules/dnn/src/op_cann.cpp +++ b/modules/dnn/src/op_cann.cpp @@ -61,14 +61,14 @@ CannConstOp::CannConstOp(const uint8_t* data, const int dtype, const std::vector { case CV_32F: break; case CV_32S: ge_dtype = ge::DT_INT32; break; - default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + default: CV_Error(Error::StsNotImplemented, cv::format("Unsupported data type %d of node %s", dtype, name.c_str())); } auto size_of_type = sizeof(float); switch (dtype) { case CV_32F: break; case CV_32S: size_of_type = sizeof(int); break; - default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + default: CV_Error(Error::StsNotImplemented, cv::format("Unsupported data type %d of node %s", dtype, name.c_str())); } desc_ = std::make_shared(ge_shape, ge::FORMAT_NCHW, ge_dtype); auto ge_tensor = std::make_shared(); diff --git a/modules/dnn/src/op_inf_engine.cpp b/modules/dnn/src/op_inf_engine.cpp index a13b7a2e74..1dfe489071 100644 --- a/modules/dnn/src/op_inf_engine.cpp +++ b/modules/dnn/src/op_inf_engine.cpp @@ -369,7 +369,7 @@ cv::String getInferenceEngineCPUType() { auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); CV_UNUSED(networkBackend); -#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM64) +#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) return CV_DNN_INFERENCE_ENGINE_CPU_TYPE_ARM_COMPUTE; #else return CV_DNN_INFERENCE_ENGINE_CPU_TYPE_X86; diff --git a/modules/flann/include/opencv2/flann/autotuned_index.h b/modules/flann/include/opencv2/flann/autotuned_index.h index d90f739aff..0b02f5670a 100644 --- a/modules/flann/include/opencv2/flann/autotuned_index.h +++ b/modules/flann/include/opencv2/flann/autotuned_index.h @@ -54,7 +54,7 @@ NNIndex* create_index_by_type(const Matrix #endif diff --git a/modules/flann/include/opencv2/flann/kmeans_index.h b/modules/flann/include/opencv2/flann/kmeans_index.h index fd7fe2bd39..7ced99ca81 100644 --- a/modules/flann/include/opencv2/flann/kmeans_index.h +++ b/modules/flann/include/opencv2/flann/kmeans_index.h @@ -62,7 +62,7 @@ struct KMeansIndexParams : public IndexParams { KMeansIndexParams(int branching = 32, int iterations = 11, flann_centers_init_t centers_init = FLANN_CENTERS_RANDOM, - float cb_index = 0.2, int trees = 1 ) + float cb_index = 0.2f, int trees = 1 ) { (*this)["algorithm"] = FLANN_INDEX_KMEANS; // branching factor diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index 42df4d4aee..194d6cefa0 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -78,7 +78,7 @@ using namespace cv; static const char* trackbar_text = " "; -#if defined _M_X64 || defined __x86_64 || defined _M_ARM64 +#if defined _M_X64 || defined __x86_64 || defined _M_ARM64 || defined _M_ARM64EC #define icvGetWindowLongPtr GetWindowLongPtr #define icvSetWindowLongPtr(hwnd, id, ptr) SetWindowLongPtr(hwnd, id, (LONG_PTR)(ptr)) diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index a061cf1889..68841e93b6 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -23,11 +23,6 @@ if(HAVE_JPEG) list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES}) endif() -if(HAVE_JPEGXL) - ocv_include_directories(${OPENJPEG_INCLUDE_DIRS}) - list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES}) -endif() - if(HAVE_WEBP) add_definitions(-DHAVE_WEBP) ocv_include_directories(${WEBP_INCLUDE_DIR}) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 1cc6866634..ddb8608c8c 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -251,6 +251,15 @@ enum ImwriteGIFCompressionFlags { IMWRITE_GIF_COLORTABLE_SIZE_256 = 8 }; +enum ImageMetadataType +{ + IMAGE_METADATA_UNKNOWN = -1, + IMAGE_METADATA_EXIF = 0, + IMAGE_METADATA_XMP = 1, + IMAGE_METADATA_ICCP = 2, + IMAGE_METADATA_MAX = 2 +}; + //! @} imgcodecs_flags /** @brief Represents an animation with multiple frames. @@ -277,6 +286,8 @@ struct CV_EXPORTS_W_SIMPLE Animation CV_PROP_RW std::vector durations; //! Vector of frames, where each Mat represents a single frame. CV_PROP_RW std::vector frames; + //! image that can be used for the format in addition to the animation or if animation is not supported in the reader (like in PNG). + CV_PROP_RW Mat still_image; /** @brief Constructs an Animation object with optional loop count and background color. @@ -358,6 +369,17 @@ The image passing through the img parameter can be pre-allocated. The memory is */ CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR ); +/** @brief Reads an image from a file together with associated metadata. + +The function imreadWithMetadata reads image from the specified file. It does the same thing as imread, but additionally reads metadata if the corresponding file contains any. +@param filename Name of the file to be loaded. +@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType. +@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata +@param flags Flag that can take values of cv::ImreadModes +*/ +CV_EXPORTS_W Mat imreadWithMetadata( const String& filename, CV_OUT std::vector& metadataTypes, + OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR); + /** @brief Loads a multi-page image from a file. The function imreadmulti loads a multi-page image from the specified file into a vector of Mat objects. @@ -462,7 +484,7 @@ filename extension (see cv::imread for the list of extensions). In general, only single-channel or 3-channel (with 'BGR' channel order) images can be saved using this function, with these exceptions: -- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. +- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. More than 4 channels can be saved. (imread can load it then.) - 8-bit unsigned (CV_8U) images are not supported. - With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved. - All images will be converted to 32-bit float (CV_32F). @@ -507,6 +529,20 @@ It also demonstrates how to save multiple images in a TIFF file: CV_EXPORTS_W bool imwrite( const String& filename, InputArray img, const std::vector& params = std::vector()); +/** @brief Saves an image to a specified file with metadata + +The function imwriteWithMetadata saves the image to the specified file. It does the same thing as imwrite, but additionally writes metadata if the corresponding format supports it. +@param filename Name of the file. As with imwrite, image format is determined by the file extension. +@param img (Mat or vector of Mat) Image or Images to be saved. +@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType. +@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file +@param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags +*/ +CV_EXPORTS_W bool imwriteWithMetadata( const String& filename, InputArray img, + const std::vector& metadataTypes, + InputArrayOfArrays& metadata, + const std::vector& params = std::vector()); + //! @brief multi-image overload for bindings CV_WRAP static inline bool imwritemulti(const String& filename, InputArrayOfArrays img, @@ -528,6 +564,22 @@ See cv::imread for the list of supported formats and flags description. */ CV_EXPORTS_W Mat imdecode( InputArray buf, int flags ); +/** @brief Reads an image from a buffer in memory together with associated metadata. + +The function imdecode reads an image from the specified buffer in the memory. If the buffer is too short or +contains invalid data, the function returns an empty matrix ( Mat::data==NULL ). + +See cv::imread for the list of supported formats and flags description. + +@note In the case of color images, the decoded images will have the channels stored in **B G R** order. +@param buf Input array or vector of bytes. +@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType. +@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata +@param flags The same flags as in cv::imread, see cv::ImreadModes. +*/ +CV_EXPORTS_W Mat imdecodeWithMetadata( InputArray buf, CV_OUT std::vector& metadataTypes, + OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR ); + /** @overload @param buf Input array or vector of bytes. @param flags The same flags as in cv::imread, see cv::ImreadModes. @@ -566,6 +618,24 @@ CV_EXPORTS_W bool imencode( const String& ext, InputArray img, CV_OUT std::vector& buf, const std::vector& params = std::vector()); +/** @brief Encodes an image into a memory buffer. + +The function imencode compresses the image and stores it in the memory buffer that is resized to fit the +result. See cv::imwrite for the list of supported formats and flags description. + +@param ext File extension that defines the output format. Must include a leading period. +@param img Image to be compressed. +@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType. +@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file +@param buf Output buffer resized to fit the compressed image. +@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags. +*/ +CV_EXPORTS_W bool imencodeWithMetadata( const String& ext, InputArray img, + const std::vector& metadataTypes, + InputArrayOfArrays metadata, + CV_OUT std::vector& buf, + const std::vector& params = std::vector()); + /** @brief Encodes array of images into a memory buffer. The function is analog to cv::imencode for in-memory multi-page image compression. @@ -589,7 +659,7 @@ This can be useful for verifying support for a given image format before attempt @return true if an image reader for the specified file is available and the file can be opened, false otherwise. @note The function checks the availability of image codecs that are either built into OpenCV or dynamically loaded. -It does not check for the actual existence of the file but rather the ability to read the specified file type. +It does not load the image codec implementation and decode data, but uses signature check. If the file cannot be opened or the format is unsupported, the function will return false. @sa cv::haveImageWriter, cv::imread, cv::imdecode diff --git a/modules/imgcodecs/misc/java/test/ImgcodecsTest.java b/modules/imgcodecs/misc/java/test/ImgcodecsTest.java index 1f5de6a2ef..91066eb93d 100644 --- a/modules/imgcodecs/misc/java/test/ImgcodecsTest.java +++ b/modules/imgcodecs/misc/java/test/ImgcodecsTest.java @@ -15,6 +15,10 @@ import java.util.List; public class ImgcodecsTest extends OpenCVTestCase { public void testAnimation() { + if (!Imgcodecs.haveImageWriter("*.apng")) { + return; + } + Mat src = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH, Imgcodecs.IMREAD_REDUCED_COLOR_4); assertFalse(src.empty()); diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 8ed9760556..3f1bbdbe18 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -94,6 +94,10 @@ ExifEntry_t ExifReader::getTag(const ExifTagName tag) const return entry; } +const std::vector& ExifReader::getData() const +{ + return m_data; +} /** * @brief Parsing the exif data buffer and prepare (internal) exif directory diff --git a/modules/imgcodecs/src/exif.hpp b/modules/imgcodecs/src/exif.hpp index a8914bec03..3c5fbc7fe8 100644 --- a/modules/imgcodecs/src/exif.hpp +++ b/modules/imgcodecs/src/exif.hpp @@ -175,6 +175,10 @@ public: */ ExifEntry_t getTag( const ExifTagName tag ) const; + /** + * @brief Get the whole exif buffer + */ + const std::vector& getData() const; private: std::vector m_data; diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index c35eb50306..c1b86362e0 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -68,8 +68,8 @@ avifResult CopyToMat(const avifImage *image, int channels, bool useRGB , Mat *ma return avifImageYUVToRGB(image, &rgba); } -AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, - int bit_depth) { +AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, int bit_depth, + const std::vector >& metadata) { CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U); const int width = img.cols; @@ -112,6 +112,33 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, result->yuvRange = AVIF_RANGE_FULL; } + if (!metadata.empty()) { + const std::vector& metadata_exif = metadata[IMAGE_METADATA_EXIF]; + const std::vector& metadata_xmp = metadata[IMAGE_METADATA_XMP]; + const std::vector& metadata_iccp = metadata[IMAGE_METADATA_ICCP]; +#if AVIF_VERSION_MAJOR >= 1 + if ((!metadata_exif.empty() && + avifImageSetMetadataExif(result, (const uint8_t *)metadata_exif.data(), + metadata_exif.size()) != AVIF_RESULT_OK) || + (!metadata_xmp.empty() && + avifImageSetMetadataXMP(result, (const uint8_t *)metadata_xmp.data(), + metadata_xmp.size()) != AVIF_RESULT_OK) || + (!metadata_iccp.empty() && + avifImageSetProfileICC(result, (const uint8_t *)metadata_iccp.data(), + metadata_iccp.size()) != AVIF_RESULT_OK)) { + avifImageDestroy(result); + return nullptr; + } +#else + if (!metadata_exif.empty()) + avifImageSetMetadataExif(result, (const uint8_t*)metadata_exif.data(), metadata_exif.size()); + if (!metadata_xmp.empty()) + avifImageSetMetadataXMP(result, (const uint8_t*)metadata_xmp.data(), metadata_xmp.size()); + if (!metadata_iccp.empty()) + avifImageSetProfileICC(result, (const uint8_t*)metadata_iccp.data(), metadata_iccp.size()); +#endif + } + avifRGBImage rgba; avifRGBImageSetDefaults(&rgba, result); if (img.channels() == 3) { @@ -120,7 +147,7 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, CV_Assert(img.channels() == 4); rgba.format = AVIF_RGB_FORMAT_BGRA; } - rgba.rowBytes = img.step[0]; + rgba.rowBytes = (uint32_t)img.step[0]; rgba.depth = bit_depth; rgba.pixels = const_cast(reinterpret_cast(img.data)); @@ -287,6 +314,10 @@ bool AvifDecoder::nextPage() { AvifEncoder::AvifEncoder() { m_description = "AVIF files (*.avif)"; m_buf_supported = true; + m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false); + m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true; + m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true; + m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true; encoder_ = avifEncoderCreate(); } @@ -349,7 +380,7 @@ bool AvifEncoder::writeanimation(const Animation& animation, img.channels() == 1 || img.channels() == 3 || img.channels() == 4, "AVIF only supports 1, 3, 4 channels"); - images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth)); + images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth, m_metadata)); } for (size_t i = 0; i < images.size(); i++) diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index dc3d07ab78..1241edb077 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -58,11 +58,30 @@ BaseImageDecoder::BaseImageDecoder() m_frame_count = 1; } +bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const +{ + if (type == IMAGE_METADATA_EXIF) + return !m_exif.getData().empty(); + return false; +} + +Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const +{ + if (type == IMAGE_METADATA_EXIF) { + const std::vector& exif = m_exif.getData(); + if (!exif.empty()) { + Mat exifmat(1, (int)exif.size(), CV_8U, (void*)exif.data()); + return exifmat; + } + } + return Mat(); +} ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const { return m_exif.getTag(tag); } + bool BaseImageDecoder::setSource( const String& filename ) { m_filename = filename; @@ -140,6 +159,23 @@ bool BaseImageEncoder::setDestination( std::vector& buf ) return true; } +bool BaseImageEncoder::addMetadata(ImageMetadataType type, const Mat& metadata) +{ + CV_Assert_N(type >= IMAGE_METADATA_EXIF, type <= IMAGE_METADATA_MAX); + if (metadata.empty()) + return true; + size_t itype = (size_t)type; + if (itype >= m_support_metadata.size() || !m_support_metadata[itype]) + return false; + if (m_metadata.empty()) + m_metadata.resize((size_t)IMAGE_METADATA_MAX+1); + CV_Assert(metadata.elemSize() == 1); + CV_Assert(metadata.isContinuous()); + const unsigned char* data = metadata.ptr(); + m_metadata[itype].assign(data, data + metadata.total()); + return true; +} + bool BaseImageEncoder::write(const Mat &img, const std::vector ¶ms) { std::vector img_vec(1, img); return writemulti(img_vec, params); diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index ae5622528c..2eeb2fc130 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -58,12 +58,31 @@ public: */ size_t getFrameCount() const { return m_frame_count; } + /** + * @brief Set the internal m_frame_count variable to 1. + */ + void resetFrameCount() { m_frame_count = 1; } + /** * @brief Get the type of the image (e.g., color format, depth). * @return The type of the image. */ virtual int type() const { return m_type; } + /** + * @brief Checks whether file contains metadata of the certain type. + * @param type The type of metadata to look for + */ + virtual bool haveMetadata(ImageMetadataType type) const; + + /** + * @brief Retrieves metadata (if any) of the certain kind. + * If there is no such metadata, the method returns empty array. + * + * @param type The type of metadata to look for + */ + virtual Mat getMetadata(ImageMetadataType type) const; + /** * @brief Fetch a specific EXIF tag from the image's metadata. * @param tag The EXIF tag to retrieve. @@ -200,6 +219,13 @@ public: */ virtual bool setDestination(std::vector& buf); + /** + * @brief Sets the metadata to write together with the image data + * @param type The type of metadata to add + * @param metadata The packed metadata (Exif, XMP, ...) + */ + virtual bool addMetadata(ImageMetadataType type, const Mat& metadata); + /** * @brief Encode and write the image data. * @param img The Mat object containing the image data to be encoded. @@ -238,6 +264,8 @@ public: virtual void throwOnError() const; protected: + std::vector > m_metadata; // see IMAGE_METADATA_... + std::vector m_support_metadata; String m_description; ///< Description of the encoder (e.g., format name, capabilities). String m_filename; ///< Destination file name for encoded data. std::vector* m_buf; ///< Pointer to the buffer for encoded data if using memory-based destination. diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index dc13988f68..1fd1de1557 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -97,7 +97,8 @@ ExrDecoder::ExrDecoder() m_ischroma = false; m_hasalpha = false; m_native_depth = false; - + m_multispectral = false; + m_channels = 0; } @@ -119,7 +120,7 @@ void ExrDecoder::close() int ExrDecoder::type() const { - return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), ((m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1)); + return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), (m_multispectral ? m_channels : (m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1)); } @@ -148,6 +149,7 @@ bool ExrDecoder::readHeader() m_green = channels.findChannel( "G" ); m_blue = channels.findChannel( "B" ); m_alpha = channels.findChannel( "A" ); + m_multispectral = channels.findChannel( "0" ) != nullptr; if( m_alpha ) // alpha channel supported in RGB, Y, and YC scenarios m_hasalpha = true; @@ -158,6 +160,23 @@ bool ExrDecoder::readHeader() m_ischroma = false; result = true; } + else if( m_multispectral ) + { + m_channels = 0; + for( auto it = channels.begin(); it != channels.end(); it++ ) + m_channels++; + + m_iscolor = true; // ??? false + m_ischroma = false; + m_hasalpha = false; + result = m_channels <= CV_CN_MAX; + + for ( int i = 1; result && i < m_channels; i++ ) // channel 0 was found previously + { + const Channel *ch = channels.findChannel( std::to_string(i) ); + result = ch && ch->xSampling == 1 && ch->ySampling == 1; // subsampling is not supported + } + } else { m_green = channels.findChannel( "Y" ); @@ -193,8 +212,9 @@ bool ExrDecoder::readHeader() bool ExrDecoder::readData( Mat& img ) { m_native_depth = CV_MAT_DEPTH(type()) == img.depth(); + bool multispectral = img.channels() > 4; bool color = img.channels() > 2; // output mat has 3+ channels; Y or YA are the 1 and 2 channel scenario - bool alphasupported = ( img.channels() % 2 == 0 ); // even number of channels indicates alpha + bool alphasupported = !multispectral && ( img.channels() % 2 == 0 ); // even number of channels indicates alpha int channels = 0; uchar* data = img.ptr(); size_t step = img.step; @@ -210,10 +230,17 @@ bool ExrDecoder::readData( Mat& img ) const size_t floatsize = sizeof(float); size_t xstep = m_native_depth ? floatsize : 1; // 4 bytes if native depth (FLOAT), otherwise converting to 1 byte U8 depth size_t ystep = 0; - const int channelstoread = ( (m_iscolor && alphasupported) ? 4 : + const int channelstoread = ( multispectral ? img.channels() : (m_iscolor && alphasupported) ? 4 : ( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img size_t xStride = floatsize * channelstoread; + if ( m_multispectral ) // possible gray/RGB conversions + { + CV_CheckChannelsEQ(img.channels(), CV_MAT_CN(type()), "OpenCV EXR decoder needs more number of channels for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYCOLOR needed + CV_CheckDepthEQ(img.depth(), CV_MAT_DEPTH(type()), "OpenCV EXR decoder supports CV_32F depth only for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYDEPTH needed + } + CV_Assert( multispectral == m_multispectral && (!multispectral || justcopy) ); // should be true after previous checks + // See https://github.com/opencv/opencv/issues/26705 // If ALGO_HINT_ACCURATE is set, read BGR and swap to RGB. // If ALGO_HINT_APPROX is set, read RGB directly. @@ -291,6 +318,15 @@ bool ExrDecoder::readData( Mat& img ) xsample[0] = m_green->xSampling; } } + else if( m_multispectral ) + { + for ( int i = 0; i < m_channels; i++ ) + { + frame.insert( std::to_string(i), Slice( m_type, + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * i), + xStride, ystep, 1, 1, 0.0 )); + } + } else { if( m_blue ) @@ -361,39 +397,42 @@ bool ExrDecoder::readData( Mat& img ) { m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y ); - if( m_iscolor ) + if( !m_multispectral ) { - if (doReadRGB) + if( m_iscolor ) { - if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) - UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); - if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); - if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) - UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); + if (doReadRGB) + { + if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) + UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); + if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); + if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) + UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); + } + else + { + if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) + UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); + if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); + if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) + UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); + } } - else - { - if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) - UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); - if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); - if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) - UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); - } - } - else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); + else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); - if( chromatorgb ) - { - if (doReadRGB) - ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); - else - ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); + if( chromatorgb ) + { + if (doReadRGB) + ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); + else + ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); + } } } - else + else // m_multispectral should be false { uchar *out = data; int x, y; @@ -782,13 +821,19 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) header.channels().insert( "B", Channel( type ) ); //printf("bunt\n"); } - else + else if( channels == 1 || channels == 2 ) { header.channels().insert( "Y", Channel( type ) ); //printf("gray\n"); } + else if( channels > 4 ) + { + for ( int i = 0; i < channels; i++ ) + header.channels().insert( std::to_string(i), Channel( type ) ); + //printf("multi-channel\n"); + } - if( channels % 2 == 0 ) + if( channels % 2 == 0 && channels <= 4) { // even number of channels indicates Alpha header.channels().insert( "A", Channel( type ) ); } @@ -821,10 +866,15 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) frame.insert( "G", Slice( type, buffer + size, size * channels, bufferstep )); frame.insert( "R", Slice( type, buffer + size * 2, size * channels, bufferstep )); } - else + else if( channels == 1 || channels == 2 ) frame.insert( "Y", Slice( type, buffer, size * channels, bufferstep )); + else if( channels > 4 ) + { + for ( int i = 0; i < channels; i++ ) + frame.insert( std::to_string(i), Slice( type, buffer + size * i, size * channels, bufferstep )); + } - if( channels % 2 == 0 ) + if( channels % 2 == 0 && channels <= 4 ) { // even channel count indicates Alpha channel frame.insert( "A", Slice( type, buffer + size * (channels - 1), size * channels, bufferstep )); } diff --git a/modules/imgcodecs/src/grfmt_exr.hpp b/modules/imgcodecs/src/grfmt_exr.hpp index 48ca09acd8..ec37649d17 100644 --- a/modules/imgcodecs/src/grfmt_exr.hpp +++ b/modules/imgcodecs/src/grfmt_exr.hpp @@ -100,6 +100,8 @@ protected: bool m_iscolor; bool m_isfloat; bool m_hasalpha; + bool m_multispectral; + int m_channels; private: ExrDecoder(const ExrDecoder &); // copy disabled diff --git a/modules/imgcodecs/src/grfmt_gdal.cpp b/modules/imgcodecs/src/grfmt_gdal.cpp index ff059338cf..2c09d3b62f 100644 --- a/modules/imgcodecs/src/grfmt_gdal.cpp +++ b/modules/imgcodecs/src/grfmt_gdal.cpp @@ -408,6 +408,9 @@ bool GdalDecoder::readData( Mat& img ){ case GCI_AlphaBand: color = 3; break; + case GCI_Undefined: + color = c; + break; default: CV_Error(cv::Error::StsError, "Invalid/unsupported mode"); } diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index a3a7f70c3c..9b2ab59b2b 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -600,6 +600,8 @@ JpegEncoder::JpegEncoder() { m_description = "JPEG files (*.jpeg;*.jpg;*.jpe)"; m_buf_supported = true; + m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false); + m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true; } @@ -815,6 +817,22 @@ bool JpegEncoder::write( const Mat& img, const std::vector& params ) jpeg_start_compress( &cinfo, TRUE ); + if (!m_metadata.empty()) { + const std::vector& metadata_exif = m_metadata[IMAGE_METADATA_EXIF]; + size_t exif_size = metadata_exif.size(); + if (exif_size > 0u) { + const char app1_exif_prefix[] = {'E', 'x', 'i', 'f', '\0', '\0'}; + size_t app1_exif_prefix_size = sizeof(app1_exif_prefix); + size_t data_size = exif_size + app1_exif_prefix_size; + + std::vector metadata_app1(data_size); + uchar* data = metadata_app1.data(); + memcpy(data, app1_exif_prefix, app1_exif_prefix_size); + memcpy(data + app1_exif_prefix_size, metadata_exif.data(), exif_size); + jpeg_write_marker(&cinfo, JPEG_APP0 + 1, data, (unsigned)data_size); + } + } + if( doDirectWrite ) { for( int y = 0; y < height; y++ ) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index c4b5a2c3a6..c78c0efc55 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -156,7 +156,7 @@ bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen) if (!src.empty()) { - png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels(); + png_uint_32 rowbytes = src.cols * (uint32_t)src.elemSize(); _width = src.cols; _height = src.rows; _colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; @@ -416,14 +416,17 @@ bool PngDecoder::readData( Mat& img ) if (m_frame_no == 0) { + if (m_mat_raw.empty()) + { + if (m_f) + fseek(m_f, -8, SEEK_CUR); + else + m_buf_pos -= 8; + } m_mat_raw = Mat(img.rows, img.cols, m_type); m_mat_next = Mat(img.rows, img.cols, m_type); frameRaw.setMat(m_mat_raw); frameNext.setMat(m_mat_next); - if (m_f) - fseek(m_f, -8, SEEK_CUR); - else - m_buf_pos -= 8; } else m_mat_next.copyTo(mat_cur); @@ -433,9 +436,6 @@ bool PngDecoder::readData( Mat& img ) if (!processing_start((void*)&frameRaw, mat_cur)) return false; - if(setjmp(png_jmpbuf(m_png_ptr))) - return false; - while (true) { id = read_chunk(chunk); @@ -446,53 +446,56 @@ bool PngDecoder::readData( Mat& img ) { if (!m_is_fcTL_loaded) { - m_is_fcTL_loaded = true; - w0 = m_width; - h0 = m_height; - } - - if (processing_finish()) - { - if (dop == 2) - memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); - - compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); - if (!delay_den) - delay_den = 100; - m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den)); - - if (mat_cur.channels() == img.channels()) - { - if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) - mat_cur.convertTo(img, CV_8U, 1. / 255); - else - mat_cur.copyTo(img); - } - else - { - Mat mat_cur_scaled; - if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) - mat_cur.convertTo(mat_cur_scaled, CV_8U, 1. / 255); - else - mat_cur_scaled = mat_cur; - - if (img.channels() == 1) - cvtColor(mat_cur_scaled, img, COLOR_BGRA2GRAY); - else if (img.channels() == 3) - cvtColor(mat_cur_scaled, img, COLOR_BGRA2BGR); - } - - if (dop != 2) - { - memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); - if (dop == 1) - for (j = 0; j < h0; j++) - memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels()); - } + m_mat_raw.copyTo(m_animation.still_image); } else { - return false; + if (processing_finish()) + { + if (dop == 2) + memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + + if (x0 + w0 > frameCur.getWidth() || y0 + h0 > frameCur.getHeight()) + return false; + + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); + if (!delay_den) + delay_den = 100; + m_animation.durations.push_back(cvRound(1000. * delay_num / delay_den)); + + if (mat_cur.channels() == img.channels()) + { + if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) + mat_cur.convertTo(img, CV_8U, 1. / 255); + else + mat_cur.copyTo(img); + } + else + { + Mat mat_cur_scaled; + if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) + mat_cur.convertTo(mat_cur_scaled, CV_8U, 1. / 255); + else + mat_cur_scaled = mat_cur; + + if (img.channels() == 1) + cvtColor(mat_cur_scaled, img, COLOR_BGRA2GRAY); + else if (img.channels() == 3) + cvtColor(mat_cur_scaled, img, COLOR_BGRA2BGR); + } + + if (dop != 2) + { + memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + if (dop == 1) + for (j = 0; j < h0; j++) + memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels()); + } + } + else + { + return false; + } } w0 = png_get_uint_32(&chunk.p[12]); @@ -508,14 +511,18 @@ bool PngDecoder::readData( Mat& img ) { return false; } - // Asking for blend over with no alpha is invalid. - if (bop == 1 && mat_cur.channels() != 4) - { - return false; - } memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8); - return true; + + if (m_is_fcTL_loaded) + return true; + else + { + m_is_fcTL_loaded = true; + ClearPngPtr(); + if (!processing_start((void*)&frameRaw, mat_cur)) + return false; + } } else if (id == id_IDAT) { @@ -650,8 +657,8 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect const size_t elem_size = img.elemSize(); if (_bop == 0) { // Overwrite mode: copy source row directly to destination - for(uint32_t j = 0; j < h; ++j) { - std::memcpy(rows_dst[j + y] + x * elem_size,rows_src[j], w * elem_size); + for (uint32_t j = 0; j < h; ++j) { + std::memcpy(rows_dst[j + y] + x * elem_size, rows_src[j], w * elem_size); } return; } @@ -665,23 +672,24 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect // Blending mode for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { - if (channels < 4 || sp[3] == 65535) { // Fully opaque in 16-bit (max value) + uint16_t alpha = sp[3]; + + if (channels < 4 || alpha == 65535 || dp[3] == 0) { + // Fully opaque OR destination fully transparent: direct copy memcpy(dp, sp, elem_size); + continue; } - else if (sp[3] != 0) { // Partially transparent - if (dp[3] != 0) { // Both source and destination have alpha - uint32_t u = sp[3] * 65535; // 16-bit max - uint32_t v = (65535 - sp[3]) * dp[3]; - uint32_t al = u + v; - dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red - dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green - dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue - dp[3] = static_cast(al / 65535); // Alpha - } - else { - // If destination alpha is 0, copy source pixel - memcpy(dp, sp, elem_size); - } + + if (alpha != 0) { + // Alpha blending + uint64_t u = static_cast(alpha) * 65535; + uint64_t v = static_cast(65535 - alpha) * dp[3]; + uint64_t al = u + v; + + dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red + dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green + dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue + dp[3] = static_cast(al / 65535); // Alpha } } } @@ -694,25 +702,24 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect // Blending mode for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { - if (channels < 4 || sp[3] == 255) { - // Fully opaque: copy source pixel directly + uint8_t alpha = sp[3]; + + if (channels < 4 || alpha == 255 || dp[3] == 0) { + // Fully opaque OR destination fully transparent: direct copy memcpy(dp, sp, elem_size); + continue; } - else if (sp[3] != 0) { + + if (alpha != 0) { // Alpha blending - if (dp[3] != 0) { - int u = sp[3] * 255; - int v = (255 - sp[3]) * dp[3]; - int al = u + v; - dp[0] = (sp[0] * u + dp[0] * v) / al; // Red - dp[1] = (sp[1] * u + dp[1] * v) / al; // Green - dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue - dp[3] = al / 255; // Alpha - } - else { - // If destination alpha is 0, copy source pixel - memcpy(dp, sp, elem_size); - } + uint32_t u = alpha * 255; + uint32_t v = (255 - alpha) * dp[3]; + uint32_t al = u + v; + + dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red + dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green + dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue + dp[3] = static_cast(al / 255); // Alpha } } } @@ -845,6 +852,8 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_ { CV_UNUSED(pass); APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); + if(row_num >= frame->getHeight()) + return; png_progressive_combine_row(png_ptr, frame->getRows()[row_num], new_row); } @@ -852,8 +861,10 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_ PngEncoder::PngEncoder() { - m_description = "Portable Network Graphics files (*.png)"; + m_description = "Portable Network Graphics files (*.png;*.apng)"; m_buf_supported = true; + m_support_metadata.assign((size_t)IMAGE_METADATA_MAX+1, false); + m_support_metadata[IMAGE_METADATA_EXIF] = true; op_zstream1.zalloc = NULL; op_zstream2.zalloc = NULL; next_seq_num = 0; @@ -985,6 +996,16 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) for( y = 0; y < height; y++ ) buffer[y] = img.data + y*img.step; + if (!m_metadata.empty()) { + std::vector& exif = m_metadata[IMAGE_METADATA_EXIF]; + if (!exif.empty()) { + writeChunk(f, "eXIf", exif.data(), (uint32_t)exif.size()); + } + // [TODO] add xmp and icc. They need special handling, + // see https://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_PNG_files and + // https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html. + } + png_write_image( png_ptr, buffer.data() ); png_write_end( png_ptr, info_ptr ); @@ -1483,7 +1504,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 1) writeChunk(m_f, "acTL", buf_acTL, 8); - else - first = 0; if (palsize > 0) writeChunk(m_f, "PLTE", (unsigned char*)(&palette), palsize * 3); @@ -1634,19 +1653,32 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 1) + { + CV_Assert(animation.still_image.type() == animation.frames[0].type() && animation.still_image.size() == animation.frames[0].size()); + APNGFrame apngFrame; + Mat tmp; + if (animation.still_image.depth() == CV_16U) + { + animation.still_image.convertTo(tmp, CV_8U, 1.0 / 255); + } + else + tmp = animation.still_image; + + if (tmp.channels() > 2) + cvtColor(tmp, tmp, COLOR_BGRA2RGBA); + apngFrame.setMat(tmp); + + deflateRectOp(apngFrame.getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); + deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); + writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size); + } + deflateRectOp(frames[0].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); - if (first) - { - writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size); - for (j = 0; j < 6; j++) - op[j].valid = 0; - deflateRectOp(frames[1].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); - deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); - } - - for (i = first; i < num_frames - 1; i++) + for (i = 0; i < num_frames - 1; i++) { uint32_t op_min; int op_best; @@ -1673,7 +1705,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector first) + if (i > 0) getRect(width, height, rest.data(), frames[i + 1].getPixels(), over3.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 2); op_min = op[0].size; @@ -1699,9 +1731,9 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 1) + if (num_frames > 1 /* don't write fcTL chunk if animation has only one frame */) { png_save_uint_32(buf_fcTL, next_seq_num++); png_save_uint_32(buf_fcTL + 4, w0); diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp index 9804c7a8ae..acf2f0d55d 100644 --- a/modules/imgcodecs/src/grfmt_spng.cpp +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -31,18 +31,18 @@ * with these values. (png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 );) For this codec implementation, * slightly modified versions are implemented in the below of this page. */ -void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step, - uchar *gray, int gray_step, - cv::Size size, int _swap_rb); - -void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step, +void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step, uchar *gray, int gray_step, - cv::Size size, int _swap_rb); + cv::Size size, int ncn, int _swap_rb); void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step, ushort *gray, int gray_step, cv::Size size, int ncn, int _swap_rb); +void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb); + namespace cv { @@ -109,7 +109,7 @@ int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t siz bool SPngDecoder::readHeader() { - volatile bool result = false; + bool result = false; close(); spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32); @@ -136,40 +136,36 @@ bool SPngDecoder::readHeader() if (!m_buf.empty() || m_f) { struct spng_ihdr ihdr; - int ret = spng_get_ihdr(ctx, &ihdr); - if (ret == SPNG_OK) + if (spng_get_ihdr(ctx, &ihdr) == SPNG_OK) { m_width = static_cast(ihdr.width); m_height = static_cast(ihdr.height); m_color_type = ihdr.color_type; m_bit_depth = ihdr.bit_depth; - if (ihdr.bit_depth <= 8 || ihdr.bit_depth == 16) + int num_trans; + switch (ihdr.color_type) { - int num_trans; - switch (ihdr.color_type) - { - case SPNG_COLOR_TYPE_TRUECOLOR: - case SPNG_COLOR_TYPE_INDEXED: - struct spng_trns trns; - num_trans = !spng_get_trns(ctx, &trns); - if (num_trans > 0) - m_type = CV_8UC4; - else - m_type = CV_8UC3; - break; - case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: - case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_INDEXED: + struct spng_trns trns; + num_trans = !spng_get_trns(ctx, &trns); + if (num_trans > 0) m_type = CV_8UC4; - break; - default: - m_type = CV_8UC1; - } - if (ihdr.bit_depth == 16) - m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); - result = true; + else + m_type = CV_8UC3; + break; + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + m_type = CV_8UC4; + break; + default: + m_type = CV_8UC1; } + if (ihdr.bit_depth == 16) + m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); + result = true; } } @@ -178,98 +174,86 @@ bool SPngDecoder::readHeader() bool SPngDecoder::readData(Mat &img) { - volatile bool result = false; - bool color = img.channels() > 1; - - struct spng_ctx *png_ptr = (struct spng_ctx *)m_ctx; + bool result = false; if (m_ctx && m_width && m_height) { - int fmt = SPNG_FMT_PNG; + struct spng_ctx* png_ptr = (struct spng_ctx*)m_ctx; + bool color = img.channels() > 1; + int fmt = img.channels() == 4 ? m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8 : SPNG_FMT_PNG; + int decode_flags = img.channels() == 4 ? SPNG_DECODE_TRNS : 0; - struct spng_trns trns; - int have_trns = spng_get_trns((struct spng_ctx *)m_ctx, &trns); - - int decode_flags = 0; - if (have_trns == SPNG_OK) - { - decode_flags = SPNG_DECODE_TRNS; - } - if (img.channels() == 4) - { - if (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || - m_color_type == SPNG_COLOR_TYPE_INDEXED || - m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE) - fmt = m_bit_depth == 16 ? SPNG_FMT_GA16 : SPNG_FMT_GA8; - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) - { - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; - } - else - fmt = SPNG_FMT_RGBA8; - } - if (img.channels() == 3) + if (img.type() == CV_8UC3) { fmt = SPNG_FMT_RGB8; - if ((m_color_type == SPNG_COLOR_TYPE_GRAYSCALE || m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) && - m_bit_depth == 16) - fmt = SPNG_FMT_RGB8; - else if (m_bit_depth == 16) - fmt = SPNG_FMT_PNG; } else if (img.channels() == 1) { if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth <= 8) fmt = SPNG_FMT_G8; - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth == 16) - { - if (img.depth() == CV_8U || img.depth() == CV_8S) - { - fmt = SPNG_FMT_RGB8; - } - else - { - fmt = SPNG_FMT_PNG; - } - } - else if (m_color_type == SPNG_COLOR_TYPE_INDEXED || - m_color_type == SPNG_COLOR_TYPE_TRUECOLOR) - { - if (img.depth() == CV_8U || img.depth() == CV_8S) - { - fmt = SPNG_FMT_RGB8; - } - else - { - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8; - } - } - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || fmt == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) - { - if (img.depth() == CV_8U || img.depth() == CV_8S) - { - fmt = SPNG_FMT_RGB8; - } - else - { - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; - } - } else - fmt = SPNG_FMT_RGB8; + fmt = img.depth() == CV_16U ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8; } + if (fmt == SPNG_FMT_PNG && m_bit_depth == 16 && m_color_type >= SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) + { + Mat tmp(m_height, m_width, CV_16UC4); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0)) + return false; + cvtColor(tmp, img, m_use_rgb ? COLOR_RGBA2RGB : COLOR_RGBA2BGR); + return true; + } + + struct spng_ihdr ihdr; + spng_get_ihdr(png_ptr, &ihdr); + size_t image_width, image_size = 0; int ret = spng_decoded_image_size(png_ptr, fmt, &image_size); - struct spng_ihdr ihdr; - spng_get_ihdr(png_ptr, &ihdr); if (ret == SPNG_OK) { image_width = image_size / m_height; + if (!color && fmt == SPNG_FMT_RGB8 && m_bit_depth == 16 && (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA)) + { + Mat tmp(m_height, m_width, CV_16UC4); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0)) + return false; + spngCvt_BGRA2Gray_16u28u_CnC1R(reinterpret_cast(tmp.data), (int)tmp.step1(), + img.data, (int)img.step1(), Size(m_width, m_height), 4, 2); + return true; + } + + if (!color && ihdr.interlace_method && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA16)) + { + if (fmt == SPNG_FMT_RGBA16) + { + Mat tmp(m_height, m_width, CV_16UC4); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), fmt, 0)) + return false; + spngCvt_BGRA2Gray_16u_CnC1R(reinterpret_cast(tmp.data), (int)tmp.step1(), + reinterpret_cast(img.data), (int)img.step1(), Size(m_width, m_height), 4, 2); + return true; + } + else + { + Mat tmp(m_height, m_width, CV_8UC3); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, fmt, 0)) + return false; + spngCvt_BGRA2Gray_8u_CnC1R(tmp.data, (int)tmp.step1(), img.data, (int)img.step1(), Size(m_width, m_height), 3, 2); + return true; + } + } + + if (fmt == SPNG_FMT_PNG && img.elemSize() * m_width / 3 == image_width) + { + Mat tmp(m_height, m_width, CV_16U); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, SPNG_FMT_PNG, 0)) + return false; + cvtColor(tmp, img, COLOR_GRAY2BGR); + return true; + } + ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags); if (ret == SPNG_OK) { @@ -279,88 +263,46 @@ bool SPngDecoder::readData(Mat &img) // decode image then convert to grayscale if (!color && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA8 || fmt == SPNG_FMT_RGBA16)) { - if (ihdr.interlace_method == 0) + AutoBuffer buffer; + buffer.allocate(image_width); + if (fmt == SPNG_FMT_RGB8) { - AutoBuffer buffer; - buffer.allocate(image_width); - if (fmt == SPNG_FMT_RGB8) + do { - do - { - ret = spng_get_row_info(png_ptr, &row_info); - if (ret) - break; + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; - ret = spng_decode_row(png_ptr, buffer.data(), image_width); - spngCvt_BGR2Gray_8u_C3C1R( - buffer.data(), - 0, - img.data + row_info.row_num * img.step, - 0, Size(m_width, 1), 2); - } while (ret == SPNG_OK); - } - else if (fmt == SPNG_FMT_RGBA8) - { - do - { - ret = spng_get_row_info(png_ptr, &row_info); - if (ret) - break; - - ret = spng_decode_row(png_ptr, buffer.data(), image_width); - spngCvt_BGRA2Gray_8u_C4C1R( - buffer.data(), - 0, - img.data + row_info.row_num * img.step, - 0, Size(m_width, 1), 2); - } while (ret == SPNG_OK); - } - else if (fmt == SPNG_FMT_RGBA16) - { - do - { - ret = spng_get_row_info(png_ptr, &row_info); - if (ret) - break; - - ret = spng_decode_row(png_ptr, buffer.data(), image_width); - spngCvt_BGRA2Gray_16u_CnC1R( - reinterpret_cast(buffer.data()), 0, - reinterpret_cast(img.data + row_info.row_num * img.step), - 0, Size(m_width, 1), - 4, 2); - } while (ret == SPNG_OK); - } + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 3, 2); + } while (ret == SPNG_OK); } - else + else if (fmt == SPNG_FMT_RGBA8) { - AutoBuffer imageBuffer(image_size); - ret = spng_decode_image(png_ptr, imageBuffer.data(), image_size, fmt, 0); - int step = m_width * img.channels(); - if (fmt == SPNG_FMT_RGB8) + do { - spngCvt_BGR2Gray_8u_C3C1R( - imageBuffer.data(), - step, - img.data, - step, Size(m_width, m_height), 2); - } - else if (fmt == SPNG_FMT_RGBA8) - { - spngCvt_BGRA2Gray_8u_C4C1R( - imageBuffer.data(), - step, - img.data, - step, Size(m_width, m_height), 2); - } - else if (fmt == SPNG_FMT_RGBA16) + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 4, 2); + } while (ret == SPNG_OK); + } + else if (fmt == SPNG_FMT_RGBA16) + { + do { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); spngCvt_BGRA2Gray_16u_CnC1R( - reinterpret_cast(imageBuffer.data()), step / 3, - reinterpret_cast(img.data), - step / 3, Size(m_width, m_height), - 4, 2); - } + reinterpret_cast(buffer.data()), 0, + reinterpret_cast(img.data + row_info.row_num * img.step), + 0, Size(m_width, 1), 4, 2); + } while (ret == SPNG_OK); } } else if (color) @@ -383,9 +325,8 @@ bool SPngDecoder::readData(Mat &img) ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); if (ihdr.interlace_method == 0 && !m_use_rgb) { - icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(buffer[row_info.row_num]), 0, - reinterpret_cast(buffer[row_info.row_num]), 0, - Size(m_width, 1)); + icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(buffer[row_info.row_num]), 0, + reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); } } while (ret == SPNG_OK); if (ihdr.interlace_method && !m_use_rgb) @@ -414,6 +355,8 @@ bool SPngDecoder::readData(Mat &img) } else if (fmt == SPNG_FMT_PNG) { + AutoBuffer bufcn4; + bufcn4.allocate(image_width); do { ret = spng_get_row_info(png_ptr, &row_info); @@ -421,16 +364,17 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); + if (ihdr.interlace_method == 0 && !m_use_rgb) { - icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(buffer[row_info.row_num]), 0, - reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(buffer[row_info.row_num]), 0, + reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); } } while (ret == SPNG_OK); if (ihdr.interlace_method && !m_use_rgb) { - icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, - reinterpret_cast(img.data), step, Size(m_width, m_height)); + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, + reinterpret_cast(img.data), step, Size(m_width, m_height)); } } else @@ -454,7 +398,6 @@ bool SPngDecoder::readData(Mat &img) } } else - { do { ret = spng_get_row_info(png_ptr, &row_info); @@ -462,8 +405,8 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, img.data + row_info.row_num * image_width, image_width); + } while (ret == SPNG_OK); - } } if (ret == SPNG_EOI) @@ -687,45 +630,32 @@ bool SPngEncoder::write(const Mat &img, const std::vector ¶ms) } -void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step, - uchar *gray, int gray_step, - cv::Size size, int _swap_rb) +void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb) { int i; for (; size.height--; gray += gray_step) { - double cBGR0 = 0.1140441895; - double cBGR2 = 0.2989807129; - if (_swap_rb) - std::swap(cBGR0, cBGR2); - for (i = 0; i < size.width; i++, bgr += 3) - { - int t = static_cast(cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2]); - gray[i] = (uchar)t; - } - - bgr += bgr_step - size.width * 3; - } -} - -void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step, - uchar *gray, int gray_step, - cv::Size size, int _swap_rb) -{ - for (; size.height--; gray += gray_step) - { - double cBGR0 = 0.1140441895; - double cBGR1 = 0.5869750977; - double cBGR2 = 0.2989807129; + int cBGR0 = 3737; + int cBGR1 = 19234; + int cBGR2 = 9797; if (_swap_rb) std::swap(cBGR0, cBGR2); - for (int i = 0; i < size.width; i++, bgra += 4) + for (i = 0; i < size.width; i++, bgr += ncn) { - gray[i] = cv::saturate_cast(cBGR0 * bgra[0] + cBGR1 * bgra[1] + cBGR2 * bgra[2]); + if (bgr[0] != bgr[1] || bgr[0] != bgr[2]) + { + gray[i] = (uchar)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]) >> 15); + } + else + { + gray[i] = bgr[0]; + } } - bgra += rgba_step - size.width * 4; + bgr += bgr_step - size.width * ncn; } } @@ -735,15 +665,43 @@ void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step, { for (; size.height--; gray += gray_step) { - double cBGR0 = 0.1140441895; - double cBGR1 = 0.5869750977; - double cBGR2 = 0.2989807129; + int cBGR0 = 3737; + int cBGR1 = 19234; + int cBGR2 = 9797; if (_swap_rb) std::swap(cBGR0, cBGR2); for (int i = 0; i < size.width; i++, bgr += ncn) { - gray[i] = (ushort)(cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]); + if (bgr[0] != bgr[1] || bgr[0] != bgr[2]) + { + gray[i] = (ushort)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15); + } + else + { + gray[i] = bgr[0]; + } + } + + bgr += bgr_step - size.width * ncn; + } +} + +void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb) +{ + int cBGR0 = 3737; + int cBGR1 = 19234; + int cBGR2 = 9797; + if (_swap_rb) + std::swap(cBGR0, cBGR2); + + for (; size.height--; gray += gray_step) + { + for (int i = 0; i < size.width; i++, bgr += ncn) + { + gray[i] = static_cast(((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15) >> 8); } bgr += bgr_step - size.width * ncn; diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 2d55995789..3e63dd7acb 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -155,14 +155,16 @@ bool WebPDecoder::readHeader() webp_data.size = data.total(); WebPAnimDecoderOptions dec_options; - WebPAnimDecoderOptionsInit(&dec_options); + if (!WebPAnimDecoderOptionsInit(&dec_options)) + CV_Error(Error::StsInternal, "Failed to initialize animated WebP decoding options"); dec_options.color_mode = m_use_rgb ? MODE_RGBA : MODE_BGRA; anim_decoder.reset(WebPAnimDecoderNew(&webp_data, &dec_options)); CV_Assert(anim_decoder.get() && "Error parsing image"); WebPAnimInfo anim_info; - WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info); + if (!WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info)) + CV_Error(Error::StsInternal, "Failed to get animated WebP information"); m_animation.loop_count = anim_info.loop_count; m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF; @@ -216,7 +218,8 @@ bool WebPDecoder::readData(Mat &img) uint8_t* buf; int timestamp; - WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp); + if (!WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp)) + CV_Error(Error::StsInternal, "Failed to decode animated WebP frame"); Mat tmp(Size(m_width, m_height), CV_8UC4, buf); if (img.type() == CV_8UC1) @@ -446,7 +449,6 @@ bool WebPEncoder::writeanimation(const Animation& animation, const std::vector 1*/ ) + // type = CV_MAKETYPE(CV_MAT_DEPTH(type), CV_MAT_CN(type)); + //else if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 ) if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 || ((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) ) type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3); @@ -410,6 +413,76 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img) } } +static void readMetadata(ImageDecoder& decoder, + std::vector* metadata_types, + OutputArrayOfArrays metadata) +{ + if (!metadata_types) + return; + int kind = metadata.kind(); + void* obj = metadata.getObj(); + std::vector* matvector = nullptr; + std::vector >* vecvector = nullptr; + if (kind == _InputArray::STD_VECTOR_MAT) { + matvector = (std::vector*)obj; + } else if (kind == _InputArray::STD_VECTOR_VECTOR) { + int elemtype = metadata.type(0); + CV_Assert(elemtype == CV_8UC1 || elemtype == CV_8SC1); + vecvector = (std::vector >*)obj; + } else { + CV_Error(Error::StsBadArg, + "unsupported metadata type, should be a vector of matrices or vector of byte vectors"); + } + std::vector src_metadata; + for (int m = (int)IMAGE_METADATA_EXIF; m <= (int)IMAGE_METADATA_MAX; m++) { + Mat mm = decoder->getMetadata((ImageMetadataType)m); + if (!mm.empty()) { + CV_Assert(mm.isContinuous()); + CV_Assert(mm.elemSize() == 1u); + metadata_types->push_back(m); + src_metadata.push_back(mm); + } + } + size_t nmetadata = metadata_types->size(); + if (matvector) { + matvector->resize(nmetadata); + for (size_t m = 0; m < nmetadata; m++) + src_metadata[m].copyTo(matvector->at(m)); + } else { + vecvector->resize(nmetadata); + for (size_t m = 0; m < nmetadata; m++) { + const Mat& mm = src_metadata[m]; + const uchar* data = (uchar*)mm.data; + vecvector->at(m).assign(data, data + mm.total()); + } + } +} + +static const char* metadataTypeToString(ImageMetadataType type) +{ + return type == IMAGE_METADATA_EXIF ? "Exif" : + type == IMAGE_METADATA_XMP ? "XMP" : + type == IMAGE_METADATA_ICCP ? "ICC Profile" : "???"; +} + +static void addMetadata(ImageEncoder& encoder, + const std::vector& metadata_types, + InputArrayOfArrays metadata) +{ + size_t nmetadata_chunks = metadata_types.size(); + for (size_t i = 0; i < nmetadata_chunks; i++) { + ImageMetadataType metadata_type = (ImageMetadataType)metadata_types[i]; + bool ok = encoder->addMetadata(metadata_type, metadata.getMat((int)i)); + if (!ok) { + std::string desc = encoder->getDescription(); + CV_LOG_WARNING(NULL, "Imgcodecs: metadata of type '" + << metadataTypeToString(metadata_type) + << "' is not supported when encoding '" + << desc << "'"); + } + } +} + /** * Read an image into memory and return the information * @@ -419,11 +492,15 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img) * */ static bool -imread_( const String& filename, int flags, OutputArray mat ) +imread_( const String& filename, int flags, OutputArray mat, + std::vector* metadata_types, OutputArrayOfArrays metadata) { /// Search for the relevant decoder to handle the imagery ImageDecoder decoder; + if (metadata_types) + metadata_types->clear(); + #ifdef HAVE_GDAL if(flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL ){ decoder = GdalDecoder().newDecoder(); @@ -501,13 +578,16 @@ imread_( const String& filename, int flags, OutputArray mat ) Mat real_mat = mat.getMat(); const void * original_ptr = real_mat.data; bool success = false; + decoder->resetFrameCount(); // this is needed for PngDecoder. it should be called before decoder->readData() try { if (decoder->readData(real_mat)) { - CV_CheckTrue((decoder->getFrameCount() > 1) || original_ptr == real_mat.data, "Internal imread issue"); + CV_CheckTrue(original_ptr == real_mat.data, "Internal imread issue"); success = true; } + + readMetadata(decoder, metadata_types, metadata); } catch (const cv::Exception& e) { @@ -661,7 +741,24 @@ Mat imread( const String& filename, int flags ) Mat img; /// load the data - imread_( filename, flags, img ); + imread_( filename, flags, img, nullptr, noArray() ); + + /// return a reference to the data + return img; +} + +Mat imreadWithMetadata( const String& filename, + std::vector& metadata_types, + OutputArrayOfArrays metadata, + int flags ) +{ + CV_TRACE_FUNCTION(); + + /// create the basic container + Mat img; + + /// load the data + imread_( filename, flags, img, &metadata_types, metadata ); /// return a reference to the data return img; @@ -672,7 +769,7 @@ void imread( const String& filename, OutputArray dst, int flags ) CV_TRACE_FUNCTION(); /// load the data - imread_(filename, flags, dst); + imread_(filename, flags, dst, nullptr, noArray()); } /** @@ -800,6 +897,7 @@ imreadanimation_(const String& filename, int flags, int start, int count, Animat } animation.bgcolor = decoder->animation().bgcolor; animation.loop_count = decoder->animation().loop_count; + animation.still_image = decoder->animation().still_image; return success; } @@ -910,6 +1008,7 @@ static bool imdecodeanimation_(InputArray buf, int flags, int start, int count, } animation.bgcolor = decoder->animation().bgcolor; animation.loop_count = decoder->animation().loop_count; + animation.still_image = decoder->animation().still_image; return success; } @@ -943,6 +1042,8 @@ size_t imcount(const String& filename, int flags) static bool imwrite_( const String& filename, const std::vector& img_vec, + const std::vector& metadata_types, + InputArrayOfArrays metadata, const std::vector& params, bool flipv ) { bool isMultiImg = img_vec.size() > 1; @@ -957,7 +1058,12 @@ static bool imwrite_( const String& filename, const std::vector& img_vec, Mat image = img_vec[page]; CV_Assert(!image.empty()); +#ifdef HAVE_OPENEXR + CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 || encoder.dynamicCast() ); +#else CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 ); +#endif + Mat temp; if( !encoder->isFormatSupported(image.depth()) ) @@ -978,6 +1084,7 @@ static bool imwrite_( const String& filename, const std::vector& img_vec, } encoder->setDestination( filename ); + addMetadata(encoder, metadata_types, metadata); CV_Check(params.size(), (params.size() & 1) == 0, "Encoding 'params' must be key-value pairs"); CV_CheckLE(params.size(), (size_t)(CV_IO_MAX_IMAGE_PARAMS*2), ""); @@ -1034,7 +1141,26 @@ bool imwrite( const String& filename, InputArray _img, img_vec.push_back(_img.getMat()); CV_Assert(!img_vec.empty()); - return imwrite_(filename, img_vec, params, false); + return imwrite_(filename, img_vec, {}, noArray(), params, false); +} + +bool imwriteWithMetadata( const String& filename, InputArray _img, + const std::vector& metadata_types, + InputArrayOfArrays metadata, + const std::vector& params ) +{ + CV_TRACE_FUNCTION(); + + CV_Assert(!_img.empty()); + + std::vector img_vec; + if (_img.isMatVector() || _img.isUMatVector()) + _img.getMatVector(img_vec); + else + img_vec.push_back(_img.getMat()); + + CV_Assert(!img_vec.empty()); + return imwrite_(filename, img_vec, metadata_types, metadata, params, false); } static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector& params) @@ -1119,8 +1245,13 @@ bool imencodeanimation(const String& ext, const Animation& animation, std::vecto } static bool -imdecode_( const Mat& buf, int flags, Mat& mat ) +imdecode_( const Mat& buf, int flags, Mat& mat, + std::vector* metadata_types, + OutputArrayOfArrays metadata ) { + if (metadata_types) + metadata_types->clear(); + CV_Assert(!buf.empty()); CV_Assert(buf.isContinuous()); CV_Assert(buf.checkVector(1, CV_8U) > 0); @@ -1210,6 +1341,7 @@ imdecode_( const Mat& buf, int flags, Mat& mat ) { if (decoder->readData(mat)) success = true; + readMetadata(decoder, metadata_types, metadata); } catch (const cv::Exception& e) { @@ -1253,7 +1385,7 @@ Mat imdecode( InputArray _buf, int flags ) CV_TRACE_FUNCTION(); Mat buf = _buf.getMat(), img; - if (!imdecode_(buf, flags, img)) + if (!imdecode_(buf, flags, img, nullptr, noArray())) img.release(); return img; @@ -1265,12 +1397,24 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst ) Mat buf = _buf.getMat(), img; dst = dst ? dst : &img; - if (imdecode_(buf, flags, *dst)) + if (imdecode_(buf, flags, *dst, nullptr, noArray())) return *dst; else return cv::Mat(); } +Mat imdecodeWithMetadata( InputArray _buf, std::vector& metadata_types, + OutputArrayOfArrays metadata, int flags ) +{ + CV_TRACE_FUNCTION(); + + Mat buf = _buf.getMat(), img; + if (!imdecode_(buf, flags, img, &metadata_types, metadata)) + img.release(); + + return img; +} + static bool imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int count) { @@ -1426,8 +1570,10 @@ bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector& mats, co } } -bool imencode( const String& ext, InputArray _img, - std::vector& buf, const std::vector& params ) +bool imencodeWithMetadata( const String& ext, InputArray _img, + const std::vector& metadata_types, + InputArrayOfArrays metadata, + std::vector& buf, const std::vector& params ) { CV_TRACE_FUNCTION(); @@ -1452,7 +1598,11 @@ bool imencode( const String& ext, InputArray _img, CV_Assert(!image.empty()); const int channels = image.channels(); +#ifdef HAVE_OPENEXR + CV_Assert( channels == 1 || channels == 3 || channels == 4 || encoder.dynamicCast() ); +#else CV_Assert( channels == 1 || channels == 3 || channels == 4 ); +#endif Mat temp; if( !encoder->isFormatSupported(image.depth()) ) @@ -1477,6 +1627,7 @@ bool imencode( const String& ext, InputArray _img, code = encoder->setDestination(filename); CV_Assert( code ); } + addMetadata(encoder, metadata_types, metadata); try { if (!isMultiImg) @@ -1513,6 +1664,12 @@ bool imencode( const String& ext, InputArray _img, return code; } +bool imencode( const String& ext, InputArray img, + std::vector& buf, const std::vector& params_ ) +{ + return imencodeWithMetadata(ext, img, {}, noArray(), buf, params_); +} + bool imencodemulti( const String& ext, InputArrayOfArrays imgs, std::vector& buf, const std::vector& params) { diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp index ece0d19d29..2d45132ffd 100644 --- a/modules/imgcodecs/test/test_animation.cpp +++ b/modules/imgcodecs/test/test_animation.cpp @@ -636,6 +636,52 @@ TEST(Imgcodecs_APNG, imencode_animation) } } +TEST(Imgcodecs_APNG, animation_has_hidden_frame) +{ + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/033.png"; + Animation animation1, animation2, animation3; + + imreadanimation(filename, animation1); + + EXPECT_FALSE(animation1.still_image.empty()); + EXPECT_EQ((size_t)2, animation1.frames.size()); + + std::vector buf; + EXPECT_TRUE(imencodeanimation(".png", animation1, buf)); + EXPECT_TRUE(imdecodeanimation(buf, animation2)); + + EXPECT_FALSE(animation2.still_image.empty()); + EXPECT_EQ(animation1.frames.size(), animation2.frames.size()); + + animation1.frames.erase(animation1.frames.begin()); + animation1.durations.erase(animation1.durations.begin()); + EXPECT_TRUE(imencodeanimation(".png", animation1, buf)); + EXPECT_TRUE(imdecodeanimation(buf, animation3)); + + EXPECT_FALSE(animation1.still_image.empty()); + EXPECT_TRUE(animation3.still_image.empty()); + EXPECT_EQ((size_t)1, animation3.frames.size()); +} + +TEST(Imgcodecs_APNG, animation_imread_preview) +{ + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/034.png"; + cv::Mat imread_result; + cv::imread(filename, imread_result, cv::IMREAD_UNCHANGED); + EXPECT_FALSE(imread_result.empty()); + + Animation animation; + ASSERT_TRUE(imreadanimation(filename, animation)); + EXPECT_FALSE(animation.still_image.empty()); + EXPECT_EQ((size_t)2, animation.frames.size()); + + EXPECT_EQ(0, cv::norm(animation.still_image, imread_result, cv::NORM_INF)); +} + #endif // HAVE_PNG #if defined(HAVE_PNG) || defined(HAVE_SPNG) @@ -676,7 +722,7 @@ TEST(Imgcodecs_APNG, imread_animation_16u) img = imread(filename, IMREAD_ANYDEPTH); ASSERT_FALSE(img.empty()); EXPECT_TRUE(img.type() == CV_16UC1); - EXPECT_EQ(19519, img.at(0, 0)); + EXPECT_EQ(19517, img.at(0, 0)); img = imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH); ASSERT_FALSE(img.empty()); diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index d1a9e720a9..792c38514f 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -148,7 +148,246 @@ const std::vector exif_files }; INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif, - testing::ValuesIn(exif_files)); + testing::ValuesIn(exif_files)); +static Mat makeCirclesImage(Size size, int type, int nbits) +{ + Mat img(size, type); + img.setTo(Scalar::all(0)); + RNG& rng = theRNG(); + int maxval = (int)(1 << nbits); + for (int i = 0; i < 100; i++) { + int x = rng.uniform(0, img.cols); + int y = rng.uniform(0, img.rows); + int radius = rng.uniform(5, std::min(img.cols, img.rows)/5); + int b = rng.uniform(0, maxval); + int g = rng.uniform(0, maxval); + int r = rng.uniform(0, maxval); + circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA); + } + return img; } + +#ifdef HAVE_AVIF +TEST(Imgcodecs_Avif, ReadWriteWithExif) +{ + static const uchar exif_data[] = { + 'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5, + 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1, + 0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0, + 0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26, + 0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, + 226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0, + 1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2', + '2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0, + 0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0', + '-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', + 'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0, + 0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1 + }; + + int avif_nbits = 10; + int avif_speed = 10; + int avif_quality = 85; + int imgdepth = avif_nbits > 8 ? CV_16U : CV_8U; + int imgtype = CV_MAKETYPE(imgdepth, 3); + const string outputname = cv::tempfile(".avif"); + Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits); + + std::vector metadata_types = {IMAGE_METADATA_EXIF}; + std::vector > metadata(1); + metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + + std::vector write_params = { + IMWRITE_AVIF_DEPTH, avif_nbits, + IMWRITE_AVIF_SPEED, avif_speed, + IMWRITE_AVIF_QUALITY, avif_quality + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > read_metadata, read_metadata2; + Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED); + Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED); + EXPECT_EQ(img2.cols, img.cols); + EXPECT_EQ(img2.rows, img.rows); + EXPECT_EQ(img2.type(), imgtype); + EXPECT_EQ(read_metadata_types, read_metadata_types2); + EXPECT_GE(read_metadata_types.size(), 1u); + EXPECT_EQ(read_metadata, read_metadata2); + EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); + EXPECT_EQ(read_metadata[0], metadata[0]); + EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.); + double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols); + EXPECT_LT(mse, 1500); + remove(outputname.c_str()); } +#endif // HAVE_AVIF + +TEST(Imgcodecs_Jpeg, ReadWriteWithExif) +{ + static const uchar exif_data[] = { + 'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5, + 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1, + 0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0, + 0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26, + 0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, + 226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0, + 1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2', + '2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0, + 0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-', + 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm', + 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0, + 0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1 + }; + + int jpeg_quality = 95; + int imgtype = CV_MAKETYPE(CV_8U, 3); + const string outputname = cv::tempfile(".jpeg"); + Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8); + + std::vector metadata_types = {IMAGE_METADATA_EXIF}; + std::vector > metadata(1); + metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + + std::vector write_params = { + IMWRITE_JPEG_QUALITY, jpeg_quality + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > read_metadata, read_metadata2; + Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED); + Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED); + EXPECT_EQ(img2.cols, img.cols); + EXPECT_EQ(img2.rows, img.rows); + EXPECT_EQ(img2.type(), imgtype); + EXPECT_EQ(read_metadata_types, read_metadata_types2); + EXPECT_GE(read_metadata_types.size(), 1u); + EXPECT_EQ(read_metadata, read_metadata2); + EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); + EXPECT_EQ(read_metadata[0], metadata[0]); + EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.); + double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols); + EXPECT_LT(mse, 80); + remove(outputname.c_str()); +} + +TEST(Imgcodecs_Png, ReadWriteWithExif) +{ + static const uchar exif_data[] = { + 'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5, + 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1, + 0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0, + 0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26, + 0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, + 226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0, + 1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2', + '2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0, + 0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-', + 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm', + 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0, + 0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1 + }; + + int png_compression = 3; + int imgtype = CV_MAKETYPE(CV_8U, 3); + const string outputname = cv::tempfile(".png"); + Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8); + + std::vector metadata_types = {IMAGE_METADATA_EXIF}; + std::vector > metadata(1); + metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + + std::vector write_params = { + IMWRITE_PNG_COMPRESSION, png_compression + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > read_metadata, read_metadata2; + Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED); + Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED); + EXPECT_EQ(img2.cols, img.cols); + EXPECT_EQ(img2.rows, img.rows); + EXPECT_EQ(img2.type(), imgtype); + EXPECT_EQ(read_metadata_types, read_metadata_types2); + EXPECT_GE(read_metadata_types.size(), 1u); + EXPECT_EQ(read_metadata, read_metadata2); + EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); + EXPECT_EQ(read_metadata[0], metadata[0]); + EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.); + double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols); + EXPECT_EQ(mse, 0); // png is lossless + remove(outputname.c_str()); +} + +static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern) +{ + size_t plen = pattern.size(); + for (size_t i = 0; i + plen <= exif_size; i++) { + if (exif[i] == pattern[0] && memcmp(&exif[i], pattern.c_str(), plen) == 0) + return i; + } + return 0xFFFFFFFFu; +} + +typedef std::tuple ReadExif_Sanity_Params; +typedef testing::TestWithParam ReadExif_Sanity; + +TEST_P(ReadExif_Sanity, Check) +{ + std::string filename = get<0>(GetParam()); + size_t exif_size = get<1>(GetParam()); + std::string pattern = get<2>(GetParam()); + size_t ploc = get<3>(GetParam()); + + const string root = cvtest::TS::ptr()->get_data_path(); + filename = root + filename; + + std::vector metadata_types; + std::vector metadata; + Mat img = imreadWithMetadata(filename, metadata_types, metadata, 1); + + EXPECT_EQ(img.type(), CV_8UC3); + ASSERT_GE(metadata_types.size(), 1u); + EXPECT_EQ(metadata_types.size(), metadata.size()); + const Mat& exif = metadata[IMAGE_METADATA_EXIF]; + EXPECT_EQ(exif.type(), CV_8U); + EXPECT_EQ(exif.total(), exif_size); + ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes + // (the header + IDF0 with at least 1 entry). + EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M'); + EXPECT_EQ(exif.data[0], exif.data[1]); + EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc); +} + +static const std::vector exif_sanity_params +{ +#ifdef HAVE_JPEG + {"readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120}, +#endif +#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF + {"readwrite/testExifOrientation_5.png", 112, "ExifTool", 102}, +#endif +#ifdef HAVE_AVIF + {"readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120}, +#endif +}; + +INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity, + testing::ValuesIn(exif_sanity_params)); + +}} diff --git a/modules/imgcodecs/test/test_exr.cpp b/modules/imgcodecs/test/test_exr.cpp index e441d3c4ff..4fd9596936 100644 --- a/modules/imgcodecs/test/test_exr.cpp +++ b/modules/imgcodecs/test/test_exr.cpp @@ -71,6 +71,36 @@ TEST(Imgcodecs_EXR, readWrite_32FC3) EXPECT_EQ(0, remove(filenameOutput.c_str())); } +TEST(Imgcodecs_EXR, readWrite_32FC7) +{ // 0-6 channels (multispectral) + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/test32FC7.exr"; + const string filenameOutput = cv::tempfile(".exr"); +#ifndef GENERATE_DATA + const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED); +#else + const Size sz(3, 5); + Mat img(sz, CV_32FC7); + img.at>(0, 0)[0] = 101.125; + img.at>(2, 1)[3] = 203.500; + img.at>(4, 2)[6] = 305.875; + ASSERT_TRUE(cv::imwrite(filenameInput, img)); +#endif + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_MAKETYPE(CV_32F, 7), img.type()); + + ASSERT_TRUE(cv::imwrite(filenameOutput, img)); + const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); + EXPECT_EQ(img2.type(), img.type()); + EXPECT_EQ(img2.size(), img.size()); + EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3); + EXPECT_EQ(0, remove(filenameOutput.c_str())); + const Mat img3 = cv::imread(filenameInput, IMREAD_GRAYSCALE); + ASSERT_TRUE(img3.empty()); + const Mat img4 = cv::imread(filenameInput, IMREAD_COLOR); + ASSERT_TRUE(img4.empty()); +} + TEST(Imgcodecs_EXR, readWrite_32FC1_half) { diff --git a/modules/imgcodecs/test/test_gdal.cpp b/modules/imgcodecs/test/test_gdal.cpp new file mode 100755 index 0000000000..0ede7098f7 --- /dev/null +++ b/modules/imgcodecs/test/test_gdal.cpp @@ -0,0 +1,41 @@ +// 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 "test_common.hpp" + +namespace opencv_test { namespace { + +#ifdef HAVE_GDAL + +static void test_gdal_read(const string filename, bool required = true) { + const string path = cvtest::findDataFile(filename); + Mat img; + ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR)); + if(!required && img.empty()) + { + throw SkipTestException("GDAL is built wihout required back-end support"); + } + ASSERT_FALSE(img.empty()); + EXPECT_EQ(3, img.cols); + EXPECT_EQ(5, img.rows); + EXPECT_EQ(CV_MAKETYPE(CV_32F, 7), img.type()); + EXPECT_EQ(101.125, (img.at>(0, 0)[0])); + EXPECT_EQ(203.500, (img.at>(2, 1)[3])); + EXPECT_EQ(305.875, (img.at>(4, 2)[6])); +} + +TEST(Imgcodecs_gdal, read_envi) +{ + test_gdal_read("../cv/gdal/envi_test.raw"); +} + +TEST(Imgcodecs_gdal, read_fits) +{ + // .fit test is optional because GDAL may be built wihtout CFITSIO library support + test_gdal_read("../cv/gdal/fits_test.fit", false); +} + +#endif // HAVE_GDAL + +}} // namespace diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index f271950a5b..583039202a 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -150,19 +150,107 @@ TEST(Imgcodecs_Png, decode_regression27295) typedef testing::TestWithParam Imgcodecs_Png_PngSuite; +// Parameterized test for decoding PNG files from the PNGSuite test set TEST_P(Imgcodecs_Png_PngSuite, decode) { + // Construct full paths for the PNG image and corresponding ground truth XML file const string root = cvtest::TS::ptr()->get_data_path(); const string filename = root + "pngsuite/" + GetParam() + ".png"; const string xml_filename = root + "pngsuite/" + GetParam() + ".xml"; - FileStorage fs(xml_filename, FileStorage::READ); - EXPECT_TRUE(fs.isOpened()); + // Load the XML file containing the ground truth data + FileStorage fs(xml_filename, FileStorage::READ); + ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully + + // Load the image using IMREAD_UNCHANGED to preserve original format Mat src = imread(filename, IMREAD_UNCHANGED); + ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully + + // Load the ground truth matrix from XML Mat gt; fs.getFirstTopLevelNode() >> gt; + // Compare the image loaded with IMREAD_UNCHANGED to the ground truth EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt); + + // Declare matrices for ground truth in different imread flag combinations + Mat gt_0, gt_1, gt_2, gt_3, gt_256, gt_258; + + // Handle grayscale 8-bit and 16-bit images + if (gt.channels() == 1) + { + gt.copyTo(gt_2); // For IMREAD_ANYDEPTH + if (gt.depth() == CV_16U) + gt_2.convertTo(gt_0, CV_8U, 1. / 256); + else + gt_0 = gt_2; // For IMREAD_GRAYSCALE + + cvtColor(gt_2, gt_3, COLOR_GRAY2BGR); // For IMREAD_COLOR | IMREAD_ANYDEPTH + + if (gt.depth() == CV_16U) + gt_3.convertTo(gt_1, CV_8U, 1. / 256); + else + gt_1 = gt_3; // For IMREAD_COLOR + + gt_256 = gt_1; // For IMREAD_COLOR_RGB + gt_258 = gt_3; // For IMREAD_COLOR_RGB | IMREAD_ANYDEPTH + } + + // Handle color images (3 or 4 channels) with 8-bit and 16-bit depth + if (gt.channels() > 1) + { + // Convert to grayscale + cvtColor(gt, gt_2, COLOR_BGRA2GRAY); + if (gt.depth() == CV_16U) + gt_2.convertTo(gt_0, CV_8U, 1. / 256); + else + gt_0 = gt_2; + + // Convert to 3-channel BGR + if (gt.channels() == 3) + gt.copyTo(gt_3); + else + cvtColor(gt, gt_3, COLOR_BGRA2BGR); + + if (gt.depth() == CV_16U) + gt_3.convertTo(gt_1, CV_8U, 1. / 256); + else + gt_1 = gt_3; + + // Convert to RGB for IMREAD_COLOR_RGB variants + cvtColor(gt_1, gt_256, COLOR_BGR2RGB); + cvtColor(gt_3, gt_258, COLOR_BGR2RGB); + } + + // Perform comparisons with different imread flags + EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_GRAYSCALE), gt_0); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR), gt_1); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(4, 0), imread(filename, IMREAD_ANYDEPTH), gt_2); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), gt_3); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR_RGB), gt_256); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), gt_258); + +// Uncomment this block to write out the decoded images for visual/manual inspection +// or for regenerating expected ground truth PNGs (for example, after changing decoder logic). +#if 0 + imwrite(filename + "_0.png", imread(filename, IMREAD_GRAYSCALE)); + imwrite(filename + "_1.png", imread(filename, IMREAD_COLOR)); + imwrite(filename + "_2.png", imread(filename, IMREAD_ANYDEPTH)); + imwrite(filename + "_3.png", imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH)); + imwrite(filename + "_256.png", imread(filename, IMREAD_COLOR_RGB)); + imwrite(filename + "_258.png", imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH)); +#endif + +// Uncomment this block to verify that saved images (from above) load identically +// when read back with IMREAD_UNCHANGED. Helps ensure write-read symmetry. +#if 0 + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_GRAYSCALE), imread(filename + "_0.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR), imread(filename + "_1.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_ANYDEPTH), imread(filename + "_2.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), imread(filename + "_3.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB), imread(filename + "_256.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), imread(filename + "_258.png", IMREAD_UNCHANGED)); +#endif } const string pngsuite_files[] = @@ -243,23 +331,13 @@ const string pngsuite_files[] = "f04n2c08", "f99n0g04", "g03n0g16", - "g03n2c08", - "g03n3p04", "g04n0g16", - "g04n2c08", - "g04n3p04", "g05n0g16", - "g05n2c08", - "g05n3p04", "g07n0g16", - "g07n2c08", - "g07n3p04", "g10n0g16", "g10n2c08", "g10n3p04", "g25n0g16", - "g25n2c08", - "g25n3p04", "oi1n0g16", "oi1n2c16", "oi2n0g16", @@ -333,6 +411,49 @@ const string pngsuite_files[] = INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite, testing::ValuesIn(pngsuite_files)); +typedef testing::TestWithParam Imgcodecs_Png_PngSuite_Gamma; + +// Parameterized test for decoding PNG files from the PNGSuite test set +TEST_P(Imgcodecs_Png_PngSuite_Gamma, decode) +{ + // Construct full paths for the PNG image and corresponding ground truth XML file + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/" + GetParam() + ".png"; + const string xml_filename = root + "pngsuite/" + GetParam() + ".xml"; + + // Load the XML file containing the ground truth data + FileStorage fs(xml_filename, FileStorage::READ); + ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully + + // Load the image using IMREAD_UNCHANGED to preserve original format + Mat src = imread(filename, IMREAD_UNCHANGED); + ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully + + // Load the ground truth matrix from XML + Mat gt; + fs.getFirstTopLevelNode() >> gt; + + // Compare the image loaded with IMREAD_UNCHANGED to the ground truth + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt); +} + +const string pngsuite_files_gamma[] = +{ + "g03n2c08", + "g03n3p04", + "g04n2c08", + "g04n3p04", + "g05n2c08", + "g05n3p04", + "g07n2c08", + "g07n3p04", + "g25n2c08", + "g25n3p04" +}; + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Gamma, + testing::ValuesIn(pngsuite_files_gamma)); + typedef testing::TestWithParam Imgcodecs_Png_PngSuite_Corrupted; TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 55b69c6821..7c9d3ad978 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -235,8 +235,9 @@ enum MorphShapes { MORPH_RECT = 0, //!< a rectangular structuring element: \f[E_{ij}=1\f] MORPH_CROSS = 1, //!< a cross-shaped structuring element: //!< \f[E_{ij} = \begin{cases} 1 & \texttt{if } {i=\texttt{anchor.y } {or } {j=\texttt{anchor.x}}} \\0 & \texttt{otherwise} \end{cases}\f] - MORPH_ELLIPSE = 2 //!< an elliptic structuring element, that is, a filled ellipse inscribed + MORPH_ELLIPSE = 2, //!< an elliptic structuring element, that is, a filled ellipse inscribed //!< into the rectangle Rect(0, 0, esize.width, esize.height) + MORPH_DIAMOND = 3 //!< a diamond structuring element defined by Manhattan distance }; //! @} imgproc_filter @@ -2944,9 +2945,9 @@ Calculates the cross-power spectrum of two supplied source arrays. The arrays ar with getOptimalDFTSize. The function performs the following equations: -- First it applies a Hanning window (see ) to each -image to remove possible edge effects. This window is cached until the array size changes to speed -up processing time. +- First it applies a Hanning window to each image to remove possible edge effects, if it's provided +by user. See @ref createHanningWindow and . This window may +be cached until the array size changes to speed up processing time. - Next it computes the forward DFTs of each source array: \f[\mathbf{G}_a = \mathcal{F}\{src_1\}, \; \mathbf{G}_b = \mathcal{F}\{src_2\}\f] where \f$\mathcal{F}\f$ is the forward DFT. diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index c7871cd54c..55a5d19c60 100755 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -796,6 +796,22 @@ void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labe ippFree( pBuffer ); if (status>=0) { + // https://github.com/opencv/opencv/issues/24082 + // There is probably a rounding issue that leads to non-deterministic behavior + // between runs on positions closer to zeros by x-axis in straight direction. + // As a workaround, we detect the distances that expected to be exact + // number of pixels and round manually. + static const float correctionDiff = 1.0f / (1 << 11); + for (int i = 0; i < dst.rows; ++i) + { + float* row = dst.ptr(i); + for (int j = 0; j < dst.cols; ++j) + { + float rounded = static_cast(cvRound(row[j])); + if (fabs(row[j] - rounded) <= correctionDiff) + row[j] = rounded; + } + } CV_IMPL_ADD(CV_IMPL_IPP); return; } diff --git a/modules/imgproc/src/geometry.cpp b/modules/imgproc/src/geometry.cpp index eb6757c4f0..244cae4e19 100644 --- a/modules/imgproc/src/geometry.cpp +++ b/modules/imgproc/src/geometry.cpp @@ -271,20 +271,20 @@ static LineSegmentIntersection parallelInt( Point2f a, Point2f b, Point2f c, Poi static LineSegmentIntersection intersectLineSegments( Point2f a, Point2f b, Point2f c, Point2f d, Point2f& p, Point2f& q ) { - double denom = (a.x - b.x) * (double)(d.y - c.y) - (a.y - b.y) * (double)(d.x - c.x); + double denom = ((double)a.x - b.x) * ((double)d.y - c.y) - ((double)a.y - b.y) * ((double)d.x - c.x); // If denom is zero, then segments are parallel: handle separately. if( denom == 0. ) return parallelInt(a, b, c, d, p, q); - double num = (d.y - a.y) * (double)(a.x - c.x) + (a.x - d.x) * (double)(a.y - c.y); + double num = ((double)d.y - a.y) * ((double)a.x - c.x) + ((double)a.x - d.x) * ((double)a.y - c.y); double s = num / denom; - num = (b.y - a.y) * (double)(a.x - c.x) + (c.y - a.y) * (double)(b.x - a.x); + num = ((double)b.y - a.y) * ((double)a.x - c.x) + ((double)c.y - a.y) * ((double)b.x - a.x); double t = num / denom; - p.x = (float)(a.x + s*(b.x - a.x)); - p.y = (float)(a.y + s*(b.y - a.y)); + p.x = (float)(a.x + s*((double)b.x - a.x)); + p.y = (float)(a.y + s*((double)b.y - a.y)); q = p; return s < 0. || s > 1. || t < 0. || t > 1. ? LS_NO_INTERSECTION : diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 35fe093c2b..78d258dfd7 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -63,67 +63,6 @@ using namespace cv; namespace cv { -#if defined (HAVE_IPP) && (!IPP_DISABLE_WARPAFFINE || !IPP_DISABLE_WARPPERSPECTIVE || !IPP_DISABLE_REMAP) -typedef IppStatus (CV_STDCALL* ippiSetFunc)(const void*, void *, int, IppiSize); - -template -bool IPPSetSimple(cv::Scalar value, void *dataPointer, int step, IppiSize &size, ippiSetFunc func) -{ - CV_INSTRUMENT_REGION_IPP(); - - Type values[channels]; - for( int i = 0; i < channels; i++ ) - values[i] = saturate_cast(value[i]); - return func(values, dataPointer, step, size) >= 0; -} - -static bool IPPSet(const cv::Scalar &value, void *dataPointer, int step, IppiSize &size, int channels, int depth) -{ - CV_INSTRUMENT_REGION_IPP(); - - if( channels == 1 ) - { - switch( depth ) - { - case CV_8U: - return CV_INSTRUMENT_FUN_IPP(ippiSet_8u_C1R, saturate_cast(value[0]), (Ipp8u *)dataPointer, step, size) >= 0; - case CV_16U: - return CV_INSTRUMENT_FUN_IPP(ippiSet_16u_C1R, saturate_cast(value[0]), (Ipp16u *)dataPointer, step, size) >= 0; - case CV_32F: - return CV_INSTRUMENT_FUN_IPP(ippiSet_32f_C1R, saturate_cast(value[0]), (Ipp32f *)dataPointer, step, size) >= 0; - } - } - else - { - if( channels == 3 ) - { - switch( depth ) - { - case CV_8U: - return IPPSetSimple<3, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C3R); - case CV_16U: - return IPPSetSimple<3, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C3R); - case CV_32F: - return IPPSetSimple<3, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C3R); - } - } - else if( channels == 4 ) - { - switch( depth ) - { - case CV_8U: - return IPPSetSimple<4, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C4R); - case CV_16U: - return IPPSetSimple<4, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C4R); - case CV_32F: - return IPPSetSimple<4, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C4R); - } - } - } - return false; -} -#endif - /************** interpolation formulas and tables ***************/ const int INTER_REMAP_COEF_BITS=15; @@ -1434,58 +1373,6 @@ static bool ocl_remap(InputArray _src, OutputArray _dst, InputArray _map1, Input #endif -#if defined HAVE_IPP && !IPP_DISABLE_REMAP - -typedef IppStatus (CV_STDCALL * ippiRemap)(const void * pSrc, IppiSize srcSize, int srcStep, IppiRect srcRoi, - const Ipp32f* pxMap, int xMapStep, const Ipp32f* pyMap, int yMapStep, - void * pDst, int dstStep, IppiSize dstRoiSize, int interpolation); - -class IPPRemapInvoker : - public ParallelLoopBody -{ -public: - IPPRemapInvoker(Mat & _src, Mat & _dst, Mat & _xmap, Mat & _ymap, ippiRemap _ippFunc, - int _ippInterpolation, int _borderType, const Scalar & _borderValue, bool * _ok) : - ParallelLoopBody(), src(_src), dst(_dst), map1(_xmap), map2(_ymap), ippFunc(_ippFunc), - ippInterpolation(_ippInterpolation), borderType(_borderType), borderValue(_borderValue), ok(_ok) - { - *ok = true; - } - - virtual void operator() (const Range & range) const - { - IppiRect srcRoiRect = { 0, 0, src.cols, src.rows }; - Mat dstRoi = dst.rowRange(range); - IppiSize dstRoiSize = ippiSize(dstRoi.size()); - int type = dst.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); - - if (borderType == BORDER_CONSTANT && - !IPPSet(borderValue, dstRoi.ptr(), (int)dstRoi.step, dstRoiSize, cn, depth)) - { - *ok = false; - return; - } - - if (CV_INSTRUMENT_FUN_IPP(ippFunc, src.ptr(), ippiSize(src.size()), (int)src.step, srcRoiRect, - map1.ptr(), (int)map1.step, map2.ptr(), (int)map2.step, - dstRoi.ptr(), (int)dstRoi.step, dstRoiSize, ippInterpolation) < 0) - *ok = false; - else - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - } - } - -private: - Mat & src, & dst, & map1, & map2; - ippiRemap ippFunc; - int ippInterpolation, borderType; - Scalar borderValue; - bool * ok; -}; - -#endif - } void cv::remap( InputArray _src, OutputArray _dst, @@ -1652,47 +1539,6 @@ void cv::remap( InputArray _src, OutputArray _dst, } } -#if defined HAVE_IPP && !IPP_DISABLE_REMAP - CV_IPP_CHECK() - { - if ((interpolation == INTER_LINEAR || interpolation == INTER_CUBIC || interpolation == INTER_NEAREST) && - map1.type() == CV_32FC1 && map2.type() == CV_32FC1 && - (borderType == BORDER_CONSTANT || borderType == BORDER_TRANSPARENT)) - { - int ippInterpolation = - interpolation == INTER_NEAREST ? IPPI_INTER_NN : - interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : IPPI_INTER_CUBIC; - - ippiRemap ippFunc = - type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R : - type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R : - type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R : - type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R : - type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R : - type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R : - type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R : - type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R : - type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R : 0; - - if (ippFunc) - { - bool ok; - IPPRemapInvoker invoker(src, dst, map1, map2, ippFunc, ippInterpolation, - borderType, borderValue, &ok); - Range range(0, dst.rows); - parallel_for_(range, invoker, dst.total() / (double)(1 << 16)); - - if (ok) - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - return; - } - setIppErrorStatus(); - } - } - } -#endif - RemapNNFunc nnfunc = 0; RemapFunc ifunc = 0; const void* ctab = 0; @@ -2188,62 +2034,6 @@ private: const double *M; }; - -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPAFFINE -typedef IppStatus (CV_STDCALL* ippiWarpAffineBackFunc)(const void*, IppiSize, int, IppiRect, void *, int, IppiRect, double [2][3], int); - -class IPPWarpAffineInvoker : - public ParallelLoopBody -{ -public: - IPPWarpAffineInvoker(Mat &_src, Mat &_dst, double (&_coeffs)[2][3], int &_interpolation, int _borderType, - const Scalar &_borderValue, ippiWarpAffineBackFunc _func, bool *_ok) : - ParallelLoopBody(), src(_src), dst(_dst), mode(_interpolation), coeffs(_coeffs), - borderType(_borderType), borderValue(_borderValue), func(_func), ok(_ok) - { - *ok = true; - } - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - IppiSize srcsize = { src.cols, src.rows }; - IppiRect srcroi = { 0, 0, src.cols, src.rows }; - IppiRect dstroi = { 0, range.start, dst.cols, range.end - range.start }; - int cnn = src.channels(); - if( borderType == BORDER_CONSTANT ) - { - IppiSize setSize = { dst.cols, range.end - range.start }; - void *dataPointer = dst.ptr(range.start); - if( !IPPSet( borderValue, dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) - { - *ok = false; - return; - } - } - - // Aug 2013: problem in IPP 7.1, 8.0 : sometimes function return ippStsCoeffErr - IppStatus status = CV_INSTRUMENT_FUN_IPP(func,( src.ptr(), srcsize, (int)src.step[0], srcroi, dst.ptr(), - (int)dst.step[0], dstroi, coeffs, mode )); - if( status < 0) - *ok = false; - else - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - } - } -private: - Mat &src; - Mat &dst; - int mode; - double (&coeffs)[2][3]; - int borderType; - Scalar borderValue; - ippiWarpAffineBackFunc func; - bool *ok; - const IPPWarpAffineInvoker& operator= (const IPPWarpAffineInvoker&); -}; -#endif - #ifdef HAVE_OPENCL enum { OCL_OP_PERSPECTIVE = 1, OCL_OP_AFFINE = 0 }; @@ -2435,132 +2225,6 @@ static bool ocl_warpTransform(InputArray _src, OutputArray _dst, InputArray _M0, #endif -#ifdef HAVE_IPP -#define IPP_WARPAFFINE_PARALLEL 1 - -#ifdef HAVE_IPP_IW - -class ipp_warpAffineParallel: public ParallelLoopBody -{ -public: - ipp_warpAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, IppiInterpolationType _inter, double (&_coeffs)[2][3], ::ipp::IwiBorderType _borderType, IwTransDirection _iwTransDirection, bool *_ok):m_src(src), m_dst(dst) - { - pOk = _ok; - - inter = _inter; - borderType = _borderType; - iwTransDirection = _iwTransDirection; - - for( int i = 0; i < 2; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = _coeffs[i][j]; - - *pOk = true; - } - ~ipp_warpAffineParallel() {} - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - CV_INSTRUMENT_REGION_IPP(); - - if(*pOk == false) - return; - - try - { - ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); - CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, m_src, m_dst, coeffs, iwTransDirection, inter, ::ipp::IwiWarpAffineParams(), borderType, tile); - } - catch(const ::ipp::IwException &) - { - *pOk = false; - return; - } - } -private: - ::ipp::IwiImage &m_src; - ::ipp::IwiImage &m_dst; - - IppiInterpolationType inter; - double coeffs[2][3]; - ::ipp::IwiBorderType borderType; - IwTransDirection iwTransDirection; - - bool *pOk; - const ipp_warpAffineParallel& operator= (const ipp_warpAffineParallel&); -}; - -#endif - -static bool ipp_warpAffine( InputArray _src, OutputArray _dst, int interpolation, int borderType, const Scalar & borderValue, InputArray _M, int flags ) -{ -#ifdef HAVE_IPP_IW - CV_INSTRUMENT_REGION_IPP(); - - if (!cv::ipp::useIPP_NotExact()) - return false; - - IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); - if((int)ippInter < 0) - return false; - - // Acquire data and begin processing - try - { - Mat src = _src.getMat(); - Mat dst = _dst.getMat(); - ::ipp::IwiImage iwSrc = ippiGetImage(src); - ::ipp::IwiImage iwDst = ippiGetImage(dst); - ::ipp::IwiBorderType ippBorder(ippiGetBorderType(borderType), ippiGetValue(borderValue)); - IwTransDirection iwTransDirection; - if(!ippBorder) - return false; - - if( !(flags & WARP_INVERSE_MAP) ) - iwTransDirection = iwTransForward; - else - iwTransDirection = iwTransInverse; - - Mat M = _M.getMat(); - double coeffs[2][3]; - for( int i = 0; i < 2; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = M.at(i, j); - - const int threads = ippiSuggestThreadsNum(iwDst, 2); - - if(IPP_WARPAFFINE_PARALLEL && threads > 1) - { - bool ok = true; - Range range(0, (int)iwDst.m_size.height); - ipp_warpAffineParallel invoker(iwSrc, iwDst, ippInter, coeffs, ippBorder, iwTransDirection, &ok); - if(!ok) - return false; - - parallel_for_(range, invoker, threads*4); - - if(!ok) - return false; - } else { - CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, iwSrc, iwDst, coeffs, iwTransDirection, ippInter, ::ipp::IwiWarpAffineParams(), ippBorder); - } - - } - catch (const ::ipp::IwException &) - { - return false; - } - - return true; -#else - CV_UNUSED(_src); CV_UNUSED(_dst); CV_UNUSED(interpolation); - CV_UNUSED(borderType); CV_UNUSED(borderValue); CV_UNUSED(_M); CV_UNUSED(flags); - return false; -#endif -} - -#endif - namespace hal { static void warpAffine(int src_type, @@ -2812,8 +2476,6 @@ void cv::warpAffine( InputArray _src, OutputArray _dst, CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 2 && M0.cols == 3 ); M0.convertTo(matM, matM.type()); - CV_IPP_RUN_FAST(ipp_warpAffine(src, dst, interpolation, borderType, borderValue, matM, flags)); - if( !(flags & WARP_INVERSE_MAP) ) { double D = M[0]*M[4] - M[1]*M[3]; @@ -2826,70 +2488,6 @@ void cv::warpAffine( InputArray _src, OutputArray _dst, M[2] = b1; M[5] = b2; } -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPAFFINE - CV_IPP_CHECK() - { - int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); - if( ( depth == CV_8U || depth == CV_16U || depth == CV_32F ) && - ( cn == 1 || cn == 3 || cn == 4 ) && - ( interpolation == INTER_NEAREST || interpolation == INTER_LINEAR || interpolation == INTER_CUBIC) && - ( borderType == cv::BORDER_TRANSPARENT || borderType == cv::BORDER_CONSTANT) ) - { - ippiWarpAffineBackFunc ippFunc = 0; - if ((flags & WARP_INVERSE_MAP) != 0) - { - ippFunc = - type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C1R : - type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C3R : - type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C4R : - type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C1R : - type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C3R : - type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C4R : - type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C1R : - type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C3R : - type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C4R : - 0; - } - else - { - ippFunc = - type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C1R : - type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C3R : - type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C4R : - type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C1R : - type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C3R : - type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C4R : - type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C1R : - type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C3R : - type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C4R : - 0; - } - int mode = - interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : - interpolation == INTER_NEAREST ? IPPI_INTER_NN : - interpolation == INTER_CUBIC ? IPPI_INTER_CUBIC : - 0; - CV_Assert(mode && ippFunc); - - double coeffs[2][3]; - for( int i = 0; i < 2; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = matM.at(i, j); - - bool ok; - Range range(0, dst.rows); - IPPWarpAffineInvoker invoker(src, dst, coeffs, mode, borderType, borderValue, ippFunc, &ok); - parallel_for_(range, invoker, dst.total()/(double)(1<<16)); - if( ok ) - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - return; - } - setIppErrorStatus(); - } - } -#endif - hal::warpAffine(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, M, interpolation, borderType, borderValue.val, hint); } @@ -3212,61 +2810,6 @@ private: Scalar borderValue; }; -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPPERSPECTIVE -typedef IppStatus (CV_STDCALL* ippiWarpPerspectiveFunc)(const void*, IppiSize, int, IppiRect, void *, int, IppiRect, double [3][3], int); - -class IPPWarpPerspectiveInvoker : - public ParallelLoopBody -{ -public: - IPPWarpPerspectiveInvoker(Mat &_src, Mat &_dst, double (&_coeffs)[3][3], int &_interpolation, - int &_borderType, const Scalar &_borderValue, ippiWarpPerspectiveFunc _func, bool *_ok) : - ParallelLoopBody(), src(_src), dst(_dst), mode(_interpolation), coeffs(_coeffs), - borderType(_borderType), borderValue(_borderValue), func(_func), ok(_ok) - { - *ok = true; - } - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - IppiSize srcsize = {src.cols, src.rows}; - IppiRect srcroi = {0, 0, src.cols, src.rows}; - IppiRect dstroi = {0, range.start, dst.cols, range.end - range.start}; - int cnn = src.channels(); - - if( borderType == BORDER_CONSTANT ) - { - IppiSize setSize = {dst.cols, range.end - range.start}; - void *dataPointer = dst.ptr(range.start); - if( !IPPSet( borderValue, dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) - { - *ok = false; - return; - } - } - - IppStatus status = CV_INSTRUMENT_FUN_IPP(func,(src.ptr();, srcsize, (int)src.step[0], srcroi, dst.ptr(), (int)dst.step[0], dstroi, coeffs, mode)); - if (status != ippStsNoErr) - *ok = false; - else - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - } - } -private: - Mat &src; - Mat &dst; - int mode; - double (&coeffs)[3][3]; - int borderType; - const Scalar borderValue; - ippiWarpPerspectiveFunc func; - bool *ok; - - const IPPWarpPerspectiveInvoker& operator= (const IPPWarpPerspectiveInvoker&); -}; -#endif - namespace hal { static void warpPerspective(int src_type, @@ -3489,65 +3032,6 @@ void cv::warpPerspective( InputArray _src, OutputArray _dst, InputArray _M0, CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 3 && M0.cols == 3 ); M0.convertTo(matM, matM.type()); -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPPERSPECTIVE - CV_IPP_CHECK() - { - int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); - if( (depth == CV_8U || depth == CV_16U || depth == CV_32F) && - (cn == 1 || cn == 3 || cn == 4) && - ( borderType == cv::BORDER_TRANSPARENT || borderType == cv::BORDER_CONSTANT ) && - (interpolation == INTER_NEAREST || interpolation == INTER_LINEAR || interpolation == INTER_CUBIC)) - { - ippiWarpPerspectiveFunc ippFunc = 0; - if ((flags & WARP_INVERSE_MAP) != 0) - { - ippFunc = type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_8u_C1R : - type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_8u_C3R : - type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_8u_C4R : - type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_16u_C1R : - type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_16u_C3R : - type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_16u_C4R : - type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_32f_C1R : - type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_32f_C3R : - type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_32f_C4R : 0; - } - else - { - ippFunc = type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_8u_C1R : - type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_8u_C3R : - type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_8u_C4R : - type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_16u_C1R : - type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_16u_C3R : - type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_16u_C4R : - type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_32f_C1R : - type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_32f_C3R : - type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_32f_C4R : 0; - } - int mode = - interpolation == INTER_NEAREST ? IPPI_INTER_NN : - interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : - interpolation == INTER_CUBIC ? IPPI_INTER_CUBIC : 0; - CV_Assert(mode && ippFunc); - - double coeffs[3][3]; - for( int i = 0; i < 3; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = matM.at(i, j); - - bool ok; - Range range(0, dst.rows); - IPPWarpPerspectiveInvoker invoker(src, dst, coeffs, mode, borderType, borderValue, ippFunc, &ok); - parallel_for_(range, invoker, dst.total()/(double)(1<<16)); - if( ok ) - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - return; - } - setIppErrorStatus(); - } - } -#endif - if( !(flags & WARP_INVERSE_MAP) ) invert(matM, matM); diff --git a/modules/imgproc/src/lsd.cpp b/modules/imgproc/src/lsd.cpp index ed6b00f986..1498f906c6 100644 --- a/modules/imgproc/src/lsd.cpp +++ b/modules/imgproc/src/lsd.cpp @@ -1076,6 +1076,10 @@ void LineSegmentDetectorImpl::drawSegments(InputOutputArray _image, InputArray l } Mat _lines = lines.getMat(); + if (_lines.empty()) + { + return; + } const int N = _lines.checkVector(4); CV_Assert(_lines.depth() == CV_32F || _lines.depth() == CV_32S); diff --git a/modules/imgproc/src/morph.dispatch.cpp b/modules/imgproc/src/morph.dispatch.cpp index 5b5b4dbbc8..24abe29e2c 100644 --- a/modules/imgproc/src/morph.dispatch.cpp +++ b/modules/imgproc/src/morph.dispatch.cpp @@ -138,7 +138,7 @@ Mat getStructuringElement(int shape, Size ksize, Point anchor) int r = 0, c = 0; double inv_r2 = 0; - CV_Assert( shape == MORPH_RECT || shape == MORPH_CROSS || shape == MORPH_ELLIPSE ); + CV_Assert( shape == MORPH_RECT || shape == MORPH_CROSS || shape == MORPH_ELLIPSE || shape == MORPH_DIAMOND ); anchor = normalizeAnchor(anchor, ksize); @@ -151,6 +151,11 @@ Mat getStructuringElement(int shape, Size ksize, Point anchor) c = ksize.width/2; inv_r2 = r ? 1./((double)r*r) : 0; } + else if( shape == MORPH_DIAMOND ) + { + r = ksize.height/2; + c = ksize.width/2; + } Mat elem(ksize, CV_8U); @@ -163,6 +168,16 @@ Mat getStructuringElement(int shape, Size ksize, Point anchor) j2 = ksize.width; else if( shape == MORPH_CROSS ) j1 = anchor.x, j2 = j1 + 1; + else if( shape == MORPH_DIAMOND ) + { + int dy = std::abs(i - r); + if( dy <= r ) + { + int dx = r - dy; + j1 = std::max( c - dx, 0 ); + j2 = std::min( c + dx + 1, ksize.width ); + } + } else { int dy = i - r; diff --git a/modules/imgproc/src/phasecorr.cpp b/modules/imgproc/src/phasecorr.cpp index d2f88420be..3fbe62883d 100644 --- a/modules/imgproc/src/phasecorr.cpp +++ b/modules/imgproc/src/phasecorr.cpp @@ -38,6 +38,7 @@ #include "precomp.hpp" #include +#include "opencv2/core/hal/intrin.hpp" namespace cv { @@ -614,8 +615,27 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type) double* const wc = _wc.data(); double coeff0 = 2.0 * CV_PI / (double)(cols - 1), coeff1 = 2.0 * CV_PI / (double)(rows - 1); - for(int j = 0; j < cols; j++) - wc[j] = 0.5 * (1.0 - cos(coeff0 * j)); + int c = 0; +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + const int nlanes32 = VTraits::vlanes(); + const int nlanes64 = VTraits::vlanes(); + const int max_nlanes = VTraits::max_nlanes; + std::array index; + std::iota(index.data(), index.data()+max_nlanes, 0.f); + v_float64 vindex = vx_load(index.data()); + v_float64 delta = vx_setall_f64(VTraits::vlanes()); + v_float64 vcoeff0 = vx_setall_f64(coeff0); + v_float64 one = vx_setall_f64(1.f); + v_float64 half = vx_setall_f64(0.5f); + for (; c <= cols - nlanes64; c += nlanes64) + { + v_float64 v = v_mul(half, v_sub(one, v_cos(v_mul(vcoeff0, vindex)))); + vx_store(wc + c, v); + vindex = v_add(vindex, delta); + } +#endif + for(; c < cols; c++) + wc[c] = 0.5 * (1.0 - cos(coeff0 * c)); if(dst.depth() == CV_32F) { @@ -623,7 +643,17 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type) { float* dstData = dst.ptr(i); double wr = 0.5 * (1.0 - cos(coeff1 * i)); - for(int j = 0; j < cols; j++) + int j = 0; +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + v_float64 vwr = vx_setall_f64(wr); + for (; j <= cols - nlanes32; j += nlanes32) + { + v_float64 v0 = v_mul(vwr, vx_load(wc + j)); + v_float64 v1 = v_mul(vwr, vx_load(wc + j + nlanes64)); + vx_store(dstData + j, v_cvt_f32(v0, v1)); + } +#endif + for(; j < cols; j++) dstData[j] = (float)(wr * wc[j]); } } @@ -633,7 +663,16 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type) { double* dstData = dst.ptr(i); double wr = 0.5 * (1.0 - cos(coeff1 * i)); - for(int j = 0; j < cols; j++) + int j = 0; +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + v_float64 vwr = vx_setall_f64(wr); + for (; j <= cols - nlanes64; j += nlanes64) + { + v_float64 v = v_mul(vwr, vx_load(wc + j)); + vx_store(dstData + j, v); + } +#endif + for(; j < cols; j++) dstData[j] = wr * wc[j]; } } diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index 40be708a43..e2951325a0 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -850,7 +850,8 @@ static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _ // CCorr(I', T') = CCorr(I, T'*M) - sum(T'*M)/sum(M)*CCorr(I, M) // It does not matter what to use Mat/MatExpr, it should be evaluated to perform assign subtraction - Mat temp_res = img_mask_corr.mul(sum(templx_mask).div(mask_sum)); + Mat temp_res; + multiply(img_mask_corr, sum(templx_mask).div(mask_sum), temp_res); if (img.channels() == 1) { result -= temp_res; @@ -881,8 +882,11 @@ static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _ Mat img_mask2_corr(corrSize, img.type()); crossCorr(img2, mask2, norm_imgx, Point(0,0), 0, 0); crossCorr(img, mask2, img_mask2_corr, Point(0,0), 0, 0); - temp_res = img_mask_corr.mul(Scalar(1.0, 1.0, 1.0, 1.0).div(mask_sum)) - .mul(img_mask_corr.mul(mask2_sum.div(mask_sum)) - 2 * img_mask2_corr); + Mat temp_res1; + multiply(img_mask_corr, Scalar(1.0, 1.0, 1.0, 1.0).div(mask_sum), temp_res1); + Mat temp_res2; + multiply(img_mask_corr, mask2_sum.div(mask_sum), temp_res2); + temp_res = temp_res1.mul(temp_res2 - 2 * img_mask2_corr); if (img.channels() == 1) { norm_imgx += temp_res; diff --git a/modules/imgproc/test/test_distancetransform.cpp b/modules/imgproc/test/test_distancetransform.cpp index c6d6b827b4..c1d2e8c34a 100644 --- a/modules/imgproc/test/test_distancetransform.cpp +++ b/modules/imgproc/test/test_distancetransform.cpp @@ -176,4 +176,37 @@ TEST(Imgproc_DistanceTransform, precise_long_dist) EXPECT_EQ(cv::norm(expected, dist, NORM_INF), 0); } +TEST(Imgproc_DistanceTransform, ipp_deterministic_corner) +{ + setNumThreads(1); + + Mat src(1, 4096, CV_8U, Scalar(255)), dist; + src.at(0, 0) = 0; + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE); + for (int i = 0; i < src.cols; ++i) + { + float expected = static_cast(i); + ASSERT_EQ(expected, dist.at(0, i)) << cv::format("diff: %e", expected - dist.at(0, i)); + } +} + +TEST(Imgproc_DistanceTransform, ipp_deterministic) +{ + setNumThreads(1); + RNG& rng = TS::ptr()->get_rng(); + Mat src(1, 800, CV_8U, Scalar(255)), dist; + int p1 = cvtest::randInt(rng) % src.cols; + int p2 = cvtest::randInt(rng) % src.cols; + int p3 = cvtest::randInt(rng) % src.cols; + src.at(0, p1) = 0; + src.at(0, p2) = 0; + src.at(0, p3) = 0; + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE); + for (int i = 0; i < src.cols; ++i) + { + float expected = static_cast(min(min(abs(i - p1), abs(i - p2)), abs(i - p3))); + ASSERT_EQ(expected, dist.at(0, i)) << cv::format("diff: %e", expected - dist.at(0, i)); + } +} + }} // namespace diff --git a/modules/imgproc/test/test_intersectconvexconvex.cpp b/modules/imgproc/test/test_intersectconvexconvex.cpp index 00e3674f48..146c891b84 100644 --- a/modules/imgproc/test/test_intersectconvexconvex.cpp +++ b/modules/imgproc/test/test_intersectconvexconvex.cpp @@ -292,5 +292,27 @@ TEST(Imgproc_IntersectConvexConvex, not_convex) EXPECT_LE(area, 0.f); } +// The intersection was not properly detected when one line sneaked its way in through an edge point +TEST(Imgproc_IntersectConvexConvex, intersection_at_line_transition) +{ + std::vector convex1 = { + { -1.7604526f, -0.00028443217f }, + {1276.5778f , 0.2091252f}, + {1276.4617f , 719.27f}, + { -1.8754264f, 719.06866f} + + }; + std::vector convex2 = { + { 0.f , 0.f }, + {1280.f , 0.f }, + {1280.f , 720.f}, + { 0.f , 720.f } + }; + std::vector intersection; + + float area = cv::intersectConvexConvex(convex1, convex2, intersection, false); + EXPECT_GE(cv::contourArea(convex1), area); + EXPECT_GE(cv::contourArea(convex2), area); +} } // namespace } // opencv_test diff --git a/modules/imgproc/test/test_lsd.cpp b/modules/imgproc/test/test_lsd.cpp index 43d00b4928..5f31a15c21 100644 --- a/modules/imgproc/test/test_lsd.cpp +++ b/modules/imgproc/test/test_lsd.cpp @@ -402,4 +402,18 @@ TEST_F(Imgproc_LSD_Common, compareSegmentsVec4i) ASSERT_EQ(result2, 11); } +TEST_F(Imgproc_LSD_Common, drawSegmentsEmpty) +{ + Ptr detector = createLineSegmentDetector(LSD_REFINE_STD); + Mat1b img = Mat1b::zeros(240, 320); + + std::vector lines_4i; + detector->detect(img, lines_4i); + + Mat3b img_color = Mat3b::zeros(240, 320); + ASSERT_NO_THROW( + detector->drawSegments(img_color, lines_4i); + ); +} + }} // namespace diff --git a/modules/imgproc/test/test_structuring_element.cpp b/modules/imgproc/test/test_structuring_element.cpp new file mode 100644 index 0000000000..7ca500e10a --- /dev/null +++ b/modules/imgproc/test/test_structuring_element.cpp @@ -0,0 +1,17 @@ +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +TEST(MorphShapes, getStructuringElementDiamond) +{ + cv::Mat element = cv::getStructuringElement(cv::MORPH_DIAMOND, cv::Size(5,5)); + cv::Mat expected = (cv::Mat_(5,5) << + 0,0,1,0,0, + 0,1,1,1,0, + 1,1,1,1,1, + 0,1,1,1,0, + 0,0,1,0,0); + EXPECT_EQ(0, cvtest::norm(element, expected, cv::NORM_INF)); +} + +}} // namespace diff --git a/modules/imgproc/test/test_templmatchmask.cpp b/modules/imgproc/test/test_templmatchmask.cpp index 3c8ab665ae..c9664cc406 100644 --- a/modules/imgproc/test/test_templmatchmask.cpp +++ b/modules/imgproc/test/test_templmatchmask.cpp @@ -275,4 +275,16 @@ INSTANTIATE_TEST_CASE_P(MultiChannelMask, Imgproc_MatchTemplateWithMask2, Values(cv::TM_SQDIFF, cv::TM_SQDIFF_NORMED, cv::TM_CCORR, cv::TM_CCORR_NORMED, cv::TM_CCOEFF, cv::TM_CCOEFF_NORMED))); +TEST(Imgproc_MatchTemplateWithMask, bug_26389) { + const Mat image = Mat::ones(Size(10, 10), CV_8UC1); + const Mat templ = Mat::ones(Size(10, 7), CV_8UC1); + const Mat mask = Mat::ones(Size(10, 7), CV_8UC1); + + for (const int method : {TM_CCOEFF, TM_CCOEFF_NORMED}) + { + Mat result; + matchTemplate(image, templ, result, method, mask); + } +} + }} // namespace diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index f6e85ea99a..7aca0e84a2 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -1015,6 +1015,9 @@ class JavaWrapperGenerator(object): ret = "return (jlong) _retval_;" elif type_dict[fi.ctype]["jni_type"] == "jdoubleArray": ret = "return _da_retval_;" + elif "jni_var" in type_dict[ret_type]: + c_epilogue.append(type_dict[ret_type]["jni_var"] % {"n" : '_retval_'}) + ret = f"return {type_dict[ret_type]['jni_name'] % {'n' : '_retval_'}};" # hack: replacing func call with property set/get name = fi.name diff --git a/modules/js/CMakeLists.txt b/modules/js/CMakeLists.txt index 6d48bf7f71..3988bfac65 100644 --- a/modules/js/CMakeLists.txt +++ b/modules/js/CMakeLists.txt @@ -34,6 +34,47 @@ if(NOT EMSCRIPTEN_INCLUDE_DIR OR NOT PYTHON_DEFAULT_AVAILABLE) ocv_module_disable(js) endif() +# Get Emscripten version from emscripten/version.h +unset(EMSCRIPTEN_VERSION) +unset(EMSCRIPTEN_VERSION_CONTENTS) +unset(EMSCRIPTEN_VERSION_MAJOR) +unset(EMSCRIPTEN_VERSION_MINOR) +unset(EMSCRIPTEN_VERSION_TINY) + +set(EMSCRIPTEN_VERSION_PATH "${EMSCRIPTEN_INCLUDE_DIR}/emscripten/version.h") +if(NOT EXISTS "${EMSCRIPTEN_VERSION_PATH}") + message(STATUS "${EMSCRIPTEN_INCLUDE_DIR}/emscripten/version.h is missing") +else() + file(STRINGS "${EMSCRIPTEN_VERSION_PATH}" EMSCRIPTEN_VERSION_CONTENTS REGEX "^#define[ \t]+__EMSCRIPTEN_[a-z]+__[ \t][0-9]+") + if(NOT EMSCRIPTEN_VERSION_CONTENTS) + message(STATUS "${EMSCRIPTEN_INCLUDE_DIR}/emscripten/version.h is exists, but is not readable") + else() + if(EMSCRIPTEN_VERSION_CONTENTS MATCHES "__EMSCRIPTEN_major__[ \t]+([0-9]+)") + set(EMSCRIPTEN_VERSION_MAJOR "${CMAKE_MATCH_1}") + endif() + if(EMSCRIPTEN_VERSION_CONTENTS MATCHES "__EMSCRIPTEN_minor__[ \t]+([0-9]+)") + set(EMSCRIPTEN_VERSION_MINOR "${CMAKE_MATCH_1}") + endif() + if(EMSCRIPTEN_VERSION_CONTENTS MATCHES "__EMSCRIPTEN_tiny__[ \t]+([0-9]+)") + set(EMSCRIPTEN_VERSION_TINY "${CMAKE_MATCH_1}") + endif() + + # When version(major/minor/tiny) is 0, "if(version)" is failed. + # "if(version GREATER_EQUAL 0)" can compare version as numeric. + if( (EMSCRIPTEN_VERSION_MAJOR GREATER_EQUAL "0") AND + (EMSCRIPTEN_VERSION_MINOR GREATER_EQUAL "0") AND + (EMSCRIPTEN_VERSION_TINY GREATER_EQUAL "0") + ) + set(EMSCRIPTEN_VERSION "${EMSCRIPTEN_VERSION_MAJOR}.${EMSCRIPTEN_VERSION_MINOR}.${EMSCRIPTEN_VERSION_TINY}") + message(STATUS "js: Emscripten version = ${EMSCRIPTEN_VERSION}") + else() + message(STATUS "js: Emscripten version is not able to parsed") + message(AUTHOR_WARNING "EMSCRIPTEN_VERSION_CONTENTS = ${EMSCRIPTEN_VERSION_CONTENTS}") + endif() + endif() +endif() + + ocv_add_module(js BINDINGS PRIVATE_REQUIRED opencv_js_bindings_generator) ocv_module_include_directories(${EMSCRIPTEN_INCLUDE_DIR}) @@ -71,7 +112,12 @@ endif() set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s TOTAL_MEMORY=128MB -s WASM_MEM_MAX=1GB -s ALLOW_MEMORY_GROWTH=1") set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s MODULARIZE=1") -set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s EXPORT_NAME=\"'cv'\" -s DEMANGLE_SUPPORT=1") +set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s EXPORT_NAME=\"'cv'\"") +# See https://github.com/opencv/opencv/issues/27513 +# DEMANGLE_SUPPRT is deprecated at Emscripten 3.1.54 and later. +if(NOT EMSCRIPTEN_VERSION OR EMSCRIPTEN_VERSION VERSION_LESS "3.1.54") + set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s DEMANGLE_SUPPORT=1") +endif() set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s FORCE_FILESYSTEM=1 --use-preload-plugins --bind --post-js ${JS_HELPER} ${COMPILE_FLAGS}") set_target_properties(${the_module} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index fb3256515d..22c3810292 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -113,7 +113,8 @@ public: }; enum ECIEncodings { - ECI_UTF8 = 26 + ECI_SHIFT_JIS = 20, + ECI_UTF8 = 26, }; /** @brief QR code encoder parameters. */ @@ -194,6 +195,13 @@ public: */ CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(), OutputArray straight_qrcode = noArray()); + + /** @brief Returns a kind of encoding for the decoded info from the latest @ref decode or @ref detectAndDecode call + @param codeIdx an index of the previously decoded QR code. + When @ref decode or @ref detectAndDecode is used, valid value is zero. + For @ref decodeMulti or @ref detectAndDecodeMulti use indices corresponding to the output order. + */ + CV_WRAP QRCodeEncoder::ECIEncodings getEncoding(int codeIdx = 0); }; /** @brief QR code detector based on Aruco markers detection code. */ diff --git a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp index e10cb3f025..e9681521d3 100644 --- a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp @@ -16,6 +16,7 @@ struct CV_EXPORTS_W_SIMPLE CharucoParameters { CV_WRAP CharucoParameters() { minMarkers = 2; tryRefineMarkers = false; + checkMarkers = true; } /// cameraMatrix optional 3x3 floating-point camera matrix CV_PROP_RW Mat cameraMatrix; @@ -28,6 +29,9 @@ struct CV_EXPORTS_W_SIMPLE CharucoParameters { /// try to use refine board, default false CV_PROP_RW bool tryRefineMarkers; + + /// run check to verify that markers belong to the same board, default true + CV_PROP_RW bool checkMarkers; }; class CV_EXPORTS_W CharucoDetector : public Algorithm { diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index ed697c50c0..adc52379b9 100644 --- a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp @@ -73,6 +73,17 @@ public: */ CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), OutputArrayOfArrays straight_code = noArray()) const; + +#ifdef OPENCV_BINDINGS_PARSER + CV_WRAP_AS(detectAndDecodeBytes) NativeByteArray detectAndDecode(InputArray img, OutputArray points = noArray(), + OutputArray straight_code = noArray()) const; + CV_WRAP_AS(decodeBytes) NativeByteArray decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const; + CV_WRAP_AS(decodeBytesMulti) bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector& decoded_info, + OutputArrayOfArrays straight_code = noArray()) const; + CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), + OutputArrayOfArrays straight_code = noArray()) const; +#endif + struct Impl; protected: Ptr p; diff --git a/modules/objdetect/misc/java/filelist_common b/modules/objdetect/misc/java/filelist_common new file mode 100644 index 0000000000..56da1c5df7 --- /dev/null +++ b/modules/objdetect/misc/java/filelist_common @@ -0,0 +1 @@ +misc/java/src/cpp/objdetect_converters.hpp diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json new file mode 100644 index 0000000000..376c7ad920 --- /dev/null +++ b/modules/objdetect/misc/java/gen_dict.json @@ -0,0 +1,69 @@ +{ + "ManualFuncs" : { + "QRCodeEncoder" : { + "QRCodeEncoder" : { + "j_code" : [ + "\n", + "/** Generates QR code from input string.", + "@param encoded_info Input bytes to encode.", + "@param qrcode Generated QR code.", + "*/", + "public void encode(byte[] encoded_info, Mat qrcode) {", + " encode_1(nativeObj, encoded_info, qrcode.nativeObj);", + "}", + "\n" + ], + "jn_code": [ + "\n", + "private static native void encode_1(long nativeObj, byte[] encoded_info, long qrcode_nativeObj);", + "\n" + ], + "cpp_code": [ + "//", + "// void cv::QRCodeEncoder::encode(String encoded_info, Mat& qrcode)", + "//", + "\n", + "JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11 (JNIEnv*, jclass, jlong, jbyteArray, jlong);", + "\n", + "JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11", + "(JNIEnv* env, jclass , jlong self, jbyteArray encoded_info, jlong qrcode_nativeObj)", + "{", + "", + " static const char method_name[] = \"objdetect::encode_11()\";", + " try {", + " LOGD(\"%s\", method_name);", + " Ptr* me = (Ptr*) self; //TODO: check for NULL", + " const char* n_encoded_info = reinterpret_cast(env->GetByteArrayElements(encoded_info, NULL));", + " const jsize n_encoded_info_size = env->GetArrayLength(encoded_info);", + " Mat& qrcode = *((Mat*)qrcode_nativeObj);", + " (*me)->encode( std::string(n_encoded_info, n_encoded_info_size), qrcode );", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + "}", + "\n" + ] + } + } + }, + "type_dict": { + "NativeByteArray": { + "j_type" : "byte[]", + "jn_type": "byte[]", + "jni_type": "jbyteArray", + "jni_name": "n_%(n)s", + "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast(%(n)s.size()), reinterpret_cast(%(n)s.c_str()));", + "cast_from": "std::string" + }, + "vector_NativeByteArray": { + "j_type": "List", + "jn_type": "List", + "jni_type": "jobject", + "jni_var": "std::vector< std::string > %(n)s", + "suffix": "Ljava_util_List", + "v_type": "vector_NativeByteArray" + } + } +} diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp new file mode 100644 index 0000000000..3f9f533769 --- /dev/null +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp @@ -0,0 +1,20 @@ +#include "objdetect_converters.hpp" + +#define LOG_TAG "org.opencv.objdetect" + +void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list) +{ + static jclass juArrayList = ARRAYLIST(env); + jmethodID m_clear = LIST_CLEAR(env, juArrayList); + jmethodID m_add = LIST_ADD(env, juArrayList); + + env->CallVoidMethod(list, m_clear); + for (std::vector::iterator it = vs.begin(); it != vs.end(); ++it) + { + jsize sz = static_cast((*it).size()); + jbyteArray element = env->NewByteArray(sz); + env->SetByteArrayRegion(element, 0, sz, reinterpret_cast((*it).c_str())); + env->CallBooleanMethod(list, m_add, element); + env->DeleteLocalRef(element); + } +} diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp new file mode 100644 index 0000000000..82bb881fad --- /dev/null +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp @@ -0,0 +1,14 @@ +// 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 OBJDETECT_CONVERTERS_HPP +#define OBJDETECT_CONVERTERS_HPP + +#include +#include "opencv_java.hpp" +#include "opencv2/core.hpp" + +void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list); + +#endif /* OBJDETECT_CONVERTERS_HPP */ diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index af567cbc04..225c8c6610 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -2,13 +2,19 @@ package org.opencv.test.objdetect; import java.util.List; import org.opencv.core.Mat; +import org.opencv.core.Size; import org.opencv.objdetect.QRCodeDetector; +import org.opencv.objdetect.QRCodeEncoder; +import org.opencv.objdetect.QRCodeEncoder_Params; import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; import org.opencv.test.OpenCVTestCase; import java.util.Arrays; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; public class QRCodeDetectorTest extends OpenCVTestCase { @@ -50,4 +56,26 @@ public class QRCodeDetectorTest extends OpenCVTestCase { List < String > expectedResults = Arrays.asList("SKIP", "EXTRA", "TWO STEPS FORWARD", "STEP BACK", "QUESTION", "STEP FORWARD"); assertEquals(new HashSet(output), new HashSet(expectedResults)); } + + public void testKanji() { + byte[] inp = new byte[]{(byte)0x82, (byte)0xb1, (byte)0x82, (byte)0xf1, (byte)0x82, (byte)0xc9, (byte)0x82, + (byte)0xbf, (byte)0x82, (byte)0xcd, (byte)0x90, (byte)0xa2, (byte)0x8a, (byte)0x45}; + QRCodeEncoder_Params params = new QRCodeEncoder_Params(); + params.set_mode(QRCodeEncoder.MODE_KANJI); + QRCodeEncoder encoder = QRCodeEncoder.create(params); + + Mat qrcode = new Mat(); + encoder.encode(inp, qrcode); + Imgproc.resize(qrcode, qrcode, new Size(0, 0), 2, 2, Imgproc.INTER_NEAREST); + + QRCodeDetector detector = new QRCodeDetector(); + byte[] output = detector.detectAndDecodeBytes(qrcode); + assertEquals(detector.getEncoding(), QRCodeEncoder.ECI_SHIFT_JIS); + assertArrayEquals(inp, output); + + List < byte[] > outputs = new ArrayList< byte[] >(); + assertTrue(detector.detectAndDecodeBytesMulti(qrcode, outputs)); + assertEquals(detector.getEncoding(0), QRCodeEncoder.ECI_SHIFT_JIS); + assertArrayEquals(inp, outputs.get(0)); + } } diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 57c624de86..02a4945a83 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -8,4 +8,31 @@ typedef std::vector> vector_Ptr_CChecker; typedef dnn::Net dnn_Net; #endif +class NativeByteArray +{ +public: + inline NativeByteArray& operator=(const std::string& from) { + val = from; + return *this; + } + std::string val; +}; + +class vector_NativeByteArray : public std::vector {}; + +template<> +PyObject* pyopencv_from(const NativeByteArray& from) +{ + return PyBytes_FromStringAndSize(from.val.c_str(), from.val.size()); +} + +template<> +PyObject* pyopencv_from(const vector_NativeByteArray& results) +{ + PyObject* list = PyList_New(results.size()); + for(size_t i = 0; i < results.size(); ++i) + PyList_SetItem(list, i, PyBytes_FromStringAndSize(results[i].c_str(), results[i].size())); + return list; +} + #endif diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index 0237900572..8da95ccd00 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #!/usr/bin/env python ''' =============================================================================== @@ -8,7 +9,7 @@ import os import numpy as np import cv2 as cv -from tests_common import NewOpenCVTests +from tests_common import NewOpenCVTests, unittest class qrcode_detector_test(NewOpenCVTests): @@ -50,3 +51,36 @@ class qrcode_detector_test(NewOpenCVTests): self.assertTrue("STEP BACK" in decoded_data) self.assertTrue("QUESTION" in decoded_data) self.assertEqual(points.shape, (6, 4, 2)) + + def test_decode_non_ascii(self): + import sys + if sys.version_info[0] < 3: + raise unittest.SkipTest('Python 2.x is not supported') + + img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/umlaut.png')) + self.assertFalse(img is None) + detector = cv.QRCodeDetector() + decoded_data, _, _ = detector.detectAndDecode(img) + self.assertTrue(isinstance(decoded_data, str)) + self.assertTrue("Müllheimstrasse" in decoded_data) + + def test_kanji(self): + inp = "こんにちは世界" + inp_bytes = inp.encode("shift-jis") + + params = cv.QRCodeEncoder_Params() + params.mode = cv.QRCodeEncoder_MODE_KANJI + encoder = cv.QRCodeEncoder_create(params) + qrcode = encoder.encode(inp_bytes) + qrcode = cv.resize(qrcode, (0, 0), fx=2, fy=2, interpolation=cv.INTER_NEAREST) + + detector = cv.QRCodeDetector() + data, _, _ = detector.detectAndDecodeBytes(qrcode) + self.assertEqual(data, inp_bytes) + self.assertEqual(detector.getEncoding(), cv.QRCodeEncoder_ECI_SHIFT_JIS) + self.assertEqual(data.decode("shift-jis"), inp) + + _, data, _, _ = detector.detectAndDecodeBytesMulti(qrcode) + self.assertEqual(data[0], inp_bytes) + self.assertEqual(detector.getEncoding(0), cv.QRCodeEncoder_ECI_SHIFT_JIS) + self.assertEqual(data[0].decode("shift-jis"), inp) diff --git a/modules/objdetect/src/aruco/charuco_detector.cpp b/modules/objdetect/src/aruco/charuco_detector.cpp index 3220feac92..97f5711d5c 100644 --- a/modules/objdetect/src/aruco/charuco_detector.cpp +++ b/modules/objdetect/src/aruco/charuco_detector.cpp @@ -335,7 +335,7 @@ struct CharucoDetector::CharucoDetectorImpl { InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; detectBoard(image, charucoCorners, charucoIds, _markerCorners, _markerIds); - if (checkBoard(_markerCorners, _markerIds, charucoCorners, charucoIds) == false) { + if (charucoParameters.checkMarkers && checkBoard(_markerCorners, _markerIds, charucoCorners, charucoIds) == false) { CV_LOG_DEBUG(NULL, "ChArUco board is built incorrectly"); charucoCorners.release(); charucoIds.release(); diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 79fdf68aee..0d5d7a7be1 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -960,6 +960,7 @@ public: double epsX, epsY; mutable vector> alignmentMarkers; mutable vector updateQrCorners; + mutable vector encodings; bool useAlignmentMarkers = true; bool detect(InputArray in, OutputArray points) const override; @@ -975,6 +976,8 @@ public: String decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode); std::string detectAndDecodeCurved(InputArray in, OutputArray points, OutputArray straight_qrcode); + + QRCodeEncoder::ECIEncodings getEncoding(int codeIdx); }; QRCodeDetector::QRCodeDetector() { @@ -991,6 +994,13 @@ QRCodeDetector& QRCodeDetector::setEpsY(double epsY) { return *this; } +QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(int codeIdx) { + auto& encodings = std::dynamic_pointer_cast(p)->encodings; + CV_Assert(codeIdx >= 0); + CV_Assert(codeIdx < static_cast(encodings.size())); + return encodings[codeIdx]; +} + bool ImplContour::detect(InputArray in, OutputArray points) const { Mat inarr; @@ -1032,6 +1042,8 @@ public: uint8_t total_num = 1; } structure_info; + QRCodeEncoder::ECIEncodings eci; + protected: double getNumModules(); Mat getHomography() { @@ -2799,7 +2811,6 @@ static std::string encodeUTF8_bytesarray(const uint8_t* str, const size_t size) bool QRDecode::decodingProcess() { - QRCodeEncoder::ECIEncodings eci; const uint8_t* payload; size_t payload_len; @@ -2858,7 +2869,7 @@ bool QRDecode::decodingProcess() return true; case QRCodeEncoder::EncodeMode::MODE_KANJI: // FIXIT BUG: we must return UTF-8 compatible string - CV_LOG_WARNING(NULL, "QR: Kanji is not supported properly"); + eci = QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS; result_info.assign((const char*)payload, payload_len); return true; case QRCodeEncoder::EncodeMode::MODE_ECI: @@ -2929,6 +2940,7 @@ std::string ImplContour::decode(InputArray in, InputArray points, OutputArray st alignmentMarkers = {qrdec.alignment_coords}; updateQrCorners = qrdec.getOriginalPoints(); } + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -2962,6 +2974,7 @@ String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray s { qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1); } + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -4074,20 +4087,22 @@ bool ImplContour::decodeMulti( straight_qrcode.assign(tmp_straight_qrcodes); } - decoded_info.clear(); + decoded_info.resize(info.size()); + encodings.resize(info.size()); for (size_t i = 0; i < info.size(); i++) { auto& decoder = qrdec[i]; + encodings[i] = decoder.eci; if (!decoder.isStructured()) { - decoded_info.push_back(info[i]); + decoded_info[i] = info[i]; continue; } // Store final message corresponding to 0-th code in a sequence. if (decoder.structure_info.sequence_num != 0) { - decoded_info.push_back(""); + decoded_info[i] = ""; continue; } @@ -4108,7 +4123,7 @@ bool ImplContour::decodeMulti( break; } } - decoded_info.push_back(decoded); + decoded_info[i] = decoded; } alignmentMarkers.resize(src_points.size()); diff --git a/modules/objdetect/test/test_qrcode_encode.cpp b/modules/objdetect/test/test_qrcode_encode.cpp index f6cf1c069f..f90af1d9f9 100644 --- a/modules/objdetect/test/test_qrcode_encode.cpp +++ b/modules/objdetect/test/test_qrcode_encode.cpp @@ -343,9 +343,11 @@ TEST(Objdetect_QRCode_Encode_Kanji, regression) } Mat straight_barcode; - std::string decoded_info = QRCodeDetector().decode(resized_src, corners, straight_barcode); + QRCodeDetector detector; + std::string decoded_info = detector.decode(resized_src, corners, straight_barcode); EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded."; EXPECT_EQ(input_info, decoded_info); + EXPECT_EQ(detector.getEncoding(), QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS); } } diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index 8c1db67303..be017ce63b 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -99,7 +99,8 @@ def evaluate_conditional_inclusion_directive(directive, preprocessor_definitions ... ValueError: Failed to evaluate '#if strangedefinedvar' directive, stripped down to 'strangedefinedvar' """ - OPERATORS = { "!": "not ", "&&": "and", "&": "and", "||": "or", "|": "or" } + OPERATORS1 = {"&&": "and", "||": "or"} + OPERATORS2 = { "!": "not ", "&": "and", "|": "or" } input_directive = directive @@ -136,7 +137,10 @@ def evaluate_conditional_inclusion_directive(directive, preprocessor_definitions directive ) - for src_op, dst_op in OPERATORS.items(): + for src_op, dst_op in OPERATORS1.items(): + directive = directive.replace(src_op, dst_op) + + for src_op, dst_op in OPERATORS2.items(): directive = directive.replace(src_op, dst_op) try: diff --git a/modules/python/src2/pycompat.hpp b/modules/python/src2/pycompat.hpp index 05a3909562..c936f5e66a 100644 --- a/modules/python/src2/pycompat.hpp +++ b/modules/python/src2/pycompat.hpp @@ -84,6 +84,15 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str) } Py_XDECREF(bytes); } + else if (PyBytes_Check(obj)) + { + const char * raw = PyBytes_AsString(obj); + if (raw) + { + str = std::string(raw); + res = true; + } + } #if PY_MAJOR_VERSION < 3 else if (PyString_Check(obj)) { diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py index 909ee9061e..6595977cb7 100644 --- a/modules/python/src2/typing_stubs_generation/api_refinement.py +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -340,6 +340,7 @@ NODES_TO_REFINE = { SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"), SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"), SymbolName(("cv", ), (), "floodFill"): make_optional_arg("mask"), + SymbolName(("cv", ), ("Feature2D", ), "detectAndCompute"): make_optional_arg("mask"), SymbolName(("cv", ), (), "imread"): make_optional_none_return, SymbolName(("cv", ), (), "imdecode"): make_optional_none_return, } diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index 5d960dd07b..d3f2b8192c 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -265,6 +265,7 @@ _PREDEFINED_TYPES = ( export_name="ExtractMetaCallback", required_modules=("gapi",) ), + PrimitiveTypeNode("NativeByteArray", "bytes"), ) PREDEFINED_TYPES = dict( diff --git a/modules/python/test/test_imread.py b/modules/python/test/test_imread.py index b5f286d426..471c786acc 100644 --- a/modules/python/test/test_imread.py +++ b/modules/python/test/test_imread.py @@ -22,6 +22,18 @@ class imread_test(NewOpenCVTests): cv.imread(path, img) self.assertEqual(cv.norm(ref, img, cv.NORM_INF), 0.0) + def test_imread_with_meta(self): + path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.jpg' + img, meta_types, meta_data = cv.imreadWithMetadata(path) + self.assertTrue(img is not None) + self.assertTrue(meta_types is not None) + self.assertTrue(meta_data is not None) + + path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.png' + img, meta_types, meta_data = cv.imreadWithMetadata(path) + self.assertTrue(img is not None) + self.assertTrue(meta_types is not None) + self.assertTrue(meta_data is not None) if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/ts/include/opencv2/ts/ts_ext.hpp b/modules/ts/include/opencv2/ts/ts_ext.hpp index 66e12d77d6..f6145a0993 100644 --- a/modules/ts/include/opencv2/ts/ts_ext.hpp +++ b/modules/ts/include/opencv2/ts/ts_ext.hpp @@ -128,7 +128,7 @@ struct SkipThisTest : public ::testing::Test { } \ // Special type of tests which require / use or validate processing of huge amount of data (>= 2Gb) -#if defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__) +#if defined(_M_X64) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__x86_64__) || defined(__aarch64__) #define BIGDATA_TEST(test_case_name, test_name) TEST_(BigData_ ## test_case_name, test_name, ::testing::Test, Body,, CV__TEST_BIGDATA_BODY_IMPL) #else #define BIGDATA_TEST(test_case_name, test_name) TEST_(BigData_ ## test_case_name, DISABLED_ ## test_name, ::testing::Test, Body,, CV__TEST_BIGDATA_BODY_IMPL) diff --git a/modules/videoio/misc/java/src/cpp/videoio_converters.hpp b/modules/videoio/misc/java/src/cpp/videoio_converters.hpp index d1ec43e2be..d1bfe6e0f4 100644 --- a/modules/videoio/misc/java/src/cpp/videoio_converters.hpp +++ b/modules/videoio/misc/java/src/cpp/videoio_converters.hpp @@ -4,7 +4,7 @@ #include #include "opencv_java.hpp" #include "opencv2/core.hpp" -#include "opencv2/videoio/videoio.hpp" +#include "opencv2/videoio.hpp" class JavaStreamReader : public cv::IStreamReader { diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 9cd20fbf77..489dbe565d 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -2080,6 +2080,7 @@ void CvCapture_FFMPEG::seek(int64_t _frame_number) else { frame_number = 0; + picture_pts = AV_NOPTS_VALUE_; break; } } @@ -2097,27 +2098,14 @@ bool CvCapture_FFMPEG::setProperty( int property_id, double value ) switch( property_id ) { case CAP_PROP_POS_MSEC: + seek(value/1000.0); + return true; case CAP_PROP_POS_FRAMES: + seek((int64_t)value); + return true; case CAP_PROP_POS_AVI_RATIO: - { - switch( property_id ) - { - case CAP_PROP_POS_FRAMES: - seek((int64_t)value); - break; - - case CAP_PROP_POS_MSEC: - seek(value/1000.0); - break; - - case CAP_PROP_POS_AVI_RATIO: - seek((int64_t)(value*ic->duration)); - break; - } - - picture_pts=(int64_t)value; - } - break; + seek((int64_t)(value*ic->duration)); + return true; case CAP_PROP_FORMAT: if (value == -1) return setRaw(); diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index eeb0835078..3021bdfa4e 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -1030,4 +1030,31 @@ inline static std::string videoio_ffmpeg_mismatch_name_printer(const testing::Te INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_channel_mismatch, testing::ValuesIn(mismatch_cases), videoio_ffmpeg_mismatch_name_printer); +// PR: https://github.com/opencv/opencv/pull/27523 +// TODO: Enable the tests back on Windows after FFmpeg plugin rebuild +#ifndef _WIN32 + +// related issue: https://github.com/opencv/opencv/issues/23088 +TEST(ffmpeg_cap_properties, set_pos_get_msec) +{ + if (!videoio_registry::hasBackend(CAP_FFMPEG)) + throw SkipTestException("FFmpeg backend was not found"); + + string video_file = findDataFile("video/big_buck_bunny.mp4"); + VideoCapture cap; + EXPECT_NO_THROW(cap.open(video_file, CAP_FFMPEG)); + ASSERT_TRUE(cap.isOpened()) << "Can't open the video"; + + cap.set(CAP_PROP_POS_FRAMES, 25); + EXPECT_EQ(cap.get(CAP_PROP_POS_MSEC), 1000.0); + + cap.set(CAP_PROP_POS_MSEC, 525); + EXPECT_EQ(cap.get(CAP_PROP_POS_MSEC), 500.0); + + cap.set(CAP_PROP_POS_AVI_RATIO, 0); + EXPECT_EQ(cap.get(CAP_PROP_POS_MSEC), 0.0); +} + +#endif // WIN32 + }} // namespace diff --git a/samples/cpp/morphology2.cpp b/samples/cpp/morphology2.cpp index f1d8d15b17..e6bb659cac 100644 --- a/samples/cpp/morphology2.cpp +++ b/samples/cpp/morphology2.cpp @@ -12,12 +12,13 @@ static void help(char** argv) printf("\nShow off image morphology: erosion, dialation, open and close\n" "Call:\n %s [image]\n" - "This program also shows use of rect, ellipse and cross kernels\n\n", argv[0]); + "This program also shows use of rect, ellipse, cross and diamond kernels\n\n", argv[0]); printf( "Hot keys: \n" "\tESC - quit the program\n" "\tr - use rectangle structuring element\n" "\te - use elliptic structuring element\n" "\tc - use cross-shaped structuring element\n" + "\td - use diamond-shaped structuring element\n" "\tSPACE - loop through all the options\n" ); } @@ -101,8 +102,10 @@ int main( int argc, char** argv ) element_shape = MORPH_RECT; else if( c == 'c' ) element_shape = MORPH_CROSS; + else if( c == 'd' ) + element_shape = MORPH_DIAMOND; else if( c == ' ' ) - element_shape = (element_shape + 1) % 3; + element_shape = (element_shape + 1) % 4; } return 0; diff --git a/samples/cpp/snippets/kmeans.cpp b/samples/cpp/snippets/kmeans.cpp index 0a2663ac7c..6c4a33b2e9 100644 --- a/samples/cpp/snippets/kmeans.cpp +++ b/samples/cpp/snippets/kmeans.cpp @@ -10,7 +10,7 @@ using namespace std; // { // cout << "\nThis program demonstrates kmeans clustering.\n" // "It generates an image with random points, then assigns a random number of cluster\n" -// "centers and uses kmeans to move those cluster centers to their representitive location\n" +// "centers and uses kmeans to move those cluster centers to their representative location\n" // "Call\n" // "./kmeans\n" << endl; // } diff --git a/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp b/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp index dcbcf8d568..2e08385577 100644 --- a/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp +++ b/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp @@ -66,20 +66,20 @@ int main( int argc, char** argv ) Mat hist_base, hist_half_down, hist_test1, hist_test2; calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false ); - normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_base, hist_base, 1, 0, NORM_L1, -1, Mat() ); calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false ); - normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_half_down, hist_half_down, 1, 0, NORM_L1, -1, Mat() ); calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false ); - normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_test1, hist_test1, 1, 0, NORM_L1, -1, Mat() ); calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false ); - normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_test2, hist_test2, 1, 0, NORM_L1, -1, Mat() ); //! [Calculate the histograms for the HSV images] //! [Apply the histogram comparison methods] - for( int compare_method = 0; compare_method < 4; compare_method++ ) + for( int compare_method = 0; compare_method < 6; compare_method++ ) { double base_base = compareHist( hist_base, hist_base, compare_method ); double base_half = compareHist( hist_base, hist_half_down, compare_method ); diff --git a/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp b/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp index 33e006269d..6c30ff34ac 100644 --- a/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp +++ b/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp @@ -18,7 +18,7 @@ int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; -int const max_elem = 2; +int const max_elem = 3; int const max_kernel_size = 21; /** Function Headers */ @@ -47,7 +47,7 @@ int main( int argc, char** argv ) moveWindow( "Dilation Demo", src.cols, 0 ); /// Create Erosion Trackbar - createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo", + createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse \n 3: Diamond", "Erosion Demo", &erosion_elem, max_elem, Erosion ); @@ -56,7 +56,7 @@ int main( int argc, char** argv ) Erosion ); /// Create Dilation Trackbar - createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo", + createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse \n 3: Diamond", "Dilation Demo", &dilation_elem, max_elem, Dilation ); @@ -83,6 +83,7 @@ void Erosion( int, void* ) if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; } else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; } else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; } + else if( erosion_elem == 3) { erosion_type = MORPH_DIAMOND; } //![kernel] Mat element = getStructuringElement( erosion_type, @@ -106,6 +107,7 @@ void Dilation( int, void* ) if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; } else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; } else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; } + else if( dilation_elem == 3) { dilation_type = MORPH_DIAMOND; } Mat element = getStructuringElement( dilation_type, Size( 2*dilation_size + 1, 2*dilation_size+1 ), diff --git a/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp b/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp index 3bc40d3b0d..bab4489b7e 100644 --- a/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp +++ b/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp @@ -18,7 +18,7 @@ int morph_elem = 0; int morph_size = 0; int morph_operator = 0; int const max_operator = 4; -int const max_elem = 2; +int const max_elem = 3; int const max_kernel_size = 21; const char* window_name = "Morphology Transformations Demo"; @@ -54,7 +54,7 @@ int main( int argc, char** argv ) //![create_trackbar2] /// Create Trackbar to select kernel type - createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name, + createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond", window_name, &morph_elem, max_elem, Morphology_Operations ); //![create_trackbar2] diff --git a/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu b/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu index 51f246a37d..e9803994f8 100644 --- a/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu +++ b/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu @@ -55,7 +55,7 @@ int main(void) thrust::sequence(idxBegin, idxEnd); // Fill the key channel with random numbers between 0 and 10. A counting iterator is used here to give an integer value for each location as an input to prg::operator() thrust::transform(thrust::make_counting_iterator(0), thrust::make_counting_iterator(d_data.cols), keyBegin, prg(0, 10)); - // Sort the key channel and index channel such that the keys and indecies stay together + // Sort the key channel and index channel such that the keys and indices stay together thrust::sort_by_key(keyBegin, keyEnd, idxBegin); cv::Mat h_idx(d_data); diff --git a/samples/directx/d3d11_interop.cpp b/samples/directx/d3d11_interop.cpp index 6c55a0c48b..1869f9e31c 100644 --- a/samples/directx/d3d11_interop.cpp +++ b/samples/directx/d3d11_interop.cpp @@ -373,7 +373,7 @@ public: r = m_pD3D11SwapChain->Present(0, 0); if (FAILED(r)) { - throw std::runtime_error("switch betweem fronat and back buffers failed!"); + throw std::runtime_error("switch between fronat and back buffers failed!"); } } // try diff --git a/samples/dnn/person_reid.py b/samples/dnn/person_reid.py index 8ea4345335..5493fc6afb 100644 --- a/samples/dnn/person_reid.py +++ b/samples/dnn/person_reid.py @@ -267,4 +267,4 @@ def main(): if __name__ == '__main__': args = get_args_parser() - main() \ No newline at end of file + main() diff --git a/samples/gpu/morphology.cpp b/samples/gpu/morphology.cpp index b5e50caa3c..0ace6ddced 100644 --- a/samples/gpu/morphology.cpp +++ b/samples/gpu/morphology.cpp @@ -101,8 +101,12 @@ int App::run() element_shape = MORPH_CROSS; break; + case 'd': + element_shape = MORPH_DIAMOND; + break; + case ' ': - element_shape = (element_shape + 1) % 3; + element_shape = (element_shape + 1) % 4; break; } } @@ -113,13 +117,14 @@ void App::help() cout << "Show off image morphology: erosion, dialation, open and close \n"; cout << "Call: \n"; cout << " gpu-example-morphology [image] \n"; - cout << "This program also shows use of rect, ellipse and cross kernels \n" << endl; + cout << "This program also shows use of rect, ellipse, cross and diamond kernels \n" << endl; cout << "Hot keys: \n"; cout << "\tESC - quit the program \n"; cout << "\tr - use rectangle structuring element \n"; cout << "\te - use elliptic structuring element \n"; cout << "\tc - use cross-shaped structuring element \n"; + cout << "\td - use diamond-shaped structuring element \n"; cout << "\tSPACE - loop through all the options \n" << endl; } diff --git a/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java b/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java index fb65dc6d23..49370fa20b 100644 --- a/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java +++ b/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java @@ -52,23 +52,23 @@ class CompareHist { List hsvBaseList = Arrays.asList(hsvBase); Imgproc.calcHist(hsvBaseList, new MatOfInt(channels), new Mat(), histBase, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histBase, histBase, 0, 1, Core.NORM_MINMAX); + Core.normalize(histBase, histBase, 1, 0, Core.NORM_L1); List hsvHalfDownList = Arrays.asList(hsvHalfDown); Imgproc.calcHist(hsvHalfDownList, new MatOfInt(channels), new Mat(), histHalfDown, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histHalfDown, histHalfDown, 0, 1, Core.NORM_MINMAX); + Core.normalize(histHalfDown, histHalfDown, 1, 0, Core.NORM_L1); List hsvTest1List = Arrays.asList(hsvTest1); Imgproc.calcHist(hsvTest1List, new MatOfInt(channels), new Mat(), histTest1, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histTest1, histTest1, 0, 1, Core.NORM_MINMAX); + Core.normalize(histTest1, histTest1, 1, 0, Core.NORM_L1); List hsvTest2List = Arrays.asList(hsvTest2); Imgproc.calcHist(hsvTest2List, new MatOfInt(channels), new Mat(), histTest2, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histTest2, histTest2, 0, 1, Core.NORM_MINMAX); + Core.normalize(histTest2, histTest2, 1, 0, Core.NORM_L1); //! [Calculate the histograms for the HSV images] //! [Apply the histogram comparison methods] - for( int compareMethod = 0; compareMethod < 4; compareMethod++ ) { + for( int compareMethod = 0; compareMethod < 6; compareMethod++ ) { double baseBase = Imgproc.compareHist( histBase, histBase, compareMethod ); double baseHalf = Imgproc.compareHist( histBase, histHalfDown, compareMethod ); double baseTest1 = Imgproc.compareHist( histBase, histTest1, compareMethod ); diff --git a/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java b/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java index 084fc9544d..8f11965995 100644 --- a/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java +++ b/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java @@ -23,7 +23,7 @@ import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; public class MorphologyDemo1 { - private static final String[] ELEMENT_TYPE = { "Rectangle", "Cross", "Ellipse" }; + private static final String[] ELEMENT_TYPE = { "Rectangle", "Cross", "Ellipse", "Diamond" }; private static final String[] MORPH_OP = { "Erosion", "Dilatation" }; private static final int MAX_KERNEL_SIZE = 21; private Mat matImgSrc; @@ -79,6 +79,8 @@ public class MorphologyDemo1 { elementType = Imgproc.MORPH_CROSS; } else if (cb.getSelectedIndex() == 2) { elementType = Imgproc.MORPH_ELLIPSE; + } else if (cb.getSelectedIndex() == 3) { + elementType = Imgproc.MORPH_DIAMOND; } update(); } diff --git a/samples/python/morphology.py b/samples/python/morphology.py index fb17b5e74c..8f1d5d8d59 100755 --- a/samples/python/morphology.py +++ b/samples/python/morphology.py @@ -35,7 +35,7 @@ def main(): cv.imshow('original', img) modes = cycle(['erode/dilate', 'open/close', 'blackhat/tophat', 'gradient']) - str_modes = cycle(['ellipse', 'rect', 'cross']) + str_modes = cycle(['ellipse', 'rect', 'cross', 'diamond']) cur_mode = next(modes) cur_str_mode = next(str_modes) diff --git a/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py b/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py index 08d0dc3564..5a070e09b9 100644 --- a/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py +++ b/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py @@ -45,20 +45,20 @@ channels = [0, 1] ## [Calculate the histograms for the HSV images] hist_base = cv.calcHist([hsv_base], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_base, hist_base, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_base, hist_base, alpha=1, beta=0, norm_type=cv.NORM_L1) hist_half_down = cv.calcHist([hsv_half_down], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_half_down, hist_half_down, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_half_down, hist_half_down, alpha=1, beta=0, norm_type=cv.NORM_L1) hist_test1 = cv.calcHist([hsv_test1], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_test1, hist_test1, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_test1, hist_test1, alpha=1, beta=0, norm_type=cv.NORM_L1) hist_test2 = cv.calcHist([hsv_test2], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_test2, hist_test2, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_test2, hist_test2, alpha=1, beta=0, norm_type=cv.NORM_L1) ## [Calculate the histograms for the HSV images] ## [Apply the histogram comparison methods] -for compare_method in range(4): +for compare_method in range(6): base_base = cv.compareHist(hist_base, hist_base, compare_method) base_half = cv.compareHist(hist_base, hist_half_down, compare_method) base_test1 = cv.compareHist(hist_base, hist_test1, compare_method) diff --git a/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py b/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py index 3645eab3d1..21b404ef46 100644 --- a/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py +++ b/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py @@ -5,9 +5,9 @@ import argparse src = None erosion_size = 0 -max_elem = 2 +max_elem = 3 max_kernel_size = 21 -title_trackbar_element_shape = 'Element:\n 0: Rect \n 1: Cross \n 2: Ellipse' +title_trackbar_element_shape = 'Element:\n 0: Rect \n 1: Cross \n 2: Ellipse \n 3: Diamond' title_trackbar_kernel_size = 'Kernel size:\n 2n +1' title_erosion_window = 'Erosion Demo' title_dilation_window = 'Dilation Demo' @@ -42,6 +42,8 @@ def morph_shape(val): return cv.MORPH_CROSS elif val == 2: return cv.MORPH_ELLIPSE + elif val == 3: + return cv.MORPH_DIAMOND ## [erosion] diff --git a/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py b/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py index e0fc758467..667fa50a30 100644 --- a/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py +++ b/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py @@ -5,10 +5,10 @@ import argparse morph_size = 0 max_operator = 4 -max_elem = 2 +max_elem = 3 max_kernel_size = 21 title_trackbar_operator_type = 'Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat' -title_trackbar_element_type = 'Element:\n 0: Rect - 1: Cross - 2: Ellipse' +title_trackbar_element_type = 'Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond' title_trackbar_kernel_size = 'Kernel size:\n 2n + 1' title_window = 'Morphology Transformations Demo' morph_op_dic = {0: cv.MORPH_OPEN, 1: cv.MORPH_CLOSE, 2: cv.MORPH_GRADIENT, 3: cv.MORPH_TOPHAT, 4: cv.MORPH_BLACKHAT} @@ -24,6 +24,8 @@ def morphology_operations(val): morph_elem = cv.MORPH_CROSS elif val_type == 2: morph_elem = cv.MORPH_ELLIPSE + elif val_type == 3: + morph_elem = cv.MORPH_DIAMOND element = cv.getStructuringElement(morph_elem, (2*morph_size + 1, 2*morph_size+1), (morph_size, morph_size)) operation = morph_op_dic[morph_operator] diff --git a/samples/sycl/CMakeLists.txt b/samples/sycl/CMakeLists.txt index 442ef4e3f0..b50df8420b 100644 --- a/samples/sycl/CMakeLists.txt +++ b/samples/sycl/CMakeLists.txt @@ -23,7 +23,7 @@ if(NOT SYCL_FOUND AND NOT OPENCV_SKIP_SAMPLES_SYCL_ONEDNN) if(NOT DEFINED DNNLROOT AND DEFINED ENV{DNNLROOT}) set(DNNLROOT "$ENV{DNNLROOT}") endif() - # Some verions of called script violate CMake policy and may emit unrecoverable CMake errors + # Some versions of called script violate CMake policy and may emit unrecoverable CMake errors # Use OPENCV_SKIP_SAMPLES_SYCL=1 / OPENCV_SKIP_SAMPLES_SYCL_ONEDNN to bypass this find_package(dnnl CONFIG QUIET HINTS "${DNNLROOT}") endif() diff --git a/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp b/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp index a2854d2382..64549112d3 100644 --- a/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp +++ b/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp @@ -1047,7 +1047,7 @@ done: // Create a partial media type from our list. // -// dwTypeIndex: Index into the list of peferred media types. +// dwTypeIndex: Index into the list of preferred media types. // ppmt: Receives a pointer to the media type. HRESULT OcvImageManipulations::OnGetPartialType(DWORD dwTypeIndex, IMFMediaType **ppmt)