OpenCV实战(5)——图像运算详解

news2024/11/27 22:26:10

OpenCV实战(5)——图像运算详解

    • 0. 前言
    • 1. 图像基本运算
    • 2. 重载图像运算符
      • 2.1 加法运算符重载
      • 2.2 分割图像通道
      • 2.3 完整代码
    • 3. 图像重映射
      • 3.1 OpenCV 重映射函数
      • 3.2 完整代码
    • 小结
    • 系列链接

0. 前言

图像可以以不同的方式进行组合,因为它们是正则矩阵,所以它们可以可以执行加、减、乘或除等运算。 OpenCV 提供了各种图像算术运算符,本节将学习如何使用不同的图像运算符。我们会加载二张图像,并使用算术运算符这两张图像结合起来。

1. 图像基本运算

在本节中,我们对两个图像执行加法运算,这在要创建一些特殊效果或在图像上叠加信息时非常有用。

(1) 调用 cv::add 函数,或者更准确地说,调用 cv::addWeighted 函数,因为我们需要求取图像的加权和:

cv::addWeighted(image1,0.7,image2,0.9,0.,result);

该操作会生成一个新图像,如下图所示:

基本图像运算

所有 OpenCV 算术运算函数的工作方式都相同。提供了两个输入,第三个参数指定了输出。在某些情况下,可以指定在操作中用作标量乘数的权重,可以使用多种形式调用这些函数,例如,使用 cv::add 函数:

// c[i]= a[i]+b[i];
cv::add(imageA,imageB,resultC);
// c[i]= a[i]+k;
cv::add(imageA,cv::Scalar(k),resultC);
// c[i]= k1*a[1]+k2*b[i]+k3;
cv::addWeighted(imageA,k1,imageB,k2,k3,resultC);
// c[i]= k*a[1]+b[i];
cv::scaleAdd(imageA,k,imageB,resultC);
对于某些函数,我们还可以指定掩码进行调用:
// (mask[i]) c[i]= a[i]+b[i];
cv::add(imageA,imageB,resultC,mask);

如果应用掩码,则仅对掩码值不为空的像素执行操作(掩码必须是单通道),除了 cv::add 函数外,还可以调用 cv::subtractcv::absdiffcv::multiplycv::divide 函数观察不同 OpenCV 运算效果,除此之外,还可以使用位运算符(应用于像素二进制表示的每个单独位的运算符): cv::bitwise_andcv::bitwise_orcv::bitwise_xorcv::bitwise_not,另外,还有用于查找最大或最小像素值的 cv::mincv::max 运算符。
在进行算数运算时,使用 cv::saturate_cast 函数用于确保结果保持在定义的像素值域内(即避免像素值上溢出或下溢出)。执行运算的图像必须具有相同的尺寸大小和类型(如果与输入图像尺寸大小不匹配,将重新分配输出图像)。此外,由于操作是按元素执行的,因此可以将输入图像之一用作输出。
还有一些运算符可以接受单个图像作为输入——cv::sqrtcv::powcv::abscv::cuberootcv::expcv::log 等。也可以在 cv::Mat 实例或 cv::Mat 实例的各个通道上使用常用的 C++ 算术运算符。

2. 重载图像运算符

2.1 加法运算符重载

大多数算术函数都在 OpenCV 中重载了相应的运算符。 例如,对 cv::addWeighted 的调用可以使用以下形式:

result= 0.7*image1+0.9*image2;

使用以上代码更加紧凑,也更易于阅读,这种计算加权和的方式与 cv::addWeighted(image1,0.7,image2,0.9,0.,result); 是等价的,且 cv::saturate_cast 函数在这两种情况下都会被调用。
大多数 C++ 运算符都已重载,其中包括按位运算符 &|^~minmaxabs 函数等;比较运算符 <<===!=>>=,它们返回一个 8 位二值图像。此外,还包括 m1*m2 矩阵乘法、m1.inv() 矩阵求逆、m1.t() 转置、m1.determinant() 行列式(其中 m1m2 都是 cv::Mat 实例)、v1.norm() 向量范数、v1.cross(v2) 叉积、v1.dot(v2) 点积等等。除此之外,我们还可以定义相应的赋值运算符(例如 += 运算符)。
在高效图像扫描循环一节中,我们使用了颜色减少函数,该函数使用循环处理图像,该循环扫描图像像素以对其执行一些算术运算。利用重载运算符,该函数可以简单地使用输入图像上的算术运算符来重写:

image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);

由于我们需要处理彩色图像,因此使用 cv::Scalar。统计使用此方法的执行时间,可以看到使用图像运算符使代码变得更简单,且程序的编写也更加高效,因此在大多数情况下应当考虑使用算术运算符。

2.2 分割图像通道

有时我们希望单独处理图像的不同通道。例如,我们可能只想对图像的一个通道执行操作。我们当然可以在图像扫描循环中实现这一操作。但除此之外,也可以使用 cv::split 函数将彩色图像的三个通道复制到三个不同的 cv::Mat 实例中。假设我们只想将第二个图像添加到蓝色通道:

//  创建3个图像的矢量 
std::vector<cv::Mat> planes;
// 分割图像通道
cv::split(image1,planes);
// 将image2图像添加到蓝色通道上
planes[0] += image2;
// 合并三个通道
cv::merge(planes,result);
cv::merge 函数是cv::split函数的逆操作,它可以利用三个单通道图像创建一个彩色图像。

2.3 完整代码

完整代码如下所示:

#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

int main() {
    cv::Mat image1;
    cv::Mat image2;
    // 读取图像
    image1 = cv::imread("1.png");
    image2 = cv::imread("rain.jpg");
    cv::resize(image2, image2, cv::Size(image1.cols, image1.rows));
    if (!image1.data) return 0;
    if (!image2.data) return 0;
    cv::namedWindow("Image 1");
    cv::imshow("Image 1", image1);
    cv::namedWindow("Image 2");
    cv::imshow("Image 2", image2);
    cv::Mat result;
    // 对两个图像执行相加运算
    cv::addWeighted(image1, 0.7, image2, 0.9, 0.0, result);
    cv::namedWindow("result");
    cv::imshow("result", result);
    //使用重载运算符
    result = 0.7*image1+0.9*image2;
    cv::namedWindow("Result with operators");
    cv::imshow("Result with operators", result);
    image2 = cv::imread("rain.jpg", 0);
    cv::resize(image2, image2, cv::Size(image1.cols, image1.rows));
    // 分割彩色图像通道
    std::vector<cv::Mat> planes;
    cv::split(image1, planes);
    planes[0] += image2;
    // 合并通道
    cv::merge(planes, result);
    cv::namedWindow("Result on blue channel");
    cv::imshow("Result on blue channel", result);
    cv::waitKey();
    return 0;
}

3. 图像重映射

我们已经学习了如何读取和修改图像的像素值。最后,我们将学习如何通过移动像素来修改图像,在此过程不会改变像素值;而是将每个像素的位置重新映射到新位置。这对于在图像上创建特殊效果或校正由镜头等引起的图像失真时非常有用。

3.1 OpenCV 重映射函数

为了使用 OpenCV 重映射函数,首先需定义要在重映射过程中使用的映射,然后,将此映射应用于输入图像。显然,定义映射的方式将决定将产生的效果。在本节中,我们定义转换函数用于在图像上创建波浪效果。
重新映射的目的是生成一个新图像,其中像素的位置发生了变化。为了构建这个新图像,我们需要知道目标图像中每个像素的原始位置在源图像中的位置。因此,所需的映射函数需要根据原始像素位置计算新像素位置;反向映射 (backward mapping) 则描述了新图像的像素如何映射回原始图像。在 OpenCV 中,反向映射使用两个映射来描述——一个用于 x 坐标,一个用于 y 坐标,它们都由浮点 cv::Mat 实例表示:

cv::Mat srcX(image.rows,image.cols,CV_32F); // x-map
cv::Mat srcY(image.rows,image.cols,CV_32F); // y-map

这些矩阵的大小将定义目标图像的大小,然后可以使用以下代码在源图像中读取目标图像的 (i, j) 像素值:

(srcX.at<float>(i,j), srcY.at<float>(i,j))

(1) 用两个参数创建 wave 函数获得波浪效果,一个输入图像和一个输出图像结果:

// 通过创建波浪效果重新映射图像
void wave(const cv::Mat &image, cv::Mat &result)

(2) 创建两个映射变量,一个用于 x 位置,另一个用于 y 位置,用于存储重新映射的新位置:

cv::Mat srcX(image.rows,image.cols,CV_32F);
cv::Mat srcY(image.rows,image.cols,CV_32F);

(3) 遍历每个像素:

// 创建映射
for (int i=0; i<image.rows; i++) {
for (int j=0; j<image.cols; j++){

(4) 将实际位置 j 分配给映射 x 位置:

        srcX.at<float>(i,j) = j;

(5) 使用正弦函数计算 y 的新位置,使用 x 值作为 sin 的输入,然后关闭计算映射变量的循环:

        srcY.at<float>(i,j) = i+5*sin(j/10.0);
    }
}

(6) 将重映射函数应用于输入图像:

cv::remap(image, result, srcX, srcY, cv::INTER_LINEAR);

得到的结果如下图所示:

重映射

除了波浪效果外,我们也可以创建其他重映射效果。例如,图像翻转效果可以通过以下映射创建:

// 创建映射
for (int i=0; i<image.rows; i++) {
    for (int j=0; j<image.cols; j++) {
        // 水平翻转
        srcX.at<float>(i,j)= image.cols-j-1;
        srcY.at<float>(i,j)= i;
    }
}

要生成结果图像,只需调用 OpenCV remap 函数:

// 应用映射
cv::remap(image,    // 源图像
    result,         // 目标图像
    scrX,           // x映射
    srcY,           // y映射
    cv::INTER_LINEAR, // 插值方法
)

这两个映射包含浮点值。因此,目标图像中的像素可以映射回非整数值(即像素之间的位置),这样我们就可以选择映射函数。例如,在我们的重映射函数中,我们使用正弦函数来定义我们的变换,这也意味着我们必须在真实像素之间插入新像素。有多种不同方法执行像素插值,remap 函数的最后一个参数允许我们选择像素插值方法。

3.2 完整代码

完整代码如下所示:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <math.h>

// 重映射
void wave(const cv::Mat &image, cv::Mat &result) {
    // 映射函数
    cv::Mat srcX(image.rows, image.cols, CV_32F);
    cv::Mat srcY(image.rows, image.cols, CV_32F);
    // 创建映射
    for (int i=0; i<image.rows; i++) {
        for (int j=0; j<image.cols; j++) {
            srcX.at<float>(i,j) = j;
            srcY.at<float>(i,j) = i+3*sin(j/6.0);
            //水平翻转
            //srcX.at<float>(i,j) = image.cols-j-1;
            //srcY.at<float>(i,j) = i;
        }
    }
    // 应用映射
    cv::remap(image, // 源图像
                result, // 目标图像
                srcX,
                srcY,
                cv::INTER_LINEAR);
}

int main() {
    cv::Mat image = cv::imread("1.png");
    cv::namedWindow("Image");
    cv::imshow("Image", image);
    // 重映射
    cv::Mat result;
    wave(image, result);
    cv::namedWindow("Remapped image");
    cv::imshow("Remapped image", result);
    cv::waitKey();
    return 0;
}

小结

图像处理技术是计算机视觉项目的核心,通常是计算机视觉项目中的关键工具,可以使用它们来完成各种计算机视觉任务。因此,如果要构建计算机视觉项目,就需要对图像处理有足够的了解。图像运算也是图像处理技术的一种,在本文中,我们介绍了如何对图像执行的常见算术运算,例如按位运算、加减法、形态变换等。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作

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

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

相关文章

