yolov5使用onnxruntime進行c++部署-創(chuàng)新互聯(lián)

文章目錄
  • 前言
  • 1.使用onnxruntime
  • 2 使用onnxruntime進行c++部署
    • 1.使用onnxruntime的主要頭文件
    • 2.自定義參數(shù)配置結(jié)構(gòu)
    • 3.YOLOv5模塊
      • 初始化
      • 輸入圖像預(yù)處理
        • resize
        • 像素值歸一化并將輸入的bgr轉(zhuǎn)成rgb
      • onnxruntime推理
      • nms代碼
      • 可視化輸出結(jié)果
  • 總結(jié)

成都地區(qū)優(yōu)秀IDC服務(wù)器托管提供商(創(chuàng)新互聯(lián)建站).為客戶提供專業(yè)的托管服務(wù)器,四川各地服務(wù)器托管,托管服務(wù)器、多線服務(wù)器托管.托管咨詢專線:18980820575
前言

c++單文件部署之使用onnxruntime
通過代碼熟悉onnxrununtime用于檢測部署的整個流程

1.使用onnxruntime

在onnxruntime官網(wǎng)進行下載相應(yīng)版本的壓縮包:https://github.com/microsoft/onnxruntime/releases
在這里插入圖片描述

可以看看前面的提供的支持:
在這里插入圖片描述

將下載好的壓縮包解壓,不用編譯,直接在virtual stdio或者vscode上導(dǎo)入相應(yīng)的頭文件目錄和庫目錄以及相應(yīng)的依賴。這個百度解決,virtual stdio配置更容易,vscode相對要復(fù)雜些,Linux上更常用vscode,而且vscode更輕量也可以遠(yuǎn)程。當(dāng)然可以兩個都使用。

2 使用onnxruntime進行c++部署

對于里面一些不懂的api或者模塊,建議自己查看源碼或者去官網(wǎng)技術(shù)文檔查看:https://onnxruntime.ai/docs/api/c/struct_ort_api.html

1.使用onnxruntime的主要頭文件
#include#include#include#include#include#include// 提供cuda加速
#include // C或c++的api

// 命名空間
using namespace std;
using namespace cv;
using namespace Ort;
2.自定義參數(shù)配置結(jié)構(gòu)
// 自定義配置結(jié)構(gòu)
struct Configuration
{public: 
	float confThreshold; // Confidence threshold
	float nmsThreshold;  // Non-maximum suppression threshold
	float objThreshold;  //Object Confidence threshold
	string modelpath;
};

// 定義BoxInfo類型
typedef struct BoxInfo
{float x1;
	float y1;
	float x2;
	float y2;
	float score;
	int label;
} BoxInfo;
3.YOLOv5模塊

在yolov5類里,定義一些需要使用的成員函數(shù)和屬性

class YOLOv5
{public:
	YOLOv5(Configuration config);
	void detect(Mat& frame);
private:
	float confThreshold;
	float nmsThreshold;
	float objThreshold;
	int inpWidth;
	int inpHeight;
	int nout;
	int num_proposal;
	int num_classes;
	string classes[80] = {"person", "bicycle", "car", "motorbike", "aeroplane", "bus",
							"train", "truck", "boat", "traffic light", "fire hydrant",
							"stop sign", "parking meter", "bench", "bird", "cat", "dog",
							"horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe",
							"backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
							"skis", "snowboard", "sports ball", "kite", "baseball bat",
							"baseball glove", "skateboard", "surfboard", "tennis racket",
							"bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl",
							"banana", "apple", "sandwich", "orange", "broccoli", "carrot",
							"hot dog", "pizza", "donut", "cake", "chair", "sofa", "pottedplant",
							"bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse",
							"remote", "keyboard", "cell phone", "microwave", "oven", "toaster",
							"sink", "refrigerator", "book", "clock", "vase", "scissors",
							"teddy bear", "hair drier", "toothbrush"};
	const bool keep_ratio = true;
	vectorinput_image_;		// 輸入圖片
	void normalize_(Mat img);		// 歸一化函數(shù)
	void nms(vector& input_boxes);  
	Mat resize_image(Mat srcimg, int *newh, int *neww, int *top, int *left); // 預(yù)處理

	Env env = Env(ORT_LOGGING_LEVEL_ERROR, "yolov5-6.1"); //初始化環(huán)境,
	Session *ort_session = nullptr;    //初始化Session指針選項
	SessionOptions sessionOptions = SessionOptions();  //初始化Session對象
	//SessionOptions sessionOptions;
	vectorinput_names;  // 定義一個字符指針vector
	vectoroutput_names; // 定義一個字符指針vector
	vector>input_node_dims; // >=1 outputs  ,二維vector
	vector>output_node_dims; // >=1 outputs
};

Env:
在這里插入圖片描述

初始化
YOLOv5::YOLOv5(Configuration config)
{this->confThreshold = config.confThreshold;
	this->nmsThreshold = config.nmsThreshold;
	this->objThreshold = config.objThreshold;
	this->num_classes = sizeof(this->classes)/sizeof(this->classes[0]);  // 類別數(shù)量
	this->inpHeight = 640;
	this->inpWidth = 640;
	
	string model_path = config.modelpath;
	//std::wstring widestr = std::wstring(model_path.begin(), model_path.end());  //用于UTF-16編碼的字符

	//gpu, https://blog.csdn.net/weixin_44684139/article/details/123504222
	//CUDA加速開啟,選擇gpu
    OrtSessionOptionsAppendExecutionProvider_CUDA(sessionOptions, 0);

	sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);  //設(shè)置圖優(yōu)化類型
	//ort_session = new Session(env, widestr.c_str(), sessionOptions);  // 創(chuàng)建會話,把模型加載到內(nèi)存中
	//ort_session = new Session(env, (const ORTCHAR_T*)model_path.c_str(), sessionOptions); // 創(chuàng)建會話,把模型加載到內(nèi)存中
	ort_session = new Session(env, (const char*)model_path.c_str(), sessionOptions);
	size_t numInputNodes = ort_session->GetInputCount();  //輸入輸出節(jié)點數(shù)量                         
	size_t numOutputNodes = ort_session->GetOutputCount(); 
	AllocatorWithDefaultOptions allocator;   // 配置輸入輸出節(jié)點內(nèi)存
	for (int i = 0; i< numInputNodes; i++)
	{input_names.push_back(ort_session->GetInputName(i, allocator));		// 內(nèi)存
		Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);   // 類型
		auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();  // 
		auto input_dims = input_tensor_info.GetShape();    // 輸入shape
		input_node_dims.push_back(input_dims);	// 保存
	}
	for (int i = 0; i< numOutputNodes; i++)
	{output_names.push_back(ort_session->GetOutputName(i, allocator));
		Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
		auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();
		auto output_dims = output_tensor_info.GetShape();
		output_node_dims.push_back(output_dims);
	}
	this->inpHeight = input_node_dims[0][2];
	this->inpWidth = input_node_dims[0][3];
	this->nout = output_node_dims[0][2];      // 5+classes
	this->num_proposal = output_node_dims[0][1];  // pre_box

}

SetGraphOptimizationLevel模塊:
在這里插入圖片描述
優(yōu)化級別:
在這里插入圖片描述
Session:在這里插入圖片描述

輸入圖像預(yù)處理 resize

resize圖像尺寸和上篇博客使用opencv部署一樣

