[TensorRT] TensorRT는 Yolov5 모델(C++)을 배포합니다.

소스 주소:

  프로젝트 코드는 내 GitHub 코드 웨어하우스의 오픈 소스입니다. 내 GitHub 홈페이지는 다음과 같습니다. GitHub
  프로젝트 코드:

https://github.com/guojin-yan/Inference/blob/master/tensorrt/cpp_tensorrt_yolov5/cpp_tensorrt_yolov5.cpp

  NVIDIA TensorRT™는 딥 러닝 추론 애플리케이션에 낮은 대기 시간과 높은 처리량을 제공하는 고성능 딥 러닝 추론용 SDK입니다. 자세한 설치 방법은 다음 블로그를 참조하세요: NVIDIA TensorRT Installation (Windows C++)
여기에 이미지 설명을 삽입하세요

1. TensorRT 배포 모델의 기본 단계

  전통적인 TensorRT 배포 모델 단계는 onnx 모델 전송 엔진, 로컬 모델 읽기, 추론 엔진 생성, 추론 컨텍스트 생성, GPU 메모리 버퍼 생성, 입력 데이터 구성, 모델 추론 및 추론 결과 처리입니다.

1.1 Onnx 모델에서 엔진까지

  TensorRT는 다양한 모델 파일을 지원합니다. 그러나 onnx 모델이 개발됨에 따라 현재 다양한 모델 프레임워크에서는 onnx 모델을 중간 변환 형식으로 사용하고 있습니다. 네, 모델 구조가 점점 일반화되고 있으므로 TensorRT는 현재 주로 업데이트를 진행하고 있습니다. . 이 모델의 변형입니다. TensorRT는 엔진 파일을 직접 읽을 수 있는데 onnx 모델의 경우 일련의 변환 구성이 필요하며, 후속 추론은 엔진 엔진으로 변환된 후에만 수행할 수 있으므로 모델 추론 전에 모델 변환이 수행되어야 합니다. 변환 방법 인터페이스가 프로젝트에 제공되었습니다:

void onnx_to_engine(std::string onnx_file_path, std::string engine_file_path, int type) {

	// 构建器,获取cuda内核目录以获取最快的实现
	// 用于创建config、network、engine的其他对象的核心类
	nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(gLogger);
	const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
	// 解析onnx网络文件
	// tensorRT模型类
	nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
	// onnx文件解析类
	// 将onnx文件解析,并填充rensorRT网络结构
	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");

	// 创建推理引擎
	// 创建生成器配置对象。
	nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
	// 设置最大工作空间大小。
	config->setMaxWorkspaceSize(16 * (1 << 20));
	// 设置模型输出精度
	if (type == 1) {
		config->setFlag(nvinfer1::BuilderFlag::kFP16);
	}
	if (type == 2) {
		config->setFlag(nvinfer1::BuilderFlag::kINT8);
	}
	// 创建推理引擎
	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;
	}
	// 将模型转化为文件流数据
	nvinfer1::IHostMemory* model_stream = engine->serialize();
	// 将文件保存到本地
	file_ptr.write(reinterpret_cast<const char*>(model_stream->data()), model_stream->size());
	// 销毁创建的对象
	model_stream->destroy();
	engine->destroy();
	network->destroy();
	parser->destroy();
	std::cout << "convert onnx model to TensorRT engine model successfully!" << std::endl;
}

1.2 로컬 모델 읽기

  여기서 로컬 모델을 읽는다는 것은 이전 단계에서 로컬에 저장한 엔진 바이너리 파일을 읽어오고, 모델 파일 정보를 메모리에 읽어들이는 것을 의미합니다. 이 파일에는 해당 모델의 모든 정보와 해당 컴퓨터의 구성 정보가 저장되므로, 해당 모델 파일을 다른 컴퓨터에서 사용할 수 없습니다.

std::ifstream file_ptr(model_path_engine, std::ios::binary);
size_t size = 0;
file_ptr.seekg(0, file_ptr.end);	// 将读指针从文件末尾开始移动0个字节
size = file_ptr.tellg();	// 返回读指针的位置,此时读指针的位置就是文件的字节数
file_ptr.seekg(0, file_ptr.beg);	// 将读指针从文件开头开始移动0个字节
char* model_stream = new char[size];
file_ptr.read(model_stream, size);
file_ptr.close();

1.3 추론 엔진 만들기

  먼저, 후속 역직렬화 엔진을 생성하는 데 사용되는 로깅 인터페이스 클래스를 초기화해야 합니다. 그런 다음 직렬화되어 기능적으로 안전하지 않은 엔진의 역직렬화를 허용하는 것이 주요 기능인 역직렬화 엔진을 생성한 다음 엔진을 역직렬화 호출하여 추론 엔진을 생성해야 합니다. 이번 단계에서는 이전 단계에서 읽어온 모델 파일의 데이터와 길이만 입력하면 됩니다.

// 日志记录接口
Logger logger;
// 反序列化引擎
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
// 推理引擎
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(model_stream, size);

1.4 추론 컨텍스트 생성

  여기서 추론 컨텍스트는 이후 모델 추론을 위한 클래스인 OpenVINO의 추론 요청과 유사합니다.

nvinfer1::IExecutionContext* context = engine->createExecutionContext();

