C#叠加合并半透明图像的两种实现

C#叠加合并半透明图像

  • 说明
    • 方案一:系统自带 GDI+ 实现
    • 方案二:使用 EmguCV 实现
    • 总结

说明

OpenCV作为一个图像库,竟然没有提供一个直接的函数去做这件事情。
这里使用系统自带的GDI和EmguCV(C#封装的OpenCV)分别实现了叠加合并两张半透明图像的功能。
两个半透明颜色色的叠加计算方法
透明颜色混合算法

方案一:系统自带 GDI+ 实现

/// using System.Drawing;

/// 
/// GDI+ 方式合并半透明图像
/// 
/// 
/// 
/// 
public Bitmap ImageOverlapping1(Bitmap background, Bitmap fontground)
{
    if (background.Size != fontground.Size)
        throw new ArgumentException("background size != fontground size");

    using (Graphics g = Graphics.FromImage(background))
    {
        g.DrawImage(fontground, new Point(0, 0));
    }

    return background;
}

/// 
/// 按从下到上的顺序合并多张输入图像
/// 
/// 从下到上顺序的图像,第一张图在最下面,最后一张图在最上面
/// 
public Bitmap ImageOverlapping1(params Bitmap[] z2aImages)
{
    if (z2aImages.Length == 0)
        throw new ArgumentException();
    if (z2aImages.Length == 1)
        return z2aImages[0];
    for (int i = 0; i < z2aImages.Length - 1; i++)
    {
        z2aImages[i + 1] = ImageOverlapping1(z2aImages[i], z2aImages[i + 1]);
    }
    return z2aImages[z2aImages.Length - 1];
}

这个方式优点是代码简单,而且DrawImage的参数支持合并不同大小的图片,支持指定左上角的位置。

方案二:使用 EmguCV 实现

/// 
/// EmguCV 两张半透明图片的叠加
/// 
/// 
/// ushort a1 = A1;
/// ushort a2 = A2 - (A1 * A2) / 256;
/// ushort a = A1 + A2;
/// uint8 R = (a1* R1 + a2* R2)/a;
/// uint8 G = (a1* G1 + a2* G2)/a;
/// uint8 B = (a1* B1 + a2* B2)/a;
/// uint8 A = a;
/// 
/// 
/// 
/// 
public Bitmap ImageOverlapping2(Bitmap background, Bitmap fontground)
{
    if (background.Size != fontground.Size)
        throw new ArgumentException("background size != fontground size");
    // 因为中间计算涉及 uint8 数据的相乘与相加,
    // 因此统一使用 uint16 格式存放加载
    Image<Bgra, ushort> back = new Image<Bgra, ushort>(background);
    Image<Bgra, ushort> font = new Image<Bgra, ushort>(fontground);
    Size size = background.Size;
    UMat image1 = back.ToUMat(); // 背景
    UMat image2 = font.ToUMat(); // 前景

    // 拆分通道
    UMat[] image1Channels = image1.Split();
    UMat[] image2Channels = image2.Split();

    // 用于存放各通道计算结果
    UMat[] outputChannels = new UMat[]
    {
       new UMat(size,DepthType.Cv16U,1),
       new UMat(size,DepthType.Cv16U,1),
       new UMat(size,DepthType.Cv16U,1),
       new UMat(size,DepthType.Cv16U,1),
    };
    // 用于暂存中间结果数据
    UMat tmp = new UMat(size, DepthType.Cv16U, 1);
    // 255灰度值填充的灰度图
    UMat white = new UMat(size, DepthType.Cv16U, 1);
    white.SetTo(new MCvScalar(255));

    // Alpha 通道计算
    CvInvoke.Multiply(image1Channels[3], image2Channels[3], tmp); // tmp = A1 * A2
    CvInvoke.Divide(tmp, white, tmp); // tmp = tmp / 255
    CvInvoke.Subtract(image2Channels[3], tmp, image2Channels[3]); // A2 = A2 - tmp
    CvInvoke.Add(image1Channels[3], image2Channels[3], outputChannels[3]); // A = A1 + A2

    // B G R 通道计算
    for (int i = 0; i < 3; i++)
    {
        CvInvoke.Multiply(image1Channels[i], image1Channels[3], image1Channels[i]); // C1 = C1 * A1
        CvInvoke.Multiply(image2Channels[i], image2Channels[3], image2Channels[i]); // C2 = C2 * A2
        CvInvoke.Add(image1Channels[i], image2Channels[i], outputChannels[i]); // C = C1 + C2
        CvInvoke.Divide(outputChannels[i], outputChannels[3], outputChannels[i]); // C = C / A
    }

    // 通道合并输出
    using (UMat output = new UMat(size, DepthType.Cv16U, 4))
    {
        CvInvoke.Merge(new VectorOfUMat(outputChannels), output);

        // 释放资源
        tmp.Dispose();
        white.Dispose();    
        back.Dispose();        
        font.Dispose(); 
        image1.Dispose();
        image2.Dispose();
        for (int i = 0; i < 4; i++)
        {
            image1Channels[i].Dispose();
            image2Channels[i].Dispose();
            outputChannels[i].Dispose();
        }

        return output.ToImage<Bgra, byte>().ToBitmap();
    }
}

/// 
/// 按从下到上的顺序合并输入图像
/// 
/// 从下到上顺序的图像,第一张图在最下面,最后一张图在最上面
/// 
public Bitmap ImageOverlapping2(params Bitmap[] z2aImages)
{
    if (z2aImages.Length == 0)
        throw new ArgumentException();
    if (z2aImages.Length == 1)
        return z2aImages[0];
    for (int i = 0; i < z2aImages.Length - 1; i++)
    {
        z2aImages[i + 1] = ImageOverlapping2(z2aImages[i], z2aImages[i + 1]);
    }
    return z2aImages[z2aImages.Length - 1];
}

EmguCV写起来有几个资源释放的坑,刚开始没有释放资源,反复调用会内存错误,
建议写好之后循环执行1000遍,看内存占用。

总结

很遗憾的是.Net平台下,方案2函数的运行耗时要大于方案1函数的运行耗时。
时间消耗主要在函数头部和尾部Bitmap和UMat之间的互相转换,实际上中间的计算时间EmguCV还是略有优势的,毕竟显卡加速。

你可能感兴趣的:(软件,C#,算法)