Merge pull request #29947 from Xlawy:opt/rvv-pyrlk-tracker

video:rvv: optimize PyrLK tracker with RVV - #29947

### Summary

Add an RVV path to `LKTrackerInvoker` for patch interpolation and
covariance matrix accumulation.

### Implementation

Vectorize bilinear interpolation of image pixels and derivatives, and
accumulate A11, A12 and A22 using RVV widening operations. Reuse
temporary vectors and keep the main computation at `e16,m1` and
`e32/f32,m2` to limit vector register pressure.

### Functional Testing

```bash
./bin/opencv_test_video \
    --gtest_filter='Video_OpticalFlowPyrLK.accuracy' \
    --test_threads=1

./bin/opencv_test_video \
    --gtest_filter='Video_OpticalFlowPyrLK.submat' \
    --test_threads=1
```

Correctness was verified with:

- `Video_OpticalFlowPyrLK.accuracy`
- `Video_OpticalFlowPyrLK.submat`

### Performance Testing

```bash
OPENCV_FOR_THREADS_NUM=1 \
./bin/opencv_perf_video \
    --gtest_filter='*OpticalFlowPyrLK_self*' \
    --perf_threads=1
```

Performance was measured with `OpticalFlowPyrLK_self` using a single
thread on an RVV 1.0 system with VLEN=256.

All 96 benchmark cases improved with no regression. The sum of
per-case median execution times decreased from 409.89 ms to 332.33 ms,
a reduction of 18.9%. The best case decreased from 8.41 ms to 5.50 ms,
corresponding to a 34.6% reduction in execution time (1.53x speedup).

Median execution time (ms):

| Metric | Baseline | RVV | Time reduction |
|---|---:|---:|---:|
| Sum of per-case medians across all 96 cases | 409.89 | 332.33 | 18.9% |
| Best case | 8.41 | 5.50 | 34.6% |

### Co-authors

- Yang Wang <yangwang@iscas.ac.cn>
- Yuansheng <yuansheng@iscas.ac.cn>

### Pull Request Readiness Checklist

