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;