独家|OpenCV 1.2 如何用OpenCV扫描图像、查找表和测量时间(附链接)

共 4773字,需浏览 10分钟

 ·

2021-07-30 23:38

翻译:陈之炎

校对:张一然、林夕


本文约4400字,建议阅读10分钟

本文为大家系统地介绍了OpenCV官方教程。


目标


在这里将寻求以下问题的答案:

  • 如何遍历图像的各个像素?

  • OpenCV的矩阵值是如何存储的?

  • 如何衡量算法的性能?

  • 什么是查找表,为什么要使用查找表?


测试案例


首先来考虑一个简单的减色方法。利用C和C ++的无符号字符(unsigned char)数据类型来存储矩阵项,像素的一个通道可以具备256个不同的值。对于一幅三通道的图像来说,可以构造出多种色彩(色彩数量可达16,000,000种)。数量众多的颜色会给算法的性能带来沉重的负担。然而,有些时候,往往利用较少的色彩数便能够获得同样的结果。

在这种情况下,常见的做法是减少色彩空间(color space reduction)。这意味着,将色彩空间的当前值除以一个新的输入值,从而减少颜色的数量。例如:将零和九之间的每一个值设置为零,十和十九之间的值设置为十等等。

当一个UCHAR 值(无符号字符unsigned char -0到255之间的值)除以INT值之后,其结果也将是一个数据类型为char的值,并且相除之后的结果值只能为char数据类型的值,小数部分将被舍去。利用C和C ++的这一优势,对 UCHAR域的操作可以表示为:

简单的减色算法将该公式应用于图像矩阵中的每个像素,值得一提的是:我们进行了一次除法和一次乘法运算,这两种运算会耗费昂贵的系统开销。如果可能的话,可以用一些开销相对来说比较小的操作来取代它们,如一些减法, 加法或者一些简单的赋值运算操作。此外,需要注意的是,上述操作的输入值的数量是有限的,对于UCHAR数据类型,准确地来讲,输入值的数量为256。

对于较大的图像,则是通过使用查找表,将事先计算好所有可能的值在赋值阶段直接进行赋值操作。查找表是具有一个或多个维度的简单数组,对于给定的输入值对应一个确定的输出值。它的优势在于:无需进行计算,便能读取到结果。

测试例程(和下述代码示例)将执行以下操作:利用命令行参数传递读取图像(可以是彩色图像或灰度图像),对给定命令行参数的整数值进行减色。在OpenCV中,主要有三种方式遍历图像的每个像素。为了增加实验的趣味性,会利用这三种方式扫描图像,并打印出各自花费的时长。

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


最后一个参数是可选项,除非加载的是给定的图像的灰度格式,否则默认使用BGR色彩空间。首先,需要做的第一件事是计算查找表。


首先,利用C ++的stringstream类将第三个命令行参数由文本格式转换为整数格式。然后,利用一个看似简单的公式计算查找表。此时,没有涉及到OpenCV的具体内容。

接下来的问题是如何测量时间?OpenCV提供了cv::getTickCount()和cv::getTickFrequency() 这两个简单的函数来实现时间的测量。第一个函数cv::getTickCount()返回返回某个事件(如启动系统)之后系统CPU 的嘀嗒(Tick)数量。第二个函数cv::getTickFrequency() 返回CPU每秒钟发出多少次嘀嗒声。有了这两个函数之后,便很容易测量出两个操作之间的时间间隔:

https://docs.opencv.org/4.5.2/db/de0/group__core__utils.html



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


在上一节Mat-基本图像容器教程中,讲到像素矩阵的大小取决于所使用的色彩系统。更准确地说,取决于所使用的色彩通道数。灰度图像的情况是这样的:



多通道图像的列包含许多子列,子列的数目即通道的数量。例如:BGR色彩系统图像的情况是这样的:

注意,在这里通道的顺序是相反的:在这里是BGR ,而不是RGB。因为在大多数情况下,内存足够大,可以一行接一行顺序存储,形成一个单一的长行,有助于加快扫描的速度。可以使用 cv::Mat::isContinuous()函数查询矩阵是否以这种方式存储。请继续阅读下一节中的示例。

cv::Mat::isContinuous()

https://docs.opencv.org/4.5.2/d3/d63/classcv_1_1Mat.html


最为有效的方法


通过经典C风格操作符(指针)的方式来获取数据是性能最好的方法,因此对于赋值我们推荐的最高效的方法是:


在这里,只需要获取每一行起始的指针,然后遍历到最后一行。在某些特殊情况下,像素矩阵以连续的方式存储,只需要一次“请求指针”的操作,便能一路到底遍历所有的像素。对于彩色图像有三个色彩通道,每一行需要遍历三次。

还有另一种方式:Mat 对象的数据成员data 会返回指向第一行、第一列的指针。如果这个指针为空,则这一对象中不存在有效的输入。利用这种简单的方法,可以检查图像是否成功加载。如果像素存储是连续的,我们可以用它来遍历所有的数据指针。如果是灰度图像, 代码应该是这样的:


上述两种方法会得出相同的结果。然而,这段代码阅读起来会困难得多。如果你有更高级的技术,它阅读起来会变得更加困难。此外,在实践中,得到的性能结果却是相同的(因为大多数现代编译器会自动对代码进行优化)。

迭代器(安全的)方法


在上述所讲的方法中,你要确保传入正确数量的uchar数据类型值,并跳过行与行之间的间隙,对于用户来说,迭代器方法(iterator method)被视为是一种更安全的方式, 因为它从用户那里接管了这些任务。利用迭代器方法,只需要找出图像矩阵的起始行和结尾行,从起始行开始迭代,直到到达结尾行。使用*运算符获取迭代器指向的值(在迭代器前添加该符号)。


对于彩色图像来说,每一列包含三个UCHAR数据项,可以将这三个数据项视为一个 UCHAR数据类型的短向量,在 OpenCV中,称之为 Vec3b。用简单的操作符[]访问第n个子列。需要记住的重点是:OpenCV的迭代器遍历这些列,并会自动跳到下一行。因此,在彩色图像的情况下,如果采用一个简单的UCHAR迭代器,只能访问到蓝色通道的值。

利用引用返回值计算即时地址


不推荐采用最后一种方法扫描图像。利用这种方法可以访问或修改图像中的随机像素,基本的用法是:指定需要访问元素所在的行数和列数。在前面所述的扫描方法中,需要指定数据类型,在这里同样如此,在自动查找之前,需要手动指定使用什么数据类型。你可以在以下源代码的灰度图像的情况下观察这一点(用到了+ cv::Mat::at() 函数)


该函数根据输入的数据类型和坐标,计算出查询项的地址,然后返回这个地址的引用值。当get 这个引用值时,会获得一个常量,当set 这个引用值,它是一个非常量。为了安全起见,仅在调试模式*,可以检查输入坐标是否有效,是否确实存在。如果不是在调试模式下,会有标准错误输出流的错误提示。相比于正式发布模式,二者唯一的区别是:对于图像的每一个元素,你将获得一个新的行指针,用于我们使用 C 运算符 [] 获取列元素的内容。

如果需要使用该方法对图像做多次查找时,输入数据类型和坐标的操作会相当麻烦和费时。为解决这一问题,OpenCV添加了 cv::Mat_ 数据类型,它与Mat类似,但额外需要在定义时通过要查看的数据矩阵的内容来指定数据类型,但好处是你可以使用()操作符快速访问矩阵值。更好的是,Mat和cv::Mat数据类型之间的可以很方便的进行转换。在上述示例中,可以看到这个函数在彩色图像中的应用。然而,需要注意的是:cv::Mat::at函数中已经包含了相同的操作(具有相同的运行速度)。它只是一个偷懒的编程技巧。

cv::Mat_ 

https://docs.opencv.org/4.5.2/df/dfc/classcv_1_1Mat__.html


cv::Mat::at

https://docs.opencv.org/4.5.2/d3/d63/classcv_1_1Mat.html


核心功能


这是在图像中修改查找表的一个额外奖励的方法。在图像处理中, 用户常常会希望将给定的图像值修改为其他值。OpenCV提供一个函数,利用这个函数,无需写入图像的扫描逻辑,便可修改图像的像素值。在这里,用到核心模块的cv::LUT() 函数。首先,创建一个Mat类型的查找表:

cv::LUT() 

https://docs.opencv.org/4.5.2/d2/de8/group__core__array.html



然后调用函数,(I是输入图像, J是输出):


性能差异对比


编译并运行程序以获得最佳结果。为使差别更加明晰,我用了一个相当大(2560 X 1600)的彩色图像。此处介绍的性能适用于彩色图像. 为了得到更准确的结果,我对上百次函数调用的结果做了平均。


可以得出以下结论:尽可能使用(而不是彻底改造已有函数)OpenCV已有的函数。LUT函数是最快的方法,因为OpenCV库可以通过英特尔线程构建模块启用多线程。然而,如果需要编写一个简单的图像扫描方法可选择指针方法,迭代器是一个更加安全的选择,但是速度相对来说要慢一些。在调试模式下,使用引用返回值访问方法扫描全图的代价最高;在正式发布模式下,可能会优于迭代方法,但它以牺牲迭代器的安全特性为代价。

最后,可以观看YouTube频道上发布的程序运行视频。

https://www.youtube.com/watch?v=fB3AN5fjgwc


编辑:王菁
校对:林亦霖

下一小节:1.3 矩阵的掩膜操作

往期回顾:
独家|OpenCV 1.1 Mat - 基本图像容器(附链接)




译者简介





陈之炎,北京交通大学通信与控制工程专业毕业,获得工学硕士学位,历任长城计算机软件与系统公司工程师,大唐微电子公司工程师,现任北京吾译超群科技有限公司技术支持。目前从事智能化翻译教学系统的运营和维护,在人工智能深度学习和自然语言处理(NLP)方面积累有一定的经验。业余时间喜爱翻译创作,翻译作品主要有:IEC-ISO 7816、伊拉克石油工程项目、新财税主义宣言等等,其中中译英作品“新财税主义宣言”在GLOBAL TIMES正式发表。能够利用业余时间加入到THU 数据派平台的翻译志愿者小组,希望能和大家一起交流分享,共同进步

翻译组招募信息

工作内容:需要一颗细致的心,将选取好的外文文章翻译成流畅的中文。如果你是数据科学/统计学/计算机类的留学生,或在海外从事相关工作,或对自己外语水平有信心的朋友欢迎加入翻译小组。

你能得到:定期的翻译培训提高志愿者的翻译水平,提高对于数据科学前沿的认知,海外的朋友可以和国内技术应用发展保持联系,THU数据派产学研的背景为志愿者带来好的发展机遇。

其他福利:来自于名企的数据科学工作者,北大清华以及海外等名校学生他们都将成为你在翻译小组的伙伴。


点击文末“阅读原文”加入数据派团队~



转载须知

如需转载,请在开篇显著位置注明作者和出处(转自:数据派ID:DatapiTHU),并在文章结尾放置数据派醒目二维码。有原创标识文章,请发送【文章名称-待授权公众号名称及ID】至联系邮箱,申请白名单授权并按要求编辑。

发布后请将链接反馈至联系邮箱(见下方)。未经许可的转载以及改编者,我们将依法追究其法律责任。



点击“阅读原文”拥抱组织



浏览 45
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报