opencv中訪問圖像像素方式

       opencv中圖像是存儲在Mat類的對象中,Mat稱爲基本圖像容器。圖像矩陣的大小取決於圖像的大小和所使用的顏色模型,確切的說應該是圖像的通道數。對於灰度圖像只有一個通道,彩色圖像則會有多個通道。對於多通道圖像來說,矩陣中會有多個子列,其子列的個數等於圖像的通道數。

       注意,在opencv中子列的通道順序是反過來的:BGR而不是RGB。如果內存足夠大,圖像就能夠實現連續存儲,各行連接起來組成一個長行,有助於提升圖像的掃描速度。在opencv中可以利用函數iscontinuous()來判斷圖像是否連續存儲。

       下面主要總結訪問圖像像素的不同方法。

       1、高效的方法(efficient way)

       效率最高的訪問方式是經典的C風格運算符[](指針)。程序示例如下:

Mat& ScanImageAndReduceC(Mat& I,const uchar* const table)
{
	CV_Assert(I.depth() != sizeof(uchar));

	int channels = I.channels();

	int nRows = I.rows;
	int nCols = I.cols*channels;

	if(I.isContinuous())
	{
		nCols *= nRows;
		nRows = 1;
	}
	int i,j;
	for( i = 0; i < nRows ; i++)
	{
		uchar* p = I.ptr<uchar>(i);
		for(j = 0; j < nCols ; j++)
		{
			p[j] = table[p[j]];
		}
	}
	return I;
}
獲取每一行開始出的指針,然後遍歷至該行末尾。如果矩陣元素是連續方式存儲的,只請求一次指針,然後一直遍歷下去即可。彩色的圖像需要注意:因爲每個像素有三個通道,所以遍歷的元素的數目也需要乘以3。

另外,也可以使用data來實現遍歷。data會從Mat中返回指向第一行第一列的指針。若這個指針爲空,說明沒有數據,常用來檢驗圖像是否讀入。當圖像是連續存儲的時候,可以直接使用data來遍歷整幅圖像。對於一幅灰度圖像,具體實現過程如下:

uchar* p = I.data;
for(int i = 0; i < nCols*nRows; i++)
{ *p++ = table[*p];}

       2、使用迭代器訪問圖像中的像素

     迭代器是一種更爲安全的圖像像素訪問方法,在利用迭代器訪問圖像的像素時只需要獲得圖像矩陣的begin和end,然後增加迭代直至從begin到end。將*操作符添加在迭代指針的前面,即可獲得當前指針指向的內容。具體實現如下:

Mat& ScanImageAndReduceC(Mat& I,const uchar* const table)
{
	CV_Assert(I.depth() != sizeof(uchar));

	//Mat I = Mat::zeros(I.size(),CV_8UC3);
	const int channels = I.channels();
	switch(channels)
	{
	case 1:
		{
			MatIterator_<uchar> it = I.begin<uchar>();
			MatIterator_<uchar> end = I.end<uchar>();
			for(;it != end; it++)
				*it = table[*it];
			break;
		}
	case 3:
		{
			Mat_<Vec3b>::iterator it,end;
			for(it=I.begin<Vec3b>(),end=I.end<Vec3b>();it != end; it++)
			{
				(*it)[0] = table[(*it)[0]];
				(*it)[1] = table[(*it)[1]];
				(*it)[2] = table[(*it)[2]];
			}
			break;
		}
	default:
		break;
	}
	return I;
}
對於彩色圖像中的一行,每一列有三個uchar類型的元素,可以看做是包含uchar類型元素的一個三維的vector,在opencv中用Vec3b來表示。如果要訪問第n個子列,只需要用[]來操作即可。opencv中的迭代在掃描完一行中所有的列後,會自動跳到下一行;所以說如果在彩色圖像中只是用簡單的uchar而不是Vec3b迭代的話,就只能獲得B通道。

3.通過動態的獲取圖像像素的地址來遍歷圖像

      這種方法一般不推薦用來對圖像進行掃描,它本來是用於獲取或者改變圖像中隨機像素的值。基本方法是要首先確定你所訪問圖像像素的行和列,同時確定圖像像素的數據類型。主要使用at()函數實現,下面是訪問一個灰度圖像的代碼示例:

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
	CV_Assert(I.depth() != I.channels());

	const int channels = I.channels();

	switch(channels)
	{
	case 1:
		{
			for(int i = 0; i < I.rows ; i++)
				for(int j = 0; j < I.cols ; j++)
					I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
		    break;
		}
	case 3:
		{
			Mat_<Vec3b> _I = I;
			for(int i = 0; i < I.rows ; i++)
			{
				for(int j = 0; j < I.cols ; j++)
				{
					_I(i,j)[0] = table[_I(i,j)[0]];
					_I(i,j)[1] = table[_I(i,j)[1]];
					_I(i,j)[2] = table[_I(i,j)[2]];
				}
			}
			I = _I;
			break;
		}
	default:
		break;
	}
}


4、核心函數LUT

      opencv提供了一個函數LUT來實現批量圖像元素查找和更改操作圖像的方法。圖像處理中,最常見的操作是將某個給定的值替換成其它值,opencv提供的LUT函數直接實現該類操作,不需要自己掃描圖像。示例如下:

      首先建立一個mat型查找表:

     Mat lookUpTable(1,256,CV_8U);
     uchar* p = lookUpTable.data;
     for(int i = 0; i < 256 ; i++)
         p[i] = table[i];
      然後直接調用函數(I是輸入,J是輸出):

    LUT(I,lookUpTable ,J);


效率對比:使用opencv的內置函數,調用LUT函數可以獲得更快的速度,是因爲opencv內部可以通過英特爾線程構架啓用多線程。採用指針的算法來掃描圖像,迭代法是一個不錯的選擇,不過效率較低。在debug模式下,動態訪問是最浪費資源的一種掃描方式,在release模式下它和迭代法相差無幾。從安全角度來說,迭代法更好。


5.opencv中計算程序運行的時間

      在opencv中提供了兩個函數:getTickCount()函數和getTickFrequency()函數;

      getTickCount()函數:這個函數返回CPU自某個事件(比如電腦啓動)以來走過的時鐘週期數;

      getTickFrequency()函數:返回CPU一秒內走過的時鐘週期數。

      計算一段程序運行的時間可以按照如下方式實現:

   double t = (double)getTickCount();
   //deal with sth...
   t = ((double)getTickCount() - t)/getTickFrequency();
   cout<<" Time passed in seconds:"<<t<<endl;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章