从0开始的opencv之旅(1)cv::Mat的使用
目录
Mat
存储方法
创建一个指定像素方式的图像。
尽管我们完全可以把cv::Mat当作一个黑盒,但是笔者的建议是仍然要深入理解和学习cv::Mat自身的构造逻辑和存储原理,这样在查找问题,或者是遇到一些奇奇怪怪的图像显示问题的时候能够快速的想到问题的跟源。这是笔者打算从这里开始的一个重要的目的
在实际上,我们有多种方法从现实世界获取数字图像:数码相机、扫描仪、计算机断层扫描和磁共振成像等。当然,对于每一个初学者,更多可能是从我们的摄像头开始的。在每种情况下,我们(人类)看到的都是图像。但是,当将其转换为我们的数字设备时,我们记录的是图像每个点的数值。也就是说,我们使用数值来存储记录图像的信息。然后真正显示的时候控制设备按照我们存储的信息还原出来。就是这样的,比如说下面这种
(这个图太经典了,任何一个阅读过《学习Opencv3》的朋友都会知道这张图片),我们人眼看到的是一个车子,但是对于计算机而言,只不过是一个包含像素点所有强度值的矩阵。我们获取和存储像素值的方式可能因我们的需求而异,但最终计算机世界中的所有图像都可以简化为数值矩阵和描述矩阵本身的其他信息。OpenCV 是一个计算机视觉库,其主要重点是处理和操纵这些信息。因此,您需要熟悉的第一件事是 OpenCV 如何存储和处理图像。
Mat
OpenCV 自 2001 年以来一直存在。当时,该库是围绕 C 接口构建的,为了将图像存储在内存中,他们使用了一个名为 IplImage 的 C 结构。这是您在大多数旧教程和教育材料中都会看到的。问题在于,它把 C 语言的所有缺点都带到了桌面上。最大的问题是手动内存管理。它建立在用户负责处理内存分配和释放的假设之上。虽然这对于较小的程序来说不是问题,但一旦您的代码库增长,处理所有这些问题就会更加困难,而不是专注于解决您的开发目标。
幸运的是,C++ 出现了,并引入了类的概念,通过自动内存管理(或多或少)让用户更容易使用。好消息是 C++ 与 C 完全兼容,因此进行更改不会出现兼容性问题。因此,OpenCV 2.0 引入了一个新的 C++ 接口,提供了一种新的方式,这意味着您不需要摆弄内存管理,从而使您的代码更简洁(编写更少,实现更多)。 C++ 接口的主要缺点是,目前许多嵌入式开发系统仅支持 C。因此,除非您针对的是嵌入式平台,否则使用旧方法是没有意义的(除非您是受虐狂程序员,而且您在自找麻烦)。
关于 Mat,您需要知道的第一件事是,您不再需要手动分配内存并在不需要时立即释放它。虽然这样做仍然是可能的,但大多数 OpenCV 函数都会自动分配其输出数据。如果您传递已经为矩阵分配所需空间的现有 Mat 对象,这将被重用,这是一个不错的奖励。换句话说,我们始终只使用执行任务所需的内存。
Mat 基本上是一个包含两个数据部分的类:矩阵头(包含矩阵大小、用于存储的方法、矩阵存储在哪个地址等信息)和指向包含像素值的矩阵的指针(根据选择的存储方法采用任何维度)。矩阵头大小是恒定的,但是矩阵本身的大小可能因图像而异,并且通常大几个数量级。
我们知道,OpenCV 是一个图像处理库。它包含大量图像处理函数。为了解决计算难题,大多数时候您最终会使用库中的多个函数。因此,将图像传递给函数是一种常见的做法。我们不应忘记,我们正在讨论图像处理算法,这些算法往往计算量很大。我们最不想做的事情是通过对可能很大的图像进行不必要的复制来进一步降低程序的速度。
为了解决这个问题,OpenCV 使用引用计数系统。这个想法是每个 Mat 对象都有自己的头,但是可以通过让它们的矩阵指针指向同一地址来在两个 Mat 对象之间共享矩阵。此外,复制运算符只会复制头和指向大矩阵的指针,而不是数据本身。
我们可以具备尝试性质的测试一下。比如说:
Mat A, C; // 仅创建头部分 A = imread(argv[1], IMREAD_COLOR); // 在这里我们将知道使用的方法(分配矩阵) Mat B(A); // 使用复制构造函数 C = A; // 赋值运算符
所有上述对象最终都指向同一个数据矩阵,使用其中任何一个进行修改也会影响所有其他对象。实际上,不同的对象只是为相同的底层数据提供不同的访问方法。然而,它们的标题部分是不同的。现在您可能会问 - 如果矩阵本身可能属于多个 Mat 对象,那么当不再需要它时,谁负责清理它?简短的回答是:最后一个使用它的对象。这是通过使用引用计数机制来处理的。每当有人复制 Mat 对象的标题时,矩阵的计数器就会增加。每当清理标题时,此计数器就会减少。当计数器达到零时,矩阵将被释放。有时您也希望复制矩阵本身,因此 OpenCV 提供了 cv::Mat::clone() 和 cv::Mat::copyTo() 函数。
笔者在develop_example/examples/basic_usage示例子程序中书写了验证程序,看官可以移步查看。这是显示的效果:
(哦,实在是太长了)
值得一提的是,如果我们想要完全拷贝一个矩阵的时候:
Mat F = A.clone(); Mat G; A.copyTo(G);
现在修改 F 或 G 不会影响 A 的标头指向的矩阵。您需要记住的是:
-
OpenCV 函数的输出图像分配是自动的(除非另有说明)。
-
您无需考虑使用 OpenCV 的 C++ 接口进行内存管理。
-
赋值运算符和复制构造函数仅复制标头。
-
可以使用 cv::Mat::clone() 和 cv::Mat::copyTo() 函数复制图像的底层矩阵。
真正有趣的部分是,您可以创建仅引用完整数据的一部分的标题。例如,要在图像中创建感兴趣的区域 (ROI),只需创建一个新的头部:
Mat D (A, Rect(10, 10, 100, 100) ); // 使用矩形 Mat E = A(Range::all(), Range(1,3)); // 使用行和列边界
存储方法
这是关于如何存储像素值。您可以选择颜色空间和使用的数据类型。颜色空间是指我们如何组合颜色成分以编码给定的颜色。最简单的是灰度,其中我们可以处理的颜色是黑色和白色。这些组合使我们能够创建多种灰色阴影。
对于丰富多彩的方式,我们有更多的方法可供选择。它们中的每一个都将其分解为三个或四个基本组件,我们可以使用这些组合来创建其他组件。最流行的是 RGB,主要是因为这也是我们的眼睛构建颜色的方式。它的基本颜色是红色、绿色和蓝色。为了对颜色的透明度进行编码,有时会添加第四个元素 alpha (A)。
但是,还有许多其他颜色系统,每个都有自己的优势:
-
RGB 是最常见的,因为我们的眼睛使用类似的东西,但请记住,OpenCV 标准显示系统使用 BGR 颜色空间(红色和蓝色通道交换位置)组成颜色。
-
HSV 和 HLS 将颜色分解为色调、饱和度和值/亮度分量,这是我们描述颜色的更自然的方式。例如,您可能会忽略最后一个组件,从而使您的算法对输入图像的光照条件不太敏感。
-
YCrCb 是流行的 JPEG 图像格式。
-
CIE L*a*b* 是一个感知均匀的颜色空间,如果您需要测量给定颜色与另一种颜色的距离,它会派上用场。
每个构建组件都有自己的有效域。这导致了所使用的数据类型。我们如何存储组件定义了我们对其域的控制。最小的数据类型是 char,这意味着一个字节或 8 位。这可能是无符号的(因此可以存储从 0 到 255 的值)或有符号的(从 -127 到 +127 的值)。虽然在三个组件(如 RGB)的情况下,这个宽度已经提供了 1600 万种可能的颜色来表示,但我们可以通过对每个组件使用浮点(4 字节 = 32 位)或双精度(8 字节 = 64 位)数据类型来获得更精细的控制。不过,请记住,增加组件的大小也会增加内存中整个图片的大小。
关于这些内容,笔者后面会进行更加详细的介绍。
创建一个指定像素方式的图像。
你已经学会了如何使用 cv::imwrite() 函数将矩阵写入图像文件。(没有?你跳过了0.beginners的篇章,去看看吧)但是,出于调试目的,查看实际值会更方便。你可以使用 Mat 的 << 运算符来执行此操作。请注意,这仅适用于二维矩阵。 虽然 Mat 作为图像容器确实很有效,但它也是一个通用矩阵类。因此,可以创建和操作多维矩阵。你可以用多种方式创建 Mat 对象:对于二维和多通道图像,我们首先定义它们的大小:按行数和列数。然后,我们需要指定用于存储元素的数据类型和每个矩阵点的通道数。为此,我们根据以下约定构建了多个定义:
CV_[每项的位数][有符号或无符号][类型前缀]C[通道号]
例如,CV_8UC3 表示我们使用 8 位长的无符号字符类型,每个像素有三个这样的类型来形成三个通道。最多有四个通道的预定义类型。cv::Scalar 是四个元素的短向量。指定它,您可以使用自定义值初始化所有矩阵点。这样,你可以猜猜我们的灰度图是如何表达的呢?CV_8UC1!可以回去翻翻你数字图像处理的书!0~255,手指头一算一个字节就能表达!
std::cout << "Creating a image of 2 x 2 (0, 0, 255) Image";cv::Mat simple_image(2, 2, CV_8UC3, cv::Scalar(0, 0, 255));std::cout << "Opencv Implement the override function of the "<< "ofstream to display the cv::Mat\n";std::cout << simple_image; // will not be an error!
就是这样,我们创建了一个简单的,纯红色的图像(永远注意我们亲爱的Opencv使用的是BGR顺序来描述我们的图像!),你可以把行列拉大一些显示出来!
笔者的显示如上所示。各位看官可以看着玩!更改一下Scalar的值。
你还可以使用 C/C++ 数组并通过构造函数初始化
int sz[3] = {2,2,2}; Mat L(3,sz, CV_8UC(1), Scalar::all(0));
上例展示了如何创建一个多维矩阵。指定其维度,然后传递一个包含每个维度大小的指针,其余保持不变。
甚至可以是cv::Mat::create 函数:
M.create(4,4, CV_8UC(2)); cout << "M = "<< endl << " " << M << endl << endl;
另外,下面的这些内容属于想到了查函数的事情。笔者建议走马观花即可
Mat可以像使用Matlab函数那样的初始化方式
std::cout << "Also, we can initalize the Mat as Matlab way";cv::Mat E = cv::Mat::eye(4, 4, CV_64F);std::cout << "E = " << std::endl << " " << E << std::endl << std::endl;cv::Mat O = cv::Mat::ones(2, 2, CV_32F);std::cout << "O = " << std::endl << " " << O << std::endl << std::endl;cv::Mat Z = cv::Mat::zeros(3, 3, CV_8UC1);std::cout << "Z = " << std::endl << " " << Z << std::endl << std::endl;
opencv自己还提供了其他的数据类型,比如说Point2D, Point3D。好消息是他们都实现了各自的打印函数。
cv::Point2f pt(0, 0);cv::Point3f pt3(0, 0, 0);cv::Size sz(10, 10);cv::Rect rect(0, 0, 100, 100);cv::Scalar color(255, 0, 0);cv::Range range(0, 10);cv::Vec<int, 3> vec(0, 0, 0);cv::Vec3b vec3b(0, 0, 0);cv::Vec3f vec3f(0, 0, 0);cv::Vec3d vec3d(0, 0, 0);// you can display themstd::cout << "pt = " << pt << std::endl;std::cout << "pt3 = " << pt3 << std::endl;std::cout << "sz = " << sz << std::endl;std::cout << "rect = " << rect << std::endl;std::cout << "color = " << color << std::endl;std::cout << "range = " << range << std::endl;std::cout << "vec = " << vec << std::endl;std::cout << "vec3b = " << vec3b << std::endl;std::cout << "vec3f = " << vec3f << std::endl;std::cout << "vec3d = " << vec3d << std::endl;
当然,你可以参考
opencv/samples/cpp/tutorial_code/core/mat_the_basic_image_container/mat_the_basic_image_container.cpp
来进一步学习。
笔者的所有源码地址:Charliechen114514/CCPixelCraft: A PixelLevel Image Convertor And Processor. Also Provide Opencv4 Tourial Usage... (github.com)
相关文章:

从0开始的opencv之旅(1)cv::Mat的使用
目录 Mat 存储方法 创建一个指定像素方式的图像。 尽管我们完全可以把cv::Mat当作一个黑盒,但是笔者的建议是仍然要深入理解和学习cv::Mat自身的构造逻辑和存储原理,这样在查找问题,或者是遇到一些奇奇怪怪的图像显示问题的时候能够快速的想…...

Hoverfly 任意文件读取漏洞(CVE-2024-45388)
漏洞简介 Hoverfly 是一个为开发人员和测试人员提供的轻量级服务虚拟化/API模拟/API模拟工具。其 /api/v2/simulation 的 POST 处理程序允许用户从用户指定的文件内容中创建新的模拟视图。然而,这一功能可能被攻击者利用来读取 Hoverfly 服务器上的任意文件。尽管…...
详解网络管理
网络管理是指对计算机网络资源、设备和服务的有效配置、监控、管理和优化的过程。它的目的是确保网络的高效、可靠和安全运行。网络管理的关键任务包括网络监控、配置管理、性能管理、安全管理、故障管理和计费管理。下面是详细的讲解: 1. 网络管理的目标 高可用性…...

iOS 11 中的 HEIF 图像格式 - 您需要了解的内容
HEIF,也称为高效图像格式,是iOS 11 之后发布的新图像格式,以能够在不压缩图像质量的情况下以较小尺寸保存照片而闻名。换句话说,HEIF 图像格式可以具有相同或更好的照片质量,同时比 JPEG、PNG、GIF、TIFF 占用更少的设…...
深入AIGC领域:ChatGPT开发者获取OpenAI API Key的实用指南
在AIGC(人工智能生成内容)领域,ChatGPT作为一种强大的自然语言处理工具,正逐渐成为开发者们不可或缺的助手。然而,要充分发挥ChatGPT的潜力,首先需要获取OpenAI的API Key。本文将详细介绍如何获取OpenAI AP…...

软件工程实验-实验2 结构化分析与设计-总体设计和数据库设计
一、实验内容 1. 绘制工资支付系统的功能结构图和数据库 在系统设计阶段,要设计软件体系结构,即是确定软件系统中每个程序是由哪些模块组成的,以及这些模块相互间的关系。同时把模块组织成良好的层次系统:顶层模块通过调用它的下层…...

密码学精简版
密码学是数学上的一个分支,同时也是计算机安全方向上很重要的基础原理,设置密码的目的是保证信息的机密性、完整性和不可抵赖性,安全方向上另外的功能——可用性则无法保证,可用性有两种方案保证,冗余和备份࿰…...

开源模型迎来颠覆性突破:DeepSeek-V3与Qwen2.5如何重塑AI格局?
不用再纠结选择哪个AI模型了!chatTools 一站式提供o1推理模型、GPT4o、Claude和Gemini等多种选择,快来体验吧! 在全球人工智能模型快速发展的浪潮中,开源模型正逐渐成为一股不可忽视的力量。近日,DeepSeek-V3和Qwen 2.…...

【51单片机零基础-chapter4:LED数码管】
LED数码管本质是一种廉价的显示器,由多个发光二极管封装组成的8字形器件 如果要显示6,那么需要点亮除了B以外的所有段,并且开发板上默认是共阴极 阳极A->G除了B全点亮,所以7,4,2,1,9,10全接正极:10111110 这个就是段码,表示显示的数据 静态LED显示 开发板上是四个一体…...
【网络】什么是路由协议(Routing Protocols)?常见的路由协议包括RIP、OSPF、EIGRP和BGP
路由协议(Routing Protocols) 像 google map RIP (Routing Information Protocol):跳数 超了就废了 OSPF(Open Shortest Path First) 就好像拿着map找最短距离(跳数) EIGRP(Enhanced Interior Gateway Routing Protoco…...
Unity3D ILRuntime开发原则与接口绑定详解
引言 ILRuntime是一款基于C#的热更新框架,使用IL2CPP技术将C#代码转换成C代码,支持动态编译和执行代码,适用于Unity3D的所有平台,包括Android、iOS、Windows、Mac等。本文将详细介绍ILRuntime在Unity3D中的开发原则及接口绑定技术…...

闻泰科技涨停-操盘训练营实战-选股和操作技术解密
如上图,闻泰科技,今日涨停,这是前两天分享布局的一个潜伏短线的标的。 选股思路: 1.主图指标三条智能辅助线粘合聚拢,即将选择方向 2.上图红色框住部分,在三线聚拢位置,震荡筑底,…...

我用AI学Android Jetpack Compose之开篇
最近突发奇想,想学一下Jetpack Compose,打算用Ai学,学最新的技术应该要到官网学,不过Compose已经出来一段时间了,Ai肯定学过了,用Ai来学,应该问题不大,学习过程记录下来,…...

25考研王道数据机构课后习题-----顺序表链表部分
文章目录 1.顺序表题目2.链表相关题目3.我的个人总结 声明:以下内容来自于B站知名up主白话拆解数据结构,望获悉; 1.顺序表题目 下面的这个说的是:下面的哪一个是组成我们的顺序表的有限序列,这个应该是数据元素&#x…...

新能源电动汽车动力电池技术
新能源电动汽车动力电池技术是新能源汽车发展的核心之一,以下是动力电池技术的一些关键方面: 技术进展 能量密度提升:近年来,动力电池的能量密度有了显著提升,从2010年的100Wh/kg提高到2024年的300Wh/kg。能量密度的…...

修复 ITunes 在 Windows 或 Mac 上不断崩溃的问题 [100% 有效]
对于 iDevice 用户来说,只能通过 iTunes 在 iDevice 和计算机之间传输文件的困境一直是一个紧迫的问题。所有 iPhone 用户可能都知道,iTunes 并不是一款高效的应用程序,有时性能会很差,例如在 iDevices 和计算机之间传输文件时不断…...

Android设备使用AOA协议进行主机与配件模式通信
1.使用TYPC-C数据线连接两台华为手机: TYPE-C线,先连接下图右边的ACCESSORY 再连接左边的HOST 此时左边的HOST(白色) 会给右边的ACCESSORY(黑色) 充电 接着打开左连接的HostChart会自动调起授权,然后会启动右边的AccessoryChart USB HOS…...

Python爬虫入门实例:Python7个爬虫小案例(附源码)
引言 随着互联网的快速发展,数据成为了新时代的石油。Python作为一种高效、易学的编程语言,在数据采集领域有着广泛的应用。本文将详细讲解Python爬虫的原理、常用库以及实战案例,帮助读者掌握爬虫技能。 一、爬虫原理 爬虫,又…...
生成对抗网络 (Generative Adversarial Network, GAN) 算法MNIST图像生成任务及CelebA图像超分辨率任务
生成对抗网络 (Generative Adversarial Network, GAN) 算法详解与PyTorch实现 目录 生成对抗网络 (Generative Adversarial Network, GAN) 算法详解与PyTorch实现1. 生成对抗网络 (GAN) 算法概述1.1 生成器与判别器1.2 GAN的优势2. GAN的核心技术2.1 目标函数2.2 生成器2.3 判别…...

快速排序排序方法演示及算法分析(附代码和实例)
基本思想: 任取一个元素(比如第一个)为中心,称为枢轴(pivot)所有比它小的元素一律前放,比它大的元素后放,形成左右两个子表对各子表重新选择中心元素并以此规则调整直到每个子表的元…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

结构化文件管理实战:实现目录自动创建与归类
手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题,进而引发后续程序异常。使用工具进行标准化操作,能有效降低出错概率。 需要快速整理大量文件的技术用户而言,这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB,…...