【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/

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值