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.rhkb.cn/news/178800.html

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

相关文章

【Git企业开发】第四节.Git的分支管理策略和bug分支

文章目录 前言一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 1.2 企业分支管理策略二、bug分支三、删除临时分支四、总结总结 前言 一、Git的分支管理策略 1.1 Fast forward 模式和--no-ff 模式 通常合并分支时&#xff0c;如果可能&#xff0c;Git 会…

61. 旋转链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;李歘歘的博客 &#x1f3c6; &#x1f33a;每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点&#xff0c;以及职场小菜鸡的生活。&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&am…

基于GEE云平台一种快速修复Landsat影像条带色差的方法

这是之前关于去除遥感影像条带的另一篇文章&#xff0c;因为出版商推迟了一年发布&#xff0c;所以让大家久等了。这篇文章的主要目的是对Landsat系列卫星因为条带拼接或者镶嵌产生的条带来进行的一种在线修复方式。 原文连接 一种快速修复Landsat影像条带色差的方法 题目&a…

ffmpeg命令帮助文档

一&#xff1a;帮助文档的命令格式 ffmpeg -h帮助的基本信息ffmpeg -h long帮助的高级信息ffmpeg -h full帮助的全部信息 ffmpeg的命令使用方式&#xff1a;ffmpeg [options] [[infile options] -i infile] [[outfile options] outfile] 二&#xff1a;将帮助文档输出到文件 …

【IDEA】设置sql提示

第一步&#xff1a;注入SQL语言 1.首先选择任意一条sql语句&#xff0c;右击&#xff0c;选择 ‘显示上下文操作’ 2.选择 ‘注入语言或引用’ 3. 往下翻&#xff0c;找到MySQL 第二步&#xff1a;配置MySQL数据库连接 1.首先点击侧边的数据库&#xff0c;再点击上面的加号 2…

中兴路由器、小米路由器无线信号强度对比

最近小米新推出的路由器小米AX3000T非常火&#xff0c;在网上看到有好多人都在安利&#xff0c;引起了我的兴趣&#xff0c;刚好老家的路由器用了这么久也是时候要换一个了&#xff0c;毕竟我妈老说上网卡??所以我立马就在PDD搞了一台回来&#xff0c;打算和我现在家里用的中…

二叉树进阶 - (C++二叉搜索树的实现)

二叉树进阶 - &#xff08;二叉搜索树的实现&#xff09; 二叉搜索树1. 二叉搜索树概念2. 二叉搜索树操作2.1 二叉搜索树的查找2.2 二叉搜索树的插入2.3 二叉搜索树的删除(重点) 3. 二叉搜索树的(代码)实现 二叉搜索树 1. 二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0…

腾讯云域名备案后,如何解析到华为云服务器Linux宝塔面板

一、购买域名并且进行备案和解析&#xff0c;正常情况下&#xff0c;购买完域名&#xff0c;如果找不到去哪备案&#xff0c;可以在腾讯云上搜索“备案”关键词就会出现了&#xff0c;所以这里不做详细介绍&#xff0c;直接进行步骤提示&#xff1a; 二、申请ssl证书&#xff0…

diffusers-Load adapters

https://huggingface.co/docs/diffusers/main/en/using-diffusers/loading_adaptershttps://huggingface.co/docs/diffusers/main/en/using-diffusers/loading_adapters 有几种训练技术可以个性化扩散模型&#xff0c;生成特定主题的图像或某些风格的图像。每种训练方法都会产…

关于嵌入式rtthread系统与单片机芯片

简介 我估计已经有很久没更新了&#xff0c;近一年都在某个国企里工作&#xff0c;我做的就是嵌入式工程师的岗位&#xff0c;最近才刚刚退出来&#xff0c;想来说说自己的工作使用的软件和系统。 本身进公司的时候&#xff0c;其实做的就是写单片机的板子的程序的工作&#x…

mysql迁移data目录(Linux-Centos)

随着时间的推移&#xff0c;mysql的数据量越越大&#xff0c;使用yum默认安装的目录为系统盘 /var/lib/mysql&#xff0c;现重新挂载了一个硬盘&#xff0c;需要做数据目录的迁移到 /mnt/data/。以解决占用系统盘过高情况。 1.强烈建议这种操作。镜像一个一样的Centos系统&…

基于springboot实现游戏分享网站系统项目【项目源码+论文说明】

基于springboot实现游戏分享网站演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把游戏分享管理与现在网络相结合&#xff0c;利用java技术建设游戏分享网站&#xff0c;实现游戏分享的信息化。则对于进一步提高游戏分享管理发展&#xff0c;丰富游戏分享管理经验能起到…

canvas实现环形进度条

与setTimeout和setInterval不同&#xff0c;requestAnimationFrame不需要设置时间间隔。 效果图 源代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Canvas progress</title> </head&g…

软件测试/测试开发丨ChatGPT能否成为PPT最佳伴侣

点此获取更多相关资料 简介 PPT 已经渗透到我们的日常工作中&#xff0c;无论是工作汇报、商务报告、学术演讲、培训材料都常常要求编写一个正式的 PPT&#xff0c;协助完成一次汇报或一次演讲。PPT相比于传统文本的就是有布局、图片、动画效果等&#xff0c;可以给到观众更好…

Qt 窗口无法移出屏幕

1 使用场景 设计一个缩进/展开widget的效果&#xff0c;抽屉效果。 看到实现的方法有定时器里move窗口&#xff0c;或是使用QPropertyAnimation。 setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint |Qt::X11BypassWindowManagerHint&#xff09;&#xff1b; 记得在移…

linux笔记总结-基本命令

参考&#xff1a; 1.Linux 和Windows比 比较 &#xff08;了解&#xff09; 1. 记住一句经典的话&#xff1a;在 Linux 世界里&#xff0c;一切皆文件 2. Linux目录结构 /lib • 系统开机所需要最基本的动态连接共享库&#xff0c;其作用类似于Windows里的DLL文件。几 乎所有…

企业采用生成式人工智能需要考虑什么

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 最近&#xff0c;各行业采用人工智能生成内容&#xff08;AIGC&#xff09;的趋势显着。这种变革性技术的一些著名实施包括Notion AI…

玩一下Spring Boot

文章目录 1 开发环境1.1 JDK1.2 IntelliJ IDEA2 Spring Boot2.1 创建项目2.2 创建模板页面2.3 创建控制器2.4 启动项目2.5 访问页面1 开发环境 1.1 JDK 安装JDK21 配置环境变量 在命令行查看JDK版本 玩一玩jshell

Windows 11 Home 中启用 Hyper-V

Hyper-V 是微软开发的基于硬件的虚拟机管理程序。它允许用户在 Windows 操作系统之上运行不同操作系统的多个实例。目前&#xff0c;Hyper-V 也支持 Windows、Ubuntu 和其他 Linux 发行版。 如果发现像我这样电脑上启用Hyper-V选项可以按照以下步骤进行操作。 一、新建一个txt…

Mysql系列-索引类型

一 、索引类型别 根据叶子节点的内容分类的索引类型 InnoDB 使用B tree 索引模型&#xff0c;根据叶子节点是否存储数&#xff08;根据叶子节点的内容&#xff09;分为主键索引和非主键索引&#xff1b;非主键索引包括&#xff1a;普通索引、唯一索引、组合索引主键索引的叶子…