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合成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++图像处理 -- 文章索引

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25792.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图像合成与图像融合

这一次我来给大家介绍一下图像合成与融合。 我们经常看到一些很奇妙的PS技术&#xff0c;例如下面这张&#xff0c;它把1928年的一位叫做Frankie Yale的黑帮老大被杀时的照片&#xff0c;与现在这栋房子的照片无缝融合在一起&#xff1a; 还有这张&#xff0c;将1945年柏林街道…

Chrome安装油猴插件详细教程

Chrome安装油猴插件详细教程 一、油猴安装方法 方法一&#xff1a;Google官方商店安装&#xff08;推荐&#xff0c;需要科学上网&#xff09; 方法二&#xff1a;本地安装&#xff08;无需科学上网&#xff0c;不会科学上网的适用&#xff09; 二、安装油猴插件 方法一&am…

Tampermonkey 油猴脚本,chrome插件

tampermonkey脚本是一款在谷歌浏览器上经常使用到的浏览器脚本插件&#xff0c;油猴脚本不仅为用户提供了大量的免费脚本插件外&#xff0c;而且还可以帮助你对这些脚本插件进行统一管理&#xff0c;当有最新版本的脚本推出的时候&#xff0c;它会帮助你进行自动更新。当然&…

手机上使用油猴插件 Tampermonkey

油猴 即 Tampermonkey中文名俗称油猴&#xff0c;是一款浏览器上的扩展&#xff0c;用户可以通过Tampermonkey添加和使用脚本&#xff0c;而脚本是一种可以修改网页JavaScript的程序。 在PC端&#xff0c;可以通过 Chrome浏览器的扩展程序&#xff0c;再结合 脚本 可以实现各种…

Tampermonkey油猴插件安装使用

Tampermonkey油猴插件安装使用 Tampermonkey油猴插件——安装与使用教程 Tampermonkey的crx文件&#xff0c;将下载下来的压缩包解压出来&#xff0c;其中类型为CRX文件 就是接下来需要用到的安装文件 打开浏览器设置&#xff0c;打开扩展程序页面&#xff0c;或者直接搜索Chr…

关于油猴(Tampermonkey)

油猴Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器&#xff0c;它适用于多款常见的浏览器。 前言 那么&#xff0c;油猴具体能干啥&#xff1f; 它可以让我们使用浏览器时更加便捷。 比如说&#xff0c;对于一些网页限制的解除&#xff0c;某度盘文件直接…

Tampermonkey油猴插件——安装与使用教程

安装步骤&#xff1a; Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器&#xff0c;它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。 油猴插件下载网址&#xff1a;Tampermonkey • Home Beta是测试版&#xff1b; Stable是稳定版&am…

安装油猴(Tampermonkey)插件,让你的浏览器更强大

插件是什么 插件及扩展程序&#xff0c;目前大多数软件及程序都提供了安装插件的功能&#xff08;edge&#xff0c;chrome&#xff0c;vscode&#xff0c;pycharm&#xff0c;office等&#xff09;&#xff0c;游戏外挂也是一种插件&#xff0c;这也是使程序功能强大的手段&…

油猴脚本(Tampermonkey)的简介

油猴脚本的使用 什么是油猴脚本 「油猴」可以通过安装各类脚本对网站进行定制。当然伟大的脚本面向的是所有上网者&#xff0c;所以借由各位大神的脚本我们能实现更多更强大的功能&#xff0c;例如&#xff1a; 直接下载百度网盘文件(全速)重新定制繁杂的微博页面去掉视频播放…

ChatGpt-2 对话单页功能实现vue3+elementplus

接口可参照CHATGPT.NET自行实现 <script setup> import { RouterLink, RouterView } from "vue-router"; import HelloWorld from "./components/HelloWorld.vue"; import { MoreFilled ,CircleCheck} from "element-plus/icons-vue"; …

超乎你的想象AI绘画

(https://img-blog.csdnimg.cn/d1e19127400749818fde963723a194a9.png)(https://img-blog.csdnimg.cn/b8535f2f0b4f402ea2003ec8f352d11e.png)

【5月比赛合集】80场可报名的数据挖掘大奖赛,任君挑选!

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 Kaggle&#xff08;10场比赛&#xff09;阿里天池&#xff08…

#世界杯征文活动 #Python #2022卡塔尔世界杯 #FIFA World Cup Qatar 2022 # 机器学习 #数据分析 用Python预测世界杯决赛

今天2022年11月29日的凌晨&#xff0c;葡萄牙队2比0获击败乌拉圭队&#xff01;葡萄牙队提前晋级16强&#xff0c;你熬夜看这场比赛了吗&#xff1f; 今天晚上11点荷兰将对战卡塔尔&#xff0c;NED(荷兰&#xff09;有望取胜&#xff01; 现在让我们通过Python数据分析以及机器…

Kaggle时间序列预测相关比赛以及代码

1.预测商品销量比赛 代码1&#xff1a;使用LSTM https://www.kaggle.com/code/sanjaylalwani/lstm-predict-sales 代码2&#xff1a;传统方法AR, MA and ARMA models https://www.kaggle.com/code/jagangupta/time-series-basics-exploring-traditional-ts 代码3&#xff1a;F…

【6月比赛合集】103场可报名的数据挖掘大奖赛,任君挑选!

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 Kaggle&#xff08;7场比赛&#xff09;阿里天池&#xff08;…

Python大数据预测NBA比赛结果

大数据带给我们的是一种生活、工作和思维上的大变革&#xff0c; 当下&#xff0c;NBA季后赛打的正如火如荼&#xff0c;各位球迷肯定希望能提前预测自己喜欢的球队能不能杀入总决赛&#xff0c;拿到总冠军。 今晚&#xff0c;黑马程序员李老师&#xff0c;将为大家带来免费公开…

微信支付兑换今日好礼不再累积提现免费额度;ChatGPT 上线最强应用「代码解释器」;GCC 10.5 发布|极客头条

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&…

新手教学,如何快速地画一个PCB板子

楼主是一名在校大学生&#xff0c;接触画板有大半年了&#xff0c;也画了几十个板&#xff0c;现在想总结一下&#xff0c;发表一下自己的经验。希望各位工程师看后有错的地方提出来。 1.首先&#xff0c;拿到一个项目&#xff0c;不要急着去布线。楼主认为可以去看看原理图&a…

硬件学习 软件Cadence day04 PCB 封装绘制

1.文章内容&#xff1a; 1. 贴片式电容 PCB 封装绘制 &#xff08;型号 c0603 &#xff09; 2. 贴片式电阻 PCB 封装绘制 &#xff08;型号 r0603 &#xff09; 3. 安规式电容 PCB 封装绘制 &#xff08;这个就是 有一个电容&#xff0c;插入一个搞好的孔里面 …

Altium Designer下PCB板的画图大法

Altium Designer下PCB板的画图大法 1、封装元件2、导入PCB板3、布局4、布线5、覆铜6、电气规则检查7、生成BOM表和网络表8、输出Gerber光绘文件Gerber文件钻孔文件 修改错误提示总结 1、封装元件 在导入PCB板之前要封装元件&#xff0c;点击元件弹出右侧界面&#xff0c;选中其…