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,B2与A是独立的对象,不会共享内存。 
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是迭代器类型,用于遍历矩阵中的元素。
- OpenCV 提供了 
 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行的所有元素。 
- ptr<float>(i) 是 OpenCV 中 
 
 - 第二层循环:遍历列
j用于遍历矩阵第i行的所有列。data[j]:使用指针偏移量直接访问矩阵第i行第j列的元素(data[j]相当于B2.at<float>(i, j))。- 因为 
data是指向B2第i行的指针,因此data[j]就是矩阵B2第i行第j列的元素。 - 该行代码将矩阵 
B2的第i行第j列的元素乘以2。 
- 因为 
 
 - 计算代码段的运行时间。
 
3.2.7 系统暂停
system("pause");
- 等待用户输入以暂停程序,防止程序结束后窗口自动关闭(在Windows系统上)。
 
4. 性能比较
- 动态地址操作法:最慢的。因为每次访问矩阵元素时,
at()函数内部会进行边界检查。 - 迭代器操作法:稍快。因为迭代器在遍历时不会进行边界检查(与 
at()方法不同),相比动态地址法,性能会高一些。 - 指针操作法:最快,因为执行效率通常是最高的,因为它直接操作内存,没有边界检查或迭代器进行遍历时的开销,但缺点就是指针容易越界。
 
来自:https://zhaitianbao.blog.csdn.net/article/details/116268352
                    
                
                
            
        
浙公网安备 33010602011771号