在OpenCV中使用透明掩码合并2个图像

4
我想做的基本上是对图像进行模糊处理,然后将其与原图融合,以便只有原始图像中的特定区域会受到模糊处理(即面部应该被模糊处理)。
我的主要想法是遮盖我想要模糊处理的原始部分,然后将原始副本模糊处理并“合并”它们。
在一定程度上,这也起作用了。
我的图片:
(1)原始图像 原始图像 (2)包含需要模糊处理的部分的原始图像 包含需要模糊处理的部分的原始图像 (3)模糊图像 模糊图像 创建这些图像的我的C++代码:
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);
}

我的问题是,cv::bitwise_and(blurredImage, _mask, maskedImage);在执行时出现了失败。

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

可能是因为_mask是一张单通道图像,而blurredImagemaskedImage都是三通道图像。

我该如何合并这些图像,使得图像(2)中的当前白色区域使用带有“柔和”边缘的透明蒙版进行模糊处理?


你的口罩应该是二进制的,还是渐变的(例如,越不白越不模糊)? - Micka
@Micka已经褪色了,至少这是我试图实现的。在另一个例子中使用二进制掩模时它起作用了,但是使用这个褪色掩模时我遇到了各种问题,可能是因为我对图像处理的理解非常基础。 - Max
我今天没有太多时间,你能否在周一提醒我创建一个小例子? - Micka
@Micka 开心的星期一 :) 开个玩笑,但如果您能提供一些小例子会很好。 - Max
抱歉,忘记了复活节星期一...明天才有时间 :-( - Micka
显示剩余2条评论
3个回答

7

不需要将浮点数转换为字节,只需使用字节通道值的线性组合即可。详见:

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;
}

使用这张输入图像:

enter image description here

计算这个模糊和掩模:

enter image description here

enter image description here

通过计算 (mask/255) * blur + (1-mask/255)*blur(线性组合)得出这个输出结果:

enter image description here


1

我定义了一个函数,在OpenCV中使用CV_8UC3图像和CV_8UC1掩模对两个图像进行alphaBlend:

//! 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);
        }
    }
}

然后,对图像进行Alpha混合很容易,只需调用alphaBlend(...)。这是一个例子:


#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;
}

来源:

enter image description here

模糊:

enter image description here

遮罩1:

enter image description here

AlphaBlend 1:

enter image description here

面罩 2:

enter image description here

AlphaBlend 2:

enter image description here



一些有用的链接:

  1. OpenCV C++中的Alpha混合:在OpenCV中使用透明掩模组合2个图像

  2. OpenCV Python中的Alpha混合: 在OpenCV Python中使用渐变掩模混合


0
可能是因为_mask是单通道图像,而blurredImage和maskedImage是3通道图像。

在调用cv::bitwise_and之前加入以下代码:

P.S. 如果您不想修改您的掩码,因为您想在其他地方使用它,只需在临时变量中执行操作:

cv::Mat _mask_temp;
cv::cvtColor(_mask,_mask_temp,cv::COLOR_GRAY2BGR);
cv::bitwise_and(blurredImage, _mask_temp, maskedImage);
_mask_temp.release(); // just in case you do not want it anymore to be in your memory(optional)

编辑(另一个问题):

掩码是32F,而图像是8U。因此,您需要这样做:

cv::cvtColor(_mask,_mask,cv::COLOR_GRAY2BGR);
_mask.convertTo(_mask, CV_8UC3);

谢谢您的建议,但添加 cv::cvtColor(_mask,_mask,cv::COLOR_GRAY2BGR); 没有改变任何东西,仍然是同样的错误。 - Max
你的掩码是32F,而你的图像是8U,只需进行适当的转换即可。 - Humam Helfawi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接