使用腾讯ncnn加速推理yolo v9对比opencv dnn

前面博客 【opencv dnn模块 示例(25) 目标检测 object_detection 之 yolov9
介】 绍了 yolov9 详细使用方式,重参数化、导出端到端模型,使用 torch、opencv、tensorrt 以及 paddle 的测试。

由于存在移动端推理部署的需求,需要进行加速处理,本文在 yolov9 的基础上,使用腾讯的NCNN库进行推理测试。

1、模型转换

1.1、准备转换工具

ncnn项目提供了转换工具,可以直接冲 预编译 release 包形式获取, 链接 https://github.com/Tencent/ncnn/releases 。例如windows下提供的版本。
在这里插入图片描述

我们以 ncnn-20241226-windows-vs2015-shared 包为例,下载后截图如下,分为两个架构,include和lib 为开发者使用, bin为动态库和工具目录。我们这里使用 onnx2ncnn.exe 工具。

在这里插入图片描述

1.2、 onnx模型转换

以 yolov9s 为例,预训练或者训练得到的 yolov9s.pt 模型,先进行重参数化处理,生成精简网络后的 yolov9s-converted.pt 模型文件 ,之后导出 yolov9s-converted.onnx,同时指定参数进行模型精简。

python reparameterization_yolov9-s.py yolov9s.pt
python export.py --weights yolov9-s-converted.pt --include onnx --simplify

这里以我们训练导出好的 best-s-c.onnx 模型文件进行准换,后续也以此作为测试。
进入NCNN的bin目录,使用脚本命令

$ onnx2ncnn.exe best-s-c.onnx best-s-c.onnx.param best-s-c.onnx.binonnx2ncnn may not fully meet your needs. For more accurate and elegant
conversion results, please use PNNX. PyTorch Neural Network eXchange (PNNX) is
an open standard for PyTorch model interoperability. PNNX provides an open model
format for PyTorch. It defines computation graph as well as high level operators
strictly matches PyTorch. You can obtain pnnx through the following ways:
1. Install via pythonpip3 install pnnx
2. Get the executable from https://github.com/pnnx/pnnx
For more information, please refer to https://github.com/pnnx/pnnx

这里运行脚本之后,有一堆警告, 可以按照要求进行额外的操作。 当前转换成功,并输出了2个文件。
在这里插入图片描述

我这里模型为个人训练,6类。 使用netron查看onnx 和 ncnn 模型的网络结构、输入和输出。
两者输入 images、输出 outputs0 相同,但ncnn中输入和出书的维度都是动态的,不像onnx中为静态固定的值。

在这里插入图片描述

2、测试

2.1、测试代码

我们直接使用前面博客中 opencv dnn 测试的代码 上修改。

2.1.1、预处理

先扩充为正方形,之后缩放到 (640,640)。

opencv 代码为

// Create a 4D blob from a frame.
cv::Mat modelInput = frame;
if(letterBoxForSquare && inpWidth == inpHeight)modelInput = formatToSquare(modelInput);// preprocess
cv::dnn::blobFromImage(modelInput, blob, scale, cv::Size2f(inpWidth, inpHeight), mean, swapRB, false);

ncnn的代码如下:

cv::Mat modelInput = frame;
if(letterBoxForSquare && inpWidth == inpHeight)modelInput = formatToSquare(modelInput);// preprocess
ncnn::Mat in = ncnn::Mat::from_pixels_resize((unsigned char*)modelInput.data, ncnn::Mat::PIXEL_BGR2RGB, modelInput.cols, modelInput.rows, (int)inpWidth, (int)inpHeight);float norm_ncnn[] = {1/255.f, 1/255.f, 1/255.f};
in.substract_mean_normalize(0, norm_ncnn);

注意对比,都先转换为letterBox的正方形形式, 之后缩放转换为 4维 blob,并进行归一化。ncnn稍显复杂。

