创建mat二值图 matlab,OpenCV学习之路(二)——Mat对象
早期的 OpenCV 中,使用 IplImage 和 CvMat 數據結構來表示圖像。IplImage和 CvMat 都是 C 語言的結構。使用這兩個結構的問題是內存需要手動管理,開發者必須清楚的知道何時需要申請內存,何時需要釋放內存。這個開發者帶來了一定的負擔,開發者應該將更多精力用于算法設計,因此在新版本的 OpenCV 中引入了 Mat 類。
新加入的 Mat 類能夠自動管理內存。使用 Mat 類,你不再需要花費大量精力在內存管理上。而且你的代碼會變得很簡潔,代碼行數會變少。但 C++接口唯一的不足是當前一些嵌入式開發系統可能只支持 C 語言,如果你的開發平臺支持C++,完全沒有必要再用 IplImage 和 CvMat。在新版本的 OpenCV 中,開發者依然可以使用 IplImage 和 CvMat,但是一些新增加的函數只 供了 Mat 接口。
Mat 類的定義如下所示,關鍵的屬性如下方代碼所示:
class CV_EXPORTS Mat
{
public:
//一系列函數
...
/* flag 參數中包含許多關于矩陣的信息,如:
-Mat 的標識
-數據是否連續
-深度
-通道數目
*/
int flags;
//矩陣的維數,取值應該大于或等于 2
int dims;
//矩陣的行數和列數,如果矩陣超過 2 維,這兩個變量的值都為-1
int rows, cols;
//指向數據的指針
uchar* data;
//指向引用計數的指針
//如果數據是由用戶分配的,則為 NULL
int* refcount;
//其他成員變量和成員函數
...
};
Mat屬性的理解
data:uchar類型的指針,指向Mat數據矩陣的首地址??梢岳斫鉃闃耸疽粋€房屋的門牌號;
dims:Mat矩陣的維度,若Mat是一個二維矩陣,則dims=2,三維則dims=3,大多數情況下處理的都是二維矩陣,是一個平面上的矩陣;
rows:Mat矩陣的行數??衫斫鉃榉课輧确块g行數;
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}
可見 0和1都代表8位,2和3都代表16位,4和5代表32位,6代表64位;其中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;
M.step[m-1] 總是等于 elemSize;M.step1(m-1)總是等于 channels。
補充:
step1(i):每一維元素的通道數
step[i]:每一維元素的大小,單位字節
size[i]:每一維元素的個數
elemSize():每個元素大小,單位字節
elemSize1():每個通道大小,單位字節
每一維的元素表示什么意思呢?
這里我們以空間幾何的角度來解釋,能夠更加容易理解一點。
三維矩陣,一共有三維,我們分別類比為
面:每個二維矩陣,表示第1維的元素
線:矩陣的每一行,表示第2維的元素
點:矩陣中每行的每個元素,表示第3維的元素
那么這樣子就可以解釋清楚每一維元素的含義了。
以step[i]為例
step[0]:面的大小,第1維的元素的大小,也就是二維矩陣的大小,一個二維矩陣有8行,所以
step[0] = step[1] * 8 = 480
step[1]:線的大小,第2維的元素的大小,也就是二維矩陣每一行的大小,由于每個元素大小為6,每行有10個元素,所以
step[1] = 10 * 6 = 60
step[2]:點的大小,第3維的元素的大小,這里矩陣的每個元素類型為CV_16UC3,所以
step[2] = 2 * 3 = 6
這里注意:
1.step的大小是字節
2.注意下標與維數的對應關系:下標2對應點,1對應線,0對應面
3.矩陣有幾維,step[]數組就有幾個元素,如3維,則有3個元素,step[0],step[1],step[2].分別對應面,線,點
只要記住,最后一個總是表示點,然后依次向前為線,面...
4.第2,3 點 ,對于size和step1()也一樣。
step1(i)和size[]與step[i]原理相同。
Mat對象構造函數與常用方法
常用的構造函數有:
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。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的圖像
有些 type 參數如 CV_32F未注明通道數目,這種情況下它表示單通道。
常用方法
void copyTo(); //拷貝
Mat clone(); //拷貝
int channels(); //通道,矩陣中的每一個矩陣元素擁有的值的個數
int depth(); //深度,即每一個像素的位數(bits)
bool empty() const; //判斷是否為空
uchar* ptr(int i0=0); //指針取第0行數據
void convertTo(oclMat& m, int rtype, double alpha=1, double beta=0);
//m:轉為目標數據類型的矩陣;
//rtype: 指定目標數據類型,或者是depth(通道數),如果rtype:是負值,那么目標矩陣的數據類型和源矩形的數據類型是一致的;
//alpha:基于尺度的變化值;
//beta:在尺度上的加和;
Mat類的內存管理
Mat 是一個類,由兩個數據部分組成:矩陣頭(包含矩陣尺寸,存儲方法,存儲地址等信息)和一個指向存儲所有像素值的矩陣的指針。矩陣頭的尺寸是常數值,但矩陣本身的尺寸會依圖像的不同而不同,通常比矩陣頭的尺寸大數個數量級。復制矩陣數據往往花費較多時間,因此除非有必要,不要復制大的矩陣。
為了解決矩陣數據的傳遞,OpenCV 使用了引用計數機制。其思路是讓每個Mat 對象有自己的矩陣頭信息,但多個 Mat 對象可以共享同一個矩陣數據。讓矩陣指針指向同一地址而實現這一目的。很多函數以及很多操作(如函數參數傳值)只復制矩陣頭信息,而不復制矩陣數據。
如果 Mat 類自己申請數據空間,那么該類會多申請 4 個字節,多出的 4 個字節存儲數據被引用的次數。引用次數存儲于數據空間的后面,refcount 指向這個位置,如圖所示。當計數等于 0 時,則釋放該空間。
Mat類.jpeg
關于多個矩陣對象共享同一矩陣數據,我們可以看這個例子:
Mat A(100,100, CV_8UC1);
Mat B = A;
Mat C = A(Rect(50,50,30,30));
上面代碼中有三個Mat對象,分別是A,B和C。這三者共有同一矩陣數據,其示意圖如圖:
三個矩陣頭共用共用同一矩陣數據.jpeg
部分復制:一般情況下只會復制Mat對象的頭和指針部分,不會復制數據部分。如:
Mat A = imread(filePath);
Mat B = A;
完全復制:如果想把Mat對象的頭部和數據部分一起復制,如下:
Mat F = A.clone();
Mat G;
A.copyTo(G);
四個要點:
輸出圖像的內存是自動分配的
使用OpenCV的C++接口,不需要考慮內存分配的問題
賦值操作和拷貝構造函數只會復制頭部分
使用clone()與copyTo()兩個函數實現數據完全復制
create()函數創建對象
除了在構造函數中可以創建圖像,也可以使用 Mat 類的 create()函數創建圖像。如果 create()函數指定的參數與圖像之前的參數相同,則不進行實質的內存申請操作;如果參數不同,則減少原始數據內存的索引,并重新申請內存。使用方法如下面例程所示:
Mat M(2,2, CV_8UC3);//構造函數創建圖像
M.create(3,2, CV_8UC2);//釋放內存重新創建圖像
需要注意的時,使用 create()函數無法設置圖像像素的初始值。
Matlab 風格的創建對象方法
OpenCV 2 中 供了 Matlab 風格的函數,如 zeros(),ones()和 eyes()。這種方法使得代碼非常簡潔,使用起來也非常方便。使用這些函數需要指定圖像的大小和類型,使用方法如下:
Mat Z = Mat::zeros(3, 3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl;
Mat O = Mat::ones(3, 3, CV_32F);
cout << "O = " << endl << " " << O << endl;
Mat E = Mat::eye(3, 3, CV_64F);
cout << "E = " << endl << " " << E << endl;
show.jpeg
Mat 與 IplImage 和 CvMat 的轉換
1.Mat 轉為 IplImage 和 CvMat 格式
假如你有一個以前寫的函數,函數的定義為:
void mycvOldFunc(IplImage * p, ...);
函數的參數需要 IplImage 類型的指針。Mat 轉為 IplImage,可以用簡單的等號賦值操作來進行類型轉換,這樣實現:
Mat img(Size(320, 240), CV_8UC3);
...
IplImage iplimg = img; //轉為IplImage結構
mycvOldFunc( & iplimg, ...);//對 iplimg 取地址
如果要轉為 CvMat 類型,操作類似:
CvMat cvimg = img; //轉為CvMat結構
需要特別注意的是,類型轉換后,IplImage 和 CvMat 與 Mat 共用同一矩陣數據,而 IplImage 和 CvMat 沒有引用計數功能,如果上例中的 img 中數據被釋放,iplimg 和 cvimg 也就失去了數據。因此要牢記不可將 Mat 對象 前釋放。
2.IplImage 和 CvMat 格式轉為 Mat
Mat 類有兩個構造函數,可以實現 IplImage 和 CvMat 到 Mat 的轉換。這兩個函數都有一個參數 copyData。如果 copyData 的值是 false,那么 Mat 將與 IplImage或 CvMat 共用同一矩陣數據;如果值是 true,Mat 會新申請內存,然后將 IplImage或 CvMat 的數據復制到 Mat 的數據區。
如果共用數據,Mat 也將不會使用引用計數來管理內存,需要開發者自己來管理。
Mat::Mat(const CvMat* m, bool copyData=false)
Mat::Mat(const IplImage* img, bool copyData=false)
例子代碼如下:
IplImage * iplimg = cvLoadImage("lena.jpg");
Mat im(iplimg, true);
總結
以上是生活随笔為你收集整理的创建mat二值图 matlab,OpenCV学习之路(二)——Mat对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 分组 列转行,mysql 列
- 下一篇: 找出重复最多的字符php,javascr