目录

1 Mat像素访问

1.1 使用at方法直接进行像素访问

1.2 使用指针进行像素访问

1.3 使用forEach方法进行像素访问

1.4 将forEach与C ++ 11 Lambda一起使用

2 性能比较与代码

2.1 性能比较

2.2 代码

3 参考


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进行并行像素访问的更多相关文章

  1. [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡

    本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...

  2. [OpenCV实战]48 基于OpenCV实现图像质量评价

    本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...

  3. [OpenCV实战]45 基于OpenCV实现图像哈希算法

    目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...

  4. [OpenCV实战]44 使用OpenCV进行图像超分放大

    图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...

  5. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  6. [OpenCV实战]47 基于OpenCV实现视觉显著性检测

    人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...

  7. [OpenCV实战]16 使用OpenCV实现多目标跟踪

    目录 1 背景介绍 2 基于MultiTracker的多目标跟踪 2.1 创建单个对象跟踪器 2.2 读取视频的第一帧 2.3 在第一帧中确定我们跟踪的对象 2.4 初始化MultiTrackerer ...

  8. [OpenCV实战]28 基于OpenCV的GUI库cvui

    目录 1 cvui的使用 1.1 如何在您的应用程序中添加cvui 1.2 基本的"hello world"应用程序 2 更高级的应用 3 代码 4 参考 有很多很棒的GUI库,例 ...

  9. [OpenCV实战]33 使用OpenCV进行Hough变换

    目录 1 什么是霍夫变换 1.1 应用霍夫变换以检测图像中的线条 1.2 累加器 1.3 线条检测 1.4 圆环的检测 2 代码 3 参考 1 什么是霍夫变换 霍夫变换是用于检测图像中的简单形状(诸如 ...

随机推荐

  1. P1706 全排列问题 方法记录

    原题链接 全排列问题 题目描述 按照字典序输出自然数 \(1\) 到 \(n\) 所有不重复的排列,即 \(n\) 的全排列,要求所产生的任一数字序列中不允许出现重复的数字. 输入格式 一个整数 \( ...

  2. 周末IT入门锦鲤

    周末总结 第一小节 typora软件 是目前最火的文本编辑器 下载安装 路径尽量不要安装C盘,安装其他盘路径尽量简单方便后续查找使用. 文件路径 路径:计算机上一个文件资源的坐标,C:\XX文件\a. ...

  3. esp32把玩记-④ 星星点灯 (点亮led)

    注意 全程使用Micropython,不会安装看我第一篇文章感谢 正式开始 用Thonny烧录(运行)以下代码 import time from machine import Pin led=Pin( ...

  4. JUC(5)BlockingQueue四组API

    1.读写锁ReadWriteLock package com.readlock; import java.util.HashMap; import java.util.Map; /** * ReadW ...

  5. 6.jmespath表达式

    jmespath是json的查询语言 可以从json文档中提取和转换元素,类似于jsonpath的另外一个库   字典取值 # 一层嵌套取值 {a:1,b:2,c:3} ""&qu ...

  6. 2.pytest前后置(固件、夹具)处理

    一.setup/teardown/setup_calss/teardown_class 为什么需要这些功能? 比如:我们执行用例之前,需要做的哪些操作,我们用例执行之后,需要做哪些操作 # 在所有用例 ...

  7. 二、redis介绍

    二.redis介绍 2.1.定义 Redis(Remote Dictionary Server ,远程字典服务) 是一个使用ANSI C编写的开源.支持网络.基于内存.可选持久性的键值对存储数据库,是 ...

  8. 通过tkinter列出全部字体名称

    通过tkinter列出windows系统全部字体名称 通过 tkinter.font 的 families() 函数实现 import tkinter import tkinter.font # 把p ...

  9. vue-axios更改操作

    <template> <div class="nav"> <label for="">新部门</label>&l ...

  10. 修改linux系统时间

    在Linux系统中,可以用date命令来显示或设定系统的日期与时间 1. 查看系统时间 [root@iZ2ze0gm3scdypc0i15r8yZ ~]# date Tue Aug 16 00:10: ...