Merge pull request #29409 from Prasadayus:bilateral_filter_refactor

Refactoring and moving IPP functions to HAL for bilateral_filter in Imgproc - #29409

**Performance Numbers on Intel(R) Core(TM) i9-11900K:** https://docs.google.com/spreadsheets/d/1hnH2aGmc3D88HGnvM34xczQbRZUgsKsAHorcB-DpLq4/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-29 16:43:49 +05:30
committed by GitHub
parent 67824754cb
commit 390c4fdcb9
5 changed files with 257 additions and 163 deletions

View File

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

View File

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

View File

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

View File

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

View File

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