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_8UC2、CV_8UC3 | uchar |
CV_8SC1、CV_8SC2、CV_8SC3 | char |
CV_16UC1、CV_16UC2、CV_16UC3 | ushort |
CV_16SC1、CV_16SC2、CV_16SC3 | short |
CV_32SC1、CV_32SC2、CV_32SC3 | int |
CV_32FC1、CV_32FC2、CV_32FC3 | float |
CV_64FC1、CV_64FC2、CV_64FC3 | 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,則返回 true。
bool 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類詳解和用法