[OpenCV实战]27 在OpenCV下使用forEach进行并行像素访问
目录
1.4 将forEach与C ++ 11 Lambda一起使用
C++11扩展了for语句的语法。用这个新写法forEach,forEach可以遍历C类型的数组、初始化列表以及任何重载了非成员的begin()和end()函数的类型。OpenCV的Mat数据结构中有用到ForEach的写法。在本教程中,我们将比较Mat类的forEach方法与OpenCV中访问和转换像素值的其他方法的性能。我们将展示forEach如何比使用at方法或甚至有效地使用指针算法更快。因此本文只有C++ forEach用法介绍。Python下实现容易多,搜索即可。
OpenCV中有隐藏的功能,有时候并不是很有名。其中一个隐藏的功能是Mat类的forEach方法,它利用机器上的所有核心在每个像素上处理任何功能。
我们先来定义一个函数complexThreshold。它接收RGB像素值并对其应用复杂的阈值分割。代码如下:
// Define a pixel
typedef Point3_<uint8_t> Pixel;
// A complicated threshold is defined so
// a non-trivial amount of computation
// is done at each pixel.
void complicatedThreshold(Pixel &pixel)
{
if (pow(double(pixel.x)/10,2.5) > 100)
{
pixel.x = 255;
pixel.y = 255;
pixel.z = 255;
}
else
{
pixel.x = 0;
pixel.y = 0;
pixel.z = 0;
}
}
与简单阈值相比,该函数在计算量要多得多。这样我们不仅可以测试像素访问时间,还可以了解每个像素操作在计算量很大时forEach如何使用CPU所有核心。接下来,我们将介绍将四种不同的方法应用于图像中的每个像素并检查相对性能。
1 Mat像素访问
1.1 使用at方法直接进行像素访问
Mat类有一个方便的方法,用于访问图像中位置(行,列)的像素。以下代码使用at方法访问每个像素并对其应用complexThreshold。代码如下:
//循环测试numTrials次
for (int n = 0; n < numTrials; n++)
{
// Naive pixel access at方法直接读取数据
// Loop over all rows 遍历行
for (int r = 0; r < image.rows; r++)
{
// Loop over all columns 遍历列
for (int c = 0; c < image.cols; c++)
{
// Obtain pixel at (r, c) 直接访问像素数据
Pixel pixel = image.at<Pixel>(r, c);
// Apply complicatedTreshold 阈值分割
complicatedThreshold(pixel);
// Put result back 保存结果
image.at<Pixel>(r, c) = pixel;
}
}
}
上述方法被认为是低效的,因为每次调用at方法时都会计算存储器中像素的位置。这涉及乘法运算,而不使用像素位于连续的存储器块中相关特性。
1.2 使用指针进行像素访问
在OpenCV中,一行中的所有像素都存储在一个连续的内存块中。如果使用create创建 Mat对象,则所有像素都存储在一个连续的内存块中。由于我们正在从磁盘读取图像的imread方法会使用create方法创建一个Mat对象,因此我们可以使用不需要乘法,而通过简单指针算法简单地遍历所有像素。代码如下:
//通过指针访问像素点,类似YUV图像处理,前提图像存储是连续的
for (int n = 0; n < numTrials; n++)
{
// Get pointer to first pixel
//初始指针
Pixel *pixel = image1.ptr<Pixel>(0, 0);
// Mat objects created using the create method are stored
// in one continous memory block.
// 访问像素点位置
const Pixel *endPixel = pixel + image1.cols * image1.rows;
// Loop over all pixels
for (; pixel != endPixel; pixel++)
{
complicatedThreshold(*pixel);
}
}
这种方式是很有效的一种方法,实际较为常用,但是速度并没有达到最优,比at快不了多少,而且指针直接操作容易出错。
1.3 使用forEach方法进行像素访问
Mat类的forEach方法接受一个函数运算符Operator。用法如下:
void cv::Mat::forEach (const Functor &operation)
理解上述用法的最简单方法是通过下面的示例。我们定义了一个与forEach一起使用的函数对象(Operator)。代码如下:
// Parallel execution with function object.
struct Operator
{
void operator ()(Pixel &pixel, const int * position) const
{
// Perform a simple threshold operation
complicatedThreshold(pixel);
}
};
调用forEach很简单,只需一行代码即可完成
// Call forEach
image2.forEach<Pixel>(Operator());
这种方法速度很快,操作很简单。
1.4 将forEach与C ++ 11 Lambda一起使用
Lambda是C++11的新特性,具体使用见:
https://blog.csdn.net/lixiaogang_theanswer/article/details/80905445
代码如下:
for (int n = 0; n < numTrials; n++)
{
// Parallel execution using C++11 lambda.
image3.forEach<Pixel>([](Pixel &pixel, const int *position) -> void {
complicatedThreshold(pixel);
});
}
这种方式就不需要创建函数运算符,速度相比forEach不相上下。
2 性能比较与代码
2.1 性能比较
通过函数complicatedThreshold处理大小9000X6750的大图像。实验中使用的2.3 GHz Intel Core i5处理器有四个内核。获得以下时间。请注意,使用forEach使代码比使用Naive Pixel Access或Pointer Arithmetic方法快五倍。
方法 |
时间/ms |
at方法 |
10960.8 |
指针 |
10171.9 |
forEach |
2686.1 |
forEach (C++11 Lambda) |
2747.2 |
如果是处理300X225的小图像时,结果如下:
方法 |
时间/ms |
at方法 |
13.2 |
指针 |
11.3 |
forEach |
4.6 |
forEach (C++11 Lambda) |
2.9 |
可以看到小图像或大图像使用指针算法和at直接访问效果差距不大。而直接使用forEach适合大图像,forEach+Lambda特性更适合于小图像。用Lamdba特性处理小图像要比forEach处理快的原因在于,lambda特性更适用于不太耗时的操作使用,如普通for循环,纯CPU计算类型的操作,函数处理时间少的情况。数据库的IO操作,多线程充分利用CPU资源,lambda就不那么适合,可能时间开销更大。
2.2 代码
所有代码见:
https://github.com/luohenyueji/OpenCV-Practical-Exercise
C++:
#include "pch.h"
#include <opencv2/opencv.hpp>
// Use cv and std namespaces
using namespace cv;
using namespace std;
// Define a pixel 定义Pixel结构
typedef Point3_<uint8_t> Pixel;
/**
* @brief tic is called to start timer 开始函数运行时间计算
*
* @param t
*/
void tic(double &t)
{
t = (double)getTickCount();
}
/**
* @brief toc is called to end timer 结束函数运行时间计算
*
* @param t
* @return double 返回值运行时间ms
*/
double toc(double &t)
{
return ((double)getTickCount() - t) / getTickFrequency() * 1000;
}
/**
* @brief 阈值分割
*
* @param pixel
*/
void complicatedThreshold(Pixel &pixel)
{
//x,y,z分别代表三个通道的值
if (pow(double(pixel.x) / 10, 2.5) > 100)
{
pixel.x = 255;
pixel.y = 255;
pixel.z = 255;
}
else
{
pixel.x = 0;
pixel.y = 0;
pixel.z = 0;
}
}
/**
* @brief Parallel execution with function object. 并行处理函数结构体
*
*/
struct Operator
{
//处理函数
void operator()(Pixel &pixel, const int *position) const
{
// Perform a simple threshold operation
complicatedThreshold(pixel);
}
};
int main()
{
// Read image 读图
Mat image = imread("./image/butterfly.jpg");
// Scale image 30x 将图像扩大为30倍,长宽都变大30倍
resize(image, image, Size(), 30, 30);
// Print image size 打印图像尺寸
cout << "Image size " << image.size() << endl;
// Number of trials 测试次数
int numTrials = 5;
// Print number of trials 测试次数
cout << "Number of trials : " << numTrials << endl;
// Make two copies 图像复制
Mat image1 = image.clone();
Mat image2 = image.clone();
Mat image3 = image.clone();
// Start timer 时间函数,单位为ms
double t;
//开始计算时间
tic(t);
//循环测试numTrials次
for (int n = 0; n < numTrials; n++)
{
// Naive pixel access at方法直接读取数据
// Loop over all rows 遍历行
for (int r = 0; r < image.rows; r++)
{
// Loop over all columns 遍历列
for (int c = 0; c < image.cols; c++)
{
// Obtain pixel at (r, c) 直接访问像素数据
Pixel pixel = image.at<Pixel>(r, c);
// Apply complicatedTreshold 阈值分割
complicatedThreshold(pixel);
// Put result back 保存结果
image.at<Pixel>(r, c) = pixel;
}
}
}
//计算函数执行时间
cout << "Naive way: " << toc(t) << endl;
// Start timer
tic(t);
// image1 is guaranteed to be continous, but
// if you are curious uncomment the line below
//需要判断图像连续存储,1表示图像连续,0不连续
//cout << "Image 1 is continous : " << image1.isContinuous() << endl;
//通过指针访问像素点,类似YUV图像处理,前提图像存储是连续的
for (int n = 0; n < numTrials; n++)
{
// Get pointer to first pixel
//初始指针
Pixel *pixel = image1.ptr<Pixel>(0, 0);
// Mat objects created using the create method are stored
// in one continous memory block.
// 访问像素点位置
const Pixel *endPixel = pixel + image1.cols * image1.rows;
// Loop over all pixels
for (; pixel != endPixel; pixel++)
{
complicatedThreshold(*pixel);
}
}
cout << "Pointer Arithmetic " << toc(t) << endl;
tic(t);
//forEach遍历像素
for (int n = 0; n < numTrials; n++)
{
image2.forEach<Pixel>(Operator());
}
cout << "forEach : " << toc(t) << endl;
//C++版本
cout << __cplusplus << endl;
//使用C++11 lambda特性
tic(t);
for (int n = 0; n < numTrials; n++)
{
// Parallel execution using C++11 lambda.
image3.forEach<Pixel>([](Pixel &pixel, const int *position) -> void {
complicatedThreshold(pixel);
});
}
cout << "forEach C++11 : " << toc(t) << endl;
return 0;
}
3 参考
https://www.learnopencv.com/parallel-pixel-access-in-opencv-using-foreach/
[OpenCV实战]27 在OpenCV下使用forEach进行并行像素访问的更多相关文章
- [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡
本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...
- [OpenCV实战]48 基于OpenCV实现图像质量评价
本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...
- [OpenCV实战]45 基于OpenCV实现图像哈希算法
目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...
- [OpenCV实战]44 使用OpenCV进行图像超分放大
图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...
- [OpenCV实战]50 用OpenCV制作低成本立体相机
本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...
- [OpenCV实战]47 基于OpenCV实现视觉显著性检测
人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...
- [OpenCV实战]16 使用OpenCV实现多目标跟踪
目录 1 背景介绍 2 基于MultiTracker的多目标跟踪 2.1 创建单个对象跟踪器 2.2 读取视频的第一帧 2.3 在第一帧中确定我们跟踪的对象 2.4 初始化MultiTrackerer ...
- [OpenCV实战]28 基于OpenCV的GUI库cvui
目录 1 cvui的使用 1.1 如何在您的应用程序中添加cvui 1.2 基本的"hello world"应用程序 2 更高级的应用 3 代码 4 参考 有很多很棒的GUI库,例 ...
- [OpenCV实战]33 使用OpenCV进行Hough变换
目录 1 什么是霍夫变换 1.1 应用霍夫变换以检测图像中的线条 1.2 累加器 1.3 线条检测 1.4 圆环的检测 2 代码 3 参考 1 什么是霍夫变换 霍夫变换是用于检测图像中的简单形状(诸如 ...
随机推荐
- IDEA中直接将 SpringBoot项目打包成 Docker镜像时 pom.xml的配置
<plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactI ...
- 挑战海量数据:基于Apache DolphinScheduler对千亿级数据应用实践
点亮 ️ Star · 照亮开源之路 GitHub:https://github.com/apache/dolphinscheduler 精彩回顾 近期,初灵科技的大数据开发工程师钟霈合在社区活动的线 ...
- Linux系统管理_磁盘管理——敬请期待!!!
df 显示磁盘空间使用情况 -a 全部文件系统列表 -h 以方便阅读的方式显示 -T 列出文件系统类型 du 查看目录和文件的磁盘空间使用情况 -a 显示目录中所有文件大小 -h 以易读方式显示文件大 ...
- 成功解决:Can‘t find Python executable “python“, you can set the PYTHON env variable.
今天跑公司新项目的时候.运行前端vue.报了一个关于python的错误.就离谱 1.问题报错全部代码 actual version of core-js. npm ERR! code 1 npm ER ...
- 在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
文章目录 1.导入相关的依赖 2.创建要保存的数据信息实体类 3 .编写对应的sql语句 4.使用spring 的 aop 技术切到自定义注解上,所以先创建一个自定义注解类 5. 创建aop切面实现类 ...
- 小巧快速的ZooKeeper可视化管理+实时监控工具
Zookeeper: 是一个分布式的.开源的程序协调服务,是 hadoop 项目下的一个子项目.他提供的主要功 能包括:配置管理.名字服务.分布式锁.集群管理. 平时用zkCli.sh进行管理不免有点 ...
- 云实例初始化工具cloud-init源码分析
源码分析 代码结构 cloud-init的代码结构如下: cloud-init ├── bash_completion # bash自动补全文件 │ └── cloud-init ├── Chan ...
- springboot中使用mybatis_plus逆向工程
创建springboot项目,选择图片中所示依赖 mybatis-plus生成的依赖 <!-- mybatis_plus --> <dependency> <groupI ...
- linux如何删除多余网卡
ifconfig tunl0 down ip link delete tunl0
- mysql删库报错
3.开发人员测试环境删库报错 #解决:在数据库的物理目录中(mysql的data目录),进入要删除的数据库目录,查看是否有文件存在,若存在,使用rm -rf 命令清除:再次执行删除数据库命令即可 [r ...