Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fast guided filter #3623

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions modules/ximgproc/doc/ximgproc.bib
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ @incollection{Kaiming10
publisher={Springer}
}

@article{Kaiming15,
title={Fast guided filter},
author={He, Kaiming and Sun, Jian},
journal={arXiv preprint arXiv:1505.00996},
year={2015}
}

@inproceedings{Lee14,
title={Outdoor place recognition in urban environments using straight lines},
author={Lee, Jin Han and Lee, Sehyung and Zhang, Guoxuan and Lim, Jongwoo and Chung, Wan Kyun and Suh, Il Hong},
Expand Down
20 changes: 13 additions & 7 deletions modules/ximgproc/include/opencv2/ximgproc/edge_filter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ void dtFilter(InputArray guide, InputArray src, OutputArray dst, double sigmaSpa
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

/** @brief Interface for realizations of Guided Filter.
/** @brief Interface for realizations of (Fast) Guided Filter.

For more details about this filter see @cite Kaiming10 .
For more details about this filter see @cite Kaiming10 @cite Kaiming15 .
*/
class CV_EXPORTS_W GuidedFilter : public Algorithm
{
public:

/** @brief Apply Guided Filter to the filtering image.
/** @brief Apply (Fast) Guided Filter to the filtering image.

@param src filtering image with any numbers of channels.

Expand All @@ -153,11 +153,14 @@ channels then only first 3 channels will be used.
@param eps regularization term of Guided Filter. \f${eps}^2\f$ is similar to the sigma in the color
space into bilateralFilter.

For more details about Guided Filter parameters, see the original article @cite Kaiming10 .
@param scale subsample factor of Fast Guided Filter, use a scale less than 1 to speeds up computation
with almost no visible degradation. (e.g. scale==0.5 shrinks the image by 2x inside the filter)

For more details about (Fast) Guided Filter parameters, see the original articles @cite Kaiming10 @cite Kaiming15 .
*/
CV_EXPORTS_W Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps);
CV_EXPORTS_W Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps, double scale = 1.0);

/** @brief Simple one-line Guided Filter call.
/** @brief Simple one-line (Fast) Guided Filter call.

If you have multiple images to filter with the same guided image then use GuidedFilter interface to
avoid extra computations on initialization stage.
Expand All @@ -176,8 +179,11 @@ space into bilateralFilter.

@param dDepth optional depth of the output image.

@param scale subsample factor of Fast Guided Filter, use a scale less than 1 to speeds up computation
with almost no visible degradation. (e.g. scale==0.5 shrinks the image by 2x inside the filter)

@sa bilateralFilter, dtFilter, amFilter */
CV_EXPORTS_W void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth = -1);
CV_EXPORTS_W void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth = -1, double scale = 1.0);

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Expand Down
7 changes: 4 additions & 3 deletions modules/ximgproc/perf/perf_guided_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ namespace opencv_test { namespace {

CV_ENUM(GuideTypes, CV_8UC1, CV_8UC3, CV_32FC1, CV_32FC3);
CV_ENUM(SrcTypes, CV_8UC1, CV_8UC3, CV_32FC1, CV_32FC3);
typedef tuple<GuideTypes, SrcTypes, Size> GFParams;
typedef tuple<GuideTypes, SrcTypes, Size, double> GFParams;

typedef TestBaseWithParam<GFParams> GuidedFilterPerfTest;

PERF_TEST_P( GuidedFilterPerfTest, perf, Combine(GuideTypes::all(), SrcTypes::all(), Values(sz1080p, sz2K)) )
PERF_TEST_P( GuidedFilterPerfTest, perf, Combine(GuideTypes::all(), SrcTypes::all(), Values(sz1080p, sz2K), Values(1./1, 1./2, 1./3, 1./4)) )
{
RNG rng(0);

GFParams params = GetParam();
int guideType = get<0>(params);
int srcType = get<1>(params);
Size sz = get<2>(params);
double scale = get<3>(params);

Mat guide(sz, guideType);
Mat src(sz, srcType);
Expand All @@ -30,7 +31,7 @@ PERF_TEST_P( GuidedFilterPerfTest, perf, Combine(GuideTypes::all(), SrcTypes::al
{
int radius = rng.uniform(5, 30);
double eps = rng.uniform(0.1, 1e5);
guidedFilter(guide, src, dst, radius, eps);
guidedFilter(guide, src, dst, radius, eps, -1, scale);
}

SANITY_CHECK_NOTHING();
Expand Down
88 changes: 70 additions & 18 deletions modules/ximgproc/src/guided_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,21 @@ class GuidedFilterImpl : public GuidedFilter
{
public:

static Ptr<GuidedFilterImpl> create(InputArray guide, int radius, double eps);
static Ptr<GuidedFilterImpl> create(InputArray guide, int radius, double eps, double scale);

void filter(InputArray src, OutputArray dst, int dDepth = -1) CV_OVERRIDE;

protected:

int radius;
double eps;
double scale;
int h, w;
int hOriginal, wOriginal;

vector<Mat> guideCn;
vector<Mat> guideCnMean;
vector<Mat> guideCnOriginal;

SymArray2D<Mat> covarsInv;

Expand All @@ -149,7 +152,7 @@ class GuidedFilterImpl : public GuidedFilter

GuidedFilterImpl() {}

void init(InputArray guide, int radius, double eps);
void init(InputArray guide, int radius, double eps, double scale);

void computeCovGuide(SymArray2D<Mat>& covars);

Expand All @@ -167,6 +170,16 @@ class GuidedFilterImpl : public GuidedFilter
src.convertTo(dst, CV_32F);
}

inline void subsample(Mat& src, Mat& dst)
{
resize(src, dst, Size(w, h), 0, 0, INTER_LINEAR);
}

inline void upsample(Mat& src, Mat& dst)
{
resize(src, dst, Size(wOriginal, hOriginal), 0, 0, INTER_LINEAR);
}

private: /*Routines to parallelize boxFilter and convertTo*/

typedef void (GuidedFilterImpl::*TransformFunc)(Mat& src, Mat& dst);
Expand Down Expand Up @@ -203,6 +216,20 @@ class GuidedFilterImpl : public GuidedFilter
parallel_for_(pb.getRange(), pb);
}

template<typename V>
void parSubsample(V &src, V &dst)
{
GFTransform_ParBody pb(*this, src, dst, &GuidedFilterImpl::subsample);
parallel_for_(pb.getRange(), pb);
}

template<typename V>
void parUpsample(V &src, V &dst)
{
GFTransform_ParBody pb(*this, src, dst, &GuidedFilterImpl::upsample);
parallel_for_(pb.getRange(), pb);
}

private: /*Parallel body classes*/

inline void runParBody(const ParallelLoopBody& pb)
Expand Down Expand Up @@ -582,7 +609,7 @@ void GuidedFilterImpl::ApplyTransform_ParBody::operator()(const Range& range) co
{
float *_g[4];
for (int gi = 0; gi < gf.gCnNum; gi++)
_g[gi] = gf.guideCn[gi].ptr<float>(i);
_g[gi] = gf.guideCnOriginal[gi].ptr<float>(i);

float *betaDst, *g, *a;
for (int si = 0; si < srcCnNum; si++)
Expand All @@ -593,7 +620,7 @@ void GuidedFilterImpl::ApplyTransform_ParBody::operator()(const Range& range) co
a = alpha[si][gi].ptr<float>(i);
g = _g[gi];

add_mul(betaDst, a, g, gf.w);
add_mul(betaDst, a, g, gf.wOriginal);
}
}
}
Expand Down Expand Up @@ -666,28 +693,42 @@ void GuidedFilterImpl::getWalkPattern(int eid, int &cn1, int &cn2)
cn2 = wdata[6 * 2 * (gCnNum-1) + 6 + eid];
}

Ptr<GuidedFilterImpl> GuidedFilterImpl::create(InputArray guide, int radius, double eps)
Ptr<GuidedFilterImpl> GuidedFilterImpl::create(InputArray guide, int radius, double eps, double scale)
{
GuidedFilterImpl *gf = new GuidedFilterImpl();
gf->init(guide, radius, eps);
gf->init(guide, radius, eps, scale);
return Ptr<GuidedFilterImpl>(gf);
}

void GuidedFilterImpl::init(InputArray guide, int radius_, double eps_)
void GuidedFilterImpl::init(InputArray guide, int radius_, double eps_, double scale_)
{
CV_Assert( !guide.empty() && radius_ >= 0 && eps_ >= 0 );
CV_Assert( (guide.depth() == CV_32F || guide.depth() == CV_8U || guide.depth() == CV_16U) && (guide.channels() <= 3) );
CV_Assert( scale_ <= 1.0 );

radius = radius_;
eps = eps_;
scale = scale_;

splitFirstNChannels(guide, guideCn, 3);
gCnNum = (int)guideCn.size();
h = guideCn[0].rows;
w = guideCn[0].cols;
splitFirstNChannels(guide, guideCnOriginal, 3);
gCnNum = (int)guideCnOriginal.size();
hOriginal = guideCnOriginal[0].rows;
wOriginal = guideCnOriginal[0].cols;
h = int(hOriginal * scale);
w = int(wOriginal * scale);

parConvertToWorkType(guideCnOriginal, guideCnOriginal);
if (scale < 1.0)
{
guideCn.resize(gCnNum);
parSubsample(guideCnOriginal, guideCn);
}
else
{
guideCn = guideCnOriginal;
}

guideCnMean.resize(gCnNum);
parConvertToWorkType(guideCn, guideCn);
parMeanFilter(guideCn, guideCnMean);

SymArray2D<Mat> covars;
Expand All @@ -712,7 +753,7 @@ void GuidedFilterImpl::computeCovGuide(SymArray2D<Mat>& covars)
void GuidedFilterImpl::filter(InputArray src, OutputArray dst, int dDepth /*= -1*/)
{
CV_Assert( !src.empty() && (src.depth() == CV_32F || src.depth() == CV_8U) );
if (src.rows() != h || src.cols() != w)
if (src.rows() != hOriginal || src.cols() != wOriginal)
{
CV_Error(Error::StsBadSize, "Size of filtering image must be equal to size of guide image");
return;
Expand All @@ -725,6 +766,11 @@ void GuidedFilterImpl::filter(InputArray src, OutputArray dst, int dDepth /*= -1
vector<Mat>& srcCnMean = srcCn;
split(src, srcCn);

if (scale < 1.0)
{
parSubsample(srcCn, srcCn);
}

if (src.depth() != CV_32F)
{
parConvertToWorkType(srcCn, srcCn);
Expand All @@ -749,7 +795,13 @@ void GuidedFilterImpl::filter(InputArray src, OutputArray dst, int dDepth /*= -1
parMeanFilter(beta, beta);
parMeanFilter(alpha, alpha);

runParBody(ApplyTransform_ParBody(*this, alpha, beta));
if (scale < 1.0)
{
parUpsample(beta, beta);
parUpsample(alpha, alpha);
}

parallel_for_(Range(0, hOriginal), ApplyTransform_ParBody(*this, alpha, beta));
if (dDepth != CV_32F)
{
for (int i = 0; i < srcCnNum; i++)
Expand Down Expand Up @@ -782,15 +834,15 @@ void GuidedFilterImpl::computeCovGuideAndSrc(vector<Mat>& srcCn, vector<Mat>& sr
//////////////////////////////////////////////////////////////////////////

CV_EXPORTS_W
Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps)
Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps, double scale)
{
return Ptr<GuidedFilter>(GuidedFilterImpl::create(guide, radius, eps));
return Ptr<GuidedFilter>(GuidedFilterImpl::create(guide, radius, eps, scale));
}

CV_EXPORTS_W
void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth)
void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth, double scale)
{
Ptr<GuidedFilter> gf = createGuidedFilter(guide, radius, eps);
Ptr<GuidedFilter> gf = createGuidedFilter(guide, radius, eps, scale);
gf->filter(src, dst, dDepth);
}

Expand Down
50 changes: 50 additions & 0 deletions modules/ximgproc/test/test_guided_filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ static Mat convertTypeAndSize(Mat src, int dstType, Size dstSize)
return dst;
}

static double laplacianVariance(Mat src)
{
Mat laplacian;
Laplacian(src, laplacian, CV_64F);
Scalar mean, stddev;
meanStdDev(laplacian, mean, stddev);
double variance = stddev.val[0] * stddev.val[0];
return variance;
}

class GuidedFilterRefImpl : public GuidedFilter
{
int height, width, rad, chNum;
Expand Down Expand Up @@ -350,6 +360,46 @@ TEST_P(GuidedFilterTest, accuracy)
}
}

TEST_P(GuidedFilterTest, accuracyFastGuidedFilter)
{
int radius = 8;
double eps = 1;

GFParams params = GetParam();
string guideFileName = get<1>(params);
string srcFileName = get<2>(params);
int guideCnNum = 3;
int srcCnNum = get<0>(params);

Mat guide = imread(getOpenCVExtraDir() + guideFileName);
Mat src = imread(getOpenCVExtraDir() + srcFileName);
ASSERT_TRUE(!guide.empty() && !src.empty());

Size dstSize(guide.cols, guide.rows);
guide = convertTypeAndSize(guide, CV_MAKE_TYPE(guide.depth(), guideCnNum), dstSize);
src = convertTypeAndSize(src, CV_MAKE_TYPE(src.depth(), srcCnNum), dstSize);
Mat outputRef;
ximgproc::guidedFilter(guide, src, outputRef, radius, eps);

for (double scale : {1./2, 1./3, 1./4}) {
Mat outputFastGuidedFilter;
ximgproc::guidedFilter(guide, src, outputFastGuidedFilter, radius, eps, -1, scale);

Mat guideNaiveDownsampled, srcNaiveDownsampled, outputNaiveDownsampled;
resize(guide, guideNaiveDownsampled, {}, scale, scale, INTER_LINEAR);
resize(src, srcNaiveDownsampled, {}, scale, scale, INTER_LINEAR);
ximgproc::guidedFilter(guideNaiveDownsampled, srcNaiveDownsampled, outputNaiveDownsampled, radius, eps);
resize(outputNaiveDownsampled, outputNaiveDownsampled, dstSize, 0, 0, INTER_LINEAR);

double laplacianVarianceFastGuidedFilter = laplacianVariance(outputFastGuidedFilter);
double laplacianVarianceNaiveDownsampled = laplacianVariance(outputNaiveDownsampled);
EXPECT_GT(laplacianVarianceFastGuidedFilter, laplacianVarianceNaiveDownsampled);

double normL2 = cv::norm(outputFastGuidedFilter, outputRef, NORM_L2) / guide.total();
EXPECT_LE(normL2, 1.0/48.0/scale);
}
}

TEST_P(GuidedFilterTest, smallParamsIssue)
{
GFParams params = GetParam();
Expand Down