OpenCV4 学习指导2 —— 多种方式访问图像的像素值

news/2024/10/18 20:21:00/

图像像素访问

    • 1、测试用例
      • 1.1、颜色缩减算法
      • 1.2、颜色缩减示例
    • 2、图像矩阵的存储与访问
      • 2.1、图像的存储方式
      • 2.2、图像的访问方式
        • 2.2.1、C 指针:高效的访问方式
        • 2.2.2、迭代器:安全的访问方式
        • 2.2.3、行列索引:动态计算地址
        • 2.2.4、LUT 查询函数
      • 2.3、访问性能对比
    • 3、参考资源


1、测试用例

1.1、颜色缩减算法

让我们考虑一个简单的颜色缩减方法(color reduction method)。通过使用无符号字符 C 和 C + + 类型来存储矩阵元素,一个像素通道最多可以有256个不同的值。对于一个三通道图像,可以组合成1600万多种颜色。使用如此多的色调可能会给我们的算法性能带来沉重的负担。然而,有时候只要少用一点点就能得到相同的最终结果。

在这种情况下,我们通常做一个颜色空间缩减。这意味着我们将颜色空间当前值除以一个新的输入值,以得到更少的颜色。例如,新的值【0】代替(0~ 9)之间的每个值,新的值【10】都接受(10~19)之间的每个值接受10,以此类推。

当你用一个 int 值除一个 uchar (无符号 char,值在0~255之间)值时,结果也是 char。这些值可能只是字符值。因此,任何分数都将被四舍五入。利用这个事实,uchar 域中的操作可以表示为
I n e w = ( I o l d 10 ) ∗ 10 I_{new}=(\frac{I_{old}}{10})*10 Inew=(10Iold)10

一个简单的颜色空间缩减算法包括:访问图像矩阵的每个像素和应用这个公式。值得注意的是,我们执行了除法和乘法运算。对于一个系统来说,这些操作是非常昂贵的。如果可能的话,值得通过使用更高效的操作来避免它们,比如减法、加法,或者在最好的情况下使用简单的赋值。此外,请注意,对于上面的操作,我们只有有限数量的输入值。在 【uchar】系统中,这是【256】。

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

1.2、颜色缩减示例

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

./how_to_scan_images <imageNameToUse> <divideWith> [G]

最后一个命令行参数(G)是可选的,如果没有该参数默认处理彩色图,否则将图像转为灰度图处理。首先,我们需要计算查询表,代码片段如下:

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

2、图像矩阵的存储与访问

2.1、图像的存储方式

正如你已经在我的 Mat-The Basic Image Container 教程中读到的,矩阵的大小取决于所使用的颜色系统。更准确地说,它取决于所使用的通道的数量。对于灰度图像,我们有类似于下图所示,
在这里插入图片描述
对于多通道图像,每一列包含的子列与通道数目一样多。例如,在 BGR 颜色系统的情况下,像素分布如下图所示,比如【0,0】位置包含三个子列,对应BGR通道,
在这里插入图片描述
注意,通道的顺序是相反的:BGR 而不是 RGB。因为在许多情况下,内存足够大,可以以连续的方式存储行,从而创建一个单独的长行(long row)。因此,所有的像素都一个接一个存于同一片内存区域,这有助于加快像素的访问速度。我们可以使用 cv::Mat::isContinure()函数来询问像素在内存中是否连续存储。


2.2、图像的访问方式

2.2.1、C 指针:高效的访问方式

在性能方面,最快的仍是经典的 C 样式 operator[] (pointer)访问。因此,我们推荐的最高效的方法如下:

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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;
}

这里我们基本上只是获取一个指向每行开始的指针,然后遍历它直到结束。在矩阵以连续方式存储的特殊情况下,我们只需要请求指针一次,就可以一直到结束,否则我们需要获取多次行首指针。对于彩色图像:我们有三个通道,所以我们需要在每一行移动指针三倍的次数。


2.2.2、迭代器:安全的访问方式

为了提高效率,确保通过恰当数量的 uchar 字段并跳过行之间可能出现的空白(意味着内存不连续)。迭代器方法被认为是一种更安全的方法,因为它从用户那里接管了这些任务。所有您需要做的就是获取图像矩阵的开始和结束位置,然后只需累加开始迭代器直到结束。要获取迭代器指示位置的值,可以使用 * 操作符(在它之前添加它),具体代码如下:

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{// accept only char type matricesCV_Assert(I.depth() == CV_8U);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;
}

对于彩色图像,每列有三个 uchar 元素,这可能被认为是 uchar 元素的一个简短3元素长度的向量,它已经在 OpenCV 中以 Vec3b 名称命名(赋予具体的数据类型)。要访问第 n 个子列,我们使用简单的 operator[] 访问。重要的是,OpenCV 迭代器遍历列并自动跳到下一行。因此,在彩色图像的情况下,如果使用一个简单的 uchar 迭代器,能够只访问蓝色通道值。

涉及知识点:

  1. MatIterator_:CV的迭代器;
  2. 获取矩阵开始和结束的指针:I.begin()<Vec3b>I.end()<Vec3b>
  3. 获取具体的值:指针位置前添加星号,( ∗ i t *it it)[];
  4. Vec3b的数据定义,如下图,它可以存储3个uchar 值,
    在这里插入图片描述

2.2.3、行列索引:动态计算地址

