在Learning OpenCV書中,講到一個基礎數據類型CvMat,其中有一段程序:
1 Example 3-9. Summing all of the elements in a three-channel matrix
2 float sum( const CvMat* mat ) {
3
4 float s = 0.0f;
5 for(int row=0; row<mat->rows; row++ ) {
6 const float* ptr = (const float*)(mat->data.ptr + row * mat->step);//獲取第row行的首地址
7 for( col=0; col<mat->cols; col++ ) {
8 s += *ptr++;
9 }
10 }
11 return( s );
12 }
不知是我理解錯了,還是書中的注釋錯了,“Summing all of the elements in a three-channel matrix”,我覺得是對三通道CvMat中的所有數據進行求和,按我之前的編寫風格,這個函數應該是這樣:
Example 3-9. Summing all of the elements in a three-channel matrix
float sum( const CvMat* mat ) {
float s = 0.0f;
float* ptr=(float*) cvPtr2D(mat, 0, 0); //獲取該三通道CvMat的首地址(這一句未經測試),將三通道看成是二通道
for(int row=0; row<mat->rows; row++ ) {
for( col=0; col<mat->cols; col++ ) {
s += *ptr+*(ptr+1)+*(ptr+2);//將整個CvMat看成一個二維矩陣,則矩陣中每個元素是個三維向量,將它們累加
ptr += 3;//通道為3,則自加3可到達CvMat矩陣中的下個元素
}
}
return( s );
}
自己構建一個三通道矩陣,為測試方便,假設其為一個3*3的三通道矩陣:
( 1, 2, 3) (11, 12, 13) (21, 22, 23)
(31, 32, 33) (41, 42, 43) (51, 52, 53)
(61, 62, 63) (71, 72, 73) (81, 82, 83)
//用代碼構建如下
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++;
}
經過測試,書中的程序得到的結果是279,即如下數的和:
---------------
|( 1, 2, 3) | (11, 12, 13) (21, 22, 23)
|(31, 32, 33) | (41, 42, 43) (51, 52, 53)
|(61, 62, 63) | (71, 72, 73) (81, 82, 83)
---------------
即僅僅求了一個3*3 float矩陣的值,嚴格的說是矩陣中每行第一個元素的三通道元素和。
我們可以對其進行進一步剖析,先看CvMat的申明:
1 typedef struct CvMat
2 {
3 int type;
4 int step;
5 int* refcount;
6 union
7 {
8 uchar* ptr;
9 short* s;
10 int* i;
11 float* fl;
12 double* db;
13 } data;
14 #ifdef __cplusplus
15 union
16 {
17 int rows;
18 int height;
19 };
20 union
21 {
22 int cols;
23 int width;
24 };
25 #else
26 int rows;
27 int cols;
28 #endif
29 } CvMat;
在大多數情況下,你需要使用最有效率的方式來訪問矩陣中的數據。如果使用函數界面來訪問數據,效率比較低,你應該使用指針方式來直接訪問矩陣中數據。特別是,如果你想遍歷矩陣中所有元素時,就更需要這樣做了。
在用指針直接訪問矩陣元素時,就需要格外注意矩陣結構體中的step成員。該成員是以字節為單位的每行的長度。而矩陣結構體的cols或width就不適合此時使用,因為為了訪問效率,矩陣中的內存分配上,是以每四個字節做為最小單位的。因此如果一個矩陣的寬度是三個字節,那么就會在寬度上分配四個字節,而此時每行最后一個字節會被忽略掉。所以我們用step則會准確地按行訪問數據。
我們可以通過以下例子,看一下rows,cols,height,width,step的數據,你可以通過改變矩陣的元素類型定義,來查看step的改變:
1 #pragma comment(lib,"cxcore.lib")
2 #include"cv.h"
3 #include<stdio.h>
4 void main()
5 {
6 //矩陣元素為三通道8位浮點數
7 CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
8 printf("rows=%d,cols=%d,height=%d,width=%d,step=%d\n",mat->rows,mat->cols,mat->height,mat->width,mat->step);
9
10 }
//輸出為rows=3,cols=3,height=3,width=3,step=36
如果我們的矩陣存儲的是浮點型(或整數類型)數據,此時矩陣中每個元素占4字節,則如果我們用float類型指針指向下一行時,我們實際上要用float類型指針挪動step/4的長度,因為float類型指針每挪動一個單位就是4個字節長度。
如果我們的矩陣存儲的是double類型數據,此時矩陣中每個元素占8字節,則如果我們用double類型指針指向下一行時,我們實際上要用double類型指針挪動step/8的長度,因為double類型指針每挪動一個單位就是8個字節長度。
在書上源碼中:
const float* ptr = (const float*)(mat->data.ptr + row * mat->step);//獲取第row行的首地址
這里只是獲得一個首地址而已,然后在結下來的循環里,它僅僅累加了mat->cols,並不正確,應該改為:
1 for(int row=0; row<mat->rows; row++ ) {
2 const float* ptr = (const float*)(mat->data.ptr + row * mat->step);//獲取第row行的首地址
3 for( col=0; col<mat->cols; col++ ) {
4 s += *ptr+*(ptr+1)+*(ptr+2);
5 ptr += 3;
6 }
7 }
8 return( s );
9 }