本文主要是介绍【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类详解 之 对象创建与数据存储的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!