如何使用 OpenCV 扫描图像、查找表和时间测量

本文探讨了如何通过OpenCV实现颜色空间缩减,介绍了几种像素扫描方法,如C风格操作符[]、迭代器和查找表,以及LUT函数在性能上的优势。同时提到了大模型AI的学习路径和资源分享。
摘要由CSDN通过智能技术生成

我们的测试用例

让我们考虑一种简单的颜色减少方法。通过使用无符号字符 C 和 C++ 类型进行矩阵项存储,像素通道最多可以有 256 个不同的值。对于三通道图像,这可能会导致形成太多的颜色(准确地说是 1600 万)。使用如此多的色调可能会对我们的算法性能造成沉重打击。但是,有时只需使用更少的人即可获得相同的最终结果。

在这种情况下,我们通常会减少色彩空间。这意味着我们将颜色空间当前值除以新的输入值,最终得到更少的颜色。例如,0 到 9 之间的每个值都采用新值 0,10 到 19 之间的每个值都采用值 10,依此类推。

当您将 uchar(无符号 char - 又名介于 0 和 255 之间的值)值与 int 值相除时,结果也将是 char。这些值只能是 char 值。因此,任何分数都将向下舍入。利用这一事实,uchar 域中的上层操作可以表示为:

在这里插入图片描述

一个简单的色彩空间缩减算法将包括仅通过图像矩阵的每个像素并应用此公式。值得注意的是,我们进行了除法和乘法运算。对于系统来说,这些操作的成本非常高。如果可能的话,值得通过使用更便宜的操作来避免它们,例如一些减法、加法或在最好的情况下进行简单的赋值。此外,请注意,我们只有有限数量的上层操作的输入值。在 uchar 系统的情况下,确切地说是 256。

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

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

您可以在此处下载完整的源代码,也可以在 OpenCV 的 samples 目录中查找核心部分的 cpp 教程代码。它的基本用法是:

how_to_scan_images imageName.jpg intValueToReduce [G]

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

​ int 除以 = 0;将输入字符串转换为数字 - C++ 样式

字符串流 s;

s << argv[2];

s >> divideWith;

​ if (!s || !divideWith)

​ {

cout << “输入的用于除法的无效数字。 ” << endl;

​ 返回 -1;

​ }

乌查尔表[256];

​ for (int i = 0; i < 256; ++i)

table[i] = (uchar)(除法 * (i/divideWith));

在这里,我们首先使用 C++ stringstream 类将第三个命令行参数从文本转换为整数格式。然后我们使用简单的外观和上面的公式来计算查找表。这里没有特定于 OpenCV 的东西。

另一个问题是我们如何测量时间?OpenCV提供了两个简单的函数来实现这个 cv::getTickCount() 和 cv::getTickFrequency() 。第一个返回某个事件(例如自您启动系统以来)的系统 CPU 的滴答数。第二个返回 CPU 在一秒钟内发出滴答声的次数。因此,测量两次操作之间经过的时间就像以下几点一样简单:

double t = (double)getTickCount();

做点什么。。。

t = ((double)getTickCount() - t)/getTickFrequency();

cout << “以秒为单位的时间:” << t << endl;

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

正如您已经在我的 Mat - 基本图像容器教程中读到的那样,矩阵的大小取决于所使用的颜色系统。更准确地说,这取决于使用的通道数量。对于灰度图像,我们有如下内容:

tutorial_how_matrix_stored_1.png

对于多通道图像,列包含的子列数与通道数一样多。例如,在BGR颜色系统的情况下:

tutorial_how_matrix_stored_2.png

请注意,通道的顺序是相反的:BGR 而不是 RGB。因为在许多情况下,内存足够大,可以连续存储行,所以行可能会一个接一个地跟随,从而形成一个长行。因为所有内容都在一个地方,所以一个接一个地进行,这可能有助于加快扫描过程。我们可以使用 cv::Mat::isContinuous() 函数来询问矩阵是否是这种情况。继续下一部分以查找示例。

高效的方式

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

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)

{

​ 仅接受 char 类型矩阵

CV_Assert(I.depth() == CV_8U);

​ int 通道 = I.channels();

​ int nRows = I.rows;

​ int nCols = I.cols * 通道;

​ 如果 (I.isContinuous())

​ {

nCols *= n行;

n行 = 1;

​ }

​ 整数 i,j;

uchar* p;

​ for( i = 0; i < nRows; ++i)

​ {

p = I.ptr<uchar>(i);

​ for ( j = 0; j < nCols; ++j)

​ {

p[j] = 表[p[j]];

​ }

​ }

​ 返回我;

}

在这里,我们基本上只是获取一个指向每行开头的指针,并遍历它直到它结束。在矩阵以连续方式存储的特殊情况下,我们只需要请求指针一次,然后一直到最后。我们需要注意彩色图像:我们有三个通道,因此我们需要在每行中通过三倍以上的项目。

还有另一种方法。Mat 对象的数据成员返回指向第一行第一列的指针。如果此指针为 null,则该对象中没有有效的输入。检查这是检查图像加载是否成功的最简单方法。如果存储是连续的,我们可以使用它来遍历整个数据指针。如果是灰度图像,这将如下所示:

uchar* p = I.数据;

for( unsigned int i = 0; i < ncol*nrows; ++i)

*p++ = 表[*p];

你会得到相同的结果。但是,此代码稍后更难阅读。如果你在那里有一些更先进的技术,那就更难了。此外,在实践中,我观察到你会得到相同的性能结果(因为大多数现代编译器可能会自动为你制作这个小的优化技巧)。

