矩阵数据的存取

访问矩阵中的数据有3种方法:简单的方法、麻烦的方法和恰当的方法。

简单的方法

从矩阵中得到一个元素的最简单的方法是利用宏CV_MAT_ELEM()。这个宏(参见例3-4)传入矩阵、待提取的元素的类型、行和列数4个参数,返回提取出的元素的值。

例3-4:利用CV_MAT_ELEM()宏存取矩阵

 
 
  1. CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );  
  2. float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 ); 

更进一步,还有一个与此宏类似的宏,叫CV_MAT_ELEM_PTR()。CV_MAT_ELEM_ PTR()(参见例3-5)传入矩阵、待返回元素的行和列号这3个参数,返回指向这个元素的指针。该宏和CV_MAT_ELEM()宏的最重要的区别是后者在指针解引用之前将其转化成指定的类型。如果需要同时读取数据和设置数据,可以直接调用CV_MAT_ELEM_PTR()。但在这种情况下,必须自己将指针转化成恰当的类型。

例3-5:利用宏CV_MAT_ELEM_PTR()为矩阵设置一个数值

 
 
  1. CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );  
  2. float element_3_2 = 7.7;  
  3. *( (float*)CV_MAT_ELEM_PTR( *mat, 3, 2 ) ) = element_3_2; 

【36】

遗撼的是,这些宏在每次调用的时候都重新计算指针。这意味着要查找指向矩阵基本元素数据区的指针、计算目标数据在矩阵中的相对地址,然后将相对位置与基本位置相加。所以,即使这些宏容易使用,但也不是存取矩阵的最佳方法。在计划顺序访问矩阵中的所有元素时,这种方法的缺点尤为突出。下面我们将讲述怎么运用最好的方法完成这个重要任务。

麻烦的方法

在"简单的方法"中讨论的两个宏仅仅适用于访问1维或2维的数组(回忆一下,1维的数组,或者称为"向量"实际只是一个n×1维矩阵)。OpenCV提供了处理多维数组的机制。事实上,OpenCV可以支持普通的N维的数组,这个N值可以取值为任意大的数。

为了访问普通矩阵中的数据,我们可以利用在例3-6和例3-7中列举的cvPtr*D和cvGet*D…等函数族。cvPtr*D家族包括cvPtr1D(), cvPtr2D(), cvPtr3D()和cvPtrND()…。这三个函数都可接收CvArr*类型的矩阵指针参数,紧随其后的参数是表示索引的整数值,最后是一个可选的参数,它表示输出值的类型。函数返回一个指向所需元素的指针。对于cvPtrND()来说,第二个参数是一个指向一个整型数组的指针,这个数组中包含索引的合适数字。后文会再次介绍此函数(在这之后的原型中,也会看到一些可选参数,必要时会有讲解)。

例3-6:指针访问矩阵结构

 
 
  1. uchar* cvPtr1D(  
  2.   const CvArr*  arr,  
  3.   int           idx0,  
  4.   int*          type = NULL 
  5. );  
  6.  
  7. uchar* cvPtr2D(  
  8.   const CvArr*  arr,  
  9.   int           idx0,  
  10.  
  11.  
  12.  
  13.   int           idx1,  
  14.   int*          type = NULL 
  15. );  
  16.  
  17. uchar* cvPtr3D(  
  18.   const CvArr*  arr,  
  19.   int           idx0,  
  20.   int           idx1,  
  21.   int           idx2,  
  22.   int*          type = NULL 
  23. );  
  24. uchar* cvPtrND(  
  25.   const CvArr*  arr,  
  26.   int*          idx,  
  27.   int*          type            = NULL,  
  28.   int           create_node     = 1,  
  29.   unsigned*     precalc_hashval = NULL 
  30. ); 

【37~38】

如果仅仅是读取数据,可用另一个函数族cvGet*D。如例3-7所示,该例与例3-6类似,但是返回矩阵元素的实际值。

例3-7:CvMat和IPlImage元素函数

 
 
  1. double cvGetReal1D( const CvArr* arr, int idx0 );  
  2. double cvGetReal2D( const CvArr* arr, int idx0, int idx1 );  
  3. double cvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 );  
  4. double cvGetRealND( const CvArr* arr, int* idx );  
  5.  
  6. CvScalar cvGet1D( const CvArr* arr, int idx0 );  
  7. CvScalar cvGet2D( const CvArr* arr, int idx0, int idx1 );  
  8. CvScalar cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );  
  9. CvScalar cvGetND( const CvArr* arr, int* idx ); 

cvGet*D中有四个函数返回的是整型的,另外四个的返回值是CvScalar类型的。这意味着在使用这些函数的时候,会有很大的空间浪费。所以,只是在你认为用这些函数比较方便和高效率的时候才用它们,否则,最好用cvPtr*D。

