diff --git a/modules/ximgproc/doc/ximgproc.bib b/modules/ximgproc/doc/ximgproc.bib index f081f54d3ce..279bac1d115 100644 --- a/modules/ximgproc/doc/ximgproc.bib +++ b/modules/ximgproc/doc/ximgproc.bib @@ -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}, diff --git a/modules/ximgproc/include/opencv2/ximgproc/edge_filter.hpp b/modules/ximgproc/include/opencv2/ximgproc/edge_filter.hpp index 82be7c71b7f..19b05451147 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/edge_filter.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/edge_filter.hpp @@ -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. @@ -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 createGuidedFilter(InputArray guide, int radius, double eps); +CV_EXPORTS_W Ptr 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. @@ -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); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/modules/ximgproc/perf/perf_guided_filter.cpp b/modules/ximgproc/perf/perf_guided_filter.cpp index 9a0058a5505..7649acea9b3 100644 --- a/modules/ximgproc/perf/perf_guided_filter.cpp +++ b/modules/ximgproc/perf/perf_guided_filter.cpp @@ -7,11 +7,11 @@ 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 GFParams; +typedef tuple GFParams; typedef TestBaseWithParam 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); @@ -19,6 +19,7 @@ PERF_TEST_P( GuidedFilterPerfTest, perf, Combine(GuideTypes::all(), SrcTypes::al 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); @@ -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(); diff --git a/modules/ximgproc/src/guided_filter.cpp b/modules/ximgproc/src/guided_filter.cpp index 11fa0f6fcb0..9d8333aecba 100644 --- a/modules/ximgproc/src/guided_filter.cpp +++ b/modules/ximgproc/src/guided_filter.cpp @@ -128,7 +128,7 @@ class GuidedFilterImpl : public GuidedFilter { public: - static Ptr create(InputArray guide, int radius, double eps); + static Ptr create(InputArray guide, int radius, double eps, double scale); void filter(InputArray src, OutputArray dst, int dDepth = -1) CV_OVERRIDE; @@ -136,10 +136,13 @@ class GuidedFilterImpl : public GuidedFilter int radius; double eps; + double scale; int h, w; + int hOriginal, wOriginal; vector guideCn; vector guideCnMean; + vector guideCnOriginal; SymArray2D covarsInv; @@ -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& covars); @@ -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); @@ -203,6 +216,20 @@ class GuidedFilterImpl : public GuidedFilter parallel_for_(pb.getRange(), pb); } + template + void parSubsample(V &src, V &dst) + { + GFTransform_ParBody pb(*this, src, dst, &GuidedFilterImpl::subsample); + parallel_for_(pb.getRange(), pb); + } + + template + 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) @@ -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(i); + _g[gi] = gf.guideCnOriginal[gi].ptr(i); float *betaDst, *g, *a; for (int si = 0; si < srcCnNum; si++) @@ -593,7 +620,7 @@ void GuidedFilterImpl::ApplyTransform_ParBody::operator()(const Range& range) co a = alpha[si][gi].ptr(i); g = _g[gi]; - add_mul(betaDst, a, g, gf.w); + add_mul(betaDst, a, g, gf.wOriginal); } } } @@ -666,28 +693,42 @@ void GuidedFilterImpl::getWalkPattern(int eid, int &cn1, int &cn2) cn2 = wdata[6 * 2 * (gCnNum-1) + 6 + eid]; } -Ptr GuidedFilterImpl::create(InputArray guide, int radius, double eps) +Ptr 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(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 covars; @@ -712,7 +753,7 @@ void GuidedFilterImpl::computeCovGuide(SymArray2D& 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; @@ -725,6 +766,11 @@ void GuidedFilterImpl::filter(InputArray src, OutputArray dst, int dDepth /*= -1 vector& srcCnMean = srcCn; split(src, srcCn); + if (scale < 1.0) + { + parSubsample(srcCn, srcCn); + } + if (src.depth() != CV_32F) { parConvertToWorkType(srcCn, srcCn); @@ -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++) @@ -782,15 +834,15 @@ void GuidedFilterImpl::computeCovGuideAndSrc(vector& srcCn, vector& sr ////////////////////////////////////////////////////////////////////////// CV_EXPORTS_W -Ptr createGuidedFilter(InputArray guide, int radius, double eps) +Ptr createGuidedFilter(InputArray guide, int radius, double eps, double scale) { - return Ptr(GuidedFilterImpl::create(guide, radius, eps)); + return Ptr(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 gf = createGuidedFilter(guide, radius, eps); + Ptr gf = createGuidedFilter(guide, radius, eps, scale); gf->filter(src, dst, dDepth); } diff --git a/modules/ximgproc/test/test_guided_filter.cpp b/modules/ximgproc/test/test_guided_filter.cpp index b8293da65d0..c2263ecdd3b 100644 --- a/modules/ximgproc/test/test_guided_filter.cpp +++ b/modules/ximgproc/test/test_guided_filter.cpp @@ -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; @@ -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();