上一篇文章提到了访问矩阵中元素的前两种方式,下面讲第三种方式:正确的访问矩阵中数据的方式:
正确的方式
前面介绍的一些读取和写入矩阵数据的方式,实际上,你可能很少会使用它们。因为,在大多数情况下,你需要使用最有效率的方式来访问矩阵中的数据。如果使用以上的函数界面来访问数据,效率比较低,你应该使用指针方式来直接访问矩阵中数据。特别是,如果你想遍历矩阵中所有元素时,就更需要这样做了。
在用指针直接访问矩阵元素时,就需要格外注意矩阵结构体中的step成员。该成员是以字节为单位的每行的长度。而矩阵结构体的cols或width就不适合此时使用,因为为了访问效率,矩阵中的内存分配上,是以每四个字节做为最小单位的。因此如果一个矩阵的宽度是三个字节,那么就会在宽度上分配四个字节,而此时每行最后一个字节会被忽略掉。所以我们用step则会准确地按行访问数据。
我们可以通过以下例子,看一下rows,cols,height,width,step的数据,你可以通过改变矩阵的元素类型定义,来查看step的改变:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
//矩阵元素为三通道8位浮点数
CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
printf("rows=%d,cols=%d,height=%d,width=%d,step=%d/n",mat->rows,mat->cols,mat->height,mat->width,mat->step);
}
如果我们的矩阵存储的是浮点型(或整数类型)数据,此时矩阵中每个元素占4字节,则如果我们用float类型指针指向下一行时,我们实际上要用float类型指针挪动step/4的长度,因为float类型指针每挪动一个单位就是4个字节长度。
如果我们的矩阵存储的是double类型数据,此时矩阵中每个元素占8字节,则如果我们用double类型指针指向下一行时,我们实际上要用double类型指针挪动step/8的长度,因为double类型指针每挪动一个单位就是8个字节长度。
我们重新看一下CvMat类型的数据结构定义,其中,data就是数据部分,指向data的指针可以是多种数据类型的:
typedef struct CvMat {
int type;
int step;
int* refcount; // for internal use only
union {
uchar* ptr;
short* s;
int* i;
float* fl;
double* db;
} data;//数据部分
union {
int rows;
int height;
};
union {
int cols;
int width;
};
} CvMat;
我们可以通过为矩阵赋值,和读取的例子,查看怎样使用step:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
//矩阵元素为三通道8位浮点数
CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
float *p;
int row,col;
for(row=0; row< mat->rows; row++)
{
p = mat->data.fl + row * (mat->step/4);
for(col = 0; col < mat->cols; col++)
{
*p = (float) row+col;
*(p+1) = (float) row+col+1;
*(p+2) =(float) row+col+2;
p+=3;
}
}
for(row = 0; row < mat->rows; row++)
{
p = mat->data.fl + row * (mat->step/4);
for(col = 0; col < mat->cols; col++)
{
printf("%f,%f,%f/t",*p,*(p+1),*(p+2));
p+=3;
}
printf("/n");
}
}
如果我们使用的指针类型为uchar*类型,则事情可能会简单一些,不用考虑step/4,step/8等类似情况,我们推荐用这种方式。如下例所示:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
//矩阵元素为三通道8位浮点数
CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
float *p;
int row,col;
for(row=0; row< mat->rows; row++)
{
p = (float*)(mat->data.ptr + row * mat->step);
for(col = 0; col < mat->cols; col++)
{
*p = (float) row+col;
*(p+1) = (float) row+col+1;
*(p+2) =(float) row+col+2;
p+=3;
}
}
for(row = 0; row < mat->rows; row++)
{
p = (float*)(mat->data.ptr + row * mat->step);
for(col = 0; col < mat->cols; col++)
{
printf("%f,%f,%f/t",*p,*(p+1),*(p+2));
p+=3;
}
printf("/n");
}
}
最后要注意一下,我们在每行都要使用step重新计算一下指针的位置,这好象不如从首指针从头到尾一直指下去,如我们上一文章的例子一样
:
#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
void main()
{
//矩阵元素为三通道浮点数
CvMat* mat = cvCreateMat(3,3,CV_32FC3);
cvZero(mat);//将矩阵置0
//为矩阵元素赋值
//获得矩阵元素(0,0)的指针
float *p = (float*)cvPtr2D(mat, 0, 0);
//为矩阵赋值
for(int i = 0; i < 9; i++)
{
//为每个通道赋值
*p = (float)i*10;
p++;
*p = (float)i*10+1;
p++;
*p = (float)i*10+2;
p++;
}
//打印矩阵的值
p = (float*)cvPtr2D(mat, 0, 0);
for(i = 0; i < 9; i++)
{
printf("%2.1f,%2.1f,%2.1f/t",*p,*(p+1),*(p+2));
p+=3;
if((i+1) % 3 == 0)
printf("/n");
}
}
但是一定要注意了,这个例子其实是不对的!因为我们说过,分配矩阵内存空间时,是以四字节为最小单位的,这就很有可能有不到四个字节而取成四个字节的情况,所以,如果用矩阵首地址从头到尾指下去访问数据,就很有可能访问到不是数据的字节上去!这一点请务必牢记!!
综上所述,如果要直接访问矩阵中数据,请记住使用step的方案。
另一个需要知道的情况是,我们需要了解一个多维数组(矩阵)和一个一维,但是包含高维数据的数组之间的区别。假设,你有n个点(每个点有x,y,z坐标值) 需要保存到CvMat*中,你其实有四种方式可以使用,但这四种方式的存储形式不同。你可能使用一个二维矩阵,矩阵大小为n行3列,数据类型为 CV32FC1。你还可以使用一个二维矩阵,矩阵大小为3行n列,数据类型为CV32FC1;第三种可能性是,你使用一个一维矩阵,n行1列,数据类型为 CV32FC3;最后,你还可以使用1行三列,数据类型为CV32FC3.这几种方式,在内存分配上,有些是相同的,有些是不同的,如下所示:
n个点的集合(n=5);
(x0 y0 z0) (x1 y1 z1) (x2 y2 z2) (x3 y3 z3) (x4 y4 z4)
n行1列时(数据类型CV32FC3)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
1行n列时(数据类型CV32FC3)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
n行3列时(数据类型CV32FC1)内存分配情况
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
3行n列时(数据类型CV32FC1)内存分配情况
x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 z0 z1 z2 z3 z4
我们可以看出,前三种的内存分配情况相同,但最后一种的内存分配不同。更复杂的是,如果有n维数组,每个数组的元素是c维(c可能是通道数)时。所以,多维数组(矩阵)和一个一维但包含多维数据的数组一般是不同的。
对于一个Rows行Cols列,通道数为Channels的矩阵,访问其中第row行,第col列,第channel通道的数据,可以使用如下公式:
数据地址偏移量=row*Cols*Channels+col*Channels+channel