【CV学习笔记】之ncnnFastDet多线程c++部署

news/2024/5/2 10:49:53/文章来源:https://blog.csdn.net/weixin_42108183/article/details/129331588

1、前言

ncnn是一款非常高效易用的深度学习推理框架,支持各种神经网络模型,如pytorch、tensorflow、onnx等,以及多种硬件后端,如x86、arm、riscv、mips、vulkan等。
ncnn项目地址:https://github.com/Tencent/ncnn
FastDet是设计用来接替yolo-fastest系列算法,相比于业界已有的轻量级目标检测算法,无论是速度还是参数量都要小,适用于嵌入式上的推理,当然精度还是差一些。但是这不重要,本文只是借用FastDet来实现多线程推理,如果有需要,理论上可以移植到任何模型以及平台。
FastDet项目链接:https://github.com/dog-qiuqiu/FastestDet
在实际项目中,单线程推理是一个稳定但是比较低效的方式,尤其是在多个模型对同一张图片进行推理时,因此就需要对设计多线程来进行优化,通过学习,现在也是掌握了多线程操作中的一些知识点:
多线程推理学习链接:https://shouxieai.com/solution/trt/integ-1.12-multithread
本文代码链接:https://github.com/Rex-LK/tensorrt_learning/tree/main/sideline_learn/ncnn_multi_thread
本文完整模型以及源代码百度云链接: https://pan.baidu.com/s/1f0gHxPRP3KrppnSOqF5ZIw?pwd=5fxe 提取码: 5fxe

2、推理代码详解

2.1、代码架构简介

下载ncnn代码,运行如下命令。

cd ncnn
mkdir build && cd build
cmake .. && make -j
make install

在build/install 目录下面会需要的出现 bin、lib、include三个文件夹。

2.2、fastdet推理代码fastdet.h

下载fastdet代码,只需要代码中的example/ncnn里面的模型以及推理文件,且本项目已经将该文件进行了简单的封装,便于多线程推理时进行调用,fastdet推理的头文件如下:

class FastDet{public:// 构造函数中初始化模型FastDet(int input_width, int input_height, std::string param_path,std::string model_path);~FastDet();// 预处理void prepare_input(cv::Mat img);// 执行推理void infrence(std::string inputName, std::string outputName, int num_threads);// 后处理void postprocess(int img_width, int img_height, int class_num, float thresh);public:static const char *class_names[];std::vector<TargetBox> target_boxes;std::vector<TargetBox> nms_boxes;private:// 模型ncnn::Net net_;int input_width_;int input_height_;ncnn::Mat input_;ncnn::Mat output_;const float mean_vals_[3] = {0.f, 0.f, 0.f};const float norm_vals_[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f};};

2.3、接口类代码infer.hpp

在代码自己使用或者给其他人使用时,最好的办法是给一个简单的接口函数,无须担心函数内部发生什么变化,只要获得对应的结果即可。因此,这里原作者设计了一个十分简介的接口类infer.hpp,便于推理函数的使用。

// 接口类,使用时会用到多态的思想,即父类指针指向子类对象,使用者只会看到父类的commit函数,而无须关系子类中的函数做了什么。
class Infer
{
public:virtual std::shared_future<std::vector<fastdet::TargetBox>> commit(cv::Mat &input) = 0;
};
// 构造推力器的函数
std::shared_ptr<Infer> create_infer(const std::string &param_path,const std::string &model_path);

2.4、接口实现代码 infer.cpp

首先构建一个任务结构体,表示输入一张图片以及推理完毕后返回对应的结果。

struct Job {shared_ptr<promise<vector<TargetBox>>> pro;Mat input;
};

接口实现类

class InferImpl : public Infer
{
public:virtual ~InferImpl() { stop(); }// 线程停止void stop();// 启动workerd的函数bool startup(const string &param_path, const string &model_path);// 输入图片并返回对应的推理结果virtual shared_future<vector<TargetBox>> commit(Mat &input) override;// 在worker内加载模型并推理void worker(promise<bool> &pro);
};

下面为类中函数的实现:

//终止推理,在析构函数中调用,将线程的running状态设为false,并唤醒线程向下执行。
void InferImpl::stop()
{if (running_){running_ = false;cv_.notify_one();}if (worker_thread_.joinable())worker_thread_.join();
}
// 启动推理线程
bool InferImpl::startup(const string &param_path, const string &model_path)
{param_path_ = param_path;model_path_ = model_path;running_ = true; // 启动后,运行状态设置为true// 线程传递promise的目的,是获得线程是否初始化成功的状态// 而在线程内做初始化,好处是,初始化跟释放在同一个线程内// 代码可读性好,资源管理方便promise<bool> pro;worker_thread_ = thread(&InferImpl::worker, this, std::ref(pro));/*注意:这里thread 一构建好后,worker函数就开始执行了第一个参数是该线程要执行的worker函数,第二个参数是this指的是classInferImpl,第三个参数指的是传引用,因为我们在worker函数里要修改pro。*/return pro.get_future().get();
}
// 提交推理任务
shared_future<vector<TargetBox>> InferImpl::commit(Mat &input)
{Job job;job.input = input;job.pro.reset(new promise<vector<TargetBox>>());shared_future<vector<TargetBox>> fut =job.pro->get_future(); // 将fut与job关联起来{lock_guard<mutex> l(lock_);jobs_.emplace(std::move(job));}cv_.notify_one(); // 通知线程进行推理return fut;
}
// 加载模型、推理
void InferImpl::worker(promise<bool> &pro)
{// 加载模型fast_det_ =new FastDet(input_width_, input_height_, param_path_, model_path_);if (fast_det_ == nullptr){//如果加载模型失败,则返回falsepro.set_value(false);printf("Load model failed: %s\n", file_.c_str());return;}pro.set_value(true); // 这里的promise用来负责确认infer初始化成功了vector<Job> fetched_jobs;while (running_){{unique_lock<mutex> l(lock_);cv_.wait(l, [&](){ return !running_ || !jobs_.empty(); }); // 一直等着,cv_.wait(lock, predicate) // 如果 running不在运行状态// 或者说 jobs_有东西 而且接收到了notify one的信号// 在调用析构函数时会将running_设置为falseif (!running_)break; // 如果 不在运行 就直接结束循环for (int i = 0; i < batch_size && !jobs_.empty();++i){ // jobs_不为空的时候fetched_jobs.emplace_back(std::move(jobs_.front())); // 就往里面fetched_jobs里塞东西jobs_.pop(); // fetched_jobs塞进来一个,jobs_那边就要pop掉一个。(因为move)}}// 可以选择一次加载一批,并进行批处理// 本文设置的batchsize为1for (auto &job : fetched_jobs){int img_width = job.input.cols;int img_height = job.input.rows;fast_det_->prepare_input(job.input);fast_det_->infrence(input_name_, output_name_, infer_thread_);fast_det_->postprocess(img_width, img_height, class_num, 0.65);job.pro->set_value(fast_det_->nms_boxes);}fetched_jobs.clear();}printf("Infer worker done.\n");
}

3、代码测试

fastdet推理代码为fastdet_test.cpp,使用单线程推理一张图片。多线程推理的代码为multi_thread_infer.cpp
多线程推理代码为:

int main()
{string param_path ="/home/rex/Desktop/ncnn_multi_thread/data/model/FastestDet.param";string model_path ="/home/rex/Desktop/ncnn_multi_thread/data/model/FastestDet.bin";auto infer = create_infer(param_path,model_path); // 创建及初始化 抖音网页短视频辅助讲解: 创建及初始化推理器if (infer == nullptr){printf("Infer is nullptr.\n");return 0;}string img_path = "/home/rex/Desktop/ncnn_multi_thread/data/imgs/3.jpg";Mat img = cv::imread(img_path);auto fut = infer->commit(img);     // 将任务提交给推理器(推理器执行commit)vector<TargetBox> res = fut.get(); // 等待结果for (size_t i = 0; i < res.size(); i++){TargetBox box = res[i];rectangle(img, cv::Point(box.x1, box.y1), cv::Point(box.x2, box.y2),cv::Scalar(0, 0, 255), 2);// cv::putText(img, pred->class_names[box.category], cv::Point(box.x1,// box.y1),//             cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 255, 0), 2);}cv::imwrite("result_test.jpg", img);return 0;
}

执行:

mkdir build && cd bulid
cmake .. && make -j
./multi_thead_infer

推理结果为:
在这里插入图片描述

4、总结

本文学习了ncnn的基本使用方式,希望后续能够学习到关于ncnn更加底层的知识了,同时利用多线程推理的方法优化的推理流程,从多线程的代码中学习到了许多关于c++多线程编程的知识,并应用到实际项目中。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RK3568触摸屏驱动调试总结

硬件电路分析 RK3568 CPU通过I2C与触控板外设wdt87xx连接。 首先要根据电路图获取如下I2C的信息&#xff1a; 项目Value接在哪个I2Ci2c1I2C 寄存器地址0x2cHID 地址0x20中断B5 1、接在哪个I2C 如图,1接在I2C1&#xff1a; 2、使用哪个GPIO引脚接收触控板的中断 如图&#xf…

Buuctf Younger-drive 题解

目录 一.查壳 二.运行缺少dll 三.主函数 四.hObject线程 五.Thread线程 六.judge函数 七.解题脚本 这题的关键在于了解一定的线程相关知识 一.查壳 32位带壳,用upx脱壳 二.运行缺少dll 后续尝试了各种方法修复dll但是还是运行不了 值得一提的是脱壳后的程序不能动态调试…

MySql启动错误(Mac系统 安装 mysql-8.0.32-macos13-arm64 后每次点击启动 无法启动) --- 已解决

MySql启动的时候: 立即变红! 查看日志如下: 2023-03-04T14:18:01.089671Z 0 [System] [MY-010910] [Server] /usr/local/mysql/bin/mysqld: Shutdown complete (mysqld 8.0.32) MySQL Community Server - GPL. 2023-03-04T14:18:10.304169Z 0 [System] [MY-010116] [Server]…

HiveSQL一天一个小技巧:如何精准计算非连续日期累计值【闪电快车面试题】

0 需 求稀疏字段累计求和问题1 问题分析根据图片中数据变换的形式&#xff0c;可以看出是根据字段term补齐数据中缺失的日期&#xff0c;term为连续日期的个数&#xff0c;当为12时&#xff0c;表明由2018-12-21到2019-01-02连续日期个数为12&#xff0c;当补齐日期后&#xff…

计算机网络的166个概念你知道几个 第四部分

HTML&#xff1a;HTML 称为超文本标记语言&#xff0c;是一种标识性的语言。它包括一系列标签&#xff0e;通过这些标签可以将网络上的文档格式统一&#xff0c;使分散的 Internet 资源连接为一个逻辑整体。HTML 文本是由 HTML 命令组成的描述性文本&#xff0c;HTML 命令可以说…

【LeetCode每日一题】——605.种花问题

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 贪心算法 二【题目难度】 简单 三【题目编号】 605.种花问题 四【题目描述】 假设有一个很长…

K8S 实用工具之二 - 终端 UI K9S

开篇 &#x1f4dc; 引言&#xff1a; 磨刀不误砍柴工工欲善其事必先利其器 第一篇&#xff1a;《K8S 实用工具之一 - 如何合并多个 kubeconfig&#xff1f;》 像我这种&#xff0c;kubectl 用的不是非常溜&#xff0c;经常会碰到以下情况&#xff1a; 忘记命令&#xff0c;先…

BUU [ZJCTF 2019]Login

这是一道让我感觉很淦的题&#xff0c;整一天了才大致了解了来龙去脉 开始&#xff1a; 首先丢到虚拟机checksec一下看看有啥保护措施&#xff1a; 看到开了Canary&#xff0c;就已经感觉不妙了&#xff0c;接着丢到IDA里看看啥情况 一看&#xff0c;是令人痛苦的c风格的代码…

LeetCode-63. 不同路径 II

题目来源 63. 不同路径 II 递归 class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int row obstacleGrid.length-1;int col obstacleGrid[0].length-1;return process(row,col,0,0,obstacleGrid);}private int process(int row ,int col,int i…

Cesium三维数据格式以及生产流程详解(glb,osgb,obj,bim,ifc)等

最近收到私信问我在cesium上展示的一些三维数据是如何生产和处理的,这篇文章就给大家一次性讲个透彻。 首先我们来做做分类。市面上能接触到的,常见的,cesium上支持展示的三维数据大致分为以下几种: 1.倾斜摄影(osgb,obj) 2.点云数据(las,pts) 3.手工模型(gltf,…

【SpringCloud】SpringCloud详解之Eureka实战

目录前言SpringCloud Eureka 注册中心一.服务提供者和服务消费者二.需求三.搭建Eureka-Server四.搭建Eureka-Client(在服务提供者配置:用户订单)前言 微服务中多个服务&#xff0c;想要调用&#xff0c;怎么找到对应的服务呢&#xff1f; 这里有组件的讲解 → SpringCloud组件…

跳表--C++实现

目录 作者有话说 为何要学习跳表&#xff1f;为了快&#xff0c;为了更快&#xff0c;为了折磨自己..... 跳表作用场景 1.不少公司自己会设计哈希表&#xff0c;如果解决哈希冲突是不可避免的事情。通常情况下会使用链址&#xff0c;很好理解&#xff0c;当有冲突产生时&#…

RTOS中信号量的实现与应用

RTOS中的信号量是一种用来协调多个任务间共享资源访问的同步机制。它可以保证多个任务之间访问共享资源的正确性和一致性&#xff0c;避免了因多任务并发访问造成的不可预期的问题。 信号量的实现 信号量的实现原理比较简单&#xff0c;主要包括两个部分&#xff1a;计数器和…

十大经典排序算法【快速了解】

文章目录一、算法分类二、经典排序算法总览三、算法复杂度四、代码实现一、算法分类 十种常见排序算法可以分为两大类&#xff1a; 比较类排序&#xff1a; 通过比较来决定元素间的相对次序由于其时间复杂度不能突破O(nlogn)&#xff0c;因此也称为非线性时间比较类排序。 非…

22. linux系统基础

递归遍历指定文件下所有的文件&#xff0c;而且你还可以统计一下普通文件的总个数&#xff0c;既然能统计普通文件&#xff0c;能统计其他文件吗&#xff1f;比如目录文件&#xff0c; 这个是main函数里面我们调用了 &#xff0c;这个checkdird这个函数&#xff0c;需要传递一个…

[数据结构]:10-二叉排序树(无头结点)(C语言实现)

目录 前言 已完成内容 二叉排序树实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-BinarySearchTreeCommon.cpp 04-BinarySearchTreeFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言…

大数据框架之Hadoop:MapReduce(八)常见错误及解决方案

1、导包容易出错。尤其Text和CombineTextInputFormat。 2、Mapper中第一个输入的参数必须是LongWritable或者NullWritable&#xff0c;不可以是IntWritable. 报的错误是类型转换异常。 3、java.lang.Exception: java.io.IOException: Illegal partition for 13926435656 (4)&…

ZincSearch Java 客户端教程

ZincSearch Zinc 简单、强大&#xff0c;不了解的同学可以参见我之前的博客。今天我们这里谈谈 Java 环境如何集成 Zinc 客户端&#xff0c;跟如何使用的。 安装 Zinc 到 Github 的官方 Releases 下载&#xff1a; 我的是 Windows 开发环境&#xff0c;下载 zincsearch_0.4…

基于ANSYS的无约束梁的模态分析与实验结果比较

一、实验模型简介 该模型来源于文献&#xff1a;“Khatir, A., Capozucca, R., Khatir, S. et al. Vibration-based crack prediction on a beam model using hybrid butterfly optimization algorithm with artificial neural network. Front. Struct. Civ. Eng. 16, 976–98…

蓝桥杯第十四届校内赛(第三期) C/C++ B组

一、填空题 &#xff08;一&#xff09;最小的十六进制 问题描述   请找到一个大于 2022 的最小数&#xff0c;这个数转换成十六进制之后&#xff0c;所有的数位&#xff08;不含前导 0&#xff09;都为字母&#xff08;A 到 F&#xff09;。   请将这个数的十进制形式作…