diff --git a/cmake/OpenCVDetectCUDAUtils.cmake b/cmake/OpenCVDetectCUDAUtils.cmake index 13eaf874dd..ce00ef257f 100644 --- a/cmake/OpenCVDetectCUDAUtils.cmake +++ b/cmake/OpenCVDetectCUDAUtils.cmake @@ -437,7 +437,7 @@ endmacro() macro(ocv_check_cuda_delayed_load cuda_toolkit_root_dir) if(MSVC AND CUDA_ENABLE_DELAYLOAD) set(DELAYFLAGS "delayimp.lib") - file(GLOB CUDA_DLLS "${cuda_toolkit_root_dir}/bin/*.dll") + file(GLOB_RECURSE CUDA_DLLS "${cuda_toolkit_root_dir}/bin/*.dll") foreach(d ${CUDA_DLLS}) cmake_path(GET "d" FILENAME DLL_NAME) if(NOT ${DLL_NAME} MATCHES "cudart") diff --git a/doc/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.markdown b/doc/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.markdown index 4b7ff32e6d..d43cb104f9 100644 --- a/doc/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.markdown +++ b/doc/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.markdown @@ -52,7 +52,7 @@ cv.circle(img,(447,63), 63, (0,0,255), -1) ### Drawing Ellipse To draw the ellipse, we need to pass several arguments. One argument is the center location (x,y). -Next argument is axes lengths (major axis length, minor axis length). angle is the angle of rotation +Next argument is axes lengths (semi-major axis length, semi-minor axis length). angle is the angle of rotation of ellipse in anti-clockwise direction. startAngle and endAngle denotes the starting and ending of ellipse arc measured in clockwise direction from major axis. i.e. giving values 0 and 360 gives the full ellipse. For more details, check the documentation of **cv.ellipse()**. Below example draws a diff --git a/doc/tutorials/introduction/table_of_content_introduction.markdown b/doc/tutorials/introduction/table_of_content_introduction.markdown index 2f8303ad6e..d67dd20cbc 100644 --- a/doc/tutorials/introduction/table_of_content_introduction.markdown +++ b/doc/tutorials/introduction/table_of_content_introduction.markdown @@ -18,6 +18,7 @@ Introduction to OpenCV {#tutorial_table_of_content_introduction} - @subpage tutorial_windows_install - @subpage tutorial_windows_visual_studio_opencv - @subpage tutorial_windows_visual_studio_image_watch +- @subpage tutorial_windows_msys2_vscode ##### Java & Android - @subpage tutorial_java_dev_intro diff --git a/doc/tutorials/introduction/windows_msys2_vscode/window_install_opencv.markdown b/doc/tutorials/introduction/windows_msys2_vscode/window_install_opencv.markdown new file mode 100644 index 0000000000..a1c3bb7298 --- /dev/null +++ b/doc/tutorials/introduction/windows_msys2_vscode/window_install_opencv.markdown @@ -0,0 +1,270 @@ +Building OpenCV on Windows from Source Code using MSYS2 UCRT64 and VS Code (C++) {#tutorial_windows_msys2_vscode} +===================================================================== + +| | | +| -: | :- | +| Original author | Mahadev Kumar | +| Compatibility | OpenCV >= 4.x | + +@tableofcontents + +@note This tutorial was tested on Windows >= 7 using the MSYS2 UCRT64 environment. +@note The OpenCV team does not maintain MSYS/Cygwin configuration and does not regularly test it with continuous integration. + +--- + +## Introduction {#tutorial_windows_msys2_install_intro} + +This tutorial explains how to build OpenCV from source on Windows using the **MSYS2 UCRT64 toolchain** and use it inside **Visual Studio Code with C++**. + +The build configuration uses: + +- **MSYS2 (UCRT64 shell)** +- **GCC (UCRT toolchain)** +- **CMake** +- **mingw32-make** +- **VS Code** + +This method produces **native Windows binaries linked against the Universal C Runtime (UCRT)**. + +--- + +## Prerequisites {#tutorial_windows_install_msys2_prerequisites} + +Install the following software before proceeding: + +- [MSYS2](https://www.msys2.org) +- [Git](https://git-scm.com/install) +- [Visual Studio Code](https://code.visualstudio.com) + +After installing MSYS2, always open the: + +**MSYS2 UCRT64 Terminal** + +@note Do not use the `MSYS`, `MinGW64`, or `CLANG64` shells for this build. + +--- + +## Step 1: Update MSYS2 {#tutorial_windows_msys2_install_update} + +Open the **MSYS2 UCRT64** terminal and update the system. + +@code{.bash} +pacman -Syu +@endcode + +Restart the terminal if prompted. + +--- + +## Step 2: Install Required Packages {#tutorial_windows_msys2_install_packages} + +Install the required compiler and build tools. + +@code{.bash} +pacman -S mingw-w64-ucrt-x86_64-gcc \ + mingw-w64-ucrt-x86_64-cmake \ + mingw-w64-ucrt-x86_64-make +@endcode + +Verify installation: + +@code{.bash} +gcc --version +cmake --version +mingw32-make --version +@endcode + +@note **Add the following directory to your `Windows PATH`:** +@code{.bash} +C:\msys64\ucrt64\bin +@endcode + +--- + +## Step 3: Clone OpenCV Source Code {#tutorial_windows_msys2_install_clone} + +Clone `OpenCV` and optional `contrib modules`. + +@code{.bash} +git clone https://github.com/opencv/opencv.git +git clone https://github.com/opencv/opencv_contrib.git +@endcode + +--- + +## Step 4: Create Build Directory {#tutorial_windows_msys2_install_build_dir} + +@code{.bash} +cd opencv +mkdir build +cd build +@endcode + +--- + +## Step 5: Configure the Build with CMake {#tutorial_windows_msys2_install_configure} + +Run CMake using the **MinGW Makefiles generator**. + +@code{.bash} +cmake -G "MinGW Makefiles" ../ +@endcode + +@note If you do not want to use **opencv_contrib**, remove the `OPENCV_EXTRA_MODULES_PATH` option. + +--- + +## Step 6: Build OpenCV {#tutorial_windows_msys2_install_build} + +Compile OpenCV. + +@code{.bash} +mingw32-make -j6 +@endcode + +@note If the build fails due to memory limitations, reduce the job count. + +Example: + +@code{.bash} +mingw32-make -j4 +@endcode + +--- + +## Step 7: Install OpenCV {#tutorial_windows_msys2_install_install} + +Install the compiled libraries. + +@code{.bash} +mingw32-make install +@endcode + +After installation, OpenCV will be located in: +`opencv/build/install` + +Add the following directory to your **Windows PATH**: +@code{.bash} +C:\path\to\opencv\build\install\x64\mingw\bin +@endcode + +--- + +## Step 8: Verify with a C++ Example {#tutorial_windows_msys2_install_verify} + +- Create a folder outside the OpenCV source directory. +Example: `first-project` +- Inside the folder create **main.cpp** + +@code{.cpp} +#include +#include + +int main() +{ + std::cout << "OpenCV Version: " << CV_VERSION << std::endl; + + cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3); + + cv::imshow("OpenCV Test", img); + cv::waitKey(0); + + return 0; +} +@endcode + +- Create **CMakeLists.txt** + +@code{.cmake} +cmake_minimum_required(VERSION 3.10) + +project(OpenCVApp) + +set(OpenCV_DIR "C:/path/to/opencv/build/install/lib/cmake/opencv4") + +find_package(OpenCV REQUIRED) + +add_executable(app main.cpp) + +target_link_libraries(app ${OpenCV_LIBS}) +@endcode + +- Build the project. + +@code{.bash} +mkdir build && cd build +cmake -G "MinGW Makefiles" .. +mingw32-make +@endcode + +- Run the program. + +@code{.bash} +./app.exe +@endcode + +@note If everything is configured correctly, a window should appear displaying a blank image. + +--- + +## Using OpenCV in Visual Studio Code {#tutorial_windows_msys2_vscode_section} + +Install the following extensions in **VS Code**: + +- C/C++ +- CMake Tools + +Open your project folder in VS Code. + +Configure the project using CMake. + +@code{.bash} +cmake -G "MinGW Makefiles" .. +@endcode + +Build the project. + +@code{.bash} +mingw32-make +@endcode + +Run the executable. + +--- + +## Troubleshooting {#tutorial_windows_msys2_install_troubleshoot} + +### CMake cannot find OpenCV + +Ensure that `OpenCV_DIR` is set correctly. + +Example: + +@code{.cmake} +set(OpenCV_DIR "C:/path/to/opencv/build/install/lib/cmake/opencv4") +@endcode + +--- + +### mingw32-make not found + +Make sure the following path is added to the Windows environment variable **PATH**: `C:\msys64\ucrt64\bin` + +--- + +### Build fails due to memory limits + +Reduce parallel build jobs. + +@code{.bash} +mingw32-make -j2 +@endcode + +--- + +## Conclusion + +You have successfully built OpenCV from source using **MSYS2 UCRT64** and verified it with a **C++ project in VS Code**. + +This setup allows you to develop OpenCV-based C++ applications using a fully open-source toolchain on Windows. diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp index fe283c4271..531698c0e0 100644 --- a/hal/ipp/src/warp_ipp.cpp +++ b/hal/ipp/src/warp_ipp.cpp @@ -112,7 +112,7 @@ int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int 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; + IwTransDirection iwTransDirection = iwTransInverse; if((int)ippBorder == -1) return CV_HAL_ERROR_NOT_IMPLEMENTED; diff --git a/modules/3d/doc/pics/pinhole_homogeneous_transformation.jpg b/modules/3d/doc/pics/pinhole_homogeneous_transformation.jpg new file mode 100644 index 0000000000..00cb2702ca Binary files /dev/null and b/modules/3d/doc/pics/pinhole_homogeneous_transformation.jpg differ diff --git a/modules/3d/include/opencv2/3d.hpp b/modules/3d/include/opencv2/3d.hpp index 31b9bf5677..28a8f645f1 100644 --- a/modules/3d/include/opencv2/3d.hpp +++ b/modules/3d/include/opencv2/3d.hpp @@ -185,7 +185,7 @@ Z_w \\ The following figure illustrates the pinhole camera model. -![Pinhole camera model](pics/pinhole_camera_model.png) +![Pinhole camera model](pics/pinhole_camera_model.png) { width=70% } Real lenses usually have some distortion, mostly radial distortion, and slight tangential distortion. So, the above model is extended as: @@ -979,7 +979,7 @@ Check @ref tutorial_homography "the corresponding tutorial" for more details /** @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% } +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.jpg){ width=50% } @see @ref calib3d_solvePnP @@ -1049,7 +1049,7 @@ CV_EXPORTS_W bool solvePnP( InputArray objectPoints, InputArray imagePoints, /** @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% } +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.jpg){ width=50% } @see @ref calib3d_solvePnP @@ -1110,7 +1110,7 @@ CV_EXPORTS_W bool solvePnPRansac( InputArray objectPoints, InputArray imagePoint /** @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% } +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.jpg){ width=50% } @see @ref calib3d_solvePnP @@ -1207,7 +1207,7 @@ CV_EXPORTS_W void solvePnPRefineVVS( InputArray objectPoints, InputArray imagePo /** @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% } +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.jpg){ width=50% } @see @ref calib3d_solvePnP diff --git a/modules/calib/doc/pics/hand-eye_figure.png b/modules/calib/doc/pics/hand-eye_figure.png index ff41d33f8c..0d53be4bdf 100644 Binary files a/modules/calib/doc/pics/hand-eye_figure.png and b/modules/calib/doc/pics/hand-eye_figure.png differ diff --git a/modules/calib/doc/pics/pinhole_camera_model.png b/modules/calib/doc/pics/pinhole_camera_model.png index 111112b006..67415fe685 100644 Binary files a/modules/calib/doc/pics/pinhole_camera_model.png and b/modules/calib/doc/pics/pinhole_camera_model.png differ diff --git a/modules/calib/doc/pics/pinhole_homogeneous_transformation.png b/modules/calib/doc/pics/pinhole_homogeneous_transformation.png deleted file mode 100644 index f98e4ba217..0000000000 Binary files a/modules/calib/doc/pics/pinhole_homogeneous_transformation.png and /dev/null differ diff --git a/modules/calib/doc/pics/robot-world_hand-eye_figure.png b/modules/calib/doc/pics/robot-world_hand-eye_figure.png index 867bc81c73..26f5eca076 100644 Binary files a/modules/calib/doc/pics/robot-world_hand-eye_figure.png and b/modules/calib/doc/pics/robot-world_hand-eye_figure.png differ diff --git a/modules/calib/include/opencv2/calib.hpp b/modules/calib/include/opencv2/calib.hpp index 1436d123e8..5a06ad2af6 100644 --- a/modules/calib/include/opencv2/calib.hpp +++ b/modules/calib/include/opencv2/calib.hpp @@ -180,7 +180,7 @@ Z_w \\ The following figure illustrates the pinhole camera model. -![Pinhole camera model](pics/pinhole_camera_model.png) +![Pinhole camera model](pics/pinhole_camera_model.png) { width=70% } Real lenses usually have some distortion, mostly radial distortion, and slight tangential distortion. So, the above model is extended as: diff --git a/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp b/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp index 04b0c4c6cb..3c272d7862 100644 --- a/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp +++ b/modules/core/include/opencv2/core/parallel/backend/parallel_for.tbb.hpp @@ -13,7 +13,7 @@ #endif #include "tbb/tbb.h" #if !defined(TBB_INTERFACE_VERSION) -#error "Unknows/unsupported TBB version" +#error "Unknown/unsupported TBB version" #endif #if TBB_INTERFACE_VERSION >= 8000 diff --git a/modules/core/src/arithm_ipp.hpp b/modules/core/src/arithm_ipp.hpp index 33ade50918..ed722113a7 100644 --- a/modules/core/src/arithm_ipp.hpp +++ b/modules/core/src/arithm_ipp.hpp @@ -283,40 +283,24 @@ inline IppCmpOp arithm_ipp_convert_cmp(int cmpop) inline int arithm_ipp_cmp8u(const uchar* src1, size_t step1, const uchar* src2, size_t step2, uchar* dst, size_t step, int width, int height, int cmpop) { - // perf regression with AVX512: https://github.com/opencv/opencv/issues/28251 - if (ippCPUID_AVX512F&cv::ipp::getIppFeatures()) - return 0; - ARITHM_IPP_CMP(ippiCompare_8u_C1R, src1, (int)step1, src2, (int)step2, dst, (int)step, ippiSize(width, height)); } inline int arithm_ipp_cmp16u(const ushort* src1, size_t step1, const ushort* src2, size_t step2, uchar* dst, size_t step, int width, int height, int cmpop) { - // perf regression with AVX512: https://github.com/opencv/opencv/issues/28251 - if (ippCPUID_AVX512F&cv::ipp::getIppFeatures()) - return 0; - ARITHM_IPP_CMP(ippiCompare_16u_C1R, src1, (int)step1, src2, (int)step2, dst, (int)step, ippiSize(width, height)); } inline int arithm_ipp_cmp16s(const short* src1, size_t step1, const short* src2, size_t step2, uchar* dst, size_t step, int width, int height, int cmpop) { - // perf regression with AVX512: https://github.com/opencv/opencv/issues/28251 - if (ippCPUID_AVX512F&cv::ipp::getIppFeatures()) - return 0; - ARITHM_IPP_CMP(ippiCompare_16s_C1R, src1, (int)step1, src2, (int)step2, dst, (int)step, ippiSize(width, height)); } inline int arithm_ipp_cmp32f(const float* src1, size_t step1, const float* src2, size_t step2, uchar* dst, size_t step, int width, int height, int cmpop) { - // perf regression with AVX512: https://github.com/opencv/opencv/issues/28251 - if (ippCPUID_AVX512F&cv::ipp::getIppFeatures()) - return 0; - ARITHM_IPP_CMP(ippiCompare_32f_C1R, src1, (int)step1, src2, (int)step2, dst, (int)step, ippiSize(width, height)); } diff --git a/modules/core/src/count_non_zero.simd.hpp b/modules/core/src/count_non_zero.simd.hpp index 0acd75544f..9c61a54a4e 100644 --- a/modules/core/src/count_non_zero.simd.hpp +++ b/modules/core/src/count_non_zero.simd.hpp @@ -6,7 +6,7 @@ namespace cv { -typedef int (*CountNonZeroFunc)(const uchar*, int); +typedef int (*CountNonZeroFunc)(const void*, int); CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN @@ -32,8 +32,9 @@ static int countNonZero_(const T* src, int len ) #undef DEFINE_NONZERO_FUNC #define DEFINE_NONZERO_FUNC(funcname, suffix, ssuffix, T, VT, ST, cmp_op, add_op, update_sum, scalar_cmp_op) \ -static int funcname( const T* src, int len ) \ +static int funcname( const void* src_ptr, int len ) \ { \ + const T* src = static_cast(src_ptr); \ int i = 0, nz = 0; \ SIMD_ONLY( \ const int vlanes = VTraits::vlanes(); \ @@ -119,9 +120,9 @@ DEFINE_NONZERO_FUNC(countNonZero16f, u16, u32, ushort, v_uint16, v_uint32, VEC_C #undef DEFINE_NONZERO_FUNC_NOSIMD #define DEFINE_NONZERO_FUNC_NOSIMD(funcname, T) \ -static int funcname(const T* src, int len) \ +static int funcname(const void* src, int len) \ { \ - return countNonZero_(src, len); \ + return countNonZero_(static_cast(src), len); \ } DEFINE_NONZERO_FUNC_NOSIMD(countNonZero64s, int64) diff --git a/modules/core/src/matrix_transform.cpp b/modules/core/src/matrix_transform.cpp index 6cd342339a..b92c3197b7 100644 --- a/modules/core/src/matrix_transform.cpp +++ b/modules/core/src/matrix_transform.cpp @@ -1,7 +1,7 @@ // This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html - +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. #include "precomp.hpp" #include "opencl_kernels_core.hpp" #include "hal_replacement.hpp" @@ -285,40 +285,63 @@ void transposeND(InputArray src_, const std::vector& order, OutputArray dst } -#if CV_SIMD128 -template CV_ALWAYS_INLINE void flipHoriz_single( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size, size_t esz ) +// Generic scalar fallback +CV_ALWAYS_INLINE void flipHoriz_generic( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size, size_t esz ) +{ + int i, j, limit = (int)(((size.width + 1)/2)*esz); + int height = size.height; + AutoBuffer _tab(size.width*esz); + int* tab = _tab.data(); + + for( i = 0; i < size.width; i++ ) + for( size_t k = 0; k < esz; k++ ) + tab[i*esz + k] = (int)((size.width - i - 1)*esz + k); + + for( ; height--; src += sstep, dst += dstep ) + { + for( i = 0; i < limit; i++ ) + { + j = tab[i]; + uchar t0 = src[i], t1 = src[j]; + dst[i] = t1; dst[j] = t0; + } + } +} + +#if CV_SIMD || CV_SIMD_SCALABLE +template CV_ALWAYS_INLINE void flipHoriz_single( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size ) { typedef typename VTraits::lane_type T; - int end = (int)(size.width*esz); - int width = (end + 1)/2; - int width_1 = width & -VTraits::vlanes(); - int i, j; + const int vlanes = VTraits::vlanes(); + int end = (int)(size.width * sizeof(T)); + int width = (end + 1) / 2; + int width_simd = width & -vlanes; + int height = size.height; #if CV_STRONG_ALIGNMENT CV_Assert(isAligned(src, dst)); #endif - for( ; size.height--; src += sstep, dst += dstep ) + for( ; height--; src += sstep, dst += dstep ) { - for( i = 0, j = end; i < width_1; i += VTraits::vlanes(), j -= VTraits::vlanes() ) + int i = 0, j = end; + for( ; i < width_simd; i += vlanes, j -= vlanes ) { - V t0, t1; - - t0 = v_load((T*)((uchar*)src + i)); - t1 = v_load((T*)((uchar*)src + j - VTraits::vlanes())); + V t0 = vx_load((const T*)(src + i)); + V t1 = vx_load((const T*)(src + j - vlanes)); t0 = v_reverse(t0); t1 = v_reverse(t1); - v_store((T*)(dst + j - VTraits::vlanes()), t0); + v_store((T*)(dst + j - vlanes), t0); v_store((T*)(dst + i), t1); } + + // Scalar tail loop if (isAligned(src, dst)) { for ( ; i < width; i += sizeof(T), j -= sizeof(T) ) { - T t0, t1; - - t0 = *((T*)((uchar*)src + i)); - t1 = *((T*)((uchar*)src + j - sizeof(T))); + T t0 = *((const T*)(src + i)); + T t1 = *((const T*)(src + j - sizeof(T))); *((T*)(dst + j - sizeof(T))) = t0; *((T*)(dst + i)) = t1; } @@ -329,195 +352,194 @@ template CV_ALWAYS_INLINE void flipHoriz_single( const uchar* src, s { for (int k = 0; k < (int)sizeof(T); k++) { - uchar t0, t1; - - t0 = *((uchar*)src + i + k); - t1 = *((uchar*)src + j + k - sizeof(T)); - *(dst + j + k - sizeof(T)) = t0; - *(dst + i + k) = t1; + uchar t0 = src[i + k]; + uchar t1 = src[j + k - sizeof(T)]; + dst[j + k - sizeof(T)] = t0; + dst[i + k] = t1; } } } } } -template CV_ALWAYS_INLINE void flipHoriz_double( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size, size_t esz ) +// SIMD for C3 +template +CV_ALWAYS_INLINE void flipHoriz_c3( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size ) { - int end = (int)(size.width*esz); - int width = (end + 1)/2; + typedef typename VTraits::lane_type T; + const int vlanes = VTraits::vlanes(); + const int stride = 3 * sizeof(T); + int width = size.width; + int centre = (width + 1) / 2; + int width_simd = (centre / vlanes) * vlanes; + int height = size.height; -#if CV_STRONG_ALIGNMENT - CV_Assert(isAligned(src, dst)); - CV_Assert(isAligned(src, dst)); -#endif - - for( ; size.height--; src += sstep, dst += dstep ) + for( ; height--; src += sstep, dst += dstep ) { - for ( int i = 0, j = end; i < width; i += sizeof(T1) + sizeof(T2), j -= sizeof(T1) + sizeof(T2) ) + int i = 0; + for( ; i < width_simd; i += vlanes ) { - T1 t0, t1; - T2 t2, t3; - - t0 = *((T1*)((uchar*)src + i)); - t2 = *((T2*)((uchar*)src + i + sizeof(T1))); - t1 = *((T1*)((uchar*)src + j - sizeof(T1) - sizeof(T2))); - t3 = *((T2*)((uchar*)src + j - sizeof(T2))); - *((T1*)(dst + j - sizeof(T1) - sizeof(T2))) = t0; - *((T2*)(dst + j - sizeof(T2))) = t2; - *((T1*)(dst + i)) = t1; - *((T2*)(dst + i + sizeof(T1))) = t3; + V r0, g0, b0; + v_load_deinterleave((const T*)(src + i * stride), r0, g0, b0); + V r1, g1, b1; + v_load_deinterleave((const T*)(src + (width - i - vlanes) * stride), r1, g1, b1); + r0 = v_reverse(r0); + g0 = v_reverse(g0); + b0 = v_reverse(b0); + r1 = v_reverse(r1); + g1 = v_reverse(g1); + b1 = v_reverse(b1); + v_store_interleave((T*)(dst + (width - i - vlanes) * stride), r0, g0, b0); + v_store_interleave((T*)(dst + i * stride), r1, g1, b1); + } + // Scalar tail loop for remaining pixels + for( ; i < centre; i++ ) + { + int j = width - i - 1; + T c0 = ((const T*)(src + i * stride))[0]; + T c1 = ((const T*)(src + i * stride))[1]; + T c2 = ((const T*)(src + i * stride))[2]; + T c3 = ((const T*)(src + j * stride))[0]; + T c4 = ((const T*)(src + j * stride))[1]; + T c5 = ((const T*)(src + j * stride))[2]; + ((T*)(dst + j * stride))[0] = c0; + ((T*)(dst + j * stride))[1] = c1; + ((T*)(dst + j * stride))[2] = c2; + ((T*)(dst + i * stride))[0] = c3; + ((T*)(dst + i * stride))[1] = c4; + ((T*)(dst + i * stride))[2] = c5; } } } -#endif -static void -flipHoriz( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size, size_t esz ) +// SIMD flip when ESZ multiple of vlanes +template +CV_ALWAYS_INLINE void flipHoriz_vlanes_match( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size) { + const int vlanes = VTraits::vlanes(); + int end = (int)(size.width * ESZ); + int width = end / 2; + int height = size.height; + int eSize = (int)ESZ; + for( ; height--; src += sstep, dst += dstep ) + { + for( int i = 0, j = end - eSize; i < width; i += eSize, j -= eSize ) + { + for( int k = 0; k < eSize; k += vlanes ) + { + v_uint8 t0 = vx_load(src + i + k); + v_uint8 t1 = vx_load(src + j + k); + v_store(dst + j + k, t0); + v_store(dst + i + k, t1); + } + } + } +} + #if CV_SIMD128 -#if CV_STRONG_ALIGNMENT - size_t alignmentMark = ((size_t)src)|((size_t)dst)|sstep|dstep; -#endif - if (esz == 2 * (size_t)VTraits::vlanes()) +// SIMD flip when ESZ=16 (128-bit) +template +CV_ALWAYS_INLINE void flipHoriz_vlanes_match_128( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size) +{ + const int vlanes16 = VTraits::vlanes(); + int end = (int)(size.width * ESZ); + int width = end / 2; + int height = size.height; + for( ; height--; src += sstep, dst += dstep ) { - int end = (int)(size.width*esz); - int width = end/2; - - for( ; size.height--; src += sstep, dst += dstep ) + for( int i = 0, j = end - vlanes16; i < width; i += vlanes16, j -= vlanes16 ) { - for( int i = 0, j = end - 2 * VTraits::vlanes(); i < width; i += 2 * VTraits::vlanes(), j -= 2 * VTraits::vlanes() ) - { -#if CV_SIMD256 - v_uint8x32 t0, t1; - - t0 = v256_load((uchar*)src + i); - t1 = v256_load((uchar*)src + j); - v_store(dst + j, t0); - v_store(dst + i, t1); -#else - v_uint8x16 t0, t1, t2, t3; - - t0 = v_load((uchar*)src + i); - t1 = v_load((uchar*)src + i + VTraits::vlanes()); - t2 = v_load((uchar*)src + j); - t3 = v_load((uchar*)src + j + VTraits::vlanes()); - v_store(dst + j, t0); - v_store(dst + j + VTraits::vlanes(), t1); - v_store(dst + i, t2); - v_store(dst + i + VTraits::vlanes(), t3); -#endif - } + v_uint8x16 t0 = v_load(src + i); + v_uint8x16 t1 = v_load(src + j); + v_store(dst + j, t0); + v_store(dst + i, t1); } } - else if (esz == (size_t)VTraits::vlanes()) - { - int end = (int)(size.width*esz); - int width = end/2; - - for( ; size.height--; src += sstep, dst += dstep ) - { - for( int i = 0, j = end - VTraits::vlanes(); i < width; i += VTraits::vlanes(), j -= VTraits::vlanes() ) - { - v_uint8x16 t0, t1; - - t0 = v_load((uchar*)src + i); - t1 = v_load((uchar*)src + j); - v_store(dst + j, t0); - v_store(dst + i, t1); - } - } - } - else if (esz == 8 -#if CV_STRONG_ALIGNMENT - && isAligned(alignmentMark) -#endif - ) - { - flipHoriz_single(src, sstep, dst, dstep, size, esz); - } - else if (esz == 4 -#if CV_STRONG_ALIGNMENT - && isAligned(alignmentMark) -#endif - ) - { - flipHoriz_single(src, sstep, dst, dstep, size, esz); - } - else if (esz == 2 -#if CV_STRONG_ALIGNMENT - && isAligned(alignmentMark) -#endif - ) - { - flipHoriz_single(src, sstep, dst, dstep, size, esz); - } - else if (esz == 1) - { - flipHoriz_single(src, sstep, dst, dstep, size, esz); - } - else if (esz == 24 -#if CV_STRONG_ALIGNMENT - && isAligned(alignmentMark) -#endif - ) - { - int end = (int)(size.width*esz); - int width = (end + 1)/2; - - for( ; size.height--; src += sstep, dst += dstep ) - { - for ( int i = 0, j = end; i < width; i += VTraits::vlanes() + sizeof(uint64_t), j -= VTraits::vlanes() + sizeof(uint64_t) ) - { - v_uint8x16 t0, t1; - uint64_t t2, t3; - - t0 = v_load((uchar*)src + i); - t2 = *((uint64_t*)((uchar*)src + i + VTraits::vlanes())); - t1 = v_load((uchar*)src + j - VTraits::vlanes() - sizeof(uint64_t)); - t3 = *((uint64_t*)((uchar*)src + j - sizeof(uint64_t))); - v_store(dst + j - VTraits::vlanes() - sizeof(uint64_t), t0); - *((uint64_t*)(dst + j - sizeof(uint64_t))) = t2; - v_store(dst + i, t1); - *((uint64_t*)(dst + i + VTraits::vlanes())) = t3; - } - } - } -#if !CV_STRONG_ALIGNMENT - else if (esz == 12) - { - flipHoriz_double(src, sstep, dst, dstep, size, esz); - } - else if (esz == 6) - { - flipHoriz_double(src, sstep, dst, dstep, size, esz); - } - else if (esz == 3) - { - flipHoriz_double(src, sstep, dst, dstep, size, esz); - } -#endif - else +} #endif // CV_SIMD128 + +// SIMD flip for ESZ=16,32 +template +CV_ALWAYS_INLINE void flipHoriz_vlanes_dispatch( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size ) +{ + const int vlanes = VTraits::vlanes(); +#if CV_SIMD128 + const int vlanes16 = VTraits::vlanes(); +#endif + if ( (ESZ == (size_t)vlanes) || (ESZ == 2 * (size_t)vlanes)) { - int i, j, limit = (int)(((size.width + 1)/2)*esz); - AutoBuffer _tab(size.width*esz); - int* tab = _tab.data(); + flipHoriz_vlanes_match(src, sstep, dst, dstep, size); + return; + } +#if CV_SIMD128 + else if (ESZ == vlanes16) + { + flipHoriz_vlanes_match_128(src, sstep, dst, dstep, size); + return; + } +#endif + flipHoriz_generic(src, sstep, dst, dstep, size, ESZ); +} - for( i = 0; i < size.width; i++ ) - for( size_t k = 0; k < esz; k++ ) - tab[i*esz + k] = (int)((size.width - i - 1)*esz + k); - - for( ; size.height--; src += sstep, dst += dstep ) +#if CV_SIMD128 +// SIMD flip for ESZ=24 (CV_64FC3) +CV_ALWAYS_INLINE void flipHoriz_24( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size ) +{ +#if CV_STRONG_ALIGNMENT + // This kernel performs 64-bit scalar loads/stores, so require 8-byte alignment. + if (!isAligned<8>(((size_t)src)|((size_t)dst)|sstep|dstep)) + { + flipHoriz_generic(src, sstep, dst, dstep, size, 24); + return; + } +#endif + const int lanes16 = 16; + int end = (int)(size.width * 24); + int width = (end + 1) / 2; + int height = size.height; + for( ; height--; src += sstep, dst += dstep ) + { + for ( int i = 0, j = end; i < width; i += lanes16 + 8, j -= lanes16 + 8 ) { - for( i = 0; i < limit; i++ ) - { - j = tab[i]; - uchar t0 = src[i], t1 = src[j]; - dst[i] = t1; dst[j] = t0; - } + v_uint8x16 t0 = v_load(src + i); + uint64_t t2 = *reinterpret_cast(src + i + lanes16); + v_uint8x16 t1 = v_load(src + j - lanes16 - 8); + uint64_t t3 = *reinterpret_cast(src + j - 8); + v_store(dst + j - lanes16 - 8, t0); + *reinterpret_cast(dst + j - 8) = t2; + v_store(dst + i, t1); + *reinterpret_cast(dst + i + lanes16) = t3; } } } +#endif // CV_SIMD128 +#endif // CV_SIMD || CV_SIMD_SCALABLE + +static void flipHoriz( const uchar* src, size_t sstep, uchar* dst, size_t dstep, Size size, size_t esz ) +{ +#if CV_SIMD || CV_SIMD_SCALABLE + // SIMD-optimized dispatch + switch(esz) + { + case 1: flipHoriz_single(src, sstep, dst, dstep, size); return; // CV_8UC1: 8-bit, 1 channel + case 2: flipHoriz_single(src, sstep, dst, dstep, size); return; // CV_8UC2, CV_16UC1: 8-bit 2-channel or 16-bit 1-channel + case 3: flipHoriz_c3(src, sstep, dst, dstep, size); return; // CV_8UC3: 8-bit, 3 channels + case 4: flipHoriz_single(src, sstep, dst, dstep, size); return; // CV_8UC4, CV_16UC2, CV_32SC1, CV_32FC1: 8-bit 4-channel, 16-bit 2-channel, or 32-bit 1-channel + case 6: flipHoriz_c3(src, sstep, dst, dstep, size); return; // CV_16UC3, CV_16SC3: 16-bit, 3 channels + case 8: flipHoriz_single(src, sstep, dst, dstep, size); return; // CV_16UC4, CV_32SC2, CV_32FC2, CV_64FC1: 16-bit 4-channel, 32-bit 2-channel, or 64-bit 1-channel + case 12: flipHoriz_c3(src, sstep, dst, dstep, size); return; // CV_32SC3, CV_32FC3: 32-bit, 3 channels + case 16: flipHoriz_vlanes_dispatch<16>(src, sstep, dst, dstep, size); return; // CV_32SC4, CV_32FC4, CV_64FC2: 32-bit 4-channel or 64-bit 2-channel +#if CV_SIMD128 + case 24: flipHoriz_24(src, sstep, dst, dstep, size); return; // CV_64FC3: 64-bit, 3 channels +#endif + case 32: flipHoriz_vlanes_dispatch<32>(src, sstep, dst, dstep, size); return; // CV_64FC4: 64-bit, 4 channels + default: + break; // Fall through to generic implementation + } +#endif + // Fallback: generic scalar + flipHoriz_generic(src, sstep, dst, dstep, size, esz); +} static void flipVert( const uchar* src0, size_t sstep, uchar* dst0, size_t dstep, Size size, size_t esz ) diff --git a/modules/core/src/persistence_yml.cpp b/modules/core/src/persistence_yml.cpp index 009ab7d8b7..d926fd0b2e 100644 --- a/modules/core/src/persistence_yml.cpp +++ b/modules/core/src/persistence_yml.cpp @@ -432,12 +432,13 @@ public: CV_PARSE_ERROR_CPP( "Missing \':\'" ); saveptr = endptr + 1; + if( endptr == ptr ) + CV_PARSE_ERROR_CPP( "An empty key" ); + do c = *--endptr; while( c == ' ' ); ++endptr; - if( endptr == ptr ) - CV_PARSE_ERROR_CPP( "An empty key" ); value_placeholder = fs->addNode(map_node, std::string(ptr, endptr - ptr), FileNode::NONE); ptr = saveptr; diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 20f5318997..f00a91b6f5 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -3925,5 +3925,17 @@ TEST_P(Core_MaskTypeTest, MeanStdDev) INSTANTIATE_TEST_CASE_P(/**/, Core_MaskTypeTest, MaskType::all()); +// Still fails in 5.x: https://github.com/opencv/opencv/issues/28557 +TEST(Core_Arithm, DISABLED_mul_overflow_28557) +{ + uint16_t data[] = {5000, 60000, 5000, 60000, 5000, 60000}; + cv::Mat m(1, 6, CV_16U, data); + cv::Mat res = m.mul(m); + + for (int i = 0; i < 6; i++) + { + EXPECT_EQ(65535, res.at(0, i)); + } +} }} // namespace diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index c60626fd58..2588726861 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -1707,6 +1707,21 @@ TEST(Core_InputOutput, FileStorage_free_file_after_exception) ASSERT_EQ(0, std::remove(fileName.c_str())); } +TEST(Core_InputOutput, FileStorage_YAML_empty_key) +{ + const std::string fileName = cv::tempfile("FileStorage_YAML_empty_key_test.yml"); + const std::string content = "%YAML:1.0\n---\nkey1: value1\n: 10\n"; + + std::fstream testFile; + testFile.open(fileName.c_str(), std::fstream::out); + if(!testFile.is_open()) FAIL(); + testFile << content; + testFile.close(); + + EXPECT_THROW(FileStorage(fileName, FileStorage::READ), cv::Exception); + ASSERT_EQ(0, std::remove(fileName.c_str())); +} + TEST(Core_InputOutput, FileStorage_write_to_sequence) { const std::vector formatExts = { ".yml", ".json", ".xml" }; diff --git a/modules/core/test/test_operations.cpp b/modules/core/test/test_operations.cpp index 833fa6b794..d6913fd269 100644 --- a/modules/core/test/test_operations.cpp +++ b/modules/core/test/test_operations.cpp @@ -69,7 +69,7 @@ protected: bool TestSparseMat(); bool TestVec(); bool TestMatxMultiplication(); - bool TestMatxElementwiseDivison(); + bool TestMatxElementwiseDivision(); bool TestDivisionByValue(); bool TestInplaceDivisionByValue(); bool TestMatMatxCastSum(); @@ -958,7 +958,7 @@ bool CV_OperationsTest::TestMatMatxCastSum() return true; } -bool CV_OperationsTest::TestMatxElementwiseDivison() +bool CV_OperationsTest::TestMatxElementwiseDivision() { try { @@ -1248,7 +1248,7 @@ void CV_OperationsTest::run( int /* start_from */) if (!TestMatxMultiplication()) return; - if (!TestMatxElementwiseDivison()) + if (!TestMatxElementwiseDivision()) return; if (!TestDivisionByValue()) diff --git a/modules/dnn/src/dnn_utils.cpp b/modules/dnn/src/dnn_utils.cpp index b6f1d3fcce..5cc1359b0d 100644 --- a/modules/dnn/src/dnn_utils.cpp +++ b/modules/dnn/src/dnn_utils.cpp @@ -187,7 +187,7 @@ void blobFromImagesNCHWImpl(const std::vector& images, Mat& blob_, const Im if (param.mean == Scalar() && param.scalefactor == Scalar::all(1.0)) return; - CV_CheckTypeEQ(param.ddepth, CV_32F, "Scaling and mean substraction is supported only for CV_32F blob depth"); + CV_CheckTypeEQ(param.ddepth, CV_32F, "Scaling and mean subtraction is supported only for CV_32F blob depth"); for (size_t k = 0; k < images.size(); ++k) { diff --git a/modules/dnn/src/layers/einsum_layer.cpp b/modules/dnn/src/layers/einsum_layer.cpp index b3a2ce09d7..a1aac9ceb3 100644 --- a/modules/dnn/src/layers/einsum_layer.cpp +++ b/modules/dnn/src/layers/einsum_layer.cpp @@ -1282,7 +1282,7 @@ Mat LayerEinsumImpl::pairwiseOperandProcess( shape(currentLeft), reshaped_dims)) { - // This can be done because curent_* tensors (if they exist) and output tensors are + // This can be done because current_* tensors (if they exist) and output tensors are // intermediate tensors and cannot be input tensors to the Einsum node itself // (which are immutable). currentLeft = currentLeft.reshape(1, reshaped_dims.size(), reshaped_dims.data()); diff --git a/modules/dnn/src/layers/scatterND_layer.cpp b/modules/dnn/src/layers/scatterND_layer.cpp index 7e1c3feaf5..b0062993a8 100644 --- a/modules/dnn/src/layers/scatterND_layer.cpp +++ b/modules/dnn/src/layers/scatterND_layer.cpp @@ -39,7 +39,7 @@ public: else if (reduction_name == "min") reduction = REDUCTION::MIN; else - CV_Error(cv::Error::StsBadArg, "Unkown reduction \"" + reduction_name + "\""); + CV_Error(cv::Error::StsBadArg, "Unknown reduction \"" + reduction_name + "\""); } virtual bool supportBackend(int backendId) CV_OVERRIDE diff --git a/modules/dnn/src/layers/scatter_layer.cpp b/modules/dnn/src/layers/scatter_layer.cpp index c1079720f9..12580bad14 100644 --- a/modules/dnn/src/layers/scatter_layer.cpp +++ b/modules/dnn/src/layers/scatter_layer.cpp @@ -40,7 +40,7 @@ public: else if (reduction_name == "min") reduction = REDUCTION::MIN; else - CV_Error(cv::Error::StsBadArg, "Unkown reduction \"" + reduction_name + "\""); + CV_Error(cv::Error::StsBadArg, "Unknown reduction \"" + reduction_name + "\""); } virtual bool supportBackend(int backendId) CV_OVERRIDE diff --git a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp index 5bd573d84e..b51435dd66 100644 --- a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp +++ b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp @@ -1282,7 +1282,7 @@ public: } } } - // extract axis from original Gather node + // extract axis from original Gather node axis = 0; opencv_onnx::NodeProto* origGatherNode = inpNode.dynamicCast()->node; @@ -1306,6 +1306,7 @@ public: new_attr->set_name("axis"); new_attr->set_i(axis); } + private: int cast, gather, axis; }; diff --git a/modules/dnn/src/op_cann.cpp b/modules/dnn/src/op_cann.cpp index c36633dc15..ca6876eb44 100644 --- a/modules/dnn/src/op_cann.cpp +++ b/modules/dnn/src/op_cann.cpp @@ -215,7 +215,7 @@ void CannNet::forward() ACL_CHECK_RET(aclmdlExecute(model_id, inputs, outputs)); CV_LOG_DEBUG(NULL, "DNN/CANN: finished network forward"); - // fetch ouputs from device to host + // fetch outputs from device to host CV_LOG_DEBUG(NULL, "DNN/CANN: start fetching outputs to host"); for (size_t i = 0; i < output_wrappers.size(); ++i) { diff --git a/modules/dnn/src/op_inf_engine.cpp b/modules/dnn/src/op_inf_engine.cpp index 1dfe489071..e57c6c58ff 100644 --- a/modules/dnn/src/op_inf_engine.cpp +++ b/modules/dnn/src/op_inf_engine.cpp @@ -57,7 +57,7 @@ Mat infEngineBlobToMat(const ov::Tensor& blob) default: CV_Error(Error::StsNotImplemented, "Unsupported blob precision"); } - return Mat(size, type, blob.data()); + return Mat(size, type, const_cast(blob.data())); } void infEngineBlobsToMats(const ov::TensorVector& blobs, diff --git a/modules/dnn/src/op_timvx.cpp b/modules/dnn/src/op_timvx.cpp index 4d1bbbc884..1d94007781 100644 --- a/modules/dnn/src/op_timvx.cpp +++ b/modules/dnn/src/op_timvx.cpp @@ -535,7 +535,7 @@ TimVXBackendNode::TimVXBackendNode(const Ptr& tvGraph_, } TimVXBackendNode::TimVXBackendNode(const Ptr& tvGraph_, std::shared_ptr& op_, - std::vector& inputsIndex, std::vector& outpusIndex) + std::vector& inputsIndex, std::vector& outputsIndex) :BackendNode(DNN_BACKEND_TIMVX) { tvGraph = tvGraph_; @@ -545,8 +545,8 @@ TimVXBackendNode::TimVXBackendNode(const Ptr& tvGraph_, std::shared_ if (!inputsIndex.empty()) inputIndexList.assign(inputsIndex.begin(), inputsIndex.end()); - if (!outpusIndex.empty()) - outputIndexList.assign(outpusIndex.begin(), outpusIndex.end()); + if (!outputsIndex.empty()) + outputIndexList.assign(outputsIndex.begin(), outputsIndex.end()); } bool TimVXBackendNode::opBinding() diff --git a/modules/dnn/src/op_timvx.hpp b/modules/dnn/src/op_timvx.hpp index d08cf64bb5..ad37ec8575 100644 --- a/modules/dnn/src/op_timvx.hpp +++ b/modules/dnn/src/op_timvx.hpp @@ -96,7 +96,7 @@ public: TimVXBackendNode(const Ptr& tvGraph); TimVXBackendNode(const Ptr& tvGraph, const std::shared_ptr& op); TimVXBackendNode(const Ptr& tvGraph, std::shared_ptr& op, - std::vector& inputsIndex, std::vector& outpusIndex); + std::vector& inputsIndex, std::vector& outputsIndex); void setInputTensor(); bool opBinding(); diff --git a/modules/flann/include/opencv2/flann.hpp b/modules/flann/include/opencv2/flann.hpp index 90ee59e0b8..470fba1f9c 100644 --- a/modules/flann/include/opencv2/flann.hpp +++ b/modules/flann/include/opencv2/flann.hpp @@ -175,9 +175,9 @@ public: /** @brief Constructs a nearest neighbor search index for a given dataset. - @param features Matrix of containing the features(points) to index. The size of the matrix is + @param features Matrix containing the features(points) to index. The size of the matrix is num_features x feature_dimensionality and the data type of the elements in the matrix must - coincide with the type of the index. + match the type of the index. @param params Structure containing the index parameters. The type of index that will be constructed depends on the type of this parameter. See the description. @param distance diff --git a/modules/highgui/src/window_cocoa.mm b/modules/highgui/src/window_cocoa.mm index 4570b0b64f..332e6bb2f3 100644 --- a/modules/highgui/src/window_cocoa.mm +++ b/modules/highgui/src/window_cocoa.mm @@ -236,7 +236,8 @@ void showImageImpl( const char* name, cv::InputArray arr) int y = [window y0]; if(x >= 0 && y >= 0) { - y = [[window screen] visibleFrame].size.height - y; + NSRect visibleFrame = [[window screen] visibleFrame]; + y = NSMaxY(visibleFrame) - y; [window setFrameTopLeftPoint:NSMakePoint(x, y)]; } } @@ -273,7 +274,8 @@ void moveWindowImpl( const char* name, int x, int y) [window setY0:y]; } else { - y = [[window screen] visibleFrame].size.height - y; + NSRect visibleFrame = [[window screen] visibleFrame]; + y = NSMaxY(visibleFrame) - y; [window setFrameTopLeftPoint:NSMakePoint(x, y)]; } } diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 466646d7c6..ac5591c8c7 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -102,7 +102,8 @@ enum ImwriteFlags { IMWRITE_EXR_TYPE = (3 << 4) + 0 /* 48 */, //!< override EXR storage type (FLOAT (FP32) is default) IMWRITE_EXR_COMPRESSION = (3 << 4) + 1 /* 49 */, //!< override EXR compression type (ZIP_COMPRESSION = 3 is default) IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2 /* 50 */, //!< override EXR DWA compression level (45 is default) - IMWRITE_WEBP_QUALITY = 64, //!< For WEBP, it can be a quality from 1 to 100 (the higher is the better). By default (without any parameter) and for quality above 100 the lossless compression is used. + IMWRITE_WEBP_QUALITY = 64, //!< For WEBP, it can be a lossy quality from 1 to 100 (the higher is the better) for IMWRITE_WEBP_LOSSLESS_OFF. By default (without this parameter) or if quality > 100, IMWRITE_WEBP_LOSSLESS_ON is used instead. + IMWRITE_WEBP_LOSSLESS_MODE = 65, //!< For WEBP, it can be a lossless compression strategy. See cv::ImwriteWEBPLosslessMode. Default is IMWRITE_WEBP_LOSSLESS_OFF. For Animated WEBP, it is not supported. IMWRITE_HDR_COMPRESSION = (5 << 4) + 0 /* 80 */, //!< specify HDR compression IMWRITE_PAM_TUPLETYPE = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set. See ImwriteTiffResolutionUnitFlags. Default is IMWRITE_TIFF_RESOLUTION_UNIT_INCH. @@ -240,6 +241,14 @@ enum ImwritePAMFlags { IMWRITE_PAM_FORMAT_RGB_ALPHA = 5 }; +//! Imwrite WEBP specific values for IMWRITE_WEBP_LOSSLESS_MODE parameter key. +enum ImwriteWEBPLosslessMode { + IMWRITE_WEBP_LOSSLESS_OFF = 0, //!< Lossy compression mode. Uses IMWRITE_WEBP_QUALITY to control compression. (Default) + //!< @note If IMWRITE_WEBP_QUALITY is not specified, it falls back to IMWRITE_WEBP_LOSSLESS_ON to maintain backward compatibility. + IMWRITE_WEBP_LOSSLESS_ON = 1, //!< Standard lossless compression. May modify or discard RGB values of fully transparent pixels to improve compression ratio. + IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR = 2, //!< Exact lossless compression. Preserves all RGB data even for pixels with 0 alpha (equivalent to WebP's exact flag). +}; + //! Imwrite HDR specific values for IMWRITE_HDR_COMPRESSION parameter key enum ImwriteHDRCompressionFlags { IMWRITE_HDR_COMPRESSION_NONE = 0, diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 068be52b8b..a37bbfca54 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -338,7 +338,7 @@ WebPEncoder::WebPEncoder() m_support_metadata[IMAGE_METADATA_EXIF] = true; m_support_metadata[IMAGE_METADATA_XMP] = true; m_support_metadata[IMAGE_METADATA_ICCP] = true; - m_supported_encode_key = {IMWRITE_WEBP_QUALITY}; + m_supported_encode_key = {IMWRITE_WEBP_QUALITY, IMWRITE_WEBP_LOSSLESS_MODE}; } WebPEncoder::~WebPEncoder() { } @@ -348,32 +348,121 @@ ImageEncoder WebPEncoder::newEncoder() const return makePtr(); } +// Simple API style +static size_t cvEncodeLosslessExactBGRA(const uint8_t* rgba, int width, int height, int stride, uint8_t** output) +{ + WebPConfig config; + WebPPicture pic; + WebPMemoryWriter wrt; + + // 6 is the default value for speed/compression balance in lossless mode. + // It doesn't affect visual quality, only file size and encoding time. + if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, 6)) + { + return 0; + } + config.exact = 1; + + if (!WebPPictureInit(&pic)) + { + return 0; + } + pic.width = width; + pic.height = height; + pic.use_argb = 1; // BGRA + if (!WebPPictureImportBGRA(&pic, rgba, stride)) + { + return 0; + } + + WebPMemoryWriterInit(&wrt); + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &wrt; + + if (!WebPEncode(&config, &pic)) + { + WebPMemoryWriterClear(&wrt); + WebPPictureFree(&pic); + return 0; + } + + *output = wrt.mem; + size_t size = wrt.size; + WebPPictureFree(&pic); + return size; +} + bool WebPEncoder::write(const Mat& img, const std::vector& params) { CV_CheckDepthEQ(img.depth(), CV_8U, "WebP codec supports 8U images only"); const int width = img.cols, height = img.rows; - bool comp_lossless = true; - float quality = 100.0f; + int lossless_mode = -1; // not specified + float quality = 0.0f; // not specified for(size_t i = 0; i < params.size(); i += 2) { const int value = params[i+1]; - if (params[i] == IMWRITE_WEBP_QUALITY) + if (params[i] == IMWRITE_WEBP_LOSSLESS_MODE) { - comp_lossless = false; - quality = static_cast(value); - if (quality < 1.0f) + switch(value) { - quality = 1.0f; - CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_WEBP_QUALITY must be between 1 to 100(lossy) or more(lossless). It is fallbacked to 1", value)); - } - if (quality > 100.0f) - { - comp_lossless = true; + case IMWRITE_WEBP_LOSSLESS_ON: + case IMWRITE_WEBP_LOSSLESS_OFF: + case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR: + lossless_mode = value; + break; + default: + CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_WEBP_LOSSLESS_MODE must be one of ImwriteWEBPLosslessMode. It is ignored", value)); + break; } } + if (params[i] == IMWRITE_WEBP_QUALITY) + { + if (value < 1) + { + CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_WEBP_QUALITY must be between 1 to 100(lossy) or more(lossless). It is fallbacked to 1", value)); + quality = 1.0f; + } + else if (value > 100) + { + quality = 101.0f; + } + else // value is 1 to 100 + { + quality = static_cast(value); + } + } + } + + switch(lossless_mode) + { + case -1: // not specified by user + case IMWRITE_WEBP_LOSSLESS_OFF: + // Fallback to lossless if quality is not specified (-1.0f) or out of lossy range (>100.0f). + // This maintains backward compatibility where WebP defaults to lossless. + if ((quality < 1.0f) || (quality > 100.0f)) + { + lossless_mode = IMWRITE_WEBP_LOSSLESS_ON; + quality = 101.0f; + } + else + { + lossless_mode = IMWRITE_WEBP_LOSSLESS_OFF; + // Use specified quality for lossy compression. + } + break; + + case IMWRITE_WEBP_LOSSLESS_ON: + case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR: + // Force quality value to lossless range when explicit lossless mode is selected. + quality = 101.0f; + break; + + default: + CV_Error(Error::StsError, cv::format("Unexpected lossless_mode(%d)", lossless_mode)); + break; } int channels = img.channels(); @@ -391,29 +480,45 @@ bool WebPEncoder::write(const Mat& img, const std::vector& params) uint8_t *encoder_out = NULL; size_t size = 0; - if (comp_lossless) + if (channels == 3) { - if (channels == 3) + switch(lossless_mode) { - size = WebPEncodeLosslessBGR(image->ptr(), width, height, (int)image->step, &encoder_out); - } - else if (channels == 4) - { - size = WebPEncodeLosslessBGRA(image->ptr(), width, height, (int)image->step, &encoder_out); + case IMWRITE_WEBP_LOSSLESS_OFF: + size = WebPEncodeBGR(image->ptr(), width, height, (int)image->step, quality, &encoder_out); + break; + case IMWRITE_WEBP_LOSSLESS_ON: + case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR: + size = WebPEncodeLosslessBGR(image->ptr(), width, height, (int)image->step, &encoder_out); + break; + default: + CV_Error(Error::StsError, cv::format("Unexcepted lossless_mode(%d)", lossless_mode)); + break; } } else { - if (channels == 3) + CV_CheckEQ(channels, 4, "Unexpected channels is used"); + switch(lossless_mode) { - size = WebPEncodeBGR(image->ptr(), width, height, (int)image->step, quality, &encoder_out); - } - else if (channels == 4) - { - size = WebPEncodeBGRA(image->ptr(), width, height, (int)image->step, quality, &encoder_out); + case IMWRITE_WEBP_LOSSLESS_OFF: + size = WebPEncodeBGRA(image->ptr(), width, height, (int)image->step, quality, &encoder_out); + break; + case IMWRITE_WEBP_LOSSLESS_ON: + size = WebPEncodeLosslessBGRA(image->ptr(), width, height, (int)image->step, &encoder_out); + break; + case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR: + size = cvEncodeLosslessExactBGRA(image->ptr(), width, height, (int)image->step, &encoder_out); + break; } } + if (size == 0) + { + CV_LOG_ERROR(NULL, cv::format("WebP encoding failed with lossless_mode=%d", lossless_mode)); + return false; + } + #if WEBP_DECODER_ABI_VERSION >= 0x0206 Ptr out_cleaner(encoder_out, WebPFree); #else diff --git a/modules/imgcodecs/test/test_webp.cpp b/modules/imgcodecs/test/test_webp.cpp index b3fe9b2caf..b016dbb40d 100644 --- a/modules/imgcodecs/test/test_webp.cpp +++ b/modules/imgcodecs/test/test_webp.cpp @@ -114,6 +114,154 @@ TEST(Imgcodecs_WebP, encode_decode_with_alpha_webp) EXPECT_EQ(512, img_webp_bgr.rows); } + +// See https://github.com/opencv/opencv/issues/28503 +TEST(Imgcodecs_WebP, encode_decode_LOSSLESS_MODE) +{ + cv::Mat img(cv::Size(64,4), CV_8UC4, cv::Scalar(124,64,67,0) ); + for(int ix = 0; ix < img.size().width; ix++) + { + img.at(0, ix)[3] = 0; // Transpacency pixel + img.at(1, ix)[3] = 1; + img.at(2, ix)[3] = 254; + img.at(3, ix)[3] = 255; + } + + std::vector work; + EXPECT_NO_THROW(cv::imencode(".webp", img, work, {IMWRITE_WEBP_LOSSLESS_MODE, IMWRITE_WEBP_LOSSLESS_ON})); + cv::Mat img_ON = cv::imdecode(work, IMREAD_UNCHANGED); + EXPECT_NO_THROW(cv::imencode(".webp", img, work, {IMWRITE_WEBP_LOSSLESS_MODE, IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR})); + cv::Mat img_PRESERVE_COLOR = cv::imdecode(work, IMREAD_UNCHANGED); + + for(int ix = 0; ix < img.size().width; ix++) + { + EXPECT_EQ(img_ON.at(0, ix), Vec4b(0, 0, 0, 0)); // LOSSLESS_ON -> COLOR will be optimized/dropped. + EXPECT_EQ(img_PRESERVE_COLOR.at(0, ix), Vec4b(124,64,67,0)); // PRESERVE_COLOR + + EXPECT_EQ(img_ON.at(1, ix), img_PRESERVE_COLOR.at(1, ix) ); + EXPECT_EQ(img_ON.at(2, ix), img_PRESERVE_COLOR.at(2, ix) ); + EXPECT_EQ(img_ON.at(3, ix), img_PRESERVE_COLOR.at(3, ix) ); + } +} + +// Expected result categories for WebP encoding tests. +enum ImencodeLosslessResult { + LOSSY, // Expect lossy compression (pixel differences allowed) + LOSSLESS, // Expect standard lossless compression (pixel values match) + EXACT // Expect exact lossless (preserving RGB values of transparent pixels) +}; + +typedef std::tuple WebPModePriorityParams; +class Imgcodecs_WebP_Mode_Priority : public testing::TestWithParam {}; + +TEST_P(Imgcodecs_WebP_Mode_Priority, encode_webp_mode_priority) +{ + const int mode = std::get<0>(GetParam()); + const int quality = std::get<1>(GetParam()); + const ImencodeLosslessResult expected = std::get<2>(GetParam()); + + // Generate a 100x100 RGBA test image. + // Set a transparent pixel with specific color (Blue) to verify EXACT mode. + Mat src(100, 100, CV_8UC4, Scalar(255, 255, 255, 255)); + src.at(0, 0) = Vec4b(255, 0, 0, 0); // Transparent Blue (B:255, G:0, R:0, A:0) + + // Build the imwrite parameter vector dynamically. + std::vector params; + if (mode != -1) { + params.push_back(IMWRITE_WEBP_LOSSLESS_MODE); + params.push_back(mode); + } + if (quality != -1) { + params.push_back(IMWRITE_WEBP_QUALITY); + params.push_back(quality); + } + + // Encode to memory and decode back. + std::vector buf; + ASSERT_TRUE(imencode(".webp", src, buf, params)); + Mat dst = imdecode(buf, IMREAD_UNCHANGED); + ASSERT_FALSE(dst.empty()); + + // Validation logic + if (expected == LOSSY) { + // We expect some differences in lossy mode + double diff = cv::norm(src, dst, NORM_INF); + EXPECT_GT(diff, 0) << "Should be lossy (Quality: " << quality << ")"; + } + else if (expected == LOSSLESS) { + // Standard lossless: we allow the library to modify RGB values + // of fully transparent pixels (A=0) to improve compression ratio. + // Thus, we compare only visible pixels or check with a slightly relaxed condition. + + // Option A: If you want to allow RGB changes on A=0: + // We can't use cv::norm directly if A=0 pixels are modified. + // Let's check if they are identical except for the transparent pixel. + Mat diff; + absdiff(src, dst, diff); + Scalar total_diff = sum(diff); + // If only the (0,0) pixel changed from (255,0,0,0) to (0,0,0,0), + // total_diff will be 255. + EXPECT_LE(total_diff[0] + total_diff[1] + total_diff[2], 255) + << "Standard lossless should not have significant pixel differences"; + EXPECT_EQ(src.at(0,0)[3], dst.at(0,0)[3]) << "Alpha must be preserved"; + } + else if (expected == EXACT) { + // Exact lossless: Every single bit must match, including transparent pixels. + double diff = cv::norm(src, dst, NORM_INF); + EXPECT_EQ(0, diff) << "Exact mode must preserve all pixel values perfectly"; + EXPECT_EQ(src.at(0, 0), dst.at(0, 0)) + << "RGB values of transparent pixels must be preserved in EXACT mode"; + } + else { + FAIL() << "Unknown expectation type"; + } +} + +/** + * Helper to generate human-readable test names in gtest output. + */ +static std::string getModeStr(int m) { + if (m == -1) return "OMIT"; + if (m == IMWRITE_WEBP_LOSSLESS_OFF) return "OFF"; + if (m == IMWRITE_WEBP_LOSSLESS_ON) return "ON"; + if (m == IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR) return "PRESERVE"; + return "UNKNOWN"; +} + +static std::string getExpectStr(ImencodeLosslessResult r) { + return (r == LOSSY) ? "LOSSY" : (r == EXACT) ? "EXACT" : "LOSSLESS"; +} + +INSTANTIATE_TEST_CASE_P(Imgcodecs, Imgcodecs_WebP_Mode_Priority, + testing::Values( + // Default (OMIT mode) cases + WebPModePriorityParams(-1, -1, LOSSLESS), + WebPModePriorityParams(-1, 80, LOSSY), + WebPModePriorityParams(-1, 101, LOSSLESS), + + // LOSSLESS_OFF (Explicitly off) + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, -1, LOSSLESS), + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, 80, LOSSY), + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, 101, LOSSLESS), + + // LOSSLESS_ON (Force lossless) + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, -1, LOSSLESS), + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, 80, LOSSLESS), + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, 101, LOSSLESS), + + // PRESERVE_COLOR (Exact lossless) + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, -1, EXACT), + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, 80, EXACT), + WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, 101, EXACT) + ), + [](const testing::TestParamInfo& info_) { + std::string mode = getModeStr(std::get<0>(info_.param)); + int q = std::get<1>(info_.param); + std::string q_str = (q == -1) ? "omit" : std::to_string(q); + return mode + "_q" + q_str + "_" + getExpectStr(std::get<2>(info_.param)); + } +); + #endif // HAVE_WEBP }} // namespace diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index edf90fa78a..4f6a598232 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -217,15 +217,15 @@ enum MorphTypes{ MORPH_ERODE = 0, //!< see #erode MORPH_DILATE = 1, //!< see #dilate MORPH_OPEN = 2, //!< an opening operation - //!< \f[\texttt{dst} = \mathrm{open} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \mathrm{erode} ( \texttt{src} , \texttt{element} ))\f] + //!< \f[\texttt{dst} = \mathrm{open} ( \texttt{src} , \texttt{kernel} )= \mathrm{dilate} ( \mathrm{erode} ( \texttt{src} , \texttt{kernel} ))\f] MORPH_CLOSE = 3, //!< a closing operation - //!< \f[\texttt{dst} = \mathrm{close} ( \texttt{src} , \texttt{element} )= \mathrm{erode} ( \mathrm{dilate} ( \texttt{src} , \texttt{element} ))\f] + //!< \f[\texttt{dst} = \mathrm{close} ( \texttt{src} , \texttt{kernel} )= \mathrm{erode} ( \mathrm{dilate} ( \texttt{src} , \texttt{kernel} ))\f] MORPH_GRADIENT = 4, //!< a morphological gradient - //!< \f[\texttt{dst} = \mathrm{morph\_grad} ( \texttt{src} , \texttt{element} )= \mathrm{dilate} ( \texttt{src} , \texttt{element} )- \mathrm{erode} ( \texttt{src} , \texttt{element} )\f] + //!< \f[\texttt{dst} = \mathrm{morph\_grad} ( \texttt{src} , \texttt{kernel} )= \mathrm{dilate} ( \texttt{src} , \texttt{kernel} )- \mathrm{erode} ( \texttt{src} , \texttt{kernel} )\f] MORPH_TOPHAT = 5, //!< "top hat" - //!< \f[\texttt{dst} = \mathrm{tophat} ( \texttt{src} , \texttt{element} )= \texttt{src} - \mathrm{open} ( \texttt{src} , \texttt{element} )\f] + //!< \f[\texttt{dst} = \mathrm{tophat} ( \texttt{src} , \texttt{kernel} )= \texttt{src} - \mathrm{open} ( \texttt{src} , \texttt{kernel} )\f] MORPH_BLACKHAT = 6, //!< "black hat" - //!< \f[\texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{element} )= \mathrm{close} ( \texttt{src} , \texttt{element} )- \texttt{src}\f] + //!< \f[\texttt{dst} = \mathrm{blackhat} ( \texttt{src} , \texttt{kernel} )= \mathrm{close} ( \texttt{src} , \texttt{kernel} )- \texttt{src}\f] MORPH_HITMISS = 7 //!< "hit or miss" //!< .- Only supported for CV_8UC1 binary images. A tutorial can be found in the documentation }; @@ -2356,7 +2356,7 @@ Check @ref tutorial_opening_closing_hats "the corresponding tutorial" for more d The function erodes the source image using the specified structuring element that determines the shape of a pixel neighborhood over which the minimum is taken: -\f[\texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] +\f[\texttt{dst} (x,y) = \min _{(x',y'): \, \texttt{kernel} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] The function supports the in-place mode. Erosion can be applied several ( iterations ) times. In case of multi-channel images, each channel is processed independently. @@ -2364,7 +2364,7 @@ case of multi-channel images, each channel is processed independently. @param src input image; the number of channels can be arbitrary, but the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F or CV_64F. @param dst output image of the same size and type as src. -@param kernel structuring element used for erosion; if `element=Mat()`, a `3 x 3` rectangular +@param kernel structuring element used for erosion; if `kernel=Mat()`, a `3 x 3` rectangular structuring element is used. Kernel can be created using #getStructuringElement. @param anchor position of the anchor within the element; default value (-1, -1) means that the anchor is at the element center. @@ -2388,7 +2388,7 @@ Check @ref tutorial_erosion_dilatation "the corresponding tutorial" for more det The function dilates the source image using the specified structuring element that determines the shape of a pixel neighborhood over which the maximum is taken: -\f[\texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] +\f[\texttt{dst} (x,y) = \max _{(x',y'): \, \texttt{kernel} (x',y') \ne0 } \texttt{src} (x+x',y+y')\f] The function supports the in-place mode. Dilation can be applied several ( iterations ) times. In case of multi-channel images, each channel is processed independently. @@ -2396,7 +2396,7 @@ case of multi-channel images, each channel is processed independently. @param src input image; the number of channels can be arbitrary, but the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F or CV_64F. @param dst output image of the same size and type as src. -@param kernel structuring element used for dilation; if element=Mat(), a 3 x 3 rectangular +@param kernel structuring element used for dilation; if `kernel=Mat()`, a `3 x 3` rectangular structuring element is used. Kernel can be created using #getStructuringElement @param anchor position of the anchor within the element; default value (-1, -1) means that the anchor is at the element center. @@ -3826,7 +3826,7 @@ CV_EXPORTS_W void demosaicing(InputArray src, OutputArray dst, int code, int dst The function computes moments, up to the 3rd order, of a vector shape or a rasterized shape. The results are returned in the structure cv::Moments. -@param array Single chanel raster image (CV_8U, CV_16U, CV_16S, CV_32F, CV_64F) or an array ( +@param array Single channel raster image (CV_8U, CV_16U, CV_16S, CV_32F, CV_64F) or an array ( \f$1 \times N\f$ or \f$N \times 1\f$ ) of 2D points (Point or Point2f). @param binaryImage If it is true, all non-zero image pixels are treated as 1's. The parameter is used for images only. diff --git a/modules/imgproc/perf/perf_contours.cpp b/modules/imgproc/perf/perf_contours.cpp index bc8b530016..8fabdb334b 100644 --- a/modules/imgproc/perf/perf_contours.cpp +++ b/modules/imgproc/perf/perf_contours.cpp @@ -106,4 +106,39 @@ PERF_TEST_P(TestBoundingRect, BoundingRect, SANITY_CHECK_NOTHING(); } +typedef TestBaseWithParam< tuple > TestMinEnclosingCircle; +PERF_TEST_P(TestMinEnclosingCircle, minEnclosingCircle, + Combine( + testing::Values(CV_32S, CV_32F), + Values(400, 1000, 10000, 100000) + )) +{ + int ptType = get<0>(GetParam()); + int n = get<1>(GetParam()); + Mat pts(n, 2, ptType); + declare.in(pts, WARMUP_RNG); + + Point2f center; + float radius; + TEST_CYCLE() minEnclosingCircle(pts, center, radius); + SANITY_CHECK_NOTHING(); +} + +typedef TestBaseWithParam TestMinEnclosingCircleWorstCase; +PERF_TEST_P(TestMinEnclosingCircleWorstCase, minEnclosingCircle_sequential, + Values(400, 1000, 5000, 10000)) +{ + int n = GetParam(); + vector contour; + for(int i = 0; i < n; ++i) { + float angle = (float)(i * 2 * CV_PI / n); + contour.push_back(Point2f(cos(angle) * 100, sin(angle) * 100)); + } + + Point2f center; + float radius; + TEST_CYCLE() minEnclosingCircle(contour, center, radius); + SANITY_CHECK_NOTHING(); +} + } } // namespace diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index 55a5d19c60..1354cf95f8 100755 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -70,7 +70,7 @@ static void distanceTransform_3x3( const Mat& _src, Mat& _temp, Mat& _dist, const float* metrics ) { const int BORDER = 1; - int i, j; + const unsigned int HV_DIST = CV_FLT_TO_FIX( metrics[0], DIST_SHIFT ); const unsigned int DIAG_DIST = CV_FLT_TO_FIX( metrics[1], DIST_SHIFT ); const unsigned int DIST_MAX = UINT_MAX - DIAG_DIST; @@ -79,65 +79,190 @@ distanceTransform_3x3( const Mat& _src, Mat& _temp, Mat& _dist, const float* met const uchar* src = _src.ptr(); int* temp = _temp.ptr(); float* dist = _dist.ptr(_dist.rows - 1); - int srcstep = (int)(_src.step/sizeof(src[0])); - int step = (int)(_temp.step/sizeof(temp[0])); - int dststep = (int)(_dist.step/sizeof(dist[0])); - Size size = _src.size(); + + const int srcstep = (int)_src.step; + const int step = (int)(_temp.step / sizeof(int)); + const int dststep = (int)(_dist.step / sizeof(float)); + + const int width = _src.cols; + const int height = _src.rows; initTopBottom( _temp, BORDER, DIST_MAX ); // forward pass - unsigned int* tmp = (unsigned int*)(temp + BORDER*step) + BORDER; - const uchar* s = src; - for( i = 0; i < size.height; i++ ) - { - for( j = 0; j < BORDER; j++ ) - tmp[-j-1] = tmp[size.width + j] = DIST_MAX; + unsigned int* row = (unsigned int*)(temp + BORDER * step) + BORDER; + const uchar* srow = src; - for( j = 0; j < size.width; j++ ) + for (int i = 0; i < height; i++) + { + unsigned int* cur = row; + const uchar* s = srow; + const unsigned int* top = (const unsigned int*)(cur - step); + + // set horizontal border sentinels + cur[-1] = DIST_MAX; + cur[width] = DIST_MAX; + + unsigned int left = cur[-1]; + +#if defined(CV_SIMD) + const int BLOCK = 8; + int j = 0; + const v_uint32 v_diag = vx_setall(DIAG_DIST); + const v_uint32 v_hv = vx_setall(HV_DIST); + + for (; j <= width - BLOCK; j += BLOCK) { - if( !s[j] ) - tmp[j] = 0; - else + v_uint32 tl0 = vx_load(top + j - 1); + v_uint32 t0 = vx_load(top + j); + v_uint32 tr0 = vx_load(top + j + 1); + v_uint32 tl1 = vx_load(top + j + 3); + v_uint32 t1 = vx_load(top + j + 4); + v_uint32 tr1 = vx_load(top + j + 5); + tl0 = v_add(tl0, v_diag); + t0 = v_add(t0, v_hv); + tr0 = v_add(tr0, v_diag); + tl1 = v_add(tl1, v_diag); + t1 = v_add(t1, v_hv); + tr1 = v_add(tr1, v_diag); + v_uint32 m0 = v_min(tl0, t0); + m0 = v_min(m0, tr0); + v_uint32 m1 = v_min(tl1, t1); + m1 = v_min(m1, tr1); + v_store(cur + j, m0); + v_store(cur + j + 4, m1); + + for (int k = 0; k < BLOCK; k++) { - unsigned int t0 = tmp[j-step-1] + DIAG_DIST; - unsigned int t = tmp[j-step] + HV_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-step+1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-1] + HV_DIST; - if( t0 > t ) t0 = t; - tmp[j] = (t0 > DIST_MAX) ? DIST_MAX : t0; + int idx = j + k; + if (!s[idx]) + { + cur[idx] = 0; + left = 0; + } + else + { + unsigned int m = cur[idx]; + unsigned int lm = left + HV_DIST; + if (lm < m) m = lm; + if (m > DIST_MAX) m = DIST_MAX; + cur[idx] = m; + left = m; + } } } - tmp += step; - s += srcstep; + + for (; j < width; j++) + { + if (!s[j]) + { + cur[j] = 0; + left = 0; + } + else + { + unsigned int t0 = top[j - 1] + DIAG_DIST; + unsigned int t1 = top[j] + HV_DIST; + unsigned int t2 = top[j + 1] + DIAG_DIST; + unsigned int t3 = left + HV_DIST; + + unsigned int m = t0 < t1 ? t0 : t1; + m = m < t2 ? m : t2; + m = m < t3 ? m : t3; + + cur[j] = m > DIST_MAX ? DIST_MAX : m; + left = cur[j]; + } + } +#else + for (int j = 0; j < width; j++) + { + if (!s[j]) + { + cur[j] = 0; + left = 0; + } + else + { + unsigned int t0 = top[j - 1] + DIAG_DIST; + unsigned int t1 = top[j] + HV_DIST; + unsigned int t2 = top[j + 1] + DIAG_DIST; + unsigned int t3 = left + HV_DIST; + + unsigned int m = t0 < t1 ? t0 : t1; + m = m < t2 ? m : t2; + m = m < t3 ? m : t3; + + cur[j] = m > DIST_MAX ? DIST_MAX : m; + left = cur[j]; + } + } +#endif + + row += step; + srow += srcstep; } // backward pass - float* d = (float*)dist; - for( i = size.height - 1; i >= 0; i-- ) + float* drow = dist; + unsigned int* cur = row - step; +#if defined(CV_SIMD) + const v_float32 scale_v = vx_setall(scale); +#endif + for (int i = height - 1; i >= 0; i--) { - tmp -= step; + unsigned int* bottom = cur + step; - for( j = size.width - 1; j >= 0; j-- ) + unsigned int right = cur[width]; + for (int j = width - 1; j >= 0; j--) { - unsigned int t0 = tmp[j]; - if( t0 > HV_DIST ) + unsigned int t0 = cur[j]; + + if (t0 > HV_DIST) { - unsigned int t = tmp[j+step+1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step] + HV_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step-1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+1] + HV_DIST; - if( t0 > t ) t0 = t; - tmp[j] = t0; + unsigned int b1 = bottom[j + 1] + DIAG_DIST; + unsigned int b2 = bottom[j] + HV_DIST; + unsigned int b3 = bottom[j - 1] + DIAG_DIST; + unsigned int r = right + HV_DIST; + + unsigned int t = b1 < b2 ? b1 : b2; + t = t < b3 ? t : b3; + t = t < r ? t : r; + + if (t < t0) + { + t0 = t; + cur[j] = t0; + } } - d[j] = (float)(t0 * scale); + + right = cur[j]; } - d -= dststep; + +#if defined(CV_SIMD) + int j = 0; + const uint32_t* tptr = (const uint32_t*)cur; + float* dptr = drow; + + for (; j <= width - 8; j += 8) + { + v_uint32 v0 = vx_load(tptr + j); + v_uint32 v1 = vx_load(tptr + j + 4); + v_float32 f0 = v_mul(v_cvt_f32(v0), scale_v); + v_float32 f1 = v_mul(v_cvt_f32(v1), scale_v); + v_store(dptr + j, f0); + v_store(dptr + j + 4, f1); + } + + for (; j < width; j++) + dptr[j] = (float)(tptr[j] * scale); +#else + for (int j = 0; j < width; j++) + drow[j] = (float)(cur[j] * scale); +#endif + + cur -= step; + drow -= dststep; } } @@ -146,91 +271,248 @@ static void distanceTransform_5x5( const Mat& _src, Mat& _temp, Mat& _dist, const float* metrics ) { const int BORDER = 2; - int i, j; + const unsigned int HV_DIST = CV_FLT_TO_FIX( metrics[0], DIST_SHIFT ); const unsigned int DIAG_DIST = CV_FLT_TO_FIX( metrics[1], DIST_SHIFT ); const unsigned int LONG_DIST = CV_FLT_TO_FIX( metrics[2], DIST_SHIFT ); const unsigned int DIST_MAX = UINT_MAX - LONG_DIST; const float scale = 1.f/(1 << DIST_SHIFT); - const uchar* src = _src.ptr(); + const uchar* src = _src.ptr(); int* temp = _temp.ptr(); float* dist = _dist.ptr(_dist.rows - 1); - int srcstep = (int)(_src.step/sizeof(src[0])); - int step = (int)(_temp.step/sizeof(temp[0])); - int dststep = (int)(_dist.step/sizeof(dist[0])); - Size size = _src.size(); - initTopBottom( _temp, BORDER, DIST_MAX ); + const int srcstep = (int)_src.step; + const int step = (int)(_temp.step / sizeof(int)); + const int dststep = (int)(_dist.step / sizeof(float)); + + const int width = _src.cols; + const int height = _src.rows; + + initTopBottom(_temp, BORDER, DIST_MAX); // forward pass - unsigned int* tmp = (unsigned int*)(temp + BORDER*step) + BORDER; - const uchar* s = src; - for( i = 0; i < size.height; i++ ) - { - for( j = 0; j < BORDER; j++ ) - tmp[-j-1] = tmp[size.width + j] = DIST_MAX; + unsigned int* row = (unsigned int*)(temp + BORDER * step) + BORDER; + const uchar* srow = src; - for( j = 0; j < size.width; j++ ) + for (int i = 0; i < height; i++) + { + unsigned int* cur = row; + const uchar* s = srow; + const unsigned int* top1 = cur - step; + const unsigned int* top2 = cur - step * 2; + cur[-1] = cur[-2] = DIST_MAX; + cur[width] = cur[width + 1] = DIST_MAX; +#if defined(CV_SIMD) + unsigned int left1 = DIST_MAX; + unsigned int left2 = DIST_MAX; + const int BLOCK = 8; + int j = 0; + const v_uint32 v_hv = vx_setall(HV_DIST); + const v_uint32 v_diag = vx_setall(DIAG_DIST); + const v_uint32 v_long = vx_setall(LONG_DIST); + const v_uint32 v_max = vx_setall(DIST_MAX); + + for (; j <= width - BLOCK; j += BLOCK) { - if( !s[j] ) - tmp[j] = 0; - else + v_uint32 t2m1_0 = vx_load(top2 + j - 1); + v_uint32 t2p1_0 = vx_load(top2 + j + 1); + v_uint32 t1m2_0 = vx_load(top1 + j - 2); + v_uint32 t1m1_0 = vx_load(top1 + j - 1); + v_uint32 t1_0 = vx_load(top1 + j); + v_uint32 t1p1_0 = vx_load(top1 + j + 1); + v_uint32 t1p2_0 = vx_load(top1 + j + 2); + + t2m1_0 = v_add(t2m1_0, v_long); + t2p1_0 = v_add(t2p1_0, v_long); + t1m2_0 = v_add(t1m2_0, v_long); + t1m1_0 = v_add(t1m1_0, v_diag); + t1_0 = v_add(t1_0, v_hv); + t1p1_0 = v_add(t1p1_0, v_diag); + t1p2_0 = v_add(t1p2_0, v_long); + + v_uint32 m0 = v_min(t2m1_0, t2p1_0); + v_uint32 m0_1 = v_min(t1m2_0, t1m1_0); + v_uint32 m0_2 = v_min(t1p1_0, t1p2_0); + m0 = v_min(m0, m0_1); + m0_2 = v_min(m0_2, t1_0); + m0 = v_min(m0, m0_2); + m0 = v_min(m0, v_max); + + v_store(cur + j, m0); + + v_uint32 t2m1_1 = vx_load(top2 + j + 3); + v_uint32 t2p1_1 = vx_load(top2 + j + 5); + v_uint32 t1m2_1 = vx_load(top1 + j + 2); + v_uint32 t1m1_1 = vx_load(top1 + j + 3); + v_uint32 t1_1 = vx_load(top1 + j + 4); + v_uint32 t1p1_1 = vx_load(top1 + j + 5); + v_uint32 t1p2_1 = vx_load(top1 + j + 6); + + t2m1_1 = v_add(t2m1_1, v_long); + t2p1_1 = v_add(t2p1_1, v_long); + t1m2_1 = v_add(t1m2_1, v_long); + t1m1_1 = v_add(t1m1_1, v_diag); + t1_1 = v_add(t1_1, v_hv); + t1p1_1 = v_add(t1p1_1, v_diag); + t1p2_1 = v_add(t1p2_1, v_long); + + v_uint32 m1 = v_min(t2m1_1, t2p1_1); + v_uint32 m1_1 = v_min(t1m2_1, t1m1_1); + v_uint32 m1_2 = v_min(t1p1_1, t1p2_1); + m1 = v_min(m1, m1_1); + m1_2 = v_min(m1_2, t1_1); + m1 = v_min(m1, m1_2); + m1 = v_min(m1, v_max); + + v_store(cur + j + 4, m1); + + for (int k = 0; k < BLOCK; k++) { - unsigned int t0 = tmp[j-step*2-1] + LONG_DIST; - unsigned int t = tmp[j-step*2+1] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-step-2] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-step-1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-step] + HV_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-step+1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-step+2] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j-1] + HV_DIST; - if( t0 > t ) t0 = t; - tmp[j] = (t0 > DIST_MAX) ? DIST_MAX : t0; + int idx = j + k; + if (!s[idx]) + { + cur[idx] = 0; + left1 = 0; + left2 = 0; + } + else + { + unsigned int m = cur[idx]; + unsigned int lm = left1 + HV_DIST; + if (lm < m) m = lm; + if (m > DIST_MAX) m = DIST_MAX; + cur[idx] = m; + left2 = left1; + left1 = m; + } } } - tmp += step; - s += srcstep; + + for (; j < width; j++) + { + if (!s[j]) + { + cur[j] = 0; + left1 = left2 = 0; + } + else + { + unsigned int t0 = top2[j - 1] + LONG_DIST; + unsigned int t = top2[j + 1] + LONG_DIST; + if (t < t0) t0 = t; + t = top1[j - 2] + LONG_DIST; if (t < t0) t0 = t; + t = top1[j - 1] + DIAG_DIST; if (t < t0) t0 = t; + t = top1[j] + HV_DIST; if (t < t0) t0 = t; + t = top1[j + 1] + DIAG_DIST; if (t < t0) t0 = t; + t = top1[j + 2] + LONG_DIST; if (t < t0) t0 = t; + t = left1 + HV_DIST; if (t < t0) t0 = t; + + cur[j] = t0 > DIST_MAX ? DIST_MAX : t0; + left2 = left1; + left1 = cur[j]; + } + } +#else + unsigned int left = DIST_MAX; + for (int j = 0; j < width; j++) + { + if (!s[j]) + { + cur[j] = 0; + left = 0; + } + else + { + unsigned int t0 = top2[j - 1] + LONG_DIST; + unsigned int t = top2[j + 1] + LONG_DIST; + if (t < t0) t0 = t; + t = top1[j - 2] + LONG_DIST; + if (t < t0) t0 = t; + t = top1[j - 1] + DIAG_DIST; + if (t < t0) t0 = t; + t = top1[j] + HV_DIST; + if (t < t0) t0 = t; + t = top1[j + 1] + DIAG_DIST; + if (t < t0) t0 = t; + t = top1[j + 2] + LONG_DIST; + if (t < t0) t0 = t; + t = left + HV_DIST; + if (t < t0) t0 = t; + cur[j] = t0 > DIST_MAX ? DIST_MAX : t0; + left = cur[j]; + } + } +#endif + row += step; + srow += srcstep; } // backward pass - float* d = (float*)dist; - for( i = size.height - 1; i >= 0; i-- ) - { - tmp -= step; + float* drow = dist; + unsigned int* cur = row - step; - for( j = size.width - 1; j >= 0; j-- ) +#if defined(CV_SIMD) + const v_float32 scale_v = vx_setall(scale); +#endif + + for (int i = height - 1; i >= 0; i--) + { + unsigned int* bot1 = cur + step; + unsigned int* bot2 = cur + step * 2; + unsigned int right = DIST_MAX; + + for (int j = width - 1; j >= 0; j--) { - unsigned int t0 = tmp[j]; + unsigned int t0 = cur[j]; if( t0 > HV_DIST ) { - unsigned int t = tmp[j+step*2+1] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step*2-1] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step+2] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step+1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step] + HV_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step-1] + DIAG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+step-2] + LONG_DIST; - if( t0 > t ) t0 = t; - t = tmp[j+1] + HV_DIST; - if( t0 > t ) t0 = t; - tmp[j] = t0; + unsigned int t = bot2[j + 1] + LONG_DIST; + if (t < t0) t0 = t; + t = bot2[j - 1] + LONG_DIST; + if (t < t0) t0 = t; + t = bot1[j + 2] + LONG_DIST; + if (t < t0) t0 = t; + t = bot1[j + 1] + DIAG_DIST; + if (t < t0) t0 = t; + t = bot1[j] + HV_DIST; + if (t < t0) t0 = t; + t = bot1[j - 1] + DIAG_DIST; + if (t < t0) t0 = t; + t = bot1[j - 2] + LONG_DIST; + if (t < t0) t0 = t; + t = right + HV_DIST; + if (t < t0) t0 = t; + + cur[j] = t0; } - d[j] = (float)(t0 * scale); + right = cur[j]; } - d -= dststep; + +#if defined(CV_SIMD) + int j = 0; + const uint32_t* tptr = (const uint32_t*)cur; + float* dptr = drow; + + for (; j <= width - 8; j += 8) + { + v_uint32 v0 = vx_load(tptr + j); + v_uint32 v1 = vx_load(tptr + j + 4); + v_float32 f0 = v_mul(v_cvt_f32(v0), scale_v); + v_float32 f1 = v_mul(v_cvt_f32(v1), scale_v); + v_store(dptr + j, f0); + v_store(dptr + j + 4, f1); + } + + for (; j < width; j++) + dptr[j] = (float)(tptr[j] * scale); +#else + for (int j = 0; j < width; j++) + drow[j] = (float)(cur[j] * scale); +#endif + + cur -= step; + drow -= dststep; } } diff --git a/modules/imgproc/src/min_enclosing_convex_polygon.cpp b/modules/imgproc/src/min_enclosing_convex_polygon.cpp index c97ada4abc..2f12b0e474 100644 --- a/modules/imgproc/src/min_enclosing_convex_polygon.cpp +++ b/modules/imgproc/src/min_enclosing_convex_polygon.cpp @@ -913,6 +913,7 @@ std::vector Chains::reconstructHSidedChain(int h, int i, int j) std::vector Chains::findKSides(int k, int i, int j) { std::vector sides; + sides.reserve(k); // Optimization and avoiding GCC's false positive warning (-Wstringop-overflow) sides.push_back({i, true}); sides.push_back({j, true}); diff --git a/modules/imgproc/src/shapedescr.cpp b/modules/imgproc/src/shapedescr.cpp index d1c9af3a8e..302a718862 100644 --- a/modules/imgproc/src/shapedescr.cpp +++ b/modules/imgproc/src/shapedescr.cpp @@ -130,8 +130,31 @@ void findSecondPoint(const PT *pts, int i, Point2f ¢er, float &radius) template -static void findMinEnclosingCircle(const PT *pts, int count, Point2f ¢er, float &radius) +static void findMinEnclosingCircle(const PT *pts_in, int count, Point2f ¢er, float &radius) { + // Welzl's algorithm requires random permutation for expected O(n) time. + // Without shuffling, sorted inputs trigger O(n^3) worst case. + cv::AutoBuffer pts_buf(count); + std::copy(pts_in, pts_in + count, pts_buf.data()); + PT* pts = pts_buf.data(); + if (count > 10) + { + Cv32suf x0, y0, xn, yn; + x0.f = (float)pts[0].x; + y0.f = (float)pts[0].y; + xn.f = (float)pts[count-1].x; + yn.f = (float)pts[count-1].y; + + uint32_t seed = (uint32_t)count ^ x0.u ^ (y0.u << 8) ^ (xn.u << 16) ^ (yn.u << 24); + cv::RNG rng(seed); + + for (int i = 1; i < count; ++i) + { + int j = rng.uniform(0, i + 1); + std::swap(pts[i], pts[j]); + } + } + center.x = (float)(pts[0].x + pts[1].x) / 2.0f; center.y = (float)(pts[0].y + pts[1].y) / 2.0f; float dx = (float)(pts[0].x - pts[1].x); @@ -750,7 +773,7 @@ cv::RotatedRect cv::fitEllipseDirect( InputArray _points ) M(2,1) = (DM(0,1) + (DM(0,3)*TM(0,1) + DM(0,4)*TM(1,1) + DM(0,5)*TM(2,1))/Ts)/2.; M(2,2) = (DM(0,2) + (DM(0,3)*TM(0,2) + DM(0,4)*TM(1,2) + DM(0,5)*TM(2,2))/Ts)/2.; - double det = fabs(cv::determinant(M)); + double det = cv::determinant(M); if (fabs(det) > 1.0e-10) break; eps = (float)(s/(n*2)*1e-2); diff --git a/modules/imgproc/src/smooth.simd.hpp b/modules/imgproc/src/smooth.simd.hpp index 4bbd7722e6..6509ab532b 100644 --- a/modules/imgproc/src/smooth.simd.hpp +++ b/modules/imgproc/src/smooth.simd.hpp @@ -620,6 +620,8 @@ void inline smooth3N121Impl(const ET* src, int cn, ET *dst, int ito, int idst, int len = (width - offset) * cn; int x = offset * cn; int maxRow = min((ito - vOffset),height-vOffset); + if (v >= maxRow) return; + #if (CV_SIMD || CV_SIMD_SCALABLE) VFT v_8 = vx_setall((WET)8); const int VECSZ = VTraits::vlanes(); @@ -713,6 +715,7 @@ void inline smooth5N14641Impl(const ET* src, int cn, ET* dst, int ito, int idst, int len = (width - offset) * cn; int x = offset * cn; int maxRow = min((ito - vOffset),height-vOffset); + if (v >= maxRow) return; #if (CV_SIMD || CV_SIMD_SCALABLE) VFT v_6 = vx_setall((WET)6); diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index e54b0302ec..df2ea5e92a 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -996,6 +996,33 @@ TEST(Imgproc_Warp, regression_19566) // valgrind should detect problem if any } +TEST(Imgproc_Warp, regression_28554) +{ + const Size inSize(128, 128); + const Size outSize(256, 256); + + Mat inMat = Mat::ones(inSize, CV_16S); + Mat outMat = Mat(outSize, CV_16S); + Mat coeffs = Mat::eye(2, 3, CV_64F); + coeffs.at(0, 2) = 64.; + coeffs.at(1, 2) = 64.; + + warpAffine( + inMat, + outMat, + coeffs, + outSize, + INTER_NEAREST, + cv::BORDER_CONSTANT, + 0.0 + ); + + Mat reference = Mat::zeros(outSize, CV_16S); + reference(cv::Rect(64, 64, 128, 128)) = 1; + ASSERT_EQ(0.0, cvtest::norm(reference, outMat, NORM_INF)); +} + + TEST(Imgproc_GetAffineTransform, singularity) { Point2f A_sample[3]; diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp index 9f3e28c511..bf15437aa9 100644 --- a/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp @@ -20,6 +20,8 @@ enum CornerRefineMethod{ CORNER_REFINE_APRILTAG, ///< Tag and corners detection based on the AprilTag 2 approach @cite wang2016iros }; +static constexpr float DEFAULT_VALID_BIT_ID_THRESHOLD{0.49f}; + /** @brief struct DetectorParameters is used by ArucoDetector */ struct CV_EXPORTS_W_SIMPLE DetectorParameters { @@ -49,7 +51,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters { aprilTagQuadSigma = 0.0; aprilTagMinClusterPixels = 5; aprilTagMaxNmaxima = 10; - aprilTagCriticalRad = (float)(10* CV_PI /180); + aprilTagCriticalRad = (float)(10 * CV_PI / 180); aprilTagMaxLineFitMse = 10.0; aprilTagMinWhiteBlackDiff = 5; aprilTagDeglitch = 0; @@ -57,6 +59,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters { useAruco3Detection = false; minSideLengthCanonicalImg = 32; minMarkerLengthRatioOriginalImg = 0.0; + validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD; } /** @brief Read a new set of DetectorParameters from FileNode (use FileStorage.root()). @@ -168,12 +171,12 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters { */ CV_PROP_RW double maxErroneousBitsInBorderRate; - /** @brief minimun standard deviation in pixels values during the decodification step to apply Otsu + /** @brief minimum standard deviation in pixels values during the decodification step to apply Otsu * thresholding (otherwise, all the bits are set to 0 or 1 depending on mean higher than 128 or not) (default 5.0) */ CV_PROP_RW double minOtsuStdDev; - /// error correction rate respect to the maximun error correction capability for each dictionary (default 0.6). + /// error correction rate respect to the maximum error correction capability for each dictionary (default 0.6). CV_PROP_RW double errorCorrectionRate; /** @brief April :: User-configurable parameters. @@ -231,6 +234,9 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters { /// range [0,1], eq (2) from paper. The parameter tau_i has a direct influence on the processing speed. CV_PROP_RW float minMarkerLengthRatioOriginalImg; + + /// range [0,1], define the acceptable threshold when comparing the detected marker to the dictionary during marker identification. + CV_PROP_RW float validBitIdThreshold; }; /** @brief struct RefineParameters is used by ArucoDetector diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp index 5f6fae64df..d918e08129 100644 --- a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp +++ b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp @@ -65,6 +65,12 @@ class CV_EXPORTS_W_SIMPLE Dictionary { */ CV_WRAP bool identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const; + /** @brief Given a matrix of pixel ratio raging from 0 to 1. Returns whether if marker is identified or not. + * + * Returns reference to the marker id in the dictionary (if any) and its rotation. + */ + CV_WRAP bool identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const; + /** @brief Returns Hamming distance of the input bits to the specific id. * * If `allRotations` flag is set, the four possible marker rotations are considered @@ -84,6 +90,10 @@ class CV_EXPORTS_W_SIMPLE Dictionary { /** @brief Transform list of bytes to matrix of bits */ CV_WRAP static Mat getBitsFromByteList(const Mat &byteList, int markerSize, int rotationId = 0); + + /** @brief Get ground truth bits float + */ + CV_WRAP Mat getMarkerBits(int markerId, int rotationId = 0) const; }; diff --git a/modules/objdetect/misc/python/test/test_objdetect_aruco.py b/modules/objdetect/misc/python/test/test_objdetect_aruco.py index 6934de9266..9a85e29042 100644 --- a/modules/objdetect/misc/python/test/test_objdetect_aruco.py +++ b/modules/objdetect/misc/python/test/test_objdetect_aruco.py @@ -323,6 +323,7 @@ class aruco_objdetect_test(NewOpenCVTests): imgSize = (500, 500) params = cv.aruco.DetectorParameters() params.minDistanceToBorder = 3 + params.validBitIdThreshold = 0.5 board = cv.aruco.CharucoBoard((4, 4), 0.03, 0.015, cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)) detector = cv.aruco.CharucoDetector(board, detectorParams=params) diff --git a/modules/objdetect/src/aruco/aruco_detector.cpp b/modules/objdetect/src/aruco/aruco_detector.cpp index 9d1dcb6d57..069a7efb34 100644 --- a/modules/objdetect/src/aruco/aruco_detector.cpp +++ b/modules/objdetect/src/aruco/aruco_detector.cpp @@ -310,12 +310,11 @@ static void _detectInitialCandidates(const Mat &grey, vector > & /** - * @brief Given an input image and candidate corners, extract the bits of the candidate, including + * @brief Given an input image and candidate corners, extract the cell pixel ratio of the candidate, including * the border bits */ -static Mat _extractBits(InputArray _image, const vector& corners, int markerSize, - int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu, - OutputArray _cellPixelRatio = noArray()) { +static Mat _extractCellPixelRatio(InputArray _image, const vector& corners, int markerSize, + int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu) { CV_Assert(_image.getMat().channels() == 1); CV_Assert(corners.size() == 4ull); CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 0.5); @@ -339,11 +338,11 @@ static Mat _extractBits(InputArray _image, const vector& corners, int m warpPerspective(_image, resultImg, transformation, Size(resultImgSize, resultImgSize), INTER_NEAREST); - // output image containing the bits - Mat bits(markerSizeWithBorders, markerSizeWithBorders, CV_8UC1, Scalar::all(0)); + // output image containing the ratio of white pixels in each cell + Mat cellPixelRatio(markerSizeWithBorders, markerSizeWithBorders, CV_32FC1, Scalar::all(0)); // check if standard deviation is enough to apply Otsu - // if not enough, it probably means all bits are the same color (black or white) + // if not enough, it probably means all pixels are the same color (black or white) Mat mean, stddev; // Remove some border just to avoid border noise from perspective transformation Mat innerRegion = resultImg.colRange(cellSize / 2, resultImg.cols - cellSize / 2) @@ -351,18 +350,13 @@ static Mat _extractBits(InputArray _image, const vector& corners, int m meanStdDev(innerRegion, mean, stddev); if(stddev.ptr< double >(0)[0] < minStdDevOtsu) { // all black or all white, depending on mean value - if(mean.ptr< double >(0)[0] > 127) - bits.setTo(1); - else - bits.setTo(0); - if(_cellPixelRatio.needed()) bits.convertTo(_cellPixelRatio, CV_32F); - return bits; - } + if(mean.ptr< double >(0)[0] > 127){ + cellPixelRatio.setTo(1); + } else { + cellPixelRatio.setTo(0); + } - Mat cellPixelRatio; - if (_cellPixelRatio.needed()) { - _cellPixelRatio.create(markerSizeWithBorders, markerSizeWithBorders, CV_32FC1); - cellPixelRatio = _cellPixelRatio.getMatRef(); + return cellPixelRatio; } // now extract code, first threshold using Otsu @@ -377,38 +371,40 @@ static Mat _extractBits(InputArray _image, const vector& corners, int m cellSize - 2 * cellMarginPixels)); // count white pixels on each cell to assign its value size_t nZ = (size_t) countNonZero(square); - if(nZ > square.total() / 2) bits.at(y, x) = 1; // define the cell pixel ratio as the ratio of the white pixels. For inverted markers, the ratio will be inverted. - if(_cellPixelRatio.needed()) cellPixelRatio.at(y, x) = (nZ / (float)square.total()); + cellPixelRatio.at(y, x) = (nZ / (float)square.total()); } } - return bits; + return cellPixelRatio; } /** - * @brief Return number of erroneous bits in border, i.e. number of white bits in border. + * @brief Return number of erroneous bits in border, i.e. bits for which pixel ratio > validBitIdThreshold. */ -static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) { + static int _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold) { int sizeWithBorders = markerSize + 2 * borderSize; - CV_Assert(markerSize > 0 && bits.cols == sizeWithBorders && bits.rows == sizeWithBorders); + CV_Assert(markerSize > 0 && cellPixelRatio.cols == sizeWithBorders && cellPixelRatio.rows == sizeWithBorders); + // Get border error. cellPixelRatio has the opposite color as the borders. int totalErrors = 0; for(int y = 0; y < sizeWithBorders; y++) { for(int k = 0; k < borderSize; k++) { - if(bits.ptr(y)[k] != 0) totalErrors++; - if(bits.ptr(y)[sizeWithBorders - 1 - k] != 0) totalErrors++; + // Left and right vertical sides + if(cellPixelRatio.ptr(y)[k] > validBitIdThreshold) totalErrors++; + if(cellPixelRatio.ptr(y)[sizeWithBorders - 1 - k] > validBitIdThreshold) totalErrors++; } } for(int x = borderSize; x < sizeWithBorders - borderSize; x++) { for(int k = 0; k < borderSize; k++) { - if(bits.ptr(k)[x] != 0) totalErrors++; - if(bits.ptr(sizeWithBorders - 1 - k)[x] != 0) totalErrors++; + // Top and bottom horizontal sides + if(cellPixelRatio.ptr(k)[x] > validBitIdThreshold) totalErrors++; + if(cellPixelRatio.ptr(sizeWithBorders - 1 - k)[x] > validBitIdThreshold) totalErrors++; } } return totalErrors; @@ -482,49 +478,43 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i scaled_corners[i].y = _corners[i].y * scale; } - Mat cellPixelRatio; - Mat candidateBits = - _extractBits(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits, - params.perspectiveRemovePixelPerCell, - params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev, - cellPixelRatio); + Mat cellPixelRatio = + _extractCellPixelRatio(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits, + params.perspectiveRemovePixelPerCell, + params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev); // analyze border bits int maximumErrorsInBorder = - int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate); + int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate); int borderErrors = - _getBorderErrors(candidateBits, dictionary.markerSize, params.markerBorderBits); + _getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold); // check if it is a white marker if(params.detectInvertedMarker){ - // to get from 255 to 1 - Mat invertedImg = ~candidateBits-254; - int invBError = _getBorderErrors(invertedImg, dictionary.markerSize, params.markerBorderBits); + Mat invCellPixelRatio = 1.f - cellPixelRatio; + int invBError = _getBorderErrors(invCellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold); // white marker if(invBError maximumErrorsInBorder) return 0; // border is wrong // take only inner bits - Mat onlyBits = - candidateBits.rowRange(params.markerBorderBits, - candidateBits.rows - params.markerBorderBits) - .colRange(params.markerBorderBits, candidateBits.cols - params.markerBorderBits); + Mat onlyCellPixelRatio = + cellPixelRatio.rowRange(params.markerBorderBits, + cellPixelRatio.rows - params.markerBorderBits) + .colRange(params.markerBorderBits, cellPixelRatio.cols - params.markerBorderBits); - // try to indentify the marker - if(!dictionary.identify(onlyBits, idx, rotation, params.errorCorrectionRate)) + // try to identify the marker + if(!dictionary.identify(onlyCellPixelRatio, idx, rotation, params.errorCorrectionRate, params.validBitIdThreshold)) return 0; // compute the candidate's confidence if(confidenceNeeded) { - Mat groundTruthbits; - Mat bitsUints = dictionary.getBitsFromByteList(dictionary.bytesList.rowRange(idx, idx + 1), dictionary.markerSize, rotation); - bitsUints.convertTo(groundTruthbits, CV_32F); + Mat groundTruthbits = dictionary.getMarkerBits(idx, rotation); markerConfidence = _getMarkerConfidence(groundTruthbits, cellPixelRatio, dictionary.markerSize, params.markerBorderBits); } @@ -1403,11 +1393,14 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board if(refineParams.errorCorrectionRate >= 0) { // extract bits - Mat bits = _extractBits( + Mat cellPixelRatio = _extractCellPixelRatio( grey, rotatedMarker, dictionary.markerSize, detectorParams.markerBorderBits, detectorParams.perspectiveRemovePixelPerCell, detectorParams.perspectiveRemoveIgnoredMarginPerCell, detectorParams.minOtsuStdDev); + Mat bits; + cellPixelRatio.convertTo(bits, CV_8UC1); + Mat onlyBits = bits.rowRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits) .colRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits); diff --git a/modules/objdetect/src/aruco/aruco_dictionary.cpp b/modules/objdetect/src/aruco/aruco_dictionary.cpp index 350ccc63eb..7dfdcc3009 100644 --- a/modules/objdetect/src/aruco/aruco_dictionary.cpp +++ b/modules/objdetect/src/aruco/aruco_dictionary.cpp @@ -73,25 +73,32 @@ void Dictionary::writeDictionary(FileStorage& fs, const String &name) } -bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double maxCorrectionRate) const { - CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize); +bool Dictionary::identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const { + CV_Assert(onlyCellPixelRatio.rows == markerSize && onlyCellPixelRatio.cols == markerSize); int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate); - // get as a byte list - Mat candidateBytes = getByteListFromBits(onlyBits); - idx = -1; // by default, not found // search closest marker in dict for(int m = 0; m < bytesList.rows; m++) { int currentMinDistance = markerSize * markerSize + 1; int currentRotation = -1; - for(unsigned int r = 0; r < 4; r++) { - int currentHamming = cv::hal::normHamming( - bytesList.ptr(m)+r*candidateBytes.cols, - candidateBytes.ptr(), - candidateBytes.cols); + for(int r = 0; r < 4; r++) { + + Mat bitsRot = getBitsFromByteList(bytesList.rowRange(m, m + 1), markerSize, r); + bitsRot.convertTo(bitsRot, CV_32F); + + // Loop over all bits dictBitsList [m, markerSize * markerSize, 4]; onlyCellPixelRatio [markerSize, markerSize] + int currentHamming = 0; + for(int i = 0; i < markerSize; i++) { + for(int j = 0; j < markerSize; j++) { + // If detected bit is too far from the ground truth, consider it false. + if(fabs(onlyCellPixelRatio.at(i, j) - static_cast(bitsRot.at(i, j))) > validBitIdThreshold){ + currentHamming++; + } + } + } if(currentHamming < currentMinDistance) { currentMinDistance = currentHamming; @@ -111,6 +118,16 @@ bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double m } +bool Dictionary::identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const { + CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize); + + Mat candidateBitRatio; + onlyBits.convertTo(candidateBitRatio, CV_32F); + const float validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD; + return identify(candidateBitRatio, idx, rotation, maxCorrectionRate, validBitIdThreshold); +} + + int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const { CV_Assert(id >= 0 && id < bytesList.rows); @@ -147,7 +164,8 @@ void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, i Mat innerRegion = tinyMarker.rowRange(borderBits, tinyMarker.rows - borderBits) .colRange(borderBits, tinyMarker.cols - borderBits); // put inner bits - Mat bits = 255 * getBitsFromByteList(bytesList.rowRange(id, id + 1), markerSize); + Mat bits = getMarkerBits(id); + bits.convertTo(bits, CV_8U, 255.0); CV_Assert(innerRegion.total() == bits.total()); bits.copyTo(innerRegion); @@ -194,12 +212,28 @@ Mat Dictionary::getByteListFromBits(const Mat &bits) { } +Mat Dictionary::getMarkerBits(int markerId, int rotationId) const { + + const int nbRotations = 4; + CV_Assert(markerId < bytesList.rows); + CV_Assert(rotationId < nbRotations); + + Mat bits(markerSize, markerSize, CV_32F, Scalar::all(0)); + Mat bitsUints = getBitsFromByteList(bytesList.rowRange(markerId, markerId + 1), markerSize, rotationId); + bitsUints.convertTo(bits, CV_32F); + + CV_Assert(bits.rows == markerSize && bits.cols == markerSize); + return bits; +} + + Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize, int rotationId) { CV_Assert(byteList.total() > 0 && byteList.total() >= (unsigned int)markerSize * markerSize / 8 && byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1); - CV_Assert(rotationId >=0 && rotationId < 4); + CV_Assert(byteList.channels() >= 4); + CV_Assert(rotationId >= 0 && rotationId < 4); Mat bits(markerSize, markerSize, CV_8UC1, Scalar::all(0)); diff --git a/modules/objdetect/test/test_aruco_tutorial.cpp b/modules/objdetect/test/test_aruco_tutorial.cpp index 182c6a2cdc..686633504d 100644 --- a/modules/objdetect/test/test_aruco_tutorial.cpp +++ b/modules/objdetect/test/test_aruco_tutorial.cpp @@ -189,6 +189,7 @@ TEST(CV_ArucoTutorial, can_find_diamondmarkers) aruco::DetectorParameters detectorParams; detectorParams.readDetectorParameters(fs.root()); detectorParams.cornerRefinementMethod = aruco::CORNER_REFINE_APRILTAG; + detectorParams.validBitIdThreshold = 0.5f; aruco::CharucoBoard charucoBoard(Size(3, 3), 0.4f, 0.25f, dictionary); aruco::CharucoDetector detector(charucoBoard, aruco::CharucoParameters(), detectorParams); diff --git a/modules/objdetect/test/test_arucodetection.cpp b/modules/objdetect/test/test_arucodetection.cpp index 4ee91967f2..28bc132d6f 100644 --- a/modules/objdetect/test/test_arucodetection.cpp +++ b/modules/objdetect/test/test_arucodetection.cpp @@ -473,7 +473,7 @@ markerDetectionGT applyTemperingToMarkerCells(cv::Mat &marker, ++cellsTempered; // cell too tempered, no detection expected - if(cellTempConfig.cellRatioToTemper > 0.5f) { + if(cellTempConfig.cellRatioToTemper > params.validBitIdThreshold) { if(isBorder){ ++borderErrors; } else { @@ -556,7 +556,7 @@ static void runArucoDetectionConfidence(ArucoAlgParams arucoAlgParam) { // make sure there are no bits have any detection errors params.maxErroneousBitsInBorderRate = 0.0; params.errorCorrectionRate = 0.0; - params.perspectiveRemovePixelPerCell = 8; // esnsure that there is enough resolution to properly handle distortions + params.perspectiveRemovePixelPerCell = 8; // ensure that there is enough resolution to properly handle distortions aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params); const bool detectInvertedMarker = (arucoAlgParam == ArucoAlgParams::DETECT_INVERTED_MARKER); @@ -674,6 +674,144 @@ static void runArucoDetectionConfidence(ArucoAlgParams arucoAlgParam) { } } + +// Helper struc and functions for CV_ArucoDetectionUnc +struct ArucoThresholdTestConfig { + MarkerTemperingConfig markerTemperingConfig; // Configuration of cells to invert (percentage, number and markerRegionToTemper) + float validBitIdThreshold; // range [0,1], define the acceptable threshold when comparing the detected marker to the dictionary during marker identification. + float perspectiveRemoveIgnoredMarginPerCell; // Width of the margin of pixels on each cell not considered for the marker identification + int markerBorderBits; // Number of bits of the marker border + float distortionRatio; // Percentage of offset used for perspective distortion, bigger means more distorted +}; + +/** + * @brief Test the param validBitIdThreshold + * Loops over a set of detector configurations (validBitIdThreshold, distortion, DetectorParameters such as markerBorderBits) + * For each configuration, it creates a synthetic image containing four markers arranged in a 2x2 grid. + * Each marker is generated with its own configuration (id, size, rotation). + * Make sure that markers are detected or not based on validBitIdThreshold and percentage of tempering. + * Finally, it runs the detector and checks that each marker is detected or not based on the threshold. + * + */ +static void runArucoDetectionThreshold(ArucoAlgParams arucoAlgParam) { + + aruco::DetectorParameters params; + // make sure there are no bits have any detection errors + params.perspectiveRemovePixelPerCell = 20; // ensure that there is enough resolution to properly handle distortions + params.maxErroneousBitsInBorderRate = 0.f; + params.errorCorrectionRate = 0.f; + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_5X5_250), params); // Max correction: 6bits + + const bool detectInvertedMarker = (arucoAlgParam == ArucoAlgParams::DETECT_INVERTED_MARKER); + + // define several detector configurations to test different settings + // {{MarkerTemperingConfig}, validBitIdThreshold, perspectiveRemoveIgnoredMarginPerCell, markerBorderBits, distortionRatio} + + vector detectorConfigs = { + // No tempering, expect detection for every threshold + {{0.f, 0, MarkerRegionToTemper::ALL}, 0.3f, 0.f, 1, 0.f}, + {{0.f, 0, MarkerRegionToTemper::ALL}, 0.5f, 0.f, 1, 0.f}, + {{0.f, 0, MarkerRegionToTemper::ALL}, 0.9f, 0.f, 1, 0.f}, + // Include distortions + {{0.f, 0, MarkerRegionToTemper::ALL}, 0.3f, 0.f, 1, 0.05f}, + {{0.f, 0, MarkerRegionToTemper::ALL}, 0.5f, 0.f, 1, 0.1f}, + {{0.f, 0, MarkerRegionToTemper::ALL}, 0.9f, 0.f, 1, 0.2f}, + + // 20% temper, expect detection with threshold above 0.2 + {{0.2f, 5, MarkerRegionToTemper::BORDER}, 0.30f, 0.f, 1, 0.f}, // Detection + + {{0.2f, 1, MarkerRegionToTemper::BORDER}, 0.18f, 0.f, 1, 0.f}, // No detection + {{0.2f, 1, MarkerRegionToTemper::BORDER}, 0.18f, 0.f, 1, 0.f}, // No detection + {{0.2f, 10, MarkerRegionToTemper::INNER}, 0.22f, 0.f, 1, 0.f}, // Detection + {{0.2f, 1, MarkerRegionToTemper::INNER}, 0.18f, 0.f, 1, 0.f} // No detection + + // distortions + }; + + // define marker configurations for the 4 markers in each image + const int markerSidePixels = 700; // To simplify the cell division, markerSidePixels is a multiple of 7. (5x5 dict + 2 border bits) + vector markerCreationConfig = { + {0, markerSidePixels, markerRot::ROT_90}, // {id, markerSidePixels, rotation} + {1, markerSidePixels, markerRot::ROT_270}, + {2, markerSidePixels, markerRot::NONE}, + {3, markerSidePixels, markerRot::ROT_180} + }; + + // loop over each detector configuration + for (size_t cfgIdx = 0; cfgIdx < detectorConfigs.size(); cfgIdx++) { + ArucoThresholdTestConfig detCfg = detectorConfigs[cfgIdx]; + + // update detector parameters + params.validBitIdThreshold =detCfg.validBitIdThreshold; + params.perspectiveRemoveIgnoredMarginPerCell = detCfg.perspectiveRemoveIgnoredMarginPerCell; + params.markerBorderBits = detCfg.markerBorderBits; + params.detectInvertedMarker = detectInvertedMarker; + detector.setDetectorParameters(params); + + // create a blank image large enough to hold 4 markers in a 2x2 grid + const int margin = markerSidePixels / 2; + const int imageSize = (markerSidePixels * 2) + margin * 3; + Mat img(imageSize, imageSize, CV_8UC1, Scalar(255)); + + vector groundTruths; + const aruco::Dictionary &dictionary = detector.getDictionary(); + + // place each marker into the image + for (int row = 0; row < 2; row++) { + for (int col = 0; col < 2; col++) { + int index = row * 2 + col; + MarkerCreationConfig markerCfg = markerCreationConfig[index]; + // adjust marker id to be unique for each detector configuration + markerCfg.id += static_cast(cfgIdx * markerCreationConfig.size()); + + // generate img + Mat markerImg; + markerDetectionGT gt = generateTemperedMarkerImage(markerImg, markerCfg, detCfg.markerTemperingConfig, params, dictionary, detCfg.distortionRatio); + + groundTruths.push_back(gt); + + // place marker in the image + Point2f topLeft(static_cast(margin + col * (markerSidePixels + margin)), + static_cast(margin + row * (markerSidePixels + margin))); + placeMarker(img, markerImg, topLeft); + } + } + + // if testing inverted markers globally, invert the whole image + if (detectInvertedMarker) { + bitwise_not(img, img); + } + + // run detection. + vector> corners, rejected; + vector ids; + vector markerConfidence; + detector.detectMarkersWithConfidence(img, corners, ids, markerConfidence, rejected); + + ASSERT_EQ(ids.size(), corners.size()); + ASSERT_EQ(ids.size(), markerConfidence.size()); + + std::map confidenceById; + for (size_t i = 0; i < ids.size(); i++) { + confidenceById[ids[i]] = markerConfidence[i]; + } + + // verify that every marker is detected and its confidence is within tolerance + for (const auto& currentGT : groundTruths) { + const auto it = confidenceById.find(currentGT.id); + const bool detected = it != confidenceById.end(); + EXPECT_EQ(currentGT.expectDetection, detected) + << "Marker id: " << currentGT.id << " (detector config " << cfgIdx << ")"; + + if (currentGT.expectDetection && detected) { + EXPECT_NEAR(currentGT.confidence, it->second, 0.05) + << "Marker id: " << currentGT.id << " (detector config " << cfgIdx << ")"; + } + } + } +} + + /** * @brief Check max and min size in marker detection parameters */ @@ -981,6 +1119,14 @@ TEST(CV_InvertedFlagArucoDetectionConfidence, algorithmic) { } } +TEST(CV_ArucoDetectionThreshold, algorithmic) { + runArucoDetectionThreshold(ArucoAlgParams::USE_DEFAULT); +} + +TEST(CV_InvertedArucoDetectionThreshold, algorithmic) { + runArucoDetectionThreshold(ArucoAlgParams::DETECT_INVERTED_MARKER); +} + TEST(CV_ArucoDetectMarkers, regression_3192) { aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50)); @@ -1304,6 +1450,7 @@ TEST_P(ArucoThreading, number_of_threads_does_not_change_results) aruco::DetectorParameters detectorParameters = detector.getDetectorParameters(); detectorParameters.cornerRefinementMethod = (int)GetParam(); + detectorParameters.validBitIdThreshold = 0.5f; detector.setDetectorParameters(detectorParameters); vector > original_corners; diff --git a/modules/objdetect/test/test_boarddetection.cpp b/modules/objdetect/test/test_boarddetection.cpp index 312bb5b56b..f95413ab32 100644 --- a/modules/objdetect/test/test_boarddetection.cpp +++ b/modules/objdetect/test/test_boarddetection.cpp @@ -66,6 +66,7 @@ void CV_ArucoBoardPose::run(int) { vector > corners; vector ids; detectorParameters.markerBorderBits = markerBorder; + detectorParameters.validBitIdThreshold = 0.5f; detector.setDetectorParameters(detectorParameters); detector.detectMarkers(img, corners, ids); diff --git a/modules/python/package/cv2/__init__.py b/modules/python/package/cv2/__init__.py index 7e148fc9f2..11994a2cd3 100644 --- a/modules/python/package/cv2/__init__.py +++ b/modules/python/package/cv2/__init__.py @@ -26,7 +26,7 @@ def __load_extra_py_code_for_module(base, name, enable_debug_print=False): native_module = sys.modules.pop(module_name, None) try: py_module = importlib.import_module(module_name) - except ImportError as err: + except (ImportError, AttributeError) as err: if enable_debug_print: print("Can't load Python code for module:", module_name, ". Reason:", err) diff --git a/modules/python/src2/copy_typings_stubs_on_success.py b/modules/python/src2/copy_typings_stubs_on_success.py index efcf84ee82..eb5a2e4e6c 100644 --- a/modules/python/src2/copy_typings_stubs_on_success.py +++ b/modules/python/src2/copy_typings_stubs_on_success.py @@ -13,6 +13,21 @@ else: from distutils.dir_util import copy_tree +def _remove_stale_pyi_files(directory): + """Remove .pyi files and py.typed markers from the directory tree. + + During incremental builds, disabling a previously enabled module leaves + stale typing stubs in the loader directory from a previous copy. Since + copy_tree merges rather than replaces, those stale files persist. + Removing all stub files before copying ensures only stubs for currently + enabled modules are present. Runtime .py files are not affected. + """ + for dirpath, dirnames, filenames in os.walk(directory): + for fname in filenames: + if fname.endswith('.pyi') or fname == 'py.typed': + os.remove(os.path.join(dirpath, fname)) + + def main(): args = parse_arguments() py_typed_path = os.path.join(args.stubs_dir, 'py.typed') @@ -24,6 +39,8 @@ def main(): 'generation phase.'.format(py_typed_path) ) return + if os.path.isdir(args.output_dir): + _remove_stale_pyi_files(args.output_dir) copy_tree(args.stubs_dir, args.output_dir) diff --git a/modules/python/src2/cv2.cpp b/modules/python/src2/cv2.cpp index 443a582d9a..87ffd72790 100644 --- a/modules/python/src2/cv2.cpp +++ b/modules/python/src2/cv2.cpp @@ -227,7 +227,7 @@ static PyObject* createSubmodule(PyObject* parent_module, const std::string& nam } /// Populates parent module dictionary. Submodule lifetime should be managed /// by the global modules dictionary and parent module dictionary, so Py_DECREF after - /// successfull call to the `PyDict_SetItemString` is redundant. + /// successful call to the `PyDict_SetItemString` is redundant. if (PyDict_SetItemString(parent_module_dict, submodule_name.c_str(), submodule) < 0) { return PyErr_Format(PyExc_ImportError, "Can't register a submodule '%s' (full name: '%s')", diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py index 7290878efc..60ab2ba78b 100644 --- a/modules/python/src2/typing_stubs_generation/api_refinement.py +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -8,7 +8,7 @@ from .nodes import (NamespaceNode, FunctionNode, OptionalTypeNode, TypeNode, ClassProperty, PrimitiveTypeNode, ASTNodeTypeNode, AggregatedTypeNode, CallableTypeNode, AnyTypeNode, TupleTypeNode, UnionTypeNode, ProtocolClassNode, - DictTypeNode, ClassTypeNode) + DictTypeNode, ClassTypeNode, AliasRefTypeNode) from .ast_utils import (find_function_node, SymbolName, for_each_function_overload) from .types_conversion import create_type_node @@ -376,6 +376,62 @@ def _find_argument_index(arguments: Sequence[FunctionNode.Arg], return None +def make_matlike_or_scalar_arg(*arg_names: str) -> Callable[[NamespaceNode, SymbolName], None]: + """Make arguments accept both MatLike and Scalar types. + + This is used for functions like inRange where the C++ InputArray parameter + can accept both Mat objects and Scalar values (tuples, floats, etc.). + + Example: cv2.inRange(img, (0, 0, 0), (255, 255, 255)) should be valid. + """ + def _make_matlike_or_scalar_arg(root_node: NamespaceNode, + function_symbol_name: SymbolName) -> None: + from .predefined_types import PREDEFINED_TYPES + + function = find_function_node(root_node, function_symbol_name) + for arg_name in arg_names: + found_overload_with_arg = False + + for overload in function.overloads: + arg_idx = _find_argument_index(overload.arguments, arg_name) + + # skip overloads without this argument + if arg_idx is None: + continue + + current_type = overload.arguments[arg_idx].type_node + + # Check if it's already a union or if it already includes Scalar + if isinstance(current_type, UnionTypeNode): + # Check if Scalar is already in the union + has_scalar = any( + isinstance(item, AliasRefTypeNode) and item.typename == "Scalar" + for item in current_type.items + ) + if has_scalar: + continue + # Add Scalar to existing union + scalar_ref = AliasRefTypeNode("Scalar") + current_type.items = current_type.items + (scalar_ref,) + else: + # Create a union of current type and Scalar + scalar_ref = AliasRefTypeNode("Scalar") + overload.arguments[arg_idx].type_node = UnionTypeNode( + f"{arg_name}_type", + (cast(TypeNode, current_type), scalar_ref) + ) + + found_overload_with_arg = True + + if not found_overload_with_arg: + raise RuntimeError( + f"Failed to find argument with name: '{arg_name}'" + f" in '{function_symbol_name.name}' overloads" + ) + + return _make_matlike_or_scalar_arg + + NODES_TO_REFINE = { SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"), SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"), @@ -401,6 +457,11 @@ NODES_TO_REFINE = { SymbolName(("cv", "fisheye"), (), "initUndistortRectifyMap"): make_optional_arg("D"), SymbolName(("cv", ), (), "imread"): make_optional_none_return, SymbolName(("cv", ), (), "imdecode"): make_optional_none_return, + SymbolName(("cv", ), (), "HoughCircles"): make_optional_none_return, + SymbolName(("cv", ), (), "HoughLines"): make_optional_none_return, + SymbolName(("cv", ), (), "HoughLinesP"): make_optional_none_return, + # Fix for issue #28534: inRange should accept Scalar for lowerb and upperb + SymbolName(("cv", ), (), "inRange"): make_matlike_or_scalar_arg("lowerb", "upperb"), } ERROR_CLASS_PROPERTIES = ( diff --git a/modules/python/src2/typing_stubs_generation/generation.py b/modules/python/src2/typing_stubs_generation/generation.py index 084a37a94b..f27f64df0a 100644 --- a/modules/python/src2/typing_stubs_generation/generation.py +++ b/modules/python/src2/typing_stubs_generation/generation.py @@ -3,6 +3,7 @@ __all__ = ("generate_typing_stubs", ) from io import StringIO from pathlib import Path import re +import shutil from typing import (Callable, NamedTuple, Union, Set, Dict, Collection, Tuple, List) import warnings @@ -24,6 +25,22 @@ from .nodes.type_node import (TypeNode, AliasTypeNode, AliasRefTypeNode, ConditionalAliasTypeNode, PrimitiveTypeNode) +def _clean_stale_stubs_dirs(stubs_root: Path) -> None: + """Remove all subdirectories under stubs_root. + + During incremental builds, disabling a previously enabled module leaves + behind its typing stub directory (e.g. cv2/gapi/). Removing all + subdirectories before regeneration ensures only stubs for currently + enabled modules are present. Top-level files (py.typed, __init__.pyi) + are kept because they are managed separately. + """ + if not stubs_root.is_dir(): + return + for item in stubs_root.iterdir(): + if item.is_dir(): + shutil.rmtree(item) + + def generate_typing_stubs(root: NamespaceNode, output_path: Path): """Generates typing stubs for the AST with root `root` and outputs created files tree to directory pointed by `output_path`. @@ -88,6 +105,12 @@ def generate_typing_stubs(root: NamespaceNode, output_path: Path): # The whole process should fail !only! when all possible scopes are # checked and at least 1 node is still unresolved. root.resolve_type_nodes() + # Remove stale typing stub subdirectories from previous builds. + # In incremental builds, disabling a module (e.g. -DBUILD_opencv_gapi=OFF) + # no longer generates its stubs, but leftover directories from a previous + # build persist and propagate through the copy/install steps, causing + # type-checker errors for stubs referencing unavailable modules. + _clean_stale_stubs_dirs(Path(output_path) / root.export_name) _generate_typing_module(root, output_path) _populate_reexported_symbols(root) _generate_typing_stubs(root, output_path) @@ -708,7 +731,7 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None: """ def has_all_required_modules(type_node: TypeNode) -> bool: - return all(em in root.namespaces for em in node.required_modules) + return all(em in root.namespaces for em in type_node.required_modules) def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None: assert isinstance(type_node, AggregatedTypeNode), \ diff --git a/modules/stereo/test/test_stereomatching.cpp b/modules/stereo/test/test_stereomatching.cpp index 826c4c8a3b..9ea16110b1 100644 --- a/modules/stereo/test/test_stereomatching.cpp +++ b/modules/stereo/test/test_stereomatching.cpp @@ -219,7 +219,7 @@ void computeOcclusionBasedMasks( const Mat& leftDisp, const Mat& _rightDisp, } /* - Calculate depth discontinuty regions: pixels whose neiboring disparities differ by more than + Calculate depth discontinuity regions: pixels whose neighboring disparities differ by more than dispGap, dilated by window of width discontWidth. */ void computeDepthDiscontMask( const Mat& disp, Mat& depthDiscontMask, const Mat& unknDispMask = Mat(), diff --git a/modules/video/CMakeLists.txt b/modules/video/CMakeLists.txt index 4e5bd36a14..410823389c 100644 --- a/modules/video/CMakeLists.txt +++ b/modules/video/CMakeLists.txt @@ -1,4 +1,5 @@ set(the_description "Video Analysis") +ocv_add_dispatched_file(lkpyramid SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL) ocv_define_module(video opencv_imgproc OPTIONAL diff --git a/modules/video/src/lkpyramid.cpp b/modules/video/src/lkpyramid.cpp index 25fdc35c5b..6a01c03a5d 100644 --- a/modules/video/src/lkpyramid.cpp +++ b/modules/video/src/lkpyramid.cpp @@ -12,6 +12,7 @@ // // Copyright (C) 2000, Intel Corporation, all rights reserved. // Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, @@ -72,85 +73,7 @@ static void calcScharrDeriv(const cv::Mat& src, cv::Mat& dst) void cv::detail::ScharrDerivInvoker::operator()(const Range& range) const { - using cv::detail::deriv_type; - int rows = src.rows, cols = src.cols, cn = src.channels(), colsn = cols*cn; - - int x, y, delta = (int)alignSize((cols + 2)*cn, 16); - AutoBuffer _tempBuf(delta*2 + 64); - deriv_type *trow0 = alignPtr(_tempBuf.data() + cn, 16), *trow1 = alignPtr(trow0 + delta, 16); - -#if CV_SIMD128 - v_int16x8 c3 = v_setall_s16(3), c10 = v_setall_s16(10); -#endif - - for( y = range.start; y < range.end; y++ ) - { - const uchar* srow0 = src.ptr(y > 0 ? y-1 : rows > 1 ? 1 : 0); - const uchar* srow1 = src.ptr(y); - const uchar* srow2 = src.ptr(y < rows-1 ? y+1 : rows > 1 ? rows-2 : 0); - deriv_type* drow = (deriv_type *)dst.ptr(y); - - // do vertical convolution - x = 0; -#if CV_SIMD128 - { - for( ; x <= colsn - 8; x += 8 ) - { - v_int16x8 s0 = v_reinterpret_as_s16(v_load_expand(srow0 + x)); - v_int16x8 s1 = v_reinterpret_as_s16(v_load_expand(srow1 + x)); - v_int16x8 s2 = v_reinterpret_as_s16(v_load_expand(srow2 + x)); - - v_int16x8 t1 = v_sub(s2, s0); - v_int16x8 t0 = v_add(v_mul_wrap(v_add(s0, s2), c3), v_mul_wrap(s1, c10)); - - v_store(trow0 + x, t0); - v_store(trow1 + x, t1); - } - } -#endif - - for( ; x < colsn; x++ ) - { - int t0 = (srow0[x] + srow2[x])*3 + srow1[x]*10; - int t1 = srow2[x] - srow0[x]; - trow0[x] = (deriv_type)t0; - trow1[x] = (deriv_type)t1; - } - - // make border - int x0 = (cols > 1 ? 1 : 0)*cn, x1 = (cols > 1 ? cols-2 : 0)*cn; - for( int k = 0; k < cn; k++ ) - { - trow0[-cn + k] = trow0[x0 + k]; trow0[colsn + k] = trow0[x1 + k]; - trow1[-cn + k] = trow1[x0 + k]; trow1[colsn + k] = trow1[x1 + k]; - } - - // do horizontal convolution, interleave the results and store them to dst - x = 0; -#if CV_SIMD128 - { - for( ; x <= colsn - 8; x += 8 ) - { - v_int16x8 s0 = v_load(trow0 + x - cn); - v_int16x8 s1 = v_load(trow0 + x + cn); - v_int16x8 s2 = v_load(trow1 + x - cn); - v_int16x8 s3 = v_load(trow1 + x); - v_int16x8 s4 = v_load(trow1 + x + cn); - - v_int16x8 t0 = v_sub(s1, s0); - v_int16x8 t1 = v_add(v_mul_wrap(v_add(s2, s4), c3), v_mul_wrap(s3, c10)); - - v_store_interleave((drow + x*2), t0, t1); - } - } -#endif - for( ; x < colsn; x++ ) - { - deriv_type t0 = (deriv_type)(trow0[x+cn] - trow0[x-cn]); - deriv_type t1 = (deriv_type)((trow1[x+cn] + trow1[x-cn])*3 + trow1[x]*10); - drow[x*2] = t0; drow[x*2+1] = t1; - } - } + ScharrDerivInvoker_impl(src, const_cast(dst), range); } cv::detail::LKTrackerInvoker::LKTrackerInvoker( diff --git a/modules/video/src/lkpyramid.dispatch.cpp b/modules/video/src/lkpyramid.dispatch.cpp new file mode 100644 index 0000000000..2775d9af0a --- /dev/null +++ b/modules/video/src/lkpyramid.dispatch.cpp @@ -0,0 +1,22 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. + +#include "precomp.hpp" +#include "lkpyramid.hpp" +#include "opencv2/core/hal/intrin.hpp" + +#include "lkpyramid.simd.hpp" +#include "lkpyramid.simd_declarations.hpp" // defines CV_CPU_DISPATCH_MODES_ALL based on CMakeLists.txt + +namespace cv { +namespace detail { + +void ScharrDerivInvoker_impl(const Mat& src, Mat& dst, const Range& range) +{ + CV_CPU_DISPATCH(ScharrDerivInvoker_SIMD, (src, dst, range), CV_CPU_DISPATCH_MODES_ALL); +} + +} // namespace detail +} // namespace cv diff --git a/modules/video/src/lkpyramid.hpp b/modules/video/src/lkpyramid.hpp index 62fab0564b..18ff61c75e 100644 --- a/modules/video/src/lkpyramid.hpp +++ b/modules/video/src/lkpyramid.hpp @@ -7,6 +7,8 @@ namespace detail typedef short deriv_type; + void ScharrDerivInvoker_impl(const Mat& src, Mat& dst, const Range& range); + struct ScharrDerivInvoker : ParallelLoopBody { ScharrDerivInvoker(const Mat& _src, const Mat& _dst) diff --git a/modules/video/src/lkpyramid.simd.hpp b/modules/video/src/lkpyramid.simd.hpp new file mode 100644 index 0000000000..4408b8affc --- /dev/null +++ b/modules/video/src/lkpyramid.simd.hpp @@ -0,0 +1,118 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. +#include "precomp.hpp" +#include "lkpyramid.hpp" +#include "opencv2/core/hal/intrin.hpp" + +namespace cv { +namespace detail { + +CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN + +// SIMD-optimized implementation +void ScharrDerivInvoker_SIMD(const Mat& src, Mat& dst, const Range& range); + +CV_CPU_OPTIMIZATION_NAMESPACE_END + +} // namespace detail +} // namespace cv + +#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +namespace cv { +namespace detail { + +CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN + +void ScharrDerivInvoker_SIMD(const Mat& src, Mat& dst, const Range& range) +{ + typedef short deriv_type; + int rows = src.rows, cols = src.cols, cn = src.channels(), colsn = cols*cn; + + int x, y, delta = (int)alignSize((cols + 2)*cn, 16); + AutoBuffer _tempBuf(delta*2 + 64); + deriv_type *trow0 = alignPtr(_tempBuf.data() + cn, 16), *trow1 = alignPtr(trow0 + delta, 16); + +#if (CV_SIMD) + const int vlanes = VTraits::vlanes(); + v_int16 c3 = vx_setall_s16(3), c10 = vx_setall_s16(10); +#endif + + for( y = range.start; y < range.end; y++ ) + { + const uchar* srow0 = src.ptr(y > 0 ? y-1 : rows > 1 ? 1 : 0); + const uchar* srow1 = src.ptr(y); + const uchar* srow2 = src.ptr(y < rows-1 ? y+1 : rows > 1 ? rows-2 : 0); + deriv_type* drow = (deriv_type *)dst.ptr(y); + + // do vertical convolution + x = 0; +#if (CV_SIMD) + { + for( ; x <= colsn - vlanes; x += vlanes ) + { + v_int16 s0 = v_reinterpret_as_s16(vx_load_expand(srow0 + x)); + v_int16 s1 = v_reinterpret_as_s16(vx_load_expand(srow1 + x)); + v_int16 s2 = v_reinterpret_as_s16(vx_load_expand(srow2 + x)); + + v_int16 t1 = v_sub(s2, s0); + v_int16 t0 = v_add(v_mul_wrap(v_add(s0, s2), c3), v_mul_wrap(s1, c10)); + + v_store(trow0 + x, t0); + v_store(trow1 + x, t1); + } + } +#endif + + for( ; x < colsn; x++ ) + { + int t0 = (srow0[x] + srow2[x])*3 + srow1[x]*10; + int t1 = srow2[x] - srow0[x]; + trow0[x] = (deriv_type)t0; + trow1[x] = (deriv_type)t1; + } + + // make border + int x0 = (cols > 1 ? 1 : 0)*cn, x1 = (cols > 1 ? cols-2 : 0)*cn; + for( int k = 0; k < cn; k++ ) + { + trow0[-cn + k] = trow0[x0 + k]; trow0[colsn + k] = trow0[x1 + k]; + trow1[-cn + k] = trow1[x0 + k]; trow1[colsn + k] = trow1[x1 + k]; + } + + // do horizontal convolution, interleave the results and store them to dst + x = 0; +#if (CV_SIMD) + { + for( ; x <= colsn - vlanes; x += vlanes ) + { + v_int16 s0 = vx_load(trow0 + x - cn); + v_int16 s1 = vx_load(trow0 + x + cn); + v_int16 s2 = vx_load(trow1 + x - cn); + v_int16 s3 = vx_load(trow1 + x); + v_int16 s4 = vx_load(trow1 + x + cn); + + v_int16 t0 = v_sub(s1, s0); + v_int16 t1 = v_add(v_mul_wrap(v_add(s2, s4), c3), v_mul_wrap(s3, c10)); + + v_store_interleave((drow + x*2), t0, t1); + } + } +#endif + for( ; x < colsn; x++ ) + { + deriv_type t0 = (deriv_type)(trow0[x+cn] - trow0[x-cn]); + deriv_type t1 = (deriv_type)((trow1[x+cn] + trow1[x-cn])*3 + trow1[x]*10); + drow[x*2] = t0; drow[x*2+1] = t1; + } + } +} + +CV_CPU_OPTIMIZATION_NAMESPACE_END + +} // namespace detail +} // namespace cv + +#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index b820f3b2b5..5560fedb0d 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -607,9 +607,19 @@ enum { CAP_PROP_IOS_DEVICE_FOCUS = 9001, enum { CAP_PROP_GIGA_FRAME_OFFSET_X = 10001, CAP_PROP_GIGA_FRAME_OFFSET_Y = 10002, CAP_PROP_GIGA_FRAME_WIDTH_MAX = 10003, - CAP_PROP_GIGA_FRAME_HEIGH_MAX = 10004, + CAP_PROP_GIGA_FRAME_HEIGHT_MAX = 10004, +// Typo in pre-5.x sources. Remain for source compatibility +#if CV_VERSION_MAJOR <= 4 + CAP_PROP_GIGA_FRAME_HEIGH_MAX = CAP_PROP_GIGA_FRAME_HEIGHT_MAX, //!< @deprecated +#endif + CAP_PROP_GIGA_FRAME_SENS_WIDTH = 10005, - CAP_PROP_GIGA_FRAME_SENS_HEIGH = 10006 + CAP_PROP_GIGA_FRAME_SENS_HEIGHT = 10006 +// Typo in pre-5.x sources. Remain for source compatibility +#if CV_VERSION_MAJOR <= 4 + , + CAP_PROP_GIGA_FRAME_SENS_HEIGH = CAP_PROP_GIGA_FRAME_SENS_HEIGHT //!< @deprecated +#endif }; //! @} Smartek diff --git a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp index 18c6f4782d..7681469d9d 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp +++ b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp @@ -373,7 +373,7 @@ void MSMFStreamChannel::start(const StreamProfile& profile, FrameCallback frameC break; } } - streamState_ = quit ? streamState_ : STREAM_STOPED; + streamState_ = quit ? streamState_ : STREAM_STOPPED; } void MSMFStreamChannel::stop() @@ -385,7 +385,7 @@ void MSMFStreamChannel::stop() streamReader_->Flush(currentStreamIndex_); std::unique_lock lk(streamStateMutex_); streamStateCv_.wait_for(lk, std::chrono::milliseconds(1000), [&]() { - return streamState_ == STREAM_STOPED; + return streamState_ == STREAM_STOPPED; }); } } @@ -474,7 +474,7 @@ STDMETHODIMP MSMFStreamChannel::OnReadSample(HRESULT hrStatus, DWORD dwStreamInd streamStateCv_.notify_all(); } - if (streamState_ != STREAM_STOPPING && streamState_ != STREAM_STOPED) + if (streamState_ != STREAM_STOPPING && streamState_ != STREAM_STOPPED) { HR_FAILED_LOG(streamReader_->ReadSample(dwStreamIndex, 0, nullptr, nullptr, nullptr, nullptr)); if (sample) @@ -505,10 +505,10 @@ STDMETHODIMP MSMFStreamChannel::OnEvent(DWORD /*sidx*/, IMFMediaEvent* /*event*/ STDMETHODIMP MSMFStreamChannel::OnFlush(DWORD) { - if (streamState_ != STREAM_STOPED) + if (streamState_ != STREAM_STOPPED) { std::unique_lock lock(streamStateMutex_); - streamState_ = STREAM_STOPED; + streamState_ = STREAM_STOPPED; streamStateCv_.notify_all(); } return S_OK; diff --git a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_v4l2.cpp b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_v4l2.cpp index 0764ac3ace..2bc3b67250 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_v4l2.cpp +++ b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_v4l2.cpp @@ -176,7 +176,7 @@ Ptr V4L2Context::createStreamChannel(const UvcDeviceInfo& devInf V4L2StreamChannel::V4L2StreamChannel(const UvcDeviceInfo &devInfo) : IUvcStreamChannel(devInfo), devFd_(-1), - streamState_(STREAM_STOPED) + streamState_(STREAM_STOPPED) { devFd_ = open(devInfo_.id.c_str(), O_RDWR | O_NONBLOCK, 0); @@ -203,7 +203,7 @@ V4L2StreamChannel::~V4L2StreamChannel() noexcept void V4L2StreamChannel::start(const StreamProfile& profile, FrameCallback frameCallback) { - if (streamState_ != STREAM_STOPED) + if (streamState_ != STREAM_STOPPED) { CV_LOG_ERROR(NULL, devInfo_.id << ": repetitive operation!") return; @@ -248,7 +248,7 @@ void V4L2StreamChannel::start(const StreamProfile& profile, FrameCallback frameC streamState_ = STREAM_STARTING; uint32_t type = V4L2_BUF_TYPE_VIDEO_CAPTURE; IOCTL_FAILED_EXEC(xioctl(devFd_, VIDIOC_STREAMON, &type), { - streamState_ = STREAM_STOPED; + streamState_ = STREAM_STOPPED; for (uint32_t i = 0; i < MAX_FRAME_BUFFER_NUM; i++) { if (frameBuffList[i].ptr) @@ -279,7 +279,7 @@ void V4L2StreamChannel::grabFrame() IOCTL_FAILED_EXEC(xioctl(devFd_, VIDIOC_QBUF, &buf), { std::unique_lock lk(streamStateMutex_); - streamState_ = STREAM_STOPED; + streamState_ = STREAM_STOPPED; streamStateCv_.notify_all(); return; }); @@ -303,7 +303,7 @@ void V4L2StreamChannel::grabFrame() IOCTL_FAILED_CONTINUE(xioctl(devFd_, VIDIOC_QBUF, &buf)); } std::unique_lock lk(streamStateMutex_); - streamState_ = STREAM_STOPED; + streamState_ = STREAM_STOPPED; streamStateCv_.notify_all(); } @@ -357,7 +357,7 @@ void V4L2StreamChannel::stop() streamState_ = STREAM_STOPPING; std::unique_lock lk(streamStateMutex_); streamStateCv_.wait_for(lk, std::chrono::milliseconds(1000), [&](){ - return streamState_ == STREAM_STOPED; + return streamState_ == STREAM_STOPPED; }); uint32_t type = V4L2_BUF_TYPE_VIDEO_CAPTURE; IOCTL_FAILED_LOG(xioctl(devFd_, VIDIOC_STREAMOFF, &type)); diff --git a/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp b/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp index d967f928d8..1ad7f95e55 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp +++ b/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp @@ -42,7 +42,7 @@ struct UvcDeviceInfo enum StreamState { - STREAM_STOPED = 0, // stoped or ready + STREAM_STOPPED = 0, // stopped or ready STREAM_STARTING = 1, STREAM_STARTED = 2, STREAM_STOPPING = 3, diff --git a/modules/videoio/test/test_camera.cpp b/modules/videoio/test/test_camera.cpp index 30592b2932..441582c7cd 100644 --- a/modules/videoio/test/test_camera.cpp +++ b/modules/videoio/test/test_camera.cpp @@ -55,7 +55,7 @@ static void test_readFrames(/*const*/ VideoCapture& capture, const int N = 100, // Check that the time between two camera frames and two system time calls // are within 1.5 frame periods of one another. // - // 1.5x is chosen to accomodate for a dropped frame, and an additional 50% + // 1.5x is chosen to accommodate for a dropped frame, and an additional 50% // to account for drift in the scale of the camera and system time domains. EXPECT_NEAR(sysTimeElapsedSecs, camTimeElapsedSecs, framePeriod * 1.5); } diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index 65db9e6086..abf28acbc1 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -851,7 +851,7 @@ TEST(videoio_ffmpeg, DISABLED_open_from_web) if (!videoio_registry::hasBackend(CAP_FFMPEG)) throw SkipTestException("FFmpeg backend was not found"); - string video_file = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; + string video_file = "https://dl.opencv.org/data/BigBuckBunny.mp4"; VideoCapture cap(video_file, CAP_FFMPEG); int n_frames = -1; EXPECT_NO_THROW(n_frames = (int)cap.get(CAP_PROP_FRAME_COUNT)); diff --git a/samples/cpp/calibration.cpp b/samples/cpp/calibration.cpp index 871a03cc80..1f8ff2b7d3 100644 --- a/samples/cpp/calibration.cpp +++ b/samples/cpp/calibration.cpp @@ -91,6 +91,7 @@ static void help(char** argv) " # the calibration grid. If this parameter is specified, a more\n" " # accurate calibration method will be used which may be better\n" " # with inaccurate, roughly planar target.\n" + " [-imread-apply-exif-rot] # use this flag to apply the exif orientation when reading images from list\n" " [input_data] # input data, one of the following:\n" " # - text file with a list of the images of the board\n" " # the text file can be generated with imagelist_creator\n" @@ -409,6 +410,7 @@ int main( int argc, char** argv ) "{oo||}{ws|11|}{dt||}" "{fx||}{fy||}{cx||}{cy||}" "{imshow-scale|1|}{enable-k3|0|}" + "{imread-apply-exif-rot||}" "{@input_data|0|}"); if (parser.has("help")) { @@ -498,11 +500,16 @@ int main( int argc, char** argv ) } int viewScaleFactor = parser.get("imshow-scale"); bool useK3 = parser.get("enable-k3"); - std::cout << "Use K3 distortion coefficient? " << useK3 << std::endl; + std::cout << "Use K3 distortion coefficient? " << (useK3 ? "yes" : "no") << std::endl; if (!useK3) { flags |= CALIB_FIX_K3; } + int imread_flag = IMREAD_COLOR; + if (!parser.has("imread-apply-exif-rot")) + { + imread_flag |= IMREAD_IGNORE_ORIENTATION; + } float grid_width = squareSize *(pattern != CHARUCOBOARD ? (boardSize.width - 1): (boardSize.width - 2) ); bool release_object = false; @@ -530,20 +537,27 @@ int main( int argc, char** argv ) return fprintf( stderr, "Invalid board height\n" ), -1; cv::aruco::Dictionary dictionary; - if (dictFilename == "None") { - std::cout << "Using predefined dictionary with id: " << arucoDict << std::endl; - dictionary = aruco::getPredefinedDictionary(arucoDict); - } - else { - std::cout << "Using custom dictionary from file: " << dictFilename << std::endl; - cv::FileStorage dict_file(dictFilename, cv::FileStorage::Mode::READ); - cv::FileNode fn(dict_file.root()); - dictionary.readDictionary(fn); + if (pattern == CHARUCOBOARD) { + if (dictFilename == "None") { + std::cout << "Using predefined dictionary with id: " << arucoDict << std::endl; + dictionary = aruco::getPredefinedDictionary(arucoDict); + } + else { + std::cout << "Using custom dictionary from file: " << dictFilename << std::endl; + cv::FileStorage dict_file(dictFilename, cv::FileStorage::Mode::READ); + cv::FileNode fn(dict_file.root()); + dictionary.readDictionary(fn); + } } - cv::aruco::CharucoBoard ch_board(boardSize, squareSize, markerSize, dictionary); + cv::Ptr ch_board; std::vector markerIds; - cv::aruco::CharucoDetector ch_detector(ch_board); + cv::Ptr ch_detector; + if (pattern == CHARUCOBOARD) { + ch_board = cv::makePtr(boardSize, squareSize, markerSize, dictionary); + ch_detector = cv::makePtr(cv::aruco::CharucoDetector(*ch_board)); + } + if( !inputFilename.empty() ) { @@ -564,7 +578,7 @@ int main( int argc, char** argv ) if( capture.isOpened() ) printf( "%s", liveCaptureHelp ); - namedWindow( "Image View", 1 ); + namedWindow( "Image View", cv::WINDOW_AUTOSIZE ); for(i = 0;;i++) { @@ -578,7 +592,7 @@ int main( int argc, char** argv ) view0.copyTo(view); } else if( i < (int)imageList.size() ) - view = imread(imageList[i], IMREAD_COLOR); + view = imread(imageList[i], imread_flag); if(view.empty()) { @@ -613,7 +627,7 @@ int main( int argc, char** argv ) break; case CHARUCOBOARD: { - ch_detector.detectBoard(view, pointbuf, markerIds); + ch_detector->detectBoard(view, pointbuf, markerIds); found = pointbuf.size() == (size_t)(boardSize.width-1)*(boardSize.height-1); break; } @@ -714,7 +728,7 @@ int main( int argc, char** argv ) for( i = 0; i < (int)imageList.size(); i++ ) { - view = imread(imageList[i], IMREAD_COLOR); + view = imread(imageList[i], imread_flag); if(view.empty()) continue; remap(view, rview, map1, map2, INTER_LINEAR);