基于OpenCV实现Photoshop的17种图层混合

摘要:
一、图层混合模式是什么?

一、图层混合模式是什么?

    所谓图层混合模式就是指一个层与其下图层的色彩叠加方式,在这之前我们所使用的是正常模式,除了正常以外,还有很多种混合模式,它们都可以产生迥异的合成效果。

基于OpenCV实现Photoshop的17种图层混合第1张

二、PhotoShop的27种混合模式

从很久之前的版本开始,PhotoShop就保持了27种图层混合模式。

基于OpenCV实现Photoshop的17种图层混合第2张基于OpenCV实现Photoshop的17种图层混合第3张

并且可以进一步分为:普通模式、变暗模式、变亮模式、饱和度模式、差集模式和颜色模式。

基于OpenCV实现Photoshop的17种图层混合第4张

      1. 正常(Normal)模式
    在“正常”模式下,“混合色”的显示与不透明度的设置有关。当“不透明度”为100%,也就是说完全不透明时,“结果色”的像素将完全由所用的“混合色”代替;当“不透明度”小于100%时,混合色的像素会透过所用的颜色显示出来,显示的程度取决于不透明度的设置与“基色”的颜色
  2. 溶解(Dissolve)模式
  在“溶解”模式中,根据任何像素位置的不透明度,“结果色”由“基色”或“混合色”的像素随机替换。

  3. 变暗(Darken)模式
  在“变暗”模式中,查看每个通道中的颜色信息,并选择“基色”或“混合色”中较暗的颜色作为“结果色”。比“混合色”亮的像素被替换,比“混合色”暗的像素保持不变。“变暗”模式将导致比背景颜色更淡的颜色从“结果色”中被去掉了。

  4. 正片叠底(Multiply)模式
  在“正片叠底”模式中,查看每个通道中的颜色信息,并将“基色”与“混合色”复合。“结果色”总是较暗的颜色。任何颜色与黑色复合产生黑色。任何颜色与白色复合保持不变。

  5. 颜色加深(Clolor Burn)模式
  在“颜色加深”模式中,查看每个通道中的颜色信息,并通过增加对比度使基色变暗以反映混合色,如果与白色混合的话将不会产生变化。

  6. 线性加深(Linear Burn)模式
  在“线性加深”模式中,查看每个通道中的颜色信息,并通过减小亮度使“基色”变暗以反映混合色。如果“混合色”与“基色”上的白色混合后将不会产生变化,

  7. 变亮(Lighten)模式
  在“变亮”模式中,查看每个通道中的颜色信息,并选择“基色”或“混合色”中较亮的颜色作为“结果色”。比“混合色”暗的像素被替换,比“混合色”亮的像素保持不变。 在这种与“变暗”模式相反的模式下,较淡的颜色区域在最终的“合成色”中占主要地位。

  8. 滤色(Screen)模式
  “滤色”模式与“正片叠底”模式正好相反,它将图像的“基色”颜色与“混合色”颜色结合起来产生比两种颜色都浅的第三种颜色。
  9. 颜色减淡(Clolor Dodge)模式
  在“颜色减淡”模式中,查看每个通道中的颜色信息,并通过减小对比度使基色变亮以反映混合色。与黑色混合则不发生变化。

  10. 线性减淡(Linear Dodge)模式
  在“线性减淡”模式中,查看每个通道中的颜色信息,并通过增加亮度使基色变亮以反映混合色。

  11. 叠加(Overlay)模式
  “叠加”模式把图像的“基色”颜色与“混合色”颜色相混合产生一种中间色。“基色”内颜色比“混合色”颜色暗的颜色使“混合色”颜色倍增,比“混合色”颜色亮的颜色将使“混合色”颜色被遮盖,而图像内的高亮部分和阴影部分保持不变,因此对黑色或白色像素着色时“叠加”模式不起作用。“叠加”模式以一种非艺术逻辑的方式把放置或应用到一个层上的颜色同背景色进行混合,然而,却能得到有趣的效果。背景图像中的纯黑色或纯白色区域无法在“叠加”模式下显示层上的“叠加”着色或图像区域。背景区域上落在黑色和白色之间的亮度值同“叠加”材料的颜色混合在一起,产生最终的合成颜色。为了使背景图像看上去好像是同设计或文本一起拍摄的。

  12. 柔光(Soft Light)模式
  “柔光”模式会产生一种柔光照射的效果。如果“混合色”颜色比“基色颜色的像素更亮一些,那么“结果色”将更亮;如果“混合色”颜色比“基色”颜色的像素更暗一些,那么“结果色”颜色将更暗,使图像的亮度反差增大。例如,如果在背景图像上涂了50%黑色,这是一个从黑色到白色的梯度,那着色时梯度的较暗区域变得更暗,而较亮区域呈现出更亮的色调。 其实使颜色变亮或变暗,具体取决于“混合色”。此效果与发散的聚光灯照在图像上相似。 如果“混合色”比 50% 灰色亮,则图像变亮,就像被减淡了一样。如果“混合色”比 50% 灰色暗,则图像变暗,就象被加深了一样。用纯黑色或纯白色绘画会产生明显较暗或较亮的区域,但不会产生纯黑色或纯白色。
  13. 强光(Hard Light)模式
  “强光”模式将产生一种强光照射的效果。如果“混合色”颜色“基色”颜色的像素更亮一些,那么“结果色”颜色将更亮;如果“混合色”颜色比“基色”颜色的像素更暗一些,那么“结果色”将更暗。除了根据背景中的颜色而使背景色是多重的或屏蔽的之外,这种模式实质上同“柔光”模式是一样的。它的效果要比“柔光”模式更强烈一些,同“叠加”一样,这种模式也可以在背景对象的表面模拟图案或文本,例如,如果混合色比 50% 灰色亮,则图像变亮,就像过滤后的效果。这对于向图像中添加高光非常有用。如果混合色比 50%灰色暗,则图像变暗,就像复合后的效果。这对于向图像添加暗调非常有用。用纯黑色或纯白色绘画会产生纯黑色或纯白色。
  14. 亮光(Vivid Light)模式
  通过增加或减小对比度来加深或减淡颜色,具体取决于混合色。如果混合色(光源)比 50% 灰色亮,则通过减小对比度使图像变亮。如果混合色比 50% 灰色暗,则通过增加对比度使图像变暗,

  15. 线性光(Linear Light)模式
  通过减小或增加亮度来加深或减淡颜色,具体取决于混合色。如果混合色(光源)比 50% 灰色亮,则通过增加亮度使图像变亮。如果混合色比 50% 灰色暗,则通过减小亮度使图像变暗。

  16. 点光(Pin Light)模式
  “点光”模式其实就是替换颜色,其具体取决于“混合色”。如果“混合色”比 50% 灰色亮,则替换比“混合色”暗的像素,而不改变比“混合色”亮的像素。如果“混合色”比 50% 灰色暗,则替换比“混合色”亮的像素,而不改变比“混合色”暗的像素。这对于向图像添加特殊效果非常有用。

  17. 差值(Diference)模式
  在“差值”模式中,查看每个通道中的颜色信息,“差值”模式是将从图像中“基色”颜色的亮度值减去“混合色”颜色的亮度值,如果结果为负,则取正值,产生反相效果。由于黑色的亮度值为0,白色的亮度值为255,因此用黑色着色不会产生任何影响,用白色着色则产生被着色的原始像素颜色的反相。“差值”模式创建背景颜色的相反色彩,例如,在“差值”模式下,当把蓝色应用到绿色背景中时将产生一种青绿组合色。“差值”模式适用于模拟原始设计的底片,而且尤其可用来在其背景颜色从一个区域到另一区域发生变化的图像中生成突出效果。

  18. 排除(Exclusion)模式
  “排除”模式与“差值”模式相似,但是具有高对比度和低饱和度的特点。比用“差值”模式获得的颜色要柔和、更明亮一些。建议你在处理图像时,首先选择“差值”模式,若效果不够理想,可以选择“排除”模式来试试。其中与白色混合将反转“基色”值,而与黑色混合则不发生变化。其实无论是“差值”模式还是“排除”模式都能使人物或自然景色图像产生更真实或更吸引人的图像合成。

  19. 色相(Hue)模式
  “色相”模式只用“混合色”颜色的色相值进行着色,而使饱和度和亮度值保持不变。当“基色”颜色与“混合色”颜色的色相值不同时,才能使用描绘颜色进行着色,如图30所示。但是要注意的是“色相”模式不能用于灰度模式的图像。

  20. 饱和度(Saturation)模式
  “饱和度”模式的作用方式与“色相”模式相似,它只用“混合色”颜色的饱和度值进行着色,而使色相值和亮度值保持不变。当“基色”颜色与“混合色”颜色的饱和度值不同时,才能使用描绘颜色进行着色处理,如图31所示。在无饱和度的区域上(也就是灰色区域中)用“饱和度”模式是不会产生任何效果的。

  21. 颜色(Color)模式
  “颜色”模式能够使用“混合色”颜色的饱和度值和色相值同时进行着色,而使“基色”颜色的亮度值保持不变。“颜色”模式模式可以看成是“饱合度”模式和“色相”模式的综合效果。该模式能够使灰色图像的阴影或轮廓透过着色的颜色显示出来,产生某种色彩化的效果。这样可以保留图像中的灰阶,并且对于给单色图像上色和给彩色图像着色都会非常有用。

  22. 亮度(Luminosity)模式
  “亮度”模式能够使用“混合色”颜色的亮度值进行着色,而保持“基色”颜色的饱和度和色相数值不变。其实就是用“基色”中的“色相”和“饱和度”以及“混合色”的亮度创建“结果色”。

三、代码实现:

其实这里的混合算法大多不复杂,特别是在有算法文档 http://www.deepskycolors.com/archivo/2010/04/21/formulas-for-Photoshop-blending-modes.html。支持的前提下,这里我们就文档中提到的16种再加上我之前有研究过的划分算法,整理出表格。

Blend modeFormula
Darkenmin(Target,Blend)         
MultiplyTarget * Blend         
Color Burn1 - (1-Target) / Blend         
Linear BurnTarget + Blend - 1         
Lightenmax(Target,Blend)         
Screen1 - (1-Target) * (1-Blend)         
Color DodgeTarget / (1-Blend)         
Linear DodgeTarget + Blend         
Overlay(Target > 0.5) * (1 - (1-2*(Target-0.5)) * (1-Blend)) +
(Target <= 0.5) * ((2*Target) * Blend)
Soft Light(Blend > 0.5) * (1 - (1-Target) * (1-(Blend-0.5))) +
(Blend <= 0.5) * (Target * (Blend+0.5))
Hard Light(Blend > 0.5) * (1 - (1-Target) * (1-2*(Blend-0.5))) +
(Blend <= 0.5) * (Target * (2*Blend))
Vivid Light(Blend > 0.5) * (1 - (1-Target) / (2*(Blend-0.5))) +
(Blend <= 0.5) * (Target / (1-2*Blend))
Linear Light(Blend > 0.5) * (Target + 2*(Blend-0.5)) +
(Blend <= 0.5) * (Target + 2*Blend - 1)
Pin Light(Blend > 0.5) * (max(Target,2*(Blend-0.5))) +
(Blend <= 0.5) * (min(Target,2*Blend)))
Difference| Target - Blend |         
Exclusion0.5 - 2*(Target-0.5)*(Blend-0.5)

编写具有OpenCV风格的代码:

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/photo/photo.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
 
using namespace std;
using namespace cv;
 
#define EPSILON      1e-6f
 
#define SAFE_DIV_MIN EPSILON
#define SAFE_DIV_MAX (1.0f / SAFE_DIV_MIN)
 
#define    CLAMP(f,min,max)    ((f)<(min)?(min):(f)>(max)?(max):(f))
//! layerModelBlending algorithm flags
enum
{
    DARKEN = 1,                //min(Target,Blend)
    MULTIPY = 2,               //Target * Blend      
    COLOR_BURN = 3,            //1 - (1-Target) / Blend         
    LINEAR_BRUN = 4,            //Target + Blend - 1         
    LIGHTEN = 5,               //max(Target,Blend)       
    SCREEN = 6,                //1 - (1-Target) * (1-Blend)         
    COLOR_DODGE = 7,           //Target / (1-Blend)         
    LINEAR_DODGE = 8,          //Target + Blend         
    OVERLAY = 9,               //(Target > 0.5) * (1 - (1-2*(Target-0.5)) * (1-Blend)) +(Target <= 0.5) * ((2*Target) * Blend)
    SOFT_LIGHT = 10,           //(Blend > 0.5) * (1 - (1-Target) * (1-(Blend-0.5))) +(Blend <= 0.5) * (Target * (Blend+0.5))
    HARD_LIGHT = 11,           //(Blend > 0.5) * (1 - (1-Target) * (1-2*(Blend-0.5))) +(Blend <= 0.5) * (Target * (2*Blend))
    VIVID_LIGHT = 12,          //(Blend > 0.5) * (1 - (1-Target) / (2*(Blend-0.5))) +(Blend <= 0.5) * (Target / (1-2*Blend))
    LINEAR_LIGHT = 13,         //(Blend > 0.5) * (Target + 2*(Blend-0.5)) +(Blend <= 0.5) * (Target + 2*Blend - 1)
    PIN_LIGHT = 14,            //(Blend > 0.5) * (max(Target,2*(Blend-0.5))) +(Blend <= 0.5) * (min(Target,2*Blend)))
    DIFFERENCE = 15,           //| Target - Blend |         
    EXCLUSION = 16,            //0.5 - 2*(Target-0.5)*(Blend-0.5)
    DIVIDE = 17                //Target/Blend
 

};

