rtsp_tensorrt/pipeline/inference/trt_inference.cpp
sladro e13cb3659c feat: 初始化项目结构
- 创建基本项目结构和目录
- 添加CMake构建系统
- 实现基础的配置解析功能
- 添加YOLO推理框架支持
- 集成RTSP和视频流处理功能
- 添加性能监控和日志系统
2024-12-24 16:25:03 +08:00

577 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "trt_inference.hpp"
#include "cuda_helper.hpp"
#include <fstream>
#include <NvOnnxParser.h>
#include <iostream>
#include <filesystem>
#include "../common/logger.hpp"
#include <opencv2/imgproc.hpp>
namespace pipeline {
TrtInference::TrtInference(const InferenceConfig& config)
: config_(config),
logger_(std::make_unique<detail::Logger>()),
stream_(std::make_unique<detail::CudaStream>()) {
// 创建预处理器配置
Preprocessor::Config preprocess_config;
preprocess_config.input_shape = config.model.input_shape;
preprocess_config.use_cuda = true; // 默认使用CUDA加速
preprocess_config.max_batch_size = config.max_batch_size;
preprocess_config.scale = 1.0f/255.0f; // 归一化系数
// 创建预处理器实例
preprocessor_ = std::make_unique<Preprocessor>(preprocess_config);
}
TrtInference::~TrtInference() {
destroy();
}
bool TrtInference::convertOnnxToEngine(const std::string& onnx_path, const std::string& engine_path) {
try {
Logger::info("=== Starting ONNX to TensorRT Conversion ===");
Logger::info("ONNX file: " + onnx_path);
// 检查文件是否存在
if (!std::filesystem::exists(onnx_path)) {
Logger::error("ONNX file does not exist: " + onnx_path);
return false;
}
// 创建 builder
std::unique_ptr<nvinfer1::IBuilder, void(*)(nvinfer1::IBuilder*)> builder(
nvinfer1::createInferBuilder(*logger_),
[](nvinfer1::IBuilder* b) { delete b; });
if (!builder) {
Logger::error("Failed to create TensorRT Builder");
return false;
}
// 创建网络定义
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
const auto explicit_batch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
#pragma GCC diagnostic pop
std::unique_ptr<nvinfer1::INetworkDefinition, void(*)(nvinfer1::INetworkDefinition*)> network(
builder->createNetworkV2(explicit_batch),
[](nvinfer1::INetworkDefinition* n) { delete n; });
if (!network) {
Logger::error("Failed to create network definition");
return false;
}
// 创建 ONNX 解析器
std::unique_ptr<nvonnxparser::IParser, void(*)(nvonnxparser::IParser*)> parser(
nvonnxparser::createParser(*network, *logger_),
[](nvonnxparser::IParser* p) { delete p; });
if (!parser) {
Logger::error("Failed to create ONNX parser");
return false;
}
// 解析 ONNX 文件
if (!parser->parseFromFile(onnx_path.c_str(), static_cast<int>(nvinfer1::ILogger::Severity::kINFO))) {
Logger::error("Failed to parse ONNX file");
return false;
}
// 创建构建配置
std::unique_ptr<nvinfer1::IBuilderConfig, void(*)(nvinfer1::IBuilderConfig*)> config(
builder->createBuilderConfig(),
[](nvinfer1::IBuilderConfig* c) { delete c; });
if (!config) {
Logger::error("Failed to create builder config");
return false;
}
// 修改优化配置部分
auto profile = builder->createOptimizationProfile();
auto input = network->getInput(0);
if (!input) {
Logger::error("Failed to get network input");
return false;
}
// 获取输入名称和维度
const char* input_name = input->getName();
nvinfer1::Dims dims = input->getDimensions();
// 确保所有维度都是正数
for (int i = 0; i < dims.nbDims; i++) {
if (dims.d[i] < 0) {
if (i == 0) {
// batch维度
dims.d[i] = 1;
} else {
// 其他维度使用配置中的值
dims.d[i] = config_.model.input_shape[i-1];
}
}
}
// 设置最小、最优和最大维度
nvinfer1::Dims min_dims = dims;
nvinfer1::Dims opt_dims = dims;
nvinfer1::Dims max_dims = dims;
// 只修改batch维度
min_dims.d[0] = 1; // 最小batch size
opt_dims.d[0] = 1; // 最优batch size
max_dims.d[0] = config_.max_batch_size; // 最大batch size
// 设置优化配置
profile->setDimensions(input_name, nvinfer1::OptProfileSelector::kMIN, min_dims);
profile->setDimensions(input_name, nvinfer1::OptProfileSelector::kOPT, opt_dims);
profile->setDimensions(input_name, nvinfer1::OptProfileSelector::kMAX, max_dims);
if (!profile->isValid()) {
Logger::error("Invalid optimization profile");
return false;
}
config->addOptimizationProfile(profile);
// 设置工作空间大小
config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, config_.workspace_size);
// 设置精度
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (builder->platformHasFastFp16()) {
Logger::info("Enabling FP16 precision");
config->setFlag(nvinfer1::BuilderFlag::kFP16);
}
#pragma GCC diagnostic pop
// 构建引擎
std::unique_ptr<nvinfer1::IHostMemory, void(*)(nvinfer1::IHostMemory*)> serializedEngine(
builder->buildSerializedNetwork(*network, *config),
[](nvinfer1::IHostMemory* m) { delete m; });
if (!serializedEngine) {
Logger::error("Failed to build TensorRT engine");
return false;
}
// 保存引擎文件
std::ofstream engineFile(engine_path, std::ios::binary);
if (!engineFile) {
Logger::error("Failed to open engine file for writing");
return false;
}
engineFile.write(static_cast<const char*>(serializedEngine->data()), serializedEngine->size());
engineFile.close();
Logger::info("Successfully saved TensorRT engine to: " + engine_path);
return true;
}
catch (const std::exception& e) {
Logger::error("Error in convertOnnxToEngine: " + std::string(e.what()));
return false;
}
}
bool TrtInference::loadEngine() {
try {
// 确保CUDA上下文初始化
cudaFree(0); // 强制初始化CUDA上下文
// 检查文件是否存在
if (!std::filesystem::exists(config_.model.engine_path)) {
Logger::error("Engine file not found: " + config_.model.engine_path);
return false;
}
// 读取引擎文件
std::ifstream file(config_.model.engine_path, std::ios::binary);
if (!file) {
Logger::error("Failed to open engine file");
return false;
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> engineData(size);
file.read(engineData.data(), size);
file.close();
// 创建 runtime
runtime_ = nvinfer1::createInferRuntime(*logger_);
if (!runtime_) {
Logger::error("Failed to create TensorRT Runtime");
return false;
}
// 反序列化引擎
engine_ = runtime_->deserializeCudaEngine(engineData.data(), size);
if (!engine_) {
Logger::error("Failed to deserialize TensorRT engine");
return false;
}
// 创建执行上下文
if (!createContext()) {
Logger::error("Failed to create execution context");
return false;
}
// 分配缓冲区
if (!allocateBuffers()) {
Logger::error("Failed to allocate CUDA buffers");
return false;
}
// 同步设备
cudaDeviceSynchronize();
Logger::info("Successfully loaded TensorRT engine");
return true;
}
catch (const std::exception& e) {
Logger::error("Error in loadEngine: " + std::string(e.what()));
return false;
}
}
bool TrtInference::infer(const std::vector<cv::Mat>& images, std::vector<DetectionResult>& results) {
if (!isLoaded()) {
Logger::error("Engine not loaded");
return false;
}
try {
// 确保CUDA上下文有效
cudaFree(0);
// 1. 预处理
if (!preprocess(images)) {
return false;
}
// 2. 设置输入形状
if (!context_->setInputShape("images",
nvinfer1::Dims4(images.size(), config_.model.input_shape[0],
config_.model.input_shape[1], config_.model.input_shape[2]))) {
Logger::error("Failed to set input shape");
return false;
}
// 3. 设置输入和输出张量地址
if (!context_->setTensorAddress("images", input_buffers_[0]->devicePtr())) {
Logger::error("Failed to set input tensor address");
return false;
}
const char* output_name = engine_->getIOTensorName(engine_->getNbIOTensors() - 1);
if (!context_->setTensorAddress(output_name, output_buffers_[0]->devicePtr())) {
Logger::error("Failed to set output tensor address");
return false;
}
// 4. 执行推理
if (!context_->enqueueV3(stream_->get())) {
Logger::error("Inference failed");
return false;
}
// 5. 后处理
if (!postprocess(results)) {
return false;
}
// 6. 同步流和设备
if (!stream_->sync()) {
Logger::error("Failed to synchronize CUDA stream");
return false;
}
cudaDeviceSynchronize();
return true;
}
catch (const std::exception& e) {
Logger::error("Error in inference: " + std::string(e.what()));
return false;
}
}
bool TrtInference::preprocess(const std::vector<cv::Mat>& images) {
try {
// 检查输入
if (images.empty() || images.size() > static_cast<size_t>(config_.max_batch_size)) {
Logger::error("Invalid batch size: " + std::to_string(images.size()));
return false;
}
// 使用预处理器处理图像
if (!preprocessor_->process(images, *input_buffers_[0])) {
Logger::error("Preprocessing failed");
return false;
}
return true;
}
catch (const std::exception& e) {
Logger::error("Error in preprocessing: " + std::string(e.what()));
return false;
}
}
bool TrtInference::postprocess(std::vector<DetectionResult>& results) {
try {
// 获取输出尺寸
const auto output_dims = engine_->getTensorShape(output_names_[0].c_str());
const int num_outputs = output_dims.d[1]; // 每个检测的输出维度
const int max_detections = output_dims.d[2]; // 最大检测数量
// 分配主机内存接收结果
const size_t output_size = output_buffers_[0]->size();
std::vector<float> output_data(output_size / sizeof(float));
// 从GPU拷贝结果
if (cudaMemcpyAsync(output_data.data(), output_buffers_[0]->devicePtr(),
output_size, cudaMemcpyDeviceToHost, stream_->get()) != cudaSuccess) {
Logger::error("Failed to copy output data from GPU");
return false;
}
// 等待拷贝完成
if (cudaStreamSynchronize(stream_->get()) != cudaSuccess) {
Logger::error("Failed to synchronize CUDA stream");
return false;
}
// 解析结果
results.clear();
results.resize(1); // 每批次的结果
auto& result = results[0];
// YOLOv8输出格式[batch_id, x, y, w, h, conf, class_conf, class_id]
for (int i = 0; i < max_detections; ++i) {
const float* det = output_data.data() + i * num_outputs;
const float conf = det[5];
if (conf < config_.threshold.conf) continue;
BBox box;
box.x1 = det[1] - det[3] / 2.0f; // center_x - width/2
box.y1 = det[2] - det[4] / 2.0f; // center_y - height/2
box.x2 = det[1] + det[3] / 2.0f; // center_x + width/2
box.y2 = det[2] + det[4] / 2.0f; // center_y + height/2
box.score = conf; // 使用 score 而不是 conf
box.class_id = static_cast<int>(det[7]);
result.boxes.push_back(box);
}
return true;
}
catch (const std::exception& e) {
Logger::error("Error in postprocessing: " + std::string(e.what()));
return false;
}
}
void TrtInference::doNMS(std::vector<BBox>& boxes, float nms_thresh) {
// 按置信度排序,修改 lambda 表达式确保返回 bool 值
std::sort(boxes.begin(), boxes.end(),
[](const BBox& a, const BBox& b) -> bool {
return a.score > b.score; // 使用 score 而不是 conf
});
std::vector<bool> keep(boxes.size(), true);
// NMS
for (size_t i = 0; i < boxes.size(); ++i) {
if (!keep[i]) continue;
for (size_t j = i + 1; j < boxes.size(); ++j) {
if (!keep[j]) continue;
// 如果是同一类别且IOU大于阈值则抑制
if (boxes[i].class_id == boxes[j].class_id &&
calculateIOU(boxes[i], boxes[j]) > nms_thresh) {
keep[j] = false;
}
}
}
// 移除被抑制的框
std::vector<BBox> nms_boxes;
for (size_t i = 0; i < boxes.size(); ++i) {
if (keep[i]) {
nms_boxes.push_back(boxes[i]);
}
}
boxes.swap(nms_boxes);
}
float TrtInference::calculateIOU(const BBox& box1, const BBox& box2) {
float x1 = std::max(box1.x1, box2.x1);
float y1 = std::max(box1.y1, box2.y1);
float x2 = std::min(box1.x2, box2.x2);
float y2 = std::min(box1.y2, box2.y2);
float intersection = std::max(0.0f, x2 - x1) * std::max(0.0f, y2 - y1);
float area1 = (box1.x2 - box1.x1) * (box1.y2 - box1.y1);
float area2 = (box2.x2 - box2.x1) * (box2.y2 - box2.y1);
return intersection / (area1 + area2 - intersection);
}
bool TrtInference::createContext() {
if (!engine_) {
return false;
}
context_ = engine_->createExecutionContext();
if (!context_) {
return false;
}
// 设置优化的推理维度
if (config_.max_batch_size > 0) {
// TensorRT 8.x 不再使用 setOptimizationProfile
context_->setOptimizationProfileAsync(0, stream_->get());
}
return true;
}
bool TrtInference::allocateBuffers() {
if (!engine_ || !context_) {
return false;
}
try {
// 先释放旧的缓冲区
input_buffers_.clear();
output_buffers_.clear();
bindings_.clear();
output_names_.clear();
// 获取网络的输入输出数量
int numInputs = 0;
int numOutputs = 0;
int maxBindings = engine_->getNbIOTensors();
bindings_.resize(maxBindings);
// 分配输入输出缓冲区
for (int i = 0; i < maxBindings; i++) {
const char* name = engine_->getIOTensorName(i);
nvinfer1::TensorIOMode mode = engine_->getTensorIOMode(name);
if (mode == nvinfer1::TensorIOMode::kINPUT) {
numInputs++;
nvinfer1::Dims dims = engine_->getTensorShape(name);
nvinfer1::DataType dtype = engine_->getTensorDataType(name);
// 计算缓冲区大小
size_t size = 1;
for (int j = 0; j < dims.nbDims; j++) {
if (dims.d[j] < 0) {
dims.d[j] = (j == 0) ? 1 : config_.model.input_shape[j-1];
}
size *= dims.d[j];
}
size *= (dtype == nvinfer1::DataType::kFLOAT ? 4 : 2); // FP32或FP16
// 使用固定内存分配
void* d_ptr = nullptr;
cudaMalloc(&d_ptr, size);
if (!d_ptr) {
Logger::error("Failed to allocate input buffer");
return false;
}
input_buffers_.emplace_back(std::make_unique<detail::CudaBuffer>(size));
bindings_[i] = d_ptr;
} else if (mode == nvinfer1::TensorIOMode::kOUTPUT) {
numOutputs++;
nvinfer1::Dims dims = engine_->getTensorShape(name);
nvinfer1::DataType dtype = engine_->getTensorDataType(name);
// 计算缓冲区大小
size_t size = 1;
for (int j = 0; j < dims.nbDims; j++) {
if (dims.d[j] < 0) {
dims.d[j] = (j == 0) ? 1 : config_.model.input_shape[j-1];
}
size *= dims.d[j];
}
size *= (dtype == nvinfer1::DataType::kFLOAT ? 4 : 2); // FP32或FP16
// 使用固定内存分配
void* d_ptr = nullptr;
cudaMalloc(&d_ptr, size);
if (!d_ptr) {
Logger::error("Failed to allocate output buffer");
return false;
}
output_buffers_.emplace_back(std::make_unique<detail::CudaBuffer>(size));
bindings_[i] = d_ptr;
output_names_.push_back(name);
}
}
// 同步设备
cudaDeviceSynchronize();
return (numInputs > 0 && numOutputs > 0);
}
catch (const std::exception& e) {
Logger::error("Error in allocateBuffers: " + std::string(e.what()));
return false;
}
}
void TrtInference::destroy() {
try {
// 先同步CUDA流和设备
if (stream_) {
stream_->sync();
}
cudaDeviceSynchronize();
// 释放绑定的设备内存
for (void* ptr : bindings_) {
if (ptr) {
cudaFree(ptr);
}
}
// 释放缓冲区
input_buffers_.clear();
output_buffers_.clear();
bindings_.clear();
output_names_.clear();
// 释放TensorRT资源
if (context_) {
delete context_; // 使用delete而不是destroy()
context_ = nullptr;
}
if (engine_) {
delete engine_; // 使用delete而不是destroy()
engine_ = nullptr;
}
if (runtime_) {
delete runtime_; // 使用delete而不是destroy()
runtime_ = nullptr;
}
// 最后再次同步设备
cudaDeviceSynchronize();
}
catch (const std::exception& e) {
Logger::error("Error in destroy: " + std::string(e.what()));
}
}
} // namespace pipeline