1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-27 22:33:03 +04:00
Files
Teddy-Yangjiale 3803eb828e Merge pull request #29440 from Teddy-Yangjiale:rvv-k1-06-filter-cov
imgproc: extend boxFilter / gaussianBlurBinomial coverage in RISC-V RVV HAL #29440

### Summary

This PR extends RVV HAL coverage of the two most frequently used smoothing filters. Previously, `cv_hal_boxFilter` was only implemented for single-channel types (plus 32FC3) at 3×3/5×5, and `cv_hal_gaussianBlurBinomial` for 8UC1/16UC1/8UC4. The most common cases in practice — interleaved **8UC3** images and **7×7** box kernels — fell back to the core `FilterEngine` path, which runs single-threaded, costing 6–19× on multi-core RISC-V hardware.

New coverage:

- `boxFilter`: **8UC3 / 8UC4** (3×3, 5×5, 7×7); **7×7** for 8UC1, 16SC1, 32FC1, 32FC3 and the 8U→16U variant
- `gaussianBlurBinomial`: **8UC3** (3×3, 5×5)

Combinations already covered upstream keep their existing kernels byte-for-byte; net diff is +274/−162 across two files.

### Implementation

1. **Channel-blind "flat" kernels** (`boxFilterFlat8U<ksize, cn>`, `gaussianBlurFlat8U<ksize, cn>`). An interleaved row is processed as `width*cn` flat u8 elements whose horizontal taps sit at strides of `cn` elements: element *i* sums exactly its own channel's taps at `i, i+cn, …, i+(ksize−1)·cn`. One template therefore serves all channel counts, and:
   - the horizontal pass becomes `ksize` independent unaligned `vle8` loads combined with widening adds — no `vlseg`/`vsseg` and no serial `vslide1down` chains, which are microcoded/serialized on current RVV hardware;
   - the vertical pass is a single contiguous u16 stream processed at LMUL=8;
   - the output is a plain `vse8` after saturating narrowing.
2. **Incremental vertical sliding window** for box with `ksize > 3`: the column-sum ring buffer keeps `ksize+1` rows so the window slides in O(1) per output row (add the entering row, subtract the leaving row) instead of re-summing `ksize` rows. u16 modular arithmetic stays exact because window sums never exceed 49·255 = 12495.
3. **Division-free normalization**: normalized box output uses multiply-high + shift (`m = ceil(2^18 / k²)`, Granlund–Montgomery; exact for divisors 9/25/49 over the full value range, verified per divisor) instead of long-latency vector integer division. Rounding is bit-identical to the previous `(sum + k²/2) / k²`.
4. **Existing kernels are extended, not rewritten**: `boxFilterC1` and the float `boxFilterC3` gain a 7×7 step in their existing unrolled style, so the 3×3/5×5 instantiations compile to the same code as before. The previous per-channel gaussian 8UC4 segment kernel is replaced by the flat kernel at measured parity, which is why the diff removes more gaussian lines than it adds.
5. Constraints respected: no hard-coded VLEN, RVV 1.0 intrinsics only, no scalable vector types in arrays.

### Performance

SpacemiT K1 (X60, rv64gcv, VLEN=256, 8×1.6 GHz), 1280×720, against a clean build of current 5.x HEAD.

**Measurement protocol:** each case is timed for **30 iterations** (after 2 warm-up runs) and the minimum is taken; the whole benchmark is executed in **2 interleaved rounds per library** (baseline/candidate alternated to cancel frequency/thermal drift, measured at up to 2× per run on this board), reporting the per-case minimum across rounds.

| Case | Generic | HAL | Speedup |
|------|--------:|----:|--------:|
| boxFilter 8UC1 7×7 | 6.539 ms | 0.330 ms | 19.8× |
| boxFilter 8UC3 3×3 | 7.325 ms | 0.994 ms | 7.4× |
| boxFilter 8UC3 5×5 | 8.118 ms | 1.142 ms | 7.1× |
| boxFilter 8UC3 7×7 | 15.930 ms | 1.328 ms | 12.0× |
| boxFilter 8UC4 3×3 | 9.552 ms | 1.466 ms | 6.5× |
| boxFilter 8UC4 5×5 | 10.775 ms | 1.841 ms | 5.9× |
| boxFilter 8UC4 7×7 | 14.268 ms | 2.112 ms | 6.8× |
| gaussianBlur 8UC3 3×3 | 2.382 ms | 1.023 ms | 2.3× |
| gaussianBlur 8UC3 5×5 | 2.746 ms | 1.684 ms | 1.6× |

**Geometric mean: ~6.1×** across the newly covered cases. Most of the box gain is the HAL's row-parallel execution (core `FilterEngine` has no internal parallelism) multiplied by the flat-layout kernels; the incremental window gives 7×7 a further ~1.9× over the plain flat version.

Regression check: the full `opencv_perf_imgproc` sweep (5610 timed cases, `--perf_min_samples=10`, one round per library) shows a geomean of 1.015× with no regressions beyond run-to-run noise — every case initially below 0.90× was re-verified with 2×2 interleaved rounds and per-case minima, and none persisted. The blur fixtures independently confirm 4.7–8.6× on 8UC4 across sizes and border types.

### Accuracy

- **Bit-exact against the core fallback**: for every newly covered combination (all 15 type/ksize pairs exercised), the HAL output was checksummed against the generic implementation on identical random inputs — all checksums match exactly. This includes the multiply-high normalization (bit-identical rounding to the previous division) and the incremental vertical window (u16 modular add/subtract cancels exactly).
- **Full accuracy suites**: `opencv_test_imgproc` filter/blur/smooth tests (`*BoxFilter*:*GaussianBlur*:*Blur*:*blur*:*Smooth*:*smooth*`) were run on K1 against a clean upstream HEAD build: **819 tests pass on both libraries, and the failure sets (pre-existing upstream failures, unrelated to this PR) are line-for-line identical**. These suites cover random anchors, all border modes, ROI offsets, and in-place operation.
- Combinations already covered upstream are unchanged by construction (same kernels, same codegen), so their accuracy behavior is inherited.

Note for reviewers: the large baseline gap also reflects that core `FilterEngine` runs single-threaded; that affects all non-HAL targets and may deserve a separate issue.


### 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
2026-07-14 12:16:46 +03:00

315 lines
8.9 KiB
C++

// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "perf_precomp.hpp"
namespace opencv_test {
typedef tuple<Size, MatType, int> Size_MatType_kSize_t;
typedef perf::TestBaseWithParam<Size_MatType_kSize_t> Size_MatType_kSize;
PERF_TEST_P(Size_MatType_kSize, medianBlur,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1),
testing::Values(3, 5)
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
int ksize = get<2>(GetParam());
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
if (CV_MAT_DEPTH(type) > CV_16S || CV_MAT_CN(type) > 1)
declare.time(15);
TEST_CYCLE() medianBlur(src, dst, ksize);
SANITY_CHECK(dst);
}
CV_ENUM(BorderType3x3, BORDER_REPLICATE, BORDER_CONSTANT)
CV_ENUM(BorderType, BORDER_REPLICATE, BORDER_CONSTANT, BORDER_REFLECT, BORDER_REFLECT101)
typedef tuple<Size, MatType, BorderType3x3> Size_MatType_BorderType3x3_t;
typedef perf::TestBaseWithParam<Size_MatType_BorderType3x3_t> Size_MatType_BorderType3x3;
typedef tuple<Size, MatType, BorderType> Size_MatType_BorderType_t;
typedef perf::TestBaseWithParam<Size_MatType_BorderType_t> Size_MatType_BorderType;
typedef tuple<Size, int, BorderType3x3> Size_ksize_BorderType_t;
typedef perf::TestBaseWithParam<Size_ksize_BorderType_t> Size_ksize_BorderType;
typedef tuple<Size, MatType, BorderType, int> Size_MatType_BorderType_ksize_t;
typedef perf::TestBaseWithParam<Size_MatType_BorderType_ksize_t> Size_MatType_BorderType_ksize;
PERF_TEST_P(Size_MatType_BorderType3x3, gaussianBlur3x3,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1),
BorderType3x3::all()
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
BorderType3x3 btype = get<2>(GetParam());
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() GaussianBlur(src, dst, Size(3,3), 0, 0, btype);
SANITY_CHECK(dst, 1);
}
PERF_TEST_P(Size_MatType_BorderType3x3, blur3x3,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1),
BorderType3x3::all()
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
BorderType3x3 btype = get<2>(GetParam());
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() blur(src, dst, Size(3,3), Point(-1,-1), btype);
SANITY_CHECK(dst, 1);
}
PERF_TEST_P(Size_MatType_BorderType, blur16x16,
testing::Combine(
testing::Values(szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1),
BorderType::all()
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
BorderType btype = get<2>(GetParam());
double eps = 1.25e-3;
eps = CV_MAT_DEPTH(type) <= CV_32S ? 1 : eps;
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() blur(src, dst, Size(16,16), Point(-1,-1), btype);
SANITY_CHECK(dst, eps);
}
PERF_TEST_P(Size_MatType_BorderType_ksize, box,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_16SC1, CV_32SC1, CV_32FC1, CV_32FC3),
BorderType::all(),
testing::Values(3, 5)
)
)
{
auto p = GetParam();
Size size = get<0>(p);
int type = get<1>(p);
BorderType btype = get<2>(p);
int ksize = get<3>(p);
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() boxFilter(src, dst, -1, Size(ksize, ksize), Point(-1,-1), false, btype);
SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE);
}
PERF_TEST_P(Size_ksize_BorderType, box_CV8U_CV16U,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(3, 5, 15),
BorderType3x3::all()
)
)
{
Size size = get<0>(GetParam());
int ksize = get<1>(GetParam());
BorderType3x3 btype = get<2>(GetParam());
Mat src(size, CV_8UC1);
Mat dst(size, CV_16UC1);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() boxFilter(src, dst, CV_16UC1, Size(ksize, ksize), Point(-1,-1), false, btype);
SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE);
}
PERF_TEST_P(Size_MatType_BorderType_ksize, box_inplace,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_16SC1, CV_32SC1, CV_32FC1, CV_32FC3),
BorderType::all(),
testing::Values(3, 5)
)
)
{
auto p = GetParam();
Size size = get<0>(p);
int type = get<1>(p);
BorderType btype = get<2>(p);
int ksize = get<3>(p);
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
while(next())
{
src.copyTo(dst);
startTimer();
boxFilter(dst, dst, -1, Size(ksize, ksize), Point(-1,-1), false, btype);
stopTimer();
}
SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE);
}
PERF_TEST_P(Size_MatType_BorderType, gaussianBlur5x5,
testing::Combine(
testing::Values(szODD, szQVGA, szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1),
BorderType::all()
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
BorderType btype = get<2>(GetParam());
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() GaussianBlur(src, dst, Size(5,5), 0, 0, btype);
SANITY_CHECK(dst, 1);
}
PERF_TEST_P(Size_MatType_BorderType, blur5x5,
testing::Combine(
testing::Values(szVGA, sz720p),
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1, CV_32FC3),
BorderType::all()
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
BorderType btype = get<2>(GetParam());
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() blur(src, dst, Size(5,5), Point(-1,-1), btype);
SANITY_CHECK(dst, 1);
}
///////////// BlendLinear ////////////////////////
PERF_TEST_P(Size_MatType, BlendLinear,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p, sz2160p),
testing::Values(CV_8UC1, CV_32FC1, CV_8UC3, CV_32FC3, CV_8UC4, CV_32FC4)
)
)
{
const Size srcSize = get<0>(GetParam());
const int srcType = get<1>(GetParam());
Mat src1(srcSize, srcType), src2(srcSize, srcType), dst(srcSize, srcType);
Mat weights1(srcSize, CV_32FC1), weights2(srcSize, CV_32FC1);
declare.in(src1, src2, WARMUP_RNG).in(weights1, weights2, WARMUP_READ).out(dst);
randu(weights1, 0, 1);
randu(weights2, 0, 1);
TEST_CYCLE() blendLinear(src1, src2, weights1, weights2, dst);
SANITY_CHECK_NOTHING();
}
///////////// Stackblur ////////////////////////
PERF_TEST_P(Size_MatType, stackblur3x3,
testing::Combine(
testing::Values(sz720p, sz1080p, sz2160p),
testing::Values(CV_8UC1, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1)
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
double eps = 1e-3;
eps = CV_MAT_DEPTH(type) <= CV_32S ? 1 : eps;
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() stackBlur(src, dst, Size(3,3));
SANITY_CHECK_NOTHING();
}
PERF_TEST_P(Size_MatType, stackblur101x101,
testing::Combine(
testing::Values(sz720p, sz1080p, sz2160p),
testing::Values(CV_8UC1, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1)
)
)
{
Size size = get<0>(GetParam());
int type = get<1>(GetParam());
double eps = 1e-3;
eps = CV_MAT_DEPTH(type) <= CV_32S ? 1 : eps;
Mat src(size, type);
Mat dst(size, type);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE() stackBlur(src, dst, Size(101,101));
SANITY_CHECK_NOTHING();
}
} // namespace