[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 什么是霍夫变换 霍夫变换是用于检测图像中的简单形状(诸如 ...
随机推荐
- P2216 [HAOI2007]理想的正方形 方法记录
[HAOI2007]理想的正方形 题目描述 有一个 \(a \times b\) 的整数组成的矩阵,现请你从中找出一个 \(n \times n\) 的正方形区域,使得该区域所有数中的最大值和最小值的 ...
- activeMq不能被主机访问的问题
环境说明 主机:mac 虚拟机:VirtualBox 虚拟系统:Centos6.5 问题:虚拟机启动了 activemq. 也关闭了防火墙,但是在主机访问web界面,http://192.168.1. ...
- 学生管理系统(C语言简单实现)
仅供借鉴.仅供借鉴.仅供借鉴(整理了一下大一C语言每个章节的练习题.没得题目.只有程序了) 文章目录 1 .实训名称 2.实训目的及要求 3. 源码 4.实验小结 1 .实训名称 实训12:文件 2. ...
- python关于Django搭建简单博客项目(详解一)
上一篇我们说了如何搭建简易博客网站,下面我们来进行详细解答.本文没有特定顺序,请结合上一篇和源代码参照学习. 相关源代码和解析请参看:https://github.com/Cheng0829/mysi ...
- python用ffmpeg进行视频处理
1.下载及安装 在ffmpeg官网https://ffmpeg.zeranoe.com/builds/可以下载到需要的版本,然后解压到D盘,添加环境变量(如D:\ffmpeg\bin) 在cmd输入f ...
- 三十一、kubernetes网络介绍
Kubernetes 网络介绍 Service是Kubernetes的核心概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上 ...
- 十三、Pod的资源控制器类型
Pod 的资源控制器类型 一.Pod 的资源控制器类型 什么是控制器呢?简单来说,控制器就好比是影视剧里面的剧本,演员会根据剧本所写的内容来针对不同的角色进行演绎,而我们的控制器就好比是剧本,Kube ...
- 词云(WordCloud)
WordCloud的参数: font_path:可用于指定字体路径 width:词云的宽度,默认为 400: height:词云的⾼度,默认为 200: mask:蒙版,可⽤于定制词云的形状: min ...
- UE优化性能
UE 优化 参考:风恋残雪的博客 Stat unit 启动一个非Debug的游戏进程 打开控制台输入 Stat UNIT .PC端 ` , Android 四指点击 正常的渲染状态: 名称 功能 Fr ...
- 从 QFramework 重新开始
自从上一篇博文写完后,就进入了繁忙工作状态,直到最近才开始有一点空闲时间. 这次繁忙的状态从 2022 年 10 月 11 日 持续到 2022 年 11 月 5 日. 主要的工作就是 QFramew ...