迭代器(安全)方法

在有效方法的情况下,请确保您通过适量的 uchar 字段并跳过行之间可能出现的间隙是您的责任。迭代器方法被认为是一种更安全的方法,因为它从用户手中接管了这些任务。您需要做的就是询问图像矩阵的开始和结束,然后增加开始迭代器,直到到达末尾。若要获取迭代器指向的值,请使用 * 运算符(在它前面添加它)。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)

{

​ 仅接受 char 类型矩阵

CV_Assert(I.depth() == CV_8U);

​ const int 通道 = I.channels();

​ switch(通道)

​ {

​ 案例一:

​ {

MatIterator_ 它,结束;

​ for( it = I.begin(), end = I.end<uchar>(); it != end; ++it)

*it = table[*it];

​ 破;

​ }

​ 案例3:

​ {

MatIterator_ 它,结束;

​ for( it = I.begin(), end = I.end<Vec3b>(); it != end; ++it)

​ {

(*它)[0] = 表[(*it)[0]];

(*它)[1] = 表[(*it)[1]];

(*它)[2] = 表[(*it)[2]];

​ }

​ }

​ }

​ 返回我;

}

对于彩色图像,我们每列有三个 uchar 项目。这可以被认为是 uchar 项目的短向量,它已在 OpenCV 中以 Vec3b 名称受洗。要访问第 n 个子列,我们使用简单的 operator[] 访问。重要的是要记住,OpenCV 迭代器会遍历列并自动跳到下一行。因此,对于彩色图像,如果您使用简单的 uchar 迭代器,您将只能访问蓝色通道值。

动态地址计算和引用返回

不建议使用最后一种方法进行扫描。它是为了获取或修改图像中的随机元素而制作的。它的基本用途是指定要访问的项的行号和列号。在我们早期的扫描方法中,您已经注意到,我们通过哪种类型查看图像很重要。这里没有什么不同,因为您需要手动指定在自动查找时使用的类型。在以下源代码的灰度图像中,您可以观察到这一点(+ cv::Mat::at() 函数的使用):

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)

{

​ 仅接受 char 类型矩阵

CV_Assert(I.depth() == CV_8U);

​ const int 通道 = I.channels();

​ switch(通道)

​ {

​ 案例一:

​ {

​ 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)];

​ 破;

​ }

​ 案例3:

​ {

Mat_ _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;

​ 破;

​ }

​ }

​ 返回我;

}

该函数采用您的输入类型和坐标,并计算查询项的地址。然后返回对该引用的引用。当您获取该值时,这可能是一个常量,而在设置该值时,这可能是一个非常量。作为调试模式下的安全步骤*,将检查输入坐标是否有效且确实存在。如果不是这种情况,您将在标准错误输出流上收到一条很好的输出消息。与发布模式下的有效方法相比,使用它的唯一区别是,对于图像的每个元素,您将获得一个新的行指针,用于我们使用 C 运算符 [] 来获取列元素。

如果需要使用此方法对图像进行多次查找,则为每个访问输入类型和 at 关键字可能会很麻烦且耗时。为了解决这个问题,OpenCV 有一个 cv::Mat_ 数据类型。它与 Mat 相同,但需要额外的需求,即在定义时,您需要通过查看数据矩阵来指定数据类型,但是作为回报,您可以使用 operator() 来快速访问项目。为了让事情变得更好,这很容易从通常的 cv::Mat 数据类型转换。在上面函数的彩色图像的情况下,您可以看到它的示例用法。不过,需要注意的是,可以使用 cv::Mat::at 函数完成相同的操作(具有相同的运行时速度)。这只是为懒惰的程序员技巧编写的少一些内容。

核心功能

这是在图像中实现查找表修改的一种奖励方法。在图像处理中,通常希望将所有给定的图像值修改为其他值。OpenCV提供了修改图像值的功能,无需编写图像的扫描逻辑。我们使用核心模块的 cv::LUT() 函数。首先,我们构建一个 Mat 类型的查找表:

垫子 lookUpTable(1, 256, CV_8U);

uchar* p = lookUpTable.ptr();

​ for( int i = 0; i < 256; ++i)

p[i] = 表[i];

最后调用函数(I 是我们的输入图像,J 是输出图像):

LUT(I, lookUpTable, J);

性能差异

为获得最佳效果,请编译程序并自行运行。为了更清楚地说明差异,我使用了一张相当大的 (2560 X 1600) 图像。此处介绍的性能适用于彩色图像。为了获得更准确的值,我将从函数调用中获得的值平均了一百次。

方法时间
高效方式79.4717 毫秒
迭 代83.7201 毫秒
即时 RA93.7878 毫秒
LUT 函数32.5759 毫秒

我们可以得出几点结论。如果可能的话,使用 OpenCV 已经制作的函数(而不是重新发明这些函数)。最快的方法是 LUT 函数。这是因为 OpenCV 库是通过英特尔线程构建模块实现多线程的。但是,如果您需要编写简单的图像扫描,则首选指针方法。迭代器是一个更安全的选择,但速度要慢得多。在调试模式下,使用动态引用访问方法进行完整映像扫描的成本最高。在发布模式下,它可能会击败迭代器方法,但肯定会为此牺牲迭代器的安全特性。

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

在这里插入图片描述

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

😝有需要的小伙伴,可以Vx扫描下方二维码免费领取==🆓

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值