1. 程式人生 > >OpenCv2 學習筆記(1) Mat建立、複製、釋放

OpenCv2 學習筆記(1) Mat建立、複製、釋放

opencv和VS2013的安裝圖文教程網上有很多,建議安裝好之後,用VS2013建立一個空工程,用屬性管理器分別新建一個對應debug和release工程的props配置檔案,以後直接根據工程需要新增對應配置檔案,而不需要每次新建工程後填寫引用目錄、庫目錄、附加依賴項,減少重複工作。

用WLW編輯,段間距有點大!需要說明的是,本學習筆記不會按照先講資料結構,再講如何使用。與OpenCv1.x不用中,opencv2.x及3.x中用Mat代替了CvMat和IplImage。因此,對Mat的使用,會從一些例子給出一個直觀的感受,之後再根據一些例子遇到新的東西就進行詳細的講解,遵循學習中遇到問題解決問題的方式。

為了使得Mat的輸出更美觀,自己寫了一個Mat的輸出;首先建立工程,cpp檔案的主程式如下:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;  

void coutMat(const char *str, InputArray &_m) // _m可以是各種矩陣形式,包括vec、vector和表示式等。

{   //通過getMat()獲取不同輸入格式的Mat的資料,淺複製
    cout << str << endl << " " << _m.getMat() << endl << endl; 
}
void main()
{
    //編輯程式碼
    waitKet(); //(呼叫imshow函式後有效)避免命令列視窗一閃而過,可以借用 getchar() 或者 system("pause")等輸入命令
}

1、Mat的建立、複製

/*
* Create Mat
*/
Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255));   // 建構函式的一種
cout << "M=" << endl << " " << M << endl << endl;
Mat A;
M.copyTo(A);
M.release();
cout << A << endl;        // 釋放不影響
 Mat B;
B = M.clone();
 M.release();
cout << "B=" << endl << " " << B << endl<<endl;    

Mat的一個建構函式 C++: Mat::Mat(int rows, int cols, int type, const Scalar& s) ,其中rows和cols是需要建立的矩陣的行數和列數,type是Mat的資料型別,s是Scalar型別的矩陣初值。

對於type,是基本資料型別,首先有列舉 enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 分別對應,8位無符號(uchar)、8位有符號(char)、16位無符號(ushort)、16位有符號(short)、32位有符號(int)、32位浮點(float)和64位雙精度(double);其次 CV_8UC1、CV_16FC2、.. CV_64FC4等是多通道的型別,可以用CV_(深度)(型別)(通道數)描述, 例如本例中CV_8UC3,是指8位無符號3通道,其他類推。

對於s的Scalar型別,它的源頭實際是一個4行1列的Mat,這裡的Scalar(0,0,255),直接可以理解成M矩陣的每一個元素都是(0,0,255),當M看成影象,就是一個2x2的紅色方塊,Scalar有3個值,可以對應RGB色彩,通道順序為(B,G,R)。那麼,CV_8UC2,可以用Scalar(1,2)賦值,CV_64UC4可以用Scalar(0,0.1,0.08,100.1)賦值,其他類推。

Mat類的兩個拷貝函式,copyTo()和clone(),都是進行深複製,也就是會另外開闢一個記憶體儲存被複制的資料區域,對複製得到的新矩陣進行釋放releas()不會影響原矩陣的資料(有其他方式會影響,後面遇到再講)。這裡的copyTo()和clone()區別在於,copyTo()可選一個引數掩膜mask,根據mask的值選擇複製區域。

上面例子的結果如下:

image

2、Mat的釋放

