一姥闭、Mat類的綜述
1箭启、Mat類存儲(chǔ)圖像
在計(jì)算機(jī)內(nèi)存中,數(shù)字圖像是已矩陣的形式保存的涂乌。OpenCV2中艺栈,數(shù)據(jù)結(jié)構(gòu)Mat是保存圖像像素信息的矩陣,它主要包含兩部分:矩陣頭和一個(gè)指向像素?cái)?shù)據(jù)的矩陣指針湾盒。
矩陣頭主要包含湿右,矩陣尺寸、存儲(chǔ)方法历涝、存儲(chǔ)地址和引用次數(shù)等诅需。矩陣頭的大小是一個(gè)常數(shù),不會(huì)隨著圖像的大小而改變荧库,但是保存圖像像素?cái)?shù)據(jù)的矩陣則會(huì)隨著圖像的大小而改變堰塌,通常數(shù)據(jù)量會(huì)很大,比矩陣頭大幾個(gè)數(shù)量級(jí)分衫。這樣场刑,在圖像復(fù)制和傳遞過(guò)程中,主要的開(kāi)銷是由存放圖像像素的矩陣而引起的蚪战。
那么Mat類如何存儲(chǔ)的圖像呢牵现?
我們都知道圖像分為彩色圖像和灰度圖像,這里我有一個(gè)誤區(qū)邀桑,一直認(rèn)為彩色圖像是一種三維矩陣瞎疼,就是立方體的那種結(jié)構(gòu),一個(gè)圖像分為三層壁畸。
但是這種理解是錯(cuò)誤的贼急,是錯(cuò)誤的茅茂,是錯(cuò)誤的!
其實(shí)在存儲(chǔ)的圖像不管是彩色的還是灰度圖像太抓,都是二維的矩陣空闲,具體的存儲(chǔ)格式如下
(1)灰度圖像的格式:
(2)彩色圖像的格式:
看到了嗎,雖然彩色圖像由BGR三個(gè)通道走敌,但是是存儲(chǔ)在同一個(gè)平面內(nèi)的碴倾,只不過(guò)OpenCV在這里把三列才當(dāng)作一列,因此有img.cols等于圖像的列數(shù)掉丽。
一般我們用Opencv讀取的灰度圖像的數(shù)據(jù)類型為uchar類型的跌榔,而彩色圖像的一個(gè)像素的數(shù)據(jù)類型為<Vec3b>類型的,灰度圖一個(gè)像素占用1個(gè)字節(jié)机打,而彩色圖像一個(gè)像素3個(gè)字節(jié)矫户。
接下來(lái)就引出了我們?nèi)绾伟聪袼刈x取圖像呢?下面有詳解
Mat對(duì)象屬性
Mat的常見(jiàn)屬性
- data uchar型的指針残邀。Mat類分為了兩個(gè)部分:矩陣頭和指向矩陣數(shù)據(jù)部分的指針,data就是指向矩陣數(shù)據(jù)的指針柑蛇。
- dims 矩陣的維度芥挣,例如5*6矩陣是二維矩陣,則dims=2耻台,三維矩陣dims=3.
- rows 矩陣的行數(shù)
- cols 矩陣的列數(shù)
- size 矩陣的大小空免,size(cols,rows),如果矩陣的維數(shù)大于2,則是size(-1,-1)
- channels 矩陣元素?fù)碛械耐ǖ罃?shù)盆耽,例如常見(jiàn)的彩色圖像蹋砚,每一個(gè)像素由RGB三部分組成,則channels = 3
下面的幾個(gè)屬性是和Mat中元素的數(shù)據(jù)類型相關(guān)的摄杂。
- type
表示了矩陣中元素的類型以及矩陣的通道個(gè)數(shù)坝咐,它是一系列的預(yù)定義的常量,其命名規(guī)則為CV_(位數(shù))+(數(shù)據(jù)類型)+(通道數(shù))析恢。具體的有以下值:
CV_8UC1 | CV_8UC2 | CV_8UC3 | CV_8UC4 |
---|---|---|---|
CV_8SC1 | CV_8SC2 | CV_8SC3 | CV_8SC4 |
CV_16UC1 | CV_16UC2 | CV_16UC3 | CV_16UC4 |
CV_16SC1 | CV_16SC2 | CV_16SC3 | CV_16SC4 |
CV_32SC1 | CV_32SC2 | CV_32SC3 | CV_32SC4 |
CV_32FC1 | CV_32FC2 | CV_32FC3 | CV_32FC4 |
CV_64FC1 | CV_64FC2 | CV_64FC3 | CV_64FC4 |
這里U(unsigned integer)表示的是無(wú)符號(hào)整數(shù)墨坚,S(signed integer)是有符號(hào)整數(shù),F(xiàn)(float)是浮點(diǎn)數(shù)映挂。
例如:CV_16UC2泽篮,表示的是元素類型是一個(gè)16位的無(wú)符號(hào)整數(shù),通道為2.
C1柑船,C2帽撑,C3,C4則表示通道是1,2,3,4
type一般是在創(chuàng)建Mat對(duì)象時(shí)設(shè)定鞍时,如果要取得Mat的元素類型亏拉,則無(wú)需使用type历恐,使用下面的depth
- depth
矩陣中元素的一個(gè)通道的數(shù)據(jù)類型,這個(gè)值和type是相關(guān)的专筷。例如 type為 CV_16SC2弱贼,一個(gè)2通道的16位的有符號(hào)整數(shù)。那么磷蛹,depth則是CV_16S吮旅。depth也是一系列的預(yù)定義值,
將type的預(yù)定義值去掉通道信息就是depth值:
CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F - elemSize
矩陣一個(gè)元素占用的字節(jié)數(shù)味咳,例如:type是CV_16SC3庇勃,那么elemSize = 3 * 16 / 8 = 6 bytes - elemSize1
矩陣元素一個(gè)通道占用的字節(jié)數(shù),例如:type是CV_16CS3槽驶,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels
Mat img(3, 4, CV_16UC4, Scalar_<uchar>(1, 2, 3, 4));
cout << img << endl;
cout << "dims:" << img.dims << endl;
cout << "rows:" << img.rows << endl;
cout << "cols:" << img.cols << endl;
cout << "channels:" << img.channels() << endl;
cout << "type:" << img.type() << endl;
cout << "depth:" << img.depth() << endl;
cout << "elemSize:" << img.elemSize() << endl;
cout << "elemSize1:" << img.elemSize1() << endl;
首先創(chuàng)建了一個(gè)3*4的具有4個(gè)通道的矩陣责嚷,其元素類型是CV_16U。Scalar_是一個(gè)模板向量掂铐,用來(lái)初始化矩陣的每個(gè)像素罕拂,因?yàn)榫仃嚲哂?個(gè)通道,Scalar_有四個(gè)值全陨。其運(yùn)行結(jié)果:
運(yùn)行結(jié)果首先打印了Mat中的矩陣爆班,接著是Mat的各個(gè)屬性。注意其type = 26,而depth = 2辱姨。這是由于上面所說(shuō)的各種預(yù)定義類型
例如柿菩,CV_16UC4,CV_8U是一些預(yù)定義的常量雨涛。
- step step這個(gè)屬性理解起來(lái)有點(diǎn)麻煩
參考:
openCV Mat各屬性簡(jiǎn)介(step1) - Sunshine_in_Moon的專欄 - CSDN博客 https://blog.csdn.net/sunshine_in_moon/article/details/45268971
Mat類常用的構(gòu)造方法如下:
Mat::Mat()
Mat::Mat(int rows, int cols, int type)
Mat::Mat(Size size, int type)
Mat::Mat(int rows, int cols, int type, const Scalar& s)
Mat::Mat(Size size, int type, const Scalar& s)
Mat::Mat(const Mat& m)
無(wú)參構(gòu)造方法:
Mat::Mat()創(chuàng)建行數(shù)為rows枢舶,列為col,類型為type的圖像(圖像元素類型替久,如CV_8UC3等)
Mat::Mat(int rows, int cols, int type)創(chuàng)建大小為size凉泄,類型為type的圖像
Mat::Mat(Size size, int type)創(chuàng)建行數(shù)為 rows,列數(shù)為 col侣肄,類型為 type 的圖像旧困,并將所有元素初始化為值 s
Mat::Mat(int rows, int cols, int type, const Scalar& s)創(chuàng)建大小為 size,類型為 type 的圖像稼锅,并將所有元素初始化為值 s
Mat::Mat(Size size, int type, const Scalar& s)將 m 賦值給新創(chuàng)建的對(duì)象吼具,此處不會(huì)對(duì)圖像數(shù)據(jù)進(jìn)行復(fù)制,m 和新對(duì)象共用圖像數(shù)據(jù)
Mat::Mat(const Mat& m)
OpenCV學(xué)習(xí)之路(二)——Mat對(duì)象 - 簡(jiǎn)書 http://www.reibang.com/p/883684519e80
Mat對(duì)象的復(fù)制與克戮鼐唷:
矩陣頭的大小是一個(gè)常數(shù)拗盒,不會(huì)隨著圖像的大小而改變,但是保存圖像像素?cái)?shù)據(jù)的矩陣則會(huì)隨著圖像的大小而改變锥债,通常數(shù)據(jù)量會(huì)很大陡蝇,比矩陣頭大幾個(gè)數(shù)量級(jí)痊臭。這樣,在圖像復(fù)制和傳遞過(guò)程中登夫,主要的開(kāi)銷是由存放圖像像素的矩陣而引起的广匙。因此,OpenCV使用了引用次數(shù)恼策,當(dāng)進(jìn)行圖像復(fù)制和傳遞時(shí)鸦致,不再?gòu)?fù)制整個(gè)Mat數(shù)據(jù),而只是復(fù)制矩陣頭和指向像素矩陣的指針涣楷。例如:
cv::Mat a ;//創(chuàng)建矩陣頭
a = cv::imread("f:\\psb.jpg");//讀入圖像
cv::Mat b = a ;//復(fù)制
上面的a分唾,b有各自的矩陣頭,但是其矩陣指針指向同一個(gè)矩陣狮斗,也就是其中任何一個(gè)改變了矩陣數(shù)據(jù)都會(huì)影響另外一個(gè)绽乔。
那么,多個(gè)Mat共用一個(gè)矩陣數(shù)據(jù)碳褒,最后誰(shuí)來(lái)釋放矩陣數(shù)據(jù)呢折砸?
這就是引用計(jì)數(shù)的作用,當(dāng)Mat對(duì)象每被復(fù)制一次時(shí)骤视,就會(huì)將引用計(jì)數(shù)加1鞍爱,而每銷毀一個(gè)Mat對(duì)象(共用同一個(gè)矩陣數(shù)據(jù))時(shí)引用計(jì)數(shù)會(huì)被減1,當(dāng)引用計(jì)數(shù)為0時(shí)专酗,矩陣數(shù)據(jù)會(huì)被清理。
上圖是Mat對(duì)象a盗扇,b共用一個(gè)矩陣祷肯,故其引用計(jì)數(shù)refcount為2.
但是有些時(shí)候仍然會(huì)需要復(fù)制矩陣數(shù)據(jù)本身(不只是矩陣頭和矩陣指針),這時(shí)候可以使用clone 和copyTo方法疗隶。
cv::Mat c = a.clone();
cv::Mat d ;
a.copyTo(d);
上面代碼中的c佑笋,d各自擁有自己的矩陣,改變自己的矩陣數(shù)據(jù)不會(huì)相互影響斑鼻。
在使用Mat中蒋纬,需要記住:
- OpenCV中的內(nèi)存分配是自動(dòng)完成的(不是特別指定的話)
- 使用OpenCV的C++ 接口時(shí)不需要考慮內(nèi)存釋放問(wèn)題
- Mat的賦值運(yùn)算和拷貝構(gòu)造函數(shù)只會(huì)拷貝矩陣頭坚弱,仍然共同同一個(gè)矩陣
- 如果要復(fù)制矩陣數(shù)據(jù)蜀备,可以使用clone和copyTo函數(shù)
Mat類除構(gòu)造方法外其他方法:
img.create(4,4,CV_8UC(2));
bool imwrite(const string& filename,InputArray img,constvector<int>& params=vector<int>())
src.convertTo(dst, type, scale, shift)
convertTo()函數(shù)負(fù)責(zé)轉(zhuǎn)換數(shù)據(jù)類型不同的Mat,即可以將類似float型的Mat轉(zhuǎn)換到imwrite()函數(shù)能夠接受的類型荒叶。
而cvtColor()函數(shù)是負(fù)責(zé)轉(zhuǎn)換不同通道的Mat碾阁,因?yàn)樵摵瘮?shù)的第4個(gè)參數(shù)就可以設(shè)置目的Mat數(shù)據(jù)的通道數(shù)(只是我們一般沒(méi)有用到它,一般情況下這個(gè)函數(shù)是用來(lái)進(jìn)行色彩空間轉(zhuǎn)換的)些楣。
mat zeros(size,type)
mat eye(size,type)
Mat類按像素讀取圖像
這里主要介紹兩種方法脂凶,一種非常簡(jiǎn)單宪睹,易于編程,但是效率會(huì)比較低蚕钦;另外一種效率高亭病,但是不太好記。下面依次看代碼:
(1)易于編程的
對(duì)于灰度圖像進(jìn)行操作:
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("1.jpg");
resize(img, img, Size(375, 500));//resize為500*375的圖像
cvtColor(img, img, CV_RGB2GRAY);//轉(zhuǎn)為灰度圖
imshow("gray_ori", img);
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
//at<類型>(i,j)進(jìn)行操作嘶居,對(duì)于灰度圖
img.at<uchar>(i, j) = i+j;
}
}
imshow("gray_result", img);
waitKey(0);
return 0;
}
可以看出罪帖,使用at的操作很容易定位,就跟操作一個(gè)普通的二維數(shù)組一樣食听,那么對(duì)于彩色圖像呢胸蛛,方法很簡(jiǎn)單,只需要把a(bǔ)t<類型>中的類型改變?yōu)閂ec3b即可樱报,代碼如下:
#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("1.jpg");
resize(img, img, Size(375, 500));//resize為500*375的圖像
imshow("ori", img);
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
//at<類型>(i,j)進(jìn)行操作葬项,對(duì)于灰度圖
img.at<Vec3b>(i, j)[0] = 255;//對(duì)于藍(lán)色通道進(jìn)行操作
//img.at<Vec3b>(i, j)[1] = 255;//對(duì)于綠色通道進(jìn)行操作
//img.at<Vec3b>(i, j)[2] = 255;//對(duì)于紅色通道進(jìn)行操作
}
}
imshow("result", img);
waitKey(0);
return 0;
}
(2)采用指針對(duì)圖像進(jìn)行訪問(wèn)
這里直接寫對(duì)于彩色圖像的操作:
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("1.jpg");
int rows = img.rows;
int cols = img.cols * img.channels();
if(img.isContinuous())//判斷是否在內(nèi)存中連續(xù)
{
cols = cols * rows;
rows = 1;
}
imshow("ori",img);
for(int i = 0;i<rows;i++)
{
//調(diào)取存儲(chǔ)圖像內(nèi)存的第i行的指針
uchar *pointer = img.ptr<uchar>(i);
for(int j = 0;j<cols;j += 3)
{
//pointer[j] = 255;//對(duì)藍(lán)色通道進(jìn)行操作
//pointer[j+1] = 255;//對(duì)綠色通道進(jìn)行操作
pointer[j+2] = 255;//對(duì)紅色通道進(jìn)行操作
}
}
imshow("result",img);
waitKey();
return 0;
}
從上面?zhèn)€的代碼中可以很明顯的看出我們是如何操作圖像的數(shù)據(jù)以及圖像在Mat中的存放格式的,就是我們上面那個(gè)彩色圖像的存放示意圖中的格式迹蛤,這里把彩色圖像中的一個(gè)像素點(diǎn)分成三份民珍,每一份都是uchar類型,因此我們這里不需要使用Vec3b數(shù)據(jù)類型盗飒。把彩色圖像看成一個(gè)rows * (cols * channels)的二維數(shù)組進(jìn)行操作嚷量,其中的每個(gè)元素的類型都是uchar類型。
這里需要注意的是j += 3是因?yàn)槲覀儼凑找粋€(gè)像素點(diǎn)進(jìn)行操作逆趣,而一個(gè)像素點(diǎn)在這里面又被分成三份蝶溶,因此需要j += 3,如果是灰度圖像則直接j++即可
這種操作方式雖然復(fù)雜一些宣渗,但是執(zhí)行效率會(huì)比上面的算法高很多抖所。
下面我們給出這兩種方式進(jìn)行同一操作的時(shí)間對(duì)比:
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("1.jpg");
Mat img2;
img.copyTo(img2);
cout<<"圖像的行數(shù): "<<img.rows<<endl;
cout<<"圖像的列數(shù): "<<img.cols<<endl;
cout<<"圖像通道數(shù): "<<img.channels()<<endl;
double time1;
time1 = (double)getTickCount();
int rows = img.rows;
int cols = img.cols * img.channels();
if(img.isContinuous())//判斷是否在內(nèi)存中連續(xù)
{
cols = cols * rows;
rows = 1;
}
for(int i = 0;i<rows;i++)
{
//調(diào)取存儲(chǔ)圖像內(nèi)存的第i行的指針
uchar *pointer = img.ptr<uchar>(i);
for(int j = 0;j<cols;j += 3)
{
//pointer[j] = 255;//對(duì)藍(lán)色通道進(jìn)行操作
//pointer[j+1] = 255;//對(duì)綠色通道進(jìn)行操作
pointer[j+2] = 255;//對(duì)紅色通道進(jìn)行操作
}
}
time1 = 1000 * ((double)getTickCount() - time1) / getTickFrequency();
//imshow("result",img);
cout<<"第一種方法用時(shí): "<<time1<<endl;
double time2 = (double)getTickCount();
for (int i = 0; i < img2.rows; i++)
{
for (int j = 0; j < img2.cols; j++)
{
//at<類型>(i,j)進(jìn)行操作,對(duì)于灰度圖
img2.at<Vec3b>(i, j)[0] = 255;//對(duì)于藍(lán)色通道進(jìn)行操作
//img.at<Vec3b>(i, j)[1] = 255;//對(duì)于綠色通道進(jìn)行操作
//img.at<Vec3b>(i, j)[2] = 255;//對(duì)于紅色通道進(jìn)行操作
}
}
time2 = 1000 * ((double)getTickCount() - time2)/getTickFrequency();
cout<<"第二種方法用時(shí): "<<time2<<endl;
imshow("img",img);
imshow("img2",img2);
waitKey(0);
return 0;
}
Opencv中的Mat類使用方法總結(jié) - OpenCV知識(shí)庫(kù) http://lib.csdn.net/article/opencv/42000