源碼地址:
? 該項目代碼在本人GitHub代碼倉庫開源,本人GitHub主頁為:GitHub
? 項目代碼:
https://github.com/guojin-yan/Inference/blob/master/tensorrt/cpp_tensorrt_yolov5/cpp_tensorrt_yolov5.cpp
? NVIDIA TensorRT? 是用于高性能深度學(xué)習(xí)推理的 SDK,可為深度學(xué)習(xí)推理應(yīng)用提供低延遲和高吞吐量。詳細(xì)安裝方式參考以下博客: NVIDIA TensorRT 安裝 (Windows C++)
1. TensorRT部署模型基本步驟
? 經(jīng)典的一個TensorRT部署模型步驟為:onnx模型轉(zhuǎn)engine、讀取本地模型、創(chuàng)建推理引擎、創(chuàng)建推理上下文、創(chuàng)建GPU顯存緩沖區(qū)、配置輸入數(shù)據(jù)、模型推理以及處理推理結(jié)果。
1.1 onnx模型轉(zhuǎn)engine
? TensorRT支持多種模型文件,不過隨著onnx模型的發(fā)展,目前多種模型框架都將onnx模型當(dāng)作中間轉(zhuǎn)換格式,是的該模型結(jié)構(gòu)變得越來越通用,因此TensorRT目前主要在更新的就是針對該模型的轉(zhuǎn)換。TensorRT是可以直接讀取engine文件,對于onnx模型需要進(jìn)行一些列轉(zhuǎn)換配置,轉(zhuǎn)為engine引擎才可以進(jìn)行后續(xù)的推理,因此在進(jìn)行模型推理前,需要先進(jìn)行模型的轉(zhuǎn)換。項目中已經(jīng)提供了轉(zhuǎn)換方法接口:
void onnx_to_engine(std::string onnx_file_path, std::string engine_file_path, int type) {
// 構(gòu)建器,獲取cuda內(nèi)核目錄以獲取最快的實(shí)現(xiàn)
// 用于創(chuàng)建config、network、engine的其他對象的核心類
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(gLogger);
const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
// 解析onnx網(wǎng)絡(luò)文件
// tensorRT模型類
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
// onnx文件解析類
// 將onnx文件解析,并填充rensorRT網(wǎng)絡(luò)結(jié)構(gòu)
nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, gLogger);
// 解析onnx文件
parser->parseFromFile(onnx_file_path.c_str(), 2);
for (int i = 0; i < parser->getNbErrors(); ++i) {
std::cout << "load error: " << parser->getError(i)->desc() << std::endl;
}
printf("tensorRT load mask onnx model successfully!!!...\n");
// 創(chuàng)建推理引擎
// 創(chuàng)建生成器配置對象。
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
// 設(shè)置最大工作空間大小。
config->setMaxWorkspaceSize(16 * (1 << 20));
// 設(shè)置模型輸出精度
if (type == 1) {
config->setFlag(nvinfer1::BuilderFlag::kFP16);
}
if (type == 2) {
config->setFlag(nvinfer1::BuilderFlag::kINT8);
}
// 創(chuàng)建推理引擎
nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
// 將推理銀槍保存到本地
std::cout << "try to save engine file now~~~" << std::endl;
std::ofstream file_ptr(engine_file_path, std::ios::binary);
if (!file_ptr) {
std::cerr << "could not open plan output file" << std::endl;
return;
}
// 將模型轉(zhuǎn)化為文件流數(shù)據(jù)
nvinfer1::IHostMemory* model_stream = engine->serialize();
// 將文件保存到本地
file_ptr.write(reinterpret_cast<const char*>(model_stream->data()), model_stream->size());
// 銷毀創(chuàng)建的對象
model_stream->destroy();
engine->destroy();
network->destroy();
parser->destroy();
std::cout << "convert onnx model to TensorRT engine model successfully!" << std::endl;
}
1.2 讀取本地模型
? 此處讀取本地模型為讀取上一步保存在本地的engine二進(jìn)制文件,將模型文件信息讀取到內(nèi)存中。該文件保存了模型的所有信息以及電腦的配置信息,因此該模型文件不支持在不同電腦上使用。
std::ifstream file_ptr(model_path_engine, std::ios::binary);
size_t size = 0;
file_ptr.seekg(0, file_ptr.end); // 將讀指針從文件末尾開始移動0個字節(jié)
size = file_ptr.tellg(); // 返回讀指針的位置,此時讀指針的位置就是文件的字節(jié)數(shù)
file_ptr.seekg(0, file_ptr.beg); // 將讀指針從文件開頭開始移動0個字節(jié)
char* model_stream = new char[size];
file_ptr.read(model_stream, size);
file_ptr.close();
1.3 創(chuàng)建推理引擎
? 首先需要初始化日志記錄接口類,該類用于創(chuàng)建后續(xù)反序列化引擎使用;然后創(chuàng)建反序列化引擎,其主要作用是允許對序列化的功能上不安全的引擎進(jìn)行反序列化,接下調(diào)用反序列化引擎來創(chuàng)建推理引擎,這一步只需要輸入上一步中讀取的模型文件數(shù)據(jù)以及長度即可。
// 日志記錄接口
Logger logger;
// 反序列化引擎
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
// 推理引擎
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(model_stream, size);
1.4 創(chuàng)建推理上下文
? 這里的推理上下文與OpenVINO中的推理請求相似,為后面進(jìn)行模型推理的類。
nvinfer1::IExecutionContext* context = engine->createExecutionContext();
1.5 創(chuàng)建GPU顯存緩沖區(qū)
? TensorRT是利用英偉達(dá)顯卡進(jìn)行模型推理的,但是我們的推理數(shù)據(jù)以及后續(xù)處理數(shù)據(jù)是在內(nèi)存中實(shí)現(xiàn)的,因此需要創(chuàng)建顯存緩沖區(qū),用于輸入推理數(shù)據(jù)以及讀取推理結(jié)果數(shù)據(jù)。
// 創(chuàng)建GPU顯存緩沖區(qū)
void** data_buffer = new void* [num_ionode];
// 創(chuàng)建GPU顯存輸入緩沖區(qū)
int input_node_index = engine->getBindingIndex(input_node_name);
cudaMalloc(&(data_buffer[input_node_index]), input_data_length * sizeof(float));
// 創(chuàng)建GPU顯存輸出緩沖區(qū)
int output_node_index = engine->getBindingIndex(output_node_name);
cudaMalloc(&(data_buffer[output_node_index]), output_data_length * sizeof(float));
1.6 配置輸入數(shù)據(jù)
? 配置輸入數(shù)據(jù)時只需要調(diào)用cudaMemcpyAsync()方法,便可將cuda流數(shù)據(jù)加載到與i里模型上。但數(shù)據(jù)需要根據(jù)模型要求進(jìn)行預(yù)處理,除此以外需要將數(shù)據(jù)結(jié)果加入到cuda流中。
// 創(chuàng)建輸入cuda流
cudaStream_t stream;
cudaStreamCreate(&stream);
std::vector<float> input_data(input_data_length);
memcpy(input_data.data(), BN_image.ptr<float>(), input_data_length * sizeof(float));
// 輸入數(shù)據(jù)由內(nèi)存到GPU顯存
cudaMemcpyAsync(data_buffer[input_node_index], input_data.data(), input_data_length * sizeof(float), cudaMemcpyHostToDevice, stream);
1.7 模型推理
context->enqueueV2(data_buffer, stream, nullptr);
1.8 處理推理結(jié)果
? 我們最后處理數(shù)據(jù)是在內(nèi)存上實(shí)現(xiàn)的,首先需要將數(shù)據(jù)由顯存讀取到內(nèi)存中。
float* result_array = new float[output_data_length];
cudaMemcpyAsync(result_array, data_buffer[output_node_index], output_data_length * sizeof(float), cudaMemcpyDeviceToHost, stream);
? 接下來就是根據(jù)模型輸出結(jié)果進(jìn)行數(shù)據(jù)處理,不同的模型會有不同的數(shù)據(jù)處理方式。
2. TensorRT 部署Yolov5模型
2.1 新建C++項目
? 右擊解決方案,選擇添加新建項目,添加一個C++空項目,將C++項目命名為:cpp_tensorrt_yolov5。進(jìn)入項目后,右擊源文件,選擇添加→新建項→C++文件(cpp),進(jìn)行的文件的添加。
? 右擊當(dāng)前項目,進(jìn)入屬性設(shè)置,配置TensorRT以及OpenCV的屬性。
設(shè)置包含目錄:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.4\include
D:\Program Files\TensorRT-8.4.0.6\include
E:\OpenCV Source\opencv-4.5.5\build\include
E:\OpenCV Source\opencv-4.5.5\build\include\opencv2
設(shè)置 ** 庫目錄**:
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.4\lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.4\lib\x64
D:\Program Files\TensorRT-8.4.0.6\lib
E:\OpenCV Source\opencv-4.5.5\build\x64\vc15\lib
設(shè)置附加依賴項:
nvinfer.lib
nvinfer_plugin.lib
nvonnxparser.lib
nvparsers.lib
cublas.lib
cublasLt.lib
cuda.lib
cudadevrt.lib
cudart.lib
cudart_static.lib
cudnn.lib
cudnn64_8.lib
cudnn_adv_infer.lib
cudnn_adv_infer64_8.lib
cudnn_adv_train.lib
cudnn_adv_train64_8.lib
cudnn_cnn_infer.lib
cudnn_cnn_infer64_8.lib
cudnn_cnn_train.lib
cudnn_cnn_train64_8.lib
cudnn_ops_infer.lib
cudnn_ops_infer64_8.lib
cudnn_ops_train.lib
cudnn_ops_train64_8.lib
cufft.lib
cufftw.lib
curand.lib
cusolver.lib
cusolverMg.lib
cusparse.lib
nppc.lib
nppial.lib
nppicc.lib
nppidei.lib
nppif.lib
nppig.lib
nppim.lib
nppist.lib
nppisu.lib
nppitc.lib
npps.lib
nvblas.lib
nvjpeg.lib
nvml.lib
nvrtc.lib
OpenCL.lib
opencv_world455.lib
2.2 定義yolov5模型相關(guān)信息
const char* model_path_onnx = "E:/Text_Model/yolov5/yolov5s.onnx";
const char* model_path_engine = "E:/Text_Model/yolov5/yolov5s.engine";
const char* image_path = "E:/Text_dataset/YOLOv5/0001.jpg";
std::string lable_path = "E:/Git_space/Al模型部署開發(fā)方式/model/yolov5/lable.txt";
const char* input_node_name = "images";
const char* output_node_name = "output";
int num_ionode = 2;
2.3 讀取本地模型信息
std::ifstream file_ptr(model_path_engine, std::ios::binary);
if (!file_ptr.good()) {
std::cerr << "文件無法打開,請確定文件是否可用!" << std::endl;
}
size_t size = 0;
file_ptr.seekg(0, file_ptr.end); // 將讀指針從文件末尾開始移動0個字節(jié)
size = file_ptr.tellg(); // 返回讀指針的位置,此時讀指針的位置就是文件的字節(jié)數(shù)
file_ptr.seekg(0, file_ptr.beg); // 將讀指針從文件開頭開始移動0個字節(jié)
char* model_stream = new char[size];
file_ptr.read(model_stream, size);
file_ptr.close();
2.4 初始化推理引擎
在此處我們需要初始化反序列化引擎以及推理引擎,并創(chuàng)建用于推理的上下文。
Logger logger;
// 反序列化引擎
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
// 推理引擎
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(model_stream, size);
// 上下文
nvinfer1::IExecutionContext* context = engine->createExecutionContext();
2.5 創(chuàng)建GPU顯存緩沖區(qū)
GPU顯存緩沖區(qū)的數(shù)量主要與模型的輸入輸出節(jié)點(diǎn)有關(guān),我們在此處只需要按照模型輸入輸出的節(jié)點(diǎn)數(shù)量進(jìn)行設(shè)置。
void** data_buffer = new void* [num_ionode];
// 創(chuàng)建GPU顯存輸入緩沖區(qū)
int input_node_index = engine->getBindingIndex(input_node_name);
nvinfer1::Dims input_node_dim = engine->getBindingDimensions(input_node_index);
size_t input_data_length = input_node_dim.d[1]* input_node_dim.d[2] * input_node_dim.d[3];
cudaMalloc(&(data_buffer[input_node_index]), input_data_length * sizeof(float));
// 創(chuàng)建GPU顯存輸出緩沖區(qū)
int output_node_index = engine->getBindingIndex(output_node_name);
nvinfer1::Dims output_node_dim = engine->getBindingDimensions(output_node_index);
size_t output_data_length = output_node_dim.d[1] * output_node_dim.d[2] ;
cudaMalloc(&(data_buffer[output_node_index]), output_data_length * sizeof(float));
2.6 配置模型輸入
? 首先對輸入圖片按照模型數(shù)據(jù)輸入要求進(jìn)行處理,首先是將圖片數(shù)據(jù)復(fù)制到正方形背景中,然后交換RGB通道、縮放至指定大小以及歸一化處理,在OpenCV中,blobFromImage()方法可以直接實(shí)現(xiàn)上述功能。
// 圖象預(yù)處理 - 格式化操作
cv::Mat image = cv::imread(image_path);
int max_side_length = std::max(image.cols, image.rows);
cv::Mat max_image = cv::Mat::zeros(cv::Size(max_side_length, max_side_length), CV_8UC3);
cv::Rect roi(0, 0, image.cols, image.rows);
image.copyTo(max_image(roi));
// 將圖像歸一化,并放縮到指定大小
cv::Size input_node_shape(input_node_dim.d[2], input_node_dim.d[3]);
cv::Mat BN_image = cv::dnn::blobFromImage(max_image, 1 / 255.0, input_node_shape, cv::Scalar(0, 0, 0), true, false);
? 接下來創(chuàng)建cuda流,將處理后的數(shù)據(jù)放置在input_data容器里;最后直接使用cudaMemcpyAsync()方法,將輸入數(shù)據(jù)輸送到顯存。
// 創(chuàng)建輸入cuda流
cudaStream_t stream;
cudaStreamCreate(&stream);
std::vector<float> input_data(input_data_length);
memcpy(input_data.data(), BN_image.ptr<float>(), input_data_length * sizeof(float));
// 輸入數(shù)據(jù)由內(nèi)存到GPU顯存
cudaMemcpyAsync(data_buffer[input_node_index], input_data.data(), input_data_length * sizeof(float), cudaMemcpyHostToDevice, stream);
2.7 模型推理
context->enqueueV2(data_buffer, stream, nullptr);
2.8 處理推理結(jié)果
? 首先讀取推理結(jié)果數(shù)據(jù),主要是將GPU顯存上的推理數(shù)據(jù)結(jié)果賦值到內(nèi)存上,方便后續(xù)對數(shù)據(jù)的進(jìn)一步處理。
float* result_array = new float[output_data_length];
cudaMemcpyAsync(result_array, data_buffer[output_node_index], output_data_length * sizeof(float), cudaMemcpyDeviceToHost, stream);
? 接下來就是處理數(shù)據(jù),Yolov5輸出結(jié)果為85x25200大小的數(shù)組,其中沒85個數(shù)據(jù)為一組,在該項目中我們提供了專門用于處理yolov5數(shù)據(jù)結(jié)果的結(jié)果處理類,因此在此處我們只需要調(diào)用該結(jié)果類即可:文章來源:http://www.zghlxwxcb.cn/news/detail-425844.html
ResultYolov5 result;
result.factor = max_side_length / (float) input_node_dim.d[2];
result.read_class_names(lable_path);
cv::Mat result_image = result.yolov5_result(image, result_array);
? 下圖為我們測試效果。文章來源地址http://www.zghlxwxcb.cn/news/detail-425844.html
到了這里,關(guān)于【TensorRT】TensorRT 部署Yolov5模型(C++)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!