C++图像处理 -- 图像合成

阅读提示

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括《C++图像处理 -- 数据类型及公用函数文章中的BmpData.h头文件。

 

    在图像处理过程中,图像的合成操作是使用频率最高的,如图像显示、图像拷贝、图像拼接以及的图层拼合叠加等。

    图像合成,其实也就是图像像素颜色的混合,在Photoshop中,颜色混合是个很复杂的东西,不同的混合模式,将产生不同的合成效果,如果将之全部研究透彻,估计就得写一本书。因此,本文只谈谈最基本的图像合成,也就是Photoshop中的正常混合模式。

    只要接触过图像处理的,都知道有个图像像素混合公式:

    1)dstRGB = srcRGB * alpha + dstRGB * (1 - alpha)

    其中,dstRGB为目标图像素值;srcRGB为源图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。

    其实,这个像素混合公式有很大局限性,只适合不含Alpha信息的图像。

    要处理包括带Alpha通道图像(层)的混合,其完整的公式应该是:

    2-1)srcRGB = srcRGB * srcAlpha * alpha / 255      (源图像素预乘转换为PARGB)

    2-2)dstRGB = dstRGB * dstAlpha / 255    (目标图像素预乘转换为PARGB)

    2-3)dstRGB = dstRGB + srcRGB - dstRGB * srcAlpha * alpha / 255    (源图像素值与目标图像素值混合)

    2-4)dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255    (混合后的目标图Alpha通道值)

    2-5)dstRGB = dstRGB * 255 / dstAlpha    (混合后的目标图像素转换为ARGB)

    其中,dstRGB为目标图像素值;srcRGB为源图像素值;dstAlpha为目标图Alpha通道值;srcAlpha为源图Alpha通道值;dstARGB为含Alpha目标图像素值;alpha为源图像素值混合比例(不透明度,范围0 - 1)。

    将公式2中的2-1式代入2-3式,简化可得:

    3-1)dstRGB = dstRGB * dstAlpha / 255

    3-2)dstRGB = dstRGB +  (srcRGB - dstRGB) * srcAlpha * alpha / 255

    3-3)dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255

    3-4)dstRGB = dstRGB * 255 / dstAlpha

    当dstAlpha=srcAlpha=255时,公式3中3-1式、3-3式和3-4式没有意义,3-2式也变化为:

   4)dstRGB = dstRGB +  (srcRGB - dstRGB) * alpha

    不难看出,公式4是公式1的变形。因此,公式1只是公式3(或者公式2)在目标图和源图都不含Alpha信息(或者Alpha=255)情况下的一个特例而已。

    当公式4中的alpha=1时,目标图像素等于源图像素,所以,本文前面说图像拷贝其实也是图像合成的范畴。

    通过上面较详细的分析,可以看出,即使是最基本正常图像混合模式也是很复杂的。其实,上面还不是完整的分析,因为按照目标图Alpha信息、源图Alpha信息以及源图合成比例等三个要素的完全的排列组合,最多可以派生8个公式。

    下面就按正常混合模式的全部8种情况(有2项重合,实际为7种情况)来分别进行代码实现,也可完善和补充上面的文字叙述:

//---------------------------------------------------------------------------

FORCEINLINE
static VOID ARGBMixer(PARGBQuad pd, CONST PARGBQuad ps, INT alpha)
{
	pd->Blue += (((ps->Blue - pd->Blue) * alpha + 127) / 255);
	pd->Green += (((ps->Green - pd->Green) * alpha + 127) / 255);
	pd->Red += (((ps->Red - pd->Red) * alpha + 127) / 255);
}
//---------------------------------------------------------------------------

FORCEINLINE
static VOID PARGBMixer(PARGBQuad pd, CONST PARGBQuad ps, INT alpha)
{
	pd->Blue = (pd->Blue * pd->Alpha + 127) / 255;
	pd->Green = (pd->Green * pd->Alpha + 127) / 255;
	pd->Red = (pd->Red * pd->Alpha + 127) / 255;

	pd->Blue += (((ps->Blue - pd->Blue) * alpha + 127) / 255);
	pd->Green += (((ps->Green - pd->Green) * alpha + 127) / 255);
	pd->Red += (((ps->Red - pd->Red) * alpha + 127) / 255);
	pd->Alpha += (alpha - (pd->Alpha * alpha + 127) / 255);

	pd->Blue = pd->Blue * 255 / pd->Alpha;
	pd->Green = pd->Green * 255 / pd->Alpha;
	pd->Red = pd->Red * 255 / pd->Alpha;
}
//---------------------------------------------------------------------------

