OpenCV实战(2)——OpenCV核心数据结构

news2025/7/15 3:43:19

OpenCV实战(2)——OpenCV核心数据结构

    • 0. 前言
    • 1. cv::Mat 数据结构
      • 1.1 cv::Mat 简介
      • 1.2 cv::Mat 属性
      • 1.3 完整代码示例
    • 2. 探索 cv::Mat 数据结构
      • 2.1 cv::Mat 对象的创建
      • 2.2 OpenCV 输入和输出数组
    • 小结
    • 系列链接

0. 前言

cv::Mat 类是用于保存图像(以及其他矩阵数据)的数据结构,该数据结构是所有 OpenCV 类和函数的核心,这是 OpenCV 库的一个关键元素,用于处理图像和矩阵(从计算和数学的角度来看,图像本质上是一个矩阵),同时 cv::Mat 数据结构结合了优雅的内存管理机制,因此,使用起来十分高效。此数据结构在应用程序开发中广泛使用,因此再进一步学习前我们必须熟悉 cv::Mat 数据结构。

1. cv::Mat 数据结构

1.1 cv::Mat 简介

cv::Mat 数据结构基本上由两部分组成:标头 (header) 和数据块 (data block)。标头包含与矩阵相关的所有信息(尺寸大小、通道数、数据类型等)。我们已经介绍了如何访问包含在 cv::Mat 标头中的一些属性,例如,通过使用 colsrowschannels 访问矩阵的列数、行数或通道数;数据块包含图像的所有像素值,标头中包含一个指向这个数据块的指针变量,即 data 属性。cv::Mat 数据结构的一个重要特性是内存块仅在明确请求时才被复制,大多数操作只是简单地复制 cv::Mat 标头,这样多个对象本质上同时指向同一个数据块,这种内存管理模型使应用程序更高效,同时可以避免内存泄漏。

1.2 cv::Mat 属性

接下来,我们通过编写测试程序 (mat.cpp) 来测试 cv::Mat 数据结构的不同属性。

1. 首先,声明 opencv 头文件和 c++ i/o 流文件:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

2. 创建函数来生成新的灰度图像,其所有像素具有相同的默认值:

cv::Mat function() {
   // 创建新图像并返回
   cv::Mat ima(500, 500, CV_8U, 50);
   return ima;
}

默认情况下, cv::Mat 对象在创建时的大小为零,但也可以指定初始大小:

cv::Mat image1(240,320,CV_8U,100);

如果指定图像初始大小,就需要指定每个矩阵元素的类型,以上代码使用 CV_8U 类型,对应创建的图像像素为 1 字节 (8 位),字母 U 表示它是无符号的;还可以使用字母 S 声明有符号整数类型。对于彩色图像,需要指定三个通道 (CV_8UC3),也可以声明大小为 1632 的整数(同样可以为有符号或无符号)图像,例如,CV_16SC3 表示 16 位有符号整数类型。除了整数类型,还可以使用 32 位或 64 位浮点数,例如,CV_32F 表示 32 位浮点数。

3. 在主函数中,创建六个窗口来显示图像处理结果:

cv::namedWindow("Image 1");
cv::namedWindow("Image 2");
cv::namedWindow("Image 3");
cv::namedWindow("Image 4");
cv::namedWindow("Image 5");
cv::namedWindow("Image");

4. 创建多个不同的图像矩阵(具有不同的尺寸、通道和默认值),并等待按键被按下:

// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值
cv::Mat image1(240,320,CV_8U,100);
cv::imshow("Image", image1); // 显示图像
cv::waitKey(0); 
// 当尺寸或数据类型不同时,需要重新分配内存
image1.create(200,200,CV_8U);
image1 = 200;
cv::imshow("Image", image1); // 显示图像
cv::waitKey(0); 
// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGR
cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));
// 也可以使用以下方法定义
// cv::Mat image2(cv::Size(320,240),CV_8UC3);
// image2= cv::Scalar(0,0,255);
cv::imshow("Image", image2); // 显示图像
cv::waitKey(0); 

图像(或矩阵)的每个元素可以由多个值组成(例如,彩色图像具有三个通道,因此每个坐标点都有三个像素值),因此,OpenCV 引入了一种简单的数据结构,用于将像素值传递给函数,即 cv::Scalar 结构体,一般用来保存一个值或者三个值。例如,要创建一个用红色像素初始化的彩色图像:

cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));

同样,灰度图像的初始化也可以通过使用此结构完成 (cv::Scalar(100))。
图像尺寸大小通常也需要作为参数传递给函数,我们已经知道 colsrows 属性可用于获取 cv::Mat 实例的维度。尺寸大小信息也可以通过 cv::Size 结构提供,它只包含矩阵的高度和宽度,size() 方法可以获取当前矩阵大小。cv::Size 结构是许多必须指定矩阵大小的方法中常用的格式。例如,使用以下方式创建图像:

cv::Mat image2(cv::Size(320,240),CV_8UC3);

5. 使用 imread 函数读取图像并将其复制到另一个图像矩阵:

// 读取图像
cv::Mat image3 =  cv::imread("1.png"); 
// 令 image4 和 image1 指向同一个数据块 image3
cv::Mat image4(image3);
image1 = image3;
// image2 和 image5 是 image3 的副本
image3.copyTo(image2);
cv::Mat image5 = image3.clone();

6. 对复制后的图像应用图像转换函数 (cv::flip()),显示创建的所有图像,然后等待按键:

// 水平翻转图像
cv::flip(image3,image3,1); 
// 检查图像
cv::imshow("Image 3", image3); 
cv::imshow("Image 1", image1); 
cv::imshow("Image 2", image2); 
cv::imshow("Image 4", image4); 
cv::imshow("Image 5", image5); 
cv::waitKey(0); 

7. 使用创建的函数来生成一个新的灰色图像:

// 使用 function 函数创建灰度图像
cv::Mat gray = function();
cv::imshow("Image", gray); // 显示图像
cv::waitKey(0); 

8. 加载一个彩色图像,在加载过程中将其转换为灰度图像,然后,将其值转换为浮点矩阵:

// 将图像读取为灰度图像
image1=  cv::imread("1.png", cv::IMREAD_GRAYSCALE); 
// 将图像转换为值在 [0, 1] 区间内的浮点图像
image1.convertTo(image2,CV_32F,1/255.0,0.0);
cv::imshow("Image", image2); // 显示图像

1.3 完整代码示例

完整代码 (mat.cpp) 如下所示:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

cv::Mat function() {
   // 创建新图像并返回
   cv::Mat ima(500,500,CV_8U,50);
   return ima;
}

int main() {
	// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值
	cv::Mat image1(240,320,CV_8U,100);
	cv::imshow("Image", image1); // 显示图像
	cv::waitKey(0); 
	// 当尺寸或数据类型不同时,需要重新分配内存
	image1.create(200,200,CV_8U);
	image1 = 200;
	cv::imshow("Image", image1); // 显示图像
	cv::waitKey(0); 
	// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGR
	cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));
	// 也可以使用以下方法定义
	// cv::Mat image2(cv::Size(320,240),CV_8UC3);
	// image2= cv::Scalar(0,0,255);
	cv::imshow("Image", image2); // 显示图像
	cv::waitKey(0); 
	// 读取图像
	cv::Mat image3 =  cv::imread("1.png"); 
	// 令 image4 和 image1 指向同一个数据块 image3
	cv::Mat image4(image3);
	image1 = image3;
	// image2 和 image5 是 image3 的副本
	image3.copyTo(image2);
	cv::Mat image5 = image3.clone();
	// 水平翻转图像
	cv::flip(image3,image3,1); 
	// 检查图像
	cv::imshow("Image 3", image3); 
	cv::imshow("Image 1", image1); 
	cv::imshow("Image 2", image2); 
	cv::imshow("Image 4", image4); 
	cv::imshow("Image 5", image5); 
	cv::waitKey(0); 
	// 使用 function 函数创建灰度图像
	cv::Mat gray = function();
	cv::imshow("Image", gray); // 显示图像
	cv::waitKey(0); 
	// 将图像读取为灰度图像
	image1=  cv::imread("1.png", cv::IMREAD_GRAYSCALE); 
	// 将图像转换为值在 [0, 1] 区间内的浮点图像
	image1.convertTo(image2,CV_32F,1/255.0,0.0);
	cv::imshow("Image", image2); // 显示图像
	// 使用 cv::Matx 创建一个 3x3 的双精度 (double-precision) 矩阵
	cv::Matx33d matrix(3.0, 2.0, 1.0,
		               2.0, 1.0, 3.0,
		               1.0, 2.0, 3.0);
	// 使用 cv::Matx 创建一个 3x1 的双精度矩阵 (向量)
	cv::Matx31d vector(5.0, 1.0, 3.0);
	// 矩阵相乘
	cv::Matx31d result = matrix*vector;
	std::cout << result;
	cv::waitKey(0); 
	return 0;
}

