ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TensorRT加速YOLOv5+DeepSORT行人跟踪部署实战

TensorRT加速YOLOv5+DeepSORT行人跟踪部署实战 简介本资源是一套基于TensorRT加速的YOLOv5DeepSORT行人检测与跟踪完整部署方案面向具备Python基础和CUDA/TensorRT环境配置经验的算法工程师与边缘计算开发者解决目标检测模型在Jetson Xavier及x86平台上的高效推理与多目标轨迹追踪落地难题。压缩包共35个文件含26个核心Python脚本如detector_trt.py、tracker_trt.py、demo_trt.py、2个预编译TensorRT引擎yolov5s.engine等、1个C插件动态库libmyplugins.so、配置文件yaml、依赖清单requirements.txt及测试视频test.mp4整体大小42.95MB结构清晰、模块解耦便于快速复现与二次开发。目前已有400人学习下载提供从模型转换、引擎加载、多线程推理到ID关联的全流程可运行代码附带LICENSE与README说明显著降低TensorRT部署门槛。1. 为什么行人检测跟踪在边缘端卡在“能跑”和“能用”之间TensorRT YOLOv5 DeepSORT 这套组合不是拼凑而是工程闭环你手头有一台 T4 显卡的工控机接了 4 路 1080p25fps 的 IPC 摄像头想做实时行人计数与轨迹分析——但直接跑 PyTorch 版 YOLOv5 DeepSORTGPU 利用率飙到 95%延迟抖动超过 300ms第二路就开始丢帧。这不是模型不行是部署链路断在了“推理引擎选型”和“前后处理耦合”这两个黑匣子上。本文讲的正是把 YOLOv5 的检测头、DeepSORT 的卡尔曼滤波ReID 匹配逻辑用 TensorRT 做端到端融合编译、内存零拷贝调度、异步流水线编排的真实落地路径。它不教你怎么训练 YOLOv5也不展开 DeepSORT 的匈牙利匹配数学推导只聚焦一个目标让 640×640 输入分辨率下单 T4 实现 4 路 1080p25fps 行人检测跟踪稳定输出平均延迟 ≤ 42ms/帧CPU 占用 35%。适合正在做智能安防、园区巡检、无人配送后台系统的 C/Python 工程师尤其适合那些已经训好模型、却被部署性能卡住、反复改 config 却收效甚微的实战派。2. 从 PyTorch 到 TensorRT三步剥离冗余构建可编译的检测-跟踪联合图YOLOv5 和 DeepSORT 原生是两个独立模块YOLOv5 输出 bboxconfDeepSORT 拿 bbox 做状态预测、外观提取、关联匹配。但在 TensorRT 部署中这种“先检测、再传数据、再跟踪”的串行模式会引入至少 3 次 host-device 内存拷贝det_out → CPU → track_in → GPU → track_out成为吞吐瓶颈。我们必须把它们捏合成一张可统一优化的计算图。这不是魔改模型结构而是通过ONNX 中间表示 自定义算子注入 TensorRT Plugin 注册实现逻辑融合。2.1 导出 YOLOv5 为 ONNX避开 dynamic axes 和 opset 版本陷阱YOLOv5 官方 export.py 默认导出带--dynamic的 ONNX这对 TensorRT 7.2 支持不稳尤其 batch1 时 shape 推导失败。我们强制固定输入尺寸并禁用动态轴python export.py \ --weights yolov5s.pt \ --include onnx \ --imgsz 640 \ --batch-size 1 \ --opset 11 \ --simplify \ --dynamic False注意--opset 11是关键。TensorRT 8.4 对 opset 12 的NonMaxSuppression支持存在 stride 计算偏差而 opset 11 的 NMS 算子被 TRT 封装为TRT_NMS_PLUGIN兼容性最稳。--simplify会调用 onnx-simplifier 清除冗余 reshape/unqueeze但需提前pip install onnx-simplifier0.4.33新版 0.4.35 在 TRT 8.4 下会触发Invalid value for attribute value错误。导出后用 Netron 打开yolov5s.onnx确认输入节点名为images输出为output3 个 tensorshape 分别为[1,3,80,80,85],[1,3,40,40,85],[1,3,20,20,85]。若输出名含Concat_XX或Transpose_XX说明 simplify 失败需手动用onnx.shape_inference.infer_shapes()补全。2.2 DeepSORT 的 TRT 化不碰 ReID backbone只 TRT 化匹配与滤波核心DeepSORT 的耗时大头在 ReID 提取ResNet50 或 OSNet但它本质是 CNN 特征提取器可单独导出为 ONNX 并编译进 TRT 引擎。但更高效的做法是保留 ReID 用 FP16 TRT 引擎而将卡尔曼滤波预测、IOU/Hungarian 匹配、track 状态更新全部写成 Custom Plugin。原因有三KalmanFilter 的predict()和update()是纯矩阵运算F x B u,H xTRT 的MatrixMultiply层可直接映射比 CPU 循环快 8~12 倍Hungarian 算法在 track 数 50 时CPU 实现已足够快 0.3ms强行 TRT 化反而因 kernel launch 开销得不偿失最关键的是状态向量[x,y,a,h,vx,vy]的维度固定6D可预分配 device memory避免频繁 malloc/free。我们采用折中方案ReID backbone如 osnet_x0_25导出为reid.onnx用 TRT 编译为reid.engineKalman 预测/更新、track 管理逻辑用 C Plugin 实现后文详述IOU 计算用 TRT 的ElementWiseReduceSum组合实现避免 host 端计算。2.3 构建联合 ONNX 图用 onnx-graphsurgeon 注入 tracking head原始 YOLOv5 ONNX 只输出 detection 结果。我们要在output后插入 tracking head其输入为det_bboxes: shape[N,4]归一化 xyxydet_scores: shape[N,1]det_features: shape[N,512]来自 ReID backbone用onnx-graphsurgeon插入新节点import onnx_graphsurgeon as gs import numpy as np graph gs.import_onnx(onnx.load(yolov5s.onnx)) # 获取原始输出节点 output_node [n for n in graph.nodes if n.name output][0] # 创建 det_bboxes/scores 输入占位符 bboxes gs.Variable(det_bboxes, dtypenp.float32, shape(1, -1, 4)) scores gs.Variable(det_scores, dtypenp.float32, shape(1, -1, 1)) # 插入 dummy node 占位实际由 plugin 替换 track_node gs.Node(TrackPlugin, track_head, inputs[bboxes, scores], outputs[tracks]) graph.nodes.append(track_node) # 连接 YOLO 输出到 tracking head 输入需后处理解码 # ...此处省略 bbox decode logic见 3.2 节 graph.cleanup() onnx.save(gs.export_onnx(graph), yolov5_deepsort.onnx)逻辑说明TrackPlugin是占位符最终会被 TRT 的IPluginV2DynamicExt实现替换。det_bboxes和det_scores不是原始 YOLO 输出而是经TRTBatchedNMSPlugin解码后的结果——这正是我们跳过 PyTorch post-process、全程在 GPU 上完成的关键。3. TensorRT 引擎构建量化、插件注册与内存池配置TRT 引擎构建不是builder.build_engine(network)一行代码的事。YOLOv5DeepSORT 联合部署对 builder 配置极度敏感尤其在 INT8 量化和 plugin 注册顺序上。3.1 Builder 配置必须显式设置 max_workspace_size 和 fp16/int8 策略T4 显存仅 16GB但 TRT 默认 workspace 仅 1GB导致某些 layer如 YOLO 的 upsample因显存不足 fallback 到 CPU 实现。必须显式放大IBuilder* builder createInferBuilder(logger); IBuilderConfig* config builder-createBuilderConfig(); config-setMaxWorkspaceSize(4ULL 30); // 4GB config-setFlag(BuilderFlag::kFP16); // FP16 加速T4 必开 // 若需 INT8额外添加 // config-setFlag(BuilderFlag::kINT8); // config-setCalibrationData(calibrator); // calibrator 实现见 3.3 节参数说明setMaxWorkspaceSize(4ULL 30)中ULL是关键避免 int32 溢出kFP16在 T4 上实测比 FP32 快 2.1 倍且精度损失 0.3% mAPkINT8仅建议在 4 路以上场景启用单路 INT8 比 FP16 慢 8% 因 calibration 开销。3.2 注册 TrackPlugin继承 IPluginV2DynamicExt重写 enqueue()TrackPlugin是整个 pipeline 的心脏。它接收 YOLO 的 raw output未解码的 grid 输出在 GPU 上完成Grid 解码sigmoid anchor scalingNMSTRT 内置 BatchedNMSPluginKalman predict/update自定义 matrix opsReID 特征提取调用 reid.engineHungarian 匹配host 端但输入已预拷贝核心是enqueue()实现int TrackPlugin::enqueue(const PluginTensorDesc* inputDesc, const PluginTensorDesc* outputDesc, const void* const* inputs, void* const* outputs, void* workspace, cudaStream_t stream) noexcept { // inputs[0]: YOLO output (3 tensors) // inputs[1]: frame_id (int32, host) const float* det_output0 static_castconst float*(inputs[0]); const float* det_output1 static_castconst float*(inputs[1]); const float* det_output2 static_castconst float*(inputs[2]); // Step 1: Decode grids on GPU (custom kernel) decodeGridsgrid, block, 0, stream( det_output0, det_output1, det_output2, d_bboxes, d_scores, d_labels, kAnchor0, kAnchor1, kAnchor2); // Step 2: Call TRTs BatchedNMSPlugin nmsPlugin-enqueue(...); // 输入 d_bboxes/d_scores输出 d_nms_bboxes // Step 3: Copy NMS results to host for Hungarian (async) cudaMemcpyAsync(h_nms_bboxes, d_nms_bboxes, nms_size, cudaMemcpyDeviceToHost, stream); // Step 4: Run ReID engine on d_nms_bboxes crops (using reid.context) reidContext-enqueueV2(reidBindings, stream, nullptr); return 0; }关键点decodeGridskernel 必须用__half类型FP16 输入否则与 TRT 的 FP16 engine 不匹配h_nms_bboxes拷贝是异步的确保 Hungarian 在cudaStreamSynchronize(stream)后执行避免 race condition。3.3 INT8 校准用真实视频帧而非 COCO 子集校准 200 帧足矣INT8 量化对行人检测影响极大——小目标 bbox 回归误差会被放大。校准必须用部署环境的真实数据录制 200 帧 1080p 室内走廊视频含遮挡、侧身、背影每帧送入原始 PyTorch 模型提取 YOLO 的outputtensor3 个 feature map将这些 tensor 作为 calibration data而非用 COCO val2017 的 5000 张图。class DeepSORTCalibrator(trt.IInt8Calibrator): def __init__(self, frames_dir): self.frames sorted(glob(f{frames_dir}/*.pt))[:200] # .pt 是 torch.save 的 output tensor self.current_index 0 def get_batch(self, names): if self.current_index len(self.frames): return None # load pre-extracted YOLO output (not image!) data torch.load(self.frames[self.current_index]) # convert to FP32 host buffer host_data data[0].cpu().numpy().astype(np.float32) # [1,3,80,80,85] cuda.memcpy_htod(self.device_input, host_data) self.current_index 1 return [int(self.device_input)]血泪经验用图像校准而非 YOLO output会导致 TRT 误判 feature map 的 dynamic rangeNMS 前置层量化误差达 15%漏检率上升 3.2%。务必用模型中间输出校准。4. 避坑YOLOv5 DeepSORT TensorRT 联合部署的 5 个致命陷阱这套组合看似文档齐全实则每个环节都有反直觉的坑。以下是我在线上系统翻车 7 次后总结的硬核避坑指南按现象→原因→解决结构化呈现4.1 现象TRT 引擎加载成功但首帧检测框全为 [0,0,0,0]后续帧正常原因YOLOv5 的models/common.py中Detect层的self.stride在 ONNX 导出时被固化为常量但 TRT 8.4 的TRTBatchedNMSPlugin依赖 stride 动态计算 anchor scale。当输入尺寸非 640 时如 1280stride 仍为 8/16/32导致 bbox 解码偏移。解决导出 ONNX 前修改models/yolo.py的Detect.forward()将self.stride替换为torch.tensor([8,16,32], devicex[0].device)确保 stride 随输入动态生成。4.2 现象DeepSORT track ID 在 20 帧内频繁跳变如 ID1→ID5→ID1原因TRT 引擎中 ReID 特征提取的context-enqueueV2()未同步等待导致特征向量未就绪就进入 Hungarian 匹配cosine distance 计算错误。解决在TrackPlugin::enqueue()中ReID 推理后必须加cudaStreamSynchronize(stream)或改用context-executeV2()同步版本。4.3 现象T4 上 4 路 1080p25fpsGPU 利用率仅 40%但延迟高达 120ms原因默认IExecutionContext使用单个 stream4 路视频复用同一 context形成串行瓶颈。TRT 的enqueueV2()是异步的但若未为每路分配独立IExecutionContext和cudaStream_tstream 会隐式同步。解决为每路视频创建独立IExecutionContext并绑定专属cudaStream_tstd::vectorIExecutionContext* contexts(4); std::vectorcudaStream_t streams(4); for (int i 0; i 4; i) { contexts[i] engine-createExecutionContext(); cudaStreamCreate(streams[i]); contexts[i]-setOptimizationProfile(0); } // 推理时contexts[i]-enqueueV2(..., streams[i], nullptr);4.4 现象INT8 引擎下行人 occlusion 场景 ID 切换率比 FP16 高 3 倍原因INT8 量化对 ReID 特征向量的 cosine distance 敏感度极高微小误差导致匹配失败。TRT 的 default calibrator 对特征向量分布拟合不准。解决ReID backbone 单独用EntropyCalibrator2并设置setQuantizationAlgo(QuantizationAlgo::kLEGACY_CALIBRATION)该算法对 embedding 向量更鲁棒。4.5 现象Ubuntu 20.04 CUDA 11.1 TRT 8.2libnvinfer.so找不到 symbolnvrtcCompileProgram原因TRT 8.2 依赖libnvrtc.so.11.1但 Ubuntu 20.04 默认安装libnvrtc.so.11.0CUDA 11.0。符号版本不匹配。解决不升级 CUDA而是软链接修复sudo ln -sf /usr/local/cuda-11.1/targets/x86_64-linux/lib/libnvrtc.so.11.1 \ /usr/local/cuda-11.1/targets/x86_64-linux/lib/libnvrtc.so.11.05. 性能压测与多路调度T4 上 4 路 1080p25fps 的实测参数表与调度技巧理论再完美不压测等于没落地。我们在 T4驱动 515.65.01CUDA 11.1TRT 8.2.5上实测了不同配置下的吞吐与延迟结论颠覆常识不是模型越小越快而是 batch size 与 stream 数的组合决定上限。5.1 关键参数实测对比单路 vs 4 路配置项单路 1080p25fps4 路 1080p25fps说明引擎类型FP16FP16INT8 在 4 路下反而慢calibration 开销摊薄不足输入分辨率640×640640×6401280×720 使 YOLO backbone 计算量180%延迟翻倍batch size11每路独立合并 batch4 会因 NMS 复杂度 O(N²) 导致延迟激增CUDA stream 数14每路 1 stream少于 4 stream 时GPU 利用率 50%平均延迟/帧38.2ms41.7ms4 路总延迟仅9%证明流水线有效GPU 显存占用3.2GB5.8GBTRT engine ReID engine stream bufferCPU 占用top12%32%主要消耗在 Hungarian 匹配host 端表格解读4 路下延迟仅比单路高 3.5ms证明异步 stream 调度成功隐藏了 ReID 推理和匹配开销。CPU 占用 32% 是瓶颈下一步应将 Hungarian 移至 GPU用 Thrust 库预计可降 CPU 占用至 18%。5.2 多路视频调度技巧用 AVFrame 时间戳对齐而非轮询常见错误是用cv2.VideoCapture轮询 4 个 URL导致帧时间戳错乱IPC 网络抖动。正确做法是每路 IPC 开启 RTSP 的?tcp参数强制 TCP 保序用ffmpeg的av_read_frame()获取AVPacket解析pkt-ptspresentation timestamp维护一个全局std::priority_queueFramePacket, vectorFramePacket, ComparePTS按 PTS 排序主循环每次 pop 出 PTS 最小的帧送入对应路的 TRT context。struct FramePacket { uint8_t* data; int64_t pts; // 来自 AVPacket.pts int src_id; // 0~3 bool operator(const FramePacket rhs) const { return pts rhs.pts; } };这样即使某路 IPC 丢 2 帧其他路仍能按真实时间戳推进避免“等最慢一路”导致整体卡顿。5.3 验证跟踪质量不用 MOT16/MOT20用自有场景的 IDSW 指标MOT Challenge 的 HOTA/mAP 指标在工业场景不适用——我们关心的是 ID 切换次数IDSW和轨迹连续性。实测方法在走廊固定位置架设 4 路 IPC人工标注 10 分钟视频的 ground truthID bbox运行 TRT pipeline输出每帧track_id, bbox, frame_id用py-motmetrics计算idf1和idswacc mm.MOTAccumulator() for frame_id in range(1, 15000): # 10min25fps gt_ids, gt_dets get_gt(frame_id) pred_ids, pred_dets get_pred(frame_id) acc.update(gt_ids, pred_ids, mm.distances.iou_matrix(gt_dets, pred_dets)) mh mm.metrics.create() summary mh.compute(acc, metrics[idf1, idsw], nametest) print(summary.loc[test][IDF1]) # 我们的 TRT pipeline 达到 72.3%PyTorch 原版 73.1%关键技巧IDF1 70% 即可商用。低于 65% 说明 ReID 特征区分度不足需换 backbone如从 osnet_x0_25 升级到 mobilenetv3_large_100或增加 track 状态保持阈值max_age30→max_age45。我坚持一个习惯每次上线新 TRT 引擎前必用nvidia-smi dmon -s um监控 10 分钟看sm__inst_executed是否平稳抖动 5%这是 GPU 流水线是否健康的黄金指标。希望帮到你。本文还有配套的精品资源点击获取
返回列表