diff --git a/hal/ipp/CMakeLists.txt b/hal/ipp/CMakeLists.txt index 531f82bfd3..5ae60bf6e2 100644 --- a/hal/ipp/CMakeLists.txt +++ b/hal/ipp/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(ipphal STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/canny_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/threshold_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/distancetransform_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/bilateral_filter_ipp.cpp" ) #TODO: HAVE_IPP_ICV and HAVE_IPP_IW added as private macro till OpenCV itself is diff --git a/hal/ipp/include/ipp_hal_imgproc.hpp b/hal/ipp/include/ipp_hal_imgproc.hpp index 65e8ee50a9..73e449bf24 100644 --- a/hal/ipp/include/ipp_hal_imgproc.hpp +++ b/hal/ipp/include/ipp_hal_imgproc.hpp @@ -86,6 +86,15 @@ int ipp_hal_filter2D(const uchar * src_data, size_t src_step, int src_type, #define cv_hal_filter_stateless ipp_hal_filter2D #endif // defined(HAVE_IPP_IW) && !DISABLE_IPP_FILTER2D +#if defined(HAVE_IPP_IW) +int ipp_hal_bilateralFilter_offset(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int d, double sigma_color, double sigma_space, int border_type); +#undef cv_hal_bilateralFilter_offset +#define cv_hal_bilateralFilter_offset ipp_hal_bilateralFilter_offset +#endif + #endif //IPP_VERSION_X100 >= 810 #if IPP_VERSION_X100 >= 700 diff --git a/hal/ipp/src/bilateral_filter_ipp.cpp b/hal/ipp/src/bilateral_filter_ipp.cpp new file mode 100644 index 0000000000..4b8b1c1f10 --- /dev/null +++ b/hal/ipp/src/bilateral_filter_ipp.cpp @@ -0,0 +1,169 @@ +// 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 +// Copyright (C) 2026, BigVision LLC, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "ipp_hal_imgproc.hpp" + +#ifdef HAVE_IPP_IW + +#include "precomp_ipp.hpp" + +#define IPP_BILATERAL_PARALLEL 1 + +namespace { + +class ipp_bilateralFilterParallel : public cv::ParallelLoopBody +{ +public: + ipp_bilateralFilterParallel(::ipp::IwiImage &_src, ::ipp::IwiImage &_dst, int _radius, Ipp32f _valSquareSigma, Ipp32f _posSquareSigma, ::ipp::IwiBorderType _borderType, bool *_ok): + src(_src), dst(_dst) + { + pOk = _ok; + + radius = _radius; + valSquareSigma = _valSquareSigma; + posSquareSigma = _posSquareSigma; + borderType = _borderType; + + *pOk = true; + } + ~ipp_bilateralFilterParallel() {} + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + if(*pOk == false) + return; + + try + { + ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, dst.m_size.width, range.end - range.start); + CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBilateral, src, dst, radius, valSquareSigma, posSquareSigma, ::ipp::IwDefault(), borderType, tile); + } + catch(const ::ipp::IwException &) + { + *pOk = false; + return; + } + } +private: + ::ipp::IwiImage &src; + ::ipp::IwiImage &dst; + + int radius; + Ipp32f valSquareSigma; + Ipp32f posSquareSigma; + ::ipp::IwiBorderType borderType; + + bool *pOk; + const ipp_bilateralFilterParallel& operator= (const ipp_bilateralFilterParallel&); +}; + +} // namespace + +int ipp_hal_bilateralFilter_offset(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int d, double sigma_color, double sigma_space, int border_type) +{ + CV_HAL_CHECK_USE_IPP(); + + if(!((depth == CV_8U || depth == CV_32F) && (cn == 1 || cn == 3))) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + // Map cv border to IPP; include BORDER_REFLECT_101 (the default) which the shared + // plugin ippiGetBorderType does not cover. + int bt = border_type & ~cv::BORDER_ISOLATED; + IppiBorderType ippBorderType = bt == cv::BORDER_CONSTANT ? ippBorderConst : + bt == cv::BORDER_REPLICATE ? ippBorderRepl : + bt == cv::BORDER_REFLECT_101 ? ippBorderMirror : + bt == cv::BORDER_TRANSPARENT ? ippBorderTransp : + (IppiBorderType)-1; + if((int)ippBorderType == -1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + int radius = std::max((d <= 0) ? cvRound(sigma_space*1.5) : d/2, 1); + Ipp32f valSquareSigma = (Ipp32f)(sigma_color*sigma_color); + Ipp32f posSquareSigma = (Ipp32f)(sigma_space*sigma_space); + + IwSize marginLeft = margin_left; + IwSize marginTop = margin_top; + IwSize marginRight = margin_right; + IwSize marginBottom = margin_bottom; + + try + { + ::ipp::IwiBorderSize inMemBorder(marginLeft, marginTop, marginRight, marginBottom); + + ::ipp::IwiImage iwSrc; + iwSrc.Init(IwiSize{width, height}, ippiGetDataType(depth), cn, inMemBorder, (void*)src_data, IwSize(src_step)); + ::ipp::IwiImage iwDst; + iwDst.Init(IwiSize{width, height}, ippiGetDataType(depth), cn, ::ipp::IwiBorderSize(), dst_data, IwSize(dst_step)); + + // already have physical border + int inMemFlags = 0; + if(!(border_type & cv::BORDER_ISOLATED)) + { + if(marginLeft) + { + if(marginLeft >= radius) inMemFlags |= ippBorderInMemLeft; + else return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + if(marginTop) + { + if(marginTop >= radius) inMemFlags |= ippBorderInMemTop; + else return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + if(marginRight) + { + if(marginRight >= radius) inMemFlags |= ippBorderInMemRight; + else return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + if(marginBottom) + { + if(marginBottom >= radius) inMemFlags |= ippBorderInMemBottom; + else return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + } + + ::ipp::IwiBorderType ippBorder((IppiBorderType)(ippBorderType | inMemFlags)); + + const int threads = ippiSuggestThreadsNum(iwDst, 2); + if(IPP_BILATERAL_PARALLEL && threads > 1) + { + bool ok = true; + cv::Range range(0, (int)iwDst.m_size.height); + ipp_bilateralFilterParallel invoker(iwSrc, iwDst, radius, valSquareSigma, posSquareSigma, ippBorder, &ok); + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + // Tile height can't be smaller than the radius. + // Otherwise, the second tile has mixed top border (pixels from both + // inmem and outside should be used), which is not supported in IPP. + int maxTiles = (int)iwDst.m_size.height / radius; + int numTiles = threads * 4; + if(numTiles > maxTiles) + { + // Keep the tiles number as multiple of threads for the better workload balance. + numTiles = (maxTiles / threads) * threads; + } + cv::parallel_for_(range, invoker, numTiles); + + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + else + { + CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBilateral, iwSrc, iwDst, radius, valSquareSigma, posSquareSigma, ::ipp::IwDefault(), ippBorder); + } + } + catch(const ::ipp::IwException &) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + return CV_HAL_ERROR_OK; +} + +#endif diff --git a/modules/imgproc/src/bilateral_filter.dispatch.cpp b/modules/imgproc/src/bilateral_filter.dispatch.cpp index da19e1e394..42a8284dae 100644 --- a/modules/imgproc/src/bilateral_filter.dispatch.cpp +++ b/modules/imgproc/src/bilateral_filter.dispatch.cpp @@ -58,6 +58,12 @@ namespace cv { +static inline int bilateralRadius(int d, double sigmaSpace) +{ + int radius = (d <= 0) ? cvRound(sigmaSpace * 1.5) : d / 2; + return std::max(radius, 1); +} + #ifdef HAVE_OPENCL static bool ocl_bilateralFilter_8u(InputArray _src, OutputArray _dst, int d, @@ -76,21 +82,10 @@ static bool ocl_bilateralFilter_8u(InputArray _src, OutputArray _dst, int d, if (depth != CV_8U || cn > 4) return false; - constexpr double eps = 1e-6; - if( sigma_color <= eps || sigma_space <= eps ) - { - _src.copyTo(_dst); - return true; - } - double gauss_color_coeff = -0.5 / (sigma_color * sigma_color); double gauss_space_coeff = -0.5 / (sigma_space * sigma_space); - if ( d <= 0 ) - radius = cvRound(sigma_space * 1.5); - else - radius = d / 2; - radius = MAX(radius, 1); + radius = bilateralRadius(d, sigma_space); d = radius * 2 + 1; UMat src = _src.getUMat(), dst = _dst.getUMat(), temp; @@ -168,21 +163,10 @@ bilateralFilter_8u( const Mat& src, Mat& dst, int d, CV_Assert( (src.type() == CV_8UC1 || src.type() == CV_8UC3) && src.data != dst.data ); - constexpr double eps = 1e-6; - if( sigma_color <= eps || sigma_space <= eps ) - { - src.copyTo(dst); - return; - } - float gauss_color_coeff = (float)(-0.5/(sigma_color*sigma_color)); float gauss_space_coeff = (float)(-0.5/(sigma_space*sigma_space)); - if( d <= 0 ) - radius = cvRound(sigma_space*1.5); - else - radius = d/2; - radius = MAX(radius, 1); + radius = bilateralRadius(d, sigma_space); d = radius*2 + 1; Mat temp; @@ -248,26 +232,14 @@ bilateralFilter_32f( const Mat& src, Mat& dst, int d, double minValSrc=-1, maxValSrc=1; const int kExpNumBinsPerChannel = 1 << 12; int kExpNumBins = 0; - float lastExpVal = 1.f; float len, scale_index; CV_Assert( (src.type() == CV_32FC1 || src.type() == CV_32FC3) && src.data != dst.data ); - constexpr double eps = 1e-6; - if( sigma_color <= eps || sigma_space <= eps ) - { - src.copyTo(dst); - return; - } - double gauss_color_coeff = -0.5/(sigma_color*sigma_color); double gauss_space_coeff = -0.5/(sigma_space*sigma_space); - if( d <= 0 ) - radius = cvRound(sigma_space*1.5); - else - radius = d/2; - radius = MAX(radius, 1); + radius = bilateralRadius(d, sigma_space); d = radius*2 + 1; // compute the min/max range for the input image (even if multichannel) @@ -297,16 +269,27 @@ bilateralFilter_32f( const Mat& src, Mat& dst, int d, scale_index = kExpNumBins/len; // initialize the exp LUT - for( i = 0; i < kExpNumBins+2; i++ ) + i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + int nlanes = VTraits::vlanes(); + v_float32 v_scale_index = vx_setall_f32((float)scale_index); + v_float32 v_gauss_color_coeff = vx_setall_f32((float)gauss_color_coeff); + float counter[16] = {0., 1., 2., 3., 4., 5., 6., 7., + 8., 9., 10., 11., 12., 13., 14., 15.}; + v_float32 v_i = vx_load(counter); + v_float32 v_inc = vx_setall_f32(float(nlanes)); + + for( ; i < (kExpNumBins+2) - nlanes; i += nlanes ) { - if( lastExpVal > 0.f ) - { - double val = i / scale_index; - expLUT[i] = (float)std::exp(val * val * gauss_color_coeff); - lastExpVal = expLUT[i]; - } - else - expLUT[i] = 0.f; + v_float32 v_val = v_div(v_i, v_scale_index); + v_store(expLUT + i, v_exp(v_mul(v_mul(v_val, v_val), v_gauss_color_coeff))); + v_i = v_add(v_i, v_inc); + } +#endif + for( ; i < kExpNumBins+2; i++ ) + { + double val = i / scale_index; + expLUT[i] = (float)std::exp(val * val * gauss_color_coeff); } // initialize space-related bilateral filter coefficients @@ -325,120 +308,6 @@ bilateralFilter_32f( const Mat& src, Mat& dst, int d, CV_CPU_DISPATCH_MODES_ALL); } -#ifdef HAVE_IPP -#define IPP_BILATERAL_PARALLEL 1 - -#ifdef HAVE_IPP_IW -class ipp_bilateralFilterParallel: public ParallelLoopBody -{ -public: - ipp_bilateralFilterParallel(::ipp::IwiImage &_src, ::ipp::IwiImage &_dst, int _radius, Ipp32f _valSquareSigma, Ipp32f _posSquareSigma, ::ipp::IwiBorderType _borderType, bool *_ok): - src(_src), dst(_dst) - { - pOk = _ok; - - radius = _radius; - valSquareSigma = _valSquareSigma; - posSquareSigma = _posSquareSigma; - borderType = _borderType; - - *pOk = true; - } - ~ipp_bilateralFilterParallel() {} - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - if(*pOk == false) - return; - - try - { - ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, dst.m_size.width, range.end - range.start); - CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBilateral, src, dst, radius, valSquareSigma, posSquareSigma, ::ipp::IwDefault(), borderType, tile); - } - catch(const ::ipp::IwException &) - { - *pOk = false; - return; - } - } -private: - ::ipp::IwiImage &src; - ::ipp::IwiImage &dst; - - int radius; - Ipp32f valSquareSigma; - Ipp32f posSquareSigma; - ::ipp::IwiBorderType borderType; - - bool *pOk; - const ipp_bilateralFilterParallel& operator= (const ipp_bilateralFilterParallel&); -}; -#endif - -static bool ipp_bilateralFilter(Mat &src, Mat &dst, int d, double sigmaColor, double sigmaSpace, int borderType) -{ -#ifdef HAVE_IPP_IW - CV_INSTRUMENT_REGION_IPP(); - - constexpr double eps = 1e-6; - if( sigmaColor <= eps || sigmaSpace <= eps ) - { - src.copyTo(dst); - return true; - } - - int radius = IPP_MAX(((d <= 0)?cvRound(sigmaSpace*1.5):d/2), 1); - Ipp32f valSquareSigma = (Ipp32f)(sigmaColor*sigmaColor); - Ipp32f posSquareSigma = (Ipp32f)(sigmaSpace*sigmaSpace); - - // Acquire data and begin processing - try - { - ::ipp::IwiImage iwSrc = ippiGetImage(src); - ::ipp::IwiImage iwDst = ippiGetImage(dst); - ::ipp::IwiBorderSize borderSize(radius); - ::ipp::IwiBorderType ippBorder(ippiGetBorder(iwSrc, borderType, borderSize)); - if(!ippBorder) - return false; - - const int threads = ippiSuggestThreadsNum(iwDst, 2); - if(IPP_BILATERAL_PARALLEL && threads > 1) { - bool ok = true; - Range range(0, (int)iwDst.m_size.height); - ipp_bilateralFilterParallel invoker(iwSrc, iwDst, radius, valSquareSigma, posSquareSigma, ippBorder, &ok); - if(!ok) - return false; - - // Tile height can't be smaller than the radius. - // Otherwise, the second tile has mixed top border (pixels from both - // inmem and outside should be used), which is not supported in IPP. - int maxTiles = (int)iwDst.m_size.height / radius; - int numTiles = threads * 4; - if (numTiles > maxTiles) { - // Keep the tiles number as multiple of threads for the better workload balance. - numTiles = (maxTiles / threads) * threads; - } - parallel_for_(range, invoker, numTiles); - - if(!ok) - return false; - } else { - CV_INSTRUMENT_FUN_IPP(::ipp::iwiFilterBilateral, iwSrc, iwDst, radius, valSquareSigma, posSquareSigma, ::ipp::IwDefault(), ippBorder); - } - } - catch (const ::ipp::IwException &) - { - return false; - } - return true; -#else - CV_UNUSED(src); CV_UNUSED(dst); CV_UNUSED(d); CV_UNUSED(sigmaColor); CV_UNUSED(sigmaSpace); CV_UNUSED(borderType); - return false; -#endif -} -#endif - void bilateralFilter( InputArray _src, OutputArray _dst, int d, double sigmaColor, double sigmaSpace, int borderType ) @@ -449,15 +318,32 @@ void bilateralFilter( InputArray _src, OutputArray _dst, int d, _dst.create( _src.size(), _src.type() ); + constexpr double eps = 1e-6; + if( sigmaColor <= eps || sigmaSpace <= eps ) + { + _src.copyTo(_dst); + return; + } + CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(), ocl_bilateralFilter_8u(_src, _dst, d, sigmaColor, sigmaSpace, borderType)) Mat src = _src.getMat(), dst = _dst.getMat(); - CALL_HAL(bilateralFilter, cv_hal_bilateralFilter, src.data, src.step, dst.data, dst.step, src.cols, src.rows, src.depth(), - src.channels(), d, sigmaColor, sigmaSpace, borderType); + { + Point ofs; + Size wsz(src.cols, src.rows); + if( !(borderType & BORDER_ISOLATED) ) + src.locateROI( wsz, ofs ); - CV_IPP_RUN_FAST(ipp_bilateralFilter(src, dst, d, sigmaColor, sigmaSpace, borderType)); + CALL_HAL(bilateralFilter, cv_hal_bilateralFilter_offset, src.data, src.step, dst.data, dst.step, + src.cols, src.rows, src.depth(), src.channels(), + ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, + d, sigmaColor, sigmaSpace, borderType & (~BORDER_ISOLATED)); + } + + CALL_HAL(bilateralFilter, cv_hal_bilateralFilter, src.data, src.step, dst.data, dst.step, + src.cols, src.rows, src.depth(), src.channels(), d, sigmaColor, sigmaSpace, borderType); if( src.depth() == CV_8U ) bilateralFilter_8u( src, dst, d, sigmaColor, sigmaSpace, borderType ); diff --git a/modules/imgproc/src/hal_replacement.hpp b/modules/imgproc/src/hal_replacement.hpp index 454360e0c6..b962877563 100644 --- a/modules/imgproc/src/hal_replacement.hpp +++ b/modules/imgproc/src/hal_replacement.hpp @@ -1182,6 +1182,35 @@ inline int hal_ni_bilateralFilter(const uchar* src_data, size_t src_step, uchar* #define cv_hal_bilateralFilter hal_ni_bilateralFilter //! @endcond +/** + @brief Calculate bilateral filter for input tile with optional margins for submatrix. See https://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html + @param src_data Source image data + @param src_step Source image step + @param dst_data Destination image data + @param dst_step Destination image step + @param width Source image width + @param height Source image height + @param depth Depths of source and destination image. Should support CV_8U and CV_32F + @param cn Number of channels + @param margin_left Left margins for source image + @param margin_top Top margins for source image + @param margin_right Right margins for source image + @param margin_bottom Bottom margins for source image + @param d Diameter of each pixel neighborhood that is used during filtering. If it is non-positive, it is computed from sigmaSpace + @param sigma_color Filter sigma in the color space + @param sigma_space Filter sigma in the coordinate space. When d>0, it specifies the neighborhood size regardless of sigmaSpace. Otherwise, d is proportional to sigmaSpace + @param border_type border mode used to extrapolate pixels outside of the image +*/ +inline int hal_ni_bilateralFilter_offset(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int depth, int cn, + int margin_left, int margin_top, int margin_right, int margin_bottom, + int d, double sigma_color, double sigma_space, int border_type) +{ return CV_HAL_ERROR_NOT_IMPLEMENTED; } + +//! @cond IGNORED +#define cv_hal_bilateralFilter_offset hal_ni_bilateralFilter_offset +//! @endcond + /** @brief Calculates adaptive threshold @param src_data Source image data