编译后,运行此程序查看生成图像(生成的纯色图像未列出):

$ g++ mat.cpp -o mat `pkg-config --cflags --libs opencv4`
$ ./mat

cv::Mat 数据结构探索

2. 探索 cv::Mat 数据结构

2.1 cv::Mat 对象的创建

图像的数据块可以使用 create() 方法分配或重新分配。当图像先前已被分配时,它的旧内容首先被释放,出于效率考虑,如果新分配的尺寸大小和类型与现有的尺寸大小和类型匹配,则不会执行新的内存分配:

image1.create(200,200,CV_8U);

当没有引用指向给定的 cv::Mat 对象时,分配的内存会自动释放,这样可以避免在 C++ 中经常与动态内存分配相关的常见内存泄漏问题,这是 OpenCV 中的一个关键机制,通过让 cv::Mat 类实现引用计数和浅复制来实现。因此,当一个图像分配给另一个图像时,图像数据(即像素)不会被复制,两个图像都将指向同一个内存块,按值传递或按值返回的图像同样也是如此。保留引用计数,以便仅当对图像的所有引用都被销毁或分配给另一个图像时才会释放内存:

cv::Mat image4(image3);
image1= image3;

应用于以上图像 (image3image4image1) 之一的任何变换也将影响其他图像;如果希望创建图像内容的深层副本,需要使用 copyTo() 方法,在这种情况下,将在目标图像上调用 create() 方法;另一种生成图像副本的方法是 clone() 方法,它创建一个相同的新图像:

image3.copyTo(image2);
cv::Mat image5= image3.clone();

如果需要将一个图像复制到另一个不一定具有相同数据类型的图像中,则必须使用 convertTo() 方法:

image1.convertTo(image2,CV_32F,1/255.0,0.0);

以上代码中,源图像 image3 被复制到浮点图像 image2 中。convertTo() 方法包括两个可选参数——比例因子和偏移量。需要注意的是,两个图像的通道数必须相同。
cv::Mat 对象的分配模型还允许我们安全地编写返回图像的函数(或类方法):

cv::Mat function() {
   // 创建新图像并返回
   cv::Mat ima(500,500,CV_8U,50);
   return ima;
}

可以从主函数 main() 中调用这个函数 function()

cv::Mat gray= function();

如果我们调用函数 function(),那么变量 gray 将保存由函数 function() 创建的图像,而无需分配额外的内存。事实上,从 funtion() 返回的 cv::Mat 实例只是 ima 图像的浅拷贝。当 ima 局部变量超出范围时,该变量被释放,但由于关联的引用计数器指示其内部图像数据正在被另一个实例(即变量 gray )引用,因此不会释放其内存块。
需要注意的是,在创建类实例时,不要返回图像的类属性。接下来,我们介绍一个容易出错的实现示例:

class ErrorExample {
	// 图像属性
	cv::Mat ima;
	public:
	ErrorExample(): ima(240, 320, CV_8U, cv::Scalar(100)){}
	cv::Mat method() {return ima;}
}

在以上代码中,如果一个函数调用这个类的方法,它会获得一个图像属性的浅拷贝。如果以后这个副本被修改,类属性也会被修改,这会影响类的后续行为(反之亦然),为了避免这类错误,我们应该返回属性的副本。

2.2 OpenCV 输入和输出数组

查看 OpenCV 文档,可以看到许多方法和函数都接受 cv::InputArray 类型的参数作为输入。该类型是一个简单的代理类,引入此类型用于概括 OpenCV 中数组的概念,从而避免重复实现具有不同输入参数类型的相同方法或函数的多个版本,这意味着可以通过提供 cv::Mat 对象或其他兼容类型作为参数,该类只是一个接口,所以不应该在代码中显式地声明它。
cv::InputArray 也可以使用 std::vector 类构造,这意味着这类对象可以用作 OpenCV 方法和函数的输入。其他兼容类型包括 cv::Scalarcv::Vec;相应的,还存在一个 cv::OutputArray 代理类,用于指定某些方法或函数返回的数组。

小结

cv::Mat 数据结构是 OpenCV 中最基础也是最核心的数据结构,通过此数据结构构成了 OpenCV 图像处理所用的复杂类和函数。本节中,我们介绍了 cv::Mat 结构的基本用法,包括如何创建空白/非空白图像、修改图像数据类型以及复制图像内容等操作,并且深入探索了创建 cv::Mat 数据结构时的内存分配方式,最后介绍了 OpenCV 输入和输出数组的两个代理类,包括 cv::InputArraycv::OutputArray

系列链接

OpenCV实战(1)——OpenCV与图像处理基础

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

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

相关文章

2022 SPSSPRO杯A|B|C题全网最全解题思路+数据分享

一&#xff0c;认证杯数学建模2022 ABC题干分析 2022年第十五届“SPSSPRO杯”数学中国数学建模网络挑战赛 2022认证杯数学中国数学建模网络挑战赛 认证杯这次叫spssrpo 二&#xff0c;A题 人员的紧急疏散 在过去的几十年里&#xff0c;由于大规模集会活动的数量和规模的增加…

大数据采集工具与采集业务划分

目录1- FlumeAgentSourceChannelSinkEvent2- Fluentd3- Logstash4- Chukwa5- Scribe6- Splunk7- Scrapy8- Kafka9- Datax10-日志采集11-数据源数据同步1- Flume https://flume.apache.org/ Flume是Cloudera提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志…

代码随想录56——动态规划:583两个字符串的删除操作、72编辑距离

文章目录1.583两个字符串的删除操作1.1.题目1.2.解答2.72编辑距离2.1.题目2.2.解答1.583两个字符串的删除操作 参考&#xff1a;代码随想录&#xff0c;583两个字符串的删除操作&#xff1b;力扣题目链接 1.1.题目 1.2.解答 本题和 动态规划&#xff1a;115.不同的子序列 相…

在Python中 先乘再除 和 先除再乘 是有差别的

浮点数的原因<font colorblue size4 face"楷体">1 问题来源<font colorblue size4 face"楷体">2 为什么会这样&#xff1f;<font colorblue size4 face"楷体">2.1 分解公式<font colorblue size4 face"楷体">…

最近面试被问到的vue题

v-for 为什么要加 key 如果不使用 key&#xff0c;Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记&#xff0c;通过这个 key&#xff0c;我们的 diff 操作可以更准确、更快速 更准确&#xff1a;因为…

初阶牛之牛客网刷题集(1)

前言 记录一下牛牛自己在牛客网上刷到的一些题目.分享一下牛牛的解题思路,希望可以帮到大家. 目录前言1.母牛的故事解题思路&#xff1a;代码实现&#xff1a;2.替换空格解题思路:代码实现3.二进制中1的个数解题思路代码实现结语1.母牛的故事 题目链接:传送门 有一头母牛&am…

43期《深入浅出Pytorch》课程 - Task01:PyTorch的安装和基础知识+前置知识打卡

Task011、Pytorch安装2、基础知识2.1 张量(Tensor)2.2 自动求导2.3 梯度2.4 并行计算3、前置知识打卡1、Pytorch安装 由于之前使用过Pytorch&#xff0c;所以说不需要再重新下载&#xff0c;直接开始后续的基础知识 2、基础知识 由于之前学习过numpy系列&#xff0c;所以说…

用专业团队管理软件工具轻松“拿捏”年轻运营团队

本文旨在抛砖引玉&#xff0c;欢迎大家拍砖讨论&#xff0c;通过一款时下流行的专业团队管理软件飞项做案例&#xff0c;一起探讨和交流团队管理专业工具软件和一些对应的方法论。 说到国内这几年流行起来的团队管理工具软件&#xff0c;我们先看看互联网这几年的发展。这几年&…

网络舆情监测是干嘛的?

近年来&#xff0c;有赖于移动网络技术的发展和社会化媒体概念的崛起&#xff0c;讯息的传播与扩散速度得到了空前的提高&#xff0c;而对于民营企业来说同样带来了不少的挑战&#xff0c;接下来TOOM舆情监测系统小编带您简单介绍网络舆情监测是干什么的? 一、什么是网络舆情监…

用户画像系列——布隆过滤器在策略引擎中的应用

在用户画像系列——当我们聊用户画像&#xff0c;我们在聊什么&#xff1f; 介绍了用户画像的应用场景: (1)个性化推荐 通过用户标签给用户推荐合适的商品或者内容 (2)营销圈选 参考&#xff1a;用户画像系列——Lookalike在营销圈选扩量中的应用 (3)策略引擎 根据用户标…

HTML5网页设计制作基础大二dreamweaver作业、使用HTML+CSS技术制作博客网站(5个页面)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

重磅推出!CrownCAD 2023全功能试用通道已开启!

CrownCAD 2023已于10月28日正式发布&#xff0c;为了更好地提升设计效率&#xff0c;不仅增强已有功能的智能性、标准性、应用性&#xff0c;更是在产品定义、属性、仿真、数据管理等方面再度突破&#xff0c;更加贴合中国制造需求。 CrownCAD 2023全功能试用通道已开启 为让…

如何在Win10上安装docker

[版权申明] 非商业目的注明出处可自由转载 出自&#xff1a;shusheng007 前言 没有了Mac Pro&#xff0c;我又捡起了我的Windows…&#xff0c;这是我给她安装docker的的记录&#xff0c;你愿意也可以瞅一眼 检查win10版本 我们的安装基于WSL2&#xff08;Windows subsystem…

【深入理解Kotlin协程】lifecycleScope源码追踪扒皮

lifecycleScope是LifecycleOwner的扩展属性&#xff0c;而 ComponentActivity 和 Fragment&#xff08;androidx&#xff09;都实现了 LifecycleOwner 接口&#xff0c;所以这就是为什么说lifecycleScope的作用范围是只能在Activity、Fragment中使用。public val LifecycleOwne…

vue-element-admin动态菜单(后台获取)

vue-element-admin动态菜单&#xff08;后台获取&#xff09;&#xff0c;此教程面向纯小白攻略&#xff0c;不要嫌我啰嗦&#xff0c;翻到自己需要的地方即可 前提 vue-element-admin官网&#xff1a; vue-element-admin (gitee.io) vue-element-admin页面展示&#xff1a;…

Spring Boot框架下实现Excel服务端导入导出

Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。今天我们就使用纯前对按表格控件带大家了解&#xff0c;如何在Spring…

高精度算法【加减乘除】

全文目录&#x1f60d; 前言&#x1f600; 高精度加法&#x1f914; 操作步骤&#x1f635;‍&#x1f4ab; 代码模板&#x1f600;高精度减法&#x1f914;操作步骤&#x1f635;‍&#x1f4ab; 代码模板&#x1f600;高精度乘法&#x1f914;操作步骤&#x1f635;‍&#x…

[R]第二节 对象介绍与赋值运算

前言 R 创建、控制的实体(entity)称为对象(object)。向量(vector)矩阵(matrix)数组(array)数据框(data frame)列表(list)因子(factor)函数(function)通过以上实体定义的更为一般性的结构(structures) 数据的存储形式 R语言进行数据存储选择一种合适的数据结构将已有的数据输入…

【Python实战】海量表情包炫酷来袭,快来pick斗图新姿势吧~(超好玩儿)

前言 有温度 有深度 有广度 就等你来关注哦~ 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 你有在聊天中遇到不知道该如何表达&#xff0c;如何回复的情况吗&#xff1f; 或许&#xff0c;使…

FastDFS学习(四)

目录&#xff1a; &#xff08;1&#xff09;FastDFS搭建集群的环境准备 &#xff08;2&#xff09;FastDFS集群搭建负载均衡环境-使用Nginx进行负载均衡 &#xff08;1&#xff09;FastDFS搭建集群的环境准备 架构图 如果你公司刚好用这个&#xff0c;那你就会搭建集群涉及…