ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLOv5+DeepSORT TensorRT边缘部署实战:Jetson Xavier NX达25FPS

YOLOv5+DeepSORT TensorRT边缘部署实战:Jetson Xavier NX达25FPS

简介:本资源是一套基于TensorRT加速的YOLOv5+DeepSORT行人检测与跟踪完整部署方案,面向计算机视觉方向的算法工程师、嵌入式AI开发者及Jetson平台实践者,解决模型在边缘设备(如NVIDIA Jetson Xavier NX)上高效推理与实时多目标跟踪的落地难题。压缩包共35个文件,含26个Python脚本(覆盖检测器、追踪器、引擎加载与主流程)、2个预编译TensorRT引擎文件、1个YOLOv5配置yaml、1个测试视频mp4、1个C++插件so库及LICENSE等必要组件,整体大小42.95MB,结构清晰,适配X86与ARM架构。已有400人学习下载,提供从环境配置、模型转换到端到端推理的全流程可运行代码,包含detector_trt.py与tracker_trt.py等核心模块、requirements依赖清单及详细README说明,显著降低TensorRT部署门槛,助力快速验证与二次开发。

1. 把 YOLOv5 + DeepSORT 搬上 TensorRT:在 Jetson Xavier NX 上跑出 25 FPS@1080p 的行人跟踪,不是玄学而是可复现的工程闭环

你手头有一台 Jetson Xavier NX,想跑实时行人跟踪——但直接用 PyTorch 原生推理,YOLOv5s + DeepSORT 在 1080p 输入下卡在 8~10 FPS,CPU 占满、GPU 利用率不到 40%,热得像暖手宝。这不是模型不行,是部署链路断了。这个资源包(yolov5_deepsort_tensorrt.zip)不是又一个“能跑就行”的 demo,而是一套完整落地到嵌入式边缘设备的 TensorRT 工程化方案:它把 YOLOv5 的 ONNX 导出、TensorRT 引擎构建、DeepSORT 的 CUDA 加速卡尔曼滤波与 ReID 特征提取全部封装进libmyplugins.so,并用detector_trt.py和tracker_trt.py两个轻量级 Python 胶水层解耦前后处理与推理核心。它不依赖torch运行时,只靠tensorrt+cuda+cv2三件套,实测在 Xavier NX(32GB SD card, JetPack 4.6.3)上稳定输出25.3 FPS @ 1920×1080 输入,batch=1,FP16 精度。适合正在做智能安防、无人巡检、客流统计的嵌入式算法工程师,也适合需要把训练好的 YOLOv5 模型快速迁移到边缘端的 CV 研发——它不教你怎么训练,只告诉你怎么让训好的模型真正“动起来”。


2. 为什么必须用 TensorRT?YOLOv5 + DeepSORT 的瓶颈不在模型,而在数据搬运和 kernel 调度

2.1 YOLOv5 原生推理的三大隐性开销:从 PyTorch 到 TensorRT 的迁移逻辑

YOLOv5 官方 repo 的detect.py在 CPU 上跑得慢,很多人第一反应是“换 GPU”,但真插上 RTX 3090 后你会发现:PyTorch 的model(input)调用背后藏着三重隐性开销:

  • 内存拷贝开销:每次input.cuda()→model()→output.cpu()至少触发 3 次 host-device 同步,尤其对 batch=1 的视频流场景,同步耗时占比超 40%;
  • kernel launch 频次过高:YOLOv5 的 Neck(PANet)含大量小尺寸卷积(如 1×1、3×3),PyTorch 默认为每个 op 单独 launch CUDA kernel,而 TensorRT 可将连续 conv-bn-silu 融合为单个 kernel,实测减少 62% 的 kernel launch 次数;
  • 动态 shape 处理低效:DeepSORT 的检测框数量每帧变化,PyTorch 的 dynamic batch 处理需反复 re-allocate memory,TensorRT 通过IExecutionContext::enqueueV2()配合预分配 binding buffer,彻底规避 runtime realloc。

提示:本项目中yolov5s.engine是针对640×640输入、FP16 精度、batch=1 固定 shape 构建的。不要试图用它跑1280×720——TensorRT 引擎不支持 runtime resize,强行 resize 会导致 output tensor shape 错乱,后续 NMS 直接崩。

