1. 程式人生 > >【OpenCV3影象處理】Mat類詳解 之 元素的獲取與賦值 ( 對比.at<>()函式 和 .ptr<>()函式)

【OpenCV3影象處理】Mat類詳解 之 元素的獲取與賦值 ( 對比.at<>()函式 和 .ptr<>()函式)

Mat中畫素的獲取與賦值

計算機視覺中,影象的讀取是影象處理的基礎,影象就是一系列畫素值,OpenCV使用資料結構cv::Mat來儲存影象。cv::Mat是一個矩陣類,矩陣中每一個元素都代表一個畫素,對於灰度影象,畫素用8位無符號數,0表示黑色,255表示白色。對於彩色畫素而言,每個畫素需要三位這樣的8位無符號數來表示,即三個通道(R,G,B),矩陣則依次儲存一個畫素的三個通道的值,然後再儲存下一個畫素點。

cv::Mat中,

cols代表影象的寬度(影象的列數),

rows代表影象的高度(影象的行數),

step代表以位元組為單位的影象的有效寬度,

elemSize返回畫素的大小,

channels()方法返回影象的通道數,

total函式返回影象的畫素數。

畫素的大小 = 顏色大小(位元組)*通道數,

比如:

三通道short型矩陣(CV_16SC3)的大小為2*3 = 6,

三通道Byte型矩陣(CV_8UC3)的大小為1*3= 3,畫素的channels方法返回影象的通道數,total函式返回影象的畫素數。

RGB影象的顏色數目是256*256*256,本文對影象進行量化,縮減顏色數目到256的1/8(即32*32*32)為目標,分別利用一下幾種方法實現,比較幾種方法的安全和效率。

方法一:使用Mat的成員函式ptr<>()

cv::Mat中提供ptr函式訪問任意一行畫素的首地址,特別方便影象的一行一行的橫向訪問,如果需要一列一列的縱向訪問影象,就稍微麻煩一點。但是ptr訪問效率比較高,程式也比較安全,有越界判斷。

int nl = image.rows; //行數  
int nc = image.cols * image.channels();
for (int j = 0; j<nl; j++)
{
	uchar* data = image.ptr<uchar>(j);
	for (int i = 0; i<nc; i++)
	{
		data[i] = data[i] / div*div + div / 2;
	}
}

方法二:使用迭代器遍歷影象

cv::Mat同樣有標準模板庫(STL),可以使用迭代器訪問資料。

用迭代器來遍歷影象畫素,可簡化過程降低出錯的機會,比較安全,不過效率較低;如果想避免修改輸入影象例項cv::Mat,可採用const_iterator。iterator有兩種呼叫方法,cv::MatIterator_<cv::Vec3b>it;cv::Mat_<cv::Vec3b>::iterator it;中間cv::Vec3b是因為影象是彩色影象,3通道,cv::Vec3b可以代表一個畫素。

cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
for (; it != itend; ++it)
{
	(*it)[0] = (*it)[0] / div*div + div / 2;
	(*it)[1] = (*it)[1] / div*div + div / 2;
	(*it)[2] = (*it)[2] / div*div + div / 2;
}

方法三:使用Mat的成員函式at<>()

cv::Mat也是向量,可以使at方法取值,使用呼叫方法image.at<cv::Vec3b>(j,i),at方法方便,直接給i,j賦值就可以隨意訪問影象中任何一個畫素,其中j表示第j行,i表示該行第i個畫素。但是at方法效率是這3中訪問方法中最慢的一個,所以如果遍歷影象或者訪問畫素比較多時,建議不要使用這個方法,畢竟程式的效率還是比程式的可讀性要重要的。下面是完整的呼叫方法,其執行時間在下面會介紹。

for (int j = 0; j< image.rows; j++)
{
	for (int i = 0; i< image.cols; i++)
	{
		image.at<cv::Vec3b>(j, i)[0] = image.at<cv::Vec3b>(j, i)[0] / div*div + div / 2;
		image.at<cv::Vec3b>(j, i)[1] = image.at<cv::Vec3b>(j, i)[1] / div*div + div / 2;
		image.at<cv::Vec3b>(j, i)[2] = image.at<cv::Vec3b>(j, i)[2] / div*div + div / 2;
	} // end of line                     
}

注意:使用at函式時,應該知道矩陣元素的型別和通道數,根據矩陣元素型別和通道數來確定at函式傳遞的型別,使用的是Vec3b這個元素型別,他是一個包含3個unsigned char型別向量。之所以採用這個型別來接受at的返回值,是因為,我們的矩陣im是3通道,型別為unsigned char型別

完整例項:

#include <iostream>  
#include < opencv.hpp>  
using namespace cv;
using namespace std;

int main()
{
	//新建一個uchar型別的3通道矩陣
	Mat img(5, 3, CV_8UC3, Scalar(50,50,50));
	cout << img.rows << endl; //5

	cout << img.cols << endl;  //3

	cout << img.channels() << endl;  //3

	cout << img.depth() << endl;  //CV_8U  0

	cout << img.dims << endl;  //2

	cout << img.elemSize() << endl;    //1 * 3,一個位置,三個通道的CV_8U
	cout << img.elemSize1() << endl;   //1
	
	cout << img.size[0] << endl;   //5
	cout << img.size[1] << endl;   //3

	cout << img.step[0] << endl;   //3 * ( 1 * 3 )
	cout << img.step[1] << endl;   //1 * 3

	cout << img.step1(0) << endl;  //3 * 3
	cout << img.step1(1) << endl;  //3

	cout << img.total() << endl;   //3*5

	//--------------------------------------          地址運算         --------------------------------//
	for (int row = 0; row < img.rows; row++)
	{
		for (int col = 0; col < img.cols; col++)
		{
			//[row, col]畫素的第 1 通道地址被 * 解析(blue通道)
			*(img.data + img.step[0] * row + img.step[1] * col) += 15;

			//[row, col]畫素的第 2 通道地址被 * 解析(green通道)
			*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) += 15;

			//[row, col]畫素的第 3 通道地址被 * 解析(red通道)
			*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) += 15;
		}
	}
	cout << img << endl;
	//--------------------------------------          Mat的成員函式at<>( )         --------------------------------//
	for (int row = 0; row < img.rows; row++)
	{
		for (int col = 0; col < img.cols; col++)
		{
			img.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
		}
	}
	cout << img << endl;
	//--------------------------------------         使用Mat的成員函式ptr<>()         --------------------------------//
	for (int row = 0; row < img.rows; row++)
	{
		// data 是 uchar* 型別的, m.ptr(row) 返回第 row 行資料的首地址
		// 需要注意的是該行資料是按順序存放的,也就是對於一個 3 通道的 Mat, 一個畫素3個通道值, [B,G,R][B,G,R][B,G,R]... 
		// 所以一行長度為:sizeof(uchar) * m.cols * m.channels() 個位元組 
		uchar* data = img.ptr(row);
		for (int col = 0; col < img.cols; col++)
		{
			data[col * 3] = 50;     //第row行的第col個畫素點的第一個通道值 Blue
		
			data[col * 3 + 1] = 50; // Green
		
			data[col * 3 + 2] = 50; // Red
		}
	}
	cout << img << endl;

	Vec3b *pix(NULL);
	for (int r = 0; r < img.rows; r++)
	{
		pix = img.ptr<Vec3b>(r);
		for (int c = 0; c < img.cols; c++)
		{
			pix[c] = pix[c] * 2;
		}
	}
	cout << img << endl;
	//--------------------------------------         使用Mat的成員函式ptr<>()         --------------------------------//	
	MatIterator_<Vec3b> it_im, itEnd_im;
	it_im = img.begin<Vec3b>();
	itEnd_im = img.end<Vec3b>();
	for(; it_im != itEnd_im; it_im++)
	{
		*it_im = (*it_im) * 2;
	}
	cout << img << endl;
	
	cvWaitKey();
	return 0;

}