이 기사를 읽기 전에 Win10에서 tensorrt와 함께 yolov5를 배포하는 방법에 대한 제 강의 기사를 읽으십시오. 그러면 돌아와서 이 기사를 읽는 것이 더 쉬울 것입니다.
딥 러닝 대상 탐지 프로젝트를 수행할 때 다음 표와 같은 요구 사항이 있는 경우가 있습니다.
알고리즘 FPS 요구 사항 | ≥50 |
운영 체제 | 윈10 |
개발 언어 | C++ |
모델 로딩 방식 | 하나의 GPU가 여러 모델을 로드합니다. |
여러 GPU가 단일 모델을 개별적으로 로드합니다. |
다음 블로그 게시물을 통해 이 두 가지 요구 사항을 개별적으로 완료할 수 있습니다.
하나의 GPU가 여러 모델을 로드합니다.
여러 모델을 로드하기 위해 GPU를 구현하려면 서로 다른 엔진 파일이 순서대로 로드된다는 것을 이해할 수 있습니다. 즉, 두 세트의 Cuda 스트림 및 버퍼 변수를 정의하거나 스트림 변수와 버퍼 변수를 클래스에 캡슐화해야 합니다. , 그런 다음 두 개의 인터페이스 클래스를 만들고 해당 API를 호출하여 하나의 GPU에 여러 모델을 로드합니다. yolov5의 가중치 엔진을 호출하는 것을 예로 들어 보겠습니다.먼저 클래스를 캡슐화합니다.
- .h 파일의 구현은 다음과 같습니다.
class True_interface : { public: True_interface(); virtual void load_model(int,std::string); // API:加载模型 virtual void release_buffer(); // API:释放缓冲及指针变量 virtual void interface(cv::Mat, float); // API:前向推理外部调用接口(输入图像和置信度阈值) void doInference(IExecutionContext& context, cudaStream_t& stream, void** buffers, float* input, float* output, int batchSize); // API:前向推理 const int INPUT_H = Yolo::INPUT_H; const int INPUT_W = Yolo::INPUT_W; const int OUTPUT_SIZE = Yolo::MAX_OUTPUT_BBOX_COUNT * sizeof(Yolo::Detection) / sizeof(float) + 1; float data[BATCH_SIZE * 3 * Yolo::INPUT_H * Yolo::INPUT_W]; float prob[BATCH_SIZE * (Yolo::MAX_OUTPUT_BBOX_COUNT * sizeof(Yolo::Detection) / sizeof(float) + 1)]; ICudaEngine* engine; IExecutionContext* context; // 创建context,创建一些空间来存储中间激活值 void* buffers[2]; // 创建buffers指向 GPU 上输入和输出缓冲区 cudaStream_t stream; // 创建cuda流 int inputIndex; int outputIndex; IRuntime* runtime; // 创建runtime const char* INPUT_BLOB_NAME = "data"; const char* OUTPUT_BLOB_NAME = "prob"; Logger gLogger; };
- .cpp 파일의 API 구현은 다음과 같습니다.실제로 정말 유용한 코드는 다음과 같습니다.
- load_model():
void True_interface::load_model(int gpu_id, std::string model_Name_) { if ((gpu_id == 0) || (gpu_id == 1)) // 选择要加载的GPU设备 { cudaError_t cudaError = cudaSetDevice(gpu_id); if (cudaError == cudaSuccess) std::cout << "GPU device selected successfully!" << std::endl; else std::cout << "GPU device selection failed!" << std::endl; } else { std::cerr << "GPU device assignment error!" << std::endl; return; } std::string engine_name; if (model_Name_ == "model_one") { engine_name = std::string("model_one.engine"); } else if(model_Name_ == "model_two") { engine_name = std::string("model_two.engine"); // 权重路径 } else { std::cout << "Please enter the correct detector type!" << std::endl; return; } float gd = 0.0f, gw = 0.0f; std::ifstream file(engine_name, std::ios::binary); if (!file.good()) { std::cerr << "read " << engine_name << " error!" << std::endl; } char* trtModelStream = nullptr; size_t size = 0; file.seekg(0, file.end); size = file.tellg(); file.seekg(0, file.beg); trtModelStream = new char[size]; file.read(trtModelStream, size); file.close(); runtime = createInferRuntime(gLogger); // 创建runtime engine = runtime->deserializeCudaEngine(trtModelStream, size); // 反序列化引擎 context = engine->createExecutionContext(); // 创建一个上下文,创建一些空间用来存储中间值 delete[] trtModelStream; inputIndex = engine->getBindingIndex(INPUT_BLOB_NAME); // 使用输入和输出blob名称来获得相应的输入和输出索引 outputIndex = engine->getBindingIndex(OUTPUT_BLOB_NAME); // 使用输入和输出blob名称来获得相应的输入和输出索引 CUDA_CHECK(cudaMalloc(&buffers[inputIndex], BATCH_SIZE * 3 * INPUT_H * INPUT_W * sizeof(float))); // 为输入输出开辟GPU显存 CUDA_CHECK(cudaMalloc(&buffers[outputIndex], BATCH_SIZE * OUTPUT_SIZE * sizeof(float))); // 为输入输出开辟GPU显存 CUDA_CHECK(cudaStreamCreate(&stream));// 绑定Cuda流 if (model_Name_ == "model_one") { std::cout << "model_one loaded successfully!" << std::endl; } else if (model_Name_ == "model_two") { std::cout << "model_two loaded successfully!" << std::endl; } }
- doInference():
void True_interface::doInference(IExecutionContext& context, cudaStream_t& stream, void** buffers, float* input, float* output, int batchSize) { // ---------------------------------从CPU到GPU,拷贝input数据----------------------------------------------------- CUDA_CHECK(cudaMemcpyAsync(buffers[0], // //显存上的存储区域,用于存放输入数据 input, batchSize * 3 * INPUT_H * INPUT_W * sizeof(float), // //读入内存中的数据 cudaMemcpyHostToDevice, stream)); context.enqueue(batchSize, buffers, stream, nullptr); // 异步推理 // ---------------------------------GPU到CPU,拷贝output数据----------------------------------------------------- CUDA_CHECK(cudaMemcpyAsync(output, buffers[1], batchSize * OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost, stream)); cudaStreamSynchronize(stream); // 同步cuda流 }
- 상호 작용():
void True_interface::interface_B(cv::Mat img, float conf_thresh) { // ---------------------------------------------------清空结果向量---------------------------------------------------- result_B.clear(); // ----------------------------------------------------图像预处理----------------------------------------------------- cv::Mat pr_img = preprocess_img(img, INPUT_W, INPUT_H); // resize 图像并转换通道 int i = 0; for (int row = 0; row < INPUT_H; ++row) { uchar* uc_pixel = pr_img.data + row * pr_img.step; for (int col = 0; col < INPUT_W; ++col) { data[i] = (float)uc_pixel[2] / 255.0; data[i + INPUT_H * INPUT_W] = (float)uc_pixel[1] / 255.0; data[i + 2 * INPUT_H * INPUT_W] = (float)uc_pixel[0] / 255.0; uc_pixel += 3; ++i; } } // ----------------------------------------------------前向推理----------------------------------------------------- doInference(*context, stream, buffers, data, prob, BATCH_SIZE); // ----------------------------------------------------非极大值抑制------------------------------------------------- std::vector<std::vector<Yolo::Detection>> batch_res(1); auto& res = batch_res[0]; nms(res, &prob[0], conf_thresh, NMS_THRESH); // ----------------------------------------------------输出目标坐标及置信度------------------------------------------------- for (size_t j = 0; j < res.size(); j++) { cv::Rect r = get_rect(img, res[j].bbox); std::vector<int> temp = { r.tl().x, r.tl().y, r.br().x, r.br().y, (int)res[j].class_id ,int(100*res[j].conf) }; result_B.push_back(temp); } }
- release_buffer():
void True_interface::release_buffer() { // 释放资源 cudaStreamDestroy(stream); CUDA_CHECK(cudaFree(buffers[inputIndex])); CUDA_CHECK(cudaFree(buffers[outputIndex])); // 销毁变量 context->destroy(); engine->destroy(); runtime->destroy(); std::cout << "Memory freed successfully!" << std::endl; }
- load_model():
- 그런 다음 두 개의 클래스를 초기화하고 load_model() 메서드를 각각 호출하여 하나의 GPU에 두 개의 모델을 로드하기만 하면 됩니다.
다른 GPU에 모델을 로드하려면 load_model() 메서드 를 호출 할 때 다른 GPU의 ID만 첫 번째 매개변수로 전달하면 됩니다 . 편리하고 간단합니까?