最后一种方法不推荐用于访问图像。它被用来获取或修改图像中的随机元素。它的基本用法是指定要访问元素的行号和列号。在我们早期的像素访问的方法中,很重要的一点是要明确要访问图像的数据类型。这里没有什么不同,因为您需要在自动查找中手动指定要使用的类型。对于以下源代码(cv::Mat::at ()函数的用法)的灰度图像,可以观察到这一点:

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{// accept only char type matricesCV_Assert(I.depth() == CV_8U);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.at<Vec3b>(i,j)[0],需要指定数据类型_I(i,j)[1] = table[_I(i,j)[1]];  // 或者 _I.at<Vec3b>(i,j)[1]_I(i,j)[2] = table[_I(i,j)[2]];  // 或者 _I.at<Vec3b>(i,j)[2]}I = _I;break;}}return I;
}

补充知识点:

  1. Mat_ 继承 Mat,包含了一些特殊的处理方法,具体描述如下图
    这里是引用
  1. 类 Mat _ < _ Tp > 是 Mat 类之上的一个简化版模板包装器。它没有任何额外的数据字段。这个类和 Mat 都没有任何虚方法。因此,对这两个类的引用或指针可以自由地相互转换,但也需注意一下具体写法。参考下面的代码片段,
// 创建 100x100 8-bit 矩阵
Mat M(100,100,CV_8U);
// this will be compiled fine. no any data conversion will be done.
Mat_<float>& M1 = (Mat_<float>&)M;
  1. 多通道图像或矩阵使用Mat_,传递 Vec 作为 Mat_ 参数,举例如下
// 创建 320x240 彩色图像,填充为绿色的值
Mat_<Vec3b> img(240, 320, Vec3b(0,255,0));
// 对角的像素值改为白色
for(int i = 0; i < 100; i++)img(i,i)=Vec3b(255,255,255);

2.2.4、LUT 查询函数

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

Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)p[i] = table[i];

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

LUT(I, lookUpTable, J);

2.3、访问性能对比

图像大小为 512x512,循环运行1000次,平均每次运行时间如下图:
在这里插入图片描述

3、参考资源

  • How to scan images, lookup tables and time measurement with OpenCV
  • 官方示例代码文件

http://www.ppmy.cn/news/64545.html

相关文章

百度搜索排名的提升,就靠这10个优化技巧!

随着互联网的快速发展&#xff0c;网站排名已经成为了企业竞争的一个重要指标。其中&#xff0c;百度搜索排名的提升更是众多企业都非常关注的问题。因此&#xff0c;在这篇文章中&#xff0c;我将为大家介绍10个优化技巧&#xff0c;以帮助企业提升百度搜索排名。 1.关键词优化…

模型类的编写有没有什么靠谱的优化方法?

模型类的编写需要私有属性&#xff0c;setter…getter…方法、toString方法 和构造函数。虽然这些内容不难&#xff0c;同时也都是通过IDEA工具生成的&#xff0c;但是过程还是必须得走一遍&#xff0c;那么对于模型类的编写有没有什么优化方法?可以通过Lombok来实现优化。 L…

【OAI】OAI5G核心网VPP-UPF网元分析

文章目录 VPP_UPF_CONFIG_GENERATION.mdVPP UPF Configuration GenerationEnvironment variablesInterfacesInterface Configuration ExamplesCentral UPFA-UPFI-UPFUL CL FEATURE_SET.mdVPP_UPG_CLI参考文献 VPP_UPF_CONFIG_GENERATION.md VPP UPF Configuration Generation …

在线文档编辑工具哪个更好?

在线文档编辑工具相当于一个轻量级、跨平台、多途径的Office。使用在线文档编辑工具&#xff0c;首先我们不用安装Office软件&#xff1b;其次在电脑网页上、手机小程序里我们都可以使用在线文档进行简单的编辑&#xff1b;最后我们编辑的文档可以实时更新、分享、协作等。今天…

软考--数据传输控制方式总结(DMA/程序控制/程序中断)含例题

数据传输控制方式&#xff1a;这里指的是存储器与外设、外设与外设之间直接交换数据控制问题。 总述 1.程序控制方式【程序查询方式】&#xff1a; 【最低级&#xff0c;CPU介入最多】&#xff0c;这个过程中外设不会主动反馈信息&#xff0c;全过程都要CPU参与&#xff0c;C…

实时聊天如何做,让客户眼前一亮(一)

网站上的实时聊天功能应该非常有用&#xff0c;因为它允许客户支持立即帮助用户。在线实时聊天可以快速轻松地访问客户服务部门&#xff0c;而它也代表着企业的门面。 让我们讨论一下如何利用SaleSmartly&#xff08;ss客服&#xff09;在网站中的实时聊天视图如何提供出色的实…

2023 FP独立站的运营玩法汇总

无论Paypal怎么风控封号、冻结资金&#xff0c;但还是有很多新人前仆后继地加入外贸独立站这一行&#xff0c;特别是做FP。因为明白人都知道&#xff0c;FP利润空间比普货产品大得多。 那么2023年的独立站我们该怎么做&#xff0c;怎么运营才能逐步起色&#xff1f;今天我就跟…

大数据 | (五)通过Sqoop实现从MySQL导入数据到HDFS

知识目录 一、前言二、导入前的准备2.1 Hadoop集群搭建2.2 Hadoop启停脚本 三、docker安装MySQL四、安装Sqoop4.1 Sqoop准备4.2 Sqoop连接Mysql数据测试 五、导入MySQL数据到hdfs5.1 准备MySQL数据5.2 导入数据 六、Sqoop现状七、结语 一、前言 各位CSDN的朋友们大家好&#x…