OpenCV2.4+遍歷讀寫像素方法總結及時間度量


以下文本及代碼基本基於《OpenCV 2.4.13.0 documentation》的How to scan images, lookup tables and time measurement with OpenCV一節,英文好的同學可以直接看原文。

  • 1. 顏色壓縮

顏色壓縮(Color Reduction)最簡單的理解就是減少表示圖像的顏色數目,我們都知道,8位位深的3通道RGB真彩圖像包括了1600多萬(16777216)的顏色數目,其實在某些應用中用不到這么多數量(例如圖像傳輸(transmission)、分割(segmentation)、壓縮(compression))的顏色。這也是一個研究的小方向,想了解更多,可以閱讀文章Adaptive Color ReductionColor reduction and estimation of the number of dominant colors by using a self-growing and self-organized neural gas 。

在這里,我們實現一個很簡單的方法:

I_old 為輸入的像素值,I_new為輸出的像素值,divideWidth代表要減少的度,我們可以理解為當divideWidth為128的時候,對於灰度圖像就做的是一個閾值為128的二值化。

上點圖更直觀一點,左邊為灰度原始圖像,右邊為輸出圖像:

當divideWidth為128時:

 

當divideWidth為64時:

根據以上描述,實際上這個公式我們可以建立一個映射表來避免重復計算,對於0-255的有限的輸入值,建立輸出值的映射表:

// color space divide width
const int divideWidth = 128;
// converting table for reducing color space
uchar table[256];
// first, we should build the converting table
for (int i = 0; i < 256; i++)
{
table[i] = (uchar)(divideWidth * (i / divideWidth));
}

  

我們的測試程序做的就是:

1. 讀入一幅灰度圖像和一幅RGB彩色圖像;

2. 按照下文描述的四種訪問像素的方式來實現這個算法;

3. 多次分別跑算法,取平均,對四種訪問像素的方式進行對比。

  • 2. 圖像數據的存儲

首先大致說明下圖像數據如何在內存中存儲Mat是OpenCV2.x版本以上基本的圖像類型,Mat可以視為一個矩陣,矩陣的大小依賴於該Mat是什么顏色空間(Color Space),比如最基本的灰度(Gray scale)或者RGB,CMYK,YCbCr等,因為這決定了該Mat具有多少個通道,一般來講,灰度圖像只有一個通道,而RGB圖像具有三個通道。

對於灰度圖像來講,圖像數據在內存中的存儲如圖所示:

 

對於多通道圖像來講,有幾個通道,每一列就包含多少個子列。對於經常使用的基於RGB顏色空間,其圖像數據存儲如下:

 

需要注意的是通道的順序是BGR而非RGB。

一般而言,圖像數據的每一行在內存中都是連續存儲的,因為這樣對於遍歷圖像數據更高效。Mat提供了isContinuous()函數來獲取是否是連續存儲的數據。

  • 3. 時間的度量

OpenCV提供了兩個簡單的函數,getTickCount()getTickFrequency()。getTickCount返回從操作系統啟動到當前所經的計時周期數,類型為int64。getTickFrequency返回每秒的計時周期數,類型為double。因此就可以用如下的代碼計算以秒為單位的兩個操作所耗費的時間:

double dtime = (double) getTickCount();
// do something
dtime = ((double)getTickCount() - dtime)/getTickFrequency();
  • 4. 圖像像素的訪問方式

4.1 ptr操作和指針-高效的方式

這種方式基於.ptr和C的[]操作,這種方式也是比較推薦的遍歷圖像的方式。

/** @Method 1: the efficient method
 accept grayscale image and RGB image */
int ScanImageEfficiet(Mat & image)
{
	// channels of the image
	int iChannels = image.channels();
	// rows(height) of the image
	int iRows = image.rows;
	// cols(width) of the image
	int iCols = image.cols * iChannels;

	// check if the image data is stored continuous
	if (image.isContinuous())
	{
		iCols *= iRows;
		iRows = 1;
	}

	uchar* p;
	for (int i = 0; i < iRows; i++)
	{
		// get the pointer to the ith row
		p = image.ptr<uchar>(i);
		// operates on each pixel
		for (int j = 0; j < iCols; j++)
		{
			// assigns new value
			p[j] = table[p[j]];
		}
	}

	return 0;
}

這里獲取一個指向每一行的指針,然后遍歷這一行所有的數據。當圖像數據是連續存儲的時候,只需要取一次指針,然后就可以遍歷整個圖像數據。

4.2 迭代器-比較安全的方式