用cvPtr*D()函数族还有另外一个原因,即可以用这些指针函数访问矩阵中的特定的点,然后由这个点出发,用指针的算术运算得到指向矩阵中的其他数据的指针。在多通道的矩阵中,务必记住一点:通道是连续的,例如,在一个3通道2维的表示红、绿、蓝(RGB)矩阵中。矩阵数据如下存储rgbrgbrgb . . .。所以,要将指向该数据类型的指针移动到下一通道,我们只需要将其增加1。如果想访问下一个"像素"或者元素集,我们只需要增加一定的偏移量,使其与通道数相等。

另一个需要知道的技巧是矩阵数组的step元素(参见例3-1和例3-3),step是矩阵中行的长度,单位为字节。在那些结构中,仅靠cols或width是无法在矩阵的不同行之间移动指针的,出于效率的考虑,矩阵或图像的内存分配都是4字节的整数倍。所以,三个字节宽度的矩阵将被分配4个字节,最后一个字节被忽略。因此,如果我们得到一个字节指针,该指针指向数据元素,那么我们可以用step和这个指针相加以使指针指向正好在我们的点的下一行元素。如果我们有一个整型或者浮点型的矩阵,对应的有整型和浮点型的指针指向数据区域,我们将让step/4与指针相加来移到下一行,对双精度型的,我们让step/8与指针相加(这里仅仅考虑了C将自动地将差值与我们添加的数据类型的字节数相乘)。                【38】

例3-8中的cvSet*D和cvGet*D多少有些相似,它通过一次函数调用为一个矩阵或图像中的元素设置值,函数cvSetReal*D()和函数cvSet*D()可以用来设置矩阵或者图像中元素的数值。

例3-8:为CvMat或者IplImage元素设定值的函数

 
 
  1. void cvSetReal1D( CvArr* arr, int idx0, double value );  
  2. void cvSetReal2D( CvArr* arr, int idx0, int idx1, double value );  
  3. void cvSetReal3D(  
  4.   CvArr* arr,  
  5.   int idx0,  
  6.   int idx1,  
  7.   int idx2,  
  8.   double value  
  9. );  
  10. void cvSetRealND( CvArr* arr, int* idx, double value );  
  11.  
  12. void cvSet1D( CvArr* arr, int idx0, CvScalar value );  
  13. void cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );  
  14. void cvSet3D(  
  15.   CvArr* arr,  
  16.   int idx0,  
  17.  
  18.   int idx1,  
  19.   int idx2,  
  20.   CvScalar value  
  21. );  
  22. void cvSetND( CvArr* arr, int* idx, CvScalar value ); 

为了方便,我们也可以使用cvmSet()和cvmGet(),这两个函数用于处理浮点型单通道矩阵,非常简单。

 
 
  1. double cvmGet( const CvMat* mat, int row, int col )  
  2. void cvmSet( CvMat* mat, int row, int col, double value ) 

以下函数调用cvmSet():

 
 
  1. cvmSet( mat, 2, 2, 0.5000 ); 

等同于cvSetReal2D函数调用:

 
 
  1. cvSetReal2D( mat, 2, 2, 0.5000 ); 


恰当的方法

从以上所有那些访问函数来看,你可能会想,没有必要再介绍了。实际上,这些set和get函数很少派上用场。大多数时侯,计算机视觉是一种运算密集型的任务,因而你想尽量利用最有效的方法做事。毋庸置疑,通过这些函数接口是不可能做到十分高效的。相反地,应该定义自己的指针计算并且在矩阵中利用自己的方法。如果打算对数组中的每一个元素执行一些操作,使用自己的指针是尤为重要的(假设没有可以为你执行任务的OpenCV函数)。

要想直接访问矩阵,其实只需要知道一点,即数据是按光栅扫描顺序存储的,列("x")是变化最快的变量。通道是互相交错的,这意味着,对于一个多通道矩阵来说,它们变化的速度仍然比较快。例3-9显示了这一过程。   【39~40】

例3-9:累加一个三通道矩阵中的所有元素

 
 
  1. float sum( const CvMat* mat ) {  
  2.  
  3.   float s = 0.0f;  
  4.   for(int row=0; row<mat->rows; row++ ) {  
  5.     const float* ptr=(const float*)(mat->data.ptr + row * mat->step);  
  6.     for( col=0; col<mat->cols; col++ ) {  
  7.       s += *ptr++;  
  8.  
  9.     }  
  10.   }  
  11.   return( s );  
计算指向矩阵的指针时,记住一点:矩阵的元素data是一个联合体。所以,对这个指针解引用的时候,必须指明结构体中的正确的元素以便得到正确的指针类型。然后,为了使指针产生正确的偏移,必须用矩阵的行数据长度(step)元素。我们以前曾提过,行数据元素的是用字节来计算的。为了安全,指针最好用字节计算,然后分配恰当的类型,如浮点型。CvMat结构中为了兼容IplImage结构,有宽度和高度的概念,这个概念已经被最新的行和列取代。最后要注意,我们为每行都重新计算了ptr,而不是简单地从开头开始,尔后每次读的时候累加指针。这看起来好像很繁琐,但是因为CvMat数据指针可以指向一个大型数组中的ROI,所以无法保证数据会逐行连续存取。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值