C++: 并行加速图像读取和处理的过程

news2024/11/29 9:57:38

文章目录

    • 1. 目的
    • 2. 设计
    • 3. 串行实现
    • 4. 并行实现
    • 5. 比对:耗时和正确性
    • 6. 加速比探讨

在这里插入图片描述

1. 目的

读取单张图像,计算整图均值,这很好实现,运行耗时很短。

读取4000张相同大小的图像,分别计算均值,这也很好实现,运行耗时则较长。

对于单张图的读取、单张图的均值计算,可以认为 OpenCV 已经做了不错的优化。因此主要从多线程角度出发,用多个CPU核心分别读图、分别计算均值。

最终的耗时:从48.78秒降低到5.36秒。以下具体展开。

2. 设计

  • 给出串行的实现,并测量耗时
  • 给出并行的实现,并测量耗时
  • 测量耗时:使用先前博文中给出的 Timer 类 C++: 计时器类的设计和实现
  • 并行实现:使用先前博文中给出的线程安全队列 Queue 类 C++:设计一个线程安全的队列
  • 串行和并行实现的结果比对:分别把结果写入文本文件
    • 每个结果的形式: 图像名字, b通道均值,g通道均值,r通道均值
    • 使用 VSCode / Beyond Compare / diff 命令 等方式, 比对差异
  • 使用 fmtlib 实现字符串格式化
  • 使用 c++11 thread 创建多线程(pthread亦可)

3. 串行实现

定义基础数据结构:

class Result
{
public:
    Result() = default;
    Result(std::string img_name, cv::Scalar& m):
        image_name(img_name), mean(m)
    {}

public:
    std::string image_name;
    cv::Scalar mean;
};

static void write_result_to_txt(const std::vector<Result>& vec, const std::string filename)
{
    std::ofstream fout(filename);
    for (int i = 0; i < image_num; i++)
    {
        const Result& res = vec[i];
        const std::string& img_name = res.image_name;
        const cv::Scalar& m = res.mean;
        fout << fmt::format("{} {:f}{:f}{:f}\n", img_name, m.val[0], m.val[1], m.val[2]);
    }
}

串行代码的核心实现:

int sequential_read_and_process(digimon::Timer& timer)
{
    timer.start();
    const std::string img_dir = get_image_dir();
    std::vector<Result> vec(image_num);
    for (int i = 0; i < image_num; i++)
    {
        const std::string img_name = fmt::format("{:06d}.png", i);
        const std::string img_path = img_dir + "/" + img_name;

        cv::Mat im = cv::imread(img_path);
        cv::Scalar m = cv::mean(im);
        
        vec[i] = Result(img_name, m);
    }
    timer.stop();

    write_result_to_txt(vec, "sequential_result.txt");

    return 0;
}

4. 并行实现

void read_and_process(int startIdx, int endIdx, digimon::Queue<Result>& result_queue)
{
    const std::string img_dir = get_image_dir();
    for (int i = startIdx; i < endIdx; i++)
    {
        const std::string img_name = fmt::format("{:06d}.png", i);
        const std::string img_path = img_dir + "/" + img_name;

        cv::Mat im = cv::imread(img_path);
        
        cv::Scalar m = cv::mean(im);
        
        Result res(img_name, m);
        result_queue.push(res);
    }
}

int parallel_read_and_process(digimon::Timer& timer)
{
    timer.start();
    std::vector<std::thread> threads(16);
    
    digimon::Queue<Result> result_queue;
    for (size_t i = 0; i < threads.size(); i++)
    {
        int startIdx = i * (image_num / threads.size());
        int endIdx = (i == threads.size() - 1) ? image_num : ((i + 1) * (image_num / threads.size()));
        threads[i] = std::thread(read_and_process, startIdx, endIdx, std::ref(result_queue));
    }

    for (size_t i = 0; i < threads.size(); i++)
    {
        threads[i].join();
    }
    timer.stop();

    std::vector<Result> vec(image_num);
    for (int i = 0; i < image_num; i++)
    {
        vec[i] = result_queue.pop();
    }

    std::sort(vec.begin(), vec.end(), [](const Result& r1, const Result& r2){
        return r1.image_name < r2.image_name;
    });

    write_result_to_txt(vec, "parallel_result.txt");

    return 0;
}

5. 比对:耗时和正确性

int main()
{
    {
        digimon::Timer timer1(false);
        sequential_read_and_process(timer1);
        std::cout << fmt::format("sequential_read_and_process(): {:6.2f} s\n", timer1.getElapsedAsSecond());
    }

    {
        digimon::Timer timer2(false);
        parallel_read_and_process(timer2);
        std::cout << fmt::format("parallel_read_and_process(): {:6.2f} s\n", timer2.getElapsedAsSecond());
    }

    return 0;
}

