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

news2024/11/21 2:27:48

图像像素访问

    • 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 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;
}

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


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

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

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_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 matrices
    CV_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.coloradmin.cn/o/501969.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

tiechui_lesson06_注册表操作

这节课主要学习注册表的相关操作&#xff0c;包括注册表的打开&#xff0c;读取&#xff0c;修改&#xff0c;删除。可以自己通过底层API手动来获取和设置注册表&#xff0c;也可以用微软提供的运行时函数。我的看法是底层函数用来了解原理&#xff0c;真正在代码中使用的时候还…

《三十岁,一切刚刚开始》读书笔记

目录 书籍简介 经典语句 书籍简介 在抖音上看到此书&#xff0c;顺便看了看。 《三十岁&#xff0c;一切刚刚开始》作者李尚龙。写给敢于探险、敢于突破自己的年轻人的进取之书。人这一生可以有无数种可能。 如果你近期特别丧&#xff0c;或者正处于瓶颈期间&#xff0c;不…

VLAN【实验】

虚拟局域网VLAN VLAN即虚拟局域网&#xff0c;是将一个物理的局域网在逻辑上划分成为多个广播域的技术。通过在交换机上配置VLAN&#xff0c;可以实现在同一个VLAN内的用户进行二层互访&#xff0c;而不通的VLAN间的用户被二层隔离。这样既能够隔离广播域&#xff0c;又能够提…

身临其境文化之旅——VR全景图片畅游世界各地文化景点

文化旅游是一种独特的旅游体验&#xff0c;它不仅让人们感受到异域文化的魅力&#xff0c;更能增进对世界的了解和认知。VR全景图片和文化旅游之间有着密不可分的关系。作为一种数字化技术&#xff0c;VR全景图片可以为文化旅游提供更加深入、真实、立体的体验。随着VR技术的发…

品牌宣传软文发布,多久一次合适?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 品牌宣传有许多手段&#xff0c;线下活动&#xff0c;电视广告&#xff0c;讲座&#xff0c;招商等手段&#xff0c;其中软文发布是性价比较高的一种方式&#xff0c;那么软文宣传多久一…

神奇,声网Web SDK还能这么实现直播中美颜功能

前言 本篇文章是通过使用声网Web SDK来实现直播中美颜效果的深度体验文章&#xff0c;其中发现了屏幕共享并本地合图多个视频、图片&#xff0c;声网美颜插件等功能特性十分强大和专业&#xff0c;特为此做一个技术体验的分享&#xff0c;毕竟好技术就是要去传播的。 随着音视…

深入了解Facebook广告工具和资源,优化你的营销活动

作为企业主或营销人员&#xff0c;利用Facebook的广告工具和资源&#xff0c;可以帮助你更好地推广品牌、吸引潜在客户&#xff0c;并提升销售业绩。在本文中&#xff0c;我们将深入了解Facebook的广告工具和资源&#xff0c;帮助你优化营销活动&#xff0c;实现更好的效果。 1…

Ubuntu多卡服务器、普通用户安装paddlepaddle环境

Ubuntu多卡服务器、普通用户安装paddlepaddle环境 1. 建立conda虚拟环境2. 安装paddlepaddle gpu版本2.1 选择cuda版本2.2 安装paddle 3. 验证及排错3.1 验证方法3.2 第一次报错&#xff1a;cuda问题3.3 第二次报错&#xff1a;NCCL问题&#xff08;多卡&#xff09; 4. 设置环…

echarts 地图_地图 json 免费下载_自定义icon

Echarts 常用各类图表模板配置 注意&#xff1a; 这里主要就是基于各类图表&#xff0c;更多的使用 Echarts 的各类配置项&#xff1b; 以下代码都可以复制到 Echarts 官网&#xff0c;直接预览&#xff1b; 图标模板目录 Echarts 常用各类图表模板配置一、地图二、环形图三、…

Microsoft Edge浏览器崩溃,错误代码: STATUS_STACK_BUFFER_OVERRUN

--------------------8日15:34更新------------------- 8号下午经历了短暂的兴奋&#xff0c;突然就好了&#xff0c;但是转瞬即逝&#xff0c;试用一会后就不行了。 目前使用beta版本是很稳定的。 最新版的beta也可以&#xff0c;注意数据同步。 --------------------原文-…

