OpenCV中對Mat裏面depth,dims,channels,step,data,elemSize和數據地址計算的理解

cv::Mat
depth/dims/channels/step/data/elemSize
The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store (Mat類的對象用於表示一個多維度的單通道或者多通道稠密數組,它可以用來存儲以下東西)
real or complex-valued vectors or matrices (實數值或複合值向量、矩陣)
grayscale or color images (灰度圖或者彩色圖)
voxel volumes (立體元素)
vector fields (矢量場)
point clouds (點雲)
tensors (張量)
histograms (though, very high-dimensional histograms may be better stored in a SparseMat ) (直方圖,高緯度的最好存放在SparseMat中)
舊版本的OpenCV中的C結構體有 CvMat 和 CvMatND,目前我用的是 2.3 版,裏面的文檔指出 CvMat 和 CvMatND 棄用了,在C++封裝中用 Mat 代替,另外舊版還有一個 IplImage,同樣用 Mat 代替(可以參考博文 OpenCV中的結構體、類與Emgu.CV的對應表).
矩陣 (M) 中數據元素的地址計算公式:
addr(Mi0,i1,…im-1) = M.data + M.step[0] * i0 + M.step[1] * i1 + … + M.step[m-1] * im-1 (其中 m = M.dims M的維度)

data:Mat對象中的一個指針,指向內存中存放矩陣數據的一塊內存 (uchar* data)
dims:Mat所代表的矩陣的維度,如 3 * 4 的矩陣爲 2 維, 3 * 4 * 5 的爲3維
channels:通道,矩陣中的每一個矩陣元素擁有的值的個數,比如說 3 * 4 矩陣中一共 12 個元素,如果每個元素有三個值,那麼就說這個矩陣是 3 通道的,即 channels = 3。常見的是一張彩色圖片有紅、綠、藍三個通道。
depth:深度,即每一個像素的位數(bits),在opencv的Mat.depth()中得到的是一個 0 – 6 的數字,分別代表不同的位數:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 可見 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位;
step:是一個數組,定義了矩陣的佈局,具體見下面圖片分析,另外注意 step1 (step / elemSize1),M.step[m-1] 總是等於 elemSize,M.step1(m-1)總是等於 channels;
elemSize : 矩陣中每一個元素的數據大小,如果Mat中的數據的數據類型是 CV_8U 那麼 elemSize = 1,CV_8UC3 那麼 elemSize = 3,CV_16UC2 那麼 elemSize = 4;記住另外有個 elemSize1 表示的是矩陣中數據類型的大小,即 elemSize / channels 的大小
圖片分析1:考慮二維情況(stored row by row)按行存儲
 





上面是一個 3 X 4 的矩陣,假設其數據類型爲 CV_8U,也就是單通道的 uchar 類型

這是一個二維矩陣,那麼維度爲 2 (M.dims == 2);
M.rows == 3; M.cols == 4;
sizeof(uchar) = 1,那麼每一個數據元素大小爲 1 (M.elemSize() == 1, M.elemSize1() == 1);
CV_8U 得到 M.depth() == 0, M.channels() == 1;
因爲是二維矩陣,那麼 step 數組只有兩個值, step[0] 和 step[1] 分別代表一行的數據大小和一個元素的數據大小,則 M.step[0] == 4, M.step[1] == 1;
M.step1(0) == M.cols = 4; M.step1(1) == 1;
假設上面的矩陣數據類型是 CV_8UC3,也就是三通道

M.dims == 2; M.channels() == 3;M.depth() == 0;
M.elemSize() == 3 (每一個元素包含3個uchar值) M.elemSize1() == 1 (elemSize / channels)
M.step[0] == M.cols * M.elemSize() == 12, M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;
M.step(0) == M.cols * M.channels() == 12 ; M.step(1) == M.channels() == 3;
圖片分析2:考慮三維情況(stored plane by plane)按面存儲


 

上面是一個 3 X 4 X 6 的矩陣,假設其數據類型爲 CV_16SC4,也就是 short 類型

M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;
M.rows == M.cols == –1;
M.elemSize() == M.elemSize1() * M.channels() == M.step[M.dims-1] == M.step[2] == 2 * 4 == 8;
M.step[0] == 4 * 6 * M.elemSize() == 192;
M.step[1] == 6 * M.elemSize() == 48;
M.step[2] == M.elemSize() == 8;
M.step1(0) == M.step[0] / M.elemSize() == 48 / 2 == 96 (第一維度(即面的元素個數) * 通道數);
M.step1(1) == M.step[1] / M.elemSize() == 12 / 2 == 24(第二維度(即行的元素個數/列寬) * 通道數);
M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三維度(即元素) * 通道數);
End :

