diff --git a/modules/ximgproc/README.md b/modules/ximgproc/README.md index 59e744e0238..76933c54790 100644 --- a/modules/ximgproc/README.md +++ b/modules/ximgproc/README.md @@ -16,3 +16,4 @@ Extended Image Processing - Pei&Lin Normalization - Ridge Detection Filter - Binary morphology on run-length encoded images +- Global sampling based method for alpha matting diff --git a/modules/ximgproc/include/opencv2/ximgproc.hpp b/modules/ximgproc/include/opencv2/ximgproc.hpp index 4632f53127a..32330435b45 100644 --- a/modules/ximgproc/include/opencv2/ximgproc.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc.hpp @@ -59,6 +59,7 @@ #include "ximgproc/run_length_morphology.hpp" #include "ximgproc/edgepreserving_filter.hpp" #include "ximgproc/color_match.hpp" +#include "ximgproc/globalmatting.hpp" /** @defgroup ximgproc Extended Image Processing diff --git a/modules/ximgproc/include/opencv2/ximgproc/globalmatting.hpp b/modules/ximgproc/include/opencv2/ximgproc/globalmatting.hpp new file mode 100644 index 00000000000..10a5d795277 --- /dev/null +++ b/modules/ximgproc/include/opencv2/ximgproc/globalmatting.hpp @@ -0,0 +1,28 @@ +// 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. +#ifndef __OPENCV_XIMGPROC_GLOBAL_MATTING_HPP__ +#define __OPENCV_XIMGPROC_GLOBAL_MATTING_HPP__ + +#include +#include + + +namespace cv { namespace ximgproc { + +class CV_EXPORTS GlobalMatting +{ +public: + GlobalMatting(); + virtual ~GlobalMatting(); + + virtual void globalMatting(InputArray image, InputArray trimap, OutputArray foreground, OutputArray alpha, OutputArray conf = noArray()) = 0; + + virtual void getMat(InputArray image, InputArray trimap, OutputArray foreground, OutputArray alpha, int niter=9) = 0; +}; + +CV_EXPORTS Ptr createGlobalMatting(); + +}} // namespace + +#endif // __OPENCV_XIMGPROC_GLOBAL_MATTING_HPP__ diff --git a/modules/ximgproc/samples/globalmatting.cpp b/modules/ximgproc/samples/globalmatting.cpp new file mode 100644 index 00000000000..2f1b9cf7442 --- /dev/null +++ b/modules/ximgproc/samples/globalmatting.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +using namespace std; +using namespace cv; +using namespace ximgproc; + +int main(int argc, char** argv) +{ + if (argc < 3) + { + cout << "arg1: location of input image" << endl; + cout << "arg2: location of its trimap" << endl; + cout << "arg3(optional): number of iterations to run expansion of trimap" << endl; + return -1; + } + string img_path = argv[1]; + string tri_path = argv[2]; + int niter = 9; + if (argc == 4) + { + niter = atoi(argv[3]); + } + Mat image = imread(img_path, IMREAD_COLOR); + Mat trimap = imread(tri_path, IMREAD_GRAYSCALE); + if (image.empty() || trimap.empty()) + { + cout << "Could not load the inputs" << endl; + return -2; + } + // (optional) exploit the affinity of neighboring pixels to reduce the + // size of the unknown region. please refer to the paper + // 'Shared Sampling for Real-Time Alpha Matting'. + + Mat foreground, alpha; + + Ptr gm = createGlobalMatting(); + + gm->getMat(image, trimap, foreground, alpha, niter); + + imwrite("alpha-matte.png", alpha); + + imshow("input", image); + imshow("trimap", trimap); + imshow("alpha-matte", alpha); + waitKey(); + + return 0; +} diff --git a/modules/ximgproc/src/globalmatting.cpp b/modules/ximgproc/src/globalmatting.cpp new file mode 100644 index 00000000000..3c106fea54e --- /dev/null +++ b/modules/ximgproc/src/globalmatting.cpp @@ -0,0 +1,616 @@ +// 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. +#include "precomp.hpp" + +namespace cv { namespace ximgproc { + +using namespace std; + +template +inline T sqr(T a) +{ + return a * a; +} + +struct IntensityComp +{ + IntensityComp(const Mat_ &img_temp) : img(img_temp) + { + // nothing + } + + bool operator()(const Point &p0, const Point &p1) const + { + const Vec3b &c0 = img(p0.y, p0.x); + const Vec3b &c1 = img(p1.y, p1.x); + + return ((int)c0[0] + (int)c0[1] + (int)c0[2]) < ((int)c1[0] + (int)c1[1] + (int)c1[2]); + } + + const Mat_ &img; +}; + + + +class GlobalMattingImpl final : public GlobalMatting +{ +private: + vector findBoundaryPixels(const Mat_ &trimap, int a, int b); + + // Eq. 2 + float calculateAlpha(const Vec3b &F, const Vec3b &B, const Vec3b &I); + + // Eq. 3 + float colorCost(const Vec3b &F, const Vec3b &B, const Vec3b &I, float alpha); + + // Eq. 4 + float distCost(const Point &p0, const Point &p1, float minDist); + + float colorDist(const Vec3b &I0, const Vec3b &I1); + float nearestDistance(const vector &boundary, const Point &p); + + + void expansionOfKnownRegions(const Mat_& img, Mat_ trimap, int niter); + + void expansionOfKnownRegions(const Mat_ &image, + Mat_ &trimap, + int r, float c); + + // erode foreground and background regions to increase the size of unknown region + void erodeFB(Mat_ &trimap, int r); + + + + struct Sample + { + int fi, bj; + float df, db; + float cost, alpha; + }; + + void calculateAlphaPatchMatch(const Mat_ &image, + const Mat_ &trimap, + const vector &foregroundBoundary, + const vector &backgroundBoundary, + vector > &samples); + + void expansionOfKnownRegionsHelper(const Mat_ &image, + Mat_ &trimap, + int r, float c); + + + void globalMattingHelper(const Mat_& image, const Mat_& trimap, Mat& foreground, Mat& alpha, Mat& conf); +public: + GlobalMattingImpl() {} + ~GlobalMattingImpl() override {} + + void globalMatting(InputArray image, InputArray trimap, OutputArray foreground, OutputArray alpha, OutputArray conf) override; + + void getMat(InputArray image, InputArray trimap, OutputArray foreground, OutputArray alpha, int niter=9) override; +}; + + + +vector GlobalMattingImpl::findBoundaryPixels(const Mat_ &trimap, int a, int b) +{ + vector result; + + for (int x = 1; x < trimap.cols - 1; ++x) + for (int y = 1; y < trimap.rows - 1; ++y) + { + if (trimap(y, x) == a) + { + if (trimap(y - 1, x) == b || + trimap(y + 1, x) == b || + trimap(y, x - 1) == b || + trimap(y, x + 1) == b) + { + result.push_back(Point(x, y)); + } + } + } + + return result; +} + +// Eq. 2 +float GlobalMattingImpl::calculateAlpha(const Vec3b &F, const Vec3b &B, const Vec3b &I) +{ + float result = 0; + float div = 1e-6f; + for (int c = 0; c < 3; ++c) + { + float f = F[c]; + float b = B[c]; + float i = I[c]; + + result += (i - b) * (f - b); + div += (f - b) * (f - b); + } + + return min(max(result / div, 0.f), 1.f); +} + +// Eq. 3 +float GlobalMattingImpl::colorCost(const Vec3b &F, const Vec3b &B, const Vec3b &I, float alpha) +{ + float result = 0; + for (int c = 0; c < 3; ++c) + { + float f = F[c]; + float b = B[c]; + float i = I[c]; + + result += sqr(i - (alpha * f + (1 - alpha) * b)); + } + + return sqrt(result); +} + +// Eq. 4 +float GlobalMattingImpl::distCost(const Point &p0, const Point &p1, float minDist) +{ + int dist = normL2Sqr(p0 - p1); + return sqrt((float)dist) / minDist; +} + +float GlobalMattingImpl::colorDist(const Vec3b &I0, const Vec3b &I1) +{ + int result = 0; + + for (int c = 0; c < 3; ++c) + result += sqr((int)I0[c] - (int)I1[c]); + + return sqrt((float)result); +} + +float GlobalMattingImpl::nearestDistance(const vector &boundary, const Point &p) +{ + int minDist2 = INT_MAX; + for (size_t i = 0; i < boundary.size(); ++i) + { + int dist2 = sqr(boundary[i].x - p.x) + sqr(boundary[i].y - p.y); + minDist2 = min(minDist2, dist2); + } + + return sqrt((float)minDist2); +} + +void GlobalMattingImpl::expansionOfKnownRegions(const Mat_& img, Mat_ trimap, int niter) +{ + for (int i = 0; i < niter; ++i) + expansionOfKnownRegionsHelper(img, trimap, i + 1, float(niter - i)); + erodeFB(trimap, 2); +} + +void GlobalMattingImpl::expansionOfKnownRegions( + const Mat_ &image, + Mat_ &trimap, + int r, float c) +{ + int w = image.cols; + int h = image.rows; + + for (int y = 0; y< w; ++y) + for (int x = 0; x< h; ++x) + { + if (trimap(y, x) != 128) + continue; + + const Vec3b &I = image(y, x); + + for (int j = y-r; j <= y+r; ++j) + { + for (int i = x-r; i <= x+r; ++i) + { + if (i < 0 || i >= w || j < 0 || j >= h) + continue; + + if (trimap(j, i) != 0 && trimap(j, i) != 255) + continue; + + const Vec3b &I2 = image(j, i); + + float pd = sqrt((float)(sqr(x - i) + sqr(y - j))); + float cd = colorDist(I, I2); + + if (pd <= r && cd <= c) + { + if (trimap(j, i) == 0) + trimap(y, x) = 1; + else if (trimap(j, i) == 255) + trimap(y, x) = 254; + } + } + } + } + + for (int x = 0; x < trimap.cols; ++x) + { + for (int y = 0; y < trimap.rows; ++y) + { + if (trimap(y, x) == 1) + trimap(y, x) = 0; + else if (trimap(y, x) == 254) + trimap(y, x) = 255; + } + } +} + +// erode foreground and background regions to increase the size of unknown region +void GlobalMattingImpl::erodeFB(Mat_ &trimap, int r) +{ + int w = trimap.cols; + int h = trimap.rows; + + Mat_ foreground(trimap.size(), (uchar)0); + Mat_ background(trimap.size(), (uchar)0); + + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) + { + if (trimap(y, x) == 0) + background(y, x) = 1; + else if (trimap(y, x) == 255) + foreground(y, x) = 1; + } + + + Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(r, r)); + + // FIXIT "Inplace" filtering call is ineffective in general (involves input data copying) + erode(background, background, kernel); + erode(foreground, foreground, kernel); + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + if (background(y, x) == 0 && foreground(y, x) == 0) + trimap(y, x) = 128; + } + } +} + + +void GlobalMattingImpl::calculateAlphaPatchMatch(const Mat_ &image, + const Mat_ &trimap, + const vector &foregroundBoundary, + const vector &backgroundBoundary, + vector > &samples) +{ + int w = image.cols; + int h = image.rows; + + samples.resize(h, vector(w)); + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + if (trimap(y, x) == 128) + { + Point p(x, y); + + samples[y][x].fi = rand() % foregroundBoundary.size(); + samples[y][x].bj = rand() % backgroundBoundary.size(); + samples[y][x].df = nearestDistance(foregroundBoundary, p); + samples[y][x].db = nearestDistance(backgroundBoundary, p); + samples[y][x].cost = FLT_MAX; + } + } + } + + vector coords(w * h); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + coords[x + y * w] = Point(x, y); + } + } + + for (int iter = 0; iter < 10; ++iter) + { + // propagation + //random_shuffle(coords.begin(), coords.end()); + randShuffle(coords); + + for (size_t i = 0; i < coords.size(); ++i) + { + const Point &p = coords[i]; + + int x = p.x; + int y = p.y; + + if (trimap(y, x) != 128) + continue; + + const Vec3b &I = image(y, x); + + Sample &s = samples[y][x]; + + for (int y2 = y - 1; y2 <= y + 1; ++y2) + { + for (int x2 = x - 1; x2 <= x + 1; ++x2) + { + if (x2 < 0 || x2 >= w || y2 < 0 || y2 >= h) + continue; + + if (trimap(y2, x2) != 128) + continue; + + Sample &s2 = samples[y2][x2]; + + const Point &fp = foregroundBoundary[s2.fi]; + const Point &bp = backgroundBoundary[s2.bj]; + + const Vec3b F = image(fp.y, fp.x); + const Vec3b B = image(bp.y, bp.x); + + float alpha = calculateAlpha(F, B, I); + + float cost = colorCost(F, B, I, alpha) + distCost(p, fp, s.df) + distCost(p, bp, s.db); + + if (cost < s.cost) + { + s.fi = s2.fi; + s.bj = s2.bj; + s.cost = cost; + s.alpha = alpha; + } + } + } + } + + // random walk + int w2 = (int)max(foregroundBoundary.size(), backgroundBoundary.size()); + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + if (trimap(y, x) != 128) + continue; + + Point p(x, y); + + const Vec3b &I = image(y, x); + + Sample &s = samples[y][x]; + + for (int k = 0; ; k++) + { + float r = w2 * pow(0.5f, k); + + if (r < 1) + break; + + int di = int(r * (rand() / (RAND_MAX + 1.f))); + int dj = int(r * (rand() / (RAND_MAX + 1.f))); + + int fi = s.fi + di; + int bj = s.bj + dj; + + if (fi < 0 || (unsigned)fi >= foregroundBoundary.size() || bj < 0 || (unsigned)bj >= backgroundBoundary.size()) + continue; + + const Point &fp = foregroundBoundary[fi]; + const Point &bp = backgroundBoundary[bj]; + + const Vec3b F = image(fp.y, fp.x); + const Vec3b B = image(bp.y, bp.x); + + float alpha = calculateAlpha(F, B, I); + + float cost = colorCost(F, B, I, alpha) + distCost(p, fp, s.df) + distCost(p, bp, s.db); + + if (cost < s.cost) + { + s.fi = fi; + s.bj = bj; + s.cost = cost; + s.alpha = alpha; + } + } + } + } + } // iteration +} + +void GlobalMattingImpl::expansionOfKnownRegionsHelper( + const Mat_ &image, + Mat_ &trimap, + int r, float c) +{ + int w = image.cols; + int h = image.rows; + + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + if (trimap(y, x) != 128) + continue; + + const Vec3b &I = image(y, x); + + for (int j = y-r; j <= y+r; ++j) + { + for (int i = x-r; i <= x+r; ++i) + { + if (i < 0 || i >= w || j < 0 || j >= h) + continue; + + if (trimap(j, i) != 0 && trimap(j, i) != 255) + continue; + + const Vec3b &I2 = image(j, i); + + float pd = sqrt((float)(sqr(x - i) + sqr(y - j))); // FIXIT sqrt is not needed, compare with r*r instead. + float cd = colorDist(I, I2); + + if (pd <= r && cd <= c) + { + if (trimap(j, i) == 0) + trimap(y, x) = 1; + else if (trimap(j, i) == 255) + trimap(y, x) = 254; + } + } + } + } + } + + for (int y = 0; y < trimap.rows; ++y) + { + for (int x = 0; x < trimap.cols; ++x) + { + if (trimap(y, x) == 1) + trimap(y, x) = 0; + else if (trimap(y, x) == 254) + trimap(y, x) = 255; + } + } +} + +void GlobalMattingImpl::globalMattingHelper( + const Mat_ &image, const Mat_& trimap, + Mat &_foreground, Mat &_alpha, Mat &_conf +) +{ + vector foregroundBoundary = findBoundaryPixels(trimap, 255, 128); + vector backgroundBoundary = findBoundaryPixels(trimap, 0, 128); + + int n = (int)(foregroundBoundary.size() + backgroundBoundary.size()); + for (int i = 0; i < n; ++i) + { + int x = rand() % trimap.cols; + int y = rand() % trimap.rows; + + if (trimap(y, x) == 0) + backgroundBoundary.push_back(Point(x, y)); + else if (trimap(y, x) == 255) + foregroundBoundary.push_back(Point(x, y)); + } + + sort(foregroundBoundary.begin(), foregroundBoundary.end(), IntensityComp(image)); + sort(backgroundBoundary.begin(), backgroundBoundary.end(), IntensityComp(image)); + + vector > samples; + calculateAlphaPatchMatch(image, trimap, foregroundBoundary, backgroundBoundary, samples); + + _foreground.create(image.size(), CV_8UC3); + _alpha.create(image.size(), CV_8UC1); + _conf.create(image.size(), CV_8UC1); + + Mat_ &foreground = (Mat_&)_foreground; + Mat_ &alpha = (Mat_&)_alpha; + Mat_ &conf = (Mat_&)_conf; + + for (int y = 0; y < alpha.rows; ++y) + { + for (int x = 0; x < alpha.cols; ++x) + { + switch (trimap(y, x)) + { + case 0: + alpha(y, x) = 0; + conf(y, x) = 255; + foreground(y, x) = 0; + break; + case 128: + { + alpha(y, x) = uchar(255 * samples[y][x].alpha); + conf(y, x) = uchar(255 * exp(-samples[y][x].cost / 6)); + Point p = foregroundBoundary[samples[y][x].fi]; + foreground(y, x) = image(p.y, p.x); + break; + } + case 255: + alpha(y, x) = 255; + conf(y, x) = 255; + foreground(y, x) = image(y, x); + break; + } + } + } +} + +void GlobalMattingImpl::globalMatting(InputArray _image, InputArray _trimap, OutputArray _foreground, OutputArray _alpha, OutputArray _conf) +{ + Mat image = _image.getMat(); + Mat trimap = _trimap.getMat(); + + if (image.empty()) + CV_Error(CV_StsBadArg, "image is empty"); + + CV_CheckTypeEQ(image.type(), CV_8UC3, "image mush have CV_8UC3 type"); + + if (trimap.empty()) + CV_Error(CV_StsBadArg, "trimap is empty"); + + CV_CheckTypeEQ(trimap.type(), CV_8UC1, "trimap mush have CV_8UC1 type"); + + if (!_image.sameSize(_trimap)) + CV_Error(CV_StsBadArg, "image and trimap mush have same size"); + + const Mat_ &image_ = (const Mat_&)image; + const Mat_ &trimap_ = (const Mat_&)trimap; + + // FIXIT unsafe code, strong checks are required to call .getMatRef() + Mat &foreground = _foreground.getMatRef(); + Mat &alpha = _alpha.getMatRef(); + Mat tempConf; + + globalMattingHelper(image_, trimap_, foreground, alpha, tempConf); + + ximgproc::guidedFilter(image, alpha, alpha, 10, 1e-5); + + if (_conf.needed()) + tempConf.copyTo(_conf); +} + +void GlobalMattingImpl::getMat(InputArray image, InputArray trimap, OutputArray foreground, OutputArray alpha, int niter) +{ + Mat conf; + globalMatting(image, trimap, foreground, alpha, conf); + + CV_CheckTypeEQ(image.type(), CV_8UC3, "image mush have CV_8UC3 type"); + CV_CheckTypeEQ(trimap.type(), CV_8UC1, "trimap mush have CV_8UC1 type"); + const Mat_ &image_ = (const Mat_&)image.getMat(); + const Mat_ &trimap_ = (const Mat_&)trimap.getMat(); + expansionOfKnownRegions(image_, trimap_, niter); + + CV_Assert(alpha.sameSize(trimap)); + CV_CheckTypeEQ(alpha.type(), CV_8UC1, ""); + Mat alpha_ = alpha.getMat(); + for (int y = 0; y < trimap_.rows; ++y) + { + for (int x = 0; x < trimap_.cols; ++x) + { + if (trimap_.at(y, x) == 0) + alpha_.at(y, x) = 0; + else if (trimap_.at(y, x) == 255) + alpha_.at(y, x) = 255; + } + } +} + +GlobalMatting::GlobalMatting() +{ + // nothing +} + +GlobalMatting::~GlobalMatting() +{ + // nothing +} + +CV_EXPORTS Ptr createGlobalMatting() +{ + return makePtr(); +} + +}} // namespace diff --git a/modules/ximgproc/test/test_globalmatting.cpp b/modules/ximgproc/test/test_globalmatting.cpp new file mode 100644 index 00000000000..d8926c05ef1 --- /dev/null +++ b/modules/ximgproc/test/test_globalmatting.cpp @@ -0,0 +1,47 @@ +// 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. +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + + +const string INPUT_DIR = "cv/ximgproc/globalmatting"; + +TEST(GlobalMattingTest, accuracy) +{ + Ptr gm = createGlobalMatting(); + string img_path = cvtest::findDataFile(INPUT_DIR + "/input.png"); + string trimap_path = cvtest::findDataFile(INPUT_DIR + "/trimap.png"); + + Mat img = imread(img_path, IMREAD_COLOR); + Mat trimap = imread(trimap_path, IMREAD_GRAYSCALE); + ASSERT_FALSE(img.empty()) << "The Image could not be loaded: "<< img_path; + ASSERT_FALSE(trimap.empty()) << "The trimap could not be loaded: "<< trimap_path; + + ASSERT_EQ(img.cols, trimap.cols); + ASSERT_EQ(img.rows, trimap.rows); + + Mat foreground, alpha; + int niter = 9; + gm->getMat(img, trimap, foreground, alpha, niter); + + ASSERT_FALSE(foreground.empty()) << " Could not extract the foreground "; + ASSERT_FALSE(alpha.empty()) << " Could not generate alpha matte "; + + ASSERT_EQ(img.cols, alpha.cols); + ASSERT_EQ(img.rows, alpha.rows); + + std::string ref_alpha_path = cvtest::findDataFile(INPUT_DIR + "/ref_alpha.png"); + Mat ref_alpha = imread(trimap_path, IMREAD_GRAYSCALE); + + EXPECT_LE(cvtest::norm(ref_alpha, alpha, NORM_L2 | NORM_RELATIVE), 1e-3); // FIXIT! Result is unstable +#if 0 + imshow("alpha", alpha); + waitKey(); + imwrite("globalmatting_alpha.png", alpha); +#endif +} + + +}} // namespace diff --git a/modules/ximgproc/tutorials/globalmatting.md b/modules/ximgproc/tutorials/globalmatting.md new file mode 100644 index 00000000000..cb7937be173 --- /dev/null +++ b/modules/ximgproc/tutorials/globalmatting.md @@ -0,0 +1,31 @@ +### A Global sampling method for Alpha Matting ### + +The Global sampling based method uses all known samples(from a trimap), Unlike other sampling based methods +which collect only nearby samples.
+It first estimates the foreground and background color and uses them to compute the alpha matte. + +Input to the model is the Image and the trimap, and the output is the alpha matte of the image. + +This [blog post](https://medium.com/vedacv/paper-summary-a-global-sampling-method-for-alpha-matting-490a4217eb2) gives a summary of the paper. + +### Results ### + +After evaluating this implementation on alphamatting.com, the results are almost as good as the original implementation. + +Following were the results: + +| Error type | Original implementation | This implementation | +| ----------- | ------------------------ | ------------------- | +| Sum of absolute differences | 31 | 31.3 | +| Mean square error | 28.3 | 29.5 | +| Gradient error | 25 | 26.3 | +| Connectivity error | 28 | 36.3 | + + +Some of the outputs with of this implementation are as follows : + +| Image | Trimap | Alpha matte(this implementation) | +| -------------- | -------------- | ------------------------ | +|![alt text](images/doll-input.jpg ) |![alt text](images/doll-trimap.jpg ) |![alt text](images/doll-result.png ) | +|![alt text](images/donkey-input.jpg ) |![alt text](images/donkey-trimap.jpg ) |![alt text](images/donkey-result.png ) | +|![alt text](images/troll-input.jpg ) |![alt text](images/troll-trimap.jpg ) |![alt text](images/troll-result.png ) | diff --git a/modules/ximgproc/tutorials/images/doll-input.jpg b/modules/ximgproc/tutorials/images/doll-input.jpg new file mode 100644 index 00000000000..9c42715e57c Binary files /dev/null and b/modules/ximgproc/tutorials/images/doll-input.jpg differ diff --git a/modules/ximgproc/tutorials/images/doll-result.png b/modules/ximgproc/tutorials/images/doll-result.png new file mode 100644 index 00000000000..84a997fdeb0 Binary files /dev/null and b/modules/ximgproc/tutorials/images/doll-result.png differ diff --git a/modules/ximgproc/tutorials/images/doll-trimap.jpg b/modules/ximgproc/tutorials/images/doll-trimap.jpg new file mode 100644 index 00000000000..acb1bd08415 Binary files /dev/null and b/modules/ximgproc/tutorials/images/doll-trimap.jpg differ diff --git a/modules/ximgproc/tutorials/images/donkey-input.jpg b/modules/ximgproc/tutorials/images/donkey-input.jpg new file mode 100644 index 00000000000..2ebb6277f81 Binary files /dev/null and b/modules/ximgproc/tutorials/images/donkey-input.jpg differ diff --git a/modules/ximgproc/tutorials/images/donkey-result.png b/modules/ximgproc/tutorials/images/donkey-result.png new file mode 100644 index 00000000000..efa47275ca3 Binary files /dev/null and b/modules/ximgproc/tutorials/images/donkey-result.png differ diff --git a/modules/ximgproc/tutorials/images/donkey-trimap.jpg b/modules/ximgproc/tutorials/images/donkey-trimap.jpg new file mode 100644 index 00000000000..03d33dba3d5 Binary files /dev/null and b/modules/ximgproc/tutorials/images/donkey-trimap.jpg differ diff --git a/modules/ximgproc/tutorials/images/troll-input.jpg b/modules/ximgproc/tutorials/images/troll-input.jpg new file mode 100644 index 00000000000..d9000b8f85c Binary files /dev/null and b/modules/ximgproc/tutorials/images/troll-input.jpg differ diff --git a/modules/ximgproc/tutorials/images/troll-result.png b/modules/ximgproc/tutorials/images/troll-result.png new file mode 100644 index 00000000000..d6ad21a37a6 Binary files /dev/null and b/modules/ximgproc/tutorials/images/troll-result.png differ diff --git a/modules/ximgproc/tutorials/images/troll-trimap.jpg b/modules/ximgproc/tutorials/images/troll-trimap.jpg new file mode 100644 index 00000000000..66ca222c0ec Binary files /dev/null and b/modules/ximgproc/tutorials/images/troll-trimap.jpg differ