1. 程式人生 > >【Opencv】Opencv之Mat

【Opencv】Opencv之Mat

Mat

Mat的簡單使用

從實際出發,先看看他幹啥的,怎麼用。
一般我們用到Mat有兩個重要的用途:
1.儲存影象(其實影象可以看成一個高行寬列的一個矩陣)
2.儲存矩陣

先來看看Mat用於影象和矩陣的最基本操作,讀取一副影象,修改影象中某些畫素的值,最後顯示並儲存,建立矩陣並進行矩陣運算
(以下例子採用最簡單的方法,基本使用預設引數)

簡潔版(如果你只想使用,而不想知道為什麼、怎麼改進)

#include <iostream> //輸入輸出流標頭檔案
#include <opencv2/opencv.hpp>  //OpenCV標頭檔案
using
namespace cv;//名稱空間 int main() { /////////////////////////////////////////////////////////////////////////////////////////////////////// ////1.Mat用於影象的讀取、操作和儲存 /////////////////////////////////////////////////////////////////////////////////////////////////////// cv::Mat srcImage = cv::imread("D:/MyImage.jpg"); //讀取 cv::imshow("srcImage", srcImage); //顯示 cv::Mat dstImage = srcImage.clone(); //賦值 for (size_t i = 0; i < dstImage.rows; i++) //對元素進行操作 { for (size_t j = 0; j < dstImage.cols; j++) { //直觀、安全、不會有i,j溢位的危險,讀取速度慢
dstImage.at<cv::Vec3b>(i, j)[0] *= 0.5; dstImage.at<cv::Vec3b>(i, j)[1] *= 0.5; dstImage.at<cv::Vec3b>(i, j)[2] = 255; } } cv::imshow("dstImage", dstImage); //顯示結果圖 cv::waitKey(1); //延遲1ms,等待顯示 若之後沒有waitKey則顯示視窗將在1ms後關閉 cv::imwrite("D:/dstImage.jpg", dstImage); //儲存影象 /////////////////////////////////////////////////////////////////////////////////////////////////////// ////2.Mat用於矩陣的應用,
/////////////////////////////////////////////////////////////////////////////////////////////////////// //初始化 cv::Mat rotationMatrix = (cv::Mat_<double>(3, 3) << 1.0, 2.0, 3.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); cv::Mat A = cv::Mat::zeros(3, 3, CV_64F); //全零矩陣 cv::Mat B = cv::Mat::ones(3, 3, CV_64F); //全一矩陣 cv::Mat C = cv::Mat::eye(3, 3, CV_64F); //單位矩陣 A = B + C; //加法 A = B - C; //減法 A = B * C; //矩陣乘法 必須滿足矩陣相乘的行列數對應規則 A = 5 * B; //標量乘法 每個元素擴大5倍 A = B.t(); //B轉置 A = B.inv(); //B逆矩陣 //以上是最基本的操作,矩陣運算遠遠不是這幾種,Mat包含了幾乎所有的操作,用到的時候再查吧 std::cout<<"A:"<<A<<std::endl; cv::waitKey(0); //當引數為0時 一直等待,為了顯示視窗一直顯示 return 1; }

進階版(如果你想採用最適合的方法)

//輸入輸出流標頭檔案
#include <iostream> 
//標頭檔案  opencv.hpp中包含基本的標頭檔案 (我們採用的OpenCV版本是 2.4.13.6)
#include <opencv2/opencv.hpp>  

//名稱空間,指明在cv下的所有類名、引數名、函式名等能夠在不加 cv::下就能夠使用
using namespace cv;

int main() 
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	////1.Mat用於影象的讀取、操作和儲存
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	//建立一個Mat型別物件 image (以下出現的"cv::",如果添加了 using namespace cv;就可以省略)
	cv::Mat srcImage;
	
	//通過絕對路徑讀取一副BGR彩色影象,引數1是影象的絕對路徑(注意:路徑可採用"\\"或"/" 不能使用windows資料夾路徑的"\");
	srcImage = cv::imread("D:/MyImage.jpg");
	
	//先顯示image這個影象 引數1是顯示視窗名稱,引數2是Mat型別的物件;
	cv::imshow("srcImage", srcImage);
	
	//建立一個Mat型別物件 dstImage,
	//且分配給他一個srcImage.rows行,srcImage.cols列的空間,
	//資料型別是CV_8UC3,即3通道(C3)8bit的無符號整型(8U,uchar)
	//且將所有元素內所有通道值置0,Scalar::all(0),這裡修改"0"位置的值可得到你想要的值得矩陣
	//你也可以使用zeros函式達到全賦值為0效果;
	//cv::Mat dstImage = cv::Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);
	//當然,因為我們之後要將srcImage給dstImage 所以此處直接使用 cv::Mat dstImage; 即可。
	cv::Mat dstImage(srcImage.rows,srcImage.cols,CV_8UC3,cv::Scalar::all(0));
	
	//將一個Mat A賦值給另一個Mat B,有四種方法
	//1.建構函式法         Mat A(B);
	//2.過載運算子法       A = B;
	//3.複製法            A.copyTo(B);
	//4.克隆法            B=A.clone();

	//這裡需要知道一個Mat類的概念:
	//Mat 是一個類,由兩個資料部分組成:矩陣頭(包含矩陣尺寸,儲存方法,儲存地址等資訊)
	//和一個指向儲存所有畫素值的矩陣(根據所選儲存方法的不同矩陣可以是不同的維數)的指標。

	//方法1、2是淺拷貝(時間短,不安全),只拷貝矩陣頭,不拷貝資料部分,A和B共用一塊資料,A對元素的操作會影響B ;
	//方法3、4是深拷貝(時間長,相對安全),拷貝矩陣的所有資料,包括矩陣頭,區別在於clone()會給目標矩陣重新分配新地址,
	//而copyTo()不會,copyTo()只是修改目標矩陣內的元素的值與當前矩陣值相同
	dstImage = srcImage.clone();
	
	//兩層迴圈訪問所有元素通道值,並將其中藍色通道(B)減小一半,綠通道(G)值減小一半,紅色通道(R)置255
	for (size_t i = 0; i < dstImage.rows; i++)
	{//外層迴圈行數
		for (size_t j = 0; j < dstImage.cols; j++)
		{//內層迴圈列數
		
			////採用這種直觀的方式,這種方法更安全不會有i,j溢位的危險,但是讀取速度慢
			////例如 影象為640x480解析度,i,j取482、642改方法將會報錯,但使用prt方法則一樣會計算,不報錯。
			//dstImage.at<cv::Vec3b>(i, j)[0] *= 0.5;
			//dstImage.at<cv::Vec3b>(i, j)[1] *= 0.5;
			//dstImage.at<cv::Vec3b>(i, j)[2] = 255;
			
			//ptr OpenCV中使用的智慧指標,<>內是影象元素的型別 Vec3b 是一個包含三個uchar型別元素的一維陣列
			//(i)[j][0]分別代表資料的行號、列號、和通道號,如果是單通道則(i)[j]即可,
			//由於opencv預設讀取彩色影象是BGR,則0為藍色、1為綠、2為紅
			//uchar 能儲存0~255的值 都為零則為黑色 都為255則為白色
			dstImage.ptr<cv::Vec3b>(i)[j][0] *= 0.5;
			dstImage.ptr<cv::Vec3b>(i)[j][1] *= 0.5;
			dstImage.ptr<cv::Vec3b>(i)[j][2] = 255;
		}
	}
	
	//建立一個名為dstImage的視窗用來顯示影象,如果顯示結束可通cv::destroyWindow("dstImage");進行指定銷燬,
	//也可通過cv::destroyAllWindows();銷燬存在的所有視窗。
	cv::namedWindow("dstImage");
	
	//顯示結果圖到名為dstImage的視窗中
	cv::imshow("dstImage", dstImage);

	//儲存影象到某一地址,引數1.要儲存的絕對路徑和檔名,引數2.要儲存的Mat影象
	cv::imwrite("D:/dstImage.jpg", dstImage);

	//延遲1ms秒時間等待鍵盤輸入,如果引數為0則一直等待。在imshow之後,
	//如果沒有waitKey語句則不會顯示影象,若之後沒有waitKey()則顯示視窗將在1ms後關閉。
	cv::waitKey(1);	

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	////2.Mat用於矩陣的應用,
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	//建立一個矩陣
	cv::Mat rotationMatrix;
	
	//給矩陣賦值,矩陣行列很小時,用這種方法直觀、方便
	rotationMatrix = ( cv::Mat_<double>(3, 3) <<
						1.0, 2.0, 3.0,
						0.0, 1.0, 0.0,
						0.0, 0.0, 1.0);
						
	//如果是全零矩陣,可以如下進行初始化 當然 行、列和資料型別自己選擇
	cv::Mat A = cv::Mat::zeros(3, 3, CV_64F);
	//如果是全一矩陣,可以如下進行初始化 當然 行、列和資料型別自己選擇
	cv::Mat B = cv::Mat::ones(3, 3, CV_64F);
	//如果是單位矩陣,可以如下進行初始化 當然 行、列和資料型別自己選擇
	cv::Mat C = cv::Mat::eye(3, 3, CV_64F);

	A = B + C; //加法
	A = B - C; //減法
	A = B * C; //矩陣乘法  必須滿足矩陣相乘的行列數對應規則
	A = 5 * B; //標量乘法 每個元素擴大5倍
	A = B.t(); //B轉置
	A = B.inv(); //B逆矩陣

	//以上是最基本的操作,矩陣運算遠遠不是這幾種,Mat包含了幾乎所有的操作,用到的時候再查吧
	
	//如果包含了c++輸入輸出流的標頭檔案 #include <iostream> 那麼Mat可輸出至螢幕通過
	std::cout<<" A is : "<< A <<std::endl;
	
	//一直等待,以顯示影象視窗
	cv::waitKey(0);
	cv::destroyAllWindows();	
	return 1;
}

Mat的結構

OpenCV裡資料的基本儲存型別,意思是矩陣(Matrix)。因為數字影象可以看成矩陣,所以Mat不僅可以儲存矩陣,更重要的他是影象的載體。一個Mat分為,Mat頭和Mat資料兩部分。Mat頭部分大小是固定的,包含矩陣的大小,儲存的方式,矩陣儲存的地址等等,資料部分是一個指向矩陣包含畫素值的指標(data)。

標頭檔案

core.hpp 目前使用只要包含 opencv.hpp 即可,opencv.hpp包含了常用的標頭檔案。

名稱空間

cv
1.可以在包含標頭檔案後新增 “using namespace cv;”之後程式碼可直接使用 Mat 類;
2.在不適用 “using namespace cv; ” ,在每次使用 Mat 都需使用cv名稱空間,如,cv::Mat 。
(我習慣在使用時新增cv,這樣的好處是在使用多個庫編寫程式時,不至於出現相同類名或方法名重名的狀況,並且程式碼更加直觀。)

常用引數

引數 型別 描述
flags int Mat中的一些標記,一共32位,從低位到高位,包含了資料型別(0-2位)、通道數(3-11)等等,
具體請參考【OpenCV】從Mat的flags中可以讀到的資訊,以及相關巨集定義
data uchar* 指向資料的指標
dims int 矩陣的維度
rows int 矩陣行數 維度大於2 則rows = -1
cols int 矩陣列數 維度大於2 則cols = -1
size MSize 資料內是一維陣列,有個兩元素,size[0]矩陣的行數,size[1]矩陣的列數
step MStep 資料內是一維陣列,有個兩元素,step[0]矩陣一行共多少位元組,step[1]表示矩陣一個元素多少位元組
refcount int* 矩陣資料的引用次數,淺拷貝得到的矩陣同步這個引數,同時增加或減少,可以用來判斷有多少矩陣使用該矩陣的資料內容

channels() 通道數,矩陣每個畫素可以儲存一個數組,通道數是陣列中元素的個數,常見的彩色影象,每個畫素存藍、綠、紅(BGR)三個顏色,其channels = 3。

type() 表示了矩陣中元素的型別以及矩陣的通道個數,它是一系列的預定義的常量,其命名規則為CV_(位數)+(資料型別)+(通道數)。例如 CV_8UC1

depth() 矩陣中元素的一個通道的資料型別,這個值和type是相關的。例如 type為 CV_16SC2,一個2通道的16位的有符號整數。那麼,depth則是CV_16S。depth也是一系列的預定義值,將type的預定義值去掉通道資訊就是depth值:
CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F

型別 type

Mat中每個元素可以是單通道、三通道、多通道等等,其儲存的資料型別也可能是char、uchar、short、ing、float、double等等。Mat對於這些型別進行了定義,即type,可以呼叫type()函式來返回當前Mat的型別。它是一系列的預定義的常量,表示了矩陣中元素的型別以及矩陣的通道個數,其命名規則為CV_(位數)+(資料型別)+(通道數)。具體的有以下值:

type 資料型別
CV_8UC1、CV_8UC1、CV_8UC1 uchar
CV_8SC1、CV_8SC1、CV_8SC1 char
CV_16UC1、CV_16UC1、CV_16UC1 ushort
CV_16SC1、CV_16SC1、CV_16SC1 short
CV_32SC1、CV_32SC1、CV_32SC1 int
CV_32FC1、CV_32FC1、CV_32FC1 float
CV_64FC1、CV_64FC1、CV_64FC1 double

建構函式

1. Mat()
無參構造方法無參構造方法;

2. Mat(int rows, int cols, int type)
建立行數為 rows,列數為 col,型別為 type 的影象;

3. Mat(Size size, int type)
建立大小為 size,型別為 type 的影象;

4. Mat(int rows, int cols, int type, const Scalar& s)
建立行數為 rows,列數為 col,型別為 type 的影象,並將所有元素初始化為值 s;

5. Mat(Size size, int type, const Scalar& s)
建立大小為 size,型別為 type 的影象,並將所有元素初始化為值 s;

6. Mat(const Mat& m)
將m賦值給新建立的物件,此處不會對影象資料進行復制,m和新物件共用影象資料,屬於淺拷貝;

7. Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
建立行數為rows,列數為col,型別為type的影象,此建構函式不建立影象資料所需記憶體,而是直接使用data所指記憶體,影象的行步長由 step指定。

8. Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
建立大小為size,型別為type的影象,此建構函式不建立影象資料所需記憶體,而是直接使用data所指記憶體,影象的行步長由step指定。

9. Mat(const Mat& m, const Range& rowRange, const Range& colRange)
建立的新影象為m的一部分,具體的範圍由rowRange和colRange指定,此建構函式也不進行影象資料的複製操作,新影象與m共用影象資料;

10. Mat::Mat(const Mat& m, const Rect& roi)
建立的新影象為m的一部分,具體的範圍roi指定,此建構函式也不進行影象資料的複製操作,新影象與m共用影象資料。


建構函式原文:淺談Opencv Mat類(常用建構函式和成員函式整理)

賦值

將一個Mat A賦值給另一個Mat B,有四種方法
1. 建構函式法    Mat A(B);
2. 過載運算子法   A = B;
3. 複製法     A.copyTo(B);
4. 克隆法     B=A.clone();

方法1、2是淺拷貝(時間短,不安全),只拷貝矩陣頭,不拷貝資料部分,A和B共用一塊資料,A對元素的操作會影響B。
方法3、4是深拷貝(時間長,相對安全),拷貝矩陣的所有資料,包括矩陣頭,最大的區別在於clone()會給目標矩陣重新分配新地址,而copyTo()不會,copyTo()只是修改目標矩陣內的元素的值與當前矩陣值相同

資料操作

前面程式碼裡也提到了,一般採用兩種操作,1.使用at函式,2.使用prt函式。當然Mat對於資料的操作有很多方法,而我覺得這兩種是最具有代表性的。
以一個數據型別為三通道無符號整型(uchar)的Mat A為例,即type為CV_8UC3。讀取其中第一個通道的方法是:
1. A.at<Vec3b>(i, j)[0]; 這裡 A.at() 返回對指定陣列元素的引用,這種方法會檢查i,j是否越界相比較來說比較安全,但是速度相對慢一些。
2. A.ptr<Vec3b>(i)[j][0]; 這裡 A.ptr() 返回指定矩陣行的指標,既然是指標,當發生越界的時候也不會出錯而繼續進行,這有可能修改別的資料,相比來說沒那麼安全,但是速度很快。

常用函式

分配新的陣列資料
void Mat::create(int rows, int cols, int type)

如果陣列有沒有 elemens,則返回 truebool Mat::empty()

返回一個矩陣元素的型別
int Mat::type()

返回一個矩陣元素的深度
int Mat::depth()

返回通道數
int Mat::channels()

克隆
Mat Mat::clone()

把矩陣複製到另一個矩陣中
void Mat::copyTo(OutputArray m)

在縮放或不縮放的情況下轉換為另一種資料型別
void Mat::convertTo(OutputArray m,int rtype,double alpha=1,double beta=0)

將陣列中所有的或部分的元素設定為指定的值
Mat& Mat::setTo(const Scalar& s, InputArray mask=noArray())

在無需複製資料的前提下改變2D矩陣的形狀和通道數或其中之一
Mat Mat::reshape(int cn, int rows=0)

計算3元素向量的一個叉乘積
Mat Mat::cross(InputArray m)

更改矩陣的行數。
void Mat::resize(size_t sz)

返回陣列元素的總數
size_t Mat::total()

返回矩陣元素大小 (以位元組為單位)該方法返回以位元組為單位的矩陣元素大小。
例如,如果矩陣型別是 CV_16SC3,該方法返回3*sizeof(short)6
size_t Mat::elemSize()

該方法通過矩陣表示式(matrix expression)實現矩陣的轉置
The method performs matrix transposition by means of matrix expressions. 
它並未真正完成了轉置但卻返回一個臨時的可以進一步用在更復雜的
矩陣表示式中或賦給一個矩陣的轉置矩陣物件
MatExpr Mat::t()

該方法執行矩陣的反轉矩陣表達。這意味著該方法返回一個臨時矩陣反轉物件並可進一步用於更復雜的
矩陣表示式的中或分配給一個矩陣。
DECOMP_LU是 LU 分解一定不能是單數的
DECOMP_CHOLESKY 是 Cholesky LLT只適用於對稱正矩陣的分解。
該型別在處理大的矩陣時的速度是LU的兩倍左右。
DECOMP_SVD是 SVD 分解。如果矩陣是單數或甚至不是2維,函式就會計算偽反轉矩陣
MatExpr Mat::inv(int method=DECOMP_LU)

常用函式詳情請參考 OpenCV Mat類詳解和用法