预处理的效率对比,三种实现方式如下,

         preprocesscv::TickMeter tk;tk.reset();for(int i = 0; i < 100; i++) {tk.start();cv::dnn::blobFromImage(modelInput, blob, scale, cv::Size2f(inpWidth, inpHeight), mean, swapRB, false);ncnn::Mat in2;in2.w = inpWidth;in2.h = inpHeight;in2.d = 1;in2.c = 3;in2.data = blob.data;in2.elemsize = 4;in2.elempack = 1;in2.dims = 3;in2.cstep = inpWidth*inpHeight;tk.stop();}std::cout<< tk.getTimeMilli() << "  " << tk.getAvgTimeMilli() << std::endl;tk.reset();for(int i = 0; i < 100; i++) {tk.start();cv::dnn::blobFromImage(modelInput, blob, scale, cv::Size2f(inpWidth, inpHeight), mean, swapRB, false);ncnn::Mat in2(inpWidth, inpHeight, 3, blob.data, 4, 1);tk.stop();}std::cout << tk.getTimeMilli() << "  " << tk.getAvgTimeMilli() << std::endl;tk.reset();for(int i = 0; i < 100; i++) {tk.start();ncnn::Mat in = ncnn::Mat::from_pixels_resize((unsigned char*)modelInput.data, ncnn::Mat::PIXEL_BGR2RGB, modelInput.cols, modelInput.rows, (int)inpWidth, (int)inpHeight);float norm_ncnn[] = {1 / 255.f, 1 / 255.f, 1 / 255.f};in.substract_mean_normalize(0, norm_ncnn);tk.stop();}std::cout << tk.getTimeMilli() << "  " << tk.getAvgTimeMilli() << std::endl;

运行100次,测量总时间和平均时间,对比结果可知ncnn的效率略高13%于opencv dnn。

374.684  3.74684
373.745  3.73745
327.09  3.2709

2.1.2、推理

  • opencv 的推理

    // Run a model.
    net.setInput(blob);
    // output
    std::vector<Mat> outs;
    net.forward(outs, outNames);   // 亦可以使用 单一输出 Mat out=net.forward(outNames);postprocess(frame, modelInput.size(), outs, net);
    

    后处理函数,对网络输出 [1, clsass_num+4, 8400] 进行解码,之后nms处理并绘制。

  • ncnn 的推理

    由于格式不同,为复用后处理函数,对输出进行转换处理

    ex.input("images", in);
    ex.extract("output0", output);
    // 复用opencv dnn的后处理
    std::vector<Mat> outs;
    outs.push_back(cv::Mat({1,output.h,output.w}, CV_32F, output.data));
    

2.2、效率对比

相同图片,使用训练的 yolov9-s模型,仅计算推理时间。

opencv dnn(CPU):300ms
opencv dnn(GPU):15ms

ncnn CPU:170ms
ncnn GPU(vulkan): 报错。

目前仅看cpu,推理加速快接近50%… 在移动端还是提升客观的。

2.3、主体代码

注意,引用 #include "ncnn/net.h" 是,如果报奇怪的未定义错误,将引用提前。

