版權聲明:本文為博主原創文章,未經博主允許不得轉載。
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(); //避免命令行窗口一閃而過
}
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的值選擇復制區域。
上面例子的結果如下:
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

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才能被再次使用。