Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining 2 images with transparent mask in opencv

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?

like image 647
Max Avatar asked Mar 13 '23 19:03

Max


1 Answers

Instead of float conversion you can just use the linearcombination of byte channel values. See

int main(int argc, char* argv[])
{
    cv::Mat srcImage = cv::imread("C:/StackOverflow/Input/transparentMaskInput.jpg");

    //  blur whole image
    cv::Mat blurredImage;
    //cv::bilateralFilter(srcImage, blurredImage, 10, 20, 5); // use EITHER bilateral OR Gaússian filter
    cv::GaussianBlur(srcImage, blurredImage, cv::Size(19, 19), 0, 0);

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

    cv::Mat mask = cv::Mat::zeros(srcImage.size(), CV_8UC1);

    // 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::GaussianBlur(mask, mask, cv::Size(21, 21), 11.0);

    // byte inversion:
    cv::Mat invertedMask = 255 - mask; // instead of inversion you could just draw the "face" black on a white background!


    cv::Mat outputImage = cv::Mat(srcImage.size(), srcImage.type());
    // for each pixel, merge blurred and original image regarding the blur-mask

    for (int y = 0; y < outputImage.rows; ++y)
    for (int x = 0; x < outputImage.cols; ++x)
    {
        cv::Vec3b pixelOrig = srcImage.at<cv::Vec3b>(y, x);
        cv::Vec3b pixelBlur = blurredImage.at<cv::Vec3b>(y, x);
        float blurVal = invertedMask.at<unsigned char>(y, x)/255.0f; // value between 0 and 1: zero means 100% orig image, one means 100% blurry image
        cv::Vec3b pixelOut = blurVal * pixelBlur + (1.0f - blurVal)* pixelOrig;

        outputImage.at<cv::Vec3b>(y, x) = pixelOut;
    }   

    cv::imshow("input", srcImage);
    cv::imshow("blurred", blurredImage);
    cv::imshow("mask", mask);
    cv::imshow("inverted mask", invertedMask);
    cv::imshow("output", outputImage);

    return 0;
}

using this input image:

enter image description here

computing this blurred and mask:

enter image description here

enter image description here

resulting in this output, by computing (mask/255) * blur + (1-mask/255)*blur (linear combination):

enter image description here

like image 70
Micka Avatar answered Mar 23 '23 21:03

Micka