【OpenCV C++20 学习笔记】基本图像容器——Mat
【OpenCV C++20 学习笔记】基本图像容器——Mat
- 概述
- `Mat`
- 内部结构
- 引用计数机制
- 颜色数据格式
- 显式创建`Mat`对象
- 使用`cv::Mat::Mat`构造函数
- 矩阵的数据项
- 使用数组进行初始化的构造函数
- `cv::Mat::create`函数
- MATLAB风格的初始化
- 小型矩阵
- 通过复制创建`Mat`对象
- `Mat`对象的输出
- 其他普通数据项的输出
概述
电子设备中储存的图像本质是图像在每个像素点上的数值,这些数据形成一个矩阵,除此之外还包括一些描述这个数值矩阵的信息。OpenCV作为一个计算机视觉库,同样也是要处理这样的信息。所以,要学习OpenCV,首要的事情是了解在OpenCV中是如何储存图像的数值矩阵以及描述信息的。
(本文较长,详细介绍了Mat对象的原理、创建方式和输出方式,读者可根据目录跳转至相关章节)
Mat
2001年OpenCV诞生的时候,是在C语言的接口上创建的,并将图像储存在一个称作IplImage的C语言数据结构中。这种方式最大的一个缺点就是需要用户自己管理内存。如果是小型的项目尚可,如果数据量变大,管理内存就会使人很头疼。
OpenCV2.0 引入了C++接口,实现了内存的自动管理。Mat
成为了OpenCV储存图片信息的数据结构。
Mat
不需要手动分配或释放内存,大部分的OpenCV方法都会自动为输出的Mat
对象分配内存。如果你已经为一个Mat
对象分配了它需要的内存,那么你在传输它的时候,这个内存会被重复利用。也就是说,在执行任务的时候,不会使用多余的内存。
内部结构
Mat
实质上是一个包含了两个部分的类:
- 矩阵头(matrix header):它包含了矩阵的大小、存储方式、存储地址等信息
- 指向矩阵的指针:
Mat
对象只是储存了矩阵的指针,并没有储存矩阵本身,而矩阵中包含了像素值(像素值矩阵的维度由存储方式决定)
Mat
对象的大小是固定的,但是矩阵本身的大小是跟随图像变化的。
在函数间传递图像是OpenCV中非常常见的操作,而且某些图像处理算法很复杂。为了提高程序的运行速度,OpenCV使用了“引用计数机制”。每个Mat
对象都有自己独立的矩阵头,但是同一个矩阵可能会被多个Mat
对象共享,即多个Mat
对象的指针可能会指向内存中的同一个矩阵。而复制操作只会复制Mat
对象的矩阵头以及指向矩阵的指针,并不会直接复制矩阵的数值!
下面的代码详细展示了Mat
对象在实际应用中的内存分配问题:
Mat A, C; //创建Mat对象的时候只是创建了矩阵头的部分
A = imread(argv[1], IMREAD_COLOR); //读取图片,分配内存存储图片的数值矩阵,并将A的指针指向这个矩阵的内存地址Mat B(A); //调用复制构造函数创建B,但仅仅是将A中的指向图片矩阵的指针复制到B中,并没有复制图片的数值矩阵C = A; //赋值操作也只是将A中的指针复制到C中
上面的代码最终使A、B、C3个Mat对象中的指针都指向同一个图片的数值矩阵,虽然进行了复制和赋值操作,但内存中始终只有一个数值矩阵,如下图:
因为3个Mat
对象的指针都是指向同一个数据矩阵,所以在任何一个Mat
对象中对数据矩阵进行修改都会影响到其他Mat
对象。实际上,不同的Mat
对象只是为处理同一个数据矩阵提供了不同的使用方法。但是这些Mat
对象的矩阵头部分是不同的,你甚至可以创建一个只指向数据矩阵的其中一部分的Mat
对象。例如,要想在图像中创建一个感兴趣区域(region of interest,ROI),你可以新建一个Mat
对象:
Mat D(A, Rect(10, 10, 100, 100); //使用矩形区域
Mat E = A(Range::all(), Range(1, 3)); //使用行和列
引用计数机制
如果像上面的例子一样,同一个数据矩阵属于不同的Mat
对象,那到底谁来负责释放它的内存呢?答案是:最后一个使用它的Mat
对象。这就是通过上面所说的“引用计数机制”来实现的。当有指向数据矩阵A的Mat
对象被复制的时候,矩阵A的引用计数就会增加;当有指向矩阵A的Mat
对象被销毁的时候,矩阵A的引用计数就会减少。当计数为0的时候,矩阵A就会被释放。
OpenCV还提供了深度复制数据矩阵的方法,当你不想只是复制指针,而是想复制矩阵的值的时候,可以使用cv::Mat::clone()
和cv::Mat::copyTo()
方法。
Mat F = A.clone(); //将A指向的数据矩阵复制给F
Mat G;
A.copyTo(G); //将A指向的数据矩阵复制到G
这样,修改F和G的时候就不会影响A指向的数据矩阵了。
总结一下:
- OpenCV中函数导出的图像数据是自动分配内存的(除非特别指定不自动分配)
- 使用OpenCV的C++接口的时候不用考虑内存管理的问题
- 赋值运算符和复制构造函数只是复制Mat对象的头部信息和指针
- 可以用
cv::Mat::clone()
和cv::Mat::copyTo()
方法实现底层的图片数据矩阵的复制
颜色数据格式
对于如何储存像素的值,通常从两个方面考虑:颜色空间和数据类型。
颜色空间是指利用基本的颜色组合成特定的颜色的方式。有多种方式可以选择:
- RGB:这是最常用的,因为它与人眼编码颜色的方式相似;由红、绿、蓝3中基本颜色的值,加上透明度alpha,来确定最终颜色;注意,OpenCV中的标准颜色显示系统为BGR,红色和蓝色的值调换了位置
- HSV和HLS:将颜色分解为色调、饱和度和亮度;这种方式能更方便地处理图片的亮度
- YCrCb:这是JPEG格式的图片常用的颜色编码方式
- CIT Lab*:这种编码方式能够方便测量两种颜色之间的差距
- 灰度:只有黑色和白色两种基本颜色
显式创建Mat
对象
使用cv::Mat::Mat
构造函数
Mat M(2,2, CV_8U3, Scalar(0,0,255));
cout << "M = " << endl << " " << M << endl << endl;
这里使用了Mat
类的其中一个构造函数。该构造函数一共包括4个参数:
- 行数:定义矩阵行数
- 列数:定义矩阵列数
- 数据类型:定义每个数据项的类型,下文详述
Scalar
常量:用来定义每个数据项的值的向量数组
矩阵的数据项
矩阵数据项的数据类型的定义遵循以下语法规则:
CV_[每个数据项的比特数][有符号或无符号][类型前缀]C[通道数量]
- 比特数:确定每个数据项,即像素点,的数值的长度,如8比特;比特数越高,每个像素点的值域就越大,比如,32比特的浮点类型比8比特的char类型能够储存更多的颜色值
- 有符号或无符号:确定每个数据项的值是否是有符号的(可省略,默认为无)
- 类型前缀:如果是
char
类型,则为C,如果是float
类型,则为F…… - 通道数量:确定每个数据项中包含的颜色通道数量;比如,RGB颜色空间可以有4个通道,分别是红色值、绿色值、蓝色值和透明度值;通道数量可以加上括号,如
CV_8UC(3)
(可省略,默认为1)
上面代码中的CV_8U3
就代表每个数据项的是具有3个通道的8比特无符号的值,输出结果如下:
可以看到矩阵中每个项有3个数值,代表3个颜色通道;共有2*2个项;每个项中的3个颜色通道的值都与Scalar
中定义的相同。
使用数组进行初始化的构造函数
除了2维的矩阵,也可以创建3维矩阵的Mat
对象
int sz[3]{ 2,2,2 };
Mat L(3, sz, CV_8UC1, Scalar::all(0));
这个构造函数也使用4个参数:
- 维度:确定矩阵的维度
- 大小:一个数组,用来确定每个维度的大小
- 数据项的数据类型:同上一个构造函数
Scalar
常量:同上一个构造函数
所以,这里创建了一个3维的矩阵,每个维度都只有2个数据项,即222;每个数据项使用的都是只有1个颜色通道的8比特无符号数值;每个数据项的值都为0。
cv::Mat::create
函数
这个函数看起来像是在创建一个Mat
对象,但其实它只能修改已有的Mat
对象。
比如,对上面创建的M对象进行修改:
M.create(4, 4, CV_8UC2);
cout << "M = "<< endl << " " << M << endl << endl;
cv::Mat::create
函数使用了3个参数:
- 行数:修改后的行数
- 列数:修改后的列数
- 数据项类型:修改后的数据项类型
输出结果为:
可以看到原本22的3颜色通道的矩阵变成了44的2颜色通道矩阵。cv::Mat::create
函数为M对象重新分配了内存,使其能储存修改之后的更大的矩阵。
MATLAB风格的初始化
cv::Mat::zeros
, cv::Mat::ones
, cv::Mat::eye
等与MATLAB语言类似的函数也可以用来初始化OpenCV中的Mat
对象
zeros
函数用来创建全为0值的矩阵;ones
函数用来创建全为1值的矩阵;eye
函数用来创建对角线为1,其他值为0的矩阵
Mat E {Mat::eye(4, 4, CV_64F)};cout << "E = " << endl << " " << E << endl << endl;Mat O {Mat::ones(2, 2, CV_32F)};cout << "O = " << endl << " " << O << endl << endl;Mat Z {Mat::zeros(3,3, CV_8UC1)};cout << "Z = " << endl << " " << Z << endl << endl;
这些函数都使用相同的参数列表:
- 行数
- 列数
- 数据项类型
输出结果如下:
小型矩阵
如果要构造小型矩阵,可以直接以逗号为间隔,用<<
运算符将每个值一行一行依次输入;
在C++11之后,也可以使用{}风格的初始化列表
//<<运算符Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);cout << "C = " << endl << " " << C << endl << endl;//初始化列表C = (Mat_<double>({0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3); //reshape函数将矩阵中的数据项变成3通道类型cout << "C = " << endl << " " << C << endl << endl;
输出结果如下:
通过复制创建Mat
对象
要复制Mat
对象,需要第二节讲的使用cv::Mat::clone
或 cv::Mat::copyTo
函数
Mat
对象的输出
上面的例子中的输出使用的都是默认格式,但还有几种其他的输出格式
首先使用随机数创建一个3通道的3*2矩阵
Mat R {Mat(3, 2, CV_8UC3)};randu(R, Scalar::all(0), Scalar::all(255));
cv::randu()
为随机数生成函数,使用3个参数:
Mat
对象:用来储存随机值的Mat对象- 最低值:
Scalar
常量类型,确定随机数的最小值 - 最高值:
Scalar
常量类型,确定随机数的最大值
接下来使用format
函数定义输出格式,该函数使用两个参数: Mat
对象:需要输出的Mat
对象- 格式定义:在
Formatter
中定义的枚举类型
详见以下代码:
cout << "R (default) = " << endl << " " << R << endl << endl;
cout << "R (Python) = " << endl << format(R, Formatter::FMT_PYTHON) << endl << endl;
cout << "R (csv) = " << endl << format(R, Formatter::FMT_CSV) << endl << endl;
cout << "R (numpy) = " << endl << format(R, Formatter::FMT_NUMPY) << endl << endl;
cout << "R (C) = " << endl << format(R, Formatter::FMT_C) << endl << endl;
输出结果如下:
其他普通数据项的输出
OpenCV中的大部分数据结构都支持<<
运算符
以下代码展示了如何运用<<
运算符输出点、向量类型的对象
Point2f P(5, 1);
cout << "Point (2D)= " << P << endl << endl;Point3f P3f(2, 6, 7);
cout << "Point (3D) = " << P3f << endl << endl;vector<float> v;
v.push_back((float)CV_PI); v.push_back(2); v.push_back(3.01f);
cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;vector<Point2f> vPoints(20);
for (size_t i = 0; i < vPoints.size(); ++i)vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));
cout << "A vector of 2D Points = " << vPoints << endl << endl;
输出结果如下:
相关文章:

【OpenCV C++20 学习笔记】基本图像容器——Mat
【OpenCV C20 学习笔记】基本图像容器——Mat 概述Mat内部结构引用计数机制颜色数据格式 显式创建Mat对象使用cv::Mat::Mat构造函数矩阵的数据项 使用数组进行初始化的构造函数cv::Mat::create函数MATLAB风格的初始化小型矩阵通过复制创建Mat对象 Mat对象的输出其他普通数据项的…...
枚举单例是怎么保证线程安全和防止反射的
枚举单例在Java中具有天然的线程安全性和防止反射攻击的特性,这是由于Java对枚举类型的特殊处理方式。以下是详细解释: 1. 线程安全性 Java 枚举类的特性 类加载机制:枚举类型在Java中是特殊的类,由JVM保证其线程安全性。枚举类…...

传知代码-智慧医疗:纹理特征VS卷积特征(论文复现)
代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 论文链接:https://www.sciencedirect.com/science/article/abs/pii/S1076633223003537?__cf_chl_rt_tkJ9Aipfxyk5d.leu48P20ePFNd4B2aunaSmzVpXCg.7g-1721292386-0.0.1.1-6249 论文概述 今天我们把视线…...

数据结构中的八大金刚--------八大排序算法
目录 引言 一:InsertSort(直接插入排序) 二:ShellSort(希尔排序) 三:BubbleSort(冒泡排序) 四: HeapSort(堆排序) 五:SelectSort(直接选择排序) 六:QuickSort(快速排序) 1.Hoare版本 2.前后指针版本 …...

ACC2.【C语言】经验积累 栈区简单剖析
int main() {int i0;int arr[10]{1,2,3,4,5,6,7,8,9,10};for (i0;i<12;i){arr[i]0;printf("A");}return 0; } 执行后无限打印A 在VS2022,X86,Debug环境下,用监视后,原因是arr[12]的地址与i的地址重合(数组越界&…...
c# 索引器
索引器(Indexer)允许你像访问数组一样,通过索引访问对象的属性或数据。索引器的主要用途是在对象内部封装复杂的数据结构,使得数据访问更加直观。下面是关于 C# 索引器的详细解释及示例: 基本语法 索引器的语法类似于…...

低代码如何加速数字化转型
数字化转型,正日益决定企业成功的关键。这里的一个关键因素是它可以以更快的速度和质量来实施技术计划。在当今瞬息万变的商业环境中,战略性地采用低代码平台对于旨在加快上市时间、增强业务敏捷性和促进跨团队无缝协作的首席技术官来说至关重要。日益增…...

Pytest进阶之fixture的使用(超详细)
目录 Fixture定义 Fixture使用方式 作为参数使用 Fixture间相互调用(作为参数调用) 作为conftest.py文件传入 Fixture作用范围Scope function class module session Fixture中params和ids Fixture中autouse Fixture中Name 总结 pytest fixture 是一种用来管理测试…...
GitHub 详解教程
1. 引言 GitHub 是一个用于版本控制和协作的代码托管平台,基于 Git 构建。它提供了强大的功能,使开发者可以轻松管理代码、追踪问题、进行代码审查和协作开发。 2. Git 与 GitHub 的区别 Git 是一个分布式版本控制系统,用于跟踪文件的更改…...

边界网关IPSEC VPN实验
拓扑: 实验要求:通过IPSEC VPN能够使PC2通过网络访问PC3 将整个路线分为三段 IPSEC配置在FW1和FW2上,在FW1与FW2之间建立隧道,能够传递IKE(UDP500)和ESP数据包,然后在FW1与PC2之间能够流通数据…...

力扣高频SQL 50题(基础版)第六题
文章目录 1378. 使用唯一标识码替换员工ID题目说明思路分析实现过程结果截图总结 1378. 使用唯一标识码替换员工ID 题目说明 Employees 表: ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | ------…...
在一个事物方法中开启新事物,完成对数据库的修改
在Java中,使用Transactional注解来管理事务非常常见。但是,在一个已经标记为Transactional的方法内部调用另一个也标记了Transactional的方法时,如果不正确处理,可能会导致一些意料之外的行为。这是因为默认情况下,Spr…...

ffmpeg的vignetting filter
vignetting filter是暗角过滤器 vignetting filter在官网是vignette。但是我查了一下,vignetting应该是正确的表达,vignette是什么鬼? 官网参数 官书参数 参数解释 angle,x0,y0可以使用表达式。 angle:不知道什么意思…...

商场导航系统:从电子地图到AR导航,提升顾客体验与运营效率的智能解决方案
商场是集娱乐、休闲、社交于一体的综合性消费空间,随着商场规模的不断扩大和布局的日益复杂,顾客在享受丰富选择的同时,也面临着寻路难、店铺曝光率低以及商场管理效率低下等挑战。商场导航系统作为提升购物体验的关键因素,其重要…...
vue3中父子组件的双向绑定defineModel详细使用方法
文章目录 一、defineProps() 和 defineEmits()二、defineModel() 的双向绑定2.1、基础示例2.2、定义类型2.3、声明prop名称2.4、其他声明2.5、绑定多个值2.6、修饰符和转换器2.7、修饰符串联 一、defineProps() 和 defineEmits() 组件之间通讯,通过 props 和 emits…...

耳机、音响UWB传输数据模组,飞睿智能低延迟、高速率超宽带uwb模块技术音频应用
在数字化浪潮席卷全球的今天,无线通信技术日新月异,其中超宽带(Ultra-Wideband,简称UWB)技术以其独特的优势,正逐步成为无线传输领域的新星。本文将深入探讨飞睿智能UWB传输数据模组在音频应用中的创新应用…...
webpack配置报错:Invalid options object.
前言: 今天在使用webpack进行项目配置的时候,运行之后终端报错:Invalid options object. Dev Server has been initialized using an options object that does not match the API schema. - options has an unknown property inline. Thes…...

Java 并发编程:一文了解 Java 内存模型(处理器优化、指令重排序与内存屏障的深层解析)
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 022 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进…...

谷粒商城实战笔记-64-商品服务-API-品牌管理-OSS前后联调测试上传
文章目录 1,拷贝文件到前端工程2,局部修改3,在品牌编辑界面使用上传组件4,OSS配置允许跨域5,测试multiUpload.vue完整代码singleUpload.vue完整代码policy.js代码 在Web应用开发中,文件上传是一项非常常见的…...

Springboot 开发之 RestTemplate 简介
一、什么是RestTemplate RestTemplate 是Spring框架提供的一个用于应用中调用REST服务的类。它简化了与HTTP服务的通信,统一了RESTFul的标准,并封装了HTTP连接,我们只需要传入URL及其返回值类型即可。RestTemplate的设计原则与许多其他Sprin…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...