从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)所有比它小的元素一律前放,比它大的元素后放,形成左右两个子表对各子表重新选择中心元素并以此规则调整直到每个子表的元…...

库迪困境:供应链补救失效背后的市场错配
作者 | 曾响铃 文 | 响铃说 近日,红餐网证实了库迪咖啡暂停便捷店招商的消息。库迪官方回应称,店中店模式招商只是按下了暂停键,不排除未来重启的可能。 但一批被“暂停”的便捷店加盟商,不知道等不等起库迪的未来重启。 小红…...

解决openpyxl操纵带公式的excel或者csv之后,pandas无法读取数值的问题
1 功能特点 openpyxl: 这是一个专门用于操作Excel文件(.xlsx/.xlsm)的库。它提供了丰富的功能来读取、写入和修改Excel文件的各个元素,如单元格、行、列、工作表等。例如,可以通过openpyxl轻松地创建一个新的Excel工作…...

基于傅立叶神经网络(FNN)与物理信息神经网络(PINN)求解泊松方程(附Pytorch源代码)
基于傅立叶神经网络(FNN)与物理信息神经网络(PINN)求解泊松方程 一、引言 偏微分方程(Partial Differential Equation, PDE)在科学与工程领域有着广泛的应用。传统数值方法(如有限差分法、有限元法)在求解这类问题时,尽管已经非常成熟,但随着问题复杂度的增加,其计…...

小程序组件 —— 28 组件案例 - 推荐商品区域 - 实现结构样式
这一节目标是实现底部推荐商品的结构和样式,由于这里要求横向滚动,所以需要使用上节介绍的 scroll-view 功能,并使用 scroll-x 属性支持横向滚动,推荐商品区域中的每一个商品是一个单独的 view,每个view 中需要写三个组…...

Flink读写Kafka(DataStream API)
在Flink里,已经预定义了kafka connector,使用该connector我们可以读写kafka,并且能实现exactly once的语义。 要使用需要引入相关的maven依赖,在这里,因为读写kafka,就会涉及一个问题,kafka-client和broker的版本兼容问题,不过因为kafka client和broker的双向兼容的良…...

SCAU期末笔记 - 数据库系统概念往年试卷解析
数据库搞得人一头雾水,题型太多太杂,已经准备摆烂了。就刷刷往年试卷,挂不挂听天由命。 2019年 Question 1 选择题 1. R ∩ S R∩S R∩S等于一下哪个选项? 画个文氏图秒了 所以选A. R ∩ S R − ( R − S ) R∩SR-(R-S) R∩…...

flutter在windows平台中运行报错
PS D:\F\luichun> flutter run当运行flutter项目时,【解决如下报错】 /C:/flutter/packages/flutter/lib/src/painting/star_border.dart:530:27: Error: The getter Matrix4 isnt defined for the class _StarGenerator.- _StarGenerator is from package:flut…...

HTML——75. 内联框架
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>内联框架</title><style type"text/css">iframe{width: 100%;height: 500px;}</style></head><body><!--iframe元素会创建包含…...

python对mongodb的增删查改
python对mongodb的增删查改 1. 安装 pymongo2. 连接 MongoDB3. 创建(插入)文档插入单个文档插入多个文档 4. 查询文档查询单个文档查询多个文档复杂查询嵌套查询分页条件查询(通用模版) 5. 更新文档更新单个文档更新多个文档更新嵌…...

【JS】期约的Promise.all()和 Promise.race()区别
概述 Promise.all() 和 Promise.race() 都是 JavaScript 中处理多个异步操作的 Promise 方法,但它们的行为和返回结果有所不同。 Promise.all()和Promise.race() 1. Promise.all() Promise.all() 接受一个由多个 Promise 实例组成的可迭代对象(例如数…...