From 524fbae162ea8b6ceb08cf3bf76d2096ef60cecc Mon Sep 17 00:00:00 2001 From: Prasad Ayush Kumar <129419372+Prasadayus@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:56:18 +0530 Subject: [PATCH] Merge pull request #29410 from Prasadayus:bilateral_filter_ipp_extract Extract IPP integration as HAL function for bilateral_filter - #29410 Backport of https://github.com/opencv/opencv/pull/29409 **Performance Numbers on Intel(R) Core(TM) i9-11900K:** https://docs.google.com/spreadsheets/d/1rmNB3X_V8rWttUGBqXRs1FmkxeKR0O93x_ez5tVjutY/edit?usp=sharing ### 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 - [x] 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. - [x] The feature is well documented and sample code can be built with the project CMake --- hal/ipp/CMakeLists.txt | 1 + hal/ipp/include/ipp_hal_imgproc.hpp | 10 ++ hal/ipp/src/bilateral_filter_ipp.cpp | 162 ++++++++++++++++++ .../imgproc/src/bilateral_filter.dispatch.cpp | 130 ++------------ modules/imgproc/src/hal_replacement.hpp | 29 ++++ 5 files changed, 215 insertions(+), 117 deletions(-) create mode 100644 hal/ipp/src/bilateral_filter_ipp.cpp diff --git a/hal/ipp/CMakeLists.txt b/hal/ipp/CMakeLists.txt index aac7fd0d16..45e99ce8df 100644 --- a/hal/ipp/CMakeLists.txt +++ b/hal/ipp/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(ipphal STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/deriv_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/resize_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/box_filter_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/bilateral_filter_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/sum_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/color_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/filter_ipp.cpp" diff --git a/hal/ipp/include/ipp_hal_imgproc.hpp b/hal/ipp/include/ipp_hal_imgproc.hpp index 368285bdc2..dee149daa0 100644 --- a/hal/ipp/include/ipp_hal_imgproc.hpp +++ b/hal/ipp/include/ipp_hal_imgproc.hpp @@ -66,6 +66,16 @@ int ipp_hal_boxFilter(const uchar* src_data, size_t src_step, uchar* dst_data, s #define cv_hal_boxFilter ipp_hal_boxFilter #endif // defined(HAVE_IPP_IW) && !DISABLE_IPP_BOX_FILTER +#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 // HAVE_IPP_IW + int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, int dst_height, float* mapx, size_t mapx_step, float* mapy, size_t mapy_step, diff --git a/hal/ipp/src/bilateral_filter_ipp.cpp b/hal/ipp/src/bilateral_filter_ipp.cpp new file mode 100644 index 0000000000..fa37892546 --- /dev/null +++ b/hal/ipp/src/bilateral_filter_ipp.cpp @@ -0,0 +1,162 @@ +// 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 +#include "precomp_ipp.hpp" + +#define IPP_BILATERAL_PARALLEL 1 + +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&); +}; + +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; + + constexpr double eps = 1e-6; + if(sigma_color <= eps || sigma_space <= eps) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + 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 = IPP_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, iwDst; + iwSrc.Init(IwiSize{width, height}, ippiGetDataType(depth), cn, + inMemBorder, (void*)src_data, IwSize(src_step)); + iwDst.Init(IwiSize{width, height}, ippiGetDataType(depth), cn, + 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; + + int maxTiles = (int)iwDst.m_size.height / radius; + int numTiles = threads * 4; + if(numTiles > maxTiles) { + 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..c15544f2fa 100644 --- a/modules/imgproc/src/bilateral_filter.dispatch.cpp +++ b/modules/imgproc/src/bilateral_filter.dispatch.cpp @@ -325,120 +325,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 ) @@ -454,10 +340,20 @@ void bilateralFilter( InputArray _src, OutputArray _dst, int d, 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 ccdc838a78..919ceda0be 100644 --- a/modules/imgproc/src/hal_replacement.hpp +++ b/modules/imgproc/src/hal_replacement.hpp @@ -1204,6 +1204,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