XGBoost论文阅读

XGBoost: A Scalable Tree Boosting System 目录 XGBoost: A Scalable Tree Boosting System 1.摘要 2.方法 2.1 正则化学习目标 2.2 梯度提升树 2.3 收缩率和列采样 2.4分裂点查找算法 1.摘要 提出了一种新的稀疏性感知算法&#xff0c;用于稀疏数据和加权全图草图&a…

Python教程:什么是三级模式和二级映像?

美国国家标准学会(American National Standards Institute,ANSI)所属的标准计划与需求委员会&#xff08;Standards Planning and Requirements Committee,SPARC)在1971年公布的研究报告中提出了ANSI-SPARC体系结构&#xff0c;即三级模式结构&#xff08;或称为三层体系结构&a…

ArcGIS基础实验操作100例--实验53导出线、面要素的坐标值

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验53 导出线、面要素的坐标值 目录 一、实验背景 二、实验数据 三、实验步骤 &#xf…

笔记杂项(一)

都是踩过的坑&#xff0c;趟过的水。 ubuntu虚拟机终端字体太小的设置方法&#xff1a;ubuntu18.04调整终端字体大小 这个方法试试看&#xff1a;https://zhuanlan.zhihu.com/p/139305626 容器里面编译内核代码&#xff0c;进程被杀掉的原因是触发了内核OOM killer&#xff0c…

干货| app自动化测试之Andriod微信小程序的自动化测试

随着微信小程序的功能和生态日益完善&#xff0c;很多公司的小程序项目页面结构越来越多&#xff0c;业务逻辑也越来越复杂。如何做好小程序的自动化测试就成为测试同学普遍面临的一大痛点难题。微信小程序小程序内嵌于微信内部&#xff0c;页面包含 Native 原生元素和 Web 元素…

华为防火墙与二层交换机对接配置VLAN上网设置

拓扑图 一、防火墙设置 1、G1/0/0接口设置IP&#xff0c;G1/0/1接口切换二层口设置VLAN&#xff0c;G1/0/0 桥接了本地无线网卡来模拟公网地址 <USG6000V1>sys [USG6000V1]sys FW1 [FW1]un in en# 设置公网IP [FW1]int g1/0/0 [FW1-GigabitEthernet1/0/0]ip addr 192.1…

package.json配置解读之入门

文章目录前言一、描述配置nameversionrepositorydescriptionkeywordslicenseauthor二、文件配置filestypemainbrowsermoduleexportsworkspaces三、脚本配置scriptsconfig四、结语前言 package.json是每个前端项目都会有的json文件&#xff0c;位于项目的根目录中。很多脚手架在…

RHCE(chrony服务器)

chrony服务器 chrony服务器是一个开源自由的网络时间协议NTP的客户端和服务器的软件&#xff0c;他能让计算机保持系统时钟和时钟服务器保持同步&#xff0c;让计算机保持精确的时间&#xff0c;chrony也可以作为服务端软件为其他计算机提供时间同步服务 chrony由两部分组成&…

openAI--十拳剑助你做AI时代的弄潮儿

AI它厉害&#xff08;diao&#xff09;吗&#xff1f; 最近大家玩chatgpt还好吗&#xff1f; 有被它的恋爱情商暴击到吗&#xff1f; 有没有觉得那在leetcode上所向无敌的技巧都是浮云吗&#xff1f; 今天&#xff0c;我为大家带来十个很好的AI平台。这一篇先介绍一下&…

【远程桌面】nomachine下载安装使用教程、zerotier下载安装使用教程超详细

文章目录一、软件介绍二、NoMachine远程桌面1.Windows下载安装使用2.Linux下载安装使用3.Android下载安装使用4.ARM下载安装使用&#xff08;未实践&#xff09;三、ZeroTier内网穿透0.官网注册账户1.Windows下载安装使用2.Linux下载安装使用3.Android下载安装使用4.ARM下载安装…

Android 学习 - 不完善

