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
This commit is contained in:
Prasad Ayush Kumar
2026-08-26 15:56:18 +05:30
committed by GitHub
parent 8b006606b5
commit 524fbae162
5 changed files with 215 additions and 117 deletions

View File

@@ -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"

View File

@@ -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,

View File

@@ -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 <opencv2/core.hpp>
#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

View File

@@ -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 );

View File

@@ -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