首页 > 其他分享 >OpenCV(图像像素遍历方法比较)

OpenCV(图像像素遍历方法比较)

时间:2024-09-03 11:05:43浏览次数:5  
标签:遍历 Mat 迭代 操作法 矩阵 像素 OpenCV getTickCount B2

目录



1. 使用场景

使用OpenCV,避免不了的就是对图像像素进行操作,遍历操作更是家常便饭,当图像数据不多时,各类方法的速度差不太多;但面对数据计算次数非常多,尤其在进行迭代、拟合、递归等数据反复计算的工作时,此时不得不面对的就是程序性能、算法提速和优化的问题了。

在OpenCV中有三种最常用的像素操作方法,分别是动态地址操作法、迭代器操作法和指针操作法。下面简单介绍下该三种方法的优劣和使用场景:

  • 动态地址操作法。最常用,比如直接A.at,操作Mat矩阵A第i行第j列的像素值,简洁明了,适合对某个或某几个值直接操作时使用,定位非常方便,也可以遍历计算使用,但是速度方面比其他两个方法都要慢;
  • 迭代器操作法。运用了C++中STL的理念,通过迭代器的方式,获取矩信息的头尾(begin和end),然后依次操作,该方法适合连续计算时使用,速度和动态地址操作法差不多,比指针法慢,但是胜在安全性好;
  • 指针法。我最喜欢的遍历法,一遇到遍历操作必用ptr,该方法缺点就是指针容易越界,编代码时要慎重,确保安全和稳定,但是速度没得说。

三种方法一般情况在debug下运行差异性很明显,release下没那么明显;但是一旦遍历的函数复杂性加大了,release下的差异性就会体现出来了,指针法绝对是最快的,我在做图像迭代拟合计算时,用指针法替代动态地址法,速度至少提高5-10倍,一点不夸张。。。



2. 示例代码

#include<iostream>
#include<opencv2/opencv.hpp>
#include<ctime>
using namespace std;
using namespace cv;
int main(void)
{
	Mat A = Mat::zeros(10000, 10000, CV_32FC1);
	// 随意创建一个A矩阵
	for (int i = 0; i < A.rows; i++)
	{
		for (int j = 0; j < A.cols; j++)
		{
			A.at<float>(i, j) = rand()%100/100.f;
		}
	}
	Mat B0,B1,B2;
	B0 = A.clone();
	B1 = A.clone();
	B2 = A.clone();
 
	// 动态地址操作法
	double time0 = static_cast<double>(getTickCount());
	for (int i = 0; i < A.rows; i++)
	{	
		for (int j = 0; j < A.cols; j++)
		{
			B0.at<float>(i, j) *= 2;
		}
	}
	time0 = ((double)getTickCount() - time0) / getTickFrequency();
	cout << "    动态地址法运行时间为:" << time0 << "秒" << endl << endl;
 
	// 迭代器操作法
	double time1 = static_cast<double>(getTickCount());
	Mat_<float>::iterator it = B1.begin<float>();
	Mat_<float>::iterator itend = B1.end<float>();
	for (; it!=itend; ++it)
	{
		(*it)*= 2;
	}
	time1 = ((double)getTickCount() - time1) / getTickFrequency();
	cout << "    迭代器法运行时间为:" << time1 << "秒" << endl << endl;
 
	// 指针操作法
	double time2 = static_cast<double>(getTickCount());
	for (int i = 0; i < A.rows; i++)
	{	
		float *data = B2.ptr<float>(i);
		for (int j = 0; j < A.cols; j++)
		{
			data[j]*= 2;
		}
	}
	time2 = ((double)getTickCount() - time2) / getTickFrequency();
	cout << "    指针法运行时间为:" << time2<< "秒" << endl << endl;
 
	system("pause");
	return 0;
}


3. 示例代码解析

使用OpenCV库来处理矩阵操作,并通过三种不同的方式对矩阵元素进行操作,比较它们的执行时间。

3.1 头文件和命名空间

#include<iostream>
#include<opencv2/opencv.hpp>
#include<ctime>
using namespace std;
using namespace cv;
  • #include<iostream>:引入标准输入输出流库,用于控制台输出。
  • #include<opencv2/opencv.hpp>:引入OpenCV库的头文件,提供矩阵操作的功能。
  • #include<ctime>:提供时间相关函数库,这里其实没有用到(应该是冗余的,因为计时使用了OpenCV自带的计时函数)。
  • using namespace std;using namespace cv;:为了方便使用标准库和OpenCV库中的符号。


3.2 主函数 main

3.2.1 创建并初始化矩阵A

Mat A = Mat::zeros(10000, 10000, CV_32FC1);
  • 创建一个10000×10000的矩阵 A,数据类型为单通道32位浮点数(CV_32FC1),矩阵所有元素初始值为0。