耗时:开启了16个线程,耗时从48.78秒降低到5.36秒

zz@Legion-R7000P% ./testbed 
sequential_read_and_process():  48.78 s
parallel_read_and_process():   5.36 s

正确性比对:diff 结果为空, 说明完全一致

zz@Legion-R7000P% diff parallel_result.txt sequential_result.txt 

注意检查文本内容本身非空:
在这里插入图片描述

6. 加速比探讨

开启多线程后确实有加速的,但是加速比例并没有达到线程数量, 原因主要是CPU温度升高, 一方面频率有所降低, 另一方面会限制实际并行的CPU数量。

这一点在 macbook pro 电脑上可能更明显:

  • 在开启了Chrome,VSode等应用时, 单个进程 CPU 利用率只能达到300%左右, 整个电脑CPU占用率50%左右;
  • 关掉各种应用程序,冷却电脑一会儿,大概降低温度到45度,重新运行,耗时得到明显降低(24秒->13秒),CPU占用率从50%升高到100%

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/523945.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【OpenCv • c++】形态学技术操作 —— 开运算与闭运算

&#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;【OpenCV • c】计算机视觉&#x1f308; 若有帮助&#xff0c;还请关注➕点赞➕收藏&#xff…

openGauss5.0.0在vscode成功调试

之前在虚拟机上编译成功过&#xff0c;但今天启动数据库的时候出现权限错误问题&#xff0c;我重新删除了data文件夹&#xff0c;重新初始化启动数据库还是不成功&#xff0c;后来对报错文件进行赋权&#xff0c;成功解决&#xff01; 问题&#xff08;一&#xff09; 1.启动…

图像水印MATLAB实验

文章目录 一、实验目的二、实验内容1. 简单的可见水印嵌入实验2. 不可见脆弱水印实验3. 不可见鲁棒水印实验 一、实验目的 了解数字图像水印技术的基本原理、分类和应用。掌握简单的可见水印和不可见水印的嵌入方法。实现一种基于DCT的不可见鲁棒水印&#xff0c;并进行水印鲁…

Dubbo 服务端源码深入分析 (7)

目录 1. 前提 2. 认识 Protocol 和 ProxyFactory Protocal ProxyFactory Dubbo服务流程 服务端源码分析 测试代码&#xff1a; Protocal代理的源码 ProxyFactory源码&#xff1a; 获取invoker对象 具体步骤 1. 我们调用的是ProxyFactory的代理对象的getInvoker方法…

Linux线程同步(6)——更高并行性的读写锁

互斥锁或自旋锁要么是加锁状态、要么是不加锁状态&#xff0c;而且一次只有一个线程可以对其加锁。读写锁有 3 种状态&#xff1a;读模式下的加锁状态&#xff08;以下简称读加锁状态&#xff09;、写模式下的加锁状态&#xff08;以下简称写加锁状态&#xff09;和不加锁状态&…

django视图(request请求response返回值)

一、视图函数介绍 视图就是应用中views.py中定义的函数&#xff0c;称为视图函数 def index(request):return HttpResponse("hello world&#xff01;") 1、视图的第一个参数必须为HttpRequest对象&#xff0c;还可能包含下参数如通过正则表达式组获取的位置参数、通…

VBA——01篇(入门篇——简单基础语法)

VBA——01篇&#xff08;入门篇——简单基础语法&#xff09; 1. 语法格式1.1 简单语法1.2 简单例子 2. 变量2.1 常用数据类型2.2 声明变量的常用方式2.3 简单例子 3. 单元格赋值3.1 直接赋值3.2 拷贝单元格 4. 简单的逻辑语法4.1 简单if4.2 简单for循环4.2.1 简单语法例子4.2.…

基于混合整数二阶锥(MISOCP)的配电网重构(附matlab代码)

参考资料&#xff1a;主动配电网网络分析与运行调控 (sciencereading.cn) 配电网重构是指在满足配电网运行基本约束的前提下&#xff0c;通过改变配电网中一个或多个开关的状态对配电网中一个或多个指标进行优化。通过配电网重构&#xff0c;可以在不增加设备投资的情况下&…

注解实现:判空赋值

工作中的小玩意~~ 流程&#xff1a; 注解实现反射工具类 注解定义及实现 注解定义&#xff1a; Documented Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface CheckParam {String value() default "-1"; }简单解释上述其相关注解…

哈工大2023春计算机组成原理真题回忆

