分享

OpenCv常用图像和矩阵操作

 学海无涯GL 2013-05-08

学习资料

书籍

Learning OpenCV(影印版)

作者:Gary Bradski, Adrian Kaehler

出版社:东南大学出版社

学习OpenCV(中文版)

作者:Gary Bradski, Adrian Kaehler

译者:于仕琪 刘瑞祯

出版社:清华大学出版社

OpenCV中文教程

作者:刘瑞祯 于仕琪

网站:

http://www./index.php/%E9%A6%96%E9%A1%B5

http://book.51cto.com/art/200912/172349.htm

本地安装目录

在安装目录 OpenCV1.0\docs 下有各种学习资料

只用在本地安装目录下面就可以查询到大部分需要的信息,当然也可以直接百度,google


图像IplImage

Structure IplImage

OpenCv中图像的结构体为IplImage,位于头文件cxcore.h中,IplImage 结构体的定义如下:

/////////////////////////////////////////////////////////////////////////////

typedef struct _IplImage

  {

  int nSize; /* IplImage大小 */

  int ID; /* 版本 (=0)*/

  int nChannels; /* 大多数OPENCV函数支持1,2,3 4 个通道 */

  int alphaChannel; /* OpenCV忽略 */

  int depth; /* 像素的位深度,主要有以下支持格式: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,IPL_DEPTH_16S, IPL_DEPTH_32S,

  IPL_DEPTH_32F IPL_DEPTH_64F */

  char colorModel[4]; /* OpenCV忽略 */

  char channelSeq[4]; /* 同上 */

  int dataOrder; /* 0 - 交叉存取颜色通道, 1 - 分开的颜色通道.

  只有cvCreateImage可以创建交叉存取图像 */

  int origin; /*图像原点位置: 0表示顶-左结构,1表示底-左结构 */

  int align; /* 图像行排列方式 (4 or 8),在 OpenCV 被忽略,使用 widthStep 代替 */

  int width; /* 图像宽像素数 */

  int height; /* 图像高像素数*/

  struct _IplROI *roi; /* 图像感兴趣区域,当该值非空时,

  只对该区域进行处理 */

  struct _IplImage *maskROI; /* OpenCV中必须为NULL */

  void *imageId; /* 同上*/

  struct _IplTileInfo *tileInfo; /*同上*/

  int imageSize; /* 图像数据大小(在交叉存取格式下ImageSize=image->height*image->widthStep),单位字节*/

  char *imageData; /* 指向排列的图像数据 */

  int widthStep; /* 排列的图像行大小,以字节为单位 */

  int BorderMode[4]; /* 边际结束模式, OpenCV 被忽略*/

  int BorderConst[4]; /* 同上 */

  char *imageDataOrigin; /* 指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的 */

  } IplImage;    }
    IplImage;

/////////////////////////////////////////////////////////////////////////////

   主要的成员变量有

   nChannels :  图像的通道数目,即灰度图像:nChannels = 1; RGB图像nChannels = 3

   depth:      每个像素值的数据类型和所占的存储空间

   origin变量可以有两种取值:IPL_ORIGIN_TL 或者 IPL_ORIGIN_BL,分别设置坐标原点的位置于图像的左上角或者左下角。在计算机视觉领域,一个重要的错误来源就是原点位置的定义不统一。具体而言,图像的来源、操作系统、编解码器和存储格式等因素都可以影响图像坐标原点的选取。举例来说,你或许认为自己正在从图像上面的脸部附近取样,但实际上却在图像下方的裙子附近取样。避免此类现象发生的最好办法是在最开始的时候检查一下系统,在所操作的图像块的地方画点东西试试。