ChatGPT的Prompts关键词提示工程集合:包含AI绘画和GPT文本对话

文章目录 1 前言2 Awesome ChatGPT Prompts2.1 文本提示词2.2 提示词汇总2.3 绘画提示词 3 PromptBase4 Prompt-Engineering-Guide5 讨论 1 前言 明确一个概念&#xff0c;ChatGPT是一种大型的自然语言处理模型&#xff0c;它基于深度神经网络和语言模型技术&#xff0c;可以通…

c++中vector初始化的一个有趣的细节问题

vector的初始化&#xff0c;相信会写c的人都很熟悉&#xff0c;c11对此也改进了很多&#xff0c;更方便。 以下是使用vector初始化的几个例子&#xff1a; 从数组初始化vector&#xff1a; int arr[] {1, 2, 3, 4, 5}; vector<int> vec(arr, arr sizeof(arr) / size…

定时清理文件脚本

一、定时清理文件 编写一个bat脚本。新建一个文本文档&#xff08;txt文件&#xff09;&#xff0c;在里面输入echo offdel /f /s /q E:\temp\*.*&#xff0c;E代表E盘&#xff0c;temp是E盘下的需要清理的文件夹&#xff0c;运行脚本后&#xff0c;就是清理E:\temp文件夹下的…

Win下查看端口占用情况并释放该端口

开发中&#xff0c;经常会遇到端口意外被占用&#xff0c;需释放该端口的问题。一般解决思路是找到被占用端口关联的进程号&#xff0c;即PID&#xff0c;后通过PID终止该进程以释放该端口。 netstat 命令 显示协议统计信息和当前 TCP/IP网络连接,常用参数: -a: 显示所有连接和…

Packet Tracer - 配置基于区域的策略防火墙 (ZPF)

Packet Tracer - 配置基于区域的策略防火墙 (ZPF) 拓扑图 地址分配表 设备 接口 IP 地址 子网掩码 默认网关 交换机端口 R1 G0/1 192.168.1.1 255.255.255.0 不适用 S1 F0/5 S0/0/0 (DCE) 10.1.1.1 255.255.255.252 不适用 不适用 R2 S0/0/0 10.1.1.2 255…

项目管理工具的必备功能,你需要知道这些

一个项目有很多事情需要计划、控制和管理&#xff0c;因此需要使用项目管理软件来对项目进行管理&#xff0c;除了制定计划、进度跟踪等之外&#xff0c;项目管理软件还应该有这些功能&#xff1a; 甘特图&#xff1a;帮你规划任务。比如说甘特图就可以设置多级任务、耗时和分…

AI与全民开发:挑战和机会并存

注&#xff1a;全民开发的英文是Citizen Development&#xff0c;由咨询公司Gartner在2010年提出的概念&#xff0c;指非专业开发人员使用低代码或无代码平台创建应用程序&#xff0c;无需IT部门的支持&#xff0c;旨在提高生产力并降低开发成本。 国内普遍将Citizen Developme…

dubbo服务导出源码解析

服务导出流程 需要对源码debug&#xff0c;可以参考官网的源码分析进行debug&#xff1a;https://cn.dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/接收到 ContextRefreshedEvent 刷新事件&#xff0c;调用 ServiceBean.export()&#xff0c;会执行 ServiceCon…

刷力扣 LeetCode 算法题需要充值会员吗?

一、刷题成就 大家好&#xff0c;我是『负雪明烛』。 在过去的这些年里&#xff0c;我的一项业余爱好就是写作算法题解。如今写了上千篇题解了&#xff01; 在 CSDN 上&#xff0c;我的博客获得了 200 多万的阅读。 在力扣中国题解区&#xff0c;我也获得了180 万的阅读。…

我忽然发现周围同事都在无效内卷

想写这篇文章已经很久了&#xff0c;在三月份的时候就想写这篇文章了。 可三月份那时候需求比较多&#xff0c;每天下班时间基本都在九点多了&#xff0c;回到家就想躺着&#xff0c;压根不想写&#xff1b;四月份则是2022年度绩效评比沟通&#xff0c;一月时间又没了&#xff…