OpenCV作为一个图像库,竟然没有提供一个直接的函数去做这件事情。
这里使用系统自带的GDI和EmguCV(C#封装的OpenCV)分别实现了叠加合并两张半透明图像的功能。
两个半透明颜色色的叠加计算方法
透明颜色混合算法
/// 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 两张半透明图片的叠加 /// /// /// 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还是略有优势的,毕竟显卡加速。