using namespace cv;
using namespace dnn;float inpWidth;
float inpHeight;
float confThreshold, scoreThreshold, nmsThreshold;
std::vector<std::string> classes;
std::vector<cv::Scalar> colors;bool letterBoxForSquare = true;cv::Mat formatToSquare(const cv::Mat &source);void postprocess(Mat& frame, cv::Size inputSz, const std::vector<Mat>& out, Net& net);void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame);std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dis(100, 255);int test_ncnn()
{// 根据选择的检测模型文件进行配置 confThreshold = 0.25;scoreThreshold = 0.45;nmsThreshold = 0.5;float scale = 1 / 255.0;  //0.00392Scalar mean = {0,0,0};bool swapRB = true;inpWidth = 640;inpHeight = 640;//String modelPath = R"(E:\DeepLearning\yolov9\custom-data\traffic_accident_vehicle_test_0218\best-s-c.onnx)";String classesFile = R"(E:\DeepLearning\yolov9\custom-data\traffic_accident_vehicle_test_0218\cls.txt)";std::string param_path = R"(E:\1、交通事故\Traffic Accident Processes For IOS\models\20250221\ncnn\best-s-c.onnx.param)";std::string bin_path =   R"(E:\1、交通事故\Traffic Accident Processes For IOS\models\20250221\ncnn\best-s-c.onnx.bin)";ncnn::Net net;net.load_param(param_path.c_str());net.load_model(bin_path.c_str());net.opt.use_vulkan_compute = true;// Open file with classes names.if(!classesFile.empty()) {const std::string& file = classesFile;std::ifstream ifs(file.c_str());if(!ifs.is_open())CV_Error(Error::StsError, "File " + file + " not found");std::string line;while(std::getline(ifs, line)) {classes.push_back(line);colors.push_back(cv::Scalar(dis(gen), dis(gen), dis(gen)));}}// Create a windowstatic const std::string kWinName = "Deep learning object detection in OpenCV";cv::namedWindow(kWinName, 0);// Open a video file or an image file or a camera stream.VideoCapture cap;cap.open(R"(E:\DeepLearning\yolov9\bus.jpg)");cv::TickMeter tk;// Process frames.Mat frame, blob;while(waitKey(1) < 0) {cap >> frame;if(frame.empty()) {waitKey();break;}// Create a 4D blob from a frame.cv::Mat modelInput = frame;if(letterBoxForSquare && inpWidth == inpHeight)modelInput = formatToSquare(modelInput);// preprocess//cv::dnn::blobFromImage(modelInput, blob, scale, cv::Size2f(inpWidth, inpHeight), mean, swapRB, false);ncnn::Mat in = ncnn::Mat::from_pixels_resize((unsigned char*)modelInput.data, ncnn::Mat::PIXEL_BGR2RGB, modelInput.cols, modelInput.rows, (int)inpWidth, (int)inpHeight);float norm_ncnn[] = {1/255.f, 1/255.f, 1/255.f};in.substract_mean_normalize(0, norm_ncnn);// Run a model.ncnn::Extractor ex = net.create_extractor();ex.input("images", in);ncnn::Mat output;auto tt1 = cv::getTickCount();ex.extract("output0", output);auto tt2 = cv::getTickCount();//for(int i = 0; i < 20; i++) {//    auto tt1 = cv::getTickCount();//    ex.input("images", in);//    ex.extract("output0", output);//    auto tt2 = cv::getTickCount();//   std::cout << "infer time: " << (tt2 - tt1) / cv::getTickFrequency() * 1000 << std::endl;//}std::vector<Mat> outs;outs.push_back(cv::Mat({1,output.h,output.w}, CV_32F, output.data));cv::dnn::Net nullNet;postprocess(frame, modelInput.size(), outs, nullNet);//tk.stop();std::string label = format("Inference time: %.2f ms", (tt2 - tt1) / cv::getTickFrequency() * 1000);cv::putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));cv::imshow(kWinName, frame);}return 0;
}cv::Mat formatToSquare(const cv::Mat &source)
{int col = source.cols;int row = source.rows;int _max = MAX(col, row);cv::Mat result = cv::Mat::zeros(_max, _max, CV_8UC3);source.copyTo(result(cv::Rect(0, 0, col, row)));return result;
}void postprocess(Mat& frame, cv::Size inputSz, const std::vector<Mat>& outs, Net& net)
{// yolov8 has an output of shape (batchSize, 84, 8400) (Num classes + box[x,y,w,h] + confidence[c])auto tt1 = cv::getTickCount();float x_factor = inputSz.width / inpWidth;float y_factor = inputSz.height / inpHeight;std::vector<int> class_ids;std::vector<float> confidences;std::vector<cv::Rect> boxes;//int rows = outs[0].size[1];//int dimensions = outs[0].size[2];// [1, 84, 8400] -> [8400,84]int rows = outs[0].size[2];int dimensions = outs[0].size[1];auto tmp = outs[0].reshape(1, dimensions);cv::transpose(tmp, tmp);float *data = (float *)tmp.data;for(int i = 0; i < rows; ++i) {//float confidence = data[4];//if(confidence >= confThreshold) {float *classes_scores = data + 4;cv::Mat scores(1, classes.size(), CV_32FC1, classes_scores);cv::Point class_id;double max_class_score;minMaxLoc(scores, 0, &max_class_score, 0, &class_id);if(max_class_score > scoreThreshold) {confidences.push_back(max_class_score);class_ids.push_back(class_id.x);float x = data[0];float y = data[1];float w = data[2];float h = data[3];          int left = int((x - 0.5 * w) * x_factor);int top = int((y - 0.5 * h) * y_factor);int width = int(w * x_factor);int height = int(h * y_factor);boxes.push_back(cv::Rect(left, top, width, height));}//}data += dimensions;}std::vector<int> indices;NMSBoxes(boxes, confidences, scoreThreshold, nmsThreshold, indices);auto tt2 = cv::getTickCount();std::string label = format("NMS time: %.2f ms", (tt2 - tt1) / cv::getTickFrequency() * 1000);cv::putText(frame, label, Point(0, 30), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));for(size_t i = 0; i < indices.size(); ++i) {int idx = indices[i];Rect box = boxes[idx];drawPred(class_ids[idx], confidences[idx], box.x, box.y,box.x + box.width, box.y + box.height, frame);//printf("cls = %d, prob = %.2f\n", class_ids[idx], confidences[idx]);std::cout << "cls " << class_ids[idx] << ", prob = " << confidences[idx] << ", "<< box  << "\n";}
}void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
{rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 255, 0));std::string label = format("%.2f", conf);Scalar color = Scalar::all(255);if(!classes.empty()) {CV_Assert(classId < (int)classes.size());label = classes[classId] + ": " + label;color = colors[classId];}int baseLine;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);top = max(top, labelSize.height);rectangle(frame, Point(left, top - labelSize.height),Point(left + labelSize.width, top + baseLine), color, FILLED);cv::putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar());
}

