【OpenCV Tutorials】How to scan images, lookup tables and time measurement with OpenCV

How to scan images, lookup tables and time measurement with OpenCV


注:本文仅作个人翻译学习记录,具体内容请以官方手册为准!


Prev Tutorial: Mat - The Basic Image Container
Next Tutorial: Mask operations on matrices

目标

我们将为以下问题寻找答案:

  • 如何遍历图像的每个像素?
  • OpenCV矩阵值是如何存储的?
  • 如何衡量我们算法的性能?
  • 什么是查找表,为什么要使用?

测试案例

让我们考虑一个简单的色彩还原方法。通过使用无符号字符C和C++类型存储矩阵项,一个像素通道最多可以有256个不同的值。对于三通道图像,这可能会形成太多的颜色(准确地说是1600万)。使用如此多的颜色阴影可能会给我们的算法性能带来沉重的打击。然而,有时用少得多的人就足以得到同样的最终结果。在这种情况下,我们通常会缩小颜色空间。这意味着我们用一个新的输入值来划分颜色空间的当前值,以较少的颜色结束。例如,0到9之间的每个值取新值0,10到19之间的每个值取值10,以此类推。当你用一个int值除一个uchar(unsigned char有又被称作0到255之间的值)时,结果也是char。这些值只能是字符值。因此,任何分数都会被向下舍入。利用这一事实,uchar域中的上操作可以表示为:
在这里插入图片描述
一个简单的色彩空间缩减算法将包括通过一个图像矩阵的每个像素,并应用这个公式。值得注意的是,我们做除法和乘法运算。这些操作对一个系统来说是非常昂贵的。如果可能的话,使用一些更便宜的操作来避免它们是值得的,比如一些减法、加法,或者在最好的情况下一个简单的赋值。此外,请注意,我们只有有限数量的输入值用于上层操作。在uchar系统的情况下,准确地说是256。

因此,对于较大的图像,明智的做法是事先计算所有可能的值,并在分配过程中使用查找表进行分配。查找表是简单的数组(具有一个或多个维度),对于给定的输入值变化,它保存最终的输出值。它的优点是我们不需要进行计算,我们只需要读取结果。

我们的测试用例程序(以及下面的代码示例)将执行以下操作:
读入一个作为命令行参数传递的图像(它可能是彩色的,也可能是灰度的),并用给定的命令行参数整数值进行缩减。在OpenCV中,目前有三种主要的方法逐个像素地浏览图像。为了让事情变得更有趣,我们将使用这些方法对图像进行扫描,并打印出扫描花费的时间。

您可以在 这里 下载完整的源代码,或者在OpenCV的示例目录中查找核心部分的cpp教程代码。它的基本用法是:

how_to_scan_images imageName.jpg intValueToReduce [G]

最后一个参数是可选的。如果给定,图像将以灰度格式加载,否则使用BGR颜色空间。第一件事是计算查找表。

int divideWith = 0; // convert our input string to number - C++ style 
stringstream s; 
s << argv[2]; 
s >> divideWith; 
if (!s || !divideWith) 
{ 
	cout << "Invalid number entered for dividing. " << endl; 
	return -1; 
} 

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

在这里,我们首先使用C++ stringstream类将第三个命令行参数从文本转换为整数格式。然后我们使用一个简单的外观和上面的公式来计算查找表。这里没有OpenCV具体的东西。另一个问题是我们如何测量时间?好了OpenCV提供了两个简单的函数来实现这个cv::getTickCount()cv::getTickFrequency()。第一个返回特定事件中系统CPU的节拍数(比如自系统启动以来)。第二个返回您的CPU在一秒钟内发出滴答的次数。因此,测量两次操作之间经过的时间很简单:

double t = (double)getTickCount(); 
// do something ... 
t = ((double)getTickCount() - t)/getTickFrequency(); 
cout << "Times passed in seconds: " << t << endl;

图像矩阵是如何存储在内存中的?

正如你已经在我的 Mat - The Basic Image Container 教程中读到的,矩阵的大小取决于所使用的颜色系统。更准确地说,这取决于使用的通道数量。在灰度图像的情况下,我们有如下内容:
在这里插入图片描述
对于多通道图像,列包含的子列与通道数一样多。例如,在BGR颜色系统的情况下:
在这里插入图片描述
请注意,通道的顺序是相反的:BGR而不是RGB。因为在许多情况下,内存足够大,可以以连续的方式存储行,所以行可能会一个接一个地出现,从而创建一个长行。因为所有东西都在一个地方,一个接一个,这可能有助于加快扫描过程。我们可以使用cv::Mat::is continuity()函数来询问矩阵是否是这种情况。继续下一节,找到一个例子。

有效的方法

说到性能,你无法击败经典的C风格operator[](指针)访问。因此,我们可以推荐的最有效的分配方法是:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table) 
{ 
    // accept only char type matrices 
    CV_Assert(I.depth() == CV_8U); 
    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) 
    { 
        p = I.ptr<uchar>(i); 
        for ( j = 0; j < nCols; ++j) 
        { 
            p[j] = table[p[j]]; 
        } 
    } 
    return I; 
} 

、、、

具体内容请查看官方手册:https://docs.opencv.org/master/db/da5/tutorial_how_to_scan_images.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值