1. 程式人生 > >CvMat用法詳解

CvMat用法詳解

https://blog.csdn.net/zx3517288/article/details/51760541

CvMat是OpenCV比較基礎的函式。初學者應該掌握並熟練應用。但是我認為計算機專業學習的方法是,不斷的總結並且提煉,同時還要做大量的實踐,如編碼,才能記憶深刻,體會深刻,從而引導自己想更高層次邁進。

綜述: OpenCV有針對矩陣操作的C語言函式. 許多其他方法提供了更加方便的C++介面,其效率與OpenCV一樣.
OpenCV將向量作為1維矩陣處理.
矩陣按行儲存,每行有4位元組的校整.

分配矩陣空間: CvMat* cvCreateMat(int rows, int cols, int type);
type: 矩陣元素型別. 格式為CV_(S|U|F)C.
例如: CV_8UC1 表示8位無符號單通道矩陣, CV_32SC2表示32位有符號雙通道矩陣.
例程:

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

    1

•釋放矩陣空間:

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

    1
    2

•複製矩陣:

1.CvMat* M1 = cvCreateMat(4,4,CV_32FC1); 
2.CvMat* M2; 
3.M2=cvCloneMat(M1); 

    1
    2
    3
    4

•初始化矩陣:

1.double a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; 
2.CvMat Ma=cvMat(3, 4, CV_64FC1, a); 

    1
    2
    3

另一種方法:

1.CvMat Ma; 
2.cvInitMatHeader(&Ma, 3, 4, CV_64FC1, a); 

    1
    2
    3

•初始化矩陣為單位陣:

1.CvMat* M = cvCreateMat(4,4,CV_32FC1); 
2.cvSetIdentity(M); // 這裡似乎有問題,不成功 

    1
    2
    3
    4

存取矩陣元素
•假設需要存取一個2維浮點矩陣的第(i,j)個元素.
•間接存取矩陣元素:

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

    1
    2

•直接存取,假設使用4-位元組校正:

1.CvMat* M = cvCreateMat(4,4,CV_32FC1); 
2.int n = M->cols; 
3.float *data = M->data.fl; 
4.data[i*n+j] = 3.0; 

    1
    2
    3
    4

•直接存取,校正位元組任意:

1.CvMat* M = cvCreateMat(4,4,CV_32FC1); 
2.int step = M->step/sizeof (float ); 
3.float *data = M->data.fl; 
4.(data+i*step)[j] = 3.0; 

    1
    2
    3
    4
    5
    6

•直接存取一個初始化的矩陣元素:

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

    1
    2
    3

矩陣/向量操作
•矩陣-矩陣操作:

1.CvMat *Ma, *Mb, *Mc; 
2.cvAdd(Ma, Mb, Mc); // Ma+Mb -> Mc 
3.cvSub(Ma, Mb, Mc); // Ma-Mb -> Mc 
4.cvMatMul(Ma, Mb, Mc); // Ma*Mb -> Mc 

    1
    2
    3
    4
    5

•按元素的矩陣操作:

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

    1
    2
    3
    4
    5

•向量乘積:

1.double va[] = {1, 2, 3}; 
2.double vb[] = {0, 0, 1}; 
3.double vc[3]; 
4.CvMat Va=cvMat(3, 1, CV_64FC1, va); 
5.CvMat Vb=cvMat(3, 1, CV_64FC1, vb); 
6.CvMat Vc=cvMat(3, 1, CV_64FC1, vc); 
7.double res=cvDotProduct(&Va,&Vb); // 點乘: Va . Vb -> res 
8.cvCrossProduct(&Va, &Vb, &Vc); // 向量積: Va x Vb -> Vc 
9.end{verbatim} 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

注意 Va, Vb, Vc 在向量積中向量元素個數須相同.

•單矩陣操作:

1.CvMat *Ma, *Mb; 
2.cvTranspose(Ma, Mb); // transpose(Ma) -> Mb (不能對自身進行轉置) 
3.CvScalar t = cvTrace(Ma); // trace(Ma) -> t.val[0] 
4.double d = cvDet(Ma); // det(Ma) -> d 
5.cvInvert(Ma, Mb); // inv(Ma) -> Mb 

    1
    2
    3
    4
    5
    6

•非齊次線性系統求解:

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

    1
    2
    3
    4
    5

•特徵值分析(針對對稱矩陣):

1.CvMat* A = cvCreateMat(3,3,CV_32FC1); 
2.CvMat* E = cvCreateMat(3,3,CV_32FC1); 
3.CvMat* l = cvCreateMat(3,1,CV_32FC1); 
4.cvEigenVV(&A, &E, &l); // l = A的特徵值 (降序排列) , E = 對應的特徵向量 (每行) 

    1
    2
    3
    4
    5

•奇異值分解SVD:

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

    1
    2
    3
    4
    5

1.初始化矩陣:
方式一、逐點賦值式:

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

    1
    2
    3
    4
    5
    6
    7

方式二、連線現有陣列式:

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

    1
    2
    3
    4
    5

// 不需要cvReleaseMat,因為資料記憶體分配是由double定義的陣列進行的。

2.IplImage 到cvMat的轉換

方式一、cvGetMat方式:

CvMat mathdr, *mat = cvGetMat( img, &mathdr ); 

    1
    2

方式二、cvConvert方式:

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

    1
    2
    3
    4

3.cvArr(IplImage或者cvMat)轉化為cvMat
方式一、cvGetMat方式:

int coi = 0;
cvMat *mat = (CvMat*)arr;
if( !CV_IS_MAT(mat) )
{
    mat = cvGetMat( mat, &matstub, &coi );
    if (coi != 0) reutn; // CV_ERROR_FROM_CODE(CV_BadCOI);
}

    1
    2
    3
    4
    5
    6
    7

寫成函式為:

// This is just an example of function
// to support both IplImage and cvMat as an input
CVAPI( void ) cvIamArr( const CvArr* arr )
{
    CV_FUNCNAME( "cvIamArr" );
    __BEGIN__;
    CV_ASSERT( mat == NULL );
    CvMat matstub, *mat = (CvMat*)arr;
    int coi = 0;
    if( !CV_IS_MAT(mat) )
    {
        CV_CALL( mat = cvGetMat( mat, &matstub, &coi ) );
        if (coi != 0) CV_ERROR_FROM_CODE(CV_BadCOI);
    }
    // Process as cvMat
    __END__;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

4.影象直接操作
方式一:直接陣列操作 int col, row, z;

uchar b, g, r;
for( y = 0; row < img->height; y++ )
{
   for ( col = 0; col < img->width; col++ )
   {
     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];
   }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

方式二:巨集操作:

int row, col;
uchar b, g, r;
for( row = 0; row < img->height; row++ )
{
   for ( col = 0; col < img->width; col++ )
   {
     b = CV_IMAGE_ELEM( img, uchar, row, col * 3 );
     g = CV_IMAGE_ELEM( img, uchar, row, col * 3 + 1 );
     r = CV_IMAGE_ELEM( img, uchar, row, col * 3 + 2 );
   }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

注:CV_IMAGE_ELEM( img, uchar, row, col * img->nChannels + ch )

5.cvMat的直接操作

陣列的直接操作比較鬱悶,這是由於其決定於陣列的資料型別。
對於CV_32FC1 (1 channel float):

CvMat* M = cvCreateMat( 4, 4, CV_32FC1 );
M->data.fl[ row * M->cols + col ] = (float)3.0; 

    1
    2
    3

對於CV_64FC1 (1 channel double):

CvMat* M = cvCreateMat( 4, 4, CV_64FC1 );
M->data.db[ row * M->cols + col ] = 3.0; 

    1
    2
    3

一般的,對於1通道的陣列:

CvMat* M = cvCreateMat( 4, 4, CV_64FC1 );
CV_MAT_ELEM( *M, double, row, col ) = 3.0;

    1
    2
    3

注意double要根據陣列的資料型別來傳入,這個巨集對多通道無能為力。

對於多通道:
看看這個巨集的定義:

#define CV_MAT_ELEM_CN( mat, elemtype, row, col ) \
    (*(elemtype*)((mat).data.ptr + (size_t)(mat).step*(row) + sizeof(elemtype)*(col)))
if( CV_MAT_DEPTH(M->type) == CV_32F )
    CV_MAT_ELEM_CN( *M, float, row, col * CV_MAT_CN(M->type) + ch ) = 3.0;
if( CV_MAT_DEPTH(M->type) == CV_64F )
    CV_MAT_ELEM_CN( *M, double, row, col * CV_MAT_CN(M->type) + ch ) = 3.0;

    1
    2
    3
    4
    5
    6
    7

更優化的方法是:

 #define CV_8U   0
   #define CV_8S   1
   #define CV_16U 2
   #define CV_16S 3
   #define CV_32S 4
   #define CV_32F 5
   #define CV_64F 6
   #define CV_USRTYPE1 7 
int elem_size = CV_ELEM_SIZE( mat->type );
for( col = start_col; col < end_col; col++ ) {
    for( row = 0; row < mat->rows; row++ ) {
        for( elem = 0; elem < elem_size; elem++ ) {
            (mat->data.ptr + ((size_t)mat->step * row) + (elem_size * col))[elem] =
                (submat->data.ptr + ((size_t)submat->step * row) + (elem_size * (col - start_col)))[elem];
        }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

對於多通道的陣列,以下操作是推薦的:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

對於兩通道和四通道而言:

CvMat* vector = cvCreateMat( 1, 3, CV_32SC2 );
CV_MAT_ELEM( *vector, CvPoint, 0, 0 ) = cvPoint(100,100); 
CvMat* vector = cvCreateMat( 1, 3, CV_64FC4 );
CV_MAT_ELEM( *vector, CvScalar, 0, 0 ) = cvScalar(0,0,0,0); 

    1
    2
    3
    4
    5

6.間接訪問cvMat
cvmGet/Set是訪問CV_32FC1 和 CV_64FC1型陣列的最簡便的方式,其訪問速度和直接訪問幾乎相同
cvmSet( mat, row, col, value );
cvmGet( mat, row, col );
舉例:列印一個數組

inline void cvDoubleMatPrint( const CvMat* mat )
{
    int i, j;
    for( i = 0; i < mat->rows; i++ )
    {
        for( j = 0; j < mat->cols; j++ )
        {
            printf( "%f ",cvmGet( mat, i, j ) );
        }
        printf( "\n" );
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

而對於其他的,比如是多通道的後者是其他資料型別的,cvGet/Set2D是個不錯的選擇

CvScalar scalar = cvGet2D( mat, row, col );
cvSet2D( mat, row, col, cvScalar( r, g, b ) ); 

    1
    2
    3

注意:資料不能為int,因為cvGet2D得到的實質是double型別。
舉例:列印一個多通道矩陣:

inline void cv3DoubleMatPrint( const CvMat* mat )
{
    int i, j;
    for( i = 0; i < mat->rows; i++ )
    {
        for( j = 0; j < mat->cols; j++ )
        {
            CvScalar scal = cvGet2D( mat, i, j );
            printf( "(%f,%f,%f) ", scal.val[0], scal.val[1], scal.val[2] );
        }
        printf( "\n" );
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

7.修改矩陣的形狀——cvReshape的操作
經實驗表明矩陣操作的進行的順序是:首先滿足通道,然後滿足列,最後是滿足行。
注意:這和Matlab是不同的,Matlab是行、列、通道的順序。
我們在此舉例如下:
對於一通道:

// 1 channel
CvMat *mat, mathdr;
double data[] = { 11, 12, 13, 14,
                   21, 22, 23, 24,
                   31, 32, 33, 34 };
CvMat* orig = &cvMat( 3, 4, CV_64FC1, data );
//11 12 13 14
//21 22 23 24
//31 32 33 34
mat = cvReshape( orig, &mathdr, 1, 1 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
// 11 12 13 14 21 22 23 24 31 32 33 34
mat = cvReshape( mat, &mathdr, 1, 3 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//11 12 13 14
//21 22 23 24
//31 32 33 34
mat = cvReshape( orig, &mathdr, 1, 12 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
// 11
// 12
// 13
// 14
// 21
// 22
// 23
// 24
// 31
// 32
// 33
// 34
mat = cvReshape( mat, &mathdr, 1, 3 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//11 12 13 14
//21 22 23 24
//31 32 33 34
mat = cvReshape( orig, &mathdr, 1, 2 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//11 12 13 14 21 22
//23 24 31 32 33 34
mat = cvReshape( mat, &mathdr, 1, 3 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//11 12 13 14
//21 22 23 24
//31 32 33 34
mat = cvReshape( orig, &mathdr, 1, 6 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
// 11 12
// 13 14
// 21 22
// 23 24
// 31 32
// 33 34
mat = cvReshape( mat, &mathdr, 1, 3 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//11 12 13 14
//21 22 23 24
//31 32 33 34
// Use cvTranspose and cvReshape( mat, &mathdr, 1, 2 ) to get
// 11 23
// 12 24
// 13 31
// 14 32
// 21 33
// 22 34
// Use cvTranspose again when to recover

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67

對於三通道

// 3 channels
CvMat mathdr, *mat;
double data[] = { 111, 112, 113, 121, 122, 123,
211, 212, 213, 221, 222, 223 };
CvMat* orig = &cvMat( 2, 2, CV_64FC3, data );
//(111,112,113) (121,122,123)
//(211,212,213) (221,222,223)
mat = cvReshape( orig, &mathdr, 3, 1 ); // new_ch, new_rows
cv3DoubleMatPrint( mat ); // above
// (111,112,113) (121,122,123) (211,212,213) (221,222,223)
// concatinate in column first order
mat = cvReshape( orig, &mathdr, 1, 1 );// new_ch, new_rows
cvDoubleMatPrint( mat ); // above
// 111 112 113 121 122 123 211 212 213 221 222 223
// concatinate in channel first, column second, row third
mat = cvReshape( orig, &mathdr, 1, 3); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//111 112 113 121
//122 123 211 212
//213 221 222 223
// channel first, column second, row third
mat = cvReshape( orig, &mathdr, 1, 4 ); // new_ch, new_rows
cvDoubleMatPrint( mat ); // above
//111 112 113
//121 122 123
//211 212 213
//221 222 223
// channel first, column second, row third
// memorize this transform because this is useful to
// add (or do something) color channels
CvMat* mat2 = cvCreateMat( mat->cols, mat->rows, mat->type );
cvTranspose( mat, mat2 );
cvDoubleMatPrint( mat2 ); // above
//111 121 211 221
//112 122 212 222
//113 123 213 223
cvReleaseMat( &mat2 ); 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

8.計算色彩距離
我們要計算img1,img2的每個畫素的距離,用dist表示,定義如下

IplImage *img1 = cvCreateImage( cvSize(w,h), IPL_DEPTH_8U, 3 );
IplImage *img2 = cvCreateImage( cvSize(w,h), IPL_DEPTH_8U, 3 );
CvMat *dist = cvCreateMat( h, w, CV_64FC1 );

    1
    2
    3
    4

比較笨的思路是:cvSplit->cvSub->cvMul->cvAdd
程式碼如下:

IplImage *img1B = cvCreateImage( cvGetSize(img1), img1->depth, 1 );
IplImage *img1G = cvCreateImage( cvGetSize(img1), img1->depth, 1 );
IplImage *img1R = cvCreateImage( cvGetSize(img1), img1->depth, 1 );
IplImage *img2B = cvCreateImage( cvGetSize(img1), img1->depth, 1 );
IplImage *img2G = cvCreateImage( cvGetSize(img1), img1->depth, 1 );
IplImage *img2R = cvCreateImage( cvGetSize(img1), img1->depth, 1 );
IplImage *diff    = cvCreateImage( cvGetSize(img1), IPL_DEPTH_64F, 1 );
cvSplit( img1, img1B, img1G, img1R );
cvSplit( img2, img2B, img2G, img2R );
cvSub( img1B, img2B, diff );
cvMul( diff, diff, dist );
cvSub( img1G, img2G, diff );
cvMul( diff, diff, diff);
cvAdd( diff, dist, dist );
cvSub( img1R, img2R, diff );
cvMul( diff, diff, diff );
cvAdd( diff, dist, dist );
cvReleaseImage( &img1B );
cvReleaseImage( &img1G );
cvReleaseImage( &img1R );
cvReleaseImage( &img2B );
cvReleaseImage( &img2G );
cvReleaseImage( &img2R );
cvReleaseImage( &diff ); 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

比較聰明的思路是

int D = img1->nChannels; // D: Number of colors (dimension)
int N = img1->width * img1->height; // N: number of pixels
CvMat mat1hdr, *mat1 = cvReshape( img1, &mat1hdr, 1, N ); // N x D(colors)
CvMat mat2hdr, *mat2 = cvReshape( img2, &mat2hdr, 1, N ); // N x D(colors)
CvMat diffhdr, *diff = cvCreateMat( N, D, CV_64FC1 ); // N x D, temporal buff
cvSub( mat1, mat2, diff );
cvMul( diff, diff, diff );
dist = cvReshape( dist, &disthdr, 1, N ); // nRow x nCol to N x 1
cvReduce( diff, dist, 1, CV_REDUCE_SUM ); // N x D to N x 1
dist = cvReshape( dist, &disthdr, 1, img1->height ); // Restore N x 1 to nRow x nCol
cvReleaseMat( &diff ); 
#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
int main()
{
CvMat* mat = cvCreateMat(3,3,CV_32FC1);
cvZero(mat);//將矩陣置0
//為矩陣元素賦值
CV_MAT_ELEM( *mat, float, 0, 0 ) = 1.f;
CV_MAT_ELEM( *mat, float, 0, 1 ) = 2.f;
CV_MAT_ELEM( *mat, float, 0, 2 ) = 3.f;
CV_MAT_ELEM( *mat, float, 1, 0 ) = 4.f;
CV_MAT_ELEM( *mat, float, 1, 1 ) = 5.f;
CV_MAT_ELEM( *mat, float, 1, 2 ) = 6.f;
CV_MAT_ELEM( *mat, float, 2, 0 ) = 7.f;
CV_MAT_ELEM( *mat, float, 2, 1 ) = 8.f;
CV_MAT_ELEM( *mat, float, 2, 2 ) = 9.f;
//獲得矩陣元素(0,2)的值
float *p = (float*)cvPtr2D(mat, 0, 2);
printf("%f\n",*p);
    return 0;
}