Mat YOLOv5::resize_image(Mat srcimg, int *newh, int *neww, int *top, int *left)
{int srch = srcimg.rows, srcw = srcimg.cols;
	*newh = this->inpHeight;
	*neww = this->inpWidth;
	Mat dstimg;
	if (this->keep_ratio && srch != srcw) {float hw_scale = (float)srch / srcw;
		if (hw_scale >1) {	*newh = this->inpHeight;
			*neww = int(this->inpWidth / hw_scale);
			resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
			*left = int((this->inpWidth - *neww) * 0.5);
			copyMakeBorder(dstimg, dstimg, 0, 0, *left, this->inpWidth - *neww - *left, BORDER_CONSTANT, 114);
		}
		else {	*newh = (int)this->inpHeight * hw_scale;
			*neww = this->inpWidth;
			resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
			*top = (int)(this->inpHeight - *newh) * 0.5;
			copyMakeBorder(dstimg, dstimg, *top, this->inpHeight - *newh - *top, 0, 0, BORDER_CONSTANT, 114);
		}
	}
	else {resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
	}
	return dstimg;
}
像素值歸一化并將輸入的bgr轉(zhuǎn)成rgb
void YOLOv5::normalize_(Mat img)
{//    img.convertTo(img, CV_32F);
	int row = img.rows;
	int col = img.cols;
	this->input_image_.resize(row * col * img.channels());  // vector大小
	for (int c = 0; c< 3; c++)  // bgr
	{for (int i = 0; i< row; i++)  // 行
		{	for (int j = 0; j< col; j++)  // 列
			{		float pix = img.ptr(i)[j * 3 + 2 - c];  // Mat里的ptr函數(shù)訪問任意一行像素的首地址,2-c:表示rgb
				this->input_image_[c * row * col + i * col + j] = pix / 255.0; // 歸一化
			}
		}
	}
}
onnxruntime推理

在對輸入圖片預(yù)處理后,不同于dnn的部署,ort這里要對輸入先進行內(nèi)存的分配。然后將輸入數(shù)據(jù)變成Tensor類型。

void YOLOv5::detect(Mat& frame)
{int newh = 0, neww = 0, padh = 0, padw = 0;
	Mat dstimg = this->resize_image(frame, &newh, &neww, &padh, &padw);
	this->normalize_(dstimg);
	// 定義一個輸入矩陣,int64_t是要符合下面輸入?yún)?shù)時的類型
	arrayinput_shape_{1, 3, this->inpHeight, this->inpWidth };  
    //創(chuàng)建輸入tensor
	auto allocator_info = MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
	Value input_tensor_ = Value::CreateTensor(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());

	// 開始推理
	vectorort_outputs = ort_session->Run(RunOptions{nullptr }, &input_names[0], &input_tensor_, 1, output_names.data(), output_names.size());   // 開始推理
	/generate proposals
	vectorgenerate_boxes;  // BoxInfo自定義的結(jié)構(gòu)體
	float ratioh = (float)frame.rows / newh, ratiow = (float)frame.cols / neww;
	float* pdata = ort_outputs[0].GetTensorMutableData(); // GetTensorMutableData
	for(int i = 0; i< num_proposal; ++i) // 遍歷所有的num_pre_boxes
	{int index = i * nout;      // prob[b*num_pred_boxes*(classes+5)]  
		float obj_conf = pdata[index + 4];  // 置信度分?jǐn)?shù)
		if (obj_conf >this->objThreshold)  // 大于閾值
		{	// 求大分?jǐn)?shù)和索引
			int class_idx = 0;
			float max_class_socre = 0;
			for (int k = 0; k< this->num_classes; ++k)
			{		if (pdata[k + index + 5] >max_class_socre)
				{max_class_socre = pdata[k + index + 5];
					class_idx = k;
				}
			}
			max_class_socre *= obj_conf;   // 大的類別分?jǐn)?shù)*置信度
			if (max_class_socre >this->confThreshold) // 再次篩選
			{		float cx = pdata[index];  //x
				float cy = pdata[index+1];  //y
				float w = pdata[index+2];  //w
				float h = pdata[index+3];  //h

				float xmin = (cx - padw - 0.5 * w)*ratiow;
				float ymin = (cy - padh - 0.5 * h)*ratioh;
				float xmax = (cx - padw + 0.5 * w)*ratiow;
				float ymax = (cy - padh + 0.5 * h)*ratioh;

				generate_boxes.push_back(BoxInfo{xmin, ymin, xmax, ymax, max_class_socre, class_idx });
			}
		}
	}

CreateCpu: 通過枚舉參數(shù)列表,選擇相應(yīng)的輸入?yún)?shù)給設(shè)備分配內(nèi)存
OrtMemTypeCPU:非CPU執(zhí)行提供程序分配的臨時CPU可訪問內(nèi)存,即CUDA_PINNED。
在這里插入圖片描述
Run:運行模型,返回Ort分配vector里的結(jié)果。
注意輸入?yún)?shù)的類型,最好在定義的時候要符合輸入類型標(biāo)準(zhǔn)。
在這里插入圖片描述

