Combining 2 images with transparent mask in opencv

Viewed 6396

What I'm basically trying to do is blur an image, and combine it back with the orignal, so that only certain areas in the original image are blurred (the face should be blurred).

My general idea was to mask the parts in the original Iwant to have blurred, then blur the original as a copy and "merge" them together again.

To a certain extend this also worked.

My images:

(1) Original Original

(2) Original with parts that should be blurred Original with parts that should be blurred

(3) Blurred Blurred

My C++ code that creates these images:

int main(void) {
    cv::Mat srcImage = cv::imread(path);
    srcImage.convertTo(srcImage, CV_32FC3, 1.0/255.0);

    Mat _mask;
    Mat img_gray;

    cv::Scalar white = cv::Scalar(255, 255, 255);
    cv::Scalar black = cv::Scalar(0, 0, 0);

    cv::cvtColor(srcImage, img_gray, cv::COLOR_BGR2GRAY);
    img_gray.convertTo(_mask, CV_32FC1);

    // face
    cv::circle(_mask, cv::Point(430, 350), 200, black, -1, 8, 0);

    // eyes
    cv::circle(_mask, cv::Point(502, 260), 27, white, -1, 8, 0);
    cv::circle(_mask, cv::Point(390, 260), 27, white, -1, 8, 0);

    // mouth
    cv::ellipse(_mask, cv::Point(440, 390), cv::Point(60, 25), 0, 0, 360, white, -1, 8, 0);
    cv::threshold(1.0-_mask, _mask, 0.9, 1.0, cv::THRESH_BINARY_INV);

    cv::GaussianBlur(_mask,_mask,Size(21,21),11.0);


    cv::Mat res;
    cv::Mat bg = Mat(srcImage.size(), CV_32FC3);
    bg = cv::Scalar(1.0, 1.0 ,1.0);

    vector<Mat> ch_img(3);
    vector<Mat> ch_bg(3);
    cv::split(srcImage, ch_img);
    cv::split(bg, ch_bg);

    ch_img[0] = ch_img[0].mul(_mask) + ch_bg[0].mul(1.0 - _mask);
    ch_img[1] = ch_img[1].mul(_mask) + ch_bg[1].mul(1.0 - _mask);
    ch_img[2] = ch_img[2].mul(_mask) + ch_bg[2].mul(1.0 - _mask);

    cv::merge(ch_img, res);
    cv::merge(ch_bg, bg);

    // original but with white mask
    res.convertTo(res, CV_8UC3, 255.0);
    imwrite("original_with_mask.jpg", res);


    // blur original image
    cv::Mat blurredImage;
    bilateralFilter(srcImage, blurredImage, 10, 20, 5);
    GaussianBlur(srcImage, blurredImage, Size(19, 19), 0, 0);

    blurredImage.convertTo(blurredImage, CV_8UC3, 255.0);
    imwrite("blurred.jpg", blurredImage);

    cv::Mat maskedImage;
    maskedImage = Mat(srcImage.size(), CV_32FC3);

    // now combine blurred image and original using mask
    // this fails
    cv::bitwise_and(blurredImage, _mask, maskedImage);
    cv::imwrite("masked.jpg", maskedImage);
}

My problem is that cv::bitwise_and(blurredImage, _mask, maskedImage); fails with

OpenCV Error: Sizes of input arguments do not match (The operation is neither 'array op array' (where arrays have the same size and type), nor 'array op scalar', nor 'scalar op array') in binary_op

Probably because _mask is a single channel image and blurredImage and maskedImage are 3-channel images.

How can I combine the images I got so that the currently white areas in image (2) are blurred using a transparent mask with "soft" edges?

3 Answers

I define a function to do alphaBlend for two images of CV_8UC3 with a mask of CV_8UC1 in OpenCV:

//! 2018.01.16 13:54:39 CST
//! 2018.01.16 14:43:26 CST
void alphaBlend(Mat& img1, Mat&img2, Mat& mask, Mat& blended){
    // Blend img1 and img2 (of CV_8UC3) with mask (CV_8UC1)
    assert(img1.size() == img2.size() && img1.size() == mask.size());
    blended = cv::Mat(img1.size(), img1.type());
    for (int y = 0; y < blended.rows; ++y){
        for (int x = 0; x < blended.cols; ++x){
            float alpha = mask.at<unsigned char>(y, x)/255.0f;
            blended.at<cv::Vec3b>(y,x) = alpha*img1.at<cv::Vec3b>(y,x) + (1-alpha)*img2.at<cv::Vec3b>(y,x);
        }
    }
}

Then, it's easy to do alpha bend on the images, just call alphaBlend(...). Here is an example:


#include <opencv2/opencv.hpp>
using namespace cv;

//! 2018.01.16 13:54:39 CST
//! 2018.01.16 14:43:26 CST
void alphaBlend(Mat& img1, Mat&img2, Mat& mask, Mat& blended){
    // Blend img1 and img2 (of CV_8UC3) with mask (CV_8UC1)
    assert(img1.size() == img2.size() && img1.size() == mask.size());
    blended = cv::Mat(img1.size(), img1.type());
    for (int y = 0; y < blended.rows; ++y){
        for (int x = 0; x < blended.cols; ++x){
            float alpha = mask.at<unsigned char>(y, x)/255.0f;
            blended.at<cv::Vec3b>(y,x) = alpha*img1.at<cv::Vec3b>(y,x) + (1-alpha)*img2.at<cv::Vec3b>(y,x);
        }
    }
}

Mat createMask(Size sz){
    // create mask
    cv::Mat mask = cv::Mat::zeros(sz, CV_8UC1);
    // white and black
    cv::Scalar white = cv::Scalar(255, 255, 255);
    cv::Scalar black = cv::Scalar(0, 0, 0);
    // face
    cv::circle(mask, cv::Point(430, 350), 200, black, -1, 8, 0);
    // eyes
    cv::circle(mask, cv::Point(502, 260), 27, white, -1, 8, 0);
    cv::circle(mask, cv::Point(390, 260), 27, white, -1, 8, 0);

    // mouth
    cv::ellipse(mask, cv::Point(440, 390), cv::Point(60, 25), 0, 0, 360, white, -1, 8, 0);

    // Blur
    cv::GaussianBlur(mask, mask, cv::Size(21, 21), 11.0);
    return mask;
}

int main(){
    cv::Mat img = cv::imread("img04.jpg");
    //  blur whole image
    cv::Mat blured;
    //cv::bilateralFilter(img, blured, 10, 20, 5); // use EITHER bilateral OR Gaússian filter
    cv::GaussianBlur(img, blured, cv::Size(19, 19), 0, 0);

    // Create the mask
    Mat mask = createMask(img.size());
    Mat mask_inv = 255 - mask;

    // Alpha blend
    Mat blended1, blended2;
    alphaBlend(img, blured, mask, blended1);
    alphaBlend(img, blured, mask_inv, blended2);

    // Display
    cv::imshow("source", img);
    cv::imshow("blured", blured);
    cv::imshow("mask", mask);
    cv::imshow("mask_inv", mask_inv);
    cv::imshow("blended1", blended1);
    cv::imshow("blended2", blended2);
    cv::waitKey();
    return 0;
}

Source:

enter image description here

Blured:

enter image description here

Mask1:

enter image description here

AlphaBlend 1:

enter image description here

Mask 2:

enter image description here

AlphaBlend 2:

enter image description here



Some useful links:

  1. Alpha Blending in OpenCV C++ : Combining 2 images with transparent mask in opencv

  2. Alpha Blending in OpenCV Python: Gradient mask blending in opencv python

Related