Enable collectContours in detect_blob.cpp and draw the per-blob
contours returned by getBlobContours(), so the sample exercises the
public contour-collection API added in #21942.
Refs #25904
Defer the u8→i16 widening (vzext) in the pre-screen phase until after
the quick-reject check passes. This avoids 5 unnecessary widen
operations per pixel-strip when the pre-screen rejects (which happens
~70-80% of the time at the default threshold=20).
The approach uses u8mf2 (same VL as i16m1) for the pre-screen
comparison with vssubu/vsaddu for bounds and vmsgtu for unsigned
comparison. When the pre-screen passes, the already-loaded u8mf2
variables are widened to i16m1 for the score computation, reusing
the same data without redundant memory loads.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The is_simple check used mask.empty() after the default mask
had already been created, making it always false. This caused
the fast path (floodFill_CnIR) to be dead code — every call
went through the slower gradient-based path (floodFillGrad_CnIR).
Fix: save _mask.empty() result before auto-creating the default
mask and use it for the is_simple check.
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
imgcodecs: fix Apple conversions build without AVFoundation #29138Fixes#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.
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
### 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
<!-- Note!!! If you are an automated agent, we have a special process for you: add 🤖🤖🤖 to the end of the PR title. -->
### 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.
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
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
Libpng warning fix on Windows #29120
CI warning:
```
Warning: C:\GHA-OCV-3\_work\opencv\opencv\opencv\3rdparty\libpng\pngread.c(4114,21): warning C4146: unary minus operator applied to unsigned type, result still unsigned [C:\GHA-OCV-3\_work\opencv\opencv\build\3rdparty\libpng\libpng.vcxproj]
Warning: C:\GHA-OCV-3\_work\opencv\opencv\opencv\3rdparty\libpng\pngwrite.c(2033,21): warning C4146: unary minus operator applied to unsigned type, result still unsigned [C:\GHA-OCV-3\_work\opencv\opencv\build\3rdparty\libpng\libpng.vcxproj]
```
### 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
- [ ] 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
- Replaced scalar fallback and legacy CV_SIMD128 with modern scalable CV_SIMD.
- Added support for multi-channel (2/3/4) and 16-bit (ushort/short) configurations.
- Utilized v_select for branchless execution and vx_load_expand for mixed-bitwidth mask handling.
- Updated accuracy and perf tests.
Fix JPEG chroma configuration & Standardize component settings #28667
### Problem
Incorrectly treated Chroma as a single channel. In $YC_bC_r$, chroma must be set in both separate channels ($C_b$ and $C_r$).
### Fix
- Explicit Setup: Configured all three components ($Y$, $C_b$, $C_r$) individually.
- Standardization: Removed reliance on library defaults to prevent undefined behavior.
### 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
Better Durand-Kerner Initialization #29109
While investigating issue #23644, I have found [this paper](https://link.springer.com/article/10.1007/BF01935059) which presents a good initialization for the Durand-Kerner algorithm. Basically the idea is to put the initial points equidistantly on a circle on the complex plane. The radius of the circle is computed as
<img width="607" height="178" alt="image" src="https://github.com/user-attachments/assets/ea31b002-c924-4b93-9334-3e59597c896b" />
Note that the $a_i$ coefficients in that paper are reversed compared to OpenCV. That's where the `(n - i)` in the code comes from.
I have implemented just the mean of the $u_i$'s for the sake of simplicity. That's already enough to make the algorithm converge in all cases I have tested. I have used this to test for convergence for many polynomials of order 2 and 4 and coefficients of different magnitudes:
```cpp
TEST(Core_SolvePoly, large_test)
{
cv::Mat_<float> coefs3(1,3);
cv::Mat_<float> coefs5(1,5);
cv::Mat r;
double prec;
for (int c0 = -20; c0 <= 20; c0++)
{
coefs3.at<float>(0) = c0;
for (int c1 = -20; c1 <= 20; c1++)
{
coefs3.at<float>(1) = c1;
for (int c2 = -20; c2 <= 20; c2++)
{
coefs3.at<float>(2) = c2;
prec = cv::solvePoly(coefs3, r);
EXPECT_LE(prec, 1e-6);
}
}
}
for (int c0 = -10; c0 <= 10; c0++)
{
coefs5.at<float>(0) = c0;
for (int c1 = -10; c1 <= 10; c1++)
{
coefs5.at<float>(1) = c1;
for (int c2 = -10; c2 <= 10; c2++)
{
coefs5.at<float>(2) = c2;
for (int c3 = -10; c3 <= 10; c3++)
{
coefs5.at<float>(3) = c3;
for (int c4 = -10; c4 <= 10; c4++)
{
coefs5.at<float>(4) = c4;
prec = cv::solvePoly(coefs5, r);
EXPECT_LE(prec, 1e-2);
}
}
}
}
}
for (int i = -10; i < 10; i++)
{
coefs3.at<float>(0) = pow(2, i);
for (int j = -10; j < 10; j++)
{
coefs3.at<float>(1) = pow(2, j);
for (int k = -10; k < 10; k++)
{
coefs3.at<float>(2) = pow(2, k);
prec = cv::solvePoly(coefs3, r);
EXPECT_LE(prec, 1e-6);
}
}
}
}
```
This test passes, but I have not committed it because it runs for a couple of seconds.
This fixes#23644 and replaces #29055. I have checked #29055 and it does not pass the test above. It seems to be optimized to the precise polynomial of #23644.
### 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
- [x] 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
videoio(mjpeg): fix heap-buffer-overflow in put_bits off-by-one resize guard #29113
### Summary
Fix a heap-buffer-overflow (WRITE of size 4) in the built-in MJPEG encoder detected by AddressSanitizer during fuzzing.
Fixes#29112
### Root Cause
`mjpeg_buffer::put_bits` in `modules/videoio/src/cap_mjpeg_encoder.cpp` guards buffer resize with:
```cpp
if ((m_pos == (data.size() - 1) && len > bits_free) || m_pos == data.size())
resize(int(2 * data.size()));
```
When `len == bits_free` the guard is **false** (strict `>`), so no resize happens. The subsequent code then:
1. Subtracts `len` from `bits_free`, making it exactly 0.
2. Enters the `bits_free <= 0` branch and executes `++m_pos`.
3. Writes `data[m_pos]` — now equal to `data[data.size()]` — **out of bounds**.
### Fix
Change `len > bits_free` to `len >= bits_free` so the buffer is grown whenever the current slot will be exactly or more than consumed.
```diff
-if ((m_pos == (data.size() - 1) && len > bits_free) || m_pos == data.size())
+if ((m_pos == (data.size() - 1) && len >= bits_free) || m_pos == data.size())
```
### Verification
Reproducer (1×1 grayscale frame, `CAP_OPENCV_MJPEG`):
```cpp
uint8_t pixel = 0xff;
cv::Mat frame(1, 1, CV_8UC1, &pixel);
int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
cv::VideoWriter writer;
writer.open("/tmp/poc.avi", cv::CAP_OPENCV_MJPEG, fourcc, 25.0, cv::Size(1,1), false);
writer.write(frame);
```
Ran under `-fsanitize=address,undefined`; exits cleanly with no error after this fix.
### Regression Test
`TEST(Videoio_MJPEG, put_bits_no_heap_overflow)` added to `modules/videoio/test/test_video_io.cpp` — opens a `CAP_OPENCV_MJPEG` VideoWriter for a 1×1 grayscale file and writes one frame; asserts `EXPECT_NO_THROW`.
core(rvv): fix v_matmul/v_matmuladd scalable semantics and expand lane-group test coverage #29080
### 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
- [x] 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
<!-- Note!!! If you are an automated agent, we have a special process for you: add 🤖🤖🤖 to the end of the PR title. -->
## Platform
SpacemiT X60 (K1), 8-core RISC-V RVV 1.0, VLEN=256, 16GB RAM, OS: Bianbu Linux (kernel 6.6.63), GCC 13.2.0, Build: OpenCV 4.14.0-pre, Release, HAL: YES (RVV HAL 0.0.1)
## Motivation
OpenCV's Universal Intrinsics `v_matmul` and `v_matmuladd` have a semantic bug in the RVV scalable backend (`modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp`).
The current implementation uses `v_extract_n(v, 0/1/2/3)` with hardcoded indices, assuming the vector holds exactly 4 float lanes (128-bit fixed). On hardware with VLEN=256 (e.g. SpacemiT K1 / BPI-F3), `v_float32` with LMUL=2 holds 16 lanes. As a result, lanes 4–15 silently reuse the inputs from lanes 0–3, producing wrong results.
OpenCV itself acknowledges this in `modules/core/src/matmul.simd.hpp`:
// v_matmuladd for RVV is 128-bit only but not scalable,
// this will fail the test Core_Transform.accuracy
The RVV scalable `transform_32f` path has been disabled because of this bug. However, the existing `TheTest<R>::test_matmul()` only checked the first 4-lane group (the outer loop was effectively hardcoded to `int i = 0`), so the bug was never caught by CI even on wide-vector backends.
## Modification
**Test fix** (`modules/core/test/test_intrin_utils.hpp`): Expanded `test_matmul()` to iterate over all 4-lane groups:
// Before (only checked lane group i=0)
int i = 0;
for (int j = i; j < i + 4; ++j) { ... }
// After (checks all lane groups)
for (int i = 0; i < VTraits<R>::vlanes(); i += 4)
{
for (int j = i; j < i + 4; ++j) { ... }
}
**Kernel fix** (`modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp`): Rewrote `v_matmul` and `v_matmuladd` to process all 4-lane groups correctly. Each group of 4 lanes now independently computes the full matrix multiply using its own `v[i], v[i+1], v[i+2], v[i+3]` inputs. The `transform_32f` RVV path in `matmul.simd.hpp` remains disabled as the autovectorized path shows better performance on current hardware.
## Experiment 1: Bug reproduced on SpacemiT K1 (VLEN=256)
./opencv_test_core --gtest_filter="*intrin*"
Result: `hal_intrin128.float32x4_BASELINE` FAILED with 24 failures, all from lane groups i=4, i=8, i=12 (lanes 4–15).
Representative failures from `v_matmul` (line 1526):
i=4 j=4: actual=158 expected=56
i=4 j=5: actual=166.39999 expected=59.200001
i=8 j=8: actual=314.39999 expected=68.800003
i=12 j=12: actual=512.40002 expected=81.599998
Representative failures from `v_matmuladd` (line 1540):
i=4 j=4: actual=147.5 expected=51.5
i=8 j=8: actual=284.70001 expected=60.700001
i=12 j=12: actual=453.89999 expected=69.900002
Lane group i=0 (j=0..3) passed correctly — confirming the bug only affects lanes beyond the first 4, exactly as expected from the hardcoded `v_extract_n(v, 0/1/2/3)` implementation.
## Experiment 2: Both tests pass after fixing the kernel
./opencv_test_core --gtest_filter='hal_intrin128.float32x4_BASELINE'
[ OK ] hal_intrin128.float32x4_BASELINE (1859 ms)
[ PASSED ] 1 test.
./opencv_test_core --gtest_filter='Core_Transform.accuracy'
[ OK ] Core_Transform.accuracy (819 ms)
[ PASSED ] 1 test.
## Experiment 3: RVV transform path remains disabled (performance regression)
After re-enabling the RVV scalable `transform_32f` path experimentally, benchmarks showed a significant regression vs the compiler-autovectorized scalar path (CV_32FC3):
Size RVV path Scalar path Ratio
640x480 7.83 ms 1.48 ms 5.3x slower
1280x720 23.96 ms 5.17 ms 4.6x slower
1920x1080 53.76 ms 10.12 ms 5.3x slower
The compiler-autovectorized path outperforms the hand-written RVV kernel for this workload, consistent with the original comment in `matmul.simd.hpp`. The `transform_32f` RVV path is therefore kept disabled in this PR. The kernel fix to `v_matmul`/`v_matmuladd` remains necessary for correctness on wide-vector hardware, and the expanded test ensures the bug cannot regress silently in future.
KleidiCV update to verison 26.03 #28744
KleidiCV release: https://gitlab.arm.com/kleidi/kleidicv/-/releases/26.03
Tuned DNN test threshold as resize linear produces slightly different result with the new KleidiCV version.
### 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
- [ ] 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