SharedPreference 共享参数用法 SharedPreference 是 Android 的一个轻量级存储工具, 采用的存储结构是Key-Value的键值对方式. 共享参数的存储介质是符合XML规范的配置文件. 保存路径是: /data/data/应用包名/shared_prefs/文件名.xml 利用元数据配置快捷菜单 (1)元数据的met…

【阶段二】Python数据分析Pandas工具使用11篇:探索性数据分析:数据的检验:卡方检验与t检验

本篇的思维导图: 探索性数据分析:数据的检验 卡方检验 在实际的学习或工作中,也会碰到关于离散型变量之间的探索性分析,如两个离散变量之间是否相互独立。对于该问题的解答,就需要运用统计学中的卡方检验了。卡方检验属于非参数的检验方法,其原假设是两个离散变…

Spring——最全Spring目录

&#x1f4eb;作者简介&#xff1a;zhz小白 公众号&#xff1a;小白的Java进阶之路 专业技能&#xff1a; 1、Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理 2、熟悉Java基础&#xff0c;并精通多线程的开发&#xff0c;熟悉JVM原理&#xff0c;具备⼀定的线…

2022 IoTDB Summit:IoTDB PMC侯昊男《Apache IoTDB首创时序顺乱序分离存储引擎 IoTLSM》...

12 月 3 日、4日&#xff0c;2022 Apache IoTDB 物联网生态大会在线上圆满落幕。大会上发布 Apache IoTDB 的分布式 1.0 版本&#xff0c;并分享 Apache IoTDB 实现的数据管理技术与物联网场景实践案例&#xff0c;深入探讨了 Apache IoTDB 与物联网企业如何共建活跃生态&#…

Flutter项目中添加Webview(八)使用JavaScript渠道

借助JavascriptChannel&#xff0c;您的应用可以在WebView的JavaScript上下文中注册回调处理程序&#xff0c;可以调用这些回调处理程序将值传递回应用的Dart代码。在此步骤中&#xff0c;您将注册一个使用 SMLHttpRequest的结果调用SnackBar。 将WebViewStack类更新如下所示&…

Mysql的锁(自用笔记)

笔记(https://www.bilibili.com/video/BV1Kr4y1i7ru) 什么是锁? mysql中有哪几种锁 表级锁 表级锁-表锁 write lock 写锁, 加锁客户端,可以读写操作, 其他客户端不能 读,写操作 表级锁-元数据锁 一个例子,事务中,增删改查时候,会自动加入元数据锁,不允许对表结构进行修改 …

Keras model.predict输出的概率值转换为类别

问题&#xff1a;使用Keras做分类任务&#xff0c;model.predict预测得到的值为每个类别的概率值&#xff0c;而不是类别。 源码&#xff1a; y_test_pred model.predict(x_test, batch_size256, verbose1)解决&#xff1a; import numpy as np y_test_pred model.predict(…

[ctf.show pwn] 新手杯,七夕杯

闲来无事作练习新手杯pwn1好长的代码&#xff0c;看了十几分钟&#xff0c;发现最后一个函数是后门&#xff0c;而且是不用敲的那种。void __noreturn door() {char s[32]; // [rsp0h] [rbp-50h] BYREFchar command[40]; // [rsp20h] [rbp-30h] BYREFunsigned __int64 v2; // […

【新年心安】新冠感染“阳康”套餐,“阳康”后的你,很有必要

你有没有 在阳康后还伴随 呼吸急促&#xff08;气短&#xff09;、全身乏力、咳嗽、出冷汗等 健康问题阳康健康检查套餐 潍坊正大光明老年病医院为更好的服务患者现推出阳康健康检查套餐&#xff0c;“阳康”后的你&#xff0c;体检先行很有必要&#xff01;详情如下&#xff1…

【LeetCode面试TOP100】力扣打卡第一天!

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【LeetCode面试TOP100】 ✈️✈️本篇内容:力扣Top100——第1,2题&#xff01; &#x1f680;&#x1f680;代码存放仓库gitee&#xff1a;力扣面试Top100题&…