/*  local function prototypes  */
static inline float    safe_div(float afloat b);
/* returns a / b, clamped to [-SAFE_DIV_MAX, SAFE_DIV_MAX].
 * if -SAFE_DIV_MIN <= a <= SAFE_DIV_MIN, returns 0.
 */
static inline float safe_div(float afloat b)
{
    float result = 0.0f;
 
    if (fabsf(a) > SAFE_DIV_MIN)
    {
        result = a / b;
        result = CLAMP(result, -SAFE_DIV_MAXSAFE_DIV_MAX);
    }
 
    return result;
}
//TODO target和blend应该是同样大小

CV_EXPORTS_W void layerModelBlending(Mat targetMat blendMat dstint flag);

CV_EXPORTS_W void layerModelBlending(Mat targetMat blendMat dstint flag)
{
    for (int index_row = 0; index_row < target.rowsindex_row++)
        for (int index_col = 0; index_col < target.colsindex_col++)
            for (int index_c = 0; index_c < 3; index_c++)
                switch (flag)
                {
                case DARKEN:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] = min(
                        target.at<Vec3f>(index_rowindex_col)[index_c],
                        blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case MULTIPY:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] *
                        blend.at<Vec3f>(index_rowindex_col)[index_c];
                    break;
                case COLOR_BURN:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] = 1 -
                        safe_div((1 - target.at<Vec3f>(index_rowindex_col)[index_c]),
                            blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case LINEAR_BRUN:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] +
                        blend.at<Vec3f>(index_rowindex_col)[index_c] - 1;
                    break;
                case LIGHTEN:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] = max(
                        target.at<Vec3f>(index_rowindex_col)[index_c],
                        blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case SCREEN:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] = 1 -
                        (1 - target.at<Vec3f>(index_rowindex_col)[index_c]) *
                        (1 - blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case COLOR_DODGE:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] = safe_div
                    (target.at<Vec3f>(index_rowindex_col)[index_c],
                        1 - blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case LINEAR_DODGE:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] +
                        blend.at<Vec3f>(index_rowindex_col)[index_c];
                    break;
                case OVERLAY:
                    if (target.at<Vec3f>(index_rowindex_col)[index_c] > 0.5f)
                        dst.at<Vec3f>(index_rowindex_col)[index_c] = 1 -
                        (1 - 2 * (target.at<Vec3f>(index_rowindex_col)[index_c] - 0.5)) *
                        (1 - blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    else
                        dst.at<Vec3f>(index_rowindex_col)[index_c] = 2 *
                        target.at<Vec3f>(index_rowindex_col)[index_c] *
                        blend.at<Vec3f>(index_rowindex_col)[index_c];
                    break;
                case SOFT_LIGHT:
                    if (target.at<Vec3f>(index_rowindex_col)[index_c] > 0.5f)
                        dst.at<Vec3f>(index_rowindex_col)[index_c] = 1 -
                        (1 - target.at<Vec3f>(index_rowindex_col)[index_c]) *
                        (1 - (blend.at<Vec3f>(index_rowindex_col)[index_c] - 0.5));
                    else
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] *
                        (blend.at<Vec3f>(index_rowindex_col)[index_c] + 0.5);
                    break;
                case HARD_LIGHT:
                    if (target.at<Vec3f>(index_rowindex_col)[index_c] > 0.5f)
                        dst.at<Vec3f>(index_rowindex_col)[index_c] = 1 -
                        (1 - target.at<Vec3f>(index_rowindex_col)[index_c]) *
                        (1 - 2 * blend.at<Vec3f>(index_rowindex_col)[index_c] - 0.5);
                    else
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] *
                        (2 * blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case VIVID_LIGHT:
                    if (target.at<Vec3f>(index_rowindex_col)[index_c] > 0.5f)
                        dst.at<Vec3f>(index_rowindex_col)[index_c] = 1 -
                        safe_div(1 - target.at<Vec3f>(index_rowindex_col)[index_c],
                        (2 * (blend.at<Vec3f>(index_rowindex_col)[index_c] - 0.5)));
                    else
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        safe_div(target.at<Vec3f>(index_rowindex_col)[index_c],
                        (1 - 2 * blend.at<Vec3f>(index_rowindex_col)[index_c]));
                    break;
                case LINEAR_LIGHT:
                    if (target.at<Vec3f>(index_rowindex_col)[index_c] > 0.5f)
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] +
                        (2 * (blend.at<Vec3f>(index_rowindex_col)[index_c] - 0.5));
                    else
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] +
                        2 * blend.at<Vec3f>(index_rowindex_col)[index_c] - 1;
                    break;
                case PIN_LIGHT:
                    if (target.at<Vec3f>(index_rowindex_col)[index_c] > 0.5f)
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        max(target.at<Vec3f>(index_rowindex_col)[index_c],
                        (float)(2 * (blend.at<Vec3f>(index_rowindex_col)[index_c] - 0.5)));
                    else
                        dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        min(target.at<Vec3f>(index_rowindex_col)[index_c],
                            2 * blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case DIFFERENCE:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        abs(target.at<Vec3f>(index_rowindex_col)[index_c] -
                            blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                case EXCLUSION:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        target.at<Vec3f>(index_rowindex_col)[index_c] +
                        blend.at<Vec3f>(index_rowindex_col)[index_c] -
                        2 * target.at<Vec3f>(index_rowindex_col)[index_c] * blend.at<Vec3f>(index_rowindex_col)[index_c];
                    break;
                case DIVIDE:
                    dst.at<Vec3f>(index_rowindex_col)[index_c] =
                        safe_div(target.at<Vec3f>(index_rowindex_col)[index_c],
                            blend.at<Vec3f>(index_rowindex_col)[index_c]);
                    break;
                }

}

