OpenCV3.4.0学习笔记(一)——cv::Mat的内存结构与访问

cv::Mat的内存结构与访问

cv::Mat 是新版opencv主打的也是最为常用的一种数据类型, 可以用于存储任意维度的多通道数组。

本文目的在于记录学习过程中得到关于 cv::Mat 内存结构,成员变量的一些认识。从数组、指针的角度解释 cv::Mat ,提供从最底层操作 cv::Mat 的任一内容的方法。

首先,cv::Mat 被认为是一个多维数组,那么对任何数组最重要的操作就是数组任意元素的读和写。数组的读写操作也即数组的访问操作。以下先来讲讲 Mat 的元素访问操作,方便进一步延伸到探讨 Mat 中数据内存结构的问题。

现有的常见的数组元素访问方式有三种:

  1. at函数方式访问
  2. iteration迭代器方式访问
  3. ptr指针访问

以下分别给出三种访问方式的代码范例

at函数方式访问
	//定义2行2列,CV_8UC1型的 Mat
	cv::Mat mat(2,2,CV_8UC1);
	//at成员函数方式访问 mat 元素
	uchar p = mat.at<uchar>(0,1); //读0行1列元素
	mat.at<uchar>(1,0) = 128;	//写1行0列元素

at函数方法比较直观,且为最保守的做法,但是需要函数调度开销,效率较低。

iteration迭代器方式访问
	//定义2行2列,CV_8UC1型的 Mat
	cv::Mat mat(2,2,CV_8UC1);
	//定义mat的数据迭代器
	cv::MatIterator_<uchar> iter = mat.begin();
	//迭代器方式访问
	for (int i = 0; i < 2; i++)
		cout << *(iter++) << endl;

迭代器访问也涉及函数调用,效率一般。

ptr指针访问
	//定义2行2列,CV_8UC3型的 Mat
	cv::Mat roi(2,2,CV_8UC3);
	//遍历roi所有元素
	for (int i = roi.rows-1; i >= 0; i--)
	{
		//roi中行元素的首地址储存可能不连续
		unsigned char *ptr = roi.ptr<unsigned char>(i);
		for (int j = roi.cols-1; j >= 0; j--)
		{
			ptr[3 * j + 0] = 0; //roi(i,j)的第一通道元素
			ptr[3 * j + 1] = 255; //roi(i,j)的第二通道元素
			ptr[3 * j + 2] = 0; //roi(i,j)的第三通道元素
		}
	}

mat最快的访问方式,也是opencv推荐的访问方式(二维数组)。这里需要注意,mat数据中,不同行的储存可能是不连续的。行间是否连续可以通过 roi.isContinus() 来获得。

现在问题来了——内存连续性?:

对于高维数组想要采用最高效的指针访问方式【ptr方式】
怎么判断内存连续不连续?只是第一维(行)不连续?第二维呢?第三维?
要去到第几维ptr才能使用指针连续偏移去访问数组元素?

opencv给出了高维数组的更一般化的访问方法:

data+step访问方法

&amp; m a t ( i 0 , i 1 , . . . i n ) = m a t . d a t a + i 0 ∗ s t e p [ 0 ] + i 1 ∗ s t e p [ 1 ] + . . . + i n ∗ s t e p [ n ] \&amp; mat(i_0,i_1,...i_n)=mat.data+i_0*step[0]+i_1*step[1]+...+i_n*step[n] &mat(i0,i1,...in)=mat.data+i0step[0]+i1step[1]+...+instep[n]
对应代码:

	extern int* index; //需要访问元素的下标构成的数组
	matdataType* elementPointer ; //被访问元素的指针
	uchar* tempPointer = mat.data; //临时指针,用于偏移到被访问指针
	for(int i=0;i<mat.dims,i++)
		tempPointer += index[i]*mat.step[i]; //偏移指针
	elementPointer = (matdataType*)tempPointer; //地址强制转化

