1. 程式人生 > 其它 >OpenCV筆記(3) CV::Mat

OpenCV筆記(3) CV::Mat

1. 建立一個數組

1.1 使用建構函式

    cv::Mat a;     //預設建構函式                                                       
    cv::Mat b = cv::Mat();  //預設建構函式
    cv::Mat c = cv::Mat(3, 3, CV_8UC1);  //指定型別的二維陣列
    cv::Mat d = cv::Mat(cv::Size(3, 3),CV_8UC1); //指定型別的二維陣列
    cv::Mat e = cv::Mat(cv::Size(3, 3), CV_32FC2, cv::Scalar(1
, 2)); //指定初始化值 cv::Mat f = cv::Mat(cv::Size(3, 3), CV_8UC3, cv::Scalar(1, 2, 3)); //指定初始化值 cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; cout << "d = " << d << endl; cout
<< "e = " << e << endl; cout << "f = " << f << endl;

下表列舉了cv::Mat的基本建構函式。除了預設建構函式,它們主要分為如下幾個型別:

  • 要求輸入行數和列數來構造二位陣列的
  • 使用cv::Size物件來構造二維陣列
  • 構造n維陣列並且要求你通過一個整型的序列來確定每一位資料維度的
建構函式 說明
cv::Mat() 預設建構函式
cv::Mat(int rows, int cols, int type); 指定型別的二維陣列
cv::Mat(cv::Size size, int type);
指定型別的二維陣列(大小由size指定)
cv::Mat(int rows, int cols, int type, const Scalar& s); 指定型別的二維陣列,並指定初始化值
cv::Mat(cv::Size size, int type, const Scalar& s); 指定型別的二維陣列,並指定初始化值(大小由size指定)
cv::Mat(int ndims, const int* sizes, int type); 指定型別的多維陣列
Mat(int ndims, const int* sizes, int type, const Scalar& s); 指定型別的多維陣列,並指定初始化值

Mat(int rows, int cols, int type, void* data,

size_t step=AUTO_STEP)

指定型別的多維陣列,並指定預先儲存的資料

1.2 從其他cv::Mat複製資料

方法主要有:

  • 從一個數組建立另一個數組
  • 從已存在的陣列子區域建立陣列

   1)輸入行列範圍(只在二維矩陣的情況下有效)

   2)使用cv::Rect來指定一個矩形的子區域(只在二維矩陣的情況下有效)

   3)輸入一個range陣列 (range所指向的有效範圍必須和mat的維度相等,如果你的mat時多維(>2)的陣列,必須使用第三種)

  • 從矩陣表達中生成新的矩陣
建構函式 說明
cv::Mat(const Mat& m) 複製建構函式

cv::Mat(const Mat& m,

const cv::Range& rows,

constcv::Range& cols);

只從指定的行列中複製資料的複製建構函式
cv::Mat(const Mat& m, const cv::Rect& roi); 只從感興趣區域中複製資料的複製建構函式
cv::Mat(const Mat& m, const cv::Range* ranges); 服務於n維陣列的,從泛化的ROI中複製資料的複製建構函式
cv::Mat(const cv::MatExpr& expr); 從其他矩陣的線性代數表述中生成新矩陣的複製建構函式

1.3 通過create函式來建立陣列

cv::Mat m;//陣列沒有大小和資料型別
m.create(4, 4, CV_8UC1);//再次使用成員函式create()來申請一個記憶體區域
cout << "m = " << m << endl;

1.4 通過opencv提供的類matlab的函式建立

    cv::Mat Me = cv::Mat::eye(4, 4, CV_64F);
    cout << "Me = " << Me << endl;

    cv::Mat Mo = cv::Mat::ones(4, 4, CV_64F);
    cout << "Mo = " << Mo << endl;

    cv::Mat Mz = cv::Mat::zeros(4, 4, CV_64F);
    cout << "Mz = " << Mz << endl;

1.5 資料自定義矩陣建立

    cv::Mat m = (cv::Mat_<int>(3, 3) <<
        1, 2, 3,
        4, 5, 6,
        7, 8, 9);

2. 訪問陣列元素

2.1 訪問一個元素

  • 通過模板函式at<>()來實現。

這個函式有很多變體,對不同維度的陣列有不同的引數要求。這個函式的工作方式是先將at<>()特化到矩陣所包含的資料型別,然後使用你想要的資料的行和列的位置訪問該元素。

 cv::Mat m1 = cv::Mat::eye(10, 10, CV_32FC1);  //單通道陣列
 printf(
"Element(3,3) is %f\n", m1.at<float>(3, 3));

  cv::Mat m2 = cv::Mat::eye(10, 10, CV_32FC2); //多通道陣列
  printf("Element(3,3) is (%f, %f)\n",m2.at<cv::Vec2f>(3, 3)[0], m2.at<cv::Vec2f>(3, 3)[1]);

輸出結果為:

Element(3,3) is 1.000000
Element(3,3) is (1.000000, 0.000000)
  • 通過模板函式ptr<>()來實現。

首先需要一個型別名來例項化。然後接受一個整型引數來指示希望指標指向的行,函式將返回一個和矩陣原始資料型別相同的資料指標(比如說,如果陣列的型別是CV_32FC3,它將會返回一個float*)。因此給定一個型別為float三通道的矩陣mtx,結構體mtx.ptr<Vec3f>(3)將會返回mtx對應行指向第一個元素第一個(浮點)通道的指標,這通常是訪問陣列組塊的一種方式。因為一旦你擁有指標,就可以向指定的位置寫入資料。

