OpenCV 笔记(4):图像的算术运算、逻辑运算

Part11.  图像的算术运算

图像的本质是一个矩阵,所以可以对它进行一些常见的算术运算,例如加、减、乘、除、平方根、对数、绝对值等等。除此之外,还可以对图像进行逻辑运算和几何变换。

我们先从简单的图像加、减、逻辑运算开始介绍。后续会有专门的内容介绍图像的几何变换等。

11.1 图像加法

图像的加法是将两个大小、类型相同的图像按照逐个像素进行相加,最后得到一个新的图像。

图像的加、减、乘、除运算,都是两个大小、类型相同的图像进行运算。

1.1.1 加法的例子

图像相加的公式:

也可以使用:dst += src1,其中 += 是 C++ 可重载的运算符。

举个简单的例子:

Mat a = imread(".../cat.jpg");// 加载了一张猫的图片
imshow("a", a);Mat b = Mat(Size(a.cols,a.rows),a.type(), Scalar(0,0,255));// 生成跟a大小类型一样,红色的图像Mat c;
cv::add(a,b,c);// 将 a、b 相加,结果为c
imshow("c", c);
e652cd2c39032b9a33d8980d6cbfc7f3.jpeg
add.png

上述代码中 Mat 对象 c 是 Mat 对象 a、b 相加得到的产物。如果将 b 改成白色也就是 Scalar(255,255,255)。那么 c 会变成什么呢?答案依然是白色。因为加法是像素相加,如果两个像素点超出255,那么依旧会变成255。

1.1.2 实现 add() 函数的功能

为了解释上面的问题,我们尝试自己实现一个 add 函数的功能。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像
imshow("a", a);Mat b = Mat(Size(a.cols,a.rows),a.type(), Scalar(0,0,255));int h = a.rows; // 图像 a 的高
int w = a.cols; // 图像 a 的宽Mat c = Mat::zeros(a.size(), a.type());
for (int row = 0; row < h; row++)
{for (int col = 0; col < w; col++){Vec3b p1 = a.at<Vec3b>(row, col);Vec3b p2 = b.at<Vec3b>(row, col);c.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);c.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);c.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(p1[2] + p2[2]);}
}imshow("c", c);

通过2层for循环遍历 a、b 图像的每个像素点,并将结果相加赋值给 c 图像对应的像素点。在相加的时候,使用了 saturate_cast() 函数。

saturate_cast() 是一个模版函数,它的作用是防止溢出。它支持 uchar、short、int、float、double 等各种类型。

对于 uchar 类型,如果像素值超过255,使用 saturate_cast() 函数后它的值变为255。这也正好解释了,如果 b 是白色,那么最终得到的 c 对象也会是白色。

1.1.3 使用 copyTo() 函数实现的图像叠加

前面的文章我们曾介绍过 copyTo() 函数,它可以将 Mat 对象拷贝到另一个 Mat 对象上。

现在再来回顾一下它的使用

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像Mat b = imread(".../leaf.png"); // 加载一张小尺寸的树叶的图像Mat roi = a(Rect(0,0,b.cols,b.rows));b.copyTo(roi);imshow("result", a);

在上述代码中, roi 对象是从 a 对象中截取一块区域,并且该区域跟 b 对象大小一样。由于提取 roi 的操作是浅拷贝,将 b 对象复制到 roi 对象之后,就会改变 a 对象本身。

下面是执行的结果:

2c569c933cc58502bd1382c73856cddc.jpeg
copyTo.png

因此,可以借助 copyTo() 函数来实现图像的叠加。

21.2 图像的线性混合(linear blending)

图像的线性混合公式:$$dst = src1alpha + src2beta + gamma$$

其中,alpha、beta 分别表示图像1和图像2的权重,gamma 是亮度调节量。当 alpha = beta = 1 且 gamma = 0 时,表示两个图像的相加。

进行线性混合的两个图像,也必须大小和类型一致。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像Mat b = imread(".../chinese_flag.png"); // 加载五星红旗的图像resize(a, a,Size(b.cols,b.rows));// 缩放a的大小,跟b保持一致Mat dst;
addWeighted(a, 0.5, b, 0.5,0, dst);imshow("dst", dst);

由于图像 a、b 大小不一样,因此在线性混合之前需要用 resize() 函数将图像 a 的大小按照图像 b 的大小进行缩放。

02db30447a3998ea6c3028b4dd52adcd.jpeg
linear_lending.png

上面的代码,将猫和五星红旗完成了线性混合。如果还想尝试做一个国庆版本的渐变头像,则需要离红旗越近,红旗的权重越大。

我们可以这样写代码:

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像Mat flag = imread(".../chinese_flag.png");
int flag_width = flag.cols;
int flag_height = flag.rows;Mat dst;resize(a, dst, Size(flag_width, flag_height));int radius = 0;
if (flag_width > flag_height) {radius = flag_width;
} else {radius = flag_height;
}for (int i=0; i < dst.rows; i++) {for (int j=0; j < dst.cols; j++) {int distance = std::sqrt(i*i+j*j);double alpha;if (distance > radius) {alpha =  1;}  else {alpha = (double) distance / radius;}double beta = 1 - alpha;Vec3b v1 = dst.at<Vec3b>(i, j);dst.at<Vec3b>(i, j)[0]= alpha * v1[0] + beta * flag.at<Vec3b>(i, j)[0];dst.at<Vec3b>(i, j)[1]= alpha * v1[1] + beta * flag.at<Vec3b>(i, j)[1];dst.at<Vec3b>(i, j)[2]= alpha * v1[2] + beta * flag.at<Vec3b>(i, j)[2];}
}imshow("dst", dst);
b5c58c05ccebdee7f367b9164b981ae0.jpeg
avatar.png

31.3 图像减法

图像相减是两个图像按照逐个像素进行相减,图像相减可以检测出两个图像的差异。利用这个差异可以做各种检测,因此图像减法在很多领域都有实际的用途。

图像相减的公式:

也可以使用:dst -= src1,其中 -= 是 C++ 可重载的运算符。

举个简单的例子:

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像int width = a.cols;
int height = a.rows;Mat b = Mat(Size(width,height), a.type(),Scalar(0,0,0));
circle(b, Point(width/2, height/2), 600, Scalar(255,255,255), -1);Mat dst;
subtract(a,b,dst);imshow("dst", dst);
ab81bd140cefb86fb98759a9a6c67dae.jpeg
subtract.png

上述执行的结果是图像 a 减去图像 b 之后得到的结果,将中间的猫“抠掉”了。如果只想要中间的猫,而不要背景该怎么做呢?本文后续会用 bitwise_and 运算来获取。

再举个例子,对加载图像进行高斯模糊,然后用原图减去高斯模糊后的图,会得到两张图像的差异。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像
imshow("a",a);Mat b;
GaussianBlur(a, b,Size(15,15),0,0);
imshow("b",b);Mat dst;
subtract(a,b,dst);
imshow("dst",dst);
15eab01ffd75bb6cb5a6b10e2c8c89db.jpeg
diff.png

图像的减法介绍完之后,图像的乘法(multiply)、除法(divide)、差的绝对值(absdiff)的用法都很类似,在实际工作中也经常会用到。特别是 absdiff() 函数,用公式表示:

可以用它获取 差分图,经常应用在视频分析中。

Part22. 图像的逻辑运算

42.1 掩模的基础知识

在介绍图像的逻辑运算之前,再来回顾一下掩模(mask)的知识,因为 OpenCV 很多的函数中都会用到 mask 这个参数。

图像的算术运算、逻辑运算都支持 mask。

掩模是小于或等于源图像的单通道矩阵,掩模中的值分为 0 和非 0。

图像掩模是用选定的图像、图形或物体,对处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程。

掩模的作用:

  • 提取 ROI

  • 屏蔽作用

  • 提取结果特征

  • 制作特殊形状的图像

掩模的生成方式有很多种。

我们可以自己创建一个,将图像减法的第一个例子图像 b 稍微改一下即可。因为 mask 是单通道的矩阵。

Mat mask = Mat(Size(width,height), CV_8UC1,Scalar(0,0,0));
circle(mask, Point(width/2, height/2), 600, Scalar(255,255,255), -1);

我们也可以通过图像二值化阈值分割来提取 mask,例如:

Mat src = imread(".../leaf.png"); // 加载一张小尺寸的树叶的图像
imshow("src",src);Mat gray;
cvtColor(src,gray,COLOR_BGR2GRAY);Mat mask;
threshold(gray, mask, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);imshow("mask",mask);
f8b06902fdb95bc438ddf363532a8033.jpeg
mask.png

图像二值化的相关内容后续文章会专门介绍。总之,mask 的制作有很多方式。

52.2 逻辑运算

两个图像可以进行与、或、异或等逻辑运算。下面是逻辑操作的真值表:

aba AND ba OR ba XOR bNOT a
000001
010111
100110
111100

其中,

  • 与运算的原理:如果 a、b 两个值有0,则与的结果为0;如果 a、b 全为1,则与的结果为1。

  • 或运算的原理:如果 a、b 两个值有1,则或的结果为1;如果 a、b 全为0,则与或的结果为0。

  • 异或运算的原理:如果 a、b 两个值不相同,则异或结果为1;如果 a、b 两个值相同,则异或结果为0。

  • 非运算的原理:如果 a 的值为1,则非运算的结果为0;如果 a 的值为0,则非运算的结果为1。

图像的逻辑运算也需要两个大小、类型相同的图像才能进行运算。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像Mat b = Mat(Size(a.cols,a.rows),a.type(), Scalar(0,0,255));// 生成跟a大小类型一样,红色的图像Mat dst1,dst2,dst3,dst4;
bitwise_and(a,b,dst1);
bitwise_or(a,b,dst2);
bitwise_xor(a,b,dst3);
bitwise_not(a,dst4);imshow("bitwise_and", dst1);
imshow("bitwise_or", dst2);
imshow("bitwise_xor", dst3);
imshow("bitwise_not", dst4);
92cff44b254575d5858af9a7b2dc1069.jpeg
bitwise_op.png

OpenCV 中的逻辑与、或、异或、非运算对应的函数分别是 bitwise_and、bitwise_or、bitwise_xor、bitwise_not。上图也分别展示了这些函数的执行结果。

现在我们来回答一下前面的问题,如何只“抠掉”中间的猫?答案是只要使用 bitwise_and 函数即可。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像int width = a.cols;
int height = a.rows;Mat b = Mat(Size(width,height), a.type(),Scalar(0,0,0));
circle(b, Point(width/2, height/2), 600, Scalar(255,255,255), -1);Mat dst;
bitwise_and(a,b,dst);
imshow("dst", dst);
cfbcdfe246fa305478844a28507a2989.jpeg
bitwise_and.png

62.3 利用 mask 进行图像融合

对刚才的代码稍微改动一下,把图像 b 的类型改成 CV_8UC1 之后,并改名成 mask。bitwise_and 函数的使用也稍作调整。当 mask 参与 bitwise_and 运算的时候,执行的结果跟刚才是一致的。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像int width = a.cols;
int height = a.rows;Mat mask = Mat(Size(width,height), CV_8UC1,Scalar(0,0,0));
circle(mask, Point(width/2, height/2), 600, Scalar(255,255,255), -1);Mat dst;
bitwise_and(a,a, dst,mask);
imshow("dst", dst);

因为,当 bitwise_and 函数使用 mask 参数时,该运算只会在掩模值非空的像素点执行。所以可以用来去除背景提取 ROI。

利用 mask 进行“逻辑与”运算,即掩膜图像白色区域是对需要处理图像像素的保留,黑色区域则是对需要处理图像像素的剔除,其余逻辑操作原理类似只是效果不同而已。

之前使用 copyTo() 函数实现的图像叠加生成的图片,效果并不理想,因为树叶不是透明的。

下面,尝试一下将两张图像完美的融合。

Mat a = imread(".../cat.jpg"); // 加载 cat 的图像Mat b = imread(".../leaf.png"); // 加载一张小尺寸的树叶的图像Mat b2gray;
cvtColor(b,b2gray,COLOR_BGR2GRAY); // 对 b 转换成灰度图像
imshow("b2gray", b2gray);Mat mask,mask_inv;
threshold(b2gray, mask, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);// 二值分割获取 mask
imshow("mask", mask);bitwise_not(mask,mask_inv);
imshow("mask_inv", mask_inv);Mat roi = a(Rect(0,0,b.cols,b.rows));
Mat fg,bg;
bitwise_and(roi,roi,bg, mask_inv);
imshow("bg", bg); // 提取 roi 的背景
bitwise_and(b,b,fg,mask);
imshow("fg", fg); // 提取 b 的前景Mat dst;
add(bg,fg,dst);
dst.copyTo(roi);imshow("result", a);

首先加载两张图像,分别为 a、b 对象。

将 b 对象转换成灰度图像,然后通过二值分割获取 mask,以及对 mask 进行非运算获得 mask_inv。

对 a 对象进行截取 roi 的操作,roi 的大小跟 b 对象一致。

然后分别用 与运算 提取 roi 的背景和 b 对象的前景。将两者相加,并将结果拷贝到 roi 对象上。最后,我们可以看到两张图像完美融合的结果。

下面的几张图分别展示了代码中各个阶段生成的对象,以及最后的结果。

e71b5480bcd394a135c84945cd0ae50f.jpeg
step1.png
4fa4e3ed656152439725791737731ace.jpeg
step2.png
3ee7cc086c684619ad80dbefbb72d40f.jpeg
result.png

Part33. 总结

本文分成两个部分。第一部分介绍了图像的算术运算,主要是介绍了图像加法、减法以及它们的实现原理和使用场景,还介绍了图像的线性混合。

第二部分介绍了图像的逻辑运算,回顾了 mask 的用途,以及如何在 bitwise_and 函数中使用 mask。

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

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

相关文章

文件fd.

共识原理: 1.文件 内容 属性 2.文件分为打开的文件 和 没打开的文件 3.打开的文件: 谁打开? 进程! ----本质是研究进程和文件的关系! 根据冯诺依曼原理&#xff0c;文件被打开&#xff0c;必须先被加载到内存&#xff01;不然CPU怎么访问它 那么是文件内容被加载&#xff…

系列七、Mybatis的二级缓存

一、概述 Mybatis的二级缓存是多个sqlSession共享的&#xff0c;其作用域是mapper的同一个namespace&#xff0c;不同的sqlSession执行两次相同的查询&#xff0c;mybatis会将第一次执行完的数据放到二级缓存中&#xff08;坑&#xff1a;需要执行close操作&#xff0c;要不然不…

通过Python脚本支持OC代码重构实践(二):数据项提供模块接入数据通路的代码生成

作者 | 刘俊启 导读 在软件开发中&#xff0c;经常会遇到一些代码问题&#xff0c;例如逻辑结构复杂、依赖关系混乱、代码冗余、不易读懂的命名等。这些问题可能导致代码的可维护性下降&#xff0c;增加维护成本&#xff0c;同时也会影响到开发效率。这时通常通过重构的方式对已…

创建ABAP数据库表和ABAP字典对象-使用基本类型增加账号字段03

新增字段 现在你将增加字段&#xff1a;account_number,基于基本类型 1.输入以下内容(包括句点)&#xff0c;然后选择“代码补全”(Ctrl空格): key account_number : abap.2.从下拉列表中选择numc(len)并指定len为8。另外&#xff0c;指定这个关键字段为not null: key accoun…

频频让“智造大佬”们追加投资,长沙凭啥?

文 | 智能相对论 作者 | 范柔丝 自2021年长沙将每年11月1日确定为长沙“企业家日”后&#xff0c;今年已到了第三个长沙“企业家日”。 在完美闭幕的2023年第三届长沙“企业家日”活动中&#xff0c;长沙发布了民企研发投入增量10强、湘商回归标志性项目10强、湘商回归突出贡…

sql server数据库跟踪——SQL Server Profiler解析

工具&#xff1a; SQL Server Profiler这个工具是SQL Server数据库自带的语句执行跟踪工具&#xff0c;常使用于分析软件修改数据库时所执行的语句&#xff0c;适合用来研究软件运行数据库的原理。 打开方式&#xff1a; 本机安装了SQL server的话&#xff0c;都是自带的。直接…

c++设计模式二:原型模式

使用场景&#xff1a;当需要构建多个相同的类对象时&#xff0c;而且该类对象结构较为复杂&#xff0c;如果每个都重新组织构建会很麻烦。 其实&#xff0c;就是写一个拷贝构造函数&#xff0c;或者写一个拷贝每个成员变量的clone()方法。 举例说明&#xff1a;比如一个相亲网站…

R语言如何写一个爬虫代码模版

R语言爬虫是利用R语言中的网络爬虫包&#xff0c;如XML、RCurl、rvest等&#xff0c;批量自动将网页的内容抓取下来。在进行R语言爬虫之前&#xff0c;需要了解HTML、XML、JSON等网页语言&#xff0c;因为正是通过这些语言我们才能在网页中提取数据。 在爬虫过程中&#xff0c;…

阿里云无影升级2.0 云电脑解决方案时代到来

10月31日&#xff0c;杭州云栖大会上&#xff0c;阿里云宣布无影全新升级2.0&#xff1a;从云电脑到云上解决方案&#xff0c;帮助中小企业更便捷地构建云上办公&#xff0c;并开放无影产品及解决方案能力&#xff0c;为生态合作伙伴提供企业云平台&#xff0c;帮助其打造定制化…