- [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
This commit is contained in:
zhangjinhan
2026-09-15 10:05:01 +03:00
committed by GitHub
parent 03ae9eac50
commit 28ba2e2883
+104
View File
@@ -210,6 +210,12 @@ void cv::detail::LKTrackerInvoker::operator()(const Range& range) const
const int32x4_t q11 = vdupq_n_s32((int32_t)shifter1);
const int32x4_t q12 = vdupq_n_s32((int32_t)shifter2);
#elif CV_RVV
const size_t rvv_lanes_A = __riscv_vsetvlmax_e16m1();
vfloat32m2_t rvvA11 = __riscv_vfmv_v_f_f32m2(0.f, rvv_lanes_A);
vfloat32m2_t rvvA12 = __riscv_vfmv_v_f_f32m2(0.f, rvv_lanes_A);
vfloat32m2_t rvvA22 = __riscv_vfmv_v_f_f32m2(0.f, rvv_lanes_A);
#endif
// extract the patch from the first image, compute covariation matrix of derivatives
@@ -378,6 +384,96 @@ void cv::detail::LKTrackerInvoker::operator()(const Range& range) const
d8d12.val[0] = d8; d8d12.val[1] = d12;
vst2_s16(dIptr, d8d12);
}
#elif CV_RVV
for( ; x < winSize.width*cn; )
{
const size_t vl = __riscv_vsetvl_e16m1(winSize.width*cn - x);
// Bilinear interpolation of the source image. Keep only one widened
// pixel vector live at a time to limit vector register pressure.
vuint16m1_t pix = __riscv_vwcvtu_x_x_v_u16m1(
__riscv_vle8_v_u8mf2(src + x, vl), vl);
vint32m2_t interp = __riscv_vwmul_vx_i32m2(
__riscv_vreinterpret_i16m1(pix), (int16_t)iw00, vl);
pix = __riscv_vwcvtu_x_x_v_u16m1(
__riscv_vle8_v_u8mf2(src + x + cn, vl), vl);
interp = __riscv_vwmacc_vx_i32m2(
interp, (int16_t)iw01, __riscv_vreinterpret_i16m1(pix), vl);
pix = __riscv_vwcvtu_x_x_v_u16m1(
__riscv_vle8_v_u8mf2(src + x + stepI, vl), vl);
interp = __riscv_vwmacc_vx_i32m2(
interp, (int16_t)iw10, __riscv_vreinterpret_i16m1(pix), vl);
pix = __riscv_vwcvtu_x_x_v_u16m1(
__riscv_vle8_v_u8mf2(src + x + stepI + cn, vl), vl);
interp = __riscv_vwmacc_vx_i32m2(
interp, (int16_t)iw11, __riscv_vreinterpret_i16m1(pix), vl);
interp = __riscv_vsra_vx_i32m2(
__riscv_vadd_vx_i32m2(
interp, 1 << (W_BITS1 - 5 - 1), vl),
W_BITS1 - 5, vl);
__riscv_vse16_v_i16m1(
Iptr + x, __riscv_vncvt_x_x_w_i16m1(interp, vl), vl);
// Interpolate Ix/Iy together. m1 inputs widen to m2 accumulators;
// reusing the segment-load temporaries keeps the live register set small.
vint16m1x2_t grad = __riscv_vlseg2e16_v_i16m1x2(dsrc, vl);
vint16m1_t gx = __riscv_vget_v_i16m1x2_i16m1(grad, 0);
vint16m1_t gy = __riscv_vget_v_i16m1x2_i16m1(grad, 1);
vint32m2_t ix = __riscv_vwmul_vx_i32m2(gx, (int16_t)iw00, vl);
vint32m2_t iy = __riscv_vwmul_vx_i32m2(gy, (int16_t)iw00, vl);
grad = __riscv_vlseg2e16_v_i16m1x2(dsrc + cn2, vl);
gx = __riscv_vget_v_i16m1x2_i16m1(grad, 0);
gy = __riscv_vget_v_i16m1x2_i16m1(grad, 1);
ix = __riscv_vwmacc_vx_i32m2(ix, (int16_t)iw01, gx, vl);
iy = __riscv_vwmacc_vx_i32m2(iy, (int16_t)iw01, gy, vl);
grad = __riscv_vlseg2e16_v_i16m1x2(dsrc + dstep, vl);
gx = __riscv_vget_v_i16m1x2_i16m1(grad, 0);
gy = __riscv_vget_v_i16m1x2_i16m1(grad, 1);
ix = __riscv_vwmacc_vx_i32m2(ix, (int16_t)iw10, gx, vl);
iy = __riscv_vwmacc_vx_i32m2(iy, (int16_t)iw10, gy, vl);
grad = __riscv_vlseg2e16_v_i16m1x2(dsrc + dstep + cn2, vl);
gx = __riscv_vget_v_i16m1x2_i16m1(grad, 0);
gy = __riscv_vget_v_i16m1x2_i16m1(grad, 1);
ix = __riscv_vwmacc_vx_i32m2(ix, (int16_t)iw11, gx, vl);
iy = __riscv_vwmacc_vx_i32m2(iy, (int16_t)iw11, gy, vl);
ix = __riscv_vsra_vx_i32m2(
__riscv_vadd_vx_i32m2(ix, 1 << (W_BITS1 - 1), vl),
W_BITS1, vl);
iy = __riscv_vsra_vx_i32m2(
__riscv_vadd_vx_i32m2(iy, 1 << (W_BITS1 - 1), vl),
W_BITS1, vl);
{
vint16m1_t ix16 = __riscv_vncvt_x_x_w_i16m1(ix, vl);
vint16m1_t iy16 = __riscv_vncvt_x_x_w_i16m1(iy, vl);
vint16m1x2_t out =
__riscv_vset_v_i16m1_i16m1x2(grad, 0, ix16);
out = __riscv_vset_v_i16m1_i16m1x2(out, 1, iy16);
__riscv_vsseg2e16_v_i16m1x2(dIptr, out, vl);
}
vint32m2_t prod = __riscv_vmul_vv_i32m2(ix, ix, vl);
rvvA11 = __riscv_vfadd_tu(
rvvA11, rvvA11, __riscv_vfcvt_f_x_v_f32m2(prod, vl), vl);
prod = __riscv_vmul_vv_i32m2(ix, iy, vl);
rvvA12 = __riscv_vfadd_tu(
rvvA12, rvvA12, __riscv_vfcvt_f_x_v_f32m2(prod, vl), vl);
prod = __riscv_vmul_vv_i32m2(iy, iy, vl);
rvvA22 = __riscv_vfadd_tu(
rvvA22, rvvA22, __riscv_vfcvt_f_x_v_f32m2(prod, vl), vl);
x += (int)vl;
dsrc += vl*2;
dIptr += vl*2;
}
#endif
for( ; x < winSize.width*cn; x++, dsrc += 2, dIptr += 2 )
@@ -409,6 +505,14 @@ void cv::detail::LKTrackerInvoker::operator()(const Range& range) const
iA11 += nA11[0] + nA11[1] + nA11[2] + nA11[3];
iA12 += nA12[0] + nA12[1] + nA12[2] + nA12[3];
iA22 += nA22[0] + nA22[1] + nA22[2] + nA22[3];
#elif CV_RVV
vfloat32m1_t rvvZeroA = __riscv_vfmv_v_f_f32m1(0.f, 1);
iA11 += __riscv_vfmv_f_s_f32m1_f32(
__riscv_vfredusum_vs_f32m2_f32m1(rvvA11, rvvZeroA, rvv_lanes_A));
iA12 += __riscv_vfmv_f_s_f32m1_f32(
__riscv_vfredusum_vs_f32m2_f32m1(rvvA12, rvvZeroA, rvv_lanes_A));
iA22 += __riscv_vfmv_f_s_f32m1_f32(
__riscv_vfredusum_vs_f32m2_f32m1(rvvA22, rvvZeroA, rvv_lanes_A));
#endif
A11 = iA11*FLT_SCALE;