nms代碼

對應(yīng)nms代碼和python的一樣,不過這里不是用加上偏移值進行多類別的nms,而是只對對同類的iou是否大于閾值來判斷,對應(yīng)方法二:yolov5的推理輸出detect.py部分

void YOLOv5::nms(vector& input_boxes)
{sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b) {return a.score >b.score; }); // 降序排列
	vectorvArea(input_boxes.size());  // 面積
	for (int i = 0; i< input_boxes.size(); ++i)
	{vArea[i] = (input_boxes[i].x2 - input_boxes[i].x1 + 1)
			* (input_boxes[i].y2 - input_boxes[i].y1 + 1);
	}
	// 全初始化為false,用來作為記錄是否保留相應(yīng)索引下pre_box的標(biāo)志vector
	vectorisSuppressed(input_boxes.size(), false);  
	for (int i = 0; i< input_boxes.size(); ++i)
	{if (isSuppressed[i]) {continue; }
		for (int j = i + 1; j< input_boxes.size(); ++j)
		{	if (isSuppressed[j]) {continue; }
			float xx1 = max(input_boxes[i].x1, input_boxes[j].x1);
			float yy1 = max(input_boxes[i].y1, input_boxes[j].y1);
			float xx2 = min(input_boxes[i].x2, input_boxes[j].x2);
			float yy2 = min(input_boxes[i].y2, input_boxes[j].y2);

			float w = max(0.0f, xx2 - xx1 + 1);
			float h = max(0.0f, yy2 - yy1 + 1);
			float inter = w * h;	// 交集
			// 這里是對應(yīng)yolov5里的nms,要進行多類別判斷,只有同一類才進行iou計算
			if(input_boxes[i].label == input_boxes[j].label)  
			{		float ovr = inter / (vArea[i] + vArea[j] - inter);  // 計算iou
				if (ovr >= this->nmsThreshold)
				{isSuppressed[j] = true;
				}
			}	
		}
	}
	// return post_nms;
	int idx_t = 0;
    // remove_if()函數(shù) remove_if(beg, end, op) //移除區(qū)間[beg,end)中每一個“令判斷式:op(elem)獲得true”的元素
	input_boxes.erase(remove_if(input_boxes.begin(), input_boxes.end(), [&idx_t, &isSuppressed](const BoxInfo& f) {return isSuppressed[idx_t++]; }), input_boxes.end());
}
可視化輸出結(jié)果

圖:
在這里插入圖片描述
時間:
在這里插入圖片描述
在這里插入圖片描述

總結(jié)

c++部署
整個代碼放在:https://github.com/yzy12-max/yolov5_deploy
參考:
https://onnxruntime.ai/docs/api/c/struct_ort_api.html
https://github.com/hpc203/yolov5-v6.1-opencv-onnxrun

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧

網(wǎng)頁名稱:yolov5使用onnxruntime進行c++部署-創(chuàng)新互聯(lián)
網(wǎng)站URL:http://bm7419.com/article0/gigio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站網(wǎng)站設(shè)計、移動網(wǎng)站建設(shè)、外貿(mào)建站、外貿(mào)網(wǎng)站建設(shè)、關(guān)鍵詞優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)