int main() {
    Mat target = cv::imread("e:/template/lena.jpg");
    Mat blend = cv::imread("e:/template/opencv-logo.png");
    Mat dst(target.size(), CV_32FC3Scalar::all(0));
    Mat dst2(target.size(), CV_8UC3Scalar::all(0));
    if (target.empty()) {
        std::cout << "Unable to load target! ";
    }
    if (blend.empty()) {
        std::cout << "Unable to load blend! ";
    }
    resize(blendblendtarget.size());
    target.convertTo(targetCV_32F, 1.0 / 255);
    blend.convertTo(blendCV_32F, 1.0 / 255);
 
    layerModelBlending(targetblenddstDARKEN);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/DARKEN_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstMULTIPY);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/MULTIPY_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstCOLOR_BURN);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/COLOR_BURN.jpg"dst2);
 
    layerModelBlending(targetblenddstLINEAR_BRUN);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/LINEAR_BRUN.jpg"dst2);
 
    layerModelBlending(targetblenddstLIGHTEN);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/LIGHTEN.jpg"dst2);
 
    layerModelBlending(targetblenddstSCREEN);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/SCREEN_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstCOLOR_DODGE);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/COLOR_DODGE_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstLINEAR_DODGE);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/LINEAR_DODGE_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstOVERLAY);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/OVERLAY_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstSOFT_LIGHT);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/SOFT_LIGHT_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstHARD_LIGHT);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/HARD_LIGHT_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstVIVID_LIGHT);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/VIVID_LIGHT_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstLINEAR_LIGHT);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/LINEAR_LIGHT_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstPIN_LIGHT);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/PIN_LIGHT_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstDIFFERENCE);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/DIFFERENCE_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstEXCLUSION);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/EXCLUSION_RESULT.jpg"dst2);
 
    layerModelBlending(targetblenddstDIVIDE);
    dst.convertTo(dst2CV_8UC3, 255);
    imwrite("E:/sandbox/layerBlendingModles/DIVIDE_RESULT.jpg"dst2);
}

17种结果展示如下。

基于OpenCV实现Photoshop的17种图层混合第5张

四、重要的参考:

在本例代码实现过程中,主要参考了两个方面的代码:

一个是GIMP的代码:来自gimpoperationlayermode-blend.h。

函数名称核心代码
gimp_operation_layer_mode_blend_addition
for (c = 0; c < 3; c++)
            comp[c] = in[c] + layer[c];
gimp_operation_layer_mode_blend_burn
 for (c = 0; c < 3; c++)
            comp[c] = 1.0f - safe_div (1.0f - in[c], layer[c]);
gimp_operation_layer_mode_blend_darken_only
 for (c = 0; c < 3; c++)
            comp[c] = MIN (in[c], layer[c]);
gimp_operation_layer_mode_blend_difference
 for (c = 0; c < 3; c++)
            comp[c] = fabsf (in[c] - layer[c]);