// source alpha = false, dest alpha = false, alpha < 255
static VOID Mixer0(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
			ARGBMixer(pd, ps, alpha);
	}
}
//---------------------------------------------------------------------------

// source alpha = false, dest alpha = false, alpha = 255
// source alpha = false, dest alpha = true, alpha = 255
static VOID Mixer1(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, *pd ++ = *ps ++);
	}
}
//---------------------------------------------------------------------------

// source alpha = false, dest alpha = true, alpha < 255
static VOID Mixer2(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
			PARGBMixer(pd, ps, alpha);
	}
}
//---------------------------------------------------------------------------

// source alpha = true, dest alpha = false, alpha < 255
static VOID Mixer4(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
			ARGBMixer(pd, ps, (alpha * ps->Alpha + 127) / 255);
	}
}
//---------------------------------------------------------------------------

// source alpha = true, dest alpha = false, alpha = 255
static VOID Mixer5(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
			ARGBMixer(pd, ps, ps->Alpha);
	}
}
//---------------------------------------------------------------------------

// source alpha = true, dest alpha = true, alpha < 255
static VOID Mixer6(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
		{
			INT alpha0 = (alpha * ps->Alpha + 127) / 255;
			if (alpha0)
				PARGBMixer(pd, ps, alpha0);
		}
	}
}
//---------------------------------------------------------------------------

// source alpha = true, dest alpha = true, alpha = 255
static VOID Mixer7(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
		{
			if (ps->Alpha)
				PARGBMixer(pd, ps, ps->Alpha);
		}
	}
}
//---------------------------------------------------------------------------

typedef VOID (*MixerProc)(BitmapData*, CONST BitmapData*, INT);

// 图像合成。参数:32位目标数据,32位源数据,不透明度(0 - 1.0)
VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, FLOAT alpha)
{
	INT alphaI = (INT)(alpha * 255);
	if (alphaI <= 0) return;
	if (alphaI > 255) alphaI = 255;

	MixerProc proc[] = {Mixer0, Mixer1, Mixer2, Mixer1, Mixer4, Mixer5, Mixer6, Mixer7};
	INT index = (alphaI / 255) |
				(HasAlphaFlag(dest) << 1) |
				(HasAlphaFlag(source) << 2);
	proc[index](dest, source, alphaI);
}
//---------------------------------------------------------------------------

    函数ImageMixer有三个参数,分别为目标图数据结构(借用GDI+的BitmapData结构)指针、源图数据结构指针和源图像素混合比例(不透明度,取值范围为0 - 1)。函数体中的proc数组包括了图像混合的全部8种情况的子函数,而index则按混合比例、目标图Alpha信息和源图Alpha信息组合成子函数调用下标值(Alpha信息在BitmapData结构的保留字段中)。

    当然,在实际的运用中,全部8种情况似乎是多了点,可根据情况进行适当合并取舍,以兼顾代码的复杂度和执行效率。下面是我认为比较合理的精简版ImageMixer函数:

// 图像合成。参数:32位目标数据,32位源数据,不透明度(0 - 1.0)
VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, FLOAT alpha)
{
	INT alphaI = (INT)(alpha * 255);
	if (alphaI <= 0) return;
	if (alphaI > 255) alphaI = 255;

	if (alphaI == 255 && !HasAlphaFlag(source))
		Mixer1(dest, source, alphaI);	// 拷贝合成
	else if (HasAlphaFlag(dest))
		Mixer6(dest, source, alphaI);	// PARGB合成
	else
		Mixer4(dest, source, alphaI);	// ARGB合成
}
//---------------------------------------------------------------------------

    这个ImageMixer函数只保留了3个调用子函数,其中,Mixer6是完全的正常混合模式,即前面公式3的实现;Mixer4为对不含Alpha信息目标图的混合,即在公式4基础上稍稍扩充了的情况;而Mixer1则为拷贝模式。

    下面是采用BCB2010和GDI+调用ImageMixer函数的例子:

//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{

	Gdiplus::Bitmap *dest =  new Gdiplus::Bitmap(L"d:\\xmas_011.png");
	Gdiplus::Bitmap *source =  new Gdiplus::Bitmap(L"d:\\Apple.png");

	Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
	g->DrawImage(dest, 0, 0);
	g->DrawImage(source, dest->GetWidth(), 0);

	BitmapData dst, src;
	LockBitmap(dest, &dst);
	LockBitmap(source, &src);
	ImageMixer(&dst, &src, 0.75);
	UnlockBitmap(source, &src);
	UnlockBitmap(dest, &dst);

	g->DrawImage(dest, dest->GetWidth() << 1, 0);

	delete g;
	delete source;
	delete dest;
}
//---------------------------------------------------------------------------

    下面是运行效果截图:

    左边是目标图,中间是源图,右边是源图按不透明度0.75进行的正常混合。

 

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《C++图像处理 -- 文章索引

  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
1. \学习版Imgcx 打开文件夹Imgcx,执行SetUp,按提示安装Imgcx。该书各个章节所处理的图像都包含在安装后的文件夹…Imgcx\Image里。 2. \专业版Imgc 介绍该书配套的专业版软件Imgc。专业版软件包括该书全部图像处理的C语言源程序以及可执行的Visual C++ 界面源程序,可以满足大学教师、科研人员以及图像处理专业人员的需要。 3. \ImageSys试用版 包括通用图像处理系统ImgeSys的介绍和试用版的安装程序。通用图像处理系统ImageSys是一套大型专业图像处理软件,安装试用版后可以体会专业图像处理软件的魅力。 4. \动态图像处理 介绍北京现代富博科技有限公司的二维、三维动态图像处理软件系统。 有以下数字图像处理方法的实现: 1.一般2值化处理 void Threshold(BYTE *image_in, BYTE *image_out, int xsize, int ysize, int thresh, int mode); 2. 双阈值2值化处理 void Threshold_mid(BYTE *image_in, BYTE *image_out, int xsize, int ysize, int thresh_low, int thresh_high); 3.反转图像 void Reverse_image(BYTE *image_in, BYTE *image_out, int xsize, int ysize); 4.像素分布直方图 void Histgram(BYTE *image, int xsize, int ysize, long hist[256]); 5.计算直方图百分比 void CalHistPercent(long hist[], float hist_radio[], float &max_percent); 6.直方图平滑化 void Hist_smooth(long hist_in[256], long hist_out[256]); 7.直方图图像化(图像宽度大于等于64) void Hist_to_image(long hist[256], BYTE* image_hist, int xsize, int ysize); /*--------------------------提取轮廓-----------------------------*/ 8.1阶微分边沿检出(梯度算子) void Differential(BYTE *image_in, BYTE *image_out, int xsize, int ysize, float amp); 9.2阶微分边沿检出(拉普拉斯算子) void Differential2(BYTE *image_in, BYTE *image_out, int xsize, int ysize, float amp); 10.Prewitt法边沿检出 void Prewitt(BYTE *image_in, BYTE *image_out, int xsize, int ysize, float amp); 11.2值图像的细线化处理 void Thinning(BYTE *image_in, BYTE *image_out, int xsize, int ysize); /*--------------------------消除噪音-----------------------------*/ 12.去噪音处理(移动平均) void Image_smooth(BYTE *image_in, BYTE *image_out, int xsize, int ysize); 13.去噪音处理(中值) void Median(BYTE *image_in, BYTE *image_out, int xsize, int ysize); 14.膨胀 void Dilation(BYTE *image_in, BYTE *image_out, int xsize, int ysize); 15.腐蚀 void Erodible(BYTE *image_in, BYTE *image_out, int xsize, int ysize); /*--------------------------获取清晰图像------------------------*/ 16.亮度n倍 void Brightness_amplify(BYTE *image_in, BYTE *image_out, int xsize, int ysize, float n); 17.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值