可见,mat.step[m]记录了第m维度中,i_m 每增加1,地址的偏移量(以byte为计数单位)。当mat被创建,step数组也同时被确定了下来,也即第m维度中,i_m 每增加1,地址的偏移量(以byte为计数单位)同时也是一个固定值了。

更直观地标书,方便起见,讨论当 m=0,此时的step[0]即为行维度坐标增1,地址的偏移量(以byte为计数单位),而 无关于你是第几行

也就是说: 行存储就算不连续,但是相邻行间数据内存首地址间隔保持固定!!!

不只是行,这个可以推广到更高的维度,直到第m维度,第m+1维度的数据存储不管连续与否,数据首地址都是等间隔的。

而这个就是cv::Mat数据内存结构的特点。

以下为第m+1维的数据连续储存的判断

step[m] == step[m+1]*size[m+1]

cv::Mat数据内存结构总结

cv::Mat中 数组data[][]…[m] 与 data[][]…[m+1] 在内存上不一定连续储存,但是同一维度内,地址增1的内存首地址偏移量为常量,数学表达即
d a t a [ ] [ ] . . . [ m + 1 ] − d a t a [ ] [ ] . . . [ m ] = s t e p [ m ] , ∀ m = 1 , 2 , . . . , s i z e [ m ] data[][]...[m+1] - data[][]...[m] = step[m] , \forall m =1,2,...,size[m] data[][]...[m+1]data[][]...[m]=step[m],m=1,2,...,size[m]
且始终有以下关系存在:
s t e p [ m ] &gt; = s t e p [ m + 1 ] ∗ s i z e [ m + 1 ] step[m] &gt;= step[m+1]*size[m+1] step[m]>=step[m+1]size[m+1]
mat.data配合mat.step数组对数据进行访问是最通用的方式。
&amp; m a t ( i 0 , i 1 , . . . i n ) = m a t . d a t a + i 0 ∗ s t e p [ 0 ] + i 1 ∗ s t e p [ 1 ] + . . . + i n ∗ s t e p [ n ] \&amp; mat(i_0,i_1,...i_n)=mat.data+i_0*step[0]+i_1*step[1]+...+i_n*step[n] &mat(i0,i1,...in)=mat.data+i0step[0]+i1step[1]+...+instep[n]
这一访问方式可以访问任意维度cv::Mat任意位置的元素,但是系列的乘法操作会占用较多的时间。

当cv::Mat内存是连续的 (mat.isContinus() == true) ,采用 mat.data 自增方式遍历所有mat元素是效率最高的访问方式。

关于cv::Mat内存结构的延伸性思考

cv::Mat 的 data+step 这种内存寻址方式也是 cv::Mat() 实现获取图像roi(region of interest)引用的基础。也即

cv::Mat cv::Mat(cv::Rect(x,y,h,w)); 
//本质为修改本对象矩阵头副本中的data,step,size,并将矩阵头副本返回
//原data所指向的矩阵元素内容及其排列方式均不发生改变

换句话说,cv::Mat 的 data+step 这种内存寻址方式很大程度上是为了提取roi时提高效率,不涉及新data内存开辟与内容复制而存在的。而在 一般的矩阵开辟过程中,内存的开辟一般是连续的

举个例子对以上结论进行说明:

cv::Mat src(cv::Size(3,3),CV_8UC1); //本质为新开辟data内存
cv::Mat a;
cv::Mat b;

//获取图像roi,本质为修改本对象矩阵头副本中的data,step,size,并将矩阵头副本返回
//原data所指向的矩阵元素内容及其排列方式均不发生改变
a = src(cv::Range(0,1), cv::Range(0,1)); 
//本质为新开辟data内存,重新构建矩阵并复制内容
b = src.clone();

cout << src.isContinuous() << endl; //输出true
cout << a.isContinuous() << endl; //输出false
cout << b.isContinuous() << endl; //输出true

进一步地,我们还有如下结论:

当需要将 cv::Mat a 变成连续存储的数据(不考虑新开辟空间造成的资源开销)时,可以使用如下语句实现:

a = a.clone();

注:以上为作者学习过程中自己认识的记录,如有问题,敬请指正。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值