OpenCV 之 Mat 類


  數字圖像可看作一個數值矩陣, 其中每個元素代表一個像素點,如下:

   

   OpenCV 中,用 Mat 表示數值矩陣,Mat 是很關鍵的一種數據結構,因為 OpenCV 中的大部分函數都和 Mat 有關:

   有的是 Mat 的成員函數;有的把 Mat 作為參數;還有的將 Mat 作為返回值。

 

 1  Mat 簡介

  Mat 表示的是 N 維稠密矩陣,與稠密矩陣相對的是稀疏矩陣(只存儲非零的像素值),后者常用於直方圖處理中,OpenCV 中對應為 cv::SparseMat

  如下所示:第一個為稠密矩陣的存儲方式,存儲所有的像素數值;第二個為稀疏矩陣的存儲方式,只存儲非零的像素值

      $\quad \begin{bmatrix} 0 & 2 & 0 \\ 1 & 0 & 1 \\ 0 & 2 & 0 \end{bmatrix} $        $\quad \begin{bmatrix}  & 2 &  \\ 1 &  & 1 \\  & 2 &  \end{bmatrix} $

  當 N=1 時,所有像素存儲為一行;當 N=2 時,所有像素按照一行行的順序排列;當 N=3 時,所有像素按照一面面的順序排列,其中一行行的像素構成一個平面。

  下圖左,為灰度圖的存儲方式;圖右,為 RGB 圖像的存儲方式,注意其存儲順序為 BGR (Blue->Green->Red)

         

2  Mat 特點

2.1  組成

   Mat 類包含兩部分,一是 矩陣頭 (matrix header),二是 矩陣指針 (pointer to matrix),部分矩陣頭如下:

int  flags;  // signaling the contents of the matrix
int  dims;   // dimensions
int  rows, cols;  // rows and columns 
MatSize  size;  // 
MatStep  step;  //

  矩陣指針如下,指向包含所有像素值的矩陣

uchar* data;  // pointer to the data

2.2  賦值算子

  Mat 類中的賦值算子 "=" 和 拷貝構造函數,涉及的是淺拷貝,因此,當執行這兩個操作時,僅僅是復制了矩陣頭。

  如果想要深拷貝,達到復制圖像矩陣的目的,應使用 clone()copyTo() 函數,如下圖所示 (摘自參考資料 -- 4):

  

2.3  代碼示例

  簡單驗證如下,將矩陣 m3 由 copyTo() 函數復制給 m1,而 m2 由 m1 直接賦值,二者指向的是同樣的數據。因此,如果改變了 m1,則 m2 對應的矩陣數值,也會進行相應的改變。

Mat m1(3, 3, CV_32FC1, Scalar(1.1f) );
cout << "m1 = " << endl << " " << m1 << endl << endl;
// using assign operator Mat m2
= m1; cout << "m2 = " << endl << " " << m2 << endl << endl; Mat m3(3, 3, CV_32FC1, Scalar(3.3f) ); m3.copyTo(m1); cout << "m1 = " << endl << " " << m1 << endl << endl; cout << "m2 = " << endl << " " << m2 << endl << endl;

3  Mat 創建

3.1  數據類型

  在創建 Mat 之前,首先了解 Mat 中元素的數據類型,其格式為 CV_{8U, 16S, 16U, 32S, 32F, 64F}C{1, 2, 3}CV_{8U, 16S, 16U, 32S, 32F, 64F}C(n)

  第一個 {} 表示數據的類型:

CV_8U - 8-bit 無符號整數 ( 0..255 ) CV_8S - 8-bit 有符號整數 ( -128..127 )
CV_16U - 16-bit 無符號整數 ( 0..65535 )
CV_16S - 16-bit 有符號整數 ( -32768..32767 ) CV_32S - 32-bit 有符號整數 ( -2147483648..2147483647 ) CV_32F - 32-bit 浮點數 ( -FLT_MAX..FLT_MAX, INF, NAN )
CV_64F - 64-bit 浮點數 ( -DBL_MAX..DBL_MAX, INF, NAN )

 第二個 {} 或 (n),表示的是通道:

CV_8UC3 等價於 CV_8UC(3) - 3通道 8-bit 無符號整數

3.2  創建方式

3.2.1  構造函數

  創建一個 3 行 5 列,3 通道 32 位,浮點型的矩陣,通道 1, 2, 3 的值分別為 1.1f,2.2f,3.3f

Mat m(3, 5, CV_32FC3, Scalar(1.1f, 2.2f, 3.3f) );
cout << "m = " << endl << " " << m << endl << endl;

  輸出的矩陣如下:

  

3.2.2  create 函數

  使用 Mat() + create() + setTo(),也可以構建如上的數值矩陣

Mat m;
// Create data area for 3 rows and 10 columns of 3-channel 32-bit floats m.create(
3,5,CV_32FC3);
// Set the values in the 1st channel to 1.0, the 2nd to 0.0, and the 3rd to 1.0 m.setTo(Scalar(
1.1f, 2.2f,3.3f)); cout << "m = " << endl << " " << m << endl << endl;

3.2.3  特殊矩陣

  單位矩陣 (ones),對角矩陣 (eye),零矩陣 (zeros),如下所示:

// 單位矩陣
Mat O = Mat::ones(3, 3, CV_32F);
cout << "O = " << endl << " " << O << endl << endl;
// 零矩陣
Mat Z = Mat::zeros(3, 3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl << endl;
// 對角矩陣
Mat E = Mat::eye(3, 3, CV_64F);
cout << "E = " << endl << " " << E << endl << endl;

4  Mat 遍歷

4.1  at<>() 函數

   常用來遍歷 Mat 元素的基本函數為 at<>(),其中 <> 內的數據類型,取決於 Mat 中元素的數據類型,二者的對應關系如下:

CV_8U  --  Mat.at<uchar>(y,x)
CV_8S  --  Mat.at<schar>(y,x)
CV_16U --  Mat.at<ushort>(y,x)
CV_16S --  Mat.at<short>(y,x)
CV_32S --  Mat.at<int>(y,x)
CV_32F --  Mat.at<float>(y,x)
CV_64F --  Mat.at<double>(y,x)

  簡單的遍歷如下,使用了 Qt 的 qDebug() 來顯示輸出

Mat m1 = Mat::eye(10, 10, CV_32FC1);
// use qDebug() qDebug()
<< "Element (3,3) is : " << m1.at<float>(3,3); Mat m2 = Mat::eye(10, 10, CV_32FC2);
// use qDebug()
qDebug()
<< "Element (3,3) is " << m2.at<cv::Vec2f>(3,3)[0] << "," << m2.at<cv::Vec2f>(3,3)[1];

  注意:at<>() 函數中 () 內行索引號在前,列索引號在后,也即 (y, x)

4.2  遍歷方式

4.2.1  高效遍歷

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    int channels = I.channels();
    int nRows = I.rows;
    int nCols = I.cols * channels;
    if (I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;
    }
    int i,j;
    uchar* p;
    for(i=0; i<nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for (j = 0; j<nCols; ++j)
        {
            p[j] = table[p[j]];
        }
    }
    return I;
}

4.2.2  迭代器遍歷

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    const int channels = I.channels();
    switch(channels)
    {
    case 1:
        {
            MatIterator_<uchar> it, end;
            for(it=I.begin<uchar>(), end=I.end<uchar>(); it!=end; ++it)
                *it = table[*it];
            break;
        }
    case 3:
        {
            MatIterator_<Vec3b> it, end;
            for(it=I.begin<Vec3b>(), end=I.end<Vec3b>(); it!=end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    return I;
}

 4.2.3  耗時計算

  比較上面兩種方法的耗時,可使用如下代碼來進行計算:

double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
qDebug() << "Times passed in seconds: " << t << endl; // using qDebug()

 

參考資料:

 1.  <Learning OpenCV3> chapter 4

 2.  OpenCV Tutorials / The Core Functionality (core module) / Mat - The Basic Image Container

 3.  OpenCV Tutorials / The Core Functionality (core module) / How to scan images, lookup tables and time measurement with OpenCV

 4.  OpenCV基礎篇之Mat數據結構 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM