天天看點

OpenCV中Mat資料結構

轉自:

豆瓣作者Lirpa,不知道為何一貼上連結就非法···

第一部分:Mat資料存儲方式

首先看Opencv官方文檔中:

OpenCV中Mat資料結構

注意到位址計算公式,那麼step這個數組到底是什麼呢?如此的神奇!

先來看一下Opencv中Mat資料結構:

class CV_EXPORTS Mat
{
public:
    /*
	* functions
	*/
    enum { MAGIC_VAL=0x42FF0000, AUTO_STEP=0, CONTINUOUS_FLAG=CV_MAT_CONT_FLAG, SUBMATRIX_FLAG=CV_SUBMAT_FLAG };

    /*! includes several bit-fields:
         - the magic signature ---//Magic number-wikipedia
         - continuity flag
         - depth
         - number of channels
     */
    int flags;
    //! the matrix dimensionality, >= 2
    int dims;
    //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
    int rows, cols;
    //! pointer to the data
    uchar* data;

    //! pointer to the reference counter;
    // when matrix points to user-allocated data, the pointer is NULL
    int* refcount;
    
    //! helper fields used in locateROI and adjustROI
    uchar* datastart;
    uchar* dataend;
    uchar* datalimit;
    
    //! custom allocator
    MatAllocator* allocator;
    
    struct CV_EXPORTS MSize
    {
        MSize(int* _p);
        Size operator()() const;
        const int& operator[](int i) const;
        int& operator[](int i);
        operator const int*() const;
        bool operator == (const MSize& sz) const;
        bool operator != (const MSize& sz) const;
        
        int* p;
    };
    
    struct CV_EXPORTS MStep
    {
        MStep();
        MStep(size_t s);
        const size_t& operator[](int i) const;
        size_t& operator[](int i);
        operator size_t() const;
        MStep& operator = (size_t s);
        
        size_t* p;
        size_t buf[2];
    protected:
        MStep& operator = (const MStep&);
    };
    
    MSize size;
    MStep step;
};

           

下面是上面注釋部分的内容解釋:

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 的大小

OpenCV中Mat資料結構

依照上圖舉個例子:

上面是一個 3 X 4 的矩陣,假設其資料類型為 CV_8UC3,也就是三通道的 uchar 類型

M.dims == 2; M.channels() == 3;M.depth() == 0;

M.elemSize() == 3//每一個元素所占位元組數,即nchannels個uchar位元組長度

M.elemSize1() == 1 (elemSize / channels)//每一個元素類型所占位元組數,即uchar所占位元組數

M.step[0] == M.cols * M.elemSize() == 12, //每一行所占位元組數,對應Iplimage中的stepWidth

M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;//每第一次元的第二次元機關長度,這兒指一個元素所占位元組數,是以有M.steps[m-1] = elemSize

M.step1(0) == M.cols * M.channels() == 12 //第一維(行)資料元素個數

; M.step1(1) == M.channels() == 3;//第一次元中第二維(每個元素)資料元素個數,即通道個數

OpenCV中Mat資料結構

上面是一個 3 X 4 X 6 的矩陣,注意第一維指的是面的個數。假設其資料類型為 CV_16SC4,也就是 short 類型

M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;

M.rows == M.cols == –1;//參見Mat資料結構注釋

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(第三次元(即元素) * 通道數);

第二部分: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]

第三部分:Mat 的通路方式

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

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()來判斷圖像的資料是否連續

這樣,我們就提出了對第一種方法的改進,當然除此之外還可以将

void colorReduce2(cv::Mat& image, cv::Mat& result, int div){
    if(image.isContinuous()){
        int ncol * =image.cols*image.rows;//可以替代為image.reshape(1,image.cols*image.rows);這樣會不改變記憶體的情況下将Mat當行向量處理
    }
    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;
        }
    }
}
           

第四種方案是使用疊代器(比較推薦使用,在面向對象程式設計中常用)

Mat_<傳回類型>::iterator it;或MatIterator_ <傳回類型> it;  還可以指定const_Iterator來說明你不想修改image的值或image本身就是const image,此時使用:

Mat_<傳回類型>::const_iterator it;或MatConstIterator_<傳回類型> it;

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

第四部分:Mat中step和Iplimage中的stepWidth

轉自http://blog.csdn.net/ljbkiss/article/details/7369947

參考第三部分可以知道:

