如何在 OpenCV 中重现 Photoshop 的多重混合?

How to reproduce Photoshop's multiply blending in OpenCV?

我正在尝试在 OpenCV 中重现 Photoshop 的多重混合模式。与此等效的是您在 GIMP 中找到的,或者当您在 Apple 的 CoreImage 框架中使用 CIMultiplyBlendMode 时。

我在网上阅读的所有内容都表明乘法混合只需将两个输入图像的通道相乘即可完成(即 Blend = AxB)。而且,这有效,除了 alpha < 1.0 的情况。

您可以在 GIMP/PhotoShop/CoreImage 中非常简单地测试它,方法是创建两个 layers/images,为每个层填充不同的纯色,然后修改第一层的不透明度。 (顺便说一句,当您修改 alpha 时,出于某种原因,该操作在 GIMP 中不再是可交换的。)

一个简单的例子:如果 A = (0,0,0,0) 和 B = (0.4,0,0,1.0),并且 C = AxB,那么我希望 C 为 (0,0 ,0,0).这是简单的乘法。但这并不是这种混合在实践中的实施方式。实际上,C = (0.4,0,0,1.0),或 C = B.

底线是这样的:我需要找出乘法混合模式的公式(这显然比 AxB 多),然后在 OpenCV 中实现它(一旦我有了公式,这应该是微不足道的)。

非常感谢任何见解。

此外,作为参考,这里有一些链接将乘法混合显示为简单的 AxB:

How does photoshop blend two images together

Wikipedia - Blend Modes

Photoshop Blend Modes

我设法解决了这个问题。如有任何改进建议,请随时发表评论。

首先,我找到了一个关于如何在这个post中实现乘法函数的线索:

multiply blending

这里是 C++ 中的 OpenCV 快速实现。

Mat MultiplyBlend(const Mat& cvSource, const Mat& cvBackground) {

// assumption: cvSource and cvBackground are of type CV_8UC4

// formula: (cvSource.rgb * cvBackground.rgb * cvSource.a) + (cvBackground.rgb * (1-cvSource.a))
Mat cvAlpha(cvSource.size(), CV_8UC3, Scalar::all(0));
Mat input[] = { cvSource };
int from_to[] = { 3,0, 3,1, 3,2 };
mixChannels(input, 1, &cvAlpha, 1, from_to, 3);

Mat cvBackgroundCopy;
Mat cvSourceCopy;
cvtColor(cvSource, cvSourceCopy, CV_RGBA2RGB);
cvtColor(cvBackground, cvBackgroundCopy, CV_RGBA2RGB);

// A = cvSource.rgb * cvBackground.rgb * cvSource.a
Mat cvBlendResultLeft;
multiply(cvSourceCopy, cvBackgroundCopy, cvBlendResultLeft, 1.0 / 255.0);
multiply(cvBlendResultLeft, cvAlpha, cvBlendResultLeft, 1.0 / 255.0);
delete(cvSourceCopy);

// invert alpha
bitwise_not(cvAlpha, cvAlpha);

// B = cvBackground.rgb * (1-cvSource.a)
Mat cvBlendResultRight;
multiply(cvBackgroundCopy, cvAlpha, cvBlendResultRight, 1.0 / 255.0);
delete(cvBackgroundCopy, cvAlpha);

// A + B
Mat cvBlendResult;
add(cvBlendResultLeft, cvBlendResultRight, cvBlendResult);
delete(cvBlendResultLeft, cvBlendResultRight);

cvtColor(cvBlendResult, cvBlendResult, CV_RGB2RGBA);

return cvBlendResult;
}

这是一个基于 GIMP 源代码的 OpenCV 解决方案,特别是函数 gimp_operation_multiply_mode_process_pixels.

注意

  • 不是在所有像素上循环,它可以被矢量化,但我遵循了 GIMP 的步骤。
  • 输入图像的类型必须为 CV_8UC3 或 CV_8UC4。
  • 它还支持 opacity 值,该值必须在 [0, 255]
  • 在最初的 GIMP 实现中也支持遮罩。最终可以将其简单地添加到代码中。
  • 这个实现实际上是不对称的,并且会重现您的奇怪行为。

代码:

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

Mat blend_multiply(const Mat& level1, const Mat& level2, uchar opacity)
{
    CV_Assert(level1.size() == level2.size());
    CV_Assert(level1.type() == level2.type());
    CV_Assert(level1.channels() == level2.channels());

    // Get 4 channel float images
    Mat4f src1, src2;

    if (level1.channels() == 3)
    {
        Mat4b tmp1, tmp2;
        cvtColor(level1, tmp1, COLOR_BGR2BGRA);
        cvtColor(level2, tmp2, COLOR_BGR2BGRA);
        tmp1.convertTo(src1, CV_32F, 1. / 255.);
        tmp2.convertTo(src2, CV_32F, 1. / 255.);
    }
    else
    {
        level1.convertTo(src1, CV_32F, 1. / 255.);
        level2.convertTo(src2, CV_32F, 1. / 255.);
    }

    Mat4f dst(src1.rows, src1.cols, Vec4f(0., 0., 0., 0.));

    // Loop on every pixel

    float fopacity = opacity / 255.f;
    float comp_alpha, new_alpha;

    for (int r = 0; r < src1.rows; ++r)
    {
        for (int c = 0; c < src2.cols; ++c)
        {
            const Vec4f& v1 = src1(r, c);
            const Vec4f& v2 = src2(r, c);
            Vec4f& out = dst(r, c);

            comp_alpha = min(v1[3], v2[3]) * fopacity;
            new_alpha = v1[3] + (1.f - v1[3]) * comp_alpha;

            if ((comp_alpha > 0.) && (new_alpha > 0.))
            {
                float ratio = comp_alpha / new_alpha;

                out[0] = max(0.f, min(v1[0] * v2[0], 1.f)) * ratio + (v1[0] * (1.f - ratio));
                out[1] = max(0.f, min(v1[1] * v2[1], 1.f)) * ratio + (v1[1] * (1.f - ratio));
                out[2] = max(0.f, min(v1[2] * v2[2], 1.f)) * ratio + (v1[2] * (1.f - ratio));
            }
            else
            {
                out[0] = v1[0];
                out[1] = v1[1];
                out[2] = v1[2];
            }

            out[3] = v1[3];

        }
    }

    Mat3b dst3b;
    Mat4b dst4b;
    dst.convertTo(dst4b, CV_8U, 255.);
    cvtColor(dst4b, dst3b, COLOR_BGRA2BGR);

    return dst3b;
}

int main()
{
    Mat3b layer1 = imread("path_to_image_1");
    Mat3b layer2 = imread("path_to_image_2");

    Mat blend = blend_multiply(layer1, layer2, 255);

    return 0;
}