使用騰訊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.pswp.cn/pingmian/71732.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/71732.shtml
英文地址,請注明出處:http://en.pswp.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;以山地車…