1.5 GPU 메모리 버퍼 생성

  TensorRT는 모델 추론을 위해 NVIDIA 그래픽 카드를 사용하지만, 추론 데이터와 후속 처리 데이터는 메모리에 구현되므로 추론 데이터 입력을 위한 메모리 버퍼를 만들고 추론 결과 데이터를 읽어야 합니다.

// 创建GPU显存缓冲区
void** data_buffer = new void* [num_ionode];
// 创建GPU显存输入缓冲区
int input_node_index = engine->getBindingIndex(input_node_name);
cudaMalloc(&(data_buffer[input_node_index]), input_data_length * sizeof(float));
// 创建GPU显存输出缓冲区
int output_node_index = engine->getBindingIndex(output_node_name);
cudaMalloc(&(data_buffer[output_node_index]), output_data_length * sizeof(float));

1.6 입력 데이터 구성

  입력 데이터를 구성할 때 cudaMemcpyAsync() 메서드를 호출하여 cuda 스트림 데이터를 i 모델에 로드하기만 하면 됩니다. 그러나 모델의 요구 사항에 따라 데이터를 전처리해야 하며, 데이터 결과를 Cuda 스트림에 추가해야 합니다.

// 创建输入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));
// 输入数据由内存到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 추론 결과 처리

  마지막으로 메모리의 데이터를 처리합니다.먼저 비디오 메모리에서 메모리로 데이터를 읽어야 합니다.

float* result_array = new float[output_data_length];
cudaMemcpyAsync(result_array, data_buffer[output_node_index], output_data_length * sizeof(float), cudaMemcpyDeviceToHost, stream);

  다음 단계는 모델의 출력 결과에 따라 데이터를 처리하는 것입니다. 모델마다 데이터 처리 방법이 다릅니다.

2. TensorRT는 Yolov5 모델을 배포합니다.

2.1 새 C++ 프로젝트 만들기

  솔루션을 마우스 오른쪽 단추로 클릭하고 새 프로젝트 추가를 선택한 후 C++ 빈 프로젝트를 추가하고 C++ 프로젝트 이름을 cpp_tensorrt_yolov5로 지정합니다. 프로젝트 진입 후 소스 파일을 마우스 오른쪽 버튼으로 클릭하고 추가 → 새 항목 → C++ 파일(cpp)을 선택하여 파일을 추가합니다.
  현재 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 속성 설정을 입력한 후 TensorRT 및 OpenCV의 속성을 구성합니다.

포함 디렉터리 설정 :

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

**라이브러리 디렉토리** 설정:

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

추가 종속성을 설정합니다 .

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 모델의 관련 정보 정의

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模型部署开发方式/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个字节
size = file_ptr.tellg();	// 返回读指针的位置,此时读指针的位置就是文件的字节数
file_ptr.seekg(0, file_ptr.beg);	// 将读指针从文件开头开始移动0个字节
char* model_stream = new char[size];
file_ptr.read(model_stream, size);
file_ptr.close();

2.4 추론 엔진 초기화

여기서는 역직렬화 엔진과 추론 엔진을 초기화하고 추론을 위한 컨텍스트를 생성해야 합니다.

Logger logger;
// 反序列化引擎
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);
// 推理引擎
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(model_stream, size);
// 上下文
nvinfer1::IExecutionContext* context = engine->createExecutionContext();

2.5 GPU 메모리 버퍼 생성

GPU 메모리 버퍼 수는 주로 모델의 입력 및 출력 노드와 관련이 있으며 여기서는 모델의 입력 및 출력 노드 수에 따라 설정하면 됩니다.

void** data_buffer = new void* [num_ionode];
// 创建GPU显存输入缓冲区
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));
// 创建GPU显存输出缓冲区
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 모델 입력 구성

  먼저 모델 데이터 입력 요구 사항에 따라 입력 이미지를 처리한 다음, 이미지 데이터를 정사각형 배경에 복사한 다음 RGB 채널을 교환하고 지정된 크기로 스케일링한 후 정규화합니다. OpenCV에서는 blobFromImage( ) 메소드는 위 함수를 직접 구현할 수 있습니다.

// 图象预处理 - 格式化操作
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);

  다음으로, cuda 스트림을 생성하고 처리된 데이터를 input_data 컨테이너에 배치하고, 마지막으로 cudaMemcpyAsync() 메서드를 직접 사용하여 입력 데이터를 비디오 메모리로 전송합니다.

// 创建输入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));
// 输入数据由内存到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 추론 결과 처리

  먼저 추론 결과 데이터를 읽습니다. 주로 GPU 메모리의 추론 데이터 결과를 메모리에 할당하여 후속 데이터 처리를 용이하게 합니다.

float* result_array = new float[output_data_length];
cudaMemcpyAsync(result_array, data_buffer[output_node_index], output_data_length * sizeof(float), cudaMemcpyDeviceToHost, stream);

  다음 단계는 데이터를 처리하는 것으로, Yolov5의 출력 결과는 85x25200 크기의 배열로 85개 데이터가 그룹화되어 있지 않으며, 본 프로젝트에서는 yolov5 데이터의 결과를 처리하기 위해 특별히 사용되는 결과 처리 클래스를 제공하므로 여기서는 결과 클래스만 호출하면 됩니다.

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);

  아래 그림은 테스트 결과를 보여줍니다.
여기에 이미지 설명을 삽입하세요

추천

출처blog.csdn.net/grape_yan/article/details/128550102