3、其他优化

ncnn自带的工具

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

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

相关文章

前端小食堂 | Day10 - 前端路由の时空裂隙

🕳️ 今日穿梭指南:两种维度の路由宇宙 1. Hash 模式:锚点の量子隧道 // 手动创建路由监听器 window.addEventListener(hashchange, () => {const path = location.hash.slice(1) || /; console.log(进入哈希宇宙:, path); renderComponent(path); }); // 编程…

C语言学习笔记-进阶(7)字符串函数3

1. strstr的使用和模拟实现 char * strstr ( const char * str1, const char * str2); Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1. &#xff08;函数返回字符串str2在字符串str1中第⼀次出现的位置&#x…

HarmonyOS Next 属性动画和转场动画

HarmonyOS Next 属性动画和转场动画 在鸿蒙应用开发中&#xff0c;动画是提升用户体验的关键要素。通过巧妙运用动画&#xff0c;我们能让应用界面更加生动、交互更加流畅&#xff0c;从而吸引用户的注意力并增强其使用粘性。鸿蒙系统为开发者提供了丰富且强大的动画开发能力&…

PHP:phpstudy无法启动MySQL服务问题解决

文章目录 一、问题说明二、解决问题 一、问题说明 我的Windows10系统&#xff0c;之前安装过MySQL5.7的版本。 然后&#xff0c;用phpstudy安装MySQL8&#xff0c;并启动MySQL8。 发生无法启动的情况。 二、解决问题 1、删除本地MySQL7的服务 net stop MySQL //这里的服务名…

Nginx(基础安装+配置文件)

目录 一.Nginx基础 1.基础知识点 2.异步非阻塞机制 二.Nginx安装 2.1安装nginx3种方式 1.包管理工具安装&#xff08;yum/apt&#xff09; 2.本地包安装&#xff08;rpm/dpkg&#xff09; 3.源码编译安装 3.1 源码编译安装nginx流程&#xff08;ubuntu&#xff09; 1.…

C++ Windows下屏幕截图

屏幕截图核心代码&#xff08;如果要求高帧率&#xff0c;请使用DxGI&#xff09;&#xff1a; // RGB到YUV的转换公式 #define RGB_TO_Y(r, g, b) ((int)((0.299 * (r)) (0.587 * (g)) (0.114 * (b)))) #define RGB_TO_U(r, g, b) ((int)((-0.169 * (r)) - (0.331 * (g)) …

修改jupyter notebook的工作空间

今天&#xff0c;我之前R配置jupyter工作空间&#xff0c;讲了各种语言内核分配不同的工作空间&#xff0c;虽然是方便管理&#xff0c;但有个问题就是需要每次都进入C盘的配置文件找到notebook的工作空间设置路径打开修改嘛。 因此&#xff0c;今天我编写了一个python脚本&am…

江科大51单片机笔记【9】DS1302时钟可调时钟(下)

在写代码前&#xff0c;记得把上一节的跳线帽给插回去&#xff0c;不然LCD无法显示 一.DS1302时钟 1.编写DS1302.c文件 &#xff08;1&#xff09;重新对端口定义名字 sbit DS1302_SCLKP3^6; sbit DS1302_IOP3^4; sbit DS1302_CEP3^5;&#xff08;2&#xff09;初始化 因为…

电商行业门店管理软件架构设计与数据可视化实践

一、行业痛点与核心诉求 在电商多平台运营成为主流的背景下,企业普遍面临三大管理难题: ​数据碎片化:某头部服饰品牌2023年运营报告显示,其分布在8个平台的162家门店,日均产生23万条订单数据,但财务部门需要5个工作日才能完成跨平台利润核算。​成本核算失真:行业调研…

创新算法!BKA-Transformer-BiLSTM黑翅鸢优化算法多变量时间序列预测

