【OpenCV3图像处理】 Mat类详解 之 对象创建与数据存储

2024-06-12 08:58

本文主要是介绍【OpenCV3图像处理】 Mat类详解 之 对象创建与数据存储,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

一、Mat类的定义:OpenCV3 参考文档:cv::Mat Class

 

Mat类的对象用于表示一个多维度的单通道或者多通道稠密数组,它可以用来存储以下东西:

real or complex-valued vectors or matrices(实数或复数的向量或者矩阵)

grayscale or color images (灰度图或者彩色图)

voxel volumes (立体元素)

vector fields (矢量场)

point clouds (点云)

tensors (张量)

histograms (直方图) 

 

矩阵 (M) 中数据元素的地址计算公式:

addr(Mi0,i1,…im-1) = M.data + M.step[0] * i0 + M.step[1] * i1 + … + M.step[m-1] * im-1

(其中 m = M.dims ,M的维度)


data:Mat对象中的一个指针,指向内存中存放矩阵数据的一块内存 (uchar* data),数据存储方式是按像素存储,不是按通道存储
dims:Mat所代表的矩阵的维度,如 3 * 4 的矩阵为 2 维, 3 * 4 * 5 的为3维
channels:通道数,Mat.channels()得到的是矩阵中的每一个像素拥有的值的个数

depth:深度,即每一个像素的位数(bits),在opencv的Mat.depth()中得到的是一个0–6的数字,enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };

step:是一个数组,定义了矩阵的布局,另外注意 step1 = step / elemSize1,M.step[m-1] 总是等于 elemSize,M.step1(m-1)总是等于 channels;

elemSize : 矩阵中每一个像素的数据大小,如果Mat中的数据的数据类型是 CV_8U 则elemSize = 1;CV_8UC3 则elemSize = 3,记住另外有个 elemSize1 表示的是矩阵中数据类型的大小,即 elemSize / channels 的大小

 

考虑二维情况(stored row by row)按行存储

一个 3 X 4 的矩阵,M.rows == 3; M.cols == 4;二维矩阵,那么维度为 2 (M.dims == 2);

假设其数据类型为 CV_8U,也就是单通道的 uchar 类型,sizeof(uchar) = 1,那么每一个像素大小为 1

(M.elemSize() == 1, M.elemSize1() == 1);

M.depth() == 0, M.channels() == 1;

因为是二维矩阵,step 数组只有两个值, step[0] 和 step[1] 分别代表一行的数据大小和一个像素的数据大小

则 M.step[0] == 4, M.step[1] == 1;

 

假设上面的矩阵数据类型是 CV_8UC3,也就是三通道

M.dims == 2;

M.channels() == 3;

M.depth() == 0;

M.elemSize() == 3 (每一个像素包含3个uchar值)

M.elemSize1() == 1 (elemSize / channels)

M.step[0] == M.cols * M.elemSize() == 12,

M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;

 

考虑三维情况(stored plane by plane)按面存储

一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型
M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;

M.rows == M.cols == –1;

M.elemSize() == M.elemSize1() * M.channels() == 2 * 4 == 8;

M.step[M.dims-1]==M.elemSize() == 8;

M.step[0] == 4 * 6 * M.elemSize() == 192;

M.step[1] == 6 * M.elemSize() == 48;

M.step[2] == M.elemSize() == 8;

M.step1(0) == M.step[0] / M.elemSize() == 192 / 2 == 96 (第一维度(即面的像素个数) * 通道数);

M.step1(1) == M.step[1] / M.elemSize() == 48 / 2 == 24(第二维度(即行的像素个数/列宽) * 通道数);

M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三维度(即像素) * 通道数);

 

 

二、创建Mat类对象的方式:

1.构造函数

(1)方式一 :制定行数列数,但是不初始化赋值

Mat::Mat(int rows, int cols, int type)
创建行数为 rows,列数为col,类型为type的图像;

Mat::Mat(Size size, int type)
创建大小为 size,类型为type的图像;

 

type的类型有CV_8UC1,CV_16SC1,…,CV_64FC4等。里面的8U表示8位无符号整数,16S表示16位有符号整数,64F表示64位浮点数(即double类型);C后面的数表示通道数,例如C1表示一个通道的图像,C4表示4个通道的图像,以此类推。

如果你需要更多的通道数,需要用宏CV_8UC(n),例如:Mat M(3,2, CV_8UC(5));      //创建行数为3,列数为2,通道数为5的图像

 

(2)方式二 :指定行数和列数,使用Scalar初始化赋值

Mat::Mat(int rows, int cols, int type, const Scalar& s)

创建行数为rows,列数为col,类型为type的图像,并将所有元素初始化为值s;

Mat::Mat(Size size, int type, const Scalar& s)
创建大小为 size,类型为type的图像,并将所有元素初始化为值s

 

Mat m(3, 5, CV_32FC1, Scalar(1));   // m为3*5的矩阵,float型的单通道,把每个点都初始化为1

cout<<m;


输出为:
[1, 1, 1, 1, 1;
  1, 1, 1, 1, 1;
  1, 1, 1, 1, 1]
 

Mat m(3, 5, CV_32FC2, Scalar(1, 2));    // m为3*5的矩阵,float型的2通道,把每个点都初始化为1 2
cout<<m;

输出为:
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
  1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
  1, 2, 1, 2, 1, 2, 1, 2, 1, 2]

 

Mat m(3, 5, CV_32FC3, Scalar(1, 2, 3));// m为3*5的矩阵,float型的3通道,把每个点都初始化为1 2 3
cout << m

输出为:
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
  1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
  1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

 

(3)方式三:使用已有的的mat对象赋值,其实就是对已有的mat对象取了别名

Mat::Mat(const Mat& m)
将 m 赋值给新创建的对象,此处不会对图像数据进行复制, m 和新对象共用图像数据;

 

(4)方式四:指定行数和列数,使用已有数据源的数据地址初始化赋值

Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)

创建行数为 rows,列数为cols,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定。

Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
创建大小为 size,类型为type的图像,此构造函数不创建图像数据所需内存,而是直接使用data所指内存,图像的行步长由step指定。

uchar *data = new uchar[15];
for (int i = 0; i < 15; i++)
{
   data[i] = 2;
}
Mat m(3, 5, CV_8UC1, data);
cout << m;


输出为:
[2, 2, 2, 2, 2;
  2, 2, 2, 2, 2;
  2, 2, 2, 2, 2]


如果接着

delete [] data;
cout << m;


输出为:
[-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144]
可见,这里只是进行了浅拷贝,当数据源不在的时候,Mat里的数据也就是乱码了。

 

uint *data = new uint[24];
for (int i =1; i < =24; i++)
{
   data[i] = i;
}
Mat m(2, 4, CV_16UC3, data);
cout << m;

 

输出为: 

[1, 2, 3, 4, 5,6,7,8,9,10,11,12;

13,14,15,16, 17, 18, 19, 20,21,22,23,24]

可知,数据存储方式是按像素存储,不是按通道存储

uchar *data = new uchar[15];
for (int i = 0; i < 15; i++)
{
   data[i] = 2;
}
Mat m(3, 5, CV_8UC1, data);
cout << m;


输出为:
[2, 2, 2, 2, 2;
  2, 2, 2, 2, 2;
  2, 2, 2, 2, 2]


如果接着

delete [] data;
cout << m;


输出为:
[-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144]


可见,这里只是进行了浅拷贝!!!!!!!   当数据源不在的时候,Mat里的数据也就是乱码了

 

uint *data = new uint[24];
for (int i =1; i < =24; i++)
{
   data[i] = i;
}
Mat m(2, 4, CV_16UC3, data);
cout << m;

 

输出为: 

[1, 2, 3, 4, 5,6,7,8,9,10,11,12;

13,14,15,16, 17, 18, 19, 20,21,22,23,24]

可知,数据存储方式是按像素存储,不是按通道存储

 

(5)方式五:使用已有mat对象的部分区域初始化赋值

Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
创建的新图像为 m 的一部分,具体的范围由 rowRange 和 colRange 指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据;

Mat::Mat(const Mat& m, const Rect& roi)
创建的新图像为 m 的一部分,具体的范围 roi 指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据。
 

2.create()函数创建对象

Mat M;

M.create(3,2,CV_8UC2);

需要注意的时,使用create()函数无法设置图像像素的初始值。

Mat M(2,2,CV_8UC3);   //构造函数创建图像
M.create(3,2, CV_8UC2);   //释放内存重新创建图像


如果create( )函数指定的参数与图像之前的参数相同,则不进行实质的内存申请操作;如果参数不同,则减少原始数据内存的索引,并重新申请内存
 

 

三、Mat类对象之间的拷贝

Mat这个类有两部分数据:

一个是matrix header,这部分的大小是固定的,包含矩阵的大小,存储的方式,矩阵存储的地址等等。

另一个部分是一个指向矩阵包含像素值的指针

Mat A, C;   

A = imread(argv[1], CV_LOAD_IMAGE_COLOR); 

Mat B(A);

C = A; 

copy这样的操作只是copy了矩阵的matrix header和那个指针,而不是矩阵的本身,也就意味着两个矩阵的数据指针指向的是同一个地址,需要开发者格外注意。比如上面这段程序,A、B、C指向的是同一块数据,他们的header不同,但对于A的操作同样也影响着B、C的结果。当我不再使用A的时候就把内存释放了,那时候再操作B和C岂不是很危险,不用担心,OpenCV的大神为我们已经考虑了这个问题,是在最后一个Mat不再使用的时候才会释放内存,咱们就放心用就行了。

 

如果想建立互不影响的Mat,是真正的复制操作,需要使用函数clone()或者copyTo()

说到数据的存储,这一直就是一个值得关注的问题,Mat_<uchar>对应的是CV_8U,Mat_<char>对应的是CV_8S,Mat_<int>对应的是CV_32S,Mat_<float>对应的是CV_32F,Mat_<double>对应的是CV_64F,对应的数据深度如下:

• CV_8U - 8-bit unsigned integers ( 0..255 )

• CV_8S - 8-bit signed integers ( -128..127 )

• CV_16U - 16-bit unsigned integers ( 0..65535 )

• CV_16S - 16-bit signed integers ( -32768..32767 )

• CV_32S - 32-bit signed integers ( -2147483648..2147483647 )

• CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )

• CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )

这里还需要注意一个问题,很多OpenCV的函数支持的数据深度只有8位和32位的,所以要少使用CV_64F,但是vs的编译器又会把float数据自动变成double型,有些不太爽。

 

参考:https://www.douban.com/note/265479171/

这篇关于【OpenCV3图像处理】 Mat类详解 之 对象创建与数据存储的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1053765

相关文章

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

python管理工具之conda安装部署及使用详解

《python管理工具之conda安装部署及使用详解》这篇文章详细介绍了如何安装和使用conda来管理Python环境,它涵盖了从安装部署、镜像源配置到具体的conda使用方法,包括创建、激活、安装包... 目录pytpshheraerUhon管理工具:conda部署+使用一、安装部署1、 下载2、 安装3

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat