日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

視覺SLAM——OpenCV之Mat結(jié)構(gòu)詳解 數(shù)據(jù)成員和構(gòu)造函數(shù) 創(chuàng)建Mat方法 遍歷Mat方法

 印度阿三17 2019-03-25

前言

OpenCV1時代采用基于C語言接口構(gòu)建函數(shù)庫,使用名為IplImage的結(jié)構(gòu)體在內(nèi)存中存儲圖像,其問題在于需要用戶手動管理內(nèi)存,如果不手動釋放內(nèi)存會造成內(nèi)存泄漏。
OpenCV2引入面向?qū)ο缶幊趟枷耄尤肓艘粋€c 接口,使用Mat類數(shù)據(jù)結(jié)構(gòu)作為主打,可以實現(xiàn)自動內(nèi)存管理,且擴展性大大提高。

Mat概述

對于Mat類,首先要知道的是
1)不必手動為其開辟空間;
2)不必再在不需要時將空間釋放。
但手動做還也是可以的:大多數(shù)OpenCV函數(shù)仍會手動地為輸出數(shù)據(jù)開辟空間。當傳遞一個已經(jīng)存在的 Mat 對象時,開辟好的矩陣空間會被重用。

Mat是一個類,由兩個數(shù)據(jù)部分組成:矩陣頭(包含矩陣尺寸、存儲方法、存儲地址等)和一個指向存儲所有像素值矩陣的指針。

Mat類最重要的一點是淺拷貝和深拷貝問題。由于OpenCV處理圖像時很多時候沒有必要重新復(fù)制一份圖像矩陣,因而采用了引用計數(shù)機制。其思路是讓每個Mat對象有自己的信息頭,但共享一個圖像矩陣(矩陣指針指向同一地址)。賦值運算符和拷貝構(gòu)造函數(shù)只復(fù)制矩陣頭和矩陣指針,而不復(fù)制矩陣。

Mat A , C;
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 為矩陣開辟內(nèi)存
Mat B(A);                                 // 拷貝構(gòu)造函數(shù)
C = A;                                    // 賦值

這里A、B、C矩陣頭不同,但指向了相同的圖像矩陣,其中一個對象對矩陣數(shù)據(jù)進行改變也會影響其他對象。如果矩陣屬于多個Mat對象,由最后一個使用它的對象進行內(nèi)存清理。這通過引用計數(shù)來判斷,每復(fù)制一個Mat對象,計數(shù)器加一,每釋放一個計數(shù)器減一,當計數(shù)器值為0時矩陣就會被清理。

如果需要進行對象的深拷貝可以采用clone()函數(shù)或者copyTo()。

Mat A;
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); 
Mat B = A.clone();
Mat C;
A.copyTo(C);

Mat類的數(shù)據(jù)成員

	/*
	flag的詳細解釋可以看 https://blog.csdn.net/yiyuehuan/article/details/43701797
	0-2位 depth:每一個像素的位數(shù),也就是每個通道的位數(shù),即數(shù)據(jù)類型(如CV_8U)
		enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }
		8U 表示 8 位無符號整數(shù),16S 表示 16 位有符號整數(shù),64F表示 64 位浮點數(shù)
	3-11位 number of channels:代表通道數(shù)channels,最高512位
	0-11位共同代表type:矩陣元素的類型,即通道數(shù)和數(shù)據(jù)類型(如CV_8UC3、CV_16UC2)
	14位 continuity flag:代表Mat的內(nèi)存是否連續(xù)
	15位 submat flag:代表該Mat是否為某一個Mat的submatrix
	16-31位 the magic signature:用來區(qū)分Mat的類型,如果Mat和SparseMat
	*/
	int flags;
	
    //矩陣的維數(shù),一般大于2
    int dims;
    
    //矩陣的行數(shù)與列數(shù),超過2維矩陣時(-1,-1)
    int rows, cols;
    
    //指向存放矩陣數(shù)據(jù)的內(nèi)存
    uchar* data;
    
    //用來控制ROI區(qū)域,來獲取一些圖像的局部切片,減少計算量或者特殊需求的。
    const uchar* datastart;
    const uchar* dataend;
    const uchar* datalimit;
    
	//如果需要創(chuàng)建一個新矩陣的內(nèi)存空間,會調(diào)用MatAllocator類作為分配符進行內(nèi)存的分配。
    MatAllocator* allocator;
    
    //interaction with UMat
    //UMatData結(jié)構(gòu)體總有一個成員refcount:記錄了矩陣的數(shù)據(jù)被其他變量引用了多少次
    UMatData* u;
    
    //返回矩陣大小
    MatSize size;
    
    //矩陣元素尋址,step[i]表示第i維的總大小,單位字節(jié)
    //對于2維矩陣:step[0]是矩陣中一行元素的字節(jié)數(shù),step[1]是矩陣中一個元素的字節(jié)數(shù)
    //以下公式可以得到Mat中任意元素地址
    //addr(M{i,j})=M.data M.step[0]?i M.step[1]?j;
    MatStep step;

此外其他版本還存在以下成員:
elemSize :矩陣一個元素占用的字節(jié)數(shù),例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes。
elemSize1 :矩陣元素一個通道占用的字節(jié)數(shù),例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels。

Mat類的構(gòu)造函數(shù)

//1、默認構(gòu)造函數(shù),無參數(shù)
Mat::Mat();
	
//2、行數(shù)為rows,列數(shù)為cols,類型為type(如CV_8UC1、CV_16UC2)
Mat(int rows, int cols, int type);

//3、矩陣大小為size,類型為type
//注意size的構(gòu)造函數(shù)是Size_(_Tp _width,_Tp _height) 先列后行
Mat(Size size, int type);

//4、行數(shù)為rows,列數(shù)為cols(或矩陣大小為size),類型為type,所有元素初始化為s
//Scalar表示具有4個元素的數(shù)組,如Scalar(a,b,b),其原型為Scalar_<double>
Mat(int rows, int cols, int type, const Scalar& s);
Mat(Size size, int type, const Scalar& s);

//5、矩陣維數(shù)為ndims,sizes為指定ndims維數(shù)組形狀的整數(shù)數(shù)組,所有元素初始化為s
Mat(int ndims, const int* sizes, int type);
Mat(const std::vector<int>& sizes, int type);
Mat(int ndims, const int* sizes, int type, const Scalar& s);
Mat(const std::vector<int>& sizes, int type, const Scalar& s);

//6、拷貝構(gòu)造函數(shù),將m賦值給新創(chuàng)建的對象,淺拷貝
Mat(const Mat& m);

//7、行數(shù)為rows,列數(shù)為cols,類型為type,矩陣數(shù)據(jù)為data,直接使用data所指內(nèi)存,淺拷貝
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);
Mat(Size size, int type, void* data, size_t step=AUTO_STEP);
Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0);
Mat(const std::vector<int>& sizes, int type, void* data, const size_t* steps=0);

//8、創(chuàng)建的新圖像為m的一部分,范圍由rowRange和colRange指定,新圖像與m共用圖像數(shù)據(jù),淺拷貝
Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());
Mat(const Mat& m, const Range* ranges);
Mat(const Mat& m, const std::vector<Range>& ranges);

//創(chuàng)建的新圖像為m的一部分,具體的范圍由矩陣對象roi指定
//Rect的成員函數(shù)有x,y,width,height,分別為左上角點的坐標好矩陣寬和高
Mat(const Mat& m, const Rect& roi);

創(chuàng)建Mat對象

1、使用Mat()構(gòu)造函數(shù)

Mat M(2,2, CV_8UC3, Scalar(0,0,255)); 

需要指定行數(shù)、列數(shù)、存儲元素的數(shù)據(jù)類型以及每個矩陣點的通道數(shù)。

2、使用Mat()構(gòu)造函數(shù)2

int sz[3] = {2,2,2}; 
Mat L(3,sz, CV_8UC(1), Scalar::all(0));

常用于創(chuàng)建一個超過兩維的矩陣:指定維數(shù),然后傳遞一個指向一個數(shù)組的指針,這個數(shù)組包含每個維度的尺寸,其余的相同

3、為已存在IplImage指針創(chuàng)建信息頭(一般不用)

IplImage* img = cvLoadImage("greatwave.png", 1);
Mat mtx(img); // convert IplImage* -> Mat

4、利用create()函數(shù)
這個創(chuàng)建方法不能為矩陣設(shè)初值,它只是在改變尺寸時重新為矩陣數(shù)據(jù)開辟內(nèi)存

 M.create(4,4, CV_8UC(2));//4X4的圖像矩陣,通道數(shù)為2,沒有初值

5、MATLAB形式的初始化方式: zeros(), ones(), eyes()

 Mat E = Mat::eye(4, 4, CV_64F);//4X4的單位矩陣   
  
 Mat O = Mat::ones(2, 2, CV_32F);//2X2的全為1矩陣

 Mat Z = Mat::zeros(3,3, CV_8UC1);//3X3的零矩陣

6、小矩陣使用逗號分隔式初始化

 Mat C = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1); //3X3的單位矩陣

7、使用clone()或copyto()為已存在對象創(chuàng)建新信息頭

Mat RowClone = C.row(1).clone();//復(fù)制 C中的第2行[0,1,0]作為新矩陣(深拷貝)

遍歷Mat對象

首先假設(shè)需要對圖像將那些顏色空間縮減,如imgnew=imgold/10?10img_{new} = img_{old} / 10 * 10imgnew?=imgold?/10?10。對于較大的圖像,一般不是對每個像素點每個通道的值進行如上計算,而是預(yù)先計算所有可能的值,構(gòu)建查找表,然后利用查找表對其直接復(fù)制。

	//構(gòu)建查找表table
	int divideWith; 
    cin >> divideWith;
    uchar table[256]; 
    for (int i = 0; i < 256;   i)
       table[i] = divideWith* (i/divideWith);

1、ptr指針單像素單通道值訪問
即通過uchar* ptr =img.ptr<char>(i); 得到第i行首地址,然后采用指針運算( )或者操作符[]遍歷。

Mat& ScanImage(Mat& img,const uchar* const table)
{
	CV_Assert(img.depth() != sizeof(uchar))//只接收uchar類型矩陣
	int channels = img.channel();
	int nRows = img.rows * channels;//注意行數(shù)乘以通道數(shù)
	int nCols = img.cols;
	
	if(img.isContinuous())//如果存儲連續(xù)則可以使用一次循環(huán)
	{
		nCols *= nRows;
		nRows = 1; 
	}
	uchar* ptr;
	for(int i=0; i<nRows;   i)
	{
		ptr = img.ptr<char>(i);
		for( j=0; j<nCols;   j)
		{
			ptr[j] = table[ptr[j]]
			//*ptr   =  table[*ptr]
		}
	}
	return I;
}

2、ptr指針單像素訪問
利用cv::Vec3b *ptr =img.ptr<cv::Vec3b>(i);得到第i行第一個像素的3個通道地址。

Mat& ScanImage(Mat& img,const uchar* const table)
{
	CV_Assert(img.depth() != sizeof(uchar))//只接收uchar類型矩陣
	int channels = img.channel();
	int nRows = img.rows;//每一行
	int nCols = img.cols;
	Vec3b *ptr;
	for(int i=0; i<nRows;   i)
	{
		ptr = img.ptr<Vec3b>(i);
		for( j=0; j<nCols;   j)
		{
			ptr[j][0] = table[ptr[j][0]];
			ptr[j][1] = table[ptr[j][1]];
			ptr[j][2] = table[ptr[j][2]];
		}
	}
	return I;
}

3、對data操作
data會從Mat中返回指向矩陣第一行第一列的指針。常用來檢查圖像是否被成功讀入(如果指針為NULL則表示無輸入)。當矩陣式連續(xù)存儲時,可以通過data遍歷矩陣。
也可以采用addr(M{i,j})=M.data M.step[0]?i M.step[1]?j;得到每個元素的地址,但是不常用。

if(I.isContinuous())
{
	uchar* p = img.data;
	for( unsigned int i =0; i < ncol * nrows;   i)
    	*p   = table[*p];
}

4、迭代器iterator訪問
使用迭代器操作像素,與STL庫用法相似,只需要獲得圖像矩陣的begin和end,然后增加迭代直至從begin到end。將*操作符添加在迭代指針前,即可訪問當前指向的內(nèi)容。
迭代器可以采用MatIterator_以及Mat的模板子類Mat_,它重載了operator()。

MatIterator_<uchar> it = img.begin<uchar>();
Mat_<uchar>::iterator it = img.begin<uchar>();
Mat& ScanImage(Mat& I,const uchar* const table)
{
	CV_Assert(img.depth() != sizeof(uchar));
	const int channels = img.channels();
	switch(channels)
	{
		case 1:
			{
				Mat_<uchar>::iterator it = img.begin<uchar>();
				Mat_<uchar>::iterator itend = img.end<uchar>();
				for(;it != itend;   it)
					*it = table[*it];
				break;
			}
		case 3:
			{
				Mat_<Vec3b>::iterator it = img.begin<uchar>();
				Mat_<Vec3b>::iterator itend = img.end<uchar>();
				for(; it != itend ;   it)
				{
					(*it)[0] = table[(*it)[0]];
					(*it)[1] = table[(*it)[1]];
					(*it)[2] = table[(*it)[2]];
				}
			}
	}
	return img;
}

5、動態(tài)地址計算(at)
該方法一般用于獲取或更改圖像中的某個元素(或隨機元素),而不用于進行全圖掃描。它的基本用途是要確定你試圖訪問的元素的所在行數(shù)與列數(shù)。
在debug模式下該方法會檢查你的輸入坐標是否有效或者超出范圍,如果坐標有誤則會輸出一個標準的錯誤信息;在release模式下,它和其他的區(qū)別僅僅是對于矩陣的每個元素,都會獲取一個新的行指針,然后通過該指針和[]操作獲取元素。

Mat& ScanImage(Mat& img,const uchar* const table)
{
	CV_Assert(img.depth() != sizeof(uchar));
	const int channels = img.channels();
	switch(channels)
	{
		case 1:
			{
				for(int i=0; i<img.rows;   i)
					for(int j=0; j<img.cols;   i)
						img.at<uchar>(i,j) = table[img.at<uchar>(i,j)];
				break;
			}
		case 3:
			{
				for(int i=0; i<img.rows;   i)
				{
					for(int j=0; j<img.cols;   i)
					{
						img.at<vec3b>(i,j)[0] = table[img.at<vec3b>(i,j)[0]];
						img.at<vec3b>(i,j)[1] = table[img.at<vec3b>(i,j)[1]];
						img.at<vec3b>(i,j)[2] = table[img.at<vec3b>(i,j)[2]];
					}
				}
				break;
			}
	}
	return img;
}

6、LUT函數(shù)
LUT(Look up table)被官方推薦用于實現(xiàn)批量圖像元素查找和更改。對于修改像素值,OpenCV提供函數(shù)operationsOnArray:LUT()<lut>,直接實現(xiàn)該操作,而不需要掃描圖像。

//建立mat類對象用于查表
Mat LookUpTable(1,256,CV_8U);
uchar* p = LookUpTable.data;
for(int i=0; i<256;   i)
	p[i] = table[i];
//調(diào)用函數(shù)(I時輸入,J是輸出)
LUT(I, LookUpTable, J);

一般認為,采用ptr指針訪問圖像像素的效率更高;而迭代器則被認為是更安全的方式,僅需要獲得圖像矩陣的begin和end;at方法一般不用于對圖像進行遍歷;而調(diào)用LUT函數(shù)可以獲得最快的速度,因為OpenCV庫可以通過英特爾線程架構(gòu)啟用多線程。其他也存在一些比較偏的遍歷方法,不過這幾種最為常見,效率和安全性也相對較好。

來源:http://www./content-4-148551.html

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多