gimp_operation_layer_mode_blend_divide (const gfloat *in,
for (c = 0; c < 3; c++)
            comp[c] = safe_div (in[c], layer[c]);
gimp_operation_layer_mode_blend_dodge
for (c = 0; c < 3; c++)
            comp[c] = safe_div (in[c], 1.0f - layer[c]);
gimp_operation_layer_mode_blend_exclusion
for (c = 0; c < 3; c++)
            comp[c] = 0.5f - 2.0f * (in[c] - 0.5f) * (layer[c] - 0.5f);
gimp_operation_layer_mode_blend_grain_extract
for (c = 0; c < 3; c++)
            comp[c] = in[c] - layer[c] + 0.5f;
gimp_operation_layer_mode_blend_grain_merge
for (c = 0; c < 3; c++)
            comp[c] = in[c] + layer[c] - 0.5f;
gimp_operation_layer_mode_blend_hard_mix
for (c = 0; c < 3; c++)
            comp[c] = in[c] + layer[c] < 1.0f ? 0.0f : 1.0f;
gimp_operation_layer_mode_blend_hardlight
for (c = 0; c < 3; c++)
            {
              gfloat val;

              if (layer[c] > 0.5f)
                {
                  val = (1.0f - in[c]) * (1.0f - (layer[c] - 0.5f) * 2.0f);
                  val = MIN (1.0f - val, 1.0f);
                }
              else
                {
                  val = in[c] * (layer[c] * 2.0f);
                  val = MIN (val, 1.0f);
                }

              comp[c] = val;
            }
gimp_operation_layer_mode_blend_hsl_color



未整理



gimp_operation_layer_mode_blend_hsv_hue
gimp_operation_layer_mode_blend_hsv_saturation
gimp_operation_layer_mode_blend_hsv_value
gimp_operation_layer_mode_blend_lch_chroma
gimp_operation_layer_mode_blend_lch_color
gimp_operation_layer_mode_blend_lch_hue
gimp_operation_layer_mode_blend_lch_lightness
if (in[ALPHA] != 0.0f && layer[ALPHA] != 0.0f)
        {
          comp[0] = layer[0];
          comp[1] = in[1];
          comp[2] = in[2];
        }
gimp_operation_layer_mode_blend_lighten_only
 for (c = 0; c < 3; c++)
            comp[c] = MAX (in[c], layer[c]);
gimp_operation_layer_mode_blend_linear_burn
for (c = 0; c < 3; c++)
            comp[c] = in[c] + layer[c] - 1.0f;
gimp_operation_layer_mode_blend_linear_light
for (c = 0; c < 3; c++)
            {
              gfloat val;

              if (layer[c] <= 0.5f)
                val = in[c] + 2.0f * layer[c] - 1.0f;
              else
                val = in[c] + 2.0f * (layer[c] - 0.5f);

              comp[c] = val;
            }
gimp_operation_layer_mode_blend_luma_darken_only
 if (dest_luminance <= src_luminance)
            {
              for (c = 0; c < 3; c++)
                comp[c] = in[c];
            }
          else
            {
              for (c = 0; c < 3; c++)
                comp[c] = layer[c];
            }
gimp_operation_layer_mode_blend_luma_lighten_only
 if (dest_luminance >= src_luminance)
            {
              for (c = 0; c < 3; c++)
                comp[c] = in[c];
            }
          else
            {
              for (c = 0; c < 3; c++)
                comp[c] = layer[c];
            }
gimp_operation_layer_mode_blend_luminance
 if (layer[ALPHA] != 0.0f && in[ALPHA] != 0.0f)
        {
          gfloat ratio = safe_div (layer_Y_p[0], in_Y_p[0]);
          gint   c;

          for (c = 0; c < 3; c ++)
            comp[c] = in[c] * ratio;
        }
gimp_operation_layer_mode_blend_multiply
 for (c = 0; c < 3; c++)
            comp[c] = in[c] * layer[c];
gimp_operation_layer_mode_blend_overlay
for (c = 0; c < 3; c++)
            {
              gfloat val;

              if (in[c] < 0.5f)
                val = 2.0f * in[c] * layer[c];
              else
                val = 1.0f - 2.0f * (1.0f - layer[c]) * (1.0f - in[c]);

              comp[c] = val;
            }
gimp_operation_layer_mode_blend_pin_light
未整理

一个是网站的代码:来自https://blog.csdn.net/matrix_space

算法名实现
溶解
Dissolve

// Dissolve
void Dissolve(Mat& src1, Mat& src2, Mat& dst, double alpha)
{
    dst=src1;
    Mat Rand_mat(src1.size(), CV_32FC1);
    cv::randu(Rand_mat, 0,1);
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            b=Rand_mat.at<float>(index_row, index_col);
            if(b<alpha)
            {
                for(int index_c=0; index_c<3; index_c++)
               {
                   a=src2.at<Vec3f>(index_row, index_col)[index_c];
                   dst.at<Vec3f>(index_row, index_col)[index_c]=a;
               }
            }
        }
    }
}
变暗
Darken
C = MIN(A,B)
// Darken
void Darken(Mat& src1, Mat& src2, Mat& dst)
{
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=min(
                         src1.at<Vec3f>(index_row, index_col)[index_c],
                         src2.at<Vec3f>(index_row, index_col)[index_c]);
        }
    }
正片叠底
Multiply
C=A*B/255
// Multiply 正片叠底
void Multiply(Matsrc1Matsrc2Matdst)
{
    for(int index_row=0; index_row<src1.rowsindex_row++)
    {
        for(int index_col=0; index_col<src1.colsindex_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_rowindex_col)[index_c]=
                src1.at<Vec3f>(index_rowindex_col)[index_c]*
                src2.at<Vec3f>(index_rowindex_col)[index_c];
        }
    }
}
颜色加深
Color_Burn
C = A-((255-A)×(255-B))/ B

// Color_Burn 颜色加深
void Color_Burn(Matsrc1Matsrc2Matdst)
{
    for(int index_row=0; index_row<src1.rowsindex_row++)
    {
        for(int index_col=0; index_col<src1.colsindex_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_rowindex_col)[index_c]=1-
                (1-src1.at<Vec3f>(index_rowindex_col)[index_c])/
                src2.at<Vec3f>(index_rowindex_col)[index_c];
        }
    }
}
线性加深
Linear_Burn
C=A+B-255
// 线性增强
void(Matsrc1Matsrc2Matdst)
{
    for(int index_row=0; index_row<src1.rowsindex_row++)
    {
        for(int index_col=0; index_col<src1.colsindex_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_rowindex_col)[index_c]=max(
                src1.at<Vec3f>(index_rowindex_col)[index_c]+
                src2.at<Vec3f>(index_rowindex_col)[index_c]-1, (float)0.0);
        }
    }
}
变亮
C = MAX(A,B)
 
滤色
Screen

// Screen
void Screen(Mat& src1, Mat& src2, Mat& dst)
{
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=1-
                         (1-src1.at<Vec3f>(index_row, index_col)[index_c])*
                         (1-src2.at<Vec3f>(index_row, index_col)[index_c]);
        }
    }
}
颜色减淡
Color_Dodge

// Color_Dodge 颜色减淡
void Color_Dodge(Mat& src1, Mat& src2, Mat& dst)
{
    for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=
                          src2.at<Vec3f>(index_row, index_col)[index_c]/
                         (1-src1.at<Vec3f>(index_row, index_col)[index_c]);
        }
    }
}
线性减淡(添加)
C=A+B
// Lighten
void Lighten(Mat& src1, Mat& src2, Mat& dst)
{
    for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
                dst.at<Vec3f>(index_row, index_col)[index_c]=max(
                         src1.at<Vec3f>(index_row, index_col)[index_c],
                         src2.at<Vec3f>(index_row, index_col)[index_c]);
        }
    }
}
浅色 
叠加
Add_Color
// Add color
void Add_Color(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                if(b>0.5)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a*b;
                }
                else
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=1-2*(1-a)*(1-b);
                }
            }
        }
    }
}
柔光
Soft_Lighten
// Soft Lighten
void Soft_Lighten(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                if(a<=0.5)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=(2*a-1)*(b-b*b)+b;
                }
                else
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=(2*a-1)*(sqrt(b)-b)+b;
                }
            }
        }
    }
}
强光
Strong_Lighten
void Strong_Lighten(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                if(a<=0.5)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a*b;
                }
                else
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=1-2*(1-a)*(1-b);
                }
            }
        }
    }
}
亮光
Vivid_Lighten
//Vivid Lighten
void Vivid_Lighten(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                if(a<=0.5)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=1-(1-b)/(2*a);
                }
                else
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=b/(2*(1-a));
                }
            }
        }
    }
}
线性光
Linear_Lighten
// Linear Lighten
void Linear_Lighten(Mat& src1, Mat& src2, Mat& dst)
{
    dst=src2+2*src1-1;
}
点光
Pin_Lighten
// Pin lighten
void Pin_Lighten(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                if(b<=2*a-1)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a-1;
                }
                else if(b<=2*a)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=b;
                }
                else
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=2*a;
                }
            }
        }
    }
}
实色混合
Hard_mix
// Hard mix
void Hard_mix(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                if(a<1-b)
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=0.0;
                }
                else
                {
                    dst.at<Vec3f>(index_row, index_col)[index_c]=1.0;
                }
            }
        }
    }
}
差值
Difference
// Difference
void Difference(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                dst.at<Vec3f>(index_row, index_col)[index_c]=abs(a-b);
            }
        }
    }
}
排除
Exclusion
// Exclusion
void Exclusion(Mat& src1, Mat& src2, Mat& dst)
{
    float a=0;
    float b=0;
     for(int index_row=0; index_row<src1.rows; index_row++)
    {
        for(int index_col=0; index_col<src1.cols; index_col++)
        {
            for(int index_c=0; index_c<3; index_c++)
            {
                a=src1.at<Vec3f>(index_row, index_col)[index_c];
                b=src2.at<Vec3f>(index_row, index_col)[index_c];
                dst.at<Vec3f>(index_row, index_col)[index_c]=a+b-2*a*b;
            }
        }
    }
}
减去 
划分
divide
结果色 = (基色 / 混合色) * 255
    Mat src = imread("t1.jpeg");
    src.convertTo(src,CV_32FC3,1.0/255);
    Mat gauss;
    Mat dst = src.clone();
    cv::GaussianBlur(src,gauss,Size(101,101),0);
    dst = src/gauss;

免责声明:文章转载自《基于OpenCV实现Photoshop的17种图层混合》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【CI/CD】Jenkins查询及自定义工作空间(自由风格项目、maven项目)使用XDocReport将HTML格式数据转换为Word下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

STM32硬件调试详解

STM32的基本系统主要涉及下面几个部分:  一、电源  1)、无论是否使用模拟部分和AD部分,MCU外围出去VCC和GND,VDDA、VSSA、Vref(如果封装有该引脚)都必需要连接,不可悬空;  2)、对于每组对应的VDD和GND都应至少放置一个104的陶瓷电容用于滤波,并接该电容应放置尽量靠近MCU; 3)、用万用表测试供电电压是否正确。调试时最好...

Ubuntu Linux 查看、编辑、比较二进制文件

查看二进制有以下几种方法: 方法一:hexdump apt-get install libdata-hexdumper-perl 安装好之后就可以直接hexdump your_binary_file 也可以直接使用hd命令来代替hexdump 如果想要慢慢看 : hd your_binary_file | more 方法二: Vim 可以用来查看和编辑二...

opencv4.2.0.34+python3.8.2+(直线检测、圆检测、轮廓发现、对象测量、膨胀和腐蚀、开闭操作、形态学操作、分水岭算法、人脸检测、识别验证码)

(有的运行结果没弄上去,但文中代码本人亲测均通过;至于有人因版本问题出现个别错误,我相信对于大家应该没什么问题,文档就是很好的辅助学习资料) 文章目录 二十二、直线检测 二十三、圆检测 二十四、轮廓发现 二十五、对象测量 二十六、膨胀和腐蚀 二十七、开闭操作 二十八、其他形态学操作 二十九、分水岭算法(基于距离变换) 三十、人脸检测 三十一、识别验证码...

联想笔记本切换功能键

在联想E440笔记本上面,默认使用F1~F12是附加功能,而不是原始功能,需要先按住Fn键,再按下对于的Fx键,这在正常使用的时候,比较别扭。正常情况下,原始功能使用的最为频繁,附加功能不是那么经常被用到的,因此,需要更改一下默认配置。 联想Thinkpad E420/E440 快捷键作用介绍: F1:静音 F2:音量减 F3:音量加 F4:麦克...

【从零学习openCV】IOS7人脸识别实战

前言 接着上篇《IOS7下的人脸检測》,我们顺藤摸瓜的学习怎样在IOS7下用openCV的进行人脸识别,实际上非常easy,因为人脸检測部分已经完毕,剩下的无非调用openCV的方法对採集到的人脸样本进行训练,终于得到一个能够预測人脸的模型。可是当中的原理可谓是博大精深,因为快临最近末考试了,没时间去琢磨当中详细的细节,这次就先写个大概的demo,...

css3新特性总结(视觉表现方面)

1.半透明rgba 在rgba出现之前,半透明可以用opacity来创建,可是这样子导致的结果是不仅元素的背景会变透明,标签元素包含的文字也会变透明。这样子会导致可读性降低,所以都会嵌套一个包装标签来创建半透明,这样子很麻烦,自从可以用rgba来表示颜色之后就方便多了。rgba表示方法为: .rgba{ background:rgba(25...