3.2.2 随机初始化矩阵A的值

for (int i = 0; i < A.rows; i++) {
    for (int j = 0; j < A.cols; j++) {
        A.at<float>(i, j) = rand() % 100 / 100.f;
    }
}
  • 使用两重循环给矩阵 A 的每个元素赋值,值为随机生成的0到1之间的浮点数。
  • rand() % 100 生成0到99的整数。
  • / 100.f 将其转为浮点数并缩放至[0, 1)的范围。

3.2.3 克隆矩阵

Mat B0, B1, B2;
B0 = A.clone();
B1 = A.clone();
B2 = A.clone();
  • 创建三个矩阵 B0, B1, B2,并分别通过 A.clone() 初始化为矩阵 A 的副本。使用 clone() 确保 B0, B1, B2A 是独立的对象,不会共享内存。

3.2.4 使用动态地址操作法进行矩阵操作并计时

double time0 = static_cast<double>(getTickCount());
for (int i = 0; i < A.rows; i++) {
    for (int j = 0; j < A.cols; j++) {
        B0.at<float>(i, j) *= 2;
    }
}
time0 = ((double)getTickCount() - time0) / getTickFrequency();
cout << "动态地址法运行时间为:" << time0 << "秒" << endl << endl;

动态地址法:每次访问矩阵元素时,at() 函数内部会进行边界检查,这就是所谓的动态地址法。

  • getTickCount() 用于获取系统时钟周期数。返回值是一个 int 类型。
  • static_cast 是 C++ 中的一种类型转换运算符,用于在不同的数据类型之间进行安全转换。将 getTickCount() 返回的时钟周期数(int 类型)转换为 double 类型。
  • B0.at<float>(i, j) 获取矩阵元素的值并对其每个元素的值乘以2。
  • 通过 getTickCount() 的差值除以 getTickFrequency()(表示每秒的时钟周期数),计算代码段的运行时间。

3.2.5 使用迭代器操作法进行矩阵操作并计时

double time1 = static_cast<double>(getTickCount());
Mat_<float>::iterator it = B1.begin<float>();
Mat_<float>::iterator itend = B1.end<float>();
for (; it != itend; ++it) {
    (*it) *= 2;
}
time1 = ((double)getTickCount() - time1) / getTickFrequency();
cout << "迭代器法运行时间为:" << time1 << "秒" << endl << endl;

迭代器操作法:迭代器在遍历时不会进行边界检查(与 at() 方法不同),因此相比动态地址法,性能会高一些,但不如指针操作法(直接访问矩阵的内存)的性能高。

  • Mat_<float>::iterator
    • OpenCV 提供了 Mat_ 模板类用于方便操作矩阵的元素,Mat_<float>Mat_ 的一个具体实例,它表示一个浮点类型(float)的矩阵。
    • iterator 是迭代器类型,用于遍历矩阵中的元素。
  • B1.begin<float>()B1.end<float>() 获取矩阵 B1 的起始(矩阵首元素)和结束迭代器(最后一个元素的后一个位置),迭代器类型为 Mat_<float>::iterator
  • *it 解引用迭代器,获取当前迭代器指向的矩阵元素。通过 (*it) *= 2,将当前元素的值乘以 2。
  • 计算代码段的运行时间。

3.2.6 使用指针操作法进行矩阵操作并计时

double time2 = static_cast<double>(getTickCount());
for (int i = 0; i < A.rows; i++) {
    float* data = B2.ptr<float>(i);
    for (int j = 0; j < A.cols; j++) {
        data[j] *= 2;
    }
}
time2 = ((double)getTickCount() - time2) / getTickFrequency();
cout << "指针法运行时间为:" << time2 << "秒" << endl << endl;

指针法:执行效率通常是最高的,因为它直接操作内存,没有边界检查或迭代器进行遍历时的开销。

  • 第一层循环:遍历行
    • float* data = B2.ptr<float>(i);:获取指向第 i 行的指针,然后可以用该指针直接访问该行的所有元素。
    • B2.ptr<float>(i):获取矩阵 B2 的第 i 行的首地址(即该行第一个元素的指针)。
      • ptr<float>(i) 是 OpenCV 中 Mat 类的一个成员函数,返回一个指向矩阵第 i 行的第一个元素的指针,类型为 float*(假设矩阵为浮点型 CV_32FC1)。
      • 该指针 data 可以直接用于访问第 i 行的所有元素。
  • 第二层循环:遍历列
    • j 用于遍历矩阵第 i 行的所有列。
    • data[j]:使用指针偏移量直接访问矩阵第 i 行第 j 列的元素(data[j] 相当于 B2.at<float>(i, j))。
      • 因为 data 是指向 B2i 行的指针,因此 data[j] 就是矩阵 B2i 行第 j 列的元素。
      • 该行代码将矩阵 B2 的第 i 行第 j 列的元素乘以2。
  • 计算代码段的运行时间。

