
简介本资源是一套基于 TensorRT 与 YOLO 算法的深度学习部署项目面向需要将目标检测与实例分割能力落地到实际工程中的开发者、算法研究人员及企业技术团队。项目同时提供 C 与 Python 两种实现方式可在 Linux 和 Windows 平台上编译运行帮助读者解决跨平台推理部署、性能优化与多语言集成等实际问题适用于安防监控、工业质检、智能交通及医疗图像分析等场景。压缩包共 1523 个文件约 314.08MB以 hpp、h、cpp、cu 等 C 与 CUDA 源码py 脚本以及 cmake、vcxproj、sln 等构建工程文件为主另含 dll、lib、engine、whl 等运行依赖与模型文件并配有 md、txt 文档和示例图片目录结构完整。目前已有 307 人学习下载。资源内含完整项目文档与可运行工程读者可据此快速搭建环境、理解推理流程、对照 C 与 Python 两套代码进行集成与二次开发并参考文档完成数据配置与结果验证。1. TensorRT 部署 YOLO 实例分割与目标检测一套代码同时吃下 Linux 和 Windows同一个 YOLO 权重PyTorch 里跑 30 FPS换到 TensorRT 上能到 120 FPS但代价是你要先把.pt转成.engine再写一套 C 或 Python 的推理前后处理还得同时照顾 Linux 和 Windows 两套编译环境。这就是「TensorRT 部署 YOLO 项目实例分割 目标检测C 和 Python 两种方式支持 Linux 和 Windows」这个标题真正要解决的问题。它面向的是已经能训练出 YOLO 模型、但卡在工程落地这一步的从业者模型精度够了延迟下不去显存吃满或者想把检测和分割统一到一个推理管线里。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲C 和 Python 两条路都会给到可抄的命令和参数。2. 为什么 TensorRT 能把 YOLO 推理压到极致从计算图到 kernel 选择2.1 TensorRT 到底对 YOLO 做了什么YOLO 这类单阶段检测器推理时的计算量集中在卷积、BN、激活和最后的 NMS 上。PyTorch 的 eager 模式每层单独调度 kernel中间张量反复读写显存这是延迟的主要来源。TensorRT 做的事情可以概括成三层图层融合、精度校准、kernel 自动调优。图层融合最典型的是 Conv BN SiLU 合并成一个节点YOLOv8/v11 的 backbone 里这种结构密集出现融合后 kernel 启动次数能降三到四成。精度校准指的是 FP16 和 INT8FP16 在 Turing 及以后的卡上几乎无损INT8 需要校准集生成 scale 因子检测头对量化更敏感实例分割的 mask 分支尤其容易掉点。kernel 自动调优是 TensorRT 在 build 阶段用实际 shape 去试不同的 CUDA 实现把最快的那个固化进 engine所以同一个模型在不同卡上 build 出来的 engine 不能混用。实例分割比纯检测多一条 mask 分支YOLOv8-seg 的输出是[1, 116, 8400]这种拼接张量前 4 个是 box中间 80 个是类别分数后 32 个是 mask 系数。TensorRT 对这条分支的优化空间比 backbone 小所以分割模型的加速比通常低于纯检测这点要有预期。2.2 选 C 还是 Python先看部署边界Python 路线的优势是开发快pycuda或cuda-python配合tensorrt包几十行就能跑通适合验证和内部工具。但 Python 的 GIL 和解释器开销在高并发场景下会成为瓶颈而且目标机器上要装匹配版本的 Python、CUDA、TensorRT依赖地狱很常见。C 路线的优势是部署干净编译出一个可执行文件加几个 so/dll目标机器只要有 CUDA runtime 和 TensorRT runtime 就能跑延迟也更稳。代价是前后处理要自己写图像解码、letterbox、NMS、mask 后处理都得手撸调试成本高。我的建议是先用 Python 把 engine 跑通、把前后处理逻辑验证对再把这套逻辑平移到 C。两条路共用同一个.engine文件这是关键——engine 是硬件和 TensorRT 版本相关的但和调用语言无关。2.3 环境版本怎么配才不翻车TensorRT 的版本和 CUDA、驱动、显卡架构强绑定。GTX 1070 是 Pascal 架构算力 6.1TensorRT 10.x 仍然支持但 INT8 的收益有限FP16 在 Pascal 上也没有 Tensor Core 加速所以 1070 上 FP16 和 FP32 差距不大。真正吃 FP16 红利的是图灵及以后的卡。组件Linux 推荐Windows 推荐说明CUDA11.8 / 12.211.8 / 12.2与 TensorRT 版本对应TensorRT8.6 / 10.x8.6 / 10.x10.x API 有 breaking changecuDNN8.98.9分割后处理不直接依赖Python3.8–3.103.8–3.103.11 部分 wheel 缺失编译器GCC 9MSVC 2019C17 起步提示TensorRT 8.x 和 10.x 的 API 差异不小IBuilder的创建方式、enqueueV2到enqueueV3的迁移都要注意。选版本前先确认你的显卡驱动能支持到哪个 CUDA。3. 从 .pt 到 .engine导出、转换与精度选择3.1 导出 ONNX 的四个必调参数不管走 C 还是 Python第一步都是把.pt转成 ONNX。Ultralytics 的导出接口封装得比较好但有几个参数直接决定后面能不能转成功。from ultralytics import YOLO # 加载训练好的分割或检测模型 model YOLO(yolov8s-seg.pt) # 检测模型换成 yolov8s.pt # 导出 ONNX关键参数逐个说明 model.export( formatonnx, opset12, # TensorRT 8.x 建议 1210.x 可用 17 simplifyTrue, # 用 onnxsim 去掉冗余节点减少转换报错 dynamicFalse, # 固定 batch 和 shapeengine 性能最好 imgsz640, # 必须和训练/推理一致 halfFalse, # 导出阶段不开 FP16留给 TensorRT build 决定 )opset选 12 是因为 TensorRT 8.6 对 13 以上的某些算子支持不完整尤其是分割分支里的Resize和Concat。simplifyTrue会调用 onnxsim能消掉导出时产生的一堆Identity和Constant节点这些节点在 TensorRT 里会变成额外的 kernel。dynamicFalse是性能关键固定 shape 后 TensorRT 能做更激进的融合动态 shape 会保留很多运行时判断。halfFalse是因为导出时转 FP16 会丢精度精度控制应该放在 TensorRT build 阶段。导出完成后用onnx.checker验一遍再用onnxruntime跑一张图对比 PyTorch 输出确认数值对齐再往下走。3.2 trtexec 命令行转换最快验证路径TensorRT 自带的trtexec是最省事的转换工具不用写代码就能验证 engine 能不能 build 出来。# Linux 下 FP16 转换固定 batch1 trtexec \ --onnxyolov8s-seg.onnx \ --saveEngineyolov8s-seg-fp16.engine \ --fp16 \ --workspace4096 \ --minShapesimages:1x3x640x640 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:1x3x640x640 # Windows 下路径用反斜杠其余一致 trtexec.exe --onnxyolov8s-seg.onnx --saveEngineyolov8s-seg-fp16.engine --fp16 --workspace4096--workspace4096是给 TensorRT 的临时显存上限单位 MB分割模型建议给到 4096 以上太小会 build 失败并报out of workspace。--fp16开启半精度如果显卡不支持会静默回退到 FP32。minShapes/optShapes/maxShapes三个一致就是固定 shape只写--shapes也行。build 完成后 trtexec 会打印各层耗时和总延迟重点看GPU Compute Time这一行。如果某个层耗时异常高通常是该层没被融合需要回看 ONNX 里是不是有 TensorRT 不支持的算子。3.3 INT8 量化分割模型要谨慎INT8 能把延迟再压一半但实例分割的 mask 分支对量化误差敏感。做法是准备 500 到 1000 张有代表性的校准图用IInt8EntropyCalibrator2生成校准表。import tensorrt as trt import numpy as np import pycuda.driver as cuda class Calibrator(trt.IInt8EntropyCalibrator2): def __init__(self, calib_files, batch_size1, cache_filecalib.cache): super().__init__() self.files calib_files self.batch_size batch_size self.cache_file cache_file self.current 0 # 预分配 device 内存shape 必须和 engine 输入一致 self.device_input cuda.mem_alloc(1 * 3 * 640 * 640 * 4) def get_batch_size(self): return self.batch_size def get_batch(self, names): if self.current self.batch_size len(self.files): return None batch np.stack([self._preprocess(f) for f in self.files[self.current:self.currentself.batch_size]]) cuda.memcpy_htod(self.device_input, batch.astype(np.float32).ravel()) self.current self.batch_size return [int(self.device_input)] def read_calibration_cache(self): try: with open(self.cache_file, rb) as f: return f.read() except FileNotFoundError: return None def write_calibration_cache(self, cache): with open(self.cache_file, wb) as f: f.write(cache)校准集要覆盖实际场景的分布否则量化后的检测框会漂。分割模型建议先跑 FP16确认精度达标后再试 INT8并且用同一批测试图对比 mask IoU掉超过 2 个点就退回 FP16。4. Python 推理管线从 engine 加载到 mask 后处理4.1 加载 engine 并跑通一次前向Python 侧用tensorrt加pycuda是最直接的组合。核心是创建 runtime、反序列化 engine、分配绑定内存、执行execute_v2。import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit import numpy as np import cv2 TRT_LOGGER trt.Logger(trt.Logger.WARNING) def load_engine(engine_path): with open(engine_path, rb) as f, trt.Runtime(TRT_LOGGER) as runtime: return runtime.deserialize_cuda_engine(f.read()) def infer(engine, image): # 预处理letterbox 到 640x640归一化到 0-1HWC 转 CHW blob, ratio, pad preprocess(image, 640) context engine.create_execution_context() # TensorRT 10.x 用 get_tensor_name8.x 用 get_binding_name input_name engine.get_tensor_name(0) output_name engine.get_tensor_name(1) context.set_input_shape(input_name, blob.shape) # 分配 host 和 device 内存 d_input cuda.mem_alloc(blob.nbytes) output_shape context.get_tensor_shape(output_name) h_output cuda.pagelocked_empty(trt.volume(output_shape), dtypenp.float32) d_output cuda.mem_alloc(h_output.nbytes) stream cuda.Stream() cuda.memcpy_htod_async(d_input, blob.ravel(), stream) context.set_tensor_address(input_name, int(d_input)) context.set_tensor_address(output_name, int(d_output)) context.execute_async_v3(stream_handlestream.handle) cuda.memcpy_dtoh_async(h_output, d_output, stream) stream.synchronize() return h_output.reshape(output_shape), ratio, padset_tensor_address是 TensorRT 10.x 的写法8.x 用set_binding_shape加execute_v2。execute_async_v3比同步版本快因为它不阻塞 host。输出 shape 对分割模型是[1, 116, 8400]检测模型是[1, 84, 8400]这个差异决定了后处理分支。4.2 检测头后处理NMS 的阈值怎么定输出张量要先转置成[8400, 116]再拆出 box、类别分数、mask 系数。box 是cx, cy, w, h格式需要还原到原图坐标。def postprocess_det(output, conf_thres0.25, iou_thres0.45): # output: [1, 84, 8400] - [8400, 84] pred output[0].T boxes pred[:, :4] scores pred[:, 4:] class_ids scores.argmax(axis1) confidences scores.max(axis1) # 置信度过滤 mask confidences conf_thres boxes, confidences, class_ids boxes[mask], confidences[mask], class_ids[mask] # cxcywh - xyxy xyxy np.empty_like(boxes) xyxy[:, 0] boxes[:, 0] - boxes[:, 2] / 2 xyxy[:, 1] boxes[:, 1] - boxes[:, 3] / 2 xyxy[:, 2] boxes[:, 0] boxes[:, 2] / 2 xyxy[:, 3] boxes[:, 1] boxes[:, 3] / 2 # 按类别做 NMS keep [] for c in np.unique(class_ids): idx np.where(class_ids c)[0] k nms(xyxy[idx], confidences[idx], iou_thres) keep.extend(idx[k]) return xyxy[keep], confidences[keep], class_ids[keep]conf_thres0.25和iou_thres0.45是 Ultralytics 的默认值实际部署时如果漏检多就降 conf误检多就升 conf。NMS 要按类别分开做否则不同类别的重叠框会互相抑制。分割模型还要保留 mask 系数后面和原型 mask 做矩阵乘。4.3 分割分支原型 mask 与系数矩阵乘YOLOv8-seg 的输出除了[1, 116, 8400]还有一路[1, 32, 160, 160]的原型 mask。后处理是把每个检测框的 32 维系数和原型 mask 做加权求和再裁剪到框内。def postprocess_seg(output, proto, boxes, ratios, pads, orig_shape): # output: [1, 116, 8400], proto: [1, 32, 160, 160] pred output[0].T mask_coeffs pred[:, 84:116] # 后 32 维是 mask 系数 proto proto[0] # [32, 160, 160] masks [] for i, box in enumerate(boxes): # 系数与原型做矩阵乘得到 160x160 的 mask m mask_coeffs[i] proto.reshape(32, -1) m m.reshape(160, 160) m 1 / (1 np.exp(-m)) # sigmoid # 裁剪到检测框内再还原到原图尺寸 masks.append(crop_and_resize(m, box, ratios[i], pads[i], orig_shape)) return masksmask_coeffs的维度是 32和原型 mask 的通道数一致这是 YOLOv8-seg 的设计。矩阵乘后要过 sigmoid再按检测框裁剪。裁剪时注意 letterbox 的 ratio 和 pad还原回原图坐标时要把 pad 减掉再除以 ratio否则 mask 会偏移。5. C 推理管线编译、内存绑定与跨平台差异5.1 CMake 工程结构与依赖链接C 侧的核心依赖是 TensorRT、CUDA、OpenCV。Linux 用 CMake 找库Windows 用 MSVC 加环境变量。cmake_minimum_required(VERSION 3.16) project(yolo_trt_deploy CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # TensorRT 路径Windows 下改成实际安装目录 set(TENSORRT_ROOT /usr/local/TensorRT-8.6.1.6) find_package(OpenCV REQUIRED) find_package(CUDA REQUIRED) include_directories( ${TENSORRT_ROOT}/include ${CUDA_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ) link_directories(${TENSORRT_ROOT}/lib) add_executable(yolo_trt main.cpp yolo.cpp preprocess.cpp postprocess.cpp) target_link_libraries(yolo_trt nvinfer nvinfer_plugin cudart ${OpenCV_LIBS} )Windows 下nvinfer对应nvinfer.lib路径在C:\Program Files\TensorRT\lib。如果链接时报LNK2019多半是 TensorRT 版本和 CUDA 版本不匹配或者 Debug/Release 混用。Linux 下如果运行时找不到 so用ldd查依赖缺哪个补哪个或者设LD_LIBRARY_PATH。5.2 内存绑定与 enqueueV3 调用C 的内存绑定比 Python 啰嗦但逻辑一致分配 device 内存、拷贝输入、执行、拷回输出。// 创建 context 和绑定 auto context std::unique_ptrnvinfer1::IExecutionContext(engine-createExecutionContext()); void* buffers[2]; cudaMalloc(buffers[0], inputSize * sizeof(float)); cudaMalloc(buffers[1], outputSize * sizeof(float)); // TensorRT 10.x 用 setTensorAddress context-setTensorAddress(images, buffers[0]); context-setTensorAddress(output0, buffers[1]); // 异步执行 cudaStream_t stream; cudaStreamCreate(stream); cudaMemcpyAsync(buffers[0], inputData, inputSize * sizeof(float), cudaMemcpyHostToDevice, stream); context-enqueueV3(stream); cudaMemcpyAsync(outputData, buffers[1], outputSize * sizeof(float), cudaMemcpyDeviceToHost, stream); cudaStreamSynchronize(stream);enqueueV3是 TensorRT 10.x 的接口8.x 用enqueueV2并传bindings数组。setTensorAddress的字符串名字要和 ONNX 里的输入输出名一致用engine-getIOTensorName(i)可以查。如果输出 shape 是动态的要先setInputShape再getTensorShape拿实际输出大小。5.3 Linux 与 Windows 的编译差异清单差异点LinuxWindows处理方式库文件.so.dll / .libCMake 用条件判断路径分隔/\用 std::filesystem编译器GCCMSVC避免 GCC 扩展语法运行时依赖LD_LIBRARY_PATHPATH部署时打包或设环境变量内存对齐默认 16 字节默认 8 字节显式 alignas(16)跨平台代码里最容易翻车的是std::filesystem的路径拼接和 OpenCV 的imread中文路径。Windows 下imread遇到中文路径会返回空 Mat解决办法是用imdecode配合ifstream读二进制。另外 MSVC 对alignas的处理和 GCC 不同TensorRT 的输入内存建议显式对齐到 16 字节。6. 避坑与排查那些让 engine 跑不起来的细节6.1 现象build engine 时报 unsupported operator原因通常是 ONNX 里有 TensorRT 不支持的算子或者 opset 版本过高。分割模型的Resize在 opset 13 以上会变成Resize加Scales输入TensorRT 8.6 对动态 scales 支持不好。解决导出时把 opset 降到 12或者用onnxsim把动态 scales 固化成常量。如果还不行用polygraphy的surgeon工具把不支持的子图切出来单独处理。6.2 现象推理结果全是一堆重叠框原因是 NMS 没生效或者输出张量的维度理解错了。检测模型输出[1, 84, 8400]84 是 4 个 box 加 80 个类别如果误以为是[1, 8400, 84]就会把 box 和类别搞混。解决先打印输出 shape确认维度顺序。转置后再拆 box 和 scores。NMS 要按类别分开做cv::dnn::NMSBoxes可以指定类别。6.3 现象分割 mask 整体偏移或缩放不对原因是 letterbox 的 ratio 和 pad 在还原时算错了。letterbox 是等比缩放后居中填充还原时要先减 pad 再除以 ratio。解决把 preprocess 返回的 ratio 和 pad 存下来后处理时严格按(x - pad_x) / ratio还原。如果 mask 是 160x160 的要先 resize 到 640x640 再还原顺序不能反。6.4 现象Windows 下编译通过但运行时报 0xc0000005这是 access violation常见于 TensorRT 的 context 在析构时访问了已释放的 engine或者 device 内存没同步就释放。解决确保 context 的生命周期短于 engine用cudaStreamSynchronize后再释放内存。MSVC 下还要注意/MD和/MT运行库选项要和 TensorRT 的 lib 一致混用会导致堆损坏。6.5 现象INT8 量化后检测框抖动严重原因是校准集不具代表性或者某些层的量化 scale 被截断。检测头的分类分支对量化最敏感。解决校准集覆盖所有类别和光照条件数量加到 1000 张。如果还抖把检测头的最后几层设为 FP16用setPrecision逐层控制。分割模型建议直接放弃 INT8。7. 进阶技巧用 polygraphy 对比精度、用 batch 推理榨干吞吐engine build 出来只是开始真正上线前要做两件事精度对齐和吞吐优化。精度对齐用polygraphy它能同时跑 ONNX Runtime 和 TensorRT逐层对比输出差异。polygraphy run yolov8s-seg.onnx \ --trt \ --fp16 \ --onnxrt \ --input-shapes images:1x3x640x640 \ --validate \ --atol 1e-2 --rtol 1e-2--atol和--rtol是绝对和相对容差FP16 下 1e-2 是合理范围。如果某层超差polygraphy 会标出来通常是该层被量化或融合后精度损失。这个工具在排查「为什么 TensorRT 结果和 PyTorch 对不上」时特别省事比手动逐层 dump 快得多。吞吐优化上固定 batch 的 engine 只能跑固定 batch要提吞吐就得 build 多 batch 的 engine或者用动态 shape。我的习惯是 build 一个batch1的低延迟 engine 和一个batch8的高吞吐 engine按请求量切换。多 batch 时 NMS 要按 batch 维度循环mask 后处理也要逐样本做别想着向量化收益不大还容易出错。还有一个容易被忽略的点engine 的 build 时间。分割模型在 4090 上 build 一次要两三分钟如果每次启动都 build 就太慢了。正确做法是 build 一次存成.engine文件运行时直接反序列化。但要注意 engine 和 TensorRT 版本、CUDA 版本、显卡型号绑定换机器就要重新 build。我一般会在部署脚本里加一个版本校验engine 文件旁边放一个meta.json记录 build 时的版本信息加载前先比对不匹配就报错提示重新 build省得运行时出玄学问题。最后说个血泪教训别在目标机器上装完整的 TensorRT 开发包只需要 runtime 和对应的 so/dll。开发包体积大还容易和系统里的 CUDA 版本冲突。部署时把libnvinfer.so、libnvinfer_plugin.so、libcudart.so这几个拷过去就行Windows 下对应nvinfer.dll、nvinfer_plugin.dll、cudart64_*.dll。这个习惯帮我省过好几次现场排查的时间。希望帮到你。本文还有配套的精品资源点击获取