diff --git a/3rdparty/libjpeg-turbo/CMakeLists.txt b/3rdparty/libjpeg-turbo/CMakeLists.txt index fe9b574805..b257116755 100644 --- a/3rdparty/libjpeg-turbo/CMakeLists.txt +++ b/3rdparty/libjpeg-turbo/CMakeLists.txt @@ -136,9 +136,6 @@ endif() set(JPEG_LIB_VERSION 70) -# OpenCV -set(JPEG_LIB_VERSION "${VERSION}-${JPEG_LIB_VERSION}" PARENT_SCOPE) - set(THREAD_LOCAL "") # WITH_TURBOJPEG is not used add_definitions(-DNO_GETENV -DNO_PUTENV) diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index 55a0a7fbde..73e23a580f 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -95,8 +95,23 @@ if(WITH_JPEG) macro(ocv_detect_jpeg_version header_file) if(NOT DEFINED JPEG_LIB_VERSION AND EXISTS "${header_file}") ocv_parse_header("${header_file}" JPEG_VERSION_LINES JPEG_LIB_VERSION) + + if(DEFINED JPEG_LIB_VERSION) + # Extract libjpeg-turbo version from the header file if JPEG_LIB_VERSION is found. + file(STRINGS "${header_file}" JPEG_TURBO_VERSION_LINE REGEX "^#define[\t ]+LIBJPEG_TURBO_VERSION[\t ]") + + if(JPEG_TURBO_VERSION_LINE) + # Support both raw values (e.g., 3.1.2) and quoted strings (e.g., "3.1.2"). + string(REGEX REPLACE "^#define[\t ]+LIBJPEG_TURBO_VERSION[\t ]+\"?([^\"]+)\"?.*" "\\1" JPEG_TURBO_VERSION_STRING "${JPEG_TURBO_VERSION_LINE}") + if(JPEG_TURBO_VERSION_STRING) + string(STRIP "${JPEG_TURBO_VERSION_STRING}" JPEG_TURBO_VERSION_STRING) + set(JPEG_LIB_VERSION "${JPEG_TURBO_VERSION_STRING}-${JPEG_LIB_VERSION}") + endif() + endif() + endif() endif() endmacro() + ocv_detect_jpeg_version("${JPEG_INCLUDE_DIR}/jpeglib.h") if(DEFINED CMAKE_CXX_LIBRARY_ARCHITECTURE) ocv_detect_jpeg_version("${JPEG_INCLUDE_DIR}/${CMAKE_CXX_LIBRARY_ARCHITECTURE}/jconfig.h") diff --git a/cmake/OpenCVInstallLayout.cmake b/cmake/OpenCVInstallLayout.cmake index bbe5f7b5c3..e92b5f6755 100644 --- a/cmake/OpenCVInstallLayout.cmake +++ b/cmake/OpenCVInstallLayout.cmake @@ -96,7 +96,11 @@ else() # UNIX endif() -ocv_update(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${OPENCV_LIB_INSTALL_PATH}") +if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + ocv_update(CMAKE_INSTALL_RPATH "@executable_path/Frameworks;@loader_path/Frameworks;@executable_path/../${OPENCV_3P_LIB_INSTALL_PATH}") +else() + ocv_update(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${OPENCV_LIB_INSTALL_PATH}") +endif() set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) if(INSTALL_TO_MANGLED_PATHS) diff --git a/cmake/platforms/OpenCV-Darwin.cmake b/cmake/platforms/OpenCV-Darwin.cmake index 1bb8bf6d7f..5f6c32ac4d 100644 --- a/cmake/platforms/OpenCV-Darwin.cmake +++ b/cmake/platforms/OpenCV-Darwin.cmake @@ -1 +1,8 @@ -# empty +# Additional flags for dynamic framework (macOS) +if(APPLE_FRAMEWORK AND BUILD_SHARED_LIBS) + string(APPEND CMAKE_MODULE_LINKER_FLAGS " -rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") + string(APPEND CMAKE_SHARED_LINKER_FLAGS " -rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") + string(APPEND CMAKE_EXE_LINKER_FLAGS " -rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") + set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG 1) + set(CMAKE_INSTALL_NAME_DIR "@rpath") +endif() diff --git a/cmake/templates/OpenCVConfig-CUDALanguage.cmake.in b/cmake/templates/OpenCVConfig-CUDALanguage.cmake.in index 259141006a..44d8de8225 100644 --- a/cmake/templates/OpenCVConfig-CUDALanguage.cmake.in +++ b/cmake/templates/OpenCVConfig-CUDALanguage.cmake.in @@ -10,6 +10,14 @@ set(OpenCV_CUDNN_VERSION "@CUDNN_VERSION@") set(OpenCV_USE_CUDNN "@HAVE_CUDNN@") set(ENABLE_CUDA_FIRST_CLASS_LANGUAGE ON) +# By default OpenCV only requires the consumer's CUDA Toolkit to match the +# major.minor version it was built with. The CUDA runtime API is stable within +# a minor release and patch versions change frequently, so pinning the patch +# component forces needless rebuilds (issue #26965). Set +# OPENCV_STRONG_CUDA_VERSION_CHECK before find_package(OpenCV) to require an +# exact match including the patch component. +string(REGEX MATCH "^[0-9]+\\.[0-9]+" OpenCV_CUDA_VERSION_MAJOR_MINOR "${OpenCV_CUDA_VERSION}") + if(NOT CUDAToolkit_FOUND) if(NOT CMAKE_VERSION VERSION_LESS 3.18) if(UNIX AND NOT CMAKE_CUDA_COMPILER AND NOT CUDAToolkit_ROOT) @@ -17,15 +25,28 @@ if(NOT CUDAToolkit_FOUND) set(CUDA_PATH "/usr/local/cuda" CACHE INTERNAL "") set(ENV{CUDA_PATH} ${CUDA_PATH}) endif() - find_package(CUDAToolkit ${OpenCV_CUDA_VERSION} EXACT REQUIRED) + if(OPENCV_STRONG_CUDA_VERSION_CHECK) + find_package(CUDAToolkit ${OpenCV_CUDA_VERSION} EXACT REQUIRED) + else() + find_package(CUDAToolkit ${OpenCV_CUDA_VERSION_MAJOR_MINOR} REQUIRED) + endif() else() message(FATAL_ERROR "Using OpenCV compiled with CUDA as first class language requires CMake \>= 3.18.") endif() -else() - if(CUDAToolkit_FOUND) - set(CUDA_VERSION_STRING ${CUDAToolkit_VERSION}) - endif() - if(NOT CUDA_VERSION_STRING VERSION_EQUAL OpenCV_CUDA_VERSION) - message(FATAL_ERROR "OpenCV library was compiled with CUDA ${OpenCV_CUDA_VERSION} support. Please, use the same version or rebuild OpenCV with CUDA ${CUDA_VERSION_STRING}") - endif() +endif() + +if(CUDAToolkit_FOUND) + set(CUDA_VERSION_STRING ${CUDAToolkit_VERSION}) +endif() + +string(REGEX MATCH "^[0-9]+\\.[0-9]+" CUDA_VERSION_STRING_MAJOR_MINOR "${CUDA_VERSION_STRING}") +if(OPENCV_STRONG_CUDA_VERSION_CHECK) + set(__ocv_cuda_found "${CUDA_VERSION_STRING}") + set(__ocv_cuda_built "${OpenCV_CUDA_VERSION}") +else() + set(__ocv_cuda_found "${CUDA_VERSION_STRING_MAJOR_MINOR}") + set(__ocv_cuda_built "${OpenCV_CUDA_VERSION_MAJOR_MINOR}") +endif() +if(NOT __ocv_cuda_found VERSION_EQUAL __ocv_cuda_built) + message(FATAL_ERROR "OpenCV library was compiled with CUDA ${OpenCV_CUDA_VERSION} support. Please, use the same version or rebuild OpenCV with CUDA ${CUDA_VERSION_STRING}") endif() diff --git a/doc/js_tutorials/js_core/js_basic_ops/js_basic_ops.markdown b/doc/js_tutorials/js_core/js_basic_ops/js_basic_ops.markdown index ee110c37cb..19b7cc6157 100644 --- a/doc/js_tutorials/js_core/js_basic_ops/js_basic_ops.markdown +++ b/doc/js_tutorials/js_core/js_basic_ops/js_basic_ops.markdown @@ -105,7 +105,7 @@ let mat = new cv.Mat(); let matVec = new cv.MatVector(); // Push a Mat back into MatVector matVec.push_back(mat); -// Get a Mat fom MatVector +// Get a Mat from MatVector let cnt = matVec.get(0); mat.delete(); matVec.delete(); cnt.delete(); @endcode diff --git a/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown b/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown index 09cad1003c..5564c8a6e9 100644 --- a/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown +++ b/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown @@ -27,7 +27,7 @@ foreground object (Always try to keep foreground in white). So what it does? The through the image (as in 2D convolution). A pixel in the original image (either 1 or 0) will be considered 1 only if all the pixels under the kernel is 1, otherwise it is eroded (made to zero). -So what happends is that, all the pixels near boundary will be discarded depending upon the size of +So what happens is that, all the pixels near boundary will be discarded depending upon the size of kernel. So the thickness or size of the foreground object decreases or simply white region decreases in the image. It is useful for removing small white noises (as we have seen in colorspace chapter), detach two connected objects etc. @@ -174,4 +174,4 @@ Try it -\endhtmlonly \ No newline at end of file +\endhtmlonly diff --git a/doc/opencv.bib b/doc/opencv.bib index aa44dcbe1b..80e789eaf5 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1265,7 +1265,7 @@ @article{TRUCO2026, title={TRUCO: A Scalable Lock-Free Algorithm for Parallel Contour Extraction}, author={Mu\~noz-Salinas, Rafael and Romero-Ramírez, Francisco J. and Marín-Jiménez, Manuel J.}, - journal={Pattern Recognition under review}, + journal={To be published}, year={2026} } diff --git a/doc/tutorials/app/highgui_wayland_ubuntu.markdown b/doc/tutorials/app/highgui_wayland_ubuntu.markdown index 2b8020ad19..c1aa7d19c5 100644 --- a/doc/tutorials/app/highgui_wayland_ubuntu.markdown +++ b/doc/tutorials/app/highgui_wayland_ubuntu.markdown @@ -103,4 +103,4 @@ int main(void) Limitation/Known problem ------------------------ -- cv::moveWindow() is not implementated. ( See. https://github.com/opencv/opencv/issues/25478 ) +- cv::moveWindow() is not implemented. ( See. https://github.com/opencv/opencv/issues/25478 ) diff --git a/doc/tutorials/app/orbbec_astra_openni.markdown b/doc/tutorials/app/orbbec_astra_openni.markdown index 0b5ae5a8c5..6b1d8a88f3 100644 --- a/doc/tutorials/app/orbbec_astra_openni.markdown +++ b/doc/tutorials/app/orbbec_astra_openni.markdown @@ -72,8 +72,8 @@ In order to use the Astra camera's depth sensor with OpenCV you should do the fo @note The last tried version `2.3.0.86_202210111154_4c8f5aa4_beta6` does not work correctly with modern Linux, even after libusb rebuild as recommended by the instruction. The last know good - configuration is version 2.3.0.63 (tested with Ubuntu 18.04 amd64). It's not provided officialy - with the downloading page, but published by Orbbec technical suport on Orbbec community forum + configuration is version 2.3.0.63 (tested with Ubuntu 18.04 amd64). It's not provided officially + with the downloading page, but published by Orbbec technical support on Orbbec community forum [here](https://3dclub.orbbec3d.com/t/universal-download-thread-for-astra-series-cameras/622). -# Now you can configure OpenCV with OpenNI support enabled by setting the `WITH_OPENNI2` flag in CMake. diff --git a/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown b/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown index 94cb37e5b5..dae706d124 100644 --- a/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown +++ b/doc/tutorials/calib3d/camera_calibration_pattern/camera_calibration_pattern.markdown @@ -62,7 +62,7 @@ Example code to generate features coordinates for calibration with symmetric gri } } ``` -Example code to generate features corrdinates for calibration with asymmetic grid (object points): +Example code to generate features coordinates for calibration with asymmetric grid (object points): ``` std::vector objectPoints; for (int i = 0; i < boardSize.height; i++) { @@ -83,7 +83,7 @@ about ArUco pairs. In opposite to the previous pattern partially occluded board corners are labeled. The board is rotation invariant, but set of ArUco markers and their order should be known to detector apriori. It cannot detect ChAruco board with predefined size and random set of markers. -Example code to generate features corrdinates for calibration (object points) for board size in units: +Example code to generate features coordinates for calibration (object points) for board size in units: ``` std::vector objectPoints; for (int i = 0; i < boardSize.height-1; ++i) { diff --git a/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown b/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown index 6948c30563..46a6dea7d8 100644 --- a/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown +++ b/doc/tutorials/dnn/dnn_text_spotting/dnn_text_spotting.markdown @@ -65,25 +65,25 @@ We encourage you to add new algorithms to these APIs. ``` crnn.onnx: -url: https://drive.google.com/uc?export=dowload&id=1ooaLR-rkTl8jdpGy1DoQs0-X0lQsB6Fj +url: https://drive.google.com/uc?export=download&id=1ooaLR-rkTl8jdpGy1DoQs0-X0lQsB6Fj sha: 270d92c9ccb670ada2459a25977e8deeaf8380d3, -alphabet_36.txt: https://drive.google.com/uc?export=dowload&id=1oPOYx5rQRp8L6XQciUwmwhMCfX0KyO4b +alphabet_36.txt: https://drive.google.com/uc?export=download&id=1oPOYx5rQRp8L6XQciUwmwhMCfX0KyO4b parameter setting: -rgb=0; description: The classification number of this model is 36 (0~9 + a~z). The training dataset is MJSynth. crnn_cs.onnx: -url: https://drive.google.com/uc?export=dowload&id=12diBsVJrS9ZEl6BNUiRp9s0xPALBS7kt +url: https://drive.google.com/uc?export=download&id=12diBsVJrS9ZEl6BNUiRp9s0xPALBS7kt sha: a641e9c57a5147546f7a2dbea4fd322b47197cd5 -alphabet_94.txt: https://drive.google.com/uc?export=dowload&id=1oKXxXKusquimp7XY1mFvj9nwLzldVgBR +alphabet_94.txt: https://drive.google.com/uc?export=download&id=1oKXxXKusquimp7XY1mFvj9nwLzldVgBR parameter setting: -rgb=1; description: The classification number of this model is 94 (0~9 + a~z + A~Z + punctuations). The training datasets are MJsynth and SynthText. crnn_cs_CN.onnx: -url: https://drive.google.com/uc?export=dowload&id=1is4eYEUKH7HR7Gl37Sw4WPXx6Ir8oQEG +url: https://drive.google.com/uc?export=download&id=1is4eYEUKH7HR7Gl37Sw4WPXx6Ir8oQEG sha: 3940942b85761c7f240494cf662dcbf05dc00d14 -alphabet_3944.txt: https://drive.google.com/uc?export=dowload&id=18IZUUdNzJ44heWTndDO6NNfIpJMmN-ul +alphabet_3944.txt: https://drive.google.com/uc?export=download&id=18IZUUdNzJ44heWTndDO6NNfIpJMmN-ul parameter setting: -rgb=1; description: The classification number of this model is 3944 (0~9 + a~z + A~Z + Chinese characters + special characters). The training dataset is ReCTS (https://rrc.cvc.uab.es/?ch=12). @@ -97,25 +97,25 @@ You can train more models by [CRNN](https://github.com/meijieru/crnn.pytorch), a ``` - DB_IC15_resnet50.onnx: -url: https://drive.google.com/uc?export=dowload&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf +url: https://drive.google.com/uc?export=download&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf sha: bef233c28947ef6ec8c663d20a2b326302421fa3 recommended parameter setting: -inputHeight=736, -inputWidth=1280; description: This model is trained on ICDAR2015, so it can only detect English text instances. - DB_IC15_resnet18.onnx: -url: https://drive.google.com/uc?export=dowload&id=1vY_KsDZZZb_svd5RT6pjyI8BS1nPbBSX +url: https://drive.google.com/uc?export=download&id=1vY_KsDZZZb_svd5RT6pjyI8BS1nPbBSX sha: 19543ce09b2efd35f49705c235cc46d0e22df30b recommended parameter setting: -inputHeight=736, -inputWidth=1280; description: This model is trained on ICDAR2015, so it can only detect English text instances. - DB_TD500_resnet50.onnx: -url: https://drive.google.com/uc?export=dowload&id=19YWhArrNccaoSza0CfkXlA8im4-lAGsR +url: https://drive.google.com/uc?export=download&id=19YWhArrNccaoSza0CfkXlA8im4-lAGsR sha: 1b4dd21a6baa5e3523156776970895bd3db6960a recommended parameter setting: -inputHeight=736, -inputWidth=736; description: This model is trained on MSRA-TD500, so it can detect both English and Chinese text instances. - DB_TD500_resnet18.onnx: -url: https://drive.google.com/uc?export=dowload&id=1sZszH3pEt8hliyBlTmB-iulxHP1dCQWV +url: https://drive.google.com/uc?export=download&id=1sZszH3pEt8hliyBlTmB-iulxHP1dCQWV sha: 8a3700bdc13e00336a815fc7afff5dcc1ce08546 recommended parameter setting: -inputHeight=736, -inputWidth=736; description: This model is trained on MSRA-TD500, so it can detect both English and Chinese text instances. @@ -134,11 +134,11 @@ This model is based on https://github.com/argman/EAST ``` Text Recognition: -url: https://drive.google.com/uc?export=dowload&id=1nMcEy68zDNpIlqAn6xCk_kYcUTIeSOtN +url: https://drive.google.com/uc?export=download&id=1nMcEy68zDNpIlqAn6xCk_kYcUTIeSOtN sha: 89205612ce8dd2251effa16609342b69bff67ca3 Text Detection: -url: https://drive.google.com/uc?export=dowload&id=149tAhIcvfCYeyufRoZ9tmc2mZDKE_XrF +url: https://drive.google.com/uc?export=download&id=149tAhIcvfCYeyufRoZ9tmc2mZDKE_XrF sha: ced3c03fb7f8d9608169a913acf7e7b93e07109b ``` diff --git a/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown b/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown index 81eb67d44d..c286b5189a 100644 --- a/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown +++ b/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown @@ -128,7 +128,7 @@ than YOLOX) in case it is needed. However, usually each YOLO repository has pred #### Exporting YOLOv10 model -In oder to run YOLOv10 one needs to cut off postporcessing with dynamic shapes from torch and then convert it to ONNX. If someone is looking for on how to cut off the postprocessing, there is this [forked branch](https://github.com/Abdurrahheem/yolov10/tree/ash/opencv-export) from official YOLOv10. The forked branch cuts of the postprocessing by [returning output](https://github.com/Abdurrahheem/yolov10/blob/4fdaafd912c8891642bfbe85751ea66ec20f05ad/ultralytics/nn/modules/head.py#L522) of the model before postprocessing procedure itself. To convert torch model to ONNX follow this proceduce. +In order to run YOLOv10 one needs to cut off postprocessing with dynamic shapes from torch and then convert it to ONNX. If someone is looking for on how to cut off the postprocessing, there is this [forked branch](https://github.com/Abdurrahheem/yolov10/tree/ash/opencv-export) from official YOLOv10. The forked branch cuts off the postprocessing by [returning output](https://github.com/Abdurrahheem/yolov10/blob/4fdaafd912c8891642bfbe85751ea66ec20f05ad/ultralytics/nn/modules/head.py#L522) of the model before postprocessing procedure itself. To convert torch model to ONNX follow this procedure. @code{.bash} git clone git@github.com:Abdurrahheem/yolov10.git diff --git a/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown b/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown index 232bf49839..da23634315 100644 --- a/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown +++ b/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown @@ -168,7 +168,7 @@ for `cv::aruco::Dictionary`. The data member of board classes are public and can To do so, you will need to use an external rendering engine library, such as OpenGL. The aruco module only provides the functionality to obtain the camera pose, i.e. the rotation and translation vectors, which is necessary to create the augmented reality effect. However, you will need to adapt the rotation -and traslation vectors from the OpenCV format to the format accepted by your 3d rendering library. +and translation vectors from the OpenCV format to the format accepted by your 3d rendering library. The original ArUco library contains examples of how to do it for OpenGL and Ogre3D. diff --git a/doc/tutorials/objdetect/charuco_diamond_detection/charuco_diamond_detection.markdown b/doc/tutorials/objdetect/charuco_diamond_detection/charuco_diamond_detection.markdown index 04ae79ded0..a962861d80 100644 --- a/doc/tutorials/objdetect/charuco_diamond_detection/charuco_diamond_detection.markdown +++ b/doc/tutorials/objdetect/charuco_diamond_detection/charuco_diamond_detection.markdown @@ -12,7 +12,7 @@ It is similar to a ChArUco board in appearance, however they are conceptually di In both, ChArUco board and Diamond markers, their detection is based on the previous detected ArUco markers. In the ChArUco case, the used markers are selected by directly looking their identifiers. This means that if a marker (included in the board) is found on a image, it will be automatically assumed to belong to the board. Furthermore, -if a marker board is found more than once in the image, it will produce an ambiguity since the system wont +if a marker board is found more than once in the image, it will produce an ambiguity since the system won't be able to know which one should be used for the Board. On the other hand, the detection of Diamond marker is not based on the identifiers. Instead, their detection diff --git a/hal/carotene/include/carotene/functions.hpp b/hal/carotene/include/carotene/functions.hpp index 15a12e765b..31a5d0af94 100644 --- a/hal/carotene/include/carotene/functions.hpp +++ b/hal/carotene/include/carotene/functions.hpp @@ -2285,7 +2285,7 @@ namespace CAROTENE_NS { f64 alpha, f64 beta); /* - Reduce matrix to a vector by calculatin given operation for each column + Reduce matrix to a vector by calculating given operation for each column */ void reduceColSum(const Size2D &size, const u8 * srcBase, ptrdiff_t srcStride, diff --git a/hal/carotene/src/accumulate.cpp b/hal/carotene/src/accumulate.cpp index ee9ce22d35..6ab86ae2dd 100644 --- a/hal/carotene/src/accumulate.cpp +++ b/hal/carotene/src/accumulate.cpp @@ -231,7 +231,7 @@ void accumulateSquare(const Size2D &size, internal::assertSupportedConfiguration(); #ifdef CAROTENE_NEON - // this ugly contruction is needed to avoid: + // this ugly construction is needed to avoid: // /usr/lib/gcc/arm-linux-gnueabihf/4.8/include/arm_neon.h:3581:59: error: argument must be a constant // return (int16x8_t)__builtin_neon_vshr_nv8hi (__a, __b, 1); diff --git a/hal/carotene/src/canny.cpp b/hal/carotene/src/canny.cpp index f61bc23e9b..b668d85cbe 100644 --- a/hal/carotene/src/canny.cpp +++ b/hal/carotene/src/canny.cpp @@ -524,7 +524,7 @@ inline void Canny3x3(const Size2D &size, s32 cn, //i == 0 normEstimator.firstRow(size, cn, srcBase, srcStride, dxBase, dxStride, dyBase, dyStride, mag_buf); - // calculate magnitude and angle of gradient, perform non-maxima supression. + // calculate magnitude and angle of gradient, perform non-maxima suppression. // fill the map with one of the following values: // 0 - the pixel might belong to an edge // 1 - the pixel can not belong to an edge diff --git a/hal/carotene/src/intrinsics.hpp b/hal/carotene/src/intrinsics.hpp index 062a3f897b..5b04bc91a1 100644 --- a/hal/carotene/src/intrinsics.hpp +++ b/hal/carotene/src/intrinsics.hpp @@ -66,7 +66,7 @@ inline float32x2_t vrecp_f32(float32x2_t val) return reciprocal; } -// caclulate sqrt value +// calculate sqrt value inline float32x4_t vrsqrtq_f32(float32x4_t val) { diff --git a/hal/ipp/CMakeLists.txt b/hal/ipp/CMakeLists.txt index 9318b931d1..beaa91e7f3 100644 --- a/hal/ipp/CMakeLists.txt +++ b/hal/ipp/CMakeLists.txt @@ -9,6 +9,7 @@ set(IPP_HAL_HEADERS CACHE INTERNAL "") add_library(ipphal STATIC + "${CMAKE_CURRENT_SOURCE_DIR}/src/math_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/mean_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/minmax_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/norm_ipp.cpp" @@ -16,8 +17,11 @@ add_library(ipphal STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/color_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/transforms_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/warp_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/deriv_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/resize_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/box_filter_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/sum_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/color_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/filter_ipp.cpp" ) diff --git a/hal/ipp/include/ipp_hal_core.hpp b/hal/ipp/include/ipp_hal_core.hpp index 7b71c8d28a..359764a7bf 100644 --- a/hal/ipp/include/ipp_hal_core.hpp +++ b/hal/ipp/include/ipp_hal_core.hpp @@ -12,8 +12,10 @@ int ipp_hal_meanStdDev(const uchar* src_data, size_t src_step, int width, int height, int src_type, double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); -#undef cv_hal_meanStdDev -#define cv_hal_meanStdDev ipp_hal_meanStdDev +// IPP version is less efficient than implementation with universtal intrinsics +// See https://github.com/opencv/opencv/pull/29339 +//#undef cv_hal_meanStdDev +//#define cv_hal_meanStdDev ipp_hal_meanStdDev int ipp_hal_minMaxIdxMaskStep(const uchar* src_data, size_t src_step, int width, int height, int depth, double* _minVal, double* _maxVal, int* _minIdx, int* _maxIdx, uchar* mask, size_t mask_step); @@ -74,6 +76,33 @@ int ipp_hal_transpose2d(const uchar* src_data, size_t src_step, uchar* dst_data, #undef cv_hal_transpose2d #define cv_hal_transpose2d ipp_hal_transpose2d +int ipp_hal_invSqrt32f(const float* src, float* dst, int len); +int ipp_hal_invSqrt64f(const double* src, double* dst, int len); + +#undef cv_hal_invSqrt32f +#define cv_hal_invSqrt32f ipp_hal_invSqrt32f + +#undef cv_hal_invSqrt64f +#define cv_hal_invSqrt64f ipp_hal_invSqrt64f + +int ipp_hal_exp32f(const float* src, float* dst, int len); +int ipp_hal_exp64f(const double* src, double* dst, int len); + +#undef cv_hal_exp32f +#define cv_hal_exp32f ipp_hal_exp32f + +#undef cv_hal_exp64f +#define cv_hal_exp64f ipp_hal_exp64f + +int ipp_hal_log32f(const float* src, float* dst, int len); +int ipp_hal_log64f(const double* src, double* dst, int len); + +#undef cv_hal_log32f +#define cv_hal_log32f ipp_hal_log32f + +#undef cv_hal_log64f +#define cv_hal_log64f ipp_hal_log64f + //! @endcond #endif diff --git a/hal/ipp/include/ipp_hal_imgproc.hpp b/hal/ipp/include/ipp_hal_imgproc.hpp index 7272cd2fe1..3c3cf6c111 100644 --- a/hal/ipp/include/ipp_hal_imgproc.hpp +++ b/hal/ipp/include/ipp_hal_imgproc.hpp @@ -24,6 +24,21 @@ int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int // Does not pass tests in 5.x branch //#undef cv_hal_warpAffine //#define cv_hal_warpAffine ipp_hal_warpAffine + +int ipp_hal_sobel(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int src_depth, int dst_depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int dx, int dy, int ksize, double scale, double delta, int border_type); +#undef cv_hal_sobel +#define cv_hal_sobel ipp_hal_sobel + +int ipp_hal_scharr(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int src_depth, int dst_depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int dx, int dy, double scale, double delta, int border_type); +#undef cv_hal_scharr +#define cv_hal_scharr ipp_hal_scharr + #endif #if IPP_VERSION_X100 >= 202600 @@ -44,6 +59,14 @@ int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int s #undef cv_hal_remap32f #define cv_hal_remap32f ipp_hal_remap32f +#if defined(HAVE_IPP_IW) +int ipp_hal_resize(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, + uchar *dst_data, size_t dst_step, int dst_width, int dst_height, + double inv_scale_x, double inv_scale_y, int interpolation); +#undef cv_hal_resize +#define cv_hal_resize ipp_hal_resize +#endif // HAVE_IPP_IW + #if defined(HAVE_IPP_IW) && !DISABLE_IPP_BOX_FILTER int ipp_hal_boxFilter(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, int width, int height, int src_depth, int dst_depth, int cn, diff --git a/hal/ipp/src/deriv_ipp.cpp b/hal/ipp/src/deriv_ipp.cpp new file mode 100644 index 0000000000..31aa6f44bc --- /dev/null +++ b/hal/ipp/src/deriv_ipp.cpp @@ -0,0 +1,247 @@ +// 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, Intel Corporation, all rights reserved. + +#include "ipp_hal_imgproc.hpp" +#include "precomp_ipp.hpp" + +#include +#include + +#if IPP_VERSION_X100 >= 810 + +#ifdef HAVE_IPP_IW +#include "iw++/iw.hpp" + +static inline IppiMaskSize ippiGetMaskSize(int kx, int ky) +{ + return (kx == 1 && ky == 3) ? ippMskSize1x3 : + (kx == 1 && ky == 5) ? ippMskSize1x5 : + (kx == 3 && ky == 1) ? ippMskSize3x1 : + (kx == 3 && ky == 3) ? ippMskSize3x3 : + (kx == 5 && ky == 1) ? ippMskSize5x1 : + (kx == 5 && ky == 5) ? ippMskSize5x5 : + (IppiMaskSize)-1; +} + +static inline IwiDerivativeType ippiGetDerivType(int dx, int dy, bool nvert) +{ + return (dx == 1 && dy == 0) ? ((nvert)?iwiDerivNVerFirst:iwiDerivVerFirst) : + (dx == 0 && dy == 1) ? iwiDerivHorFirst : + (dx == 2 && dy == 0) ? iwiDerivVerSecond : + (dx == 0 && dy == 2) ? iwiDerivHorSecond : + (IwiDerivativeType)-1; +} + +// Build an IwiImage from a raw buffer, encoding the in-memory border (the OpenCV +// margins around the ROI) so that BORDER_*-with-physical-pixels works as in core. +// TODO: promote to precomp_ipp.hpp and unify with the raw-pointer ippiGetImage in +// transforms_ipp.cpp once more filter HALs share it +static inline ::ipp::IwiImage ippiGetImage(int depth, int channels, const uchar* data, size_t step, + int width, int height, + int margin_left, int margin_top, int margin_right, int margin_bottom) +{ + ::ipp::IwiImage image; + ::ipp::IwiBorderSize inMemBorder((IwSize)margin_left, (IwSize)margin_top, (IwSize)margin_right, (IwSize)margin_bottom); + image.Init({width, height}, ippiGetDataType(depth), channels, inMemBorder, (void*)data, step); + return image; +} + +// Translate the OpenCV border type into an IPP border, accounting for in-memory pixels. +// Returns (IppiBorderType)0 on unsupported configuration. +// TODO: promote to precomp_ipp.hpp once shared by other filter HALs (box/gaussian/bilateral/ +// sepFilter etc.). Note the shared ippiGetBorderType there does not map BORDER_REFLECT_101; +// widening it would also affect warp_ipp.cpp, so keep this filter-specific mapping separate +// (or add a dedicated filter border helper) when consolidating. +static inline IppiBorderType ippiGetBorder(::ipp::IwiImage &image, int ocvBorderType, ::ipp::IwiBorderSize &borderSize) +{ + int inMemFlags = 0; + int borderTypeNI = ocvBorderType & ~cv::BORDER_ISOLATED; + IppiBorderType border = borderTypeNI == cv::BORDER_CONSTANT ? ippBorderConst : + borderTypeNI == cv::BORDER_TRANSPARENT ? ippBorderTransp : + borderTypeNI == cv::BORDER_REPLICATE ? ippBorderRepl : + borderTypeNI == cv::BORDER_REFLECT_101 ? ippBorderMirror : + (IppiBorderType)-1; + if((int)border == -1) + return (IppiBorderType)0; + + if(!(ocvBorderType & cv::BORDER_ISOLATED)) + { + if(image.m_inMemSize.left) + { + if(image.m_inMemSize.left >= borderSize.left) + inMemFlags |= ippBorderInMemLeft; + else + return (IppiBorderType)0; + } + else + borderSize.left = 0; + if(image.m_inMemSize.top) + { + if(image.m_inMemSize.top >= borderSize.top) + inMemFlags |= ippBorderInMemTop; + else + return (IppiBorderType)0; + } + else + borderSize.top = 0; + if(image.m_inMemSize.right) + { + if(image.m_inMemSize.right >= borderSize.right) + inMemFlags |= ippBorderInMemRight; + else + return (IppiBorderType)0; + } + else + borderSize.right = 0; + if(image.m_inMemSize.bottom) + { + if(image.m_inMemSize.bottom >= borderSize.bottom) + inMemFlags |= ippBorderInMemBottom; + else + return (IppiBorderType)0; + } + else + borderSize.bottom = 0; + } + else + borderSize.left = borderSize.right = borderSize.top = borderSize.bottom = 0; + + return (IppiBorderType)(border|inMemFlags); +} + +// Shared worker for Sobel (useScharr == false) and Scharr (useScharr == true). +static int ipp_Deriv(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int src_depth, int dst_depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int dx, int dy, int ksize, double scale, double delta, int borderType, + bool useScharr) +{ + IppDataType srcType = ippiGetDataType(src_depth); + IppDataType dstType = ippiGetDataType(dst_depth); + bool useScale = false; + + if(cn < 1 || cn > 4) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + if(src_depth < 0 || src_depth >= CV_DEPTH_MAX || dst_depth < 0 || dst_depth >= CV_DEPTH_MAX) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + // Supported (source depth -> destination depth) x channels combinations. + // Index order: [src_depth][dst_depth][channel-1]; depths are 8U,8S,16U,16S,32S,32F,64F. + // IPP iwiFilterSobel/iwiFilterScharr provide only single-channel kernels for + // 8u->16s, 16s->16s and 32f->32f; 8u->8u is realized as an 8u->16s filter plus a 16s->8u scale. + // 8u->32f and 16s->32f are intentionally left disabled: IPP would need an extra full-image + // conversion pass there and is slower than OpenCV's fused sepFilter2D. + /* dst: 8U 8S 16U 16S 32S 32F 64F */ +#if defined(IPP_CALLS_ENFORCED) + const char impl[CV_DEPTH_MAX][CV_DEPTH_MAX][4] = { + /* src 8U */ {{1,0,0,0},{0,0,0,0},{0,0,0,0},{1,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 8S */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 16U */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 16S */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 32S */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 32F */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,0,0,0},{0,0,0,0}}, + /* src 64F */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}}; +#else + const char impl[CV_DEPTH_MAX][CV_DEPTH_MAX][4] = { + /* src 8U */ {{1,0,0,0},{0,0,0,0},{0,0,0,0},{1,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 8S */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 16U */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 16S */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 32S */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, + /* src 32F */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,0,0,0},{0,0,0,0}}, + /* src 64F */ {{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}}}; +#endif // IPP_CALLS_ENFORCED + if(impl[src_depth][dst_depth][cn-1] == 0) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if(fabs(delta) > FLT_EPSILON || fabs(scale-1) > FLT_EPSILON) + useScale = true; + + // cv::Sobel accepts ksize == FILTER_SCHARR (-1, i.e. ksize <= 0) which selects the 3x3 + // Scharr derivative, so the Sobel entry point may legitimately request a Scharr filter. + if(ksize <= 0) + { + ksize = 3; + useScharr = true; + } + + IppiMaskSize maskSize = ippiGetMaskSize(ksize, ksize); + if((int)maskSize < 0) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + +#if IPP_VERSION_X100 <= 201703 + // Bug with mirror wrap + if(borderType == cv::BORDER_REFLECT_101 && (ksize/2+1 > width || ksize/2+1 > height)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; +#endif + + IwiDerivativeType derivType = ippiGetDerivType(dx, dy, (useScharr)?false:true); + if((int)derivType < 0) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + try + { + ::ipp::IwiImage iwSrc = ippiGetImage(src_depth, cn, src_data, src_step, width, height, + margin_left, margin_top, margin_right, margin_bottom); + ::ipp::IwiImage iwDst = ippiGetImage(dst_depth, cn, dst_data, dst_step, width, height, + 0, 0, 0, 0); + ::ipp::IwiImage iwSrcProc = iwSrc; + ::ipp::IwiImage iwDstProc = iwDst; + ::ipp::IwiBorderSize borderSize(maskSize); + ::ipp::IwiBorderType ippBorder(ippiGetBorder(iwSrc, borderType, borderSize)); + if(!ippBorder) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + // IPP needs an extra iwiScale pass for 32f output with scale/delta, slower than OpenCV's fused approach + if(useScale && dstType == ipp32f) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if(srcType == ipp8u && dstType == ipp8u) + { + iwDstProc.Alloc(iwDst.m_size, ipp16s, cn); + useScale = true; + } + + if(useScharr) + CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterScharr, iwSrcProc, iwDstProc, derivType, maskSize, ::ipp::IwDefault(), ippBorder); + else + CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterSobel, iwSrcProc, iwDstProc, derivType, maskSize, ::ipp::IwDefault(), ippBorder); + + if(useScale) + CV_INSTRUMENT_FUN_IPP(::ipp::iwiScale, iwDstProc, iwDst, scale, delta, ::ipp::IwiScaleParams(ippAlgHintFast)); + } + catch (const ::ipp::IwException &) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + return CV_HAL_ERROR_OK; +} + +int ipp_hal_sobel(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int src_depth, int dst_depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int dx, int dy, int ksize, double scale, double delta, int border_type) +{ + CV_HAL_CHECK_USE_IPP(); + return ipp_Deriv(src_data, src_step, dst_data, dst_step, width, height, src_depth, dst_depth, cn, + margin_left, margin_top, margin_right, margin_bottom, + dx, dy, ksize, scale, delta, border_type, false); +} + +int ipp_hal_scharr(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int src_depth, int dst_depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int dx, int dy, double scale, double delta, int border_type) +{ + CV_HAL_CHECK_USE_IPP(); + return ipp_Deriv(src_data, src_step, dst_data, dst_step, width, height, src_depth, dst_depth, cn, + margin_left, margin_top, margin_right, margin_bottom, + dx, dy, 0, scale, delta, border_type, true); +} + +#endif // HAVE_IPP_IW + +#endif // IPP_VERSION_X100 >= 810 diff --git a/hal/ipp/src/math_ipp.cpp b/hal/ipp/src/math_ipp.cpp new file mode 100644 index 0000000000..cb83a34264 --- /dev/null +++ b/hal/ipp/src/math_ipp.cpp @@ -0,0 +1,80 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +#include "ipp_hal_core.hpp" + +#include +#include + +int ipp_hal_invSqrt32f(const float* src, float* dst, int len) +{ + CV_HAL_CHECK_USE_IPP(); + IppStatus status = CV_INSTRUMENT_FUN_IPP(ippsInvSqrt_32f_A21, src, dst, len); + if (status >= 0) + { + return CV_HAL_ERROR_OK; + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} + +int ipp_hal_invSqrt64f(const double* src, double* dst, int len) +{ + CV_HAL_CHECK_USE_IPP(); + IppStatus status = CV_INSTRUMENT_FUN_IPP(ippsInvSqrt_64f_A50, src, dst, len); + if (status >= 0) + { + return CV_HAL_ERROR_OK; + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} + +int ipp_hal_exp32f(const float* src, float* dst, int len) +{ + CV_HAL_CHECK_USE_IPP(); + IppStatus status = CV_INSTRUMENT_FUN_IPP(ippsExp_32f_A21, src, dst, len); + if (status >= 0) + { + return CV_HAL_ERROR_OK; + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} + +int ipp_hal_exp64f(const double* src, double* dst, int len) +{ + CV_HAL_CHECK_USE_IPP(); + IppStatus status = CV_INSTRUMENT_FUN_IPP(ippsExp_64f_A50, src, dst, len); + if (status >= 0) + { + return CV_HAL_ERROR_OK; + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} + +int ipp_hal_log32f(const float* src, float* dst, int len) +{ + CV_HAL_CHECK_USE_IPP(); + IppStatus status = CV_INSTRUMENT_FUN_IPP(ippsLn_32f_A21, src, dst, len); + if (status >= 0) + { + return CV_HAL_ERROR_OK; + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} + +int ipp_hal_log64f(const double* src, double* dst, int len) +{ + CV_HAL_CHECK_USE_IPP(); + IppStatus status = CV_INSTRUMENT_FUN_IPP(ippsLn_64f_A50, src, dst, len); + if (status >= 0) + { + return CV_HAL_ERROR_OK; + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} diff --git a/hal/ipp/src/resize_ipp.cpp b/hal/ipp/src/resize_ipp.cpp new file mode 100644 index 0000000000..e6d3d35ccc --- /dev/null +++ b/hal/ipp/src/resize_ipp.cpp @@ -0,0 +1,196 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +#include "ipp_hal_imgproc.hpp" + +#ifdef HAVE_IPP_IW + +#include +#include "precomp_ipp.hpp" + +#include "iw++/iw.hpp" + +#include + +#define IPP_RESIZE_PARALLEL 1 + +class ipp_resizeParallel: public cv::ParallelLoopBody +{ +public: + ipp_resizeParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, bool &ok): + m_src(src), m_dst(dst), m_ok(ok) {} + ~ipp_resizeParallel() + { + } + + void Init(IppiInterpolationType inter) + { + iwiResize.InitAlloc(m_src.m_size, m_dst.m_size, m_src.m_dataType, m_src.m_channels, inter, ::ipp::IwiResizeParams(0, 0, 0.75, 4), ippBorderRepl); + + m_ok = true; + } + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + if(!m_ok) + return; + + try + { + ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); + CV_INSTRUMENT_FUN_IPP(iwiResize, m_src, m_dst, ippBorderRepl, tile); + } + catch(const ::ipp::IwException &) + { + m_ok = false; + return; + } + } +private: + ::ipp::IwiImage &m_src; + ::ipp::IwiImage &m_dst; + + mutable ::ipp::IwiResize iwiResize; + + volatile bool &m_ok; + const ipp_resizeParallel& operator= (const ipp_resizeParallel&); +}; + +class ipp_resizeAffineParallel: public cv::ParallelLoopBody +{ +public: + ipp_resizeAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, bool &ok): + m_src(src), m_dst(dst), m_ok(ok) {} + ~ipp_resizeAffineParallel() + { + } + + void Init(IppiInterpolationType inter, double scaleX, double scaleY) + { + double shift = (inter == ippNearest)?-1e-10:-0.5; + double coeffs[2][3] = { + {scaleX, 0, shift+0.5*scaleX}, + {0, scaleY, shift+0.5*scaleY} + }; + + iwiWarpAffine.InitAlloc(m_src.m_size, m_dst.m_size, m_src.m_dataType, m_src.m_channels, coeffs, iwTransForward, inter, ::ipp::IwiWarpAffineParams(0, 0, 0.75), ippBorderRepl); + + m_ok = true; + } + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + if(!m_ok) + return; + + try + { + ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); + CV_INSTRUMENT_FUN_IPP(iwiWarpAffine, m_src, m_dst, tile); + } + catch(const ::ipp::IwException &) + { + m_ok = false; + return; + } + } +private: + ::ipp::IwiImage &m_src; + ::ipp::IwiImage &m_dst; + + mutable ::ipp::IwiWarpAffine iwiWarpAffine; + + volatile bool &m_ok; + const ipp_resizeAffineParallel& operator= (const ipp_resizeAffineParallel&); +}; + +int ipp_hal_resize(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, + uchar *dst_data, size_t dst_step, int dst_width, int dst_height, + double inv_scale_x, double inv_scale_y, int interpolation) +{ + CV_HAL_CHECK_USE_IPP(); + + int depth = CV_MAT_DEPTH(src_type), channels = CV_MAT_CN(src_type); + + IppDataType ippDataType = ippiGetDataType(depth); + IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); + if((int)ippInter < 0) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + // Resize which doesn't match OpenCV exactly + if (!cv::ipp::useIPP_NotExact()) + { + if (ippInter == ippNearest || ippInter == ippSuper || (ippDataType == ipp8u && ippInter == ippLinear)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + if(ippInter != ippLinear && ippDataType == ipp64f) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + +#if IPP_VERSION_X100 < 201801 + // Degradations on int^2 linear downscale + if (ippDataType != ipp64f && ippInter == ippLinear && inv_scale_x < 1 && inv_scale_y < 1) // if downscale + { + int scale_x = (int)(1 / inv_scale_x); + int scale_y = (int)(1 / inv_scale_y); + if (1 / inv_scale_x - scale_x < DBL_EPSILON && 1 / inv_scale_y - scale_y < DBL_EPSILON) // if integer + { + if (!(scale_x&(scale_x - 1)) && !(scale_y&(scale_y - 1))) // if power of 2 + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + } +#endif + + bool affine = false; + const double IPP_RESIZE_EPS = (depth == CV_64F)?0:1e-10; + double ex = fabs((double)dst_width / src_width - inv_scale_x) / inv_scale_x; + double ey = fabs((double)dst_height / src_height - inv_scale_y) / inv_scale_y; + + // Use affine transform resize to allow sub-pixel accuracy + if(ex > IPP_RESIZE_EPS || ey > IPP_RESIZE_EPS) + affine = true; + + // Affine doesn't support Lanczos and Super interpolations + if(affine && (ippInter == ippLanczos || ippInter == ippSuper)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + try + { + ::ipp::IwiImage iwSrc(::ipp::IwiSize(src_width, src_height), ippDataType, channels, 0, (void*)src_data, src_step); + ::ipp::IwiImage iwDst(::ipp::IwiSize(dst_width, dst_height), ippDataType, channels, 0, (void*)dst_data, dst_step); + + bool ok; + int threads = ippiSuggestThreadsNum(iwDst, 1+((double)(src_width*src_height)/(dst_width*dst_height))); + cv::Range range(0, dst_height); + ipp_resizeParallel invokerGeneral(iwSrc, iwDst, ok); + ipp_resizeAffineParallel invokerAffine(iwSrc, iwDst, ok); + cv::ParallelLoopBody *pInvoker = NULL; + if(affine) + { + pInvoker = &invokerAffine; + invokerAffine.Init(ippInter, inv_scale_x, inv_scale_y); + } + else + { + pInvoker = &invokerGeneral; + invokerGeneral.Init(ippInter); + } + + if(IPP_RESIZE_PARALLEL && threads > 1) + cv::parallel_for_(range, *pInvoker, threads*4); + else + pInvoker->operator()(range); + + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + catch(const ::ipp::IwException &) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + return CV_HAL_ERROR_OK; +} + +#endif // HAVE_IPP_IW diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp index b7a0093516..17885d69cd 100644 --- a/hal/ipp/src/warp_ipp.cpp +++ b/hal/ipp/src/warp_ipp.cpp @@ -81,6 +81,8 @@ static bool IPPSet(const double value[4], void *dataPointer, int step, IppiSize int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, int dst_height, const double M[6], int interpolation, int borderType, const double borderValue[4]) { + CV_HAL_CHECK_USE_IPP(); + //CV_INSTRUMENT_REGION_IPP(); IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); @@ -188,6 +190,8 @@ int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, const double M[9], int interpolation, int borderType, const double borderValue[4]) { + CV_HAL_CHECK_USE_IPP(); + //CV_INSTRUMENT_REGION_IPP(); IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); @@ -384,6 +388,8 @@ int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int s float *mapx, size_t mapx_step, float *mapy, size_t mapy_step, int interpolation, int border_type, const double border_value[4]) { + CV_HAL_CHECK_USE_IPP(); + if (!((interpolation == cv::INTER_LINEAR || interpolation == cv::INTER_CUBIC || interpolation == cv::INTER_NEAREST) && (border_type == cv::BORDER_CONSTANT || border_type == cv::BORDER_TRANSPARENT))) { diff --git a/hal/ndsrvp/src/filter.cpp b/hal/ndsrvp/src/filter.cpp index 85f02b99c0..b873b87474 100644 --- a/hal/ndsrvp/src/filter.cpp +++ b/hal/ndsrvp/src/filter.cpp @@ -141,7 +141,7 @@ int filter(cvhalFilter2D *context, int cal_y = offset_y - ctx->anchor_y; // negative if top border exceeded // calculate source border - ctx->padding.resize(cal_width * cal_height * cnes); + ctx->padding.resize((size_t)cal_width * cal_height * cnes); uchar* pad_data = &ctx->padding[0]; int pad_step = cal_width * cnes; diff --git a/hal/riscv-rvv/src/core/mean.cpp b/hal/riscv-rvv/src/core/mean.cpp index 2fc2f98f65..264c06e46b 100644 --- a/hal/riscv-rvv/src/core/mean.cpp +++ b/hal/riscv-rvv/src/core/mean.cpp @@ -19,6 +19,14 @@ inline int meanStdDev_32FC1(const uchar* src_data, size_t src_step, int width, i int meanStdDev(const uchar* src_data, size_t src_step, int width, int height, int src_type, double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + + // NOTE: The OpenCV imlementation with universal intrinscs is more efficient + // See https://github.com/opencv/opencv/pull/29339#issuecomment-4778104352 + if (!mask && !stddev_val) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + switch (src_type) { case CV_8UC1: diff --git a/hal/riscv-rvv/src/features2d/fast.cpp b/hal/riscv-rvv/src/features2d/fast.cpp index 8b7cf66e40..fd9324ac6c 100644 --- a/hal/riscv-rvv/src/features2d/fast.cpp +++ b/hal/riscv-rvv/src/features2d/fast.cpp @@ -56,32 +56,35 @@ inline int fast_16(const uchar* src_data, size_t src_step, size_t vl; for (; j < width - 3; j += vl, ptr += vl) { - vl = __riscv_vsetvl_e16m1(width - 3 - j); + /* u8mf2 pre-screen: same VL as i16m1, defer vzext until needed */ + vl = __riscv_vsetvl_e8mf2(width - 3 - j); + vuint8mf2_t vcen_8 = __riscv_vle8_v_u8mf2(ptr, vl); + vuint8mf2_t vlo_8 = __riscv_vssubu_vx_u8mf2(vcen_8, (uint8_t)threshold, vl); + vuint8mf2_t vhi_8 = __riscv_vsaddu_vx_u8mf2(vcen_8, (uint8_t)threshold, vl); + vuint8mf2_t vk0_8 = __riscv_vle8_v_u8mf2(ptr + pixel[0], vl); + vuint8mf2_t vk4_8 = __riscv_vle8_v_u8mf2(ptr + pixel[4], vl); + vuint8mf2_t vk8_8 = __riscv_vle8_v_u8mf2(ptr + pixel[8], vl); + vuint8mf2_t vk12_8 = __riscv_vle8_v_u8mf2(ptr + pixel[12], vl); - /* Load center pixel and widen to int16 */ - vint16m1_t vcen = __riscv_vreinterpret_v_u16m1_i16m1( - __riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr, vl), vl)); - vint16m1_t vlo = __riscv_vsub_vx_i16m1(vcen, threshold, vl); - vint16m1_t vhi = __riscv_vadd_vx_i16m1(vcen, threshold, vl); - - /* 4-direction quick reject */ - vint16m1_t vk0 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr + pixel[0], vl), vl)); - vint16m1_t vk4 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr + pixel[4], vl), vl)); - vint16m1_t vk8 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr + pixel[8], vl), vl)); - vint16m1_t vk12 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr + pixel[12], vl), vl)); - - vbool16_t bright = __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vk0, vhi, vl), __riscv_vmsgt_vv_i16m1_b16(vk4, vhi, vl), vl); - vbool16_t dark = __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vlo, vk0, vl), __riscv_vmsgt_vv_i16m1_b16(vlo, vk4, vl), vl); - bright = __riscv_vmor_mm_b16(bright, __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vk4, vhi, vl), __riscv_vmsgt_vv_i16m1_b16(vk8, vhi, vl), vl), vl); - dark = __riscv_vmor_mm_b16(dark, __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vlo, vk4, vl), __riscv_vmsgt_vv_i16m1_b16(vlo, vk8, vl), vl), vl); - bright = __riscv_vmor_mm_b16(bright, __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vk8, vhi, vl), __riscv_vmsgt_vv_i16m1_b16(vk12, vhi, vl), vl), vl); - dark = __riscv_vmor_mm_b16(dark, __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vlo, vk8, vl), __riscv_vmsgt_vv_i16m1_b16(vlo, vk12, vl), vl), vl); - bright = __riscv_vmor_mm_b16(bright, __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vk12, vhi, vl), __riscv_vmsgt_vv_i16m1_b16(vk0, vhi, vl), vl), vl); - dark = __riscv_vmor_mm_b16(dark, __riscv_vmand_mm_b16(__riscv_vmsgt_vv_i16m1_b16(vlo, vk12, vl), __riscv_vmsgt_vv_i16m1_b16(vlo, vk0, vl), vl), vl); + vbool16_t bright = __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vk0_8, vhi_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vk4_8, vhi_8, vl), vl); + vbool16_t dark = __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk0_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk4_8, vl), vl); + bright = __riscv_vmor_mm_b16(bright, __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vk4_8, vhi_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vk8_8, vhi_8, vl), vl), vl); + dark = __riscv_vmor_mm_b16(dark, __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk4_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk8_8, vl), vl), vl); + bright = __riscv_vmor_mm_b16(bright, __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vk8_8, vhi_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vk12_8, vhi_8, vl), vl), vl); + dark = __riscv_vmor_mm_b16(dark, __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk8_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk12_8, vl), vl), vl); + bright = __riscv_vmor_mm_b16(bright, __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vk12_8, vhi_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vk0_8, vhi_8, vl), vl), vl); + dark = __riscv_vmor_mm_b16(dark, __riscv_vmand_mm_b16(__riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk12_8, vl), __riscv_vmsgtu_vv_u8mf2_b16(vlo_8, vk0_8, vl), vl), vl); if (__riscv_vfirst_m_b16(__riscv_vmor_mm_b16(bright, dark, vl), vl) < 0) continue; + /* Widen pre-loaded u8mf2 to i16m1 for score computation */ + vint16m1_t vcen = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(vcen_8, vl)); + vint16m1_t vk0 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(vk0_8, vl)); + vint16m1_t vk4 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(vk4_8, vl)); + vint16m1_t vk8 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(vk8_8, vl)); + vint16m1_t vk12 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(vk12_8, vl)); + /* Load remaining 12 neighbors */ vint16m1_t vk1 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr + pixel[1], vl), vl)); vint16m1_t vk2 = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vzext_vf2(__riscv_vle8_v_u8mf2(ptr + pixel[2], vl), vl)); diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 8f6d51e200..ecc27202d5 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -8,14 +8,15 @@ ocv_add_dispatched_file(convert_scale SSE2 AVX2 LASX) ocv_add_dispatched_file(count_non_zero SSE2 AVX2 LASX) ocv_add_dispatched_file(has_non_zero SSE2 AVX2 LASX ) ocv_add_dispatched_file(matmul SSE2 SSE4_1 AVX2 AVX512_SKX NEON_DOTPROD LASX) -ocv_add_dispatched_file(mean SSE2 AVX2 LASX) +ocv_add_dispatched_file(mean SSE2 AVX2 AVX512_SKX AVX512_ICL LASX) ocv_add_dispatched_file(merge SSE2 AVX2 LASX) -ocv_add_dispatched_file(minmax SSE2 SSE4_1 AVX2 VSX3 LASX) +ocv_add_dispatched_file(minmax SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL VSX3 LASX) ocv_add_dispatched_file(nan_mask SSE2 AVX2 LASX) ocv_add_dispatched_file(split SSE2 AVX2 LASX) -ocv_add_dispatched_file(sum SSE2 AVX2 LASX) +ocv_add_dispatched_file(sum SSE2 AVX2 AVX512_SKX AVX512_ICL LASX) ocv_add_dispatched_file(reduce SSE2 SSSE3 AVX2 NEON_DOTPROD) ocv_add_dispatched_file(norm SSE2 SSE4_1 AVX AVX2 NEON_DOTPROD LASX) +ocv_add_dispatched_file(lut AVX512_ICL) ocv_add_dispatched_file(transpose AVX AVX2 NEON RVV LASX) # dispatching for accuracy tests diff --git a/modules/core/include/opencv2/core/hal/intrin_avx.hpp b/modules/core/include/opencv2/core/hal/intrin_avx.hpp index f2525f0b24..aadfa2f5f1 100644 --- a/modules/core/include/opencv2/core/hal/intrin_avx.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_avx.hpp @@ -1603,6 +1603,20 @@ inline v_uint8x32 v256_lut(const uchar* tab, const int* idx) { return v_reinterp inline v_uint8x32 v256_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut_pairs((const schar *)tab, idx)); } inline v_uint8x32 v256_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut_quads((const schar *)tab, idx)); } +inline v_uint8x32 v256_lut(const uchar* tab, const v_uint8x32& idx) +{ + uchar CV_DECL_ALIGNED(32) indices[32], result[32]; + _mm256_store_si256((__m256i*)indices, idx.val); + for (int i = 0; i < 32; i++) result[i] = tab[indices[i]]; + return v_uint8x32(_mm256_load_si256((const __m256i*)result)); +} +inline v_int8x32 v256_lut(const schar* tab, const v_uint8x32& idx) +{ return v_reinterpret_as_s8(v256_lut((const uchar *)tab, idx)); } + + +inline v_uint8x32 v_lut(const uchar* tab, const v_uint8x32& idx) { return v256_lut(tab, idx); } +inline v_int8x32 v_lut(const schar* tab, const v_uint8x32& idx) { return v256_lut(tab, idx); } + inline v_int16x16 v256_lut(const short* tab, const int* idx) { return v_int16x16(_mm256_setr_epi16(tab[idx[0]], tab[idx[1]], tab[idx[ 2]], tab[idx[ 3]], tab[idx[ 4]], tab[idx[ 5]], tab[idx[ 6]], tab[idx[ 7]], diff --git a/modules/core/include/opencv2/core/hal/intrin_avx512.hpp b/modules/core/include/opencv2/core/hal/intrin_avx512.hpp index 077b4d17a7..e06dc05d72 100644 --- a/modules/core/include/opencv2/core/hal/intrin_avx512.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_avx512.hpp @@ -1634,6 +1634,122 @@ inline v_uint8x64 v512_lut(const uchar* tab, const int* idx) { return v_reinterp inline v_uint8x64 v512_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v512_lut_pairs((const schar *)tab, idx)); } inline v_uint8x64 v512_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v512_lut_quads((const schar *)tab, idx)); } +// Byte-indexed LUT: takes v_uint8x64 byte indices instead of int* indices +// Table must have at least 256 entries (byte indices cover 0-255) +// VBMI path uses vpermb (register-to-register), fallback uses gather +inline v_uint8x64 v512_lut(const uchar* tab, const v_uint8x64& idx) +{ +#if CV_AVX_512VBMI + const __m512i lut0 = _mm512_loadu_si512(tab); + const __m512i lut1 = _mm512_loadu_si512(tab + 64); + const __m512i lut2 = _mm512_loadu_si512(tab + 128); + const __m512i lut3 = _mm512_loadu_si512(tab + 192); + + __m512i idx6 = _mm512_and_si512(idx.val, _mm512_set1_epi8(0x3F)); + + __m512i r0 = _mm512_permutexvar_epi8(idx6, lut0); + __m512i r1 = _mm512_permutexvar_epi8(idx6, lut1); + __m512i r2 = _mm512_permutexvar_epi8(idx6, lut2); + __m512i r3 = _mm512_permutexvar_epi8(idx6, lut3); + + __mmask64 k6 = _mm512_test_epi8_mask(idx.val, _mm512_set1_epi8(0x40)); + __mmask64 k7 = _mm512_test_epi8_mask(idx.val, _mm512_set1_epi8((char)0x80)); + + __m512i low = _mm512_mask_blend_epi8(k6, r0, r1); + __m512i high = _mm512_mask_blend_epi8(k6, r2, r3); + return v_uint8x64(_mm512_mask_blend_epi8(k7, low, high)); +#else + uchar CV_DECL_ALIGNED(64) indices[64], result[64]; + _mm512_store_si512((__m512i*)indices, idx.val); + for (int i = 0; i < 64; i++) result[i] = tab[indices[i]]; + return v_uint8x64(_mm512_load_si512((const __m512i*)result)); +#endif +} +inline v_int8x64 v512_lut(const schar* tab, const v_uint8x64& idx) +{ return v_reinterpret_as_s8(v512_lut((const uchar *)tab, idx)); } + +// Universal v_lut overloads for vector byte indices (aliases to v512_lut) +inline v_uint8x64 v_lut(const uchar* tab, const v_uint8x64& idx) +{ return v512_lut(tab, idx); } + +inline v_int8x64 v_lut(const schar* tab, const v_uint8x64& idx) +{ return v512_lut(tab, idx); } + +// Byte-indexed pair LUT: 32 byte indices (lower half of idx) → 32 pairs = 64 bytes +inline v_uint8x64 v512_lut_pairs(const uchar* tab, const v_uint8x64& idx) +{ +#if CV_AVX_512VBMI + const __m512i lut0 = _mm512_loadu_si512(tab); + const __m512i lut1 = _mm512_loadu_si512(tab + 64); + const __m512i lut2 = _mm512_loadu_si512(tab + 128); + const __m512i lut3 = _mm512_loadu_si512(tab + 192); + + // Expand 32 byte indices to interleaved pairs: (i0, i0+1, i1, i1+1, ...) + __m512i idx16 = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(idx.val)); + __m512i idx_dup = _mm512_or_si512(idx16, _mm512_slli_epi16(idx16, 8)); + __m512i idx_pairs = _mm512_add_epi8(idx_dup, _mm512_set1_epi16(0x0100)); + + __m512i idx6 = _mm512_and_si512(idx_pairs, _mm512_set1_epi8(0x3F)); + + __m512i r0 = _mm512_permutexvar_epi8(idx6, lut0); + __m512i r1 = _mm512_permutexvar_epi8(idx6, lut1); + __m512i r2 = _mm512_permutexvar_epi8(idx6, lut2); + __m512i r3 = _mm512_permutexvar_epi8(idx6, lut3); + + __mmask64 k6 = _mm512_test_epi8_mask(idx_pairs, _mm512_set1_epi8(0x40)); + __mmask64 k7 = _mm512_test_epi8_mask(idx_pairs, _mm512_set1_epi8((char)0x80)); + + __m512i low = _mm512_mask_blend_epi8(k6, r0, r1); + __m512i high = _mm512_mask_blend_epi8(k6, r2, r3); + return v_uint8x64(_mm512_mask_blend_epi8(k7, low, high)); +#else + uchar CV_DECL_ALIGNED(64) indices[64], result[64]; + _mm512_store_si512((__m512i*)indices, idx.val); + for (int i = 0; i < 64; i++) result[i] = tab[indices[i]]; + return v_uint8x64(_mm512_load_si512((const __m512i*)result)); +#endif +} +inline v_int8x64 v512_lut_pairs(const schar* tab, const v_uint8x64& idx) +{ return v_reinterpret_as_s8(v512_lut_pairs((const uchar *)tab, idx)); } + +// Byte-indexed quad LUT: 16 byte indices (lower 16 of idx) → 16 quads = 64 bytes +inline v_uint8x64 v512_lut_quads(const uchar* tab, const v_uint8x64& idx) +{ +#if CV_AVX_512VBMI + const __m512i lut0 = _mm512_loadu_si512(tab); + const __m512i lut1 = _mm512_loadu_si512(tab + 64); + const __m512i lut2 = _mm512_loadu_si512(tab + 128); + const __m512i lut3 = _mm512_loadu_si512(tab + 192); + + // Expand 16 byte indices to quad offsets: (i0, i0+1, i0+2, i0+3, i1, ...) + __m512i idx32 = _mm512_cvtepu8_epi32(_mm512_castsi512_si128(idx.val)); + __m512i t1 = _mm512_or_si512(idx32, _mm512_slli_epi32(idx32, 8)); + __m512i idx_bcast = _mm512_or_si512(t1, _mm512_slli_epi32(t1, 16)); + __m512i idx_quads = _mm512_add_epi8(idx_bcast, _mm512_set1_epi32(0x03020100)); + + __m512i idx6 = _mm512_and_si512(idx_quads, _mm512_set1_epi8(0x3F)); + + __m512i r0 = _mm512_permutexvar_epi8(idx6, lut0); + __m512i r1 = _mm512_permutexvar_epi8(idx6, lut1); + __m512i r2 = _mm512_permutexvar_epi8(idx6, lut2); + __m512i r3 = _mm512_permutexvar_epi8(idx6, lut3); + + __mmask64 k6 = _mm512_test_epi8_mask(idx_quads, _mm512_set1_epi8(0x40)); + __mmask64 k7 = _mm512_test_epi8_mask(idx_quads, _mm512_set1_epi8((char)0x80)); + + __m512i low = _mm512_mask_blend_epi8(k6, r0, r1); + __m512i high = _mm512_mask_blend_epi8(k6, r2, r3); + return v_uint8x64(_mm512_mask_blend_epi8(k7, low, high)); +#else + uchar CV_DECL_ALIGNED(64) indices[64], result[64]; + _mm512_store_si512((__m512i*)indices, idx.val); + for (int i = 0; i < 64; i++) result[i] = tab[indices[i]]; + return v_uint8x64(_mm512_load_si512((const __m512i*)result)); +#endif +} +inline v_int8x64 v512_lut_quads(const schar* tab, const v_uint8x64& idx) +{ return v_reinterpret_as_s8(v512_lut_quads((const uchar *)tab, idx)); } + inline v_int16x32 v512_lut(const short* tab, const int* idx) { __m256i p0 = _mm512_cvtepi32_epi16(_mm512_i32gather_epi32(_mm512_loadu_si512((const __m512i*)idx ), (const int *)tab, 2)); diff --git a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp index 25f7c2bbbc..6e00a210e2 100644 --- a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp @@ -14,6 +14,7 @@ // Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Copyright (C) 2013, OpenCV Foundation, all rights reserved. // Copyright (C) 2015, Itseez Inc., 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, @@ -2714,6 +2715,16 @@ template inline v_reg<_Tp, simd128_width / sizeof(_Tp)> v_lut_quad return c; } +template inline v_reg v_lut(const uchar* tab, const v_reg& idx) +{ + v_reg c; + for( int i = 0; i < n; i++ ) + c.s[i] = tab[idx.s[i]]; + return c; +} +template inline v_reg v_lut(const schar* tab, const v_reg& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + template inline v_reg v_lut(const int* tab, const v_reg& idx) { v_reg c; diff --git a/modules/core/include/opencv2/core/hal/intrin_lasx.hpp b/modules/core/include/opencv2/core/hal/intrin_lasx.hpp index 97df327767..7523025fd5 100644 --- a/modules/core/include/opencv2/core/hal/intrin_lasx.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_lasx.hpp @@ -1677,6 +1677,21 @@ inline v_uint8x32 v256_lut(const uchar* tab, const int* idx) { return v_reinterp inline v_uint8x32 v256_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut_pairs((const schar *)tab, idx)); } inline v_uint8x32 v256_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut_quads((const schar *)tab, idx)); } +// Byte-indexed LUT: 32 byte indices in a vector -> 32 looked-up bytes +inline v_uint8x32 v256_lut(const uchar* tab, const v_uint8x32& idx) +{ + uchar CV_DECL_ALIGNED(32) indices[32], result[32]; + __lasx_xvst(idx.val, indices, 0); + for (int i = 0; i < 32; i++) result[i] = tab[indices[i]]; + return v_uint8x32(__lasx_xvld(result, 0)); +} +inline v_int8x32 v256_lut(const schar* tab, const v_uint8x32& idx) +{ return v_reinterpret_as_s8(v256_lut((const uchar*)tab, idx)); } + +// Universal v_lut overloads for vector byte indices (aliases to v256_lut) +inline v_uint8x32 v_lut(const uchar* tab, const v_uint8x32& idx) { return v256_lut(tab, idx); } +inline v_int8x32 v_lut(const schar* tab, const v_uint8x32& idx) { return v256_lut(tab, idx); } + inline v_int16x16 v256_lut(const short* tab, const int* idx) { return v_int16x16(_v256_setr_h(tab[idx[ 0]], tab[idx[ 1]], tab[idx[ 2]], tab[idx[ 3]], tab[idx[ 4]], diff --git a/modules/core/include/opencv2/core/hal/intrin_lsx.hpp b/modules/core/include/opencv2/core/hal/intrin_lsx.hpp index bc720e0a4b..6dac0286c4 100644 --- a/modules/core/include/opencv2/core/hal/intrin_lsx.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_lsx.hpp @@ -1447,6 +1447,16 @@ inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((const schar*)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + __lsx_vst(idx.val, indices, 0); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(__lsx_vld(result, 0)); +} +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + inline v_int16x8 v_lut(const short* tab, const int* idx) { return v_int16x8(_v128_setr_h(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], diff --git a/modules/core/include/opencv2/core/hal/intrin_msa.hpp b/modules/core/include/opencv2/core/hal/intrin_msa.hpp index 3917faa292..a1458e224d 100644 --- a/modules/core/include/opencv2/core/hal/intrin_msa.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_msa.hpp @@ -1566,6 +1566,15 @@ inline v_uint8x16 v_lut(const uchar* tab, const int* idx) { return v_reinterpret inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((schar*)tab, idx)); } inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((schar*)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + msa_st1q_u8(indices, idx.val); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(msa_ld1q_u8(result)); +} +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } inline v_int16x8 v_lut(const short* tab, const int* idx) { diff --git a/modules/core/include/opencv2/core/hal/intrin_neon.hpp b/modules/core/include/opencv2/core/hal/intrin_neon.hpp index 044c00ca4e..9dd2957f2f 100644 --- a/modules/core/include/opencv2/core/hal/intrin_neon.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_neon.hpp @@ -14,6 +14,7 @@ // Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Copyright (C) 2013, OpenCV Foundation, all rights reserved. // Copyright (C) 2015, Itseez Inc., 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, @@ -2707,6 +2708,48 @@ inline v_uint8x16 v_lut(const uchar* tab, const int* idx) { return v_reinterpret inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((schar*)tab, idx)); } inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((schar*)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ +#if CV_NEON_AARCH64 + uint8x16_t index = idx.val; + uint8x16_t idx6 = vandq_u8(index, vdupq_n_u8(0x3F)); + + uint8x16x4_t t0, t1, t2, t3; + t0.val[0] = vld1q_u8(tab); t0.val[1] = vld1q_u8(tab + 16); + t0.val[2] = vld1q_u8(tab + 32); t0.val[3] = vld1q_u8(tab + 48); + uint8x16_t r0 = vqtbl4q_u8(t0, idx6); + + t1.val[0] = vld1q_u8(tab + 64); t1.val[1] = vld1q_u8(tab + 80); + t1.val[2] = vld1q_u8(tab + 96); t1.val[3] = vld1q_u8(tab + 112); + uint8x16_t r1 = vqtbl4q_u8(t1, idx6); + + t2.val[0] = vld1q_u8(tab + 128); t2.val[1] = vld1q_u8(tab + 144); + t2.val[2] = vld1q_u8(tab + 160); t2.val[3] = vld1q_u8(tab + 176); + uint8x16_t r2 = vqtbl4q_u8(t2, idx6); + + t3.val[0] = vld1q_u8(tab + 192); t3.val[1] = vld1q_u8(tab + 208); + t3.val[2] = vld1q_u8(tab + 224); t3.val[3] = vld1q_u8(tab + 240); + uint8x16_t r3 = vqtbl4q_u8(t3, idx6); + + uint8x16_t bit6 = vtstq_u8(index, vdupq_n_u8(0x40)); + uint8x16_t low = vbslq_u8(bit6, r1, r0); + uint8x16_t high = vbslq_u8(bit6, r3, r2); + + uint8x16_t bit7 = vtstq_u8(index, vdupq_n_u8(0x80)); + return v_uint8x16(vbslq_u8(bit7, high, low)); +#else + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + vst1q_u8(indices, idx.val); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(vld1q_u8(result)); +#endif +} + +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ + return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); +} + inline v_int16x8 v_lut(const short* tab, const int* idx) { short CV_DECL_ALIGNED(32) elems[8] = diff --git a/modules/core/include/opencv2/core/hal/intrin_rvv071.hpp b/modules/core/include/opencv2/core/hal/intrin_rvv071.hpp index 146335dc01..8e470b18e5 100644 --- a/modules/core/include/opencv2/core/hal/intrin_rvv071.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_rvv071.hpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html // Copyright (C) 2015, PingTouGe Semiconductor Co., Ltd., all rights reserved. +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. #ifndef OPENCV_HAL_INTRIN_RISCVV_HPP #define OPENCV_HAL_INTRIN_RISCVV_HPP @@ -1625,6 +1626,16 @@ inline v_uint8x16 v_lut(const uchar* tab, const int* idx) { return v_reinterpret inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((schar*)tab, idx)); } inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((schar*)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + vse8_v_u8m1(indices, idx.val, 16); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(vle8_v_u8m1(result, 16)); +} +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + inline v_int16x8 v_lut(const short* tab, const int* idx) { #if 0 diff --git a/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp b/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp index 7abfad01df..2afc277ee1 100644 --- a/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp @@ -4,6 +4,7 @@ // The original implementation is contributed by HAN Liutong. // Copyright (C) 2022, Institute of Software, Chinese Academy of Sciences. +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. #ifndef OPENCV_HAL_INTRIN_RVV_SCALABLE_HPP #define OPENCV_HAL_INTRIN_RVV_SCALABLE_HPP @@ -703,6 +704,13 @@ inline void v_lut_deinterleave(const double* tab, const v_int32& vidx, v_float64 inline v_uint8 v_lut(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut((schar*)tab, idx)); } inline v_uint8 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((schar*)tab, idx)); } inline v_uint8 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((schar*)tab, idx)); } + +// Byte-indexed LUT: vector byte indices -> looked-up bytes (uses RVV indexed load) +inline v_uint8 v_lut(const uchar* tab, const v_uint8& idx) +{ return __riscv_vluxei8_v_u8m2(tab, idx, VTraits::vlanes()); } +inline v_int8 v_lut(const schar* tab, const v_uint8& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + inline v_uint16 v_lut(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v_lut((short*)tab, idx)); } inline v_uint16 v_lut_pairs(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v_lut_pairs((short*)tab, idx)); } inline v_uint16 v_lut_quads(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v_lut_quads((short*)tab, idx)); } diff --git a/modules/core/include/opencv2/core/hal/intrin_sse.hpp b/modules/core/include/opencv2/core/hal/intrin_sse.hpp index 26ea340263..3cd2cbff6b 100644 --- a/modules/core/include/opencv2/core/hal/intrin_sse.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_sse.hpp @@ -14,6 +14,7 @@ // Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Copyright (C) 2013, OpenCV Foundation, all rights reserved. // Copyright (C) 2015, Itseez Inc., 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, @@ -3070,71 +3071,55 @@ inline v_float64x2 v_cvt_f64(const v_int64x2& v) inline v_int8x16 v_lut(const schar* tab, const int* idx) { -#if defined(_MSC_VER) - return v_int8x16(_mm_setr_epi8(tab[idx[0]], tab[idx[1]], tab[idx[ 2]], tab[idx[ 3]], tab[idx[ 4]], tab[idx[ 5]], tab[idx[ 6]], tab[idx[ 7]], - tab[idx[8]], tab[idx[9]], tab[idx[10]], tab[idx[11]], tab[idx[12]], tab[idx[13]], tab[idx[14]], tab[idx[15]])); -#else - return v_int8x16(_mm_setr_epi64( - _mm_setr_pi8(tab[idx[0]], tab[idx[1]], tab[idx[ 2]], tab[idx[ 3]], tab[idx[ 4]], tab[idx[ 5]], tab[idx[ 6]], tab[idx[ 7]]), - _mm_setr_pi8(tab[idx[8]], tab[idx[9]], tab[idx[10]], tab[idx[11]], tab[idx[12]], tab[idx[13]], tab[idx[14]], tab[idx[15]]) - )); -#endif + return v_int8x16(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], + tab[idx[4]], tab[idx[5]], tab[idx[6]], tab[idx[7]], + tab[idx[8]], tab[idx[9]], tab[idx[10]], tab[idx[11]], + tab[idx[12]], tab[idx[13]], tab[idx[14]], tab[idx[15]]); } inline v_int8x16 v_lut_pairs(const schar* tab, const int* idx) { -#if defined(_MSC_VER) - return v_int8x16(_mm_setr_epi16(*(const short*)(tab + idx[0]), *(const short*)(tab + idx[1]), *(const short*)(tab + idx[2]), *(const short*)(tab + idx[3]), - *(const short*)(tab + idx[4]), *(const short*)(tab + idx[5]), *(const short*)(tab + idx[6]), *(const short*)(tab + idx[7]))); -#else - return v_int8x16(_mm_setr_epi64( - _mm_setr_pi16(*(const short*)(tab + idx[0]), *(const short*)(tab + idx[1]), *(const short*)(tab + idx[2]), *(const short*)(tab + idx[3])), - _mm_setr_pi16(*(const short*)(tab + idx[4]), *(const short*)(tab + idx[5]), *(const short*)(tab + idx[6]), *(const short*)(tab + idx[7])) - )); -#endif + return v_int8x16(tab[idx[0]], tab[idx[0] + 1], tab[idx[1]], tab[idx[1] + 1], + tab[idx[2]], tab[idx[2] + 1], tab[idx[3]], tab[idx[3] + 1], + tab[idx[4]], tab[idx[4] + 1], tab[idx[5]], tab[idx[5] + 1], + tab[idx[6]], tab[idx[6] + 1], tab[idx[7]], tab[idx[7] + 1]); } inline v_int8x16 v_lut_quads(const schar* tab, const int* idx) { -#if defined(_MSC_VER) - return v_int8x16(_mm_setr_epi32(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1]), - *(const int*)(tab + idx[2]), *(const int*)(tab + idx[3]))); -#else - return v_int8x16(_mm_setr_epi64( - _mm_setr_pi32(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1])), - _mm_setr_pi32(*(const int*)(tab + idx[2]), *(const int*)(tab + idx[3])) - )); -#endif + return v_int8x16( + tab[idx[0]], tab[idx[0] + 1], tab[idx[0] + 2], tab[idx[0] + 3], + tab[idx[1]], tab[idx[1] + 1], tab[idx[1] + 2], tab[idx[1] + 3], + tab[idx[2]], tab[idx[2] + 1], tab[idx[2] + 2], tab[idx[2] + 3], + tab[idx[3]], tab[idx[3] + 1], tab[idx[3] + 2], tab[idx[3] + 3]); } inline v_uint8x16 v_lut(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut((const schar *)tab, idx)); } inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((const schar *)tab, idx)); } inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((const schar *)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + _mm_store_si128((__m128i*)indices, idx.val); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(_mm_load_si128((const __m128i*)result)); +} +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + inline v_int16x8 v_lut(const short* tab, const int* idx) { -#if defined(_MSC_VER) - return v_int16x8(_mm_setr_epi16(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], - tab[idx[4]], tab[idx[5]], tab[idx[6]], tab[idx[7]])); -#else - return v_int16x8(_mm_setr_epi64( - _mm_setr_pi16(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]]), - _mm_setr_pi16(tab[idx[4]], tab[idx[5]], tab[idx[6]], tab[idx[7]]) - )); -#endif + return v_int16x8(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], + tab[idx[4]], tab[idx[5]], tab[idx[6]], tab[idx[7]]); } inline v_int16x8 v_lut_pairs(const short* tab, const int* idx) { -#if defined(_MSC_VER) - return v_int16x8(_mm_setr_epi32(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1]), - *(const int*)(tab + idx[2]), *(const int*)(tab + idx[3]))); -#else - return v_int16x8(_mm_setr_epi64( - _mm_setr_pi32(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1])), - _mm_setr_pi32(*(const int*)(tab + idx[2]), *(const int*)(tab + idx[3])) - )); -#endif + return v_int16x8(tab[idx[0]], tab[idx[0] + 1], tab[idx[1]], tab[idx[1] + 1], + tab[idx[2]], tab[idx[2] + 1], tab[idx[3]], tab[idx[3] + 1]); } inline v_int16x8 v_lut_quads(const short* tab, const int* idx) { - return v_int16x8(_mm_set_epi64x(*(const int64_t*)(tab + idx[1]), *(const int64_t*)(tab + idx[0]))); + return v_int16x8(tab[idx[0]], tab[idx[0] + 1], tab[idx[0] + 2], + tab[idx[0] + 3], tab[idx[1]], tab[idx[1] + 1], + tab[idx[1] + 2], tab[idx[1] + 3]); } inline v_uint16x8 v_lut(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v_lut((const short *)tab, idx)); } inline v_uint16x8 v_lut_pairs(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v_lut_pairs((const short *)tab, idx)); } @@ -3142,15 +3127,7 @@ inline v_uint16x8 v_lut_quads(const ushort* tab, const int* idx) { return v_rein inline v_int32x4 v_lut(const int* tab, const int* idx) { -#if defined(_MSC_VER) - return v_int32x4(_mm_setr_epi32(tab[idx[0]], tab[idx[1]], - tab[idx[2]], tab[idx[3]])); -#else - return v_int32x4(_mm_setr_epi64( - _mm_setr_pi32(tab[idx[0]], tab[idx[1]]), - _mm_setr_pi32(tab[idx[2]], tab[idx[3]]) - )); -#endif + return v_int32x4(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]]); } inline v_int32x4 v_lut_pairs(const int* tab, const int* idx) { diff --git a/modules/core/include/opencv2/core/hal/intrin_vsx.hpp b/modules/core/include/opencv2/core/hal/intrin_vsx.hpp index 0a0915a22f..a0383e7352 100644 --- a/modules/core/include/opencv2/core/hal/intrin_vsx.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_vsx.hpp @@ -1175,6 +1175,16 @@ inline v_uint8x16 v_lut(const uchar* tab, const int* idx) { return v_reinterpret inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((const schar*)tab, idx)); } inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((const schar*)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + vsx_st(idx.val, 0, indices); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(vsx_ld(0, result)); +} +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + inline v_int16x8 v_lut(const short* tab, const int* idx) { return v_int16x8(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], tab[idx[4]], tab[idx[5]], tab[idx[6]], tab[idx[7]]); diff --git a/modules/core/include/opencv2/core/hal/intrin_wasm.hpp b/modules/core/include/opencv2/core/hal/intrin_wasm.hpp index a15a8ddef1..d6e881a1a1 100644 --- a/modules/core/include/opencv2/core/hal/intrin_wasm.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_wasm.hpp @@ -2581,6 +2581,16 @@ inline v_uint8x16 v_lut(const uchar* tab, const int* idx) { return v_reinterpret inline v_uint8x16 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((const schar *)tab, idx)); } inline v_uint8x16 v_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_quads((const schar *)tab, idx)); } +inline v_uint8x16 v_lut(const uchar* tab, const v_uint8x16& idx) +{ + uchar CV_DECL_ALIGNED(16) indices[16], result[16]; + wasm_v128_store(indices, idx.val); + for (int i = 0; i < 16; i++) result[i] = tab[indices[i]]; + return v_uint8x16(wasm_v128_load(result)); +} +inline v_int8x16 v_lut(const schar* tab, const v_uint8x16& idx) +{ return v_reinterpret_as_s8(v_lut((const uchar*)tab, idx)); } + inline v_int16x8 v_lut(const short* tab, const int* idx) { return v_int16x8(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index f1afcd9843..47fa176b7f 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -687,6 +687,7 @@ public: Mat_ m2({2, 3}, {1, 2, 3, 4, 5, 6}); // 2x3 Mat Mat_ R({2, 2}, {a, -b, b, a}); // from example + \endcode */ template class MatCommaInitializer_ { diff --git a/modules/core/perf/perf_arithm.cpp b/modules/core/perf/perf_arithm.cpp index 54053fd652..a7eea48d3e 100644 --- a/modules/core/perf/perf_arithm.cpp +++ b/modules/core/perf/perf_arithm.cpp @@ -436,7 +436,22 @@ PERF_TEST_P_(BinaryOpTest, transpose2d) SANITY_CHECK_NOTHING(); } -PERF_TEST_P_(BinaryOpTest, transposeND) +static cv::Mat makeTransposeNDOutput(const cv::Mat& src, const std::vector& order) +{ + std::vector new_sz(order.size()); + for (size_t i = 0; i < order.size(); ++i) + new_sz[i] = src.size[order[i]]; + return Mat(static_cast(new_sz.size()), new_sz.data(), src.type()); +} + +static cv::Mat makeTransposeNDInput3D(Size sz, int type) +{ + const int channels = CV_MAT_CN(type); + int dims[] = { sz.height, sz.width, channels }; + return Mat(3, dims, CV_MAKETYPE(CV_MAT_DEPTH(type), 1)); +} + +PERF_TEST_P_(BinaryOpTest, transposeND_identity_order) { Size sz = get<0>(GetParam()); int type = get<1>(GetParam()); @@ -444,14 +459,66 @@ PERF_TEST_P_(BinaryOpTest, transposeND) std::vector order(a.dims); std::iota(order.begin(), order.end(), 0); - std::reverse(order.begin(), order.end()); - std::vector new_sz(a.dims); - std::copy(a.size.p, a.size.p + a.dims, new_sz.begin()); - std::reverse(new_sz.begin(), new_sz.end()); - cv::Mat b = Mat(new_sz, type); + cv::Mat b = makeTransposeNDOutput(a, order); - declare.in(a,WARMUP_RNG).out(b); + declare.in(a, WARMUP_RNG).out(b); + + TEST_CYCLE() cv::transposeND(a, order, b); + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(BinaryOpTest, transposeND_2d_swap_order) +{ + Size sz = get<0>(GetParam()); + int type = get<1>(GetParam()); + cv::Mat a = Mat(sz, type).reshape(1); + + std::vector order{1, 0}; + + cv::Mat b = makeTransposeNDOutput(a, order); + + declare.in(a, WARMUP_RNG).out(b); + + TEST_CYCLE() cv::transposeND(a, order, b); + + SANITY_CHECK_NOTHING(); +} + + +PERF_TEST_P_(BinaryOpTest, transposeND_generic_keep_tail_order) +{ + Size sz = get<0>(GetParam()); + int type = get<1>(GetParam()); + cv::Mat a = makeTransposeNDInput3D(sz, type); + + std::vector order{1, 0, 2}; + + cv::Mat b = makeTransposeNDOutput(a, order); + + randu(a, 0, 255); + //BUG: //BUG: in/out calls do not support arrays with more than 2 dimentions + //declare.in(a).out(b); + + TEST_CYCLE() cv::transposeND(a, order, b); + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(BinaryOpTest, transposeND_generic_move_tail_order) +{ + Size sz = get<0>(GetParam()); + int type = get<1>(GetParam()); + cv::Mat a = makeTransposeNDInput3D(sz, type); + + std::vector order{2, 0, 1}; + + cv::Mat b = makeTransposeNDOutput(a, order); + + randu(a, 0, 255); + //BUG: in/out calls do not support arrays with more than 2 dimentions + //declare.in(a).out(b); TEST_CYCLE() cv::transposeND(a, order, b); diff --git a/modules/core/perf/perf_gemm.cpp b/modules/core/perf/perf_gemm.cpp new file mode 100644 index 0000000000..de8ad54815 --- /dev/null +++ b/modules/core/perf/perf_gemm.cpp @@ -0,0 +1,117 @@ +#include "perf_precomp.hpp" + +namespace opencv_test +{ +using namespace perf; + +CV_FLAGS(GemmFlag, 0, GEMM_1_T, GEMM_2_T, GEMM_3_T) + +typedef tuple, MatType, GemmFlag> GemmTestParams_t; +class GemmTest : public perf::TestBaseWithParam +{ + public: + void runGemmTest(const GemmTestParams_t& params) + { + int M = get<0>(get<0>(params)); + int N = get<1>(get<0>(params)); + int K = get<2>(get<0>(params)); + int type = get<1>(params); + int flags = get<2>(params); + + Size aSize = (flags & GEMM_1_T) ? Size(M, K) : Size(K, M); + Size bSize = (flags & GEMM_2_T) ? Size(K, N) : Size(N, K); + + Mat src1(aSize, type), src2(bSize, type), src3(M, N, type), dst(M, N, type); + declare.in(src1, src2, src3, WARMUP_RNG).out(dst); + + TEST_CYCLE() cv::gemm(src1, src2, 1.0, src3, 1.0, dst, flags); + + if (dst.total() * dst.channels() < 26) + SANITY_CHECK_NOTHING(); + else + SANITY_CHECK(dst, (CV_MAT_DEPTH(type) == CV_32F) ? 1e-4 : 1e-6, ERROR_RELATIVE); + }; +}; + +// Sparse coverage: exercise tiny/small/rectangular shapes and m=1/n=1 edge cases. +// Large square sizes (640+) are covered by opencl/perf_gemm.cpp on the CPU perf tool. +PERF_TEST_P(GemmTest, gemmTiny, + testing::Combine( + testing::Values( + make_tuple(2, 2, 2), + make_tuple(3, 3, 3) + ), + testing::Values(CV_32FC1, CV_64FC1), + testing::Values(0, (int)GEMM_1_T, (int)GEMM_2_T, + (int)(GEMM_1_T | GEMM_2_T)) + )) +{ + GemmTest::runGemmTest(GetParam()); +} + +PERF_TEST_P(GemmTest, gemmSmall, + testing::Combine( + testing::Values( + make_tuple(8, 8, 8), + make_tuple(32, 32, 32), + make_tuple(64, 64, 64), + make_tuple(8, 16, 32) + ), + testing::Values(CV_32FC1), + testing::Values(0, (int)GEMM_2_T) + )) +{ + GemmTest::runGemmTest(GetParam()); +} + +PERF_TEST_P(GemmTest, gemmSquare, + testing::Combine( + testing::Values( + make_tuple(256, 256, 256), + make_tuple(512, 512, 512) + ), + testing::Values(CV_32FC1), + testing::Values(0) + )) +{ + GemmTest::runGemmTest(GetParam()); +} + +PERF_TEST_P(GemmTest, gemmRect, + testing::Combine( + testing::Values( + make_tuple(1024, 64, 256), + make_tuple(256, 1024, 512) + ), + testing::Values(CV_32FC1), + testing::Values(0) + )) +{ + GemmTest::runGemmTest(GetParam()); +} + +PERF_TEST_P(GemmTest, gemmM1, + testing::Combine( + testing::Values( + make_tuple(1, 64, 2500) + ), + testing::Values(CV_32FC1), + testing::Values(0, (int)GEMM_1_T) + )) +{ + GemmTest::runGemmTest(GetParam()); +} + +PERF_TEST_P(GemmTest, gemmN1, + testing::Combine( + testing::Values( + make_tuple(256, 1, 256) + ), + testing::Values(CV_32FC1), + testing::Values(0) + )) +{ + GemmTest::runGemmTest(GetParam()); +} + +} // namespace diff --git a/modules/core/src/hal_internal.cpp b/modules/core/src/hal_internal.cpp index 1444f91084..2274c96f97 100644 --- a/modules/core/src/hal_internal.cpp +++ b/modules/core/src/hal_internal.cpp @@ -47,7 +47,6 @@ #ifdef HAVE_LAPACK -#include #include "opencv_lapack.h" #include @@ -722,4 +721,4 @@ int lapack_gemm64fc(const double *src1, size_t src1_step, const double *src2, si #pragma clang diagnostic pop #endif -#endif //HAVE_LAPACK \ No newline at end of file +#endif //HAVE_LAPACK diff --git a/modules/core/src/lut.cpp b/modules/core/src/lut.cpp index b32edcad2a..80a8d20b01 100644 --- a/modules/core/src/lut.cpp +++ b/modules/core/src/lut.cpp @@ -1,6 +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" @@ -8,6 +9,11 @@ #include "convert.hpp" #include +namespace cv { +void LUT8u_dispatch( const uchar* src, const uchar* lut, uchar* dst, int len, int cn, int lutcn ); +void LUT16u_dispatch( const uchar* src, const ushort* lut, ushort* dst, int len, int cn, int lutcn ); +} // namespace cv + /****************************************************************************************\ * LUT Transform * \****************************************************************************************/ @@ -40,9 +46,9 @@ static LUTFunc getLUTFunc(const int srcDepth, const int dstDepth) { switch(dstDepth) { - case CV_8U: ret = (LUTFunc)LUT_; break; + case CV_8U: ret = (LUTFunc)LUT8u_dispatch; break; case CV_8S: ret = (LUTFunc)LUT_; break; - case CV_16U: ret = (LUTFunc)LUT_; break; + case CV_16U: ret = (LUTFunc)LUT16u_dispatch; break; case CV_16S: ret = (LUTFunc)LUT_; break; case CV_32S: ret = (LUTFunc)LUT_; break; case CV_32F: ret = (LUTFunc)LUT_; break; // float diff --git a/modules/core/src/lut.dispatch.cpp b/modules/core/src/lut.dispatch.cpp new file mode 100644 index 0000000000..a95a54daa0 --- /dev/null +++ b/modules/core/src/lut.dispatch.cpp @@ -0,0 +1,56 @@ +// 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 "lut.simd.hpp" +#include "lut.simd_declarations.hpp" // defines CV_CPU_DISPATCH_MODES_ALL=AVX512_ICL,...,BASELINE based on CMakeLists.txt content + +namespace cv { + +namespace { + +typedef void (*LUT8uFunc)( const uchar*, const uchar*, uchar*, int, int, int ); +typedef void (*LUT16uFunc)( const uchar*, const ushort*, ushort*, int, int, int ); + +static LUT8uFunc resolveLUT8uFunc() +{ +#if CV_TRY_AVX512_ICL + if (cv::checkHardwareSupport(CV_CPU_AVX512_ICL)) + return opt_AVX512_ICL::LUT8u_; +#endif + return cpu_baseline::LUT8u_; +} + +static LUT16uFunc resolveLUT16uFunc() +{ +#if CV_TRY_AVX512_ICL + if (cv::checkHardwareSupport(CV_CPU_AVX512_ICL)) + return opt_AVX512_ICL::LUT16u_; +#endif + return cpu_baseline::LUT16u_; +} + +} // namespace + +void LUT8u_dispatch( const uchar* src, const uchar* lut, uchar* dst, int len, int cn, int lutcn ); + +void LUT8u_dispatch( const uchar* src, const uchar* lut, uchar* dst, int len, int cn, int lutcn ) +{ + CV_INSTRUMENT_REGION(); + static LUT8uFunc fn = resolveLUT8uFunc(); + fn(src, lut, dst, len, cn, lutcn); +} + +void LUT16u_dispatch( const uchar* src, const ushort* lut, ushort* dst, int len, int cn, int lutcn ); + +void LUT16u_dispatch( const uchar* src, const ushort* lut, ushort* dst, int len, int cn, int lutcn ) +{ + CV_INSTRUMENT_REGION(); + static LUT16uFunc fn = resolveLUT16uFunc(); + fn(src, lut, dst, len, cn, lutcn); +} + +} // namespace cv diff --git a/modules/core/src/lut.simd.hpp b/modules/core/src/lut.simd.hpp new file mode 100644 index 0000000000..c8152a5ae5 --- /dev/null +++ b/modules/core/src/lut.simd.hpp @@ -0,0 +1,146 @@ +// 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" + +// VBMI vpermb is the only x86 SIMD path faster than scalar for byte LUT. +// x86 gather (AVX2/SSE) is slower than scalar due to high gather latency. +// Non-x86 (NEON, etc.) benefits from v_lut implementation. +#if CV_AVX_512VBMI +#define CV_LUT8U_SIMD 1 +#elif (defined(CV_CPU_COMPILE_SSE) || defined(CV_CPU_COMPILE_SSE2) || defined(CV_CPU_COMPILE_SSE3) || \ + defined(CV_CPU_COMPILE_SSSE3) || defined(CV_CPU_COMPILE_SSE4_1) || defined(CV_CPU_COMPILE_SSE4_2) || \ + defined(CV_CPU_COMPILE_AVX) || defined(CV_CPU_COMPILE_AVX2) || defined(CV_CPU_COMPILE_AVX_512F) || \ + defined(CV_CPU_COMPILE_AVX512_COMMON) || defined(CV_CPU_COMPILE_AVX512_SKX) || \ + defined(CV_CPU_COMPILE_AVX512_CNL) || defined(CV_CPU_COMPILE_AVX512_CLX)) +#define CV_LUT8U_SIMD 0 +#elif CV_SIMD || CV_SIMD_SCALABLE +#define CV_LUT8U_SIMD 1 +#else +#define CV_LUT8U_SIMD 0 +#endif + +namespace cv { +CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN + +void LUT8u_( const uchar* src, const uchar* lut, uchar* dst, int len, int cn, int lutcn ); +void LUT16u_( const uchar* src, const ushort* lut, ushort* dst, int len, int cn, int lutcn ); + +#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +void LUT8u_( const uchar* src, const uchar* lut, uchar* dst, int len, int cn, int lutcn ) +{ + const int total = len * cn; + + if( lutcn == 1 ) + { + int i = 0; +#if CV_LUT8U_SIMD + const int nlanes = VTraits::vlanes(); + for( ; i <= total - nlanes; i += nlanes ) + { + v_uint8 idx = vx_load(src + i); + v_uint8 result = v_lut(lut, idx); + v_store(dst + i, result); + } +#endif + for( ; i < total; i++ ) + dst[i] = lut[src[i]]; + } +#if CV_LUT8U_SIMD + else if( cn == 3 ) + { + // Deinterleave the 3-channel LUT into per-channel tables + uchar CV_DECL_ALIGNED(64) lut0[256], lut1[256], lut2[256]; + const int nlanes = VTraits::vlanes(); + { + unsigned j = 0; + for( ; j + (unsigned)nlanes <= 256; j += (unsigned)nlanes ) + { + v_uint8 a, b, c; + v_load_deinterleave(lut + j * 3, a, b, c); + v_store(lut0 + j, a); + v_store(lut1 + j, b); + v_store(lut2 + j, c); + } + for( ; j < 256; j++ ) + { + const unsigned idx = j * 3; + lut0[j] = lut[idx]; + lut1[j] = lut[idx + 1]; + lut2[j] = lut[idx + 2]; + } + } + int i = 0; + for( ; i <= total - nlanes*3; i += nlanes*3 ) + { + v_uint8 r, g, b; + v_load_deinterleave(src + i, r, g, b); + r = v_lut(lut0, r); + g = v_lut(lut1, g); + b = v_lut(lut2, b); + v_store_interleave(dst + i, r, g, b); + } + for( ; i < total; i += 3 ) + { + dst[i] = lut0[src[i]]; + dst[i+1] = lut1[src[i+1]]; + dst[i+2] = lut2[src[i+2]]; + } + } +#endif + else + { + for( int i = 0; i < total; i += cn ) + for( int k = 0; k < cn; k++ ) + dst[i+k] = lut[src[i+k]*cn + k]; + } +} + +void LUT16u_( const uchar* src, const ushort* lut, ushort* dst, int len, int cn, int lutcn ) +{ + const int total = len * cn; + + if( lutcn == 1 ) + { + int i = 0; +#if CV_LUT8U_SIMD + // Split ushort LUT into low-byte and high-byte tables, + // then use two byte v_lut (vpermb on VBMI) + interleave. + uchar CV_DECL_ALIGNED(64) lut_lo[256], lut_hi[256]; + for( int j = 0; j < 256; j++ ) + { + lut_lo[j] = (uchar)(lut[j]); + lut_hi[j] = (uchar)(lut[j] >> 8); + } + const int nlanes8 = VTraits::vlanes(); + const int nlanes16 = VTraits::vlanes(); + for( ; i <= total - nlanes8; i += nlanes8 ) + { + v_uint8 idx = vx_load(src + i); + v_uint8 lo = v_lut(lut_lo, idx); + v_uint8 hi = v_lut(lut_hi, idx); + // Zip low and high bytes: [l0,h0,l1,h1,...] → ushort values on little-endian + v_uint8 res0, res1; + v_zip(lo, hi, res0, res1); + v_store(dst + i, v_reinterpret_as_u16(res0)); + v_store(dst + i + nlanes16, v_reinterpret_as_u16(res1)); + } +#endif + for( ; i < total; i++ ) + dst[i] = lut[src[i]]; + } + else + { + for( int i = 0; i < total; i += cn ) + for( int k = 0; k < cn; k++ ) + dst[i+k] = lut[src[i+k]*cn + k]; + } +} + +#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +CV_CPU_OPTIMIZATION_NAMESPACE_END +} // namespace cv diff --git a/modules/core/src/mathfuncs_core.dispatch.cpp b/modules/core/src/mathfuncs_core.dispatch.cpp index 84e4e6a652..db27713c51 100644 --- a/modules/core/src/mathfuncs_core.dispatch.cpp +++ b/modules/core/src/mathfuncs_core.dispatch.cpp @@ -107,7 +107,6 @@ void invSqrt32f(const float* src, float* dst, int len) CV_INSTRUMENT_REGION(); CALL_HAL(invSqrt32f, cv_hal_invSqrt32f, src, dst, len); - CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsInvSqrt_32f_A21, src, dst, len) >= 0); CV_CPU_DISPATCH(invSqrt32f, (src, dst, len), CV_CPU_DISPATCH_MODES_ALL); @@ -119,7 +118,6 @@ void invSqrt64f(const double* src, double* dst, int len) CV_INSTRUMENT_REGION(); CALL_HAL(invSqrt64f, cv_hal_invSqrt64f, src, dst, len); - CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsInvSqrt_64f_A50, src, dst, len) >= 0); CV_CPU_DISPATCH(invSqrt64f, (src, dst, len), CV_CPU_DISPATCH_MODES_ALL); @@ -152,7 +150,6 @@ void exp32f(const float *src, float *dst, int n) CV_INSTRUMENT_REGION(); CALL_HAL(exp32f, cv_hal_exp32f, src, dst, n); - CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsExp_32f_A21, src, dst, n) >= 0); CV_CPU_DISPATCH(exp32f, (src, dst, n), CV_CPU_DISPATCH_MODES_ALL); @@ -163,7 +160,6 @@ void exp64f(const double *src, double *dst, int n) CV_INSTRUMENT_REGION(); CALL_HAL(exp64f, cv_hal_exp64f, src, dst, n); - CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsExp_64f_A50, src, dst, n) >= 0); CV_CPU_DISPATCH(exp64f, (src, dst, n), CV_CPU_DISPATCH_MODES_ALL); @@ -174,7 +170,6 @@ void log32f(const float *src, float *dst, int n) CV_INSTRUMENT_REGION(); CALL_HAL(log32f, cv_hal_log32f, src, dst, n); - CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsLn_32f_A21, src, dst, n) >= 0); CV_CPU_DISPATCH(log32f, (src, dst, n), CV_CPU_DISPATCH_MODES_ALL); @@ -185,7 +180,6 @@ void log64f(const double *src, double *dst, int n) CV_INSTRUMENT_REGION(); CALL_HAL(log64f, cv_hal_log64f, src, dst, n); - CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippsLn_64f_A50, src, dst, n) >= 0); CV_CPU_DISPATCH(log64f, (src, dst, n), CV_CPU_DISPATCH_MODES_ALL); diff --git a/modules/core/src/mathfuncs_core.simd.hpp b/modules/core/src/mathfuncs_core.simd.hpp index d9289ecb4e..8c4dbffe1d 100644 --- a/modules/core/src/mathfuncs_core.simd.hpp +++ b/modules/core/src/mathfuncs_core.simd.hpp @@ -595,7 +595,7 @@ void sqrt64f(const double* src, double* dst, int len) // Workaround for ICE in MSVS 2015 update 3 (issue #7795) // CV_AVX is not used here, because generated code is faster in non-AVX mode. // (tested with disabled IPP on i5-6300U) -#if (defined _MSC_VER && _MSC_VER >= 1900) || defined(__EMSCRIPTEN__) +#if (defined _MSC_VER && _MSC_VER >= 1900 && (defined(_M_IX86) || defined(_M_X64))) || defined(__EMSCRIPTEN__) void exp32f(const float *src, float *dst, int n) { CV_INSTRUMENT_REGION(); diff --git a/modules/core/src/matrix_expressions.cpp b/modules/core/src/matrix_expressions.cpp index ab7005de78..b9d6c3b01d 100644 --- a/modules/core/src/matrix_expressions.cpp +++ b/modules/core/src/matrix_expressions.cpp @@ -1735,8 +1735,16 @@ MatExpr Mat::mul(InputArray m, double scale) const { CV_INSTRUMENT_REGION(); + Mat b = m.getMat(); + // Unless the argument is a refcounted Mat/UMat, the header returned by getMat() may be + // a non-owning view of caller memory (e.g. a scalar bound to _InputArray(const double&), + // a Matx or a Vec) that the returned MatExpr can outlive, so snapshot it. + // See https://github.com/opencv/opencv/issues/23577 + if( !m.isMat() && !m.isUMat() ) + b = b.clone(); + MatExpr e; - MatOp_Bin::makeExpr(e, '*', *this, m.getMat(), scale); + MatOp_Bin::makeExpr(e, '*', *this, b, scale); return e; } diff --git a/modules/core/src/matrix_operations.cpp b/modules/core/src/matrix_operations.cpp index 9a5ae5fa01..d59b8b54c4 100644 --- a/modules/core/src/matrix_operations.cpp +++ b/modules/core/src/matrix_operations.cpp @@ -987,7 +987,6 @@ namespace cv template static void sort_( const Mat& src, Mat& dst, int flags ) { - AutoBuffer buf; int n, len; bool sortRows = (flags & 1) == SORT_EVERY_ROW; bool inplace = src.data == dst.data; @@ -996,44 +995,48 @@ template static void sort_( const Mat& src, Mat& dst, int flags ) if( sortRows ) n = src.rows, len = src.cols; else - { n = src.cols, len = src.rows; - buf.allocate(len); - } - T* bptr = buf.data(); - - for( int i = 0; i < n; i++ ) + parallel_for_(Range(0, n), [&](const Range& range) { - T* ptr = bptr; - if( sortRows ) - { - T* dptr = dst.ptr(i); - if( !inplace ) - { - const T* sptr = src.ptr(i); - memcpy(dptr, sptr, sizeof(T) * len); - } - ptr = dptr; - } - else - { - for( int j = 0; j < len; j++ ) - ptr[j] = src.ptr(j)[i]; - } - - std::sort( ptr, ptr + len ); - if( sortDescending ) - { - for( int j = 0; j < len/2; j++ ) - std::swap(ptr[j], ptr[len-1-j]); - } - + AutoBuffer buf; if( !sortRows ) - for( int j = 0; j < len; j++ ) - dst.ptr(j)[i] = ptr[j]; - } + buf.allocate(len); + T* bptr = buf.data(); + + for( int i = range.start; i < range.end; i++ ) + { + T* ptr = bptr; + if( sortRows ) + { + T* dptr = dst.ptr(i); + if( !inplace ) + { + const T* sptr = src.ptr(i); + memcpy(dptr, sptr, sizeof(T) * len); + } + ptr = dptr; + } + else + { + for( int j = 0; j < len; j++ ) + ptr[j] = src.ptr(j)[i]; + } + + std::sort( ptr, ptr + len ); + if( sortDescending ) + { + for( int j = 0; j < len/2; j++ ) + std::swap(ptr[j], ptr[len-1-j]); + } + + if( !sortRows ) + for( int j = 0; j < len; j++ ) + dst.ptr(j)[i] = ptr[j]; + } + }); } + #ifdef HAVE_IPP typedef IppStatus (CV_STDCALL *IppSortFunc)(void *pSrcDst, int len, Ipp8u *pBuffer); diff --git a/modules/core/src/matrix_transform.cpp b/modules/core/src/matrix_transform.cpp index 90994fe91d..21dffcad7b 100644 --- a/modules/core/src/matrix_transform.cpp +++ b/modules/core/src/matrix_transform.cpp @@ -526,6 +526,33 @@ void transposeND(InputArray src_, const std::vector& order, OutputArray dst CV_CheckEQ(inp.channels(), 1, "Input array should be single-channel"); CV_CheckEQ(order.size(), static_cast(inp.dims), "Number of dimensions shouldn't change"); + bool isIdentityOrder = true; + for (size_t i = 0; i < order.size(); ++i) + { + if (order[i] != static_cast(i)) + { + isIdentityOrder = false; + break; + } + } + if (isIdentityOrder) + { + dst_.create(inp.dims, inp.size.p, inp.type()); + Mat out = dst_.getMat(); + CV_Assert(out.isContinuous()); + + if (inp.data != out.data) + inp.copyTo(out); + return; + } + + const bool is2DSwap = inp.dims == 2 && order.size() == 2 && order[0] == 1 && order[1] == 0; + if (is2DSwap) + { + transpose(inp, dst_); + return; + } + auto order_ = order; std::sort(order_.begin(), order_.end()); for (size_t i = 0; i < order_.size(); ++i) @@ -533,7 +560,7 @@ void transposeND(InputArray src_, const std::vector& order, OutputArray dst CV_CheckEQ(static_cast(order_[i]), i, "New order should be a valid permutation of the old one"); } - std::vector newShape(order.size()); + AutoBuffer newShape(order.size()); for (size_t i = 0; i < order.size(); ++i) { newShape[i] = inp.size[order[i]]; @@ -557,7 +584,7 @@ void transposeND(InputArray src_, const std::vector& order, OutputArray dst size_t continuous_size = continuous_idx == 0 ? out.total() : out.step1(continuous_idx - 1); size_t outer_size = out.total() / continuous_size; - std::vector steps(order.size()); + AutoBuffer steps(order.size()); for (int i = 0; i < static_cast(steps.size()); ++i) { steps[i] = inp.step1(order[i]); @@ -1206,7 +1233,7 @@ void broadcast(InputArray _src, InputArray _shape, OutputArray _dst) { Mat dst = _dst.getMat(); if (dst.total() == 0) return; - std::vector is_same_shape(dims_shape, 0); + cv::AutoBuffer is_same_shape(dims_shape, 0); for (int i = 0; i < static_cast(shape_src.size()); ++i) { if (shape_src[i] == ptr_shape[i]) { is_same_shape[i] = 1; @@ -1313,7 +1340,7 @@ void broadcast(InputArray _src, InputArray _shape, OutputArray _dst) { std::memcpy(p_dst + dst_offset, p_src + src_offset, dst.elemSize()); } // broadcast copy (dst inplace) - std::vector cumulative_shape(dims_shape, 1); + AutoBuffer cumulative_shape(dims_shape, 1); int total = static_cast(dst.total()); for (int i = dims_shape - 1; i >= 0; --i) { cumulative_shape[i] = static_cast(total / ptr_shape[i]); diff --git a/modules/core/src/mean.simd.hpp b/modules/core/src/mean.simd.hpp index db7b411f39..f030eed976 100644 --- a/modules/core/src/mean.simd.hpp +++ b/modules/core/src/mean.simd.hpp @@ -2,6 +2,7 @@ // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html +// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved. #include "precomp.hpp" #include "stat.hpp" @@ -31,7 +32,35 @@ struct SumSqr_SIMD { int operator () (const uchar * src0, const uchar * mask, int * sum, int * sqsum, int len, int cn) const { - if (mask || (cn != 1 && cn != 2 && cn != 4)) + if (mask) + return 0; + // cn==3: deinterleave so each lane belongs to one channel, then reduce per + // channel. sum/sqsum stay exact in s32 within the dispatcher's 1<<15 block. + if (cn == 3) + { + const int vl8 = VTraits::vlanes(); + v_int32 vs0 = vx_setzero_s32(), vs1 = vx_setzero_s32(), vs2 = vx_setzero_s32(); + v_int32 vq0 = vx_setzero_s32(), vq1 = vx_setzero_s32(), vq2 = vx_setzero_s32(); + auto acc = [](const v_uint8& ch, v_int32& vs, v_int32& vq) + { + v_uint16 lo, hi; v_expand(ch, lo, hi); + v_uint32 s0, s1, s2, s3; v_expand(lo, s0, s1); v_expand(hi, s2, s3); + vs = v_add(vs, v_reinterpret_as_s32(v_add(v_add(s0, s1), v_add(s2, s3)))); + v_int16 d0 = v_reinterpret_as_s16(lo), d1 = v_reinterpret_as_s16(hi); + vq = v_add(vq, v_add(v_dotprod(d0, d0), v_dotprod(d1, d1))); + }; + int e = 0; + for (; e <= len - vl8; e += vl8) + { + v_uint8 a, b, c; v_load_deinterleave(src0 + e * 3, a, b, c); + acc(a, vs0, vq0); acc(b, vs1, vq1); acc(c, vs2, vq2); + } + sum[0] += v_reduce_sum(vs0); sum[1] += v_reduce_sum(vs1); sum[2] += v_reduce_sum(vs2); + sqsum[0] += v_reduce_sum(vq0); sqsum[1] += v_reduce_sum(vq1); sqsum[2] += v_reduce_sum(vq2); + v_cleanup(); + return e; + } + if (cn != 1 && cn != 2 && cn != 4) return 0; len *= cn; @@ -98,7 +127,32 @@ struct SumSqr_SIMD { int operator () (const schar * src0, const uchar * mask, int * sum, int * sqsum, int len, int cn) const { - if (mask || (cn != 1 && cn != 2 && cn != 4)) + if (mask) + return 0; + if (cn == 3) + { + const int vl8 = VTraits::vlanes(); + v_int32 vs0 = vx_setzero_s32(), vs1 = vx_setzero_s32(), vs2 = vx_setzero_s32(); + v_int32 vq0 = vx_setzero_s32(), vq1 = vx_setzero_s32(), vq2 = vx_setzero_s32(); + auto acc = [](const v_int8& ch, v_int32& vs, v_int32& vq) + { + v_int16 lo, hi; v_expand(ch, lo, hi); + v_int32 s0, s1, s2, s3; v_expand(lo, s0, s1); v_expand(hi, s2, s3); + vs = v_add(vs, v_add(v_add(s0, s1), v_add(s2, s3))); + vq = v_add(vq, v_add(v_dotprod(lo, lo), v_dotprod(hi, hi))); + }; + int e = 0; + for (; e <= len - vl8; e += vl8) + { + v_int8 a, b, c; v_load_deinterleave(src0 + e * 3, a, b, c); + acc(a, vs0, vq0); acc(b, vs1, vq1); acc(c, vs2, vq2); + } + sum[0] += v_reduce_sum(vs0); sum[1] += v_reduce_sum(vs1); sum[2] += v_reduce_sum(vs2); + sqsum[0] += v_reduce_sum(vq0); sqsum[1] += v_reduce_sum(vq1); sqsum[2] += v_reduce_sum(vq2); + v_cleanup(); + return e; + } + if (cn != 1 && cn != 2 && cn != 4) return 0; len *= cn; @@ -160,6 +214,212 @@ struct SumSqr_SIMD } }; +#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F) + +// Expand a 16-bit vector into two s32 halves. The unsigned overload reinterprets +// after a widening expand (values 0..65535 stay non-negative in s32, so the +// subsequent v_cvt_f64 is exact); the signed overload expands directly. This is +// the only real difference between the ushort and short reductions below. +static inline void sumSqrExpandS32(const v_uint16& v, v_int32& lo, v_int32& hi) +{ + v_uint32 a, b; v_expand(v, a, b); + lo = v_reinterpret_as_s32(a); hi = v_reinterpret_as_s32(b); +} +static inline void sumSqrExpandS32(const v_int16& v, v_int32& lo, v_int32& hi) +{ + v_expand(v, lo, hi); +} + +// Shared 16-bit -> {s32 sum, f64 sqsum} reduction for ushort/short. The register +// type is deduced from the pointer, so the body is identical for both depths. +template +static inline int sumSqr16ToF64(const T* src0, int* sum, double* sqsum, int len, int cn) +{ + typedef decltype(vx_load(src0)) VT; // v_uint16 or v_int16 + + if (cn == 3) + { + const int vl = VTraits::vlanes(); + v_int32 vs0 = vx_setzero_s32(), vs1 = vx_setzero_s32(), vs2 = vx_setzero_s32(); + v_float64 vq0 = vx_setzero_f64(), vq1 = vx_setzero_f64(), vq2 = vx_setzero_f64(); + auto acc = [](const VT& ch, v_int32& vs, v_float64& vq) + { + v_int32 lo, hi; sumSqrExpandS32(ch, lo, hi); + vs = v_add(vs, v_add(lo, hi)); + v_float64 f0 = v_cvt_f64(lo), f1 = v_cvt_f64_high(lo); + v_float64 f2 = v_cvt_f64(hi), f3 = v_cvt_f64_high(hi); + vq = v_fma(f0, f0, v_fma(f1, f1, v_fma(f2, f2, v_fma(f3, f3, vq)))); + }; + int e = 0; + for (; e <= len - vl; e += vl) + { + VT a, b, c; v_load_deinterleave(src0 + e * 3, a, b, c); + acc(a, vs0, vq0); acc(b, vs1, vq1); acc(c, vs2, vq2); + } + sum[0] += v_reduce_sum(vs0); sum[1] += v_reduce_sum(vs1); sum[2] += v_reduce_sum(vs2); + sqsum[0] += v_reduce_sum(vq0); sqsum[1] += v_reduce_sum(vq1); sqsum[2] += v_reduce_sum(vq2); + v_cleanup(); + return e; + } + if (cn != 1 && cn != 2 && cn != 4) + return 0; + len *= cn; + + const int vl16 = VTraits::vlanes(); + const int vl32 = VTraits::vlanes(); + const int vl64 = VTraits::vlanes(); + + // The lane->channel scatter below (i % cn) and the returned pixel count + // (x / cn) require the lane counts to be a multiple of cn. This always holds + // for fixed-width SIMD (vlanes is a power of two >= cn for cn in {1,2,4}), + // but scalable backends (e.g. RVV) may report a non-multiple; fall back to + // scalar there. vl16 == 2*vl32, so checking vl32 covers both scatter loops. + if (vl32 % cn != 0) + return 0; + + v_int32 v_sum = vx_setzero_s32(); + v_float64 v_sq0 = vx_setzero_f64(), v_sq1 = vx_setzero_f64(); + v_float64 v_sq2 = vx_setzero_f64(), v_sq3 = vx_setzero_f64(); + + int x = 0; + for (; x <= len - vl16; x += vl16) + { + VT v_src = vx_load(src0 + x); + v_int32 lo, hi; sumSqrExpandS32(v_src, lo, hi); + v_sum = v_add(v_sum, v_add(lo, hi)); + + v_float64 f0 = v_cvt_f64(lo); + v_float64 f1 = v_cvt_f64_high(lo); + v_float64 f2 = v_cvt_f64(hi); + v_float64 f3 = v_cvt_f64_high(hi); + v_sq0 = v_fma(f0, f0, v_sq0); + v_sq1 = v_fma(f1, f1, v_sq1); + v_sq2 = v_fma(f2, f2, v_sq2); + v_sq3 = v_fma(f3, f3, v_sq3); + } + + int CV_DECL_ALIGNED(CV_SIMD_WIDTH) sbuf[VTraits::max_nlanes]; + double CV_DECL_ALIGNED(CV_SIMD_WIDTH) qbuf[VTraits::max_nlanes * 4]; + v_store_aligned(sbuf, v_sum); + v_store_aligned(qbuf + vl64 * 0, v_sq0); + v_store_aligned(qbuf + vl64 * 1, v_sq1); + v_store_aligned(qbuf + vl64 * 2, v_sq2); + v_store_aligned(qbuf + vl64 * 3, v_sq3); + + // sbuf lane j (j channel j%cn (vl32%cn==0). + // qbuf index i (i channel i%cn (vl16%cn==0). + for (int i = 0; i < vl32; ++i) + sum[i % cn] += sbuf[i]; + for (int i = 0; i < vl16; ++i) + sqsum[i % cn] += qbuf[i]; + + v_cleanup(); + return x / cn; +} + +// Shared 32-bit -> f64 reduction for int/float. Both sum and sqsum accumulate in +// f64 to match the scalar reference's double accumulation; values are widened to +// f64 before squaring so the square is computed in double (same as (double)v*v). +// The register type is deduced from the pointer, so int and float share one body. +template +static inline int sumSqr32ToF64(const T* src0, double* sum, double* sqsum, int len, int cn) +{ + typedef decltype(vx_load(src0)) VT; // v_int32 or v_float32 + + if (cn == 3) + { + const int vl = VTraits::vlanes(); + v_float64 vs0 = vx_setzero_f64(), vs1 = vx_setzero_f64(), vs2 = vx_setzero_f64(); + v_float64 vq0 = vx_setzero_f64(), vq1 = vx_setzero_f64(), vq2 = vx_setzero_f64(); + auto acc = [](const VT& ch, v_float64& vs, v_float64& vq) + { + v_float64 f0 = v_cvt_f64(ch), f1 = v_cvt_f64_high(ch); + vs = v_add(vs, v_add(f0, f1)); + vq = v_fma(f0, f0, v_fma(f1, f1, vq)); + }; + int e = 0; + for (; e <= len - vl; e += vl) + { + VT a, b, c; v_load_deinterleave(src0 + e * 3, a, b, c); + acc(a, vs0, vq0); acc(b, vs1, vq1); acc(c, vs2, vq2); + } + sum[0] += v_reduce_sum(vs0); sum[1] += v_reduce_sum(vs1); sum[2] += v_reduce_sum(vs2); + sqsum[0] += v_reduce_sum(vq0); sqsum[1] += v_reduce_sum(vq1); sqsum[2] += v_reduce_sum(vq2); + v_cleanup(); + return e; + } + if (cn != 1 && cn != 2 && cn != 4) + return 0; + len *= cn; + + const int vl32 = VTraits::vlanes(); + const int vl64 = VTraits::vlanes(); + + // See note in sumSqr16ToF64: guard scalable backends whose lane count may + // not divide cn (the i % cn scatter / x / cn rely on it). + if (vl32 % cn != 0) + return 0; + + v_float64 vs0 = vx_setzero_f64(), vs1 = vx_setzero_f64(); + v_float64 vq0 = vx_setzero_f64(), vq1 = vx_setzero_f64(); + + int x = 0; + for (; x <= len - vl32; x += vl32) + { + VT v_src = vx_load(src0 + x); + v_float64 f0 = v_cvt_f64(v_src); + v_float64 f1 = v_cvt_f64_high(v_src); + vs0 = v_add(vs0, f0); + vs1 = v_add(vs1, f1); + vq0 = v_fma(f0, f0, vq0); + vq1 = v_fma(f1, f1, vq1); + } + + double CV_DECL_ALIGNED(CV_SIMD_WIDTH) sbuf[VTraits::max_nlanes * 2]; + double CV_DECL_ALIGNED(CV_SIMD_WIDTH) qbuf[VTraits::max_nlanes * 2]; + v_store_aligned(sbuf, vs0); v_store_aligned(sbuf + vl64, vs1); + v_store_aligned(qbuf, vq0); v_store_aligned(qbuf + vl64, vq1); + + for (int i = 0; i < vl32; ++i) + { + sum[i % cn] += sbuf[i]; + sqsum[i % cn] += qbuf[i]; + } + + v_cleanup(); + return x / cn; +} + +template <> +struct SumSqr_SIMD +{ + int operator () (const ushort* src0, const uchar* mask, int* sum, double* sqsum, int len, int cn) const + { return mask ? 0 : sumSqr16ToF64(src0, sum, sqsum, len, cn); } +}; + +template <> +struct SumSqr_SIMD +{ + int operator () (const short* src0, const uchar* mask, int* sum, double* sqsum, int len, int cn) const + { return mask ? 0 : sumSqr16ToF64(src0, sum, sqsum, len, cn); } +}; + +template <> +struct SumSqr_SIMD +{ + int operator () (const int* src0, const uchar* mask, double* sum, double* sqsum, int len, int cn) const + { return mask ? 0 : sumSqr32ToF64(src0, sum, sqsum, len, cn); } +}; + +template <> +struct SumSqr_SIMD +{ + int operator () (const float* src0, const uchar* mask, double* sum, double* sqsum, int len, int cn) const + { return mask ? 0 : sumSqr32ToF64(src0, sum, sqsum, len, cn); } +}; + +#endif // CV_SIMD_64F + #endif template @@ -253,6 +513,21 @@ static int sumsqr_(const T* src0, const uchar* mask, ST* sum, SQT* sqsum, int le sum[0] = s0; sqsum[0] = sq0; } + else if( cn == 2 ) + { + ST s0 = sum[0], s1 = sum[1]; + SQT sq0 = sqsum[0], sq1 = sqsum[1]; + for( i = 0; i < len; i++, src += 2 ) + if( mask[i] ) + { + T v0 = src[0], v1 = src[1]; + s0 += v0; sq0 += (SQT)v0*v0; + s1 += v1; sq1 += (SQT)v1*v1; + nzm++; + } + sum[0] = s0; sum[1] = s1; + sqsum[0] = sq0; sqsum[1] = sq1; + } else if( cn == 3 ) { ST s0 = sum[0], s1 = sum[1], s2 = sum[2]; @@ -269,6 +544,23 @@ static int sumsqr_(const T* src0, const uchar* mask, ST* sum, SQT* sqsum, int le sum[0] = s0; sum[1] = s1; sum[2] = s2; sqsum[0] = sq0; sqsum[1] = sq1; sqsum[2] = sq2; } + else if( cn == 4 ) + { + ST s0 = sum[0], s1 = sum[1], s2 = sum[2], s3 = sum[3]; + SQT sq0 = sqsum[0], sq1 = sqsum[1], sq2 = sqsum[2], sq3 = sqsum[3]; + for( i = 0; i < len; i++, src += 4 ) + if( mask[i] ) + { + T v0 = src[0], v1 = src[1], v2 = src[2], v3 = src[3]; + s0 += v0; sq0 += (SQT)v0*v0; + s1 += v1; sq1 += (SQT)v1*v1; + s2 += v2; sq2 += (SQT)v2*v2; + s3 += v3; sq3 += (SQT)v3*v3; + nzm++; + } + sum[0] = s0; sum[1] = s1; sum[2] = s2; sum[3] = s3; + sqsum[0] = sq0; sqsum[1] = sq1; sqsum[2] = sq2; sqsum[3] = sq3; + } else { for( i = 0; i < len; i++, src += cn ) diff --git a/modules/core/src/norm.dispatch.cpp b/modules/core/src/norm.dispatch.cpp index 930c3b758a..ca9aac1f26 100644 --- a/modules/core/src/norm.dispatch.cpp +++ b/modules/core/src/norm.dispatch.cpp @@ -1,6 +1,8 @@ // 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" @@ -155,20 +157,27 @@ float normL2Sqr_(const float* a, const float* b, int n) { int j = 0; float d = 0.f; #if (CV_SIMD || CV_SIMD_SCALABLE) + const int vl = VTraits::vlanes(); v_float32 v_d0 = vx_setzero_f32(), v_d1 = vx_setzero_f32(); v_float32 v_d2 = vx_setzero_f32(), v_d3 = vx_setzero_f32(); - for (; j <= n - 4 * VTraits::vlanes(); j += 4 * VTraits::vlanes()) + for (; j <= n - 4 * vl; j += 4 * vl) { v_float32 t0 = v_sub(vx_load(a + j), vx_load(b + j)); - v_float32 t1 = v_sub(vx_load(a + j + VTraits::vlanes()), vx_load(b + j + VTraits::vlanes())); + v_float32 t1 = v_sub(vx_load(a + j + vl), vx_load(b + j + vl)); v_d0 = v_muladd(t0, t0, v_d0); - v_float32 t2 = v_sub(vx_load(a + j + 2 * VTraits::vlanes()), vx_load(b + j + 2 * VTraits::vlanes())); + v_float32 t2 = v_sub(vx_load(a + j + 2 * vl), vx_load(b + j + 2 * vl)); v_d1 = v_muladd(t1, t1, v_d1); - v_float32 t3 = v_sub(vx_load(a + j + 3 * VTraits::vlanes()), vx_load(b + j + 3 * VTraits::vlanes())); + v_float32 t3 = v_sub(vx_load(a + j + 3 * vl), vx_load(b + j + 3 * vl)); v_d2 = v_muladd(t2, t2, v_d2); v_d3 = v_muladd(t3, t3, v_d3); } - d = v_reduce_sum(v_add(v_add(v_add(v_d0, v_d1), v_d2), v_d3)); + v_d0 = v_add(v_add(v_d0, v_d1), v_add(v_d2, v_d3)); + for (; j <= n - vl; j += vl) + { + v_float32 t0 = v_sub(vx_load(a + j), vx_load(b + j)); + v_d0 = v_muladd(t0, t0, v_d0); + } + d = v_reduce_sum(v_d0); #endif for( ; j < n; j++ ) { @@ -183,16 +192,20 @@ float normL1_(const float* a, const float* b, int n) { int j = 0; float d = 0.f; #if (CV_SIMD || CV_SIMD_SCALABLE) + const int vl = VTraits::vlanes(); v_float32 v_d0 = vx_setzero_f32(), v_d1 = vx_setzero_f32(); v_float32 v_d2 = vx_setzero_f32(), v_d3 = vx_setzero_f32(); - for (; j <= n - 4 * VTraits::vlanes(); j += 4 * VTraits::vlanes()) + for (; j <= n - 4 * vl; j += 4 * vl) { v_d0 = v_add(v_d0, v_absdiff(vx_load(a + j), vx_load(b + j))); - v_d1 = v_add(v_d1, v_absdiff(vx_load(a + j + VTraits::vlanes()), vx_load(b + j + VTraits::vlanes()))); - v_d2 = v_add(v_d2, v_absdiff(vx_load(a + j + 2 * VTraits::vlanes()), vx_load(b + j + 2 * VTraits::vlanes()))); - v_d3 = v_add(v_d3, v_absdiff(vx_load(a + j + 3 * VTraits::vlanes()), vx_load(b + j + 3 * VTraits::vlanes()))); + v_d1 = v_add(v_d1, v_absdiff(vx_load(a + j + vl), vx_load(b + j + vl))); + v_d2 = v_add(v_d2, v_absdiff(vx_load(a + j + 2 * vl), vx_load(b + j + 2 * vl))); + v_d3 = v_add(v_d3, v_absdiff(vx_load(a + j + 3 * vl), vx_load(b + j + 3 * vl))); } - d = v_reduce_sum(v_add(v_add(v_add(v_d0, v_d1), v_d2), v_d3)); + v_d0 = v_add(v_add(v_d0, v_d1), v_add(v_d2, v_d3)); + for (; j <= n - vl; j += vl) + v_d0 = v_add(v_d0, v_absdiff(vx_load(a + j), vx_load(b + j))); + d = v_reduce_sum(v_d0); #endif for( ; j < n; j++ ) d += std::abs(a[j] - b[j]); @@ -203,11 +216,14 @@ int normL1_(const uchar* a, const uchar* b, int n) { int j = 0, d = 0; #if (CV_SIMD || CV_SIMD_SCALABLE) - for (; j <= n - 4 * VTraits::vlanes(); j += 4 * VTraits::vlanes()) + const int vl = VTraits::vlanes(); + for (; j <= n - 4 * vl; j += 4 * vl) d += v_reduce_sad(vx_load(a + j), vx_load(b + j)) + - v_reduce_sad(vx_load(a + j + VTraits::vlanes()), vx_load(b + j + VTraits::vlanes())) + - v_reduce_sad(vx_load(a + j + 2 * VTraits::vlanes()), vx_load(b + j + 2 * VTraits::vlanes())) + - v_reduce_sad(vx_load(a + j + 3 * VTraits::vlanes()), vx_load(b + j + 3 * VTraits::vlanes())); + v_reduce_sad(vx_load(a + j + vl), vx_load(b + j + vl)) + + v_reduce_sad(vx_load(a + j + 2 * vl), vx_load(b + j + 2 * vl)) + + v_reduce_sad(vx_load(a + j + 3 * vl), vx_load(b + j + 3 * vl)); + for (; j <= n - vl; j += vl) + d += v_reduce_sad(vx_load(a + j), vx_load(b + j)); #endif for( ; j < n; j++ ) d += std::abs(a[j] - b[j]); diff --git a/modules/core/src/opengl.cpp b/modules/core/src/opengl.cpp index 83be34f147..b7a85a61a4 100644 --- a/modules/core/src/opengl.cpp +++ b/modules/core/src/opengl.cpp @@ -1682,27 +1682,12 @@ Context& initializeContextFromGL() if(extensionSize > 0) { - char* extensions = nullptr; - - try { - extensions = new char[extensionSize]; - - status = clGetDeviceInfo(devices[j], CL_DEVICE_EXTENSIONS, extensionSize, extensions, &extensionSize); - if (status != CL_SUCCESS) - continue; - } catch(...) { - CV_Error(cv::Error::OpenCLInitError, "OpenCL: Exception thrown during device extensions gathering"); - } - - std::string devString; - - if(extensions != nullptr) { - devString = extensions; - delete[] extensions; - } - else { - CV_Error(cv::Error::OpenCLInitError, "OpenCL: Unexpected error during device extensions gathering"); - } + std::string devString(extensionSize, '\0'); + status = clGetDeviceInfo(devices[j], CL_DEVICE_EXTENSIONS, devString.size(), &devString[0], &extensionSize); + if (status != CL_SUCCESS) + continue; + if (extensionSize > 0 && devString.size() >= extensionSize) + devString.resize(extensionSize - 1); size_t oldPos = 0; size_t spacePos = devString.find(' ', oldPos); // extensions string is space delimited diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index abcbc15f0c..8256ebd7b5 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -268,7 +268,7 @@ int decodeFormat( const char* dt, int* fmt_pairs, int max_len ) int calcElemSize( const char* dt, int initial_size ) { int size = 0; - int fmt_pairs[CV_FS_MAX_FMT_PAIRS], i, fmt_pair_count; + int fmt_pairs[CV_FS_MAX_FMT_PAIRS*2], i, fmt_pair_count; int comp_size; fmt_pair_count = decodeFormat( dt, fmt_pairs, CV_FS_MAX_FMT_PAIRS ); @@ -323,7 +323,7 @@ int calcStructSize( const char* dt, int initial_size ) int decodeSimpleFormat( const char* dt ) { int elem_type = -1; - int fmt_pairs[CV_FS_MAX_FMT_PAIRS], fmt_pair_count; + int fmt_pairs[CV_FS_MAX_FMT_PAIRS*2], fmt_pair_count; fmt_pair_count = decodeFormat( dt, fmt_pairs, CV_FS_MAX_FMT_PAIRS ); if( fmt_pair_count != 1 || fmt_pairs[0] >= CV_CN_MAX) diff --git a/modules/core/src/persistence_base64_encoding.cpp b/modules/core/src/persistence_base64_encoding.cpp index 149117d436..c2d81b94f6 100644 --- a/modules/core/src/persistence_base64_encoding.cpp +++ b/modules/core/src/persistence_base64_encoding.cpp @@ -65,15 +65,15 @@ public: /* * a convertor must provide : * - `operator >> (uchar * & dst)` for writing current binary data to `dst` and moving to next data. - * - `operator bool` for checking if current loaction is valid and not the end. + * - `operator bool` for checking if current location is valid and not the end. */ template inline Base64ContextEmitter & write(_to_binary_convertor_t & convertor) { - static const size_t BUFFER_MAX_LEN = 1024U; + constexpr size_t BUFFER_MAX_LEN = 1024U; - std::vector buffer(BUFFER_MAX_LEN); - uchar * beg = buffer.data(); + uchar buffer[BUFFER_MAX_LEN]; + uchar * beg = buffer; uchar * end = beg; while (convertor) { diff --git a/modules/core/src/persistence_types.cpp b/modules/core/src/persistence_types.cpp index 8aa929cc1d..a1614c2c8b 100644 --- a/modules/core/src/persistence_types.cpp +++ b/modules/core/src/persistence_types.cpp @@ -75,7 +75,7 @@ void write( FileStorage& fs, const String& name, const SparseMat& m ) fs << "data" << "[:"; size_t i = 0, n = m.nzcount(); - std::vector elems(n); + AutoBuffer elems(n); SparseMatConstIterator it = m.begin(), it_end = m.end(); for( ; it != it_end; ++it ) @@ -142,6 +142,7 @@ void read(const FileNode& node, Mat& m, const Mat& default_mat) CV_Assert( !sizes_node.empty() ); dims = (int)sizes_node.size(); + CV_Assert( dims >= 0 && dims <= CV_MAX_DIM ); sizes_node.readRaw("i", sizes, dims*sizeof(sizes[0])); m.create(dims, sizes, elem_type); @@ -180,6 +181,7 @@ void read( const FileNode& node, SparseMat& m, const SparseMat& default_mat ) CV_Assert( !sizes_node.empty() ); int dims = (int)sizes_node.size(); + CV_Assert( dims > 0 && dims <= CV_MAX_DIM ); sizes_node.readRaw("i", sizes, dims*sizeof(sizes[0])); m.create(dims, sizes, elem_type); diff --git a/modules/core/src/stat.simd.hpp b/modules/core/src/stat.simd.hpp index e363313e5b..0322c6e4e2 100644 --- a/modules/core/src/stat.simd.hpp +++ b/modules/core/src/stat.simd.hpp @@ -1,6 +1,8 @@ // 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 "opencv2/core/hal/intrin.hpp" @@ -8,12 +10,18 @@ namespace cv { namespace hal { extern const uchar popCountTable[256]; +typedef int (*NormHammingFunc)(const uchar*, int); +typedef int (*NormHammingDiffFunc)(const uchar*, const uchar*, int); + CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN // forward declarations int normHamming(const uchar* a, int n); int normHamming(const uchar* a, const uchar* b, int n); +NormHammingFunc getNormHammingFunc(); +NormHammingDiffFunc getNormHammingDiffFunc(); + #ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY #if CV_AVX2 @@ -125,6 +133,17 @@ int normHamming(const uchar* a, const uchar* b, int n) return result; } +NormHammingFunc getNormHammingFunc() +{ + NormHammingFunc f = &normHamming; // disambiguate the (a,n) overload + return f; +} +NormHammingDiffFunc getNormHammingDiffFunc() +{ + NormHammingDiffFunc f = &normHamming; // disambiguate the (a,b,n) overload + return f; +} + #endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY CV_CPU_OPTIMIZATION_NAMESPACE_END diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index a52946b46a..c2adb5eeee 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -1176,12 +1176,19 @@ String tempfile( const char* suffix ) #else // Use GUID-based naming to avoid race condition with GetTempFileNameA // See issue #19648 - char temp_dir2[MAX_PATH] = { 0 }; + wchar_t temp_dir2_w[MAX_PATH] = { 0 }; if (temp_dir.empty()) { - ::GetTempPathA(sizeof(temp_dir2), temp_dir2); - temp_dir = std::string(temp_dir2); + ::GetTempPathW(sizeof(temp_dir2_w)/sizeof(wchar_t), temp_dir2_w); + // Convert from UTF-16 to UTF-8 to support Unicode paths + int len = WideCharToMultiByte(CP_UTF8, 0, temp_dir2_w, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, temp_dir2_w, -1, utf8_buf.data(), len, NULL, NULL); + temp_dir = std::string(utf8_buf.data()); + } } GUID g; @@ -2481,7 +2488,12 @@ public: IPPInitSingleton() { useIPP = true; + +#if defined(OPENCV_ALGO_HINT_DEFAULT) + useIPP_NE = OPENCV_ALGO_HINT_DEFAULT == cv::ALGO_HINT_APPROX; +#else useIPP_NE = false; +#endif ippStatus = 0; funcname = NULL; filename = NULL; diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp index adc5d85c6c..1fa58cf03a 100644 --- a/modules/core/src/utils/filesystem.cpp +++ b/modules/core/src/utils/filesystem.cpp @@ -438,11 +438,18 @@ cv::String getCacheDirectory(const char* sub_directory_name, const char* configu { cv::String default_cache_path; #ifdef _WIN32 - char tmp_path_buf[MAX_PATH+1] = {0}; - DWORD res = GetTempPath(MAX_PATH, tmp_path_buf); + wchar_t tmp_path_buf_w[MAX_PATH+1] = {0}; + DWORD res = GetTempPathW(MAX_PATH, tmp_path_buf_w); if (res > 0 && res <= MAX_PATH) { - default_cache_path = tmp_path_buf; + // Convert from UTF-16 to UTF-8 to support Unicode paths + int len = WideCharToMultiByte(CP_UTF8, 0, tmp_path_buf_w, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, tmp_path_buf_w, -1, utf8_buf.data(), len, NULL, NULL); + default_cache_path = std::string(utf8_buf.data()); + } } #elif defined __ANDROID__ // no defaults diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index fe5e01aed3..6289db4de6 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -855,6 +855,61 @@ TEST(Core_InputOutput, filestorage_heap_overflow) EXPECT_EQ(0, remove(name.c_str())); } +TEST(Core_InputOutput, filestorage_nd_matrix_too_many_dims) +{ + // A declared dimension count above CV_MAX_DIM used to write the sizes list + // past the fixed-size sizes[CV_MAX_DIM] stack buffer in cv::read(). + std::string sizes; + for (int i = 0; i < CV_MAX_DIM + 8; i++) + sizes += "2, "; + sizes += "2"; + + const std::string content = + "%YAML:1.0\n---\n" + "m: !!opencv-nd-matrix\n" + " sizes: [ " + sizes + " ]\n" + " dt: f\n" + " data: [ 0., 0. ]\n" + "sm: !!opencv-sparse-matrix\n" + " sizes: [ " + sizes + " ]\n" + " dt: f\n" + " data: [ ]\n"; + + FileStorage fs(content, FileStorage::READ | FileStorage::MEMORY); + + Mat m; + EXPECT_ANY_THROW(fs["m"] >> m); + + SparseMat sm; + EXPECT_ANY_THROW(fs["sm"] >> sm); +} + +TEST(Core_InputOutput, filestorage_matrix_dt_too_long) +{ + // A "dt" string with many distinct adjacent types makes decodeFormat() + // emit more pairs than the caller buffer holds. decodeSimpleFormat() sized + // its fmt_pairs buffer as CV_FS_MAX_FMT_PAIRS ints while decodeFormat writes + // up to 2*CV_FS_MAX_FMT_PAIRS ints, so a long enough dt wrote past the stack + // buffer before the length guard could reject it. + // CV_FS_MAX_FMT_PAIRS is 128; well over 2x that many distinct pairs. + std::string dt; + for (int i = 0; i < 300; i++) + dt += (i & 1) ? 'c' : 'u'; + + const std::string content = + "%YAML:1.0\n---\n" + "m: !!opencv-matrix\n" + " rows: 1\n" + " cols: 1\n" + " dt: \"" + dt + "\"\n" + " data: [ 0 ]\n"; + + FileStorage fs(content, FileStorage::READ | FileStorage::MEMORY); + + Mat m; + EXPECT_ANY_THROW(fs["m"] >> m); +} + TEST(Core_InputOutput, filestorage_base64_valid_call) { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); @@ -2222,6 +2277,24 @@ TEST(Core_InputOutput, FileStorage_int64_26829) } } +TEST(Core_InputOutput, FileStorage_read_bigint_as_real_29363) +{ + // An integer above INT_MAX (e.g. from externally-produced json/yaml/xml) must + // convert to float/double without truncating to int32. Regression for #29363. + String content = + "%YAML:1.0\n" + "a: 6662329666\n" // ~6.6e9, exact in double + "b: -9876543210\n" + "c: 4294967296\n"; // 2^32, exact in double and float + FileStorage fs(content, FileStorage::READ | FileStorage::MEMORY); + + EXPECT_EQ(6662329666.0, (double)fs["a"]); + EXPECT_EQ(-9876543210.0, (double)fs["b"]); + EXPECT_EQ(4294967296.0, (double)fs["c"]); + EXPECT_EQ(4294967296.0f, (float)fs["c"]); + EXPECT_EQ((int64_t)6662329666LL, (int64_t)fs["a"]); // int64 path unchanged +} + template T fsWriteRead(const T& expectedValue, const char* ext) { diff --git a/modules/core/test/test_operations.cpp b/modules/core/test/test_operations.cpp index d6913fd269..bebbf137e7 100644 --- a/modules/core/test/test_operations.cpp +++ b/modules/core/test/test_operations.cpp @@ -1561,6 +1561,37 @@ TEST(Core_MatExpr, empty_check_15760) EXPECT_THROW(Mat c = Mat().cross(Mat()), cv::Exception); } +// https://github.com/opencv/opencv/issues/23577 +// A scalar passed to Mat::mul() binds to _InputArray(const double&), i.e. to a temporary +// double on the caller's stack. The returned MatExpr used to keep a Mat header pointing at +// that stack slot after it died. The helpers are called through volatile function pointers +// so they cannot be inlined, which makes the stale-stack read deterministic. +MatExpr makeScalarMulExpr(const Mat& m, double scale) +{ + return m.mul(scale); +} + +void overwriteStackFrame() +{ + volatile double buf[256]; + for (int i = 0; i < 256; i++) + buf[i] = -1.0; + (void)buf; +} + +TEST(Core_MatExpr, mul_scalar_use_after_scope_23577) +{ + MatExpr (*volatile makeExprFn)(const Mat&, double) = makeScalarMulExpr; + void (*volatile overwriteFn)() = overwriteStackFrame; + + Mat m(2, 3, CV_32FC1, Scalar::all(3.0f)); + MatExpr e = makeExprFn(m, 7.0); + overwriteFn(); + Mat res = e; + + EXPECT_EQ(0, cvtest::norm(res, Mat(2, 3, CV_32FC1, Scalar::all(21.0f)), NORM_INF)); +} + TEST(Core_Arithm, scalar_handling_19599) // https://github.com/opencv/opencv/issues/19599 (OpenCV 4.x+ only) { Mat a(1, 1, CV_32F, Scalar::all(1)); diff --git a/modules/dnn/perf/perf_utils.cpp b/modules/dnn/perf/perf_utils.cpp index c6dd0bf058..4bae8b398b 100644 --- a/modules/dnn/perf/perf_utils.cpp +++ b/modules/dnn/perf/perf_utils.cpp @@ -69,4 +69,343 @@ INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImages, std::vector{16, 2048, 2048}) ); +// NCHW, 8U->32F, C3, mean+scale+swapRB at 640x640 +using Utils_blobFromImage_8U_NCHW = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_8U_NCHW, MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0/255.0, Size(), Scalar(104, 117, 123), true, false, CV_32F); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_8U_NCHW, + Values(std::vector{ 640, 640}) +); + +// NHWC, 8U->32F, C3 +using Utils_blobFromImage_8U_NHWC = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_8U_NHWC, SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + + TEST_CYCLE() { + Mat blob = blobFromImageWithParams(input, params); + } + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(Utils_blobFromImage_8U_NHWC, MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0/255.0); + params.mean = Scalar(104, 117, 123); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + + TEST_CYCLE() { + Mat blob = blobFromImageWithParams(input, params); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_8U_NHWC, + Values(std::vector{ 224, 224}, + std::vector{ 640, 640}) +); + +// NHWC, 32F->32F, C3 +using Utils_blobFromImage_32F_NHWC = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_32F_NHWC, SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC3); + randu(input, 0.0f, 1.0f); + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + + TEST_CYCLE() { + Mat blob = blobFromImageWithParams(input, params); + } + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(Utils_blobFromImage_32F_NHWC, MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC3); + randu(input, 0.0f, 1.0f); + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0/0.226); + params.mean = Scalar(0.485, 0.456, 0.406); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + + TEST_CYCLE() { + Mat blob = blobFromImageWithParams(input, params); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_32F_NHWC, + Values(std::vector{ 224, 224}, + std::vector{ 640, 640}) +); + +// Resize+crop, 8U->32F, C3, mean+scale+swapRB to 640x640 +using Utils_blobFromImage_8U_Resize = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_8U_Resize, NHWC_Crop_MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0/255.0); + params.size = Size(640, 640); + params.mean = Scalar(104, 117, 123); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + params.paddingmode = DNN_PMODE_CROP_CENTER; + + TEST_CYCLE() { + Mat blob = blobFromImageWithParams(input, params); + } + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(Utils_blobFromImage_8U_Resize, NCHW_Crop_MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0/255.0, Size(640, 640), Scalar(104, 117, 123), true, true, CV_32F); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_8U_Resize, + Values(std::vector{ 720, 1280}, + std::vector{ 1080, 1920}, + std::vector{ 2160, 3840}) +); + +// Resize+crop, NCHW, 32F->32F, C3, mean+scale+swapRB to 300x300 +using Utils_blobFromImage_32F_NCHW_Resize = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_32F_NCHW_Resize, Crop_MeanScale_SwapRB_To300) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC3); + randu(input, 0.0f, 1.0f); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0/0.226, Size(300, 300), Scalar(0.485, 0.456, 0.406), true, true, CV_32F); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_32F_NCHW_Resize, + Values(std::vector{ 720, 1280}, + std::vector{ 1080, 1920}) +); + +// Resize+crop, NCHW, 8U->8U, C3 +using Utils_blobFromImage_8U_to_8U_Crop = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_8U_to_8U_Crop, NCHW_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0, Size(640, 640), Scalar(), true, true, CV_8U); + } + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(Utils_blobFromImage_8U_to_8U_Crop, NCHW) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0, Size(640, 640), Scalar(), false, true, CV_8U); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_8U_to_8U_Crop, + Values(std::vector{ 1080, 1920}, + std::vector{ 2160, 3840}) +); + +// Resize, NCHW, 8U->8U, C3 +using Utils_blobFromImage_8U_to_8U_Resize = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_8U_to_8U_Resize, NCHW_SwapRB) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_8UC3); + randu(input, 0, 255); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0, Size(640, 640), Scalar(), true, false, CV_8U); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_8U_to_8U_Resize, + Values(std::vector{ 1080, 1920}, + std::vector{ 2160, 3840}) +); + +// Resize+crop, NCHW, 32F->32F, C1, mean +using Utils_blobFromImage_32F_NCHW_C1 = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage_32F_NCHW_C1, Crop_MeanScale_To224) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC1); + randu(input, 0.0f, 1.0f); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0/0.226, Size(224, 224), Scalar(0.5), false, true, CV_32F); + } + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(Utils_blobFromImage_32F_NCHW_C1, Crop_MeanScale_To640) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC1); + randu(input, 0.0f, 1.0f); + + TEST_CYCLE() { + Mat blob = blobFromImage(input, 1.0/0.226, Size(640, 640), Scalar(0.5), false, true, CV_32F); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage_32F_NCHW_C1, + Values(std::vector{ 1080, 1920}, + std::vector{ 2160, 3840}) +); + +// Batch=8, NHWC, 8U->32F, C3, mean+scale+swapRB +using Utils_blobFromImages_NoResize = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImages_NoResize, NHWC_MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + int batch = input_shape.front(); + std::vector input_shape_no_batch(input_shape.begin()+1, input_shape.end()); + + std::vector inputs; + for (int i = 0; i < batch; i++) { + Mat input(input_shape_no_batch, CV_8UC3); + randu(input, 0, 255); + inputs.push_back(input); + } + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0/255.0); + params.mean = Scalar(104, 117, 123); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + + TEST_CYCLE() { + Mat blob = blobFromImagesWithParams(inputs, params); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImages_NoResize, + Values(std::vector{8, 640, 640}) +); + +// Batch=8, resize+crop to 640x640, 8U->32F, C3, mean+scale+swapRB +using Utils_blobFromImages_Resize = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImages_Resize, NHWC_Crop_MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + int batch = input_shape.front(); + std::vector input_shape_no_batch(input_shape.begin()+1, input_shape.end()); + + std::vector inputs; + for (int i = 0; i < batch; i++) { + Mat input(input_shape_no_batch, CV_8UC3); + randu(input, 0, 255); + inputs.push_back(input); + } + + Image2BlobParams params; + params.scalefactor = Scalar::all(1.0/255.0); + params.size = Size(640, 640); + params.mean = Scalar(104, 117, 123); + params.swapRB = true; + params.datalayout = DNN_LAYOUT_NHWC; + params.paddingmode = DNN_PMODE_CROP_CENTER; + + TEST_CYCLE() { + Mat blob = blobFromImagesWithParams(inputs, params); + } + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P_(Utils_blobFromImages_Resize, NCHW_Crop_MeanScale_SwapRB) { + std::vector input_shape = GetParam(); + int batch = input_shape.front(); + std::vector input_shape_no_batch(input_shape.begin()+1, input_shape.end()); + + std::vector inputs; + for (int i = 0; i < batch; i++) { + Mat input(input_shape_no_batch, CV_8UC3); + randu(input, 0, 255); + inputs.push_back(input); + } + + TEST_CYCLE() { + Mat blob = blobFromImages(inputs, 1.0/255.0, Size(640, 640), Scalar(104, 117, 123), true, true, CV_32F); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImages_Resize, + Values(std::vector{8, 720, 1280}, + std::vector{8, 1080, 1920}) +); + } diff --git a/modules/dnn/src/dnn_utils.cpp b/modules/dnn/src/dnn_utils.cpp index 5cc1359b0d..29135fc6df 100644 --- a/modules/dnn/src/dnn_utils.cpp +++ b/modules/dnn/src/dnn_utils.cpp @@ -152,29 +152,69 @@ void blobFromImagesNCHWImpl(const std::vector& images, Mat& blob_, const Im if (param.swapRB) std::swap(p_blob_r, p_blob_b); - for (size_t i = 0; i < h; ++i) + if (nch == 1) { - const Tinp* p_img_row = images[k].ptr(i); - - if (nch == 1) + for (size_t i = 0; i < (size_t)h; ++i) { - for (size_t j = 0; j < w; ++j) + const Tinp* p_img_row = images[k].ptr(i); + for (size_t j = 0; j < (size_t)w; ++j) { p_blob[i * w + j] = p_img_row[j]; } } - else if (nch == 3) + } + else if (nch == 3) + { + if (images[k].isContinuous()) { - for (size_t j = 0; j < w; ++j) + const Tinp* p_src = images[k].ptr(); + size_t i = 0; +// NOTE: Visual Studio compiler is not able to vectorize the following loop on ARM64 CPU. +// GCC and Clang does it efficiently. +// TODO: Drop NEON block when MSVC is able to vectorize it too. +#if CV_NEON + if (sizeof(Tinp) == 4 && sizeof(Tout) == 4) { - p_blob_r[i * w + j] = p_img_row[j * 3 ]; - p_blob_g[i * w + j] = p_img_row[j * 3 + 1]; - p_blob_b[i * w + j] = p_img_row[j * 3 + 2]; + const float* src_f = reinterpret_cast(p_src); + float* dst_r = reinterpret_cast(p_blob_r); + float* dst_g = reinterpret_cast(p_blob_g); + float* dst_b = reinterpret_cast(p_blob_b); + for (; i + 4 <= (size_t)wh; i += 4) + { + float32x4x3_t rgb = vld3q_f32(src_f + i * 3); + vst1q_f32(dst_r + i, rgb.val[0]); + vst1q_f32(dst_g + i, rgb.val[1]); + vst1q_f32(dst_b + i, rgb.val[2]); + } + } +#endif + for (; i < (size_t)wh; ++i) + { + p_blob_r[i] = p_src[i * 3 ]; + p_blob_g[i] = p_src[i * 3 + 1]; + p_blob_b[i] = p_src[i * 3 + 2]; } } - else // if (nch == 4) + else { - for (size_t j = 0; j < w; ++j) + for (size_t i = 0; i < (size_t)h; ++i) + { + const Tinp* p_img_row = images[k].ptr(i); + for (size_t j = 0; j < (size_t)w; ++j) + { + p_blob_r[i * w + j] = p_img_row[j * 3 ]; + p_blob_g[i * w + j] = p_img_row[j * 3 + 1]; + p_blob_b[i * w + j] = p_img_row[j * 3 + 2]; + } + } + } + } + else // if (nch == 4) + { + for (size_t i = 0; i < (size_t)h; ++i) + { + const Tinp* p_img_row = images[k].ptr(i); + for (size_t j = 0; j < (size_t)w; ++j) { p_blob_r[i * w + j] = p_img_row[j * 4 ]; p_blob_g[i * w + j] = p_img_row[j * 4 + 1]; diff --git a/modules/dnn/src/layers/einsum_layer.cpp b/modules/dnn/src/layers/einsum_layer.cpp index de26131b09..4699fa5f81 100644 --- a/modules/dnn/src/layers/einsum_layer.cpp +++ b/modules/dnn/src/layers/einsum_layer.cpp @@ -1056,6 +1056,8 @@ void LayerEinsumImpl::processEquation(const std::vector& inputs) CV_CheckNE(letterIdx, -1, "The only permissible subscript labels are lowercase letters (a-z) and uppercase letters (A-Z)."); + CV_CheckLT(dim_count, rank, + "The Einsum subscripts string has an excessive number of subscript labels compared to the rank of the input."); int dimValue = shape[dim_count]; // The subscript label was not found in the global subscript label array @@ -1083,8 +1085,7 @@ void LayerEinsumImpl::processEquation(const std::vector& inputs) ++letter2count[letterIdx]; currTokenIndices.push_back(letter2index[letterIdx]); - CV_CheckLE(++dim_count, rank, - "The Einsum subscripts string has an excessive number of subscript labels compared to the rank of the input."); + ++dim_count; } } diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index f11fe510f2..5405d938d2 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -960,6 +960,32 @@ struct GeluApproximationFunctor : public BaseDefaultFunctor::vlanes(); + v_float32 one = vx_setall_f32(1.f), two = vx_setall_f32(2.f), half = vx_setall_f32(0.5f); + v_float32 c1 = vx_setall_f32(GeluApproximationConstants::sqrt_2_pi); + v_float32 c2 = vx_setall_f32(GeluApproximationConstants::coef_sqrt_2_pi); + v_float32 lo = vx_setall_f32(-88.f), hi = vx_setall_f32(88.f); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + v_float32 u = v_mul(x, v_fma(v_mul(x, x), c2, c1)); + v_float32 e = v_exp(v_min(v_max(v_add(u, u), lo), hi)); + v_float32 th = v_sub(one, v_div(two, v_add(e, one))); + vx_store(dstptr + i, v_mul(v_mul(half, x), v_add(one, th))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + int64 getFLOPSPerElement() const { return 100; } }; @@ -993,6 +1019,28 @@ struct TanHFunctor : public BaseDefaultFunctor return tanh(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), two = vx_setall_f32(2.f); + v_float32 lo = vx_setall_f32(-88.f), hi = vx_setall_f32(88.f); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + v_float32 e = v_exp(v_min(v_max(v_add(x, x), lo), hi)); // e^{2x}, clamped + vx_store(dstptr + i, v_sub(one, v_div(two, v_add(e, one)))); // 1 - 2/(e+1) + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1517,6 +1565,27 @@ struct BNLLFunctor : public BaseDefaultFunctor return x > 0 ? x + log(1.f + exp(-x)) : log(1.f + exp(x)); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), z = vx_setzero_f32(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + // stable single-branch form: max(x,0) + log(1 + exp(-|x|)) + vx_store(dstptr + i, v_add(v_max(x, z), v_log(v_add(one, v_exp(v_sub(z, v_abs(x))))))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1671,6 +1740,22 @@ struct LogFunctor : public BaseDefaultFunctor return log(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i <= len - vlanes; i += vlanes) + vx_store(dstptr + i, v_log(vx_load(srcptr + i))); +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1801,6 +1886,26 @@ struct AcoshFunctor : public BaseDefaultFunctor return acosh(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_log(v_add(x, v_sqrt(v_sub(v_mul(x, x), one))))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1863,6 +1968,28 @@ struct AsinhFunctor : public BaseDefaultFunctor return asinh(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), z = vx_setzero_f32(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + v_float32 ax = v_abs(x); + v_float32 r = v_log(v_add(ax, v_sqrt(v_fma(ax, ax, one)))); + vx_store(dstptr + i, v_select(v_lt(x, z), v_sub(z, r), r)); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1925,6 +2052,26 @@ struct AtanhFunctor : public BaseDefaultFunctor return atanh(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f), half = vx_setall_f32(0.5f); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_mul(half, v_log(v_div(v_add(one, x), v_sub(one, x))))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1960,6 +2107,25 @@ struct CosFunctor : public BaseDefaultFunctor return cos(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_cos(x)); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -1995,6 +2161,26 @@ struct CoshFunctor : public BaseDefaultFunctor return cosh(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 half = vx_setall_f32(0.5f), z = vx_setzero_f32(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_mul(half, v_add(v_exp(x), v_exp(v_sub(z, x))))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -2030,6 +2216,22 @@ struct ErfFunctor : public BaseDefaultFunctor return erf(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i <= len - vlanes; i += vlanes) + vx_store(dstptr + i, v_erf(vx_load(srcptr + i))); +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -2156,6 +2358,25 @@ struct SinFunctor : public BaseDefaultFunctor return sin(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_sin(x)); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -2191,6 +2412,26 @@ struct SinhFunctor : public BaseDefaultFunctor return sinh(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 half = vx_setall_f32(0.5f), z = vx_setzero_f32(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_mul(half, v_sub(v_exp(x), v_exp(v_sub(z, x))))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -2226,6 +2467,26 @@ struct SoftplusFunctor : public BaseDefaultFunctor return log1p(exp(x)); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 one = vx_setall_f32(1.f); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_log(v_add(one, v_exp(x)))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -2288,6 +2549,25 @@ struct TanFunctor : public BaseDefaultFunctor return tan(x); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_div(v_sin(x), v_cos(x))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + #ifdef HAVE_CUDA Ptr initCUDA(int target, csl::Stream stream) { @@ -2765,6 +3045,26 @@ struct ExpFunctor : public BaseDefaultFunctor return exp(normScale * x + normShift); } + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const + { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) + { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int vlanes = VTraits::vlanes(); + v_float32 vsc = vx_setall_f32(normScale), vsh = vx_setall_f32(normShift); + for (; i <= len - vlanes; i += vlanes) + { + v_float32 x = vx_load(srcptr + i); + vx_store(dstptr + i, v_exp(v_fma(x, vsc, vsh))); + } +#endif + for (; i < len; i++) + dstptr[i] = calculate(srcptr[i]); + } + } + inline void setKernelParams(ocl::Kernel& kernel) const { kernel.set(3, normScale); diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 699c44d6e0..ac8c550286 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -3210,13 +3210,14 @@ void ONNXImporter::parseTile(LayerParams& layerParams, const opencv_onnx::NodePr // input2 in tile-1: axis, 1d tensor of shape [1] Mat input2_blob = getIntBlob(node_proto, 2); CV_CheckEQ(input2_blob.total(), 1ull, "ONNX/Tile: axis must be a 0D tensor or 1D tensor of shape [1]."); - int axis = input2_blob.at(0); + int axis = normalize_axis(input2_blob.at(0), input0_dims); repeats_vec[axis] = tiles; } else { // input1 in tile>1: repeats CV_CheckLE(input1_blob.dims, 2, "ONNX/Tile: repeats must be a 1D tensor."); // 1D tensor is represented as a 2D Mat + CV_CheckEQ((int)input1_blob.total(), input0_dims, "ONNX/Tile: repeats length must match the input rank."); for (int i = 0; i < input1_blob.total(); i++) repeats_vec[i] = input1_blob.at(i); } diff --git a/modules/dnn/src/tensorflow/tf_importer.cpp b/modules/dnn/src/tensorflow/tf_importer.cpp index 196a2cb54d..c69e06c2a1 100644 --- a/modules/dnn/src/tensorflow/tf_importer.cpp +++ b/modules/dnn/src/tensorflow/tf_importer.cpp @@ -1708,6 +1708,7 @@ void TFImporter::parseSlice(tensorflow::GraphDef& net, const tensorflow::NodeDef CV_Assert_N(!begins.empty(), !sizes.empty()); CV_CheckTypeEQ(begins.type(), CV_32SC1, ""); CV_CheckTypeEQ(sizes.type(), CV_32SC1, ""); + CV_CheckEQ(begins.total(), sizes.total(), "Slice: 'begin' and 'size' must have equal length"); if (begins.total() == 4 && getDataLayout(name, data_layouts) == DNN_LAYOUT_NHWC) { diff --git a/modules/dnn/src/tflite/tflite_importer.cpp b/modules/dnn/src/tflite/tflite_importer.cpp index 8e495ec27e..b09c4b612d 100644 --- a/modules/dnn/src/tflite/tflite_importer.cpp +++ b/modules/dnn/src/tflite/tflite_importer.cpp @@ -540,6 +540,7 @@ void TFLiteImporter::parseConvolution(const Operator& op, const std::string& opc if (filterScales->size() == 1) { layerParams.blobs[2].setTo(inpScale * filterScales->Get(0) / outScale); } else { + CV_CheckEQ((int)filterScales->size(), oc, "TFLite: number of filter quantization scales must match the number of output channels"); for (size_t i = 0; i < filterScales->size(); ++i) { layerParams.blobs[2].at(i) = inpScale * filterScales->Get(i) / outScale; } @@ -605,6 +606,7 @@ void TFLiteImporter::parseDWConvolution(const Operator& op, const std::string& o if (filterScales->size() == 1) { layerParams.blobs[2].setTo(inpScale * filterScales->Get(0) / outScale); } else { + CV_CheckEQ((int)filterScales->size(), oc, "TFLite: number of filter quantization scales must match the number of output channels"); for (size_t i = 0; i < filterScales->size(); ++i) { layerParams.blobs[2].at(i) = inpScale * filterScales->Get(i) / outScale; } @@ -631,16 +633,19 @@ void TFLiteImporter::parsePadding(const Operator& op, const std::string& opcode, Mat paddings = allTensors[op.inputs()->Get(1)].clone(); CV_CheckTypeEQ(paddings.type(), CV_32S, ""); - // N H W C - // 0 1 2 3 4 5 6 7 - std::swap(paddings.at(2), paddings.at(6)); - std::swap(paddings.at(3), paddings.at(7)); - // N C W H - // 0 1 2 3 4 5 6 7 - std::swap(paddings.at(4), paddings.at(6)); - std::swap(paddings.at(5), paddings.at(7)); - // N C H W - // 0 1 2 3 4 5 6 7 + if (paddings.total() == 8) + { + // N H W C + // 0 1 2 3 4 5 6 7 + std::swap(paddings.at(2), paddings.at(6)); + std::swap(paddings.at(3), paddings.at(7)); + // N C W H + // 0 1 2 3 4 5 6 7 + std::swap(paddings.at(4), paddings.at(6)); + std::swap(paddings.at(5), paddings.at(7)); + // N C H W + // 0 1 2 3 4 5 6 7 + } layerParams.set("paddings", DictValue::arrayInt((int32_t*)paddings.data, paddings.total())); addLayer(layerParams, op); @@ -946,6 +951,7 @@ void TFLiteImporter::parseResize(const Operator& op, const std::string& opcode, layerParams.set("half_pixel_centers", options->half_pixel_centers()); } Mat shape = allTensors[op.inputs()->Get(1)].reshape(1, 1); + CV_CheckGE(shape.total(), (size_t)2, "TFLite Resize: size tensor must hold height and width"); layerParams.set("height", shape.at(0, 0)); layerParams.set("width", shape.at(0, 1)); addLayer(layerParams, op); diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index 036f0eee4a..7e53228ad6 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -3427,6 +3427,9 @@ TEST_P(Test_ONNX_nets, YOLOv5n) TEST_P(Test_ONNX_layers, Tile) { testONNXModels("tile", pb); + // tile-1 (opset 1) form with a negative axis; the parser must normalize it + // against the input rank instead of indexing the repeats buffer directly. + testONNXModels("tile_neg_axis"); } TEST_P(Test_ONNX_layers, Gelu) diff --git a/modules/features/perf/perf_matcher.cpp b/modules/features/perf/perf_matcher.cpp new file mode 100644 index 0000000000..fe8a892a8a --- /dev/null +++ b/modules/features/perf/perf_matcher.cpp @@ -0,0 +1,91 @@ +// 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. + +// Brute-force descriptor matching perf tests. These exercise the core distance +// kernels (hal::normL2Sqr_ / normL1_ / normHamming via cv::batchDistance) that +// back BFMatcher, which is how float descriptors (SIFT 128-d, SURF 64-d) and +// binary descriptors (ORB 32-byte) are matched. + +#include "perf_precomp.hpp" + +namespace opencv_test +{ +using namespace perf; + +// (descriptor dimension, query/train descriptor count) +typedef tuple Dim_Count_t; +typedef TestBaseWithParam DescriptorMatcherFixture; + +// Float descriptors matched with L2 (SIFT=128, SURF=64) — uses hal::normL2Sqr_. +PERF_TEST_P(DescriptorMatcherFixture, bfmatch_knn_L2_float, + testing::Combine( + testing::Values(64, 128), // SURF, SIFT descriptor sizes + testing::Values(512, 1000) + )) +{ + const int dim = get<0>(GetParam()); + const int count = get<1>(GetParam()); + + Mat query(count, dim, CV_32F); + Mat train(count, dim, CV_32F); + declare.in(query, train, WARMUP_RNG); + declare.time(60); + + BFMatcher matcher(NORM_L2, false); + std::vector > matches; + + TEST_CYCLE() matcher.knnMatch(query, train, matches, 2); + + SANITY_CHECK_NOTHING(); +} + +// Float descriptors matched with L1 — uses hal::normL1_. +PERF_TEST_P(DescriptorMatcherFixture, bfmatch_knn_L1_float, + testing::Combine( + testing::Values(64, 128), + testing::Values(512, 1000) + )) +{ + const int dim = get<0>(GetParam()); + const int count = get<1>(GetParam()); + + Mat query(count, dim, CV_32F); + Mat train(count, dim, CV_32F); + declare.in(query, train, WARMUP_RNG); + declare.time(60); + + BFMatcher matcher(NORM_L1, false); + std::vector > matches; + + TEST_CYCLE() matcher.knnMatch(query, train, matches, 2); + + SANITY_CHECK_NOTHING(); +} + +// Binary descriptors matched with Hamming (ORB/BRISK=32 bytes) — uses hal::normHamming. +PERF_TEST_P(DescriptorMatcherFixture, bfmatch_knn_Hamming_binary, + testing::Combine( + testing::Values(32, 64), // ORB (32), BRISK/FREAK (64) byte sizes + testing::Values(512, 1000) + )) +{ + const int bytes = get<0>(GetParam()); + const int count = get<1>(GetParam()); + + Mat query(count, bytes, CV_8U); + Mat train(count, bytes, CV_8U); + declare.in(query, train, WARMUP_RNG); + declare.time(60); + + BFMatcher matcher(NORM_HAMMING, false); + std::vector > matches; + + TEST_CYCLE() matcher.knnMatch(query, train, matches, 2); + + SANITY_CHECK_NOTHING(); +} + +} // namespace diff --git a/modules/features/src/blobdetector.cpp b/modules/features/src/blobdetector.cpp index 96229b0359..d130ae3573 100644 --- a/modules/features/src/blobdetector.cpp +++ b/modules/features/src/blobdetector.cpp @@ -326,11 +326,13 @@ void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImag //compute blob radius { - std::vector dists; - for (size_t pointIdx = 0; pointIdx < contours[contourIdx].size(); pointIdx++) + const std::vector& contour = contours[contourIdx]; + const size_t contourSize = contour.size(); + AutoBuffer dists(contourSize); + for (size_t pointIdx = 0; pointIdx < contourSize; pointIdx++) { - Point2d pt = contours[contourIdx][pointIdx]; - dists.push_back(norm(center.location - pt)); + const Point2d& pt = contour[pointIdx]; + dists[pointIdx] = norm(center.location - pt); } std::sort(dists.begin(), dists.end()); center.radius = (dists[(dists.size() - 1) / 2] + dists[dists.size() / 2]) / 2.; diff --git a/modules/features/src/fast.cpp b/modules/features/src/fast.cpp index b1267bba77..9eac4aa742 100644 --- a/modules/features/src/fast.cpp +++ b/modules/features/src/fast.cpp @@ -441,6 +441,14 @@ void FAST(InputArray _img, std::vector& keypoints, int threshold, bool { CV_INSTRUMENT_REGION(); + if (type != FastFeatureDetector::TYPE_5_8 && + type != FastFeatureDetector::TYPE_7_12 && + type != FastFeatureDetector::TYPE_9_16) + { + CV_Error_(Error::StsBadArg, + ("Unknown FastFeatureDetector detector type: %d", static_cast(type))); + } + const size_t max_fast_features = std::max(_img.total()/100, size_t(1000)); // Simple heuristic that depends on resolution. CV_OCL_RUN(_img.isUMat() && type == FastFeatureDetector::TYPE_9_16, @@ -541,7 +549,7 @@ public: else if(prop == FAST_N) type = static_cast(cvRound(value)); else - CV_Error(Error::StsBadArg, ""); + CV_Error_(Error::StsBadArg, ("Unknown FastFeatureDetector property: %d", prop)); } double get(int prop) const @@ -552,7 +560,7 @@ public: return nonmaxSuppression; if(prop == FAST_N) return static_cast(type); - CV_Error(Error::StsBadArg, ""); + CV_Error_(Error::StsBadArg, ("Unknown FastFeatureDetector property: %d", prop)); return 0; } diff --git a/modules/features/src/keypoint.cpp b/modules/features/src/keypoint.cpp index aad9fe36e3..38374890a0 100644 --- a/modules/features/src/keypoint.cpp +++ b/modules/features/src/keypoint.cpp @@ -221,8 +221,8 @@ struct KeyPoint_LessThan void KeyPointsFilter::removeDuplicated( std::vector& keypoints ) { int i, j, n = (int)keypoints.size(); - std::vector kpidx(n); - std::vector mask(n, (uchar)1); + AutoBuffer kpidx(n); + AutoBuffer mask(n, (uchar)1); for( i = 0; i < n; i++ ) kpidx[i] = i; diff --git a/modules/features/src/orb.cpp b/modules/features/src/orb.cpp index b9dd44fd80..ce4a72ddfa 100644 --- a/modules/features/src/orb.cpp +++ b/modules/features/src/orb.cpp @@ -839,7 +839,7 @@ static void computeKeyPoints(const Mat& imagePyramid, #endif int i, nkeypoints, level, nlevels = (int)layerInfo.size(); - std::vector nfeaturesPerLevel(nlevels); + AutoBuffer nfeaturesPerLevel(nlevels); // fill the extractors and descriptors for the corresponding scales float factor = (float)(1.0 / scaleFactor); @@ -877,7 +877,7 @@ static void computeKeyPoints(const Mat& imagePyramid, allKeypoints.clear(); std::vector keypoints; - std::vector counters(nlevels); + AutoBuffer counters(nlevels); keypoints.reserve(nfeaturesPerLevel[0]*2); for( level = 0; level < nlevels; level++ ) @@ -1266,7 +1266,25 @@ void ORB_Impl::detectAndCompute( InputArray _image, InputArray _mask, Ptr ORB::create(int nfeatures, float scaleFactor, int nlevels, int edgeThreshold, int firstLevel, int wta_k, ORB::ScoreType scoreType, int patchSize, int fastThreshold) { - CV_Assert(firstLevel >= 0); + CV_CheckGE(nfeatures, 0, "nfeatures must be non-negative"); + CV_CheckGT(scaleFactor, 1.f, "scaleFactor must be greater than 1"); + CV_CheckGT(nlevels, 0, "nlevels must be positive"); + CV_CheckGE(edgeThreshold, 0, "edgeThreshold must be non-negative"); + CV_CheckGE(firstLevel, 0, "firstLevel must be non-negative"); + CV_CheckGE(patchSize, 2, "patchSize must be at least 2"); + + if (wta_k != 2 && wta_k != 3 && wta_k != 4) + { + CV_Error_(Error::StsBadArg, + ("wta_k must be 2, 3, or 4, but got %d", wta_k)); + } + + if (scoreType != ORB::HARRIS_SCORE && scoreType != ORB::FAST_SCORE) + { + CV_Error_(Error::StsBadArg, + ("Unknown ORB score type: %d", static_cast(scoreType))); + } + return makePtr(nfeatures, scaleFactor, nlevels, edgeThreshold, firstLevel, wta_k, scoreType, patchSize, fastThreshold); } diff --git a/modules/features/src/sift.dispatch.cpp b/modules/features/src/sift.dispatch.cpp index 3f4a7ceb41..f7c6116093 100644 --- a/modules/features/src/sift.dispatch.cpp +++ b/modules/features/src/sift.dispatch.cpp @@ -225,7 +225,7 @@ void SIFT_Impl::buildGaussianPyramid( const Mat& base, std::vector& pyr, in { CV_TRACE_FUNCTION(); - std::vector sig(nOctaveLayers + 3); + AutoBuffer sig(nOctaveLayers + 3); pyr.resize(nOctaves*(nOctaveLayers + 3)); // precompute Gaussian sigmas using the following formula: diff --git a/modules/features/test/test_fast.cpp b/modules/features/test/test_fast.cpp index 8f8f587586..a6245859af 100644 --- a/modules/features/test/test_fast.cpp +++ b/modules/features/test/test_fast.cpp @@ -165,4 +165,14 @@ TEST(Features2d_FAST, noNMS) ASSERT_EQ( 0, cvtest::norm(gt_kps, kps, NORM_L2)); } +TEST(Features2d_FAST, invalidDetectorType) +{ + Mat img = Mat::zeros(16, 16, CV_8UC1); + vector keypoints; + + EXPECT_THROW(FAST(img, keypoints, 10, true, + static_cast(-1)), + cv::Exception); +} + }} // namespace diff --git a/modules/features/test/test_orb.cpp b/modules/features/test/test_orb.cpp index 92fe8e9838..0153dcd298 100644 --- a/modules/features/test/test_orb.cpp +++ b/modules/features/test/test_orb.cpp @@ -195,4 +195,15 @@ TEST(Features2D_ORB, MaskValue) ASSERT_EQ(countNonZero(diff), 0); } +TEST(Features2D_ORB, invalidCreateParameters) +{ + EXPECT_THROW(ORB::create(500, 1.0f), cv::Exception); + EXPECT_THROW(ORB::create(500, 1.2f, 0), cv::Exception); + EXPECT_THROW(ORB::create(500, 1.2f, 8, 31, 0, 5), cv::Exception); + EXPECT_THROW(ORB::create(500, 1.2f, 8, 31, 0, 2, + static_cast(-1)), cv::Exception); + EXPECT_THROW(ORB::create(500, 1.2f, 8, 31, 0, 2, + ORB::HARRIS_SCORE, 1), cv::Exception); +} + }} // namespace diff --git a/modules/flann/include/opencv2/flann/ground_truth.h b/modules/flann/include/opencv2/flann/ground_truth.h index 4c030bc040..6cd2d6731c 100644 --- a/modules/flann/include/opencv2/flann/ground_truth.h +++ b/modules/flann/include/opencv2/flann/ground_truth.h @@ -47,8 +47,8 @@ void find_nearest(const Matrix& dataset, typenam typedef typename Distance::ResultType DistanceType; int n = nn + skip; - std::vector match(n); - std::vector dists(n); + cv::AutoBuffer match(n); + cv::AutoBuffer dists(n); dists[0] = distance(dataset[0], query, dataset.cols); match[0] = 0; diff --git a/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h b/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h index 9567d71f5e..979c771960 100644 --- a/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h +++ b/modules/flann/include/opencv2/flann/hierarchical_clustering_index.h @@ -532,7 +532,11 @@ public: const bool explore_all_trees = get_param(searchParams,"explore_all_trees",false); // Priority queue storing intermediate branches in the best-bin-first search - const cv::Ptr>& heap = Heap::getPooledInstance(cv::utils::getThreadID(), (int)size_); + // Kept in thread_local storage so each thread owns an independent heap + // and no process-wide lock is taken on the search hot path (issue #25281). + thread_local cv::Ptr> heap = cv::makePtr>((int)size_); + heap->clear(); + heap->reserve((int)size_); std::vector checked(size_,false); int checks = 0; @@ -686,8 +690,8 @@ private: return; } - std::vector centers(branching); - std::vector labels(indices_length); + cv::AutoBuffer centers(branching); + cv::AutoBuffer labels(indices_length); int centers_length; (this->*chooseCenters)(branching, dsindices, indices_length, ¢ers[0], centers_length); diff --git a/modules/flann/include/opencv2/flann/index_testing.h b/modules/flann/include/opencv2/flann/index_testing.h index 203974562b..63b90e600c 100644 --- a/modules/flann/include/opencv2/flann/index_testing.h +++ b/modules/flann/include/opencv2/flann/index_testing.h @@ -99,8 +99,8 @@ float search_with_ground_truth(NNIndex& index, const Matrix resultSet(nn+skipMatches); SearchParams searchParams(checks); - std::vector indices(nn+skipMatches); - std::vector dists(nn+skipMatches); + cv::AutoBuffer indices(nn+skipMatches); + cv::AutoBuffer dists(nn+skipMatches); int* neighbors = &indices[skipMatches]; int correct = 0; diff --git a/modules/flann/include/opencv2/flann/kdtree_index.h b/modules/flann/include/opencv2/flann/kdtree_index.h index 569844e475..a279aeae77 100644 --- a/modules/flann/include/opencv2/flann/kdtree_index.h +++ b/modules/flann/include/opencv2/flann/kdtree_index.h @@ -449,7 +449,11 @@ private: DynamicBitset checked(size_); // Priority queue storing intermediate branches in the best-bin-first search - const cv::Ptr>& heap = Heap::getPooledInstance(cv::utils::getThreadID(), (int)size_); + // Kept in thread_local storage so each thread owns an independent heap + // and no process-wide lock is taken on the search hot path (issue #25281). + thread_local cv::Ptr> heap = cv::makePtr>((int)size_); + heap->clear(); + heap->reserve((int)size_); /* Search once through each tree down to root. */ for (i = 0; i < trees_; ++i) { diff --git a/modules/flann/include/opencv2/flann/kmeans_index.h b/modules/flann/include/opencv2/flann/kmeans_index.h index b079b9f5e5..7270508c52 100644 --- a/modules/flann/include/opencv2/flann/kmeans_index.h +++ b/modules/flann/include/opencv2/flann/kmeans_index.h @@ -528,7 +528,11 @@ public: } else { // Priority queue storing intermediate branches in the best-bin-first search - const cv::Ptr>& heap = Heap::getPooledInstance(cv::utils::getThreadID(), (int)size_); + // Kept in thread_local storage so each thread owns an independent heap + // and no process-wide lock is taken on the search hot path (issue #25281). + thread_local cv::Ptr> heap = cv::makePtr>((int)size_); + heap->clear(); + heap->reserve((int)size_); int checks = 0; for (int i=0; i::getStats() const != end; ) if (*iterator < bin_end) { if (is_new_bin) { - stats.size_histogram_.push_back(std::vector(3, 0)); + stats.size_histogram_.emplace_back(3, 0); stats.size_histogram_.back()[0] = bin_start; stats.size_histogram_.back()[1] = bin_end - 1; is_new_bin = false; diff --git a/modules/flann/test/test_heap.cpp b/modules/flann/test/test_heap.cpp new file mode 100644 index 0000000000..838606a167 --- /dev/null +++ b/modules/flann/test/test_heap.cpp @@ -0,0 +1,100 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#include "test_precomp.hpp" + +#include "opencv2/core/utility.hpp" +#include "opencv2/flann/heap.h" + +#include + +#if !defined(OPENCV_DISABLE_THREAD_SUPPORT) +#include +#endif + +namespace opencv_test { namespace { + +using cvflann::Heap; + +// Basic single-threaded behaviour: popMin returns the stored elements in +// ascending order and reports emptiness correctly. +TEST(Flann_Heap, popMin_returns_sorted) +{ + const int capacity = 8; + Heap heap(capacity); + + const int values[] = {5, 1, 4, 2, 8, 3, 7, 6}; + for (int v : values) + heap.insert(v); + + EXPECT_EQ(heap.size(), capacity); + + int prev = -1, out = 0; + int popped = 0; + while (heap.popMin(out)) + { + EXPECT_GT(out, prev); // strictly increasing + prev = out; + ++popped; + } + EXPECT_EQ(popped, capacity); + EXPECT_TRUE(heap.empty()); +} + +// Capacity is a hard limit: extra inserts are dropped, not stored. +TEST(Flann_Heap, insert_respects_capacity) +{ + const int capacity = 3; + Heap heap(capacity); + for (int i = 0; i < 10; ++i) + heap.insert(i); + EXPECT_EQ(heap.size(), capacity); +} + +#if !defined(OPENCV_DISABLE_THREAD_SUPPORT) + +// getPooledInstance() is no longer used on the FLANN search hot path, but it +// remains public API, so keep it covered: many threads each key the shared +// pool with their own thread id and must get usable, correctly ordered heaps +// without tripping the internal use_count guard. +TEST(Flann_Heap, getPooledInstance_concurrent_usage) +{ + const int numThreads = 8; + const int capacity = 32; + std::vector threads; + std::vector ok(numThreads, 0); + + for (int t = 0; t < numThreads; ++t) + { + threads.emplace_back([&, t]() + { + bool good = true; + for (int iter = 0; iter < 200 && good; ++iter) + { + cv::Ptr> heap = + Heap::getPooledInstance(cv::utils::getThreadID(), capacity); + for (int v = capacity - 1; v >= 0; --v) + heap->insert(v); + + int prev = -1, out = 0, count = 0; + while (heap->popMin(out)) + { + if (out <= prev) { good = false; break; } + prev = out; + ++count; + } + if (count != capacity) good = false; + } + ok[t] = good ? 1 : 0; + }); + } + for (auto& th : threads) + th.join(); + + for (int t = 0; t < numThreads; ++t) + EXPECT_EQ((int)ok[t], 1) << "thread " << t << " produced incorrect heap results"; +} + +#endif // !OPENCV_DISABLE_THREAD_SUPPORT + +}} // namespace diff --git a/modules/geometry/src/homography_decomp.cpp b/modules/geometry/src/homography_decomp.cpp index 36b697e394..5f52e27c06 100644 --- a/modules/geometry/src/homography_decomp.cpp +++ b/modules/geometry/src/homography_decomp.cpp @@ -516,7 +516,7 @@ void filterHomographyDecompByVisibleRefpoints(InputArrayOfArrays _rotations, CV_Assert(pointsMask.empty() || pointsMask.checkVector(1, CV_8U) == npoints); const uchar* pointsMaskPtr = pointsMask.data; - std::vector solutionMask(nsolutions, (uchar)1); + AutoBuffer solutionMask(nsolutions, (uchar)1); std::vector normals(nsolutions); std::vector rotnorm(nsolutions); Mat R; @@ -558,7 +558,8 @@ void filterHomographyDecompByVisibleRefpoints(InputArrayOfArrays _rotations, if( solutionMask[i] ) possibleSolutions.push_back(i); - Mat(possibleSolutions).copyTo(_possibleSolutions); + constexpr int cvType = traits::Type::value; + Mat(static_cast(possibleSolutions.size()), 1, cvType, possibleSolutions.data()).copyTo(_possibleSolutions); } } //namespace cv diff --git a/modules/geometry/src/moments.cpp b/modules/geometry/src/moments.cpp index 7f4d26142d..b683f7ebcf 100644 --- a/modules/geometry/src/moments.cpp +++ b/modules/geometry/src/moments.cpp @@ -256,56 +256,65 @@ struct MomentsInTile_SIMD } }; +#endif // CV_SIMD128 + +#if (CV_SIMD || CV_SIMD_SCALABLE) + +namespace { +template +struct IotaInit { + T CV_DECL_ALIGNED(CV_SIMD_WIDTH) data[N]; + IotaInit() { for (int i = 0; i < N; i++) data[i] = (T)i; } +}; +static const IotaInit::max_nlanes> g_ix0_init; +} + template <> struct MomentsInTile_SIMD { - MomentsInTile_SIMD() - { - // nothing - } + MomentsInTile_SIMD() {} int operator() (const ushort * ptr, int len, int & x0, int & x1, int & x2, int64 & x3) { int x = 0; + const int vlanes32 = VTraits::vlanes(); + v_int32 v_delta = vx_setall_s32(vlanes32); + v_int32 v_ix0 = vx_load(g_ix0_init.data); + v_uint32 z = vx_setzero_u32(); + v_uint32 v_x0 = z, v_x1 = z, v_x2 = z; + v_uint64 v_x3 = vx_setzero_u64(); + + for ( ; x <= len - vlanes32; x += vlanes32 ) { - v_int32x4 v_delta = v_setall_s32(4), v_ix0 = v_int32x4(0, 1, 2, 3); - v_uint32x4 z = v_setzero_u32(), v_x0 = z, v_x1 = z, v_x2 = z; - v_uint64x2 v_x3 = v_reinterpret_as_u64(z); + v_int32 v_src = v_reinterpret_as_s32(vx_load_expand(ptr + x)); - for( ; x <= len - 4; x += 4 ) - { - v_int32x4 v_src = v_reinterpret_as_s32(v_load_expand(ptr + x)); + v_x0 = v_add(v_x0, v_reinterpret_as_u32(v_src)); + v_x1 = v_add(v_x1, v_reinterpret_as_u32(v_mul(v_src, v_ix0))); - v_x0 = v_add(v_x0, v_reinterpret_as_u32(v_src)); - v_x1 = v_add(v_x1, v_reinterpret_as_u32(v_mul(v_src, v_ix0))); + v_int32 v_ix1 = v_mul(v_ix0, v_ix0); + v_x2 = v_add(v_x2, v_reinterpret_as_u32(v_mul(v_src, v_ix1))); - v_int32x4 v_ix1 = v_mul(v_ix0, v_ix0); - v_x2 = v_add(v_x2, v_reinterpret_as_u32(v_mul(v_src, v_ix1))); + v_ix1 = v_mul(v_ix0, v_ix1); + v_src = v_mul(v_src, v_ix1); + v_uint64 v_lo, v_hi; + v_expand(v_reinterpret_as_u32(v_src), v_lo, v_hi); + v_x3 = v_add(v_x3, v_add(v_lo, v_hi)); - v_ix1 = v_mul(v_ix0, v_ix1); - v_src = v_mul(v_src, v_ix1); - v_uint64x2 v_lo, v_hi; - v_expand(v_reinterpret_as_u32(v_src), v_lo, v_hi); - v_x3 = v_add(v_x3, v_add(v_lo, v_hi)); - - v_ix0 = v_add(v_ix0, v_delta); - } - - x0 = v_reduce_sum(v_x0); - x1 = v_reduce_sum(v_x1); - x2 = v_reduce_sum(v_x2); - v_store_aligned(buf64, v_reinterpret_as_s64(v_x3)); - x3 = buf64[0] + buf64[1]; + v_ix0 = v_add(v_ix0, v_delta); } + x0 = v_reduce_sum(v_x0); + x1 = v_reduce_sum(v_x1); + x2 = v_reduce_sum(v_x2); + x3 = (int64)v_reduce_sum(v_x3); + + vx_cleanup(); return x; } - - int64 CV_DECL_ALIGNED(16) buf64[2]; }; -#endif +#endif // CV_SIMD || CV_SIMD_SCALABLE template #if defined __GNUC__ && __GNUC__ == 4 && __GNUC_MINOR__ >= 5 && __GNUC_MINOR__ < 9 diff --git a/modules/geometry/src/subdivision2d.cpp b/modules/geometry/src/subdivision2d.cpp index 78432f456d..ec27011119 100644 --- a/modules/geometry/src/subdivision2d.cpp +++ b/modules/geometry/src/subdivision2d.cpp @@ -793,6 +793,7 @@ void Subdiv2D::getLeadingEdgeList(std::vector& leadingEdgeList) const { leadingEdgeList.clear(); int i, total = (int)(qedges.size()*4); + //use a std::vector to benefit from the "bitset size/8" implementation std::vector edgemask(total, false); for( i = 4; i < total; i += 2 ) @@ -813,6 +814,7 @@ void Subdiv2D::getTriangleList(std::vector& triangleList) const { triangleList.clear(); int i, total = (int)(qedges.size()*4); + //use a std::vector to benefit from the "bitset size/8" implementation std::vector edgemask(total, false); Rect2f rect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); diff --git a/modules/imgcodecs/src/apple_conversions.mm b/modules/imgcodecs/src/apple_conversions.mm index 6b1834504b..75f837e7ee 100644 --- a/modules/imgcodecs/src/apple_conversions.mm +++ b/modules/imgcodecs/src/apple_conversions.mm @@ -28,7 +28,7 @@ CGImageRef MatToCGImage(const cv::Mat& image) { // Preserve alpha transparency, if exists bool alpha = image.channels() == 4; - CGBitmapInfo bitmapInfo = (alpha ? kCGImageAlphaLast : kCGImageAlphaNone) | kCGBitmapByteOrderDefault; + CGBitmapInfo bitmapInfo = (CGBitmapInfo)(alpha ? kCGImageAlphaLast : kCGImageAlphaNone) | (CGBitmapInfo)kCGBitmapByteOrderDefault; // Creating CGImage from cv::Mat CGImageRef imageRef = CGImageCreate(image.cols, @@ -73,8 +73,8 @@ void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist) { colorSpace = CGColorSpaceCreateDeviceRGB(); m.create(rows, cols, CV_8UC4); // 8 bits per component, 4 channels if (!alphaExist) - bitmapInfo = kCGImageAlphaNoneSkipLast | - kCGBitmapByteOrderDefault; + bitmapInfo = (CGBitmapInfo)kCGImageAlphaNoneSkipLast | + (CGBitmapInfo)kCGBitmapByteOrderDefault; else m = cv::Scalar(0); contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8, @@ -86,8 +86,8 @@ void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist) { { m.create(rows, cols, CV_8UC4); // 8 bits per component, 4 channels if (!alphaExist) - bitmapInfo = kCGImageAlphaNoneSkipLast | - kCGBitmapByteOrderDefault; + bitmapInfo = (CGBitmapInfo)kCGImageAlphaNoneSkipLast | + (CGBitmapInfo)kCGBitmapByteOrderDefault; else m = cv::Scalar(0); contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8, diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 5484031c50..ab0ac787c5 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -54,34 +54,37 @@ namespace { namespace cv { -static std::string HexStringToBytes(const char* hexstring, size_t expected_length); +static std::string HexStringToBytes(const char* hexstring, size_t hexstring_len, size_t expected_length); -// Converts the NULL terminated 'hexstring' which contains 2-byte character -// representations of hex values to raw data. +// Converts the 'hexstring' (of at most 'hexstring_len' bytes) which contains +// 2-byte character representations of hex values to raw data. // 'hexstring' may contain values consisting of [A-F][a-f][0-9] in pairs, // e.g., 7af2..., separated by any number of newlines. // 'expected_length' is the anticipated processed size. // On success the raw buffer is returned with its length equivalent to -// 'expected_length'. NULL is returned if the processed length is less than -// 'expected_length' or any character aside from those above is encountered. -// The returned buffer must be freed by the caller. +// 'expected_length'. An empty buffer is returned if the processed length is +// less than 'expected_length' or any character aside from those above is +// encountered. All reads are bounded by 'hexstring_len'. static std::string HexStringToBytes(const char* hexstring, - size_t expected_length) { + size_t hexstring_len, size_t expected_length) { const char* src = hexstring; + const char* const src_end = hexstring + hexstring_len; size_t actual_length = 0; std::string raw_data; raw_data.resize(expected_length); char* dst = const_cast(raw_data.data()); - for (; actual_length < expected_length && *src != '\0'; ++src) { - char* end; + while (actual_length < expected_length && src < src_end) { + if (*src == '\n') { ++src; continue; } + if (src + 1 >= src_end) break; // need two characters to form a byte char val[3]; - if (*src == '\n') continue; - val[0] = *src++; - val[1] = *src; + char* end; + val[0] = src[0]; + val[1] = src[1]; val[2] = '\0'; *dst++ = static_cast(strtol(val, &end, 16)); if (end != val + 2) break; + src += 2; ++actual_length; } @@ -138,31 +141,64 @@ const std::vector& ExifReader::getData() const } bool ExifReader::processRawProfile(const char* profile, size_t profile_len) { - const char* src = profile; - char* end; - int expected_length; - if (profile == nullptr || profile_len == 0) return false; + const char* src = profile; + const char* const profile_end = profile + profile_len; + // ImageMagick formats 'raw profiles' as - // '\n\n(%8lu)\n\n'. + // '\n\n(%8lu)\n\n'. Every scan below is bounded by + // 'profile_end' so that a malformed (e.g. not NUL-terminated, or missing a + // newline) profile can never be read past its buffer. if (*src != '\n') { - CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, expected '\\n' got '\\x%.2X'", *src)); + CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, expected '\\n' got '\\x%.2X'", (unsigned char)*src)); return false; } ++src; - // skip the profile name and extract the length. - while (*src != '\0' && *src++ != '\n') {} - expected_length = static_cast(strtol(src, &end, 10)); - if (*end != '\n') { - CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, expected '\\n' got '\\x%.2X'", *src)); + + // skip the profile name up to the next newline + while (src < profile_end && *src != '\n') ++src; + if (src >= profile_end) { // name not terminated within the buffer + CV_LOG_WARNING(NULL, "Malformed raw profile, profile name is not terminated"); return false; } - ++end; + ++src; - // 'end' now points to the profile payload. - std::string payload = HexStringToBytes(end, expected_length); - if (payload.size() == 0) return false; + // the length token runs up to the next newline; parse it from a bounded, + // NUL-terminated copy (strtol handles the leading spaces of the %8lu field). + const char* len_begin = src; + while (src < profile_end && *src != '\n') ++src; + if (src >= profile_end) { // length not terminated within the buffer + CV_LOG_WARNING(NULL, "Malformed raw profile, length field is not terminated"); + return false; + } + const std::string len_str(len_begin, src); + ++src; // 'src' now points to the hex payload + + char* parse_end = nullptr; + const long declared_length = strtol(len_str.c_str(), &parse_end, 10); + if (parse_end == len_str.c_str() || *parse_end != '\0') { // not a clean decimal number + CV_LOG_WARNING(NULL, "Malformed raw profile, length field is not a valid number"); + return false; + } + + // the payload starts with a 6-byte "Exif\0\0" header, so a shorter declared + // length underflows the size and pointer handed to parseExif() below. it also + // cannot be larger than the profile text that carries it (two hex characters + // encode one byte), so reject an over-large value to avoid a huge speculative + // allocation in HexStringToBytes(). + if (declared_length < 6 || static_cast(declared_length) > profile_len) { + CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, declared length %ld is out of range (profile size %llu)", + declared_length, (unsigned long long)profile_len)); + return false; + } + const size_t expected_length = static_cast(declared_length); + + std::string payload = HexStringToBytes(src, (size_t)(profile_end - src), expected_length); + if (payload.size() == 0) { // fewer hex bytes than the declared length + CV_LOG_WARNING(NULL, "Malformed raw profile, hex payload shorter than declared length"); + return false; + } return parseExif((unsigned char*)payload.c_str() + 6, expected_length - 6); } diff --git a/modules/imgcodecs/src/grfmt_pam.cpp b/modules/imgcodecs/src/grfmt_pam.cpp index efae09f033..abc48cd1ad 100644 --- a/modules/imgcodecs/src/grfmt_pam.cpp +++ b/modules/imgcodecs/src/grfmt_pam.cpp @@ -175,28 +175,28 @@ rgb_convert (void *src, void *target, int width, int target_channels, int target */ static void -basic_conversion (void *src, const struct channel_layout *layout, int src_sampe_size, +basic_conversion (void *src, const struct channel_layout *layout, int src_sample_size, int src_width, void *target, int target_channels, int target_depth, bool use_rgb) { switch (target_depth) { case CV_8U: { uchar *d = (uchar *)target, *s = (uchar *)src, - *end = ((uchar *)src) + src_width; + *end = ((uchar *)src) + src_width * src_sample_size; switch (target_channels) { case 1: - for( ; s < end; d += 3, s += src_sampe_size ) - d[0] = d[1] = d[2] = s[layout->graychan]; + for( ; s < end; d += 1, s += src_sample_size ) + d[0] = s[layout->graychan]; break; case 3: if (use_rgb) - for( ; s < end; d += 3, s += src_sampe_size ) { + for( ; s < end; d += 3, s += src_sample_size ) { d[0] = s[layout->rchan]; d[1] = s[layout->gchan]; d[2] = s[layout->bchan]; } else - for( ; s < end; d += 3, s += src_sampe_size ) { + for( ; s < end; d += 3, s += src_sample_size ) { d[0] = s[layout->bchan]; d[1] = s[layout->gchan]; d[2] = s[layout->rchan]; @@ -210,21 +210,21 @@ basic_conversion (void *src, const struct channel_layout *layout, int src_sampe_ case CV_16U: { ushort *d = (ushort *)target, *s = (ushort *)src, - *end = ((ushort *)src) + src_width; + *end = ((ushort *)src) + src_width * src_sample_size; switch (target_channels) { case 1: - for( ; s < end; d += 3, s += src_sampe_size ) - d[0] = d[1] = d[2] = s[layout->graychan]; + for( ; s < end; d += 1, s += src_sample_size ) + d[0] = s[layout->graychan]; break; case 3: if (use_rgb) - for( ; s < end; d += 3, s += src_sampe_size ) { + for( ; s < end; d += 3, s += src_sample_size ) { d[0] = s[layout->rchan]; d[1] = s[layout->gchan]; d[2] = s[layout->bchan]; } else - for( ; s < end; d += 3, s += src_sampe_size ) { + for( ; s < end; d += 3, s += src_sample_size ) { d[0] = s[layout->bchan]; d[1] = s[layout->gchan]; d[2] = s[layout->rchan]; diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index d176b4594b..c223ec9edd 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -566,6 +566,65 @@ TEST(Imgcodecs_Png, Read_Exif_From_Text) EXPECT_EQ(read_metadata[0], exif_data); } +static uint32_t pngCrc32(const uchar* data, size_t len) +{ + uint32_t crc = 0xFFFFFFFFu; + for (size_t i = 0; i < len; i++) + { + crc ^= data[i]; + for (int k = 0; k < 8; k++) + crc = (crc & 1u) ? ((crc >> 1) ^ 0xEDB88320u) : (crc >> 1); + } + return crc ^ 0xFFFFFFFFu; +} + +static void pngAppendBE32(std::vector& v, uint32_t x) +{ + v.push_back((uchar)(x >> 24)); v.push_back((uchar)(x >> 16)); + v.push_back((uchar)(x >> 8)); v.push_back((uchar)x); +} + +// Regression: a PNG "Raw profile type exif" text chunk whose declared length is +// far larger than the payload it carries must be rejected (no multi-GB +// speculative allocation / no out-of-bounds read in ExifReader::processRawProfile) +// while the image itself still decodes. +TEST(Imgcodecs_Png, Read_Exif_From_Text_oversized_length_rejected) +{ + Mat img(8, 8, CV_8UC3, Scalar(10, 20, 30)); + std::vector png; + ASSERT_TRUE(imencode(".png", img, png)); + ASSERT_GT(png.size(), 33u); // 8-byte signature + 25-byte IHDR chunk + + const std::string keyword = "Raw profile type exif"; + const std::string profile = "\nexif\n999999999\n41414141\n"; // 9e8 declared, tiny payload + std::vector data(keyword.begin(), keyword.end()); + data.push_back(0); // keyword / text separator + data.insert(data.end(), profile.begin(), profile.end()); + + std::vector chunk; + pngAppendBE32(chunk, (uint32_t)data.size()); + const char type[4] = { 't', 'E', 'X', 't' }; + chunk.insert(chunk.end(), type, type + 4); + chunk.insert(chunk.end(), data.begin(), data.end()); + std::vector crc_input(type, type + 4); + crc_input.insert(crc_input.end(), data.begin(), data.end()); + pngAppendBE32(chunk, pngCrc32(crc_input.data(), crc_input.size())); + + // splice the tEXt chunk right after IHDR (valid placement for ancillary chunks) + png.insert(png.begin() + 33, chunk.begin(), chunk.end()); + + std::vector metadata_types; + std::vector > metadata; + Mat decoded; + ASSERT_NO_THROW(decoded = imdecodeWithMetadata(png, metadata_types, metadata, IMREAD_COLOR)); + ASSERT_FALSE(decoded.empty()); + EXPECT_EQ(decoded.rows, 8); + EXPECT_EQ(decoded.cols, 8); + // the malformed profile must not produce EXIF metadata + for (size_t i = 0; i < metadata_types.size(); i++) + EXPECT_NE(metadata_types[i], IMAGE_METADATA_EXIF); +} + static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern) { size_t plen = pattern.size(); diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index 08accd3bbd..8c50bf214b 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -564,6 +564,37 @@ TEST(Imgcodecs_Pam, read_write) remove(writefile.c_str()); remove(writefile_no_param.c_str()); } + +// Regression test: a 2-channel (GRAYSCALE_ALPHA) PAM decoded as single channel +// used to overflow the output row in basic_conversion() (3 bytes written per +// source pixel into a 1-channel row). Verify it decodes safely and correctly. +TEST(Imgcodecs_Pam, decode_graya_as_gray) +{ + const int width = 9, height = 3; // odd width to expose off-by-row overflow + std::string header = cv::format( + "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 2\nMAXVAL 255\n" + "TUPLTYPE GRAYSCALE_ALPHA\nENDHDR\n", width, height); + + std::vector buf(header.begin(), header.end()); + Mat gray_ref(height, width, CV_8UC1); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + uchar gray = (uchar)((y * width + x) * 7 + 1); + uchar alpha = (uchar)(255 - gray); + gray_ref.at(y, x) = gray; + buf.push_back(gray); // channel 0: gray + buf.push_back(alpha); // channel 1: alpha (must be ignored) + } + + Mat decoded; + ASSERT_NO_THROW(decoded = imdecode(buf, IMREAD_GRAYSCALE)); + ASSERT_FALSE(decoded.empty()); + EXPECT_EQ(width, decoded.cols); + EXPECT_EQ(height, decoded.rows); + EXPECT_EQ(1, decoded.channels()); + EXPECT_EQ(0, cvtest::norm(gray_ref, decoded, NORM_INF)); +} #endif #ifdef HAVE_IMGCODEC_PFM diff --git a/modules/imgproc/CMakeLists.txt b/modules/imgproc/CMakeLists.txt index c0cdf03090..e1b812a39f 100644 --- a/modules/imgproc/CMakeLists.txt +++ b/modules/imgproc/CMakeLists.txt @@ -4,15 +4,16 @@ ocv_add_dispatched_file(accum SSE4_1 AVX AVX2) ocv_add_dispatched_file(bilateral_filter SSE2 AVX2 AVX512_SKX AVX512_ICL) ocv_add_dispatched_file(box_filter SSE2 SSE4_1 AVX2 AVX512_SKX) ocv_add_dispatched_file(filter SSE2 SSE4_1 AVX2) -ocv_add_dispatched_file(color_hsv SSE2 SSE4_1 AVX2) -ocv_add_dispatched_file(color_rgb SSE2 SSE4_1 AVX2) -ocv_add_dispatched_file(color_yuv SSE2 SSE4_1 AVX2) +ocv_add_dispatched_file(color_hsv SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL) +ocv_add_dispatched_file(color_rgb SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL) +ocv_add_dispatched_file(color_yuv SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL) ocv_add_dispatched_file(median_blur SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL) ocv_add_dispatched_file(morph SSE2 SSE4_1 AVX2) ocv_add_dispatched_file(smooth SSE2 SSE4_1 AVX2 AVX512_ICL) ocv_add_dispatched_file(sumpixels SSE2 AVX2 AVX512_SKX) ocv_add_dispatched_file(warp_kernels SSE2 SSE4_1 AVX2 NEON NEON_FP16 RVV LASX) ocv_add_dispatched_file(undistort SSE2 AVX2) +ocv_add_dispatched_file(equalize_hist AVX512_ICL) ocv_define_module(imgproc opencv_core opencv_geometry WRAP java objc python js) diff --git a/modules/imgproc/perf/perf_emd.cpp b/modules/imgproc/perf/perf_emd.cpp new file mode 100644 index 0000000000..1952a58867 --- /dev/null +++ b/modules/imgproc/perf/perf_emd.cpp @@ -0,0 +1,37 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +#include "perf_precomp.hpp" +#include + +namespace opencv_test { namespace { + +typedef tuple EMD_Size_Dim_t; +typedef perf::TestBaseWithParam EMD_Fixture; + +PERF_TEST_P(EMD_Fixture, L1_Distance, testing::Combine( + testing::Values(100, 500, 1000), + testing::Values(3, 64) +)) +{ + int size = get<0>(GetParam()); + int dims = get<1>(GetParam()); + + Mat sign1(size, dims + 1, CV_32FC1); + Mat sign2(size, dims + 1, CV_32FC1); + + theRNG().fill(sign1, RNG::UNIFORM, 0.1, 1.0); + theRNG().fill(sign2, RNG::UNIFORM, 0.1, 1.0); + + declare.in(sign1, sign2); + + TEST_CYCLE() + { + cv::EMD(sign1, sign2, cv::DIST_L1); + } + + SANITY_CHECK_NOTHING(); +} + +}} \ No newline at end of file diff --git a/modules/imgproc/src/bilateral_filter.dispatch.cpp b/modules/imgproc/src/bilateral_filter.dispatch.cpp index 1cc21521f6..da19e1e394 100644 --- a/modules/imgproc/src/bilateral_filter.dispatch.cpp +++ b/modules/imgproc/src/bilateral_filter.dispatch.cpp @@ -98,8 +98,8 @@ static bool ocl_bilateralFilter_8u(InputArray _src, OutputArray _dst, int d, return false; copyMakeBorder(src, temp, radius, radius, radius, radius, borderType); - std::vector _space_weight(d * d); - std::vector _space_ofs(d * d); + AutoBuffer _space_weight(d * d); + AutoBuffer _space_ofs(d * d); float * const space_weight = &_space_weight[0]; int * const space_ofs = &_space_ofs[0]; @@ -188,9 +188,9 @@ bilateralFilter_8u( const Mat& src, Mat& dst, int d, Mat temp; copyMakeBorder( src, temp, radius, radius, radius, radius, borderType ); - std::vector _color_weight(cn*256); - std::vector _space_weight(d*d); - std::vector _space_ofs(d*d); + AutoBuffer _color_weight(cn*256); + AutoBuffer _space_weight(d*d); + AutoBuffer _space_ofs(d*d); float* color_weight = &_color_weight[0]; float* space_weight = &_space_weight[0]; int* space_ofs = &_space_ofs[0]; @@ -283,15 +283,15 @@ bilateralFilter_32f( const Mat& src, Mat& dst, int d, copyMakeBorder( src, temp, radius, radius, radius, radius, borderType ); // allocate lookup tables - std::vector _space_weight(d*d); - std::vector _space_ofs(d*d); + AutoBuffer _space_weight(d*d); + AutoBuffer _space_ofs(d*d); float* space_weight = &_space_weight[0]; int* space_ofs = &_space_ofs[0]; // assign a length which is slightly more than needed len = (float)(maxValSrc - minValSrc) * cn; kExpNumBins = kExpNumBinsPerChannel * cn; - std::vector _expLUT(kExpNumBins+2); + AutoBuffer _expLUT(kExpNumBins+2); float* expLUT = &_expLUT[0]; scale_index = kExpNumBins/len; diff --git a/modules/imgproc/src/color_hsv.simd.hpp b/modules/imgproc/src/color_hsv.simd.hpp index f3d952a7b8..4fc6a1f2f9 100644 --- a/modules/imgproc/src/color_hsv.simd.hpp +++ b/modules/imgproc/src/color_hsv.simd.hpp @@ -87,9 +87,18 @@ struct RGB2HSV_b #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); - for ( ; i <= n - vsize; + for ( ; i < n; i += vsize, src += scn*vsize, dst += 3*vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * 3; + } v_uint8 b, g, r; if(scn == 4) { @@ -297,8 +306,17 @@ struct RGB2HSV_f #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); - for ( ; i <= n - 3*vsize; i += 3*vsize, src += scn * vsize) + const float* const src0 = src; + for ( ; i < n; i += 3*vsize, src += scn * vsize) { + if ( i > n - 3*vsize ) { + if (i == 0 || src0 == dst) { + break; + } + int backup = (i - (n - 3*vsize)) / 3; + i = n - 3*vsize; + src -= backup * scn; + } v_float32 r, g, b, a; if(scn == 4) { @@ -463,8 +481,17 @@ struct HSV2RGB_f #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); v_float32 valpha = vx_setall_f32(alpha); - for (; i <= n - vsize*3; i += vsize*3, dst += dcn * vsize) + float* const dst0 = dst; + for (; i < n; i += vsize*3, dst += dcn * vsize) { + if ( i > n - vsize*3 ) { + if (i == 0 || src == dst0) { + break; + } + int backup = (i - (n - vsize*3)) / 3; + i = n - vsize*3; + dst -= backup * dcn; + } v_float32 h, s, v, b, g, r; v_load_deinterleave(src + i, h, s, v); @@ -709,9 +736,18 @@ struct RGB2HLS_f const int vsize = VTraits::vlanes(); v_float32 vhscale = vx_setall_f32(hscale); - for ( ; i <= n - vsize; + for ( ; i < n; i += vsize, src += scn * vsize, dst += 3 * vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * 3; + } v_float32 r, g, b, h, l, s; if(scn == 4) @@ -1005,8 +1041,17 @@ struct HLS2RGB_f #if (CV_SIMD || CV_SIMD_SCALABLE) static const int vsize = VTraits::vlanes(); - for (; i <= n - vsize; i += vsize, src += 3*vsize, dst += dcn*vsize) + for (; i < n; i += vsize, src += 3*vsize, dst += dcn*vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * 3; + dst -= backup * dcn; + } v_float32 h, l, s, r, g, b; v_load_deinterleave(src, h, l, s); diff --git a/modules/imgproc/src/color_lab.cpp b/modules/imgproc/src/color_lab.cpp index 9dbfc83ba3..57b11305f1 100644 --- a/modules/imgproc/src/color_lab.cpp +++ b/modules/imgproc/src/color_lab.cpp @@ -1134,20 +1134,23 @@ static LABLUVLUT_s16_t initLUTforLABLUVs16(const softfloat & un, const softfloat AutoBuffer RGB2Labprev(LAB_LUT_DIM*LAB_LUT_DIM*LAB_LUT_DIM*3); AutoBuffer RGB2Luvprev(LAB_LUT_DIM*LAB_LUT_DIM*LAB_LUT_DIM*3); - for(int p = 0; p < LAB_LUT_DIM; p++) + + softfloat gammaTab[LAB_LUT_DIM]; + for(int n = 0; n < LAB_LUT_DIM; n++) + gammaTab[n] = applyGamma(softfloat(n)/lld); + + cv::parallel_for_(cv::Range(0, LAB_LUT_DIM), [&](const cv::Range& prange) + { + for(int p = prange.start; p < prange.end; p++) { for(int q = 0; q < LAB_LUT_DIM; q++) { for(int r = 0; r < LAB_LUT_DIM; r++) { int idx = p*3 + q*LAB_LUT_DIM*3 + r*LAB_LUT_DIM*LAB_LUT_DIM*3; - softfloat R = softfloat(p)/lld; - softfloat G = softfloat(q)/lld; - softfloat B = softfloat(r)/lld; - - R = applyGamma(R); - G = applyGamma(G); - B = applyGamma(B); + softfloat R = gammaTab[p]; + softfloat G = gammaTab[q]; + softfloat B = gammaTab[r]; //RGB 2 Lab LUT building { @@ -1188,6 +1191,7 @@ static LABLUVLUT_s16_t initLUTforLABLUVs16(const softfloat & un, const softfloat } } } + }); int16_t *RGB2LabLUT_s16 = cv::allocSingleton(LAB_LUT_DIM*LAB_LUT_DIM*LAB_LUT_DIM*3*8); int16_t *RGB2LuvLUT_s16 = cv::allocSingleton(LAB_LUT_DIM*LAB_LUT_DIM*LAB_LUT_DIM*3*8); diff --git a/modules/imgproc/src/color_rgb.simd.hpp b/modules/imgproc/src/color_rgb.simd.hpp index d0c8c6a51a..ea97af62b1 100644 --- a/modules/imgproc/src/color_rgb.simd.hpp +++ b/modules/imgproc/src/color_rgb.simd.hpp @@ -125,9 +125,18 @@ struct RGB2RGB #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*scn, dst += vsize*dcn) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * dcn; + } vt a, b, c, d; if(scn == 4) { @@ -189,9 +198,18 @@ struct RGB5x52RGB #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); v_uint8 vz = vx_setzero_u8(), vn0 = vx_setall_u8(255); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*sizeof(ushort), dst += vsize*dcn) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * sizeof(ushort); + dst -= backup * dcn; + } v_uint16 t0 = v_reinterpret_as_u16(vx_load(src)); v_uint16 t1 = v_reinterpret_as_u16(vx_load(src + sizeof(ushort)*VTraits::vlanes())); @@ -296,9 +314,18 @@ struct RGB2RGB5x5 v_uint16 vn7 = vx_setall_u16((ushort)(~7)); v_uint16 vz = vx_setzero_u16(); v_uint8 v7 = vx_setall_u8((uchar)(~7)); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*scn, dst += vsize*sizeof(ushort)) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * sizeof(ushort); + } v_uint8 r, g, b, a; if(scn == 3) { @@ -387,9 +414,18 @@ struct Gray2RGB #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); vt valpha = v_set<_Tp>::set(alpha); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize, dst += vsize*dcn) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup; + dst -= backup * dcn; + } vt g = vx_load(src); if(dcn == 3) @@ -429,9 +465,18 @@ struct Gray2RGB5x5 #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); v_uint16 v3 = vx_setall_u16((ushort)(~3)); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize, dst += vsize*sizeof(ushort)) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup; + dst -= backup * sizeof(ushort); + } v_uint8 t8 = vx_load_low(src); v_uint16 t = v_expand_low(t8); @@ -500,9 +545,18 @@ struct RGB5x52Gray v_zip(vx_setall_s16(RY), vx_setall_s16( 1), r12y, dummy); v_int16 delta = vx_setall_s16(1 << (shift-1)); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*sizeof(ushort), dst += vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * sizeof(ushort); + dst -= backup; + } v_uint16 t = vx_load((ushort*)src); v_uint16 r, g, b; @@ -616,9 +670,18 @@ struct RGB2Gray #if (CV_SIMD || CV_SIMD_SCALABLE) const int vsize = VTraits::vlanes(); v_float32 rv = vx_setall_f32(cr), gv = vx_setall_f32(cg), bv = vx_setall_f32(cb); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*scn, dst += vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup; + } v_float32 r, g, b, a; if(scn == 3) { @@ -680,9 +743,18 @@ struct RGB2Gray v_zip(vx_setall_s16(cr), vx_setall_s16( 1), r12y, dummy); v_int16 delta = vx_setall_s16(1 << (shift-1)); - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += scn*vsize, dst += vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup; + } v_uint8 r, g, b, a; if(scn == 3) { @@ -780,9 +852,18 @@ struct RGB2Gray v_int16 delta = vx_setall_s16(1 << (shift-1)); - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += scn*vsize, dst += vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup; + } v_uint16 r, g, b, a; if(scn == 3) { @@ -876,9 +957,18 @@ struct RGBA2mRGBA // processing 4 registers per loop cycle is about 10% faster // than processing 1 register - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += 4*vsize, dst += 4*vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * 4; + dst -= backup * 4; + } v_uint8 v[4]; for(int j = 0; j < 4; j++) v[j] = vx_load(src + j*vsize); @@ -980,9 +1070,18 @@ struct mRGBA2RGBA v_uint8 amask = v_reinterpret_as_u8(vx_setall_u32(0xFF000000)); v_uint8 vmax = vx_setall_u8(max_val); - for( ; i <= n-vsize/4; + for( ; i < n; i += vsize/4, src += vsize, dst += vsize) { + if ( i > n - vsize/4 ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize/4); + i = n - vsize/4; + src -= backup * 4; + dst -= backup * 4; + } v_uint8 s = vx_load(src + 0*vsize); // r0,g0,b0,a0,r1,g1,b1,a1 => 00,00,00,a0,00,00,00,a1 => diff --git a/modules/imgproc/src/color_yuv.simd.hpp b/modules/imgproc/src/color_yuv.simd.hpp index 63f24262a9..ba3133c77d 100644 --- a/modules/imgproc/src/color_yuv.simd.hpp +++ b/modules/imgproc/src/color_yuv.simd.hpp @@ -154,9 +154,18 @@ struct RGB2YCrCb_f v_float32 vc3 = vx_setall_f32(C3), vc4 = vx_setall_f32(C4); v_float32 vdelta = vx_setall_f32(delta); const int vsize = VTraits::vlanes(); - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += vsize*scn, dst += vsize*3) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * 3; + } v_float32 b, g, r, dummy; if(scn == 3) { @@ -286,9 +295,18 @@ struct RGB2YCrCb_i v_int32 vc4 = vx_setall_s32(C4); v_int32 vdd = vx_setall_s32(sdelta + descale); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*scn, dst += vsize*3) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * 3; + } v_uint16 r, g, b, a; if(scn == 3) { @@ -421,9 +439,18 @@ struct RGB2YCrCb_i v_int16 vdescale = vx_setall_s16(descaleShift); - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += scn*vsize, dst += 3*vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * scn; + dst -= backup * 3; + } v_uint8 r, g, b, a; if(scn == 3) { @@ -620,9 +647,18 @@ struct YCrCb2RGB_f v_float32 vdelta = vx_setall_f32(delta); v_float32 valpha = vx_setall_f32(alpha); const int vsize = VTraits::vlanes(); - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += vsize*3, dst += vsize*dcn) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * 3; + dst -= backup * dcn; + } v_float32 y, cr, cb; if(yuvOrder) v_load_deinterleave(src, y, cb, cr); @@ -743,9 +779,18 @@ struct YCrCb2RGB_i // to fit in short by short multiplication v_int16 vc3 = vx_setall_s16(yuvOrder ? (short)(C3-(1 << 15)) : (short)C3); - for( ; i <= n-vsize; + for( ; i < n; i += vsize, src += 3*vsize, dst += dcn*vsize) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * 3; + dst -= backup * dcn; + } v_uint8 y, cr, cb; if(yuvOrder) { @@ -889,9 +934,18 @@ struct YCrCb2RGB_i // to fit in short by short multiplication v_int16 vc3 = vx_setall_s16(yuvOrder ? (short)(C3-(1 << 15)) : (short)C3); v_int32 vdescale = vx_setall_s32(descaleShift); - for(; i <= n-vsize; + for(; i < n; i += vsize, src += vsize*3, dst += vsize*dcn) { + if ( i > n - vsize ) { + if (i == 0 || src == dst) { + break; + } + int backup = i - (n - vsize); + i = n - vsize; + src -= backup * 3; + dst -= backup * dcn; + } v_uint16 y, cr, cb; if(yuvOrder) { diff --git a/modules/imgproc/src/connectedcomponents.cpp b/modules/imgproc/src/connectedcomponents.cpp index e6285bdecd..065e871c5a 100644 --- a/modules/imgproc/src/connectedcomponents.cpp +++ b/modules/imgproc/src/connectedcomponents.cpp @@ -1153,10 +1153,10 @@ namespace cv{ //Array used to store info and labeled pixel by each thread. //Different threads affect different memory location of chunksSizeAndLabels const int chunksSizeAndLabelsSize = roundUp(h, 2); - std::vector chunksSizeAndLabels(chunksSizeAndLabelsSize); + AutoBuffer chunksSizeAndLabels(chunksSizeAndLabelsSize); //Tree of labels - std::vector P(Plength, 0); + AutoBuffer P(Plength, 0); //First label is for background //P[0] = 0; @@ -1176,7 +1176,7 @@ namespace cv{ } //Array for statistics data - std::vector sopArray(h); + AutoBuffer sopArray(h); sop.init(nLabels); //Second scan @@ -1218,7 +1218,7 @@ namespace cv{ // ............ const size_t Plength = size_t(((h + 1) / 2) * size_t((w + 1) / 2)) + 1; - std::vector P_(Plength, 0); + AutoBuffer P_(Plength, 0); LabelT *P = P_.data(); //P[0] = 0; LabelT lunique = 1; @@ -1782,10 +1782,10 @@ namespace cv{ //Array used to store info and labeled pixel by each thread. //Different threads affect different memory location of chunksSizeAndLabels - std::vector chunksSizeAndLabels(roundUp(h, 2)); + AutoBuffer chunksSizeAndLabels(roundUp(h, 2)); //Tree of labels - std::vector P_(Plength, 0); + AutoBuffer P_(Plength, 0); LabelT* P = P_.data(); //First label is for background //P[0] = 0; @@ -1806,7 +1806,7 @@ namespace cv{ } //Array for statistics dataof threads - std::vector sopArray(h); + AutoBuffer sopArray(h); sop.init(nLabels); //Second scan @@ -1842,7 +1842,7 @@ namespace cv{ // ............ const size_t Plength = size_t((size_t(h) * size_t(w) + 1) / 2) + 1; - std::vector P_(Plength, 0); + AutoBuffer P_(Plength, 0); LabelT* P = P_.data(); P[0] = 0; LabelT lunique = 1; @@ -2315,10 +2315,10 @@ namespace cv{ //Array used to store info and labeled pixel by each thread. //Different threads affect different memory location of chunksSizeAndLabels - std::vector chunksSizeAndLabels(roundUp(h, 2)); + AutoBuffer chunksSizeAndLabels(roundUp(h, 2)); //Tree of labels - std::vector P_(Plength, 0); + AutoBuffer P_(Plength, 0); LabelT *P = P_.data(); //First label is for background //P[0] = 0; @@ -2352,7 +2352,7 @@ namespace cv{ } //Array for statistics dataof threads - std::vector sopArray(h); + AutoBuffer sopArray(h); sop.init(nLabels); //Second scan @@ -2387,7 +2387,7 @@ namespace cv{ //Obviously, 4-way connectivity upper bound is also good for 8-way connectivity labeling const size_t Plength = (size_t(h) * size_t(w) + 1) / 2 + 1; //array P for equivalences resolution - std::vector P_(Plength, 0); + AutoBuffer P_(Plength, 0); LabelT *P = P_.data(); //first label is for background pixels //P[0] = 0; @@ -4265,10 +4265,10 @@ namespace cv{ //Array used to store info and labeled pixel by each thread. //Different threads affect different memory location of chunksSizeAndLabels const int chunksSizeAndLabelsSize = roundUp(h, 2); - std::vector chunksSizeAndLabels(chunksSizeAndLabelsSize); + AutoBuffer chunksSizeAndLabels(chunksSizeAndLabelsSize); //Tree of labels - std::vector P(Plength, 0); + AutoBuffer P(Plength, 0); //First label is for background //P[0] = 0; @@ -4288,7 +4288,7 @@ namespace cv{ } //Array for statistics data - std::vector sopArray(h); + AutoBuffer sopArray(h); sop.init(nLabels); //Second scan @@ -4323,7 +4323,7 @@ namespace cv{ //............ const size_t Plength = size_t(((h + 1) / 2) * size_t((w + 1) / 2)) + 1; - std::vector P_(Plength, 0); + AutoBuffer P_(Plength, 0); LabelT *P = P_.data(); //P[0] = 0; LabelT lunique = 1; diff --git a/modules/imgproc/src/deriv.cpp b/modules/imgproc/src/deriv.cpp index ee87bf6af2..a61d968a6f 100644 --- a/modules/imgproc/src/deriv.cpp +++ b/modules/imgproc/src/deriv.cpp @@ -101,7 +101,7 @@ static void getSobelKernels( OutputArray _kx, OutputArray _ky, if( _ksize % 2 == 0 || _ksize > 31 ) CV_Error( cv::Error::StsOutOfRange, "The kernel size must be odd and not larger than 31" ); - std::vector kerI(std::max(ksizeX, ksizeY) + 1); + AutoBuffer kerI(std::max(ksizeX, ksizeY) + 1); CV_Assert( dx >= 0 && dy >= 0 && dx+dy > 0 ); @@ -181,98 +181,6 @@ cv::Ptr cv::createDerivFilter(int srcType, int dstType, kx, ky, Point(-1,-1), 0, borderType ); } - -#if 0 //defined HAVE_IPP -namespace cv -{ - -static bool ipp_Deriv(InputArray _src, OutputArray _dst, int dx, int dy, int ksize, double scale, double delta, int borderType) -{ -#ifdef HAVE_IPP_IW - CV_INSTRUMENT_REGION_IPP(); - - ::ipp::IwiSize size(_src.size().width, _src.size().height); - IppDataType srcType = ippiGetDataType(_src.depth()); - IppDataType dstType = ippiGetDataType(_dst.depth()); - int channels = _src.channels(); - bool useScale = false; - bool useScharr = false; - - if(channels != _dst.channels() || channels > 1) - return false; - - if(fabs(delta) > FLT_EPSILON || fabs(scale-1) > FLT_EPSILON) - useScale = true; - - if(ksize <= 0) - { - ksize = 3; - useScharr = true; - } - - IppiMaskSize maskSize = ippiGetMaskSize(ksize, ksize); - if((int)maskSize < 0) - return false; - -#if IPP_VERSION_X100 <= 201703 - // Bug with mirror wrap - if(borderType == BORDER_REFLECT_101 && (ksize/2+1 > size.width || ksize/2+1 > size.height)) - return false; -#endif - - IwiDerivativeType derivType = ippiGetDerivType(dx, dy, (useScharr)?false:true); - if((int)derivType < 0) - return false; - - // Acquire data and begin processing - try - { - Mat src = _src.getMat(); - Mat dst = _dst.getMat(); - ::ipp::IwiImage iwSrc = ippiGetImage(src); - ::ipp::IwiImage iwDst = ippiGetImage(dst); - ::ipp::IwiImage iwSrcProc = iwSrc; - ::ipp::IwiImage iwDstProc = iwDst; - ::ipp::IwiBorderSize borderSize(maskSize); - ::ipp::IwiBorderType ippBorder(ippiGetBorder(iwSrc, borderType, borderSize)); - if(!ippBorder) - return false; - - if(srcType == ipp8u && dstType == ipp8u) - { - iwDstProc.Alloc(iwDst.m_size, ipp16s, channels); - useScale = true; - } - else if(srcType == ipp8u && dstType == ipp32f) - { - iwSrc -= borderSize; - iwSrcProc.Alloc(iwSrc.m_size, ipp32f, channels); - CV_INSTRUMENT_FUN_IPP(::ipp::iwiScale, iwSrc, iwSrcProc, 1, 0, ::ipp::IwiScaleParams(ippAlgHintFast)); - iwSrcProc += borderSize; - } - - if(useScharr) - CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterScharr, iwSrcProc, iwDstProc, derivType, maskSize, ::ipp::IwDefault(), ippBorder); - else - CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterSobel, iwSrcProc, iwDstProc, derivType, maskSize, ::ipp::IwDefault(), ippBorder); - - if(useScale) - CV_INSTRUMENT_FUN_IPP(::ipp::iwiScale, iwDstProc, iwDst, scale, delta, ::ipp::IwiScaleParams(ippAlgHintFast)); - } - catch (const ::ipp::IwException &) - { - return false; - } - - return true; -#else - CV_UNUSED(_src); CV_UNUSED(_dst); CV_UNUSED(dx); CV_UNUSED(dy); CV_UNUSED(ksize); CV_UNUSED(scale); CV_UNUSED(delta); CV_UNUSED(borderType); - return false; -#endif -} -} -#endif - #ifdef HAVE_OPENCL namespace cv { @@ -378,8 +286,6 @@ void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy, CALL_HAL(sobel, cv_hal_sobel, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, ddepth, cn, ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, dx, dy, ksize, scale, delta, borderType&~BORDER_ISOLATED); - //CV_IPP_RUN_FAST(ipp_Deriv(src, dst, dx, dy, ksize, scale, delta, borderType)); - sepFilter2D(src, dst, ddepth, kx, ky, Point(-1, -1), delta, borderType ); } @@ -430,8 +336,6 @@ void cv::Scharr( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy, CALL_HAL(scharr, cv_hal_scharr, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, ddepth, cn, ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, dx, dy, scale, delta, borderType&~BORDER_ISOLATED); - //CV_IPP_RUN_FAST(ipp_Deriv(src, dst, dx, dy, 0, scale, delta, borderType)); - sepFilter2D( src, dst, ddepth, kx, ky, Point(-1, -1), delta, borderType ); } diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp index a79bffd96f..24369cf343 100644 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -1845,6 +1845,7 @@ void line( InputOutputArray _img, Point pt1, Point pt2, const Scalar& color, void arrowedLine(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness, int line_type, int shift, double tipLength) { + CV_Assert( tipLength > 0.0 && tipLength <= 1.0 ); CV_INSTRUMENT_REGION(); const double tipSize = norm(pt1-pt2)*tipLength; // Factor to normalize the size of the tip depending on the length of the arrow diff --git a/modules/imgproc/src/emd_new.cpp b/modules/imgproc/src/emd_new.cpp index 59a6b24342..81528d01b0 100644 --- a/modules/imgproc/src/emd_new.cpp +++ b/modules/imgproc/src/emd_new.cpp @@ -442,119 +442,78 @@ double EMDSolver::calcFlow(Mat* flow_) const int EMDSolver::findBasicVars() const { - int i, j; - int u_cfound, v_cfound; - Node1D u0_head, u1_head, *cur_u, *prev_u; - Node1D v0_head, v1_head, *cur_v, *prev_v; - bool found; - CV_Assert(u != 0 && v != 0); - /* initialize the rows list (u) and the columns list (v) */ - u0_head.next = u; - for (i = 0; i < ssize; i++) - { - u[i].next = u + i + 1; - } - u[ssize - 1].next = 0; - u1_head.next = 0; + // 1. Initialize status flags using contiguous memory to eliminate pointer chasing + AutoBuffer computed_buf(ssize + dsize); + char* row_computed = computed_buf.data(); + char* col_computed = computed_buf.data() + ssize; + memset(row_computed, 0, ssize + dsize); - v0_head.next = ssize > 1 ? v + 1 : 0; - for (i = 1; i < dsize; i++) - { - v[i].next = v + i + 1; - } - v[dsize - 1].next = 0; - v1_head.next = 0; + // 2. Create BFS queues + AutoBuffer queue_buf(ssize + dsize); + int* row_queue = queue_buf.data(); + int* col_queue = queue_buf.data() + ssize; - /* there are ssize+dsize variables but only ssize+dsize-1 independent equations, - so set v[0]=0 */ + int row_head = 0, row_tail = 0; + int col_head = 0, col_tail = 0; + + // Initial condition: enqueue column 0 as the root node and set its value to 0 v[0].val = 0; - v1_head.next = v; - v1_head.next->next = 0; + col_computed[0] = true; + col_queue[col_tail++] = 0; - /* loop until all variables are found */ - u_cfound = v_cfound = 0; - while (u_cfound < ssize || v_cfound < dsize) + int u_cfound = 0; + int v_cfound = 1; + + // 3. Dual-queue interactive BFS traversal over the spanning tree (Time Complexity: O(N + M)) + while (row_head < row_tail || col_head < col_tail) { - found = false; - if (v_cfound < dsize) + // Process currently marked columns to update their connected rows + while (col_head < col_tail) { - /* loop over all marked columns */ - prev_v = &v1_head; - cur_v = v1_head.next; - found = found || (cur_v != 0); - for (; cur_v != 0; cur_v = cur_v->next) - { - float cur_v_val = cur_v->val; + int j = col_queue[col_head++]; + float cur_v_val = v[j].val; - j = (int)(cur_v - v); - /* find the variables in column j */ - prev_u = &u0_head; - for (cur_u = u0_head.next; cur_u != 0;) + // Use adjacency list cols_x to directly access rows connected to column j, avoiding full scans + for (Node2D* xp = cols_x[j]; xp != 0; xp = xp->next[1]) + { + int i = xp->i; + if (!row_computed[i]) { - i = (int)(cur_u - u); - if (getIsX(i, j)) - { - /* compute u[i] */ - cur_u->val = getCost(i, j) - cur_v_val; - /* ...and add it to the marked list */ - prev_u->next = cur_u->next; - cur_u->next = u1_head.next; - u1_head.next = cur_u; - cur_u = prev_u->next; - } - else - { - prev_u = cur_u; - cur_u = cur_u->next; - } + u[i].val = getCost(i, j) - cur_v_val; + row_computed[i] = true; + row_queue[row_tail++] = i; // Enqueue the newly resolved row + u_cfound++; } - prev_v->next = cur_v->next; - v_cfound++; } } - if (u_cfound < ssize) + // Process currently marked rows to update their connected columns + while (row_head < row_tail) { - /* loop over all marked rows */ - prev_u = &u1_head; - cur_u = u1_head.next; - found = found || (cur_u != 0); - for (; cur_u != 0; cur_u = cur_u->next) + int i = row_queue[row_head++]; + float cur_u_val = u[i].val; + + // Use adjacency list rows_x to directly access columns connected to row i + for (Node2D* xp = rows_x[i]; xp != 0; xp = xp->next[0]) { - float cur_u_val = cur_u->val; - i = (int)(cur_u - u); - /* find the variables in rows i */ - prev_v = &v0_head; - for (cur_v = v0_head.next; cur_v != 0;) + int j = xp->j; + if (!col_computed[j]) { - j = (int)(cur_v - v); - if (getIsX(i, j)) - { - /* compute v[j] */ - cur_v->val = getCost(i, j) - cur_u_val; - /* ...and add it to the marked list */ - prev_v->next = cur_v->next; - cur_v->next = v1_head.next; - v1_head.next = cur_v; - cur_v = prev_v->next; - } - else - { - prev_v = cur_v; - cur_v = cur_v->next; - } + v[j].val = getCost(i, j) - cur_u_val; + col_computed[j] = true; + col_queue[col_tail++] = j; // Enqueue the newly resolved column + v_cfound++; } - prev_u->next = cur_u->next; - u_cfound++; } } - - if (!found) - return -1; } + // If the number of traversed nodes is insufficient, the graph is disconnected and the spanning tree is incomplete + if (u_cfound < ssize || v_cfound < dsize) + return -1; + return 0; } @@ -1008,4 +967,4 @@ float cv::wrapperEMD(InputArray _sign1, OutputArray _flow) { return EMD(_sign1, _sign2, distType, _cost, lowerBound.get(), _flow); -} +} \ No newline at end of file diff --git a/modules/imgproc/src/equalize_hist.dispatch.cpp b/modules/imgproc/src/equalize_hist.dispatch.cpp new file mode 100644 index 0000000000..ebe0c347a3 --- /dev/null +++ b/modules/imgproc/src/equalize_hist.dispatch.cpp @@ -0,0 +1,37 @@ +// 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 "equalize_hist.simd.hpp" +#include "equalize_hist.simd_declarations.hpp" // defines CV_CPU_DISPATCH_MODES_ALL=AVX512_ICL,...,BASELINE + +namespace cv { + +namespace { + +typedef void (*EqualizeHistLutFunc)( const uchar*, uchar*, int, const uchar* ); + +static EqualizeHistLutFunc resolveEqualizeHistLutFunc() +{ +#if CV_TRY_AVX512_ICL + if (cv::checkHardwareSupport(CV_CPU_AVX512_ICL)) + return opt_AVX512_ICL::equalizeHistLut_; +#endif + return cpu_baseline::equalizeHistLut_; +} + +} // namespace + +void equalizeHistLut_dispatch( const uchar* src, uchar* dst, int len, const uchar* lut ); + +void equalizeHistLut_dispatch( const uchar* src, uchar* dst, int len, const uchar* lut ) +{ + CV_INSTRUMENT_REGION(); + static EqualizeHistLutFunc fn = resolveEqualizeHistLutFunc(); + fn(src, dst, len, lut); +} + +} // namespace cv diff --git a/modules/imgproc/src/equalize_hist.simd.hpp b/modules/imgproc/src/equalize_hist.simd.hpp new file mode 100644 index 0000000000..3fc3595d5e --- /dev/null +++ b/modules/imgproc/src/equalize_hist.simd.hpp @@ -0,0 +1,61 @@ +// 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 "opencv2/core/hal/intrin.hpp" + +// VBMI vpermb is the only x86 SIMD path faster than scalar for byte LUT. +// Non-VBMI x86 SIMD (gather-based) is slower than scalar for byte LUT. +// Non-x86 (NEON, RVV, etc.) benefits from v_lut implementation. +#if CV_AVX_512VBMI +#define CV_EQUALIZE_HIST_SIMD 1 +#elif (defined(CV_CPU_COMPILE_SSE) || defined(CV_CPU_COMPILE_SSE2) || defined(CV_CPU_COMPILE_SSE3) || \ + defined(CV_CPU_COMPILE_SSSE3) || defined(CV_CPU_COMPILE_SSE4_1) || defined(CV_CPU_COMPILE_SSE4_2) || \ + defined(CV_CPU_COMPILE_AVX) || defined(CV_CPU_COMPILE_AVX2) || defined(CV_CPU_COMPILE_AVX_512F) || \ + defined(CV_CPU_COMPILE_AVX512_COMMON) || defined(CV_CPU_COMPILE_AVX512_SKX) || \ + defined(CV_CPU_COMPILE_AVX512_CNL) || defined(CV_CPU_COMPILE_AVX512_CLX)) +#define CV_EQUALIZE_HIST_SIMD 0 +#elif CV_SIMD || CV_SIMD_SCALABLE +#define CV_EQUALIZE_HIST_SIMD 1 +#else +#define CV_EQUALIZE_HIST_SIMD 0 +#endif + +namespace cv { +CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN + +void equalizeHistLut_( const uchar* src, uchar* dst, int len, const uchar* lut ); + +#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +void equalizeHistLut_( const uchar* src, uchar* dst, int len, const uchar* lut ) +{ + int x = 0; +#if CV_EQUALIZE_HIST_SIMD + const int nlanes = VTraits::vlanes(); + for( ; x <= len - nlanes; x += nlanes ) + { + v_uint8 idx = vx_load(src + x); + v_uint8 result = v_lut(lut, idx); + v_store(dst + x, result); + } +#endif + for( ; x <= len - 4; x += 4 ) + { + uchar v0 = src[x], v1 = src[x+1]; + dst[x] = lut[v0]; + dst[x+1] = lut[v1]; + v0 = src[x+2]; v1 = src[x+3]; + dst[x+2] = lut[v0]; + dst[x+3] = lut[v1]; + } + for( ; x < len; x++ ) + dst[x] = lut[src[x]]; +} + +#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +CV_CPU_OPTIMIZATION_NAMESPACE_END +} // namespace cv diff --git a/modules/imgproc/src/floodfill.cpp b/modules/imgproc/src/floodfill.cpp index 620830e9d8..3141756cb3 100644 --- a/modules/imgproc/src/floodfill.cpp +++ b/modules/imgproc/src/floodfill.cpp @@ -494,7 +494,8 @@ int cv::floodFill( InputOutputArray _image, InputOutputArray _mask, if( connectivity != 0 && connectivity != 4 && connectivity != 8 ) CV_Error( cv::Error::StsBadFlag, "Connectivity must be 4, 0(=4) or 8" ); - if( _mask.empty() ) + bool noUserMask = _mask.empty(); + if( noUserMask ) { _mask.create( size.height + 2, size.width + 2, CV_8UC1 ); _mask.setTo(0); @@ -508,7 +509,7 @@ int cv::floodFill( InputOutputArray _image, InputOutputArray _mask, Mat mask_inner = mask( Rect(1, 1, mask.cols - 2, mask.rows - 2) ); copyMakeBorder( mask_inner, mask, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(1) ); - bool is_simple = mask.empty() && (flags & FLOODFILL_MASK_ONLY) == 0; + bool is_simple = noUserMask && (flags & FLOODFILL_MASK_ONLY) == 0; for( i = 0; i < cn; i++ ) { diff --git a/modules/imgproc/src/hal_replacement.hpp b/modules/imgproc/src/hal_replacement.hpp index ef6bbdbb7d..23887cd000 100644 --- a/modules/imgproc/src/hal_replacement.hpp +++ b/modules/imgproc/src/hal_replacement.hpp @@ -1067,6 +1067,16 @@ inline int hal_ni_cvtRGBAtoMultipliedRGBA(const uchar * src_data, size_t src_ste */ inline int hal_ni_cvtMultipliedRGBAtoRGBA(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height) { return CV_HAL_ERROR_NOT_IMPLEMENTED; } +/** @brief Extract Y-plane from YUV 4:2:0 image. + @param src_data Source image data pointer (points to Y plane). + @param src_step Source step. + @param dst_data Destination data pointer. + @param dst_step Destination step. + @param width Image width. + @param height Image height (of the Y plane, not the full YUV image). + */ +inline int hal_ni_cvtColorYUV2Gray(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height) { return CV_HAL_ERROR_NOT_IMPLEMENTED; } + //! @cond IGNORED #define cv_hal_cvtBGRtoBGR hal_ni_cvtBGRtoBGR #define cv_hal_cvtBGRtoBGR5x5 hal_ni_cvtBGRtoBGR5x5 @@ -1100,6 +1110,7 @@ inline int hal_ni_cvtMultipliedRGBAtoRGBA(const uchar * src_data, size_t src_ste #define cv_hal_cvtOnePlaneBGRtoYUVApprox hal_ni_cvtOnePlaneBGRtoYUVApprox #define cv_hal_cvtRGBAtoMultipliedRGBA hal_ni_cvtRGBAtoMultipliedRGBA #define cv_hal_cvtMultipliedRGBAtoRGBA hal_ni_cvtMultipliedRGBAtoRGBA +#define cv_hal_cvtColorYUV2Gray hal_ni_cvtColorYUV2Gray //! @endcond /** diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 835d35c285..0d2f260518 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -2407,10 +2407,14 @@ private: cv::Mutex* histogramLock_; }; +namespace cv { + void equalizeHistLut_dispatch( const uchar* src, uchar* dst, int len, const uchar* lut ); +} + class EqualizeHistLut_Invoker : public cv::ParallelLoopBody { public: - EqualizeHistLut_Invoker( cv::Mat& src, cv::Mat& dst, int* lut ) + EqualizeHistLut_Invoker( cv::Mat& src, cv::Mat& dst, uchar* lut ) : src_(src), dst_(dst), lut_(lut) @@ -2423,7 +2427,7 @@ public: int width = src_.cols; int height = rowRange.end - rowRange.start; - int* lut = lut_; + const uchar* lut = lut_; if (src_.isContinuous() && dst_.isContinuous()) { @@ -2436,26 +2440,7 @@ public: for (; height--; sptr += sstep, dptr += dstep) { - int x = 0; - for (; x <= width - 4; x += 4) - { - int v0 = sptr[x]; - int v1 = sptr[x+1]; - int x0 = lut[v0]; - int x1 = lut[v1]; - dptr[x] = (uchar)x0; - dptr[x+1] = (uchar)x1; - - v0 = sptr[x+2]; - v1 = sptr[x+3]; - x0 = lut[v0]; - x1 = lut[v1]; - dptr[x+2] = (uchar)x0; - dptr[x+3] = (uchar)x1; - } - - for (; x < width; ++x) - dptr[x] = (uchar)lut[sptr[x]]; + cv::equalizeHistLut_dispatch(sptr, dptr, width, lut); } } @@ -2469,7 +2454,7 @@ private: cv::Mat& src_; cv::Mat& dst_; - int* lut_; + uchar* lut_; }; #ifdef HAVE_OPENCL @@ -2545,10 +2530,9 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ; int hist[hist_sz] = {0,}; - int lut[hist_sz]; + int lut[hist_sz] = {0,}; EqualizeHistCalcHist_Invoker calcBody(src, hist, &histogramLockInstance); - EqualizeHistLut_Invoker lutBody(src, dst, lut); cv::Range heightRange(0, src.rows); if(EqualizeHistCalcHist_Invoker::isWorthParallel(src)) @@ -2575,6 +2559,12 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) lut[i] = saturate_cast(sum * scale); } + uchar lut_u8[hist_sz]; + for (int j = 0; j < hist_sz; ++j) + lut_u8[j] = (uchar)lut[j]; + + EqualizeHistLut_Invoker lutBody(src, dst, lut_u8); + if(EqualizeHistLut_Invoker::isWorthParallel(src)) parallel_for_(heightRange, lutBody); else diff --git a/modules/imgproc/src/hough.cpp b/modules/imgproc/src/hough.cpp index 644b9af283..4254f49ef0 100644 --- a/modules/imgproc/src/hough.cpp +++ b/modules/imgproc/src/hough.cpp @@ -184,31 +184,82 @@ HoughLinesStandard( InputArray src, OutputArray lines, int type, irho, tabSin, tabCos); // stage 1. fill accumulator - if (use_edgeval) { - for( i = 0; i < height; i++ ) - for( j = 0; j < width; j++ ) - { - if( image[i * step + j] != 0 ) - for(int n = 0; n < numangle; n++ ) - { - int r = cvRound( j * tabCos[n] + i * tabSin[n] ); - r += (numrho - 1) / 2; - accum[(n + 1) * (numrho + 2) + r + 1] += image[i * step + j]; - } - } + // Use the serial implementation for small numangle values to avoid parallel overhead. + constexpr int kParallelAngleThreshold = 100; + if (numangle < kParallelAngleThreshold) { + if (use_edgeval) { + for( i = 0; i < height; i++ ) + for( j = 0; j < width; j++ ) + { + if( image[i * step + j] != 0 ) + for(int n = 0; n < numangle; n++ ) + { + int r = cvRound( j * tabCos[n] + i * tabSin[n] ); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1] += image[i * step + j]; + } + } + } else { + for( i = 0; i < height; i++ ) + for( j = 0; j < width; j++ ) + { + if( image[i * step + j] != 0 ) + for(int n = 0; n < numangle; n++ ) + { + int r = cvRound( j * tabCos[n] + i * tabSin[n] ); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1]++; + } + } + } } else { - for( i = 0; i < height; i++ ) - for( j = 0; j < width; j++ ) - { - if( image[i * step + j] != 0 ) - for(int n = 0; n < numangle; n++ ) - { - int r = cvRound( j * tabCos[n] + i * tabSin[n] ); - r += (numrho - 1) / 2; - accum[(n + 1) * (numrho + 2) + r + 1]++; - } + // Extract the coordinates of all edge points + std::vector x_coords, y_coords, edge_vals; + size_t estimated_edges = (size_t)width * (size_t)height / 10; + x_coords.reserve(estimated_edges); + y_coords.reserve(estimated_edges); + if (use_edgeval) { + edge_vals.reserve(estimated_edges); + } + + for (int y = 0; y < height; y++) { + const uchar* row_ptr = image + y * step; + for (int x = 0; x < width; x++) { + int val = row_ptr[x]; + if (val != 0) { + x_coords.push_back(x); + y_coords.push_back(y); + if (use_edgeval) edge_vals.push_back(val); + } } - } + } + int num_edges = (int)x_coords.size(); + + // Perform multi-threaded segmentation according to the numangle + // Since accum is divided into blocks according to angles, the accum areas written by different threads will not overlap + auto process_hough_by_angle = [&](const cv::Range& range) { + for (int n = range.start; n < range.end; n++) { + float cos_n = tabCos[n]; + float sin_n = tabSin[n]; + + int* accum_n = accum + (n + 1) * (numrho + 2) + 1 + (numrho - 1) / 2; + + if (use_edgeval) { + for (int k = 0; k < num_edges; k++) { + int r = cvRound(x_coords[k] * cos_n + y_coords[k] * sin_n); + accum_n[r] += edge_vals[k]; + } + } else { + for (int k = 0; k < num_edges; k++) { + int r = cvRound(x_coords[k] * cos_n + y_coords[k] * sin_n); + accum_n[r]++; + } + } + } + }; + + cv::parallel_for_(cv::Range(0, numangle), process_hough_by_angle); + } // stage 2. find local maximums findLocalMaximums( numrho, numangle, threshold, accum, _sort_buf ); @@ -308,13 +359,13 @@ HoughLinesSDiv( InputArray image, OutputArray lines, int type, lst.push_back(hough_index(threshold, -1.f, 0.f)); // Precalculate sin table - std::vector _sinTable( 5 * tn * stn ); + AutoBuffer _sinTable( 5 * tn * stn ); float* sinTable = &_sinTable[0]; for( index = 0; index < 5 * tn * stn; index++ ) sinTable[index] = (float)cos( stheta * index * 0.2f ); - std::vector _caccum(rn * tn, (uchar)0); + AutoBuffer _caccum(rn * tn, (uchar)0); uchar* caccum = &_caccum[0]; // Counting all feature pixels @@ -322,7 +373,7 @@ HoughLinesSDiv( InputArray image, OutputArray lines, int type, for( col = 0; col < w; col++ ) fn += _POINT( row, col ) != 0; - std::vector _x(fn), _y(fn); + AutoBuffer _x(fn), _y(fn); int* x = &_x[0], *y = &_y[0]; // Full Hough Transform (it's accumulator update part) @@ -394,7 +445,7 @@ HoughLinesSDiv( InputArray image, OutputArray lines, int type, return; } - std::vector _buffer(srn * stn + 2); + AutoBuffer _buffer(srn * stn + 2); uchar* buffer = &_buffer[0]; uchar* mcaccum = buffer + 1; @@ -535,7 +586,7 @@ HoughLinesProbabilistic( Mat& image, Mat accum = Mat::zeros( numangle, numrho, CV_32SC1 ); Mat mask( height, width, CV_8UC1 ); - std::vector trigtab(numangle*2); + AutoBuffer trigtab(numangle*2); for( int n = 0; n < numangle; n++ ) { @@ -2331,7 +2382,7 @@ static void HoughCircles( InputArray _image, OutputArray _circles, if( type == CV_32FC4 ) { - std::vector cw(ncircles); + AutoBuffer cw(ncircles); for( i = 0; i < ncircles; i++ ) cw[i] = GetCircle4f(circles[i]); if (ncircles > 0) @@ -2339,7 +2390,7 @@ static void HoughCircles( InputArray _image, OutputArray _circles, } else if( type == CV_32FC3 ) { - std::vector cwow(ncircles); + AutoBuffer cwow(ncircles); for( i = 0; i < ncircles; i++ ) cwow[i] = GetCircle(circles[i]); if (ncircles > 0) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 9a76877e90..a8ef6bb82a 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -311,8 +311,8 @@ static const void* initInterTab2D( int method, bool fixpt ) for( j = 0; j < INTER_TAB_SIZE; j++, tab += ksize*ksize, itab += ksize*ksize ) { int isum = 0; - NNDeltaTab_i[i*INTER_TAB_SIZE+j][0] = j < INTER_TAB_SIZE/2; - NNDeltaTab_i[i*INTER_TAB_SIZE+j][1] = i < INTER_TAB_SIZE/2; + NNDeltaTab_i[i*INTER_TAB_SIZE+j][0] = j >= INTER_TAB_SIZE/2; + NNDeltaTab_i[i*INTER_TAB_SIZE+j][1] = i >= INTER_TAB_SIZE/2; for( k1 = 0; k1 < ksize; k1++ ) { diff --git a/modules/imgproc/src/median_blur.simd.hpp b/modules/imgproc/src/median_blur.simd.hpp index 0b9db8ebb4..d2db45cec1 100644 --- a/modules/imgproc/src/median_blur.simd.hpp +++ b/modules/imgproc/src/median_blur.simd.hpp @@ -128,8 +128,8 @@ medianBlur_8u_O1( const Mat& _src, Mat& _dst, int ksize ) # define CV_ALIGNMENT 16 #endif - std::vector _h_coarse(1 * 16 * (STRIPE_SIZE + 2*r) * cn + CV_ALIGNMENT); - std::vector _h_fine(16 * 16 * (STRIPE_SIZE + 2*r) * cn + CV_ALIGNMENT); + AutoBuffer _h_coarse(1 * 16 * (STRIPE_SIZE + 2*r) * cn + CV_ALIGNMENT); + AutoBuffer _h_fine(16 * 16 * (STRIPE_SIZE + 2*r) * cn + CV_ALIGNMENT); HT* h_coarse = alignPtr(&_h_coarse[0], CV_ALIGNMENT); HT* h_fine = alignPtr(&_h_fine[0], CV_ALIGNMENT); diff --git a/modules/imgproc/src/morph.dispatch.cpp b/modules/imgproc/src/morph.dispatch.cpp index 94326b545d..810ddd60ca 100644 --- a/modules/imgproc/src/morph.dispatch.cpp +++ b/modules/imgproc/src/morph.dispatch.cpp @@ -887,7 +887,7 @@ static bool ocl_morphOp(InputArray _src, OutputArray _dst, InputArray _kernel, if (actual_op < 0) actual_op = op; - std::vector kernels(iterations); + AutoBuffer kernels(iterations); for (int i = 0; i < iterations; i++) { int current_op = iterations == i + 1 ? actual_op : op; diff --git a/modules/imgproc/src/opencl/remap.cl b/modules/imgproc/src/opencl/remap.cl index 106b80460e..9c6f3878a1 100644 --- a/modules/imgproc/src/opencl/remap.cl +++ b/modules/imgproc/src/opencl/remap.cl @@ -306,8 +306,8 @@ __kernel void remap_16SC2_16UC1(__global const uchar * srcptr, int src_step, int __global T * dst = (__global T *)(dstptr + dst_index); int map2Value = convert_int(map2[0]) & (INTER_TAB_SIZE2 - 1); - int dx = (map2Value & (INTER_TAB_SIZE - 1)) < (INTER_TAB_SIZE >> 1) ? 1 : 0; - int dy = (map2Value >> INTER_BITS) < (INTER_TAB_SIZE >> 1) ? 1 : 0; + int dx = (map2Value & (INTER_TAB_SIZE - 1)) >= (INTER_TAB_SIZE >> 1) ? 1 : 0; + int dy = (map2Value >> INTER_BITS) >= (INTER_TAB_SIZE >> 1) ? 1 : 0; int2 gxy = convert_int2(map1[0]) + (int2)(dx, dy); #if WARP_RELATIVE gxy.x += x; diff --git a/modules/imgproc/src/resize.cpp b/modules/imgproc/src/resize.cpp index 08a5abdd06..9dae651663 100644 --- a/modules/imgproc/src/resize.cpp +++ b/modules/imgproc/src/resize.cpp @@ -3636,198 +3636,6 @@ static bool ocl_resize( InputArray _src, OutputArray _dst, Size dsize, #endif -#ifdef HAVE_IPP -#define IPP_RESIZE_PARALLEL 1 - -#ifdef HAVE_IPP_IW -class ipp_resizeParallel: public ParallelLoopBody -{ -public: - ipp_resizeParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, bool &ok): - m_src(src), m_dst(dst), m_ok(ok) {} - ~ipp_resizeParallel() - { - } - - void Init(IppiInterpolationType inter) - { - iwiResize.InitAlloc(m_src.m_size, m_dst.m_size, m_src.m_dataType, m_src.m_channels, inter, ::ipp::IwiResizeParams(0, 0, 0.75, 4), ippBorderRepl); - - m_ok = true; - } - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - CV_INSTRUMENT_REGION_IPP(); - - if(!m_ok) - return; - - try - { - ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); - CV_INSTRUMENT_FUN_IPP(iwiResize, m_src, m_dst, ippBorderRepl, tile); - } - catch(const ::ipp::IwException &) - { - m_ok = false; - return; - } - } -private: - ::ipp::IwiImage &m_src; - ::ipp::IwiImage &m_dst; - - mutable ::ipp::IwiResize iwiResize; - - volatile bool &m_ok; - const ipp_resizeParallel& operator= (const ipp_resizeParallel&); -}; - -class ipp_resizeAffineParallel: public ParallelLoopBody -{ -public: - ipp_resizeAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, bool &ok): - m_src(src), m_dst(dst), m_ok(ok) {} - ~ipp_resizeAffineParallel() - { - } - - void Init(IppiInterpolationType inter, double scaleX, double scaleY) - { - double shift = (inter == ippNearest)?-1e-10:-0.5; - double coeffs[2][3] = { - {scaleX, 0, shift+0.5*scaleX}, - {0, scaleY, shift+0.5*scaleY} - }; - - iwiWarpAffine.InitAlloc(m_src.m_size, m_dst.m_size, m_src.m_dataType, m_src.m_channels, coeffs, iwTransForward, inter, ::ipp::IwiWarpAffineParams(0, 0, 0.75), ippBorderRepl); - - m_ok = true; - } - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - CV_INSTRUMENT_REGION_IPP(); - - if(!m_ok) - return; - - try - { - ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); - CV_INSTRUMENT_FUN_IPP(iwiWarpAffine, m_src, m_dst, tile); - } - catch(const ::ipp::IwException &) - { - m_ok = false; - return; - } - } -private: - ::ipp::IwiImage &m_src; - ::ipp::IwiImage &m_dst; - - mutable ::ipp::IwiWarpAffine iwiWarpAffine; - - volatile bool &m_ok; - const ipp_resizeAffineParallel& operator= (const ipp_resizeAffineParallel&); -}; -#endif - -static bool ipp_resize(const uchar * src_data, size_t src_step, int src_width, int src_height, - uchar * dst_data, size_t dst_step, int dst_width, int dst_height, double inv_scale_x, double inv_scale_y, - int depth, int channels, int interpolation) -{ -#ifdef HAVE_IPP_IW - CV_INSTRUMENT_REGION_IPP(); - - IppDataType ippDataType = ippiGetDataType(depth); - IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); - if((int)ippInter < 0) - return false; - - // Resize which doesn't match OpenCV exactly - if (!cv::ipp::useIPP_NotExact()) - { - if (ippInter == ippNearest || ippInter == ippSuper || (ippDataType == ipp8u && ippInter == ippLinear)) - return false; - } - - if(ippInter != ippLinear && ippDataType == ipp64f) - return false; - -#if IPP_VERSION_X100 < 201801 - // Degradations on int^2 linear downscale - if (ippDataType != ipp64f && ippInter == ippLinear && inv_scale_x < 1 && inv_scale_y < 1) // if downscale - { - int scale_x = (int)(1 / inv_scale_x); - int scale_y = (int)(1 / inv_scale_y); - if (1 / inv_scale_x - scale_x < DBL_EPSILON && 1 / inv_scale_y - scale_y < DBL_EPSILON) // if integer - { - if (!(scale_x&(scale_x - 1)) && !(scale_y&(scale_y - 1))) // if power of 2 - return false; - } - } -#endif - - bool affine = false; - const double IPP_RESIZE_EPS = (depth == CV_64F)?0:1e-10; - double ex = fabs((double)dst_width / src_width - inv_scale_x) / inv_scale_x; - double ey = fabs((double)dst_height / src_height - inv_scale_y) / inv_scale_y; - - // Use affine transform resize to allow sub-pixel accuracy - if(ex > IPP_RESIZE_EPS || ey > IPP_RESIZE_EPS) - affine = true; - - // Affine doesn't support Lanczos and Super interpolations - if(affine && (ippInter == ippLanczos || ippInter == ippSuper)) - return false; - - try - { - ::ipp::IwiImage iwSrc(::ipp::IwiSize(src_width, src_height), ippDataType, channels, 0, (void*)src_data, src_step); - ::ipp::IwiImage iwDst(::ipp::IwiSize(dst_width, dst_height), ippDataType, channels, 0, (void*)dst_data, dst_step); - - bool ok; - int threads = ippiSuggestThreadsNum(iwDst, 1+((double)(src_width*src_height)/(dst_width*dst_height))); - Range range(0, dst_height); - ipp_resizeParallel invokerGeneral(iwSrc, iwDst, ok); - ipp_resizeAffineParallel invokerAffine(iwSrc, iwDst, ok); - ParallelLoopBody *pInvoker = NULL; - if(affine) - { - pInvoker = &invokerAffine; - invokerAffine.Init(ippInter, inv_scale_x, inv_scale_y); - } - else - { - pInvoker = &invokerGeneral; - invokerGeneral.Init(ippInter); - } - - if(IPP_RESIZE_PARALLEL && threads > 1) - parallel_for_(range, *pInvoker, threads*4); - else - pInvoker->operator()(range); - - if(!ok) - return false; - } - catch(const ::ipp::IwException &) - { - return false; - } - return true; -#else - CV_UNUSED(src_data); CV_UNUSED(src_step); CV_UNUSED(src_width); CV_UNUSED(src_height); CV_UNUSED(dst_data); CV_UNUSED(dst_step); - CV_UNUSED(dst_width); CV_UNUSED(dst_height); CV_UNUSED(inv_scale_x); CV_UNUSED(inv_scale_y); CV_UNUSED(depth); - CV_UNUSED(channels); CV_UNUSED(interpolation); - return false; -#endif -} -#endif - //================================================================================================== namespace hal { @@ -3853,8 +3661,6 @@ void resize(int src_type, saturate_cast(src_height*inv_scale_y)); CV_Assert( !dsize.empty() ); - CV_IPP_RUN_FAST(ipp_resize(src_data, src_step, src_width, src_height, dst_data, dst_step, dsize.width, dsize.height, inv_scale_x, inv_scale_y, depth, cn, interpolation)) - static ResizeFunc linear_tab[CV_DEPTH_MAX] = { resizeGeneric_< diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index e2951325a0..73677bde73 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -568,7 +568,6 @@ void crossCorr( const Mat& img, const Mat& _templ, Mat& corr, { const double blockScale = 4.5; const int minBlockSize = 256; - std::vector buf; Mat templ = _templ; int depth = img.depth(), cn = img.channels(); @@ -624,7 +623,7 @@ void crossCorr( const Mat& img, const Mat& _templ, Mat& corr, if( (ccn > 1 || cn > 1) && cdepth != maxDepth ) bufSize = std::max( bufSize, blocksize.width*blocksize.height*CV_ELEM_SIZE(cdepth)); - buf.resize(bufSize); + AutoBuffer buf(bufSize); Ptr c = hal::DFT2D::create(dftsize.width, dftsize.height, dftTempl.depth(), 1, 1, CV_HAL_DFT_IS_INPLACE, templ.rows); @@ -975,13 +974,14 @@ static void common_matchTemplate( Mat& img, Mat& templ, Mat& result, int method, for( i = 0; i < result.rows; i++ ) { - float* rrow = result.ptr(i); + float* rrow = result.depth() == CV_32F ? result.ptr(i) : nullptr; + double* drow = result.depth() == CV_64F ? result.ptr(i) : nullptr; int idx = i * sumstep; int idx2 = i * sqstep; for( j = 0; j < result.cols; j++, idx += cn, idx2 += cn ) { - double num = rrow[j], t; + double num = rrow ? (double)rrow[j] : drow[j], t; double wndMean2 = 0, wndSum2 = 0; if( numType == 1 ) @@ -1027,7 +1027,8 @@ static void common_matchTemplate( Mat& img, Mat& templ, Mat& result, int method, num = method != cv::TM_SQDIFF_NORMED ? 0 : 1; } - rrow[j] = (float)num; + if (rrow) rrow[j] = (float)num; + else drow[j] = num; } } } @@ -1120,6 +1121,11 @@ static bool ipp_matchTemplate( Mat& img, Mat& templ, Mat& result, int method) if(templ.size().area()*4 > img.size().area()) return false; + // CV_8U SQDIFF/SQDIFF_NORMED suffer from float32 catastrophic cancellation + // in IPP's internal accumulators; fall through to the double-precision path instead. + if(img.depth() == CV_8U && (method == cv::TM_SQDIFF || method == cv::TM_SQDIFF_NORMED)) + return false; + if(method == cv::TM_SQDIFF) { if(ipp_sqrDistance(img, templ, result)) @@ -1192,9 +1198,18 @@ void cv::matchTemplate( InputArray _img, InputArray _templ, OutputArray _result, CV_IPP_RUN_FAST(ipp_matchTemplate(img, templ, result, method)) - crossCorr( img, templ, result, Point(0,0), 0, 0); + bool use64f = (depth == CV_8U) && (method == cv::TM_SQDIFF || method == cv::TM_SQDIFF_NORMED); + Mat result64f; + Mat& workResult = use64f ? result64f : result; + if (use64f) + result64f.create(corrSize, CV_64F); - common_matchTemplate(img, templ, result, method, cn); + crossCorr( img, templ, workResult, Point(0,0), 0, 0); + + common_matchTemplate(img, templ, workResult, method, cn); + + if (use64f) + result64f.convertTo(result, CV_32F); } /* End of file. */ diff --git a/modules/imgproc/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp index e8eb1a5a40..dc227e8b54 100644 --- a/modules/imgproc/test/test_drawing.cpp +++ b/modules/imgproc/test/test_drawing.cpp @@ -1334,4 +1334,23 @@ TEST(Drawing, line_connectivity_regression_26413) EXPECT_GT(count4, 15) << "LINE_4 diagonal should have significantly more pixels due to staircase"; } +//This test ensures that the tipLength geometric ratio is strictly bounded within the logical range (0.0, 1.0]. +TEST(Imgproc_Drawing, arrowedLine_tipLength_validation) +{ + // Create a simple miniature canvas for testing. Added cv:: prefix. + cv::Mat img = cv::Mat::zeros(100, 100, CV_8UC3); + cv::Point pt1(10, 10), pt2(90, 90); + + // 1. Validate legal parameters: should not throw any exceptions (Normal cases) + EXPECT_NO_THROW(cv::arrowedLine(img, pt1, pt2, cv::Scalar(255, 255, 255), 1, 8, 0, 0.1)); + EXPECT_NO_THROW(cv::arrowedLine(img, pt1, pt2, cv::Scalar(255, 255, 255), 1, 8, 0, 1.0)); + + // 2. Validate illegal parameters: expect cv::Exception to be thrown (Boundary violations) + // Negative ratio (tipLength <= 0.0) + EXPECT_THROW(cv::arrowedLine(img, pt1, pt2, cv::Scalar(255, 255, 255), 1, 8, 0, -0.5), cv::Exception); + + // Overflow ratio (tipLength > 1.0) + EXPECT_THROW(cv::arrowedLine(img, pt1, pt2, cv::Scalar(255, 255, 255), 1, 8, 0, 1.5), cv::Exception); +} + }} // namespace diff --git a/modules/imgproc/test/test_templmatch.cpp b/modules/imgproc/test/test_templmatch.cpp index a59ef04500..76229100fa 100644 --- a/modules/imgproc/test/test_templmatch.cpp +++ b/modules/imgproc/test/test_templmatch.cpp @@ -341,4 +341,20 @@ INSTANTIATE_TEST_CASE_P(/**/, testing::Values(1, 3), testing::Values(TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED))); + +TEST(Imgproc_MatchTemplate, bug_21786) +{ + // CV_8U identical image/template with large patch sums triggers float32 + // catastrophic cancellation in TM_SQDIFF. Result must be exactly zero. + Mat img(100, 100, CV_8U, Scalar(255)); + Mat templ(25, 25, CV_8U, Scalar(255)); + Mat result; + + matchTemplate(img, templ, result, TM_SQDIFF); + EXPECT_NEAR(0.0, cv::norm(result, cv::NORM_INF), 1e-6); + + matchTemplate(img, templ, result, TM_SQDIFF_NORMED); + EXPECT_NEAR(0.0, cv::norm(result, cv::NORM_INF), 1e-6); +} + }} // namespace diff --git a/modules/js/generator/embindgen.py b/modules/js/generator/embindgen.py index 7c456fce3d..4a2ce5cc90 100644 --- a/modules/js/generator/embindgen.py +++ b/modules/js/generator/embindgen.py @@ -342,6 +342,16 @@ class JSWrapperGenerator(object): } return tp in string_types + def _qualify_factory_ptr_return_type(self, ret_type, class_info): + if class_info is None or not ret_type.startswith('Ptr<') or not ret_type.endswith('>'): + return ret_type + inner = ret_type[len('Ptr<'):-1].strip() + if '::' in inner: + return ret_type + if inner == class_info.name or inner == class_info.cname.split('::')[-1]: + return 'Ptr<%s>' % class_info.cname + return ret_type + def _generate_class_properties(self, class_info, class_bindings): # Generate bindings for properties for prop in class_info.props: @@ -523,12 +533,8 @@ class JSWrapperGenerator(object): # Return type ret_type = 'void' if variant.rettype.strip() == '' else variant.rettype - # FIX: Ensure namespaced smart-pointer return types in factory methods, e.g.: - # Ptr → Ptr if factory and class_info is not None and ret_type.startswith('Ptr<'): - inner = ret_type[len('Ptr<'):-1].strip() - if '::' not in inner and inner == class_info.name: - ret_type = 'Ptr<%s>' % class_info.cname + ret_type = self._qualify_factory_ptr_return_type(ret_type, class_info) if ret_type.startswith('Ptr'): # smart pointer ptr_type = ret_type.replace('Ptr<', '').replace('>', '') @@ -715,11 +721,8 @@ class JSWrapperGenerator(object): ret_type = 'void' if variant.rettype.strip() == '' else variant.rettype ret_type = ret_type.strip() - # Same namespace fix for factory methods: Ptr -> Ptr if factory and class_info is not None and ret_type.startswith('Ptr<'): - inner = ret_type[len('Ptr<'):-1].strip() - if '::' not in inner and inner == class_info.name: - ret_type = 'Ptr<%s>' % class_info.cname + ret_type = self._qualify_factory_ptr_return_type(ret_type, class_info) if ret_type.startswith('Ptr'): #smart pointer ptr_type = ret_type.replace('Ptr<', '').replace('>', '') diff --git a/modules/ml/perf/perf_knn.cpp b/modules/ml/perf/perf_knn.cpp new file mode 100644 index 0000000000..f911b6520e --- /dev/null +++ b/modules/ml/perf/perf_knn.cpp @@ -0,0 +1,38 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#include "perf_precomp.hpp" + +namespace opencv_test { namespace { + +// KNN brute-force findNearest: dominated by the per-sample L2 distance reduction. +typedef TestBaseWithParam< tuple > KNNFindNearest; // (train samples, dims, K) + +PERF_TEST_P(KNNFindNearest, brute_force, testing::Values( + make_tuple(5000, 128, 5), + make_tuple(10000, 64, 10))) +{ + const int nsamples = get<0>(GetParam()); + const int dims = get<1>(GetParam()); + const int K = get<2>(GetParam()); + const int nquery = 2000; + + Mat train(nsamples, dims, CV_32F), responses(nsamples, 1, CV_32F), query(nquery, dims, CV_32F); + RNG& rng = theRNG(); + rng.fill(train, RNG::UNIFORM, 0.f, 1.f); + rng.fill(query, RNG::UNIFORM, 0.f, 1.f); + for (int i = 0; i < nsamples; i++) + responses.at(i) = (float)(i & 1); + + Ptr knn = KNearest::create(); + knn->setAlgorithmType(KNearest::BRUTE_FORCE); + knn->setDefaultK(K); + knn->train(train, ROW_SAMPLE, responses); + + Mat results, neighbors, dists; + TEST_CYCLE() knn->findNearest(query, K, results, neighbors, dists); + + SANITY_CHECK_NOTHING(); +} + +}} // namespace diff --git a/modules/ml/perf/perf_main.cpp b/modules/ml/perf/perf_main.cpp new file mode 100644 index 0000000000..ccdf300c6e --- /dev/null +++ b/modules/ml/perf/perf_main.cpp @@ -0,0 +1,6 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#include "perf_precomp.hpp" + +CV_PERF_TEST_MAIN(ml) diff --git a/modules/ml/perf/perf_precomp.hpp b/modules/ml/perf/perf_precomp.hpp new file mode 100644 index 0000000000..de2c7acaba --- /dev/null +++ b/modules/ml/perf/perf_precomp.hpp @@ -0,0 +1,15 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#ifndef __OPENCV_PERF_PRECOMP_HPP__ +#define __OPENCV_PERF_PRECOMP_HPP__ + +#include +#include + +namespace opencv_test { +using namespace perf; +using namespace cv::ml; +} + +#endif diff --git a/modules/ml/perf/perf_svm.cpp b/modules/ml/perf/perf_svm.cpp new file mode 100644 index 0000000000..b47c367a88 --- /dev/null +++ b/modules/ml/perf/perf_svm.cpp @@ -0,0 +1,48 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#include "perf_precomp.hpp" + +namespace opencv_test { namespace { + +// SVM::predict is dominated by the per-feature kernel reduction over the support +// vectors: calc_non_rbf_base (LINEAR/POLY/SIGMOID dot product), calc_rbf +// (squared distance), calc_intersec (min-sum) and calc_chi2. Train data is kept +// non-negative so the INTER and CHI2 kernels stay in their valid domain. +typedef TestBaseWithParam< tuple > SVMPredict; // (samples, dims, kernelType) + +PERF_TEST_P(SVMPredict, kernels, testing::Combine( + testing::Values(1500), + testing::Values(512), + testing::Values((int)SVM::RBF, (int)SVM::POLY, (int)SVM::INTER, (int)SVM::CHI2))) +{ + const int nsamples = get<0>(GetParam()); + const int dims = get<1>(GetParam()); + const int kernel = get<2>(GetParam()); + const int nquery = 1000; + + Mat train(nsamples, dims, CV_32F), query(nquery, dims, CV_32F); + Mat responses(nsamples, 1, CV_32S); + RNG& rng = theRNG(); + rng.fill(train, RNG::UNIFORM, 0.f, 1.f); + rng.fill(query, RNG::UNIFORM, 0.f, 1.f); + for (int i = 0; i < nsamples; i++) + responses.at(i) = i & 1; + + Ptr svm = SVM::create(); + svm->setType(SVM::C_SVC); + svm->setKernel(kernel); + svm->setC(1); + svm->setGamma(0.1); + svm->setDegree(3); // POLY + svm->setCoef0(1); // POLY + svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-3)); + svm->train(train, ROW_SAMPLE, responses); + + Mat results; + TEST_CYCLE() svm->predict(query, results); + + SANITY_CHECK_NOTHING(); +} + +}} // namespace diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp index d918e08129..faa79f0207 100644 --- a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp +++ b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp @@ -65,7 +65,7 @@ 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. + /** @brief Given a matrix of pixel ratio ranging from 0 to 1. Returns whether the marker is identified or not. * * Returns reference to the marker id in the dictionary (if any) and its rotation. */ @@ -77,6 +77,22 @@ class CV_EXPORTS_W_SIMPLE Dictionary { */ CV_WRAP int getDistanceToId(InputArray bits, int id, bool allRotations = true) const; + /** @brief Returns number of cells that differ from the specific id. + * + * For each cell, the distance is increased when the difference between the detected + * cell pixel ratio and the dictionary bit value is greater than `validBitIdThreshold`. + * If `allRotations` is set, the four possible marker rotations are considered. + * + * @param onlyCellPixelRatio markerSize x markerSize matrix (CV_32FC1) holding, for each cell, + * the ratio of white pixels ranging from 0 to 1 + * @param id marker id in the dictionary to compute the distance to + * @param allRotations if set, the four possible marker rotations are considered and the + * smallest distance is returned + * @param validBitIdThreshold maximum allowed difference between a cell pixel ratio and the + * dictionary bit value; cells exceeding it are counted as differing + */ + CV_WRAP int getDistanceToId(InputArray onlyCellPixelRatio, int id, bool allRotations, float validBitIdThreshold) const; + /** @brief Generate a canonical marker image */ CV_WRAP void generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits = 1) const; diff --git a/modules/objdetect/misc/python/test/test_objdetect_aruco.py b/modules/objdetect/misc/python/test/test_objdetect_aruco.py index 9a85e29042..d2aa8b87a8 100644 --- a/modules/objdetect/misc/python/test/test_objdetect_aruco.py +++ b/modules/objdetect/misc/python/test/test_objdetect_aruco.py @@ -144,6 +144,32 @@ class aruco_objdetect_test(NewOpenCVTests): self.assertEqual(dist, 0) + def test_getDistanceToId_cell_pixel_ratio(self): + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50) + idx = 7 + valid_bit_id_threshold = 0.49 + bit_marker = np.array([[0, 1, 0, 1], [0, 1, 1, 1], [1, 1, 0, 0], [0, 1, 0, 0]], dtype=np.uint8) + ratio_marker = bit_marker.astype(np.float32) + + # Same marker as test_getDistanceToId, but passed as float cell ratios. + dist = aruco_dict.getDistanceToId(ratio_marker, idx, True, valid_bit_id_threshold) + self.assertEqual(dist, 0) + + # A small drift stays within the threshold. + accepted_ratio = ratio_marker.copy() + accepted_ratio[0, 0] = 0.4 + dist = aruco_dict.getDistanceToId(accepted_ratio, idx, True, valid_bit_id_threshold) + self.assertEqual(dist, 0) + + # A full flip crosses the threshold and counts as one bad cell. + erroneous_ratio = ratio_marker.copy() + erroneous_ratio[0, 0] = 1.0 - erroneous_ratio[0, 0] + dist = aruco_dict.getDistanceToId(onlyCellPixelRatio=erroneous_ratio, + id=idx, + allRotations=True, + validBitIdThreshold=valid_bit_id_threshold) + self.assertEqual(dist, 1) + def test_aruco_detector(self): aruco_params = cv.aruco.DetectorParameters() aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250) diff --git a/modules/objdetect/perf/perf_cicrlesGrid.cpp b/modules/objdetect/perf/perf_cicrlesGrid.cpp index 6b16e512a7..13d85de3af 100644 --- a/modules/objdetect/perf/perf_cicrlesGrid.cpp +++ b/modules/objdetect/perf/perf_cicrlesGrid.cpp @@ -39,4 +39,35 @@ PERF_TEST_P(String_Size, asymm_circles_grid, testing::Values( SANITY_CHECK(ptvec, 2); } +// Perf test using synthetic keypoints (no image I/O). Exercises the RNG and +// findLongestPath code paths directly with a pre-detected point set. +typedef perf::TestBaseWithParam CirclesGrid_RNG_Size; + +PERF_TEST_P(CirclesGrid_RNG_Size, detect_keypoints_symmetric, + testing::Values(cv::Size(6, 5), cv::Size(8, 6), cv::Size(10, 8), cv::Size(15, 12), cv::Size(20, 15))) +{ + const cv::Size patternSize = GetParam(); + const float spacing = 30.f; + + std::vector pts; + pts.reserve(patternSize.area()); + for (int r = 0; r < patternSize.height; r++) + for (int c = 0; c < patternSize.width; c++) + pts.push_back(cv::Point2f(c * spacing, r * spacing)); + + // Shuffle so the detector works from an unordered set, same as real use. + cv::RNG& rng = cv::theRNG(); + for (int k = (int)pts.size() - 1; k > 0; k--) + std::swap(pts[k], pts[rng.uniform(0, k + 1)]); + + std::vector centers; + centers.resize(patternSize.area()); + declare.in(cv::Mat(pts)).out(centers); + + TEST_CYCLE() ASSERT_TRUE(findCirclesGrid(cv::Mat(pts), patternSize, centers, + CALIB_CB_SYMMETRIC_GRID, cv::Ptr())); + + SANITY_CHECK_NOTHING(); +} + } // namespace diff --git a/modules/objdetect/src/aruco/aruco_detector.cpp b/modules/objdetect/src/aruco/aruco_detector.cpp index c641f89ab2..3fa882f704 100644 --- a/modules/objdetect/src/aruco/aruco_detector.cpp +++ b/modules/objdetect/src/aruco/aruco_detector.cpp @@ -383,31 +383,41 @@ static Mat _extractCellPixelRatio(InputArray _image, const vector& corn /** - * @brief Return number of erroneous bits in border, i.e. bits for which pixel ratio > validBitIdThreshold. + * @brief Return number of erroneous bits in border. + * + * Black border error if cellPixelRatio > validBitIdThreshold -> borderErrors + * White border error if 1 - cellPixelRatio > validBitIdThreshold + * <=> cellPixelRatio < 1 - validBitIdThreshold) -> invBorderErrors (inverted markers) */ - static int _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold) { +static void _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold, + int &borderErrors, int &invBorderErrors) { int sizeWithBorders = markerSize + 2 * borderSize; CV_Assert(markerSize > 0 && cellPixelRatio.cols == sizeWithBorders && cellPixelRatio.rows == sizeWithBorders); // Get border error. cellPixelRatio has the opposite color as the borders. - int totalErrors = 0; + const float invThreshold = 1.f - validBitIdThreshold; + borderErrors = invBorderErrors = 0; + const auto countCell = [&](float ratio) { + if(ratio > validBitIdThreshold) borderErrors++; + if(ratio < invThreshold) invBorderErrors++; + }; for(int y = 0; y < sizeWithBorders; y++) { + const float* row = cellPixelRatio.ptr(y); for(int k = 0; k < borderSize; k++) { // Left and right vertical sides - if(cellPixelRatio.ptr(y)[k] > validBitIdThreshold) totalErrors++; - if(cellPixelRatio.ptr(y)[sizeWithBorders - 1 - k] > validBitIdThreshold) totalErrors++; + countCell(row[k]); + countCell(row[sizeWithBorders - 1 - k]); } } for(int x = borderSize; x < sizeWithBorders - borderSize; x++) { for(int k = 0; k < borderSize; k++) { // Top and bottom horizontal sides - if(cellPixelRatio.ptr(k)[x] > validBitIdThreshold) totalErrors++; - if(cellPixelRatio.ptr(sizeWithBorders - 1 - k)[x] > validBitIdThreshold) totalErrors++; + countCell(cellPixelRatio.ptr(k)[x]); + countCell(cellPixelRatio.ptr(sizeWithBorders - 1 - k)[x]); } } - return totalErrors; } @@ -456,7 +466,6 @@ static float _getMarkerConfidence(const Mat& groundTruthbits, const Mat &cellPix return std::max(0.f, std::min(1.f, normalizedMarkerConfidence)); } - /** * @brief Tries to identify one candidate given the dictionary * @return candidate typ. zero if the candidate is not valid, @@ -486,27 +495,24 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i // analyze border bits int maximumErrorsInBorder = int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate); - int borderErrors = - _getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold); + int borderErrors = 0, invBorderErrors = 0; + _getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold, + borderErrors, invBorderErrors); // check if it is a white marker - if(params.detectInvertedMarker){ - 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 onlyCellPixelRatio = - cellPixelRatio.rowRange(params.markerBorderBits, - cellPixelRatio.rows - params.markerBorderBits) - .colRange(params.markerBorderBits, cellPixelRatio.cols - params.markerBorderBits); + Mat onlyCellPixelRatio = cellPixelRatio( + Rect(params.markerBorderBits, params.markerBorderBits, + cellPixelRatio.cols - 2 * params.markerBorderBits, + cellPixelRatio.rows - 2 * params.markerBorderBits)); // try to identify the marker if(!dictionary.identify(onlyCellPixelRatio, idx, rotation, params.errorCorrectionRate, params.validBitIdThreshold)) @@ -1398,15 +1404,13 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board detectorParams.perspectiveRemovePixelPerCell, detectorParams.perspectiveRemoveIgnoredMarginPerCell, detectorParams.minOtsuStdDev); - Mat bits; - cellPixelRatio.convertTo(bits, CV_8UC1); + Mat onlyCellPixelRatio = cellPixelRatio( + Rect(detectorParams.markerBorderBits, detectorParams.markerBorderBits, + cellPixelRatio.cols - 2 * detectorParams.markerBorderBits, + cellPixelRatio.rows - 2 * detectorParams.markerBorderBits)); - Mat onlyBits = - bits.rowRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits) - .colRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits); - - codeDistance = - dictionary.getDistanceToId(onlyBits, undetectedMarkersIds[i], false); + codeDistance = dictionary.getDistanceToId(onlyCellPixelRatio, undetectedMarkersIds[i], + false, detectorParams.validBitIdThreshold); } // if everythin is ok, assign values to current best match diff --git a/modules/objdetect/src/aruco/aruco_dictionary.cpp b/modules/objdetect/src/aruco/aruco_dictionary.cpp index b8e478fc85..3e975c00bf 100644 --- a/modules/objdetect/src/aruco/aruco_dictionary.cpp +++ b/modules/objdetect/src/aruco/aruco_dictionary.cpp @@ -15,6 +15,86 @@ namespace aruco { using namespace std; +struct CellBitMasks { + CellBitMasks(const Mat &onlyCellPixelRatio, int markerSize, float validBitIdThreshold) + : s((markerSize * markerSize + 8 - 1) / 8), + totalCells(markerSize * markerSize), + temp(4 * s), + not0(temp.data()), not1(not0 + s), notXor(not1 + s), temp0(temp.data() + 3 * s) { + uint8_t* not0Writable = temp.data(); + uint8_t* not1Writable = not0Writable + s; + uint8_t* notXorWritable = not1Writable + s; + + // Fill bit masks of cells that are not black (not0) and not white (not1). + unsigned char not0Byte = 0, not1Byte = 0; + int currentByte = 0, currentBit = 0; + for(int j = 0; j < markerSize; j++) { + const float* cellPixelRatioRow = onlyCellPixelRatio.ptr(j); + for(int i = 0; i < markerSize; i++) { + not0Byte <<= 1; not1Byte <<= 1; + if(cellPixelRatioRow[i] > validBitIdThreshold) not0Byte |= 1; + if(cellPixelRatioRow[i] < 1 - validBitIdThreshold) not1Byte |= 1; + ++currentBit; + if(currentBit == 8) { + not0Writable[currentByte] = not0Byte; + not1Writable[currentByte] = not1Byte; + not0Byte = not1Byte = 0; + ++currentByte; + currentBit = 0; + } + } + } + if(currentBit != 0) { + not0Writable[currentByte] = not0Byte; + not1Writable[currentByte] = not1Byte; + } + + // Computing: notXor = not0 ^ not1 + hal::xor8u(not0, s, not1, s, notXorWritable, s, s, 1, nullptr); + } + + CellBitMasks(const CellBitMasks&) = delete; + CellBitMasks& operator=(const CellBitMasks&) = delete; + + // Smallest Hamming distance between these cell masks and dictionary marker `id`, + // searching the tested rotations; `rotation` returns the best one. + // Mutates the internal buffer (temp0). + int hammingDistanceToId(const Mat& bytesList, int id, bool allRotations, int& rotation) { + CV_Assert(id >= 0 && id < bytesList.rows); + + const unsigned int nRotations = allRotations ? 4u : 1u; + int currentMinDistance = totalCells + 1; + rotation = -1; + + const uchar* bytesRot = bytesList.ptr(id); + for(unsigned int r = 0; r < nRotations; r++, bytesRot += s) { + // Error if (marker is 0 and input is not 0) or (marker is 1 and input is not 1) + // i.e.: (!bytesRot && not0) || (bytesRot && not1) + // This is equivalent to: not0 ^ ((not0 ^ not1) & bytesRot) + // Computing: temp0 = (not0 ^ not1) & bytesRot + hal::and8u(notXor, s, bytesRot, s, temp0, s, s, 1, nullptr); + // Computing the final result (xor is performed internally). + int currentHamming = cv::hal::normHamming(not0, temp0, s); + + if(currentHamming < currentMinDistance) { + currentMinDistance = currentHamming; + rotation = static_cast(r); + // Break for perfect distance. + if(currentMinDistance == 0) break; + } + } + + return currentMinDistance; + } + + const int s; // bytes per rotation + const int totalCells; + std::vector temp; + const uint8_t *not0, *not1, *notXor; + uint8_t *temp0; // internal scratch workspace +}; + + Dictionary::Dictionary(): markerSize(0), maxCorrectionBits(0) {} @@ -46,6 +126,7 @@ bool Dictionary::readDictionary(const cv::FileNode& fn) { return true; } + void Dictionary::writeDictionary(FileStorage& fs, const String &name) { CV_Assert(fs.isOpened()); @@ -75,6 +156,9 @@ void Dictionary::writeDictionary(FileStorage& fs, const String &name) 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); + CV_Assert(onlyCellPixelRatio.type() == CV_32FC1); + + CellBitMasks cellBitMasks(onlyCellPixelRatio, markerSize, validBitIdThreshold); int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate); @@ -82,29 +166,8 @@ bool Dictionary::identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT // search closest marker in dict for(int m = 0; m < bytesList.rows; m++) { - int currentMinDistance = markerSize * markerSize + 1; int currentRotation = -1; - 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; - currentRotation = r; - } - } + int currentMinDistance = cellBitMasks.hammingDistanceToId(bytesList, m, true, currentRotation); // if maxCorrection is fulfilled, return this one if(currentMinDistance <= maxCorrectionRecalculed) { @@ -122,9 +185,8 @@ bool Dictionary::identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rota 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); + Mat(onlyBits > 0).convertTo(candidateBitRatio, CV_32F, 1.0 / 255.0); + return identify(candidateBitRatio, idx, rotation, maxCorrectionRate, DEFAULT_VALID_BIT_ID_THRESHOLD); } @@ -151,6 +213,19 @@ int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) cons } +int Dictionary::getDistanceToId(InputArray onlyCellPixelRatio, int id, bool allRotations, float validBitIdThreshold) const { + + Mat onlyCellPixelRatioMat = onlyCellPixelRatio.getMat(); + CV_Assert(onlyCellPixelRatioMat.rows == markerSize && onlyCellPixelRatioMat.cols == markerSize); + CV_Assert(onlyCellPixelRatioMat.type() == CV_32FC1); + CV_Assert(id >= 0 && id < bytesList.rows); + + int rotation = -1; + CellBitMasks cellBitMasks(onlyCellPixelRatioMat, markerSize, validBitIdThreshold); + return cellBitMasks.hammingDistanceToId(bytesList, id, allRotations, rotation); +} + + void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits) const { CV_Assert(sidePixels >= (markerSize + 2*borderBits)); CV_Assert(id < bytesList.rows); @@ -379,6 +454,7 @@ static Mat _generateRandomMarker(int markerSize, RNG &rng) { return marker; } + /** * @brief Calculate selfDistance of the codification of a marker Mat. Self distance is the Hamming * distance of the marker to itself in the other rotations. diff --git a/modules/objdetect/src/chessboard.cpp b/modules/objdetect/src/chessboard.cpp index 2caa855bf8..fe9341ccd1 100644 --- a/modules/objdetect/src/chessboard.cpp +++ b/modules/objdetect/src/chessboard.cpp @@ -6,7 +6,7 @@ #include "opencv2/flann.hpp" #include "opencv2/geometry.hpp" #include "chessboard.hpp" -#include "math.h" +#include //#define CV_DETECTORS_CHESSBOARD_DEBUG #ifdef CV_DETECTORS_CHESSBOARD_DEBUG diff --git a/modules/objdetect/src/circlesgrid.cpp b/modules/objdetect/src/circlesgrid.cpp index 8088b2c2bd..c08ec77363 100644 --- a/modules/objdetect/src/circlesgrid.cpp +++ b/modules/objdetect/src/circlesgrid.cpp @@ -43,6 +43,7 @@ #include "precomp.hpp" #include "circlesgrid.hpp" #include +#include // Requires CMake flag: DEBUG_opencv_calib=ON //#define DEBUG_CIRCLES @@ -574,8 +575,6 @@ CirclesGridFinder::Segment::Segment(cv::Point2f _s, cv::Point2f _e) : { } -void computeShortestPath(Mat &predecessorMatrix, int v1, int v2, std::vector &path); -void computePredecessorMatrix(const Mat &dm, int verticesCount, Mat &predecessorMatrix); CirclesGridFinderParameters::CirclesGridFinderParameters() { @@ -1209,79 +1208,91 @@ void CirclesGridFinder::computeRNG(Graph &rng, std::vector &vectors rng = Graph(keypoints.size()); vectors.clear(); - //TODO: use more fast algorithm instead of naive N^3 - for (size_t i = 0; i < keypoints.size(); i++) - { - for (size_t j = 0; j < keypoints.size(); j++) - { - if (i == j) - continue; - - Point2f vec = keypoints[i] - keypoints[j]; - double dist = norm(vec); - - bool isNeighbors = true; - for (size_t k = 0; k < keypoints.size(); k++) - { - if (k == i || k == j) - continue; - - double dist1 = norm(keypoints[i] - keypoints[k]); - double dist2 = norm(keypoints[j] - keypoints[k]); - if (dist1 < dist && dist2 < dist) - { - isNeighbors = false; - break; - } - } - - if (isNeighbors) - { - rng.addEdge(i, j); - vectors.push_back(keypoints[i] - keypoints[j]); - if (drawImage != 0) - { - line(*drawImage, keypoints[i], keypoints[j], Scalar(255, 0, 0), 2); - circle(*drawImage, keypoints[i], 3, Scalar(0, 0, 255), -1); - circle(*drawImage, keypoints[j], 3, Scalar(0, 0, 255), -1); - } - } - } - } -} - -void computePredecessorMatrix(const Mat &dm, int verticesCount, Mat &predecessorMatrix) -{ - CV_Assert( dm.type() == CV_32SC1 ); - predecessorMatrix.create(verticesCount, verticesCount, CV_32SC1); - predecessorMatrix = -1; - for (int i = 0; i < predecessorMatrix.rows; i++) - { - for (int j = 0; j < predecessorMatrix.cols; j++) - { - int dist = dm.at (i, j); - for (int k = 0; k < verticesCount; k++) - { - if (dm.at (i, k) == dist - 1 && dm.at (k, j) == 1) - { - predecessorMatrix.at (i, j) = k; - break; - } - } - } - } -} - -static void computeShortestPath(Mat &predecessorMatrix, size_t v1, size_t v2, std::vector &path) -{ - if (predecessorMatrix.at ((int)v1, (int)v2) < 0) - { - path.push_back(v1); + const size_t n = keypoints.size(); + if (n < 2) return; + + // RNG is a subgraph of the Delaunay triangulation, so we only need to test + // Delaunay edges as candidates. This brings the complexity from O(N^3) down + // to O(N^2) in the worst case, and much better in practice for regular grids + // where Delaunay edges (~3N) are almost all RNG edges anyway. + + float minX = keypoints[0].x, minY = keypoints[0].y; + float maxX = minX, maxY = minY; + for (size_t i = 1; i < n; i++) + { + minX = std::min(minX, keypoints[i].x); + minY = std::min(minY, keypoints[i].y); + maxX = std::max(maxX, keypoints[i].x); + maxY = std::max(maxY, keypoints[i].y); } - computeShortestPath(predecessorMatrix, v1, predecessorMatrix.at ((int)v1, (int)v2), path); - path.push_back(v2); + // Subdiv2D requires a rect that strictly contains all points. + const float margin = 1.f; + Rect2f rect(minX - margin, minY - margin, + (maxX - minX) + 2*margin, + (maxY - minY) + 2*margin); + Subdiv2D subdiv(rect); + subdiv.insert(std::vector(keypoints.begin(), keypoints.end())); + + // Map coordinates back to keypoint indices. Subdiv2D stores and returns the + // exact float values we inserted, so direct comparison is safe here. + std::map, size_t> ptToIdx; + for (size_t i = 0; i < n; i++) + ptToIdx[{keypoints[i].x, keypoints[i].y}] = i; + + std::vector edgeList; + subdiv.getEdgeList(edgeList); + + for (const Vec4f& e : edgeList) + { + auto it1 = ptToIdx.find({e[0], e[1]}); + auto it2 = ptToIdx.find({e[2], e[3]}); + // Edges involving the virtual bounding-rect vertices won't be in ptToIdx. + if (it1 == ptToIdx.end() || it2 == ptToIdx.end()) + continue; + + size_t i = it1->second; + size_t j = it2->second; + if (i == j) + continue; + if (i > j) + std::swap(i, j); + + Point2f vec = keypoints[i] - keypoints[j]; + double distSq = (double)vec.x*vec.x + (double)vec.y*vec.y; + + bool isRNG = true; + for (size_t k = 0; k < n; k++) + { + if (k == i || k == j) + continue; + Point2f d1 = keypoints[i] - keypoints[k]; + Point2f d2 = keypoints[j] - keypoints[k]; + double d1Sq = (double)d1.x*d1.x + (double)d1.y*d1.y; + double d2Sq = (double)d2.x*d2.x + (double)d2.y*d2.y; + if (d1Sq < distSq && d2Sq < distSq) + { + isRNG = false; + break; + } + } + + if (isRNG) + { + rng.addEdge(i, j); + // Push both directions; findBasis needs the full set to cluster into + // the 4 groups (two grid axes and their negatives) via k-means. + vectors.push_back(keypoints[i] - keypoints[j]); + vectors.push_back(keypoints[j] - keypoints[i]); + if (drawImage != 0) + { + line(*drawImage, keypoints[i], keypoints[j], Scalar(255, 0, 0), 2); + circle(*drawImage, keypoints[i], 3, Scalar(0, 0, 255), -1); + circle(*drawImage, keypoints[j], 3, Scalar(0, 0, 255), -1); + } + } + } } size_t CirclesGridFinder::findLongestPath(std::vector &basisGraphs, Path &bestPath) @@ -1290,40 +1301,90 @@ size_t CirclesGridFinder::findLongestPath(std::vector &basisGraphs, Path std::vector confidences; size_t bestGraphIdx = 0; - const int infinity = -1; for (size_t graphIdx = 0; graphIdx < basisGraphs.size(); graphIdx++) { const Graph &g = basisGraphs[graphIdx]; - Mat distanceMatrix; - g.floydWarshall(distanceMatrix, infinity); - Mat predecessorMatrix; - computePredecessorMatrix(distanceMatrix, (int)g.getVerticesCount(), predecessorMatrix); + const int n = (int)g.getVerticesCount(); - double maxVal; - Point maxLoc; - minMaxLoc(distanceMatrix, 0, &maxVal, 0, &maxLoc); + // BFS from every vertex to find the diameter (longest shortest path). + // basisGraphs are sparse -- each vertex connects only to grid neighbors in + // one direction -- so this is O(N^2) vs Floyd-Warshall's O(N^3). + std::vector dist(n); + std::queue q; - if (maxVal > longestPaths[0].length) + int maxDist = 0; + int srcBest = 0, dstBest = 0; + + for (int src = 0; src < n; src++) + { + std::fill(dist.begin(), dist.end(), -1); + dist[src] = 0; + q.push(src); + while (!q.empty()) + { + int v = q.front(); q.pop(); + for (size_t nb : g.getNeighbors((size_t)v)) + { + int u = (int)nb; + if (dist[u] < 0) + { + dist[u] = dist[v] + 1; + q.push(u); + } + } + } + for (int dst = 0; dst < n; dst++) + { + if (dist[dst] > maxDist) + { + maxDist = dist[dst]; + srcBest = src; + dstBest = dst; + } + } + } + + if (maxDist > longestPaths[0].length) { longestPaths.clear(); confidences.clear(); bestGraphIdx = graphIdx; } - if (longestPaths.empty() || (maxVal == longestPaths[0].length && graphIdx == bestGraphIdx)) + if (longestPaths.empty() || (maxDist == longestPaths[0].length && graphIdx == bestGraphIdx)) { - Path path = Path(maxLoc.x, maxLoc.y, cvRound(maxVal)); - CV_Assert(maxLoc.x >= 0 && maxLoc.y >= 0) - ; - size_t id1 = static_cast (maxLoc.x); - size_t id2 = static_cast (maxLoc.y); - computeShortestPath(predecessorMatrix, id1, id2, path.vertices); + Path path = Path(srcBest, dstBest, maxDist); + + // BFS again from srcBest to reconstruct the path to dstBest + std::vector pred(n, -1); + std::fill(dist.begin(), dist.end(), -1); + dist[srcBest] = 0; + q.push(srcBest); + while (!q.empty()) + { + int v = q.front(); q.pop(); + for (size_t nb : g.getNeighbors((size_t)v)) + { + int u = (int)nb; + if (dist[u] < 0) + { + dist[u] = dist[v] + 1; + pred[u] = v; + q.push(u); + } + } + } + std::vector pathVertices; + for (int cur = dstBest; cur != srcBest; cur = pred[cur]) + pathVertices.push_back((size_t)cur); + pathVertices.push_back((size_t)srcBest); + std::reverse(pathVertices.begin(), pathVertices.end()); + path.vertices = pathVertices; + longestPaths.push_back(path); int conf = 0; for (int v2 = 0; v2 < (int)path.vertices.size(); v2++) - { - conf += (int)basisGraphs[1 - (int)graphIdx].getDegree(v2); - } + conf += (int)basisGraphs[1 - (int)graphIdx].getDegree(path.vertices[v2]); confidences.push_back(conf); } } @@ -1339,7 +1400,6 @@ size_t CirclesGridFinder::findLongestPath(std::vector &basisGraphs, Path } } - //int bestPathIdx = rand() % longestPaths.size(); bestPath = longestPaths.at(bestPathIdx); bool needReverse = (bestGraphIdx == 0 && keypoints[bestPath.lastVertex].x < keypoints[bestPath.firstVertex].x) || (bestGraphIdx == 1 && keypoints[bestPath.lastVertex].y < keypoints[bestPath.firstVertex].y); diff --git a/modules/objdetect/src/qrcode_encoder.cpp b/modules/objdetect/src/qrcode_encoder.cpp index a58eb08f9e..a08951ad77 100644 --- a/modules/objdetect/src/qrcode_encoder.cpp +++ b/modules/objdetect/src/qrcode_encoder.cpp @@ -1359,11 +1359,11 @@ private: void extractCodewords(Mat& source, std::vector& codewords); bool errorCorrection(std::vector& codewords); bool errorCorrectionBlock(std::vector& codewords); - void decodeSymbols(String& result); + bool decodeSymbols(String& result); void decodeNumeric(String& result); - void decodeAlpha(String& result); + bool decodeAlpha(String& result); void decodeByte(String& result); - void decodeECI(String& result); + bool decodeECI(String& result); void decodeKanji(String& result); void decodeStructuredAppend(String& result); }; @@ -1472,7 +1472,10 @@ bool QRCodeDecoderImpl::run(const Mat& straight, String& decoded_info) { if (!errorCorrection(bitstream.data)) { return false; } - decodeSymbols(decoded_info); + if (!decodeSymbols(decoded_info)) { + decoded_info = ""; + return false; + } return true; } @@ -1737,7 +1740,7 @@ void QRCodeDecoderImpl::extractCodewords(Mat& source, std::vector& code } } -void QRCodeDecoderImpl::decodeSymbols(String& result) { +bool QRCodeDecoderImpl::decodeSymbols(String& result) { CV_Assert(!bitstream.empty()); // Decode depends on the mode @@ -1750,15 +1753,19 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) { } if (currMode == 0 || bitstream.empty()) - return; + return true; if (currMode == QRCodeEncoder::EncodeMode::MODE_NUMERIC) decodeNumeric(result); - else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC) - decodeAlpha(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC) { + if (!decodeAlpha(result)) + return false; + } else if (currMode == QRCodeEncoder::EncodeMode::MODE_BYTE) decodeByte(result); - else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI) - decodeECI(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI) { + if (!decodeECI(result)) + return false; + } else if (currMode == QRCodeEncoder::EncodeMode::MODE_KANJI) decodeKanji(result); else if (currMode == QRCodeEncoder::EncodeMode::MODE_STRUCTURED_APPEND) { @@ -1769,6 +1776,7 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) { else CV_Error(Error::StsNotImplemented, format("mode %d", currMode)); } + return true; } void QRCodeDecoderImpl::decodeNumeric(String& result) { @@ -1788,7 +1796,7 @@ void QRCodeDecoderImpl::decodeNumeric(String& result) { } } -void QRCodeDecoderImpl::decodeAlpha(String& result) { +bool QRCodeDecoderImpl::decodeAlpha(String& result) { static const char map[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', @@ -1798,13 +1806,18 @@ void QRCodeDecoderImpl::decodeAlpha(String& result) { int num = bitstream.next(version <= 9 ? 9 : (version <= 26 ? 11 : 13)); for (int i = 0; i < num / 2; ++i) { int tuple = bitstream.next(11); + if (tuple >= 45 * 45) + return false; result += map[tuple / 45]; result += map[tuple % 45]; } if (num % 2) { int value = bitstream.next(6); + if (value >= 45) + return false; result += map[value]; } + return true; } void QRCodeDecoderImpl::decodeByte(String& result) { @@ -1814,7 +1827,7 @@ void QRCodeDecoderImpl::decodeByte(String& result) { } } -void QRCodeDecoderImpl::decodeECI(String& result) { +bool QRCodeDecoderImpl::decodeECI(String& result) { int eciAssignValue = bitstream.next(8); for (int i = 0; i < 8; ++i) { if (eciAssignValue & 1 << (7 - i)) @@ -1825,8 +1838,7 @@ void QRCodeDecoderImpl::decodeECI(String& result) { if (this->eci == 0) { this->eci = static_cast(eciAssignValue); } - decodeSymbols(result); - + return decodeSymbols(result); } void QRCodeDecoderImpl::decodeKanji(String& result) { diff --git a/modules/objdetect/test/test_boarddetection.cpp b/modules/objdetect/test/test_boarddetection.cpp index f95413ab32..c2e52782c6 100644 --- a/modules/objdetect/test/test_boarddetection.cpp +++ b/modules/objdetect/test/test_boarddetection.cpp @@ -206,6 +206,94 @@ void CV_ArucoRefine::run(int) { } } +// Find the position of a given marker id in the detection results, or -1 if absent. +static int findMarkerIndex(const vector& ids, int markerId) { + for(size_t i = 0; i < ids.size(); i++) { + if(ids[i] == markerId) + return (int)i; + } + return -1; +} + +// Warp a marker image onto an arbitrary quad in the scene and paint it over the +// background. A neutral grey (127) is used as the "background", the marker +// only contains black/white pixels, so everything that stays 127 after the warp +// is background and is left untouched. +static void drawMarkerAtCorners(Mat& image, const Mat& marker, const vector& corners) { + vector originalCorners = { + Point2f(0.f, 0.f), + Point2f((float)marker.cols - 1.f, 0.f), + Point2f((float)marker.cols - 1.f, (float)marker.rows - 1.f), + Point2f(0.f, (float)marker.rows - 1.f) + }; + Mat transformation = getPerspectiveTransform(originalCorners, corners); + + Mat warped(image.size(), image.type(), Scalar::all(127)); + warpPerspective(marker, warped, transformation, image.size(), INTER_NEAREST, BORDER_CONSTANT, Scalar::all(127)); + + Mat mask = warped != 127; + warped.copyTo(image, mask); +} + +// Degrade the marker image: find its first black inner cell and partially fill it with white so +// that the cell's white-pixel ratio becomes ~whiteRatio. This lets the test control how far a +// single cell drifts from its ground-truth bit, which is what validBitIdThreshold gates. +static bool setFirstBlackInnerCellWhiteRatio(Mat& marker, const aruco::Dictionary& dictionary, + int markerId, int markerBorderBits, float whiteRatio) { + const int markerSizeWithBorders = dictionary.markerSize + 2 * markerBorderBits; + const int cellSize = marker.rows / markerSizeWithBorders; + if(marker.cols != marker.rows || cellSize * markerSizeWithBorders != marker.rows) + return false; + + Mat markerBits = dictionary.getMarkerBits(markerId); + for(int y = 0; y < dictionary.markerSize; y++) { + for(int x = 0; x < dictionary.markerSize; x++) { + if(markerBits.ptr(y)[x] != 0.f) + continue; // skip white cells + + Rect cell((x + markerBorderBits) * cellSize, (y + markerBorderBits) * cellSize, + cellSize, cellSize); + marker(cell).setTo(Scalar::all(0)); + + // A centred white square of side sqrt(whiteRatio)*cellSize covers ~whiteRatio of the cell. + int whiteSide = cvRound(cellSize * std::sqrt(whiteRatio)); + whiteSide = std::max(1, std::min(cellSize, whiteSide)); + const int offset = (cellSize - whiteSide) / 2; + marker(Rect(cell.x + offset, cell.y + offset, whiteSide, whiteSide)).setTo(Scalar::all(255)); + return true; + } + } + + return false; +} + +// Drop a marker from the detection results and move its corners to the rejected list, so that +// refineDetectedMarkers() has a rejected candidate to try to recover. +static bool removeMarkerAndMakeRejected(int markerId, vector>& corners, + vector& ids, vector>& rejected) { + const int markerIndex = findMarkerIndex(ids, markerId); + if(markerIndex < 0) + return false; + + rejected.clear(); + rejected.push_back(corners[(size_t)markerIndex]); + corners.erase(corners.begin() + markerIndex); + ids.erase(ids.begin() + markerIndex); + return true; +} + +// Render a flat board image and detect its markers. +// Returns true only when every board marker was found. +static bool generateBoardForRefine(const aruco::GridBoard& board, int markerBorderBits, + Mat& image, const aruco::ArucoDetector& detector, + vector>& corners, vector& ids) { + board.generateImage(Size(760, 760), image, 50, markerBorderBits); + + vector> rejected; + detector.detectMarkers(image, corners, ids, rejected); + return board.getIds().size() == ids.size(); +} + TEST(CV_ArucoBoardPose, accuracy) { CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT); test.safe_run(); @@ -229,6 +317,75 @@ TEST(CV_Aruco3Refine, accuracy) { test.safe_run(); } +// refineDetectedMarkers() must use detectorParams.validBitIdThreshold when matching a rejected +// candidate's cell ratios against the expected marker code. Both cases below refine the very same +// image: a board whose dropped marker 0 is redrawn with one black cell brightened to a 0.6 white +// ratio and differ only in the threshold: the strict default (0.49) treats that cell as a bit +// error and leaves the marker rejected, while a relaxed 0.7 tolerates the deviation and recovers it. +class CV_ArucoRefineValidBitIdThreshold : public testing::Test { +protected: + void SetUp() override { + const int markerBorderBits = 1; + const int markerSidePixels = 300; + + dictionary = aruco::getPredefinedDictionary(aruco::DICT_4X4_50); + board = aruco::GridBoard(Size(2, 2), 1.f, 0.2f, dictionary); + + detectorParameters.markerBorderBits = markerBorderBits; + detectorParameters.perspectiveRemovePixelPerCell = 20; + detectorParameters.perspectiveRemoveIgnoredMarginPerCell = 0.; + + const aruco::ArucoDetector detector(dictionary, detectorParameters, refineParameters); + + // Start from a fully detected board (clean markers, so the threshold is irrelevant here). + ASSERT_TRUE(generateBoardForRefine(board, markerBorderBits, image, detector, corners, ids)); + + // Drop marker 0 so it becomes a rejected candidate for refinement. + ASSERT_TRUE(removeMarkerAndMakeRejected(markerId, corners, ids, rejected)); + + // Draw a degraded version of marker 0 (one black cell at 0.6 white ratio) at its location. + Mat marker; + dictionary.generateImageMarker(markerId, markerSidePixels, marker, markerBorderBits); + ASSERT_TRUE(setFirstBlackInnerCellWhiteRatio(marker, dictionary, markerId, markerBorderBits, 0.6f)); + drawMarkerAtCorners(image, marker, rejected[0]); + } + + // Refine the shared image with a given threshold and report whether marker 0 was recovered. + // refineDetectedMarkers() mutates its inputs, so each attempt runs on its own copy. + bool isMarkerRecovered(float validBitIdThreshold) const { + aruco::DetectorParameters attemptParameters = detectorParameters; + attemptParameters.validBitIdThreshold = validBitIdThreshold; + const aruco::ArucoDetector attemptDetector(dictionary, attemptParameters, refineParameters); + + vector> attemptCorners = corners; + vector attemptIds = ids; + vector> attemptRejected = rejected; + attemptDetector.refineDetectedMarkers(image, board, attemptCorners, attemptIds, attemptRejected); + return findMarkerIndex(attemptIds, markerId) >= 0; + } + + const int markerId = 0; + aruco::Dictionary dictionary; + aruco::GridBoard board; + aruco::DetectorParameters detectorParameters; + aruco::RefineParameters refineParameters{10.f, 1.f, true}; + + Mat image; + vector> corners; + vector ids; + vector> rejected; +}; + +// Strict threshold: the 0.6 white cell is treated as a bit error, so the marker is not recovered. +TEST_F(CV_ArucoRefineValidBitIdThreshold, strictThresholdKeepsMarkerRejected) { + EXPECT_FALSE(isMarkerRecovered(0.49f)); +} + +// Relaxed threshold: the deviation is tolerated, so the marker is recovered. +TEST_F(CV_ArucoRefineValidBitIdThreshold, relaxedThresholdRecoversMarker) { + EXPECT_TRUE(isMarkerRecovered(0.7f)); +} + TEST(CV_ArucoBoardPose, CheckNegativeZ) { double matrixData[9] = { -3.9062571886921410e+02, 0., 4.2350000000000000e+02, @@ -329,6 +486,85 @@ TEST(CV_ArucoDictionary, extendDictionary) { ASSERT_EQ(custom_dictionary.bytesList.rows, 150); ASSERT_EQ(cv::norm(custom_dictionary.bytesList, base_dictionary.bytesList.rowRange(0, 150)), 0.); } + +// Unit-test both getDistanceToId() overloads on a known marker: the existing bit-based overload +// must keep its exact Hamming behaviour, and the new ratio-based overload must count a cell as an +// error only when it deviates from the expected bit by more than validBitIdThreshold. +TEST(CV_ArucoDictionary, getDistanceToIdCellPixelRatio) { + const int markerId = 0; + const float validBitIdThreshold = 0.49f; + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_4X4_50); + + // Bit overload: the exact marker bits are at distance 0 from their own id. + Mat bits = aruco::Dictionary::getBitsFromByteList(dictionary.bytesList.rowRange(markerId, markerId + 1), + dictionary.markerSize); + EXPECT_EQ(0, dictionary.getDistanceToId(bits, markerId, false)); + + // Bit overload: flipping a single bit yields a Hamming distance of exactly 1. + Mat erroneousBits = bits.clone(); + erroneousBits.ptr(0)[0] = (uchar)!erroneousBits.ptr(0)[0]; + EXPECT_EQ(1, dictionary.getDistanceToId(erroneousBits, markerId, false)); + + // Ground-truth bit values (0.f or 1.f) for the ratio overload checks below. + Mat markerRatio = dictionary.getMarkerBits(markerId); + const float expectedBit = markerRatio.ptr(0)[0]; + + // Ratio overload: a 0.4 drift toward the wrong value stays within the 0.49 tolerance -> no error. + Mat acceptedRatio = markerRatio.clone(); + acceptedRatio.ptr(0)[0] = expectedBit > 0.5f ? 0.6f : 0.4f; + EXPECT_EQ(0, dictionary.getDistanceToId(acceptedRatio, markerId, false, validBitIdThreshold)); + + // Ratio overload: a 0.6 drift exceeds the 0.49 tolerance -> the cell counts as one error. + Mat rejectedRatio = markerRatio.clone(); + rejectedRatio.ptr(0)[0] = expectedBit > 0.5f ? 0.4f : 0.6f; + EXPECT_EQ(1, dictionary.getDistanceToId(rejectedRatio, markerId, false, validBitIdThreshold)); +} + +// 5x5 markers leave one meaningful bit in the final packed byte. Flip only that cell +// far enough from its expected value and verify that the ratio distance counts it. +TEST(CV_ArucoDictionary, getDistanceToIdCellPixelRatioPartialByte) { + const int markerId = 15; + const float validBitIdThreshold = 0.49f; + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_5X5_50); + + Mat markerRatio = dictionary.getMarkerBits(markerId); + EXPECT_EQ(0, dictionary.getDistanceToId(markerRatio, markerId, false, validBitIdThreshold)); + + Mat rotatedMarkerRatio = dictionary.getMarkerBits(markerId, 1); + EXPECT_EQ(0, dictionary.getDistanceToId(rotatedMarkerRatio, markerId, true, validBitIdThreshold)); + + Mat rejectedRatio = markerRatio.clone(); + float& lastCellRatio = rejectedRatio.ptr(dictionary.markerSize - 1)[dictionary.markerSize - 1]; + lastCellRatio = lastCellRatio > 0.5f ? 0.4f : 0.6f; + EXPECT_EQ(1, dictionary.getDistanceToId(rejectedRatio, markerId, false, validBitIdThreshold)); +} + +TEST(CV_ArucoDictionary, identifyBitMask) { + const int markerId = 7; + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_4X4_50); + + // Start with a 0/1 bit matrix for the marker and confirm that the bit-based + // identify overload handles it without any ratio threshold input. + Mat bits = aruco::Dictionary::getBitsFromByteList(dictionary.bytesList.rowRange(markerId, markerId + 1), + dictionary.markerSize); + + int idx = -1; + int rotation = -1; + ASSERT_TRUE(dictionary.identify(bits, idx, rotation, 0.0)); + EXPECT_EQ(markerId, idx); + EXPECT_EQ(0, rotation); + + // OpenCV comparisons produce masks with values 0 and 255, not 0 and 1. The raw-bit + // identify overload must normalize those masks before delegating to the ratio path. + Mat bitMask; + bits.convertTo(bitMask, CV_8U, 255.0); + idx = -1; + rotation = -1; + ASSERT_TRUE(dictionary.identify(bitMask, idx, rotation, 0.0)); + EXPECT_EQ(markerId, idx); + EXPECT_EQ(0, rotation); +} + TEST(CV_ArucoBoardGenerateImage_RotationTest, HandlesRotatedMarkersWithoutBoundingBoxError) { using namespace cv; diff --git a/modules/objdetect/test/test_chesscorners.cpp b/modules/objdetect/test/test_chesscorners.cpp index f8d4f7f20e..e9ff554939 100644 --- a/modules/objdetect/test/test_chesscorners.cpp +++ b/modules/objdetect/test/test_chesscorners.cpp @@ -853,6 +853,97 @@ TEST(Calib3d_RotatedCirclesPatternDetector, issue_24964) EXPECT_LE(error, precise_success_error_level); } +// Generate a perfect W x H symmetric circle grid at the given spacing. +// Points are returned in shuffled order so the detector can't rely on input ordering. +static std::vector makeSyntheticSymmetricGrid(int cols, int rows, float spacing) +{ + std::vector pts; + pts.reserve(cols * rows); + for (int r = 0; r < rows; r++) + for (int c = 0; c < cols; c++) + pts.push_back(Point2f(c * spacing, r * spacing)); + + cv::RNG& rng = cv::theRNG(); + for (int k = (int)pts.size() - 1; k > 0; k--) + std::swap(pts[k], pts[rng.uniform(0, k + 1)]); + + return pts; +} + +// Generate an asymmetric circle grid. Even rows start at x=0, odd rows are offset by spacing/2. +static std::vector makeSyntheticAsymmetricGrid(int cols, int rows, float spacing) +{ + std::vector pts; + pts.reserve(cols * rows); + for (int r = 0; r < rows; r++) + for (int c = 0; c < cols; c++) + pts.push_back(Point2f(c * spacing + (r % 2) * spacing * 0.5f, r * spacing * 0.5f)); + + cv::RNG& rng = cv::theRNG(); + for (int k = (int)pts.size() - 1; k > 0; k--) + std::swap(pts[k], pts[rng.uniform(0, k + 1)]); + + return pts; +} + +typedef testing::TestWithParam Calib3d_CirclesGrid_RNG_Symmetric; + +TEST_P(Calib3d_CirclesGrid_RNG_Symmetric, synthetic) +{ + // Verify that findCirclesGrid correctly detects synthetic perfect symmetric grids of + // various sizes. This exercises the computeRNG path (Delaunay-based) end-to-end. + const float spacing = 30.f; + const Size gridSize = GetParam(); + + std::vector pts = makeSyntheticSymmetricGrid(gridSize.width, gridSize.height, spacing); + + std::vector centers; + bool found = findCirclesGrid(Mat(pts), gridSize, centers, + CALIB_CB_SYMMETRIC_GRID, Ptr()); + + ASSERT_TRUE(found) << "Symmetric grid " << gridSize.width << "x" << gridSize.height << " not detected"; + ASSERT_EQ((int)centers.size(), gridSize.area()); + for (const Point2f& c : centers) + { + bool matched = false; + for (const Point2f& p : pts) + if (cv::norm(c - p) < 1.f) { matched = true; break; } + EXPECT_TRUE(matched) << "Detected center " << c << " does not match any input point " + << "for grid " << gridSize.width << "x" << gridSize.height; + } +} + +INSTANTIATE_TEST_CASE_P(/**/, Calib3d_CirclesGrid_RNG_Symmetric, + testing::Values(Size(4, 4), Size(6, 5), Size(8, 6), Size(10, 8))); + +typedef testing::TestWithParam Calib3d_CirclesGrid_RNG_Asymmetric; + +TEST_P(Calib3d_CirclesGrid_RNG_Asymmetric, synthetic) +{ + const float spacing = 30.f; + const Size gridSize = GetParam(); + + std::vector pts = makeSyntheticAsymmetricGrid(gridSize.width, gridSize.height, spacing); + + std::vector centers; + bool found = findCirclesGrid(Mat(pts), gridSize, centers, + CALIB_CB_ASYMMETRIC_GRID, Ptr()); + + ASSERT_TRUE(found) << "Asymmetric grid " << gridSize.width << "x" << gridSize.height << " not detected"; + ASSERT_EQ((int)centers.size(), gridSize.area()); + for (const Point2f& c : centers) + { + bool matched = false; + for (const Point2f& p : pts) + if (cv::norm(c - p) < 1.f) { matched = true; break; } + EXPECT_TRUE(matched) << "Detected center " << c << " does not match any input point " + << "for grid " << gridSize.width << "x" << gridSize.height; + } +} + +INSTANTIATE_TEST_CASE_P(/**/, Calib3d_CirclesGrid_RNG_Asymmetric, + testing::Values(Size(4, 6), Size(5, 8))); + TEST(Calib3d_CornerOrdering, issue_26830) { const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/"; const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_white.png"); diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index c49480d38f..f6dfb14aa1 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -571,6 +571,54 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25) EXPECT_EQ(expect_msg, decoded_msg); } +TEST(Objdetect_QRCode_decode, decode_alphanumeric_out_of_range) +{ + // Version 1 QR with a valid Reed-Solomon block whose alphanumeric segment + // carries an 11-bit pair value of 2047. decodeAlpha computes 2047 / 45 == 45 + // and used to read map[45], one past the 45-entry table, leaking an adjacent + // byte into the decoded string. The decoder must reject it and return an + // empty string instead. + static const char* modules[21] = { + "000000011011010000000", + "011111010011010111110", + "010001011011010100010", + "010001010011010100010", + "010001011011010100010", + "011111010011010111110", + "000000010101010000000", + "111111111011011111111", + "000001000011001010101", + "111111101001011011000", + "000111010001011011000", + "001001100111011011000", + "000001011011011011000", + "111111110101011011000", + "000000010111011011001", + "011111011101011011001", + "010001010001011011011", + "010001010001011011011", + "010001010001011011011", + "011111010001011011010", + "000000010001011011011" + }; + Mat qr(21, 21, CV_8UC1); + for (int i = 0; i < 21; i++) + for (int j = 0; j < 21; j++) + qr.at(i, j) = modules[i][j] == '1' ? 255 : 0; + + Mat src; + cv::resize(qr, src, qr.size() * 10, 0, 0, INTER_NEAREST); + cv::copyMakeBorder(src, src, 40, 40, 40, 40, BORDER_CONSTANT, Scalar(255)); + + QRCodeDetector qrcode; + std::vector corners; + ASSERT_TRUE(qrcode.detect(src, corners)); + Mat straight_barcode; + std::string decoded_info; + EXPECT_NO_THROW(decoded_info = qrcode.decode(src, corners, straight_barcode)); + EXPECT_TRUE(decoded_info.empty()); +} + TEST_P(Objdetect_QRCode_detectAndDecodeMulti, decode_9_qrcodes_version7) { const std::string name_current_image = "9_qrcodes_version7.jpg"; diff --git a/modules/photo/src/npr.hpp b/modules/photo/src/npr.hpp index 6e4bb7355a..940151f5c9 100644 --- a/modules/photo/src/npr.hpp +++ b/modules/photo/src/npr.hpp @@ -44,7 +44,7 @@ #include #include #include -#include "math.h" +#include using namespace std; diff --git a/modules/ts/src/ts_gtest.cpp b/modules/ts/src/ts_gtest.cpp index d3752a5fe4..faef082c99 100644 --- a/modules/ts/src/ts_gtest.cpp +++ b/modules/ts/src/ts_gtest.cpp @@ -10452,20 +10452,41 @@ class CapturedStream { // The ctor redirects the stream to a temporary file. explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { # if GTEST_OS_WINDOWS - char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT - char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT + wchar_t temp_dir_path_w[MAX_PATH + 1] = { L'\0' }; + wchar_t temp_file_path_w[MAX_PATH + 1] = { L'\0' }; - ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); - const UINT success = ::GetTempFileNameA(temp_dir_path, - "gtest_redir", + ::GetTempPathW(sizeof(temp_dir_path_w)/sizeof(wchar_t), temp_dir_path_w); + // Convert to UTF-8 to support Unicode paths + int len = WideCharToMultiByte(CP_UTF8, 0, temp_dir_path_w, -1, NULL, 0, NULL, NULL); + std::string temp_dir_path; + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, temp_dir_path_w, -1, utf8_buf.data(), len, NULL, NULL); + temp_dir_path = std::string(utf8_buf.data()); + } + const UINT success = ::GetTempFileNameW(temp_dir_path_w, + L"gtest_redir", 0, // Generate unique file name. - temp_file_path); - GTEST_CHECK_(success != 0) - << "Unable to create a temporary file in " << temp_dir_path; - const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); - GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " - << temp_file_path; - filename_ = temp_file_path; + temp_file_path_w); + // Convert filename back to UTF-8 + std::string temp_file_path; + int captured_fd = -1; + if (success != 0) + { + len = WideCharToMultiByte(CP_UTF8, 0, temp_file_path_w, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, temp_file_path_w, -1, utf8_buf.data(), len, NULL, NULL); + temp_file_path = std::string(utf8_buf.data()); + } + // Create file and get fd for redirect + captured_fd = _open(temp_file_path.c_str(), _O_CREAT | _O_WRONLY | _O_TRUNC, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " + << temp_file_path; + filename_ = temp_file_path; + } # else // There's no guarantee that a test has write access to the current // directory, so we create the temporary file in the /tmp directory diff --git a/modules/video/src/optflowgf.cpp b/modules/video/src/optflowgf.cpp index 7d654f747b..5827ebea28 100644 --- a/modules/video/src/optflowgf.cpp +++ b/modules/video/src/optflowgf.cpp @@ -822,7 +822,7 @@ private: float m_ig[4]; void setPolynomialExpansionConsts(int n, double sigma) { - std::vector buf(n*6 + 3); + AutoBuffer buf(n*6 + 3); float* g = &buf[0] + n; float* xg = g + n*2 + 1; float* xxg = xg + n*2 + 1; diff --git a/modules/videoio/src/cap_avfoundation_mac.mm b/modules/videoio/src/cap_avfoundation_mac.mm index 547a59b36f..09f1cff418 100644 --- a/modules/videoio/src/cap_avfoundation_mac.mm +++ b/modules/videoio/src/cap_avfoundation_mac.mm @@ -1211,7 +1211,21 @@ CvVideoWriter_AVFoundation::~CvVideoWriter_AVFoundation() { if (mMovieWriterInput && mMovieWriter && mMovieWriterAdaptor) { [mMovieWriterInput markAsFinished]; - [mMovieWriter finishWriting]; + + // Use finishWritingWithCompletionHandler + semaphore to synchronously + // wait for the async completion block to finish, avoiding a race + // condition where NSOperation KVO observer blocks are dispatched + // asynchronously to GCD worker threads and may access already-released + // objects after the autorelease pool is drained. + // Replaces deprecated finishWriting (sync) which falsely appears safe + // but does NOT wait for internal NSOperation KVO callbacks to complete. + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + [mMovieWriter finishWritingWithCompletionHandler:^{ + dispatch_semaphore_signal(sem); + }]; + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + dispatch_release(sem); + [mMovieWriter release]; [mMovieWriterInput release]; [mMovieWriterAdaptor release]; diff --git a/samples/cpp/morphology2.cpp b/samples/cpp/morphology2.cpp index e6bb659cac..159dff2bc3 100644 --- a/samples/cpp/morphology2.cpp +++ b/samples/cpp/morphology2.cpp @@ -10,7 +10,7 @@ using namespace cv; static void help(char** argv) { -printf("\nShow off image morphology: erosion, dialation, open and close\n" +printf("\nShow off image morphology: erosion, dilation, opening and closing\n" "Call:\n %s [image]\n" "This program also shows use of rect, ellipse, cross and diamond kernels\n\n", argv[0]); printf( "Hot keys: \n" diff --git a/samples/cpp/snippets/detect_blob.cpp b/samples/cpp/snippets/detect_blob.cpp index c57b5b646c..cf5236b038 100644 --- a/samples/cpp/snippets/detect_blob.cpp +++ b/samples/cpp/snippets/detect_blob.cpp @@ -107,6 +107,8 @@ int main(int argc, char *argv[]) pDefaultBLOB.filterByConvexity = false; pDefaultBLOB.minConvexity = 0.95f; pDefaultBLOB.maxConvexity = (float)1e37; + // Enable contour collection so we can draw blob outlines (see getBlobContours() below). + pDefaultBLOB.collectContours = true; // Descriptor array for BLOB vector typeDesc; // Param array for BLOB @@ -189,6 +191,11 @@ int main(int argc, char *argv[]) int i = 0; for (vector::iterator k = keyImg.begin(); k != keyImg.end(); ++k, ++i) circle(result, k->pt, (int)k->size, palette[i % 65536]); + // Retrieve the per-blob contours collected during detect() and outline each blob. + // Requires SimpleBlobDetector::Params::collectContours to be true. + const vector >& blobContours = sbd->getBlobContours(); + for (size_t c = 0; c < blobContours.size(); ++c) + drawContours(result, blobContours, (int)c, Scalar(0, 255, 0), 1); } namedWindow(*itDesc + label, WINDOW_AUTOSIZE); imshow(*itDesc + label, result); diff --git a/samples/python/qrcode.py b/samples/python/qrcode.py index e6521eec5e..6af554ae1c 100644 --- a/samples/python/qrcode.py +++ b/samples/python/qrcode.py @@ -12,6 +12,19 @@ import cv2 as cv import argparse +# Colors for distinguishing multiple QR codes visually +QR_COLORS = [ + (0, 255, 0), # green + (255, 0, 0), # blue + (0, 0, 255), # red + (255, 255, 0), # cyan + (0, 255, 255), # yellow + (255, 0, 255), # magenta + (128, 255, 0), # lime + (255, 128, 0), # orange +] + + class QrSample: def __init__(self, args): self.fname = '' @@ -36,15 +49,14 @@ class QrSample: cv.putText(result, message, (20, 20), 1, cv.FONT_HERSHEY_DUPLEX, (0, 0, 255)) - def drawQRCodeContours(self, image, cnt): + def drawQRCodeContours(self, image, cnt, color=(0, 255, 0)): if cnt.size != 0: rows, cols, _ = image.shape show_radius = 2.813 * ((rows / cols) if rows > cols else (cols / rows)) contour_radius = show_radius * 0.4 - cv.drawContours(image, [cnt], 0, (0, 255, 0), int(round(contour_radius))) + cv.drawContours(image, [cnt], 0, color, int(round(contour_radius))) tpl = cnt.reshape((-1, 2)) for x in tuple(tpl.tolist()): - color = (255, 0, 0) cv.circle(image, tuple(x), int(round(contour_radius)), color, -1) def drawQRCodeResults(self, result, points, decode_info, fps): @@ -54,7 +66,8 @@ class QrSample: if n > 0: for i in range(n): cnt = np.array(points[i]).reshape((-1, 1, 2)).astype(np.int32) - self.drawQRCodeContours(result, cnt) + color = QR_COLORS[i % len(QR_COLORS)] + self.drawQRCodeContours(result, cnt, color) msg = 'QR[{:d}]@{} : '.format(i, *(cnt.reshape(1, -1).tolist())) print(msg, end="") if len(decode_info) > i: