【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

相关文章

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

C#数据结构之字符串(string)详解

《C#数据结构之字符串(string)详解》:本文主要介绍C#数据结构之字符串(string),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录转义字符序列字符串的创建字符串的声明null字符串与空字符串重复单字符字符串的构造字符串的属性和常用方法属性常用方法总结摘

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

Java中StopWatch的使用示例详解

《Java中StopWatch的使用示例详解》stopWatch是org.springframework.util包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比,这篇文章主要介绍... 目录stopWatch 是org.springframework.util 包下的一个工具类,使用它

Java进行文件格式校验的方案详解

《Java进行文件格式校验的方案详解》这篇文章主要为大家详细介绍了Java中进行文件格式校验的相关方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、背景异常现象原因排查用户的无心之过二、解决方案Magandroidic Number判断主流检测库对比Tika的使用区分zip

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

Java利用JSONPath操作JSON数据的技术指南

《Java利用JSONPath操作JSON数据的技术指南》JSONPath是一种强大的工具,用于查询和操作JSON数据,类似于SQL的语法,它为处理复杂的JSON数据结构提供了简单且高效... 目录1、简述2、什么是 jsONPath?3、Java 示例3.1 基本查询3.2 过滤查询3.3 递归搜索3.4

springboot security快速使用示例详解

《springbootsecurity快速使用示例详解》:本文主要介绍springbootsecurity快速使用示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录创www.chinasem.cn建spring boot项目生成脚手架配置依赖接口示例代码项目结构启用s

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2