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



1. 使用场景

使用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() 方法不同),相比动态地址法,性能会高一些。
  • 指针操作法:最快,因为执行效率通常是最高的,因为它直接操作内存,没有边界检查或迭代器进行遍历时的开销,但缺点就是指针容易越界。


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

posted @   做梦当财神  阅读(86)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
历史上的今天:
2021-09-03 Shell printf命令
2021-09-03 Shell echo命令
点击右上角即可分享
微信分享提示