创建mat二值图 matlab,OpenCV学习之路(二)——Mat对象
早期的 OpenCV 中,使用 IplImage 和 CvMat 數(shù)據(jù)結(jié)構(gòu)來表示圖像。IplImage和 CvMat 都是 C 語言的結(jié)構(gòu)。使用這兩個結(jié)構(gòu)的問題是內(nèi)存需要手動管理,開發(fā)者必須清楚的知道何時需要申請內(nèi)存,何時需要釋放內(nèi)存。這個開發(fā)者帶來了一定的負(fù)擔(dān),開發(fā)者應(yīng)該將更多精力用于算法設(shè)計,因此在新版本的 OpenCV 中引入了 Mat 類。
新加入的 Mat 類能夠自動管理內(nèi)存。使用 Mat 類,你不再需要花費(fèi)大量精力在內(nèi)存管理上。而且你的代碼會變得很簡潔,代碼行數(shù)會變少。但 C++接口唯一的不足是當(dāng)前一些嵌入式開發(fā)系統(tǒng)可能只支持 C 語言,如果你的開發(fā)平臺支持C++,完全沒有必要再用 IplImage 和 CvMat。在新版本的 OpenCV 中,開發(fā)者依然可以使用 IplImage 和 CvMat,但是一些新增加的函數(shù)只 供了 Mat 接口。
Mat 類的定義如下所示,關(guān)鍵的屬性如下方代碼所示:
class CV_EXPORTS Mat
{
public:
//一系列函數(shù)
...
/* flag 參數(shù)中包含許多關(guān)于矩陣的信息,如:
-Mat 的標(biāo)識
-數(shù)據(jù)是否連續(xù)
-深度
-通道數(shù)目
*/
int flags;
//矩陣的維數(shù),取值應(yīng)該大于或等于 2
int dims;
//矩陣的行數(shù)和列數(shù),如果矩陣超過 2 維,這兩個變量的值都為-1
int rows, cols;
//指向數(shù)據(jù)的指針
uchar* data;
//指向引用計數(shù)的指針
//如果數(shù)據(jù)是由用戶分配的,則為 NULL
int* refcount;
//其他成員變量和成員函數(shù)
...
};
Mat屬性的理解
data:uchar類型的指針,指向Mat數(shù)據(jù)矩陣的首地址。可以理解為標(biāo)示一個房屋的門牌號;
dims:Mat矩陣的維度,若Mat是一個二維矩陣,則dims=2,三維則dims=3,大多數(shù)情況下處理的都是二維矩陣,是一個平面上的矩陣;
rows:Mat矩陣的行數(shù)。可理解為房屋內(nèi)房間行數(shù);
cols:Mat矩陣的列數(shù)。可理解為房屋內(nèi)房間列數(shù);
size():首先size是一個結(jié)構(gòu)體,定義了Mat矩陣內(nèi)數(shù)據(jù)的分布形式,數(shù)值上有關(guān)系式:
image.size().width==image.cols;
image.size().height==image.rows;
可以理解為房屋內(nèi)房間的整體布局,這其中包括了房間分別在行列上分布的數(shù)量信息;
channels():Mat矩陣元素?fù)碛械耐ǖ罃?shù)。例如常見的RGB彩色圖像,channels==3;而灰度圖像只有一個灰度分量信息,channels==1。可以理解為每個房間內(nèi)放有多少床位,3通道的放了3張床,單通道的放了1張床;
depth:用來度量每一個像素中每一個通道的精度,但它本身與圖像的通道數(shù)無關(guān)!depth數(shù)值越大,精度越高。在Opencv中,Mat.depth()得到的是一個0~6的數(shù)字,分別代表不同的位數(shù),對應(yīng)關(guān)系如下:
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,也就是有符號和無符號數(shù)。可以理解為房間內(nèi)每張床可以睡多少人,這個跟房間內(nèi)有多少床并無關(guān)系;
elemSize:elem是element(元素)的縮寫,表示矩陣中每一個元素的數(shù)據(jù)大小,如果Mat中的數(shù)據(jù)類型是CV_8UC1,那么elemSize==1;如果是CV_8UC3或CV_8SC3,那么elemSize==3;如果是CV_16UC3或者CV_16SC3,那么elemSize==6;即elemSize是以8位(一個字節(jié))為一個單位,乘以通道數(shù)和8位的整數(shù)倍;可以理解為整個房間可以睡多少人,這個時候就得累計上房間內(nèi)所有床位數(shù)(通道)和每張床的容納量了;
elemSize1:elemSize加上一個“1”構(gòu)成了elemSize1這個屬性,1可以認(rèn)為是元素內(nèi)1個通道的意思,這樣從命名上拆分后就很容易解釋這個屬性了:表示Mat矩陣中每一個元素單個通道的數(shù)據(jù)大小,以字節(jié)為一個單位,所以有:
eleSize1==elemSize/channels;
step:可以理解為Mat矩陣中每一行的“步長”,以字節(jié)為基本單位,每一行中所有元素的字節(jié)總量,是累計了一行中所有元素、所有通道、所有通道的elemSize1之后的值;
step1():以字節(jié)為基本單位,Mat矩陣中每一個像素的大小,累計了所有通道的elemSize1之后的值,所以有:
step1==step/elemSize1;
M.step[m-1] 總是等于 elemSize;M.step1(m-1)總是等于 channels。
補(bǔ)充:
step1(i):每一維元素的通道數(shù)
step[i]:每一維元素的大小,單位字節(jié)
size[i]:每一維元素的個數(shù)
elemSize():每個元素大小,單位字節(jié)
elemSize1():每個通道大小,單位字節(jié)
每一維的元素表示什么意思呢?
這里我們以空間幾何的角度來解釋,能夠更加容易理解一點。
三維矩陣,一共有三維,我們分別類比為
面:每個二維矩陣,表示第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的大小是字節(jié)
2.注意下標(biāo)與維數(shù)的對應(yīng)關(guān)系:下標(biāo)2對應(yīng)點,1對應(yīng)線,0對應(yīng)面
3.矩陣有幾維,step[]數(shù)組就有幾個元素,如3維,則有3個元素,step[0],step[1],step[2].分別對應(yīng)面,線,點
只要記住,最后一個總是表示點,然后依次向前為線,面...
4.第2,3 點 ,對于size和step1()也一樣。
step1(i)和size[]與step[i]原理相同。
Mat對象構(gòu)造函數(shù)與常用方法
常用的構(gòu)造函數(shù)有:
Mat::Mat()
無參數(shù)構(gòu)造方法;
Mat::Mat(int rows, int cols, int type)
創(chuàng)建行數(shù)為 rows,列數(shù)為 col,類型為 type 的圖像;
Mat::Mat(Size size, int type)
創(chuàng)建大小為 size,類型為 type 的圖像;
Mat::Mat(int rows, int cols, int type, const Scalar& s)
創(chuàng)建行數(shù)為 rows,列數(shù)為 col,類型為 type 的圖像,并將所有元素初始化為值 s;
Mat::Mat(Size size, int type, const Scalar& s)
創(chuàng)建大小為 size,類型為 type 的圖像,并將所有元素初始化為值 s;
Mat::Mat(const Mat& m)
將 m 賦值給新創(chuàng)建的對象,此處不會對圖像數(shù)據(jù)進(jìn)行復(fù)制,m 和新對象共用圖像數(shù)據(jù);
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
創(chuàng)建行數(shù)為 rows,列數(shù)為 col,類型為 type 的圖像,此構(gòu)造函數(shù)不創(chuàng)建圖像數(shù)據(jù)所需內(nèi)存,而是直接使用 data 所指內(nèi)存,圖像的行步長由 step指定。
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
創(chuàng)建大小為 size,類型為 type 的圖像,此構(gòu)造函數(shù)不創(chuàng)建圖像數(shù)據(jù)所需內(nèi)存,而是直接使用 data 所指內(nèi)存,圖像的行步長由 step 指定。
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
創(chuàng)建的新圖像為 m 的一部分,具體的范圍由 rowRange 和 colRange 指定,此構(gòu)造函數(shù)也不進(jìn)行圖像數(shù)據(jù)的復(fù)制操作,新圖像與 m 共用圖像數(shù)據(jù);
Mat::Mat(const Mat& m, const Rect& roi)
創(chuàng)建的新圖像為 m 的一部分,具體的范圍 roi 指定,此構(gòu)造函數(shù)也不進(jìn)行圖像數(shù)據(jù)的復(fù)制操作,新圖像與 m 共用圖像數(shù)據(jù)。
這些構(gòu)造函數(shù)中,很多都涉及到類型 type。type 可以是 CV_8UC1,CV_16SC1,...,CV_64FC4 等。里面的 8U 表示 8 位無符號整數(shù),16S 表示 16 位有符號整數(shù),64F表示 64 位浮點數(shù)(即 double 類型);C 后面的數(shù)表示通道數(shù),例如 C1 表示一個通道的圖像,C4 表示 4 個通道的圖像,以此類推。
如果你需要更多的通道數(shù),需要用宏 CV_8UC(n),例如:
Mat M(3,2, CV_8UC(5));//創(chuàng)建行數(shù)為3,列數(shù)為2,通道數(shù)為5的圖像
有些 type 參數(shù)如 CV_32F未注明通道數(shù)目,這種情況下它表示單通道。
常用方法
void copyTo(); //拷貝
Mat clone(); //拷貝
int channels(); //通道,矩陣中的每一個矩陣元素?fù)碛械闹档膫€數(shù)
int depth(); //深度,即每一個像素的位數(shù)(bits)
bool empty() const; //判斷是否為空
uchar* ptr(int i0=0); //指針取第0行數(shù)據(jù)
void convertTo(oclMat& m, int rtype, double alpha=1, double beta=0);
//m:轉(zhuǎn)為目標(biāo)數(shù)據(jù)類型的矩陣;
//rtype: 指定目標(biāo)數(shù)據(jù)類型,或者是depth(通道數(shù)),如果rtype:是負(fù)值,那么目標(biāo)矩陣的數(shù)據(jù)類型和源矩形的數(shù)據(jù)類型是一致的;
//alpha:基于尺度的變化值;
//beta:在尺度上的加和;
Mat類的內(nèi)存管理
Mat 是一個類,由兩個數(shù)據(jù)部分組成:矩陣頭(包含矩陣尺寸,存儲方法,存儲地址等信息)和一個指向存儲所有像素值的矩陣的指針。矩陣頭的尺寸是常數(shù)值,但矩陣本身的尺寸會依圖像的不同而不同,通常比矩陣頭的尺寸大數(shù)個數(shù)量級。復(fù)制矩陣數(shù)據(jù)往往花費(fèi)較多時間,因此除非有必要,不要復(fù)制大的矩陣。
為了解決矩陣數(shù)據(jù)的傳遞,OpenCV 使用了引用計數(shù)機(jī)制。其思路是讓每個Mat 對象有自己的矩陣頭信息,但多個 Mat 對象可以共享同一個矩陣數(shù)據(jù)。讓矩陣指針指向同一地址而實現(xiàn)這一目的。很多函數(shù)以及很多操作(如函數(shù)參數(shù)傳值)只復(fù)制矩陣頭信息,而不復(fù)制矩陣數(shù)據(jù)。
如果 Mat 類自己申請數(shù)據(jù)空間,那么該類會多申請 4 個字節(jié),多出的 4 個字節(jié)存儲數(shù)據(jù)被引用的次數(shù)。引用次數(shù)存儲于數(shù)據(jù)空間的后面,refcount 指向這個位置,如圖所示。當(dāng)計數(shù)等于 0 時,則釋放該空間。
Mat類.jpeg
關(guān)于多個矩陣對象共享同一矩陣數(shù)據(jù),我們可以看這個例子:
Mat A(100,100, CV_8UC1);
Mat B = A;
Mat C = A(Rect(50,50,30,30));
上面代碼中有三個Mat對象,分別是A,B和C。這三者共有同一矩陣數(shù)據(jù),其示意圖如圖:
三個矩陣頭共用共用同一矩陣數(shù)據(jù).jpeg
部分復(fù)制:一般情況下只會復(fù)制Mat對象的頭和指針部分,不會復(fù)制數(shù)據(jù)部分。如:
Mat A = imread(filePath);
Mat B = A;
完全復(fù)制:如果想把Mat對象的頭部和數(shù)據(jù)部分一起復(fù)制,如下:
Mat F = A.clone();
Mat G;
A.copyTo(G);
四個要點:
輸出圖像的內(nèi)存是自動分配的
使用OpenCV的C++接口,不需要考慮內(nèi)存分配的問題
賦值操作和拷貝構(gòu)造函數(shù)只會復(fù)制頭部分
使用clone()與copyTo()兩個函數(shù)實現(xiàn)數(shù)據(jù)完全復(fù)制
create()函數(shù)創(chuàng)建對象
除了在構(gòu)造函數(shù)中可以創(chuàng)建圖像,也可以使用 Mat 類的 create()函數(shù)創(chuàng)建圖像。如果 create()函數(shù)指定的參數(shù)與圖像之前的參數(shù)相同,則不進(jìn)行實質(zhì)的內(nèi)存申請操作;如果參數(shù)不同,則減少原始數(shù)據(jù)內(nèi)存的索引,并重新申請內(nèi)存。使用方法如下面例程所示:
Mat M(2,2, CV_8UC3);//構(gòu)造函數(shù)創(chuàng)建圖像
M.create(3,2, CV_8UC2);//釋放內(nèi)存重新創(chuàng)建圖像
需要注意的時,使用 create()函數(shù)無法設(shè)置圖像像素的初始值。
Matlab 風(fēng)格的創(chuàng)建對象方法
OpenCV 2 中 供了 Matlab 風(fēng)格的函數(shù),如 zeros(),ones()和 eyes()。這種方法使得代碼非常簡潔,使用起來也非常方便。使用這些函數(shù)需要指定圖像的大小和類型,使用方法如下:
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 的轉(zhuǎn)換
1.Mat 轉(zhuǎn)為 IplImage 和 CvMat 格式
假如你有一個以前寫的函數(shù),函數(shù)的定義為:
void mycvOldFunc(IplImage * p, ...);
函數(shù)的參數(shù)需要 IplImage 類型的指針。Mat 轉(zhuǎn)為 IplImage,可以用簡單的等號賦值操作來進(jìn)行類型轉(zhuǎn)換,這樣實現(xiàn):
Mat img(Size(320, 240), CV_8UC3);
...
IplImage iplimg = img; //轉(zhuǎn)為IplImage結(jié)構(gòu)
mycvOldFunc( & iplimg, ...);//對 iplimg 取地址
如果要轉(zhuǎn)為 CvMat 類型,操作類似:
CvMat cvimg = img; //轉(zhuǎn)為CvMat結(jié)構(gòu)
需要特別注意的是,類型轉(zhuǎn)換后,IplImage 和 CvMat 與 Mat 共用同一矩陣數(shù)據(jù),而 IplImage 和 CvMat 沒有引用計數(shù)功能,如果上例中的 img 中數(shù)據(jù)被釋放,iplimg 和 cvimg 也就失去了數(shù)據(jù)。因此要牢記不可將 Mat 對象 前釋放。
2.IplImage 和 CvMat 格式轉(zhuǎn)為 Mat
Mat 類有兩個構(gòu)造函數(shù),可以實現(xiàn) IplImage 和 CvMat 到 Mat 的轉(zhuǎn)換。這兩個函數(shù)都有一個參數(shù) copyData。如果 copyData 的值是 false,那么 Mat 將與 IplImage或 CvMat 共用同一矩陣數(shù)據(jù);如果值是 true,Mat 會新申請內(nèi)存,然后將 IplImage或 CvMat 的數(shù)據(jù)復(fù)制到 Mat 的數(shù)據(jù)區(qū)。
如果共用數(shù)據(jù),Mat 也將不會使用引用計數(shù)來管理內(nèi)存,需要開發(fā)者自己來管理。
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);
總結(jié)
以上是生活随笔為你收集整理的创建mat二值图 matlab,OpenCV学习之路(二)——Mat对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 分组 列转行,mysql 列
- 下一篇: matlab usewhitebg,我有