无需服务器内网穿透Windows下快速搭建个人WEB项目

&#x1f4d1;前言 本文主要是windows下内网穿透文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ 参考自&#xff1a;Windows搭建web站点&#xff1a;免费内网穿透发布至公网 &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首…

[C++ ]:5.类和对象中(运算符重载补充)+ 类和对象下(初始化列表)

类和对象中&#xff08;运算符重载补充&#xff09; 类和对象下&#xff08;初始化列表&#xff09; 一.运算符重载补充&#xff1a;1.流插入运算符&#xff1a;1.考虑到隐含的参数指针&#xff1a;2.进行优化&#xff01;2-1&#xff1a;解决办法&#xff1a;友元2-2&#xff…

Websocket传递JWT令牌

在访问带有[Authorize]的方法的时候&#xff0c;需要前端通过自定义报文头的形式将JWT令牌传递给后端进行验证&#xff0c;否则是不能访问带有[Authorize]的方法。 [Authorize]是用于限制对web应用程序中某些操作或控制器的访问。当[授权]属性应用于操作或控制器时&#xff0c;…

妙手ERP本期功能更新:TikTok支持自定义SKU规格、Temu支持创建尺码表、仓库库存可同步至Shopee全球产品 ......

为了给卖家朋友带来更好的使用体验&#xff0c;更高效地运营跨境店铺&#xff0c;妙手ERP在上周优化了以下多项功能。 01、产品模块优化 全平台 - 插件采集支持批量采集速卖通产品 - 店铺互踩、店铺产品增加销量筛选项 - 公用采集箱支持编辑产品父SKU、AI生成、SKU规格、尺码…

批量压缩图片大小的绝妙技巧,让你的图片更轻盈

在制作幻灯片演示时&#xff0c;经常需要插入图片作为视觉辅助&#xff0c;通过批量缩小图片大小&#xff0c;可以减小演示文件的大小&#xff0c;方便共享和传输。 那么怎么将图片缩小成了问题的关键&#xff0c;市面上不少方法都是需要通过下载软件来处理图片的&#xff0c;…

Chatgpt网页版根据关键词自动批量写原创文章软件【可多开自动登录切换gpt账号】

Chatgpt网页版根据关键词自动批量写原创文章软件介绍&#xff1a; 1、需要放入GPT账号和密码放入在账号库.txt里&#xff0c;可以放入多组账号密码&#xff0c;账号切换轮流使用。 2、可以自定义回答指令&#xff0c;也可多个回答指令随机切换。 3、可以给关键词加双标题&…

Django中的FBV和CBV

一、两者的区别 1、在我们日常学习Django中&#xff0c;都是用的FBV&#xff08;function base views&#xff09;方式&#xff0c;就是在视图中用函数处理各种请求。而CBV&#xff08;class base view&#xff09;则是通过类来处理请求。 2、Python是一个面向对象的编程语言…

java强转实验

不存在继承关系时&#xff0c;强转会出现编译时异常。即&#xff1a;无法将两个不同类型的对象做转换 当存在继承关系时&#xff0c;强转正常。备注&#xff1a;同名字段&#xff0c;类型一致&#xff0c;可以强转替代getset。同名字段&#xff0c;类型不一致&#xff0c;强转会…

HarmonyOS数据管理与应用数据持久化(一)

一. 数据管理概述 功能介绍 数据管理为开发者提供数据存储、数据管理能力&#xff0c;比如联系人应用数据可以保存到数据库中&#xff0c;提供数据库的安全、可靠等管理机制。 数据存储&#xff1a;提供通用数据持久化能力&#xff0c;根据数据特点&#xff0c;分为用户首选项、…

Spring Cloud应用- Eureka原理、搭建

初期对Spring Cloud的学习以应用搭建为主&#xff0c;所以内容不会太枯燥。 一直以来&#xff0c;自以为Spring全家桶的学习中&#xff0c;Spring framework是基础中的基础&#xff0c;部分内容也还是必须要读源码去理解底层原理&#xff0c;SpringMVC、SpringBoot&#xff0c…

11.2树的高度,表达式树,非递归遍历,层序遍历,奇偶树

课上 前序&#xff0c;根左右 中序&#xff0c;左根右 若前序中序相同&#xff0c;则树都没有左节点 求树的高度 表达式树 中缀表达式树 主要考虑括号问题 这个就是考虑递归底层&#xff0c;要结束时的情形&#xff1b;以及根节点的情形&#xff1b; 由于表达式树是满树&…