[OpenCV學習筆記][Mat數據類型和操作]

®.運行環境:Linux(RedHat+OpenCV3.0)

1.Mat的作用:

Mat類用於表示一個多維的單通道或者多通道的稠密數組。能夠用來保存實數或複數的向量、矩陣,灰度或彩色圖像,立體元素,點雲,張量以及直方圖(高維的直方圖使用SparseMat保存比較好)。簡而言之,Mat就是用來保存多維的矩陣的。

2.Mat的常見屬性:

  • data:  

       uchar類型的指針,指向Mat數據矩陣的首地址。可以理解爲標示一個房屋的門牌號;

  • dims: 

        Mat矩陣的維度,若Mat是一個二維矩陣,則dims=2,三維則dims=3,大多數情況下處理的都是二維矩陣,是一         個平面上的矩陣。

        可以理解爲房屋是一個一層的平房,三維或更多維的則是多層樓房;

  • rows:

        Mat矩陣的行數。可理解爲房屋內房間行數;

  • cols: 

        Mat矩陣的列數。可理解爲房屋內房間列數;

  • size():

        首先size是一個結構體,定義了Mat矩陣內數據的分佈形式,數值上有關係式:

         image.size().width==image.cols;        image.size().height==image.rows                                                      

         可以理解爲房屋內房間的整體佈局,這其中包括了房間分別在行列上分佈的數量信息;

  • channels():

        Mat矩陣元素擁有的通道數。例如常見的RGB彩色圖像,channels==3;而灰度圖像只有一個灰度分量信息,             channels==1。

        可以理解爲每個房間內放有多少牀位,3通道的放了3張牀,單通道的放了1張牀;

  • depth: 

        用來度量每一個像素中每一個通道的精度,但它本身與圖像的通道數無關!depth數值越大,精度越高。在                 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}          

        其中U是unsigned的意思,S表示signed,也就是有符號和無符號數。

        可以理解爲房間內每張牀可以睡多少人,這個跟房間內有多少牀並無關係;

  • elemSize:

        elem是element(元素)的縮寫,表示矩陣中每一個元素的數據大小,如果Mat中的數據類型是CV_8UC1,那麼             elemSize==1;如果是CV_8UC3或CV_8SC3,那麼elemSize==3;如果是CV_16UC3或者CV_16SC3,那麼             elemSize==6;即elemSize是以8位(一個字節)爲一個單位,乘以通道數和8位的整數倍;

        可以理解爲整個房間可以睡多少人,這個時候就得累計上房間內所有牀位數(通道)和每張牀的容納量了;

  • elemSize1:

        elemSize加上一個“1”構成了elemSize1這個屬性,1可以認爲是元素內1個通道的意思,這樣從命名上拆分後就很         容易解釋這個屬性了:表示Mat矩陣中每一個元素單個通道的數據大小,以字節爲一個單位,所以有: 

        eleSize1==elemSize/channels;

  • step:

        可以理解爲Mat矩陣中每一行的“步長”,以字節爲基本單位,每一行中所有元素的字節總量,是累計了一行中所           有元素、所有通道、所有通道的elemSize1之後的值;

  • step1(): 

       以字節爲基本單位,Mat矩陣中每一個像素的大小,累計了所有通道、所有通道的elemSize1之後的值,所以有:

        step1==step/elemSize1;

  • type:

        Mat矩陣的類型,包含有矩陣中元素的類型以及通道數信息,type的命名格式爲CV_(位數)+(數據類型)+(通道數),所有取值如下:


3.矩陣的構造、初始化、釋放

注:1、在程序的最開始加上: using namespace cv;  2、把Mat改爲 cv::Mat  (由於本人不會C++所有開始有點沒明白如何使用函數)

創建Mat類的方式:1.構造函數   2.create()函數創建對象3.從已有的數據源初始化

1.構造函數

 Mat::Mat()
無參數構造方法;

Mat::Mat(int rows, int cols, int type)
創建行數爲 rows,列數爲col,類型爲type的圖像;

Mat::Mat(Size size, int type)
創建大小爲 size,類型爲type的圖像;

Mat::Mat(int rows, int cols, int type, const Scalar& s)
創建行數爲rows,列數爲col,類型爲type的圖像,並將所有元素初始化爲值s;

Mat::Mat(Size size, int type, const Scalar& s)
創建大小爲 size,類型爲type的圖像,並將所有元素初始化爲值s

Mat::Mat(const Mat& m)
將 m 賦值給新創建的對象,此處不會對圖像數據進行復制, m 和新對象共用圖像數據;

Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
創建行數爲 rows,列數爲col,類型爲type的圖像,此構造函數不創建圖像數據所需內存,而是直接使用data所指內存,圖像的行步長由step指定。

Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
創建大小爲 size,類型爲type的圖像,此構造函數不創建圖像數據所需內存,而是直接使用data所指內存,圖像的行步長由step指定。

Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
創建的新圖像爲 m 的一部分,具體的範圍由 rowRange 和 colRange 指定,此構造函數也不進行圖像數據的複製操作,新圖像與m共用圖像數據;

Mat::Mat(const Mat& m, const Rect& roi)
創建的新圖像爲 m 的一部分,具體的範圍 roi 指定,此構造函數也不進行圖像數據的複製操作,新圖像與m共用圖像數據。

type的類型有CV_8UC1,CV_16SC1,…,CV_64FC4等。裏面的8U表示8位無符號整數,16S表示16位有符號整數,64F表示64位浮點數(即double類型);C後面的數表示通道數,例如C1表示一個通道的圖像,C4表示4個通道的圖像,以此類推。


如果你需要更多的通道數,需要用宏CV_8UC(n),例如:Mat M(3,2, CV_8UC(5));//創建行數爲3,列數爲2,通道數爲5的圖像

 

計算機視覺中,圖像的讀取是圖像處理的基礎,圖像就是一系列像素值,OpenCV使用數據結構cv::Mat來存儲圖像。cv::Mat是一個矩陣類,矩陣中每一個元素都代表一個像素,對於灰度圖像,像素用8位無符號數,0表示黑色,255表示白色。對於彩色像素而言,每個像素需要三位這樣的8位無符號數來表示,即三個通道(R,G,B),矩陣則依次存儲一個像素的三個通道的值,然後再存儲下一個像素點。

 

cv::Mat中,

cols代表圖像的寬度(圖像的列數),

rows代表圖像的高度(圖像的行數),

step代表以字節爲單位的圖像的有效寬度,

elemSize返回像素的大小,

channels()方法返回圖像的通道數,

total函數返回圖像的像素數。

像素的大小 = 顏色大小(字節)*通道數,

比如:

三通道short型矩陣(CV_16SC3)的大小爲2*3 = 6,

三通道Byte型矩陣(CV_8UC3)的大小爲1*3= 3,像素的channels方法返回圖像的通道數,total函數返回圖像的像素數。

RGB圖像的顏色數目是256*256*256,本文對圖像進行量化,縮減顏色數目到256的1/8(即32*32*32)爲目標,分別利用一下幾種方法實現,比較幾種方法的安全和效率。

 

方法一:使用Mat的成員函數ptr<>()

cv::Mat中提供ptr函數訪問任意一行像素的首地址,特別方便圖像的一行一行的橫向訪問,如果需要一列一列的縱向訪問圖像,就稍微麻煩一點。但是ptr訪問效率比較高,程序也比較安全,有越界判斷。

 

[cpp] view plain copy
 
  1. int nl = image.rows; //行數    
  2. int nc = image.cols * image.channels();  
  3. for (int j = 0; j<nl; j++)  
  4. {  
  5.     uchar* data = image.ptr<uchar>(j);  
  6.     for (int i = 0; i<nc; i++)  
  7.     {  
  8.         data[i] = data[i] / div*div + div / 2;  
  9.     }  
  10. }  

方法二:使用迭代器遍歷圖像

cv::Mat同樣有標準模板庫(STL),可以使用迭代器訪問數據。

用迭代器來遍歷圖像像素,可簡化過程降低出錯的機會,比較安全,不過效率較低;如果想避免修改輸入圖像實例cv::Mat,可採用const_iterator。iterator有兩種調用方法,cv::MatIterator_<cv::Vec3b>it;cv::Mat_<cv::Vec3b>::iterator it;中間cv::Vec3b是因爲圖像是彩色圖像,3通道,cv::Vec3b可以代表一個像素。

 

[cpp] view plain copy
 
  1. cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();  
  2. cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();  
  3. for (; it != itend; ++it)  
  4. {  
  5.     (*it)[0] = (*it)[0] / div*div + div / 2;  
  6.     (*it)[1] = (*it)[1] / div*div + div / 2;  
  7.     (*it)[2] = (*it)[2] / div*div + div / 2;  
  8. }  

方法三:使用Mat的成員函數at<>()

cv::Mat也是向量,可以使at方法取值,使用調用方法image.at<cv::Vec3b>(j,i),at方法方便,直接給i,j賦值就可以隨意訪問圖像中任何一個像素,其中j表示第j行,i表示該行第i個像素。但是at方法效率是這3中訪問方法中最慢的一個,所以如果遍歷圖像或者訪問像素比較多時,建議不要使用這個方法,畢竟程序的效率還是比程序的可讀性要重要的。下面是完整的調用方法,其運行時間在下面會介紹。

 

[cpp] view plain copy
 
  1. for (int j = 0; j< image.rows; j++)  
  2. {  
  3.     for (int i = 0; i< image.cols; i++)  
  4.     {  
  5.         image.at<cv::Vec3b>(j, i)[0] = image.at<cv::Vec3b>(j, i)[0] / div*div + div / 2;  
  6.         image.at<cv::Vec3b>(j, i)[1] = image.at<cv::Vec3b>(j, i)[1] / div*div + div / 2;  
  7.         image.at<cv::Vec3b>(j, i)[2] = image.at<cv::Vec3b>(j, i)[2] / div*div + div / 2;  
  8.     } // end of line                       
  9. }  

