內容:
1.什麼是Mat
2.Mat的屬性與操作
3.創建Mat
1.什麼是Mat
- 圖像文件的內存數據對象
對於人類來說,看到一個圖像時,腦子裏會想到這個圖像內容,比如一張帥哥的照片,在我們看來就是帥哥,對於計算機來說,它只能識別0和1,不管是一張什麼圖像,在它眼中也只是由一些特定數字組成的數據,所以對它來說,一張圖像就是一個二維矩陣。而Mat就是存儲這個數據的對象,也就是存儲圖像文件的內存數據對象,而這個對象最主要的就是一些矩陣。
或者說是將imread()讀取到的圖像信息存儲起來的一個數據結構,就是Mat。
Mat對象可分成兩個部分,一個是包含圖像長寬、色彩等信息的頭部,存儲源數據; 一個是含有像素信息的數據部分。想改變圖像信息時,就要改變像素信息。
當讀了一個圖像信息之後,把它賦給另一個Mat對象時,這兩個Mat對象都指向同一個Data Block,數據部分並沒有改變,只進行了一個地址的指向。當使用克隆或者拷貝的API時,會創建一個新的Mat對象,跟原來的Mat對象是兩個類層的Mat對象。
輸出Mat對象所包含的一些信息,比如寬、高、通道數目、通道深度和字節類型:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 讀入圖像
Mat src = imread("../test.jpg",IMREAD_COLOR);
// 讀入灰度圖像
//Mat src = imread("../test.jpg",IMREAD_GRAYSCALE);
if(src.empty())
{
printf("不能找到文件。\n");
return -1;
}
namedWindow("input",WINDOW_AUTOSIZE);
imshow("input", src);
int width = src.cols;
int height = src.rows;
int dim = src.channels();
// 通道深度
int d = src.depth();
// 字節類型
int t = src.type();
// 打印寬、高、通道數
// 8位+無符號Char+3個通道
if(t == CV_8UC3)
// 枚舉類型是16
printf("type:%d\n",t);
// 只有當深度類型變爲浮點數時,深度值纔會改變
printf("d:%d\n",d);
printf("width:%d height:%d dim:%d\n",width,height,dim);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出結果:
type:16
d:0
width:474 height:237 dim:3
關於通道深度和字節類型可參考下面圖片中的內容:
2.Mat對象創建與使用
- 創建空白Mat對象
1.第一種方法Mat( , , )
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個256×256大小的灰度(單通道)圖像
Mat t1 = Mat(256,256,CV_8UC1);
// 0代表黑色
t1 = Scalar(0);
imshow("t1", t1);
// 創建了一個256×256大小的三通道圖像
Mat t2 = Mat(256,256,CV_8SC3);
// 創建了紅色圖像
t2 = Scalar(0,0,255);
imshow("t2", t2);
waitKey(0);
destroyAllWindows();
return 0;
}
運行結果:
2.第二種方法Mat(Size(), )
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個512×512大小的三通道圖像
Mat t1 = Mat(Size(512, 512), CV_8UC3);
t1 = Scalar(255, 255, 0);
imshow("t1", t1);
}
輸出結果:
3.第三種方法Mat::zeros(,):
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個初始值全爲0的256×256大小的三通道圖像
Mat t2 = Mat::zeros(Size(256,256),CV_8SC3);
imshow("t2", t2);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出結果:
- 從現有圖像創建
1.賦值和克隆
從現有圖像創建Mat對象,然後順便驗證一下最開始說的賦值和克隆的區別。
將t1的值賦值給t3後不改變t3的值,t4是t1克隆的值:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個256×256大小的三通道圖像
Mat t1 = Mat(Size(256, 256), CV_8UC3);
t1 = Scalar(255, 255, 0);
imshow("t1", t1);
// 賦值
Mat t3 = t1;
imshow("t3", t3);
// 改變t3的值
//t3 = Scalar(0,255,0);
// 克隆
Mat t4 = t1.clone();
imshow("t4", t4);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出結果:
修改t3的值:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個256×256大小的三通道圖像
Mat t1 = Mat(Size(256, 256), CV_8UC3);
t1 = Scalar(255, 255, 0);
imshow("t1", t1);
// 賦值
Mat t3 = t1;
imshow("t3", t3);
// 改變t3的值
t3 = Scalar(0,255,0);
// 克隆
Mat t4 = t1.clone();
imshow("t4", t4);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出改變t3前的t1、t3值,以及改變t3後t4的值:
可以看到由於t3的改變,t1的值也隨之變化,t4的值也隨t1的值發生改變。這說明t1和被t1賦值後的t3是指向同一個數據的。修改其一,數據就會發生改變。
若只改變克隆t1的t4的值,輸出改變後的t1、t3、t4的值:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個256×256大小的三通道圖像
Mat t1 = Mat(Size(256, 256), CV_8UC3);
t1 = Scalar(255, 255, 0);
// 賦值
Mat t3 = t1;
// 克隆
Mat t4 = t1.clone();
// 改變t4的值
t4 = Scalar(0,255,0);
imshow("t1", t1);
imshow("t3", t3);
imshow("t4", t4);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出結果:
很明顯可以看出克隆t1的t4的值的改變是影響不了t1、t3的值的。
2.拷貝
還有一種就是拷貝:
Mat t5;
t1.copyTo(t5);
意爲將t1的值拷貝給t5,t5和t4的性質是相同的,在這就不進行驗證了,有興趣可以自己嘗試一下。
3.Mat::zeros(,)
還可以創建和原始圖像大小、類型相同,但內容可能不同的圖像:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
// 創建了一個256×256大小的三通道圖像
Mat t1 = Mat(Size(256, 256), CV_8UC3);
t1 = Scalar(255, 255, 0);
// 創建了一個與t1大小、類型相同的黑色圖像;
Mat t6 = Mat::zeros(t1.size(),t1.type());
imshow("t1", t1);
imshow("t6", t6);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出結果:
- 遍歷與訪問像素值
1.基於數組方式遍歷
爲了更容易看出遍歷過一邊圖像的所有像素值,我會在遍歷過程中改變一下遍歷過的像素值,更容易看出遍歷操作。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
Mat src = imread("../test.jpg");
// 遍歷每個像素值
int height = src.rows;
int width = src.cols;
int ch = src.channels();
for(int row =0;row < height; row++)
{
for(int col = 0;col < width; col++)
{
if(ch == 3)
{
Vec3b pixel = src.at<Vec3b>(row,col);
int blue = pixel[0];
int green = pixel[1];
int red = pixel[2];
// 字節類型+3通道+bit 通過Vec3b獲得
src.at<Vec3b>(row,col)[0] = 255 - blue;
src.at<Vec3b>(row,col)[1] = 255 - green;
src.at<Vec3b>(row,col)[2] = 255 - red;
}
if(ch == 1)
{
// 單通道類型
int pv = src.at<uchar>(row,col);
src.at<uchar>(row,col) = (255 - pv);
}
}
}
imshow("pixel-demo",src);
waitKey(0);
destroyAllWindows();
return 0;
}
輸出結果:
可以看出來,與原圖明顯有差別,每個像素的值都改變過了。
2.用指針的方式遍歷
把每個用指針遍歷過的像素的值拷貝給另一個圖像,最後可以輸出一個和原圖完全相同的圖像。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
Mat src = imread("../test.jpg");
imshow("原圖",src);
// 遍歷每個像素值
int height = src.rows;
int width = src.cols;
int ch = src.channels();
// 記錄結果,用於後面把原圖像中的遍歷過的每個像素值賦值給result;
Mat result = Mat::zeros(src.size(),src.type());
// 所有像素的數據都作爲一個個uchar類型的數據塊放在內存地址當中,要從指向開頭的指針開始遍歷數據
for(int row =0;row < height; row++)
{
// 獲取當前行的指針
uchar* curr_row = src.ptr<uchar>(row);
uchar* result_row = result.ptr<uchar>(row);
for(int col = 0;col < width; col++)
{
if(ch == 3)
{
int blue = *curr_row++;
int green = *curr_row++;
int red = *curr_row++;
*result_row++ = blue;
*result_row++ = green;
*result_row++ = red;
}
if(ch == 1)
{
int pv = *curr_row++;
src.at<uchar>(row,col) = (255 - pv);
*result_row++ = pv;
}
}
}
imshow("pixel-demo",result);
waitKey(0);
destroyAllWindows();
return 0;
}
運行結果:
使用數組方式時要明確圖像像素的具體類型是什麼,而對於指針方式來說,使用的都是uchar類型,省去了一些麻煩,兩種方式對比來說,後者的性能更加高效一些。