创新算法&#xff01;BKA-Transformer-BiLSTM黑翅鸢优化算法多变量时间序列预测 目录 创新算法&#xff01;BKA-Transformer-BiLSTM黑翅鸢优化算法多变量时间序列预测预测效果基本介绍BKA-Transformer-BiLSTM黑翅鸢优化算法多变量时间序列预测一、引言1.1、研究背景和意义1.2、…

leetcode 95.不同的二叉搜索树 Ⅱ

首先分析一下什么是二叉搜索树。因为我本科学习数据结构的时候就是单纯背了一下题库&#xff0c;考试非常简单。现在额外补充学一些之前自己没有学过的内容。有序向量可以二分查找&#xff0c;列表可以快速插入和删除。二叉搜索树可以实现按照关键码访问。call by key .数据表现…

数据安全防线:备份文件的重要性与自动化实践

在数字化时代&#xff0c;信息已成为企业运营和个人生活的核心资源。无论是企业的核心数据、客户的敏感信息&#xff0c;还是个人的珍贵照片、重要文档&#xff0c;这些数据一旦丢失或受损&#xff0c;都可能带来不可估量的损失。因此&#xff0c;备份文件的重要性不言而喻&…

碰一碰发视频系统之写卡功能开发了,支持OEM

一、引言 在碰一碰发视频系统中&#xff0c;NFC&#xff08;Near Field Communication&#xff0c;近场通信&#xff09;技术扮演着关键角色。其中&#xff0c;写卡功能是实现用户与系统便捷交互的重要环节&#xff0c;通过将特定的视频相关信息写入 NFC 标签&#xff0c;用户…

【数据结构初阶第十八节】八大排序系列(上篇)—[详细动态图解+代码解析]

看似不起眼的日复一日&#xff0c;总会在某一天让你看到坚持的意义。​​​​​​云边有个稻草人-CSDN博客 hello&#xff0c;好久不见&#xff01; 目录 一. 排序的概念及运用 1. 概念 2. 运用 3. 常见排序算法 二. 实现常见排序算法 1. 插入排序 &#xff08;1&…

python爬虫系列课程8:js浏览器window对象属性

python爬虫系列课程8:js浏览器window对象属性 一、JavaScript的组成二、document常见属性对象三、navigator对象一、JavaScript的组成 JavaScript可以分为三个部分:ECMAScript标准、DOM、BOM。 ECMAScript标准:即JS的基本语法,JavaScript的核心,描述了语言的基本语法和数…

快速使用PPASR V3版不能语音识别框架

前言 本文章主要介绍如何快速使用PPASR语音识别框架训练和推理&#xff0c;本文将致力于最简单的方式去介绍使用&#xff0c;如果使用更进阶功能&#xff0c;还需要从源码去看文档。仅需三行代码即可实现训练和推理。 源码地址&#xff1a;https://github.com/yeyupiaoling/P…

cannon g3810打印机设置

现在AI这么厉害&#xff0c;是不是很少人来这里搜索资料了。 不过我还是写一下。 买了一台cannon g3810打印机。一直都用USB打印&#xff0c;今天突然想用手机打印。于是又折腾了两个小时&#xff0c;终于折腾完了。 步骤如下&#xff1a; [1]打开官网&#xff0c;下载佳能…

使用 Arduino 和 ThingSpeak 通过 Internet 进行心跳监测

使用 Arduino 和 ThingSpeak 通过 Internet 进行心跳监测 在这个项目中,我们将使用 Arduino 制作一个心跳检测和监测系统,该系统将使用脉搏传感器检测心跳,并在与其连接的 LCD 上显示 BPM(每分钟心跳次数)读数。它还将使用 Wi-Fi 模块ESP8266将读数发送到 ThingSpeak 服务…

vulnhub靶场之【digitalworld.local系列】的snakeoil靶机

前言 靶机&#xff1a;digitalworld.local-snakeoil&#xff0c;IP地址为192.168.10.11 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机选择使用VMware打开文件&#xff0c;都选择桥接网络 这里官方给的有两种方式&#xff0…

自行车的主要品牌

一、国际知名品牌&#xff08;专注运动与高端市场&#xff09; 捷安特&#xff08;GIANT&#xff09; 台湾品牌&#xff0c;全球最大自行车制造商之一&#xff0c;覆盖山地车、公路车、通勤车等多品类。 美利达&#xff08;MERIDA&#xff09; 台湾品牌&#xff0c;以山地车…