OpenCV 一 深入理解Mat

2021-09-25 17:30:18 字數 4000 閱讀 2906

1, 前世與今生

opencv 自 2001 年出現以來。在那些日子裡庫是圍繞c介面構建的。在那些日子裡,他們使用名為iplimage c 的結構在記憶體中儲存影象[1]。而c結構最大的問題是要程式設計師自己分配和管理記憶體,當**量大了,管理成本就很高,而導致不能著眼於真正要解決的實際問題。

隨c++的出現,引入類的概念,於是opencv2.0版本開始引入c++介面,不需要手動記憶體管理了,使用起來也更加方便了。這個引入的c++結構就是偉大的影象容器類mat。

2,廬山真面目

是時候上這副著名的圖了,直接祭出來。

從這張圖中,我們明顯地可以得出這樣乙個事實:人類看到的是影象,而計算機看到的是這種數字矩陣。計算機通過矩陣來認識一張並儲存它,也就是說mat的本質其實就是矩陣/多維陣列。

再來看看c++中mat的詳細定義,追根溯源,窺其原始碼:

class cv_exports mat

這裡只列出了一些主要的成員變數,省略了函式部分,並且分為三個部分[2]:

第<1>部分是與矩陣相關的一些資訊,flags 儲存了矩陣的標識、是否連續、深度、通道數等資訊,dims 代表矩陣的維數,rows 和 cols 代表矩陣的行數與列數,data 是指向儲存矩陣資料的指標。

第<2>部分是與感興趣區域有關的資訊,感興趣區域即 roi(region of interset)。

第<3>部分是乙個指標,指向乙個 umatdata 型別,是跟引用計數相關的。(這是很容易理解的:由一張儲存成的矩陣通常不小,例如一張 1024 * 768 的三通道彩色圖,儲存的矩陣就佔了 1024 * 768 * 3 * 1 = 2359296 byte = 2304 kb。而 opencv 作為計算機視覺庫,它就是負責處理一大堆的這些影象資訊,所以經常拷貝大的影象,開銷是非常大的。自然地,就採用了引用計數。)

其實綜合一下,mat結構主要包含兩個部分:矩陣頭,指向矩陣資料的指標

矩陣頭包含:矩陣尺寸、儲存方式、儲存位址、引用計數等,每個mat,都有恆定大小的矩陣頭。指標所指向的矩陣資料則因實際儲存的影象不同而不同。

這裡插一段,有指標就需要**它的拷貝函式是深拷貝還是淺拷貝。假設有了乙個mat物件src, 現在要將它拷貝給另乙個空的mat物件dst。則如下兩種方法是淺拷貝:

//1, 賦值運算子

dst = src;

//2, 建構函式

mat dst(src);

這種淺拷貝,複製矩陣頭,以及指向矩陣資料的指標。但真實的矩陣資料仍然只有乙份,也就是說改掉dst的某個畫素值,src的對應畫素值也會被更改。當然下面兩種方法也可以實現深拷貝:

//1, clone()方法

dst = src.clone();

//2, copyto()方法

src.copyto(dst);

深拷貝,包括拷貝乙份真實的矩陣資料,dst,src分別更改時互不干擾。以上兩種方法效果都是一樣的。

3,武功秘籍

明白了mat是什麼,更要明白它該如何用,發揮它最大的實力,也就是學會它的方法。

3.1 構造方法

mat ()

mat (int rows, int cols, int type)

mat (size size, int type)

mat (int rows, int cols, int type, const scalar &s)

mat (size size, int type, const scalar &s)

mat (int ndims, const int *sizes, int type)

mat (int ndims, const int *sizes, int type, const scalar &s)

mat (const mat &m)

比如構造乙個mat物件:mat m(3, 3, cv_8uc3, scalar(0, 255, 255)); 表示建立乙個3x3,三通道無符號char型別的矩陣,每個通道佔8位,每個畫素有三個通道且值固定為(0, 255, 255)。

還能通過create()方法構造mat,但該方法不能提供畫素值(也就是沒有scalar引數),通常建立乙個與src同大小同型別的mat: mat dst = src.create(src.size(), src.type());

除了自身構造,還可以拷貝構造,分深淺拷貝,之前說過了這裡就不多費口舌了。

3.2 convertto()方法

void convertto( outputarray m, int rtype, double alpha=1, double beta=0 ) const;

可以在縮放或者不縮放的情況下改變資料型別。dst表示目標矩陣;type是需要輸出的型別,若是負值(常用-1)則表示輸入輸出矩陣型別相同;alpha是比例因子;beta是將輸入陣列元素按比例縮放後新增的值[3]。

比如經常會在svm**現src.convertto(src, cv_32fc1); 因為svm的訓練資料集必須是單通道32位有符號char型別資料。

3.3 reshape()方法

mat reshape(int cn, int rows=0) const;

mat reshape(int cn, int newndims, const int* newsz) const;

其中cn表示通道數,rows表示行數。若灰度影象src呼叫 src.reshape(1, 1);可以實現將灰度圖扁平化,變成乙個「虛vector」(有點類似python的numpy裡面的arraybyte()方法)。這裡還找到許多reshape的例子,例子在[4]。

3.4 at()方法

這個方法有很多過載,定義時用了泛型,就不放上來了。簡單來說at()方法可以用於獲取影象矩陣某點的值或者改變它。

對於單通道影象的使用方法:src.at< t >(i, j); 其中t表示泛型,可以是uchar, float, double等

對於三通道影象的使用方法:src.at< t >(i, j)[0] = 255; 之類, 表示修改(i, j)畫素第0通道的值為255。

3.5 定義小矩陣

可以用mat模板的子類mat_< t >來定義小矩陣。它的計算比較省時間,效率高。通常用於卷積的核就被宣告為mat_< t>。比如mat kernel = (mat_< float >(3, 3)<< 0, -1, 0, -1, 5, -1, 0, -1, 0);

(注意:mat_< t >取畫素值或改變它不需要用at()方法了,比如 rgb影象 src(i, j)[0] = 255 )

3.6 vector通過mat

vector可以通過mat, 就是說,vector可以傳入mat的建構函式,構造成乙個mat。例如:

vector< float > data;

mat m = mat(data);

有時候,想直接輸出乙個vector, 但cout《沒有對vector過載,於是,,可以cout當然,還有很多其他神奇的方法,限於篇幅,大家自己去探索了。

參考:[1] 《opencv mat類詳解和用法》

[2] 《opencv 筆記(二):mat 類初探》

[3] 《opencv中convertto的用法》

[4] 《opencv3學習:reshape函式》

深入理解REST(一)

1.什麼是 rest rest是representational state transfer的縮寫,於 r.fielding 的一篇博士 architectural styles and the design of network based software architectures rest...

深入理解DataAdapter(一)

ado.net有兩個核心元件 基於連線的data provider元件以及基於非連線的dataset元件。基於連線的data provider元件常用於實時地從資料庫中檢索資料。而基於非連線的dataset,似乎與資料庫沒有直接聯絡,僅僅用於在本地記憶體中儲存data provider提供的資料表或...

js深入理解 一

1if a b 兩者等價 a b alert hello word 2.給eval取別名var a 111 var b eval var c b a alert c 輸出 111 3.刪除元素 不能刪除 var語句宣告的變數 var o delete o.x alert o.x 輸出 undefin...