其中M.size[k]是第k維長度,ex.在rows*cols=300*200的二維圖像中,第0維的size,即M.size[0]=300,M.size[1]=200,M.dims=2。

step[k],即步長可以看作是與第k維的存儲機關,在2維的矩陣中,因為存儲是按照行的順序存儲的,整個矩陣存儲為一個平面,是以第k=0維的步長也就是機關肯定就是一行所占的位元組數;如果是3維的話,第0維是按照面為機關來存儲的,第1維是按照行為機關來存儲的,第2維是按照元素類型為機關存儲的,每個元素類型是基本類型(即uchar,float,short等等)與通道數的乘積...;      
也就是基本資料類型與通道數組成元素,多個元素組成了行,多行組成了面,多個面組成了3維體,多個3維體 組成4維超體。。。以此類推,如此看來某一維的步長應該等于低一維的步長step(該維所占位元組數)*低一維的大小size,那麼>=是怎麼回事?      
OpenCV中Mat資料結構
這就是記憶體對齊啦!      
為了提高計算速度,對資料存儲按照字長進行了對齊,在32位機器中是按照4位元組對齊的,就像一個row=100,cols=101大小的單通道uchar圖像,如果進行位元組對齊則其step[0]=104,而不是101;      
##注##--使用cv::Mat時,如果資料是從圖像加載的,或者使用構造函數、create等建立配置設定的資料,是沒有位元組對齊的,所有的資料都是順序存儲,是continuous的,但是如果       
  (1)是使用自己的資料,然後用了Mat的頭來通路,而指定的step不是AUTO_STEP的話,那麼是否存在位元組對齊就不一定了,但是如果是指定了step=AUTO_STEP則沒有位元組對齊,這個可以從手冊中使用外部資料的構造函數對step參數的介紹中知道。      
(2)圖像是用IplImage加載的,然後轉換為cv::Mat那就存在位元組對齊的現象(不複制資料時),如果複制資料進行初始化則不會記憶體對齊。      
總結:Mat預設不記憶體對齊,所有資料連續存儲。對于Iplimage和CvMat這種原就預設記憶體對齊的情況,如果直接建立矩陣頭初始化而不複制資料(預設方式),則依舊記憶體對齊mat.step[0]=image->stepWidth;因為沒有重新配置設定記憶體      
如果明确指定要記憶體對齊,那肯定就要記憶體對齊啦!      
下面給出一個關于step,size的測試例子,從其輸出中可以驗證這些      
Mat load_mat = imread("d:/picture/temp1.bmp");
	cout<<"step[0]="<<load_mat.step[0]<<",size[0]="<<load_mat.size[0]<<",step[1]="<<load_mat.step[1]<<",size[1]="<<load_mat.size[1]<<endl;
	cout<<"rows="<<load_mat.rows<<",cols="<<load_mat.cols<<"elemSize="<<load_mat.elemSize()<<",continuous="<<load_mat.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;

	Mat mem_mat(101,104, CV_8UC3, Scalar::all(255));
	cout<<"step[0]="<<mem_mat.step[0]<<",size[0]="<<mem_mat.size[0]<<",step[1]="<<mem_mat.step[1]<<",size[1]="<<mem_mat.size[1]<<endl;
	cout<<"rows="<<mem_mat.rows<<",cols="<<mem_mat.cols<<"elemSize="<<mem_mat.elemSize()<<",continuous="<<mem_mat.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;
	
	int sz[]={101,101,101};
	Mat cube(3, sz, CV_32FC3, Scalar::all(0));
	cout<<"step[0]="<<cube.step[0]<<",size[0]="<<cube.size[0]<<",step[1]="<<cube.step[1]<<",size[1]="<<cube.size[1]<<endl;
	cout<<"step[2]="<<cube.step[2]<<",size[2]="<<cube.size[2]<<",step1*size1="<<cube.step[1]*cube.size[1]<<endl;
	cout<<"step2*size2="<<cube.size[2]*cube.step[2]<<",elemSize="<<cube.elemSize()<<",continuous="<<cube.isContinuous()<<endl;
	cout<<"rows="<<cube.rows<<",cols="<<cube.cols<<endl;
	cout<<"----------------------------------------------------"<<endl;

	uchar data[1212]={0};
	Mat createdmat(cv::Size(101,12),CV_8UC1, data, 104);
	cout<<"step[0]="<<createdmat.step[0]<<",size[0]="<<createdmat.size[0]<<",step[1]="<<createdmat.step[1]<<",size[1]="<<createdmat.size[1]<<endl;
	cout<<"rows="<<createdmat.rows<<",cols="<<createdmat.cols<<"elemSize="<<createdmat.elemSize()<<",continuous="<<createdmat.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;
	
	Mat createdmat2(cv::Size(101,12),CV_8UC1, data);
	cout<<"step[0]="<<createdmat2.step[0]<<",size[0]="<<createdmat2.size[0]<<",step[1]="<<createdmat2.step[1]<<",size[1]="<<createdmat2.size[1]<<endl;
	cout<<"rows="<<createdmat2.rows<<",cols="<<createdmat2.cols<<"elemSize="<<createdmat2.elemSize()<<",continuous="<<createdmat2.isContinuous()<<endl;
	cout<<"----------------------------------------------------"<<endl;

	IplImage *image = cvLoadImage("d:/picture/temp1.bmp");
	cout<<"widthstep="<<image->widthStep<<",height="<<image->height<<",width="<<image->width<<endl;
	Mat cvted(image);
	cout<<"step[0]="<<cvted.step[0]<<",size[0]="<<cvted.size[0]<<",step[1]="<<cvted.step[1]<<",size[1]="<<cvted.size[1]<<endl;
	cout<<",step1*size1="<<cvted.step[1]*cvted.size[1]<<"elemSize="<<cvted.elemSize()<<",continuous="<<cvted.isContinuous()<<endl;
	cout<<"=========================================================="<<endl;

	cout<<"sizeof(loadmat)="<<sizeof(load_mat)<<",sizeof(memmat)="<<sizeof(mem_mat)<<",sizeof(cube)="<<sizeof(cube)<<endl;
	cout<<"sizeof(createdmat)="<<sizeof(createdmat)<<",sizeof(createdmat2)="<<sizeof(createdmat2)<<",sizeof(cvted)="<<sizeof(cvted)<<endl;
		
	cvReleaseImage(&image);
           

下面是輸出:

OpenCV中Mat資料結構

結合上面的分析,可以看出第一種情況是使用imread加載的圖像,雖然圖像的cols=355,但是其step[0]=1065=355*3 沒有位元組對齊,從continuous=1也可以看出,下面的第二種情況也是,第三種情況是3維矩陣,也沒有進行位元組對齊,此時可以看出cols=rows=-1;

第四種情況,使用外部資料,指定了step=104進行位元組對齊,可以看出此時step[0]!=step[1]*size[1]了,continuous=0,

但是第五種情況下,step采用預設AUTO_STEP時,沒有位元組對齊---因為一般隻是給連續數組空間加上矩陣頭,不重新配置設定存儲空間當然不記憶體對齊,

第六種情況,采用IplImage加載與第一種同一幅圖像時,可以看出此時使用了位元組對齊,因為存儲區沒變化(并沒有重新配置設定存儲空間)其widthstep等于轉換為Mat之後的step[0]的值,大于step[1]*size[1],continuous=0,

在這兒需要加上第七種情況,就是采用Mat初始化Iplimage并重新配置設定存儲空間時:

IplImage *image =cvCreateImage(cvSize(3,3),IPL_DEPTH_8U,3);
	Mat img_matF(image,false);
	Mat img_matT(image,true);
	
	cout<<image->widthStep<<endl;//12
	cout<<img_matF.step[0]<<endl;//12
	cout<<img_matT.step[0]<<endl;//9
           
另外幾個常用的成員變量:      
--------------------------------------------------      
int flags; 标志位,包含幾個位域:
 --magic signature 魔法簽名或者更确切的說應該是檔案的簽名,用于區分不同檔案格式的标志,是檔案的"身份證";在Mat構造函數中可以發現一些端倪, 在使用外部資料構造Mat的構造函數中:flags(MAGIC_VAL + (_type & TYPE_MASK)),在預設構造函數中:flags(0),在imread圖像加載時應該也進行了相應的設定【沒有确認?】,
 關于該數字簽名的一些相關東西,可以參考一下WikiPedia的Magic number詞條
 --continuity flag 即是否是連續存儲的,标志有沒有使用位元組對齊
 --depth 元素深度即基本元素類型,uchar,short,float等
 --number of channels 通道數
 --------------------------------------------------      
cols,rows 矩陣的行數,列數【注意,在圖像中行數對應的是高度,列數對應的是寬度】,當維數大于2時,均為-1;      
dims 矩陣的維數;      



繼續閱讀