Author : Ggicci

本文講解Mat 的一些基本的初始化

// m爲3*5的矩陣,float型的單通道,把每個點都初始化爲1
Mat m(3, 5, CV_32FC1, 1);
或者 Mat m(3, 5, CV_32FC1, Scalar(1));
cout<<m;
輸出爲:
[1, 1, 1, 1, 1;
  1, 1, 1, 1, 1;
  1, 1, 1, 1, 1]

// m爲3*5的矩陣,float型的2通道,把每個點都初始化爲1 2
 Mat m(3, 5, CV_32FC2, Scalar(1, 2));
cout<<m;
輸出爲
[1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
  1, 2, 1, 2, 1, 2, 1, 2, 1, 2;
  1, 2, 1, 2, 1, 2, 1, 2, 1, 2]

// m爲3*5的矩陣,float型的3通道,把每個點都初始化爲1 2 3
Mat m(3, 5, CV_32FC3, Scalar(1, 2, 3));
cout << m;
輸出爲
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
  1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3;
  1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

// 從已有的數據源初始化
double *data = new double[15];
for (int i = 0; i < 15; i++)
{
   data[i] = 1.2;
}
Mat m(3, 5, CV_32FC1, data);
cout << m;
輸出爲:
[1.2, 1.2, 1.2, 1.2, 1.2;
  1.2, 1.2, 1.2, 1.2, 1.2;
  1.2, 1.2, 1.2, 1.2, 1.2]

如果接着
delete [] data;
cout << m;
輸出爲:
[-1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144;
  -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144, -1.456815990147463e+144]
可見,這裏只是進行了淺拷貝,當數據源不在的時候,Mat裏的數據也就是亂碼了。

// 從圖像初始化
 Mat m = imread("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
 cout<< "channels ="<<m.channels()<<endl;
 cout << "cols ="<<m.cols<<endl;
 cout << "rows ="<<m.rows<<endl;
 cout << m;
輸出爲:
channels =1
cols =13
rows =12
[179, 173, 175, 189, 173, 163, 148, 190, 68, 14, 19, 31, 22;
  172, 172, 172, 180, 172, 177, 162, 190, 64, 13, 19, 30, 17;
  177, 180, 176, 175, 169, 184, 165, 181, 58, 12, 23, 38, 25;
  181, 183, 178, 178, 170, 181, 163, 182, 52, 8, 23, 37, 23;
  176, 173, 173, 184, 175, 178, 164, 195, 60, 14, 24, 35, 16;
  179, 175, 176, 187, 176, 175, 158, 191, 70, 21, 28, 37, 20;
  182, 183, 180, 184, 174, 179, 155, 174, 54, 1, 5, 15, 2;
  173, 182, 178, 176, 173, 191, 165, 169, 157, 101, 100, 107, 93;
  181, 182, 180, 177, 177, 177, 171, 162, 183, 185, 186, 185, 182;
  178, 180, 179, 177, 178, 179, 174, 167, 172, 174, 175, 174, 172;
  175, 178, 179, 178, 180, 182, 179, 173, 172, 174, 175, 175, 174;
  175, 179, 181, 180, 181, 183, 181, 177, 178, 180, 182, 183, 182]


內容來自《OpenCV 2 Computer Vision Application Programming Cookbook》

OpenCV2 訪問圖像的各個像素有各種方法

我們來用各種方法來實現減少圖像的顏色數量

color = color/div*div +div/2;

若div爲8,則原來RGB每個通道的256種顏色減少爲32種。

若div爲64,則原來RGB每個通道的256種顏色減少爲4種,此時三通道所有能表示的顏色有4×4×4 = 64 種

首先,我們來看一個函數

C++: uchar* Mat::ptr(int i=0)
i 是行號,返回的是該行數據的指針。
在OpenCV中,一張3通道圖像的一個像素點是按BGR的順序存儲的。
先來看看第一種訪問方案
void colorReduce1(cv::Mat& image, cv::Mat& result, int div=64){
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        uchar* data = image.ptr<uchar>(i);
        uchar* data_out = result.ptr<uchar>(i);
        for(int j=0; j<ncol; j++){
            data_out[j] = data[j]/div*div +div/2;
        }
    }
}

第二種方案:

先來看如下函數:

C++: bool Mat::isContinuous() const

C++: Mat Mat::reshape(int cn, int rows=0) const

出於性能方面的考慮,在圖像每一行的最後可能會填充一些像素,這樣圖像的數據就不是連續的了

我們可以用函數isContinuous()來判斷圖像的數據是否連續

reshape函數的作用如下:

Changes the shape and/or the number of channels of a 2D matrix without copying the data.

這樣,我們就提出了對第一種方法的改進

void colorReduce2(cv::Mat& image, cv::Mat& result, int div){
    if(image.isContinuous()){
        image.reshape(1,image.cols*image.rows);
    }
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        uchar* data = image.ptr<uchar>(i);
        uchar* data_out = result.ptr<uchar>(i);
        for(int j=0; j<ncol; j++){
            data_out[j] = data[j]/div*div +div/2;
        }
    }
}
第三種方案:
先來看看下面的函數
C++: template<typename T> T& Mat::at(int i, int j)
其作用是Returns a reference to the specified array element.
void colorReduce3(cv::Mat& image, cv::Mat& result, int div){
    int nrow = image.rows;
    int ncol = image.cols * image.channels();
    for(int i=0; i<nrow; i++){
        for(int j=0; j<ncol; j++){
            image.at<cv::Vec3b>(j,i)[0]= image.at<cv::Vec3b>(j,i)[0]/div*div + div/2;
            image.at<cv::Vec3b>(j,i)[1]= image.at<cv::Vec3b>(j,i)[1]/div*div + div/2;
            image.at<cv::Vec3b>(j,i)[2]= image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
        }
    }
}
第四種方案是使用迭代器
會使用到如下函數:
C++: template<typename _Tp> MatIterator_<_Tp> Mat::begin()
C++: MatIterator_<_Tp> Mat::end()
void colorReduce4(cv::Mat& image, cv::Mat& result, int div){
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator itout = result.begin<cv::Vec3b>();
    for(; it!=itend; ++it,++itout){
        (*itout)[0] = (*it)[0]/div*div + div/2;
        (*itout)[1] = (*it)[1]/div*div + div/2;
        (*itout)[2] = (*it)[2]/div*div + div/2;
    }
}
OpenCV中矩陣數據的訪問(二)(Learning OpenCV第三章3)

2009-08-14 21:45:19| 分類: 科研學習 |字號 訂閱
上一篇文章提到了訪問矩陣中元素的前兩種方式,下面講第三種方式:正確的訪問矩陣中數據的方式:

正確的方式
前面介紹的一些讀取和寫入矩陣數據的方式,實際上,你可能很少會使用它們。因爲,在大多數情況下,你需要使用最有效率的方式來訪問矩陣中的數據。如果使用以上的函數界面來訪問數據,效率比較低,你應該使用指針方式來直接訪問矩陣中數據。特別是,如果你想遍歷矩陣中所有元素時,就更需要這樣做了。
在用指針直接訪問矩陣元素時,就需要格外注意矩陣結構體中的step成員。該成員是以字節爲單位的每行的長度。而矩陣結構體的cols或width就不適合此時使用,因爲爲了訪問效率,矩陣中的內存分配上,是以每四個字節做爲最小單位的。因此如果一個矩陣的寬度是三個字節,那麼就會在寬度上分配四個字節,而此時每行最後一個字節會被忽略掉。所以我們用step則會準確地按行訪問數據。
我們可以通過以下例子,看一下rows,cols,height,width,step的數據,你可以通過改變矩陣的元素類型定義,來查看step的改變:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
    //矩陣元素爲三通道8位浮點數
    CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
    printf("rows=%d,cols=%d,height=%d,width=%d,step=%d\n",mat->rows,mat->cols,mat->height,mat->width,mat->step);

}
如果我們的矩陣存儲的是浮點型(或整數類型)數據,此時矩陣中每個元素佔4字節,則如果我們用float類型指針指向下一行時,我們實際上要用float類型指針挪動step/4的長度,因爲float類型指針每挪動一個單位就是4個字節長度。
如果我們的矩陣存儲的是double類型數據,此時矩陣中每個元素佔8字節,則如果我們用double類型指針指向下一行時,我們實際上要用double類型指針挪動step/8的長度,因爲double類型指針每挪動一個單位就是8個字節長度。
我們重新看一下CvMat類型的數據結構定義,其中,data就是數據部分,指向data的指針可以是多種數據類型的:
typedef struct CvMat {
    int type;
    int step;
    int* refcount; // for internal use only
    union {
         uchar* ptr;
         short* s;
         int* i;
         float* fl;
         double* db;
    } data;//數據部分
    union {
         int rows;
         int height;
    };
    union {
         int cols;
         int width;
    };
} CvMat;