仅供同学参考&#xff0c;严禁用作商业用途 如发现将追究责任 2023-5-14 属鼠经历了计算机组成原理考试 现将本人真题回忆如下&#xff1a;欢迎大家补充&#xff0c;并期待大家一起参与这个开源的项目。 致谢:真诚感谢草履虫同学提供的图片 15个选择部分回忆如下 &#xff1a…

【历史上的今天】4 月 13 日:Damn Small Linux 首次发布;谷歌关闭短网址服务;数学先驱出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 13 日&#xff0c;在 2006 年的今天&#xff0c;盛大文学榕树下网站被民营企业收购&#xff1b;原创文学网站榕树下被民营传媒集团欢乐传媒收购&#xff…

hnust 湖南科技大学 2023 软件测试技术 期中考试 复习资料

前言 写的比较匆忙&#xff0c;重点也不明确&#xff0c;没什么参考价值致谢&#xff1a;ly&#xff0c;zxq重点来源&#xff1a;信安※&#xff1a;补充内容★&#xff1a;重点✦&#xff1a;个人推测考点考试范围&#xff1a;1-9章获取最新版本 题型 判断&#xff1a;10简…

AMBER分子动力学模拟之TOP准备-- HIV蛋白酶-抑制剂复合物(1)

AMBER分子动力学模拟之TOP准备-- HIV蛋白酶-抑制剂复合物(1) 我们以HIV蛋白酶-抑制剂复合物为例子&#xff0c;跑Amber动力学模拟 下载1phv 从PBD下载文件&#xff1a;https://www.rcsb.org/ PDB文件预处理 我们以 “protein(water) ligandcomplex” 为例来说一下如何处…

系统设计基本原理-耦合与内聚

耦合 耦合是模块之间的相互独立性(互相连接的紧密程度)的度量&#xff0c;耦合取决于各个模块之间接口的复杂程度、调用模块的方式以及通过接口的信息类型等。 耦合类型 无直接耦合&#xff1a;指两个模块之间没有直接的关系&#xff0c;它们分别从属于不同模块的控制与调用&…

k8s基础11——安全控制之RBAC用户授权、RBAC用户组授权、SA程序授权

文章目录 一、K8s安全框架1.1 鉴权1.1.1 HTTPS证书认证1.1.2 HTTP Token认证 1.2 授权1.3 准入控制1.4 集群四大角色 二、RBAC给用户授权&#xff08;TLS&#xff09;2.1 签发客户端证书2.2 生成kubeconfig授权文件2.2.1 手动生成2.2.2 脚本生成2.2.3 切换操作集群 2.3 定义RBA…

移动应用开发实验-内容提供者-ContentResolver的使用

文章目录 前言读取通讯录信息要求环境 具体实现主页面布局(activity_main.xml)关于RecyclerView库的相关问题添加RecyclerView库操作 解决报错Item布局(info.xml)添加访问权限编写实体类&#xff08;ContactInfo.java&#xff09;编写适配器&#xff08;MyAdapter.java&#xf…

20 散列表的查找

散列表的查找 简介&#xff1a;散列表&#xff08;也成哈希表&#xff09;是一种高效的数据结构&#xff0c;他可以在平均复杂度为O(1)的情况下实现查找、插入和删除操作。 哈希表的基本思想是根据关键字的值来计算其应存储的位置。这个计算过程就是通过哈希函数来实现的。 根…

计算机视觉——day 91基于双网络的鲁棒特征高光谱目标检测(偏门且很水啊)

基于双网络的鲁棒特征高光谱目标检测 I. INTRODUCTIONII. 提出的方法A. 总体框架B.训练集构建C. Dual Networks III. EXPERIMENTSIV. 结论 I. INTRODUCTION 用于高光谱目标检测的深度网络训练通常面临样本有限的问题&#xff0c;在极端情况下&#xff0c;可能只有一个目标样本…

黑盒测试方法

1 等价类划分 1.1 定义 等价类划分法是一种典型的&#xff0c;并且是最基础的黑盒测试用例设计方法。采用等价类划分法时&#xff0c;完全不用考虑程序内部结构&#xff0c;设计测试用例的唯一依据是软件需求规格说明书。 所谓等价类&#xff0c;是输入条件的一个子集合&…

kali整体版本更新方法,为啥更新?

玩过kali都知道&#xff0c;如果不更新版本&#xff0c;那么安装某个软件总是有很多依赖版本问题&#xff0c;解决起来的确麻烦&#xff0c;这篇文章彻底解决这些问题。 1&#xff0c;更新源 国外源与国内源的选择 kali默认配置的是国外源&#xff0c;但国外源的下载速度非常慢…