opencv 2.x学习笔记(六) 扫描、遍历图像

在上一篇中,我们简单的提了一下,可以利用at函数和类似于STL中的迭代器风格来访问矩阵。接下来,我们将详细的介绍着两种访问图像矩阵方法。并且同时介绍另外两种遍历图像的方法,并且来分析每种方法遍历一次图像所要花费的时间。

为了得到每种发发遍历一次图像所花费的时间,我们可以使用OpenCV为我们提供的两简单的计时函数。getTickCount()和getTickFrequency()。

  • getTickCount(): 返回CPU从某个事件以来走过的时钟周期数。这个事件可以是开机等。
  • getTickFrequency():返回CPU一秒钟所走的时钟周期数。

那么,我们就可以这样使用来或得某个方法所运行的时间(秒数)。

double exec_time = (double)getTickCount();
// 方法的调用
exec_time = ((double)getTickCount() - exec_time) / getTickFrequency();

一般我们为了提高精度(假设方法的使用需要时间不是太长),我们一般选择使用毫秒作为单位,而不是秒。即上面的一句代码可改为如下:

exec_time = ((double)getTickCount() - exec_time) * 1000 / getTickFrequency();
在我们以后的工作中,经常需要对一个图像的的数据进行处理。这里,我们采用“颜色空间缩减“来进行说明。所谓颜色空间缩减是指,把原来图像的颜色空间缩小。我们知道对于一张图像,它的类型往往是uchar类型。即可以在0-255之间取任意值。而每种值都代表不一样的颜色。现在,我们把这个颜色空间进行缩减为0-9取0值,10-19取10值,以此类推。即公式可以表示为I = (I / 10) * 10。为了更快的进行这种变换,我们可以使用一个table表,把256种情况所对应的新值都记录在表中,扫描图像时,对每一个值都进行查找替换。而不是,扫描图像,然后对图像的每一个值都利用这个公式进行计算新值。从而,提高了效率。table表的构建如下所示:

	uchar table[256];
	for( int i = 0; i < 256; ++ i )
		table[i] = (uchar)(10 * (i / 10));

方法一:高效的方法

在这种方法中,我们使用Mat类的ptr方法获得图像矩阵的每一行出的指针,然后使用[]操作符遍历每一个元素。如果矩阵是以连续方式存储的,我们只需要请求一次指针、然后一路遍历下去就可以了。我们可以使用Mat类的isContinuous方法来判断矩阵是否是连续存储的。代码如下:

Mat& ScanImageEfficient( Mat& I, const uchar * const table )
{
	int channels = I.channels();
	int nRows = I.rows;
	/* 如果是灰度图像,channels为1,彩色图像为3*/
	int nCols = I.cols * channels;

	/* 如果矩阵是连续存储的,行数为1 */
	if( I.isContinuous() )
	{
		nCols = nCols * nRows;
		nRows = 1;
	}

	int i, j;
	uchar * p;
	for( i = 0; i < nRows; ++ i )
	{
		/* 获取每一行的指针 */
		p = I.ptr<uchar>(i);
		for( j = 0; j < nCols; ++ j )
		{
			/* 从table表中查找对应值,并进行替换 */
			p[j] = table[p[j]];
		}
	}
	return I;
}

方法二:迭代器方法

在上面的方法中,需要我们来手动的进行换行操作。OpenCV为我们提供了一种类似于STL中迭代器的方法来遍历图像,通常,我们认为这种方法是最安全的。在使用这种方法时,我们仅需要或得图像矩阵的的begin和end,然后把begin逐渐增加迭代直到end,使用*操作符,金科访问当前begin指向的内容。

Mat& ScanImageIterator( Mat& I, const uchar * const table )
{
	const int channels = I.channels();
	switch( channels )
	{
	case 1:
		{
			/* 如果是灰度图像,start每次指向的值为单个值,即亮度 */
			MatIterator_<uchar> start, end;
			for( start = I.begin<uchar>(), end = I.end<uchar>(); start != end; ++ start )
				*start = table[*start];
			break;
		}
	case 3:
		{
			/* 如果是彩色图像,start每次指向的是代表BGR的向量值 
			   Vec3b 即为 Vec<uchar, 3>*/
			MatIterator_<Vec3b> start, end;
			for( start = I.begin<Vec3b>(), end = I.end<Vec3b>(); start != end; ++ start )
			{
				(*start)[0] = table[(*start)[0]];
				(*start)[1] = table[(*start)[1]];
				(*start)[2] = table[(*start)[2]];
			}
			break;
		}

	return I;
}

方法三:随机访问方法

在上一篇中,我们提到,可以使用at方法来进行访问矩阵中的每个值。所以,我们可以这样来遍历图像。

Mat& ScanImageRandomAccess( Mat& I, const uchar* const table )
{
	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.at<Vec3b>(i, j)[0] = table[I.at<Vec3b>(i, j)[0]];
					I.at<Vec3b>(i, j)[1] = table[I.at<Vec3b>(i, j)[1]];
					I.at<Vec3b>(i, j)[2] = table[I.at<Vec3b>(i, j)[2]];
					//_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;
		}
	}
	return I;
}
值得一提的是,在上面代码的注释部分,给了我们另一种更为方便的访问方法,我们首先定义了Mat_类对象 _I。 Mat_是从Mat类继承而来的,它通过重载了()操作符,为我们提供了更方便的访问矩阵元素的方法。即我们可以简单的通过_I(i, j)来访问第i行第j列的元素,我们可以很方便的把Mat对象转换成Mat_类对象,就像上面代码里所显示的那样。我们一般推荐使用Mat_类的方法,它的效率和at方法的效率是一样的。

方法四:LUT方法

这是一个最为推荐使用的实现批量图像元素查找和更改图像的方法。LUT是OpenCV里为我们提供的一个实现这种功能的函数,使用该函数并不需要我们自己去实现扫描图像的过程。LUT的原型如下:

//! transforms array of numbers using a lookup table: dst(i)=lut(src(i))
CV_EXPORTS_W void LUT(InputArray src, InputArray lut, OutputArray dst,
                      int interpolation=0);
使用这个方法,我们需要一个lookup表。构建这个表的过程如下:

    Mat lookUpTable(1, 256, CV_8U);
    for( int i = 0; i < 256; ++i)
	lookUpTable.data[i] = table[i];
然后我们直接调用LUT函数,如下所示:

LUT(I, lookUpTable, J);
I是输入图像,lookUpTable是lookup表,J是输出图像。

综合实例

#include <cv.h>
#include <highgui.h>
#include <iostream>

using namespace std;
using namespace cv;

Mat& ScanImageEfficient( Mat& I, const uchar * const table );
Mat& ScanImageIterator( Mat& I, const uchar * const table );
Mat& ScanImageRandomAccess( Mat& I, const uchar* const table );

int main( int argc, char * argv[] )
{
	cout << "四种遍历图像方法的比较!" << endl;

	if( argc < 2 )
	{
		cout << "输入参数错误!" << endl;
		return  -1;
	}
	Mat I;
	if( argc == 3 && !strcmp(argv[2], "G") )
		I = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
	else
		I = imread( argv[1], CV_LOAD_IMAGE_COLOR );

	if( !I.data )
	{
		cout << "图像没有被正确加载!" << endl;
		return -1;
	}

	uchar table[256];
	for( int i = 0; i < 256; ++ i )
		table[i] = (uchar)(10 * (i / 10));

	double exec_time;
	exec_time = (double)getTickCount();
	for( int i = 0; i < 100; ++ i )
	{
		Mat I1 = I.clone();
		ScanImageEfficient( I1, table );
	}
	exec_time = ((double)getTickCount() - exec_time) * 1000 / getTickFrequency();
	exec_time /= 100;
	cout << "100次采用高效的方法遍历图像的平均时间是:" << exec_time << "毫秒。" << endl;

	exec_time = (double)getTickCount();
	for( int i = 0; i < 100; ++ i )
	{
		Mat I2 = I.clone();
		ScanImageIterator( I2, table );
	}
	exec_time = ((double)getTickCount() - exec_time) * 1000 / getTickFrequency();
	exec_time /= 100;
	cout << "100次采用迭代的方法遍历图像的平均时间是:" << exec_time << "毫秒。" << endl;

	exec_time = (double)getTickCount();
	for( int i = 0; i < 100; ++ i )
	{
		Mat I3 = I.clone();
		ScanImageRandomAccess( I3, table );
	}
	exec_time = ((double)getTickCount() - exec_time) * 1000 / getTickFrequency();
	exec_time /= 100;
	cout << "100次采用随机访问的方法遍历图像的平均时间是:" << exec_time << "毫秒。" << endl;

	Mat lookUpTable(1, 256, CV_8U);
	Mat J;
    for( int i = 0; i < 256; ++i)
		lookUpTable.data[i] = table[i];

	exec_time = (double)getTickCount();
    for (int i = 0; i < 100; ++i)
        LUT(I, lookUpTable, J);
    exec_time = ((double)getTickCount() - exec_time) * 1000 / getTickFrequency();
    exec_time /= 100;
    cout << "100次采用LUT的方法遍历图像的平均时间是:" << exec_time << "毫秒。" << endl;

	return 0;
}

Mat& ScanImageEfficient( Mat& I, const uchar * const table )
{
	int channels = I.channels();
	int nRows = I.rows;
	/* 如果是灰度图像,channels为1,彩色图像为3*/
	int nCols = I.cols * channels;

	/* 如果矩阵是连续存储的,行数为1 */
	if( I.isContinuous() )
	{
		nCols = nCols * nRows;
		nRows = 1;
	}

	int i, j;
	uchar * p;
	for( i = 0; i < nRows; ++ i )
	{
		/* 获取每一行的指针 */
		p = I.ptr<uchar>(i);
		for( j = 0; j < nCols; ++ j )
		{
			/* 从table表中查找对应值,并进行替换 */
			p[j] = table[p[j]];
		}
	}
	return I;
}

Mat& ScanImageIterator( Mat& I, const uchar * const table )
{
	const int channels = I.channels();
	switch( channels )
	{
	case 1:
		{
			/* 如果是灰度图像,start每次指向的值为单个值,即亮度 */
			MatIterator_<uchar> start, end;
			for( start = I.begin<uchar>(), end = I.end<uchar>(); start != end; ++ start )
				*start = table[*start];
			break;
		}
	case 3:
		{
			/* 如果是彩色图像,start每次指向的是代表BGR的向量值 
			   Vec3b 即为 Vec<uchar, 3>*/
			MatIterator_<Vec3b> start, end;
			for( start = I.begin<Vec3b>(), end = I.end<Vec3b>(); start != end; ++ start )
			{
				(*start)[0] = table[(*start)[0]];
				(*start)[1] = table[(*start)[1]];
				(*start)[2] = table[(*start)[2]];
			}
			break;
		}
	}
	return I;
}

Mat& ScanImageRandomAccess( Mat& I, const uchar* const table )
{
	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.at<Vec3b>(i, j)[0] = table[I.at<Vec3b>(i, j)[0]];
					I.at<Vec3b>(i, j)[1] = table[I.at<Vec3b>(i, j)[1]];
					I.at<Vec3b>(i, j)[2] = table[I.at<Vec3b>(i, j)[2]];
					//_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;
		}
	}
	return I;
}

运行结果:

我们采用一个大小为627*616大小的图像进行测试,并对每个分别运行100次,每种方法的平均运行时间测试结果如下:

结论:

在使用LUT方法时,获得最快的运行速度。所以我们在以后的程序中,应尽量选择OpenCV的内置函数。迭代器方法是一种比较安全访问图像的方法,但是速度较慢。最糟糕的方法是随机访问的方法,我们一般用这种方法来获取某一个指定位置的值,但一般不用来去图像进行扫描。(注:在release模式下,该方法和迭代器的方法运行效率差不多)。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值