Skip to content

Commit

Permalink
implement fast guided filter
Browse files Browse the repository at this point in the history
  • Loading branch information
w43322 committed Feb 11, 2024
1 parent 9d733ef commit b603a10
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 28 deletions.
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

0 comments on commit b603a10

Please sign in to comment.