【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…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