相較於高效的方式需要自己來計算需要遍歷的數據量,以及當圖像的行與行之間數據不連續的時候需要跳過一些間隙。迭代器(iterator)方式提供了一個更安全的訪問圖像像素的方式。你只需要做的就是聲明兩個MatIterator_變量,一個指向圖像開始,一個指向圖像結束,然后迭代。

/** @Method 2: the iterator(safe) method
 accept grayscale image and RGB image */
int ScanImageIterator(Mat & image)
{
	// channels of the image
	int iChannels = image.channels();

	switch (iChannels)
	{
	case 1:
	{
		MatIterator_<uchar> it, end;
		for (it = image.begin<uchar>(), end = image.end<uchar>(); it != end; it++)
		{
			*it = table[*it];
		}
		break;
	}
	case 3:
	{
		MatIterator_<Vec3b> it, end;
		for (it = image.begin<Vec3b>(), end = image.end<Vec3b>(); it != end; it++)
		{
			(*it)[0] = table[(*it)[0]];
			(*it)[1] = table[(*it)[1]];
			(*it)[2] = table[(*it)[2]];
		}
		break;
	}
	}

	return 0;
}

彩色圖像的話,由於是三個通道的向量,OpenCV提供了Vec3b的數據類型來存儲。

  • 4.3 動態地址計算-更適合隨機訪問的方式

這種方式不推薦用來遍歷圖像,一般用在要隨機訪問很少量的圖像數據的時候。基本用法就是指定行列號,返回該位置的像素值。不過需要你事先知道返回的數據類型是uchar還是Vec3b或者其他的。

/** @Method 3: random access method
 accept grayscale image and RGB image */
int ScanImageRandomAccess(Mat & image)
{
	// channels of the image
	int iChannels = image.channels();
	// rows(height) of the image
	int iRows = image.rows;
	// cols(width) of the image
	int iCols = image.cols;
	
	switch (iChannels)
	{
	// grayscale
	case 1:
	{
		for (int i = 0; i < iRows; i++)
		{
			for (int j = 0; j < iCols; j++)
			{
				image.at<uchar>(i, j) = table[image.at<uchar>(i, j)];
			}
		}
		break;
	}
	// RGB
	case 3:
	{
		Mat_<Vec3b> _image = image;
		for (int i = 0; i < iRows; i++)
		{
			for (int j = 0; j < iCols; j++)
			{
				_image(i, j)[0] = table[_image(i, j)[0]];
				_image(i, j)[1] = table[_image(i, j)[1]];
				_image(i, j)[2] = table[_image(i, j)[2]];
			}
		}
		image = _image;
		break;
	}
	}

	return 0;
}

4.4 查找表-一顆賽艇的方式

OpenCV大概也考慮到了有很多這種需要改變單個像素值的場合(比如基於單個像素值的亮度變換,gamma矯正等),因此在core模塊提供了一個更加高效很一顆賽艇的LUT()函數來進行這種操作而且不需要遍歷整個圖像。

首先建個映射查找表:

// build a Mat type of the lookup table
Mat lookupTable(1, 256, CV_8U);
uchar* p = lookupTable.data;
for (int i = 0; i < 256; i++)
{
	p[i] = table[i];
}

然后調用LUT()函數:

// call the function
LUT(image, lookupTable, matout);

image是輸入圖像,matout是輸出圖像。

  • 5. 不同方式的性能度量

測試環境:OpenCV版本3.1.0,Windows 7 64位系統。

測試圖像是512*512的Lena灰度圖和512*512的Lena彩色圖。分別跑100次不同的方法,然后得到的平均時間如下:

Debug版本:

Release版本:

總體時間表格如下:

 

灰度圖像

(Debug)

灰度圖像

(Release)

RGB圖像

(Debug)

RGB圖像

(Release)

高效的方式

1.1676

0.3039

3.5123

1.2646

迭代器的方式

151.4467

1.2219

270.9925

1.8997

隨機訪問的方式

78.6002

0.7484

328.5551

1.9967

LUT方式

0.7442

0.1687

2.1805

0.6941

 

可以看到不管Debug版本還是Release版本,性能上都是LUT方式>高效的方式>迭代器的方式和隨機訪問的方式。Debug版本后兩種方式花費時間更大,Release版本四種方式都差別不是太大。從中也可以看出Debug版本與Release版本之間性能間的巨大差異。

 

參考鏈接:

1. http://docs.opencv.org/2.4/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#howtoscanimagesopencv

2. http://blog.csdn.net/xiaowei_cqu/article/details/7771760


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM