OpenCV学习笔记3 OpenCV核心模块与核心功能Core Module & Core Functionality(二)

2.2 OpenCV中遍历图像,查找表和时间测量

查找表(look up table),以少量空间节约大量时间

原理百度 查找表 即可。


时间测量:

使用OpenCV提供的两个函数getTickCount()和getTickFrequency()。

double t = (double)getTickCount();
// do something ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << t << endl;

图像矩阵在内存中的表示:
RGB图像在内存中存放时颜色通道的顺序是 BGR。
许多情况下图像矩阵在内存中是一行一行连续存储的,这样有助于加快图像的遍历过程。可以使用 isContinuous()函数检测图像矩阵是不是连续存储的(为了效率)。如果是连续存储的,实际上图像矩阵数据就是存储为内存中的一行。

高效的方法(The Efficient Way):
为了性能不得不使用C风格的运算符 [ ] 进行数据访问,推荐的内存访问做法如下:
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
	// accept only char type matrices
	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;
	uchar* p;
	for( i = 0; i < nRows; ++i)<span style="white-space:pre">	</span>// 核心思想
	{
		p = I.ptr<uchar>(i);
		for ( j = 0; j < nCols; ++j)
		{
			p[j] = table[p[j]];
		}
	}
	return I;
}
如果图像矩阵是连续存储在内存中的,只要知道数据起始的指针和数据大小,就能高效率地访问了。
还有一种方法,Mat对象的data数据成员返回指向图像矩阵首行首列的指针。判断指针是否为空可以知道对象是否有效,从而检查图像是否成功读入Mat对象中。如果图像是连续存储,可用如下方式访问图像数据矩阵(灰度图,单通道):
uchar* p = I.data;
for( unsigned int i =0; i < ncol*nrows; ++i)
*p++ = table[*p];
但是实现高效率的同时这种方法也是不安全的,需要程序员保证得到了正确的数据大小(小心越界),以及跳过了正确的存储空隙(不连续的情况下)。

迭代器(安全的)方法(Iterator):
使用迭代器,程序员就不用考虑上面的问题了。只需要获得图像矩阵的begin和end迭代器,传统方式一直 ++ 即可。迭代器会自动跳到下一行并跳过内存中不连续的地方。使用解引用运算符 * 获得访问值。但是这种方法会损失效率。
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
	// accept only char type matrices
	CV_Assert(I.depth() != sizeof(uchar));
	const int channels = I.channels();
	switch(channels)
	{
	case 1:
		{
			MatIterator_<uchar> it, end;
			for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
				*it = table[*it];
			break;
		}
	case 3:
		{
			MatIterator_<Vec3b> 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]];
			}
		}
	}
	return I;
}
上述代码中,彩色图像对应的三通道情况下,使用vector存储每个像素三个通道的值,OpenCV中专门有一个Vec3b数据类型,相当于 vector<unsigned char>。

随机引用访问(On-the-fly address calculation with reference returning):
随机访问方法不推荐用于全图像的遍历。它只是用来随机访问图像中任意一个像素,可以读取或修改该像素,这里用到  at() 函数。无论用何用方法访问图像矩阵,都需要指定数据类型。因此,需要给函数提供访问数据类型,数据所在的行号和列号,代码如下:
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
	// accept only char type matrices
	CV_Assert(I.depth() != sizeof(uchar));
	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;
		}
	}
	return I;
}
at() 函数输入数据类型和坐标,返回相应像素值的 引用。读操作是一个const函数,写操作是非const函数。 仅在debug模式下,函数会检查输入的坐标是否存在和有效,无效则通过标准错误输出流给出错误提示信息。相比于在release模式的高效率(第一种)方法,使用该函数的唯一的区别是,对图像中的每一个元素,你会得到一个行指针,以便于我们用 C风格的 [ ] 运算符获得列元素。
因为每次查找都要输入数据类型和坐标,用这种方法在一幅图像中多次执行查找操作是麻烦和费时的。为了解决这个问题,OpenCV提供了一个 Mat_ 的数据类型。它和 Mat 数据类型唯一的不同是需要额外指定 Mat_ 对象存储的数据矩阵的数据类型。它可以使用 () 运算符来快速访问元素,也可以容易地与 Mat 对象相互转换类型。上述代码中对彩色图像的处理(case 3)就是使用的 Mat_ 对象。


使用核心函数(The Core Function)
图像处理中经常需要将一幅图像中的所有值都替换成其他值,通过查找表可以高效实现这种替换。OpenCV提供了一个函数,使程序员不用手动地遍历图像,查表,写入替换值,而是直接 利用  LUT()  实现 。实现代码如下:
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
p[i] = table[i];

LUT(I, lookUpTable, J);
首先,创建一个1行256列的Mat对象用来存储查找表,然后调用 LUT() 函数实现转换。函数的参数中I是输入图像,J是输出图像。函数进一步的具体用法可查看 build/doc 目录中的opencv2refman.pdf 。


性能比较
教程中对上述四种方法进行了性能比较,采用了大小为 2560×1600 的彩色图像进行遍历替换,每种方法运行了上百次取平均值,得到如下结果:
Efficient Way79.4717 ms
Iterator83.7201 ms
On-The-Fly RA93.7878 ms
LUT function32.5759 ms
总结: OpenCV自身提供的函数 LUT()  速度最快,这主要是因为OpenCV的库是通过Intel Threaded Building Blocks实现了多线程。如果是简单的图像遍历,可以使用第一种方法。迭代器方法损失了速度获得了安全性。Debug模式下的随机引用访问是最耗时的,Release模式下的随机引用访问效率和迭代器方法相当,但是它牺牲了迭代器具有的安全性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值