1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 07:13:02 +04:00

Merge pull request #26941 from GenshinImpactStarts:lut_hal_rvv

Impl hal_rvv LUT | Add more LUT test #26941 

Implement through the existing `cv_hal_lut` interfaces.

Add more LUT accuracy and performance tests:
- **Accuracy test**: Multi-channel table tests are added, and the boundary of `randu` used for generating test data is broadened to make the test more robust.
- **Performance test**: Multi-channel input and multi-channel table tests are added.

Perf test done on
- MUSE-PI (vlen=256)
- Compiler: gcc 14.2 (riscv-collab/riscv-gnu-toolchain Nightly: December 16, 2024)


```sh

$ opencv_test_core --gtest_filter="Core_LUT*"
$ opencv_perf_core --gtest_filter="SizePrm_LUT*" --perf_min_samples=300 --perf_force_samples=300
```
```sh
Geometric mean (ms)

         Name of Test          scalar   ui    rvv       ui        rvv    
                                                        vs         vs    
                                                      scalar     scalar  
                                                    (x-factor) (x-factor)
LUT::SizePrm::320x240          0.248  0.249  0.052     1.00       4.74   
LUT::SizePrm::640x480          0.277  0.275  0.085     1.01       3.28   
LUT::SizePrm::1920x1080        0.950  0.947  0.634     1.00       1.50   
LUT_multi2::SizePrm::320x240   2.051  2.045  2.049     1.00       1.00   
LUT_multi2::SizePrm::640x480   2.128  2.134  2.125     1.00       1.00   
LUT_multi2::SizePrm::1920x1080 7.397  7.380  7.390     1.00       1.00   
LUT_multi::SizePrm::320x240    0.715  0.747  0.154     0.96       4.64   
LUT_multi::SizePrm::640x480    0.741  0.766  0.257     0.97       2.88   
LUT_multi::SizePrm::1920x1080  2.766  2.765  1.925     1.00       1.44  
```

This optimization is achieved by loading the entire lookup table into vector registers. Due to register size limitations, the optimization is only effective under the following conditions:  
- For the U8C1 table type, the optimization works when `vlen >= 256`
- For U16C1, it works when `vlen >= 512`
- For U32C1, it works when `vlen >= 1024`

Since I don’t have real hardware with `vlen > 256`, the corresponding accuracy tests were conducted on QEMU built from the `riscv-collab/riscv-gnu-toolchain`.

This patch does not implement optimizations for multi-channel tables.

Previous attempts:
1. For the U8C1 table type, when `vlen = 128`, it is possible to use four `u8m4` vectors to load the entire table, perform gathering, and merge the results. However, the performance is almost the same as the scalar version.
2. Loading part of the table and repeatedly loading the source data is faster for small sizes. But as the table size grows, the performance quickly degrades compared to the scalar version.
3. Using `vluxei8` as a general solution does not show any performance improvement.

### 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
This commit is contained in:
GenshinImpactStarts
2025-03-06 16:17:00 +08:00
committed by GitHub
parent dbd4e4549d
commit 57a78cb9df
4 changed files with 279 additions and 18 deletions
+1
View File
@@ -30,6 +30,7 @@
#include "hal_rvv_1p0/atan.hpp" // core
#include "hal_rvv_1p0/split.hpp" // core
#include "hal_rvv_1p0/flip.hpp" // core
#include "hal_rvv_1p0/lut.hpp" // core
#include "hal_rvv_1p0/pyramids.hpp" // imgproc
#endif
+218
View File
@@ -0,0 +1,218 @@
// 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.
#pragma once
#include <riscv_vector.h>
#include <opencv2/core/base.hpp>
#include <opencv2/core/utility.hpp>
namespace cv { namespace cv_hal_rvv {
#undef cv_hal_lut
#define cv_hal_lut cv::cv_hal_rvv::lut
// need vlen >= 256
struct LUTCacheU8
{
using ElemType = uchar;
constexpr static size_t elem_size = sizeof(ElemType);
static inline vuint8m8_t loadTable(const ElemType* lut_data)
{
return __riscv_vle8_v_u8m8(lut_data, 256);
}
static inline size_t setvl(size_t len)
{
return __riscv_vsetvl_e8m8(len);
}
static inline size_t setvlmax()
{
return __riscv_vsetvlmax_e8m8();
}
static inline void lut(const uchar* src, vuint8m8_t lut_v, ElemType* dst, size_t vl)
{
auto src_v = __riscv_vle8_v_u8m8(src, vl);
auto dst_v = __riscv_vrgather(lut_v, src_v, vl);
__riscv_vse8(dst, dst_v, vl);
}
};
// need vlen >= 512
struct LUTCacheU16
{
using ElemType = uint16_t;
constexpr static size_t elem_size = sizeof(ElemType);
static inline vuint16m8_t loadTable(const ElemType* lut_data)
{
return __riscv_vle16_v_u16m8(lut_data, 256);
}
static inline size_t setvl(size_t len)
{
return __riscv_vsetvl_e16m8(len);
}
static inline size_t setvlmax()
{
return __riscv_vsetvlmax_e16m8();
}
static inline void lut(const uchar* src, vuint16m8_t lut_v, ElemType* dst, size_t vl)
{
auto src_v = __riscv_vzext_vf2(__riscv_vle8_v_u8m4(src, vl), vl);
auto dst_v = __riscv_vrgather(lut_v, src_v, vl);
__riscv_vse16(dst, dst_v, vl);
}
};
// need vlen >= 1024
struct LUTCacheU32
{
using ElemType = uint32_t;
constexpr static size_t elem_size = sizeof(ElemType);
static inline vuint32m8_t loadTable(const ElemType* lut_data)
{
return __riscv_vle32_v_u32m8(lut_data, 256);
}
static inline size_t setvl(size_t len)
{
return __riscv_vsetvl_e32m8(len);
}
static inline size_t setvlmax()
{
return __riscv_vsetvlmax_e32m8();
}
static inline void lut(const uchar* src, vuint32m8_t lut_v, ElemType* dst, size_t vl)
{
auto src_v = __riscv_vzext_vf2(__riscv_vle8_v_u8m2(src, vl), vl);
auto dst_v = __riscv_vrgatherei16(lut_v, src_v, vl);
__riscv_vse32(dst, dst_v, vl);
}
};
template <typename LUT_TYPE>
class LUTParallelBody : public cv::ParallelLoopBody
{
using ElemType = typename LUT_TYPE::ElemType;
public:
const uchar* src_data;
const uchar* lut_data;
uchar* dst_data;
size_t src_step;
size_t dst_step;
size_t width;
LUTParallelBody(const uchar* src_data,
size_t src_step,
const uchar* lut_data,
uchar* dst_data,
size_t dst_step,
size_t width) :
src_data(src_data), lut_data(lut_data), dst_data(dst_data), src_step(src_step),
dst_step(dst_step), width(width)
{
}
void operator()(const cv::Range& range) const CV_OVERRIDE
{
auto src = src_data + range.start * src_step;
auto dst = dst_data + range.start * dst_step;
size_t h = range.size();
size_t w = width;
if (w == src_step && w * LUT_TYPE::elem_size == dst_step)
{
w = w * h;
h = 1;
}
auto lut = LUT_TYPE::loadTable((ElemType*)lut_data);
size_t maxlv = LUT_TYPE::setvlmax();
for (; h; h--, src += src_step, dst += dst_step)
{
size_t vl = maxlv;
size_t l = w;
auto s = src;
auto d = (ElemType*)dst;
for (; l >= vl; l -= vl, s += vl, d += vl)
{
LUT_TYPE::lut(s, lut, d, vl);
}
for (; l > 0; l -= vl, s += vl, d += vl)
{
vl = LUT_TYPE::setvl(l);
LUT_TYPE::lut(s, lut, d, vl);
}
}
}
private:
LUTParallelBody(const LUTParallelBody&);
LUTParallelBody& operator=(const LUTParallelBody&);
};
inline int lut(const uchar* src_data,
size_t src_step,
size_t src_type,
const uchar* lut_data,
size_t lut_channel_size,
size_t lut_channels,
uchar* dst_data,
size_t dst_step,
int width,
int height)
{
if (width <= 0 || height <= 0)
return CV_HAL_ERROR_NOT_IMPLEMENTED;
size_t w = width;
size_t h = height;
size_t vlen = __riscv_vsetvlmax_e8m8();
if (lut_channels == 1)
{
w *= CV_MAT_CN(src_type);
// Actually, vlen 128 can use four u8m4 vectors to load the whole table, gather and merge
// the result, but the performance is almost the same as the scalar.
if (lut_channel_size == 1 && vlen >= 256)
{
LUTParallelBody<LUTCacheU8> body(src_data, src_step, lut_data, dst_data, dst_step, w);
Range all(0, height);
if (w * h >= (1 << 18))
cv::parallel_for_(all, body);
else
body(all);
return CV_HAL_ERROR_OK;
}
else if (lut_channel_size == 2 && vlen >= 512)
{
LUTParallelBody<LUTCacheU16> body(src_data, src_step, lut_data, dst_data, dst_step, w);
Range all(0, height);
if (w * h >= (1 << 18))
cv::parallel_for_(all, body);
else
body(all);
return CV_HAL_ERROR_OK;
}
else if (lut_channel_size == 4 && vlen >= 1024)
{
LUTParallelBody<LUTCacheU32> body(src_data, src_step, lut_data, dst_data, dst_step, w);
Range all(0, height);
if (w * h >= (1 << 18))
cv::parallel_for_(all, body);
else
body(all);
return CV_HAL_ERROR_OK;
}
}
return CV_HAL_ERROR_NOT_IMPLEMENTED;
}
}} // namespace cv::cv_hal_rvv
+19
View File
@@ -24,4 +24,23 @@ PERF_TEST_P( SizePrm, LUT,
SANITY_CHECK(dst, 0.1);
}
PERF_TEST_P( SizePrm, LUT_multi,
testing::Values(szQVGA, szVGA, sz1080p)
)
{
Size sz = GetParam();
int maxValue = 255;
Mat src(sz, CV_8UC3);
randu(src, 0, maxValue);
Mat lut(1, 256, CV_8UC1);
randu(lut, 0, maxValue);
Mat dst(sz, CV_8UC3);
TEST_CYCLE() LUT(src, lut, dst);
SANITY_CHECK_NOTHING();
}
}} // namespace
+41 -18
View File
@@ -3225,10 +3225,10 @@ CV_ENUM(LutMatType, CV_8U, CV_16U, CV_16F, CV_32S, CV_32F, CV_64F)
struct Core_LUT: public testing::TestWithParam<LutMatType>
{
template<typename T, int ch>
template<typename T, int ch, bool same_cn>
cv::Mat referenceWithType(cv::Mat input, cv::Mat table)
{
cv::Mat ref(input.size(), CV_MAKE_TYPE(table.type(), ch));
cv::Mat ref(input.size(), CV_MAKE_TYPE(table.depth(), ch));
for (int i = 0; i < input.rows; i++)
{
for (int j = 0; j < input.cols; j++)
@@ -3242,7 +3242,14 @@ struct Core_LUT: public testing::TestWithParam<LutMatType>
Vec<T, ch> val;
for (int k = 0; k < ch; k++)
{
val[k] = table.at<T>(input.at<Vec<uchar, ch>>(i, j)[k]);
if (same_cn)
{
val[k] = table.at<Vec<T, ch>>(input.at<Vec<uchar, ch>>(i, j)[k])[k];
}
else
{
val[k] = table.at<T>(input.at<Vec<uchar, ch>>(i, j)[k]);
}
}
ref.at<Vec<T, ch>>(i, j) = val;
}
@@ -3251,32 +3258,32 @@ struct Core_LUT: public testing::TestWithParam<LutMatType>
return ref;
}
template<int ch = 1>
template<int ch = 1, bool same_cn = false>
cv::Mat reference(cv::Mat input, cv::Mat table)
{
if (table.type() == CV_8U)
if (table.depth() == CV_8U)
{
return referenceWithType<uchar, ch>(input, table);
return referenceWithType<uchar, ch, same_cn>(input, table);
}
else if (table.type() == CV_16U)
else if (table.depth() == CV_16U)
{
return referenceWithType<ushort, ch>(input, table);
return referenceWithType<ushort, ch, same_cn>(input, table);
}
else if (table.type() == CV_16F)
else if (table.depth() == CV_16F)
{
return referenceWithType<ushort, ch>(input, table);
return referenceWithType<ushort, ch, same_cn>(input, table);
}
else if (table.type() == CV_32S)
else if (table.depth() == CV_32S)
{
return referenceWithType<int, ch>(input, table);
return referenceWithType<int, ch, same_cn>(input, table);
}
else if (table.type() == CV_32F)
else if (table.depth() == CV_32F)
{
return referenceWithType<float, ch>(input, table);
return referenceWithType<float, ch, same_cn>(input, table);
}
else if (table.type() == CV_64F)
else if (table.depth() == CV_64F)
{
return referenceWithType<double, ch>(input, table);
return referenceWithType<double, ch, same_cn>(input, table);
}
return cv::Mat();
@@ -3290,7 +3297,7 @@ TEST_P(Core_LUT, accuracy)
randu(input, 0, 256);
cv::Mat table(1, 256, CV_MAKE_TYPE(type, 1));
randu(table, 0, 127);
randu(table, 0, getMaxVal(type));
cv::Mat output;
cv::LUT(input, table, output);
@@ -3307,7 +3314,7 @@ TEST_P(Core_LUT, accuracy_multi)
randu(input, 0, 256);
cv::Mat table(1, 256, CV_MAKE_TYPE(type, 1));
randu(table, 0, 127);
randu(table, 0, getMaxVal(type));
cv::Mat output;
cv::LUT(input, table, output);
@@ -3317,6 +3324,22 @@ TEST_P(Core_LUT, accuracy_multi)
ASSERT_EQ(0, cv::norm(output, gt, cv::NORM_INF));
}
TEST_P(Core_LUT, accuracy_multi2)
{
int type = (int)GetParam();
cv::Mat input(117, 113, CV_8UC3);
randu(input, 0, 256);
cv::Mat table(1, 256, CV_MAKE_TYPE(type, 3));
randu(table, 0, getMaxVal(type));
cv::Mat output;
cv::LUT(input, table, output);
cv::Mat gt = reference<3, true>(input, table);
ASSERT_EQ(0, cv::norm(output, gt, cv::NORM_INF));
}
INSTANTIATE_TEST_CASE_P(/**/, Core_LUT, LutMatType::all());