如何展平2个不同的图像层?

时间:2016-05-09 13:10:23

标签: java android opencv

我有2个Mat对象,overlaybackground

如何将overlay垫放在background垫的顶部,以便只有overlay垫的非透明像素完全遮盖background垫?

我尝试了addWeighted(),它结合了2 Mat但是两个"层"仍然可见。

  • overlay垫具有透明通道,而background垫则没有。
  • overlay Mat中的像素要么完全透明,要么完全模糊。
  • 两个垫子大小相同。

2 个答案:

答案 0 :(得分:1)

函数addWeighted将不起作用,因为它将对所有像素使用相同的alpha值。要完全按照你的意思去做,只替换背景中的非透明值,你可以为它创建一个小函数,如下所示:

cv::Mat blending(cv::Mat& overlay, cv::Mat& background){
    //must have same size for this to work
    assert(overlay.cols == background.cols && overlay.rows == background.rows);
    cv::Mat result = background.clone();
    for (int i = 0; i < result.rows; i++){
        for (int j = 0; j < result.cols; j++){
            cv::Vec4b pix = overlay.at<cv::Vec4b>(i,j);
            if (pix[3] == 0){
                result.at<cv::Vec3b>(i,j) = cv::Vec3b(pix[0], pix[1], pix[2]);
            }
        }
    }
    return result;
}

我不确定opencv中的透明值是0还是255,所以相应地改变它....我认为对于非透明的adn 255来说它是完全透明的。

如果您想使用Alpha通道的值作为混合比率,请稍微更改一下:

cv::Mat blending(cv::Mat& overlay, cv::Mat& background){
    //must have same size for this to work
    assert(overlay.cols == background.cols && overlay.rows == background.rows);
    cv::Mat result = background.clone();
    for (int i = 0; i < result.rows; i++){
        for (int j = 0; j < result.cols; j++){
            cv::Vec4b pix = overlay.at<cv::Vec4b>(i,j);
            double alphaRate = 1.0 - pix[3]/255.0;
            result.at<cv::Vec3b>(i,j) = (1.0 - alphaRate) * cv::Vec3b(pix[0], pix[1], pix[2]) + result.at<cv::Vec3b>(i,j) * alphaRate;
        }
    }
    return result;
}

很抱歉代码是用C ++而不是JAVA,但我想你可以得到一个想法。基本上只是像素中的一个循环,如果它们不透明,则将背景副本中的像素更改为覆盖的像素。

*编辑*

我将通过此编辑回答您的评论,因为它可能需要空间。问题是OpenCV矩阵的工作原理。对于具有alpha的图像,数据被组织为类似BGRA BGRA .... BGRA的数组,并且基本操作(如add,multiply等)在具有相同尺寸的矩阵中工作.....你总是可以尝试将有分裂的矩阵(这将重写矩阵,因此它可能很慢),然后将alpha通道更改为double(再次,重写),然后进行乘法和矩阵的加法。它应该更快,因为OpenCV优化了这些功能....你也可以在GPU中做到这一点....

这样的事情:

cv::Mat blending(cv::Mat& overlay, cv::Mat& background){
    std::vector<cv::Mat> channels;
    cv::split(overlay, channels);
    channels[3].convertTo(channels[3], CV_64F, 1.0/255.0);
    cv::Mat newOverlay, result;
    cv::merge(channels, newOverlay);
    result = newOverlay * channels[3] + ((1 - channels[3]) * background);
    return result;
}

不确定OpenCV是否允许CV_8U乘以CV_64F,或者这是否会更快......但可能会更快。

另外,带循环的那些在线程中没有问题,所以它可以被优化...在发布模式下运行它将大大提高速度,因为OpenCV的.at函数做了几个断言......在发布模式下没有完成。不确定这是否可以在JAVA中改变...

答案 1 :(得分:0)

我能够移植api55编辑的java答案:

private void merge(Mat background, Mat overlay) {
            List<Mat> backgroundChannels = new ArrayList<>();
            Core.split(background, backgroundChannels);

        List<Mat> overlayChannels = new ArrayList<>();
        Core.split(overlay, overlayChannels);

        // compute "alphaRate = 1 - overlayAlpha / 255"
        Mat overlayAlphaChannel = overlayChannels.get(3);
        Mat alphaRate = new Mat(overlayAlphaChannel.size(), overlayAlphaChannel.type());
        Core.divide(overlayAlphaChannel, new Scalar(255), alphaRate);
        Core.absdiff(alphaRate, new Scalar(1), alphaRate);

        for (int i = 0; i < 3; i++) {
            // compute "(1 - alphaRate) * overlay"
            Mat overlayChannel = overlayChannels.get(i);
            Mat temp = new Mat(alphaRate.size(), alphaRate.type());
            Core.absdiff(alphaRate, new Scalar(1), temp);
            Core.multiply(temp, overlayChannel, overlayChannel);
            temp.release();

            // compute "background * alphaRate"
            Mat backgroundChannel = backgroundChannels.get(i);
            Core.multiply(backgroundChannel, alphaRate, backgroundChannel);

            // compute the merged channel
            Core.add(backgroundChannel, overlayChannel, backgroundChannel);
        }

        alphaRate.release();
        Core.merge(backgroundChannels, background);
    }

与双嵌套循环计算相比,它快得多。