我們可以通過爲矩陣賦值,和讀取的例子,查看怎樣使用step:
#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
    //矩陣元素爲三通道8位浮點數
    CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
    float *p;
    int row,col;
    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;
        }
    }

    for(row = 0; row < mat->rows; row++)
    {
        p = mat->data.fl + row * (mat->step/4);
        for(col = 0; col < mat->cols; col++)
        {
            printf("%f,%f,%f\t",*p,*(p+1),*(p+2));
            p+=3;
        }
        printf("\n");
    }
}

如果我們使用的指針類型爲uchar*類型,則事情可能會簡單一些,不用考慮step/4,step/8等類似情況,我們推薦用這種方式。如下例所示:

#pragma comment(lib,"cxcore.lib")
#include"cv.h"
#include<stdio.h>
void main()
{
    //矩陣元素爲三通道8位浮點數
    CvMat *mat=cvCreateMat(3,3,CV_32FC3 );
    float *p;
    int row,col;
    for(row=0; row< mat->rows; row++)
    {
        p = (float*)(mat->data.ptr + row * mat->step);
        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;
        }
    }

    for(row = 0; row < mat->rows; row++)
    {
        p = (float*)(mat->data.ptr + row * mat->step);
        for(col = 0; col < mat->cols; col++)
        {
            printf("%f,%f,%f\t",*p,*(p+1),*(p+2));
            p+=3;
        }
        printf("\n");
    }
}

最後要注意一下,我們在每行都要使用step重新計算一下指針的位置,這好象不如從首指針從頭到尾一直指下去,如我們上一文章的例子一樣


#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");
    }
}

但是一定要注意了,這個例子其實是不對的!因爲我們說過,分配矩陣內存空間時,是以四字節爲最小單位的,這就很有可能有不到四個字節而取成四個字節的情況,所以,如果用矩陣首地址從頭到尾指下去訪問數據,就很有可能訪問到不是數據的字節上去!這一點請務必牢記!!
綜上所述,如果要直接訪問矩陣中數據,請記住使用step的方案。


另一個需要知道的情況是,我們需要了解一個多維數組(矩陣)和一個一維,但是包含高維數據的數組之間的區別。假設,你有n個點(每個點有x,y,z座標值)需要保存到CvMat*中,你其實有四種方式可以使用,但這四種方式的存儲形式不同。你可能使用一個二維矩陣,矩陣大小爲n行3列,數據類型爲CV32FC1。你還可以使用一個二維矩陣,矩陣大小爲3行n列,數據類型爲CV32FC1;第三種可能性是,你使用一個一維矩陣,n行1列,數據類型爲CV32FC3;最後,你還可以使用1行三列,數據類型爲CV32FC3.這幾種方式,在內存分配上,有些是相同的,有些是不同的,如下所示:

n個點的集合(n=5);
(x0 y0 z0) (x1 y1 z1) (x2 y2 z2) (x3 y3 z3) (x4 y4 z4)

n行1列時(數據類型CV32FC3)內存分配情況
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

1行n列時(數據類型CV32FC3)內存分配情況
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

n行3列時(數據類型CV32FC1)內存分配情況
x0 y0 z0 x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4

3行n列時(數據類型CV32FC1)內存分配情況
x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 z0 z1 z2 z3 z4


我們可以看出,前三種的內存分配情況相同,但最後一種的內存分配不同。更復雜的是,如果有n維數組,每個數組的元素是c維(c可能是通道數)時。所以,多維數組(矩陣)和一個一維但包含多維數據的數組一般是不同的。

對於一個Rows行Cols列,通道數爲Channels的矩陣,訪問其中第row行,第col列,第channel通道的數據,可以使用如下公式:
數據地址偏移量=row*Cols*Channels+col*Channels+channel
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章