2.2 DeepSORT 的 CUDA 加速点在哪?libmyplugins.so不是黑匣子,是三个关键模块的编译产物

DeepSORT 的性能瓶颈常被误认为在 ReID 模型,其实更致命的是匈牙利匹配 + 卡尔曼滤波的 CPU 循环。本项目通过libmyplugins.so将以下三部分 CUDA 化:

模块原 PyTorch 实现位置CUDA 加速方式加速比(Xavier NX)
Kalman Filter Updatedeep_sort/kalman_filter.py使用cuBLAS批量求解P = F·P·F^T + Q,x = F·x + B·u3.8×(单目标 0.8ms → 0.21ms)
IOU Matchingdeep_sort/matcher.pythrust::sort_by_key+reduce_by_key并行计算所有 det-track 对的 IOU5.2×(100dets×50tracks → 1.7ms)
ReID Feature Extractiondeep_sort/nn_matching.py将torch.nn.Sequential替换为TRT-Engine,输入为 crop 后的 bbox 图像(256×128),输出 512-dim embedding4.1×(单图 12.3ms → 3.0ms)

libmyplugins.so的编译依赖TensorRTX/yolov5的 C++ 插件框架,其CMakeLists.txt显式链接了-lcudart -lcublas -lnvinfer,且所有 CUDA kernel 使用__half类型做 FP16 计算——这正是它能在 Xavier NX 上跑满 25 FPS 的底层原因。

2.3 为什么选 YOLOv5s 而非 YOLOv5m/l?分辨率、精度、吞吐的三角平衡

项目默认使用yolov5s,不是因为“小模型好部署”,而是基于Xavier NX 的硬件约束做的硬性取舍:

  • 显存带宽瓶颈:Xavier NX 的 LPDDR4x 带宽仅 51.2 GB/s,YOLOv5m 在 640×640 下 feature map 总 size 达 1.2 GB,远超 GPU 显存(8GB)的可用连续空间;
  • CUDA Core 利用率:yolov5s的 backbone(CSPDarknet53)含 12M 参数,yolov5l达 43M,在 Xavier NX 的 384-core Volta GPU 上,yolov5l的 kernel occupancy 仅 32%,而yolov5s达 78%;
  • 后处理开销:YOLOv5 的non_max_suppression在 CPU 上执行,yolov5s输出约 1200 个 anchor,yolov5l输出 3200+,NMS 时间从 1.8ms 涨至 4.7ms——这对 25 FPS 的 pipeline 是不可接受的延迟。

注意:demo_trt.py中INPUT_W = 640,INPUT_H = 640是硬编码值,若你手头有yolov5n的.pt权重,可自行用models/export.py导出 ONNX,再用trtexec --onnx=yolov5n.onnx --fp16 --workspace=2048 --saveEngine=yolov5n.engine生成新引擎,但务必同步修改detector_trt.py中的self.input_shape = (1, 3, 640, 640)。


3. 从 .pt 到 .engine:TensorRT 引擎构建全流程与关键参数解析

3.1 ONNX 导出不是“一键生成”,YOLOv5 的导出脚本必须打补丁

官方export.py直接导出的 ONNX 存在两个致命问题:

  • Dynamic axes 未冻结:--dynamic参数会保留batch和anchors的 dynamic shape,但 TensorRT 7.2+ 不支持batch动态(除非用IExecutionContext::setBindingDimensions),导致trtexec报错Assertion failed: dimensions.nbDims == 4 && dimensions.d[0].degree == 1;
  • Hardswish 替换失败:YOLOv5 的SiLU(即hardswish)在 ONNX opset=12 下被转为HardSigmoid+Mul,但 TensorRT 对HardSigmoid的支持不完善,推理结果全零。

解决方案:修改models/export.py,在torch.onnx.export()前插入:

# patch 1: freeze batch dim model.model[-1].export = False # disable dynamic anchors # patch 2: replace SiLU with nn.SiLU() explicitly for m in model.modules(): if type(m) is torch.nn.Hardswish: m = torch.nn.SiLU()

然后执行:

python models/export.py --weights yolov5s.pt --include onnx --img 640 --batch 1 --opset 12 --simplify

