赞
踩
NVIDIA ® TensorRT ™ 是一款用于高性能深度学习推理的 SDK,包含深度学习推理优化器和运行时,可为推理应用程序提供低延迟和高吞吐量。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销,为实时目标检测领域带来了新的突破。
在本文中,我们将演示如何使用NVIDIA TensorRT C++ API 部署YOLOv10目标检测模型,实现模型推理加速。下面看一下YOLOv10模型在TensorRT上的运行效果吧:
YOLOv10实现500FPS推理速度,快到离谱!!——使用 TensorRT C++ API 调用GPU加速部署YOLOv10实现快速预测
TensorRT是NVIDIA官方推出的一个高性能深度学习推理加速引擎,它能够使深度学习模型在GPU上进行低延迟、高吞吐量的部署。TensorRT是基于CUDA和cuDNN的,专门为NVIDIA的GPU进行了优化。TensorRT支持TensorFlow、PyTorch、Caffe、MxNet等深度学习框架。对于MxNet和PyTorch,需要先将其模型转换为中间模型ONNX格式。总的来说,TensorRT是一个强大的深度学习推理加速引擎,通过优化和部署深度学习模型,能够在各种应用场景中实现快速、高效的推理性能。
YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法,该方法在Ultralytics Python包的基础上进行了多项创新和改进,主要有以下特点
通过广泛的实验验证,YOLOv10在多个模型尺度上实现了卓越的精度-延迟权衡。例如,在COCO数据集上,YOLOv10-S在相似精度下比其他实时目标检测方法更快,同时参数和浮点运算量也大幅减少。综上所述,YOLOv10通过消除NMS、优化模型架构和引入创新模块等策略,在保持高精度的同时显著降低了计算开销,为实时目标检测领域带来了新的突破。
下面简单介绍一下项目的开发环境,开发者可以根据自己的设备情况进行配置:
此处代码开发平台使用的是C++,因此在项目配置时,需要配置第三方依赖库,分别是CUDA\CUDNN、TensorRT和OpenCV三个依赖库,其配置方式此处不做详述。
YOLOv10 模型需要源码进行下载,首先克隆GitHub上的源码,输入以下指令:
git clone https://github.com/THU-MIG/yolov10.git
cd yolov10
接下来安装模型下载以及转换环境,此处使用Anaconda进行程序集管理,输入以下指令创建一个yolov10环境:
conda create -n yolov10 python=3.9
conda activate yolov10
pip install -r requirements.txt
pip install -e .
首先导出目标识别模型,此处以官方预训练模型为例,首先下载预训练模型文件,然后调用yolo
导出ONBNX格式的模型文件,最后使用 OpenVINO™ 的模型转换命令将模型转为IR格式,依次输入以下指令即可:
wget https://github.com/jameslahm/yolov10/releases/download/v1.0/yolov10s.pt
yolo export model=yolov10s.pt format=onnx opset=13 simplify
首先定义ONNX模型转换Engine格式的代码,如下所示:
#include "opencv2/opencv.hpp" #include <fstream> #include <iostream> #include "cuda.h" #include "NvInfer.h" #include "NvOnnxParser.h" class Logger : public nvinfer1::ILogger { void log(Severity severity, const char* msg) noexcept override { if (severity <= Severity::kWARNING) std::cout << msg << std::endl; } } logger; void onnxToEngine(const char* onnxFile, int memorySize) { // 将路径作为参数传递给函数 std::string path(onnxFile); std::string::size_type iPos = (path.find_last_of('\\') + 1) == 0 ? path.find_last_of('/') + 1 : path.find_last_of('\\') + 1; std::string modelPath = path.substr(0, iPos);//获取文件路径 std::string modelName = path.substr(iPos, path.length() - iPos);//获取带后缀的文件名 std::string modelName_ = modelName.substr(0, modelName.rfind("."));//获取不带后缀的文件名名 std::string engineFile = modelPath + modelName_ + ".engine"; // 构建器,获取cuda内核目录以获取最快的实现 // 用于创建config、network、engine的其他对象的核心类 nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger); // 构建器,获取cuda内核目录以获取最快的实现,用于创建config、network、engine的其他对象的核心类 const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH); // 定义网络属性 nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch); // 解析onnx网络文件,tensorRT模型类 nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, logger); // 将onnx文件解析,并填充rensorRT网络结构 parser->parseFromFile(onnxFile, 2); // 解析onnx文件 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"); // 创建推理引擎 nvinfer1::IBuilderConfig* config = builder->createBuilderConfig(); // 创建生成器配置对象。 config->setMaxWorkspaceSize(1024 * 1024 * memorySize); // 设置最大工作空间大小。 config->setFlag(nvinfer1::BuilderFlag::kFP16); // 设置模型输出精度 nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config); // 创建推理引擎 // 将推理文件保存到本地 std::cout << "try to save engine file now~~~" << std::endl; std::ofstream filePtr(engineFile, std::ios::binary); if (!filePtr) { std::cerr << "could not open plan output file" << std::endl; return; } // 将模型转化为文件流数据 nvinfer1::IHostMemory* modelStream = engine->serialize(); // 将文件保存到本地 filePtr.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size()); // 销毁创建的对象 modelStream->destroy(); engine->destroy(); network->destroy(); parser->destroy(); std::cout << "convert onnx model to TensorRT engine model successfully!" << std::endl; }
通过调用TensorRT模型优化器,可以将ONNX模型进行优化,结合本机GPU设备,进行优化加速,并转换成TensorRT模型支持的模型格式,这一步也可以在模型推理时进行,但是模型优化需要较长时间,因此最好先将模型进行转换。定义好代码后,在主函数中调用即可,如下所示:
onnxToEngine("E:\\Text_Model\\yolov10s.onnx", 50);
数据预处理此处通过OpenCV实现,将输入的图片数据转为模型需要的数据情况,代码如下所示:
void preProcess(cv::Mat *img, int length, float* factor, std::vector<float>& data) { cv::Mat mat; int rh = img->rows; int rw = img->cols; int rc = img->channels(); cv::cvtColor(*img, mat, cv::COLOR_BGR2RGB); int maxImageLength = rw > rh ? rw : rh; cv::Mat maxImage = cv::Mat::zeros(maxImageLength, maxImageLength,CV_8UC3); maxImage = maxImage * 255; cv::Rect roi (0, 0, rw, rh); mat.copyTo(cv::Mat(maxImage, roi)); cv::Mat resizeImg; cv::resize(maxImage, resizeImg, cv::Size(length, length), 0.0f, 0.0f, cv::INTER_LINEAR); *factor = (float)((float)maxImageLength / (float)length); resizeImg.convertTo(resizeImg, CV_32FC3, 1 / 255.0); rh = resizeImg.rows; rw = resizeImg.cols; rc = resizeImg.channels(); for (int i = 0; i < rc; ++i) { cv::extractChannel(resizeImg, cv::Mat(rh, rw, CV_32FC1, data.data() + i * rh * rw), i); } }
在调用时也相对简单,将相关变量传入即可,代码如下所示:
Mat frame = new frame();
std::vector<float> inputData(640 * 640 * 3);
float factor = 0;
preProcess(&frame, 640, &factor, inputData);
首先此处定义了一个结果类:
struct DetResult {
cv::Rect bbox;
float conf;
int lable;
DetResult(cv::Rect bbox,float conf,int lable):bbox(bbox),conf(conf),lable(lable){}
};
然后定义模型的结果处理方式,代码如下所示:
std::vector<DetResult> postProcess(float* result, float factor, int outputLength) { std::vector<cv::Rect> positionBoxes; std::vector <int> classIds; std::vector <float> confidences; // Preprocessing output results for (int i = 0; i < outputLength; i++) { int s = 6 * i; if ((float)result[s + 4] > 0.2) { float cx = result[s + 0]; float cy = result[s + 1]; float dx = result[s + 2]; float dy = result[s + 3]; int x = (int)((cx)* factor); int y = (int)((cy)* factor); int width = (int)((dx - cx) * factor); int height = (int)((dy - cy) * factor); cv::Rect box(x, y, width, height); positionBoxes.push_back(box); classIds.push_back((int)result[s + 5]); confidences.push_back((float)result[s + 4]); } } std::vector<DetResult> re; for (int i = 0; i < positionBoxes.size(); i++) { DetResult det(positionBoxes[i], confidences[i], classIds[i]); re.push_back(det); } return re; }
最后为了让结果可视化,定义了结果绘制方法,代码如下所示:
void drawBbox(cv::Mat& img, std::vector<DetResult>& res) {
for (size_t j = 0; j < res.size(); j++) {
cv::rectangle(img, res[j].bbox, cv::Scalar(255, 0, 255), 2);
cv::putText(img, std::to_string(res[j].lable) + "-" + std::to_string(res[j].conf),
cv::Point(res[j].bbox.x, res[j].bbox.y - 1), cv::FONT_HERSHEY_PLAIN,
1.2, cv::Scalar(0, 0, 255), 2);
}
}
上述方式调用依旧十分容易,使用代码如下所示:
std::vector<float> output_data(300 * 6);
std::vector<DetResult> result = postProcess(output_data.data(), factor, 300);
drawBbox(frame, result);
首先读取上文中转换的Engine模型,并创建推理通道,用于后文的模型推理,实现代码如下所示:
std::shared_ptr<nvinfer1::IExecutionContext> creatContext(std::string modelPath) { // 以二进制方式读取问价 std::ifstream filePtr(modelPath, std::ios::binary); if (!filePtr.good()) { std::cerr << "文件无法打开,请确定文件是否可用!" << std::endl; return std::shared_ptr<nvinfer1::IExecutionContext>(); } size_t size = 0; filePtr.seekg(0, filePtr.end); // 将读指针从文件末尾开始移动0个字节 size = filePtr.tellg(); // 返回读指针的位置,此时读指针的位置就是文件的字节数 filePtr.seekg(0, filePtr.beg); // 将读指针从文件开头开始移动0个字节 char* modelStream = new char[size]; filePtr.read(modelStream, size); // 关闭文件 filePtr.close(); nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger); nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(modelStream, size); return std::shared_ptr<nvinfer1::IExecutionContext>(engine->createExecutionContext()); }
下面结合一个视频推理,编写TensorRT推理YOLOv10的流程,代码如下所示:
#include "opencv2/opencv.hpp" #include <fstream> #include <iostream> #include "cuda.h" #include "NvInfer.h" #include "NvOnnxParser.h" class Logger : public nvinfer1::ILogger { void log(Severity severity, const char* msg) noexcept override { // suppress info-level messages if (severity <= Severity::kWARNING) std::cout << msg << std::endl; } } logger; void yolov10Infer() { const char* videoPath = "E:\\Text_dataset\\car_test.mov"; const char* enginePath = "E:\\Text_Model\\yolov10s.engine"; std::shared_ptr<nvinfer1::IExecutionContext> context = creatContext(enginePath); cv::VideoCapture capture(videoPath); // 检查摄像头是否成功打开 if (!capture.isOpened()) { std::cerr << "ERROR: 视频无法打开" << std::endl; return; } cudaStream_t stream; cudaStreamCreate(&stream); void* inputSrcDevice; void* outputSrcDevice; cudaMalloc(&inputSrcDevice, 3 * 640 * 640 * sizeof(float)); cudaMalloc(&outputSrcDevice, 1 * 300 * 6 * sizeof(float)); std::vector<float> output_data(300 * 6); std::vector<float> inputData(640 * 640 * 3); while (true) { cv::Mat frame; if (!capture.read(frame)) { break; } float factor = 0; preProcess(&frame, 640, &factor, inputData); cudaMemcpyAsync(inputSrcDevice, inputData.data(), 3 * 640 * 640 * sizeof(float), cudaMemcpyHostToDevice, stream); void* bindings[] = { inputSrcDevice, outputSrcDevice }; context->enqueueV2((void**)bindings, stream, nullptr); cudaMemcpyAsync(output_data.data(), outputSrcDevice, 300 * 6 * sizeof(float), cudaMemcpyDeviceToHost, stream); cudaStreamSynchronize(stream); std::vector<DetResult> result = postProcess(output_data.data(), factor, 300); drawBbox(frame, result); imshow("读取视频", frame); cv::waitKey(10); //延时30 } cv::destroyAllWindows(); }
通过上诉代码便可以实现使用NVIDIA TensorRT C++部署YOLOv10实现GPU加速。
在本文中,我们将演示如何使用NVIDIA TensorRT C++ API 部署YOLOv10目标检测模型,实现模型推理加速。最后我们对模型推理速度进行了测试,测试结果如下所示:
PreProcess | Inference | PostProcess | |
---|---|---|---|
Time (ms) | 7.59 | 1.35 | 6.95 |
在上述中已经提供了项目实现的全部源码,但都是零散的,如果大家在使用中有疑问,可以下载项目源码文件,下载链接为:
https://download.csdn.net/download/Grape_yan/89396724
最后如果各位开发者在使用中有任何问题,欢迎大家与我联系。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。