cv::Mat m = cv::Mat::eye(4, 4, CV_32FC1);
cout << *m.ptr<float>(0) << endl;    

cv::Mat m1 = cv::Mat::eye(4, 4, CV_32FC2);
cout << *m1.ptr<cv::Vec2f>(0) << endl;
 

輸出結果為

1
[1,0]
  • 使用cv::Mat內嵌的迭代器機制

這種機制在某種程度上是基於STL容器所提供的機制。基礎想法是OpenCV提供一對迭代器模板

  • cv::MatConstIterator<> 用於只讀(const)陣列
  • cv::MatIterator<> 用於非只讀(ono-const)陣列

cv::Mat的成員函式begin()和end()會返回這種型別的物件,因為迭代器具有足夠的智慧來處理連續的記憶體區域和非連續的記憶體區域,所以這種方法很方便。

所有迭代器都必須在陣列建立的時候宣告並且指定一個物件型別。下面有一個簡單的使用迭代器來極端三通道三維陣列中“最長”元素(一個三位向量域)的例子:

    cv::Mat m;
    m.create(cv::Size(3, 3), CV_32FC3);
    cv::randu(m, -1.0f, 1.0f);
    float max = 0.0f; 
    cv::MatConstIterator_<cv::Vec3f> it = m.begin<cv::Vec3f>();//書上的例子此處編譯錯誤,使用了迭代器和begin模板類就ok了
                                        //使用MatConstIterator類,編譯沒問題,輸出會亂碼
    while (it != m.end<cv::Vec3f>()) {
        float len2 = (*it)[0] * (*it)[0] + (*it)[1] * (*it)[1] + (*it)[2] * (*it)[2];    
        if (len2 > max) max = len2;
        it++;
    }
    cout << max << endl;

2.2 通過面訪問陣列元素

還有另一種形式的迭代器,cv::NAryMatIterator。它只要求被迭代的陣列有相同的幾何結構(維度以及每個維度的範圍)。該迭代器不會返回一個用於迭代的單獨元素,而通過返回一堆陣列進行N-ary迭代器操作,這些返回的陣列也稱為“面”(plane)。

2.3 通過塊訪問陣列元素

你可能需要將一個數組的子集作為另一個數組訪問。這個子集可能是一行或者一列,也可能是原始陣列的一個子集。

最簡單的方法是row()和col()他將一個整型變數作為引數並返回這個變數所索引的行或者列。注意:表示式m2=m.row(3),這個表示式將建立一個新的陣列頭,並且分配它的data指標、step陣列以及其他一寫東西,這樣它將可以訪問m中的第三行資料。如果修改了m2中得到的資料,也會修改m中的資料,應使用真正的拷貝資料方法copyTo()。

還有rowRange()和colRange(),可以從多個連續的行(列)中提取陣列。你可以1)指定開始和結束的行(列);2)傳遞指明想要的行(列)的cv::Range物件。在輸入兩個整數的情況下,資料範圍會包括開始的索引但不包括結束的索引所指向的行(列)。在使用cv::Range的時候,也會受到類似的處理。

函式成員diag()返回的陣列指向矩陣m的對角元素,呼叫m.diag()的時候,需要輸入一個整型引數來說明哪一個對角需要被提取。如果引數為0,那麼將會是主對角;如果是正數,相對於主對角向陣列上部分偏移;如果是負的,反之。

最後一種提取子矩陣的方式是運算子()通過這個運算子,你可以傳遞一對範圍(指示行和列範圍的cv::Range)或者一個cv::Rect來指定你想要的區域。

cv::Mat a = (cv::Mat_<int>(3, 3) <<
        1, 2, 3,
        4, 5, 6,
        7, 8, 9);
cout << a.diag() << endl;     //輸出[1;5;9]
cout << a.diag(1) << endl;  //輸出[2;6]
cv::Mat b = a.rowRange(0, 1); 
cv::Mat c
= a.colRange(0, 1);
cout
<< a(cv::Range(0, 1), cv::Range(1, 2)) << endl;//輸出[2]
cout << a(cv::Rect(0,0,2,2)) << endl;//輸出[1,2;4,5]

3. 矩陣運算

3.1 矩陣的加減乘除

操作m2=m1,m2只是對於m1的一個引用。相比而言,m2=m1+m0表達的意思則完全不一樣。因為m1+m0是矩陣表示式,者會被計算並且將結果的指標指向m2。結果將處於一個新的記憶體區域中。

示例       說明
m0 + m1; m0 - m1;
m0 + s; m0 - s; s + m0; s - m0; 矩陣和單個元素的加減
-m0;
s * m0; m0 * s;
m0.mul(m1); m0/m1; 對應元素相乘/除
m0*m1 矩陣乘法

3.2 向量的點乘和叉乘

  • m0.dot(m1);

對於向量a和向量b:

a和b的點乘(內積)公式為:

  • m0.cross(m1);只適用於3x1矩陣

對於向量a和向量b:

a和b的點乘(外積)公式為:

其中:

3.3 其他

  • 求逆 m0.inv(method)
  • 求轉置 m0.t()
  • 取絕對值 cv::abs(m0)
  • 按元素進行比較 m0>m1
  • 邏輯操作 m0&m1
  • min(m0,m1); max(m0,s);

4.成員函式

源自:《學習OpenCV3》第四章