生成yolov5s.onnx后,用onnx-checker验证:

python -c "import onnx; onnx.load('yolov5s.onnx'); print('OK')"

若报Unsupported ONNX data type: UINT8,说明--simplify过程引入了非法类型,需加--no-simplify重试。

3.2 trtexec 构建引擎:FP16、workspace、maxBatchSize 的真实含义

trtexec命令不是“复制粘贴就能跑”,每个参数都对应硬件调度策略:

trtexec --onnx=yolov5s.onnx \ --fp16 \ --workspace=2048 \ --maxBatchSize=1 \ --minShapes="input:1x3x640x640" \ --optShapes="input:1x3x640x640" \ --maxShapes="input:1x3x640x640" \ --saveEngine=yolov5s.engine \ --timingCacheFile=timing.cache
  • --fp16:启用半精度计算,Xavier NX 的 Volta GPU 的 FP16 throughput 是 FP32 的 2 倍,但需确保所有 layer 支持 FP16(YOLOv5s 全支持);
  • --workspace=2048:单位 MB,指 TensorRT 编译时允许使用的最大 device memory。设太小(如 512)会导致某些 fusion kernel fallback 到 CPU;设太大(如 4096)则浪费显存,且不提升性能;
  • --min/opt/maxShapes:因本项目固定 batch=1,三者必须完全一致,否则IExecutionContext::enqueueV2()会因 shape mismatch crash;
  • --timingCacheFile:缓存 kernel benchmark 结果,首次构建慢(约 8 分钟),后续构建快 5×,且保证不同机器生成的 engine 行为一致。

构建完成后,用trtexec --loadEngine=yolov5s.engine --dumpProfile查看各 layer 的耗时占比,重点关注conv_0(backbone stem)和conv_24(head final conv)是否占总 time > 60%——若是,说明 bottleneck 在 compute,可尝试--int8量化;若input和outputbinding 占比高,则是 memory copy 问题,需检查 host/device memory pinning。

3.3 detector_trt.py:如何用 Python 调用 TRT 引擎?binding、context、stream 的协作关系

detector_trt.py的核心是TrtYOLO类,其__init__和infer方法揭示了 TensorRT Python API 的真实用法:

def __init__(self, engine_path, input_shape=(640, 640)): self.engine = self._load_engine(engine_path) self.context = self.engine.create_execution_context() self.inputs, self.outputs, self.bindings, self.stream = self._allocate_buffers() # 注意:bindings[0] 是 input,bindings[1] 是 output0(preds),bindings[2] 是 output1(proto)

关键点解析:

  • self._allocate_buffers()中cudaMalloc分配的host_mem必须用cudaHostAlloc(page-locked memory),否则cudaMemcpyAsync会降级为同步拷贝;
  • self.context.set_binding_shape(0, (1, 3, *input_shape))必须在enqueueV2前调用,否则context.execute_async_v2()报Invalid value;
  • cudaStreamSynchronize(self.stream)不能省略,否则self.outputs[0].copy_to_host()读到的是旧数据——这是新手最常翻车的点。

推理时序如下:

host_img → cudaMemcpyAsync(host→device, stream) → context.execute_async_v2(bindings, stream) → cudaMemcpyAsync(device→host, stream) → cudaStreamSynchronize(stream) → 解析 outputs

整个 pipeline 必须在同一个cudaStream_t下串行,否则 GPU 调度乱序。


4. tracker_trt.py 的深度定制:DeepSORT 的 ReID 模型如何与 TRT 引擎无缝对接?

4.1 ReID 模型的 TRT 化:为什么不用 PyTorch,而用自定义 plugin?

DeepSORT 的原始 ReID 模型(mars或osnet)是 PyTorch 的nn.Sequential,直接转 ONNX 会遇到:

  • AdaptiveAvgPool2d的 output size 在 ONNX 中为 dynamic,TensorRT 不支持;
  • BatchNorm2d的running_mean/var在 inference mode 下需 hardcode,ONNX export 有时漏掉;
  • osnet的Conv1x1后接ReLU,ONNX opset=12 会拆成两个 node,TRT fusion 失败。

本项目采用C++ plugin 方式:在TensorRTX/deepsort目录下,用IPluginV2DynamicExt实现OsNetPlugin,其enqueue()方法内:

  • 用cublasLtMatmul()替代torch.matmul做 embedding 向量归一化;
  • 用cudaMemcpyAsync直接搬运 crop 图像到 plugin 的 device buffer;
  • 输出float*embedding,由tracker_trt.py的np.frombuffer()解析。

这样绕过 ONNX 层,保证 ReID 推理与 detector 引擎共享同一 CUDA context,避免跨 context 同步开销。

4.2 tracker_trt.py 的状态机设计:如何让卡尔曼滤波与检测框生命周期对齐?

标准 DeepSORT 的track.py用list[Track]管理轨迹,但在 TRT 加速下,Track对象的mean/covariance更新必须 GPU-accelerated。本项目定义TrackArray结构体:

struct TrackArray { float* mean; // [N, 8] x,y,a,h,vx,vy,va,vh float* covariance;// [N, 8, 8] int* hits; // [N] int* age; // [N] int* time_since_update; // [N] };

tracker_trt.py中update()方法调用libmyplugins.so的update_tracks_cuda(),传入:

  • det_bboxes:detector 输出的(N, 6)tensor(x1,y1,x2,y2,conf,cls);
  • det_features:ReID 输出的(N, 512)embedding;
  • track_array:上述结构体指针。

CUDA kernel 内部用thrust::device_vector做匈牙利匹配,并行更新所有 track 的mean/covariance,最后cudaMemcpyAsync回传track_array到 host。整个过程无 Python loop,纯 GPU 执行。

4.3 行人 ID 持续性保障:IOU + ReID + Motion Model 的三级匹配策略

tracker_trt.py的match()函数不是简单调linear_assignment,而是分三级:

  1. High-confidence IOU match:iou > 0.7的 det-track 对直接 assign,跳过 ReID;
  2. ReID cosine distance match:对剩余 det,计算与所有 unconfirmed track 的 embedding cosine distance,dist < 0.25则 assign;
  3. Motion-based gating:对仍 unmatched 的 det,用卡尔曼预测的mean[:4]与 det bbox 做 Mahalanobis distance,distance < 3.0则初始化新 track。

该策略在test.mp4(商场人流视频)上将 ID switch 降低至 2.1 per 100 frames(原 PyTorch 版为 8.7),且track_id在遮挡后恢复率超 93%。

提示:configs/deep_sort.yaml中max_iou_distance: 0.7、max_cosine_distance: 0.25、nn_budget: 100均可调。但nn_budget设太大(如 200)会导致 ReID 特征库膨胀,GPU memory leak;设太小(如 30)则长期 track 丢失。


5. 避坑指南:Xavier NX 上跑通这套流程的 5 个血泪经验

5.1 现象:demo_trt.py运行时报Segmentation fault (core dumped)

原因:libmyplugins.so编译时链接的 CUDA/cuDNN 版本与系统不匹配。Xavier NX JetPack 4.6.3 对应 CUDA 10.2 + cuDNN 8.2,但若make时nvcc --version返回 11.4,则dlopen失败。
解决:sudo apt install cuda-toolkit-10-2,然后export PATH=/usr/local/cuda-10.2/bin:$PATH,export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATH,重新make -C TensorRTX/deepsort。

5.2 现象:trtexec构建成功,但detector_trt.py推理输出全零

原因:ONNX 导出时未冻结model.eval(),或--simplify破坏了SiLU的计算图。
解决:在export.py中model.eval()后加model.training = False,并移除--simplify参数,用onnxsim单独简化:python -m onnxsim yolov5s.onnx yolov5s_sim.onnx。

5.3 现象:demo_trt.py跑 10 秒后显存泄漏,nvidia-smi显示 GPU memory 持续上涨

原因:cv2.VideoCapture的ret, frame = cap.read()在循环中未释放frame的 numpy buffer,且cudaMalloc分配的host_mem未cudaFreeHost。
解决:在demo_trt.py主循环末尾加del frame,并在TrtYOLO.__del__()中调用cudaFreeHost(self.host_inputs[0])。

5.4 现象:行人 ID 频繁跳变,同一人被分配多个 ID

原因:deep_sort/configs/deep_sort.yaml中max_age: 30过小,track 在短暂遮挡(如穿过柱子)后被删除,再出现时视为新 track。
解决:将max_age改为60(对应 2 秒),同时在tracker_trt.py的update()中增加if track.time_since_update > 30: track.mark_missed()的判断。

5.5 现象:test.mp4能跑,但自己摄像头/dev/video0输入黑屏

原因:OpenCV 默认用CAP_GSTREAMER后端,但 Xavier NX 的 GStreamer 未启用nvvidconv插件,无法将YUY2格式转BGR。
解决:在demo_trt.py中cv2.VideoCapture(0)后加:

cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)

或改用cv2.CAP_V4L2后端:cap = cv2.VideoCapture(0, cv2.CAP_V4L2)。


6. 进阶技巧:如何用这套框架快速适配自己的数据集与业务逻辑?

6.1 替换检测模型:从 YOLOv5s 到自定义 YOLOv5x 的三步改造

假设你已训练好yolov5x_custom.pt(检测锥桶、路标等),要接入本框架:

Step 1:导出 ONNX 并验证输出 shape

python models/export.py --weights yolov5x_custom.pt --include onnx --img 640 --batch 1 --opset 12 --no-simplify # 检查输出:ONNX 的 output[0] 应为 (1, 25200, 85),其中 85 = 5+80(cls_num)

Step 2:修改 detector_trt.py 的 postprocess 逻辑
原始yolov5s的output是(1, 25200, 85),但yolov5x的 anchor stride 不同,需重写postprocess()中的xywh2xyxy()和nms():

def postprocess(self, outputs, conf_th=0.4, iou_th=0.45): pred = torch.tensor(outputs[0]).view(1, -1, 85) # reshape to [1, num_anchors, 85] boxes = pred[..., :4] scores = pred[..., 4:5] * pred[..., 5:] # conf × cls_prob # 注意:yolov5x 的 grid offset 与 yolov5s 不同,需按 new_grid = torch.tensor([...]) 重新计算 ... return boxes, scores

Step 3:重建 engine 并更新 binding index
trtexec生成的 engine 中,output[0]的 dims 是(1, 25200, 85),但yolov5x是(1, 36000, 85),需在detector_trt.py中:

self.output_shapes = [(1, 36000, 85)] # update this self.outputs[0].host = np.empty(self.output_shapes[0], dtype=np.float32)

6.2 自定义跟踪逻辑:添加行人属性识别(性别、背包)的双模型 pipeline

本框架支持多 engine 并行推理。例如,在demo_trt.py中:

# 初始化两个 detector det_yolo = TrtYOLO("yolov5s.engine", (640,640)) det_attr = TrtYOLO("attr.engine", (224,224)) # attribute model, input cropped person # 主循环中 bboxes, scores = det_yolo.detect(frame) # get person bboxes for box in bboxes: x1,y1,x2,y2 = map(int, box) crop = frame[y1:y2, x1:x2] attr_out = det_attr.detect(crop) # output: [male, female, backpack, no_backpack] draw_attr(frame, box, attr_out)

关键点:det_attr的input_shape是(224,224),需在TrtYOLO.__init__()中动态 resize crop 图像,并确保cudaMalloc的host_mem大小匹配新 shape。

6.3 性能压测表格:不同分辨率/模型/硬件下的实测 FPS

硬件模型输入分辨率batch精度FPS显存占用备注
Xavier NXyolov5s640×6401FP1625.31.8 GB默认配置
Xavier NXyolov5s1280×7201FP1614.13.2 GB需修改 INPUT_W/H 并重建 engine
RTX 3090yolov5m640×6401FP16128.72.1 GBtrtexec --useCudaGraph可提至 142 FPS
Jetson Orinyolov5l640×6401FP1642.53.8 GBOrin 的 2048-core GPU 显著提升 large model 吞吐

从那以后我每次接到边缘部署需求,第一件事就是nvidia-smi -l 1监控显存曲线,第二件事是tegrastats看 CPU/GPU/util,第三件事才是跑demo_trt.py——因为 90% 的“跑不动”问题,根源都在 thermal throttling 或 memory fragmentation,而不是代码。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表