diff --git a/doc/tutorials/core/adding_images/adding_images.markdown b/doc/tutorials/core/adding_images/adding_images.markdown index cead427843..21f9c7f46c 100644 --- a/doc/tutorials/core/adding_images/adding_images.markdown +++ b/doc/tutorials/core/adding_images/adding_images.markdown @@ -25,7 +25,7 @@ Theory @note The explanation below belongs to the book [Computer Vision: Algorithms and - Applications](http://szeliski.org/Book/) by Richard Szeliski + Applications](https://szeliski.org/Book/) by Richard Szeliski From our previous tutorial, we already know a bit of *Pixel operators*. An interesting dyadic (two-input) operator is the *linear blend operator*: diff --git a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown index 1eda463b9f..f676b7cafa 100644 --- a/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown +++ b/doc/tutorials/core/basic_linear_transform/basic_linear_transform.markdown @@ -27,7 +27,7 @@ Theory @note The explanation below belongs to the book [Computer Vision: Algorithms and - Applications](http://szeliski.org/Book/) by Richard Szeliski + Applications](https://szeliski.org/Book/) by Richard Szeliski ### Image Processing @@ -266,14 +266,14 @@ be the opposite with \f$ \gamma > 1 \f$. The following image has been corrected with: \f$ \alpha = 1.3 \f$ and \f$ \beta = 40 \f$. -![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg) +![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_linear_transform_correction.jpg) { width=90% } The overall brightness has been improved but you can notice that the clouds are now greatly saturated due to the numerical saturation of the implementation used ([highlight clipping](https://en.wikipedia.org/wiki/Clipping_(photography)) in photography). The following image has been corrected with: \f$ \gamma = 0.4 \f$. -![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg) +![By Visem (Own work) [CC BY-SA 3.0], via Wikimedia Commons](images/Basic_Linear_Transform_Tutorial_gamma_correction.jpg) { width=90% } The gamma correction should tend to add less saturation effect as the mapping is non linear and there is no numerical saturation possible as in the previous method. diff --git a/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.markdown b/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.markdown index 197292808f..7f238007eb 100644 --- a/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.markdown +++ b/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.markdown @@ -4,6 +4,7 @@ File Input and Output using XML / YAML / JSON files {#tutorial_file_input_output @tableofcontents @prev_tutorial{tutorial_discrete_fourier_transform} +@next_tutorial{tutorial_how_to_use_OpenCV_parallel_for_} @next_tutorial{tutorial_how_to_use_OpenCV_parallel_for_new} | | | diff --git a/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown index 389181de3b..56638721e0 100644 --- a/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown +++ b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_/how_to_use_OpenCV_parallel_for_.markdown @@ -1,16 +1,18 @@ -How to use the OpenCV parallel_for_ to parallelize your code {#tutorial_how_to_use_OpenCV_parallel_for_} +How to use the OpenCV parallel_for_ function to parallelize your code (Mandelbrot set example) {#tutorial_how_to_use_OpenCV_parallel_for_} ================================================================== @tableofcontents @prev_tutorial{tutorial_file_input_output_with_xml_yml} +@next_tutorial{tutorial_how_to_use_OpenCV_parallel_for_new} +@next_tutorial{tutorial_univ_intrin} | | | | -: | :- | | Compatibility | OpenCV >= 3.0 | -@note See also C++ lambda usage with parallel for in [tuturial](@ref tutorial_how_to_use_OpenCV_parallel_for_new). +@note See this [tuturial](@ref tutorial_how_to_use_OpenCV_parallel_for_new) for a `parallel_for_` usage applied to image convolution. Goal ---- @@ -26,17 +28,17 @@ Precondition ------------ The first precondition is to have OpenCV built with a parallel framework. -In OpenCV 3.2, the following parallel frameworks are available in that order: +In OpenCV 4, the following parallel frameworks are available in that order: + 1. Intel Threading Building Blocks (3rdparty library, should be explicitly enabled) -2. C= Parallel C/C++ Programming Language Extension (3rdparty library, should be explicitly enabled) -3. OpenMP (integrated to compiler, should be explicitly enabled) -4. APPLE GCD (system wide, used automatically (APPLE only)) -5. Windows RT concurrency (system wide, used automatically (Windows RT only)) -6. Windows concurrency (part of runtime, used automatically (Windows only - MSVC++ >= 10)) -7. Pthreads (if available) +2. OpenMP (integrated to compiler, should be explicitly enabled) +3. APPLE GCD (system wide, used automatically (APPLE only)) +4. Windows RT concurrency (system wide, used automatically (Windows RT only)) +5. Windows concurrency (part of runtime, used automatically (Windows only - MSVC++ >= 10)) +6. Pthreads (if available) As you can see, several parallel frameworks can be used in the OpenCV library. Some parallel libraries -are third party libraries and have to be explicitly built and enabled in CMake (e.g. TBB, C=), others are +are third party libraries and have to be explicitly built and enabled in CMake (e.g. TBB), others are automatically available with the platform (e.g. APPLE GCD) but chances are that you should be enable to have access to a parallel framework either directly or by enabling the option in CMake and rebuild the library. @@ -120,7 +122,7 @@ Escape time algorithm implementation @snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-escape-time-algorithm -Here, we used the [`std::complex`](http://en.cppreference.com/w/cpp/numeric/complex) template class to represent a +Here, we used the [`std::complex`](https://en.cppreference.com/cpp/numeric/complex) template class to represent a complex number. This function performs the test to check if the pixel is in set or not and returns the "escaped" iteration. Sequential Mandelbrot implementation @@ -143,7 +145,7 @@ Finally, to assign the grayscale value to the pixels, we use the following rule: Using a linear scale transformation is not enough to perceive the grayscale variation. To overcome this, we will boost the perception by using a square root scale transformation (borrowed from Jeremy D. Frens in his -[blog post](http://www.programming-during-recess.net/2016/06/26/color-schemes-for-mandelbrot-sets/)): +[blog post](https://web.archive.org/web/20250419124416/http://www.programming-during-recess.net/2016/06/26/color-schemes-for-mandelbrot-sets/)): \f$ f \left( x \right) = \sqrt{\frac{x}{\text{maxIter}}} \times 255 \f$ ![](images/how_to_use_OpenCV_parallel_for_sqrt_scale_transformation.png) @@ -179,7 +181,7 @@ or setting `nstripes=2` should be the same as by default it will use all the pro workload only on two threads. @note -C++ 11 standard allows to simplify the parallel implementation by get rid of the `ParallelMandelbrot` class and replacing it with lambda expression: +C++ 11 standard allows simplifying the parallel implementation by get rid of the `ParallelMandelbrot` class and replacing it with lambda expression: @snippet how_to_use_OpenCV_parallel_for_.cpp mandelbrot-parallel-call-cxx11 diff --git a/doc/tutorials/core/how_to_use_OpenCV_parallel_for_new/how_to_use_OpenCV_parallel_for_new.markdown b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_new/how_to_use_OpenCV_parallel_for_new.markdown index ee4db010a1..4d19be524f 100644 --- a/doc/tutorials/core/how_to_use_OpenCV_parallel_for_new/how_to_use_OpenCV_parallel_for_new.markdown +++ b/doc/tutorials/core/how_to_use_OpenCV_parallel_for_new/how_to_use_OpenCV_parallel_for_new.markdown @@ -1,9 +1,10 @@ -How to use the OpenCV parallel_for_ to parallelize your code {#tutorial_how_to_use_OpenCV_parallel_for_new} +How to use the OpenCV parallel_for_ function to parallelize your code (convolution example) {#tutorial_how_to_use_OpenCV_parallel_for_new} ================================================================== @tableofcontents @prev_tutorial{tutorial_file_input_output_with_xml_yml} +@prev_tutorial{tutorial_how_to_use_OpenCV_parallel_for_} @next_tutorial{tutorial_univ_intrin} | | | @@ -122,7 +123,7 @@ For example, we can either To set the number of threads, you can use: @ref cv::setNumThreads. You can also specify the number of splitting using the nstripes parameter in @ref cv::parallel_for_. For instance, if your processor has 4 threads, setting `cv::setNumThreads(2)` or setting `nstripes=2` should be the same as by default it will use all the processor threads available but will split the workload only on two threads. -@note C++ 11 standard allows to simplify the parallel implementation by get rid of the `parallelConvolution` class and replacing it with lambda expression: +@note C++ 11 standard allows simplifying the parallel implementation by getting rid of the `parallelConvolution` class and replacing it with lambda expression: @snippet how_to_use_OpenCV_parallel_for_new.cpp convolution-parallel-cxx11 diff --git a/doc/tutorials/core/table_of_content_core.markdown b/doc/tutorials/core/table_of_content_core.markdown index 00f874edc0..2a5a4cbd08 100644 --- a/doc/tutorials/core/table_of_content_core.markdown +++ b/doc/tutorials/core/table_of_content_core.markdown @@ -14,5 +14,6 @@ The Core Functionality (core module) {#tutorial_table_of_content_core} ##### Advanced - @subpage tutorial_discrete_fourier_transform - @subpage tutorial_file_input_output_with_xml_yml +- @subpage tutorial_how_to_use_OpenCV_parallel_for_ - @subpage tutorial_how_to_use_OpenCV_parallel_for_new - @subpage tutorial_univ_intrin diff --git a/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.markdown b/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.markdown index 785cba0ad6..f0859ce8e5 100644 --- a/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.markdown +++ b/doc/tutorials/imgproc/gausian_median_blur_bilateral_filter/gausian_median_blur_bilateral_filter.markdown @@ -26,7 +26,7 @@ Theory ------ @note The explanation below belongs to the book [Computer Vision: Algorithms and -Applications](http://szeliski.org/Book/) by Richard Szeliski and to *LearningOpenCV* +Applications](https://szeliski.org/Book/) by Richard Szeliski and to *LearningOpenCV* - *Smoothing*, also called *blurring*, is a simple and frequently used image processing operation. diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp index 7736b884fb..b7a0093516 100644 --- a/hal/ipp/src/warp_ipp.cpp +++ b/hal/ipp/src/warp_ipp.cpp @@ -228,10 +228,10 @@ int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step /* C1 C2 C3 C4 */ char impl[CV_DEPTH_MAX][4][2]={{{0, 0}, {0, 0}, {0, 0}, {0, 0}}, //8U {{0, 0}, {0, 0}, {0, 0}, {0, 0}}, //8S - {{0, 0}, {0, 0}, {0, 1}, {0, 1}}, //16U - {{1, 1}, {0, 0}, {1, 1}, {1, 1}}, //16S + {{0, 0}, {0, 0}, {0, 0}, {0, 1}}, //16U + {{1, 1}, {0, 0}, {1, 0}, {1, 1}}, //16S {{1, 1}, {0, 0}, {1, 0}, {1, 1}}, //32S - {{1, 0}, {0, 0}, {0, 0}, {1, 0}}, //32F + {{0, 0}, {0, 0}, {0, 0}, {1, 0}}, //32F {{0, 0}, {0, 0}, {0, 0}, {0, 0}}}; //64F #endif diff --git a/hal/riscv-rvv/include/imgproc.hpp b/hal/riscv-rvv/include/imgproc.hpp index 8ff5327ab9..fb3c9c307d 100644 --- a/hal/riscv-rvv/include/imgproc.hpp +++ b/hal/riscv-rvv/include/imgproc.hpp @@ -238,10 +238,8 @@ int integral(int depth, int sdepth, int sqdepth, uchar* tilted_data, [[maybe_unused]] size_t tilted_step, int width, int height, int cn); -// Diasbled due to accuracy issue. -// Details see https://github.com/opencv/opencv/issues/27407. -//#undef cv_hal_integral -//#define cv_hal_integral cv::rvv_hal::imgproc::integral +#undef cv_hal_integral +#define cv_hal_integral cv::rvv_hal::imgproc::integral /* ############ laplacian ############ */ int laplacian(const uint8_t* src_data, size_t src_step, @@ -276,6 +274,16 @@ int canny(const uint8_t *src_data, size_t src_step, #undef cv_hal_canny #define cv_hal_canny cv::rvv_hal::imgproc::canny +/* ############ spatialGradient ############ */ +int spatialGradient(const uint8_t* src_data, size_t src_step, + int16_t* dx_data, size_t dx_step, + int16_t* dy_data, size_t dy_step, + int width, int height, + int ksize, int border_type); + +#undef cv_hal_spatialGradient +#define cv_hal_spatialGradient cv::rvv_hal::imgproc::spatialGradient + #endif // CV_HAL_RVV_1P0_ENABLED #if CV_HAL_RVV_071_ENABLED diff --git a/hal/riscv-rvv/src/core/convert_scale.cpp b/hal/riscv-rvv/src/core/convert_scale.cpp index bb0286354c..003bf40a32 100644 --- a/hal/riscv-rvv/src/core/convert_scale.cpp +++ b/hal/riscv-rvv/src/core/convert_scale.cpp @@ -62,6 +62,141 @@ inline int convertScale_8U32F(const uchar* src, size_t src_step, uchar* dst, siz return CV_HAL_ERROR_OK; } +inline int convertScale_8U16U(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, int width, int height, double alpha, double beta) +{ + if (alpha == 1.0 && beta == 0.0) + { + for (int i = 0; i < height; i++) + { + const uchar* src_row = src + i * src_step; + ushort* dst_row = reinterpret_cast(dst + i * dst_step); + int vl; + for (int j = 0; j < width; j += vl) + { + vl = __riscv_vsetvl_e8m2(width - j); + auto vec_src = __riscv_vle8_v_u8m2(src_row + j, vl); + auto vec_dst = __riscv_vzext_vf2(vec_src, vl); + __riscv_vse16_v_u16m4(dst_row + j, vec_dst, vl); + } + } + + return CV_HAL_ERROR_OK; + } + + int vlmax = __riscv_vsetvlmax_e32m8(); + auto vec_b = __riscv_vfmv_v_f_f32m8(beta, vlmax); + float a = alpha; + + for (int i = 0; i < height; i++) + { + const uchar* src_row = src + i * src_step; + ushort* dst_row = reinterpret_cast(dst + i * dst_step); + int vl; + for (int j = 0; j < width; j += vl) + { + vl = __riscv_vsetvl_e8m2(width - j); + auto vec_src = __riscv_vle8_v_u8m2(src_row + j, vl); + auto vec_src_u16 = __riscv_vzext_vf2(vec_src, vl); + auto vec_src_f32 = __riscv_vfwcvt_f(vec_src_u16, vl); + auto vec_fma = __riscv_vfmadd(vec_src_f32, a, vec_b, vl); + auto vec_dst = __riscv_vfncvt_xu(vec_fma, vl); + __riscv_vse16_v_u16m4(dst_row + j, vec_dst, vl); + } + } + + return CV_HAL_ERROR_OK; +} + +inline int convertScale_8U16S(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, int width, int height, double alpha, double beta) +{ + if (alpha == 1.0 && beta == 0.0) + { + for (int i = 0; i < height; i++) + { + const uchar* src_row = src + i * src_step; + short* dst_row = reinterpret_cast(dst + i * dst_step); + int vl; + for (int j = 0; j < width; j += vl) + { + vl = __riscv_vsetvl_e8m2(width - j); + auto vec_src = __riscv_vle8_v_u8m2(src_row + j, vl); + auto vec_dst = __riscv_vreinterpret_v_u16m4_i16m4(__riscv_vzext_vf2(vec_src, vl)); + __riscv_vse16_v_i16m4(dst_row + j, vec_dst, vl); + } + } + + return CV_HAL_ERROR_OK; + } + + int vlmax = __riscv_vsetvlmax_e32m8(); + auto vec_b = __riscv_vfmv_v_f_f32m8(beta, vlmax); + float a = alpha; + + for (int i = 0; i < height; i++) + { + const uchar* src_row = src + i * src_step; + short* dst_row = reinterpret_cast(dst + i * dst_step); + int vl; + for (int j = 0; j < width; j += vl) + { + vl = __riscv_vsetvl_e8m2(width - j); + auto vec_src = __riscv_vle8_v_u8m2(src_row + j, vl); + auto vec_src_u16 = __riscv_vzext_vf2(vec_src, vl); + auto vec_src_f32 = __riscv_vfwcvt_f(vec_src_u16, vl); + auto vec_fma = __riscv_vfmadd(vec_src_f32, a, vec_b, vl); + auto vec_dst = __riscv_vfncvt_x(vec_fma, vl); + __riscv_vse16_v_i16m4(dst_row + j, vec_dst, vl); + } + } + + return CV_HAL_ERROR_OK; +} + +inline int convertScale_16U8U(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, int width, int height, double alpha, double beta) +{ + if (alpha == 1.0 && beta == 0.0) + { + for (int i = 0; i < height; i++) + { + const ushort* src_row = reinterpret_cast(src + i * src_step); + uchar* dst_row = dst + i * dst_step; + int vl; + for (int j = 0; j < width; j += vl) + { + vl = __riscv_vsetvl_e16m4(width - j); + auto vec_src = __riscv_vle16_v_u16m4(src_row + j, vl); + auto vec_dst = __riscv_vnclipu(vec_src, 0, __RISCV_VXRM_RNU, vl); + __riscv_vse8_v_u8m2(dst_row + j, vec_dst, vl); + } + } + + return CV_HAL_ERROR_OK; + } + + int vlmax = __riscv_vsetvlmax_e32m8(); + auto vec_b = __riscv_vfmv_v_f_f32m8(beta, vlmax); + float a = alpha; + + for (int i = 0; i < height; i++) + { + const ushort* src_row = reinterpret_cast(src + i * src_step); + uchar* dst_row = dst + i * dst_step; + int vl; + for (int j = 0; j < width; j += vl) + { + vl = __riscv_vsetvl_e16m4(width - j); + auto vec_src = __riscv_vle16_v_u16m4(src_row + j, vl); + auto vec_src_f32 = __riscv_vfwcvt_f(vec_src, vl); + auto vec_fma = __riscv_vfmadd(vec_src_f32, a, vec_b, vl); + auto vec_dst_u16 = __riscv_vfncvt_xu(vec_fma, vl); + auto vec_dst = __riscv_vnclipu(vec_dst_u16, 0, __RISCV_VXRM_RNU, vl); + __riscv_vse8_v_u8m2(dst_row + j, vec_dst, vl); + } + } + + return CV_HAL_ERROR_OK; +} + inline int convertScale_16U32F(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, int width, int height, double alpha, double beta) { int vlmax = __riscv_vsetvlmax_e32m8(); @@ -164,6 +299,13 @@ int convertScale(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, if (!dst) return CV_HAL_ERROR_OK; + if ((unsigned)ddepth >= CV_DEPTH_MAX) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + // Column-shaped sources can target row-shaped continuous vectors. + if (width == 1 && height > 1 && dst_step != CV_ELEM_SIZE1(ddepth)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + switch (sdepth) { case CV_8U: @@ -171,6 +313,10 @@ int convertScale(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, { case CV_8U: return convertScale_8U8U(src, src_step, dst, dst_step, width, height, alpha, beta); + case CV_16U: + return convertScale_8U16U(src, src_step, dst, dst_step, width, height, alpha, beta); + case CV_16S: + return convertScale_8U16S(src, src_step, dst, dst_step, width, height, alpha, beta); case CV_32F: return convertScale_8U32F(src, src_step, dst, dst_step, width, height, alpha, beta); } @@ -178,6 +324,8 @@ int convertScale(const uchar* src, size_t src_step, uchar* dst, size_t dst_step, case CV_16U: switch (ddepth) { + case CV_8U: + return convertScale_16U8U(src, src_step, dst, dst_step, width, height, alpha, beta); case CV_32F: return convertScale_16U32F(src, src_step, dst, dst_step, width, height, alpha, beta); } diff --git a/hal/riscv-rvv/src/imgproc/integral.cpp b/hal/riscv-rvv/src/imgproc/integral.cpp index e0c7f44995..9a82538a2a 100644 --- a/hal/riscv-rvv/src/imgproc/integral.cpp +++ b/hal/riscv-rvv/src/imgproc/integral.cpp @@ -129,6 +129,11 @@ int integral(int depth, int sdepth, int sqdepth, return CV_HAL_ERROR_NOT_IMPLEMENTED; } + // CV_32F sqsum kept on generic path due to accumulation-order sensitivity, see #27407 + if (sqsum_data && (sdepth == CV_32F || sqdepth == CV_32F)) { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + // Skip images that are too small if (!(width >> 8 || height >> 8)) { return CV_HAL_ERROR_NOT_IMPLEMENTED; diff --git a/hal/riscv-rvv/src/imgproc/spatialgradient.cpp b/hal/riscv-rvv/src/imgproc/spatialgradient.cpp new file mode 100644 index 0000000000..5cf01eaef1 --- /dev/null +++ b/hal/riscv-rvv/src/imgproc/spatialgradient.cpp @@ -0,0 +1,254 @@ +// 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 "rvv_hal.hpp" +#include "common.hpp" + +namespace cv { namespace rvv_hal { namespace imgproc { + +#if CV_HAL_RVV_1P0_ENABLED + +namespace { + +static inline vint16m2_t widen_u8_to_i16(vuint8m1_t v, size_t vl) +{ + vuint16m2_t widened = __riscv_vzext_vf2(v, vl); + return __riscv_vreinterpret_v_u16m2_i16m2(widened); +} + +static inline void spatialGradientKernel(int16_t& dx, int16_t& dy, + int16_t v00, int16_t v01, int16_t v02, + int16_t v10, int16_t v12, + int16_t v20, int16_t v21, int16_t v22) +{ + int16_t tmp_add = v22 - v00; + int16_t tmp_sub = v02 - v20; + int16_t tmp_x = v12 - v10; + int16_t tmp_y = v21 - v01; + + dx = tmp_add + tmp_sub + tmp_x + tmp_x; + dy = tmp_add - tmp_sub + tmp_y + tmp_y; +} + +static inline void loadRow3(const uint8_t* row, int x, size_t vl, + vint16m2_t& m, vint16m2_t& n, vint16m2_t& p) +{ + m = widen_u8_to_i16(__riscv_vle8_v_u8m1(row + x - 1, vl), vl); + n = widen_u8_to_i16(__riscv_vle8_v_u8m1(row + x, vl), vl); + p = widen_u8_to_i16(__riscv_vle8_v_u8m1(row + x + 1, vl), vl); +} + +static inline void spatialGradientKernelVec(vint16m2_t& vdx, vint16m2_t& vdy, + const vint16m2_t& v_s1m, + const vint16m2_t& v_s1n, + const vint16m2_t& v_s1p, + const vint16m2_t& v_s2m, + const vint16m2_t& v_s2p, + const vint16m2_t& v_s3m, + const vint16m2_t& v_s3n, + const vint16m2_t& v_s3p, + size_t vl) +{ + // vdx = (v_s3p - v_s1m) + (v_s1p - v_s3m) + 2 * (v_s2p - v_s2m) + // vdy = (v_s3p - v_s1m) - (v_s1p - v_s3m) + 2 * (v_s3n - v_s1n) + vint16m2_t tmp_add = __riscv_vsub_vv_i16m2(v_s3p, v_s1m, vl); + vint16m2_t tmp_sub = __riscv_vsub_vv_i16m2(v_s1p, v_s3m, vl); + vint16m2_t tmp_x = __riscv_vsub_vv_i16m2(v_s2p, v_s2m, vl); + vint16m2_t tmp_y = __riscv_vsub_vv_i16m2(v_s3n, v_s1n, vl); + + vdx = __riscv_vadd_vv_i16m2(tmp_add, tmp_sub, vl); + vdx = __riscv_vadd_vv_i16m2(vdx, tmp_x, vl); + vdx = __riscv_vadd_vv_i16m2(vdx, tmp_x, vl); + + vdy = __riscv_vsub_vv_i16m2(tmp_add, tmp_sub, vl); + vdy = __riscv_vadd_vv_i16m2(vdy, tmp_y, vl); + vdy = __riscv_vadd_vv_i16m2(vdy, tmp_y, vl); +} + +static inline void processBorderPixels(const uint8_t* p_src, + const uint8_t* c_src, + const uint8_t* n_src, + int16_t* c_dx, + int16_t* c_dy, + int width, + int j_offl, + int j_offr) +{ + int j = 0; + int j_p = j + j_offl; + int j_n = 1; + if (j_n >= width) + j_n = j + j_offr; + + int16_t v00 = p_src[j_p], v01 = p_src[j], v02 = p_src[j_n]; + int16_t v10 = c_src[j_p], v12 = c_src[j_n]; + int16_t v20 = n_src[j_p], v21 = n_src[j], v22 = n_src[j_n]; + spatialGradientKernel(c_dx[j], c_dy[j], v00, v01, v02, v10, v12, v20, v21, v22); + + if (width > 1) + { + j = width - 1; + j_p = j - 1; + j_n = j + j_offr; + + v00 = p_src[j_p]; v01 = p_src[j]; v02 = p_src[j_n]; + v10 = c_src[j_p]; v12 = c_src[j_n]; + v20 = n_src[j_p]; v21 = n_src[j]; v22 = n_src[j_n]; + spatialGradientKernel(c_dx[j], c_dy[j], v00, v01, v02, v10, v12, v20, v21, v22); + } +} + +// Characters in variable names have the following meanings: +// m: offset -1 +// n: offset 0 +// p: offset 1 +static int spatialGradient_row(int start, int end, + const uint8_t* src_data, size_t src_step, + int16_t* dx_data, size_t dx_step, + int16_t* dy_data, size_t dy_step, + int width, int height, + int border_type) +{ + int i_top = 0; + int i_bottom = height - 1; + int j_offl = 0; + int j_offr = 0; + + if (border_type == BORDER_DEFAULT) + { + if (height > 1) + { + i_top = 1; + i_bottom = height - 2; + } + if (width > 1) + { + j_offl = 1; + j_offr = -1; + } + } + + int y = start; + for (; y + 1 < end; y += 2) + { + const uint8_t* p_src = src_data + (y == 0 ? i_top : y - 1) * src_step; + const uint8_t* c_src = src_data + y * src_step; + const uint8_t* n_src = src_data + (y + 1) * src_step; + const uint8_t* m_src = src_data + (y == height - 2 ? i_bottom : y + 2) * src_step; + + int16_t* c_dx = reinterpret_cast( + reinterpret_cast(dx_data) + y * dx_step); + int16_t* c_dy = reinterpret_cast( + reinterpret_cast(dy_data) + y * dy_step); + int16_t* n_dx = reinterpret_cast( + reinterpret_cast(dx_data) + (y + 1) * dx_step); + int16_t* n_dy = reinterpret_cast( + reinterpret_cast(dy_data) + (y + 1) * dy_step); + + processBorderPixels(p_src, c_src, n_src, c_dx, c_dy, width, j_offl, j_offr); + processBorderPixels(c_src, n_src, m_src, n_dx, n_dy, width, j_offl, j_offr); + + // Process rest of columns + int x = 1; + const int last = width - 1; + while (x < last) + { + size_t vl = __riscv_vsetvl_e8m1((size_t)(last - x)); + + vint16m2_t v_s1m, v_s1n, v_s1p; + vint16m2_t v_s2m, v_s2n, v_s2p; + vint16m2_t v_s3m, v_s3n, v_s3p; + loadRow3(p_src, x, vl, v_s1m, v_s1n, v_s1p); + loadRow3(c_src, x, vl, v_s2m, v_s2n, v_s2p); + loadRow3(n_src, x, vl, v_s3m, v_s3n, v_s3p); + + vint16m2_t vdx, vdy; + spatialGradientKernelVec(vdx, vdy, + v_s1m, v_s1n, v_s1p, + v_s2m, v_s2p, + v_s3m, v_s3n, v_s3p, vl); + __riscv_vse16_v_i16m2(c_dx + x, vdx, vl); + __riscv_vse16_v_i16m2(c_dy + x, vdy, vl); + + vint16m2_t v_s4m, v_s4n, v_s4p; + loadRow3(m_src, x, vl, v_s4m, v_s4n, v_s4p); + spatialGradientKernelVec(vdx, vdy, + v_s2m, v_s2n, v_s2p, + v_s3m, v_s3p, + v_s4m, v_s4n, v_s4p, vl); + __riscv_vse16_v_i16m2(n_dx + x, vdx, vl); + __riscv_vse16_v_i16m2(n_dy + x, vdy, vl); + + x += (int)vl; + } + } + + for (; y < end; ++y) + { + const uint8_t* p_src = src_data + (y == 0 ? i_top : y - 1) * src_step; + const uint8_t* c_src = src_data + y * src_step; + const uint8_t* n_src = src_data + (y == height - 1 ? i_bottom : y + 1) * src_step; + + int16_t* c_dx = reinterpret_cast( + reinterpret_cast(dx_data) + y * dx_step); + int16_t* c_dy = reinterpret_cast( + reinterpret_cast(dy_data) + y * dy_step); + + processBorderPixels(p_src, c_src, n_src, c_dx, c_dy, width, j_offl, j_offr); + + int x = 1; + const int last = width - 1; + while (x < last) + { + size_t vl = __riscv_vsetvl_e8m1((size_t)(last - x)); + vint16m2_t v_s1m, v_s1n, v_s1p; + vint16m2_t v_s2m, v_s2p; + vint16m2_t v_s3m, v_s3n, v_s3p; + loadRow3(p_src, x, vl, v_s1m, v_s1n, v_s1p); + v_s2m = widen_u8_to_i16(__riscv_vle8_v_u8m1(c_src + x - 1, vl), vl); + v_s2p = widen_u8_to_i16(__riscv_vle8_v_u8m1(c_src + x + 1, vl), vl); + loadRow3(n_src, x, vl, v_s3m, v_s3n, v_s3p); + + vint16m2_t vdx, vdy; + spatialGradientKernelVec(vdx, vdy, + v_s1m, v_s1n, v_s1p, + v_s2m, v_s2p, + v_s3m, v_s3n, v_s3p, vl); + __riscv_vse16_v_i16m2(c_dx + x, vdx, vl); + __riscv_vse16_v_i16m2(c_dy + x, vdy, vl); + + x += (int)vl; + } + } + + return CV_HAL_ERROR_OK; +} + +} // anonymous namespace + +int spatialGradient(const uint8_t* src_data, size_t src_step, + int16_t* dx_data, size_t dx_step, + int16_t* dy_data, size_t dy_step, + int width, int height, + int ksize, int border_type) +{ + if (ksize != 3) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (border_type != BORDER_REPLICATE && border_type != BORDER_DEFAULT) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (width < 1 || height < 1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + return common::invoke(height, {spatialGradient_row}, + src_data, src_step, + dx_data, dx_step, + dy_data, dy_step, + width, height, border_type); +} + +#endif // CV_HAL_RVV_1P0_ENABLED + +}}} // cv::rvv_hal::imgproc diff --git a/modules/imgcodecs/src/apple_conversions.h b/modules/imgcodecs/src/apple_conversions.h index c68b922cfb..65d3a7e08b 100644 --- a/modules/imgcodecs/src/apple_conversions.h +++ b/modules/imgcodecs/src/apple_conversions.h @@ -2,10 +2,27 @@ // 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_IMGCODECS_APPLE_CONVERSIONS_H +#define OPENCV_IMGCODECS_APPLE_CONVERSIONS_H + #include "opencv2/core.hpp" #import + +#include + +#ifdef HAVE_AVFOUNDATION #import +#endif + +#if TARGET_OS_IPHONE || (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1080) #import +#else +#import +#import +#import +#endif CV_EXPORTS CGImageRef MatToCGImage(const cv::Mat& image) CF_RETURNS_RETAINED; CV_EXPORTS void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist); + +#endif // OPENCV_IMGCODECS_APPLE_CONVERSIONS_H \ No newline at end of file diff --git a/modules/imgcodecs/src/apple_conversions.mm b/modules/imgcodecs/src/apple_conversions.mm index 6126039ce0..6b1834504b 100644 --- a/modules/imgcodecs/src/apple_conversions.mm +++ b/modules/imgcodecs/src/apple_conversions.mm @@ -5,6 +5,12 @@ #include "apple_conversions.h" #include "precomp.hpp" +#import + +#ifndef __bridge +#define __bridge +#endif + CGImageRef MatToCGImage(const cv::Mat& image) { NSData *data = [NSData dataWithBytes:image.data length:image.step.p[0] * image.rows]; diff --git a/modules/imgcodecs/src/grfmt_sunras.cpp b/modules/imgcodecs/src/grfmt_sunras.cpp index 06d55d2c44..803a0de99f 100644 --- a/modules/imgcodecs/src/grfmt_sunras.cpp +++ b/modules/imgcodecs/src/grfmt_sunras.cpp @@ -61,10 +61,22 @@ bool SunRasterDecoder::readHeader() int palSize = (m_bpp > 0 && m_bpp <= 8) ? (3*(1 << m_bpp)) : 0; m_strm.skip( 4 ); - m_encoding = (SunRasType)m_strm.getDWord(); - m_maptype = (SunRasMapType)m_strm.getDWord(); + // Read as plain integers first; validate before casting to enum types. + // Casting an out-of-range integer directly to an enum with no fixed + // underlying type is undefined behavior (C++11 ยง7.2/8). Reject invalid + // values early so no downstream code ever touches an ill-formed enum. + const int raw_encoding = (int)m_strm.getDWord(); + const int raw_maptype = (int)m_strm.getDWord(); m_maplength = m_strm.getDWord(); + if (raw_encoding < RAS_OLD || raw_encoding > RAS_FORMAT_RGB) + return false; + if (raw_maptype < RMT_NONE || raw_maptype > RMT_EQUAL_RGB) + return false; + + m_encoding = (SunRasType)raw_encoding; + m_maptype = (SunRasMapType)raw_maptype; + if( m_width > 0 && m_height > 0 && (m_bpp == 1 || m_bpp == 8 || m_bpp == 24 || m_bpp == 32) && (m_encoding == RAS_OLD || m_encoding == RAS_STANDARD || diff --git a/modules/imgcodecs/src/macosx_conversions.mm b/modules/imgcodecs/src/macosx_conversions.mm index 0023e06260..960139e2f1 100644 --- a/modules/imgcodecs/src/macosx_conversions.mm +++ b/modules/imgcodecs/src/macosx_conversions.mm @@ -13,9 +13,11 @@ NSImage* MatToNSImage(const cv::Mat& image) { CGImageRef imageRef = MatToCGImage(image); // Getting NSImage from CGImage - NSImage *nsImage = [[NSImage alloc] initWithCGImage:imageRef size:CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef))]; + NSSize imageSize = NSMakeSize(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); + NSImage *nsImage = [[NSImage alloc] initWithCGImage:imageRef size:imageSize]; CGImageRelease(imageRef); + return nsImage; } diff --git a/modules/imgcodecs/test/test_sunraster.cpp b/modules/imgcodecs/test/test_sunraster.cpp new file mode 100644 index 0000000000..4de3e079f3 --- /dev/null +++ b/modules/imgcodecs/test/test_sunraster.cpp @@ -0,0 +1,138 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +// Regression tests for the Sun Raster decoder (grfmt_sunras.cpp). +// These tests guard against: +// - UBSan "load of invalid enum value" in SunRasterDecoder::readHeader() +// (see https://github.com/opencv/opencv/issues/29150) +// - Out-of-range SunRasType / SunRasMapType values causing UB via unchecked C-cast + +#include "test_precomp.hpp" + +#include + +namespace opencv_test { namespace { + +// --------------------------------------------------------------------------- +// Helper: build a minimal Sun Raster byte buffer with caller-supplied fields. +// Sun Raster header layout (all fields big-endian, 32-bit): +// [0] magic = 0x59a66a95 +// [4] width +// [8] height +// [12] depth (bits-per-pixel) +// [16] length (image data length, may be 0 for RAS_OLD) +// [20] ras_type (SunRasType) +// [24] maptype (SunRasMapType) +// [28] maplength +// --------------------------------------------------------------------------- +static std::vector makeSunRasHeader( + uint32_t width, uint32_t height, uint32_t depth, + uint32_t length, uint32_t ras_type, uint32_t maptype, uint32_t maplength) +{ + std::vector buf(32); + auto put32 = [&](size_t off, uint32_t v) { + buf[off+0] = (v >> 24) & 0xff; + buf[off+1] = (v >> 16) & 0xff; + buf[off+2] = (v >> 8) & 0xff; + buf[off+3] = (v ) & 0xff; + }; + put32( 0, 0x59a66a95u); // magic + put32( 4, width); + put32( 8, height); + put32(12, depth); + put32(16, length); + put32(20, ras_type); + put32(24, maptype); + put32(28, maplength); + return buf; +} + +// --------------------------------------------------------------------------- +// Crash / UBSan regression โ€” issue #29150 +// Feeding an invalid maptype value (34077 / 0x851d) must NOT trigger UB; +// imdecode must return an empty Mat gracefully. +// --------------------------------------------------------------------------- +TEST(Imgcodecs_SunRaster, invalid_maptype_returns_empty_29150) +{ + // Crafted header from the original bug report: + // magic=0x59a66a95, width=0x10101, height=0x10000, depth=1, length=0x1000000 + // ras_type=0 (RAS_OLD, valid), maptype=0x851d (34077, INVALID) + const std::vector image_data = { + 0x59,0xa6,0x6a,0x95, 0x01,0x01,0x00,0x00, + 0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x01, + 0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x85,0x1d, 0xae,0x5b,0x8d,0xd5, + 0x9c,0x25,0x22,0x41, 0x51,0x92,0x13,0x14,0x33 + }; + + cv::Mat result; + ASSERT_NO_THROW(result = cv::imdecode(image_data, cv::IMREAD_REDUCED_GRAYSCALE_2)); + EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for invalid maptype"; +} + +// Invalid ras_type (value well outside [0,3]) must be rejected cleanly. +TEST(Imgcodecs_SunRaster, invalid_rastype_returns_empty) +{ + auto buf = makeSunRasHeader(/*w*/8, /*h*/8, /*depth*/8, + /*len*/0, /*ras_type*/0xFFFF, /*maptype*/0, /*mapllen*/0); + cv::Mat result; + ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE)); + EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for invalid ras_type"; +} + +// maptype = 2 is outside [0,1] (only RMT_NONE=0, RMT_EQUAL_RGB=1 are defined). +TEST(Imgcodecs_SunRaster, maptype_2_returns_empty) +{ + auto buf = makeSunRasHeader(8, 8, 8, 0, /*ras_type*/0, /*maptype*/2, 0); + cv::Mat result; + ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE)); + EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for maptype=2"; +} + +// maptype = UINT32_MAX is also invalid. +TEST(Imgcodecs_SunRaster, maptype_max_returns_empty) +{ + auto buf = makeSunRasHeader(8, 8, 8, 0, 0, 0xFFFFFFFFu, 0); + cv::Mat result; + ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE)); + EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for maptype=UINT_MAX"; +} + +// A truncated buffer (shorter than the 32-byte header) must not crash. +TEST(Imgcodecs_SunRaster, truncated_header_returns_empty) +{ + // Only 16 bytes โ€” header read will run out of data. + const std::vector buf = { + 0x59,0xa6,0x6a,0x95, 0x00,0x00,0x00,0x08, + 0x00,0x00,0x00,0x08, 0x00,0x00,0x00,0x08 + }; + cv::Mat result; + ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE)); + EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for truncated header"; +} + +// --------------------------------------------------------------------------- +// Sanity: a well-formed 8-bpp grayscale Sun Raster (RMT_NONE) still decodes. +// We build the full header + pixel data manually so the test has no file I/O. +// --------------------------------------------------------------------------- +TEST(Imgcodecs_SunRaster, valid_8bpp_grayscale_decodes) +{ + const int W = 4, H = 4; + // RAS_OLD(0), maptype=RMT_NONE(0), maplength=0 + auto buf = makeSunRasHeader(W, H, 8, W*H, 0, 0, 0); + + // Sun Raster rows are padded to 16-bit boundary. + // W=4: row_bytes = 4 (already even), no padding needed. + for (int i = 0; i < W * H; ++i) + buf.push_back(static_cast(i * 16)); // arbitrary gray values + + cv::Mat result; + ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE)); + EXPECT_FALSE(result.empty()) << "imdecode must succeed for a valid 8-bpp Sun Raster"; + EXPECT_EQ(result.cols, W); + EXPECT_EQ(result.rows, H); + EXPECT_EQ(result.type(), CV_8UC1); +} + +}} // namespace opencv_test diff --git a/modules/imgproc/perf/perf_accumulate.cpp b/modules/imgproc/perf/perf_accumulate.cpp index c52b31e84d..884ab9c295 100644 --- a/modules/imgproc/perf/perf_accumulate.cpp +++ b/modules/imgproc/perf/perf_accumulate.cpp @@ -45,6 +45,12 @@ PERF_TEST_P_ACCUMULATE(Accumulate, MAT_TYPES_ACCUMLATE, PERF_TEST_P_ACCUMULATE(AccumulateMask, MAT_TYPES_ACCUMLATE_C, PERF_ACCUMULATE_MASK_INIT(CV_32FC), accumulate(src1, dst, mask)) +PERF_TEST_P_ACCUMULATE(AccumulateMask32FC4, CV_32FC4, + PERF_ACCUMULATE_MASK_INIT(CV_32FC), accumulate(src1, dst, mask)) + +PERF_TEST_P_ACCUMULATE(AccumulateMask8UC4To32FC4, CV_8UC4, + PERF_ACCUMULATE_MASK_INIT(CV_32FC), accumulate(src1, dst, mask)) + PERF_TEST_P_ACCUMULATE(AccumulateDouble, MAT_TYPES_ACCUMLATE_D, PERF_ACCUMULATE_INIT(CV_64FC), accumulate(src1, dst)) diff --git a/modules/imgproc/src/accum.simd.hpp b/modules/imgproc/src/accum.simd.hpp index 59420ede73..51a1ea20ef 100644 --- a/modules/imgproc/src/accum.simd.hpp +++ b/modules/imgproc/src/accum.simd.hpp @@ -317,79 +317,183 @@ void acc_simd_(const uchar* src, float* dst, const uchar* mask, int len, int cn) } else { - v_uint8 v_0 = vx_setall_u8(0); - if (cn == 1) + if (x <= len - cVectorWidth) { - for ( ; x <= len - cVectorWidth; x += cVectorWidth) + v_uint8 v_0 = vx_setall_u8(0); + if (cn == 1) { - v_uint8 v_mask = vx_load(mask + x); - v_mask = v_not(v_eq(v_0, v_mask)); - v_uint8 v_src = vx_load(src + x); - v_src = v_and(v_src, v_mask); - v_uint16 v_src0, v_src1; - v_expand(v_src, v_src0, v_src1); + for ( ; x <= len - cVectorWidth; x += cVectorWidth) + { + v_uint8 v_mask = vx_load(mask + x); + v_mask = v_ne(v_0, v_mask); + v_uint8 v_src = vx_load(src + x); + v_src = v_and(v_src, v_mask); + v_uint16 v_src0, v_src1; + v_expand(v_src, v_src0, v_src1); - v_uint32 v_src00, v_src01, v_src10, v_src11; - v_expand(v_src0, v_src00, v_src01); - v_expand(v_src1, v_src10, v_src11); + v_uint32 v_src00, v_src01, v_src10, v_src11; + v_expand(v_src0, v_src00, v_src01); + v_expand(v_src1, v_src10, v_src11); - v_store(dst + x, v_add(vx_load(dst + x), v_cvt_f32(v_reinterpret_as_s32(v_src00)))); - v_store(dst + x + step, v_add(vx_load(dst + x + step), v_cvt_f32(v_reinterpret_as_s32(v_src01)))); - v_store(dst + x + step * 2, v_add(vx_load(dst + x + step * 2), v_cvt_f32(v_reinterpret_as_s32(v_src10)))); - v_store(dst + x + step * 3, v_add(vx_load(dst + x + step * 3), v_cvt_f32(v_reinterpret_as_s32(v_src11)))); + v_store(dst + x, v_add(vx_load(dst + x), v_cvt_f32(v_reinterpret_as_s32(v_src00)))); + v_store(dst + x + step, v_add(vx_load(dst + x + step), v_cvt_f32(v_reinterpret_as_s32(v_src01)))); + v_store(dst + x + step * 2, v_add(vx_load(dst + x + step * 2), v_cvt_f32(v_reinterpret_as_s32(v_src10)))); + v_store(dst + x + step * 3, v_add(vx_load(dst + x + step * 3), v_cvt_f32(v_reinterpret_as_s32(v_src11)))); + } } - } - else if (cn == 3) - { - for ( ; x <= len - cVectorWidth; x += cVectorWidth) + else if (cn == 3) { - v_uint8 v_mask = vx_load(mask + x); - v_mask = v_not(v_eq(v_0, v_mask)); - v_uint8 v_src0, v_src1, v_src2; - v_load_deinterleave(src + (x * cn), v_src0, v_src1, v_src2); - v_src0 = v_and(v_src0, v_mask); - v_src1 = v_and(v_src1, v_mask); - v_src2 = v_and(v_src2, v_mask); - v_uint16 v_src00, v_src01, v_src10, v_src11, v_src20, v_src21; - v_expand(v_src0, v_src00, v_src01); - v_expand(v_src1, v_src10, v_src11); - v_expand(v_src2, v_src20, v_src21); + for ( ; x <= len - cVectorWidth; x += cVectorWidth) + { + v_uint8 v_mask = vx_load(mask + x); + v_mask = v_ne(v_0, v_mask); + v_uint8 v_src0, v_src1, v_src2; + v_load_deinterleave(src + (x * cn), v_src0, v_src1, v_src2); + v_src0 = v_and(v_src0, v_mask); + v_src1 = v_and(v_src1, v_mask); + v_src2 = v_and(v_src2, v_mask); + v_uint16 v_src00, v_src01, v_src10, v_src11, v_src20, v_src21; + v_expand(v_src0, v_src00, v_src01); + v_expand(v_src1, v_src10, v_src11); + v_expand(v_src2, v_src20, v_src21); - v_uint32 v_src000, v_src001, v_src010, v_src011; - v_uint32 v_src100, v_src101, v_src110, v_src111; - v_uint32 v_src200, v_src201, v_src210, v_src211; - v_expand(v_src00, v_src000, v_src001); - v_expand(v_src01, v_src010, v_src011); - v_expand(v_src10, v_src100, v_src101); - v_expand(v_src11, v_src110, v_src111); - v_expand(v_src20, v_src200, v_src201); - v_expand(v_src21, v_src210, v_src211); + v_uint32 v_src000, v_src001, v_src010, v_src011; + v_uint32 v_src100, v_src101, v_src110, v_src111; + v_uint32 v_src200, v_src201, v_src210, v_src211; + v_expand(v_src00, v_src000, v_src001); + v_expand(v_src01, v_src010, v_src011); + v_expand(v_src10, v_src100, v_src101); + v_expand(v_src11, v_src110, v_src111); + v_expand(v_src20, v_src200, v_src201); + v_expand(v_src21, v_src210, v_src211); - v_float32 v_dst000, v_dst001, v_dst010, v_dst011; - v_float32 v_dst100, v_dst101, v_dst110, v_dst111; - v_float32 v_dst200, v_dst201, v_dst210, v_dst211; - v_load_deinterleave(dst + (x * cn), v_dst000, v_dst100, v_dst200); - v_load_deinterleave(dst + ((x + step) * cn), v_dst001, v_dst101, v_dst201); - v_load_deinterleave(dst + ((x + step * 2) * cn), v_dst010, v_dst110, v_dst210); - v_load_deinterleave(dst + ((x + step * 3) * cn), v_dst011, v_dst111, v_dst211); + v_float32 v_dst000, v_dst001, v_dst010, v_dst011; + v_float32 v_dst100, v_dst101, v_dst110, v_dst111; + v_float32 v_dst200, v_dst201, v_dst210, v_dst211; + v_load_deinterleave(dst + (x * cn), v_dst000, v_dst100, v_dst200); + v_load_deinterleave(dst + ((x + step) * cn), v_dst001, v_dst101, v_dst201); + v_load_deinterleave(dst + ((x + step * 2) * cn), v_dst010, v_dst110, v_dst210); + v_load_deinterleave(dst + ((x + step * 3) * cn), v_dst011, v_dst111, v_dst211); - v_dst000 = v_add(v_dst000, v_cvt_f32(v_reinterpret_as_s32(v_src000))); - v_dst100 = v_add(v_dst100, v_cvt_f32(v_reinterpret_as_s32(v_src100))); - v_dst200 = v_add(v_dst200, v_cvt_f32(v_reinterpret_as_s32(v_src200))); - v_dst001 = v_add(v_dst001, v_cvt_f32(v_reinterpret_as_s32(v_src001))); - v_dst101 = v_add(v_dst101, v_cvt_f32(v_reinterpret_as_s32(v_src101))); - v_dst201 = v_add(v_dst201, v_cvt_f32(v_reinterpret_as_s32(v_src201))); - v_dst010 = v_add(v_dst010, v_cvt_f32(v_reinterpret_as_s32(v_src010))); - v_dst110 = v_add(v_dst110, v_cvt_f32(v_reinterpret_as_s32(v_src110))); - v_dst210 = v_add(v_dst210, v_cvt_f32(v_reinterpret_as_s32(v_src210))); - v_dst011 = v_add(v_dst011, v_cvt_f32(v_reinterpret_as_s32(v_src011))); - v_dst111 = v_add(v_dst111, v_cvt_f32(v_reinterpret_as_s32(v_src111))); - v_dst211 = v_add(v_dst211, v_cvt_f32(v_reinterpret_as_s32(v_src211))); + v_dst000 = v_add(v_dst000, v_cvt_f32(v_reinterpret_as_s32(v_src000))); + v_dst100 = v_add(v_dst100, v_cvt_f32(v_reinterpret_as_s32(v_src100))); + v_dst200 = v_add(v_dst200, v_cvt_f32(v_reinterpret_as_s32(v_src200))); + v_dst001 = v_add(v_dst001, v_cvt_f32(v_reinterpret_as_s32(v_src001))); + v_dst101 = v_add(v_dst101, v_cvt_f32(v_reinterpret_as_s32(v_src101))); + v_dst201 = v_add(v_dst201, v_cvt_f32(v_reinterpret_as_s32(v_src201))); + v_dst010 = v_add(v_dst010, v_cvt_f32(v_reinterpret_as_s32(v_src010))); + v_dst110 = v_add(v_dst110, v_cvt_f32(v_reinterpret_as_s32(v_src110))); + v_dst210 = v_add(v_dst210, v_cvt_f32(v_reinterpret_as_s32(v_src210))); + v_dst011 = v_add(v_dst011, v_cvt_f32(v_reinterpret_as_s32(v_src011))); + v_dst111 = v_add(v_dst111, v_cvt_f32(v_reinterpret_as_s32(v_src111))); + v_dst211 = v_add(v_dst211, v_cvt_f32(v_reinterpret_as_s32(v_src211))); - v_store_interleave(dst + (x * cn), v_dst000, v_dst100, v_dst200); - v_store_interleave(dst + ((x + step) * cn), v_dst001, v_dst101, v_dst201); - v_store_interleave(dst + ((x + step * 2) * cn), v_dst010, v_dst110, v_dst210); - v_store_interleave(dst + ((x + step * 3) * cn), v_dst011, v_dst111, v_dst211); + v_store_interleave(dst + (x * cn), v_dst000, v_dst100, v_dst200); + v_store_interleave(dst + ((x + step) * cn), v_dst001, v_dst101, v_dst201); + v_store_interleave(dst + ((x + step * 2) * cn), v_dst010, v_dst110, v_dst210); + v_store_interleave(dst + ((x + step * 3) * cn), v_dst011, v_dst111, v_dst211); + } + } + else if (cn == 4) + { + v_uint8 v_zero = vx_setzero_u8(); + + for (; x <= len - cVectorWidth; x += cVectorWidth) + { + v_uint8 v_mask = vx_load(mask + x); + v_mask = v_ne(v_mask, v_zero); + + v_uint8 v_src0, v_src1, v_src2, v_src3; + + v_load_deinterleave(src + x * cn, + v_src0, v_src1, v_src2, v_src3); + + v_src0 = v_and(v_src0, v_mask); + v_src1 = v_and(v_src1, v_mask); + v_src2 = v_and(v_src2, v_mask); + v_src3 = v_and(v_src3, v_mask); + + v_uint16 v_src0_u16_0, v_src0_u16_1; + v_uint16 v_src1_u16_0, v_src1_u16_1; + v_uint16 v_src2_u16_0, v_src2_u16_1; + v_uint16 v_src3_u16_0, v_src3_u16_1; + + v_expand(v_src0, v_src0_u16_0, v_src0_u16_1); + v_expand(v_src1, v_src1_u16_0, v_src1_u16_1); + v_expand(v_src2, v_src2_u16_0, v_src2_u16_1); + v_expand(v_src3, v_src3_u16_0, v_src3_u16_1); + + v_uint32 v_src0_u32_0, v_src0_u32_1, v_src0_u32_2, v_src0_u32_3; + v_uint32 v_src1_u32_0, v_src1_u32_1, v_src1_u32_2, v_src1_u32_3; + v_uint32 v_src2_u32_0, v_src2_u32_1, v_src2_u32_2, v_src2_u32_3; + v_uint32 v_src3_u32_0, v_src3_u32_1, v_src3_u32_2, v_src3_u32_3; + + v_expand(v_src0_u16_0, v_src0_u32_0, v_src0_u32_1); + v_expand(v_src0_u16_1, v_src0_u32_2, v_src0_u32_3); + + v_expand(v_src1_u16_0, v_src1_u32_0, v_src1_u32_1); + v_expand(v_src1_u16_1, v_src1_u32_2, v_src1_u32_3); + + v_expand(v_src2_u16_0, v_src2_u32_0, v_src2_u32_1); + v_expand(v_src2_u16_1, v_src2_u32_2, v_src2_u32_3); + + v_expand(v_src3_u16_0, v_src3_u32_0, v_src3_u32_1); + v_expand(v_src3_u16_1, v_src3_u32_2, v_src3_u32_3); + + v_float32 v_src0_f0 = v_cvt_f32(v_reinterpret_as_s32(v_src0_u32_0)); + v_float32 v_src0_f1 = v_cvt_f32(v_reinterpret_as_s32(v_src0_u32_1)); + v_float32 v_src0_f2 = v_cvt_f32(v_reinterpret_as_s32(v_src0_u32_2)); + v_float32 v_src0_f3 = v_cvt_f32(v_reinterpret_as_s32(v_src0_u32_3)); + + v_float32 v_src1_f0 = v_cvt_f32(v_reinterpret_as_s32(v_src1_u32_0)); + v_float32 v_src1_f1 = v_cvt_f32(v_reinterpret_as_s32(v_src1_u32_1)); + v_float32 v_src1_f2 = v_cvt_f32(v_reinterpret_as_s32(v_src1_u32_2)); + v_float32 v_src1_f3 = v_cvt_f32(v_reinterpret_as_s32(v_src1_u32_3)); + + v_float32 v_src2_f0 = v_cvt_f32(v_reinterpret_as_s32(v_src2_u32_0)); + v_float32 v_src2_f1 = v_cvt_f32(v_reinterpret_as_s32(v_src2_u32_1)); + v_float32 v_src2_f2 = v_cvt_f32(v_reinterpret_as_s32(v_src2_u32_2)); + v_float32 v_src2_f3 = v_cvt_f32(v_reinterpret_as_s32(v_src2_u32_3)); + + v_float32 v_src3_f0 = v_cvt_f32(v_reinterpret_as_s32(v_src3_u32_0)); + v_float32 v_src3_f1 = v_cvt_f32(v_reinterpret_as_s32(v_src3_u32_1)); + v_float32 v_src3_f2 = v_cvt_f32(v_reinterpret_as_s32(v_src3_u32_2)); + v_float32 v_src3_f3 = v_cvt_f32(v_reinterpret_as_s32(v_src3_u32_3)); + + v_float32 v_dst0, v_dst1, v_dst2, v_dst3; + + v_load_deinterleave(dst + x * cn, v_dst0, v_dst1, v_dst2, v_dst3); + + v_store_interleave(dst + x * cn, + v_add(v_dst0, v_src0_f0), + v_add(v_dst1, v_src1_f0), + v_add(v_dst2, v_src2_f0), + v_add(v_dst3, v_src3_f0)); + + v_load_deinterleave(dst + (x + step) * cn, v_dst0, v_dst1, v_dst2, v_dst3); + + v_store_interleave(dst + (x + step) * cn, + v_add(v_dst0, v_src0_f1), + v_add(v_dst1, v_src1_f1), + v_add(v_dst2, v_src2_f1), + v_add(v_dst3, v_src3_f1)); + + v_load_deinterleave(dst + (x + 2 * step) * cn, v_dst0, v_dst1, v_dst2, v_dst3); + + v_store_interleave(dst + (x + 2 * step) * cn, + v_add(v_dst0, v_src0_f2), + v_add(v_dst1, v_src1_f2), + v_add(v_dst2, v_src2_f2), + v_add(v_dst3, v_src3_f2)); + + v_load_deinterleave(dst + (x + 3 * step) * cn, v_dst0, v_dst1, v_dst2, v_dst3); + + v_store_interleave(dst + (x + 3 * step) * cn, + v_add(v_dst0, v_src0_f3), + v_add(v_dst1, v_src1_f3), + v_add(v_dst2, v_src2_f3), + v_add(v_dst3, v_src3_f3)); + } } } } @@ -500,49 +604,109 @@ void acc_simd_(const float* src, float* dst, const uchar* mask, int len, int cn) } else { - v_float32 v_0 = vx_setzero_f32(); - if (cn == 1) + if (x <= len - cVectorWidth) { - for ( ; x <= len - cVectorWidth ; x += cVectorWidth) + v_float32 v_0 = vx_setzero_f32(); + if (cn == 1) { - v_uint16 v_masku16 = vx_load_expand(mask + x); - v_uint32 v_masku320, v_masku321; - v_expand(v_masku16, v_masku320, v_masku321); - v_float32 v_mask0 = v_reinterpret_as_f32(v_not(v_eq(v_masku320, v_reinterpret_as_u32(v_0)))); - v_float32 v_mask1 = v_reinterpret_as_f32(v_not(v_eq(v_masku321, v_reinterpret_as_u32(v_0)))); + for ( ; x <= len - cVectorWidth ; x += cVectorWidth) + { + v_uint16 v_masku16 = vx_load_expand(mask + x); + v_uint32 v_masku320, v_masku321; + v_expand(v_masku16, v_masku320, v_masku321); + v_float32 v_mask0 = v_reinterpret_as_f32(v_not(v_eq(v_masku320, v_reinterpret_as_u32(v_0)))); + v_float32 v_mask1 = v_reinterpret_as_f32(v_not(v_eq(v_masku321, v_reinterpret_as_u32(v_0)))); - v_store(dst + x, v_add(vx_load(dst + x), v_and(vx_load(src + x), v_mask0))); - v_store(dst + x + step, v_add(vx_load(dst + x + step), v_and(vx_load(src + x + step), v_mask1))); + v_store(dst + x, v_add(vx_load(dst + x), v_and(vx_load(src + x), v_mask0))); + v_store(dst + x + step, v_add(vx_load(dst + x + step), v_and(vx_load(src + x + step), v_mask1))); + } + } + else if (cn == 3) + { + for ( ; x <= len - cVectorWidth ; x += cVectorWidth) + { + v_uint16 v_masku16 = vx_load_expand(mask + x); + v_uint32 v_masku320, v_masku321; + v_expand(v_masku16, v_masku320, v_masku321); + v_float32 v_mask0 = v_reinterpret_as_f32(v_not(v_eq(v_masku320, v_reinterpret_as_u32(v_0)))); + v_float32 v_mask1 = v_reinterpret_as_f32(v_not(v_eq(v_masku321, v_reinterpret_as_u32(v_0)))); + + v_float32 v_src00, v_src01, v_src10, v_src11, v_src20, v_src21; + v_load_deinterleave(src + x * cn, v_src00, v_src10, v_src20); + v_load_deinterleave(src + (x + step) * cn, v_src01, v_src11, v_src21); + v_src00 = v_and(v_src00, v_mask0); + v_src01 = v_and(v_src01, v_mask1); + v_src10 = v_and(v_src10, v_mask0); + v_src11 = v_and(v_src11, v_mask1); + v_src20 = v_and(v_src20, v_mask0); + v_src21 = v_and(v_src21, v_mask1); + + v_float32 v_dst00, v_dst01, v_dst10, v_dst11, v_dst20, v_dst21; + v_load_deinterleave(dst + x * cn, v_dst00, v_dst10, v_dst20); + v_load_deinterleave(dst + (x + step) * cn, v_dst01, v_dst11, v_dst21); + + v_store_interleave(dst + x * cn, v_add(v_dst00, v_src00), v_add(v_dst10, v_src10), v_add(v_dst20, v_src20)); + v_store_interleave(dst + (x + step) * cn, v_add(v_dst01, v_src01), v_add(v_dst11, v_src11), v_add(v_dst21, v_src21)); + } + } + else if (cn == 4) + { + for (; x <= len - cVectorWidth; x += cVectorWidth) + { + v_uint16 v_masku16 = vx_load_expand(mask + x); + + v_uint32 v_masku320, v_masku321; + v_expand(v_masku16, v_masku320, v_masku321); + + v_float32 v_mask0 = v_reinterpret_as_f32(v_ne(v_masku320, v_reinterpret_as_u32(v_0))); + + v_float32 v_mask1 = v_reinterpret_as_f32(v_ne(v_masku321, v_reinterpret_as_u32(v_0))); + + v_float32 v_src00, v_src01; + v_float32 v_src10, v_src11; + v_float32 v_src20, v_src21; + v_float32 v_src30, v_src31; + + v_load_deinterleave(src + x * cn, + v_src00, v_src10, v_src20, v_src30); + + v_load_deinterleave(src + (x + step) * cn, + v_src01, v_src11, v_src21, v_src31); + + v_src00 = v_and(v_src00, v_mask0); + v_src10 = v_and(v_src10, v_mask0); + v_src20 = v_and(v_src20, v_mask0); + v_src30 = v_and(v_src30, v_mask0); + + v_src01 = v_and(v_src01, v_mask1); + v_src11 = v_and(v_src11, v_mask1); + v_src21 = v_and(v_src21, v_mask1); + v_src31 = v_and(v_src31, v_mask1); + + v_float32 v_dst00, v_dst01; + v_float32 v_dst10, v_dst11; + v_float32 v_dst20, v_dst21; + v_float32 v_dst30, v_dst31; + + v_load_deinterleave(dst + x * cn, v_dst00, v_dst10, v_dst20, v_dst30); + + v_load_deinterleave(dst + (x + step) * cn, v_dst01, v_dst11, v_dst21, v_dst31); + + v_store_interleave(dst + x * cn, + v_add(v_dst00, v_src00), + v_add(v_dst10, v_src10), + v_add(v_dst20, v_src20), + v_add(v_dst30, v_src30)); + + v_store_interleave(dst + (x + step) * cn, + v_add(v_dst01, v_src01), + v_add(v_dst11, v_src11), + v_add(v_dst21, v_src21), + v_add(v_dst31, v_src31)); + } } } - else if (cn == 3) - { - for ( ; x <= len - cVectorWidth ; x += cVectorWidth) - { - v_uint16 v_masku16 = vx_load_expand(mask + x); - v_uint32 v_masku320, v_masku321; - v_expand(v_masku16, v_masku320, v_masku321); - v_float32 v_mask0 = v_reinterpret_as_f32(v_not(v_eq(v_masku320, v_reinterpret_as_u32(v_0)))); - v_float32 v_mask1 = v_reinterpret_as_f32(v_not(v_eq(v_masku321, v_reinterpret_as_u32(v_0)))); - v_float32 v_src00, v_src01, v_src10, v_src11, v_src20, v_src21; - v_load_deinterleave(src + x * cn, v_src00, v_src10, v_src20); - v_load_deinterleave(src + (x + step) * cn, v_src01, v_src11, v_src21); - v_src00 = v_and(v_src00, v_mask0); - v_src01 = v_and(v_src01, v_mask1); - v_src10 = v_and(v_src10, v_mask0); - v_src11 = v_and(v_src11, v_mask1); - v_src20 = v_and(v_src20, v_mask0); - v_src21 = v_and(v_src21, v_mask1); - - v_float32 v_dst00, v_dst01, v_dst10, v_dst11, v_dst20, v_dst21; - v_load_deinterleave(dst + x * cn, v_dst00, v_dst10, v_dst20); - v_load_deinterleave(dst + (x + step) * cn, v_dst01, v_dst11, v_dst21); - - v_store_interleave(dst + x * cn, v_add(v_dst00, v_src00), v_add(v_dst10, v_src10), v_add(v_dst20, v_src20)); - v_store_interleave(dst + (x + step) * cn, v_add(v_dst01, v_src01), v_add(v_dst11, v_src11), v_add(v_dst21, v_src21)); - } - } } #endif // CV_SIMD acc_general_(src, dst, mask, len, cn, x); @@ -3106,4 +3270,4 @@ CV_CPU_OPTIMIZATION_NAMESPACE_END } // namespace cv -///* End of file. */ +///* End of file. */ \ No newline at end of file diff --git a/modules/imgproc/src/contours_new.cpp b/modules/imgproc/src/contours_new.cpp index 34dbb70d49..355a5e349c 100644 --- a/modules/imgproc/src/contours_new.cpp +++ b/modules/imgproc/src/contours_new.cpp @@ -649,7 +649,7 @@ void cv::findContours(InputArray _image, } // Fast path: RETR_LIST without hierarchy โ†’ findTRUContours (parallel contour extraction) - if (mode == RETR_LIST && !_hierarchy.needed()) + if (mode == RETR_LIST && !_hierarchy.needed() && _image.type() == CV_8UC1) { // findTRUContours requires FOREGROUND=255; binarize=true thresholds the padded // image in-place, avoiding an extra allocation (findContours accepts any non-zero value) diff --git a/modules/imgproc/src/contours_truco.cpp b/modules/imgproc/src/contours_truco.cpp index a4d3c0cce0..94f1f332f6 100644 --- a/modules/imgproc/src/contours_truco.cpp +++ b/modules/imgproc/src/contours_truco.cpp @@ -618,7 +618,7 @@ void findTRUContours(InputArray _src, OutputArrayOfArrays _contours, int minSize { CV_INSTRUMENT_REGION(); Mat src = _src.getMat(); - CV_Assert(!src.empty() && src.type() == CV_8UC1); + CV_Assert(src.type() == CV_8UC1); // Buffer handling cv::Mat padded; diff --git a/modules/imgproc/src/filter.simd.hpp b/modules/imgproc/src/filter.simd.hpp index e414bd296f..8ec4659e78 100644 --- a/modules/imgproc/src/filter.simd.hpp +++ b/modules/imgproc/src/filter.simd.hpp @@ -415,22 +415,21 @@ public: int w = std::min(tileSize, dst.cols - dst_x); int h = std::min(tileSize, dst.rows - dst_y); - int src_x1 = dst_x - dx1, src_y1 = dst_y - dy1; - int src_x2 = dst_x + w + dx2, src_y2 = dst_y + h + dy2; + Size wholeSize; + Point ofs; + src.locateROI(wholeSize, ofs); + + // Parent coordinates. + int src_x1 = ofs.x + dst_x - dx1, src_y1 = ofs.y + dst_y - dy1; + int src_x2 = ofs.x + dst_x + w + dx2, + src_y2 = ofs.y + dst_y + h + dy2; int pad_top = std::max(0, -src_y1); - int pad_bottom = std::max(0, src_y2 - src.rows); + int pad_bottom = std::max(0, src_y2 - wholeSize.height); int pad_left = std::max(0, -src_x1); - int pad_right = std::max(0, src_x2 - src.cols); + int pad_right = std::max(0, src_x2 - wholeSize.width); - int clamped_x1 = std::max(0, src_x1); - int clamped_y1 = std::max(0, src_y1); - int clamped_x2 = std::min(src.cols, src_x2); - int clamped_y2 = std::min(src.rows, src_y2); - - Mat src_region = src(Rect(clamped_x1, clamped_y1, - clamped_x2 - clamped_x1, - clamped_y2 - clamped_y1)); + Mat src_region = src(Rect(dst_x, dst_y, w, h)).adjustROI(dy1, dy2, dx1, dx2); Mat tile_mat; if (pad_top == 0 && pad_bottom == 0 && pad_left == 0 && pad_right == 0) @@ -542,9 +541,41 @@ void FilterEngine__apply(FilterEngine& this_, const Mat& src, Mat& dst, const Si (size_t)src.total() >= std::max((size_t)1024 * 1024, (size_t)nthreads * 64 * 1024) && this_.rowBorderType == this_.columnBorderType) { - // For in-place operations (e.g. morphologyEx MORPH_OPEN), clone src so that - // concurrent tiles read from an immutable snapshot rather than racing on writes. - Mat src_copy = (src.data == dst.data) ? src.clone() : src; + // Robust cloning for in-place/overlapping operations with ROI support. + Size resolved_wsz = wsz; + Point resolved_ofs = ofs; + if (resolved_wsz.width < 0) { + src.locateROI(resolved_wsz, resolved_ofs); + } + + bool overlap = (src.data <= dst.dataend && dst.data <= src.dataend); + Mat src_copy; + if (resolved_wsz == src.size()) { + src_copy = overlap ? src.clone() : src; + } else { + // In case of ROI. + Mat parent(resolved_wsz, src.type(), + (void*)(src.data - resolved_ofs.y * src.step - + resolved_ofs.x * src.elemSize()), + src.step); + if (overlap) { + int dx1 = this_.anchor.x, dx2 = this_.ksize.width - dx1 - 1; + int dy1 = this_.anchor.y, dy2 = this_.ksize.height - dy1 - 1; + + int p_x1 = std::max(0, resolved_ofs.x - dx1); + int p_y1 = std::max(0, resolved_ofs.y - dy1); + + // Clone the required region only. + Mat required_region = parent(Rect(resolved_ofs, src.size())).adjustROI(dy1, dy2, dx1, dx2).clone(); + + src_copy = required_region(Rect(resolved_ofs - Point(p_x1, p_y1), src.size())); + } else { + // Actually src_copy = src but makes sure src_copy is seen as a sub-matrix + // because sepFilter2D creates a submatrix on the fly without having it be + // an official sub-matrix (which would make locateROI fail in TiledFilterInvoker) + src_copy = parent(Rect(resolved_ofs, src.size())); + } + } // Heuristic: Balance L2 cache locality (128) vs parallel load balancing (64). int tileSize = (src.total() < (size_t)nthreads * 128 * 128 * 4) ? 64 : 128; diff --git a/modules/imgproc/src/hal_replacement.hpp b/modules/imgproc/src/hal_replacement.hpp index 0fb559adef..4d3fe4477d 100644 --- a/modules/imgproc/src/hal_replacement.hpp +++ b/modules/imgproc/src/hal_replacement.hpp @@ -1409,6 +1409,30 @@ inline int hal_ni_laplacian(const uchar* src_data, size_t src_step, uchar* dst_d #define cv_hal_laplacian hal_ni_laplacian //! @endcond +/** + @brief Compute spatial gradient (Sobel X and Y simultaneously). + @param src_data Source image data (8-bit single channel) + @param src_step Source image step + @param dx_data Destination X-gradient data (16-bit signed) + @param dx_step Destination X-gradient step + @param dy_data Destination Y-gradient data (16-bit signed) + @param dy_step Destination Y-gradient step + @param width Image width + @param height Image height + @param ksize Kernel size (must be 3) + @param border_type Border type (BORDER_DEFAULT or BORDER_REPLICATE) +*/ +inline int hal_ni_spatialGradient(const uchar* src_data, size_t src_step, + short* dx_data, size_t dx_step, + short* dy_data, size_t dy_step, + int width, int height, + int ksize, int border_type) +{ return CV_HAL_ERROR_NOT_IMPLEMENTED; } + +//! @cond IGNORED +#define cv_hal_spatialGradient hal_ni_spatialGradient +//! @endcond + /** @brief Perform Gaussian Blur and downsampling for input tile. @param depth Depths of source and destination image diff --git a/modules/imgproc/src/spatialgradient.cpp b/modules/imgproc/src/spatialgradient.cpp index f422609c40..4342d53764 100644 --- a/modules/imgproc/src/spatialgradient.cpp +++ b/modules/imgproc/src/spatialgradient.cpp @@ -113,6 +113,13 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, // TODO: Allow for other kernel sizes CV_Assert(ksize == 3); + CALL_HAL(spatialGradient, cv_hal_spatialGradient, + src.data, src.step, + dx.ptr(), dx.step, + dy.ptr(), dy.step, + src.cols, src.rows, + ksize, borderType); + // Get dimensions const int H = src.rows, W = src.cols; diff --git a/modules/imgproc/test/test_filter.cpp b/modules/imgproc/test/test_filter.cpp index 243887a0bf..2b9764ffc2 100644 --- a/modules/imgproc/test/test_filter.cpp +++ b/modules/imgproc/test/test_filter.cpp @@ -1450,4 +1450,61 @@ TEST(Imgproc_Filter2D, padding_bounds_roi_isolated) } } +class FastFilterEngineTest : public ::testing::Test { + protected: + void SetUp() override { + // Prepare separable kernels (3x3 averaging filter [1, 1, 1]). + kX = cv::Mat::ones(1, 3, CV_32F) / 3.0f; + kY = cv::Mat::ones(3, 1, CV_32F) / 3.0f; + } + + cv::Mat kX, kY; +}; + +TEST_F(FastFilterEngineTest, Submatrix) { + // num_threads == 2 triggers the fast path. + for(int num_threads : {1, 2}) { + setNumThreads(num_threads); + Mat1b parent(1200, 1200, 255); + Mat roi = parent(Rect(100, 100, 1024, 1024)); + roi.setTo(Scalar(0)); + Mat dst; + + sepFilter2D(roi, dst, -1, kX, kY, Point(-1, -1), 0, BORDER_REPLICATE); + + // Before fix in fast path: dst.at(0,0) == 0 (treated as isolated). + // With fix in fast path: dst.at(0,0) > 0 (correctly reads 255 padding from parent). + EXPECT_GT(dst.at(0, 0), 0); + + // Filter in-place directly into 'roi' (dst == roi). + sepFilter2D(roi, roi, -1, kX, kY, Point(-1, -1), 0, BORDER_REPLICATE); + + // Before fix in fast path: roi.at(0,0) == 0 (cloned only ROI, lost parent padding). + // With fix in fast path: roi.at(0,0) > 0 (clones required_region including padding). + EXPECT_GT(roi.at(0, 0), 0); + } +} + +TEST_F(FastFilterEngineTest, FullImage) { + // num_threads == 2 triggers the fast path. + for(int num_threads : {1, 2}) { + setNumThreads(num_threads); + Mat1b img(1024, 1024, 100); + + Mat dst; + + sepFilter2D(img, dst, -1, kX, kY, Point(-1, -1), 0, BORDER_REPLICATE); + + // Verifies direct pass-through (src_copy = src) executes correctly. + EXPECT_EQ(dst.at(0, 0), 100); + + // Filter in-place directly into 'img' (dst == img). + sepFilter2D(img, img, -1, kX, kY, Point(-1, -1), 0, BORDER_REPLICATE); + + // Verifies that full-image cloning (src.clone()) executes correctly without memory corruption. + EXPECT_EQ(img.at(0, 0), 100); + } +} + + }} // namespace diff --git a/modules/python/test/test_eccms.py b/modules/python/test/test_eccms.py new file mode 100644 index 0000000000..6a3726f7cd --- /dev/null +++ b/modules/python/test/test_eccms.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +''' +ECC multiscale alignment test +''' + +# Python 2/3 compatibility +from __future__ import print_function + +import numpy as np +import cv2 as cv +import inspect +import math + +from tests_common import NewOpenCVTests + +class eccms_test(NewOpenCVTests): + def test_eccms(self): + expected_res = np.array([ + [1.0225, 0.0606, -28.6452], + [-0.0475, 1.0314, 11.819], + [8.21e-06, -3.65e-07, 1.0] + ], dtype=np.float32) + + largeGray0 = self.get_sample('cv/shared/halmosh0.jpg', cv.IMREAD_GRAYSCALE) + largeGray1 = self.get_sample('cv/shared/halmosh2.jpg', cv.IMREAD_GRAYSCALE) + roiMask0 = self.get_sample('cv/shared/halmosh0mask.png', cv.IMREAD_GRAYSCALE) + roiMask1 = self.get_sample('cv/shared/halmosh2mask.png', cv.IMREAD_GRAYSCALE) + + if largeGray0 is None or largeGray1 is None or roiMask0 is None or roiMask1 is None: + self.assertEqual(0, 1, 'Missing test data') + + found = np.eye(3, 3, dtype=np.float32) + n_iters = 23 + termination_eps = 1e-6 + params = cv.ECCParameters() + params.criteria = (cv.TERM_CRITERIA_COUNT + cv.TERM_CRITERIA_EPS, n_iters, termination_eps) + params.motionType = cv.MOTION_HOMOGRAPHY + params.nlevels = 5 + params.itersPerLevel = [5, 10, 300, 300, 1000] + + _, found = cv.findTransformECCMultiScale(largeGray0,largeGray1, found, params, roiMask0, roiMask1) + + self.assertLess(cv.norm(found - expected_res, cv.NORM_L1), 0.1) + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/video/include/opencv2/video/tracking.hpp b/modules/video/include/opencv2/video/tracking.hpp index acd3eb753c..bf85889b7b 100644 --- a/modules/video/include/opencv2/video/tracking.hpp +++ b/modules/video/include/opencv2/video/tracking.hpp @@ -453,10 +453,10 @@ Affects accuracy, especially when motionType == MOTION_TRANSLATION. (DEFAULT: IN */ struct CV_EXPORTS_W_SIMPLE ECCParameters { - CV_WRAP ECCParameters() {} + CV_WRAP ECCParameters(); CV_PROP_RW int motionType = MOTION_AFFINE; - CV_PROP_RW cv::TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 50, 1e-6); - CV_PROP_RW std::vector itersPerLevel = std::vector(); + CV_PROP_RW cv::TermCriteria criteria; + CV_PROP_RW std::vector itersPerLevel; CV_PROP_RW int gaussFiltSize = 5; CV_PROP_RW int nlevels = 4; CV_PROP_RW int interpolation = INTER_LINEAR; diff --git a/modules/video/src/eccms.cpp b/modules/video/src/eccms.cpp index 963763d5d3..97557bde89 100644 --- a/modules/video/src/eccms.cpp +++ b/modules/video/src/eccms.cpp @@ -9,6 +9,9 @@ \****************************************************************************************/ namespace cv { +ECCParameters::ECCParameters() : criteria(TermCriteria::COUNT + TermCriteria::EPS, 50, 1e-6) +{} + typedef std::vector MatPyramid; template struct MotionTraits {}; diff --git a/modules/video/test/test_accum.cpp b/modules/video/test/test_accum.cpp index e69de29bb2..343d624db6 100644 --- a/modules/video/test/test_accum.cpp +++ b/modules/video/test/test_accum.cpp @@ -0,0 +1,157 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +typedef testing::TestWithParam > Video_Acc_Cn4; + +TEST_P(Video_Acc_Cn4, accuracy) +{ + const Size size = get<0>(GetParam()); + const int pattern = get<1>(GetParam()); + const int srcType = get<2>(GetParam()); + + RNG& rng = theRNG(); + + Mat src(size, srcType); + Mat dst(size, CV_32FC4); + Mat mask(size, CV_8UC1); + + if (srcType == CV_8UC4) + rng.fill(src, RNG::UNIFORM, Scalar::all(0), Scalar::all(256)); + else + rng.fill(src, RNG::UNIFORM, Scalar::all(-10.0), Scalar::all(10.0)); + + rng.fill(dst, RNG::UNIFORM, Scalar::all(-1000.0), Scalar::all(1000.0)); + + for (int y = 0; y < mask.rows; ++y) + { + uchar* row = mask.ptr(y); + + for (int x = 0; x < mask.cols; ++x) + { + switch (pattern) + { + case 0: + row[x] = 0; + break; + case 1: + row[x] = 255; + break; + case 2: + row[x] = ((x + y) % 2) ? 255 : 0; + break; + case 3: + row[x] = ((x * 13 + y * 7) % 5) ? 255 : 0; + break; + default: + row[x] = ((x * 17 + y * 11) % 3) ? 255 : 0; + break; + } + } + } + + Mat dstRef = dst.clone(); + + if (srcType == CV_32FC4) + { + for (int y = 0; y < src.rows; ++y) + { + const Vec4f* srcRow = src.ptr(y); + Vec4f* dstRefRow = dstRef.ptr(y); + const uchar* maskRow = mask.ptr(y); + + for (int x = 0; x < src.cols; ++x) + { + if (maskRow[x]) + { + for (int c = 0; c < 4; ++c) + dstRefRow[x][c] += srcRow[x][c]; + } + } + } + } + else + { + CV_Assert(srcType == CV_8UC4); + + for (int y = 0; y < src.rows; ++y) + { + const Vec4b* srcRow = src.ptr(y); + Vec4f* dstRefRow = dstRef.ptr(y); + const uchar* maskRow = mask.ptr(y); + + for (int x = 0; x < src.cols; ++x) + { + if (maskRow[x]) + { + for (int c = 0; c < 4; ++c) + dstRefRow[x][c] += static_cast(srcRow[x][c]); + } + } + } + } + + cv::accumulate(src, dst, mask); + + const double err = cv::norm(dst, dstRef, NORM_INF); + + EXPECT_EQ(0.0, err) + << "size=" << size + << ", pattern=" << pattern + << ", srcType=" << srcType; +} + +INSTANTIATE_TEST_CASE_P(Accumulate, + Video_Acc_Cn4, + testing::Combine( + testing::Values(Size(1, 1), + Size(3, 5), + Size(17, 7), + Size(37, 19), + Size(128, 16), + Size(641, 37)), + testing::Values(0, 1, 2, 3, 4), + testing::Values(CV_32FC4, CV_8UC4))); + +}} // namespace diff --git a/modules/videoio/src/cap_msmf.cpp b/modules/videoio/src/cap_msmf.cpp index 1e1e0b3607..628a5ba19a 100644 --- a/modules/videoio/src/cap_msmf.cpp +++ b/modules/videoio/src/cap_msmf.cpp @@ -1633,7 +1633,9 @@ bool CvCapture_MSMF::configureAudioFrame() chunkLengthOfBytes = bufferAudioData.size(); audioSamplePos += chunkLengthOfBytes/bytesPerSample; } - CV_Check((double)chunkLengthOfBytes, chunkLengthOfBytes >= INT_MIN || chunkLengthOfBytes <= INT_MAX, "MSMF: The chunkLengthOfBytes is out of the allowed range"); + if ((LONGLONG)bufferAudioData.size() < chunkLengthOfBytes) + chunkLengthOfBytes = (LONGLONG)bufferAudioData.size(); + CV_Check((double)chunkLengthOfBytes, chunkLengthOfBytes >= INT_MIN && chunkLengthOfBytes <= INT_MAX, "MSMF: The chunkLengthOfBytes is out of the allowed range"); copy(bufferAudioData.begin(), bufferAudioData.begin() + (int)chunkLengthOfBytes, std::back_inserter(audioDataInUse)); bufferAudioData.erase(bufferAudioData.begin(), bufferAudioData.begin() + (int)chunkLengthOfBytes); if (audioFrame.empty())