阅读提示

    《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合成elseMixer4(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++图像处理 -- 文章索引》。

C++图像处理 -- 图像合成相关推荐

  1. Delphi图像处理 -- 图像合成

    阅读提示:     <Delphi图像处理>系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM.     <C++图像处理>系列以代码清晰,可读性为主,全部使用C ...

  2. python图合并_Python图像处理实现两幅图像合成一幅图像的方法【测试可用】

    本文实例讲述了Python图像处理实现两幅图像合成一幅图像的方法.分享给大家供大家参考,具体如下: 将两幅图像合成一幅图像,是图像处理中常用的一种操作,python图像处理库PIL中提供了多种种将两幅 ...

  3. python两张图合成一张_Python图像处理实现两幅图像合成一幅图像的方法【测试可用】...

    本文实例讲述了Python图像处理实现两幅图像合成一幅图像的方法.分享给大家供大家参考,具体如下: 将两幅图像合成一幅图像,是图像处理中常用的一种操作,python图像处理库PIL中提供了多种种将两幅 ...

  4. 数字图像处理基础(3)--图像合成

    图像合成是指多幅图像之间进行的相互运算.多幅同样大小的图像可以进行差影法运算和逻辑运算.图像相加的一个重要应用就是对同一场景的多幅图像球平均值,它可以有效地降低随机噪声的影响:图像相减可用于去除一幅图 ...

  5. 数字图像处理实验边缘检测、图像分割、图像合成等

    内容要求: 采用图1,Matlab编程实现如下处理结果: 1.转换为灰度图像; 2.将夜空中的星星当作噪声滤除;. 3.将月亮.山脉和湖泊进行图像分割,并填充成 不同的灰度级做标记; 4.计算月亮的面 ...

  6. 自回归解码加速64倍,谷歌提出图像合成新模型MaskGIT

    来源:机器之心 本文约2200字,建议阅读5分钟 一种使用双向 transformer 解码器的新型图像合成模型 MaskGIT,在性能和速度上都获得了大幅改进. 来自谷歌研究院的研究者提出了一种使用 ...

  7. CVPR2022最新106篇论文整理|包含目标检测、动作识别、图像处理等32个方向

    转自:极市平台 分类目录: 检测类 2D目标检测 3D目标检测 伪装目标检测 显著性目标检测 边缘检测 消失点检测 分割类 图像分割 语义分割 视频目标分割 人脸 人脸生成 人脸检测 图像处理 图像复 ...

  8. CVPR‘22 最新106篇论文分方向整理|包含目标检测、动作识别、图像处理等32个方向

    CVPR 2022 已经放榜,本次一共有2067篇论文被接收,接收论文数量相比去年增长了24%.在CVPR2022正式会议召开前,为了让大家更快地获取和学习到计算机视觉前沿技术,极市对CVPR022 ...

  9. 【python图像处理】两幅图像的合成一幅图像(blending two images)

    将两幅图像合成一幅图像,是图像处理中常用的一种操作,python图像处理库PIL中提供了多种种将两幅图像合成一幅图像的接口. 下面我们通过不同的方式,将两图合并成一幅图像. 1.使用Image.ble ...

最新文章

  1. 【青春须早为,岂能长少年】一个初入职场程序员的阶段总结
  2. 今日代码(200714)--主客观求指标权重及求城市得分
  3. linux compress参数,compress命令_Linux compress 命令用法详解:使用Lempress-Ziv编码压缩数据文件...
  4. 写出TREE-MINIMUM 和TREE-MAXIMUM的递归版本(算法导论第三版12.2-2)
  5. php修改js内容,js怎样修改html元素的内容?HTML DOM实现修改内容
  6. php全面获取url地址栏及各种参数
  7. linux mysql 5.0.45_linux 下安装mysql-5.0.45.tar.gz
  8. OpenGL超级宝典 渲染管线(二)
  9. android 调用系统图片编辑,android 调用系统 裁剪 图片
  10. PTA:特立独行的幸福
  11. 监测 Windows 应用行为
  12. php判断学生姓名,【PHP】百家姓姓名判断
  13. python短信验证码_Python如何实现手机验证码
  14. 读书: 枪炮、病菌与钢铁
  15. 推荐系统经典论文文献及业界应用
  16. DEKRA德凯新设立首席数字官、首席运营官,任命两位新成员加入董事会
  17. 计算机 小学数学应用题教学设计,小学数学如何有效地进行应用题教学设计
  18. FHS标准的Linux目录
  19. Simulink串口调试助手的使用----显示电机运行的波形
  20. “科林明伦杯” 哈工大第十届程序设计竞赛【BCDEFHJ签到】

热门文章

  1. 【产品经理】谁是产品经理,产品经理到底要做什么?
  2. uin-app安卓打包流程
  3. 一个列子让你弄懂SpringBoot实现后台框架的搭建
  4. mplayer播放器
  5. 项目经理的日常工作,你了解吗?
  6. Ubuntu_ROS中应用kinect v2笔记
  7. Shell--变量的显示与设置、环境变量、语系变量
  8. C语言编程(恳请c语言高手赐教)
  9. RedHat Linux 9.0安装教程
  10. MAE、MAPE、MSE和RMSE的MATLAB代码