Skip to content

Commit 345371e

Browse files
authored
Merge pull request #3623 from w43322:fast-guided-filter
Implement fast guided filter
2 parents 9358ad2 + b603a10 commit 345371e

File tree

5 files changed

+144
-28
lines changed

5 files changed

+144
-28
lines changed

modules/ximgproc/doc/ximgproc.bib

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ @incollection{Kaiming10
5454
publisher={Springer}
5555
}
5656

57+
@article{Kaiming15,
58+
title={Fast guided filter},
59+
author={He, Kaiming and Sun, Jian},
60+
journal={arXiv preprint arXiv:1505.00996},
61+
year={2015}
62+
}
63+
5764
@inproceedings{Lee14,
5865
title={Outdoor place recognition in urban environments using straight lines},
5966
author={Lee, Jin Han and Lee, Sehyung and Zhang, Guoxuan and Lim, Jongwoo and Chung, Wan Kyun and Suh, Il Hong},

modules/ximgproc/include/opencv2/ximgproc/edge_filter.hpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,15 @@ void dtFilter(InputArray guide, InputArray src, OutputArray dst, double sigmaSpa
123123
//////////////////////////////////////////////////////////////////////////
124124
//////////////////////////////////////////////////////////////////////////
125125

126-
/** @brief Interface for realizations of Guided Filter.
126+
/** @brief Interface for realizations of (Fast) Guided Filter.
127127
128-
For more details about this filter see @cite Kaiming10 .
128+
For more details about this filter see @cite Kaiming10 @cite Kaiming15 .
129129
*/
130130
class CV_EXPORTS_W GuidedFilter : public Algorithm
131131
{
132132
public:
133133

134-
/** @brief Apply Guided Filter to the filtering image.
134+
/** @brief Apply (Fast) Guided Filter to the filtering image.
135135
136136
@param src filtering image with any numbers of channels.
137137
@@ -153,11 +153,14 @@ channels then only first 3 channels will be used.
153153
@param eps regularization term of Guided Filter. \f${eps}^2\f$ is similar to the sigma in the color
154154
space into bilateralFilter.
155155
156-
For more details about Guided Filter parameters, see the original article @cite Kaiming10 .
156+
@param scale subsample factor of Fast Guided Filter, use a scale less than 1 to speeds up computation
157+
with almost no visible degradation. (e.g. scale==0.5 shrinks the image by 2x inside the filter)
158+
159+
For more details about (Fast) Guided Filter parameters, see the original articles @cite Kaiming10 @cite Kaiming15 .
157160
*/
158-
CV_EXPORTS_W Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps);
161+
CV_EXPORTS_W Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps, double scale = 1.0);
159162

160-
/** @brief Simple one-line Guided Filter call.
163+
/** @brief Simple one-line (Fast) Guided Filter call.
161164
162165
If you have multiple images to filter with the same guided image then use GuidedFilter interface to
163166
avoid extra computations on initialization stage.
@@ -176,8 +179,11 @@ space into bilateralFilter.
176179
177180
@param dDepth optional depth of the output image.
178181
182+
@param scale subsample factor of Fast Guided Filter, use a scale less than 1 to speeds up computation
183+
with almost no visible degradation. (e.g. scale==0.5 shrinks the image by 2x inside the filter)
184+
179185
@sa bilateralFilter, dtFilter, amFilter */
180-
CV_EXPORTS_W void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth = -1);
186+
CV_EXPORTS_W void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth = -1, double scale = 1.0);
181187

182188
//////////////////////////////////////////////////////////////////////////
183189
//////////////////////////////////////////////////////////////////////////

modules/ximgproc/perf/perf_guided_filter.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ namespace opencv_test { namespace {
77

88
CV_ENUM(GuideTypes, CV_8UC1, CV_8UC3, CV_32FC1, CV_32FC3);
99
CV_ENUM(SrcTypes, CV_8UC1, CV_8UC3, CV_32FC1, CV_32FC3);
10-
typedef tuple<GuideTypes, SrcTypes, Size> GFParams;
10+
typedef tuple<GuideTypes, SrcTypes, Size, double> GFParams;
1111

1212
typedef TestBaseWithParam<GFParams> GuidedFilterPerfTest;
1313

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

1818
GFParams params = GetParam();
1919
int guideType = get<0>(params);
2020
int srcType = get<1>(params);
2121
Size sz = get<2>(params);
22+
double scale = get<3>(params);
2223

2324
Mat guide(sz, guideType);
2425
Mat src(sz, srcType);
@@ -30,7 +31,7 @@ PERF_TEST_P( GuidedFilterPerfTest, perf, Combine(GuideTypes::all(), SrcTypes::al
3031
{
3132
int radius = rng.uniform(5, 30);
3233
double eps = rng.uniform(0.1, 1e5);
33-
guidedFilter(guide, src, dst, radius, eps);
34+
guidedFilter(guide, src, dst, radius, eps, -1, scale);
3435
}
3536

3637
SANITY_CHECK_NOTHING();

modules/ximgproc/src/guided_filter.cpp

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,21 @@ class GuidedFilterImpl : public GuidedFilter
128128
{
129129
public:
130130

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

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

135135
protected:
136136

137137
int radius;
138138
double eps;
139+
double scale;
139140
int h, w;
141+
int hOriginal, wOriginal;
140142

141143
vector<Mat> guideCn;
142144
vector<Mat> guideCnMean;
145+
vector<Mat> guideCnOriginal;
143146

144147
SymArray2D<Mat> covarsInv;
145148

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

150153
GuidedFilterImpl() {}
151154

152-
void init(InputArray guide, int radius, double eps);
155+
void init(InputArray guide, int radius, double eps, double scale);
153156

154157
void computeCovGuide(SymArray2D<Mat>& covars);
155158

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

173+
inline void subsample(Mat& src, Mat& dst)
174+
{
175+
resize(src, dst, Size(w, h), 0, 0, INTER_LINEAR);
176+
}
177+
178+
inline void upsample(Mat& src, Mat& dst)
179+
{
180+
resize(src, dst, Size(wOriginal, hOriginal), 0, 0, INTER_LINEAR);
181+
}
182+
170183
private: /*Routines to parallelize boxFilter and convertTo*/
171184

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

219+
template<typename V>
220+
void parSubsample(V &src, V &dst)
221+
{
222+
GFTransform_ParBody pb(*this, src, dst, &GuidedFilterImpl::subsample);
223+
parallel_for_(pb.getRange(), pb);
224+
}
225+
226+
template<typename V>
227+
void parUpsample(V &src, V &dst)
228+
{
229+
GFTransform_ParBody pb(*this, src, dst, &GuidedFilterImpl::upsample);
230+
parallel_for_(pb.getRange(), pb);
231+
}
232+
206233
private: /*Parallel body classes*/
207234

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

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

596-
add_mul(betaDst, a, g, gf.w);
623+
add_mul(betaDst, a, g, gf.wOriginal);
597624
}
598625
}
599626
}
@@ -666,28 +693,42 @@ void GuidedFilterImpl::getWalkPattern(int eid, int &cn1, int &cn2)
666693
cn2 = wdata[6 * 2 * (gCnNum-1) + 6 + eid];
667694
}
668695

669-
Ptr<GuidedFilterImpl> GuidedFilterImpl::create(InputArray guide, int radius, double eps)
696+
Ptr<GuidedFilterImpl> GuidedFilterImpl::create(InputArray guide, int radius, double eps, double scale)
670697
{
671698
GuidedFilterImpl *gf = new GuidedFilterImpl();
672-
gf->init(guide, radius, eps);
699+
gf->init(guide, radius, eps, scale);
673700
return Ptr<GuidedFilterImpl>(gf);
674701
}
675702

676-
void GuidedFilterImpl::init(InputArray guide, int radius_, double eps_)
703+
void GuidedFilterImpl::init(InputArray guide, int radius_, double eps_, double scale_)
677704
{
678705
CV_Assert( !guide.empty() && radius_ >= 0 && eps_ >= 0 );
679706
CV_Assert( (guide.depth() == CV_32F || guide.depth() == CV_8U || guide.depth() == CV_16U) && (guide.channels() <= 3) );
707+
CV_Assert( scale_ <= 1.0 );
680708

681709
radius = radius_;
682710
eps = eps_;
711+
scale = scale_;
683712

684-
splitFirstNChannels(guide, guideCn, 3);
685-
gCnNum = (int)guideCn.size();
686-
h = guideCn[0].rows;
687-
w = guideCn[0].cols;
713+
splitFirstNChannels(guide, guideCnOriginal, 3);
714+
gCnNum = (int)guideCnOriginal.size();
715+
hOriginal = guideCnOriginal[0].rows;
716+
wOriginal = guideCnOriginal[0].cols;
717+
h = int(hOriginal * scale);
718+
w = int(wOriginal * scale);
719+
720+
parConvertToWorkType(guideCnOriginal, guideCnOriginal);
721+
if (scale < 1.0)
722+
{
723+
guideCn.resize(gCnNum);
724+
parSubsample(guideCnOriginal, guideCn);
725+
}
726+
else
727+
{
728+
guideCn = guideCnOriginal;
729+
}
688730

689731
guideCnMean.resize(gCnNum);
690-
parConvertToWorkType(guideCn, guideCn);
691732
parMeanFilter(guideCn, guideCnMean);
692733

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

769+
if (scale < 1.0)
770+
{
771+
parSubsample(srcCn, srcCn);
772+
}
773+
728774
if (src.depth() != CV_32F)
729775
{
730776
parConvertToWorkType(srcCn, srcCn);
@@ -749,7 +795,13 @@ void GuidedFilterImpl::filter(InputArray src, OutputArray dst, int dDepth /*= -1
749795
parMeanFilter(beta, beta);
750796
parMeanFilter(alpha, alpha);
751797

752-
runParBody(ApplyTransform_ParBody(*this, alpha, beta));
798+
if (scale < 1.0)
799+
{
800+
parUpsample(beta, beta);
801+
parUpsample(alpha, alpha);
802+
}
803+
804+
parallel_for_(Range(0, hOriginal), ApplyTransform_ParBody(*this, alpha, beta));
753805
if (dDepth != CV_32F)
754806
{
755807
for (int i = 0; i < srcCnNum; i++)
@@ -782,15 +834,15 @@ void GuidedFilterImpl::computeCovGuideAndSrc(vector<Mat>& srcCn, vector<Mat>& sr
782834
//////////////////////////////////////////////////////////////////////////
783835

784836
CV_EXPORTS_W
785-
Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps)
837+
Ptr<GuidedFilter> createGuidedFilter(InputArray guide, int radius, double eps, double scale)
786838
{
787-
return Ptr<GuidedFilter>(GuidedFilterImpl::create(guide, radius, eps));
839+
return Ptr<GuidedFilter>(GuidedFilterImpl::create(guide, radius, eps, scale));
788840
}
789841

790842
CV_EXPORTS_W
791-
void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth)
843+
void guidedFilter(InputArray guide, InputArray src, OutputArray dst, int radius, double eps, int dDepth, double scale)
792844
{
793-
Ptr<GuidedFilter> gf = createGuidedFilter(guide, radius, eps);
845+
Ptr<GuidedFilter> gf = createGuidedFilter(guide, radius, eps, scale);
794846
gf->filter(src, dst, dDepth);
795847
}
796848

modules/ximgproc/test/test_guided_filter.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ static Mat convertTypeAndSize(Mat src, int dstType, Size dstSize)
6464
return dst;
6565
}
6666

67+
static double laplacianVariance(Mat src)
68+
{
69+
Mat laplacian;
70+
Laplacian(src, laplacian, CV_64F);
71+
Scalar mean, stddev;
72+
meanStdDev(laplacian, mean, stddev);
73+
double variance = stddev.val[0] * stddev.val[0];
74+
return variance;
75+
}
76+
6777
class GuidedFilterRefImpl : public GuidedFilter
6878
{
6979
int height, width, rad, chNum;
@@ -350,6 +360,46 @@ TEST_P(GuidedFilterTest, accuracy)
350360
}
351361
}
352362

363+
TEST_P(GuidedFilterTest, accuracyFastGuidedFilter)
364+
{
365+
int radius = 8;
366+
double eps = 1;
367+
368+
GFParams params = GetParam();
369+
string guideFileName = get<1>(params);
370+
string srcFileName = get<2>(params);
371+
int guideCnNum = 3;
372+
int srcCnNum = get<0>(params);
373+
374+
Mat guide = imread(getOpenCVExtraDir() + guideFileName);
375+
Mat src = imread(getOpenCVExtraDir() + srcFileName);
376+
ASSERT_TRUE(!guide.empty() && !src.empty());
377+
378+
Size dstSize(guide.cols, guide.rows);
379+
guide = convertTypeAndSize(guide, CV_MAKE_TYPE(guide.depth(), guideCnNum), dstSize);
380+
src = convertTypeAndSize(src, CV_MAKE_TYPE(src.depth(), srcCnNum), dstSize);
381+
Mat outputRef;
382+
ximgproc::guidedFilter(guide, src, outputRef, radius, eps);
383+
384+
for (double scale : {1./2, 1./3, 1./4}) {
385+
Mat outputFastGuidedFilter;
386+
ximgproc::guidedFilter(guide, src, outputFastGuidedFilter, radius, eps, -1, scale);
387+
388+
Mat guideNaiveDownsampled, srcNaiveDownsampled, outputNaiveDownsampled;
389+
resize(guide, guideNaiveDownsampled, {}, scale, scale, INTER_LINEAR);
390+
resize(src, srcNaiveDownsampled, {}, scale, scale, INTER_LINEAR);
391+
ximgproc::guidedFilter(guideNaiveDownsampled, srcNaiveDownsampled, outputNaiveDownsampled, radius, eps);
392+
resize(outputNaiveDownsampled, outputNaiveDownsampled, dstSize, 0, 0, INTER_LINEAR);
393+
394+
double laplacianVarianceFastGuidedFilter = laplacianVariance(outputFastGuidedFilter);
395+
double laplacianVarianceNaiveDownsampled = laplacianVariance(outputNaiveDownsampled);
396+
EXPECT_GT(laplacianVarianceFastGuidedFilter, laplacianVarianceNaiveDownsampled);
397+
398+
double normL2 = cv::norm(outputFastGuidedFilter, outputRef, NORM_L2) / guide.total();
399+
EXPECT_LE(normL2, 1.0/48.0/scale);
400+
}
401+
}
402+
353403
TEST_P(GuidedFilterTest, smallParamsIssue)
354404
{
355405
GFParams params = GetParam();

0 commit comments

Comments
 (0)