dataOrder:   多通道的数据存储方式,dataOrder=0是交叉通道存储方式,即BGRBGRBGRBGR的方式存储;dataOrder=1是采用独立通道方式存储,即RRRRRRR。。。,GGGGGGG…,BBBBBB…,一般都是BGRBGRBGR的这种交叉存储方式,cvCreateImage生成的图像也是这种存储方式。

   width:      图像的宽度

   height:      图像的高度

   imageData:  图像的像素矩阵

   widthStep:    每一行像素所占的字节数目. 参数widthStep包括相邻行的同列点之间的字节数。仅凭变量width是不能计算这个值的,因为为了处理过程更高效每行都会用固定的字节数来对齐;因此在第i行末和第i+1行开始处可能会有些冗于字节。参数imageData包含一个指向第一行图像数据的指针。如果图像中有些独立的平面(如当dataOrder = IPL_DATA_ORDER_PLANE)那么把它们作为单独的图像连续摆放,总行数为heightnChannels的乘积。但通常情况下,它们是交错的,使得行数等于高度,而且每一行都有序地包含交错的通道。

    ROI-- 感兴趣的区域(ROI),实际上它是另一个IPL/IPP 结构IplROI的实例。IplROI包含xOffsetyOffsetheightwidthcoi成员变量,其中COI代表channel of interest(感兴趣的通道)ROI的思想是: 一旦设定ROI,通常作用于整幅图像的函数便会只对ROI所表示的子图像进行操作。如果IplImage变量中设置了ROI,则所有的OpenCV函数就会使用该ROI变量。如果COI被设置成非0值,则对该图像的操作就只作用于被指定的通道上了 。不幸的是,许多OpenCV函数都忽略参数COI

 

图像的常用操作

图像载入函数

  函数cvLoadImage载入指定图像文件,并返回指向该文件的IplImage指针。函数支持bmpjpg png tiff等格式的图像。其函数原型如下:

  IplImage* cvLoadImage( const char* filename, int iscolor);

  其中,filename 是待载入图像的名称,包括图像的扩展名;iscolor是一个辅助参数项,可选正数、零和负数三种值,正数表示作为三通道图像载入,零表示该图像作为单通道图像,负数表示载入图像的通道数由图像文件自身决定。

窗口定义函数

  函数cvNamedWindow定义一个窗口,用于显示图像。其函数原型如下:

  int cvNamedWindow( const char* name, unsigned long flags );

  其中,name是窗口名,flags是窗口属性指标值,可以选择CV_WINDOW_AUTOSIZE0两种值。CV_WINDOW_AUTOSIZE表示窗口尺寸与图像原始尺寸相同,0表示以固定的窗口尺寸显示图像。

图像显示函数

  函数cvShowImage是在指定的窗口中显示图像,其函数原型如下:

  void cvShowImage( const char* name, const CvArr* image );

其中,name是窗口名称,image是图像类型指针,一般是IplImage指针。

图像保存函数

  函数cvSaveImage以指定的文件名保存IplImage类型的指针变量,其函数原型如下:

  int cvSaveImage( const char* filename, const CvArr* image );

其中,filename是图像保存路径和名称,imageIplImage指针变量。

Trick

如果要保存一组图像到result文件夹,图像个数为n,保存名称按照一定的序号递增,假设为imgTmp0.jpgimgTmp1.jpgimgTmp2.jpgimgTmp3.jpg…, imgTmpn.jpg,则

操作为:

char * f[30];

for(int i=0; i<n; i++)

{

sprintf(f,”result/imgTmp%d.jpg”,i);

cvSaveImage(f,img);

}

借用sprintf函数即可以完成依次命名的功能。

图像销毁函数

  函数cvReleaseImage销毁已定义的IplImage指针变量,释放占用内存空间。其函数原型如下:

  void cvReleaseImage( IplImage** image );

其中,image为已定义的IplImage指针。

存取图像像素

包括获取像素值和对像素值赋值

l         直接获取

假设图像为 IplImage * img,图像的depth= IPL_DEPTH_8U(每个像素用8 bits表示),获取像素坐标(x,y)的操作为

1.      灰度图像(单通道 img->nChannels = 1

对像素赋值:

   ((char*)(img->imageData + y*imge->widthStep))[x] = 255;

获取像素值:

   int grayValue = ((char*)(img->imageData + y*imge->widthStep))[x];

2.彩色图像(单通道 img->nChannels = 1

对像素赋值:

   ((char*)(img->imageData + y*imge->widthStep))[3*x] = 255;

((char*)(img->imageData + y*imge->widthStep))[3*x+1] = 255;

((char*)(img->imageData + y*imge->widthStep))[3*x+2] = 255;

获取像素值:

   uchar b = ((char*)(img->imageData + y*imge->widthStep))[x];

uchar g = ((char*)(img->imageData + y*imge->widthStep))[x];

uchar r = ((char*)(img->imageData + y*imge->widthStep))[x];

注意:

注意(char*)这个指针的强制转换是针对img->imageData + y*imge->widthStep的,也就是针对图像的行指针进行的转换,注意括弧的范围。

image->depth为其他值时,则可能每个像素的数据类型需要进行(int*),(float*),(double*)等转换。

参数widthStep是相邻行的同列点之间的字节数。仅凭变量width是不能计算这个值的,因为为了处理过程更高效每行都会用固定的字节数来对齐;因此在第i行末和第i+1行开始处可能会有些冗于字节。一次在进行行切换时,一定要widthStep来进行内存的偏移,而不是用depth*width.

一般的情况下,假设有 N-通道,类型为 T 的图像:

I(x,y)c ~ ((T*)(img->imageData + img->widthStep*y))[x*N + c]    //注意x.,y的位置

例子:

void saturate_sv( IplImage* img )

 {  

  for( int y=0; y < img->height; y++ )

 {  

       uchar* ptr = (uchar*) (  Img->imageData + y * img->widthStep   );  

       for( int x=0; x < img->width; x++ ) 

{  

    ptr[3*x] = 255;  

         ptr[3*x+1] = 255;  

         ptr[3*x+2] = 255;  

     }  

   }  

在以上程序中,我们用指针ptr指向第y行的起始位置。接着,我们从指针中析出饱和度和高度在x维的值。因为这是一个三通道图像,所以C通道在x行的位置是3*x+c

该使用方法是受限的

uchar b,g,r;

b = img->imageData[img->widthStep * row + col * 3]
g = img->imageData[img->widthStep * row + col * 3 + 1];
r = img->imageData[img->widthStep * row + col * 3 + 2];
由于imageData指针始终是char*类型的,因此该方法只适用于8 bits/pixel的图像表示,其他的图像类型则需要进行指针转换。

 

l       

可以使用宏 CV_IMAGE_ELEM( image_header, elemtype, y, x_Nc )

I(x,y)c ~ CV_IMAGE_ELEM( img, T, y, x*N + c ),其中c为通道的序号 ,如彩色图像c=012

不过使用该宏是也要小心数据类型elemtype的问题。

也有针对各种图像(包括 4 通道图像)和矩阵的函数(cvGet2D, cvSet2D), 但是它们非常慢。

注:

该宏在每次调用时,都会重新计算指针的位置。这意味着,先查找数据区中第0个元素的位置,然后,根据参数中的行和列,计算所需要的元素的地址偏移量,然后将地址偏移量与第0个元素的地址相加,获得所需要的元素的地址。
  
所以,以上的方式虽然很容易使用,但是却不是获得元素的最好方式。特别是当你要顺序遍历整个图像中所有元素时,这种每次对地址的重复计算就更加显得不合理。

l        cvGet2D()cvSet2D()

可以通过cvGet2D()cvSet2D()两个函数加上一个CvScalar结构体做到获取图像的像素点。
OpenCV中,CvScalar结构为:
typedef struct CvScalar
{
    double val[4];
}
CvScalar;
 
4double型变量,算法处理时不至于被强制类型转换而降低精度了。
再来看读写函数的定义:
 
cvGet2D 获得某个点的值, idx0=hight 行值, idx1=width 列值。
CVAPI(CvScalarcvGet2Dconst CvArr* arr, int idx0, int idx1 );
 
cvSet2D 给某个点赋值。
CVAPI(voidcvSet2DCvArr* arr, int idx0, int idx1, CvScalar value );
 
有上可见,cvGet2D的返回类型和cvSet2Dvalue的类型都是CvScalar,这样定义一个CvScalar变量再调用函数就OK了。
 
OpenCV中像素点读写例子:
 


int main(int argc, char **argv)
{
    IplImage *img = cvLoadImage(argv[1], 1);
    CvScalar pixel;
    for (int i = 0; i < img->height; ++i)
    {
        for (int j = 0; j < img->width; ++j)
        {
            //
获得像素的RGB值并显示, 注意内存中存储顺序是BGR
            pixel = cvGet2D(img, i, j);
            printf("B=%f,G=%f,R=%f\t", pixel.val[0], pixel.val[1], pixel.val[2]);
            //
修改各点的值
            pixel.val[0] = 0;
            pixel.val[1] = 0;
            pixel.val[2] = 0;
            cvSet2D(img, i, j, pixel);
        }
    }
    cvNamedWindow("image", 1);
    cvShowImage("image", img);
    cvWaitKey(0);
    cvDestroyWindow("image");
    cvReleaseImage(&img);
    return 0;
}

 

 


矩阵CvMat

Struct CvMat

CvMat的结构体定义为:

typedef struct CvMat
    {
        int type; /* CvMat signature (CV_MAT_MAGIC_VAL), element type and flags */
        int step; /* full row length in bytes */
 
        int* refcount; /* underlying data reference counter */
 
        union
        {
            uchar* ptr;
            short* s;
            int* i;
            float* fl;
            double* db;
        } data; /* data pointers */
 
    #ifdef __cplusplus
        union
        {
            int rows;
            int height;
        };
 
        union
        {
            int cols;
            int width;
        };
    #else
        int rows; /* number of rows */
        int cols; /* number of columns */
    #endif
 
    } CvMat;

step 是每一行数据的长度,以字节来表示

data 是存放矩阵数据的联合体,如果矩阵pMatfloat类型的,那么获取矩阵数据指针的方式为 pMat->data.fl,如果是整型的 pMat->data.i

行数和列数在cc++中定义略有不同,但是rowscols是通用的两个变量。

 矩阵的创建和初始化

矩阵有多种创建方法。最常见的方法是用cvCreateMat(),它由多个原函数组成,如cvCreateMatHeader()cvCreateData()cvCreateMatHeader()函数创建CvMat结构,不为数据分配内存,而cvCreateData()函数只负责数据的内存分配。有时,只需要函数cvCreateMatHeader(),因为已因其他理由分配了存储空间,或因为还不准备分配存储空间。第三种方法是用函数cvCloneMat (CvMat*) ,它依据一个现有矩阵创建一个新的矩阵。当这个矩阵不再需要时,可以调用函数cvReleaseMat(CvMat*)释放它。

l        分配矩阵空间

CvMat* cvCreateMat(int rows, int cols, int type);

   type:
矩阵元素类型. 格式为CV_<bit_depth>(S|U|F)C<number_of_channels>.  
   
例如: CV_8UC1 表示8位无符号单通道矩阵, CV_32SC2表示32位有符号双通道矩阵.

 
:  CvMat* M = cvCreateMat(4,4,CV_32FC1);

 

l        逐点赋值式初始化

CvMat* mat = cvCreateMat( 2, 2, CV_64FC1 );
cvZero( mat );
cvmSet( mat, 0, 0, 1 );
cvmSet( mat, 0, 1, 2 );
cvmSet( mat, 1, 0, 3 );
cvmSet( mat, 2, 2, 4 );
cvReleaseMat( &mat );

l        使用现有数组初始化

double a[] = { 1, 2, 3, 4,
               5, 6, 7, 8,
               9, 10, 11, 12 };
CvMat mat = cvMat( 3, 4, CV_64FC1, a ); // 64FC1 for double
//
不需要cvReleaseMat,因为数据内存分配是由double定义的数组进行的。

释放矩阵

CvMat* M = cvCreateMat(4,4,CV_32FC1);
cvReleaseMat(&M);

 

复制矩阵:

CvMat* M1 = cvCreateMat(4,4,CV_32FC1);
CvMat* M2;
M2=cvCloneMat(M1);

存取矩阵元素

假设需要存取一个2维浮点矩阵的第(i,j)个元素.

简单的方法

从矩阵中得到一个元素的最简单的方法是利用宏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; 

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

 

麻烦的方法

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

为了访问普通矩阵中的数据,我们可以利用在例3-6和例3-7中列举的cvPtr*DcvGet*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.  ); 

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

3-7CvMatIPlImage元素函数

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

3-8中的cvSet*DcvGet*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 ); 

 

恰当的方法

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

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

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 );  

12. 

计算指向矩阵的指针时,记住一点:矩阵的元素data是一个联合体。所以,对这个指针解引用的时候,必须指明结构体中的正确的元素以便得到正确的指针类型。然后,为了使指针产生正确的偏移,必须用矩阵的行数据长度(step)元素。我们以前曾提过,行数据元素的是用字节来计算的。为了安全,指针最好用字节计算,然后分配恰当的类型,如浮点型。CvMat结构中为了兼容IplImage结构,有宽度和高度的概念,这个概念已经被最新的行和列取代。最后要注意,我们为每行都重新计算了ptr,而不是简单地从开头开始,尔后每次读的时候累加指针。这看起来好像很繁琐,但是因为CvMat数据指针可以指向一个大型数组中的ROI,所以无法保证数据会逐行连续存取。

l        间接存取矩阵元素

cvmSet(M,i,j,2.0); // Set M(i,j)
t = cvmGet(M,i,j); // Get M(i,j)

注意:cvmGet()cvmSet()函数只支持CV_32FC1(float)CV_64FC1(double)的类型

l        直接存取,假设使用4-字节校正

CvMat* M = cvCreateMat(4,4,CV_32FC1);
int n = M->cols;
float *data = M->data.fl;

data[i*n+j] = 3.0;

l        直接存取,校正字节任意

CvMat* M = cvCreateMat(4,4,CV_32FC1);
int step = M->step/sizeof(float);
float *data = M->data.fl;

(data+i*step)[j] = 3.0;

l        直接存取一个初始化的矩阵元素

double a[16];
CvMat Ma = cvMat(3, 4, CV_64FC1, a);
a[i*4+j] = 2.0; // Ma(i,j)=2.0;

cvmGet()cvmSet()的局限

cvmGet()cvmSet()函数只支持CV_32FC1(float)CV_64FC1(double)的类型

// 源码如下。也就是说只支持CV_32FC1(float)CV_64FC1(double)的类型

// 对于U类型的数据,可以使用cvSet2D进行读写

CV_INLINE double cvmGet( const CvMat* mat, int row, int col )
{
    int type;

    type = CV_MAT_TYPE(mat->type);
    assert( (unsigned)row < (unsigned)mat->rows &&
            (unsigned)col < (unsigned)mat->cols );

    if( type == CV_32FC1 )
        return ((float*)(mat->data.ptr + (size_t)mat->step*row))[col];
    else
    {
        assert( type == CV_64FC1 );
        return ((double*)(mat->data.ptr + (size_t)mat->step*row))[col];
    }
}


CV_INLINE void cvmSet( CvMat* mat, int row, int col, double value )
{
    int type;
    type = CV_MAT_TYPE(mat->type);
    assert( (unsigned)row < (unsigned)mat->rows &&
            (unsigned)col < (unsigned)mat->cols );

    if( type == CV_32FC1 )
        ((float*)(mat->data.ptr + (size_t)mat->step*row))[col] = (float)value;
    else
    {
        assert( type == CV_64FC1 );
        ((double*)(mat->data.ptr + (size_t)mat->step*row))[col] = (double)value;
    }
}

矩阵/向量数学操作

矩阵-矩阵操作:

CvMat *Ma, *Mb, *Mc;
cvAdd(Ma, Mb, Mc);            // Ma+Mb -> Mc
cvSub(Ma, Mb, Mc);            // Ma-Mb -> Mc
cvMatMul(Ma, Mb, Mc);         // Ma*Mb -> Mc

按元素的矩阵操作:

CvMat *Ma, *Mb, *Mc;
cvMul(Ma, Mb, Mc);            // Ma.*Mb -> Mc
cvDiv(Ma, Mb, Mc);            // Ma./Mb -> Mc
cvAddS(Ma, cvScalar(-10.0), Mc); // Ma.-10 -> Mc

向量乘积:

double va[] = {1, 2, 3};
double vb[] = {0, 0, 1};
double vc[3];

CvMat Va=cvMat(3, 1, CV_64FC1, va);
CvMat Vb=cvMat(3, 1, CV_64FC1, vb);
CvMat Vc=cvMat(3, 1, CV_64FC1, vc);

double res=cvDotProduct(&Va,&Vb); //
点乘:  Va . Vb -> res
cvCrossProduct(&Va, &Vb, &Vc);    //
向量积: Va x Vb -> Vc

注意 Va, Vb, Vc 在向量积中向量元素个数须相同.

单矩阵操作:

CvMat *Ma, *Mb;
cvTranspose(Ma, Mb);   // transpose(Ma) -> Mb (
不能对自身进行转置)
CvScalar t = cvTrace(Ma); // trace(Ma) -> t.val[0]
double d = cvDet(Ma);     // det(Ma) -> d
cvInvert(Ma, Mb);         // inv(Ma) -> Mb

非齐次线性系统求解:

CvMat* A  = cvCreateMat(3,3,CV_32FC1);
CvMat* x  = cvCreateMat(3,1,CV_32FC1);
CvMat* b  = cvCreateMat(3,1,CV_32FC1);
cvSolve(&A, &b, &x);          // solve (Ax=b) for x

特征值分析(针对对称矩阵):

CvMat* A  = cvCreateMat(3,3,CV_32FC1);
CvMat* E  = cvCreateMat(3,3,CV_32FC1);
CvMat* l  = cvCreateMat(3,1,CV_32FC1);
cvEigenVV(&A, &E, &l);        // l = A
的特征值 (降序排列)
                              // E =
对应的特征向量 (每行)

奇异值分解SVD:

CvMat* A  = cvCreateMat(3,3,CV_32FC1);
CvMat* U  = cvCreateMat(3,3,CV_32FC1);
CvMat* D  = cvCreateMat(3,3,CV_32FC1);
CvMat* V  = cvCreateMat(3,3,CV_32FC1);
cvSVD(A, D, U, V, CV_SVD_U_T|CV_SVD_V_T); // A = U D V^T

标号使得 U V 返回时被转置(若没有转置标号,则有问题不成功!!!).

 

 

 

 

 


其他

Shell函数显示图片

//用默认关联程序打开图片Vp_Line.jpg

ShellExecute(NULL,"open","Results\\Vp_Line.jpg",NULL,NULL,SW_SHOWNORMAL);

利用cvShowImage来显示图像,显示窗口是固定的,对窗口的操作很少。而利用shell函数调用相关程序(譬如windows图片查看器,画图板等),可以使用这些图片浏览工具的强大功能。

IplImage cvMat的转换

方式一、cvGetMat方式:
CvMat mathdr, *mat = cvGetMat( img, &mathdr );

方式二、cvConvert方式:
CvMat *mat = cvCreateMat( img->height, img->width, CV_64FC3 );
cvConvert( img, mat );
// #define cvConvert( src, dst ) cvConvertScale( (src), (dst), 1, 0 )

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多