C++部署深度学习模型进行实时推理TensorRT
在这篇文章里,我们来聊聊一个完整的:如何用 TensorRT 10 + CUDA + OpenCV CUDA 写一个推理引擎类TrtEngine,实现从的完整流程。如果你刚接触 TensorRT,可能会觉得 API 很复杂,名字也容易混乱。没关系,我们从实际工程代码入手,逐块来讲清楚每一步。
在这篇文章里,我们来聊聊一个完整的 C++ 工程实例:如何用 TensorRT 10 + CUDA + OpenCV CUDA 写一个推理引擎类 TrtEngine
,实现从 图像输入 → GPU 预处理 → TensorRT 推理 → 输出解析 的完整流程。
如果你刚接触 TensorRT,可能会觉得 API 很复杂,名字也容易混乱。没关系,我们从实际工程代码入手,逐块来讲清楚每一步。
1. 整体思路
这个工程的目标是:
-
给定一张 OpenCV 的
cv::Mat
图像(BGR 格式,CPU 内存)。 -
我们把它传到 GPU 上,在 GPU 上完成 resize、颜色通道转换、归一化等预处理。
-
然后调用 TensorRT 引擎跑推理,得到检测结果。
-
最后把检测框解析出来,返回给上层代码。
整个过程需要解决几个关键问题:
-
如何加载 TensorRT 引擎(
.engine
文件) -
如何准备输入输出内存(device buffer + pinned host buffer)
-
如何高效地在 GPU 上做预处理(用 OpenCV CUDA)
-
如何触发推理(
enqueueV3
) -
如何解析输出(从 GPU → Host → 解析检测框)
2. 类的结构设计
我们封装了一个 TrtEngine
类,核心接口是:
std::vector<Detection> infer(const cv::Mat& bgr, float confThres=0.25f);
调用时你只需要传入一张 OpenCV 图像,它会返回一组检测结果(矩形框 + 类别 + 置信度)。
Detection
结构体定义很简单:
struct Detection {
cv::Rect box; // 框位置
int cls; // 类别ID
float score; // 置信度
};
3. 引擎加载与资源准备
在构造函数里,我们做了几件事:
1 反序列化引擎
runtime_ = createInferRuntime(gLogger);
engine_ = runtime_->deserializeCudaEngine(buf.data(), sz);
ctx_ = engine_->createExecutionContext();
-
runtime_
:推理运行时环境 -
engine_
:具体的模型 -
ctx_
:执行上下文(真正负责跑网络)
2 找输入输出 Tensor 的名字
for (int i = 0; i < engine_->getNbIOTensors(); ++i) {
const char* name = engine_->getIOTensorName(i);
if (engine_->getTensorIOMode(name) == TensorIOMode::kINPUT) inputName_ = name;
else outputName_ = name;
}
TensorRT 10 不再推荐用 binding 索引,而是直接用名字来绑定。
3 计算输入输出大小
inputBytes_ = dimSize(inDims) * sizeof(float);
outputBytes_ = dimSize(outDims) * sizeof(float);
4 分配内存
-
输入 / 输出缓冲在 GPU 显存:
cudaMalloc
-
输出还要一份 pinned host 内存:
cudaHostAlloc
,这样从 GPU 拷回更快
5 创建 CUDA stream
-
我们需要一个
cudaStream_t
,然后用 OpenCV 的cv::cuda::StreamAccessor::wrapStream()
把它封装成 OpenCV CUDA 的 stream,方便 OpenCV 算子和 TensorRT 在同一个 stream 上协同工作。
4. GPU 预处理
这部分在 preprocessGPU()
里实现,步骤如下:
-
上传图像到 GPU
d_bgr_.upload(bgr, cvStream_);
-
resize 到模型输入大小
cv::cuda::resize(d_bgr_, d_resized_, cv::Size(W_, H_), ...);
-
颜色通道转换(BGR → RGB)
cv::cuda::cvtColor(d_resized_, d_rgb_, cv::COLOR_BGR2RGB, 0, cvStream_);
-
转成 float32 并归一化到 [0,1]
d_rgb_.convertTo(d_rgb_f32_, CV_32F, 1.0/255.0, 0.0, cvStream_);
-
通道拆分
cv::cuda::split(d_rgb_f32_, d_ch_, cvStream_);
-
把通道数据拷贝成 CHW 格式
这里用的是cudaMemcpy2DAsync
,因为GpuMat
每行可能有对齐填充,不能直接当连续数组拷。我们要保证输入张量在内存里是紧凑的 CHW 格式。for (int c = 0; c < 3; ++c) { cudaMemcpy2DAsync(dst, dstPitch, src, srcPitch, widthBytes, H_, cudaMemcpyDeviceToDevice, stream_); }
这样,模型的输入数据就准备好了,直接放在了 dInput_
这块 GPU 内存里。
5. 触发推理
核心的一行:
ctx_->enqueueV3(stream_);
-
这行代码的本质就是:把输入张量送进网络,在 GPU 上跑一遍,把结果写到输出张量里。
-
它不会阻塞 CPU,而是异步提交到 CUDA stream。
-
我们前面已经绑定好输入/输出地址,所以这里直接执行。
6. 拷回输出并解析
输出张量写到了 dOutput_
(GPU 显存),我们需要拷到 Host 端解析。
-
拷回
cudaMemcpyAsync(hOutputPinned_, dOutput_, outputBytes_, cudaMemcpyDeviceToHost, stream_); cudaStreamSynchronize(stream_);
-
解析
-
输出格式是
[N, 6]
,每一行[x1,y1,x2,y2,score,cls]
。 -
我们遍历这些行,把
score > 0
的框取出来。 -
再按原图尺寸缩放,得到正确的坐标。
-
7. 上层调用
对上层来说,使用起来非常简单:
TrtEngine engine("best_fp16.engine", 640, 640); cv::Mat frame = cv::imread("test.jpg"); auto results = engine.infer(frame, 0.25f); for (auto& d : results) { cv::rectangle(frame, d.box, {0,255,0}, 2); printf("cls=%d, score=%.2f\n", d.cls, d.score); }
8. 工程实践中的亮点
-
全 GPU 预处理:避免 CPU hotspot 和大数据拷贝,速度大幅提升。
-
Pinned host 输出:D2H 更快,特别适合实时场景。
-
名字绑定 IO:对齐 TensorRT 10 的新接口,更加稳健。
-
Stream 协同:OpenCV CUDA 和 TensorRT 在同一个 stream 上执行,减少同步。
9. 可以继续优化的地方
-
如果视频解码也用 OpenCV
cudacodec::VideoReader
,可以避免upload
。 -
解析输出时,可以用异步事件替代
cudaStreamSynchronize
,和下一帧流水化。 -
如果能接受精度下降,可以量化成 INT8,进一步提速。

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)