注意:使用at函數時,應該知道矩陣元素的類型和通道數,根據矩陣元素類型和通道數來確定at函數傳遞的類型,使用的是Vec3b這個元素類型,他是一個包含3個unsigned char類型向量。之所以採用這個類型來接受at的返回值,是因爲,我們的矩陣im是3通道,類型爲unsigned char類型

 

完整實例:

[cpp] view plain copy
 
    1. #include <iostream>    
    2. #include < opencv.hpp>    
    3. using namespace cv;  
    4. using namespace std;  
    5.   
    6. int main()  
    7. {  
    8.     //新建一個uchar類型的3通道矩陣  
    9.     Mat img(5, 3, CV_8UC3, Scalar(50,50,50));  
    10.     cout << img.rows << endl; //5  
    11.   
    12.     cout << img.cols << endl;  //3  
    13.   
    14.     cout << img.channels() << endl;  //3  
    15.   
    16.     cout << img.depth() << endl;  //CV_8U  0  
    17.   
    18.     cout << img.dims << endl;  //2  
    19.   
    20.     cout << img.elemSize() << endl;    //1 * 3,一個位置,三個通道的CV_8U  
    21.     cout << img.elemSize1() << endl;   //1  
    22.       
    23.     cout << img.size[0] << endl;   //5  
    24.     cout << img.size[1] << endl;   //3  
    25.   
    26.     cout << img.step[0] << endl;   //3 * ( 1 * 3 )  
    27.     cout << img.step[1] << endl;   //1 * 3  
    28.   
    29.     cout << img.step1(0) << endl;  //3 * 3  
    30.     cout << img.step1(1) << endl;  //3  
    31.   
    32.     cout << img.total() << endl;   //3*5  
    33.   
    34.     //--------------------------------------          地址運算         --------------------------------//  
    35.     for (int row = 0; row < img.rows; row++)  
    36.     {  
    37.         for (int col = 0; col < img.cols; col++)  
    38.         {  
    39.             //[row, col]像素的第 1 通道地址被 * 解析(blue通道)  
    40.             *(img.data + img.step[0] * row + img.step[1] * col) += 15;  
    41.   
    42.             //[row, col]像素的第 2 通道地址被 * 解析(green通道)  
    43.             *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) += 15;  
    44.   
    45.             //[row, col]像素的第 3 通道地址被 * 解析(red通道)  
    46.             *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) += 15;  
    47.         }  
    48.     }  
    49.     cout << img << endl;  
    50.     //--------------------------------------          Mat的成員函數at<>( )         --------------------------------//  
    51.     for (int row = 0; row < img.rows; row++)  
    52.     {  
    53.         for (int col = 0; col < img.cols; col++)  
    54.         {  
    55.             img.at<Vec3b>(row, col) = Vec3b(0, 0, 0);  
    56.         }  
    57.     }  
    58.     cout << img << endl;  
    59.     //--------------------------------------         使用Mat的成員函數ptr<>()         --------------------------------//  
    60.     for (int row = 0; row < img.rows; row++)  
    61.     {  
    62.         // data 是 uchar* 類型的, m.ptr(row) 返回第 row 行數據的首地址  
    63.         // 需要注意的是該行數據是按順序存放的,也就是對於一個 3 通道的 Mat, 一個像素3個通道值, [B,G,R][B,G,R][B,G,R]...   
    64.         // 所以一行長度爲:sizeof(uchar) * m.cols * m.channels() 個字節   
    65.         uchar* data = img.ptr(row);  
    66.         for (int col = 0; col < img.cols; col++)  
    67.         {  
    68.             data[col * 3] = 50;     //第row行的第col個像素點的第一個通道值 Blue  
    69.           
    70.             data[col * 3 + 1] = 50; // Green  
    71.           
    72.             data[col * 3 + 2] = 50; // Red  
    73.         }  
    74.     }  
    75.     cout << img << endl;  
    76.   
    77.     Vec3b *pix(NULL);  
    78.     for (int r = 0; r < img.rows; r++)  
    79.     {  
    80.         pix = img.ptr<Vec3b>(r);  
    81.         for (int c = 0; c < img.cols; c++)  
    82.         {  
    83.             pix[c] = pix[c] * 2;  
    84.         }  
    85.     }  
    86.     cout << img << endl;  
    87.     //--------------------------------------         使用Mat的成員函數ptr<>()         --------------------------------//   
    88.     MatIterator_<Vec3b> it_im, itEnd_im;  
    89.     it_im = img.begin<Vec3b>();  
    90.     itEnd_im = img.end<Vec3b>();  
    91.     for(; it_im != itEnd_im; it_im++)  
    92.     {  
    93.         *it_im = (*it_im) * 2;  
    94.     }  
    95.     cout << img << endl;  
    96.       
    97.     cvWaitKey();  
    98.     return 0;  
    99.   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章