OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算
數字圖像中的每個點都稱為像素(對于圖像元素),并且每個像素可以存儲一個或多個值,這取決于它是否是僅存儲一個值的黑白圖像(也稱為二進制圖像,比如只存儲0或1),還是存儲兩個值的灰度圖像,或者是存儲三個值的彩色圖像。這些值通常在整數0~255,但也可以使用其他范圍,比如在高動態范圍成像(high dynamic range imaging,簡稱HDRI)或熱圖像領域中的浮點數0~1。
圖像是以矩陣格式存儲的,其中的每個像素都有一個位置,并且可以通過列和行的編號來引用。 OpenCV 用 Mat 類來達到這個目的。在灰度圖像中,使用單個矩陣,如下圖所示。
在下圖所示的彩色圖像中,使用了一個寬度×高度×顏色通道數的矩陣。
但 Mat 類不僅僅用于存儲圖像,它還能存儲任何類型和不同大小的矩陣。你可以將其用作代數矩陣并用它執行運算。在內存中,矩陣被保存為按列和行排序的數組或值序列。表2-1顯示 BGR 圖像格式的像素序列。
關于 Mat 類, 首先我們要知道的是:
- 不必再手動為其幵辟空間。
- 不必再在不需要時立即將空M釋放。
這里指的是手動開辟空間并非必須,但它依舊是存在的—大多數 OpenCV 函數仍會手動地為輸出數據幵辟空間。當傳遞一個已經存在的 Mat 對象時, 開辟好的矩陣空間會被重用。也就是說, 我們每次都使用大小正好的內存來完成任務。
1. Mat 結構
Mat 是一個類, 由兩個數據部分組成:矩陣頭(包含矩陣尺寸、存儲方法、存儲地址等信息) 和一個指向存儲所有像素值的矩陣(根據所選存儲方法的不同,矩陣可以是不同的維數)的指針。
矩陣頭的尺寸是常數值, 但矩陣本身的尺寸會依圖像的不同而不同,通常比矩陣頭的尺寸大數個數量級。因此,
當在程序中傳遞圖像并創建副本時,大的開銷是由矩陣造成的, 而不是信息頭。
為了解決此問題, OpenCV 使用了引用計數機制。其思路是讓每個 Mat 對象有自己的信息頭,但共享同一個矩陣。這通過讓矩陣指針指向同一地址而實現。而拷貝構造函數則只復制信息頭和矩陣指針, 而不復制矩陣。
Mat A, C; / / 僅創建信息頭部分
A = imread("1.jpg") / / 這里為矩陣開辟內存
Mat B(A); / /使用拷貝構造函教
C = A; / /賦值運算符
以上代碼中的所有 Mat 對象最終都指向同一個也是唯一一個數據矩陣。雖然它們的信息頭不同,但通過任何一個對象所做的改變也會影響其他對象。
我們可以創建只引用部分數據的信息頭。比如想要創建一個感興趣區域(ROI)只需要創建包含邊界信息的信息頭:
Mat D(A, Rect(10, 10, 100, 100)) ; / / 使用矩形界定
Mat E = A(Range:all() , Range(1,3)) ; / / 用行和列來界定
如果想復制矩陣本身( 不只是信息頭和矩陣指針),這時可以使用函數 clone() 或者 copyTo() 。
Mat F = A.clone() ;
Mat G;
A.copyTo(G) ;
現在改變 F 或者 G 就不會影響 Mat 信息頭所指向的矩陣。
總結如下:
OpenCV函數中輸出圖像的內存分配是自動完成的(如果不特別指定的話);- 使用
OpenCV的C++接口時不需要考慮內存釋放問題; - 賦值運算符和拷貝構造函數(構造函數)只復制信總頭;
- 使用函數
clone()或者copyTo()來復制一幅圖像的矩陣;
2. 像素值的存儲方法
存儲像素值需要指定顏色空間和數據類型。
- 顏色空間是指針對一個給定的顏色,如何組合顏色元素以對其編碼。最簡單的顏色空間要屬灰度級空間, 只處理黑色和白色, 對它們進行組合便可以產生不同程度的灰色。
對于彩色方式則有更多種類的顏色空間, 但不論哪種方式都是把顏色分成三個或者四個基元素,通過組合基元素可以產生所有的顏色。 RGB 顏色空間是最常用的一種顏色空間,。它的基色是紅色、綠色和藍色,有時為了表示透明顏色也會加入第四個元素alpha ( A)。
每個組成元素都有自己的定義域, 而定義域収決于其數據類型,如何存儲一個元素決定了我們在其定義域上能夠控制的精度。最小的數據類型是 char ,占一個字節或者 8 位,可以是有符號型(0 到255 之間)或無符號型( -127 到+127之間)。盡管使用三個 char 型元素已經可以表示 1600 萬種可能的顏色(使用 RGB 顏色空間),但若使用 float ( 4 字節,32 位)或 double ( 8 字節,64 位)則能給出更加精細的顏色分辨能力。但同時也要切記, 增加元素的尺寸也會增加圖像所占的內存空間。
3. 創建 Mat 對象方法
我們可以通過 Mat 的運算符 << 來查看其值。但要記住, Mat 的運算符 << 只對二維矩陣有效。 Mat 不但是一個非常有用的圖像容器類,同時也是一個通用的矩陣類,我們也可以用它來創建和操作多維矩陣。
創建一個 Mat 對象有多種方法, 列舉如下:
3.1 使用 Mat 構造函數
最常用的方法是直接使用 Mat() 構造函數,示例如下:
Mat M(3, 2, CV_8UC3, Scalar(0, 0, 255));cout << "M is "<< endl << M << endl;
輸出結果:
M is
[ 0, 0, 255, 0, 0, 255;0, 0, 255, 0, 0, 255;0, 0, 255, 0, 0, 255]
對于二維多通道圖像, 首先要定義其尺寸,即行數和列數。然后,需要指定存儲元素的數據類型以及每個矩陣點的通道數。為此,依據下面的規則有多種定義:
CV_ [ The number of bits per item] [ Signed or Unsigned] [ Type Prefix ] C [ The
channel number ]
即:
cv_[位數][帶符號與否][類型前綴]c[通道數]
比如 CV_8UC3 表示使用 8 位的 unsigned char 型,每個像素由三個元素組成三通道。而預先定義的通道數可以多達四個。另外, Scalar 是個 short 型的向量。
受支持的類型取決于要存儲的數字類型和通道數,最常見的類型如下:
CV_8UC1;
CV_8UC3;
CV_8UC4;CV_32FC1;
CV_32FC3;
CV_32FC4;
3.2 通過C++構造函數進行初始化
示例代碼:
int sz[3] = {2,1,3};Mat M(3, sz, CV_8UC3, Scalar::all(0));
上面的例子演示了如何創建一個超過兩維的矩陣:指定維數,然后傳遞一個指向一個數組的指針,這個數組包含每個維度的尺寸。
3.3 利用 create() 函數
利用 Mat 類中的 Create() 成員函數進行 Mat 類的初始化操作,示范代碼如下。
Mat M;M.create(4, 3, CV_8UC(2));cout << "M is "<< endl << M << endl;
輸出結果:
M is
[ 0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0;0, 0, 0, 0, 0, 0]
需要注意的是,此創建方法不能為矩陣設初值,只是在改變尺寸時重新為矩陣數據開辟內存而已。
3.4 使用 zeros()、ones()、eyes() 初始化
示例代碼:
Mat E = Mat::eye(3, 3, CV_64F);cout << "E is "<< endl << E << endl;Mat O = Mat::ones(3, 2, CV_32F);cout << "O is "<< endl << O << endl;Mat Z = Mat::zeros(3, 2, CV_8UC1);cout << "Z is "<< endl << Z << endl;
輸出結果:
E is
[1, 0, 0;0, 1, 0;0, 0, 1]O is
[1, 1;1, 1;1, 1]Z is
[ 0, 0;0, 0;0, 0]
3.5 對小矩陣使用逗號分隔初始化
示例代碼
Mat C = (Mat_<double> (3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0 );cout << "C is "<< endl << C << endl;
輸出結果:
C is
[0, -1, 0;-1, 5, -1;0, -1, 0]
3.6 為已存在的對象創建新信息頭
使用成員函數 clone() 或者 copyTo() 為一個已存在的 Mat 對象創建一個新的信息頭,示范代碼如下。
Mat C = (Mat_<double> (3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0 );Mat RowClone = C.row (1).clone() ;cout << "RowClone is "<< endl << RowClone << endl;
輸出結果:
RowClone is
[-1, 5, -1]
4. Mat 對象運算
OpenCV 的 Mat 類能夠執行所有的矩陣運算。我們可以用 + 和 - 運算符來加上或減去兩個相同大小的矩陣,如以下代碼塊所示:
Mat a = Mat::eye(Size(3, 2), CV_32F);Mat b = Mat::ones(Size(3, 2), CV_32F);Mat c = a + b;Mat d = a - b;
運算結果如下圖:
我們可以用 * 運算符乘以一個標量,或者用 mul 函數乘以矩陣的每個元素,也可以用 * 運算符執行矩陣乘法:
Mat a = Mat::eye(2, 3, CV_32F);Mat b = Mat::ones(3, 2, CV_32F);// Mat a2 = a * 2;// Mat d = (a + 1).mul(b + 3);// Mat ab = a * b;cout << a * 2 << endl;cout << (a + 1).mul(b + 3) << endl;cout << a * b << endl;// cout << a + b << endl;// cout << a - b << endl;
理論上會有如下結果,但是調試發現報錯,待繼續分析。
其它運算請參考:
https://docs.opencv.org/2.4/modules/core/doc/core.html
總結
以上是生活随笔為你收集整理的OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022-2028年中国电子灌封胶行业市
- 下一篇: 2022-2028年中国ABS管行业市场