From 8798e7948abfc153405ae369a520e782147b2187 Mon Sep 17 00:00:00 2001 From: Tiansuanyu Date: Mon, 18 May 2026 22:25:43 +0800 Subject: [PATCH 01/15] imgproc: add RVV optimization for spatialGradient --- hal/riscv-rvv/include/imgproc.hpp | 10 + hal/riscv-rvv/src/imgproc/spatialgradient.cpp | 254 ++++++++++++++++++ modules/imgproc/src/hal_replacement.hpp | 24 ++ modules/imgproc/src/spatialgradient.cpp | 7 + 4 files changed, 295 insertions(+) create mode 100644 hal/riscv-rvv/src/imgproc/spatialgradient.cpp diff --git a/hal/riscv-rvv/include/imgproc.hpp b/hal/riscv-rvv/include/imgproc.hpp index f424a49bad..58f750799f 100644 --- a/hal/riscv-rvv/include/imgproc.hpp +++ b/hal/riscv-rvv/include/imgproc.hpp @@ -274,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/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/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; From 60b0f01528694f7268cded374fa6903427bf5edf Mon Sep 17 00:00:00 2001 From: Fulnergy <12412020@mail.sustech.edu.cn> Date: Mon, 25 May 2026 21:14:31 +0800 Subject: [PATCH 02/15] fix test hang in win11 from issue #37400 --- modules/videoio/src/cap_msmf.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/videoio/src/cap_msmf.cpp b/modules/videoio/src/cap_msmf.cpp index a79cb19716..212db5e18b 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()) From 55d3e3ff4fcc3fccf13e6e6c611e82268e95db1c Mon Sep 17 00:00:00 2001 From: Tiansuanyu Date: Tue, 26 May 2026 17:38:10 +0800 Subject: [PATCH 03/15] core: fix numerical instability and out-of-bounds store in VBLAS SIMD Backport of PR #29140 to 4.x branch. --- modules/core/src/lapack.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/core/src/lapack.cpp b/modules/core/src/lapack.cpp index 83adeeabc3..a23cec8496 100644 --- a/modules/core/src/lapack.cpp +++ b/modules/core/src/lapack.cpp @@ -302,12 +302,13 @@ template<> inline int VBLAS::givens(float* a, float* b, int n, float c, f return 0; int k = 0; v_float32 c4 = vx_setall_f32(c), s4 = vx_setall_f32(s); + v_float32 ns4 = vx_setall_f32(-s); for( ; k <= n - VTraits::vlanes(); k += VTraits::vlanes() ) { v_float32 a0 = vx_load(a + k); v_float32 b0 = vx_load(b + k); - v_float32 t0 = v_add(v_mul(a0, c4), v_mul(b0, s4)); - v_float32 t1 = v_sub(v_mul(b0, c4), v_mul(a0, s4)); + v_float32 t0 = v_fma(a0, c4, v_mul(b0, s4)); + v_float32 t1 = v_fma(a0, ns4, v_mul(b0, c4)); v_store(a + k, t0); v_store(b + k, t1); } @@ -330,9 +331,7 @@ template<> inline int VBLAS::dot(const double* a, const double* b, int n s0 = v_add(s0, v_mul(a0, b0)); } - double sbuf[2]; - v_store(sbuf, s0); - *result = sbuf[0] + sbuf[1]; + *result = v_reduce_sum(s0); vx_cleanup(); return k; } @@ -342,12 +341,13 @@ template<> inline int VBLAS::givens(double* a, double* b, int n, double { int k = 0; v_float64 c2 = vx_setall_f64(c), s2 = vx_setall_f64(s); + v_float64 ns2 = vx_setall_f64(-s); for( ; k <= n - VTraits::vlanes(); k += VTraits::vlanes() ) { v_float64 a0 = vx_load(a + k); v_float64 b0 = vx_load(b + k); - v_float64 t0 = v_add(v_mul(a0, c2), v_mul(b0, s2)); - v_float64 t1 = v_sub(v_mul(b0, c2), v_mul(a0, s2)); + v_float64 t0 = v_fma(a0, c2, v_mul(b0, s2)); + v_float64 t1 = v_fma(a0, ns2, v_mul(b0, c2)); v_store(a + k, t0); v_store(b + k, t1); } From 8ec62ad3460fd4f371000157cf98cccaaa933303 Mon Sep 17 00:00:00 2001 From: Vigh Sebastian Date: Tue, 26 May 2026 18:09:44 +0300 Subject: [PATCH 04/15] Merge pull request #29134 from svigh:gapi_u8_nd_mats_onnx_layout_fix Merge pull request #29134 from svigh/gapi_u8_nd_mats_onnx_layout_fix Gapi u8 N-D mats onnx layout workaround #29134 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- .../gapi/src/backends/onnx/gonnxbackend.cpp | 7 +++-- .../gapi/test/infer/gapi_infer_onnx_test.cpp | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/modules/gapi/src/backends/onnx/gonnxbackend.cpp b/modules/gapi/src/backends/onnx/gonnxbackend.cpp index cbdd615c20..132a2d0db5 100644 --- a/modules/gapi/src/backends/onnx/gonnxbackend.cpp +++ b/modules/gapi/src/backends/onnx/gonnxbackend.cpp @@ -330,7 +330,8 @@ inline void preprocess(const cv::Mat& src, cv::Mat& dst) { // CNN input type const auto type = toCV(ti.type); - if (src.depth() != CV_8U) { + + if (src.depth() != CV_8U || src.dims > 2) { // Just pass the tensor as-is. // No layout or dimension transformations done here! // TODO: This needs to be aligned across all NN backends. @@ -338,10 +339,10 @@ inline void preprocess(const cv::Mat& src, if (tensor_dims.size() == ti.dims.size()) { for (size_t i = 0; i < ti.dims.size(); ++i) { GAPI_Assert((ti.dims[i] == -1 || ti.dims[i] == tensor_dims[i]) && - "Non-U8 tensor dimensions should match with all non-dynamic NN input dimensions"); + "Tensor dimensions should match with all non-dynamic NN input dimensions"); } } else { - GAPI_Error("Non-U8 tensor size should match with NN input"); + GAPI_Error("Tensor size should match with NN input"); } dst = src; diff --git a/modules/gapi/test/infer/gapi_infer_onnx_test.cpp b/modules/gapi/test/infer/gapi_infer_onnx_test.cpp index 6ca394aef2..605508481d 100644 --- a/modules/gapi/test/infer/gapi_infer_onnx_test.cpp +++ b/modules/gapi/test/infer/gapi_infer_onnx_test.cpp @@ -592,6 +592,37 @@ TEST_F(ONNXClassification, InferTensor) validate(); } +TEST_F(ONNXClassification, InferU8Tensor4D) +{ + // Create a U8 N-D (4D) tensor matching model input dims {1, 3, 224, 224}. + // This simulates tools like protopipe that feed pre-filled U8 N-D tensors + // directly to the ONNX backend (bypassing 2D image preprocessing). + // Without checking dims, preprocess() enters the image path and throws + // "Couldn't identify input tensor layout" because channels()==1 for N-D mats. + + useModel("classification/squeezenet/model/squeezenet1.0-9"); + + // ONNX_API code + cv::Mat backing_buf({1, 3, 224, 224}, CV_32F); + cv::randu(backing_buf, 0.f, 255.f); + infer(backing_buf, out_onnx); + + // G_API code + cv::Mat tensor(4, backing_buf.size.p, CV_8U, backing_buf.data); + G_API_NET(SqueezNet, , "squeeznet"); + cv::GMat in; + cv::GMat out = cv::gapi::infer(in); + cv::GComputation comp(cv::GIn(in), cv::GOut(out)); + auto net = cv::gapi::onnx::Params { + model_path + }.cfgNormalize({false}); + comp.apply(cv::gin(tensor), + cv::gout(out_gapi.front()), + cv::compile_args(cv::gapi::networks(net))); + // Validate + validate(); +} + TEST_F(ONNXClassification, InferROI) { useModel("classification/squeezenet/model/squeezenet1.0-9"); From 39da6f45e47b8b52866f7fb89530a7626d68cf0c Mon Sep 17 00:00:00 2001 From: example Date: Wed, 27 May 2026 09:23:18 +0800 Subject: [PATCH 05/15] imgcodecs: fix UBSan load-invalid-value in SunRasterDecoder::readHeader (#29150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Casting the raw integer fields ras_type and maptype directly to their respective enum types (SunRasType / SunRasMapType) without a prior range check is undefined behavior under C++11 §7.2/8 when the stored value falls outside the enum's valid range. UBSan reports: grfmt_sunras.cpp:72: runtime error: load of value 34077, which is not a valid value for type 'SunRasMapType' Fix: read both fields into plain ints first, validate them against the declared enumerator bounds, and return false (reject the image) on any out-of-range value before performing the enum cast. This is the cheapest correct approach — two integer comparisons added to a path that was already doing I/O — and ensures no downstream code ever sees an ill-formed enum value. Add test_sunraster.cpp with regression tests covering: - The exact crash_001 payload (invalid maptype 34077 / 0x851d) - Invalid ras_type values - maptype = 2 and UINT_MAX (outside [0,1]) - Truncated header - A valid 8-bpp grayscale image that must still decode correctly Signed-off-by: FuzzAnything fuzzanything@gmail.com --- modules/imgcodecs/src/grfmt_sunras.cpp | 16 ++- modules/imgcodecs/test/test_sunraster.cpp | 138 ++++++++++++++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 modules/imgcodecs/test/test_sunraster.cpp 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/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 From 0ab6bd2d680e1b0ced45edb6b6c63d092012c528 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 27 May 2026 14:31:20 +0200 Subject: [PATCH 06/15] Fix ROI handling in 2D-tiled parallel execution in FilterEngine --- modules/imgproc/src/filter.simd.hpp | 61 +++++++++++++++++++++------- modules/imgproc/test/test_filter.cpp | 57 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 15 deletions(-) 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/test/test_filter.cpp b/modules/imgproc/test/test_filter.cpp index 64858836ce..91baa66e6a 100644 --- a/modules/imgproc/test/test_filter.cpp +++ b/modules/imgproc/test/test_filter.cpp @@ -2740,4 +2740,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 From 850166c720731ea58c3fc941b6a564b564a6be48 Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Thu, 28 May 2026 01:08:04 +0800 Subject: [PATCH 07/15] imgproc: re-enable RVV integral for safe cases --- hal/riscv-rvv/include/imgproc.hpp | 6 ++---- hal/riscv-rvv/src/imgproc/integral.cpp | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hal/riscv-rvv/include/imgproc.hpp b/hal/riscv-rvv/include/imgproc.hpp index 58f750799f..ffe3f75f20 100644 --- a/hal/riscv-rvv/include/imgproc.hpp +++ b/hal/riscv-rvv/include/imgproc.hpp @@ -236,10 +236,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, 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; From 8ebcf229a1fb6fc95fea3cc7277925df76d476c3 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 27 May 2026 17:52:11 +0200 Subject: [PATCH 08/15] Make sure Truco is launched on non-empty and CV_8UC1 only. This is checked in the function: https://github.com/opencv/opencv/blob/3bb68212dac2e1c42e7910de9daf3a0766eaead8/modules/imgproc/src/contours_truco.cpp#L621 --- modules/imgproc/src/contours_new.cpp | 2 +- modules/imgproc/src/contours_truco.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/imgproc/src/contours_new.cpp b/modules/imgproc/src/contours_new.cpp index 67bc7aa2dd..ed48f1e1ff 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; From a61ff0fa81c7a4e6b10beb80aff2446654febe41 Mon Sep 17 00:00:00 2001 From: Siyu Wang <12412639@mail.sustech.edu.cn> Date: Thu, 28 May 2026 17:44:16 +0800 Subject: [PATCH 09/15] Merge pull request #29094 from feitianduowen:4.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake ### Summary This PR improves SIMD coverage for masked `cv::accumulate()` on 4-channel images. The existing masked accumulate SIMD implementation has special paths for `cn == 1` and `cn == 3`, while 4-channel images fall back to the general implementation. This PR adds `cn == 4` SIMD paths using OpenCV Universal Intrinsics. Compare link: https://github.com/opencv/opencv/compare/4.x...feitianduowen:opencv:4.x Modified files: - `modules/imgproc/src/accum.simd.hpp` - Add masked `cn == 4` SIMD paths for: - `CV_32FC4 -> CV_32FC4` - `CV_8UC4 -> CV_32FC4` - `modules/video/test/test_accum.cpp` - Add deterministic correctness regression tests for the new masked 4-channel accumulate paths. - `modules/imgproc/perf/perf_accumulate.cpp` - Add performance coverage for masked 4-channel accumulate. imgproc: add SIMD paths for masked 4-channel accumulate #29094 ### Implementation The new SIMD branches are added in: - `modules/imgproc/src/accum.simd.hpp` They handle: - `src`: `CV_32FC4`, `dst`: `CV_32FC4`, `mask`: `CV_8UC1` - `src`: `CV_8UC4`, `dst`: `CV_32FC4`, `mask`: `CV_8UC1` For the `CV_32FC4` path, the implementation uses `v_load_deinterleave`, applies the pixel mask to all 4 channels, accumulates into `dst`, and stores the result with `v_store_interleave`. For the `CV_8UC4 -> CV_32FC4` path, the implementation loads 4 interleaved uchar channels, applies the pixel mask, widens the values to float, accumulates into `dst`, and stores the results back as interleaved `CV_32FC4`. The masked SIMD block is guarded with: ```cpp if (x <= len - cVectorWidth) { ... } ``` This check ensures that SIMD setup and vector operations are only executed when at least one full vector iteration can run. For very small inputs or tail-only cases, the function skips SIMD initialization and lets the existing scalar fallback handle the data through `acc_general_(src, dst, mask, len, cn, x)`. This keeps tail handling unchanged and avoids unnecessary SIMD initialization when the vector loop would be skipped. ### Accuracy tests Added a deterministic regression test in: * `modules/video/test/test_accum.cpp` New test: ```bash Video_Acc.accuracy_32FC4_masked_cn4 Video_Acc.accuracy_8UC4_to_32FC4_masked_cn4 ``` Test commands: ```bash opencv_test_video --gtest_filter=Video_Acc.accuracy_32FC4_masked_cn4 opencv_test_video --gtest_filter=Video_Acc.accuracy_8UC4_to_32FC4_masked_cn4 opencv_test_video --gtest_filter="Video_Acc.*:Video_AccSquared.*:Video_AccProduct.*:Video_RunningAvg.*" ``` The filtered accumulate-related tests passed locally. I added dedicated deterministic tests instead of only extending the existing randomized accumulate tests because this PR only changes the masked `cv::accumulate()` paths for specific 4-channel type combinations. It does not add `cn == 4` SIMD coverage for `accumulateSquare`, `accumulateProduct`, or `accumulateWeighted`. The existing base accumulate tests are shared by several accumulation functions. Extending the common randomized channel selection to include `cn == 4` would also affect tests for functions that are not optimized by this PR. The new deterministic tests directly cover the modified paths: * `cv::accumulate(src, dst, mask)` * `CV_32FC4 -> CV_32FC4` * `CV_8UC4 -> CV_32FC4` * `mask`: `CV_8UC1` The test also covers small sizes and non-vector-multiple sizes, so both the new SIMD path and the existing scalar tail fallback are exercised. ### Performance tests Added performance coverage in: * `modules/imgproc/perf/perf_accumulate.cpp` Perf command: ```bash opencv_perf_imgproc --gtest_filter="*AccumulateMask32FC4*" --perf_min_samples=1000 --perf_force_samples=1000 opencv_perf_imgproc --gtest_filter="*AccumulateMask8UC4To32FC4*" --perf_min_samples=1000 --perf_force_samples=1000 ``` Test environment: * Release build * AVX2 enabled * IPP disabled * OpenCL disabled * Windows MinGW build Performance results: `CV_32FC4 -> CV_32FC4` | Size | Before median | After median | Speedup | | --------- | ------------- | ------------ | ------- | | 1920x1080 | 3.07 ms | 2.74 ms | 1.12x | | 1280x720 | 0.84 ms | 0.73 ms | 1.15x | | 640x480 | 0.17 ms | 0.16 ms | 1.06x | | 320x240 | 0.04 ms | 0.04 ms | ~1.00x | `CV_8UC4 -> CV_32FC4` | Size | Before median | After median | Speedup | | --------- | ------------- | ------------ | ------- | | 1920x1080 | 6.09 ms | 1.75 ms | 3.48x | | 1280x720 | 2.69 ms | 0.82 ms | 3.28x | | 640x480 | 0.96 ms | 0.28 ms | 3.43x | | 320x240 | 0.24 ms | 0.06 ms | 4.00x | | 127x61 | 0.02 ms | 0.01 ms | 2.00x | ### Build and test commands The following commands were run from Windows `cmd.exe`. ```sh cmake -S . -B build_release -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF -DWITH_IPP=OFF -DWITH_OPENCL=OFF -DCMAKE_C_FLAGS=-mstackrealign -DCMAKE_CXX_FLAGS=-mstackrealign cmake --build build_release --target opencv_test_video -j 4 build_release\bin\opencv_test_video.exe --gtest_filter=Video_Acc.accuracy_32FC4_masked_cn4 build_release\bin\opencv_test_video.exe --gtest_filter=Video_Acc.accuracy_8UC4_to_32FC4_masked_cn4 build_release\bin\opencv_test_video.exe --gtest_filter=Video_Acc.*:Video_AccSquared.*:Video_AccProduct.*:Video_RunningAvg.* ``` Accuracy test commands passed locally. For the performance comparison, I kept the new perf test in `modules/imgproc/perf/perf_accumulate.cpp` and only reverted `modules/imgproc/src/accum.simd.hpp` to measure the baseline. Then I restored the SIMD patch and measured the optimized version. Baseline measurement: ```sh git diff -- modules/imgproc/src/accum.simd.hpp > accum_cn4_simd.patch git checkout -- modules/imgproc/src/accum.simd.hpp mkdir perf_logs cmake -S . -B build_perf_before -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=ON -DBUILD_EXAMPLES=OFF -DWITH_IPP=OFF -DWITH_OPENCL=OFF -DCMAKE_C_FLAGS=-mstackrealign -DCMAKE_CXX_FLAGS=-mstackrealign > perf_logs\before_cmake_config.txt 2>&1 cmake --build build_perf_before --target opencv_perf_imgproc -j 4 > perf_logs\before_build.txt 2>&1 build_perf_before\bin\opencv_perf_imgproc.exe --gtest_filter=*AccumulateMask32FC4* --perf_min_samples=1000 --perf_force_samples=1000 > perf_logs\before_perf.txt 2>&1 ``` ```sh git diff -- modules/imgproc/src/accum.simd.hpp > accum_cn4_u8_simd.patch git checkout -- modules/imgproc/src/accum.simd.hpp cmake -S . -B build_perf_before_u8 -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=ON -DBUILD_EXAMPLES=OFF -DWITH_IPP=OFF -DWITH_OPENCL=OFF -DCMAKE_C_FLAGS=-mstackrealign -DCMAKE_CXX_FLAGS=-mstackrealign cmake --build build_perf_before_u8 --target opencv_perf_imgproc -j 4 build_perf_before_u8\bin\opencv_perf_imgproc.exe --gtest_filter=*AccumulateMask8UC4To32FC4* --perf_min_samples=1000 --perf_force_samples=1000 > perf_logs\before_perf1.txt 2>&1 ``` Optimized measurement: ```sh git apply accum_cn4_simd.patch cmake -S . -B build_perf_after -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=ON -DBUILD_EXAMPLES=OFF -DWITH_IPP=OFF -DWITH_OPENCL=OFF -DCMAKE_C_FLAGS=-mstackrealign -DCMAKE_CXX_FLAGS=-mstackrealign > perf_logs\after_cmake_config.txt 2>&1 cmake --build build_perf_after --target opencv_perf_imgproc -j 4 > perf_logs\after_build.txt 2>&1 build_perf_after\bin\opencv_perf_imgproc.exe --gtest_filter=*AccumulateMask32FC4* --perf_min_samples=500 --perf_force_samples=500 > perf_logs\after_perf.txt 2>&1 ``` ```sh git apply accum_cn4_u8_simd.patch cmake -S . -B build_perf_after_u8 -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=ON -DBUILD_EXAMPLES=OFF -DWITH_IPP=OFF -DWITH_OPENCL=OFF -DCMAKE_C_FLAGS=-mstackrealign -DCMAKE_CXX_FLAGS=-mstackrealign cmake --build build_perf_after_u8 --target opencv_perf_imgproc -j 4 build_perf_after_u8\bin\opencv_perf_imgproc.exe --gtest_filter=*AccumulateMask8UC4To32FC4* --perf_min_samples=1000 --perf_force_samples=1000 > perf_logs\after_perf1.txt 2>&1 ``` Performance comparison was measured by keeping the new perf tests and only reverting modules/imgproc/src/accum.simd.hpp for the baseline run. `-mstackrealign` was used for the MinGW Windows build to avoid stack-alignment issues with AVX code generation during local testing. ### Notes The implementation keeps the existing scalar fallback path unchanged. SIMD is used only for the newly covered masked `cn == 4` vectorizable part, and any remaining tail elements are still handled by `acc_general_()`. The improvement is most visible on larger images. Small images are dominated by overhead and do not always show meaningful speedup. --- modules/imgproc/perf/perf_accumulate.cpp | 6 + modules/imgproc/src/accum.simd.hpp | 366 ++++++++++++++++------- modules/video/test/test_accum.cpp | 111 +++++++ 3 files changed, 382 insertions(+), 101 deletions(-) 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/video/test/test_accum.cpp b/modules/video/test/test_accum.cpp index 9941f20359..739ec73e66 100644 --- a/modules/video/test/test_accum.cpp +++ b/modules/video/test/test_accum.cpp @@ -246,4 +246,115 @@ TEST(Video_AccSquared, accuracy) { CV_SquareAccTest test; test.safe_run(); } TEST(Video_AccProduct, accuracy) { CV_MultiplyAccTest test; test.safe_run(); } TEST(Video_RunningAvg, accuracy) { CV_RunningAvgTest test; test.safe_run(); } +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 From 3a4dc42478e74791ce59f41a37fff78503a8b619 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 28 May 2026 21:46:49 +0300 Subject: [PATCH 10/15] Try to revert IPP update to fix access violation on Windows x64. --- 3rdparty/ippicv/ippicv.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/3rdparty/ippicv/ippicv.cmake b/3rdparty/ippicv/ippicv.cmake index 20a8ae0841..980ba25b9e 100644 --- a/3rdparty/ippicv/ippicv.cmake +++ b/3rdparty/ippicv/ippicv.cmake @@ -2,7 +2,7 @@ function(download_ippicv root_var) set(${root_var} "" PARENT_SCOPE) # Commit SHA in the opencv_3rdparty repo - set(IPPICV_COMMIT "406d398c436d0465c8e53dd432d9ecd9301d5f4a") + set(IPPICV_COMMIT "c934a2a15a6df020446ac3dfa07e3acf72b63a8f") # Define actual ICV versions if(APPLE) set(IPPICV_COMMIT "0cc4aa06bf2bef4b05d237c69a5a96b9cd0cb85a") @@ -14,8 +14,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "linux") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_lnx") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2026.0.0_lnx_intel64_20260327_general.tgz") - set(OPENCV_ICV_HASH "9a3ee0c5c3c02102faa422d60bfd1f4a") + set(OPENCV_ICV_NAME "ippicv_2022.2.0_lnx_intel64_20250730_general.tgz") + set(OPENCV_ICV_HASH "55d18247d8ef707f009b94f69d77b948") else() if(ANDROID) set(IPPICV_COMMIT "c7c6d527dde5fee7cb914ee9e4e20f7436aab3a1") @@ -31,8 +31,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "windows") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_win") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2026.0.0_win_intel64_20260327_general.zip") - set(OPENCV_ICV_HASH "73bc67cd5e4c8da706fa88fe84630231") + set(OPENCV_ICV_NAME "ippicv_2022.2.0_win_intel64_20250730_general.zip") + set(OPENCV_ICV_HASH "7c0973976ab0716bc33f03a76a50017f") else() set(IPPICV_COMMIT "7f55c0c26be418d494615afca15218566775c725") set(OPENCV_ICV_NAME "ippicv_2021.12.0_win_ia32_20240425_general.zip") From 18dad63e2ef5ac5a843cc04d62a87c8a173077dc Mon Sep 17 00:00:00 2001 From: Ziyuan Li Date: Fri, 29 May 2026 13:24:03 +0800 Subject: [PATCH 11/15] core: add RVV convertScale data type conversions --- hal/riscv-rvv/src/core/convert_scale.cpp | 148 +++++++++++++++++++++++ 1 file changed, 148 insertions(+) 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); } From 8bc22eba5946100e9b2470c60939c3f2588c28d7 Mon Sep 17 00:00:00 2001 From: s-trinh Date: Fri, 29 May 2026 08:53:01 +0200 Subject: [PATCH 12/15] Merge pull request #29173 from s-trinh:add_parallel_for_tuto_link Add parallel_for_ tutorial (Mandelbrot set) link in the main page #29173 Add the `parallel_for_` tutorial with Mandelbrot set use case in the main page: - the tutorial exists: https://docs.opencv.org/4.13.0/d7/dff/tutorial_how_to_use_OpenCV_parallel_for_.html - but is not listed in the main page: https://docs.opencv.org/4.13.0/de/d7a/tutorial_table_of_content_core.html Some minor cleaning: - removed [C=](https://www.hoopoesnest.com/cstripes/cstripes-sketch.htm) mention - use Web Archive link - prefer https link On my computer (Firefox), the images ([here](https://docs.opencv.org/4.13.0/d3/dc1/tutorial_basic_linear_transform.html)) are shown like this: image When adding a scale parameter, the images are correctly shown: image This is odd. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- .../core/adding_images/adding_images.markdown | 2 +- .../basic_linear_transform.markdown | 6 ++-- .../file_input_output_with_xml_yml.markdown | 1 + .../how_to_use_OpenCV_parallel_for_.markdown | 28 ++++++++++--------- ...ow_to_use_OpenCV_parallel_for_new.markdown | 5 ++-- .../core/table_of_content_core.markdown | 1 + ...sian_median_blur_bilateral_filter.markdown | 2 +- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/doc/tutorials/core/adding_images/adding_images.markdown b/doc/tutorials/core/adding_images/adding_images.markdown index 90679a1b39..8e91688c99 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 0f569a41b2..10381f6180 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 cb6e33e981..2dde00a38d 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 ab24d27ab1..742e49d765 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 57cec4cba1..31dda1ab9a 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 2023de809b..32388f6d9e 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. From f59c654bfcb2c33037516b4b891450c567da0970 Mon Sep 17 00:00:00 2001 From: 4ekmah Date: Fri, 29 May 2026 11:18:20 +0300 Subject: [PATCH 13/15] Merge pull request #29148 from 4ekmah:eccms_fixpython Fixing python wrapper around ECCParameters to include itersPerLevel #29148 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch --- modules/python/test/test_eccms.py | 47 +++++++++++++++++++ .../video/include/opencv2/video/tracking.hpp | 6 +-- modules/video/src/eccms.cpp | 3 ++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 modules/python/test/test_eccms.py 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 f62660494c..790f7442cf 100644 --- a/modules/video/include/opencv2/video/tracking.hpp +++ b/modules/video/include/opencv2/video/tracking.hpp @@ -444,10 +444,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 {}; From fda05a36223ac78be19fd57e2caa553cfec22094 Mon Sep 17 00:00:00 2001 From: Licardo_du Date: Fri, 29 May 2026 17:08:32 +0800 Subject: [PATCH 14/15] Merge pull request #29138 from Licardo-du:fix-imgcodecs-apple-avfoundation imgcodecs: fix Apple conversions build without AVFoundation #29138 Fixes #28553. This PR fixes Apple imgcodecs conversion builds when AVFoundation is disabled or unavailable on older macOS SDKs. Changes: - Guard AVFoundation import with HAVE_AVFOUNDATION. - Use older macOS-compatible framework imports when ImageIO is unavailable. - Add __bridge compatibility for older Objective-C++ compilers. - Use NSMakeSize for older macOS instead of passing CGSize to NSImage API. Testing: - Not tested locally: no macOS environment available. - Patch follows the fix confirmed in #28555 discussion. --- modules/imgcodecs/src/apple_conversions.h | 17 +++++++++++++++++ modules/imgcodecs/src/apple_conversions.mm | 6 ++++++ modules/imgcodecs/src/macosx_conversions.mm | 4 +++- 3 files changed, 26 insertions(+), 1 deletion(-) 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/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; } From 483266c645bc2fd21ee49d2981274c6181e997ac Mon Sep 17 00:00:00 2001 From: Andrei Fedorov Date: Sat, 30 May 2026 08:11:59 +0200 Subject: [PATCH 15/15] Merge pull request #29183 from intel-staging:reenable_icv Reenable ICV and fix #29166 #29183 Fixing https://github.com/opencv/opencv/issues/29166 and return back the 2026.0.0 ICV. Tests are passed locally on Windows machine. Feel free to check if they are passed in bigger test systems. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- 3rdparty/ippicv/ippicv.cmake | 10 +++++----- hal/ipp/src/warp_ipp.cpp | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/3rdparty/ippicv/ippicv.cmake b/3rdparty/ippicv/ippicv.cmake index 980ba25b9e..20a8ae0841 100644 --- a/3rdparty/ippicv/ippicv.cmake +++ b/3rdparty/ippicv/ippicv.cmake @@ -2,7 +2,7 @@ function(download_ippicv root_var) set(${root_var} "" PARENT_SCOPE) # Commit SHA in the opencv_3rdparty repo - set(IPPICV_COMMIT "c934a2a15a6df020446ac3dfa07e3acf72b63a8f") + set(IPPICV_COMMIT "406d398c436d0465c8e53dd432d9ecd9301d5f4a") # Define actual ICV versions if(APPLE) set(IPPICV_COMMIT "0cc4aa06bf2bef4b05d237c69a5a96b9cd0cb85a") @@ -14,8 +14,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "linux") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_lnx") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2022.2.0_lnx_intel64_20250730_general.tgz") - set(OPENCV_ICV_HASH "55d18247d8ef707f009b94f69d77b948") + set(OPENCV_ICV_NAME "ippicv_2026.0.0_lnx_intel64_20260327_general.tgz") + set(OPENCV_ICV_HASH "9a3ee0c5c3c02102faa422d60bfd1f4a") else() if(ANDROID) set(IPPICV_COMMIT "c7c6d527dde5fee7cb914ee9e4e20f7436aab3a1") @@ -31,8 +31,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "windows") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_win") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2022.2.0_win_intel64_20250730_general.zip") - set(OPENCV_ICV_HASH "7c0973976ab0716bc33f03a76a50017f") + set(OPENCV_ICV_NAME "ippicv_2026.0.0_win_intel64_20260327_general.zip") + set(OPENCV_ICV_HASH "73bc67cd5e4c8da706fa88fe84630231") else() set(IPPICV_COMMIT "7f55c0c26be418d494615afca15218566775c725") set(OPENCV_ICV_NAME "ippicv_2021.12.0_win_ia32_20240425_general.zip") 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