3.2.7 系统暂停

system("pause");
  • 等待用户输入以暂停程序,防止程序结束后窗口自动关闭(在Windows系统上)。


4. 总结

  • 动态地址操作法:最慢的。因为每次访问矩阵元素时,at() 函数内部会进行边界检查。
  • 迭代器操作法:稍快。因为迭代器在遍历时不会进行边界检查(与 at() 方法不同),相比动态地址法,性能会高一些。
  • 指针操作法:最快,因为执行效率通常是最高的,因为它直接操作内存,没有边界检查或迭代器进行遍历时的开销,但缺点就是指针容易越界。

程序的设计目的是对比三种方法的运行效率,并展示如何在OpenCV中操作大规模矩阵数据。



来自:https://zhaitianbao.blog.csdn.net/article/details/116268352

标签:遍历,Mat,迭代,操作法,矩阵,像素,OpenCV,getTickCount,B2
From: https://www.cnblogs.com/keye/p/18394161

相关文章

  • 【全网独家】OpenCV基本结构(Point、Size、Rect等)
    1.简介OpenCV(OpenSourceComputerVisionLibrary)是一个开源的计算机视觉和机器学习软件库。它提供了上千种图像处理和计算机视觉算法,广泛应用在各种领域,如机器人学、实时图像处理、模式识别等。2.基本结构介绍PointPoint用于表示图像中的二维点。可以用Point_<T>模......
  • OpenCV(cv::Mat 类的成员函数 ptr<T>())
    目录1.函数定义2.功能3.示例4.注意事项总结在OpenCV中,Mat类的成员函数ptr<T>()用于获取指向矩阵数据的指针,允许直接访问底层数据。T是指针的类型,它通常与矩阵的元素类型对应。通过这个函数,你可以以一种低开销的方式访问和修改矩阵数据。1.函数定义template<type......
  • opencv学习:基础用法和图像添加边界框设置
    1.opencv基础用法1.从文件加载图像a=cv2.imread("1.jpg")b=cv2.imread("2.jpg")2.从文件读取视频video=cv2.VideoCapture("1.mp4")3.读摄像头cv2.VideoCapture(0)4.保存图片cv2.imwrite(r"1_gray.jpg",b)5.展示图片cv2.imshow("shuiju",a)6.释......
  • [JS] 数组空位与遍历方法
    当数组中存在空位时,遍历数组需要选择合适的方法,不同的方法可能返回不同的结果。示例数组:constarr=[1,2,,3,4];数组空位不会影响数组长度,arr的长度是5。for循环最朴素的for循环会遍历到数组的每一位,对于空位,访问时返回undefined。for(leti=0;i<arr.length;i++......
  • 《深度学习》OpenCV 图像边缘检测 算法解析及代码演示
    目录一、图像边缘检测1、什么是边缘检测2、常用的边缘检测算法        1)Sobel算子    2)Scharr算子        3)Canny边缘检测算法        4)Laplacian算子3、边缘检测流程        1)预处理        2)计算梯度     ......
  • [JS] 数组空位与遍历方法
    当数组中存在空位时,遍历数组需要选择合适的方法,不同的方法可能返回不同的结果。示例数组:constarr=[1,2,,3,4];数组空位不会影响数组长度,arr的长度是5。for循环最朴素的for循环会遍历到数组的每一位,对于空位,访问时返回undefined。for(leti=0;i<arr.length;i++......
  • 25版王道数据结构课后习题详细分析 第六章 图 6.2图的遍历
    一、单项选择题————————————————————————————————————————解析:广度优先搜索以起始结点为中心,一层一层地向外层扩展遍历图的顶点,因此无法考虑到边权值,只适合求边权值相等的图的单源最短路径。广度优先搜索相当于树的层序遍历,选项Ⅲ......
  • 【OpenCV】快速入门(二)--视频处理(1)
    OpenCV–视频处理先看代码#include<iostream>#include"opencv2/highgui/highgui.hpp"#include"opencv2/imgproc/imgproc.hpp"intmain(intargc,char**argv){cv::namedWindow("Example3",cv::WINDOW_AUTOSIZE);cv::VideoCaptu......
  • Java二叉树的遍历以及最大深度问题
    Java学习+面试指南:https://javaxiaobear.cn1、树的相关概念1、树的基本定义树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构、等等。树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为......
  • 线性表:顺序表的实现以及遍历扩容
    Java学习手册+面试指南:https://javaxiaobear.cn顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来......