资讯动态

YOLOv8+ByteTrack+C++/TensorRT实时多目标跟踪落地实践

发布时间:2026/9/2 2:09:18 来源:尧图企业网站定制
简介本资源是一个基于YOLOv8与ByteTrack的高性能目标跟踪项目面向嵌入式AI开发者及C/TensorRT部署工程师解决实时多目标跟踪在Jetson系列边缘设备与x86_64服务器上的高效落地问题。项目完整封装YOLOv8检测模型与ByteTrack跟踪算法为两个独立动态链接库支持检测、跟踪一体化推理并集成类别过滤、CUDA后处理优化含NMS参数适配ByteTrack原理等实用功能。压缩包共64个文件涵盖27个头文件.h、14个C源码.cpp、5个CUDA核函数.cu及4份Markdown文档结构清晰分为tensorrtx-yolov8、bytetrack、yolo三个模块便于解耦调用与二次开发整体大小25.16MB。已有162人学习下载提供可直接编译运行的CMake工程、预置测试图像与视频含demo.mp4和effect.gif效果演示、模型转换脚本gen_wts.py及详细中英文README显著降低TensorRT部署门槛。 最近在做一个边缘设备上的实时人流统计项目场景要求是摄像头画面里同时出现几十个人既要能实时框出每个人又要能稳定区分谁是谁、维持各自的 ID。一开始用 Python 跑 YOLOv8 加 ByteTrack效果没问题但帧率就是上不去CPU 占用也高得离谱。后来把整套流程迁移到 C再用 TensorRT 做推理加速同样一块 GPU 上延迟直接降了一个数量级。这篇文章就把这个项目的完整落地过程拆开讲清楚。这套方案的核心链路很简单YOLOv8 负责目标检测ByteTrack 负责跨帧关联和 ID 管理C 作为胶水层把检测、跟踪、后处理、绘制全部串起来TensorRT 则负责把 YOLOv8 的推理压到极致。如果你也在做类似的项目比如车流统计、行人跟踪、安防监控或者只是想把 Python 原型改成能上线的 C 版本这篇文章应该能帮你省不少时间。1. 项目整体设计思路1.1 为什么是 YOLOv8 ByteTrack 而不是端到端跟踪方案目标跟踪领域其实有两条路线。一条是检测加跟踪的分离式方案也就是先靠检测器找出每一帧里的目标再用跟踪算法把这些框关联起来比如 DeepSORT、ByteTrack 都属于这一类。另一条是端到端方案直接用一个模型同时输出检测结果和跟踪 ID比如 JDE、FairMOT。端到端方案听起来很美省了关联逻辑实际用起来问题不少。首先是训练难度高检测头和跟踪头需要联合调优自己训数据很容易翻车。其次是对类别变化不友好想加一个检测类别往往要重新训练整个模型。分离式方案最大的好处是模块化检测器可以随便换跟踪器也可以随便换任何一边出了问题都能单独排查。我在实际项目里选的是 YOLOv8 加 ByteTrack核心原因是 YOLOv8 的检测精度和速度平衡做得很好而 ByteTrack 不需要额外的 ReID 特征提取网络计算开销极小非常适合对延迟敏感的场景。ByteTrack 的另一个优势是它设计思路非常朴素只依赖检测框的 IoU 来关联轨迹却能解决传统方法的一个大痛点当目标被遮挡导致检测置信度降低时很多跟踪器会直接丢掉这个目标。ByteTrack 会把低置信度的检测框也纳入匹配过程利用低分框和轨迹之间的 IoU 关系把目标救回来。这一点在人群密集的场景里特别管用实测下来 ID Switch 的次数比 DeepSORT 少不少。1.2 为什么一定要换成 C 和 TensorRT如果只是做原型验证Python 绝对是效率最高的选择YOLOv8 官方仓库开箱即用ByteTrack 也有现成的 Python 实现。但原型和产品之间有一条巨大的鸿沟。首先是延迟问题。Python 的 GIL 锁决定了多线程推理很难真正并行而 TensorRT 的 Python API 虽然能调用 GPU 加速但每个环节的数据拷贝、预处理、后处理都逃不掉 Python 解释器的开销。我实测在 GTX 1660 Ti 上Python 版 YOLOv8 加 ByteTrack 的端到端延迟在 30ms 到 50ms 之间浮动换成 C 加 TensorRT 后检测加跟踪的总耗时稳定在 10ms 出头。这个差距对实时交互系统是决定性的。其次是资源占用。Python 运行时加 PyTorch 的显存开销非常大仅仅加载模型就要吃掉 1GB 以上的显存而 TensorRT 做 FP16 推理时显存占用只有 PyTorch 的一半不到。在边缘设备上这直接决定了能不能多跑一路视频流。最后是部署问题。C 编译出来的二进制文件没有任何 Python 依赖配合动态链接库和模型文件就能直接运行特别好打包。我最后的交付物就是标题里那个 .zip解压后目录结构干净客户机器上只要装了显卡驱动和 CUDA 运行时就能跑。2. 核心模块拆解2.1 YOLOv8 检测器在 C 里的完整实现YOLOv8 的网络结构从宏观上看分成三块Backbone 负责特征提取Neck 负责多尺度特征融合Head 负责输出检测结果。在 C 里实现检测器本质上就是做三件事预处理输入图像、用 TensorRT 跑网络、解析网络输出。YOLOv8 的输入要求是 RGB 图像归一化到 0 到 1 之间分辨率通常取 640x640。这里的坑在于预处理必须和训练时的数据增强保持一致。YOLOv8 训练时用的是 Letterbox 方式也就是等比例缩放图像后填充灰色边而不是直接拉伸到目标尺寸。直接拉伸会改变目标的宽高比导致检测框偏移。我在代码里实现了一个预处理函数第一步计算缩放比例第二步复制像素并填充灰色边框第三步做 BGR 到 RGB 的通道转换和归一化。YOLOv8 的输出解析是个容易出错的环节。网络输出的张量形状是 1x84x8400这里的 8400 是三个不同尺度特征图上的 anchor 点总数84 表示 4 个框坐标信息加 80 个类别置信度。TensorRT 拿到的是 GPU 显存上的数据需要先从显存拷贝到内存然后遍历每个 anchor 点先过滤掉最大类别置信度低于阈值的框再按置信度从高到低排序最后做 NMS 去重。需要注意的是 YOLOv8 的输出坐标是中心点加宽高的形式转换成左上角右下角坐标时要注意浮点精度问题。2.2 ByteTrack 跟踪器的关联逻辑实现ByteTrack 的思路可以用一句话概括把高置信度框和低置信度框分开利用先匹配高置信度的再拿低置信度的去挽救可能丢失的轨迹。在 C 实现里核心数据结构只有三个轨迹列表、当前帧检测框列表、IoU 计算函数。我实现的 ByteTrack 流程分四步。第一步把当前帧的检测框按置信度分成高分框和低分框两组阈值我通常设 0.5。第二步用匈牙利算法在高分框和已有轨迹之间做 IoU 匹配匹配成功的轨迹更新位置和速度状态未匹配的高分框初始化新轨迹。第三步把上一轮未匹配的轨迹和低分框再做一次 IoU 匹配这一步就是 ByteTrack 的精髓能把被短暂遮挡的目标救回来。第四步对仍未匹配的轨迹判断存活时间超过阈值就删除。这里必须提一下 ByteTrack 中的状态预测机制。原版实现里用卡尔曼滤波预测轨迹在当前帧的位置但 ByteTrack 做了一个简化可以不用卡尔曼滤波直接用上一次的检测框位置做匹配。实测下来在摄像头固定、目标运动不太剧烈的场景下简化版和完整版的效果几乎一样但代码量少了一半CPU 占用也更低。如果你的场景里目标运动速度很快建议保留卡尔曼滤波。2.3 TensorRT 加速层的关键配置TensorRT 能把 YOLOv8 的推理速度压到极致核心靠的是层融合、精度校准和内核自动调优。从用户角度来说需要关注的就三件事网络输入输出的绑定方式、精度模式的选择、显存池的分配。动态输入是必须开的功能否则输入分辨率被锁死在 640x640一旦要改输入尺寸就得重新生成引擎。设置动态输入需要在构建引擎时指定三个维度最小尺寸、常规尺寸、最大尺寸。我实测下来最小尺寸用 320x320常规尺寸 640x640最大尺寸 1280x1280灵活性足够。需要注意的是在推理阶段每次调用都需要重新指定输入尺寸。精度模式的选择取决于你的 GPU 型号。RTX 系列显卡支持 FP16显存足够的话建议直接用速度提升接近一倍精度损失在 1 个百分点以内。如果你用的是只有 Tensor Core 的专业卡或者新一代 RTX甚至可以试 INT8 量化但需要准备校准数据集不然精度波动会比较大。我在 GTX 1660 Ti 上实测 FP16 比 FP32 快 35%INT8 能再快 30%但 INT8 的精度在某些小目标上会明显下滑。3. 实操构建与部署全流程3.1 环境准备与依赖版本匹配这个项目最折磨人的不是写代码而是版本匹配。我整理了一套经过验证的组合方案照着配可以省掉大量排查时间。组件版本说明Ubuntu20.04 / 22.04Windows 也能跑但很多坑的处理文档更少CUDA11.8TensorRT 8.5 以上对 CUDA 11.8 支持最稳定cuDNN8.6必须匹配 CUDA 版本TensorRT8.5.38.6 也行但 8.5 的 API 文档最全OpenCV4.5.5用于图像读取、预处理、绘制CMake3.20需要支持 C17编译器GCC 9.4建议用 GCCClang 在 CUDA 代码上兼容性稍差这里有一个非常重要的事TensorRT 的版本必须和你的 CUDA 版本匹配否则加载引擎时会直接报错。我一开始用的是 TensorRT 8.6 配 CUDA 11.6整了三天最后发现是版本不兼容。建议先安装 CUDA 和 cuDNN再根据 CUDA 版本去查 TensorRT 的兼容性表格。GTX 1660 Ti 这块卡跑这个项目没有任何问题。虽然它不支持 Tensor Core但 TensorRT 的算子融合和内核优化依然能带来显著提升。如果你手头是 RTX 3060 或更高型号FP16 带来的收益会更明显。3.2 模型导出从 PyTorch 到 TensorRT 引擎不能直接从 PyTorch 的权重文件生成 TensorRT 引擎中间必须经过 ONNX 这个中转站。导出 ONNX 的时候有两个关键参数要设置opset 版本建议用 11 或以上动态输入维度要显式指定。yolo export modelyolov8n.pt formatonnx dynamicTrue imgsz640导出 ONNX 之后不要急着转引擎先用 Netron 打开看一下网络结构确认输出节点的形状是 1x84x8400。如果输出形状不对多半是导出时参数配置有问题。生成 TensorRT 引擎有两种方式。官方推荐用 trtexec 命令行工具简单粗暴适合快速验证。但如果你想在代码里动态生成引擎比如支持运行时切换精度模式就得用 C API 自己写构建逻辑。我倾向于提前用 trtexec 生成引擎文件运行时直接反序列化加载这样可以省去构建引擎的时间启动速度快很多。/usr/src/tensorrt/bin/trtexec \ --onnxyolov8n.onnx \ --saveEngineyolov8n_fp16.engine \ --fp16 \ --minShapesimages:1x3x320x320 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:1x3x1280x1280引擎生成之后一定要验证一下用 trtexec 带着引擎文件跑一次推理看看输出结果是否合理。这一步能过滤掉大部分模型转换的问题比到了项目里再排查要高效得多。3.3 CMake 配置与项目结构设计项目的目录结构建议按功能模块拆分不要把所有代码堆在一个文件里。我最终的目录结构是object_tracking_project/ ├── CMakeLists.txt ├── include/ │ ├── yolo_detector.hpp │ ├── bytetrack.hpp │ ├── tensorrt_engine.hpp │ └── utils.hpp ├── src/ │ ├── main.cpp │ ├── yolo_detector.cpp │ ├── bytetrack.cpp │ ├── tensorrt_engine.cpp │ └── utils.cpp ├── models/ │ ├── yolov8n.engine │ └── labels.txt └── config/ └── config.yamlCMakeLists.txt 的关键配置有几处需要注意。OpenCV 用 find_package 引入TensorRT 没有官方的 CMake 模块需要手动指定头文件路径和库文件路径。cmake_minimum_required(VERSION 3.20) project(ObjectTracking) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # OpenCV find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui videoio) # TensorRT set(TENSORRT_ROOT /opt/TensorRT-8.5.3.1) include_directories(${TENSORRT_ROOT}/include) link_directories(${TENSORRT_ROOT}/lib) # CUDA find_package(CUDA REQUIRED) include_directories(${CUDA_INCLUDE_DIRS}) add_executable(object_tracking src/main.cpp src/yolo_detector.cpp src/bytetrack.cpp src/tensorrt_engine.cpp src/utils.cpp ) target_link_libraries(object_tracking ${OpenCV_LIBS} ${TENSORRT_ROOT}/lib/libnvinfer.so ${TENSORRT_ROOT}/lib/libnvonnxparser.so cudart )链接库的时候要注意TensorRT 推理依赖的是 libnvinfer.so解析 ONNX 需要 libnvonnxparser.so两个都要链上。另外记得在运行时设置环境变量 LD_LIBRARY_PATH把 TensorRT 和 CUDA 的库目录加进去不然启动时会报找不到依赖库的问题。3.4 推理循环与跟踪流程的串联主程序的逻辑看起来很简单就是读帧 - 检测 - 跟踪 - 绘制 - 显示但实际写起来有几个容易翻车的地方需要特别注意。第一个是内存管理。TensorRT 的输入输出缓冲区是在 GPU 上分配的每次推理前需要把预处理好的图像数据从 CPU 拷贝到 GPU推理完成后再把结果从 GPU 拷贝回 CPU。这两次拷贝的开销在 640x640 的输入下大约 2ms在延迟敏感的场景里不能忽略。建议在初始化阶段就分配好固定大小的缓冲区避免在循环里反复申请释放。第二个是视频帧的读取节奏。直接用 OpenCV 的 VideoCapture 读视频文件时读取速度和推理速度不匹配会导致帧堆积或者卡顿。最简单的方案是开一个独立线程读帧用队列缓存最近几帧推理线程取最新帧处理。这样处理视频文件和摄像头都能稳定跑满帧率。下面是我主循环的核心代码片段可以看到整个流程非常简洁while (true) { // 1. 从队列取一帧 cv::Mat frame frameQueue.pop(); if (frame.empty()) break; // 2. 检测预处理 - TensorRT 推理 - 后处理 std::vectorDetection detections; detector.detect(frame, detections); // 3. 跟踪更新轨迹分配 ID std::vectorTrackedObject tracked; tracker.update(detections, tracked); // 4. 绘制结果 for (const auto obj : tracked) { cv::rectangle(frame, obj.bbox, cv::Scalar(0, 255, 0), 2); cv::putText(frame, std::to_string(obj.id), cv::Point(obj.bbox.x, obj.bbox.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 255, 0), 2); } // 5. 显示 cv::imshow(Object Tracking, frame); if (cv::waitKey(1) 27) break; }3.5 预留配置接口避免硬编码项目跑通之后第一件事就是把所有关键参数抽出来放到配置文件里。包括检测置信度阈值、NMS IoU 阈值、ByteTrack 的匹配阈值、轨迹存活帧数、最小轨迹长度、输入图像尺寸、推理精度模式。这些东西在实际调试过程中会反复调整如果全部硬编码在源码里每改一次就要重新编译一次效率极低。我用的是 YAML 格式的配置文件配合一个简单的解析函数。如果是为 C 新手写的也可以先用最简单的 keyvalue 格式的文本文件逐行解析即可。一个重要的经验是把跟踪器参数和检测器参数分开配置。因为调试时往往是分别进行先用静止画面调检测参数再用连续视频调跟踪参数。这两个阶段的关注点完全不同混在一起改起来很让人抓狂。4. 实测调优与性能对比4.1 关键参数调优实测记录我在这个项目上花了不少时间调参数最后总结了几个影响最大的参数。检测置信度阈值决定了哪些框能进入跟踪器。阈值设太低保出来的全是噪声ID 会被频繁切换设太高又会漏检轨迹会断。我在实际测试中用了 0.4 作为默认值在光线变化明显的场景会临时调到 0.3。ByteTrack 对低置信度框的处理顺序是先匹配高分框再挽救低分框所以这里的阈值和 ByteTrack 内部的阈值是两个概念别搞混了。ByteTrack 的匹配阈值用 IoU 0.2 是一个比较合理的起点。所谓 IoU 0.2 的意思就是两个框的交集面积占并集面积的 20%太苛刻会漏掉被遮挡的目标太宽松会错误连接不同的目标。轨迹存活帧数的默认值设在 30也就是说一个目标连续 30 帧没匹配上就删除轨迹。帧率 30 的情况下相当于允许目标消失 1 秒超过这个时间基本可以确认目标已经离开画面或彻底被遮挡。4.2 性能数据对比Python vs C TensorRT用 GTX 1660 Ti 做的一组对比测试视频分辨率 1080p检测输入尺寸 640x640目标数量大概 20 到 30 个。方案平均延迟最高延迟GPU 显存占用CPU 占用Python PyTorch38ms65ms1800MB120%Python TensorRT18ms25ms900MB85%C TensorRT (FP32)13ms18ms850MB30%C TensorRT (FP16)9ms12ms500MB30%从数据能看到光是把 Python 换成 C不换推理引擎延迟就能降不少主要原因是省掉了 Python 解释层的开销和后处理逻辑的调度开销。再叠加 TensorRT 的加速整体效果非常可观。CPU 占用的下降尤其明显因为解码、预处理、后处理都能在 C 里高效并行这在多路视频流同时处理的场景下特别重要。延迟抖动也值得关注。Python 方案的最高延迟是平均延迟的两倍在实时系统里表现就是画面卡顿。C 加 TensorRT 方案的最高延迟和平均延迟差距小很多处理节奏更平稳这对我这种需要长时间稳定运行的场景很关键。4.3 打包成 .zip 交付时的目录设计项目最终交付压缩包的时候不要直接把编译产物和源码全扔进去会让使用的人无从下手。我最后的打包目录是这样组织的object_tracking_demo/ ├── bin/ │ └── object_tracking ├── lib/ │ ├── libnvinfer.so.8 │ ├── libnvonnxparser.so.8 │ └── libcudart.so.11.0 ├── models/ │ ├── yolov8n_fp16.engine │ └── labels.txt ├── config/ │ └── config.yaml ├── videos/ │ └── demo.mp4 └── README.md把动态库一起放进去能避免目标机器上还要重新安装 TensorRT 的麻烦。lib 目录下只需要放 TensorRT 和 CUDA runtime 相关的库不需要把整个 CUDA 工具链带过去。README 里要写清楚启动命令和注意事项尤其是需要设置 LD_LIBRARY_PATH 指向 lib 目录。启动命令建议写成脚本比如 start.sh 里写上#!/bin/bash export LD_LIBRARY_PATH$PWD/lib:$LD_LIBRARY_PATH ./bin/object_tracking --config ./config/config.yaml --video ./videos/demo.mp45. 常见问题与排查技巧5.1 编译和链接阶段的问题编译报错最多的场景是找不到 OpenCV 或 TensorRT 的头文件。首先检查 CMakeLists.txt 里的路径是否和实际安装路径一致。OpenCV 建议用自带编译的版本apt 装的版本经常是 4.2 甚至更老在接口上会有差异。TensorRT 的问题基本集中在头文件没找到或库没链上排查看回贴出来的错误信息把 find_package 换成手动 find_path 和 find_library 能大幅提高成功率。链接成功但运行时报error while loading shared libraries这是典型的动态库路径没设置。用 ldd 命令检查可执行文件依赖的库是否都能找到ldd bin/object_tracking | grep not found把 not found 的库在 LD_LIBRARY_PATH 里指过去就行。5.2 推理阶段的问题推理结果全为零通常有两种原因。一是预处理方式不对很多模型要求归一化到 0 到 1 之间直接用 0 到 255 的像素值喂进去大概率什么都检不出来。二是 NMS 实现有问题遍历顺序或去重逻辑写错导致所有框都被过滤掉了。推理速度远低于预期先排查是不是用了 FP32 而没开 FP16。GTX 1660 Ti 不支持 Tensor CoreFP16 带来的提升小一些但 RTX 30 系及更新型号上差距非常大。另一个被频繁踩的坑是在循环里动态创建 cudaStream 和 cudaEvent这是性能杀手应该在初始化阶段一次性创建。5.3 跟踪阶段的问题ID 频繁切换通常是检测置信度阈值设太低导致检测框抖动。先调检测阈值再调跟踪的匹配阈值顺序不要反。我在人群密集场景里把检测阈值从 0.4 调到 0.5同时把 IoU 匹配阈值从 0.2 调到 0.3ID Switch 次数明显变少。目标被遮挡后丢 ID这是最棘手的场景。几个组合拳打法开启低置信度框的挽救机制调大轨迹存活帧数减小匹配 IoU 阈值。如果目标之间有大量相似外观IoU 方法能做得不多需要的才考虑加 ReID 特征。ByteTrack 官方也提供了带 ReID 的扩展版但计算开销会涨不少。5.4 模型引擎跨平台部署的坑在一台机器上生成的 TensorRT 引擎文件拿到另一台机器上用是很多新手最容易跳进去的坑。TensorRT 引擎是和具体的 GPU 架构绑定的在 RTX 3090 上生成的引擎拿到 GTX 1660 Ti 上直接报错这是因为两者计算能力不同编译出来的内核无法通用。解决方案有两个方向要么在每台目标机器上用 ONNX 文件现场构建引擎要么准备同架构 GPU 的引擎版本。第一种更稳妥但启动时间会多出几十秒。另外 GPU 驱动版本和 TensorRT 版本之间也存在兼容性问题。如果目标机器上的驱动太旧即使 CUDA 运行库匹配也会在运行时崩溃。打包交付前一定要在干净的机器上实际跑一遍完整流程。6. 项目扩展与优化方向这个项目跑通之后余下的工作可以根据场景需求往两个方向扩展。一个是多路视频流的支持目前的代码处理单路视频改造的关键是把检测器和跟踪器的实例拆成多份或者引入多线程流程。多线程要注意每路视频一个跟踪器实例不能共享状态否则 ID 会混。另一个是 Tiny 模型的适配YOLOv8n 已经是最小版本如果还对速度不满意可以尝试把输入分辨率从 640 降到 480检测精度有一点损耗但在人比较少、目标不算太小的场景里完全可用。如果你的项目最终要部署到 Jetson 系列设备或者是纯 CPU 环境情况和通用 GPU 平台不太一样。Jetson 上 TensorRT 的运行方式和桌面 GPU 完全一致C 代码可以直接移植只需要注意显存和功耗限制。纯 CPU 环境跑 YOLOv8 会很吃力建议考虑在 TensorRT 不可用时回退到 ONNX Runtime 或者 OpenVINO但主流程保持不变。我在这个项目里坚持了三个原则至今仍觉得非常受用模块之间用清晰的接口隔离每个模块内部只依赖自己需要的组件所有参数外部化配置不硬编码在代码里代码和构建脚本全部放在同一个仓库里统一管理。这套方案从原型验证到交付上线整个链路走下来后续再接到类似的目标检测加跟踪项目基本能做到拿来就改、快速迭代。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价