前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >独家|OpenCV 1.2 如何用OpenCV扫描图像、查找表和测量时间(附链接)

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

作者头像
数据派THU
发布2023-03-29 13:16:51
8820
发布2023-03-29 13:16:51
举报
文章被收录于专栏:数据派THU

代码语言:javascript
复制
翻译:陈之炎
校对:张一然、林夕
本文约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】至联系邮箱,申请白名单授权并按要求编辑。

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据派THU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目标
  • 测试案例
  • 如何在内存中存储图像矩阵?
  • 最为有效的方法
  • 迭代器(安全的)方法
  • 利用引用返回值计算即时地址
  • 核心功能
  • 性能差异对比
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档