diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index 1c8a316b52..a8b7c6049a 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -708,33 +708,74 @@ CV_EXPORTS_W void decolor( InputArray src, OutputArray grayscale, OutputArray co //! @{ -//! seamlessClone algorithm flags -enum +//! Flags for the seamlessClone algorithm +enum SeamlessCloneFlags { - /** The power of the method is fully expressed when inserting objects with complex outlines into a new background*/ + /** + @brief Normal seamless cloning. + This method is ideal for inserting objects with complex outlines into a new background. + It preserves the original appearance and lighting of the inserted object, ensuring a natural blend. + */ NORMAL_CLONE = 1, - /** The classic method, color-based selection and alpha masking might be time consuming and often leaves an undesirable - halo. Seamless cloning, even averaged with the original image, is not effective. Mixed seamless cloning based on a loose selection proves effective.*/ - MIXED_CLONE = 2, - /** Monochrome transfer allows the user to easily replace certain features of one object by alternative features.*/ - MONOCHROME_TRANSFER = 3}; + + /** + @brief Mixed seamless cloning. + This method addresses cases where simple color-based selection or alpha masking is time-consuming + and may result in undesirable halos. By combining structure from the source and texture from the + destination, mixed seamless cloning is highly effective, even with loosely defined selections. + */ + MIXED_CLONE = 2, + + /** + @brief Monochrome transfer cloning. + This method allows users to replace specific features of an object, such as grayscale textures + or patterns, with alternative features. It is particularly useful for artistic effects or + targeted object modifications. + */ + MONOCHROME_TRANSFER = 3, + + /** + @brief Enhanced normal seamless cloning. + Similar to `NORMAL_CLONE`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + NORMAL_CLONE_WIDE = 9, + + /** + @brief Enhanced mixed seamless cloning. + Similar to `MIXED_CLONE`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + MIXED_CLONE_WIDE = 10, + + /** + @brief Enhanced monochrome transfer cloning. + Similar to `MONOCHROME_TRANSFER`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + MONOCHROME_TRANSFER_WIDE = 11 +}; /** @example samples/cpp/tutorial_code/photo/seamless_cloning/cloning_demo.cpp An example using seamlessClone function */ -/** @brief Image editing tasks concern either global changes (color/intensity corrections, filters, -deformations) or local changes concerned to a selection. Here we are interested in achieving local -changes, ones that are restricted to a region manually selected (ROI), in a seamless and effortless -manner. The extent of the changes ranges from slight distortions to complete replacement by novel -content @cite PM03 . +/** @brief Performs seamless cloning to blend a region from a source image into a destination image. +This function is designed for local image editing, allowing changes restricted to a region +(manually selected as the ROI) to be applied effortlessly and seamlessly. These changes can +range from slight distortions to complete replacement by novel content @cite PM03. -@param src Input 8-bit 3-channel image. -@param dst Input 8-bit 3-channel image. -@param mask Input 8-bit 1 or 3-channel image. -@param p Point in dst image where object is placed. -@param blend Output image with the same size and type as dst. -@param flags Cloning method that could be cv::NORMAL_CLONE, cv::MIXED_CLONE or cv::MONOCHROME_TRANSFER +@param src The source image (8-bit 3-channel), from which a region will be blended into the destination. +@param dst The destination image (8-bit 3-channel), where the src image will be blended. +@param mask A binary mask (8-bit, 1, 3, or 4-channel) specifying the region in the source image to blend. +Non-zero pixels indicate the region to be blended. If an empty Mat is provided, a mask with +all non-zero pixels is created internally. +@param p The point where the center of the src image is placed in the dst image. +@param blend The output image that stores the result of the seamless cloning. It has the same size and type as `dst`. +@param flags Flags that control the type of cloning method, can take values of `cv::SeamlessCloneFlags`. */ CV_EXPORTS_W void seamlessClone( InputArray src, InputArray dst, InputArray mask, Point p, OutputArray blend, int flags); diff --git a/modules/photo/src/seamless_cloning.cpp b/modules/photo/src/seamless_cloning.cpp index d21a3f21fd..f9bced841c 100644 --- a/modules/photo/src/seamless_cloning.cpp +++ b/modules/photo/src/seamless_cloning.cpp @@ -47,18 +47,17 @@ using namespace std; using namespace cv; -static Mat checkMask(InputArray _mask, Size size) +static Mat checkMask(InputArray mask, Size size) { - Mat mask = _mask.getMat(); Mat gray; - if (mask.channels() > 1) + if (mask.channels() == 3 || mask.channels() == 4) cvtColor(mask, gray, COLOR_BGRA2GRAY); else { if (mask.empty()) gray = Mat(size.height, size.width, CV_8UC1, Scalar(255)); else - mask.copyTo(gray); + return mask.getMat(); } return gray; @@ -68,9 +67,11 @@ void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point { CV_INSTRUMENT_REGION(); CV_Assert(!_src.empty()); + CV_Assert(!_dst.empty()); const Mat src = _src.getMat(); const Mat dest = _dst.getMat(); + Mat mask = checkMask(_mask, src.size()); dest.copyTo(_blend); Mat blend = _blend.getMat(); @@ -80,10 +81,18 @@ void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point Rect roi_s = boundingRect(mask); if (roi_s.empty()) return; - Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height); - Mat destinationROI = dest(roi_d).clone(); + int l_from_center = p.x - roi_s.width / 2; + int t_from_center = p.y - roi_s.height / 2; + if (flags >= NORMAL_CLONE_WIDE) + { + l_from_center = p.x - (mask.cols / 2 - roi_s.x); + t_from_center = p.y - (mask.rows / 2 - roi_s.y); + } + + Rect roi_d(l_from_center, t_from_center, roi_s.width, roi_s.height); + Mat destinationROI = dest(roi_d); Mat sourceROI = Mat::zeros(roi_s.height, roi_s.width, src.type()); src(roi_s).copyTo(sourceROI,mask(roi_s)); diff --git a/modules/photo/src/seamless_cloning_impl.cpp b/modules/photo/src/seamless_cloning_impl.cpp index 4b3258a1d9..377bdb8f8d 100644 --- a/modules/photo/src/seamless_cloning_impl.cpp +++ b/modules/photo/src/seamless_cloning_impl.cpp @@ -332,11 +332,13 @@ void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryM switch(flag) { case NORMAL_CLONE: + case NORMAL_CLONE_WIDE: arrayProduct(patchGradientX, binaryMaskFloat, patchGradientX); arrayProduct(patchGradientY, binaryMaskFloat, patchGradientY); break; case MIXED_CLONE: + case MIXED_CLONE_WIDE: { AutoBuffer maskIndices(n_elem_in_line); for (int i = 0; i < n_elem_in_line; ++i) @@ -373,6 +375,7 @@ void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryM break; case MONOCHROME_TRANSFER: + case MONOCHROME_TRANSFER_WIDE: Mat gray; cvtColor(patch, gray, COLOR_BGR2GRAY ); diff --git a/modules/photo/test/test_cloning.cpp b/modules/photo/test/test_cloning.cpp index e4d806a7f9..fa486b23c1 100644 --- a/modules/photo/test/test_cloning.cpp +++ b/modules/photo/test/test_cloning.cpp @@ -75,7 +75,7 @@ TEST(Photo_SeamlessClone_normal, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 1); + seamlessClone(source, destination, mask, p, result, NORMAL_CLONE); Mat reference = imread(reference_path); ASSERT_FALSE(reference.empty()) << "Could not load reference image " << reference_path; @@ -88,7 +88,7 @@ TEST(Photo_SeamlessClone_normal, regression) EXPECT_LE(errorL1, reference.total() * numerical_precision) << "size=" << reference.size(); mask = Scalar(0, 0, 0); - seamlessClone(source, destination, mask, p, result, 1); + seamlessClone(source, destination, mask, p, result, NORMAL_CLONE); reference = destination; errorINF = cvtest::norm(reference, result, NORM_INF); @@ -117,7 +117,7 @@ TEST(Photo_SeamlessClone_mixed, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 2); + seamlessClone(source, destination, mask, p, result, MIXED_CLONE); SAVE(result); @@ -150,7 +150,7 @@ TEST(Photo_SeamlessClone_featureExchange, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 3); + seamlessClone(source, destination, mask, p, result, MONOCHROME_TRANSFER); SAVE(result);