1. 程式人生 > >OpenCV中矩陣資料的訪問(非常好)非常全

OpenCV中矩陣資料的訪問(非常好)非常全

OpenCV中有三種方式訪問矩陣中的資料元素:容易的方式,困難的方式,以及正確的方式。以下先講容易的方式和困難的方式。容易的方式最容易的方式是使用巨集CV_MAT_ELEM( matrix, elemtype, row, col ),輸入引數是矩陣的指標,矩陣元素型別,行,列,返回值是相應行,列的矩陣元素,例如:
CvMat* mat = cvCreateMat(5,5,CV_32FC1);
float element = CV_MAT_ELEM(*mat,float,3,2);
以下是一個例子:
#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
void 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;
//
獲得矩陣元素的值
float element = CV_MAT_ELEM(*mat,float,2,2);
printf("%f/n",element);
}

CV_MAT_ELEM
巨集實際上會呼叫CV_MAT_ELEM_PTR(matrix,row,col)巨集來完成任務。CV_MAT_ELEM_PTR()巨集的引數是矩陣的指標,行,列。
CV_MAT_ELEM()
巨集和CV_MAT_ELEM_PTR()巨集的區別是,在呼叫CV_MAT_ELEM時,指向矩陣元素的指標的資料型別已經依據輸入引數中的元素型別而做了強制轉換。,以下是使用CV_MAT_ELEM_PTR()來設定元素的值:

#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
void main()
{
CvMat* mat = cvCreateMat(3,3,CV_32FC1);
cvZero(mat);//
將矩陣置0
float element_0_2 = 7.7f;
*((float*)CV_MAT_ELEM_PTR( *mat, 0, 2 ) ) = element_0_2;

//
獲得矩陣元素的值

float element = CV_MAT_ELEM(*mat,float,0,2);
printf("%f/n",element);
}

以上使用矩陣中元素的方式很方便,但不幸的是,該巨集在每次呼叫時,都會重新計算指標的位置。這意味著,先查詢矩陣資料區中第0個元素的位置,然後,根據引數中的行和列,計算所需要的元素的地址偏移量,然後將地址偏移量與第0個元素的地址相加,獲得所需要的元素的地址。所以,以上的方式雖然很容易使用,但是卻不是獲得矩陣元素的最好方式。特別是當你要順序遍歷整個矩陣中所有元素時,這種每次對地址的重複計算就更加顯得不合理。困難的方式以上兩個巨集只適合獲得一維或二維的矩陣(陣列)元素,OpenCV提供了處理多維矩陣(陣列)的方式。實際上你可以不受限制地使用N維。當訪問這樣一種N維矩陣中元素時,你需要使用一個系列的函式,叫做cvPtr*D,*代表1,2,3,4....,例如,cvPtr1D(),cvPtr2D(),cvPtr3D(),以及cvPtrND().以下為此係列函式的定義:

cvPtr*D
函式用於返回指向某陣列元素的指標

uchar* cvPtr1D( const CvArr* arr, int idx0, int* type=NULL );
uchar* cvPtr2D( const CvArr* arr, int idx0, int idx1, int* type=NULL );
uchar* cvPtr3D( const CvArr* arr, int idx0, int idx1, int idx2, int* type=NULL );
uchar* cvPtrND( const CvArr* arr, int* idx, int* type=NULL, int create_node=1, unsigned* precalc_hashval=NULL );

arr
輸入陣列(矩陣).
idx0
元素下標的第一個以0為基準的成員

idx1
元素下標的第二個以0為基準的成員
idx2
元素下標的第三個以0為基準的成員
idx
陣列元素下標
type
可選的,表示輸出引數的資料型別
create_node
可選的,為稀疏矩陣輸入的引數。如果這個引數非零就意味著被需要的元素如果不存在就會被建立。
precalc_hashval
可選的,為稀疏矩陣設定的輸入引數。如果這個指標非NULL,函式不會重新計算節點的HASH值,而是從指定位置獲取。這種方法有利於提高智慧組合資料的操作函式cvPtr*D 返回指向特殊陣列元素的指標。陣列維數應該與傳遞給函式的下標數相匹配,它可以被用於順序存取的1D2DnD密集陣列函式也可以用於稀疏陣列,並且如果被需要的節點不存在函式可以建立這個節點並設定為0
就像其它獲取陣列元素的函式 (cvGet[Real]*D, cvSet[Real]*D)如果元素的下標超出了範圍就會產生錯誤
很明顯,如果是一維陣列(矩陣),那就可以使用cvPtr1D,用引數idx0來指向要獲得的第idx0個元素,返回值為指向該元素的指標,如果是二維陣列(矩陣),就可以使用cvPtr2D,idx0idx1來指向相應的元素。如果是N維陣列,則int* idx引數指向對N維陣列中某元素定位用的下標序列。
#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
void 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);
}


我們使用cvPtr*D()函式的一個理由是,通過此函式,我們可以用指標指向矩陣中的某元素,並使用指標運算子,來設定該元素的值,或者,用指標運算來移動指標,指向從起始位置開始的矩陣中的其他元素。例如,我們可以用以下方式遍歷矩陣中的元素:
#pragma comment( lib, "cxcore.lib" )
#include "cv.h"
#include <stdio.h>
void main()
{
CvMat* mat = cvCreateMat(3,3,CV_32FC1);
cvZero(mat);//
將矩陣置0

//
獲得矩陣元素(0,0)的指標

float *p = (float*)cvPtr2D(mat, 0, 0);
//
為矩陣賦值
for(int i = 0; i < 9; i++)
{
*p = (float)i;
p++;
}

//
列印矩陣的值
p = (float*)cvPtr2D(mat, 0, 0);

for(i = 0; i < 9; i++)
{
printf("%f/t",*p);
p++;
if((i+1) % 3 == 0)
printf("/n");
}

}

但是要注意,以上為矩陣中元素的通道數為1時,可以用p++來訪問下一個矩陣中元素,但是如果通道數不為1,例如一個三通道的二維矩陣,矩陣中每個元素的值為RGB值,則矩陣中資料按以下方式儲存:rgbrgbrgb......,因此,使用指標指向下一個元素時,就需要加上相應的通道數。舉例如下:
#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");
}
}

如果你不想使用指向資料的指標,而只是想獲得矩陣中的資料,你還可以使用cvGet*D函式系列。如下所示,該函式系列以返回值型別劃分有兩種,一種返回double型別資料,另一種返回CvScalar型別資料。

Get*D
返回特殊的陣列元素
CvScalar cvGet1D( const CvArr* arr, int idx0 );
CvScalar cvGet2D( const CvArr* arr, int idx0, int idx1 );
CvScalar cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );
CvScalar cvGetND( const CvArr* arr, int* idx );

arr
輸入陣列.
idx0
元素下標第一個以0為基準的成員

idx1
元素下標第二個以0為基準的成員
idx2
元素下標第三個以0為基準的成員
idx
元素下標陣列函式cvGet*D 返回指定的陣列元素。對於稀疏陣列如果需要的節點不存在函式返回(不會建立新的節點)



GetReal*D
返回單通道陣列的指定元素double cvGetReal1D( const CvArr* arr, int idx0 );
double cvGetReal2D( const CvArr* arr, int idx0, int idx1 );
double cvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 );
double cvGetRealND( const CvArr* arr, int* idx );

arr
輸入陣列,必須是單通道
.
idx0
元素下標的第一個成員,以0為基準

idx1
元素下標的第二個成員,以0為基準
idx2
元素下標的第三個成員,以0為基準
idx
元素下標陣列函式cvGetReal*D 返回單通道陣列的指定元素,如果陣列是多通道的,就會產生執行時錯誤,而 cvGet*D 函式可以安全的被用於單通道和多通道陣列,注意,該方法返回值型別是double型別的,這意味著,矩陣中如果儲存的是int型別資料,不能用此係列方法。


#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
//
矩陣元素為1通道浮點型資料
CvMat*mat=cvCreateMat(3,3,CV_32FC1 );
cvZero(mat);//
將矩陣置0
//
為矩陣元素賦值


//
獲得矩陣元素(0,0)的指標
float *p=(float*)cvPtr2D(mat,0,0);
//
為矩陣賦值
for(int i=0;i<9;i++)
{
//
為每個通道賦值
*p=(float)i*10; 
p++;
}

for(i=0;i<3;i++)
for(int j=0;j<3;j++)
{
printf("%lf/t",cvGetReal2D(mat,i,j));

}

}

另外,我們還有類似於cvGet*D()的方法為矩陣元素賦值:cvSetReal*D()cvSet*D()

Set*D

修改指定的陣列

void cvSet1D( CvArr* arr, int idx0, CvScalar value );
void cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );
void cvSet3D( CvArr* arr, int idx0, int idx1, int idx2, CvScalar value );
void cvSetND( CvArr* arr, int* idx, CvScalar value );

arr
輸入陣列
idx0
元素下標的第一個成員,以0為基點
idx1
元素下標的第二個成員,以0為基點
idx2
元素下標的第三個成員,以0為基點
idx
元素下標陣列
value
指派的值函式 cvSet*D 指定新的值給指定的陣列元素。對於稀疏矩陣如果指定節點不存在函式建立新的節點



SetReal*D

修改指定陣列元素值

void cvSetReal1D( CvArr* arr, int idx0, double value );
void cvSetReal2D( CvArr* arr, int idx0, int idx1, double value );
void cvSetReal3D( CvArr* arr, int idx0, int idx1, int idx2, double value );
void cvSetRealND( CvArr* arr, int* idx, double value );

arr
輸入陣列.
idx0
元素下標的第一個成員,以0為基點

idx1
元素下標的第二個成員,以0為基點
idx2
元素下標的第三個成員,以0為基點
idx
元素下標陣列
value
指派的值函式 cvSetReal*D 分配新的值給單通道陣列的指定元素,如果陣列是多通道就會產生執行時錯誤。然而cvSet*D 可以安全的被用於多通道和單通道陣列。對於稀疏陣列如果指定的節點不存在函式會建立該節點。以下是一個例子:

#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
//
矩陣元素為三通道8位浮點數
CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
cvZero(mat);//
將矩陣置0
//
為矩陣元素賦值


for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
cvSet2D( mat, i, j, cvScalar(i*10,j*10,i*j*10) );

for(i=0;i<3;i++)
for(int j=0;j<3;j++)
{
printf("%lf,%lf,%lf/t",cvGet2D( mat, i, j ).val[0],cvGet2D( mat, i, j ).val[1],cvGet2D( mat, i, j ).val[2]);

}

}


為了方便起見,OpenCV還定義了兩個函式:cvmSet()cvmGet(),這兩個函式用於單通道浮點型元素矩陣的存取。例如,cvmSet(mat,2,2,0.5);就類似於cvSetReal2D(mat,2,2,0.5);

返回單通道浮點矩陣指定元素

double cvmGet( const CvMat* mat, int row, int col );
為單通道浮點矩陣的指定元素賦值。

void cvmSet( CvMat* mat, int row, int col, double value );

加上一段  cvAbs,cvAbsDiff的用法

#include "stdafx.h"

#include <cv.h>  

#include <highgui.h>  

#include <cxcore.h>  

#include <iostream>  

using namespace std; 

int main( int argc, char**argv ){ 

   CvMat *mat;  

   mat=cvCreateMat(3,4,CV_32FC1); // 陣列元素型別為CV_32FC1或者cv_8UC3

    floatvalue = 0.0;  

    inti = 0, j = 0; 

   cout<<"初始化原始陣列"<<endl;//初始化原始陣列

   for( i = 0; i < 3; i ++ ){ 

for( j = 0; j < 4; j ++ ){ 

            value -= 4.0; 

          CV_MAT_ELEM( *mat, float, i, j) = value;         

       } 

    }  

   cout<<"賦值後"<<endl;   //輸出初始化矩陣

    for( i = 0; i < 3; i ++ ){ 

       for(j = 0; j < 4; j ++ ){ 

           cout<<"\t"<<CV_MAT_ELEM( *mat, float, i, j);        

       } 

       cout<<endl; 

   } 

   CvMat *matDes;  //設定目標矩陣,併為每個元賦值

   matDes=cvCreateMat(3,4,CV_32FC1);

    cout<<"目標矩陣"<<endl; 

    for( i = 0; i < 3; i ++ ){ 

       for(j = 0; j < 4; j ++ ){   value = 0;

           CV_MAT_ELEM( *matDes, float, i, j) = value;

           cout<<"\t"<<CV_MAT_ELEM( *matDes, float, i, j);     

        }  

       cout<<endl;  

    }

    cvAbs( mat, matDes );  //計算mat裡的值的絕對值,並把結果寫到matDes中

   cout<<"陣列的絕對值"<<endl; 

   for( i = 0; i < 3; i ++ ){ 

       for(j = 0; j < 4; j ++ ){  

           cout<<"\t"<<CV_MAT_ELEM( *matDes, float, i, j);

             }  

        cout<<endl; 

    } 

   cvAbsDiff( mat, matDes, matDes); 

   cout<<"兩個差的絕對值"<<endl; 

    for( i = 0; i < 3; i ++ ){  

        for(j = 0; j < 4; j ++ ){ 

            cout<<"\t"<<CV_MAT_ELEM( *matDes, float, i, j);     

        } 

        cout<<endl; 

    }  

   cvReleaseMat( &mat );

    cvReleaseMat( &matDes );

    return0; 

}

初始化原始陣列

賦值後

        -4      -8     -12     -16

        -20    -24     -28     -32

        -36     -40    -44     -48

目標矩陣

        0       0      0       0

        0       0      0       0

        0       0      0       0

陣列的絕對值

        4       8      12      16

        20      24     28      32

        36     40      44      48

兩個差的絕對值

        8       16     24      32

        40      48     56      64

        72      80     88      96