Mat mat1 = Mat::ones(1, 5, CV_32F);
Mat mat2 = mat1;                        // 僅建立一個mat2資訊頭, mat1,mat2 資料區的地址相同
Mat mat3 = Mat::zeros(1, 4, CV_32F);
mat2.release();          //  因為mat2是對mat1的引用,這裡的mat2.release()只會清除mat2的資訊頭和資料指標
mat1.release();         //  mat1的資料區都會被釋放,但是mat資訊頭資料還會儲存(也就是還能繼續被賦值)
cout << mat1 << endl;
cout << mat2 << endl;
cout << mat3 << endl << endl;
mat3.copyTo(mat1);// 拷貝會給mat1從新分配資料區域,其原來的資料區還會保留,即mat2的資料是原來mat1的資料,
//mat1 = mat3.clone();  // 最終結果是mat1和mat3的資料相同,但是資料儲存空間不同,  mat2儲存的是mat1最初的值
mat3.release();        // mat3的釋放不會影響mat1
cout << mat1 << endl;
cout << mat2 << endl;
cout << mat3 << endl << endl;

有關注釋讀起來比較拗口,上面的例子最好除錯下。總之,對於Mat的引用(也就是淺複製,只分配資訊頭,資料區共享)情況下的釋放,只會清除本身的資訊頭和置零資料區指標,不會影響被賦值的矩陣。Mat有一個引用機制,有一個成員變數refcount,會自己根據被引用和釋放的次數,自動管理記憶體,所以一般不需要使用者自己去釋放。對於建立型別的建構函式(深複製),那麼會有屬於自己的資料區,完全和被賦值的矩陣可以獨立開

3、Mat的複製和釋放

Mat A = (Mat_<uchar>(5, 2) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); //A為5x2的uchar型別矩陣,並被賦初值1-10
Mat B = A;        // B引用A,淺複製(僅建立資訊頭,資料指標指向A的資料區,沒有資料的複製)
Mat C = B.row(3);   // 同樣是引用,C指向B的4行
Mat D = B.clone();  // D是深複製B,實際是A的深複製。
B.row(4).copyTo(C); // B、C都是引用A,這裡相當於是把A的第4行“7,8”兩個數換成了第5行“9,10”
A = D;      //D是從B也就是A深度複製,這裡A引用了D
B.release();// B是引用,淺複製,這裡釋放的B的資訊頭並將其將資料指標置為0
C = C.clone();    // 因為C是淺複製,進過clone()深度之,開闢了記憶體並完全複製了資料,是完全獨立於A的。

這一個例子,可以更加深入的瞭解Mat的複製和釋放機制,除錯的時候可以看下各矩陣的refcount變數。下面的一個例子,copyTo()函式有第二個引數mask情況,程式碼和結果如下:

Mat A = (Mat_<uchar>(5, 2) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Mat mask = (Mat_<uchar>(5, 2) << 0, 0, 1, 1, 0, 0, 0, 0, 1, 1); 
Mat C,D;
A.copyTo(C);      // 第二個引數為空,等效於A.copyTo(C,Mat());  
A.copyTo(D, mask);//mask必須和被複制矩陣大小相同 
coutMat("C = ", C);    //和C一樣
coutMat("D = ", D); //D和C大小一樣,但是隻複製了第2、5行的資料,其他為0
image

4、其他

對於數學計算還有一些基本的建構函式,如Mat::eye()對角陣(當行、列不同時,主對角線為1)、Mat::ones()單位陣、Mat::zeros()零矩陣等。

用一個矩陣的一行復制到另外一行,不能通過直接複製,必須通過運算才行(運算的結果會返回一個實際的矩陣)。如矩陣mat的第1行復制到第2行,程式碼為 mat.row(1)=mat.row(0) 無效,但是 mat.row(1)=mat.row(0)+0; 或者 mat.row(1)=mat.row(0)+mat.row(2); 都是有效的。

總結

對於Mat一般只需要區什麼是淺複製和深複製即可,何時需要就直接建立,釋放可以交給OpenCv管理。另外沒有提到的是,當Mat直接被另外一個大小不同的矩陣深幅值時,Mat會先被釋放再被複制,不需要同OpenCv1.X中先釋放再指定需要的size才能被再次使用。